4coder/filetrack/4tech_file_track_win32.c

466 lines
16 KiB
C
Raw Normal View History

2016-08-22 19:31:19 +00:00
/*
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"
2016-08-27 17:48:10 +00:00
#include "4tech_file_track_general.c"
2016-08-26 01:42:46 +00:00
#include <Windows.h>
2016-08-22 19:31:19 +00:00
typedef struct {
OVERLAPPED overlapped;
char result[2048];
2016-08-22 19:31:19 +00:00
HANDLE dir;
int32_t user_count;
2016-08-27 17:48:10 +00:00
} Win32_Directory_Listener;
2016-08-22 19:31:19 +00:00
typedef struct {
DLL_Node node;
2016-08-27 17:48:10 +00:00
Win32_Directory_Listener listener;
} Win32_Directory_Listener_Node;
2016-08-22 19:31:19 +00:00
typedef struct {
HANDLE iocp;
CRITICAL_SECTION table_lock;
void *tables;
DLL_Node free_sentinel;
2016-08-27 17:48:10 +00:00
} Win32_File_Track_Vars;
2016-08-22 19:31:19 +00:00
typedef struct {
File_Index hash;
2016-08-27 17:48:10 +00:00
HANDLE dir;
Win32_Directory_Listener_Node *listener_node;
} Win32_File_Track_Entry;
2016-08-22 19:31:19 +00:00
2016-08-27 17:48:10 +00:00
#define to_vars(s) ((Win32_File_Track_Vars*)(s))
2016-08-22 19:31:19 +00:00
#define to_tables(v) ((File_Track_Tables*)(v->tables))
FILE_TRACK_LINK File_Track_Result
2016-08-22 19:31:19 +00:00
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;
2016-08-27 17:48:10 +00:00
Win32_File_Track_Vars *vars = to_vars(system);
2016-08-22 19:31:19 +00:00
2016-08-27 17:48:10 +00:00
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){
2016-08-22 19:31:19 +00:00
// NOTE(allen): Initialize main data tables
2016-08-27 17:48:10 +00:00
vars->tables = table_memory;
File_Track_Tables *tables = to_tables(vars);
init_table_memory(tables, table_memory_size);
2016-08-22 19:31:19 +00:00
// NOTE(allen): Initialize nodes of directory watching
{
init_sentinel_node(&vars->free_sentinel);
2016-08-27 17:48:10 +00:00
Win32_Directory_Listener_Node *listener = (Win32_Directory_Listener_Node*)listener_memory;
int32_t count = listener_memory_size / sizeof(Win32_Directory_Listener_Node);
2016-08-22 19:31:19 +00:00
for (int32_t i = 0; i < count; ++i, ++listener){
insert_node(&vars->free_sentinel, &listener->node);
}
}
2016-08-26 01:42:46 +00:00
// NOTE(allen): Prepare the file tracking synchronization objects.
2016-08-22 19:31:19 +00:00
{
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);
}
2016-09-13 19:56:14 +00:00
#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
2016-08-26 01:42:46 +00:00
add_listener(File_Track_System *system, char *filename){
File_Track_Result result = FileTrack_Good;
2016-08-27 17:48:10 +00:00
Win32_File_Track_Vars *vars = to_vars(system);
2016-08-22 19:31:19 +00:00
2016-08-26 01:42:46 +00:00
EnterCriticalSection(&vars->table_lock);
{
File_Track_Tables *tables = to_tables(vars);
// TODO(allen): make this real!
2016-08-22 19:31:19 +00:00
char dir_name[1024];
internal_get_parent_name(dir_name, sizeof(dir_name), filename);
2016-08-22 19:31:19 +00:00
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);
2016-08-22 19:31:19 +00:00
if (dir != INVALID_HANDLE_VALUE){
BY_HANDLE_FILE_INFORMATION dir_info = {0};
2016-08-26 01:42:46 +00:00
DWORD getinfo_result = GetFileInformationByHandle(dir, &dir_info);
2016-08-22 19:31:19 +00:00
2016-08-26 01:42:46 +00:00
if (getinfo_result){
2016-08-22 19:31:19 +00:00
File_Index dir_hash = internal_get_file_index(dir_info);
2016-08-27 17:48:10 +00:00
File_Track_Entry *dir_lookup = tracking_system_lookup_entry(tables, dir_hash);
Win32_File_Track_Entry *win32_entry = (Win32_File_Track_Entry*)dir_lookup;
2016-08-22 19:31:19 +00:00
2016-08-27 17:48:10 +00:00
if (entry_is_available(dir_lookup)){
2016-08-26 01:42:46 +00:00
if (tracking_system_has_space(tables, 1)){
2016-08-27 17:48:10 +00:00
Win32_Directory_Listener_Node *node = (Win32_Directory_Listener_Node*)
2016-08-26 01:42:46 +00:00
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),
2016-09-13 19:56:14 +00:00
1,
FLAGS,
2016-08-26 01:42:46 +00:00
0,
&node->listener.overlapped,
0)){
node->listener.dir = dir;
node->listener.user_count = 1;
2016-08-27 17:48:10 +00:00
win32_entry->hash = dir_hash;
win32_entry->dir = dir;
win32_entry->listener_node = node;
2016-08-26 01:42:46 +00:00
++tables->tracked_count;
2016-08-22 19:31:19 +00:00
}
else{
result = FileTrack_FileSystemError;
}
}
else{
2016-08-26 01:42:46 +00:00
result = FileTrack_FileSystemError;
}
if (result != FileTrack_Good){
insert_node(&vars->free_sentinel, &node->node);
2016-08-22 19:31:19 +00:00
}
}
else{
2016-08-26 01:42:46 +00:00
result = FileTrack_OutOfListenerMemory;
2016-08-22 19:31:19 +00:00
}
}
else{
2016-08-26 01:42:46 +00:00
result = FileTrack_OutOfTableMemory;
2016-08-22 19:31:19 +00:00
}
}
else{
2016-08-27 17:48:10 +00:00
Win32_Directory_Listener_Node *node = win32_entry->listener_node;
2016-08-26 01:42:46 +00:00
++node->listener.user_count;
2016-08-22 19:31:19 +00:00
}
}
else{
result = FileTrack_FileSystemError;
}
}
else{
result = FileTrack_FileSystemError;
}
2016-08-26 01:42:46 +00:00
if (result != FileTrack_Good && dir != 0 && dir != INVALID_HANDLE_VALUE){
CloseHandle(dir);
}
}
LeaveCriticalSection(&vars->table_lock);
return(result);
}
FILE_TRACK_LINK File_Track_Result
2016-08-26 01:42:46 +00:00
remove_listener(File_Track_System *system, char *filename){
File_Track_Result result = FileTrack_Good;
2016-08-27 17:48:10 +00:00
Win32_File_Track_Vars *vars = to_vars(system);
EnterCriticalSection(&vars->table_lock);
{
File_Track_Tables *tables = to_tables(vars);
2016-08-26 01:42:46 +00:00
// 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){
2016-08-22 19:31:19 +00:00
BY_HANDLE_FILE_INFORMATION dir_info = {0};
2016-08-26 01:42:46 +00:00
DWORD getinfo_result = GetFileInformationByHandle(dir, &dir_info);
if (getinfo_result){
2016-08-22 19:31:19 +00:00
File_Index dir_hash = internal_get_file_index(dir_info);
2016-08-27 17:48:10 +00:00
File_Track_Entry *dir_lookup = tracking_system_lookup_entry(tables, dir_hash);
Win32_File_Track_Entry *win32_dir = (Win32_File_Track_Entry*)dir_lookup;
2016-08-22 19:31:19 +00:00
2016-08-27 17:48:10 +00:00
Assert(!entry_is_available(dir_lookup));
Win32_Directory_Listener_Node *node = win32_dir->listener_node;
2016-08-22 19:31:19 +00:00
--node->listener.user_count;
if (node->listener.user_count == 0){
insert_node(&vars->free_sentinel, &node->node);
2016-09-13 19:56:14 +00:00
CancelIo(win32_dir->dir);
2016-08-27 17:48:10 +00:00
CloseHandle(win32_dir->dir);
internal_free_slot(tables, dir_lookup);
2016-08-22 19:31:19 +00:00
}
}
else{
result = FileTrack_FileSystemError;
}
2016-08-26 01:42:46 +00:00
CloseHandle(dir);
2016-08-22 19:31:19 +00:00
}
else{
result = FileTrack_FileSystemError;
}
}
LeaveCriticalSection(&vars->table_lock);
return(result);
}
FILE_TRACK_LINK File_Track_Result
2016-08-22 19:31:19 +00:00
move_track_system(File_Track_System *system, void *mem, int32_t size){
File_Track_Result result = FileTrack_Good;
2016-08-27 17:48:10 +00:00
Win32_File_Track_Vars *vars = to_vars(system);
2016-08-22 19:31:19 +00:00
EnterCriticalSection(&vars->table_lock);
2016-08-27 17:48:10 +00:00
{
File_Track_Tables *original_tables = to_tables(vars);
result = move_table_memory(original_tables, mem, size);
if (result == FileTrack_Good){
2016-08-22 19:31:19 +00:00
vars->tables = mem;
}
}
LeaveCriticalSection(&vars->table_lock);
return(result);
}
FILE_TRACK_LINK File_Track_Result
2016-08-22 19:31:19 +00:00
expand_track_system_listeners(File_Track_System *system, void *mem, int32_t size){
File_Track_Result result = FileTrack_Good;
2016-08-27 17:48:10 +00:00
Win32_File_Track_Vars *vars = to_vars(system);
2016-08-22 19:31:19 +00:00
EnterCriticalSection(&vars->table_lock);
2016-08-27 17:48:10 +00:00
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);
2016-08-22 19:31:19 +00:00
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
2016-08-26 21:30:08 +00:00
get_change_event(File_Track_System *system, char *buffer, int32_t max, int32_t *size){
2016-08-22 19:31:19 +00:00
File_Track_Result result = FileTrack_NoMoreEvents;
2016-08-27 17:48:10 +00:00
Win32_File_Track_Vars *vars = to_vars(system);
2016-08-22 19:31:19 +00:00
2016-09-13 19:56:14 +00:00
static int32_t has_buffered_event = 0;
static DWORD offset = 0;
static Win32_Directory_Listener listener;
2016-08-22 19:31:19 +00:00
EnterCriticalSection(&vars->table_lock);
2016-08-26 01:42:46 +00:00
{
OVERLAPPED *overlapped = 0;
DWORD length = 0;
ULONG_PTR key = 0;
2016-08-22 19:31:19 +00:00
2016-09-13 19:56:14 +00:00
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){
2016-08-22 19:31:19 +00:00
2016-09-13 19:56:14 +00:00
FILE_NOTIFY_INFORMATION *info = (FILE_NOTIFY_INFORMATION*)(listener.result + offset);
2016-08-26 01:42:46 +00:00
2016-09-13 19:56:14 +00:00
int32_t len = info->FileNameLength / 2;
int32_t dir_len = GetFinalPathNameByHandle(listener.dir, 0, 0,
FILE_NAME_NORMALIZED);
2016-08-26 01:42:46 +00:00
2016-09-13 19:56:14 +00:00
int32_t req_size = dir_len + 1 + len;
*size = req_size;
if (req_size < max){
int32_t pos = 0;
2016-09-13 19:56:14 +00:00
pos = GetFinalPathNameByHandle(listener.dir, buffer, max,
FILE_NAME_NORMALIZED);
buffer[pos++] = '\\';
2016-09-13 19:56:14 +00:00
for (int32_t i = 0; i < len; ++i, ++pos){
buffer[pos] = (char)info->FileName[i];
2016-08-26 01:42:46 +00:00
}
2016-09-13 19:56:14 +00:00
if (buffer[0] == '\\'){
for (int32_t i = 0; i+4 < pos; ++i){
buffer[i] = buffer[i+4];
}
*size -= 4;
2016-08-26 01:42:46 +00:00
}
2016-09-13 19:56:14 +00:00
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;
}
}
}
2016-08-26 01:42:46 +00:00
LeaveCriticalSection(&vars->table_lock);
return(result);
}
FILE_TRACK_LINK File_Track_Result
2016-08-22 19:31:19 +00:00
shut_down_track_system(File_Track_System *system){
File_Track_Result result = FileTrack_Good;
2016-08-27 17:48:10 +00:00
Win32_File_Track_Vars *vars = to_vars(system);
2016-08-22 19:31:19 +00:00
DWORD win32_result = 0;
2016-08-27 17:48:10 +00:00
// 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;
2016-09-13 19:56:14 +00:00
if (!CancelIo(win32_entry->dir)){
win32_result = 1;
}
2016-08-27 17:48:10 +00:00
if (!CloseHandle(win32_entry->dir)){
win32_result = 1;
}
2016-08-22 19:31:19 +00:00
}
}
}
2016-08-27 17:48:10 +00:00
// NOTE(allen): Close all the global track system resources.
{
if (!CloseHandle(vars->iocp)){
win32_result = 1;
}
DeleteCriticalSection(&vars->table_lock);
2016-08-22 19:31:19 +00:00
}
2016-08-26 01:42:46 +00:00
2016-08-22 19:31:19 +00:00
if (win32_result){
result = FileTrack_FileSystemError;
}
return(result);
}
// BOTTOM