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