/* The OS agnostic file tracking API for applications that want to interact with potentially many files on the disk that could be changed by other applications. Created on: 20.07.2016 */ // TOP #include "4tech_file_track.h" #include "4tech_file_track_general.c" #include typedef struct { OVERLAPPED overlapped; char result[2048]; HANDLE dir; int32_t user_count; } Win32_Directory_Listener; typedef struct { DLL_Node node; Win32_Directory_Listener listener; } Win32_Directory_Listener_Node; typedef struct { HANDLE iocp; CRITICAL_SECTION table_lock; void *tables; DLL_Node free_sentinel; } Win32_File_Track_Vars; typedef struct { File_Index hash; HANDLE dir; Win32_Directory_Listener_Node *listener_node; } Win32_File_Track_Entry; #define to_vars(s) ((Win32_File_Track_Vars*)(s)) #define to_tables(v) ((File_Track_Tables*)(v->tables)) FILE_TRACK_LINK File_Track_Result init_track_system(File_Track_System *system, void *table_memory, int32_t table_memory_size, void *listener_memory, int32_t listener_memory_size){ File_Track_Result result = FileTrack_MemoryTooSmall; Win32_File_Track_Vars *vars = to_vars(system); Assert(sizeof(Win32_File_Track_Entry) <= sizeof(File_Track_Entry)); if (enough_memory_to_init_table(table_memory_size) && sizeof(Win32_Directory_Listener_Node) <= listener_memory_size){ // NOTE(allen): Initialize main data tables vars->tables = table_memory; File_Track_Tables *tables = to_tables(vars); init_table_memory(tables, table_memory_size); // NOTE(allen): Initialize nodes of directory watching { init_sentinel_node(&vars->free_sentinel); Win32_Directory_Listener_Node *listener = (Win32_Directory_Listener_Node*)listener_memory; int32_t count = listener_memory_size / sizeof(Win32_Directory_Listener_Node); for (int32_t i = 0; i < count; ++i, ++listener){ insert_node(&vars->free_sentinel, &listener->node); } } // NOTE(allen): Prepare the file tracking synchronization objects. { InitializeCriticalSection(&vars->table_lock); vars->iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 1); } result = FileTrack_Good; } return(result); } static int32_t internal_get_parent_name(char *out, int32_t max, char *name){ int32_t len, slash_i; char *ptr = name; for (; *ptr != 0; ++ptr); len = (int32_t)(ptr - name); // TODO(allen): make this system real Assert(len < max); for (slash_i = len-1; slash_i > 0 && name[slash_i] != '\\' && name[slash_i] != '/'; --slash_i); for (int32_t i = 0; i < slash_i; ++i){ out[i] = name[i]; } out[slash_i] = 0; return(slash_i); } static File_Index internal_get_file_index(BY_HANDLE_FILE_INFORMATION info){ File_Index hash; hash.id[0] = info.nFileIndexLow; hash.id[1] = info.nFileIndexHigh; hash.id[2] = info.dwVolumeSerialNumber; hash.id[3] = 0; return(hash); } FILE_TRACK_LINK File_Track_Result add_listener(File_Track_System *system, char *filename){ File_Track_Result result = FileTrack_Good; Win32_File_Track_Vars *vars = to_vars(system); EnterCriticalSection(&vars->table_lock); { File_Track_Tables *tables = to_tables(vars); // TODO(allen): make this real! char dir_name[1024]; internal_get_parent_name(dir_name, sizeof(dir_name), filename); HANDLE dir = CreateFile( dir_name, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, 0); if (dir != INVALID_HANDLE_VALUE){ BY_HANDLE_FILE_INFORMATION dir_info = {0}; DWORD getinfo_result = GetFileInformationByHandle(dir, &dir_info); if (getinfo_result){ File_Index dir_hash = internal_get_file_index(dir_info); File_Track_Entry *dir_lookup = tracking_system_lookup_entry(tables, dir_hash); Win32_File_Track_Entry *win32_entry = (Win32_File_Track_Entry*)dir_lookup; if (entry_is_available(dir_lookup)){ if (tracking_system_has_space(tables, 1)){ Win32_Directory_Listener_Node *node = (Win32_Directory_Listener_Node*) allocate_node(&vars->free_sentinel); if (node){ if (CreateIoCompletionPort(dir, vars->iocp, (ULONG_PTR)node, 1)){ ZeroStruct(node->listener.overlapped); if (ReadDirectoryChangesW(dir, node->listener.result, sizeof(node->listener.result), 0, FILE_NOTIFY_CHANGE_LAST_WRITE, 0, &node->listener.overlapped, 0)){ node->listener.dir = dir; node->listener.user_count = 1; win32_entry->hash = dir_hash; win32_entry->dir = dir; win32_entry->listener_node = node; ++tables->tracked_count; } else{ result = FileTrack_FileSystemError; } } else{ result = FileTrack_FileSystemError; } if (result != FileTrack_Good){ insert_node(&vars->free_sentinel, &node->node); } } else{ result = FileTrack_OutOfListenerMemory; } } else{ result = FileTrack_OutOfTableMemory; } } else{ Win32_Directory_Listener_Node *node = win32_entry->listener_node; ++node->listener.user_count; } } else{ result = FileTrack_FileSystemError; } } else{ result = FileTrack_FileSystemError; } if (result != FileTrack_Good && dir != 0 && dir != INVALID_HANDLE_VALUE){ CloseHandle(dir); } } LeaveCriticalSection(&vars->table_lock); return(result); } FILE_TRACK_LINK File_Track_Result remove_listener(File_Track_System *system, char *filename){ File_Track_Result result = FileTrack_Good; Win32_File_Track_Vars *vars = to_vars(system); EnterCriticalSection(&vars->table_lock); { File_Track_Tables *tables = to_tables(vars); // TODO(allen): make this real! char dir_name[1024]; internal_get_parent_name(dir_name, sizeof(dir_name), filename); HANDLE dir = CreateFile( dir_name, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, 0); if (dir != INVALID_HANDLE_VALUE){ BY_HANDLE_FILE_INFORMATION dir_info = {0}; DWORD getinfo_result = GetFileInformationByHandle(dir, &dir_info); if (getinfo_result){ File_Index dir_hash = internal_get_file_index(dir_info); File_Track_Entry *dir_lookup = tracking_system_lookup_entry(tables, dir_hash); Win32_File_Track_Entry *win32_dir = (Win32_File_Track_Entry*)dir_lookup; Assert(!entry_is_available(dir_lookup)); Win32_Directory_Listener_Node *node = win32_dir->listener_node; --node->listener.user_count; if (node->listener.user_count == 0){ insert_node(&vars->free_sentinel, &node->node); CloseHandle(win32_dir->dir); internal_free_slot(tables, dir_lookup); } } else{ result = FileTrack_FileSystemError; } CloseHandle(dir); } else{ result = FileTrack_FileSystemError; } } LeaveCriticalSection(&vars->table_lock); return(result); } FILE_TRACK_LINK File_Track_Result move_track_system(File_Track_System *system, void *mem, int32_t size){ File_Track_Result result = FileTrack_Good; Win32_File_Track_Vars *vars = to_vars(system); EnterCriticalSection(&vars->table_lock); { File_Track_Tables *original_tables = to_tables(vars); result = move_table_memory(original_tables, mem, size); if (result == FileTrack_Good){ vars->tables = mem; } } LeaveCriticalSection(&vars->table_lock); return(result); } FILE_TRACK_LINK File_Track_Result expand_track_system_listeners(File_Track_System *system, void *mem, int32_t size){ File_Track_Result result = FileTrack_Good; Win32_File_Track_Vars *vars = to_vars(system); EnterCriticalSection(&vars->table_lock); if (sizeof(Win32_Directory_Listener_Node) <= size){ Win32_Directory_Listener_Node *listener = (Win32_Directory_Listener_Node*)mem; int32_t count = size / sizeof(Win32_Directory_Listener_Node); for (int32_t i = 0; i < count; ++i, ++listener){ insert_node(&vars->free_sentinel, &listener->node); } } else{ result = FileTrack_MemoryTooSmall; } LeaveCriticalSection(&vars->table_lock); return(result); } FILE_TRACK_LINK File_Track_Result get_change_event(File_Track_System *system, char *buffer, int32_t max, int32_t *size){ File_Track_Result result = FileTrack_NoMoreEvents; Win32_File_Track_Vars *vars = to_vars(system); EnterCriticalSection(&vars->table_lock); { OVERLAPPED *overlapped = 0; DWORD length = 0; ULONG_PTR key = 0; if (GetQueuedCompletionStatus(vars->iocp, &length, &key, &overlapped, 0)){ Win32_Directory_Listener *listener_ptr = (Win32_Directory_Listener*)overlapped; // NOTE(allen): Get a copy of the state of this node so we can set the node // to work listening for changes again right away. Win32_Directory_Listener listener = *listener_ptr; ZeroStruct(listener_ptr->overlapped); ReadDirectoryChangesW(listener_ptr->dir, listener_ptr->result, sizeof(listener_ptr->result), 0, FILE_NOTIFY_CHANGE_LAST_WRITE, 0, &listener_ptr->overlapped, 0); char *listener_buffer = listener.result; DWORD offset = 0; FILE_NOTIFY_INFORMATION *info = 0; for (;;){ info = (FILE_NOTIFY_INFORMATION*)(listener_buffer + offset); int32_t len = info->FileNameLength / 2; int32_t dir_len = GetFinalPathNameByHandle(listener.dir, 0, 0, FILE_NAME_NORMALIZED); int32_t req_size = dir_len + 1 + len; *size = req_size; if (req_size < max){ int32_t pos = 0; pos = GetFinalPathNameByHandle(listener.dir, buffer, max, FILE_NAME_NORMALIZED); buffer[pos++] = '\\'; for (int32_t i = 0; i < len; ++i, ++pos){ buffer[pos] = (char)info->FileName[i]; } if (buffer[0] == '\\'){ for (int32_t i = 0; i+4 < pos; ++i){ buffer[i] = buffer[i+4]; } *size -= 4; } result = FileTrack_Good; } else{ // TODO(allen): Need some way to stash this result so that if the // user comes back with more memory we can give them the change // notification they missed. result = FileTrack_MemoryTooSmall; } if (info->NextEntryOffset != 0){ // TODO(allen): We're not ready to handle this yet. // For now I am breaking. In the future, if there // are more results we should stash them and return // them in future calls. offset += info->NextEntryOffset; break; } else{ break; } } } } LeaveCriticalSection(&vars->table_lock); return(result); } FILE_TRACK_LINK File_Track_Result shut_down_track_system(File_Track_System *system){ File_Track_Result result = FileTrack_Good; Win32_File_Track_Vars *vars = to_vars(system); DWORD win32_result = 0; // NOTE(allen): Close all the handles stored in the table. { File_Track_Tables *tables = to_tables(vars); File_Track_Entry *entries = (File_Track_Entry*)to_ptr(tables, tables->file_table); uint32_t max = tables->max; for (uint32_t index = 0; index < max; ++index){ File_Track_Entry *entry = entries + index; if (!entry_is_available(entry)){ Win32_File_Track_Entry *win32_entry = (Win32_File_Track_Entry*)entry; if (!CloseHandle(win32_entry->dir)){ win32_result = 1; } } } } // NOTE(allen): Close all the global track system resources. { if (!CloseHandle(vars->iocp)){ win32_result = 1; } DeleteCriticalSection(&vars->table_lock); } if (win32_result){ result = FileTrack_FileSystemError; } return(result); } // BOTTOM