768 lines
26 KiB
C++
768 lines
26 KiB
C++
// Reference: https://handmade.network/forums/articles/t/7002-tutorial_parsing_the_mft
|
|
|
|
#pragma pack(push,1)
|
|
struct NTFS_BootSector {
|
|
u8 jump[3];
|
|
u8 name[8];
|
|
u16 bytesPerSector;
|
|
u8 sectorsPerCluster;
|
|
u16 reservedSectors;
|
|
u8 unused0[3];
|
|
u16 unused1;
|
|
u8 media;
|
|
u16 unused2;
|
|
u16 sectorsPerTrack;
|
|
u16 headsPerCylinder;
|
|
u32 hiddenSectors;
|
|
u32 unused3;
|
|
u32 unused4;
|
|
u64 totalSectors;
|
|
u64 mftStart;
|
|
u64 mftMirrorStart;
|
|
u32 clustersPerFileRecord;
|
|
u32 clustersPerIndexBlock;
|
|
u64 serialNumber;
|
|
u32 checksum;
|
|
u8 bootloader[426];
|
|
u16 bootSignature;
|
|
};
|
|
|
|
struct NTFS_FileRecordHeader {
|
|
u32 magic;
|
|
u16 updateSequenceOffset;
|
|
u16 updateSequenceSize;
|
|
u64 logSequence;
|
|
u16 sequenceNumber;
|
|
u16 hardLinkCount;
|
|
u16 firstAttributeOffset;
|
|
u16 inUse : 1;
|
|
u16 isDirectory : 1;
|
|
u32 usedSize;
|
|
u32 allocatedSize;
|
|
u64 fileReference;
|
|
u16 nextAttributeID;
|
|
u16 unused;
|
|
u32 recordNumber;
|
|
};
|
|
|
|
struct NTFS_AttributeHeader {
|
|
u32 attributeType;
|
|
u32 length;
|
|
u8 nonResident;
|
|
u8 nameLength;
|
|
u16 nameOffset;
|
|
u16 flags;
|
|
u16 attributeID;
|
|
};
|
|
|
|
struct NTFS_ResidentAttributeHeader : NTFS_AttributeHeader {
|
|
u32 attributeLength;
|
|
u16 attributeOffset;
|
|
u8 indexed;
|
|
u8 unused;
|
|
};
|
|
|
|
struct NTFS_FileNameAttributeHeader : NTFS_ResidentAttributeHeader {
|
|
u64 parentRecordNumber : 48; // low 48 bits
|
|
u64 sequenceNumber : 16;
|
|
u64 creationTime;
|
|
u64 modificationTime;
|
|
u64 metadataModificationTime;
|
|
u64 readTime;
|
|
u64 allocatedSize;
|
|
u64 realSize;
|
|
u32 flags;
|
|
u32 repase;
|
|
u8 fileNameLength;
|
|
u8 namespaceType;
|
|
u16 fileName[1];
|
|
};
|
|
|
|
struct NTFS_NonResidentAttributeHeader : NTFS_AttributeHeader {
|
|
u64 firstCluster;
|
|
u64 lastCluster;
|
|
u16 dataRunsOffset;
|
|
u16 compressionUnit;
|
|
u32 unused;
|
|
u64 attributeAllocated; // allocatedSize
|
|
u64 attributeSize; // dataSize
|
|
u64 streamDataSize; // initializedSize
|
|
u64 compressedSize;
|
|
};
|
|
|
|
struct NTFS_RunHeader {
|
|
u8 lengthFieldBytes : 4;
|
|
u8 offsetFieldBytes : 4;
|
|
};
|
|
#pragma pack(pop)
|
|
|
|
struct NTFS_File {
|
|
u32 parent_id;
|
|
u32 record_id;
|
|
u16* name_data;
|
|
u64 file_modtime; // FILETIME
|
|
u64 file_size;
|
|
u8 name_count;
|
|
bool is_directory;
|
|
};
|
|
|
|
constexpr s64 NTFS_MFT_File_Record_Size = 1024; // File Entry Block
|
|
constexpr s64 NTFS_MFT_Files_Per_Buffer = 65536;
|
|
|
|
// #rename: should be NTFS_MFT_Internal
|
|
struct NTFS_MFT_Internal {
|
|
ArrayView<u8> mft_file;
|
|
ArrayView<u8> mft_buffer;
|
|
HANDLE handle;
|
|
s64 bytes_accessed;
|
|
s64 file_count;
|
|
};
|
|
|
|
void add_record (Dense_FS* dfs, NTFS_File* file) {
|
|
DFS_Array* array;
|
|
if (file->is_directory) {
|
|
array = &dfs->paths;
|
|
} else {
|
|
array = &dfs->files;
|
|
}
|
|
|
|
// UTF-8 (string) version
|
|
string s = wide_to_utf8(file->name_data, file->name_count);
|
|
u32 offset = AddString_NoCount(array->strings, s.data, (u8)s.count);
|
|
|
|
// I need the full path for this lol.
|
|
// file_length(s, (s64*)&file->file_size);
|
|
|
|
// UTF-16LE (wstring) version
|
|
// u32 offset = AddString_NoCount(array->wstrings, (u8*)file->name_data, file->name_count * sizeof(u16));
|
|
|
|
array_add(*array->parent_ids, file->parent_id);
|
|
array_add(*array->record_ids, file->record_id);
|
|
array_add(*array->lengths, file->name_count);
|
|
array_add(*array->offsets, (u32)offset);
|
|
array_add(*array->modtimes, file->file_modtime);
|
|
array_add(*array->sizes, file->file_size);
|
|
}
|
|
|
|
NTFS_MFT_Internal* new_ntfs_mft_internal () { // call with temp
|
|
NTFS_MFT_Internal* mft = New<NTFS_MFT_Internal>(true);
|
|
mft->mft_file = ArrayView<u8>(NTFS_MFT_File_Record_Size);
|
|
mft->mft_buffer = ArrayView<u8>(NTFS_MFT_File_Record_Size * NTFS_MFT_Files_Per_Buffer); // 64 MB
|
|
|
|
return mft;
|
|
}
|
|
|
|
// I need a better name for this!
|
|
bool NTFS_read_internal (NTFS_MFT_Internal* mft, void* buffer, u64 from, u64 count) {
|
|
s32 high = (s32)(from >> 32);
|
|
SetFilePointer(mft->handle, (s32)(from & 0xFFFFFFFF), (PLONG)&high, FILE_BEGIN);
|
|
u32 bytes_accessed_internal;
|
|
ReadFile(mft->handle, buffer, (DWORD)count, (LPDWORD)&bytes_accessed_internal, nullptr);
|
|
mft->bytes_accessed += bytes_accessed_internal;
|
|
|
|
Assert(bytes_accessed_internal == count);
|
|
return bytes_accessed_internal == count;
|
|
}
|
|
|
|
// #TODO: Release resources if we face an early return!
|
|
// #TODO: Maybe this doesn't need to return a value? Return an Error* instead.
|
|
Error* NTFS_MFT_read_raw (OS_Drive* drive) {
|
|
auto start_time = GetUnixTimestamp();
|
|
|
|
Assert(drive != nullptr);
|
|
if (drive == nullptr) { return nullptr; }
|
|
|
|
string drive_path = drive->label;
|
|
|
|
Assert(context_allocator() != temp()); // pointless as we're releasing temp end-of-scope
|
|
// Allocator primary_allocator = context_allocator();
|
|
|
|
auto_release_temp();
|
|
push_allocator(temp());
|
|
|
|
string drive_letter = Win32_drive_letter(drive_path);
|
|
|
|
string create_file_target = format_string("\\\\.\\%s:", drive_letter.data);
|
|
HANDLE file_handle = CreateFileA((LPCSTR)create_file_target.data, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
|
|
|
|
if (file_handle == INVALID_HANDLE_VALUE) {
|
|
log_error("CreateFileA failed on target %s", create_file_target.data);
|
|
os_log_error();
|
|
return nullptr;
|
|
}
|
|
|
|
NTFS_MFT_Internal* mft = new_ntfs_mft_internal();
|
|
mft->handle = file_handle;
|
|
// push_allocator(primary_allocator);
|
|
|
|
bool success;
|
|
NTFS_BootSector boot_sector;
|
|
success = NTFS_read_internal(mft, &boot_sector, 0, 512);
|
|
Assert(success);
|
|
|
|
u64 bytes_per_cluster = (boot_sector.bytesPerSector * boot_sector.sectorsPerCluster);
|
|
success = NTFS_read_internal(mft, mft->mft_file.data, boot_sector.mftStart * bytes_per_cluster, NTFS_MFT_File_Record_Size);
|
|
Assert(success);
|
|
|
|
NTFS_FileRecordHeader* file_record_start = (NTFS_FileRecordHeader*)mft->mft_file.data;
|
|
if (file_record_start->magic != 0x454C4946) {
|
|
log_error("[NTFS_read_drive_raw] Magic number check failed! This drive is not NTFS or is corrupted!");
|
|
return nullptr;
|
|
}
|
|
|
|
NTFS_AttributeHeader* attribute = (NTFS_AttributeHeader*)(mft->mft_file.data + file_record_start->firstAttributeOffset);
|
|
NTFS_NonResidentAttributeHeader* data_attribute = nullptr;
|
|
u64 approximate_record_count = 0;
|
|
|
|
while (true) {
|
|
if (attribute->attributeType == 0x80) {
|
|
data_attribute = (NTFS_NonResidentAttributeHeader*)attribute;
|
|
} else if (attribute->attributeType == 0xB0) {
|
|
approximate_record_count = ((NTFS_NonResidentAttributeHeader*)attribute)->attributeSize * 8;
|
|
} else if (attribute->attributeType == 0xFFFFFFFF) {
|
|
break;
|
|
}
|
|
|
|
attribute = (NTFS_AttributeHeader*) ((u8*) attribute + attribute->length);
|
|
} // while (true)
|
|
|
|
Assert(data_attribute != nullptr);
|
|
|
|
// #dense_fs_alloc
|
|
drive->data = New<Dense_FS>(GPAllocator());
|
|
initialize(drive->data, drive);
|
|
|
|
NTFS_RunHeader* dataRun = (NTFS_RunHeader*)((u8*)data_attribute + data_attribute->dataRunsOffset);
|
|
u64 cluster_number = 0, records_processed = 0;
|
|
|
|
// outer loop
|
|
while (((u8*)dataRun - (u8*)data_attribute) < data_attribute->length && dataRun->lengthFieldBytes) {
|
|
u64 length = 0, offset = 0;
|
|
|
|
for (u8 i = 0; i < dataRun->lengthFieldBytes; i += 1) {
|
|
length |= (u64)(((u8*)dataRun)[1 + i]) << (i * 8);
|
|
}
|
|
|
|
for (u8 i = 0; i < dataRun->offsetFieldBytes; i += 1) {
|
|
offset |= (u64)(((u8*)dataRun)[1 + dataRun->lengthFieldBytes + i]) << (i * 8);
|
|
}
|
|
|
|
if (offset & ((u64) 1 << (dataRun->offsetFieldBytes * 8 - 1))) {
|
|
for (s64 i = dataRun->offsetFieldBytes; i < 8; i += 1) {
|
|
offset |= ((u64)0xFF << (u64)(i * 8));
|
|
}
|
|
}
|
|
|
|
cluster_number += offset;
|
|
dataRun = (NTFS_RunHeader*)((u8*)dataRun + 1 + dataRun->lengthFieldBytes + dataRun->offsetFieldBytes);
|
|
|
|
u64 files_remaining = length * bytes_per_cluster / NTFS_MFT_File_Record_Size;
|
|
u64 position_in_block = 0;
|
|
|
|
while (files_remaining) { // enumerate files in chunks of 65536
|
|
u64 files_to_load = NTFS_MFT_Files_Per_Buffer;
|
|
if (files_remaining < NTFS_MFT_Files_Per_Buffer) {
|
|
files_to_load = files_remaining;
|
|
}
|
|
|
|
NTFS_read_internal(mft, mft->mft_buffer.data, cluster_number * bytes_per_cluster + position_in_block, files_to_load * NTFS_MFT_File_Record_Size);
|
|
position_in_block += files_to_load * NTFS_MFT_File_Record_Size;
|
|
files_remaining -= files_to_load;
|
|
|
|
for (s64 i = 0; i < (s64)files_to_load; i += 1) { // load
|
|
// Even on an SSD, processing the file records takes only a fraction of the time to read the data, so there's not much point in multithreading this:
|
|
NTFS_FileRecordHeader* fileRecord = (NTFS_FileRecordHeader*)(mft->mft_buffer.data + NTFS_MFT_File_Record_Size * i);
|
|
records_processed += 1;
|
|
|
|
// A file record may be blank or unused; just skip it.
|
|
if (!fileRecord->inUse) continue;
|
|
|
|
NTFS_AttributeHeader* attribute = (NTFS_AttributeHeader*)((u8*)fileRecord + fileRecord->firstAttributeOffset);
|
|
Assert(fileRecord->magic == 0x454C4946);
|
|
if (fileRecord->magic != 0x454C4946) {
|
|
log_error("[NTFS_read_drive_raw] Magic number check failed! This drive is likely corrupted!");
|
|
return nullptr;
|
|
}
|
|
// inner loop
|
|
NTFS_File file = {};
|
|
while ((u8*)attribute - (u8*)fileRecord < NTFS_MFT_File_Record_Size) {
|
|
if (attribute->attributeType == 0x30) { // $FILE_NAME
|
|
NTFS_FileNameAttributeHeader* fileNameAttribute = (NTFS_FileNameAttributeHeader*)attribute;
|
|
|
|
if (fileNameAttribute->namespaceType != 2 && !fileNameAttribute->nonResident) {
|
|
file.parent_id = (u32)fileNameAttribute->parentRecordNumber; // truncate
|
|
file.record_id = fileRecord->recordNumber;
|
|
file.is_directory = fileRecord->isDirectory;
|
|
file.name_count = fileNameAttribute->fileNameLength;
|
|
file.name_data = (u16*)fileNameAttribute->fileName;
|
|
file.file_modtime = (u64)fileNameAttribute->modificationTime;
|
|
}
|
|
}
|
|
/* #NOTE: File size doesn't work at all, so just use slower WinAPI for now :(
|
|
if (attribute->attributeType == 0x80 && attribute->nameLength == 0) { // $DATA
|
|
// #TODO: Check if file is compressed then access compressedSize
|
|
bool is_compressed = (attribute->flags & 0x0800) == 0x0800;
|
|
if ((bool)attribute->nonResident) {
|
|
NTFS_NonResidentAttributeHeader* nonresident_attribute = (NTFS_NonResidentAttributeHeader*)attribute;
|
|
if (is_compressed) {
|
|
file.file_size = nonresident_attribute->compressedSize;
|
|
} else {
|
|
file.file_size = nonresident_attribute->attributeSize; // keep bottom 48-bits
|
|
}
|
|
} else {
|
|
NTFS_ResidentAttributeHeader* res = (NTFS_ResidentAttributeHeader*)attribute;
|
|
file.file_size = res->attributeLength;
|
|
}
|
|
|
|
Assert(file.file_size < GB(64));
|
|
if (file.is_directory) { file.file_size = 0; }
|
|
}*/
|
|
if (attribute->attributeType == 0xFFFFFFFF) {
|
|
add_record(drive->data, &file);
|
|
// See Dense_FS drive->data
|
|
mft->file_count += 1;
|
|
|
|
break;
|
|
}
|
|
|
|
attribute = (NTFS_AttributeHeader*)((u8*)attribute + attribute->length);
|
|
} // while: inner loop
|
|
} // for i: 0..files_to_load-1
|
|
} // while: files_remaining
|
|
} // while: outer loop
|
|
|
|
CloseHandle(file_handle);
|
|
|
|
log_none("Found %lld files on drive %s (bytes_accessed: %s)", mft->file_count, drive_path.data, format_bytes(mft->bytes_accessed).data);
|
|
|
|
// #TODO: Generate parent_indices from record_id and parent_id
|
|
Timed_Block_Print("NTFS_MFT_read_raw: generate parent_indices");
|
|
// 1. Setup hash table:
|
|
s64 path_count = item_count(&drive->data->paths);
|
|
|
|
// #TODO: Before we start inserting stuff into the table we should ensure we have enough space
|
|
// for everything. See table_ensure_space ::
|
|
for (s64 i = 0; i < path_count; i += 1) {
|
|
table_set(&drive->data->path_table, (*drive->data->paths.record_ids)[i], (s32)i);
|
|
}
|
|
|
|
// Link directories:
|
|
array_resize(*drive->data->paths.parent_indices, path_count, /*init*/false);
|
|
s64 fail_count = 0;
|
|
for (s64 i = 0; i < path_count; i += 1) {
|
|
u32 parent_id = (*drive->data->paths.parent_ids)[i];
|
|
bool parent_exists = 0;
|
|
s32 previous_index = find_previous_index(drive->data, parent_id, &parent_exists);
|
|
fail_count += (s64)(!parent_exists);
|
|
if (!parent_exists) {
|
|
(*drive->data->paths.parent_indices)[i] = -1; // -1 if failed
|
|
} else {
|
|
(*drive->data->paths.parent_indices)[i] = previous_index;
|
|
}
|
|
}
|
|
|
|
// Link files:
|
|
s64 file_count = item_count(&drive->data->files);
|
|
array_resize(*drive->data->files.parent_indices, file_count, false);
|
|
for (s64 i = 0; i < file_count; i += 1) {
|
|
u32 parent_id = (*drive->data->files.parent_ids)[i];
|
|
bool parent_exists = 0;
|
|
s32 previous_index = find_previous_index(drive->data, parent_id, &parent_exists);
|
|
fail_count += (s64)(!parent_exists);
|
|
if (!parent_exists) {
|
|
(*drive->data->files.parent_indices)[i] = -1;
|
|
} else {
|
|
(*drive->data->files.parent_indices)[i] = previous_index;
|
|
}
|
|
}
|
|
|
|
// For all files and directories with a parent, find the parent(s) and get the file size!.
|
|
|
|
if (fail_count) {
|
|
log_warning("[%s] Failed to find parent for %lld items", drive_path.data, fail_count);
|
|
}
|
|
|
|
cleanup_after_enumeration(drive->data);
|
|
|
|
drive->file_count = mft->file_count;
|
|
drive->bytes_accessed = mft->bytes_accessed;
|
|
drive->time_to_enumerate = (f32)(GetUnixTimestamp() - start_time);
|
|
|
|
log_none("[%s] SUCCESS: total time to enumerate %.3f seconds", drive_path.data, drive->time_to_enumerate);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
struct NTFS_Enumeration_Task {
|
|
Arena* pool; // small arena just for results
|
|
|
|
ArrayView<OS_Drive*> drives;
|
|
// Should be part of OS_Drive!
|
|
|
|
Error* error;
|
|
};
|
|
|
|
s64 ntfs_enumeration_thread_proc (Thread* thread) {
|
|
auto task = thread_task(NTFS_Enumeration_Task);
|
|
|
|
log("[ntfs_enumeration_thread_proc] (Thread index: %lld) Task pointer: %p", thread->index, task);
|
|
|
|
for_each(d, task->drives) {
|
|
task->error = NTFS_MFT_read_raw(task->drives[d]);
|
|
// What we actually want to do here is push all our errors to return to the main thread.
|
|
if (task->error) return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void os_clear_drive_data () {
|
|
ArrayView<OS_Drive*> drives = os_get_available_drives();
|
|
|
|
for_each(d, drives) {
|
|
OS_Drive* drive = drives[d];
|
|
|
|
release(drive->data);
|
|
|
|
drive->data = nullptr;
|
|
}
|
|
}
|
|
|
|
constexpr u32 Win32_Drive_Magic_Number = 0x41b5c7a9;
|
|
|
|
struct NTFS_Drive {
|
|
s32 radio_button;
|
|
s32 index;
|
|
};
|
|
|
|
// #TEMPORARY STRUCTURE FOR EXPERIMENTATION.
|
|
struct NTFS_Workspace {
|
|
Array<OS_Drive*> drives;
|
|
Array<NTFS_Drive> supplementary;
|
|
Arena* arena;
|
|
// s32 results_to_show;
|
|
};
|
|
|
|
global NTFS_Workspace ntfs_workspace;
|
|
|
|
bool ntfs_workspace_files_loaded () {
|
|
if (ntfs_workspace.drives.count == 0) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Deserialize_Win32_Drives (string file_path) {
|
|
Timed_Block_Print("Deserialize_Win32_Drives");
|
|
|
|
push_allocator(temp());
|
|
auto_release_temp();
|
|
|
|
Deserializer deserializer = read_entire_file(file_path, true);
|
|
if (deserializer.count == 0) return false;
|
|
auto d = &deserializer;
|
|
|
|
auto drive_table = get_drive_table();
|
|
|
|
u32 magic_number; s32 drive_count;
|
|
Read(d, &magic_number);
|
|
Assert(magic_number == Win32_Drive_Magic_Number);
|
|
Read(d, &drive_count);
|
|
|
|
ntfs_workspace.arena = next_arena(Arena_Reserve::Size_64G);
|
|
push_arena(ntfs_workspace.arena);
|
|
Assert(ntfs_workspace.drives.count == 0);
|
|
array_resize(ntfs_workspace.supplementary, drive_count);
|
|
// ntfs_workspace.drives.allocator = GPAllocator();
|
|
|
|
log("[Deserialize_Win32_Drives] drive_count: %d", drive_count);
|
|
|
|
for (s32 i = 0; i < drive_count; i += 1) {
|
|
// look up disk based on drive_label
|
|
s32 index = 0;
|
|
Read(d, &index); Assert(i == index);
|
|
string drive_label = {};
|
|
ReadString16(d, drive_label);
|
|
|
|
OS_Drive** drive_ptr = array_add(ntfs_workspace.drives);
|
|
(*drive_ptr) = New<OS_Drive>();
|
|
OS_Drive* drive = *drive_ptr;
|
|
|
|
drive->label = copy_string(drive_label);
|
|
ReadString16(d, drive->volume_name);
|
|
Read(d, &drive->type);
|
|
Read(d, &drive->file_system);
|
|
Read(d, &drive->full_size);
|
|
Read(d, &drive->free_space);
|
|
Read(d, &drive->serial_number);
|
|
Read(d, &drive->max_component_length);
|
|
Read(d, &drive->file_system_flags);
|
|
|
|
drive->data = New<Dense_FS>();
|
|
Dense_FS_initialize(drive->data);
|
|
{ // (Dense_FS):paths
|
|
DFS_Array paths = drive->data->paths;
|
|
ReadToArenaArray(d, paths.strings);
|
|
// ReadToArenaArray(d, paths.wstrings);
|
|
ReadToArenaArray(d, paths.offsets);
|
|
ReadToArenaArray(d, paths.lengths);
|
|
ReadToArenaArray(d, paths.modtimes);
|
|
ReadToArenaArray(d, paths.sizes);
|
|
ReadToArenaArray(d, paths.parent_indices);
|
|
}
|
|
{ // (Dense_FS):files
|
|
DFS_Array files = drive->data->files;
|
|
ReadToArenaArray(d, files.strings);
|
|
// ReadToArenaArray(d, files.wstrings);
|
|
ReadToArenaArray(d, files.offsets);
|
|
ReadToArenaArray(d, files.lengths);
|
|
ReadToArenaArray(d, files.modtimes);
|
|
ReadToArenaArray(d, files.sizes);
|
|
ReadToArenaArray(d, files.parent_indices);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Serialize_Win32_Drives (ArrayView<Win32_Drive*> drives, string file_path) {
|
|
Timed_Block_Print("Serialize_Win32_Drives");
|
|
File f = file_open(file_path, true, false, true);
|
|
if (!file_is_valid(f)) return false;
|
|
|
|
Serializer* s = new_serializer(Arena_Reserve::Size_64G);
|
|
// #TODO #Serialization Unfortunately, there's a lot of needless copying here
|
|
// it would be a lot nicer if we could just write-file in place. idk how to do that though ;_;
|
|
// Serialize drive count;
|
|
Add(s, (u32)Win32_Drive_Magic_Number);
|
|
Add(s, (s32)drives.count);
|
|
|
|
for_each(d, drives) {
|
|
Win32_Drive* drive = drives[d];
|
|
// First, serialize the drive header:
|
|
Add(s, (s32)d);
|
|
AddString16(s, drive->label);
|
|
AddString16(s, drive->volume_name);
|
|
Add(s, drive->type);
|
|
Add(s, drive->file_system);
|
|
Add(s, drive->full_size);
|
|
Add(s, drive->free_space);
|
|
Add(s, drive->serial_number);
|
|
Add(s, drive->max_component_length);
|
|
Add(s, drive->file_system_flags);
|
|
// Write to file and reset
|
|
|
|
// (Dense_FS)
|
|
Assert(drive->data);
|
|
{ // (Dense_FS):paths
|
|
DFS_Array paths = drive->data->paths;
|
|
// Note these are all prefixed with their respective lengths.
|
|
AddArray(s, to_view(*paths.strings));
|
|
// AddArray(s, to_view(*paths.wstrings));
|
|
AddArray(s, to_view(*paths.offsets));
|
|
AddArray(s, to_view(*paths.lengths));
|
|
AddArray(s, to_view(*paths.modtimes));
|
|
AddArray(s, to_view(*paths.sizes));
|
|
AddArray(s, to_view(*paths.parent_indices));
|
|
}
|
|
{ // (Dense_FS):files
|
|
DFS_Array files = drive->data->files;
|
|
AddArray(s, to_view(*files.strings));
|
|
// AddArray(s, to_view(*files.wstrings));
|
|
AddArray(s, to_view(*files.offsets));
|
|
AddArray(s, to_view(*files.lengths));
|
|
AddArray(s, to_view(*files.modtimes));
|
|
AddArray(s, to_view(*files.sizes));
|
|
AddArray(s, to_view(*files.parent_indices));
|
|
}
|
|
|
|
file_write(&f, to_view(*s));
|
|
reset_serializer(s);
|
|
}
|
|
|
|
file_close(&f);
|
|
free_serializer(s);
|
|
|
|
return true;
|
|
}
|
|
|
|
void ntfs_create_enumeration_threads (s32 thread_count) {
|
|
if (!ex1_ntfs.initialized) { Timed_Block_Print("Thread initialization (ntfs)");
|
|
ex1_ntfs.initialized = true;
|
|
ex1_ntfs.threads = ArrayView<Thread>(thread_count);
|
|
ex1_ntfs.threads_in_flight.allocator = GPAllocator();
|
|
for_each(t, ex1_ntfs.threads) {
|
|
string thread_name = format_string("ntfs_enumeration_thread#%d", t);
|
|
bool success = thread_init(&ex1_ntfs.threads[t], ntfs_enumeration_thread_proc, thread_name);
|
|
Assert(success);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
void Ex1_show_ntfs_workspace () { using namespace ImGui;
|
|
|
|
SliderInt("Select path index", &ex1w.path_select, 0, count_paths(stfe)-1);
|
|
Text("%s", get_path_copy(stfe, ex1w.path_select).data);
|
|
Text("time modified: %s", format_time_datetime(get_path_modtime(stfe, ex1w.path_select)).data);
|
|
// #TODO: modtime (to indextime)
|
|
SliderInt("Select file index", &ex1w.file_select, 0, count_files(stfe)-1);
|
|
Text("%s", get_file_copy(stfe, ex1w.file_select).data);
|
|
Text("size: %s", format_bytes(get_file_size_bytes(stfe, ex1w.file_select)).data);
|
|
Text("time modified: %s", format_time_datetime(get_file_modtime(stfe, ex1w.file_select)).data);
|
|
|
|
push_allocator(temp());
|
|
for_each(d, ntfs_workspace.drives) {
|
|
OS_Drive* drive = ntfs_workspace.drives[d];
|
|
Text("%d. %s paths: %lld, files: %lld",
|
|
d, drive->label.data,
|
|
drive->data->paths.offsets->count,
|
|
drive->data->files.offsets->count);
|
|
}
|
|
// SliderInt("Results to Show", &ntfs_workspace.results_to_show, 0, 50);
|
|
for_each(d, ntfs_workspace.drives) {
|
|
OS_Drive* drive = ntfs_workspace.drives[d];
|
|
// #TODO: Radio button for choosing between paths, files
|
|
char* rb1 = format_cstring("paths##%s", drive->label.data);
|
|
RadioButton(rb1, &ntfs_workspace.supplementary[d].radio_button, 1);
|
|
SameLine();
|
|
char* rb2 = format_cstring("files##%s", drive->label.data);
|
|
RadioButton(rb2, &ntfs_workspace.supplementary[d].radio_button, 0);
|
|
SameLine();
|
|
s32 max_count = (s32)drive->data->paths.offsets->count;
|
|
if (ntfs_workspace.supplementary[d].radio_button == 0) {
|
|
max_count = (s32)drive->data->files.offsets->count;
|
|
}
|
|
char* slider_label = format_cstring("%s index", drive->label.data);
|
|
if (SliderInt(slider_label, &ntfs_workspace.supplementary[d].index, 0, max_count)) { }
|
|
}
|
|
for_each(d, ntfs_workspace.drives) {
|
|
if (ntfs_workspace.supplementary[d].radio_button == 0) { // files
|
|
OS_Drive* drive = ntfs_workspace.drives[d];
|
|
Dense_FS* dfs = drive->data;
|
|
DFS_Array* dfsa = &drive->data->files;
|
|
s64 file_index = ntfs_workspace.supplementary[d].index;
|
|
DFS_Value v = get_value(dfs, dfsa, file_index);
|
|
// #TODO NOTE: v.full_path is NOT the full path #rename
|
|
Text("Filename: %s, parent_id: %d", copy_string(v.full_path).data, v.parent_index);
|
|
string full_path = get_full_path_from_index(drive, dfsa, file_index);
|
|
Text("Full path: %s", full_path.data);
|
|
bool success = file_length(full_path, &v.size); // temp, obviously we don't wanna call this every frame lol
|
|
Text(" > size: %lld B", v.size);
|
|
Text(" > size: %s", format_bytes(v.size).data);
|
|
// Text(" > modtime: %s",
|
|
} else {
|
|
// DFS_Array* dfsa = &ntfs_workspace.drives[d]->data->paths;
|
|
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
/*SeparatorText("ex1_ntfs");
|
|
Text("Threads in flight count: %d", ex1_ntfs.threads_in_flight.count);
|
|
for_each(i, ex1_ntfs.threads) {
|
|
Text(" [%d] initialized: %d, has_context: %d, has_data: %d",
|
|
i, ex1_ntfs.threads[i].proc != nullptr, ex1_ntfs.threads[i].context != nullptr, ex1_ntfs.threads[i].data != nullptr);
|
|
}*/
|
|
/*// #NTFS_MFT_RAW
|
|
push_allocator(GPAllocator());
|
|
Array<ArrayView<OS_Drive*>> drive_split;
|
|
drive_split.allocator = temp(); // this is only needed for this frame
|
|
|
|
if (drives.count > os_cpu_physical_core_count()) {
|
|
s32 thread_count = os_cpu_physical_core_count();
|
|
array_resize(drive_split, thread_count);
|
|
ntfs_create_enumeration_threads(thread_count);
|
|
|
|
s32 threads_to_create = thread_count;
|
|
s64 drives_per_thread = (drives.count / thread_count);
|
|
s64 remainder = drives.count % thread_count;
|
|
s64 current_drive = 0;
|
|
|
|
for_each(d, drive_split) {
|
|
if (d == drive_split.count) {
|
|
drive_split[d] = ArrayView<OS_Drive*>(remainder);
|
|
} else {
|
|
drive_split[d] = ArrayView<OS_Drive*>(drives_per_thread);
|
|
}
|
|
|
|
for (s64 i = 0; i < drive_split[d].count; i += 1) {
|
|
drive_split[d][i] = drives[current_drive];
|
|
current_drive += 1;
|
|
}
|
|
}
|
|
|
|
debug_break(); // #TODO: Check that the work has been distributed correctly.
|
|
} else { // more threads than drives, or same amount
|
|
s32 thread_count = (s32)drives.count;
|
|
array_resize(drive_split, drives.count);
|
|
ntfs_create_enumeration_threads(thread_count);
|
|
|
|
for_each(d, drives) {
|
|
auto drive = drives[d];
|
|
drive_split[d] = ArrayView<OS_Drive*>(1); // Arrays of size one are sad :pensive:
|
|
|
|
drive_split[d][0] = drive;
|
|
}
|
|
}
|
|
|
|
s64 active_thread_count = drive_split.count;
|
|
|
|
ex1_ntfs.threads_started = true;
|
|
for (s64 t = 0; t < active_thread_count; t += 1) {
|
|
Thread* thread = &ex1_ntfs.threads[t];
|
|
Arena* thread_arena = next_arena(Arena_Reserve::Size_64K);
|
|
push_arena(thread_arena);
|
|
auto thread_data = New<NTFS_Enumeration_Task>();
|
|
thread_data->pool = thread_arena;
|
|
thread_data->drives = drive_split[t];
|
|
thread_start(thread, thread_data);
|
|
array_add(ex1_ntfs.threads_in_flight, thread);
|
|
}*/
|
|
|
|
|
|
/* #NTFS_MFT_RAW
|
|
if (ex1_ntfs.threads_in_flight.count) {
|
|
Text("Threads in flight: %d", ex1_ntfs.threads_in_flight.count);
|
|
|
|
for_each(t, ex1_ntfs.threads_in_flight) {
|
|
if (thread_is_done(ex1_ntfs.threads_in_flight[t])) {
|
|
push_allocator(GPAllocator());
|
|
|
|
Thread* thread = ex1_ntfs.threads_in_flight[t];
|
|
auto task = thread_task(NTFS_Enumeration_Task);
|
|
array_free(task->drives);
|
|
|
|
// make sure to retreive any data you need to from here!
|
|
release_arena(task->pool);
|
|
|
|
thread_deinit(ex1_ntfs.threads_in_flight[t], false);
|
|
array_unordered_remove_by_index(ex1_ntfs.threads_in_flight, t);
|
|
t -= 1; // check this element index again!
|
|
}
|
|
}
|
|
}*/
|
|
|
|
/* #NTFS_MFT_RAW
|
|
if (ex1_ntfs.threads_started && !ex1_ntfs.threads_in_flight.count) {
|
|
// All threads are complete, we're free to clean up remaining memory
|
|
push_allocator(GPAllocator());
|
|
array_free(ex1_ntfs.threads);
|
|
array_free(ex1_ntfs.threads_in_flight);
|
|
|
|
// Instead maybe we should just memset this to zero.
|
|
reset_struct(&ex1_ntfs);
|
|
}
|
|
|
|
// How do I tell when all files are enumerated?
|
|
// check drives[i]->data.paths.wstrings.count count?
|
|
if (all_drives_enumerated && Button("Save drive data")) {
|
|
string file_path = format_string_temp("%s_DriveData.bin", os_get_machine_name().data);
|
|
bool success = Serialize_Win32_Drives(drives, file_path);
|
|
if (!success) { log_error("Failed to save Win32_Drive data"); }
|
|
}
|
|
|
|
if (all_drives_enumerated && Button("Clear all drive data")) {
|
|
os_clear_drive_data();
|
|
}*/
|