The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* 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 <malloc.h>
#include <direct.h>
#include <errno.h>
#include <fcntl.h>
#include <io.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/utime.h>
#include <stdio.h>

#include "uv.h"
#include "internal.h"
#include "req-inl.h"


#define UV_FS_FREE_PATHS         0x0002
#define UV_FS_FREE_PTR           0x0008
#define UV_FS_CLEANEDUP          0x0010


#define QUEUE_FS_TP_JOB(loop, req)                                          \
  if (!QueueUserWorkItem(&uv_fs_thread_proc,                                \
                         req,                                               \
                         WT_EXECUTEDEFAULT)) {                              \
    uv__set_sys_error((loop), GetLastError());                              \
    return -1;                                                              \
  }                                                                         \
  uv__req_register(loop, req);

#define SET_UV_LAST_ERROR_FROM_REQ(req)                                     \
  uv__set_error(req->loop, req->errorno, req->sys_errno_);

#define SET_REQ_RESULT(req, result_value)                                   \
  req->result = (result_value);                                             \
  if (req->result == -1) {                                                  \
    req->sys_errno_ = _doserrno;                                            \
    req->errorno = uv_translate_sys_error(req->sys_errno_);                 \
  }

#define SET_REQ_WIN32_ERROR(req, sys_errno)                                 \
  req->result = -1;                                                         \
  req->sys_errno_ = (sys_errno);                                            \
  req->errorno = uv_translate_sys_error(req->sys_errno_);

#define SET_REQ_UV_ERROR(req, uv_errno, sys_errno)                          \
  req->result = -1;                                                         \
  req->sys_errno_ = (sys_errno);                                            \
  req->errorno = (uv_errno);

#define VERIFY_FD(fd, req)                                                  \
  if (fd == -1) {                                                           \
    req->result = -1;                                                       \
    req->errorno = UV_EBADF;                                                \
    req->sys_errno_ = ERROR_INVALID_HANDLE;                                 \
    return;                                                                 \
  }

#define FILETIME_TO_TIME_T(filetime)                                        \
   ((*((uint64_t*) &(filetime)) - 116444736000000000ULL) / 10000000ULL);

#define TIME_T_TO_FILETIME(time, filetime_ptr)                              \
  do {                                                                      \
    *(uint64_t*) (filetime_ptr) = ((int64_t) (time) * 10000000LL) +         \
                                  116444736000000000ULL;                    \
  } while(0)


#define IS_SLASH(c) ((c) == L'\\' || (c) == L'/')
#define IS_LETTER(c) (((c) >= L'a' && (c) <= L'z') || \
  ((c) >= L'A' && (c) <= L'Z'))

const WCHAR JUNCTION_PREFIX[] = L"\\??\\";
const WCHAR JUNCTION_PREFIX_LEN = 4;

const WCHAR LONG_PATH_PREFIX[] = L"\\\\?\\";
const WCHAR LONG_PATH_PREFIX_LEN = 4;


void uv_fs_init() {
  _fmode = _O_BINARY;
}


INLINE static int fs__capture_path(uv_loop_t* loop, uv_fs_t* req,
    const char* path, const char* new_path, const int copy_path) {
  char* buf;
  char* pos;
  ssize_t buf_sz = 0, path_len, pathw_len, new_pathw_len;

  /* new_path can only be set if path is also set. */
  assert(new_path == NULL || path != NULL);

  if (path != NULL) {
    pathw_len = MultiByteToWideChar(CP_UTF8,
                                    0,
                                    path,
                                    -1,
                                    NULL,
                                    0);
    if (pathw_len == 0) {
      uv__set_sys_error(loop, GetLastError());
      return -1;
    }

    buf_sz += pathw_len * sizeof(WCHAR);
  }

  if (path != NULL && copy_path) {
    path_len = 1 + strlen(path);
    buf_sz += path_len;
  }

  if (new_path != NULL) {
    new_pathw_len = MultiByteToWideChar(CP_UTF8,
                                        0,
                                        new_path,
                                        -1,
                                        NULL,
                                        0);
    if (new_pathw_len == 0) {
      uv__set_sys_error(loop, GetLastError());
      return -1;
    }

    buf_sz += new_pathw_len * sizeof(WCHAR);
  }


  if (buf_sz == 0) {
    req->pathw = NULL;
    req->new_pathw = NULL;
    req->path = NULL;
    return 0;
  }

  buf = (char*) malloc(buf_sz);
  if (buf == NULL) {
    uv__set_artificial_error(loop, UV_ENOMEM);
    return -1;
  }

  pos = buf;

  if (path != NULL) {
    DWORD r = MultiByteToWideChar(CP_UTF8,
                                  0,
                                  path,
                                  -1,
                                  (WCHAR*) pos,
                                  pathw_len);
    assert(r == pathw_len);
    req->pathw = (WCHAR*) pos;
    pos += r * sizeof(WCHAR);
  } else {
    req->pathw = NULL;
  }

  if (new_path != NULL) {
    DWORD r = MultiByteToWideChar(CP_UTF8,
                                  0,
                                  new_path,
                                  -1,
                                  (WCHAR*) pos,
                                  new_pathw_len);
    assert(r == new_pathw_len);
    req->new_pathw = (WCHAR*) pos;
    pos += r * sizeof(WCHAR);
  } else {
    req->new_pathw = NULL;
  }

  if (!copy_path) {
    req->path = path;
  } else if (path) {
    memcpy(pos, path, path_len);
    assert(path_len == buf_sz - (pos - buf));
    req->path = pos;
  } else {
    req->path = NULL;
  }

  req->flags |= UV_FS_FREE_PATHS;

  return 0;
}



INLINE static void uv_fs_req_init(uv_loop_t* loop, uv_fs_t* req,
    uv_fs_type fs_type, const uv_fs_cb cb) {
  uv_req_init(loop, (uv_req_t*) req);

  req->type = UV_FS;
  req->loop = loop;
  req->flags = 0;
  req->fs_type = fs_type;
  req->result = 0;
  req->ptr = NULL;
  req->errorno = UV_OK;
  req->path = NULL;

  if (cb != NULL) {
    req->cb = cb;
    memset(&req->overlapped, 0, sizeof(req->overlapped));
  }
}


