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