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 */
|