xref: /third_party/node/deps/uv/src/win/fs-event.c (revision 1cb0ef41)
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
33const unsigned int uv_directory_watcher_buffer_size = 4096;
34
35
36static 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
66static 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
83static 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
140int 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
156int 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    }
255short_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
335error:
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
372int 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
407static 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
426void 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
585void 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
597void 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