1 /* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
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:
10 * The above copyright notice and this permission notice shall be included in
11 * all copies or substantial portions of the Software.
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
30 #include "handle-inl.h"
34 const unsigned int uv_directory_watcher_buffer_size
= 4096;
37 static void uv_fs_event_init_handle(uv_loop_t
* loop
, uv_fs_event_t
* handle
,
38 const char* filename
, uv_fs_event_cb cb
) {
39 uv__handle_init(loop
, (uv_handle_t
*) handle
, UV_FS_EVENT
);
41 handle
->dir_handle
= INVALID_HANDLE_VALUE
;
42 handle
->buffer
= NULL
;
43 handle
->req_pending
= 0;
45 handle
->short_filew
= NULL
;
48 uv_req_init(loop
, (uv_req_t
*)&handle
->req
);
49 handle
->req
.type
= UV_FS_EVENT_REQ
;
50 handle
->req
.data
= (void*)handle
;
52 handle
->filename
= strdup(filename
);
53 if (!handle
->filename
) {
54 uv_fatal_error(ERROR_OUTOFMEMORY
, "malloc");
57 uv__handle_start(handle
);
61 static void uv_fs_event_queue_readdirchanges(uv_loop_t
* loop
,
62 uv_fs_event_t
* handle
) {
63 assert(handle
->dir_handle
!= INVALID_HANDLE_VALUE
);
64 assert(!handle
->req_pending
);
66 memset(&(handle
->req
.overlapped
), 0, sizeof(handle
->req
.overlapped
));
67 if (!ReadDirectoryChangesW(handle
->dir_handle
,
69 uv_directory_watcher_buffer_size
,
71 FILE_NOTIFY_CHANGE_FILE_NAME
|
72 FILE_NOTIFY_CHANGE_DIR_NAME
|
73 FILE_NOTIFY_CHANGE_ATTRIBUTES
|
74 FILE_NOTIFY_CHANGE_SIZE
|
75 FILE_NOTIFY_CHANGE_LAST_WRITE
|
76 FILE_NOTIFY_CHANGE_LAST_ACCESS
|
77 FILE_NOTIFY_CHANGE_CREATION
|
78 FILE_NOTIFY_CHANGE_SECURITY
,
80 &handle
->req
.overlapped
,
82 /* Make this req pending reporting an error. */
83 SET_REQ_ERROR(&handle
->req
, GetLastError());
84 uv_insert_pending_req(loop
, (uv_req_t
*)&handle
->req
);
87 handle
->req_pending
= 1;
91 static int uv_split_path(const WCHAR
* filename
, WCHAR
** dir
,
93 int len
= wcslen(filename
);
95 while (i
> 0 && filename
[--i
] != '\\' && filename
[i
] != '/');
99 *dir
= (WCHAR
*)malloc((MAX_PATH
+ 1) * sizeof(WCHAR
));
101 uv_fatal_error(ERROR_OUTOFMEMORY
, "malloc");
104 if (!GetCurrentDirectoryW(MAX_PATH
, *dir
)) {
111 *file
= wcsdup(filename
);
114 *dir
= (WCHAR
*)malloc((i
+ 1) * sizeof(WCHAR
));
116 uv_fatal_error(ERROR_OUTOFMEMORY
, "malloc");
118 wcsncpy(*dir
, filename
, i
);
122 *file
= (WCHAR
*)malloc((len
- i
) * sizeof(WCHAR
));
124 uv_fatal_error(ERROR_OUTOFMEMORY
, "malloc");
126 wcsncpy(*file
, filename
+ i
+ 1, len
- i
- 1);
127 (*file
)[len
- i
- 1] = L
'\0';
134 int uv_fs_event_init(uv_loop_t
* loop
, uv_fs_event_t
* handle
,
135 const char* filename
, uv_fs_event_cb cb
, int flags
) {
136 int name_size
, is_path_dir
;
137 DWORD attr
, last_error
;
138 WCHAR
* dir
= NULL
, *dir_to_watch
, *filenamew
= NULL
;
139 WCHAR short_path
[MAX_PATH
];
141 uv_fs_event_init_handle(loop
, handle
, filename
, cb
);
143 /* Convert name to UTF16. */
144 name_size
= uv_utf8_to_utf16(filename
, NULL
, 0) * sizeof(WCHAR
);
145 filenamew
= (WCHAR
*)malloc(name_size
);
147 uv_fatal_error(ERROR_OUTOFMEMORY
, "malloc");
150 if (!uv_utf8_to_utf16(filename
, filenamew
,
151 name_size
/ sizeof(WCHAR
))) {
152 uv__set_sys_error(loop
, GetLastError());
156 /* Determine whether filename is a file or a directory. */
157 attr
= GetFileAttributesW(filenamew
);
158 if (attr
== INVALID_FILE_ATTRIBUTES
) {
159 last_error
= GetLastError();
163 is_path_dir
= (attr
& FILE_ATTRIBUTE_DIRECTORY
) ? 1 : 0;
166 /* filename is a directory, so that's the directory that we will watch. */
167 handle
->dirw
= filenamew
;
168 dir_to_watch
= filenamew
;
171 * filename is a file. So we split filename into dir & file parts, and
172 * watch the dir directory.
175 /* Convert to short path. */
176 if (!GetShortPathNameW(filenamew
, short_path
, ARRAY_SIZE(short_path
))) {
177 last_error
= GetLastError();
181 if (uv_split_path(filenamew
, &dir
, &handle
->filew
) != 0) {
182 last_error
= GetLastError();
186 if (uv_split_path(short_path
, NULL
, &handle
->short_filew
) != 0) {
187 last_error
= GetLastError();
196 handle
->dir_handle
= CreateFileW(dir_to_watch
,
198 FILE_SHARE_READ
| FILE_SHARE_DELETE
|
202 FILE_FLAG_BACKUP_SEMANTICS
|
203 FILE_FLAG_OVERLAPPED
,
211 if (handle
->dir_handle
== INVALID_HANDLE_VALUE
) {
212 last_error
= GetLastError();
216 if (CreateIoCompletionPort(handle
->dir_handle
,
220 last_error
= GetLastError();
224 handle
->buffer
= (char*)_aligned_malloc(uv_directory_watcher_buffer_size
,
226 if (!handle
->buffer
) {
227 uv_fatal_error(ERROR_OUTOFMEMORY
, "malloc");
230 memset(&(handle
->req
.overlapped
), 0, sizeof(handle
->req
.overlapped
));
232 if (!ReadDirectoryChangesW(handle
->dir_handle
,
234 uv_directory_watcher_buffer_size
,
236 FILE_NOTIFY_CHANGE_FILE_NAME
|
237 FILE_NOTIFY_CHANGE_DIR_NAME
|
238 FILE_NOTIFY_CHANGE_ATTRIBUTES
|
239 FILE_NOTIFY_CHANGE_SIZE
|
240 FILE_NOTIFY_CHANGE_LAST_WRITE
|
241 FILE_NOTIFY_CHANGE_LAST_ACCESS
|
242 FILE_NOTIFY_CHANGE_CREATION
|
243 FILE_NOTIFY_CHANGE_SECURITY
,
245 &handle
->req
.overlapped
,
247 last_error
= GetLastError();
251 handle
->req_pending
= 1;
255 if (handle
->filename
) {
256 free(handle
->filename
);
257 handle
->filename
= NULL
;
262 handle
->filew
= NULL
;
265 if (handle
->short_filew
) {
266 free(handle
->short_filew
);
267 handle
->short_filew
= NULL
;
272 if (handle
->dir_handle
!= INVALID_HANDLE_VALUE
) {
273 CloseHandle(handle
->dir_handle
);
274 handle
->dir_handle
= INVALID_HANDLE_VALUE
;
277 if (handle
->buffer
) {
278 _aligned_free(handle
->buffer
);
279 handle
->buffer
= NULL
;
282 uv__set_sys_error(loop
, last_error
);
287 void uv_process_fs_event_req(uv_loop_t
* loop
, uv_req_t
* req
,
288 uv_fs_event_t
* handle
) {
289 FILE_NOTIFY_INFORMATION
* file_info
;
290 int sizew
, size
, result
;
291 char* filename
= NULL
;
292 WCHAR
* filenamew
, *long_filenamew
= NULL
;
295 assert(req
->type
== UV_FS_EVENT_REQ
);
296 assert(handle
->req_pending
);
297 handle
->req_pending
= 0;
299 /* If we're closing, don't report any callbacks, and just push the handle */
300 /* onto the endgame queue. */
301 if (handle
->flags
& UV__HANDLE_CLOSING
) {
302 uv_want_endgame(loop
, (uv_handle_t
*) handle
);
306 file_info
= (FILE_NOTIFY_INFORMATION
*)(handle
->buffer
+ offset
);
308 if (REQ_SUCCESS(req
)) {
309 if (req
->overlapped
.InternalHigh
> 0) {
311 file_info
= (FILE_NOTIFY_INFORMATION
*)((char*)file_info
+ offset
);
313 assert(!long_filenamew
);
316 * Fire the event only if we were asked to watch a directory,
317 * or if the filename filter matches.
320 _wcsnicmp(handle
->filew
, file_info
->FileName
,
321 file_info
->FileNameLength
/ sizeof(WCHAR
)) == 0 ||
322 _wcsnicmp(handle
->short_filew
, file_info
->FileName
,
323 file_info
->FileNameLength
/ sizeof(WCHAR
)) == 0) {
327 * We attempt to convert the file name to its long form for
328 * events that still point to valid files on disk.
329 * For removed and renamed events, we do not provide the file name.
331 if (file_info
->Action
!= FILE_ACTION_REMOVED
&&
332 file_info
->Action
!= FILE_ACTION_RENAMED_OLD_NAME
) {
333 /* Construct a full path to the file. */
334 size
= wcslen(handle
->dirw
) +
335 file_info
->FileNameLength
/ sizeof(WCHAR
) + 2;
337 filenamew
= (WCHAR
*)malloc(size
* sizeof(WCHAR
));
339 uv_fatal_error(ERROR_OUTOFMEMORY
, "malloc");
342 _snwprintf(filenamew
, size
, L
"%s\\%s", handle
->dirw
,
343 file_info
->FileName
);
345 filenamew
[size
- 1] = L
'\0';
347 /* Convert to long name. */
348 size
= GetLongPathNameW(filenamew
, NULL
, 0);
351 long_filenamew
= (WCHAR
*)malloc(size
* sizeof(WCHAR
));
352 if (!long_filenamew
) {
353 uv_fatal_error(ERROR_OUTOFMEMORY
, "malloc");
356 size
= GetLongPathNameW(filenamew
, long_filenamew
, size
);
358 long_filenamew
[size
] = '\0';
360 free(long_filenamew
);
361 long_filenamew
= NULL
;
367 if (long_filenamew
) {
368 /* Get the file name out of the long path. */
369 result
= uv_split_path(long_filenamew
, NULL
, &filenamew
);
370 free(long_filenamew
);
373 long_filenamew
= filenamew
;
376 long_filenamew
= NULL
;
381 * If we couldn't get the long name - just use the name
382 * provided by ReadDirectoryChangesW.
384 if (!long_filenamew
) {
385 filenamew
= file_info
->FileName
;
386 sizew
= file_info
->FileNameLength
/ sizeof(WCHAR
);
389 /* Removed or renamed callbacks don't provide filename. */
393 /* We already have the long name of the file, so just use it. */
394 filenamew
= handle
->filew
;
399 /* Convert the filename to utf8. */
400 size
= uv_utf16_to_utf8(filenamew
,
405 filename
= (char*)malloc(size
+ 1);
407 uv_fatal_error(ERROR_OUTOFMEMORY
, "malloc");
410 size
= uv_utf16_to_utf8(filenamew
,
415 filename
[size
] = '\0';
423 switch (file_info
->Action
) {
424 case FILE_ACTION_ADDED
:
425 case FILE_ACTION_REMOVED
:
426 case FILE_ACTION_RENAMED_OLD_NAME
:
427 case FILE_ACTION_RENAMED_NEW_NAME
:
428 handle
->cb(handle
, filename
, UV_RENAME
, 0);
431 case FILE_ACTION_MODIFIED
:
432 handle
->cb(handle
, filename
, UV_CHANGE
, 0);
438 free(long_filenamew
);
439 long_filenamew
= NULL
;
442 offset
= file_info
->NextEntryOffset
;
443 } while (offset
&& !(handle
->flags
& UV__HANDLE_CLOSING
));
445 handle
->cb(handle
, NULL
, UV_CHANGE
, 0);
448 uv__set_sys_error(loop
, GET_REQ_ERROR(req
));
449 handle
->cb(handle
, NULL
, 0, -1);
452 if (!(handle
->flags
& UV__HANDLE_CLOSING
)) {
453 uv_fs_event_queue_readdirchanges(loop
, handle
);
455 uv_want_endgame(loop
, (uv_handle_t
*)handle
);
460 void uv_fs_event_close(uv_loop_t
* loop
, uv_fs_event_t
* handle
) {
461 if (handle
->dir_handle
!= INVALID_HANDLE_VALUE
) {
462 CloseHandle(handle
->dir_handle
);
463 handle
->dir_handle
= INVALID_HANDLE_VALUE
;
466 if (!handle
->req_pending
) {
467 uv_want_endgame(loop
, (uv_handle_t
*)handle
);
470 uv__handle_closing(handle
);
474 void uv_fs_event_endgame(uv_loop_t
* loop
, uv_fs_event_t
* handle
) {
475 if (handle
->flags
& UV__HANDLE_CLOSING
&&
476 !handle
->req_pending
) {
477 assert(!(handle
->flags
& UV_HANDLE_CLOSED
));
479 if (handle
->buffer
) {
480 _aligned_free(handle
->buffer
);
481 handle
->buffer
= NULL
;
486 handle
->filew
= NULL
;
489 if (handle
->short_filew
) {
490 free(handle
->short_filew
);
491 handle
->short_filew
= NULL
;
494 if (handle
->filename
) {
495 free(handle
->filename
);
496 handle
->filename
= NULL
;
504 uv__handle_close(handle
);