static int is_path_dir(const WCHAR* path) {
  DWORD attr = GetFileAttributesW(path);

  if (attr != INVALID_FILE_ATTRIBUTES) {
    return attr & FILE_ATTRIBUTE_DIRECTORY ? 1 : 0;
  } else {
    return 0;
  }
}


INLINE static int fs__readlink_handle(HANDLE handle, char** target_ptr,
    int64_t* target_len_ptr) {
  char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
  REPARSE_DATA_BUFFER* reparse_data = (REPARSE_DATA_BUFFER*) buffer;
  WCHAR *w_target;
  DWORD w_target_len;
  char* target;
  int target_len;
  DWORD bytes;

  if (!DeviceIoControl(handle,
                       FSCTL_GET_REPARSE_POINT,
                       NULL,
                       0,
                       buffer,
                       sizeof buffer,
                       &bytes,
                       NULL)) {
    return -1;
  }

  if (reparse_data->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
    /* Real symlink */
    w_target = reparse_data->SymbolicLinkReparseBuffer.PathBuffer +
        (reparse_data->SymbolicLinkReparseBuffer.SubstituteNameOffset /
        sizeof(WCHAR));
    w_target_len =
        reparse_data->SymbolicLinkReparseBuffer.SubstituteNameLength /
        sizeof(WCHAR);

    /* Real symlinks can contain pretty much everything, but the only thing */
    /* we really care about is undoing the implicit conversion to an NT */
    /* namespaced path that CreateSymbolicLink will perform on absolute */
    /* paths. If the path is win32-namespaced then the user must have */
    /* explicitly made it so, and we better just return the unmodified */
    /* reparse data. */
    if (w_target_len >= 4 &&
        w_target[0] == L'\\' &&
        w_target[1] == L'?' &&
        w_target[2] == L'?' &&
        w_target[3] == L'\\') {
      /* Starts with \??\ */
      if (w_target_len >= 6 &&
          ((w_target[4] >= L'A' && w_target[4] <= L'Z') ||
           (w_target[4] >= L'a' && w_target[4] <= L'z')) &&
          w_target[5] == L':' &&
          (w_target_len == 6 || w_target[6] == L'\\')) {
        /* \??\«drive»:\ */
        w_target += 4;
        w_target_len -= 4;

      } else if (w_target_len >= 8 &&
                 (w_target[4] == L'U' || w_target[4] == L'u') &&
                 (w_target[5] == L'N' || w_target[5] == L'n') &&
                 (w_target[6] == L'C' || w_target[6] == L'c') &&
                 w_target[7] == L'\\') {
        /* \??\UNC\«server»\«share»\ - make sure the final path looks like */
        /* \\«server»\«share»\ */
        w_target += 6;
        w_target[0] = L'\\';
        w_target_len -= 6;
      }
    }

  } else if (reparse_data->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) {
    /* Junction. */
    w_target = reparse_data->MountPointReparseBuffer.PathBuffer +
        (reparse_data->MountPointReparseBuffer.SubstituteNameOffset /
        sizeof(WCHAR));
    w_target_len = reparse_data->MountPointReparseBuffer.SubstituteNameLength /
        sizeof(WCHAR);

    /* Only treat junctions that look like \??\«drive»:\ as symlink. */
    /* Junctions can also be used as mount points, like \??\Volume{«guid»}, */
    /* but that's confusing for programs since they wouldn't be able to */
    /* actually understand such a path when returned by uv_readlink(). */
    /* UNC paths are never valid for junctions so we don't care about them. */
    if (!(w_target_len >= 6 &&
          w_target[0] == L'\\' &&
          w_target[1] == L'?' &&
          w_target[2] == L'?' &&
          w_target[3] == L'\\' &&
          ((w_target[4] >= L'A' && w_target[4] <= L'Z') ||
           (w_target[4] >= L'a' && w_target[4] <= L'z')) &&
          w_target[5] == L':' &&
          (w_target_len == 6 || w_target[6] == L'\\'))) {
      SetLastError(ERROR_SYMLINK_NOT_SUPPORTED);
      return -1;
    }

    /* Remove leading \??\ */
    w_target += 4;
    w_target_len -= 4;

  } else {
    /* Reparse tag does not indicate a symlink. */
    SetLastError(ERROR_SYMLINK_NOT_SUPPORTED);
    return -1;
  }

  /* If needed, compute the length of the target. */
  if (target_ptr != NULL || target_len_ptr != NULL) {
    /* Compute the length of the target. */
    target_len = WideCharToMultiByte(CP_UTF8,
                                     0,
                                     w_target,
                                     w_target_len,
                                     NULL,
                                     0,
                                     NULL,
                                     NULL);
    if (target_len == 0) {
      return -1;
    }
  }

  /* If requested, allocate memory and convert to UTF8. */
  if (target_ptr != NULL) {
    int r;
    target = (char*) malloc(target_len + 1);
    if (target == NULL) {
      SetLastError(ERROR_OUTOFMEMORY);
      return -1;
    }

    r = WideCharToMultiByte(CP_UTF8,
                            0,
                            w_target,
                            w_target_len,
                            target,
                            target_len,
                            NULL,
                            NULL);
    assert(r == target_len);
    target[target_len] = '\0';

    *target_ptr = target;
  }

  if (target_len_ptr != NULL) {
    *target_len_ptr = target_len;
  }

  return 0;
}


