2016-08-22 19:31:19 +00:00
/*
2017-03-18 21:07:25 +00:00
* Mr . 4 th Dimention - Allen Webster
*
* 20.07 .2016
*
* File tracking win32 .
*
*/
2016-08-22 19:31:19 +00:00
// TOP
2017-03-18 21:07:25 +00:00
# include "4ed_file_track.h"
2017-03-18 22:19:51 +00:00
# include "4ed_file_track_general.cpp"
2016-08-26 01:42:46 +00:00
2016-08-29 16:52:47 +00:00
# include <Windows.h>
2016-08-22 19:31:19 +00:00
typedef struct {
OVERLAPPED overlapped ;
2017-03-18 22:19:51 +00:00
u16 result [ 1024 ] ;
2016-08-22 19:31:19 +00:00
HANDLE dir ;
2017-03-18 21:07:25 +00:00
i32 user_count ;
2016-08-27 17:48:10 +00:00
} Win32_Directory_Listener ;
2017-03-18 21:07:25 +00:00
global_const OVERLAPPED null_overlapped = { 0 } ;
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))
2016-08-29 16:52:47 +00:00
FILE_TRACK_LINK File_Track_Result
2017-03-19 23:05:05 +00:00
init_track_system ( File_Track_System * system , Partition * scratch , void * table_memory , umem table_memory_size , void * listener_memory , umem listener_memory_size ) {
2016-08-22 19:31:19 +00:00
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 ;
2017-03-19 23:05:05 +00:00
umem node_size = sizeof ( Win32_Directory_Listener_Node ) ;
u32 count = ( u32 ) ( listener_memory_size / node_size ) ;
for ( u32 i = 0 ; i < count ; + + i , + + listener ) {
2016-08-22 19:31:19 +00:00
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 ) ;
}
2017-03-18 22:19:51 +00:00
internal umem
internal_utf8_file_to_utf16_parent ( u16 * out , u32 max , u8 * name ) {
u8 * ptr = name ;
2016-08-22 19:31:19 +00:00
for ( ; * ptr ! = 0 ; + + ptr ) ;
2017-03-18 22:19:51 +00:00
umem len = ( umem ) ( ptr - name ) ;
2016-08-22 19:31:19 +00:00
// TODO(allen): make this system real
Assert ( len < max ) ;
2017-03-18 22:19:51 +00:00
umem slash_i = len - 1 ;
2017-03-18 21:07:25 +00:00
for ( ; slash_i > 0 & & name [ slash_i ] ! = ' \\ ' & & name [ slash_i ] ! = ' / ' ; - - slash_i ) ;
2016-08-22 19:31:19 +00:00
2017-03-18 22:19:51 +00:00
b32 error = false ;
slash_i = utf8_to_utf16_minimal_checking ( out , max - 1 , name , len , & error ) ;
2016-08-22 19:31:19 +00:00
out [ slash_i ] = 0 ;
return ( slash_i ) ;
}
2017-03-18 21:07:25 +00:00
internal File_Index
2016-08-22 19:31:19 +00:00
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 )
2016-08-29 16:52:47 +00:00
FILE_TRACK_LINK File_Track_Result
2017-03-19 05:10:45 +00:00
add_listener ( File_Track_System * system , Partition * scratch , u8 * filename ) {
2016-08-24 23:01:33 +00:00
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!
2017-03-18 22:19:51 +00:00
u16 dir_name [ 1024 ] ;
internal_utf8_file_to_utf16_parent ( dir_name , ArrayCount ( dir_name ) , filename ) ;
2016-08-22 19:31:19 +00:00
2017-03-20 15:55:46 +00:00
HANDLE dir = CreateFile ( ( LPWSTR ) 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 ) ) {
2017-03-18 21:07:25 +00:00
node - > listener . overlapped = null_overlapped ;
2017-02-18 01:04:41 +00:00
if ( ReadDirectoryChangesW ( dir , node - > listener . result , sizeof ( node - > listener . result ) , 1 , FLAGS , 0 , & node - > listener . overlapped , 0 ) ) {
2016-08-26 01:42:46 +00:00
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 ) ;
2016-08-24 23:01:33 +00:00
}
}
LeaveCriticalSection ( & vars - > table_lock ) ;
return ( result ) ;
}
2016-08-29 16:52:47 +00:00
FILE_TRACK_LINK File_Track_Result
2017-03-19 05:10:45 +00:00
remove_listener ( File_Track_System * system , Partition * scratch , u8 * filename ) {
2016-08-24 23:01:33 +00:00
File_Track_Result result = FileTrack_Good ;
2016-08-27 17:48:10 +00:00
Win32_File_Track_Vars * vars = to_vars ( system ) ;
2016-08-24 23:01:33 +00:00
EnterCriticalSection ( & vars - > table_lock ) ;
2017-03-18 21:07:25 +00:00
File_Track_Tables * tables = to_tables ( vars ) ;
// TODO(allen): make this real!
2017-03-18 22:19:51 +00:00
u16 dir_name [ 1024 ] ;
internal_utf8_file_to_utf16_parent ( dir_name , ArrayCount ( dir_name ) , filename ) ;
2017-03-18 21:07:25 +00:00
2017-03-20 15:55:46 +00:00
HANDLE dir = CreateFile ( ( LPWSTR ) 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 ) ;
2017-03-18 21:07:25 +00:00
if ( dir ! = INVALID_HANDLE_VALUE ) {
BY_HANDLE_FILE_INFORMATION dir_info = { 0 } ;
DWORD getinfo_result = GetFileInformationByHandle ( dir , & dir_info ) ;
2016-08-26 01:42:46 +00:00
2017-03-18 21:07:25 +00:00
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 ;
2016-08-26 01:42:46 +00:00
2017-03-18 21:07:25 +00:00
Assert ( ! entry_is_available ( dir_lookup ) ) ;
Win32_Directory_Listener_Node * node = win32_dir - > listener_node ;
- - node - > listener . user_count ;
2016-08-26 01:42:46 +00:00
2017-03-18 21:07:25 +00:00
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 ) ;
}
2016-08-22 19:31:19 +00:00
}
else {
result = FileTrack_FileSystemError ;
}
2017-03-18 21:07:25 +00:00
CloseHandle ( dir ) ;
}
else {
result = FileTrack_FileSystemError ;
2016-08-22 19:31:19 +00:00
}
LeaveCriticalSection ( & vars - > table_lock ) ;
return ( result ) ;
}
2016-08-29 16:52:47 +00:00
FILE_TRACK_LINK File_Track_Result
2017-03-19 23:05:05 +00:00
move_track_system ( File_Track_System * system , Partition * scratch , void * mem , umem size ) {
2016-08-22 19:31:19 +00:00
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 ) ;
2017-03-18 21:07:25 +00:00
File_Track_Tables * original_tables = to_tables ( vars ) ;
result = move_table_memory ( original_tables , mem , size ) ;
if ( result = = FileTrack_Good ) {
vars - > tables = mem ;
2016-08-22 19:31:19 +00:00
}
LeaveCriticalSection ( & vars - > table_lock ) ;
return ( result ) ;
}
2016-08-29 16:52:47 +00:00
FILE_TRACK_LINK File_Track_Result
2017-03-19 23:05:05 +00:00
expand_track_system_listeners ( File_Track_System * system , Partition * scratch , void * mem , umem size ) {
2016-08-22 19:31:19 +00:00
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 ;
2017-03-19 23:05:05 +00:00
umem node_size = sizeof ( Win32_Directory_Listener_Node ) ;
u32 count = ( u32 ) ( size / node_size ) ;
for ( u32 i = 0 ; i < count ; + + i , + + listener ) {
2016-08-22 19:31:19 +00:00
insert_node ( & vars - > free_sentinel , & listener - > node ) ;
}
}
else {
result = FileTrack_MemoryTooSmall ;
}
LeaveCriticalSection ( & vars - > table_lock ) ;
return ( result ) ;
}
2016-08-29 16:52:47 +00:00
FILE_TRACK_LINK File_Track_Result
2017-03-23 19:14:39 +00:00
get_change_event ( File_Track_System * system , Partition * scratch , u8 * buffer , i32 max , umem * 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
2017-03-18 21:07:25 +00:00
local_persist i32 has_buffered_event = 0 ;
local_persist DWORD offset = 0 ;
2017-03-18 22:19:51 +00:00
local_persist Win32_Directory_Listener listener = { 0 } ;
2016-09-13 19:56:14 +00:00
2017-03-19 23:05:05 +00:00
Temp_Memory temp = begin_temp_memory ( scratch ) ;
2016-08-22 19:31:19 +00:00
EnterCriticalSection ( & vars - > table_lock ) ;
2017-03-18 21:07:25 +00:00
OVERLAPPED * overlapped = 0 ;
DWORD length = 0 ;
ULONG_PTR key = 0 ;
2017-03-19 23:05:05 +00:00
b32 has_result = false ;
2017-03-18 21:07:25 +00:00
if ( has_buffered_event ) {
has_buffered_event = 0 ;
2017-03-19 23:05:05 +00:00
has_result = true ;
2017-03-18 21:07:25 +00:00
}
else {
if ( GetQueuedCompletionStatus ( vars - > iocp , & length , & key , & overlapped , 0 ) ) {
Win32_Directory_Listener * listener_ptr = ( Win32_Directory_Listener * ) overlapped ;
2017-03-19 05:10:45 +00:00
// 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.
2017-03-18 21:07:25 +00:00
listener = * listener_ptr ;
listener_ptr - > overlapped = null_overlapped ;
ReadDirectoryChangesW ( listener_ptr - > dir , listener_ptr - > result , sizeof ( listener_ptr - > result ) , 1 , FLAGS , 0 , & listener_ptr - > overlapped , 0 ) ;
offset = 0 ;
2017-03-19 23:05:05 +00:00
has_result = true ;
2016-09-13 19:56:14 +00:00
}
2017-03-18 21:07:25 +00:00
}
if ( has_result ) {
FILE_NOTIFY_INFORMATION * info = ( FILE_NOTIFY_INFORMATION * ) ( listener . result + offset ) ;
i32 len = info - > FileNameLength / 2 ;
i32 dir_len = GetFinalPathNameByHandle ( listener . dir , 0 , 0 , FILE_NAME_NORMALIZED ) ;
2016-09-13 19:56:14 +00:00
2017-03-18 21:07:25 +00:00
i32 req_size = dir_len + 1 + len ;
if ( req_size < max ) {
2017-03-19 23:05:05 +00:00
u16 * buffer_16 = push_array ( scratch , u16 , req_size + 1 ) ;
2017-03-23 19:14:39 +00:00
i32 pos = GetFinalPathNameByHandle ( listener . dir , ( LPWSTR ) buffer_16 , max , FILE_NAME_NORMALIZED ) ;
2016-08-26 01:42:46 +00:00
2017-03-19 23:05:05 +00:00
b32 convert_error = false ;
umem buffer_size = utf16_to_utf8_minimal_checking ( buffer , max - 1 , buffer_16 , pos , & convert_error ) ;
2016-08-26 01:42:46 +00:00
2017-03-19 23:05:05 +00:00
if ( buffer_size > max - 1 ) {
result = FileTrack_MemoryTooSmall ;
}
else if ( ! convert_error ) {
buffer [ buffer_size + + ] = ' \\ ' ;
if ( buffer [ 0 ] = = ' \\ ' ) {
for ( i32 i = 0 ; i + 4 < buffer_size ; + + i ) {
buffer [ i ] = buffer [ i + 4 ] ;
}
buffer_size - = 4 ;
2016-08-26 01:42:46 +00:00
}
2017-03-19 23:05:05 +00:00
* size = buffer_size ;
result = FileTrack_Good ;
}
else {
result = FileTrack_FileSystemError ;
2016-09-13 19:56:14 +00:00
}
2017-03-18 21:07:25 +00:00
}
else {
2017-03-19 23:05:05 +00:00
// 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.
2017-03-18 21:07:25 +00:00
result = FileTrack_MemoryTooSmall ;
}
if ( info - > NextEntryOffset ! = 0 ) {
2017-03-19 23:05:05 +00:00
// 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.
2017-03-18 21:07:25 +00:00
offset + = info - > NextEntryOffset ;
has_buffered_event = 1 ;
2016-08-24 23:01:33 +00:00
}
}
2016-08-26 01:42:46 +00:00
LeaveCriticalSection ( & vars - > table_lock ) ;
2017-03-19 23:05:05 +00:00
end_temp_memory ( temp ) ;
2016-08-24 23:01:33 +00:00
return ( result ) ;
}
2016-08-29 16:52:47 +00:00
FILE_TRACK_LINK File_Track_Result
2017-03-19 05:10:45 +00:00
shut_down_track_system ( File_Track_System * system , Partition * scratch ) {
2016-08-22 19:31:19 +00:00
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 ) ;
2017-03-18 21:07:25 +00:00
u32 max = tables - > max ;
2016-08-27 17:48:10 +00:00
2017-03-18 21:07:25 +00:00
for ( u32 index = 0 ; index < max ; + + index ) {
2016-08-27 17:48:10 +00:00
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