libresman

annotate src/filewatch_win32.c @ 25:1be0a35aa216

removed a random debug printf left in the code
author John Tsiombikas <nuclear@member.fsf.org>
date Thu, 13 Feb 2014 21:34:41 +0200
parents ce04fa12afdd
children
rev   line source
nuclear@22 1 /* file modification monitoring for windows */
nuclear@22 2 #ifdef WIN32
nuclear@22 3 #include <stdio.h>
nuclear@24 4 #include <stdlib.h>
nuclear@22 5 #include <assert.h>
nuclear@23 6 #include <malloc.h>
nuclear@22 7 #include "filewatch.h"
nuclear@22 8 #include "resman.h"
nuclear@22 9 #include "resman_impl.h"
nuclear@23 10 #include "dynarr.h"
nuclear@23 11
nuclear@24 12 #define RES_BUF_SIZE 8192
nuclear@24 13
nuclear@24 14 struct watch_item {
nuclear@24 15 struct resource *res;
nuclear@24 16 struct watch_item *next;
nuclear@24 17 };
nuclear@24 18
nuclear@23 19 struct watch_dir {
nuclear@23 20 HANDLE handle;
nuclear@24 21 OVERLAPPED over; /* overlapped I/O structure */
nuclear@24 22 char *buf_unaligned, *buf;
nuclear@23 23 int nref;
nuclear@24 24 char *watch_path;
nuclear@24 25
nuclear@24 26 struct watch_item *items;
nuclear@23 27 };
nuclear@22 28
nuclear@24 29 static char *abs_path(const char *fname);
nuclear@24 30 static void clean_path(char *path);
nuclear@22 31
nuclear@22 32 int resman_init_file_monitor(struct resman *rman)
nuclear@22 33 {
nuclear@23 34 if(!(rman->watch_handles = dynarr_alloc(0, sizeof *rman->watch_handles))) {
nuclear@22 35 return -1;
nuclear@22 36 }
nuclear@22 37
nuclear@23 38 rman->nresmap = rb_create(RB_KEY_ADDR);
nuclear@23 39 rman->watchdirs = rb_create(RB_KEY_STRING);
nuclear@24 40 rman->wdirbyev = rb_create(RB_KEY_ADDR);
nuclear@24 41
nuclear@22 42 return 0;
nuclear@22 43 }
nuclear@22 44
nuclear@22 45 void resman_destroy_file_monitor(struct resman *rman)
nuclear@22 46 {
nuclear@23 47 dynarr_free(rman->watch_handles);
nuclear@23 48
nuclear@22 49 rb_free(rman->nresmap);
nuclear@23 50 rb_free(rman->watchdirs);
nuclear@24 51 rb_free(rman->wdirbyev);
nuclear@22 52 }
nuclear@22 53
nuclear@22 54 int resman_start_watch(struct resman *rman, struct resource *res)
nuclear@22 55 {
nuclear@24 56 char *path = 0, *last_slash;
nuclear@24 57 struct watch_dir *wdir = 0;
nuclear@24 58 struct watch_item *witem = 0;
nuclear@22 59
nuclear@24 60 /* construct an absolute path for the directory containing this file (must free it afterwards) */
nuclear@24 61 if(!(path = abs_path(res->name))) {
nuclear@24 62 return -1;
nuclear@24 63 }
nuclear@24 64 clean_path(path);
nuclear@23 65
nuclear@24 66 /* we need the directory path, so let's find the last slash and cut it there */
nuclear@24 67 if(!(last_slash = strrchr(path, '\\'))) {
nuclear@24 68 goto err;
nuclear@22 69 }
nuclear@24 70 *last_slash = 0;
nuclear@22 71
nuclear@23 72 /* check to see if we already have a watch handle for this directory */
nuclear@23 73 if((wdir = rb_find(rman->watchdirs, path))) {
nuclear@24 74 wdir->nref++; /* ... if so, increase the refcount */
nuclear@23 75 } else {
nuclear@24 76 /* otherwise start a new watch */
nuclear@23 77 if(!(wdir = malloc(sizeof *wdir))) {
nuclear@23 78 perror("failed to allocate watchdir");
nuclear@24 79 goto err;
nuclear@24 80 }
nuclear@24 81 memset(wdir, 0, sizeof *wdir);
nuclear@24 82 wdir->nref = 1;
nuclear@24 83
nuclear@24 84 /* open the directory we need to watch */
nuclear@24 85 wdir->handle = CreateFile(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nuclear@24 86 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, 0);
nuclear@24 87 if(wdir->handle == INVALID_HANDLE_VALUE) {
nuclear@24 88 fprintf(stderr, "failed to watch %s: failed to open directory: %s\n", res->name, path);
nuclear@24 89 goto err;
nuclear@23 90 }
nuclear@23 91
nuclear@24 92 if(!(wdir->buf_unaligned = malloc(RES_BUF_SIZE + 3))) {
nuclear@24 93 fprintf(stderr, "failed to allocate watch result buffer (%d bytes)\n", RES_BUF_SIZE);
nuclear@24 94 goto err;
nuclear@23 95 }
nuclear@24 96 wdir->buf = (char*)(((intptr_t)wdir->buf_unaligned + 3) & ~(intptr_t)0x3);
nuclear@24 97
nuclear@24 98 memset(&wdir->over, 0, sizeof wdir->over);
nuclear@24 99 wdir->over.hEvent = CreateEvent(0, TRUE, FALSE, 0);
nuclear@24 100
nuclear@24 101 if(!ReadDirectoryChangesW(wdir->handle, wdir->buf, RES_BUF_SIZE, 0, FILE_NOTIFY_CHANGE_LAST_WRITE, 0, &wdir->over, 0)) {
nuclear@24 102 fprintf(stderr, "failed to start async dirchange monitoring\n");
nuclear@24 103 goto err;
nuclear@24 104 }
nuclear@24 105
nuclear@24 106 wdir->watch_path = path;
nuclear@23 107
nuclear@23 108 rb_insert(rman->watchdirs, path, wdir);
nuclear@24 109 rb_insert(rman->wdirbyev, wdir->over.hEvent, wdir);
nuclear@24 110 dynarr_push(rman->watch_handles, &wdir->over.hEvent);
nuclear@23 111 }
nuclear@23 112
nuclear@24 113 /* add a new watch item to this watch dir */
nuclear@24 114 if(!(witem = malloc(sizeof *witem))) {
nuclear@24 115 perror("failed to allocate watch item");
nuclear@24 116 goto err;
nuclear@24 117 }
nuclear@24 118 witem->next = wdir->items;
nuclear@24 119 wdir->items = witem;
nuclear@24 120 witem->res = res;
nuclear@24 121
nuclear@23 122 res->watch_path = path;
nuclear@22 123 return 0;
nuclear@24 124 err:
nuclear@24 125 free(path);
nuclear@24 126 if(wdir) {
nuclear@24 127 if(wdir->handle && wdir->handle != INVALID_HANDLE_VALUE) {
nuclear@24 128 CloseHandle(wdir->handle);
nuclear@24 129 }
nuclear@24 130 free(wdir->buf_unaligned);
nuclear@24 131 if(wdir->over.hEvent && wdir->over.hEvent != INVALID_HANDLE_VALUE) {
nuclear@24 132 CloseHandle(wdir->over.hEvent);
nuclear@24 133 }
nuclear@24 134 }
nuclear@24 135 return -1;
nuclear@22 136 }
nuclear@22 137
nuclear@22 138 void resman_stop_watch(struct resman *rman, struct resource *res)
nuclear@22 139 {
nuclear@23 140 int i, sz;
nuclear@24 141 struct watch_dir *wdir;
nuclear@23 142
nuclear@24 143 if(!res->watch_path) {
nuclear@24 144 return;
nuclear@24 145 }
nuclear@23 146
nuclear@24 147 if(!(wdir = rb_find(rman->watchdirs, res->watch_path))) {
nuclear@24 148 return;
nuclear@24 149 }
nuclear@24 150
nuclear@24 151 /* if there is no other reference to this watch dir, destroy it */
nuclear@24 152 if(--wdir->nref <= 0) {
nuclear@24 153 /* find the handle in the watch_handles array and remove it */
nuclear@24 154 sz = dynarr_size(rman->watch_handles);
nuclear@24 155 for(i=0; i<sz; i++) {
nuclear@24 156 if(rman->watch_handles[i] == wdir->handle) {
nuclear@24 157 /* swap the end for it and pop */
nuclear@24 158 rman->watch_handles[i] = rman->watch_handles[sz - 1];
nuclear@24 159 rman->watch_handles[sz - 1] = 0;
nuclear@24 160 dynarr_pop(rman->watch_handles);
nuclear@24 161 break;
nuclear@23 162 }
nuclear@23 163 }
nuclear@23 164
nuclear@24 165 rb_delete(rman->wdirbyev, wdir->over.hEvent);
nuclear@24 166 rb_delete(rman->watchdirs, wdir->watch_path);
nuclear@24 167
nuclear@24 168 CancelIo(wdir->handle);
nuclear@24 169 CloseHandle(wdir->handle);
nuclear@24 170 CloseHandle(wdir->over.hEvent);
nuclear@24 171 free(wdir->watch_path);
nuclear@24 172 free(wdir);
nuclear@24 173
nuclear@24 174 res->watch_path = 0;
nuclear@24 175 } else {
nuclear@24 176 /* just remove this watch item */
nuclear@24 177 if(wdir->items && wdir->items->res == res) {
nuclear@24 178 struct watch_item *tmp = wdir->items;
nuclear@24 179 wdir->items = wdir->items->next;
nuclear@24 180 free(tmp);
nuclear@24 181 } else {
nuclear@24 182 struct watch_item *wprev = wdir->items;
nuclear@24 183 struct watch_item *witem = wprev->next;
nuclear@24 184
nuclear@24 185 while(witem) {
nuclear@24 186 if(witem->res == res) {
nuclear@24 187 struct watch_item *tmp = witem;
nuclear@24 188 wprev->next = witem->next;
nuclear@24 189 break;
nuclear@24 190 }
nuclear@24 191 witem = witem->next;
nuclear@24 192 }
nuclear@24 193 }
nuclear@24 194 }
nuclear@24 195 }
nuclear@24 196
nuclear@24 197 static void handle_event(struct resman *rman, HANDLE hev, struct watch_dir *wdir)
nuclear@24 198 {
nuclear@24 199 struct resource *res = 0;
nuclear@24 200 struct watch_item *witem;
nuclear@24 201 FILE_NOTIFY_INFORMATION *info;
nuclear@24 202 DWORD res_size;
nuclear@24 203
nuclear@24 204 if(!GetOverlappedResult(hev, &wdir->over, &res_size, FALSE)) {
nuclear@24 205 return;
nuclear@24 206 }
nuclear@24 207
nuclear@24 208 info = (FILE_NOTIFY_INFORMATION*)wdir->buf;
nuclear@24 209
nuclear@24 210 for(;;) {
nuclear@24 211 if(info->Action == FILE_ACTION_MODIFIED) {
nuclear@24 212 char *name;
nuclear@24 213 int len = info->FileNameLength / 2;
nuclear@24 214 wchar_t *wname = alloca((len + 1) * sizeof *wname);
nuclear@24 215 memcpy(wname, info->FileName, info->FileNameLength);
nuclear@24 216 wname[len] = 0;
nuclear@24 217
nuclear@24 218 len = wcstombs(0, wname, 0);
nuclear@24 219 name = alloca(len + 1);
nuclear@24 220 wcstombs(name, wname, len + 1);
nuclear@24 221
nuclear@24 222 witem = wdir->items;
nuclear@24 223 while(witem) {
nuclear@24 224 if(strstr(witem->res->name, name)) {
nuclear@24 225 res = witem->res;
nuclear@24 226 break;
nuclear@24 227 }
nuclear@24 228 witem = witem->next;
nuclear@24 229 }
nuclear@24 230 if(!res) {
nuclear@24 231 fprintf(stderr, "failed to find the modified watch item (%s)\n", name);
nuclear@24 232 } else {
nuclear@24 233 /* found the resource, schedule a reload */
nuclear@24 234 printf("file \"%s\" modified\n", res->name);
nuclear@24 235 tpool_add_work(rman->tpool, res);
nuclear@24 236 }
nuclear@24 237 }
nuclear@24 238
nuclear@24 239 if(info->NextEntryOffset) {
nuclear@24 240 info = (FILE_NOTIFY_INFORMATION*)((char*)info + info->NextEntryOffset);
nuclear@24 241 } else {
nuclear@24 242 break;
nuclear@24 243 }
nuclear@22 244 }
nuclear@22 245 }
nuclear@22 246
nuclear@22 247 void resman_check_watch(struct resman *rman)
nuclear@22 248 {
nuclear@24 249 struct watch_dir *wdir;
nuclear@24 250 unsigned int idx;
nuclear@24 251
nuclear@23 252 unsigned int num_handles = dynarr_size(rman->watch_handles);
nuclear@24 253 if(!num_handles) {
nuclear@24 254 return;
nuclear@24 255 }
nuclear@24 256
nuclear@24 257 idx = WaitForMultipleObjectsEx(num_handles, rman->watch_handles, FALSE, 0, TRUE);
nuclear@24 258 if(idx == WAIT_FAILED) {
nuclear@24 259 unsigned int err = GetLastError();
nuclear@24 260 fprintf(stderr, "failed to check for file modification: %u\n", err);
nuclear@24 261 return;
nuclear@24 262 }
nuclear@24 263 if(idx >= WAIT_OBJECT_0 && idx < WAIT_OBJECT_0 + num_handles) {
nuclear@24 264 if(!(wdir = rb_find(rman->wdirbyev, rman->watch_handles[idx]))) {
nuclear@24 265 fprintf(stderr, "got change handle, but failed to find corresponding watch_dir!\n");
nuclear@24 266 return;
nuclear@23 267 }
nuclear@22 268
nuclear@24 269 handle_event(rman, rman->watch_handles[idx], wdir);
nuclear@22 270
nuclear@24 271 /* restart the watch call */
nuclear@24 272 ReadDirectoryChangesW(wdir->handle, wdir->buf, RES_BUF_SIZE, FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, 0, &wdir->over, 0);
nuclear@22 273 }
nuclear@22 274 }
nuclear@22 275
nuclear@24 276
nuclear@24 277 /* returns a new path buffer which is the equivalent absolute path to "fname"
nuclear@24 278 * the user takes ownership and must free it when done.
nuclear@24 279 */
nuclear@24 280 static char *abs_path(const char *fname)
nuclear@24 281 {
nuclear@24 282 if(fname[0] != '/' && fname[1] != ':') { /* not an absolute path */
nuclear@24 283 char *path;
nuclear@24 284 int cwdsz = GetCurrentDirectory(0, 0);
nuclear@24 285 int pathsz = strlen(fname) + cwdsz + 1;
nuclear@24 286
nuclear@24 287 if(!(path = malloc(pathsz + 1))) {
nuclear@24 288 return 0;
nuclear@24 289 }
nuclear@24 290 GetCurrentDirectory(pathsz, path);
nuclear@24 291
nuclear@24 292 /* now copy the rest of the path */
nuclear@24 293 strcat(path, "\\");
nuclear@24 294 strcat(path, fname);
nuclear@24 295 return path;
nuclear@24 296 }
nuclear@24 297
nuclear@24 298 return strdup(fname);
nuclear@24 299 }
nuclear@24 300
nuclear@24 301 static void clean_path(char *path)
nuclear@24 302 {
nuclear@24 303 char *ptr, *dest;
nuclear@24 304
nuclear@24 305 /* first pass, change '/' -> '\\' */
nuclear@24 306 ptr = path;
nuclear@24 307 while(*ptr) {
nuclear@24 308 if(*ptr == '/') *ptr = '\\';
nuclear@24 309 ptr++;
nuclear@24 310 }
nuclear@24 311
nuclear@24 312 /* now go through the path and remove any \.\ or \..\ parts */
nuclear@24 313 ptr = dest = path;
nuclear@24 314 while(*ptr) {
nuclear@24 315 if(ptr > path && ptr[-1] == '\\') {
nuclear@24 316 /* we're just after a slash, so any .\ or ..\ here should be purged */
nuclear@24 317 if(ptr[0] == '.' && ptr[1] == '\\') {
nuclear@24 318 ptr += 2;
nuclear@24 319 continue;
nuclear@24 320 }
nuclear@24 321 if(ptr[0] == '.' && ptr[1] == '.' && ptr[2] == '\\') {
nuclear@24 322 /* search backwards for the previous slash */
nuclear@24 323 dest -= 2;
nuclear@24 324 while(dest > path && *dest != '\\') {
nuclear@24 325 dest--;
nuclear@24 326 }
nuclear@24 327 if(*dest == '\\') {
nuclear@24 328 /* found it, continue after this one */
nuclear@24 329 dest++;
nuclear@24 330 } /* .. otherwise, reached the beginning, go from there */
nuclear@24 331
nuclear@24 332 ptr += 3;
nuclear@24 333 continue;
nuclear@24 334 }
nuclear@24 335 }
nuclear@24 336 if(dest != ptr) {
nuclear@24 337 *dest++ = *ptr++;
nuclear@24 338 } else {
nuclear@24 339 dest++;
nuclear@24 340 ptr++;
nuclear@24 341 }
nuclear@24 342 }
nuclear@24 343 }
nuclear@24 344
nuclear@24 345
nuclear@22 346 #else
nuclear@22 347 int resman_filewatch_win32_silence_empty_file_warning;
nuclear@22 348 #endif /* WIN32 */