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 is_path_dir;
161 size_t size;
162 DWORD attr, last_error;
163 WCHAR* dir = NULL, *dir_to_watch, *pathw = NULL;
164 DWORD short_path_buffer_len;
165 WCHAR *short_path_buffer;
166 WCHAR* short_path, *long_path;
167
168 short_path = NULL;
169 if (uv__is_active(handle))
170 return UV_EINVAL;
171
172 handle->cb = cb;
173 handle->path = uv__strdup(path);
174 if (!handle->path) {
175 uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
176 }
177
178 uv__handle_start(handle);
179
180 last_error = uv__convert_utf8_to_utf16(path, &pathw);
181 if (last_error)
182 goto error_uv;
183
184 /* Determine whether path is a file or a directory. */
185 attr = GetFileAttributesW(pathw);
186 if (attr == INVALID_FILE_ATTRIBUTES) {
187 last_error = GetLastError();
188 goto error;
189 }
190
191 is_path_dir = (attr & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0;
192
193 if (is_path_dir) {
194 /* path is a directory, so that's the directory that we will watch. */
195
196 /* Convert to long path. */
197 size = GetLongPathNameW(pathw, NULL, 0);
198
199 if (size) {
200 long_path = (WCHAR*)uv__malloc(size * sizeof(WCHAR));
201 if (!long_path) {
202 uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
203 }
204
205 size = GetLongPathNameW(pathw, long_path, size);
206 if (size) {
207 long_path[size] = '\0';
208 } else {
209 uv__free(long_path);
210 long_path = NULL;
211 }
212
213 if (long_path) {
214 uv__free(pathw);
215 pathw = long_path;
216 }
217 }
218
219 dir_to_watch = pathw;
220 } else {
221 /*
222 * path is a file. So we split path into dir & file parts, and
223 * watch the dir directory.
224 */
225
226 /* Convert to short path. */
227 short_path_buffer = NULL;
228 short_path_buffer_len = GetShortPathNameW(pathw, NULL, 0);
229 if (short_path_buffer_len == 0) {
230 goto short_path_done;
231 }
232 short_path_buffer = uv__malloc(short_path_buffer_len * sizeof(WCHAR));
233 if (short_path_buffer == NULL) {
234 goto short_path_done;
235 }
236 if (GetShortPathNameW(pathw,
237 short_path_buffer,
238 short_path_buffer_len) == 0) {
239 uv__free(short_path_buffer);
240 short_path_buffer = NULL;
241 }
242 short_path_done:
243 short_path = short_path_buffer;
244
245 if (uv__split_path(pathw, &dir, &handle->filew) != 0) {
246 last_error = GetLastError();
247 goto error;
248 }
249
250 if (uv__split_path(short_path, NULL, &handle->short_filew) != 0) {
251 last_error = GetLastError();
252 goto error;
253 }
254
255 dir_to_watch = dir;
256 uv__free(pathw);
257 pathw = NULL;
258 }
259
260 handle->dir_handle = CreateFileW(dir_to_watch,
261 FILE_LIST_DIRECTORY,
262 FILE_SHARE_READ | FILE_SHARE_DELETE |
263 FILE_SHARE_WRITE,
264 NULL,
265 OPEN_EXISTING,
266 FILE_FLAG_BACKUP_SEMANTICS |
267 FILE_FLAG_OVERLAPPED,
268 NULL);
269
270 if (dir) {
271 uv__free(dir);
272 dir = NULL;
273 }
274
275 if (handle->dir_handle == INVALID_HANDLE_VALUE) {
276 last_error = GetLastError();
277 goto error;
278 }
279
280 if (CreateIoCompletionPort(handle->dir_handle,
281 handle->loop->iocp,
282 (ULONG_PTR)handle,
283 0) == NULL) {
284 last_error = GetLastError();
285 goto error;
286 }
287
288 if (!handle->buffer) {
289 handle->buffer = (char*)uv__malloc(uv_directory_watcher_buffer_size);
290 }
291 if (!handle->buffer) {
292 uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
293 }
294
295 memset(&(handle->req.u.io.overlapped), 0,
296 sizeof(handle->req.u.io.overlapped));
297
298 if (!ReadDirectoryChangesW(handle->dir_handle,
299 handle->buffer,
300 uv_directory_watcher_buffer_size,
301 (flags & UV_FS_EVENT_RECURSIVE) ? TRUE : FALSE,
302 FILE_NOTIFY_CHANGE_FILE_NAME |
303 FILE_NOTIFY_CHANGE_DIR_NAME |
304 FILE_NOTIFY_CHANGE_ATTRIBUTES |
305 FILE_NOTIFY_CHANGE_SIZE |
306 FILE_NOTIFY_CHANGE_LAST_WRITE |
307 FILE_NOTIFY_CHANGE_LAST_ACCESS |
308 FILE_NOTIFY_CHANGE_CREATION |
309 FILE_NOTIFY_CHANGE_SECURITY,
310 NULL,
311 &handle->req.u.io.overlapped,
312 NULL)) {
313 last_error = GetLastError();
314 goto error;
315 }
316
317 assert(is_path_dir ? pathw != NULL : pathw == NULL);
318 handle->dirw = pathw;
319 handle->req_pending = 1;
320 return 0;
321
322 error:
323 last_error = uv_translate_sys_error(last_error);
324
325 error_uv:
326 if (handle->path) {
327 uv__free(handle->path);
328 handle->path = NULL;
329 }
330
331 if (handle->filew) {
332 uv__free(handle->filew);
333 handle->filew = NULL;
334 }
335
336 if (handle->short_filew) {
337 uv__free(handle->short_filew);
338 handle->short_filew = NULL;
339 }
340
341 uv__free(pathw);
342
343 if (handle->dir_handle != INVALID_HANDLE_VALUE) {
344 CloseHandle(handle->dir_handle);
345 handle->dir_handle = INVALID_HANDLE_VALUE;
346 }
347
348 if (handle->buffer) {
349 uv__free(handle->buffer);
350 handle->buffer = NULL;
351 }
352
353 if (uv__is_active(handle))
354 uv__handle_stop(handle);
355
356 uv__free(short_path);
357
358 return last_error;
359 }
360
361
uv_fs_event_stop(uv_fs_event_t * handle)362 int uv_fs_event_stop(uv_fs_event_t* handle) {
363 if (!uv__is_active(handle))
364 return 0;
365
366 if (handle->dir_handle != INVALID_HANDLE_VALUE) {
367 CloseHandle(handle->dir_handle);
368 handle->dir_handle = INVALID_HANDLE_VALUE;
369 }
370
371 uv__handle_stop(handle);
372
373 if (handle->filew) {
374 uv__free(handle->filew);
375 handle->filew = NULL;
376 }
377
378 if (handle->short_filew) {
379 uv__free(handle->short_filew);
380 handle->short_filew = NULL;
381 }
382
383 if (handle->path) {
384 uv__free(handle->path);
385 handle->path = NULL;
386 }
387
388 if (handle->dirw) {
389 uv__free(handle->dirw);
390 handle->dirw = NULL;
391 }
392
393 return 0;
394 }
395
396
file_info_cmp(WCHAR * str,WCHAR * file_name,size_t file_name_len)397 static int file_info_cmp(WCHAR* str, WCHAR* file_name, size_t file_name_len) {
398 size_t str_len;
399
400 if (str == NULL)
401 return -1;
402
403 str_len = wcslen(str);
404
405 /*
406 Since we only care about equality, return early if the strings
407 aren't the same length
408 */
409 if (str_len != (file_name_len / sizeof(WCHAR)))
410 return -1;
411
412 return _wcsnicmp(str, file_name, str_len);
413 }
414
415
uv__process_fs_event_req(uv_loop_t * loop,uv_req_t * req,uv_fs_event_t * handle)416 void uv__process_fs_event_req(uv_loop_t* loop, uv_req_t* req,
417 uv_fs_event_t* handle) {
418 FILE_NOTIFY_INFORMATION* file_info;
419 int err, sizew, size;
420 char* filename = NULL;
421 WCHAR* filenamew = NULL;
422 WCHAR* long_filenamew = NULL;
423 DWORD offset = 0;
424
425 assert(req->type == UV_FS_EVENT_REQ);
426 assert(handle->req_pending);
427 handle->req_pending = 0;
428
429 /* Don't report any callbacks if:
430 * - We're closing, just push the handle onto the endgame queue
431 * - We are not active, just ignore the callback
432 */
433 if (!uv__is_active(handle)) {
434 if (handle->flags & UV_HANDLE_CLOSING) {
435 uv__want_endgame(loop, (uv_handle_t*) handle);
436 }
437 return;
438 }
439
440 file_info = (FILE_NOTIFY_INFORMATION*)(handle->buffer + offset);
441
442 if (REQ_SUCCESS(req)) {
443 if (req->u.io.overlapped.InternalHigh > 0) {
444 do {
445 file_info = (FILE_NOTIFY_INFORMATION*)((char*)file_info + offset);
446 assert(!filename);
447 assert(!filenamew);
448 assert(!long_filenamew);
449
450 /*
451 * Fire the event only if we were asked to watch a directory,
452 * or if the filename filter matches.
453 */
454 if (handle->dirw ||
455 file_info_cmp(handle->filew,
456 file_info->FileName,
457 file_info->FileNameLength) == 0 ||
458 file_info_cmp(handle->short_filew,
459 file_info->FileName,
460 file_info->FileNameLength) == 0) {
461
462 if (handle->dirw) {
463 /*
464 * We attempt to resolve the long form of the file name explicitly.
465 * We only do this for file names that might still exist on disk.
466 * If this fails, we use the name given by ReadDirectoryChangesW.
467 * This may be the long form or the 8.3 short name in some cases.
468 */
469 if (file_info->Action != FILE_ACTION_REMOVED &&
470 file_info->Action != FILE_ACTION_RENAMED_OLD_NAME) {
471 /* Construct a full path to the file. */
472 size = wcslen(handle->dirw) +
473 file_info->FileNameLength / sizeof(WCHAR) + 2;
474
475 filenamew = (WCHAR*)uv__malloc(size * sizeof(WCHAR));
476 if (!filenamew) {
477 uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
478 }
479
480 _snwprintf(filenamew, size, L"%s\\%.*s", handle->dirw,
481 file_info->FileNameLength / (DWORD)sizeof(WCHAR),
482 file_info->FileName);
483
484 filenamew[size - 1] = L'\0';
485
486 /* Convert to long name. */
487 size = GetLongPathNameW(filenamew, NULL, 0);
488
489 if (size) {
490 long_filenamew = (WCHAR*)uv__malloc(size * sizeof(WCHAR));
491 if (!long_filenamew) {
492 uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
493 }
494
495 size = GetLongPathNameW(filenamew, long_filenamew, size);
496 if (size) {
497 long_filenamew[size] = '\0';
498 } else {
499 uv__free(long_filenamew);
500 long_filenamew = NULL;
501 }
502 }
503
504 uv__free(filenamew);
505
506 if (long_filenamew) {
507 /* Get the file name out of the long path. */
508 uv__relative_path(long_filenamew,
509 handle->dirw,
510 &filenamew);
511 uv__free(long_filenamew);
512 long_filenamew = filenamew;
513 sizew = -1;
514 } else {
515 /* We couldn't get the long filename, use the one reported. */
516 filenamew = file_info->FileName;
517 sizew = file_info->FileNameLength / sizeof(WCHAR);
518 }
519 } else {
520 /*
521 * Removed or renamed events cannot be resolved to the long form.
522 * We therefore use the name given by ReadDirectoryChangesW.
523 * This may be the long form or the 8.3 short name in some cases.
524 */
525 filenamew = file_info->FileName;
526 sizew = file_info->FileNameLength / sizeof(WCHAR);
527 }
528 } else {
529 /* We already have the long name of the file, so just use it. */
530 filenamew = handle->filew;
531 sizew = -1;
532 }
533
534 /* Convert the filename to utf8. */
535 uv__convert_utf16_to_utf8(filenamew, sizew, &filename);
536
537 switch (file_info->Action) {
538 case FILE_ACTION_ADDED:
539 case FILE_ACTION_REMOVED:
540 case FILE_ACTION_RENAMED_OLD_NAME:
541 case FILE_ACTION_RENAMED_NEW_NAME:
542 handle->cb(handle, filename, UV_RENAME, 0);
543 break;
544
545 case FILE_ACTION_MODIFIED:
546 handle->cb(handle, filename, UV_CHANGE, 0);
547 break;
548 }
549
550 uv__free(filename);
551 filename = NULL;
552 uv__free(long_filenamew);
553 long_filenamew = NULL;
554 filenamew = NULL;
555 }
556
557 offset = file_info->NextEntryOffset;
558 } while (offset && !(handle->flags & UV_HANDLE_CLOSING));
559 } else {
560 handle->cb(handle, NULL, UV_CHANGE, 0);
561 }
562 } else {
563 err = GET_REQ_ERROR(req);
564 /*
565 * Check whether the ERROR_ACCESS_DENIED is caused by the watched directory
566 * being actually deleted (not an actual error) or a legit error. Retrieve
567 * FileStandardInfo to check whether the directory is pending deletion.
568 */
569 FILE_STANDARD_INFO info;
570 if (err == ERROR_ACCESS_DENIED &&
571 handle->dirw != NULL &&
572 GetFileInformationByHandleEx(handle->dir_handle,
573 FileStandardInfo,
574 &info,
575 sizeof(info)) &&
576 info.Directory &&
577 info.DeletePending) {
578 uv__convert_utf16_to_utf8(handle->dirw, -1, &filename);
579 handle->cb(handle, filename, UV_RENAME, 0);
580 } else {
581 handle->cb(handle, NULL, 0, uv_translate_sys_error(err));
582 }
583 }
584
585 if (handle->flags & UV_HANDLE_CLOSING) {
586 uv__want_endgame(loop, (uv_handle_t*)handle);
587 } else if (uv__is_active(handle)) {
588 uv__fs_event_queue_readdirchanges(loop, handle);
589 }
590 }
591
592
uv__fs_event_close(uv_loop_t * loop,uv_fs_event_t * handle)593 void uv__fs_event_close(uv_loop_t* loop, uv_fs_event_t* handle) {
594 uv_fs_event_stop(handle);
595
596 uv__handle_closing(handle);
597
598 if (!handle->req_pending) {
599 uv__want_endgame(loop, (uv_handle_t*)handle);
600 }
601
602 }
603
604
uv__fs_event_endgame(uv_loop_t * loop,uv_fs_event_t * handle)605 void uv__fs_event_endgame(uv_loop_t* loop, uv_fs_event_t* handle) {
606 if ((handle->flags & UV_HANDLE_CLOSING) && !handle->req_pending) {
607 assert(!(handle->flags & UV_HANDLE_CLOSED));
608
609 if (handle->buffer) {
610 uv__free(handle->buffer);
611 handle->buffer = NULL;
612 }
613
614 uv__handle_close(handle);
615 }
616 }
617