void fs__open(uv_fs_t* req) {
  DWORD access;
  DWORD share;
  DWORD disposition;
  DWORD attributes = 0;
  HANDLE file;
  int result, current_umask;
  int flags = req->file_flags;

  /* Obtain the active umask. umask() never fails and returns the previous */
  /* umask. */
  current_umask = umask(0);
  umask(current_umask);

  /* convert flags and mode to CreateFile parameters */
  switch (flags & (_O_RDONLY | _O_WRONLY | _O_RDWR)) {
  case _O_RDONLY:
    access = FILE_GENERIC_READ;
    attributes |= FILE_FLAG_BACKUP_SEMANTICS;
    break;
  case _O_WRONLY:
    access = FILE_GENERIC_WRITE;
    break;
  case _O_RDWR:
    access = FILE_GENERIC_READ | FILE_GENERIC_WRITE;
    break;
  default:
    result  = -1;
    goto end;
  }

  if (flags & _O_APPEND) {
    access &= ~FILE_WRITE_DATA;
    access |= FILE_APPEND_DATA;
    attributes &= ~FILE_FLAG_BACKUP_SEMANTICS;
  }

  /*
   * Here is where we deviate significantly from what CRT's _open()
   * does. We indiscriminately use all the sharing modes, to match
   * UNIX semantics. In particular, this ensures that the file can
   * be deleted even whilst it's open, fixing issue #1449.
   */
  share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;

  switch (flags & (_O_CREAT | _O_EXCL | _O_TRUNC)) {
  case 0:
  case _O_EXCL:
    disposition = OPEN_EXISTING;
    break;
  case _O_CREAT:
    disposition = OPEN_ALWAYS;
    break;
  case _O_CREAT | _O_EXCL:
  case _O_CREAT | _O_TRUNC | _O_EXCL:
    disposition = CREATE_NEW;
    break;
  case _O_TRUNC:
  case _O_TRUNC | _O_EXCL:
    disposition = TRUNCATE_EXISTING;
    break;
  case _O_CREAT | _O_TRUNC:
    disposition = CREATE_ALWAYS;
    break;
  default:
    result = -1;
    goto end;
  }

  attributes |= FILE_ATTRIBUTE_NORMAL;
  if (flags & _O_CREAT) {
    if (!((req->mode & ~current_umask) & _S_IWRITE)) {
      attributes |= FILE_ATTRIBUTE_READONLY;
    }
  }

  if (flags & _O_TEMPORARY ) {
    attributes |= FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY;
    access |= DELETE;
  }

  if (flags & _O_SHORT_LIVED) {
    attributes |= FILE_ATTRIBUTE_TEMPORARY;
  }

  switch (flags & (_O_SEQUENTIAL | _O_RANDOM)) {
  case 0:
    break;
  case _O_SEQUENTIAL:
    attributes |= FILE_FLAG_SEQUENTIAL_SCAN;
    break;
  case _O_RANDOM:
    attributes |= FILE_FLAG_RANDOM_ACCESS;
    break;
  default:
    result = -1;
    goto end;
  }

  /* Setting this flag makes it possible to open a directory. */
  attributes |= FILE_FLAG_BACKUP_SEMANTICS;

  file = CreateFileW(req->pathw,
                     access,
                     share,
                     NULL,
                     disposition,
                     attributes,
                     NULL);
  if (file == INVALID_HANDLE_VALUE) {
    DWORD error = GetLastError();
    if (error == ERROR_FILE_EXISTS && (flags & _O_CREAT) &&
        !(flags & _O_EXCL)) {
      /* Special case: when ERROR_FILE_EXISTS happens and O_CREAT was */
      /* specified, it means the path referred to a directory. */
      SET_REQ_UV_ERROR(req, UV_EISDIR, error);
    } else {
      SET_REQ_WIN32_ERROR(req, GetLastError());
    }
    return;
  }
  result = _open_osfhandle((intptr_t) file, flags);
end:
  SET_REQ_RESULT(req, result);
}

void fs__close(uv_fs_t* req) {
  int fd = req->fd;
  int result;

  VERIFY_FD(fd, req);

  result = _close(fd);
  SET_REQ_RESULT(req, result);
}


void fs__read(uv_fs_t* req) {
  int fd = req->fd;
  size_t length = req->length;
  int64_t offset = req->offset;
  HANDLE handle;
  OVERLAPPED overlapped, *overlapped_ptr;
  LARGE_INTEGER offset_;
  DWORD bytes;
  DWORD error;

  VERIFY_FD(fd, req);

  handle = (HANDLE) _get_osfhandle(fd);
  if (handle == INVALID_HANDLE_VALUE) {
    SET_REQ_RESULT(req, -1);
    return;
  }

  if (length > INT_MAX) {
    SET_REQ_WIN32_ERROR(req, ERROR_INSUFFICIENT_BUFFER);
    return;
  }

  if (offset != -1) {
    memset(&overlapped, 0, sizeof overlapped);

    offset_.QuadPart = offset;
    overlapped.Offset = offset_.LowPart;
    overlapped.OffsetHigh = offset_.HighPart;

    overlapped_ptr = &overlapped;
  } else {
    overlapped_ptr = NULL;
  }

  if (ReadFile(handle, req->buf, req->length, &bytes, overlapped_ptr)) {
    SET_REQ_RESULT(req, bytes);
  } else {
    error = GetLastError();
    if (error == ERROR_HANDLE_EOF) {
      SET_REQ_RESULT(req, bytes);
    } else {
      SET_REQ_WIN32_ERROR(req, error);
    }
  }
}


void fs__write(uv_fs_t* req) {
  int fd = req->fd;
  size_t length = req->length;
  int64_t offset = req->offset;
  HANDLE handle;
  OVERLAPPED overlapped, *overlapped_ptr;
  LARGE_INTEGER offset_;
  DWORD bytes;

  VERIFY_FD(fd, req);

  handle = (HANDLE) _get_osfhandle(fd);
  if (handle == INVALID_HANDLE_VALUE) {
    SET_REQ_RESULT(req, -1);
    return;
  }

  if (length > INT_MAX) {
    SET_REQ_WIN32_ERROR(req, ERROR_INSUFFICIENT_BUFFER);
    return;
  }

  if (offset != -1) {
    memset(&overlapped, 0, sizeof overlapped);

    offset_.QuadPart = offset;
    overlapped.Offset = offset_.LowPart;
    overlapped.OffsetHigh = offset_.HighPart;

    overlapped_ptr = &overlapped;
  } else {
    overlapped_ptr = NULL;
  }

  if (WriteFile(handle, req->buf, length, &bytes, overlapped_ptr)) {
    SET_REQ_RESULT(req, bytes);
  } else {
    SET_REQ_WIN32_ERROR(req, GetLastError());
  }
}


