Skip to content

Commit

Permalink
<filesystem> support junctions in read_symlink (microsoft#2877)
Browse files Browse the repository at this point in the history
Co-authored-by: nicole mazzuca <mazzucan@outlook.com>
Co-authored-by: Stephan T. Lavavej <stl@nuwen.net>
  • Loading branch information
3 people authored and fsb4000 committed Aug 13, 2022
1 parent 3327c2e commit 069cb75
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 9 deletions.
90 changes: 82 additions & 8 deletions stl/inc/filesystem
Original file line number Diff line number Diff line change
Expand Up @@ -3203,7 +3203,7 @@ namespace filesystem {
}
}

_NODISCARD inline __std_win_error _Read_symlink_reparse_data(
_NODISCARD inline __std_win_error _Read_reparse_data(
const _Fs_file& _Handle, unique_ptr<char[]>& _Buffer_unique_ptr) noexcept {
constexpr auto _Buffer_size = 16 * 1024 + sizeof(wchar_t); // MAXIMUM_REPARSE_DATA_BUFFER_SIZE + sizeof(wchar_t)

Expand Down Expand Up @@ -3231,7 +3231,7 @@ namespace filesystem {
return _Err;
}

_Err = _Read_symlink_reparse_data(_Handle, _Buffer_unique_ptr);
_Err = _Read_reparse_data(_Handle, _Buffer_unique_ptr);
if (_Err != __std_win_error::_Success) {
return _Err;
}
Expand Down Expand Up @@ -3278,7 +3278,7 @@ namespace filesystem {
return _Err;
}

_Err = _Read_symlink_reparse_data(_Handle, _Buffer_unique_ptr);
_Err = _Read_reparse_data(_Handle, _Buffer_unique_ptr);
if (_Err != __std_win_error::_Success) {
return _Err;
}
Expand All @@ -3293,6 +3293,14 @@ namespace filesystem {
} // Close _Handle

const auto _Buffer = reinterpret_cast<__std_fs_reparse_data_buffer*>(_Buffer_unique_ptr.get());

// LWG-3744: `copy_symlink(junction, new_symlink)`'s behavior is unclear
// `read_symlink(junction)` should be allowed, but `copy_symlink(junction)` is not.
if (__std_fs_is_junction_from_reparse_data_buffer(_Buffer)) {
_Err = __std_win_error::_Reparse_tag_invalid;
return _Err;
}

unsigned short _Length;
wchar_t* _Offset;
_Err = __std_fs_read_name_from_reparse_data_buffer(_Buffer, &_Offset, &_Length);
Expand All @@ -3311,6 +3319,56 @@ namespace filesystem {
return _Err;
}

_NODISCARD inline __std_win_error _Copy_junction(const path& _Junction, const path& _New_junction) noexcept {
__std_win_error _Err;
unique_ptr<char[]> _Buffer_unique_ptr;
{
const _Fs_file _Handle(_Junction.c_str(), __std_access_rights::_File_read_attributes,
__std_fs_file_flags::_Backup_semantics | __std_fs_file_flags::_Open_reparse_point, &_Err);
if (_Err != __std_win_error::_Success) {
return _Err;
}

_Err = _Read_reparse_data(_Handle, _Buffer_unique_ptr);
if (_Err != __std_win_error::_Success) {
return _Err;
}
} // Close _Handle

const auto _Buffer = reinterpret_cast<__std_fs_reparse_data_buffer*>(_Buffer_unique_ptr.get());

const auto _Create_dir_res = __std_fs_create_directory(_New_junction.c_str());

if (_Create_dir_res._Error != __std_win_error::_Success) {
return _Create_dir_res._Error;
} else if (!_Create_dir_res._Created) {
return __std_win_error::_Already_exists;
}

struct _NODISCARD _Delete_directory_scope_guard {
const wchar_t* _Path;
~_Delete_directory_scope_guard() {
if (_Path) {
(void) __std_fs_remove(_Path);
}
}
};
_Delete_directory_scope_guard _Delete_directory{_New_junction.c_str()};

_Fs_file _To_handle{_New_junction.c_str(), __std_access_rights::_File_write_attributes,
__std_fs_file_flags::_Backup_semantics, &_Err};
if (_Err != __std_win_error::_Success) {
return _Err;
}
_Err = __std_fs_write_reparse_data_buffer(_To_handle._Raw, _Buffer);
if (_Err == __std_win_error::_Success) {
// don't delete the directory if we succeeded in making it a junction
_Delete_directory._Path = nullptr;
}

return _Err;
}

inline void copy_symlink(const path& _Symlink, const path& _New_symlink, error_code& _Ec) {
_Ec = _Make_ec(_Copy_symlink(_Symlink, _New_symlink));
}
Expand Down Expand Up @@ -4216,8 +4274,12 @@ namespace filesystem {
}
}

if (_STD filesystem::is_other(_Fstat._Status) || _STD filesystem::is_other(_Tstat._Status)) {
// report an error if is_other(f) || is_other(t) is true
const bool _Fstat_is_other =
_STD filesystem::is_other(_Fstat._Status) && _Fstat._Status.type() != file_type::junction;
const bool _Tstat_is_other =
_STD filesystem::is_other(_Tstat._Status) && _Tstat._Status.type() != file_type::junction;
if (_Fstat_is_other || _Tstat_is_other) {
// report an error if is_other(f) || is_other(t) is true, and it's not a junction
_Ec = _STD make_error_code(errc::operation_not_supported);
return;
}
Expand All @@ -4228,14 +4290,26 @@ namespace filesystem {
return;
}

if (_Fstat._Status.type() == file_type::junction) {
if ((_Options & copy_options::skip_symlinks) != copy_options::none) {
return;
}

// _Options includes copy_options::copy_symlinks,
// since _Fstat is only allowed to be a symbolic link when either skip_symlinks or copy_symlinks
if (!_STD filesystem::exists(_Tstat._Status)) {
_Ec = _Make_ec(_Copy_junction(_From, _To));
}
}

if (_STD filesystem::is_symlink(_Fstat._Status)) {
if ((_Options & copy_options::skip_symlinks) != copy_options::none) {
return;
}

if (!_STD filesystem::exists(_Tstat._Status)
&& (_Options & copy_options::copy_symlinks) != copy_options::none) {
// if (condition) then copy_symlink(from, to)
// _Options includes copy_options::copy_symlinks,
// since _Fstat is only allowed to be a symbolic link when either skip_symlinks or copy_symlinks
if (!_STD filesystem::exists(_Tstat._Status)) {
_STD filesystem::copy_symlink(_From, _To, _Ec);
return;
}
Expand Down
7 changes: 7 additions & 0 deletions stl/inc/xfilesystem_abi.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ enum class __std_win_error : unsigned long {
_Already_exists = 183, // #define ERROR_ALREADY_EXISTS 183L
_Filename_exceeds_range = 206, // #define ERROR_FILENAME_EXCED_RANGE 206L
_Directory_name_is_invalid = 267, // #define ERROR_DIRECTORY 267L
_Reparse_tag_invalid = 4393L, // #define ERROR_REPARSE_TAG_INVALID 4393L
_Max = ~0UL // sentinel not used by Win32
};

Expand Down Expand Up @@ -309,6 +310,12 @@ _NODISCARD __std_win_error __stdcall __std_fs_create_symbolic_link(
_NODISCARD __std_win_error __stdcall __std_fs_read_reparse_data_buffer(_In_ __std_fs_file_handle _Handle,
_Out_writes_bytes_(_Buffer_size) void* _Buffer, _In_ unsigned long _Buffer_size) noexcept;

_NODISCARD __std_win_error __stdcall __std_fs_write_reparse_data_buffer(
_In_ __std_fs_file_handle _Handle, _In_ const __std_fs_reparse_data_buffer* _Buffer) noexcept;

_NODISCARD bool __stdcall __std_fs_is_junction_from_reparse_data_buffer(
_In_ const __std_fs_reparse_data_buffer* _Buffer) noexcept;

_NODISCARD _Success_(return == __std_win_error::_Success) __std_win_error
__stdcall __std_fs_read_name_from_reparse_data_buffer(
_In_ __std_fs_reparse_data_buffer* _Handle, _Out_ wchar_t** _Offset, _Out_ unsigned short* _Length) noexcept;
Expand Down
31 changes: 30 additions & 1 deletion stl/src/filesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,23 @@ _Success_(return == __std_win_error::_Success) __std_win_error
return __std_win_error{GetLastError()};
}

[[nodiscard]] __std_win_error __stdcall __std_fs_write_reparse_data_buffer(
_In_ const __std_fs_file_handle _Handle, _In_ const __std_fs_reparse_data_buffer* const _Buffer) noexcept {
if (DeviceIoControl(reinterpret_cast<HANDLE>(_Handle), FSCTL_SET_REPARSE_POINT,
const_cast<__std_fs_reparse_data_buffer*>(_Buffer), sizeof(_Buffer) + _Buffer->_Reparse_data_length,
nullptr, 0, nullptr, nullptr)) {
return __std_win_error::_Success;
}

// If DeviceIoControl fails, _Bytes_returned is 0.
return __std_win_error{GetLastError()};
}

[[nodiscard]] bool __stdcall __std_fs_is_junction_from_reparse_data_buffer(
_In_ const __std_fs_reparse_data_buffer* const _Buffer) noexcept {
return _Buffer->_Reparse_tag == IO_REPARSE_TAG_MOUNT_POINT;
}

[[nodiscard]] _Success_(return == __std_win_error::_Success) __std_win_error
__stdcall __std_fs_read_name_from_reparse_data_buffer(_In_ __std_fs_reparse_data_buffer* const _Buffer,
_Out_ wchar_t** const _Offset, _Out_ unsigned short* const _Length) noexcept {
Expand All @@ -504,8 +521,20 @@ _Success_(return == __std_win_error::_Success) __std_win_error
*_Length = _Temp_length;
*_Offset = &_Symlink_buffer._Path_buffer[_Symlink_buffer._Print_name_offset / sizeof(wchar_t)];
}
} else if (_Buffer->_Reparse_tag == IO_REPARSE_TAG_MOUNT_POINT) {
// junction
auto& _Junction_buffer = _Buffer->_Mount_point_reparse_buffer;
const unsigned short _Temp_length = _Junction_buffer._Print_name_length / sizeof(wchar_t);

if (_Temp_length == 0) {
*_Length = _Junction_buffer._Substitute_name_length / sizeof(wchar_t);
*_Offset = &_Junction_buffer._Path_buffer[_Junction_buffer._Substitute_name_offset / sizeof(wchar_t)];
} else {
*_Length = _Temp_length;
*_Offset = &_Junction_buffer._Path_buffer[_Junction_buffer._Print_name_offset / sizeof(wchar_t)];
}
} else {
return __std_win_error{ERROR_REPARSE_TAG_INVALID};
return __std_win_error::_Reparse_tag_invalid;
}

return __std_win_error::_Success;
Expand Down

0 comments on commit 069cb75

Please sign in to comment.