text: /* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this software and associated documentation files (the "Software"), to
   * deal in the Software without restriction, including without limitation the
   * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
   * sell copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in
   * all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
   * IN THE SOFTWARE.
   */
  
  #include <assert.h>
  #include <errno.h>
  #include <stdio.h>
  #include <string.h>
  
  #include "uv.h"
  #include "internal.h"
  #include "handle-inl.h"
  #include "req-inl.h"
  
  
  const unsigned int uv_directory_watcher_buffer_size = 4096;
  
  
  static void uv_fs_event_queue_readdirchanges(uv_loop_t* loop,
      uv_fs_event_t* handle) {
    assert(handle->dir_handle != INVALID_HANDLE_VALUE);
    assert(!handle->req_pending);
  
    memset(&(handle->req.u.io.overlapped), 0,
           sizeof(handle->req.u.io.overlapped));
    if (!ReadDirectoryChangesW(handle->dir_handle,
                               handle->buffer,
                               uv_directory_watcher_buffer_size,
                               (handle->flags & UV_FS_EVENT_RECURSIVE) ? TRUE : FALSE,
                               FILE_NOTIFY_CHANGE_FILE_NAME      |
                                 FILE_NOTIFY_CHANGE_DIR_NAME     |
                                 FILE_NOTIFY_CHANGE_ATTRIBUTES   |
                                 FILE_NOTIFY_CHANGE_SIZE         |
                                 FILE_NOTIFY_CHANGE_LAST_WRITE   |
                                 FILE_NOTIFY_CHANGE_LAST_ACCESS  |
                                 FILE_NOTIFY_CHANGE_CREATION     |
                                 FILE_NOTIFY_CHANGE_SECURITY,
                               NULL,
                               &handle->req.u.io.overlapped,
                               NULL)) {
      /* Make this req pending reporting an error. */
      SET_REQ_ERROR(&handle->req, GetLastError());
      uv_insert_pending_req(loop, (uv_req_t*)&handle->req);
    }
  
    handle->req_pending = 1;
  }
  
  static void uv_relative_path(const WCHAR* filename,
                               const WCHAR* dir,
                               WCHAR** relpath) {
    size_t relpathlen;
    size_t filenamelen = wcslen(filename);
    size_t dirlen = wcslen(dir);
    if (dirlen > 0 && dir[dirlen - 1] == '\\')
      dirlen--;
    relpathlen = filenamelen - dirlen - 1;
    *relpath = uv__malloc((relpathlen + 1) * sizeof(WCHAR));
    if (!*relpath)
      uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
    wcsncpy(*relpath, filename + dirlen + 1, relpathlen);
    (*relpath)[relpathlen] = L'\0';
  }
  
  static int uv_split_path(const WCHAR* filename, WCHAR** dir,
      WCHAR** file) {
    int len = wcslen(filename);
    int i = len;
    while (i > 0 && filename[--i] != '\\' && filename[i] != '/');
  
    if (i == 0) {
      if (dir) {
        *dir = (WCHAR*)uv__malloc((MAX_PATH + 1) * sizeof(WCHAR));
        if (!*dir) {
          uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
        }
  
        if (!GetCurrentDirectoryW(MAX_PATH, *dir)) {
          uv__free(*dir);
          *dir = NULL;
          return -1;
        }
      }
  
      *file = wcsdup(filename);
    } else {
      if (dir) {
        *dir = (WCHAR*)uv__malloc((i + 2) * sizeof(WCHAR));
        if (!*dir) {
          uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
        }
        wcsncpy(*dir, filename, i + 1);
        (*dir)[i + 1] = L'\0';
      }
  
      *file = (WCHAR*)uv__malloc((len - i) * sizeof(WCHAR));
      if (!*file) {
        uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
      }
      wcsncpy(*file, filename + i + 1, len - i - 1);
      (*file)[len - i - 1] = L'\0';
    }
  
    return 0;
  }
  
  
  int uv_fs_event_init(uv_loop_t* loop, uv_fs_event_t* handle) {
    uv__handle_init(loop, (uv_handle_t*) handle, UV_FS_EVENT);
    handle->dir_handle = INVALID_HANDLE_VALUE;
    handle->buffer = NULL;
    handle->req_pending = 0;
    handle->filew = NULL;
    handle->short_filew = NULL;
    handle->dirw = NULL;
  
    uv_req_init(loop, (uv_req_t*)&handle->req);
    handle->req.type = UV_FS_EVENT_REQ;
    handle->req.data = handle;
  
    return 0;
  }
  
  
  int uv_fs_event_start(uv_fs_event_t* handle,
                        uv_fs_event_cb cb,
                        const char* path,
                        unsigned int flags) {
    int name_size, is_path_dir;
    DWORD attr, last_error;
    WCHAR* dir = NULL, *dir_to_watch, *pathw = NULL;
    WCHAR short_path[MAX_PATH];
  
    if (uv__is_active(handle))
      return UV_EINVAL;
  
    handle->cb = cb;
    handle->path = uv__strdup(path);
    if (!handle->path) {
      uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
    }
  
    uv__handle_start(handle);
  
    /* Convert name to UTF16. */
  
    name_size = MultiByteToWideChar(CP_UTF8, 0, path, -1, NULL, 0) *
                sizeof(WCHAR);
    pathw = (WCHAR*)uv__malloc(name_size);
    if (!pathw) {
      uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
    }
  
    if (!MultiByteToWideChar(CP_UTF8,
                             0,
                             path,
                             -1,
                             pathw,
                             name_size / sizeof(WCHAR))) {
      return uv_translate_sys_error(GetLastError());
    }
  
    /* Determine whether path is a file or a directory. */
    attr = GetFileAttributesW(pathw);
    if (attr == INVALID_FILE_ATTRIBUTES) {
      last_error = GetLastError();
      goto error;
    }
  
    is_path_dir = (attr & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0;
  
    if (is_path_dir) {
       /* path is a directory, so that's the directory that we will watch. */
      handle->dirw = pathw;
      dir_to_watch = pathw;
    } else {
      /*
       * path is a file.  So we split path into dir & file parts, and
       * watch the dir directory.
       */
  
      /* Convert to short path. */
      if (!GetShortPathNameW(pathw, short_path, ARRAY_SIZE(short_path))) {
        last_error = GetLastError();
        goto error;
      }
  
      if (uv_split_path(pathw, &dir, &handle->filew) != 0) {
        last_error = GetLastError();
        goto error;
      }
  
      if (uv_split_path(short_path, NULL, &handle->short_filew) != 0) {
        last_error = GetLastError();
        goto error;
      }
  
      dir_to_watch = dir;
      uv__free(pathw);
      pathw = NULL;
    }
  
    handle->dir_handle = CreateFileW(dir_to_watch,
                                     FILE_LIST_DIRECTORY,
                                     FILE_SHARE_READ | FILE_SHARE_DELETE |
                                       FILE_SHARE_WRITE,
                                     NULL,
                                     OPEN_EXISTING,
                                     FILE_FLAG_BACKUP_SEMANTICS |
                                       FILE_FLAG_OVERLAPPED,
                                     NULL);
  
    if (dir) {
      uv__free(dir);
      dir = NULL;
    }
  
    if (handle->dir_handle == INVALID_HANDLE_VALUE) {
      last_error = GetLastError();
      goto error;
    }
  
    if (CreateIoCompletionPort(handle->dir_handle,
                               handle->loop->iocp,
                               (ULONG_PTR)handle,
                               0) == NULL) {
      last_error = GetLastError();
      goto error;
    }
  
    if (!handle->buffer) {
      handle->buffer = (char*)uv__malloc(uv_directory_watcher_buffer_size);
    }
    if (!handle->buffer) {
      uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
    }
  
    memset(&(handle->req.u.io.overlapped), 0,
           sizeof(handle->req.u.io.overlapped));
  
    if (!ReadDirectoryChangesW(handle->dir_handle,
                               handle->buffer,
                               uv_directory_watcher_buffer_size,
                               (flags & UV_FS_EVENT_RECURSIVE) ? TRUE : FALSE,
                               FILE_NOTIFY_CHANGE_FILE_NAME      |
                                 FILE_NOTIFY_CHANGE_DIR_NAME     |
                                 FILE_NOTIFY_CHANGE_ATTRIBUTES   |
                                 FILE_NOTIFY_CHANGE_SIZE         |
                                 FILE_NOTIFY_CHANGE_LAST_WRITE   |
                                 FILE_NOTIFY_CHANGE_LAST_ACCESS  |
                                 FILE_NOTIFY_CHANGE_CREATION     |
                                 FILE_NOTIFY_CHANGE_SECURITY,
                               NULL,
                               &handle->req.u.io.overlapped,
                               NULL)) {
      last_error = GetLastError();
      goto error;
    }
  
    handle->req_pending = 1;
    return 0;
  
  error:
    if (handle->path) {
      uv__free(handle->path);
      handle->path = NULL;
    }
  
    if (handle->filew) {
      uv__free(handle->filew);
      handle->filew = NULL;
    }
  
    if (handle->short_filew) {
      uv__free(handle->short_filew);
      handle->short_filew = NULL;
    }
  
    uv__free(pathw);
  
    if (handle->dir_handle != INVALID_HANDLE_VALUE) {
      CloseHandle(handle->dir_handle);
      handle->dir_handle = INVALID_HANDLE_VALUE;
    }
  
    if (handle->buffer) {
      uv__free(handle->buffer);
      handle->buffer = NULL;
    }
  
    return uv_translate_sys_error(last_error);
  }
  
  
  int uv_fs_event_stop(uv_fs_event_t* handle) {
    if (!uv__is_active(handle))
      return 0;
  
    if (handle->dir_handle != INVALID_HANDLE_VALUE) {
      CloseHandle(handle->dir_handle);
      handle->dir_handle = INVALID_HANDLE_VALUE;
    }
  
    uv__handle_stop(handle);
  
    if (handle->filew) {
      uv__free(handle->filew);
      handle->filew = NULL;
    }
  
    if (handle->short_filew) {
      uv__free(handle->short_filew);
      handle->short_filew = NULL;
    }
  
    if (handle->path) {
      uv__free(handle->path);
      handle->path = NULL;
    }
  
    if (handle->dirw) {
      uv__free(handle->dirw);
      handle->dirw = NULL;
    }
  
    return 0;
  }
  
  
  void uv_process_fs_event_req(uv_loop_t* loop, uv_req_t* req,
      uv_fs_event_t* handle) {
    FILE_NOTIFY_INFORMATION* file_info;
    int err, sizew, size;
    char* filename = NULL;
    WCHAR* filenamew = NULL;
    WCHAR* long_filenamew = NULL;
    DWORD offset = 0;
  
    assert(req->type == UV_FS_EVENT_REQ);
    assert(handle->req_pending);
    handle->req_pending = 0;
  
    /* Don't report any callbacks if:
     * - We're closing, just push the handle onto the endgame queue
     * - We are not active, just ignore the callback
     */
    if (!uv__is_active(handle)) {
      if (handle->flags & UV__HANDLE_CLOSING) {
        uv_want_endgame(loop, (uv_handle_t*) handle);
      }
      return;
    }
  
    file_info = (FILE_NOTIFY_INFORMATION*)(handle->buffer + offset);
  
    if (REQ_SUCCESS(req)) {
      if (req->u.io.overlapped.InternalHigh > 0) {
        do {
          file_info = (FILE_NOTIFY_INFORMATION*)((char*)file_info + offset);
          assert(!filename);
          assert(!filenamew);
          assert(!long_filenamew);
  
          /*
           * Fire the event only if we were asked to watch a directory,
           * or if the filename filter matches.
           */
          if (handle->dirw ||
            _wcsnicmp(handle->filew, file_info->FileName,
              file_info->FileNameLength / sizeof(WCHAR)) == 0 ||
            _wcsnicmp(handle->short_filew, file_info->FileName,
              file_info->FileNameLength / sizeof(WCHAR)) == 0) {
  
            if (handle->dirw) {
              /*
               * We attempt to resolve the long form of the file name explicitly.
               * We only do this for file names that might still exist on disk.
               * If this fails, we use the name given by ReadDirectoryChangesW.
               * This may be the long form or the 8.3 short name in some cases.
               */
              if (file_info->Action != FILE_ACTION_REMOVED &&
                file_info->Action != FILE_ACTION_RENAMED_OLD_NAME) {
                /* Construct a full path to the file. */
                size = wcslen(handle->dirw) +
                  file_info->FileNameLength / sizeof(WCHAR) + 2;
  
                filenamew = (WCHAR*)uv__malloc(size * sizeof(WCHAR));
                if (!filenamew) {
                  uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
                }
  
                _snwprintf(filenamew, size, L"%s\\%.*s", handle->dirw,
                  file_info->FileNameLength / sizeof(WCHAR),
                  file_info->FileName);
  
                filenamew[size - 1] = L'\0';
  
                /* Convert to long name. */
                size = GetLongPathNameW(filenamew, NULL, 0);
  
                if (size) {
                  long_filenamew = (WCHAR*)uv__malloc(size * sizeof(WCHAR));
                  if (!long_filenamew) {
                    uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
                  }
  
                  size = GetLongPathNameW(filenamew, long_filenamew, size);
                  if (size) {
                    long_filenamew[size] = '\0';
                  } else {
                    uv__free(long_filenamew);
                    long_filenamew = NULL;
                  }
                }
  
                uv__free(filenamew);
  
                if (long_filenamew) {
                  /* Get the file name out of the long path. */
                  uv_relative_path(long_filenamew,
                                   handle->dirw,
                                   &filenamew);
                  uv__free(long_filenamew);
                  long_filenamew = filenamew;
                  sizew = -1;
                } else {
                  /* We couldn't get the long filename, use the one reported. */
                  filenamew = file_info->FileName;
                  sizew = file_info->FileNameLength / sizeof(WCHAR);
                }
              } else {
                /*
                 * Removed or renamed events cannot be resolved to the long form.
                 * We therefore use the name given by ReadDirectoryChangesW.
                 * This may be the long form or the 8.3 short name in some cases.
                 */
                filenamew = file_info->FileName;
                sizew = file_info->FileNameLength / sizeof(WCHAR);
              }
            } else {
              /* We already have the long name of the file, so just use it. */
              filenamew = handle->filew;
              sizew = -1;
            }
  
            /* Convert the filename to utf8. */
            uv__convert_utf16_to_utf8(filenamew, sizew, &filename);
  
            switch (file_info->Action) {
              case FILE_ACTION_ADDED:
              case FILE_ACTION_REMOVED:
              case FILE_ACTION_RENAMED_OLD_NAME:
              case FILE_ACTION_RENAMED_NEW_NAME:
                handle->cb(handle, filename, UV_RENAME, 0);
                break;
  
              case FILE_ACTION_MODIFIED:
                handle->cb(handle, filename, UV_CHANGE, 0);
                break;
            }
  
            uv__free(filename);
            filename = NULL;
            uv__free(long_filenamew);
            long_filenamew = NULL;
            filenamew = NULL;
          }
  
          offset = file_info->NextEntryOffset;
        } while (offset && !(handle->flags & UV__HANDLE_CLOSING));
      } else {
        handle->cb(handle, NULL, UV_CHANGE, 0);
      }
    } else {
      err = GET_REQ_ERROR(req);
      handle->cb(handle, NULL, 0, uv_translate_sys_error(err));
    }
  
    if (!(handle->flags & UV__HANDLE_CLOSING)) {
      uv_fs_event_queue_readdirchanges(loop, handle);
    } else {
      uv_want_endgame(loop, (uv_handle_t*)handle);
    }
  }
  
  
  void uv_fs_event_close(uv_loop_t* loop, uv_fs_event_t* handle) {
    uv_fs_event_stop(handle);
  
    uv__handle_closing(handle);
  
    if (!handle->req_pending) {
      uv_want_endgame(loop, (uv_handle_t*)handle);
    }
  
  }
  
  
  void uv_fs_event_endgame(uv_loop_t* loop, uv_fs_event_t* handle) {
    if ((handle->flags & UV__HANDLE_CLOSING) && !handle->req_pending) {
      assert(!(handle->flags & UV_HANDLE_CLOSED));
  
      if (handle->buffer) {
        uv__free(handle->buffer);
        handle->buffer = NULL;
      }
  
      uv__handle_close(handle);
    }
  }
  