void fs__rmdir(uv_fs_t* req) {
  int result = _wrmdir(req->pathw);
  SET_REQ_RESULT(req, result);
}


void fs__unlink(uv_fs_t* req) {
  const WCHAR* pathw = req->pathw;
  HANDLE handle;
  BY_HANDLE_FILE_INFORMATION info;
  FILE_DISPOSITION_INFORMATION disposition;
  IO_STATUS_BLOCK iosb;
  NTSTATUS status;

  handle = CreateFileW(pathw,
                       FILE_READ_ATTRIBUTES | DELETE,
                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                       NULL,
                       OPEN_EXISTING,
                       FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
                       NULL);

  if (handle == INVALID_HANDLE_VALUE) {
    SET_REQ_WIN32_ERROR(req, GetLastError());
    return;
  }

  if (!GetFileInformationByHandle(handle, &info)) {
    SET_REQ_WIN32_ERROR(req, GetLastError());
    CloseHandle(handle);
    return;
  }

  if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
    /* Do not allow deletion of directories, unless it is a symlink. When */
    /* the path refers to a non-symlink directory, report EPERM as mandated */
    /* by POSIX.1. */

    /* Check if it is a reparse point. If it's not, it's a normal directory. */
    if (!(info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
      SET_REQ_WIN32_ERROR(req, ERROR_ACCESS_DENIED);
      CloseHandle(handle);
      return;
    }

    /* Read the reparse point and check if it is a valid symlink. */
    /* If not, don't unlink. */
    if (fs__readlink_handle(handle, NULL, NULL) < 0) {
      DWORD error = GetLastError();
      if (error == ERROR_SYMLINK_NOT_SUPPORTED)
        error = ERROR_ACCESS_DENIED;
      SET_REQ_WIN32_ERROR(req, error);
      CloseHandle(handle);
      return;
    }
  }

  /* Try to set the delete flag. */
  disposition.DeleteFile = TRUE;
  status = pNtSetInformationFile(handle,
                                 &iosb,
                                 &disposition,
                                 sizeof disposition,
                                 FileDispositionInformation);
  if (NT_SUCCESS(status)) {
    SET_REQ_SUCCESS(req);
  } else {
    SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(status));
  }

  CloseHandle(handle);
}


void fs__mkdir(uv_fs_t* req) {
  /* TODO: use req->mode. */
  int result = _wmkdir(req->pathw);
  SET_REQ_RESULT(req, result);
}


void fs__readdir(uv_fs_t* req) {
  WCHAR* pathw = req->pathw;
  size_t len = wcslen(pathw);
  int result, size;
  WCHAR* buf = NULL, *ptr, *name;
  HANDLE dir;
  WIN32_FIND_DATAW ent = { 0 };
  size_t buf_char_len = 4096;
  WCHAR* path2;
  const WCHAR* fmt;

  if (len == 0) {
    fmt = L"./*";
  } else if (pathw[len - 1] == L'/' || pathw[len - 1] == L'\\') {
    fmt = L"%s*";
  } else {
    fmt = L"%s\\*";
  }

  /* Figure out whether path is a file or a directory. */
  if (!(GetFileAttributesW(pathw) & FILE_ATTRIBUTE_DIRECTORY)) {
    req->result = -1;
    req->errorno = UV_ENOTDIR;
    req->sys_errno_ = ERROR_SUCCESS;
    return;
  }

  path2 = (WCHAR*)malloc(sizeof(WCHAR) * (len + 4));
  if (!path2) {
    uv_fatal_error(ERROR_OUTOFMEMORY, "malloc");
  }

#ifdef _MSC_VER
  swprintf(path2, len + 3, fmt, pathw);
#else
  swprintf(path2, fmt, pathw);
#endif
  dir = FindFirstFileW(path2, &ent);
  free(path2);

  if(dir == INVALID_HANDLE_VALUE) {
    SET_REQ_WIN32_ERROR(req, GetLastError());
    return;
  }

  result = 0;

  do {
    name = ent.cFileName;

    if (name[0] != L'.' || (name[1] && (name[1] != L'.' || name[2]))) {
      len = wcslen(name);

      if (!buf) {
        buf = (WCHAR*)malloc(buf_char_len * sizeof(WCHAR));
        if (!buf) {
          uv_fatal_error(ERROR_OUTOFMEMORY, "malloc");
        }

        ptr = buf;
      }

      while ((ptr - buf) + len + 1 > buf_char_len) {
        buf_char_len *= 2;
        path2 = buf;
        buf = (WCHAR*)realloc(buf, buf_char_len * sizeof(WCHAR));
        if (!buf) {
          uv_fatal_error(ERROR_OUTOFMEMORY, "realloc");
        }

        ptr = buf + (ptr - path2);
      }

      wcscpy(ptr, name);
      ptr += len + 1;
      result++;
    }
  } while(FindNextFileW(dir, &ent));

  FindClose(dir);

  if (buf) {
    /* Convert result to UTF8. */
    size = uv_utf16_to_utf8(buf, buf_char_len, NULL, 0);
    if (!size) {
      SET_REQ_WIN32_ERROR(req, GetLastError());
      return;
    }

    req->ptr = (char*)malloc(size + 1);
    if (!req->ptr) {
      uv_fatal_error(ERROR_OUTOFMEMORY, "malloc");
    }

    size = uv_utf16_to_utf8(buf, buf_char_len, (char*)req->ptr, size);
    if (!size) {
      free(buf);
      free(req->ptr);
      req->ptr = NULL;
      SET_REQ_WIN32_ERROR(req, GetLastError());
      return;
    }
    free(buf);

    ((char*)req->ptr)[size] = '\0';
    req->flags |= UV_FS_FREE_PTR;
  } else {
    req->ptr = NULL;
  }

  SET_REQ_RESULT(req, result);
}


