xref: /libuv/src/win/fs-event.c (revision ec5a4b54)
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