1 /* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
2 *
3 * Permission is hereby granted, free of charge, to any person obtaining a copy
4 * of this software and associated documentation files (the "Software"), to
5 * deal in the Software without restriction, including without limitation the
6 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7 * sell copies of the Software, and to permit persons to whom the Software is
8 * furnished to do so, subject to the following conditions:
9 *
10 * The above copyright notice and this permission notice shall be included in
11 * all copies or substantial portions of the Software.
12 *
13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19 * IN THE SOFTWARE.
20 */
21
22 #include <assert.h>
23 #include <errno.h>
24 #include <stdio.h>
25 #include <string.h>
26
27 #include "uv.h"
28 #include "internal.h"
29 #include "handle-inl.h"
30 #include "req-inl.h"
31
32
33 const unsigned int uv_directory_watcher_buffer_size = 4096;
34
35
uv__fs_event_queue_readdirchanges(uv_loop_t * loop,uv_fs_event_t * handle)36 static void uv__fs_event_queue_readdirchanges(uv_loop_t* loop,
37 uv_fs_event_t* handle) {
38 assert(handle->dir_handle != INVALID_HANDLE_VALUE);
39 assert(!handle->req_pending);
40
41 memset(&(handle->req.u.io.overlapped), 0,
42 sizeof(handle->req.u.io.overlapped));
43 if (!ReadDirectoryChangesW(handle->dir_handle,
44 handle->buffer,
45 uv_directory_watcher_buffer_size,
46 (handle->flags & UV_FS_EVENT_RECURSIVE) ? TRUE : FALSE,
47 FILE_NOTIFY_CHANGE_FILE_NAME |
48 FILE_NOTIFY_CHANGE_DIR_NAME |
49 FILE_NOTIFY_CHANGE_ATTRIBUTES |
50 FILE_NOTIFY_CHANGE_SIZE |
51 FILE_NOTIFY_CHANGE_LAST_WRITE |
52 FILE_NOTIFY_CHANGE_LAST_ACCESS |
53 FILE_NOTIFY_CHANGE_CREATION |
54 FILE_NOTIFY_CHANGE_SECURITY,
55 NULL,
56 &handle->req.u.io.overlapped,
57 NULL)) {
58 /* Make this req pending reporting an error. */
59 SET_REQ_ERROR(&handle->req, GetLastError());
60 uv__insert_pending_req(loop, (uv_req_t*)&handle->req);
61 }
62
63 handle->req_pending = 1;
64 }
65
uv__relative_path(const WCHAR * filename,const WCHAR * dir,WCHAR ** relpath)66 static void uv__relative_path(const WCHAR* filename,
67 const WCHAR* dir,
68 WCHAR** relpath) {
69 size_t relpathlen;
70 size_t filenamelen = wcslen(filename);
71 size_t dirlen = wcslen(dir);
72 assert(!_wcsnicmp(filename, dir, dirlen));
73 if (dirlen > 0 && dir[dirlen - 1] == '\\')
74 dirlen--;
75 relpathlen = filenamelen - dirlen - 1;
76 *relpath = uv__malloc((relpathlen + 1) * sizeof(WCHAR));
77 if (!*relpath)
78 uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
79 wcsncpy(*relpath, filename + dirlen + 1, relpathlen);
80 (*relpath)[relpathlen] = L'\0';
81 }
82
uv__split_path(const WCHAR * filename,WCHAR ** dir,WCHAR ** file)83 static int uv__split_path(const WCHAR* filename, WCHAR** dir,
84 WCHAR** file) {
85 size_t len, i;
86 DWORD dir_len;
87
88 if (filename == NULL) {
89 if (dir != NULL)
90 *dir = NULL;
91 *file = NULL;
92 return 0;
93 }
94
95 len = wcslen(filename);
96 i = len;
97 while (i > 0 && filename[--i] != '\\' && filename[i] != '/');
98
99 if (i == 0) {
100 if (dir) {
101 dir_len = GetCurrentDirectoryW(0, NULL);
102 if (dir_len == 0) {
103 return -1;
104 }
105 *dir = (WCHAR*)uv__malloc(dir_len * sizeof(WCHAR));
106 if (!*dir) {
107 uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
108 }
109
110 if (!GetCurrentDirectoryW(dir_len, *dir)) {
111 uv__free(*dir);
112 *dir = NULL;
113 return -1;
114 }
115 }
116
117 *file = wcsdup(filename);
118 } else {
119 if (dir) {
120 *dir = (WCHAR*)uv__malloc((i + 2) * sizeof(WCHAR));
121 if (!*dir) {
122 uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
123 }
124 wcsncpy(*dir, filename, i + 1);
125 (*dir)[i + 1] = L'\0';
126 }
127
128 *file = (WCHAR*)uv__malloc((len - i) * sizeof(WCHAR));
129 if (!*file) {
130 uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
131 }
132 wcsncpy(*file, filename + i + 1, len - i - 1);
133 (*file)[len - i - 1] = L'\0';
134 }
135
136 return 0;
137 }
138
139
uv_fs_event_init(uv_loop_t * loop,uv_fs_event_t * handle)140 int uv_fs_event_init(uv_loop_t* loop, uv_fs_event_t* handle) {
141 uv__handle_init(loop, (uv_handle_t*) handle, UV_FS_EVENT);
142 handle->dir_handle = INVALID_HANDLE_VALUE;
143 handle->buffer = NULL;
144 handle->req_pending = 0;
145 handle->filew = NULL;
146 handle->short_filew = NULL;
147 handle->dirw = NULL;
148
149 UV_REQ_INIT(&handle->req, UV_FS_EVENT_REQ);
150 handle->req.data = handle;
151
152 return 0;
153 }
154
155
uv_fs_event_start(uv_fs_event_t * handle,uv_fs_event_cb cb,const char * path,unsigned int flags)156 int uv_fs_event_start(uv_fs_event_t* handle,
157 uv_fs_event_cb cb,
158 const char* path,
159 unsigned int flags) {
160 int name_size, is_path_dir, size;
161 DWORD attr, last_error;
162 WCHAR* dir = NULL, *dir_to_watch, *pathw = NULL;
163 DWORD short_path_buffer_len;
164 WCHAR *short_path_buffer;
165 WCHAR* short_path, *long_path;
166
167 short_path = NULL;
168 if (uv__is_active(handle))
169 return UV_EINVAL;
170
171 handle->cb = cb;
172 handle->path = uv__strdup(path);
173 if (!handle->path) {
174 uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
175 }
176
177 uv__handle_start(handle);
178
179 /* Convert name to UTF16. */
180
181 name_size = MultiByteToWideChar(CP_UTF8, 0, path, -1, NULL, 0) *
182 sizeof(WCHAR);
183 pathw = (WCHAR*)uv__malloc(name_size);
184 if (!pathw) {
185 uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
186 }
187
188 if (!MultiByteToWideChar(CP_UTF8,
189 0,
190 path,
191 -1,
192 pathw,
193 name_size / sizeof(WCHAR))) {
194 return uv_translate_sys_error(GetLastError());
195 }
196
197 /* Determine whether path is a file or a directory. */
198 attr = GetFileAttributesW(pathw);
199 if (attr == INVALID_FILE_ATTRIBUTES) {
200 last_error = GetLastError();
201 goto error;
202 }
203
204 is_path_dir = (attr & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0;
205
206 if (is_path_dir) {
207 /* path is a directory, so that's the directory that we will watch. */
208
209 /* Convert to long path. */
210 size = GetLongPathNameW(pathw, NULL, 0);
211
212 if (size) {
213 long_path = (WCHAR*)uv__malloc(size * sizeof(WCHAR));
214 if (!long_path) {
215 uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
216 }
217
218 size = GetLongPathNameW(pathw, long_path, size);
219 if (size) {
220 long_path[size] = '\0';
221 } else {
222 uv__free(long_path);
223 long_path = NULL;
224 }
225
226 if (long_path) {
227 uv__free(pathw);
228 pathw = long_path;
229 }
230 }
231
232 dir_to_watch = pathw;
233 } else {
234 /*
235 * path is a file. So we split path into dir & file parts, and
236 * watch the dir directory.
237 */
238
239 /* Convert to short path. */
240 short_path_buffer = NULL;
241 short_path_buffer_len = GetShortPathNameW(pathw, NULL, 0);
242 if (short_path_buffer_len == 0) {
243 goto short_path_done;
244 }
245 short_path_buffer = uv__malloc(short_path_buffer_len * sizeof(WCHAR));
246 if (short_path_buffer == NULL) {
247 goto short_path_done;
248 }
249 if (GetShortPathNameW(pathw,
250 short_path_buffer,
251 short_path_buffer_len) == 0) {
252 uv__free(short_path_buffer);
253 short_path_buffer = NULL;
254 }
255 short_path_done:
256 short_path = short_path_buffer;
257
258 if (uv__split_path(pathw, &dir, &handle->filew) != 0) {
259 last_error = GetLastError();
260 goto error;
261 }
262
263 if (uv__split_path(short_path, NULL, &handle->short_filew) != 0) {
264 last_error = GetLastError();
265 goto error;
266 }
267
268 dir_to_watch = dir;
269 uv__free(pathw);
270 pathw = NULL;
271 }
272
273 handle->dir_handle = CreateFileW(dir_to_watch,
274 FILE_LIST_DIRECTORY,
275 FILE_SHARE_READ | FILE_SHARE_DELETE |
276 FILE_SHARE_WRITE,
277 NULL,
278 OPEN_EXISTING,
279 FILE_FLAG_BACKUP_SEMANTICS |
280 FILE_FLAG_OVERLAPPED,
281 NULL);
282
283 if (dir) {
284 uv__free(dir);
285 dir = NULL;
286 }
287
288 if (handle->dir_handle == INVALID_HANDLE_VALUE) {
289 last_error = GetLastError();
290 goto error;
291 }
292
293 if (CreateIoCompletionPort(handle->dir_handle,
294 handle->loop->iocp,
295 (ULONG_PTR)handle,
296 0) == NULL) {
297 last_error = GetLastError();
298 goto error;
299 }
300
301 if (!handle->buffer) {
302 handle->buffer = (char*)uv__malloc(uv_directory_watcher_buffer_size);
303 }
304 if (!handle->buffer) {
305 uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
306 }
307
308 memset(&(handle->req.u.io.overlapped), 0,
309 sizeof(handle->req.u.io.overlapped));
310
311 if (!ReadDirectoryChangesW(handle->dir_handle,
312 handle->buffer,
313 uv_directory_watcher_buffer_size,
314 (flags & UV_FS_EVENT_RECURSIVE) ? TRUE : FALSE,
315 FILE_NOTIFY_CHANGE_FILE_NAME |
316 FILE_NOTIFY_CHANGE_DIR_NAME |
317 FILE_NOTIFY_CHANGE_ATTRIBUTES |
318 FILE_NOTIFY_CHANGE_SIZE |
319 FILE_NOTIFY_CHANGE_LAST_WRITE |
320 FILE_NOTIFY_CHANGE_LAST_ACCESS |
321 FILE_NOTIFY_CHANGE_CREATION |
322 FILE_NOTIFY_CHANGE_SECURITY,
323 NULL,
324 &handle->req.u.io.overlapped,
325 NULL)) {
326 last_error = GetLastError();
327 goto error;
328 }
329
330 assert(is_path_dir ? pathw != NULL : pathw == NULL);
331 handle->dirw = pathw;
332 handle->req_pending = 1;
333 return 0;
334
335 error:
336 if (handle->path) {
337 uv__free(handle->path);
338 handle->path = NULL;
339 }
340
341 if (handle->filew) {
342 uv__free(handle->filew);
343 handle->filew = NULL;
344 }
345
346 if (handle->short_filew) {
347 uv__free(handle->short_filew);
348 handle->short_filew = NULL;
349 }
350
351 uv__free(pathw);
352
353 if (handle->dir_handle != INVALID_HANDLE_VALUE) {
354 CloseHandle(handle->dir_handle);
355 handle->dir_handle = INVALID_HANDLE_VALUE;
356 }
357
358 if (handle->buffer) {
359 uv__free(handle->buffer);
360 handle->buffer = NULL;
361 }
362
363 if (uv__is_active(handle))
364 uv__handle_stop(handle);
365
366 uv__free(short_path);
367
368 return uv_translate_sys_error(last_error);
369 }
370
371
uv_fs_event_stop(uv_fs_event_t * handle)372 int uv_fs_event_stop(uv_fs_event_t* handle) {
373 if (!uv__is_active(handle))
374 return 0;
375
376 if (handle->dir_handle != INVALID_HANDLE_VALUE) {
377 CloseHandle(handle->dir_handle);
378 handle->dir_handle = INVALID_HANDLE_VALUE;
379 }
380
381 uv__handle_stop(handle);
382
383 if (handle->filew) {
384 uv__free(handle->filew);
385 handle->filew = NULL;
386 }
387
388 if (handle->short_filew) {
389 uv__free(handle->short_filew);
390 handle->short_filew = NULL;
391 }
392
393 if (handle->path) {
394 uv__free(handle->path);
395 handle->path = NULL;
396 }
397
398 if (handle->dirw) {
399 uv__free(handle->dirw);
400 handle->dirw = NULL;
401 }
402
403 return 0;
404 }
405
406
file_info_cmp(WCHAR * str,WCHAR * file_name,size_t file_name_len)407 static int file_info_cmp(WCHAR* str, WCHAR* file_name, size_t file_name_len) {
408 size_t str_len;
409
410 if (str == NULL)
411 return -1;
412
413 str_len = wcslen(str);
414
415 /*
416 Since we only care about equality, return early if the strings
417 aren't the same length
418 */
419 if (str_len != (file_name_len / sizeof(WCHAR)))
420 return -1;
421
422 return _wcsnicmp(str, file_name, str_len);
423 }
424
425
uv__process_fs_event_req(uv_loop_t * loop,uv_req_t * req,uv_fs_event_t * handle)426 void uv__process_fs_event_req(uv_loop_t* loop, uv_req_t* req,
427 uv_fs_event_t* handle) {
428 FILE_NOTIFY_INFORMATION* file_info;
429 int err, sizew, size;
430 char* filename = NULL;
431 WCHAR* filenamew = NULL;
432 WCHAR* long_filenamew = NULL;
433 DWORD offset = 0;
434
435 assert(req->type == UV_FS_EVENT_REQ);
436 assert(handle->req_pending);
437 handle->req_pending = 0;
438
439 /* Don't report any callbacks if:
440 * - We're closing, just push the handle onto the endgame queue
441 * - We are not active, just ignore the callback
442 */
443 if (!uv__is_active(handle)) {
444 if (handle->flags & UV_HANDLE_CLOSING) {
445 uv__want_endgame(loop, (uv_handle_t*) handle);
446 }
447 return;
448 }
449
450 file_info = (FILE_NOTIFY_INFORMATION*)(handle->buffer + offset);
451
452 if (REQ_SUCCESS(req)) {
453 if (req->u.io.overlapped.InternalHigh > 0) {
454 do {
455 file_info = (FILE_NOTIFY_INFORMATION*)((char*)file_info + offset);
456 assert(!filename);
457 assert(!filenamew);
458 assert(!long_filenamew);
459
460 /*
461 * Fire the event only if we were asked to watch a directory,
462 * or if the filename filter matches.
463 */
464 if (handle->dirw ||
465 file_info_cmp(handle->filew,
466 file_info->FileName,
467 file_info->FileNameLength) == 0 ||
468 file_info_cmp(handle->short_filew,
469 file_info->FileName,
470 file_info->FileNameLength) == 0) {
471
472 if (handle->dirw) {
473 /*
474 * We attempt to resolve the long form of the file name explicitly.
475 * We only do this for file names that might still exist on disk.
476 * If this fails, we use the name given by ReadDirectoryChangesW.
477 * This may be the long form or the 8.3 short name in some cases.
478 */
479 if (file_info->Action != FILE_ACTION_REMOVED &&
480 file_info->Action != FILE_ACTION_RENAMED_OLD_NAME) {
481 /* Construct a full path to the file. */
482 size = wcslen(handle->dirw) +
483 file_info->FileNameLength / sizeof(WCHAR) + 2;
484
485 filenamew = (WCHAR*)uv__malloc(size * sizeof(WCHAR));
486 if (!filenamew) {
487 uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
488 }
489
490 _snwprintf(filenamew, size, L"%s\\%.*s", handle->dirw,
491 file_info->FileNameLength / (DWORD)sizeof(WCHAR),
492 file_info->FileName);
493
494 filenamew[size - 1] = L'\0';
495
496 /* Convert to long name. */
497 size = GetLongPathNameW(filenamew, NULL, 0);
498
499 if (size) {
500 long_filenamew = (WCHAR*)uv__malloc(size * sizeof(WCHAR));
501 if (!long_filenamew) {
502 uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
503 }
504
505 size = GetLongPathNameW(filenamew, long_filenamew, size);
506 if (size) {
507 long_filenamew[size] = '\0';
508 } else {
509 uv__free(long_filenamew);
510 long_filenamew = NULL;
511 }
512 }
513
514 uv__free(filenamew);
515
516 if (long_filenamew) {
517 /* Get the file name out of the long path. */
518 uv__relative_path(long_filenamew,
519 handle->dirw,
520 &filenamew);
521 uv__free(long_filenamew);
522 long_filenamew = filenamew;
523 sizew = -1;
524 } else {
525 /* We couldn't get the long filename, use the one reported. */
526 filenamew = file_info->FileName;
527 sizew = file_info->FileNameLength / sizeof(WCHAR);
528 }
529 } else {
530 /*
531 * Removed or renamed events cannot be resolved to the long form.
532 * We therefore use the name given by ReadDirectoryChangesW.
533 * This may be the long form or the 8.3 short name in some cases.
534 */
535 filenamew = file_info->FileName;
536 sizew = file_info->FileNameLength / sizeof(WCHAR);
537 }
538 } else {
539 /* We already have the long name of the file, so just use it. */
540 filenamew = handle->filew;
541 sizew = -1;
542 }
543
544 /* Convert the filename to utf8. */
545 uv__convert_utf16_to_utf8(filenamew, sizew, &filename);
546
547 switch (file_info->Action) {
548 case FILE_ACTION_ADDED:
549 case FILE_ACTION_REMOVED:
550 case FILE_ACTION_RENAMED_OLD_NAME:
551 case FILE_ACTION_RENAMED_NEW_NAME:
552 handle->cb(handle, filename, UV_RENAME, 0);
553 break;
554
555 case FILE_ACTION_MODIFIED:
556 handle->cb(handle, filename, UV_CHANGE, 0);
557 break;
558 }
559
560 uv__free(filename);
561 filename = NULL;
562 uv__free(long_filenamew);
563 long_filenamew = NULL;
564 filenamew = NULL;
565 }
566
567 offset = file_info->NextEntryOffset;
568 } while (offset && !(handle->flags & UV_HANDLE_CLOSING));
569 } else {
570 handle->cb(handle, NULL, UV_CHANGE, 0);
571 }
572 } else {
573 err = GET_REQ_ERROR(req);
574 handle->cb(handle, NULL, 0, uv_translate_sys_error(err));
575 }
576
577 if (handle->flags & UV_HANDLE_CLOSING) {
578 uv__want_endgame(loop, (uv_handle_t*)handle);
579 } else if (uv__is_active(handle)) {
580 uv__fs_event_queue_readdirchanges(loop, handle);
581 }
582 }
583
584
uv__fs_event_close(uv_loop_t * loop,uv_fs_event_t * handle)585 void uv__fs_event_close(uv_loop_t* loop, uv_fs_event_t* handle) {
586 uv_fs_event_stop(handle);
587
588 uv__handle_closing(handle);
589
590 if (!handle->req_pending) {
591 uv__want_endgame(loop, (uv_handle_t*)handle);
592 }
593
594 }
595
596
uv__fs_event_endgame(uv_loop_t * loop,uv_fs_event_t * handle)597 void uv__fs_event_endgame(uv_loop_t* loop, uv_fs_event_t* handle) {
598 if ((handle->flags & UV_HANDLE_CLOSING) && !handle->req_pending) {
599 assert(!(handle->flags & UV_HANDLE_CLOSED));
600
601 if (handle->buffer) {
602 uv__free(handle->buffer);
603 handle->buffer = NULL;
604 }
605
606 uv__handle_close(handle);
607 }
608 }
609