Skip to content

Commit

Permalink
@@@ symlinks
Browse files Browse the repository at this point in the history
  • Loading branch information
strager committed Feb 11, 2024
1 parent 021e2ee commit 64014e5
Show file tree
Hide file tree
Showing 11 changed files with 388 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ Change_Detecting_Filesystem_Win32::~Change_Detecting_Filesystem_Win32() {

Result<Canonical_Path_Result, Canonicalize_Path_IO_Error>
Change_Detecting_Filesystem_Win32::canonicalize_path(const std::string& path) {
return quick_lint_js::canonicalize_path(path);
return quick_lint_js::canonicalize_path(path, this);
}

Result<Padded_String, Read_File_IO_Error>
Expand All @@ -122,6 +122,28 @@ Change_Detecting_Filesystem_Win32::read_file(const Canonical_Path& path) {
return *std::move(r);
}

void Change_Detecting_Filesystem_Win32::on_canonicalize_child_of_directory(
const char*) {
// We don't use char paths on Windows.
QLJS_UNIMPLEMENTED();
}

// @@@ we should only do this on symlinks and shit?
void Change_Detecting_Filesystem_Win32::on_canonicalize_child_of_directory(
const wchar_t* path) {
bool ok = this->watch_directory(path);
if (!ok) {
std::optional<std::string> narrow_path = wstring_to_mbstring(path);
if (!narrow_path.has_value()) {
QLJS_UNIMPLEMENTED();
}
this->watch_errors_.emplace_back(Watch_IO_Error{
.path = narrow_path->c_str(),
.io_error = Windows_File_IO_Error{::GetLastError()},
});
}
}

bool Change_Detecting_Filesystem_Win32::handle_event(
::OVERLAPPED* overlapped, ::DWORD number_of_bytes_transferred,
::DWORD error) {
Expand Down Expand Up @@ -179,9 +201,13 @@ bool Change_Detecting_Filesystem_Win32::watch_directory(
if (!wpath.has_value()) {
QLJS_UNIMPLEMENTED();
}
return this->watch_directory(wpath->c_str());
}

bool Change_Detecting_Filesystem_Win32::watch_directory(
const wchar_t* directory) {
Windows_Handle_File directory_handle(::CreateFileW(
wpath->c_str(), /*dwDesiredAccess=*/GENERIC_READ,
directory, /*dwDesiredAccess=*/GENERIC_READ,
/*dwShareMode=*/FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
/*lpSecurityAttributes=*/nullptr,
/*dwCreationDisposition=*/OPEN_EXISTING,
Expand Down Expand Up @@ -215,9 +241,9 @@ bool Change_Detecting_Filesystem_Win32::watch_directory(
}

QLJS_DEBUG_LOG(
"note: Directory handle %#llx: %s: Directory identity changed\n",
"note: Directory handle %#llx: %ls: Directory identity changed\n",
reinterpret_cast<unsigned long long>(old_dir->directory_handle.get()),
directory.c_str());
directory);
this->cancel_watch(std::move(watched_directory_it->second));
watched_directory_it->second = std::move(new_dir);
}
Expand Down Expand Up @@ -249,6 +275,10 @@ bool Change_Detecting_Filesystem_Win32::watch_directory(
DWORD error = ::GetLastError();
if (error == ERROR_IO_PENDING) {
// run_io_thread will handle the oplock breaking.
QLJS_DEBUG_LOG(
"note: Watching directory with handle %#llx: %ls\n",
reinterpret_cast<unsigned long long>(dir->directory_handle.get()),
directory);
} else {
// FIXME(strager): Should we close the directory handle?
return false;
Expand Down Expand Up @@ -297,7 +327,7 @@ void Change_Detecting_Filesystem_Win32::handle_oplock_broke_event(
//
// https://docs.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_request_oplock
QLJS_DEBUG_LOG(
"note: Directory handle %#llx: %s: Oplock broke\n",
"note: Directory handle %#llx: %ls: Oplock broke\n",
reinterpret_cast<unsigned long long>(dir->directory_handle.get()),
directory_it->first.c_str());
QLJS_ASSERT(number_of_bytes_transferred == sizeof(dir->oplock_response));
Expand Down
9 changes: 7 additions & 2 deletions src/quick-lint-js/configuration/change-detecting-filesystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ extern ::DWORD mock_win32_force_directory_file_id_error;
extern ::DWORD mock_win32_force_directory_ioctl_error;

// Not thread-safe.
class Change_Detecting_Filesystem_Win32 : public Configuration_Filesystem {
class Change_Detecting_Filesystem_Win32 : public Configuration_Filesystem,
Canonicalize_Observer {
public:
explicit Change_Detecting_Filesystem_Win32(
Windows_Handle_File_Ref io_completion_port, ::ULONG_PTR completion_key);
Expand All @@ -164,6 +165,9 @@ class Change_Detecting_Filesystem_Win32 : public Configuration_Filesystem {
Result<Padded_String, Read_File_IO_Error> read_file(
const Canonical_Path&) override;

void on_canonicalize_child_of_directory(const char*) override;
void on_canonicalize_child_of_directory(const wchar_t*) override;

Windows_Handle_File_Ref io_completion_port() const {
return this->io_completion_port_;
}
Expand Down Expand Up @@ -200,6 +204,7 @@ class Change_Detecting_Filesystem_Win32 : public Configuration_Filesystem {

// Calls SetLastError and returns false on failure.
bool watch_directory(const Canonical_Path&);
bool watch_directory(const wchar_t* path);

void cancel_watch(std::unique_ptr<Watched_Directory>&&);

Expand All @@ -210,7 +215,7 @@ class Change_Detecting_Filesystem_Win32 : public Configuration_Filesystem {
Windows_Handle_File_Ref io_completion_port_;
::ULONG_PTR completion_key_;

Hash_Map<Canonical_Path, std::unique_ptr<Watched_Directory>>
Hash_Map<std::wstring, std::unique_ptr<Watched_Directory>>
watched_directories_;
std::vector<std::unique_ptr<Watched_Directory>>
cancelling_watched_directories_;
Expand Down
9 changes: 9 additions & 0 deletions src/quick-lint-js/container/hash.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ template <>
struct Hasher<String8> : Hasher<String8_View> {};
#endif

template <>
struct Hasher<std::wstring_view> {
std::size_t operator()(std::wstring_view s) const {
return std::hash<std::wstring_view>()(s);
}
};
template <>
struct Hasher<std::wstring> : Hasher<std::wstring_view> {};

template <class T1, class T2>
struct Hasher<std::pair<T1, T2>> {
template <class U1, class U2>
Expand Down
125 changes: 112 additions & 13 deletions src/quick-lint-js/io/file-canonical.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
#include <cstdlib>
#include <cstring>
#include <quick-lint-js/assert.h>
#include <quick-lint-js/container/monotonic-allocator.h>
#include <quick-lint-js/container/result.h>
#include <quick-lint-js/container/string-view.h>
#include <quick-lint-js/container/vector.h>
#include <quick-lint-js/io/file-canonical.h>
#include <quick-lint-js/io/file-handle.h>
#include <quick-lint-js/io/file-path.h>
Expand All @@ -31,6 +33,7 @@

#if QLJS_HAVE_WINDOWS_H
#include <quick-lint-js/port/windows.h>
#include <winioctl.h>
#include <pathcch.h>
#endif

Expand Down Expand Up @@ -303,7 +306,7 @@ class Path_Canonicalizer_Base {
directory,
does_not_exist,
other,
symlink,
symlink_or_reparse_point,
};

quick_lint_js::Result<void, Platform_File_IO_Error> load_cwd() {
Expand Down Expand Up @@ -387,9 +390,9 @@ class Path_Canonicalizer_Base {
}
break;

case File_Type::symlink: {
case File_Type::symlink_or_reparse_point: {
quick_lint_js::Result<void, Canonicalizing_Path_IO_Error> r =
this->derived().resolve_symlink();
this->derived().resolve_symlink_or_reparse_point();
if (!r.ok()) return r.propagate();
break;
}
Expand Down Expand Up @@ -503,15 +506,16 @@ class POSIX_Path_Canonicalizer
return failed_result(POSIX_File_IO_Error{errno});
}
if (S_ISLNK(s.st_mode)) {
return File_Type::symlink;
return File_Type::symlink_or_reparse_point;
}
if (S_ISDIR(s.st_mode)) {
return File_Type::directory;
}
return File_Type::other;
}

quick_lint_js::Result<void, Canonicalizing_Path_IO_Error> resolve_symlink() {
quick_lint_js::Result<void, Canonicalizing_Path_IO_Error>
resolve_symlink_or_reparse_point() {
symlink_depth_ += 1;
if (symlink_depth_ >= symlink_depth_limit_) {
return failed_result(Canonicalizing_Path_IO_Error{
Expand Down Expand Up @@ -586,10 +590,19 @@ class Windows_Path_Canonicalizer

quick_lint_js::Result<void, Canonicalizing_Path_IO_Error>
process_start_of_path() {
// FIXME(strager): Do we need to copy (std::wstring) to add the null
// terminator?
Simplified_Path simplified_path = simplify_path_and_make_absolute(
&this->allocator_, std::wstring(path_to_process_).c_str());
// FIXME(strager): Do we need to copy to add the null terminator? If the
// null terminator is guaranteed to already exists, we should remove this
// copy.
std::wstring path(path_to_process_);

// HACK(strager): PathCchSkipRoot and PathAllocCanonicalize don't support
// \??\ but does support \\?\. Convert \??\ to \\?\.
if (starts_with(std::wstring_view(path), LR"(\??\)"sv)) {
path[1] = L'\\';
}

Simplified_Path simplified_path =
simplify_path_and_make_absolute(&this->allocator_, path.c_str());
this->canonical_ = simplified_path.root;
this->path_to_process_ = simplified_path.relative;
this->need_root_slash_ = true;
Expand Down Expand Up @@ -628,17 +641,103 @@ class Windows_Path_Canonicalizer
return failed_result(Windows_File_IO_Error{error});
}
if (attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
return File_Type::symlink;
return File_Type::symlink_or_reparse_point;
}
if (attributes & FILE_ATTRIBUTE_DIRECTORY) {
return File_Type::directory;
}
return File_Type::other;
}

quick_lint_js::Result<void, Canonicalizing_Path_IO_Error> resolve_symlink() {
// TODO(strager): Support symlinks on Windows.
QLJS_UNIMPLEMENTED();
quick_lint_js::Result<void, Canonicalizing_Path_IO_Error>
resolve_symlink_or_reparse_point() {
symlink_depth_ += 1;
if (symlink_depth_ >= symlink_depth_limit_) {
return failed_result(Canonicalizing_Path_IO_Error{
.canonicalizing_path = canonical_,
.io_error = Windows_File_IO_Error{ERROR_CANT_RESOLVE_FILENAME},
});
}

Windows_Handle_File file(
::CreateFileW(canonical_.c_str(), FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
/*lpSecurityAttributes=*/nullptr, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
/*hTemplateFile=*/nullptr));
if (!file.valid()) {
return failed_result(Canonicalizing_Path_IO_Error{
.canonicalizing_path = canonical_,
.io_error = Windows_File_IO_Error{::GetLastError()},
});
}

Monotonic_Allocator memory("resolve_symlink_or_reparse_point");
Vector<char> reparse_data_raw("reparse_data", &memory);
// See NOTE[reparse-point-null-terminator] for why we add sizeof(wchar_t).
reparse_data_raw.resize(MAXIMUM_REPARSE_DATA_BUFFER_SIZE + sizeof(wchar_t));

::DWORD bytes_returned;
if (!::DeviceIoControl(
file.get(), FSCTL_GET_REPARSE_POINT,
/*lpInBuffer=*/nullptr, /*nInBufferSize=*/0,
/*lpOutBuffer=*/reparse_data_raw.data(),
/*nOutBufferSize=*/narrow_cast<::DWORD>(reparse_data_raw.size()),
&bytes_returned, /*lpOverlapped=*/nullptr)) {
return failed_result(Canonicalizing_Path_IO_Error{
.canonicalizing_path = canonical_,
.io_error = Windows_File_IO_Error{::GetLastError()},
});
}
file.close();

::REPARSE_DATA_BUFFER *reparse_data =
reinterpret_cast<::REPARSE_DATA_BUFFER *>(reparse_data_raw.data());
switch (reparse_data->ReparseTag) {
case IO_REPARSE_TAG_MOUNT_POINT:
QLJS_UNIMPLEMENTED();
break;

case IO_REPARSE_TAG_SYMLINK: {
auto &symlink = reparse_data->SymbolicLinkReparseBuffer;
std::wstring &new_readlink_buffer =
readlink_buffers_[1 - used_readlink_buffer_];
// NOTE[reparse-point-null-terminator]: The path is not null-terminated.
std::wstring_view symlink_target(
&symlink.PathBuffer[symlink.SubstituteNameOffset / sizeof(wchar_t)],
symlink.SubstituteNameLength / sizeof(wchar_t));

if (symlink.Flags & SYMLINK_FLAG_RELATIVE) {
canonical_ = parent_path(std::move(canonical_));
new_readlink_buffer.reserve(canonical_.size() + 1 +
symlink_target.size() +
path_to_process_.size());
new_readlink_buffer.clear();
new_readlink_buffer += canonical_;
new_readlink_buffer += L'\\';
new_readlink_buffer += symlink_target;
} else {
new_readlink_buffer.reserve(symlink_target.size() +
path_to_process_.size());
new_readlink_buffer = symlink_target;
}
new_readlink_buffer += path_to_process_;
path_to_process_ = new_readlink_buffer;
// After assigning to path_to_process_,
// readlink_buffers_[used_readlink_buffer_] is no longer in use.
swap_readlink_buffers();

Result<void, Canonicalizing_Path_IO_Error> r = process_start_of_path();
if (!r.ok()) return r.propagate();

break;
}

default:
QLJS_UNIMPLEMENTED();
break;
}

return {};
}

Expand Down
Loading

0 comments on commit 64014e5

Please sign in to comment.