INLINE static int fs__stat_handle(HANDLE handle, uv_statbuf_t* statbuf) {
  BY_HANDLE_FILE_INFORMATION info;

  if (!GetFileInformationByHandle(handle, &info)) {
    return -1;
  }

  /* TODO: set st_dev, st_rdev and st_ino to something meaningful. */
  statbuf->st_ino = 0;
  statbuf->st_dev = 0;
  statbuf->st_rdev = 0;

  statbuf->st_gid = 0;
  statbuf->st_uid = 0;

  statbuf->st_mode = 0;

  if (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
    if (fs__readlink_handle(handle, NULL, &statbuf->st_size) != 0) {
      return -1;
    }
    statbuf->st_mode |= S_IFLNK;
  } else if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
    statbuf->st_mode |= _S_IFDIR;
    statbuf->st_size = 0;
  } else {
    statbuf->st_mode |= _S_IFREG;
    statbuf->st_size = ((int64_t) info.nFileSizeHigh << 32) +
                        (int64_t) info.nFileSizeLow;
  }

  if (info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
    statbuf->st_mode |= (_S_IREAD + (_S_IREAD >> 3) + (_S_IREAD >> 6));
  } else {
    statbuf->st_mode |= ((_S_IREAD|_S_IWRITE) + ((_S_IREAD|_S_IWRITE) >> 3) +
      ((_S_IREAD|_S_IWRITE) >> 6));
  }

  statbuf->st_mtime = FILETIME_TO_TIME_T(info.ftLastWriteTime);
  statbuf->st_atime = FILETIME_TO_TIME_T(info.ftLastAccessTime);
  statbuf->st_ctime = FILETIME_TO_TIME_T(info.ftCreationTime);

  statbuf->st_nlink = (info.nNumberOfLinks <= SHRT_MAX) ?
                      (short) info.nNumberOfLinks : SHRT_MAX;

  return 0;
}


INLINE static void fs__stat_prepare_path(WCHAR* pathw) {
  size_t len = wcslen(pathw);

  /* TODO: ignore namespaced paths. */
  if (len > 1 && pathw[len - 2] != L':' &&
      (pathw[len - 1] == L'\\' || pathw[len - 1] == L'/')) {
    pathw[len - 1] = '\0';
  }
}


INLINE static void fs__stat_impl(uv_fs_t* req, int do_lstat) {
  HANDLE handle;
  DWORD flags;

  flags = FILE_FLAG_BACKUP_SEMANTICS;
  if (do_lstat) {
    flags |= FILE_FLAG_OPEN_REPARSE_POINT;
  }

  handle = CreateFileW(req->pathw,
                       FILE_READ_ATTRIBUTES,
                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                       NULL,
                       OPEN_EXISTING,
                       flags,
                       NULL);
  if (handle == INVALID_HANDLE_VALUE) {
    SET_REQ_WIN32_ERROR(req, GetLastError());
    return;
  }

  if (fs__stat_handle(handle, &req->stat) != 0) {
    DWORD error = GetLastError();
    if (do_lstat && error == ERROR_SYMLINK_NOT_SUPPORTED) {
      /* We opened a reparse point but it was not a symlink. Try again. */
      fs__stat_impl(req, 0);

    } else {
      /* Stat failed. */
      SET_REQ_WIN32_ERROR(req, GetLastError());
    }

    CloseHandle(handle);
    return;
  }

  req->ptr = &req->stat;
  req->result = 0;
  CloseHandle(handle);
}


static void fs__stat(uv_fs_t* req) {
  fs__stat_prepare_path(req->pathw);
  fs__stat_impl(req, 0);
}


static void fs__lstat(uv_fs_t* req) {
  fs__stat_prepare_path(req->pathw);
  fs__stat_impl(req, 1);
}


static void fs__fstat(uv_fs_t* req) {
  int fd = req->fd;
  HANDLE handle;

  VERIFY_FD(fd, req);

  handle = (HANDLE) _get_osfhandle(fd);

  if (handle == INVALID_HANDLE_VALUE) {
    SET_REQ_WIN32_ERROR(req, ERROR_INVALID_HANDLE);
    return;
  }

  if (fs__stat_handle(handle, &req->stat) != 0) {
    SET_REQ_WIN32_ERROR(req, GetLastError());
    return;
  }

  req->ptr = &req->stat;
  req->result = 0;
}


static void fs__rename(uv_fs_t* req) {
  if (!MoveFileExW(req->pathw, req->new_pathw, MOVEFILE_REPLACE_EXISTING)) {
    SET_REQ_WIN32_ERROR(req, GetLastError());
    return;
  }

  SET_REQ_RESULT(req, 0);
}


INLINE static void fs__sync_impl(uv_fs_t* req) {
  int fd = req->fd;
  int result;

  VERIFY_FD(fd, req);

  result = FlushFileBuffers((HANDLE) _get_osfhandle(fd)) ? 0 : -1;
  if (result == -1) {
    SET_REQ_WIN32_ERROR(req, GetLastError());
  } else {
    SET_REQ_RESULT(req, result);
  }
}


static void fs__fsync(uv_fs_t* req) {
  fs__sync_impl(req);
}


static void fs__fdatasync(uv_fs_t* req) {
  fs__sync_impl(req);
}


static void fs__ftruncate(uv_fs_t* req) {
  int fd = req->fd;
  HANDLE handle;
  NTSTATUS status;
  IO_STATUS_BLOCK io_status;
  FILE_END_OF_FILE_INFORMATION eof_info;

  VERIFY_FD(fd, req);

  handle = (HANDLE)_get_osfhandle(fd);

  eof_info.EndOfFile.QuadPart = req->offset;

  status = pNtSetInformationFile(handle,
                                 &io_status,
                                 &eof_info,
                                 sizeof eof_info,
                                 FileEndOfFileInformation);

  if (NT_SUCCESS(status)) {
    SET_REQ_RESULT(req, 0);
  } else {
    SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(status));
  }
}


