466 lines
16 KiB
C
466 lines
16 KiB
C
/*
|
|
|
|
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 {
|
|
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);
|
|
}
|
|
|
|
#define FLAGS ( \
|
|
FILE_NOTIFY_CHANGE_FILE_NAME | \
|
|
FILE_NOTIFY_CHANGE_DIR_NAME | \
|
|
FILE_NOTIFY_CHANGE_ATTRIBUTES | \
|
|
FILE_NOTIFY_CHANGE_SIZE | \
|
|
FILE_NOTIFY_CHANGE_LAST_WRITE | \
|
|
FILE_NOTIFY_CHANGE_LAST_ACCESS| \
|
|
FILE_NOTIFY_CHANGE_SECURITY | \
|
|
FILE_NOTIFY_CHANGE_CREATION | \
|
|
0)
|
|
|
|
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),
|
|
1,
|
|
FLAGS,
|
|
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);
|
|
CancelIo(win32_dir->dir);
|
|
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);
|
|
|
|
static int32_t has_buffered_event = 0;
|
|
static DWORD offset = 0;
|
|
static Win32_Directory_Listener listener;
|
|
|
|
EnterCriticalSection(&vars->table_lock);
|
|
|
|
{
|
|
OVERLAPPED *overlapped = 0;
|
|
DWORD length = 0;
|
|
ULONG_PTR key = 0;
|
|
|
|
int32_t has_result = 0;
|
|
|
|
if (has_buffered_event){
|
|
has_buffered_event = 0;
|
|
has_result = 1;
|
|
}
|
|
else{
|
|
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.
|
|
listener = *listener_ptr;
|
|
|
|
ZeroStruct(listener_ptr->overlapped);
|
|
ReadDirectoryChangesW(listener_ptr->dir,
|
|
listener_ptr->result,
|
|
sizeof(listener_ptr->result),
|
|
1,
|
|
FLAGS,
|
|
0,
|
|
&listener_ptr->overlapped,
|
|
0);
|
|
|
|
offset = 0;
|
|
has_result = 1;
|
|
}
|
|
}
|
|
|
|
if (has_result){
|
|
|
|
FILE_NOTIFY_INFORMATION *info = (FILE_NOTIFY_INFORMATION*)(listener.result + 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;
|
|
has_buffered_event = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 (!CancelIo(win32_entry->dir)){
|
|
win32_result = 1;
|
|
}
|
|
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
|