libresman
view 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 |
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 char *name;
213 int len = info->FileNameLength / 2;
214 wchar_t *wname = alloca((len + 1) * sizeof *wname);
215 memcpy(wname, info->FileName, info->FileNameLength);
216 wname[len] = 0;
218 len = wcstombs(0, wname, 0);
219 name = alloca(len + 1);
220 wcstombs(name, wname, len + 1);
222 witem = wdir->items;
223 while(witem) {
224 if(strstr(witem->res->name, name)) {
225 res = witem->res;
226 break;
227 }
228 witem = witem->next;
229 }
230 if(!res) {
231 fprintf(stderr, "failed to find the modified watch item (%s)\n", name);
232 } else {
233 /* found the resource, schedule a reload */
234 printf("file \"%s\" modified\n", res->name);
235 tpool_add_work(rman->tpool, res);
236 }
237 }
239 if(info->NextEntryOffset) {
240 info = (FILE_NOTIFY_INFORMATION*)((char*)info + info->NextEntryOffset);
241 } else {
242 break;
243 }
244 }
245 }
247 void resman_check_watch(struct resman *rman)
248 {
249 struct watch_dir *wdir;
250 unsigned int idx;
252 unsigned int num_handles = dynarr_size(rman->watch_handles);
253 if(!num_handles) {
254 return;
255 }
257 idx = WaitForMultipleObjectsEx(num_handles, rman->watch_handles, FALSE, 0, TRUE);
258 if(idx == WAIT_FAILED) {
259 unsigned int err = GetLastError();
260 fprintf(stderr, "failed to check for file modification: %u\n", err);
261 return;
262 }
263 if(idx >= WAIT_OBJECT_0 && idx < WAIT_OBJECT_0 + num_handles) {
264 if(!(wdir = rb_find(rman->wdirbyev, rman->watch_handles[idx]))) {
265 fprintf(stderr, "got change handle, but failed to find corresponding watch_dir!\n");
266 return;
267 }
269 handle_event(rman, rman->watch_handles[idx], wdir);
271 /* restart the watch call */
272 ReadDirectoryChangesW(wdir->handle, wdir->buf, RES_BUF_SIZE, FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, 0, &wdir->over, 0);
273 }
274 }
277 /* returns a new path buffer which is the equivalent absolute path to "fname"
278 * the user takes ownership and must free it when done.
279 */
280 static char *abs_path(const char *fname)
281 {
282 if(fname[0] != '/' && fname[1] != ':') { /* not an absolute path */
283 char *path;
284 int cwdsz = GetCurrentDirectory(0, 0);
285 int pathsz = strlen(fname) + cwdsz + 1;
287 if(!(path = malloc(pathsz + 1))) {
288 return 0;
289 }
290 GetCurrentDirectory(pathsz, path);
292 /* now copy the rest of the path */
293 strcat(path, "\\");
294 strcat(path, fname);
295 return path;
296 }
298 return strdup(fname);
299 }
301 static void clean_path(char *path)
302 {
303 char *ptr, *dest;
305 /* first pass, change '/' -> '\\' */
306 ptr = path;
307 while(*ptr) {
308 if(*ptr == '/') *ptr = '\\';
309 ptr++;
310 }
312 /* now go through the path and remove any \.\ or \..\ parts */
313 ptr = dest = path;
314 while(*ptr) {
315 if(ptr > path && ptr[-1] == '\\') {
316 /* we're just after a slash, so any .\ or ..\ here should be purged */
317 if(ptr[0] == '.' && ptr[1] == '\\') {
318 ptr += 2;
319 continue;
320 }
321 if(ptr[0] == '.' && ptr[1] == '.' && ptr[2] == '\\') {
322 /* search backwards for the previous slash */
323 dest -= 2;
324 while(dest > path && *dest != '\\') {
325 dest--;
326 }
327 if(*dest == '\\') {
328 /* found it, continue after this one */
329 dest++;
330 } /* .. otherwise, reached the beginning, go from there */
332 ptr += 3;
333 continue;
334 }
335 }
336 if(dest != ptr) {
337 *dest++ = *ptr++;
338 } else {
339 dest++;
340 ptr++;
341 }
342 }
343 }
346 #else
347 int resman_filewatch_win32_silence_empty_file_warning;
348 #endif /* WIN32 */