static void fs__sendfile(uv_fs_t* req) {
  int fd_in = req->fd, fd_out = req->fd_out;
  size_t length = req->length;
  int64_t offset = req->offset;
  const size_t max_buf_size = 65536;
  size_t buf_size = length < max_buf_size ? length : max_buf_size;
  int n, result = 0;
  int64_t result_offset = 0;
  char* buf = (char*) malloc(buf_size);
  if (!buf) {
    uv_fatal_error(ERROR_OUTOFMEMORY, "malloc");
  }

  if (offset != -1) {
    result_offset = _lseeki64(fd_in, offset, SEEK_SET);
  }

  if (result_offset == -1) {
    result = -1;
  } else {
    while (length > 0) {
      n = _read(fd_in, buf, length < buf_size ? length : buf_size);
      if (n == 0) {
        break;
      } else if (n == -1) {
        result = -1;
        break;
      }

      length -= n;

      n = _write(fd_out, buf, n);
      if (n == -1) {
        result = -1;
        break;
      }

      result += n;
    }
  }

  SET_REQ_RESULT(req, result);
}


static void fs__chmod(uv_fs_t* req) {
  int result = _wchmod(req->pathw, req->mode);
  SET_REQ_RESULT(req, result);
}


static void fs__fchmod(uv_fs_t* req) {
  int fd = req->fd;
  int result;
  HANDLE handle;
  NTSTATUS nt_status;
  IO_STATUS_BLOCK io_status;
  FILE_BASIC_INFORMATION file_info;

  VERIFY_FD(fd, req);

  handle = (HANDLE)_get_osfhandle(fd);

  nt_status = pNtQueryInformationFile(handle,
                                      &io_status,
                                      &file_info,
                                      sizeof file_info,
                                      FileBasicInformation);

  if (nt_status != STATUS_SUCCESS) {
    result = -1;
    goto done;
  }

  if (req->mode & _S_IWRITE) {
    file_info.FileAttributes &= ~FILE_ATTRIBUTE_READONLY;
  } else {
    file_info.FileAttributes |= FILE_ATTRIBUTE_READONLY;
  }

  nt_status = pNtSetInformationFile(handle,
                                    &io_status,
                                    &file_info,
                                    sizeof file_info,
                                    FileBasicInformation);

  if (nt_status != STATUS_SUCCESS) {
    result = -1;
    goto done;
  }

  result = 0;

done:
  SET_REQ_RESULT(req, result);
}


INLINE static int fs__utime_handle(HANDLE handle, double atime, double mtime) {
  FILETIME filetime_a, filetime_m;

  TIME_T_TO_FILETIME((time_t) atime, &filetime_a);
  TIME_T_TO_FILETIME((time_t) mtime, &filetime_m);

  if (!SetFileTime(handle, NULL, &filetime_a, &filetime_m)) {
    return -1;
  }

  return 0;
}


static void fs__utime(uv_fs_t* req) {
  HANDLE handle;

  handle = CreateFileW(req->pathw,
                       FILE_WRITE_ATTRIBUTES,
                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                       NULL,
                       OPEN_EXISTING,
                       FILE_FLAG_BACKUP_SEMANTICS,
                       NULL);

  if (handle == INVALID_HANDLE_VALUE) {
    SET_REQ_WIN32_ERROR(req, GetLastError());
    return;
  }

  if (fs__utime_handle(handle, req->atime, req->mtime) != 0) {
    SET_REQ_WIN32_ERROR(req, GetLastError());
    return;
  }

  req->result = 0;
}


static void fs__futime(uv_fs_t* req) {
  int fd = req->fd;
  HANDLE handle;
  VERIFY_FD(fd, req);

  handle = (HANDLE) _get_osfhandle(fd);

  if (handle == INVALID_HANDLE_VALUE) {
    SET_REQ_WIN32_ERROR(req, ERROR_INVALID_HANDLE);
    return;
  }

  if (fs__utime_handle(handle, req->atime, req->mtime) != 0) {
    SET_REQ_WIN32_ERROR(req, GetLastError());
    return;
  }

  req->result = 0;
}


static void fs__link(uv_fs_t* req) {
  DWORD r = CreateHardLinkW(req->new_pathw, req->pathw, NULL);
  if (r == 0) {
    SET_REQ_WIN32_ERROR(req, GetLastError());
  } else {
    req->result = 0;
  }
}


