nuclear@22: /* file modification monitoring for windows */ nuclear@22: #ifdef WIN32 nuclear@22: #include nuclear@24: #include nuclear@22: #include nuclear@23: #include nuclear@22: #include "filewatch.h" nuclear@22: #include "resman.h" nuclear@22: #include "resman_impl.h" nuclear@23: #include "dynarr.h" nuclear@23: nuclear@24: #define RES_BUF_SIZE 8192 nuclear@24: nuclear@24: struct watch_item { nuclear@24: struct resource *res; nuclear@24: struct watch_item *next; nuclear@24: }; nuclear@24: nuclear@23: struct watch_dir { nuclear@23: HANDLE handle; nuclear@24: OVERLAPPED over; /* overlapped I/O structure */ nuclear@24: char *buf_unaligned, *buf; nuclear@23: int nref; nuclear@24: char *watch_path; nuclear@24: nuclear@24: struct watch_item *items; nuclear@23: }; nuclear@22: nuclear@24: static char *abs_path(const char *fname); nuclear@24: static void clean_path(char *path); nuclear@22: nuclear@22: int resman_init_file_monitor(struct resman *rman) nuclear@22: { nuclear@23: if(!(rman->watch_handles = dynarr_alloc(0, sizeof *rman->watch_handles))) { nuclear@22: return -1; nuclear@22: } nuclear@22: nuclear@23: rman->nresmap = rb_create(RB_KEY_ADDR); nuclear@23: rman->watchdirs = rb_create(RB_KEY_STRING); nuclear@24: rman->wdirbyev = rb_create(RB_KEY_ADDR); nuclear@24: nuclear@22: return 0; nuclear@22: } nuclear@22: nuclear@22: void resman_destroy_file_monitor(struct resman *rman) nuclear@22: { nuclear@23: dynarr_free(rman->watch_handles); nuclear@23: nuclear@22: rb_free(rman->nresmap); nuclear@23: rb_free(rman->watchdirs); nuclear@24: rb_free(rman->wdirbyev); nuclear@22: } nuclear@22: nuclear@22: int resman_start_watch(struct resman *rman, struct resource *res) nuclear@22: { nuclear@24: char *path = 0, *last_slash; nuclear@24: struct watch_dir *wdir = 0; nuclear@24: struct watch_item *witem = 0; nuclear@22: nuclear@24: /* construct an absolute path for the directory containing this file (must free it afterwards) */ nuclear@24: if(!(path = abs_path(res->name))) { nuclear@24: return -1; nuclear@24: } nuclear@24: clean_path(path); nuclear@23: nuclear@24: /* we need the directory path, so let's find the last slash and cut it there */ nuclear@24: if(!(last_slash = strrchr(path, '\\'))) { nuclear@24: goto err; nuclear@22: } nuclear@24: *last_slash = 0; nuclear@22: nuclear@23: /* check to see if we already have a watch handle for this directory */ nuclear@23: if((wdir = rb_find(rman->watchdirs, path))) { nuclear@24: wdir->nref++; /* ... if so, increase the refcount */ nuclear@23: } else { nuclear@24: /* otherwise start a new watch */ nuclear@23: if(!(wdir = malloc(sizeof *wdir))) { nuclear@23: perror("failed to allocate watchdir"); nuclear@24: goto err; nuclear@24: } nuclear@24: memset(wdir, 0, sizeof *wdir); nuclear@24: wdir->nref = 1; nuclear@24: nuclear@24: /* open the directory we need to watch */ nuclear@24: wdir->handle = CreateFile(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nuclear@24: 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, 0); nuclear@24: if(wdir->handle == INVALID_HANDLE_VALUE) { nuclear@24: fprintf(stderr, "failed to watch %s: failed to open directory: %s\n", res->name, path); nuclear@24: goto err; nuclear@23: } nuclear@23: nuclear@24: if(!(wdir->buf_unaligned = malloc(RES_BUF_SIZE + 3))) { nuclear@24: fprintf(stderr, "failed to allocate watch result buffer (%d bytes)\n", RES_BUF_SIZE); nuclear@24: goto err; nuclear@23: } nuclear@24: wdir->buf = (char*)(((intptr_t)wdir->buf_unaligned + 3) & ~(intptr_t)0x3); nuclear@24: nuclear@24: memset(&wdir->over, 0, sizeof wdir->over); nuclear@24: wdir->over.hEvent = CreateEvent(0, TRUE, FALSE, 0); nuclear@24: nuclear@24: if(!ReadDirectoryChangesW(wdir->handle, wdir->buf, RES_BUF_SIZE, 0, FILE_NOTIFY_CHANGE_LAST_WRITE, 0, &wdir->over, 0)) { nuclear@24: fprintf(stderr, "failed to start async dirchange monitoring\n"); nuclear@24: goto err; nuclear@24: } nuclear@24: nuclear@24: wdir->watch_path = path; nuclear@23: nuclear@23: rb_insert(rman->watchdirs, path, wdir); nuclear@24: rb_insert(rman->wdirbyev, wdir->over.hEvent, wdir); nuclear@24: dynarr_push(rman->watch_handles, &wdir->over.hEvent); nuclear@23: } nuclear@23: nuclear@24: /* add a new watch item to this watch dir */ nuclear@24: if(!(witem = malloc(sizeof *witem))) { nuclear@24: perror("failed to allocate watch item"); nuclear@24: goto err; nuclear@24: } nuclear@24: witem->next = wdir->items; nuclear@24: wdir->items = witem; nuclear@24: witem->res = res; nuclear@24: nuclear@23: res->watch_path = path; nuclear@22: return 0; nuclear@24: err: nuclear@24: free(path); nuclear@24: if(wdir) { nuclear@24: if(wdir->handle && wdir->handle != INVALID_HANDLE_VALUE) { nuclear@24: CloseHandle(wdir->handle); nuclear@24: } nuclear@24: free(wdir->buf_unaligned); nuclear@24: if(wdir->over.hEvent && wdir->over.hEvent != INVALID_HANDLE_VALUE) { nuclear@24: CloseHandle(wdir->over.hEvent); nuclear@24: } nuclear@24: } nuclear@24: return -1; nuclear@22: } nuclear@22: nuclear@22: void resman_stop_watch(struct resman *rman, struct resource *res) nuclear@22: { nuclear@23: int i, sz; nuclear@24: struct watch_dir *wdir; nuclear@23: nuclear@24: if(!res->watch_path) { nuclear@24: return; nuclear@24: } nuclear@23: nuclear@24: if(!(wdir = rb_find(rman->watchdirs, res->watch_path))) { nuclear@24: return; nuclear@24: } nuclear@24: nuclear@24: /* if there is no other reference to this watch dir, destroy it */ nuclear@24: if(--wdir->nref <= 0) { nuclear@24: /* find the handle in the watch_handles array and remove it */ nuclear@24: sz = dynarr_size(rman->watch_handles); nuclear@24: for(i=0; iwatch_handles[i] == wdir->handle) { nuclear@24: /* swap the end for it and pop */ nuclear@24: rman->watch_handles[i] = rman->watch_handles[sz - 1]; nuclear@24: rman->watch_handles[sz - 1] = 0; nuclear@24: dynarr_pop(rman->watch_handles); nuclear@24: break; nuclear@23: } nuclear@23: } nuclear@23: nuclear@24: rb_delete(rman->wdirbyev, wdir->over.hEvent); nuclear@24: rb_delete(rman->watchdirs, wdir->watch_path); nuclear@24: nuclear@24: CancelIo(wdir->handle); nuclear@24: CloseHandle(wdir->handle); nuclear@24: CloseHandle(wdir->over.hEvent); nuclear@24: free(wdir->watch_path); nuclear@24: free(wdir); nuclear@24: nuclear@24: res->watch_path = 0; nuclear@24: } else { nuclear@24: /* just remove this watch item */ nuclear@24: if(wdir->items && wdir->items->res == res) { nuclear@24: struct watch_item *tmp = wdir->items; nuclear@24: wdir->items = wdir->items->next; nuclear@24: free(tmp); nuclear@24: } else { nuclear@24: struct watch_item *wprev = wdir->items; nuclear@24: struct watch_item *witem = wprev->next; nuclear@24: nuclear@24: while(witem) { nuclear@24: if(witem->res == res) { nuclear@24: struct watch_item *tmp = witem; nuclear@24: wprev->next = witem->next; nuclear@24: break; nuclear@24: } nuclear@24: witem = witem->next; nuclear@24: } nuclear@24: } nuclear@24: } nuclear@24: } nuclear@24: nuclear@24: static void handle_event(struct resman *rman, HANDLE hev, struct watch_dir *wdir) nuclear@24: { nuclear@24: struct resource *res = 0; nuclear@24: struct watch_item *witem; nuclear@24: FILE_NOTIFY_INFORMATION *info; nuclear@24: DWORD res_size; nuclear@24: nuclear@24: if(!GetOverlappedResult(hev, &wdir->over, &res_size, FALSE)) { nuclear@24: return; nuclear@24: } nuclear@24: nuclear@24: info = (FILE_NOTIFY_INFORMATION*)wdir->buf; nuclear@24: nuclear@24: for(;;) { nuclear@24: if(info->Action == FILE_ACTION_MODIFIED) { nuclear@24: char *name; nuclear@24: int len = info->FileNameLength / 2; nuclear@24: wchar_t *wname = alloca((len + 1) * sizeof *wname); nuclear@24: memcpy(wname, info->FileName, info->FileNameLength); nuclear@24: wname[len] = 0; nuclear@24: nuclear@24: len = wcstombs(0, wname, 0); nuclear@24: name = alloca(len + 1); nuclear@24: wcstombs(name, wname, len + 1); nuclear@24: nuclear@24: witem = wdir->items; nuclear@24: while(witem) { nuclear@24: if(strstr(witem->res->name, name)) { nuclear@24: res = witem->res; nuclear@24: break; nuclear@24: } nuclear@24: witem = witem->next; nuclear@24: } nuclear@24: if(!res) { nuclear@24: fprintf(stderr, "failed to find the modified watch item (%s)\n", name); nuclear@24: } else { nuclear@24: /* found the resource, schedule a reload */ nuclear@24: printf("file \"%s\" modified\n", res->name); nuclear@24: tpool_add_work(rman->tpool, res); nuclear@24: } nuclear@24: } nuclear@24: nuclear@24: if(info->NextEntryOffset) { nuclear@24: info = (FILE_NOTIFY_INFORMATION*)((char*)info + info->NextEntryOffset); nuclear@24: } else { nuclear@24: break; nuclear@24: } nuclear@22: } nuclear@22: } nuclear@22: nuclear@22: void resman_check_watch(struct resman *rman) nuclear@22: { nuclear@24: struct watch_dir *wdir; nuclear@24: unsigned int idx; nuclear@24: nuclear@23: unsigned int num_handles = dynarr_size(rman->watch_handles); nuclear@24: if(!num_handles) { nuclear@24: return; nuclear@24: } nuclear@24: nuclear@24: idx = WaitForMultipleObjectsEx(num_handles, rman->watch_handles, FALSE, 0, TRUE); nuclear@24: if(idx == WAIT_FAILED) { nuclear@24: unsigned int err = GetLastError(); nuclear@24: fprintf(stderr, "failed to check for file modification: %u\n", err); nuclear@24: return; nuclear@24: } nuclear@24: if(idx >= WAIT_OBJECT_0 && idx < WAIT_OBJECT_0 + num_handles) { nuclear@24: if(!(wdir = rb_find(rman->wdirbyev, rman->watch_handles[idx]))) { nuclear@24: fprintf(stderr, "got change handle, but failed to find corresponding watch_dir!\n"); nuclear@24: return; nuclear@23: } nuclear@22: nuclear@24: handle_event(rman, rman->watch_handles[idx], wdir); nuclear@22: nuclear@24: /* restart the watch call */ nuclear@24: ReadDirectoryChangesW(wdir->handle, wdir->buf, RES_BUF_SIZE, FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, 0, &wdir->over, 0); nuclear@22: } nuclear@22: } nuclear@22: nuclear@24: nuclear@24: /* returns a new path buffer which is the equivalent absolute path to "fname" nuclear@24: * the user takes ownership and must free it when done. nuclear@24: */ nuclear@24: static char *abs_path(const char *fname) nuclear@24: { nuclear@24: if(fname[0] != '/' && fname[1] != ':') { /* not an absolute path */ nuclear@24: char *path; nuclear@24: int cwdsz = GetCurrentDirectory(0, 0); nuclear@24: int pathsz = strlen(fname) + cwdsz + 1; nuclear@24: nuclear@24: if(!(path = malloc(pathsz + 1))) { nuclear@24: return 0; nuclear@24: } nuclear@24: GetCurrentDirectory(pathsz, path); nuclear@24: nuclear@24: /* now copy the rest of the path */ nuclear@24: strcat(path, "\\"); nuclear@24: strcat(path, fname); nuclear@24: return path; nuclear@24: } nuclear@24: nuclear@24: return strdup(fname); nuclear@24: } nuclear@24: nuclear@24: static void clean_path(char *path) nuclear@24: { nuclear@24: char *ptr, *dest; nuclear@24: nuclear@24: /* first pass, change '/' -> '\\' */ nuclear@24: ptr = path; nuclear@24: while(*ptr) { nuclear@24: if(*ptr == '/') *ptr = '\\'; nuclear@24: ptr++; nuclear@24: } nuclear@24: nuclear@24: /* now go through the path and remove any \.\ or \..\ parts */ nuclear@24: ptr = dest = path; nuclear@24: while(*ptr) { nuclear@24: if(ptr > path && ptr[-1] == '\\') { nuclear@24: /* we're just after a slash, so any .\ or ..\ here should be purged */ nuclear@24: if(ptr[0] == '.' && ptr[1] == '\\') { nuclear@24: ptr += 2; nuclear@24: continue; nuclear@24: } nuclear@24: if(ptr[0] == '.' && ptr[1] == '.' && ptr[2] == '\\') { nuclear@24: /* search backwards for the previous slash */ nuclear@24: dest -= 2; nuclear@24: while(dest > path && *dest != '\\') { nuclear@24: dest--; nuclear@24: } nuclear@24: if(*dest == '\\') { nuclear@24: /* found it, continue after this one */ nuclear@24: dest++; nuclear@24: } /* .. otherwise, reached the beginning, go from there */ nuclear@24: nuclear@24: ptr += 3; nuclear@24: continue; nuclear@24: } nuclear@24: } nuclear@24: if(dest != ptr) { nuclear@24: *dest++ = *ptr++; nuclear@24: } else { nuclear@24: dest++; nuclear@24: ptr++; nuclear@24: } nuclear@24: } nuclear@24: } nuclear@24: nuclear@24: nuclear@22: #else nuclear@22: int resman_filewatch_win32_silence_empty_file_warning; nuclear@22: #endif /* WIN32 */