libresman

changeset 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
files src/filewatch_win32.c src/resman_impl.h
diffstat 2 files changed, 275 insertions(+), 97 deletions(-) [+]
line diff
     1.1 --- a/src/filewatch_win32.c	Thu Feb 13 13:17:07 2014 +0200
     1.2 +++ b/src/filewatch_win32.c	Thu Feb 13 21:30:05 2014 +0200
     1.3 @@ -1,6 +1,7 @@
     1.4  /* file modification monitoring for windows */
     1.5  #ifdef WIN32
     1.6  #include <stdio.h>
     1.7 +#include <stdlib.h>
     1.8  #include <assert.h>
     1.9  #include <malloc.h>
    1.10  #include "filewatch.h"
    1.11 @@ -8,12 +9,25 @@
    1.12  #include "resman_impl.h"
    1.13  #include "dynarr.h"
    1.14  
    1.15 +#define RES_BUF_SIZE	8192
    1.16 +
    1.17 +struct watch_item {
    1.18 +	struct resource *res;
    1.19 +	struct watch_item *next;
    1.20 +};
    1.21 +
    1.22  struct watch_dir {
    1.23  	HANDLE handle;
    1.24 +	OVERLAPPED over;	/* overlapped I/O structure */
    1.25 +	char *buf_unaligned, *buf;
    1.26  	int nref;
    1.27 +	char *watch_path;
    1.28 +
    1.29 +	struct watch_item *items;
    1.30  };
    1.31  
    1.32 -static void reload_modified(struct rbnode *node, void *cls);
    1.33 +static char *abs_path(const char *fname);
    1.34 +static void clean_path(char *path);
    1.35  
    1.36  int resman_init_file_monitor(struct resman *rman)
    1.37  {
    1.38 @@ -21,10 +35,10 @@
    1.39  		return -1;
    1.40  	}
    1.41  
    1.42 -	/* create the handle->resource map */
    1.43  	rman->nresmap = rb_create(RB_KEY_ADDR);
    1.44 -	/* create the watched dirs set */
    1.45  	rman->watchdirs = rb_create(RB_KEY_STRING);
    1.46 +	rman->wdirbyev = rb_create(RB_KEY_ADDR);
    1.47 +
    1.48  	return 0;
    1.49  }
    1.50  
    1.51 @@ -34,141 +48,306 @@
    1.52  
    1.53  	rb_free(rman->nresmap);
    1.54  	rb_free(rman->watchdirs);
    1.55 +	rb_free(rman->wdirbyev);
    1.56  }
    1.57  
    1.58  int resman_start_watch(struct resman *rman, struct resource *res)
    1.59  {
    1.60 -	char *path;
    1.61 -	HANDLE handle;
    1.62 -	struct watch_dir *wdir;
    1.63 +	char *path = 0, *last_slash;
    1.64 +	struct watch_dir *wdir = 0;
    1.65 +	struct watch_item *witem = 0;
    1.66  
    1.67 -	/* construct an absolute path for the directory containing this file */
    1.68 -	path = res->name;
    1.69 -	if(path[0] != '/' && path[1] != ':') {	/* not an absolute path */
    1.70 -		char *src, *dest, *lastslash;
    1.71 -		int cwdsz = GetCurrentDirectory(0, 0);
    1.72 -		int pathsz = strlen(path) + cwdsz + 1;
    1.73 +	/* construct an absolute path for the directory containing this file (must free it afterwards) */
    1.74 +	if(!(path = abs_path(res->name))) {
    1.75 +		return -1;
    1.76 +	}
    1.77 +	clean_path(path);
    1.78  
    1.79 -		path = malloc(pathsz + 1);
    1.80 -		GetCurrentDirectory(pathsz, path);
    1.81 -
    1.82 -		/* now copy the rest of the path, until the last slash, while converting path separators */
    1.83 -		src = res->name;
    1.84 -		dest = path + strlen(path);
    1.85 -
    1.86 -		lastslash = dest;
    1.87 -		*dest++ = '\\';
    1.88 -		while(*src) {
    1.89 -			if(src[-1] == '\\') {
    1.90 -				/* skip any /./ parts of the path */
    1.91 -				if(src[0] == '.' && (src[1] == '/' || src[1] == '\\')) {
    1.92 -					src += 2;
    1.93 -					continue;
    1.94 -				}
    1.95 -				/* normalize any /../ parts of the path */
    1.96 -				if(src[0] == '.' && src[1] == '.' && (src[2] == '/' || src[2] == '\\')) {
    1.97 -					src += 3;
    1.98 -					dest = strrchr(src - 2, '\\');
    1.99 -					assert(dest);
   1.100 -					dest++;
   1.101 -					continue;
   1.102 -				}
   1.103 -			}
   1.104 -
   1.105 -			if(*src == '/' || *src == '\\') {
   1.106 -				lastslash = dest;
   1.107 -				*dest++ = '\\';
   1.108 -				src++;
   1.109 -			} else {
   1.110 -				*dest++ = *src++;
   1.111 -			}
   1.112 -		}
   1.113 -
   1.114 -		*lastslash = 0;
   1.115 +	/* we need the directory path, so let's find the last slash and cut it there */
   1.116 +	if(!(last_slash = strrchr(path, '\\'))) {
   1.117 +		goto err;
   1.118  	}
   1.119 +	*last_slash = 0;
   1.120  
   1.121  	/* check to see if we already have a watch handle for this directory */
   1.122  	if((wdir = rb_find(rman->watchdirs, path))) {
   1.123 -		handle = wdir->handle;
   1.124 -		wdir->nref++;
   1.125 +		wdir->nref++;	/* ... if so, increase the refcount */
   1.126  	} else {
   1.127 +		/* otherwise start a new watch */
   1.128  		if(!(wdir = malloc(sizeof *wdir))) {
   1.129  			perror("failed to allocate watchdir");
   1.130 -			free(path);
   1.131 -			return -1;
   1.132 +			goto err;
   1.133 +		}
   1.134 +		memset(wdir, 0, sizeof *wdir);
   1.135 +		wdir->nref = 1;
   1.136 +
   1.137 +		/* open the directory we need to watch */
   1.138 +		wdir->handle = CreateFile(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
   1.139 +			0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, 0);
   1.140 +		if(wdir->handle == INVALID_HANDLE_VALUE) {
   1.141 +			fprintf(stderr, "failed to watch %s: failed to open directory: %s\n", res->name, path);
   1.142 +			goto err;
   1.143  		}
   1.144  
   1.145 -		/* otherwise start a new notification */
   1.146 -		if((handle = FindFirstChangeNotification(path, FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE)) == INVALID_HANDLE_VALUE) {
   1.147 -			unsigned int err = GetLastError();
   1.148 -			fprintf(stderr, "failed to watch %s for modification (error: %u)\n", path, err);
   1.149 -			free(wdir);
   1.150 -			free(path);
   1.151 -			return -1;
   1.152 +		if(!(wdir->buf_unaligned = malloc(RES_BUF_SIZE + 3))) {
   1.153 +			fprintf(stderr, "failed to allocate watch result buffer (%d bytes)\n", RES_BUF_SIZE);
   1.154 +			goto err;
   1.155  		}
   1.156 -		wdir->handle = handle;
   1.157 -		wdir->nref = 1;
   1.158 +		wdir->buf = (char*)(((intptr_t)wdir->buf_unaligned + 3) & ~(intptr_t)0x3);
   1.159 +
   1.160 +		memset(&wdir->over, 0, sizeof wdir->over);
   1.161 +		wdir->over.hEvent = CreateEvent(0, TRUE, FALSE, 0);
   1.162 +
   1.163 +		if(!ReadDirectoryChangesW(wdir->handle, wdir->buf, RES_BUF_SIZE, 0, FILE_NOTIFY_CHANGE_LAST_WRITE, 0, &wdir->over, 0)) {
   1.164 +			fprintf(stderr, "failed to start async dirchange monitoring\n");
   1.165 +			goto err;
   1.166 +		}
   1.167 +
   1.168 +		wdir->watch_path = path;
   1.169  
   1.170  		rb_insert(rman->watchdirs, path, wdir);
   1.171 -		dynarr_push(rman->watch_handles, &handle);
   1.172 +		rb_insert(rman->wdirbyev, wdir->over.hEvent, wdir);
   1.173 +		dynarr_push(rman->watch_handles, &wdir->over.hEvent);
   1.174  	}
   1.175  
   1.176 -	rb_insert(rman->nresmap, handle, res);
   1.177 -	res->nhandle = handle;
   1.178 +	/* add a new watch item to this watch dir */
   1.179 +	if(!(witem = malloc(sizeof *witem))) {
   1.180 +		perror("failed to allocate watch item");
   1.181 +		goto err;
   1.182 +	}
   1.183 +	witem->next = wdir->items;
   1.184 +	wdir->items = witem;
   1.185 +	witem->res = res;
   1.186 +
   1.187  	res->watch_path = path;
   1.188  	return 0;
   1.189 +err:
   1.190 +	free(path);
   1.191 +	if(wdir) {
   1.192 +		if(wdir->handle && wdir->handle != INVALID_HANDLE_VALUE) {
   1.193 +			CloseHandle(wdir->handle);
   1.194 +		}
   1.195 +		free(wdir->buf_unaligned);
   1.196 +		if(wdir->over.hEvent && wdir->over.hEvent != INVALID_HANDLE_VALUE) {
   1.197 +			CloseHandle(wdir->over.hEvent);
   1.198 +		}
   1.199 +	}
   1.200 +	return -1;
   1.201  }
   1.202  
   1.203  void resman_stop_watch(struct resman *rman, struct resource *res)
   1.204  {
   1.205  	int i, sz;
   1.206 +	struct watch_dir *wdir;
   1.207  
   1.208 -	if(res->nhandle) {
   1.209 -		struct watch_dir *wdir = rb_find(rman->watchdirs, res->watch_path);
   1.210 -		if(wdir) {
   1.211 -			if(--wdir->nref <= 0) {
   1.212 -				FindCloseChangeNotification(res->nhandle);
   1.213 +	if(!res->watch_path) {
   1.214 +		return;
   1.215 +	}
   1.216  
   1.217 -				/* find the handle in the watch_handles array and remove it */
   1.218 -				sz = dynarr_size(rman->watch_handles);
   1.219 -				for(i=0; i<sz; i++) {
   1.220 -					if(rman->watch_handles[i] == res->nhandle) {
   1.221 -						/* swap the end for it and pop */
   1.222 -						rman->watch_handles[i] = rman->watch_handles[sz - 1];
   1.223 -						rman->watch_handles[sz - 1] = 0;
   1.224 -						dynarr_pop(rman->watch_handles);
   1.225 -						break;
   1.226 -					}
   1.227 -				}
   1.228 +	if(!(wdir = rb_find(rman->watchdirs, res->watch_path))) {
   1.229 +		return;
   1.230 +	}
   1.231 +
   1.232 +	/* if there is no other reference to this watch dir, destroy it */
   1.233 +	if(--wdir->nref <= 0) {
   1.234 +		/* find the handle in the watch_handles array and remove it */
   1.235 +		sz = dynarr_size(rman->watch_handles);
   1.236 +		for(i=0; i<sz; i++) {
   1.237 +			if(rman->watch_handles[i] == wdir->handle) {
   1.238 +				/* swap the end for it and pop */
   1.239 +				rman->watch_handles[i] = rman->watch_handles[sz - 1];
   1.240 +				rman->watch_handles[sz - 1] = 0;
   1.241 +				dynarr_pop(rman->watch_handles);
   1.242 +				break;
   1.243  			}
   1.244 -			free(wdir);
   1.245  		}
   1.246  
   1.247 -		rb_delete(rman->nresmap, res->nhandle);
   1.248 -		res->nhandle = 0;
   1.249 +		rb_delete(rman->wdirbyev, wdir->over.hEvent);
   1.250 +		rb_delete(rman->watchdirs, wdir->watch_path);
   1.251 +
   1.252 +		CancelIo(wdir->handle);
   1.253 +		CloseHandle(wdir->handle);
   1.254 +		CloseHandle(wdir->over.hEvent);
   1.255 +		free(wdir->watch_path);
   1.256 +		free(wdir);
   1.257 +
   1.258 +		res->watch_path = 0;
   1.259 +	} else {
   1.260 +		/* just remove this watch item */
   1.261 +		if(wdir->items && wdir->items->res == res) {
   1.262 +			struct watch_item *tmp = wdir->items;
   1.263 +			wdir->items = wdir->items->next;
   1.264 +			free(tmp);
   1.265 +		} else {
   1.266 +			struct watch_item *wprev = wdir->items;
   1.267 +			struct watch_item *witem = wprev->next;
   1.268 +
   1.269 +			while(witem) {
   1.270 +				if(witem->res == res) {
   1.271 +					struct watch_item *tmp = witem;
   1.272 +					wprev->next = witem->next;
   1.273 +					break;
   1.274 +				}
   1.275 +				witem = witem->next;
   1.276 +			}
   1.277 +		}
   1.278 +	}
   1.279 +}
   1.280 +
   1.281 +static void handle_event(struct resman *rman, HANDLE hev, struct watch_dir *wdir)
   1.282 +{
   1.283 +	struct resource *res = 0;
   1.284 +	struct watch_item *witem;
   1.285 +	FILE_NOTIFY_INFORMATION *info;
   1.286 +	DWORD res_size;
   1.287 +
   1.288 +	if(!GetOverlappedResult(hev, &wdir->over, &res_size, FALSE)) {
   1.289 +		return;
   1.290 +	}
   1.291 +
   1.292 +	info = (FILE_NOTIFY_INFORMATION*)wdir->buf;
   1.293 +
   1.294 +	for(;;) {
   1.295 +		if(info->Action == FILE_ACTION_MODIFIED) {
   1.296 +			/*printf("file \"%s\" modified\n", res->name);
   1.297 +			tpool_add_work(rman->tpool, res);*/
   1.298 +
   1.299 +			char *name;
   1.300 +			int len = info->FileNameLength / 2;
   1.301 +			wchar_t *wname = alloca((len + 1) * sizeof *wname);
   1.302 +			memcpy(wname, info->FileName, info->FileNameLength);
   1.303 +			wname[len] = 0;
   1.304 +
   1.305 +			len = wcstombs(0, wname, 0);
   1.306 +			name = alloca(len + 1);
   1.307 +			wcstombs(name, wname, len + 1);
   1.308 +
   1.309 +			printf("MODIFIED: \"%s\"\n", name);
   1.310 +
   1.311 +			witem = wdir->items;
   1.312 +			while(witem) {
   1.313 +				if(strstr(witem->res->name, name)) {
   1.314 +					res = witem->res;
   1.315 +					break;
   1.316 +				}
   1.317 +				witem = witem->next;
   1.318 +			}
   1.319 +			if(!res) {
   1.320 +				fprintf(stderr, "failed to find the modified watch item (%s)\n", name);
   1.321 +			} else {
   1.322 +				/* found the resource, schedule a reload */
   1.323 +				printf("file \"%s\" modified\n", res->name);
   1.324 +				tpool_add_work(rman->tpool, res);
   1.325 +			}
   1.326 +		}
   1.327 +
   1.328 +		if(info->NextEntryOffset) {
   1.329 +			info = (FILE_NOTIFY_INFORMATION*)((char*)info + info->NextEntryOffset);
   1.330 +		} else {
   1.331 +			break;
   1.332 +		}
   1.333  	}
   1.334  }
   1.335  
   1.336  void resman_check_watch(struct resman *rman)
   1.337  {
   1.338 +	struct watch_dir *wdir;
   1.339 +	unsigned int idx;
   1.340 +
   1.341  	unsigned int num_handles = dynarr_size(rman->watch_handles);
   1.342 -	for(;;) {
   1.343 -		struct resource *res;
   1.344 -		unsigned int idx = WaitForMultipleObjects(num_handles, rman->watch_handles, FALSE, 0);
   1.345 -		if(idx < WAIT_OBJECT_0 || idx >= WAIT_OBJECT_0 + num_handles) {
   1.346 -			break;
   1.347 +	if(!num_handles) {
   1.348 +		return;
   1.349 +	}
   1.350 +
   1.351 +	idx = WaitForMultipleObjectsEx(num_handles, rman->watch_handles, FALSE, 0, TRUE);
   1.352 +	if(idx == WAIT_FAILED) {
   1.353 +		unsigned int err = GetLastError();
   1.354 +		fprintf(stderr, "failed to check for file modification: %u\n", err);
   1.355 +		return;
   1.356 +	}
   1.357 +	if(idx >= WAIT_OBJECT_0 && idx < WAIT_OBJECT_0 + num_handles) {
   1.358 +		if(!(wdir = rb_find(rman->wdirbyev, rman->watch_handles[idx]))) {
   1.359 +			fprintf(stderr, "got change handle, but failed to find corresponding watch_dir!\n");
   1.360 +			return;
   1.361  		}
   1.362  
   1.363 -		if(!(res = rb_find(rman->nresmap, rman->watch_handles[idx]))) {
   1.364 -			fprintf(stderr, "got modification event from unknown resource!\n");
   1.365 -			continue;
   1.366 -		}
   1.367 +		handle_event(rman, rman->watch_handles[idx], wdir);
   1.368  
   1.369 -		printf("file \"%s\" modified\n", res->name);
   1.370 -		tpool_add_work(rman->tpool, res);
   1.371 +		/* restart the watch call */
   1.372 +		ReadDirectoryChangesW(wdir->handle, wdir->buf, RES_BUF_SIZE, FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, 0, &wdir->over, 0);
   1.373  	}
   1.374  }
   1.375  
   1.376 +
   1.377 +/* returns a new path buffer which is the equivalent absolute path to "fname"
   1.378 +* the user takes ownership and must free it when done.
   1.379 +*/
   1.380 +static char *abs_path(const char *fname)
   1.381 +{
   1.382 +	if(fname[0] != '/' && fname[1] != ':') {	/* not an absolute path */
   1.383 +		char *path;
   1.384 +		int cwdsz = GetCurrentDirectory(0, 0);
   1.385 +		int pathsz = strlen(fname) + cwdsz + 1;
   1.386 +
   1.387 +		if(!(path = malloc(pathsz + 1))) {
   1.388 +			return 0;
   1.389 +		}
   1.390 +		GetCurrentDirectory(pathsz, path);
   1.391 +
   1.392 +		/* now copy the rest of the path */
   1.393 +		strcat(path, "\\");
   1.394 +		strcat(path, fname);
   1.395 +		return path;
   1.396 +	}
   1.397 +
   1.398 +	return strdup(fname);
   1.399 +}
   1.400 +
   1.401 +static void clean_path(char *path)
   1.402 +{
   1.403 +	char *ptr, *dest;
   1.404 +
   1.405 +	/* first pass, change '/' -> '\\' */
   1.406 +	ptr = path;
   1.407 +	while(*ptr) {
   1.408 +		if(*ptr == '/') *ptr = '\\';
   1.409 +		ptr++;
   1.410 +	}
   1.411 +
   1.412 +	/* now go through the path and remove any \.\ or \..\ parts */
   1.413 +	ptr = dest = path;
   1.414 +	while(*ptr) {
   1.415 +		if(ptr > path && ptr[-1] == '\\') {
   1.416 +			/* we're just after a slash, so any .\ or ..\ here should be purged */
   1.417 +			if(ptr[0] == '.' && ptr[1] == '\\') {
   1.418 +				ptr += 2;
   1.419 +				continue;
   1.420 +			}
   1.421 +			if(ptr[0] == '.' && ptr[1] == '.' && ptr[2] == '\\') {
   1.422 +				/* search backwards for the previous slash */
   1.423 +				dest -= 2;
   1.424 +				while(dest > path && *dest != '\\') {
   1.425 +					dest--;
   1.426 +				}
   1.427 +				if(*dest == '\\') {
   1.428 +					/* found it, continue after this one */
   1.429 +					dest++;
   1.430 +				} /* .. otherwise, reached the beginning, go from there */
   1.431 +
   1.432 +				ptr += 3;
   1.433 +				continue;
   1.434 +			}
   1.435 +		}
   1.436 +		if(dest != ptr) {
   1.437 +			*dest++ = *ptr++;
   1.438 +		} else {
   1.439 +			dest++;
   1.440 +			ptr++;
   1.441 +		}
   1.442 +	}
   1.443 +}
   1.444 +
   1.445 +
   1.446  #else
   1.447  int resman_filewatch_win32_silence_empty_file_warning;
   1.448  #endif	/* WIN32 */
     2.1 --- a/src/resman_impl.h	Thu Feb 13 13:17:07 2014 +0200
     2.2 +++ b/src/resman_impl.h	Thu Feb 13 21:30:05 2014 +0200
     2.3 @@ -28,7 +28,6 @@
     2.4  
     2.5  	/* file change monitoring */
     2.6  #ifdef WIN32
     2.7 -	HANDLE nhandle;
     2.8  	char *watch_path;
     2.9  #endif
    2.10  #ifdef __linux__
    2.11 @@ -58,8 +57,8 @@
    2.12  	struct rbtree *modset;
    2.13  #endif
    2.14  #ifdef WIN32
    2.15 -	struct rbtree *watchdirs;
    2.16 -	HANDLE *watch_handles;	/* dynamic array of all the watch handles */
    2.17 +	struct rbtree *watchdirs, *wdirbyev;
    2.18 +	HANDLE *watch_handles;	/* dynamic array of all the watched directory handles */
    2.19  #endif
    2.20  };
    2.21