static void fs__create_junction(uv_fs_t* req, const WCHAR* path,
    const WCHAR* new_path) {
  HANDLE handle = INVALID_HANDLE_VALUE;
  REPARSE_DATA_BUFFER *buffer = NULL;
  int created = 0;
  int target_len;
  int is_absolute, is_long_path;
  int needed_buf_size, used_buf_size, used_data_size, path_buf_len;
  int start, len, i;
  int add_slash;
  DWORD bytes;
  WCHAR* path_buf;

  target_len = wcslen(path);
  is_long_path = wcsncmp(path, LONG_PATH_PREFIX, LONG_PATH_PREFIX_LEN) == 0;

  if (is_long_path) {
    is_absolute = 1;
  } else {
    is_absolute = target_len >= 3 && IS_LETTER(path[0]) &&
      path[1] == L':' && IS_SLASH(path[2]);
  }

  if (!is_absolute) {
    /* Not supporting relative paths */
    SET_REQ_UV_ERROR(req, UV_EINVAL, ERROR_NOT_SUPPORTED);
    return;
  }

  // Do a pessimistic calculation of the required buffer size
  needed_buf_size =
      FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer) +
      JUNCTION_PREFIX_LEN * sizeof(WCHAR) +
      2 * (target_len + 2) * sizeof(WCHAR);

  // Allocate the buffer
  buffer = (REPARSE_DATA_BUFFER*)malloc(needed_buf_size);
  if (!buffer) {
    uv_fatal_error(ERROR_OUTOFMEMORY, "malloc");
  }

  // Grab a pointer to the part of the buffer where filenames go
  path_buf = (WCHAR*)&(buffer->MountPointReparseBuffer.PathBuffer);
  path_buf_len = 0;

  // Copy the substitute (internal) target path
  start = path_buf_len;

  wcsncpy((WCHAR*)&path_buf[path_buf_len], JUNCTION_PREFIX,
    JUNCTION_PREFIX_LEN);
  path_buf_len += JUNCTION_PREFIX_LEN;

  add_slash = 0;
  for (i = is_long_path ? LONG_PATH_PREFIX_LEN : 0; path[i] != L'\0'; i++) {
    if (IS_SLASH(path[i])) {
      add_slash = 1;
      continue;
    }

    if (add_slash) {
      path_buf[path_buf_len++] = L'\\';
      add_slash = 0;
    }

    path_buf[path_buf_len++] = path[i];
  }
  path_buf[path_buf_len++] = L'\\';
  len = path_buf_len - start;

  // Set the info about the substitute name
  buffer->MountPointReparseBuffer.SubstituteNameOffset = start * sizeof(WCHAR);
  buffer->MountPointReparseBuffer.SubstituteNameLength = len * sizeof(WCHAR);

  // Insert null terminator
  path_buf[path_buf_len++] = L'\0';

  // Copy the print name of the target path
  start = path_buf_len;
  add_slash = 0;
  for (i = is_long_path ? LONG_PATH_PREFIX_LEN : 0; path[i] != L'\0'; i++) {
    if (IS_SLASH(path[i])) {
      add_slash = 1;
      continue;
    }

    if (add_slash) {
      path_buf[path_buf_len++] = L'\\';
      add_slash = 0;
    }

    path_buf[path_buf_len++] = path[i];
  }
  len = path_buf_len - start;
  if (len == 2) {
    path_buf[path_buf_len++] = L'\\';
    len++;
  }

  // Set the info about the print name
  buffer->MountPointReparseBuffer.PrintNameOffset = start * sizeof(WCHAR);
  buffer->MountPointReparseBuffer.PrintNameLength = len * sizeof(WCHAR);

  // Insert another null terminator
  path_buf[path_buf_len++] = L'\0';

  // Calculate how much buffer space was actually used
  used_buf_size = FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer) +
    path_buf_len * sizeof(WCHAR);
  used_data_size = used_buf_size -
    FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer);

  // Put general info in the data buffer
  buffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
  buffer->ReparseDataLength = used_data_size;
  buffer->Reserved = 0;

  // Create a new directory
  if (!CreateDirectoryW(new_path, NULL)) {
    SET_REQ_WIN32_ERROR(req, GetLastError());
    goto error;
  }
  created = 1;

  // Open the directory
  handle = CreateFileW(new_path,
                       GENERIC_ALL,
                       0,
                       NULL,
                       OPEN_EXISTING,
                       FILE_FLAG_BACKUP_SEMANTICS |
                         FILE_FLAG_OPEN_REPARSE_POINT,
                       NULL);
  if (handle == INVALID_HANDLE_VALUE) {
    SET_REQ_WIN32_ERROR(req, GetLastError());
    goto error;
  }

  // Create the actual reparse point
  if (!DeviceIoControl(handle,
                       FSCTL_SET_REPARSE_POINT,
                       buffer,
                       used_buf_size,
                       NULL,
                       0,
                       &bytes,
                       NULL)) {
    SET_REQ_WIN32_ERROR(req, GetLastError());
    goto error;
  }

  // Clean up
  CloseHandle(handle);
  free(buffer);

  SET_REQ_RESULT(req, 0);
  return;

error:
  free(buffer);

  if (handle != INVALID_HANDLE_VALUE) {
    CloseHandle(handle);
  }

  if (created) {
    RemoveDirectoryW(new_path);
  }
}


static void fs__symlink(uv_fs_t* req) {
  WCHAR* pathw = req->pathw;
  WCHAR* new_pathw = req->new_pathw;
  int flags = req->file_flags;
  int result;


  if (flags & UV_FS_SYMLINK_JUNCTION) {
    fs__create_junction(req, pathw, new_pathw);
  } else if (pCreateSymbolicLinkW) {
    result = pCreateSymbolicLinkW(new_pathw,
                                  pathw,
                                  flags & UV_FS_SYMLINK_DIR ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0) ? 0 : -1;
    if (result == -1) {
      SET_REQ_WIN32_ERROR(req, GetLastError());
    } else {
      SET_REQ_RESULT(req, result);
    }
  } else {
    SET_REQ_UV_ERROR(req, UV_ENOSYS, ERROR_NOT_SUPPORTED);
  }
}


static void fs__readlink(uv_fs_t* req) {
  HANDLE handle;

  handle = CreateFileW(req->pathw,
                       0,
                       0,
                       NULL,
                       OPEN_EXISTING,
                       FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
                       NULL);

  if (handle == INVALID_HANDLE_VALUE) {
    SET_REQ_WIN32_ERROR(req, GetLastError());
    return;
  }

  if (fs__readlink_handle(handle, (char**) &req->ptr, NULL) != 0) {
    SET_REQ_WIN32_ERROR(req, GetLastError());
    CloseHandle(handle);
    return;
  }

  req->flags |= UV_FS_FREE_PTR;
  SET_REQ_RESULT(req, 0);

  CloseHandle(handle);
}



static void fs__chown(uv_fs_t* req) {
  req->result = 0;
}


static void fs__fchown(uv_fs_t* req) {
  req->result = 0;
}


static DWORD WINAPI uv_fs_thread_proc(void* parameter) {
  uv_fs_t* req = (uv_fs_t*) parameter;
  uv_loop_t* loop = req->loop;

  assert(req != NULL);
  assert(req->type == UV_FS);

#define XX(uc, lc)  case UV_FS_##uc: fs__##lc(req); break;
  switch (req->fs_type) {
    XX(OPEN, open)
    XX(CLOSE, close)
    XX(READ, read)
    XX(WRITE, write)
    XX(SENDFILE, sendfile)
    XX(STAT, stat)
    XX(LSTAT, lstat)
    XX(FSTAT, fstat)
    XX(FTRUNCATE, ftruncate)
    XX(UTIME, utime)
    XX(FUTIME, futime)
    XX(CHMOD, chmod)
    XX(FCHMOD, fchmod)
    XX(FSYNC, fsync)
    XX(FDATASYNC, fdatasync)
    XX(UNLINK, unlink)
    XX(RMDIR, rmdir)
    XX(MKDIR, mkdir)
    XX(RENAME, rename)
    XX(READDIR, readdir)
    XX(LINK, link)
    XX(SYMLINK, symlink)
    XX(READLINK, readlink)
    XX(CHOWN, chown)
    XX(FCHOWN, fchown);
    default:
      assert(!"bad uv_fs_type");
  }

  POST_COMPLETION_FOR_REQ(loop, req);
  return 0;
}


