diff options
Diffstat (limited to 'source/fswatcher')
| -rw-r--r-- | source/fswatcher/fswatcher.cpp | 9 | ||||
| -rw-r--r-- | source/fswatcher/fswatcher_linux.cpp | 369 | ||||
| -rw-r--r-- | source/fswatcher/fswatcher_osx.cpp | 43 | ||||
| -rw-r--r-- | source/fswatcher/fswatcher_windows.cpp | 220 |
4 files changed, 641 insertions, 0 deletions
diff --git a/source/fswatcher/fswatcher.cpp b/source/fswatcher/fswatcher.cpp new file mode 100644 index 0000000..922cc8d --- /dev/null +++ b/source/fswatcher/fswatcher.cpp @@ -0,0 +1,9 @@ +#if defined( __linux__ ) +# include "fswatcher_linux.cpp" +#elif defined( _WIN32 ) +# include "fswatcher_windows.cpp" +#elif defined( __OSX__ ) +# include "fswatcher_osx.cpp" +#else +# error "Unsupported platform" +#endif diff --git a/source/fswatcher/fswatcher_linux.cpp b/source/fswatcher/fswatcher_linux.cpp new file mode 100644 index 0000000..c2b183c --- /dev/null +++ b/source/fswatcher/fswatcher_linux.cpp @@ -0,0 +1,369 @@ +/* + A small drop-in library for watching the filesystem for changes. + + version 0.1, february, 2015 + + Copyright (C) 2015- Fredrik Kihlander + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Fredrik Kihlander +*/ + +#include <fswatcher/fswatcher.h> + +#include <sys/stat.h> +#include <sys/inotify.h> +#include <stdlib.h> // malloc +#include <unistd.h> // read +#include <stdio.h> // printf +#include <dirent.h> +#include <string.h> + +// write something about how we suppose that the kernels will keep on working as they do now: +// In the current kernel inotify implementation move events are always emitted as contiguous pairs with IN_MOVED_FROM immediately followed by IN_MOVED_TO + +struct fswatcher_item +{ + int wd; + const char* path; +}; + +struct fswatcher +{ + fswatcher_allocator* allocator; + int notifierfd; + + uint32_t watch_flags; + + size_t watches_cnt; + size_t watches_cap; + fswatcher_item* watches; +}; + +static void* fswatcher_default_realloc( fswatcher_allocator*, void* ptr, size_t, size_t new_size ) +{ + return realloc( ptr, new_size ); +} + +static void fswatcher_default_free( fswatcher_allocator*, void* ptr ) +{ + free( ptr ); +} + +static fswatcher_allocator g_fswatcher_default_alloc = { fswatcher_default_realloc, fswatcher_default_free }; + +static void* fswatcher_realloc( fswatcher_allocator* allocator, void* ptr, size_t old_size, size_t new_size ) +{ + return allocator->realloc( allocator, ptr, old_size, new_size ); +} + +static void fswatcher_free( fswatcher_allocator* allocator, void* ptr ) +{ + if( allocator->free ) + allocator->free( allocator, ptr ); +} + +static char* fswatcher_strdup( fswatcher_allocator* allocator, const char* str ) +{ + size_t len = strlen( str ) + 1; + char* res = (char*)fswatcher_realloc( allocator, 0x0, 0, len ); + strcpy( res, str ); + return res; +} + +static const char* fswatcher_find_wd_path( fswatcher_t w, int wd ) +{ + for( size_t i = 0; i < w->watches_cnt; ++ i ) + if( wd == w->watches[i].wd ) + return w->watches[i].path; + return 0x0; +} + +static void fswatcher_add( fswatcher_t w, char* path ) +{ + int wd = inotify_add_watch( w->notifierfd, path, w->watch_flags ); + if( wd < 0 ) + { + fprintf(stderr, "failed to add a watch for %s ", path); + perror(""); + return; + } + if( w->watches_cnt >= w->watches_cap ) + { + w->watches = (fswatcher_item*)fswatcher_realloc( w->allocator, w->watches, sizeof(fswatcher_item) * w->watches_cap, sizeof(fswatcher_item) * w->watches_cap * 2 ); + w->watches_cap *= 2; + } + + w->watches[ w->watches_cnt ].wd = wd; + w->watches[ w->watches_cnt ].path = fswatcher_strdup( w->allocator, path ); + ++w->watches_cnt; +} + +static void fswatcher_remove( fswatcher_t w, int wd ) +{ + for( size_t i = 0; i < w->watches_cnt; ++ i ) + { + if( wd != w->watches[i].wd ) + continue; + + fswatcher_free( w->allocator, (void*)w->watches[i].path ); + w->watches[i].wd = 0; + w->watches[i].path = 0x0; + + size_t swap_index = w->watches_cnt - 1; + if( i != swap_index ) + memcpy( w->watches + i, w->watches + swap_index, sizeof( fswatcher_item ) ); + --w->watches_cnt; + return; + } +} + +static void fswatcher_recursive_add( fswatcher_t w, char* path_buffer, size_t path_len, size_t path_max ) +{ + fswatcher_add( w, path_buffer ); + DIR* dirp = opendir( path_buffer ); + dirent* ent; + while( ( ent = readdir( dirp ) ) != 0x0 ) + { + if( ( ent->d_type != DT_DIR && ent->d_type != DT_LNK && ent->d_type != DT_UNKNOWN ) ) + continue; + + if( strcmp( ent->d_name, "." ) == 0 || strcmp( ent->d_name, ".." ) == 0 ) + continue; + + size_t d_name_size = strlen( ent->d_name ); + if( path_len + d_name_size + 2 >= path_max ) + return; // TODO: handle! + + strcpy( path_buffer + path_len, ent->d_name ); + path_buffer[ path_len + d_name_size ] = '/'; + path_buffer[ path_len + d_name_size + 1 ] = '\0'; + + if( ent->d_type == DT_LNK || ent->d_type == DT_UNKNOWN ) + { + struct stat statbuf; + if( stat( path_buffer, &statbuf ) == -1 ) + continue; + + if( !S_ISDIR( statbuf.st_mode ) ) + continue; + } + + fswatcher_recursive_add( w, path_buffer, path_len + d_name_size + 1, path_max ); + } + path_buffer[path_len] = '\0'; + + closedir( dirp ); +} + +fswatcher_t fswatcher_create( fswatcher_create_flags flags, fswatcher_event_type types, const char* watch_dir, fswatcher_allocator* allocator ) +{ + if( allocator == 0x0 ) + allocator = &g_fswatcher_default_alloc; + + fswatcher* w = (fswatcher*)fswatcher_realloc( allocator, 0x0, 0, sizeof( fswatcher ) ); + memset( w, 0x0, sizeof( fswatcher ) ); + w->allocator = allocator; + w->watch_flags = 0; + + if( types & FSWATCHER_EVENT_CREATE ) w->watch_flags |= IN_CREATE; + if( types & FSWATCHER_EVENT_REMOVE ) w->watch_flags |= IN_DELETE; + if( types & FSWATCHER_EVENT_MOVE ) w->watch_flags |= IN_MOVE; + if( types & FSWATCHER_EVENT_MODIFY ) w->watch_flags |= IN_MODIFY; + w->watch_flags |= IN_DELETE_SELF; + + int inotify_flags = 0; + if( ( flags & FSWATCHER_CREATE_BLOCKING ) == 0 ) + inotify_flags |= IN_NONBLOCK; + + w->notifierfd = inotify_init1( inotify_flags ); + if( w->notifierfd < -1 ) + { + fswatcher_free( allocator, w ); + return 0x0; + } + + w->watches_cap = 16; // 256; + w->watches = (fswatcher_item*)fswatcher_realloc( w->allocator, 0x0, 0, sizeof(fswatcher_item) * w->watches_cap ); + + char path_buffer[4096]; + strncpy( path_buffer, watch_dir, sizeof( path_buffer ) ); + // TODO: make sure path fit ... + + size_t path_len = strlen( path_buffer ); + if( path_buffer[path_len-1] != '/' ) + { + path_buffer[path_len] = '/'; + path_buffer[path_len+1] = '\0'; + ++path_len; + } + + fswatcher_recursive_add( w, path_buffer, path_len, sizeof( path_buffer ) ); + return w; +} + +void fswatcher_destroy( fswatcher_t watcher ) +{ + close( watcher->notifierfd ); + for( size_t i = 0; i < watcher->watches_cnt; ++i ) + fswatcher_free( watcher->allocator, (void*)watcher->watches[i].path ); + fswatcher_free( watcher->allocator, watcher->watches ); + fswatcher_free( watcher->allocator, watcher ); +} + +static char* fswatcher_build_full_path( fswatcher_t watcher, fswatcher_allocator* allocator, int wd, const char* name, uint32_t name_len ) +{ + const char* dirpath = fswatcher_find_wd_path( watcher, wd ); + size_t dirlen = strlen( dirpath ); + size_t length = dirlen + 1 + name_len; + char* res = (char*)fswatcher_realloc( allocator, 0x0, 0, length ); + if( res ) + { + memcpy( res, dirpath, dirlen ); + memcpy( res + dirlen, name, name_len ); + res[length-1] = 0; + } + return res; +} + +#define FS_MAKE_CALLBACK( type, src, dst ) handler->callback( handler, (type), (src), (dst) ); + +static void fswatcher_make_callback_with_src_path( fswatcher_t watcher, fswatcher_event_handler* handler, fswatcher_event_type type, inotify_event* ev ) +{ + char* src = fswatcher_build_full_path( watcher, watcher->allocator, ev->wd, ev->name, ev->len ); + FS_MAKE_CALLBACK( type, src, 0x0 ); + fswatcher_free( watcher->allocator, src ); +} + +static void fswatcher_make_callback_with_dst_path( fswatcher_t watcher, fswatcher_event_handler* handler, fswatcher_event_type type, inotify_event* ev ) +{ + char* dst = fswatcher_build_full_path( watcher, watcher->allocator, ev->wd, ev->name, ev->len ); + FS_MAKE_CALLBACK( type, 0x0, dst ); + fswatcher_free( watcher->allocator, dst ); +} + +void fswatcher_poll( fswatcher_t watcher, fswatcher_event_handler* handler, fswatcher_allocator* allocator ) +{ + if( allocator == 0x0 ) + allocator = &g_fswatcher_default_alloc; + + char* move_src = 0x0; + uint32_t move_cookie = 0; + + while( true ) + { + char read_buffer[4096]; + ssize_t read_bytes = read( watcher->notifierfd, read_buffer, sizeof( read_buffer ) ); + if( read_bytes <= 0 ) + break; + + for( char* bufp = read_buffer; bufp < read_buffer + read_bytes; ) + { + inotify_event* ev = (inotify_event*)bufp; + bool is_dir = ( ev->mask & IN_ISDIR ); + bool is_create = ( ev->mask & IN_CREATE ); + bool is_remove = ( ev->mask & IN_DELETE ); + bool is_modify = ( ev->mask & IN_MODIFY ); + bool is_move_from = ( ev->mask & IN_MOVED_FROM ); + bool is_move_to = ( ev->mask & IN_MOVED_TO ); + bool is_del_self = ( ev->mask & IN_DELETE_SELF ); + + if( is_dir ) + { + if( is_create ) + { + char* src = fswatcher_build_full_path( watcher, allocator, ev->wd, ev->name, ev->len ); + fswatcher_add( watcher, src ); + FS_MAKE_CALLBACK( FSWATCHER_EVENT_CREATE, src, 0x0 ); + fswatcher_free( allocator, src ); + } + else if( is_remove ) + fswatcher_make_callback_with_src_path( watcher, handler, FSWATCHER_EVENT_REMOVE, ev ); + else if( is_del_self ) + fswatcher_remove( watcher, ev->wd ); + } + else if( ev->mask & IN_Q_OVERFLOW ) + { + FS_MAKE_CALLBACK( FSWATCHER_EVENT_BUFFER_OVERFLOW, 0x0, 0x0 ); + } + else + { + if( is_create ) + fswatcher_make_callback_with_src_path( watcher, handler, FSWATCHER_EVENT_CREATE, ev ); + else if( is_remove ) + fswatcher_make_callback_with_src_path( watcher, handler, FSWATCHER_EVENT_REMOVE, ev ); + else if( is_modify ) + fswatcher_make_callback_with_src_path( watcher, handler, FSWATCHER_EVENT_MODIFY, ev ); + else if( is_move_from ) + { + if( move_src != 0x0 ) + { + // ... this is a new pair of a move, so the last one was move "outside" the current watch ... + FS_MAKE_CALLBACK( FSWATCHER_EVENT_MOVE, move_src, 0x0 ); + fswatcher_free( allocator, move_src ); + } + + // ... this is the first potential pair of a move ... + move_src = fswatcher_build_full_path( watcher, allocator, ev->wd, ev->name, ev->len ); + move_cookie = ev->cookie; + } + else if( is_move_to ) + { + if( move_src && move_cookie == ev->cookie ) + { + // ... this is the dst for a move ... + char* dst = fswatcher_build_full_path( watcher, allocator, ev->wd, ev->name, ev->len ); + FS_MAKE_CALLBACK( FSWATCHER_EVENT_MOVE, move_src, dst ); + fswatcher_free( watcher->allocator, dst ); + fswatcher_free( watcher->allocator, move_src ); + move_src = 0x0; + move_cookie = 0; + } + else if( move_src != 0x0 ) + { + // ... this is a "move to outside of watch" ... + FS_MAKE_CALLBACK( FSWATCHER_EVENT_MOVE, move_src, 0x0 ); + fswatcher_free( watcher->allocator, move_src ); + move_src = 0x0; + move_cookie = 0; + + // ...followed by a "move from outside to watch ... + fswatcher_make_callback_with_dst_path( watcher, handler, FSWATCHER_EVENT_MOVE, ev ); + } + else + { + // ... this is a "move from outside to watch" ... + fswatcher_make_callback_with_dst_path( watcher, handler, FSWATCHER_EVENT_MOVE, ev ); + } + } + } + + bufp += sizeof(inotify_event) + ev->len; + } + } + + if( move_src ) + { + // ... we have a "move to outside of watch" that was never closed ... + FS_MAKE_CALLBACK( FSWATCHER_EVENT_MOVE, move_src, 0x0 ); + fswatcher_free( allocator, move_src ); + } +} + +#undef FS_MAKE_CALLBACK + diff --git a/source/fswatcher/fswatcher_osx.cpp b/source/fswatcher/fswatcher_osx.cpp new file mode 100644 index 0000000..433bdd2 --- /dev/null +++ b/source/fswatcher/fswatcher_osx.cpp @@ -0,0 +1,43 @@ +/* + A small drop-in library for watching the filesystem for changes. + + version 0.1, february, 2015 + + Copyright (C) 2015- Fredrik Kihlander + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Fredrik Kihlander +*/ + +#include <fswatcher/fswatcher.h> + +fswatcher_t fswatcher_create( fswatcher_create_flags flags, fswatcher_event_type types, const char* watch_dir, fswatcher_allocator* allocator ) +{ + (void)flags; (void)types; (void)watch_dir; (void)allocator; + return 0x0; +} + +void fswatcher_destroy( fswatcher_t watcher ) +{ + (void)watcher; +} + +void fswatcher_poll( fswatcher_t watcher, fswatcher_event_handler* handler, fswatcher_allocator* allocator ) +{ + (void)watcher; (void)handler; (void)allocator; +} diff --git a/source/fswatcher/fswatcher_windows.cpp b/source/fswatcher/fswatcher_windows.cpp new file mode 100644 index 0000000..9c660f4 --- /dev/null +++ b/source/fswatcher/fswatcher_windows.cpp @@ -0,0 +1,220 @@ +/* + A small drop-in library for watching the filesystem for changes. + + version 0.1, february, 2015 + + Copyright (C) 2015- Fredrik Kihlander + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Fredrik Kihlander +*/ +#include <fswatcher/fswatcher.h> + +#include <stdlib.h> // malloc +#include <windows.h> + +#include <stdio.h> // remove + +struct fswatcher +{ + fswatcher_allocator* allocator; + bool recursive; + bool blocking; + + const char* watch_dir; + size_t watch_dir_len; + + HANDLE directory; + OVERLAPPED overlapped; + + DWORD read_buffer[2048]; // hmmmm. +}; + +static void* fswatcher_default_realloc( fswatcher_allocator*, void* ptr, size_t, size_t new_size ) +{ + return realloc( ptr, new_size ); +} + +static void fswatcher_default_free( fswatcher_allocator*, void* ptr ) +{ + free( ptr ); +} + +static fswatcher_allocator g_fswatcher_default_alloc = { fswatcher_default_realloc, fswatcher_default_free }; + +static void* fswatcher_realloc( fswatcher_allocator* allocator, void* ptr, size_t old_size, size_t new_size ) +{ + return allocator->realloc( allocator, ptr, old_size, new_size ); +} + +static void fswatcher_free( fswatcher_allocator* allocator, void* ptr ) +{ + if( allocator->free ) + allocator->free( allocator, ptr ); +} + +#define FS_MAKE_CALLBACK( type, src, dst ) handler->callback( handler, (type), (src), (dst) ); + +static char* fswatcher_build_full_path( fswatcher_t watcher, fswatcher_allocator* allocator, FILE_NOTIFY_INFORMATION* ev ) +{ + size_t path_len = (size_t)( ev->FileNameLength / 2 ); + + int utf8_len = WideCharToMultiByte( CP_UTF8, // CodePage + 0, // dwFlags + ev->FileName, // lpWideCharStr + (int)path_len, // cchWideChar + nullptr, // lpMultiByteStr + 0, // cbMultiByte + nullptr, // lpDefaultChar + nullptr ); // lpUsedDefaultChar + size_t res_len = watcher->watch_dir_len + utf8_len; + + char* res = (char*)fswatcher_realloc( allocator, 0x0, 0, res_len + 1 ); + strcpy( res, watcher->watch_dir ); + + char* file_part = res + watcher->watch_dir_len; + + WideCharToMultiByte( CP_UTF8, // CodePage + 0, // dwFlags + ev->FileName, // lpWideCharStr + (int)path_len, // cchWideChar + file_part, // lpMultiByteStr + utf8_len, // cbMultiByte + nullptr, // lpDefaultChar + nullptr ); // lpUsedDefaultChar + res[res_len] = '\0'; + return res; +} + +static void fswatcher_begin_read( fswatcher_t watcher ) +{ + ::ZeroMemory( &watcher->overlapped, sizeof( watcher->overlapped ) ); + + BOOL success = ::ReadDirectoryChangesW( watcher->directory, + watcher->read_buffer, + sizeof( watcher->read_buffer ), + watcher->recursive ? TRUE : FALSE, + FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME, + 0x0, + &watcher->overlapped, + 0x0 ); + if( !success ) + printf("NOOOOOOOOOOOOOO!"); +} + +fswatcher_t fswatcher_create( fswatcher_create_flags flags, fswatcher_event_type types, const char* watch_dir, fswatcher_allocator* allocator ) +{ + if( allocator == 0x0 ) + allocator = &g_fswatcher_default_alloc; + + fswatcher_t w = (fswatcher_t)fswatcher_realloc( allocator, 0x0, 0, sizeof( fswatcher ) ); + w->allocator = allocator; + w->watch_dir_len = strlen( watch_dir ); + w->watch_dir = (char*)fswatcher_realloc( w->allocator, 0x0, 0, w->watch_dir_len + 1 ); + strcpy( (char*)w->watch_dir, watch_dir ); + w->recursive = ( flags & FSWATCHER_CREATE_RECURSIVE ) > 0; + w->blocking = ( flags & FSWATCHER_CREATE_BLOCKING ) > 0; + w->directory = ::CreateFile( watch_dir, + FILE_LIST_DIRECTORY, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, // security descriptor + OPEN_EXISTING, // how to create + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, // file attributes + NULL ); // file with attributes to copy + // handle error ... + + fswatcher_begin_read( w ); + return w; +} + +void fswatcher_destroy( fswatcher_t watcher ) +{ + ::CloseHandle( watcher->directory ); + fswatcher_free( watcher->allocator, watcher ); +} + +void fswatcher_poll( fswatcher_t watcher, fswatcher_event_handler* handler, fswatcher_allocator* allocator ) +{ + DWORD bytes; + BOOL res = ::GetOverlappedResult( watcher->directory, + &watcher->overlapped, + &bytes, + watcher->blocking ); + if( res != TRUE ) + return; + + char* move_src = 0x0; + + FILE_NOTIFY_INFORMATION* ev = (FILE_NOTIFY_INFORMATION*)watcher->read_buffer; + do + { + switch(ev->Action) + { + case FILE_ACTION_ADDED: + { + char* src = fswatcher_build_full_path( watcher, watcher->allocator, ev ); + FS_MAKE_CALLBACK( FSWATCHER_EVENT_CREATE, src, 0x0 ); + fswatcher_free( watcher->allocator, src ); + } + break; + case FILE_ACTION_REMOVED: + { + char* src = fswatcher_build_full_path( watcher, watcher->allocator, ev ); + FS_MAKE_CALLBACK( FSWATCHER_EVENT_REMOVE, src, 0x0 ); + fswatcher_free( watcher->allocator, src ); + } + break; + case FILE_ACTION_MODIFIED: + { + char* src = fswatcher_build_full_path( watcher, watcher->allocator, ev ); + FS_MAKE_CALLBACK( FSWATCHER_EVENT_MODIFY, src, 0x0 ); + fswatcher_free( watcher->allocator, src ); + } + break; + case FILE_ACTION_RENAMED_OLD_NAME: + { + move_src = fswatcher_build_full_path( watcher, watcher->allocator, ev ); + } + break; + case FILE_ACTION_RENAMED_NEW_NAME: + { + char* dst = fswatcher_build_full_path( watcher, watcher->allocator, ev ); + FS_MAKE_CALLBACK( FSWATCHER_EVENT_MOVE, move_src, dst ); + fswatcher_free( watcher->allocator, move_src ); + fswatcher_free( watcher->allocator, dst ); + move_src = 0x0; + } + break; + default: + printf("unhandled action %d\n", ev->Action); + } + + if( ev->NextEntryOffset == 0 ) + break; + ev = (FILE_NOTIFY_INFORMATION*)((char*)ev + ev->NextEntryOffset); + } + while( true ); + + if( move_src != 0x0 ) + { + // how to handle this? + fswatcher_free( watcher->allocator, move_src ); + } + + fswatcher_begin_read( watcher ); +} |
