libresman

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