libresman
changeset 24:ce04fa12afdd
win32 file monitoring done (at last)
author | John Tsiombikas <nuclear@member.fsf.org> |
---|---|
date | Thu, 13 Feb 2014 21:30:05 +0200 |
parents | f8e5a1491275 |
children | 1be0a35aa216 |
files | src/filewatch_win32.c src/resman_impl.h |
diffstat | 2 files changed, 275 insertions(+), 97 deletions(-) [+] |
line diff
1.1 --- a/src/filewatch_win32.c Thu Feb 13 13:17:07 2014 +0200 1.2 +++ b/src/filewatch_win32.c Thu Feb 13 21:30:05 2014 +0200 1.3 @@ -1,6 +1,7 @@ 1.4 /* file modification monitoring for windows */ 1.5 #ifdef WIN32 1.6 #include <stdio.h> 1.7 +#include <stdlib.h> 1.8 #include <assert.h> 1.9 #include <malloc.h> 1.10 #include "filewatch.h" 1.11 @@ -8,12 +9,25 @@ 1.12 #include "resman_impl.h" 1.13 #include "dynarr.h" 1.14 1.15 +#define RES_BUF_SIZE 8192 1.16 + 1.17 +struct watch_item { 1.18 + struct resource *res; 1.19 + struct watch_item *next; 1.20 +}; 1.21 + 1.22 struct watch_dir { 1.23 HANDLE handle; 1.24 + OVERLAPPED over; /* overlapped I/O structure */ 1.25 + char *buf_unaligned, *buf; 1.26 int nref; 1.27 + char *watch_path; 1.28 + 1.29 + struct watch_item *items; 1.30 }; 1.31 1.32 -static void reload_modified(struct rbnode *node, void *cls); 1.33 +static char *abs_path(const char *fname); 1.34 +static void clean_path(char *path); 1.35 1.36 int resman_init_file_monitor(struct resman *rman) 1.37 { 1.38 @@ -21,10 +35,10 @@ 1.39 return -1; 1.40 } 1.41 1.42 - /* create the handle->resource map */ 1.43 rman->nresmap = rb_create(RB_KEY_ADDR); 1.44 - /* create the watched dirs set */ 1.45 rman->watchdirs = rb_create(RB_KEY_STRING); 1.46 + rman->wdirbyev = rb_create(RB_KEY_ADDR); 1.47 + 1.48 return 0; 1.49 } 1.50 1.51 @@ -34,141 +48,306 @@ 1.52 1.53 rb_free(rman->nresmap); 1.54 rb_free(rman->watchdirs); 1.55 + rb_free(rman->wdirbyev); 1.56 } 1.57 1.58 int resman_start_watch(struct resman *rman, struct resource *res) 1.59 { 1.60 - char *path; 1.61 - HANDLE handle; 1.62 - struct watch_dir *wdir; 1.63 + char *path = 0, *last_slash; 1.64 + struct watch_dir *wdir = 0; 1.65 + struct watch_item *witem = 0; 1.66 1.67 - /* construct an absolute path for the directory containing this file */ 1.68 - path = res->name; 1.69 - if(path[0] != '/' && path[1] != ':') { /* not an absolute path */ 1.70 - char *src, *dest, *lastslash; 1.71 - int cwdsz = GetCurrentDirectory(0, 0); 1.72 - int pathsz = strlen(path) + cwdsz + 1; 1.73 + /* construct an absolute path for the directory containing this file (must free it afterwards) */ 1.74 + if(!(path = abs_path(res->name))) { 1.75 + return -1; 1.76 + } 1.77 + clean_path(path); 1.78 1.79 - path = malloc(pathsz + 1); 1.80 - GetCurrentDirectory(pathsz, path); 1.81 - 1.82 - /* now copy the rest of the path, until the last slash, while converting path separators */ 1.83 - src = res->name; 1.84 - dest = path + strlen(path); 1.85 - 1.86 - lastslash = dest; 1.87 - *dest++ = '\\'; 1.88 - while(*src) { 1.89 - if(src[-1] == '\\') { 1.90 - /* skip any /./ parts of the path */ 1.91 - if(src[0] == '.' && (src[1] == '/' || src[1] == '\\')) { 1.92 - src += 2; 1.93 - continue; 1.94 - } 1.95 - /* normalize any /../ parts of the path */ 1.96 - if(src[0] == '.' && src[1] == '.' && (src[2] == '/' || src[2] == '\\')) { 1.97 - src += 3; 1.98 - dest = strrchr(src - 2, '\\'); 1.99 - assert(dest); 1.100 - dest++; 1.101 - continue; 1.102 - } 1.103 - } 1.104 - 1.105 - if(*src == '/' || *src == '\\') { 1.106 - lastslash = dest; 1.107 - *dest++ = '\\'; 1.108 - src++; 1.109 - } else { 1.110 - *dest++ = *src++; 1.111 - } 1.112 - } 1.113 - 1.114 - *lastslash = 0; 1.115 + /* we need the directory path, so let's find the last slash and cut it there */ 1.116 + if(!(last_slash = strrchr(path, '\\'))) { 1.117 + goto err; 1.118 } 1.119 + *last_slash = 0; 1.120 1.121 /* check to see if we already have a watch handle for this directory */ 1.122 if((wdir = rb_find(rman->watchdirs, path))) { 1.123 - handle = wdir->handle; 1.124 - wdir->nref++; 1.125 + wdir->nref++; /* ... if so, increase the refcount */ 1.126 } else { 1.127 + /* otherwise start a new watch */ 1.128 if(!(wdir = malloc(sizeof *wdir))) { 1.129 perror("failed to allocate watchdir"); 1.130 - free(path); 1.131 - return -1; 1.132 + goto err; 1.133 + } 1.134 + memset(wdir, 0, sizeof *wdir); 1.135 + wdir->nref = 1; 1.136 + 1.137 + /* open the directory we need to watch */ 1.138 + wdir->handle = CreateFile(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 1.139 + 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, 0); 1.140 + if(wdir->handle == INVALID_HANDLE_VALUE) { 1.141 + fprintf(stderr, "failed to watch %s: failed to open directory: %s\n", res->name, path); 1.142 + goto err; 1.143 } 1.144 1.145 - /* otherwise start a new notification */ 1.146 - if((handle = FindFirstChangeNotification(path, FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE)) == INVALID_HANDLE_VALUE) { 1.147 - unsigned int err = GetLastError(); 1.148 - fprintf(stderr, "failed to watch %s for modification (error: %u)\n", path, err); 1.149 - free(wdir); 1.150 - free(path); 1.151 - return -1; 1.152 + if(!(wdir->buf_unaligned = malloc(RES_BUF_SIZE + 3))) { 1.153 + fprintf(stderr, "failed to allocate watch result buffer (%d bytes)\n", RES_BUF_SIZE); 1.154 + goto err; 1.155 } 1.156 - wdir->handle = handle; 1.157 - wdir->nref = 1; 1.158 + wdir->buf = (char*)(((intptr_t)wdir->buf_unaligned + 3) & ~(intptr_t)0x3); 1.159 + 1.160 + memset(&wdir->over, 0, sizeof wdir->over); 1.161 + wdir->over.hEvent = CreateEvent(0, TRUE, FALSE, 0); 1.162 + 1.163 + if(!ReadDirectoryChangesW(wdir->handle, wdir->buf, RES_BUF_SIZE, 0, FILE_NOTIFY_CHANGE_LAST_WRITE, 0, &wdir->over, 0)) { 1.164 + fprintf(stderr, "failed to start async dirchange monitoring\n"); 1.165 + goto err; 1.166 + } 1.167 + 1.168 + wdir->watch_path = path; 1.169 1.170 rb_insert(rman->watchdirs, path, wdir); 1.171 - dynarr_push(rman->watch_handles, &handle); 1.172 + rb_insert(rman->wdirbyev, wdir->over.hEvent, wdir); 1.173 + dynarr_push(rman->watch_handles, &wdir->over.hEvent); 1.174 } 1.175 1.176 - rb_insert(rman->nresmap, handle, res); 1.177 - res->nhandle = handle; 1.178 + /* add a new watch item to this watch dir */ 1.179 + if(!(witem = malloc(sizeof *witem))) { 1.180 + perror("failed to allocate watch item"); 1.181 + goto err; 1.182 + } 1.183 + witem->next = wdir->items; 1.184 + wdir->items = witem; 1.185 + witem->res = res; 1.186 + 1.187 res->watch_path = path; 1.188 return 0; 1.189 +err: 1.190 + free(path); 1.191 + if(wdir) { 1.192 + if(wdir->handle && wdir->handle != INVALID_HANDLE_VALUE) { 1.193 + CloseHandle(wdir->handle); 1.194 + } 1.195 + free(wdir->buf_unaligned); 1.196 + if(wdir->over.hEvent && wdir->over.hEvent != INVALID_HANDLE_VALUE) { 1.197 + CloseHandle(wdir->over.hEvent); 1.198 + } 1.199 + } 1.200 + return -1; 1.201 } 1.202 1.203 void resman_stop_watch(struct resman *rman, struct resource *res) 1.204 { 1.205 int i, sz; 1.206 + struct watch_dir *wdir; 1.207 1.208 - if(res->nhandle) { 1.209 - struct watch_dir *wdir = rb_find(rman->watchdirs, res->watch_path); 1.210 - if(wdir) { 1.211 - if(--wdir->nref <= 0) { 1.212 - FindCloseChangeNotification(res->nhandle); 1.213 + if(!res->watch_path) { 1.214 + return; 1.215 + } 1.216 1.217 - /* find the handle in the watch_handles array and remove it */ 1.218 - sz = dynarr_size(rman->watch_handles); 1.219 - for(i=0; i<sz; i++) { 1.220 - if(rman->watch_handles[i] == res->nhandle) { 1.221 - /* swap the end for it and pop */ 1.222 - rman->watch_handles[i] = rman->watch_handles[sz - 1]; 1.223 - rman->watch_handles[sz - 1] = 0; 1.224 - dynarr_pop(rman->watch_handles); 1.225 - break; 1.226 - } 1.227 - } 1.228 + if(!(wdir = rb_find(rman->watchdirs, res->watch_path))) { 1.229 + return; 1.230 + } 1.231 + 1.232 + /* if there is no other reference to this watch dir, destroy it */ 1.233 + if(--wdir->nref <= 0) { 1.234 + /* find the handle in the watch_handles array and remove it */ 1.235 + sz = dynarr_size(rman->watch_handles); 1.236 + for(i=0; i<sz; i++) { 1.237 + if(rman->watch_handles[i] == wdir->handle) { 1.238 + /* swap the end for it and pop */ 1.239 + rman->watch_handles[i] = rman->watch_handles[sz - 1]; 1.240 + rman->watch_handles[sz - 1] = 0; 1.241 + dynarr_pop(rman->watch_handles); 1.242 + break; 1.243 } 1.244 - free(wdir); 1.245 } 1.246 1.247 - rb_delete(rman->nresmap, res->nhandle); 1.248 - res->nhandle = 0; 1.249 + rb_delete(rman->wdirbyev, wdir->over.hEvent); 1.250 + rb_delete(rman->watchdirs, wdir->watch_path); 1.251 + 1.252 + CancelIo(wdir->handle); 1.253 + CloseHandle(wdir->handle); 1.254 + CloseHandle(wdir->over.hEvent); 1.255 + free(wdir->watch_path); 1.256 + free(wdir); 1.257 + 1.258 + res->watch_path = 0; 1.259 + } else { 1.260 + /* just remove this watch item */ 1.261 + if(wdir->items && wdir->items->res == res) { 1.262 + struct watch_item *tmp = wdir->items; 1.263 + wdir->items = wdir->items->next; 1.264 + free(tmp); 1.265 + } else { 1.266 + struct watch_item *wprev = wdir->items; 1.267 + struct watch_item *witem = wprev->next; 1.268 + 1.269 + while(witem) { 1.270 + if(witem->res == res) { 1.271 + struct watch_item *tmp = witem; 1.272 + wprev->next = witem->next; 1.273 + break; 1.274 + } 1.275 + witem = witem->next; 1.276 + } 1.277 + } 1.278 + } 1.279 +} 1.280 + 1.281 +static void handle_event(struct resman *rman, HANDLE hev, struct watch_dir *wdir) 1.282 +{ 1.283 + struct resource *res = 0; 1.284 + struct watch_item *witem; 1.285 + FILE_NOTIFY_INFORMATION *info; 1.286 + DWORD res_size; 1.287 + 1.288 + if(!GetOverlappedResult(hev, &wdir->over, &res_size, FALSE)) { 1.289 + return; 1.290 + } 1.291 + 1.292 + info = (FILE_NOTIFY_INFORMATION*)wdir->buf; 1.293 + 1.294 + for(;;) { 1.295 + if(info->Action == FILE_ACTION_MODIFIED) { 1.296 + /*printf("file \"%s\" modified\n", res->name); 1.297 + tpool_add_work(rman->tpool, res);*/ 1.298 + 1.299 + char *name; 1.300 + int len = info->FileNameLength / 2; 1.301 + wchar_t *wname = alloca((len + 1) * sizeof *wname); 1.302 + memcpy(wname, info->FileName, info->FileNameLength); 1.303 + wname[len] = 0; 1.304 + 1.305 + len = wcstombs(0, wname, 0); 1.306 + name = alloca(len + 1); 1.307 + wcstombs(name, wname, len + 1); 1.308 + 1.309 + printf("MODIFIED: \"%s\"\n", name); 1.310 + 1.311 + witem = wdir->items; 1.312 + while(witem) { 1.313 + if(strstr(witem->res->name, name)) { 1.314 + res = witem->res; 1.315 + break; 1.316 + } 1.317 + witem = witem->next; 1.318 + } 1.319 + if(!res) { 1.320 + fprintf(stderr, "failed to find the modified watch item (%s)\n", name); 1.321 + } else { 1.322 + /* found the resource, schedule a reload */ 1.323 + printf("file \"%s\" modified\n", res->name); 1.324 + tpool_add_work(rman->tpool, res); 1.325 + } 1.326 + } 1.327 + 1.328 + if(info->NextEntryOffset) { 1.329 + info = (FILE_NOTIFY_INFORMATION*)((char*)info + info->NextEntryOffset); 1.330 + } else { 1.331 + break; 1.332 + } 1.333 } 1.334 } 1.335 1.336 void resman_check_watch(struct resman *rman) 1.337 { 1.338 + struct watch_dir *wdir; 1.339 + unsigned int idx; 1.340 + 1.341 unsigned int num_handles = dynarr_size(rman->watch_handles); 1.342 - for(;;) { 1.343 - struct resource *res; 1.344 - unsigned int idx = WaitForMultipleObjects(num_handles, rman->watch_handles, FALSE, 0); 1.345 - if(idx < WAIT_OBJECT_0 || idx >= WAIT_OBJECT_0 + num_handles) { 1.346 - break; 1.347 + if(!num_handles) { 1.348 + return; 1.349 + } 1.350 + 1.351 + idx = WaitForMultipleObjectsEx(num_handles, rman->watch_handles, FALSE, 0, TRUE); 1.352 + if(idx == WAIT_FAILED) { 1.353 + unsigned int err = GetLastError(); 1.354 + fprintf(stderr, "failed to check for file modification: %u\n", err); 1.355 + return; 1.356 + } 1.357 + if(idx >= WAIT_OBJECT_0 && idx < WAIT_OBJECT_0 + num_handles) { 1.358 + if(!(wdir = rb_find(rman->wdirbyev, rman->watch_handles[idx]))) { 1.359 + fprintf(stderr, "got change handle, but failed to find corresponding watch_dir!\n"); 1.360 + return; 1.361 } 1.362 1.363 - if(!(res = rb_find(rman->nresmap, rman->watch_handles[idx]))) { 1.364 - fprintf(stderr, "got modification event from unknown resource!\n"); 1.365 - continue; 1.366 - } 1.367 + handle_event(rman, rman->watch_handles[idx], wdir); 1.368 1.369 - printf("file \"%s\" modified\n", res->name); 1.370 - tpool_add_work(rman->tpool, res); 1.371 + /* restart the watch call */ 1.372 + ReadDirectoryChangesW(wdir->handle, wdir->buf, RES_BUF_SIZE, FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, 0, &wdir->over, 0); 1.373 } 1.374 } 1.375 1.376 + 1.377 +/* returns a new path buffer which is the equivalent absolute path to "fname" 1.378 +* the user takes ownership and must free it when done. 1.379 +*/ 1.380 +static char *abs_path(const char *fname) 1.381 +{ 1.382 + if(fname[0] != '/' && fname[1] != ':') { /* not an absolute path */ 1.383 + char *path; 1.384 + int cwdsz = GetCurrentDirectory(0, 0); 1.385 + int pathsz = strlen(fname) + cwdsz + 1; 1.386 + 1.387 + if(!(path = malloc(pathsz + 1))) { 1.388 + return 0; 1.389 + } 1.390 + GetCurrentDirectory(pathsz, path); 1.391 + 1.392 + /* now copy the rest of the path */ 1.393 + strcat(path, "\\"); 1.394 + strcat(path, fname); 1.395 + return path; 1.396 + } 1.397 + 1.398 + return strdup(fname); 1.399 +} 1.400 + 1.401 +static void clean_path(char *path) 1.402 +{ 1.403 + char *ptr, *dest; 1.404 + 1.405 + /* first pass, change '/' -> '\\' */ 1.406 + ptr = path; 1.407 + while(*ptr) { 1.408 + if(*ptr == '/') *ptr = '\\'; 1.409 + ptr++; 1.410 + } 1.411 + 1.412 + /* now go through the path and remove any \.\ or \..\ parts */ 1.413 + ptr = dest = path; 1.414 + while(*ptr) { 1.415 + if(ptr > path && ptr[-1] == '\\') { 1.416 + /* we're just after a slash, so any .\ or ..\ here should be purged */ 1.417 + if(ptr[0] == '.' && ptr[1] == '\\') { 1.418 + ptr += 2; 1.419 + continue; 1.420 + } 1.421 + if(ptr[0] == '.' && ptr[1] == '.' && ptr[2] == '\\') { 1.422 + /* search backwards for the previous slash */ 1.423 + dest -= 2; 1.424 + while(dest > path && *dest != '\\') { 1.425 + dest--; 1.426 + } 1.427 + if(*dest == '\\') { 1.428 + /* found it, continue after this one */ 1.429 + dest++; 1.430 + } /* .. otherwise, reached the beginning, go from there */ 1.431 + 1.432 + ptr += 3; 1.433 + continue; 1.434 + } 1.435 + } 1.436 + if(dest != ptr) { 1.437 + *dest++ = *ptr++; 1.438 + } else { 1.439 + dest++; 1.440 + ptr++; 1.441 + } 1.442 + } 1.443 +} 1.444 + 1.445 + 1.446 #else 1.447 int resman_filewatch_win32_silence_empty_file_warning; 1.448 #endif /* WIN32 */
2.1 --- a/src/resman_impl.h Thu Feb 13 13:17:07 2014 +0200 2.2 +++ b/src/resman_impl.h Thu Feb 13 21:30:05 2014 +0200 2.3 @@ -28,7 +28,6 @@ 2.4 2.5 /* file change monitoring */ 2.6 #ifdef WIN32 2.7 - HANDLE nhandle; 2.8 char *watch_path; 2.9 #endif 2.10 #ifdef __linux__ 2.11 @@ -58,8 +57,8 @@ 2.12 struct rbtree *modset; 2.13 #endif 2.14 #ifdef WIN32 2.15 - struct rbtree *watchdirs; 2.16 - HANDLE *watch_handles; /* dynamic array of all the watch handles */ 2.17 + struct rbtree *watchdirs, *wdirbyev; 2.18 + HANDLE *watch_handles; /* dynamic array of all the watched directory handles */ 2.19 #endif 2.20 }; 2.21