4coder/filetrack/4tech_file_track_win32.c

454 lines
15 KiB
C

/*
Copy Right FourTech LLC, 2016
All Rights Are Reserved
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 <Windows.h>
typedef struct {
char result[2048];
OVERLAPPED overlapped;
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_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_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_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_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_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_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_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