w_string FileDescriptor::readSymbolicLink()

in watchman/fs/FileDescriptor.cpp [360:477]


w_string FileDescriptor::readSymbolicLink() const {
#ifndef _WIN32
  struct stat st;
  if (fstat(fd_, &st)) {
    throw std::system_error(
        errno, std::generic_category(), "fstat for readSymbolicLink");
  }
  std::string result;
  result.resize(st.st_size + 1, 0);

#ifdef __linux__
  // Linux 2.6.39 and later provide this interface
  auto atlen = readlinkat(fd_, "", &result[0], result.size());
  if (atlen == int(result.size())) {
    // It's longer than we expected; TOCTOU detected!
    throw std::system_error(
        ENAMETOOLONG,
        std::generic_category(),
        "readlinkat: link contents grew while examining file");
  }
  if (atlen >= 0) {
    return w_string(result.data(), atlen);
  }
  // if we get ENOTDIR back then we're probably on an older linux and
  // should fall back to the technique used below.
  if (errno != ENOTDIR) {
    throw std::system_error(
        errno, std::generic_category(), "readlinkat for readSymbolicLink");
  }
#endif

  auto myName = getOpenedPath();
  auto len = readlink(myName.c_str(), &result[0], result.size());
  if (len == int(result.size())) {
    // It's longer than we expected; TOCTOU detected!
    throw std::system_error(
        ENAMETOOLONG,
        std::generic_category(),
        "readlink: link contents grew while examining file");
  }
  if (len >= 0) {
    return w_string(result.data(), len);
  }

  throw std::system_error(
      errno, std::generic_category(), "readlink for readSymbolicLink");
#else // _WIN32
  DWORD len = 64 * 1024;
  auto buf = malloc(len);
  if (!buf) {
    throw std::bad_alloc();
  }
  SCOPE_EXIT {
    free(buf);
  };
  WCHAR* target;
  USHORT targetlen;

  auto result = DeviceIoControl(
      (HANDLE)fd_,
      FSCTL_GET_REPARSE_POINT,
      nullptr,
      0,
      buf,
      len,
      &len,
      nullptr);

  // We only give one retry; if the size changed again already, we'll
  // have another pending notify from the OS to go look at it again
  // later, and it's totally fine to give up here for now.
  if (!result && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
    free(buf);
    buf = malloc(len);
    if (!buf) {
      throw std::bad_alloc();
    }

    result = DeviceIoControl(
        (HANDLE)fd_,
        FSCTL_GET_REPARSE_POINT,
        nullptr,
        0,
        buf,
        len,
        &len,
        nullptr);
  }

  if (!result) {
    throw std::system_error(
        GetLastError(), std::system_category(), "FSCTL_GET_REPARSE_POINT");
  }

  auto rep = reinterpret_cast<REPARSE_DATA_BUFFER*>(buf);

  switch (rep->ReparseTag) {
    case IO_REPARSE_TAG_SYMLINK:
      target = rep->SymbolicLinkReparseBuffer.PathBuffer +
          (rep->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
      targetlen =
          rep->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
      break;

    case IO_REPARSE_TAG_MOUNT_POINT:
      target = rep->MountPointReparseBuffer.PathBuffer +
          (rep->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
      targetlen =
          rep->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR);
      break;
    default:
      throw std::system_error(
          ENOSYS, std::generic_category(), "Unsupported ReparseTag");
  }

  return w_string(target, targetlen);
#endif
}