int uv_fs_open(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags,
    int mode, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_OPEN, cb);

  if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) {
    return -1;
  }

  req->file_flags = flags;
  req->mode = mode;

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__open(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_close(uv_loop_t* loop, uv_fs_t* req, uv_file fd, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_CLOSE, cb);
  req->fd = fd;

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__close(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_read(uv_loop_t* loop, uv_fs_t* req, uv_file fd, void* buf,
    size_t length, int64_t offset, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_READ, cb);

  req->fd = fd;
  req->buf = buf;
  req->length = length;
  req->offset = offset;

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__read(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_write(uv_loop_t* loop, uv_fs_t* req, uv_file fd, void* buf,
    size_t length, int64_t offset, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_WRITE, cb);

  req->fd = fd;
  req->buf = buf;
  req->length = length;
  req->offset = offset;

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__write(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_unlink(uv_loop_t* loop, uv_fs_t* req, const char* path,
    uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_UNLINK, cb);

  if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) {
    return -1;
  }

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__unlink(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_mkdir(uv_loop_t* loop, uv_fs_t* req, const char* path, int mode,
    uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_MKDIR, cb);

  if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) {
    return -1;
  }

  req->mode = mode;

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__mkdir(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_rmdir(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_RMDIR, cb);

  if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) {
    return -1;
  }

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__rmdir(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_readdir(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags,
    uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_READDIR, cb);

  if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) {
    return -1;
  }

  req->file_flags;

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__readdir(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_link(uv_loop_t* loop, uv_fs_t* req, const char* path,
    const char* new_path, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_LINK, cb);

  if (fs__capture_path(loop, req, path, new_path, cb != NULL) < 0) {
    return -1;
  }

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__link(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_symlink(uv_loop_t* loop, uv_fs_t* req, const char* path,
    const char* new_path, int flags, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_SYMLINK, cb);

  if (fs__capture_path(loop, req, path, new_path, cb != NULL) < 0) {
    return -1;
  }

  req->file_flags = flags;

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__symlink(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_readlink(uv_loop_t* loop, uv_fs_t* req, const char* path,
    uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_READLINK, cb);

  if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) {
    return -1;
  }

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__readlink(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_chown(uv_loop_t* loop, uv_fs_t* req, const char* path, int uid,
    int gid, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_CHOWN, cb);

  if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) {
    return -1;
  }

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__chown(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_fchown(uv_loop_t* loop, uv_fs_t* req, uv_file fd, int uid,
    int gid, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_FCHOWN, cb);

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__fchown(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_stat(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_STAT, cb);

  if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) {
    return -1;
  }

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__stat(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_lstat(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_LSTAT, cb);

  if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) {
    return -1;
  }

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__lstat(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_fstat(uv_loop_t* loop, uv_fs_t* req, uv_file fd, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_FSTAT, cb);
  req->fd = fd;

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__fstat(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_rename(uv_loop_t* loop, uv_fs_t* req, const char* path,
    const char* new_path, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_RENAME, cb);

  if (fs__capture_path(loop, req, path, new_path, cb != NULL) < 0) {
    return -1;
  }

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__rename(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_fsync(uv_loop_t* loop, uv_fs_t* req, uv_file fd, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_FSYNC, cb);
  req->fd = fd;

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__fsync(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_fdatasync(uv_loop_t* loop, uv_fs_t* req, uv_file fd, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_FDATASYNC, cb);
  req->fd = fd;

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__fdatasync(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_ftruncate(uv_loop_t* loop, uv_fs_t* req, uv_file fd,
    int64_t offset, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_FTRUNCATE, cb);

  req->fd = fd;
  req->offset = offset;

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__ftruncate(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}



int uv_fs_sendfile(uv_loop_t* loop, uv_fs_t* req, uv_file fd_out,
    uv_file fd_in, int64_t in_offset, size_t length, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_SENDFILE, cb);

  req->fd = fd_in;
  req->fd_out = fd_out;
  req->offset = in_offset;
  req->length = length;

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__sendfile(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_chmod(uv_loop_t* loop, uv_fs_t* req, const char* path, int mode,
    uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_CHMOD, cb);

  if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) {
    return -1;
  }

  req->mode = mode;

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__chmod(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_fchmod(uv_loop_t* loop, uv_fs_t* req, uv_file fd, int mode,
    uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_FCHMOD, cb);

  req->fd = fd;
  req->mode = mode;

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__fchmod(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_utime(uv_loop_t* loop, uv_fs_t* req, const char* path, double atime,
    double mtime, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_UTIME, cb);

  if (fs__capture_path(loop, req, path, NULL, cb != NULL) < 0) {
    return -1;
  }

  req->atime = atime;
  req->mtime = mtime;

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__utime(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


int uv_fs_futime(uv_loop_t* loop, uv_fs_t* req, uv_file fd, double atime,
    double mtime, uv_fs_cb cb) {
  uv_fs_req_init(loop, req, UV_FS_FUTIME, cb);

  req->fd = fd;
  req->atime = atime;
  req->mtime = mtime;

  if (cb) {
    QUEUE_FS_TP_JOB(loop, req);
    return 0;
  } else {
    fs__futime(req);
    SET_UV_LAST_ERROR_FROM_REQ(req);
    return req->result;
  }
}


void uv_process_fs_req(uv_loop_t* loop, uv_fs_t* req) {
  assert(req->cb);
  uv__req_unregister(loop, req);
  SET_UV_LAST_ERROR_FROM_REQ(req);
  req->cb(req);
}


void uv_fs_req_cleanup(uv_fs_t* req) {
  if (req->flags & UV_FS_CLEANEDUP)
    return;

  if (req->flags & UV_FS_FREE_PATHS)
    free(req->pathw);

  if (req->flags & UV_FS_FREE_PTR)
    free(req->ptr);

  req->path = NULL;
  req->pathw = NULL;
  req->new_pathw = NULL;
  req->ptr = NULL;

  req->flags |= UV_FS_CLEANEDUP;
}