// 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; // #TODO: FRNs should be 64-bit! 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 mft_file; ArrayView 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(true); mft->mft_file = ArrayView(NTFS_MFT_File_Record_Size); mft->mft_buffer = ArrayView(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(default_allocator()); 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 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 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 drives; Array 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 = bootstrap_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 = default_allocator(); 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* 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_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 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_count); ex1_ntfs.threads_in_flight.allocator = default_allocator(); 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(default_allocator()); Array> 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(remainder); } else { drive_split[d] = ArrayView(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(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 = bootstrap_arena(Arena_Reserve::Size_64K); push_arena(thread_arena); auto thread_data = New(); 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(default_allocator()); 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! arena_delete(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(default_allocator()); 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(); }*/