From 631cd5845e8de329d0e227aaa707d7ea228b8f8f Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:20:29 +0200 Subject: Merging upstream version 1.70.0+dfsg1. Signed-off-by: Daniel Baumann --- vendor/junction/.cargo-checksum.json | 1 + vendor/junction/CHANGELOG.md | 66 +++++++ vendor/junction/COPYRIGHT | 21 +++ vendor/junction/Cargo.toml | 59 +++++++ vendor/junction/README.md | 29 ++++ vendor/junction/src/internals.rs | 155 +++++++++++++++++ vendor/junction/src/internals/helpers.rs | 229 ++++++++++++++++++++++++ vendor/junction/src/internals/helpers/utf16.rs | 15 ++ vendor/junction/src/internals/types.rs | 105 +++++++++++ vendor/junction/src/lib.rs | 111 ++++++++++++ vendor/junction/src/tests.rs | 231 +++++++++++++++++++++++++ 11 files changed, 1022 insertions(+) create mode 100644 vendor/junction/.cargo-checksum.json create mode 100644 vendor/junction/CHANGELOG.md create mode 100644 vendor/junction/COPYRIGHT create mode 100644 vendor/junction/Cargo.toml create mode 100644 vendor/junction/README.md create mode 100644 vendor/junction/src/internals.rs create mode 100644 vendor/junction/src/internals/helpers.rs create mode 100644 vendor/junction/src/internals/helpers/utf16.rs create mode 100644 vendor/junction/src/internals/types.rs create mode 100644 vendor/junction/src/lib.rs create mode 100644 vendor/junction/src/tests.rs (limited to 'vendor/junction') diff --git a/vendor/junction/.cargo-checksum.json b/vendor/junction/.cargo-checksum.json new file mode 100644 index 000000000..4ddcfec7a --- /dev/null +++ b/vendor/junction/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CHANGELOG.md":"22da70a36c108924fe4daa627e579442c9bed138d97da8cfba3b9a026e2ac120","COPYRIGHT":"fd9d249f2332358a001c6bac6afdf5a06971fa3683085fd8aec609dd4cc264d4","Cargo.toml":"8b7dc08048cb598cfed96d57dcd80e66579e5d026b22d13a7f46b1e8c59b9198","README.md":"643287db5e132f715a63fea78c6e9538220b44698b7296d9f21d0b9e15cc5c46","src/internals.rs":"733250bbbcb62534d39dc35c741bf92d78aa6ff900dd2b2de87ae4b97222c080","src/internals/helpers.rs":"a6d711cceccec879d700fe63b6f93a2b48baacf14adc4f0d33447c49312bc1e3","src/internals/helpers/utf16.rs":"df1e6365b33653956576b6a80f9bf4b65e088a53432d2b3a2684e09c8b49704a","src/internals/types.rs":"0022454ab78785a111316689a5c58b4d812ff0ad28ed627834e5c2d4b7f1d722","src/lib.rs":"f1f9fd8961bba333ed583de641b1b2ed47643fda407d4c1dd5ad3db2aa17b577","src/tests.rs":"35558b2b39f2950abdec33d5e5f14cf9f0bdc8d8e28c1d5ce97a391bb45cb020"},"package":"ca39ef0d69b18e6a2fd14c2f0a1d593200f4a4ed949b240b5917ab51fac754cb"} \ No newline at end of file diff --git a/vendor/junction/CHANGELOG.md b/vendor/junction/CHANGELOG.md new file mode 100644 index 000000000..f6aa7fda1 --- /dev/null +++ b/vendor/junction/CHANGELOG.md @@ -0,0 +1,66 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + + +## [v1.0.0] - 2023-02-26 +### First major version +The public API of this crate has been unchanged around 3 years without complains. +It signals that the API is mature enough to be stable for a long time. + +## [v0.2.1] - 2023-02-25 +### Fixed +* Fix weird build failure when cross-compiling from non-Windows hosts + 657c176a440a64437236ba9d88a2ebd98a8babb1 + +## [v0.2.0] - 2020-09-05 +### Changed +* Some internal refactorings that requires Rust v1.46.0 + +## [v0.1.5] - 2020-03-18 +### Fixed +* Prevent a panic happen when open a reparse point (Commit fd9bbec6061fb100f79795ac9b64db59fbb6a3c0) + +## [v0.1.4] - 2020-01-30 +### Changed +* Ask for forgiveness in case we have no necessary permission + instead of always asking for permission. + +## [v0.1.3] - 2019-10-28 +### Changed +* Obtain appropriate privilege before opening directories. + +## [v0.1.0] - 2019-05-15 + +First release + +[v1.0.0]: https://github.com/lzutao/junction/compare/v0.2.1...v1.0.0 +[v0.2.1]: https://github.com/lzutao/junction/compare/v0.2.0...v0.2.1 +[v0.2.0]: https://github.com/lzutao/junction/compare/v0.1.0...v0.2.0 +[v0.1.5]: https://github.com/lzutao/junction/compare/v0.1.4...v0.1.5 +[v0.1.4]: https://github.com/lzutao/junction/compare/v0.1.3...v0.1.4 +[v0.1.3]: https://github.com/lzutao/junction/compare/v0.1.0...v0.1.3 +[v0.1.0]: https://github.com/lzutao/junction/releases/tag/v0.1.0 diff --git a/vendor/junction/COPYRIGHT b/vendor/junction/COPYRIGHT new file mode 100644 index 000000000..9cbcc2004 --- /dev/null +++ b/vendor/junction/COPYRIGHT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 lzutao + +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. diff --git a/vendor/junction/Cargo.toml b/vendor/junction/Cargo.toml new file mode 100644 index 000000000..8e4198529 --- /dev/null +++ b/vendor/junction/Cargo.toml @@ -0,0 +1,59 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "junction" +version = "1.0.0" +authors = ["Lzu Tao "] +exclude = [ + "/.github", + "/HOW-TO-RELEASE.md", + "/azure-pipelines.yml", +] +description = "library for working with NTFS junctions" +documentation = "https://docs.rs/junction/*/x86_64-pc-windows-msvc/junction/" +readme = "README.md" +keywords = [ + "junction", + "symlink", +] +categories = [ + "api-bindings", + "os::windows-apis", +] +license = "MIT" +repository = "https://github.com/lzutao/junction" + +[package.metadata.docs.rs] +targets = ["x86_64-pc-windows-msvc"] + +[dev-dependencies.tempfile] +version = "3" + +[target."cfg(windows)".dependencies.scopeguard] +version = "1" +default-features = false + +[target."cfg(windows)".dependencies.winapi] +version = "0.3" +features = [ + "errhandlingapi", + "fileapi", + "guiddef", + "handleapi", + "ioapiset", + "processthreadsapi", + "securitybaseapi", + "winbase", + "winioctl", + "winnt", +] diff --git a/vendor/junction/README.md b/vendor/junction/README.md new file mode 100644 index 000000000..2917708e7 --- /dev/null +++ b/vendor/junction/README.md @@ -0,0 +1,29 @@ +# junction + +Library for working with NTFS junctions. + +[![Build Status][actions-badge]][actions-url] +[![Documentation](https://docs.rs/junction/badge.svg)](https://docs.rs/junction) +[![Crates.io](https://img.shields.io/crates/v/junction.svg)](https://crates.io/crates/junction) + +### Minimal Supported Rust versions + +1.48.0 + +## All relevant references + +* https://www.codeproject.com/Articles/194/Windows-2000-Junction-Points#The_Solution +* https://www.codeproject.com/Articles/15633/Manipulating-NTFS-Junction-Points-in-NET +* http://www.flexhex.com/docs/articles/hard-links.phtml +* https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html +* https://github.com/googleprojectzero/symboliclink-testing-tools/blob/master/DumpReparsePoint/DumpReparsePoint.cpp +* https://github.com/googleprojectzero/symboliclink-testing-tools/blob/master/CommonUtils/ReparsePoint.cpp +* https://github.com/containerd/continuity/blob/master/syscallx/syscall_windows.go + +## License + +All the code in this repository is released under the MIT License, +for more information, please read COPYRIGHT file. + +[actions-badge]: https://github.com/lzutao/junction/workflows/Rust/badge.svg?branchName=master +[actions-url]: https://github.com/lzutao/junction/actions diff --git a/vendor/junction/src/internals.rs b/vendor/junction/src/internals.rs new file mode 100644 index 000000000..71b03863f --- /dev/null +++ b/vendor/junction/src/internals.rs @@ -0,0 +1,155 @@ +mod helpers; +mod types; + +use types::ReparseDataBuffer; +use types::{MOUNT_POINT_REPARSE_BUFFER_HEADER_SIZE, REPARSE_DATA_BUFFER_HEADER_SIZE}; + +use std::cmp; +use std::fs; +use std::path::{Path, PathBuf}; +use std::ptr; +use std::slice; +use std::{ffi::OsString, os::windows::ffi::OsStringExt}; +use std::{io, os::windows::io::AsRawHandle}; + +use winapi::um::winnt::{IO_REPARSE_TAG_MOUNT_POINT, MAXIMUM_REPARSE_DATA_BUFFER_SIZE}; + +// makes sure layout of RawHandle and winapi's HANDLE are the same +// for pointer casts between them. +const _: () = { + use std::alloc::Layout; + let std_layout = Layout::new::(); + let winapi_layout = Layout::new::(); + // MSVR(Rust v1.57): use assert! instead + [(); 1][!(std_layout.size() == winapi_layout.size()) as usize]; + [(); 1][!(std_layout.align() == winapi_layout.align()) as usize]; +}; + +/// This prefix indicates to NTFS that the path is to be treated as a non-interpreted +/// path in the virtual file system. +const NON_INTERPRETED_PATH_PREFIX: [u16; 4] = [b'\\' as u16, b'?' as _, b'?' as _, b'\\' as _]; +const WCHAR_SIZE: u16 = std::mem::size_of::() as _; + +pub fn create(target: &Path, junction: &Path) -> io::Result<()> { + const UNICODE_NULL_SIZE: u16 = WCHAR_SIZE; + const MAX_AVAILABLE_PATH_BUFFER: u16 = MAXIMUM_REPARSE_DATA_BUFFER_SIZE as u16 + - REPARSE_DATA_BUFFER_HEADER_SIZE + - MOUNT_POINT_REPARSE_BUFFER_HEADER_SIZE + - 2 * UNICODE_NULL_SIZE; + + // We're using low-level APIs to create the junction, and these are more picky about paths. + // For example, forward slashes cannot be used as a path separator, so we should try to + // canonicalize the path first. + let mut target = helpers::get_full_path(target)?; + fs::create_dir(junction)?; + let file = helpers::open_reparse_point(junction, true)?; + // "\??\" + target + let len = NON_INTERPRETED_PATH_PREFIX.len().saturating_add(target.len()); + let target_len_in_bytes = { + let min_len = cmp::min(len, u16::MAX as usize) as u16; + // Len without `UNICODE_NULL` at the end + let target_len_in_bytes = min_len.saturating_mul(WCHAR_SIZE); + // Check if `target_wchar.len()` may lead to a buffer overflow. + if target_len_in_bytes > MAX_AVAILABLE_PATH_BUFFER { + return Err(io::Error::new(io::ErrorKind::Other, "`target` is too long")); + } + target_len_in_bytes + }; + let mut target_wchar: Vec = Vec::with_capacity(len); + target_wchar.extend(&NON_INTERPRETED_PATH_PREFIX); + target_wchar.append(&mut target); + + // Redefine the above char array into a ReparseDataBuffer we can work with + let mut data = AlignAs { + value: Vec::with_capacity(MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize), + }; + let rdb = data.value.as_mut_ptr().cast::(); + let in_buffer_size: u16 = unsafe { + // Set the type of reparse point we are creating + ptr::addr_of_mut!((*rdb).reparse_tag).write(IO_REPARSE_TAG_MOUNT_POINT); + ptr::addr_of_mut!((*rdb).reserved).write(0); + + // Copy the junction's target + ptr::addr_of_mut!((*rdb).reparse_buffer.substitute_name_offset).write(0); + ptr::addr_of_mut!((*rdb).reparse_buffer.substitute_name_length).write(target_len_in_bytes); + + // Copy the junction's link name + ptr::addr_of_mut!((*rdb).reparse_buffer.print_name_offset).write(target_len_in_bytes + UNICODE_NULL_SIZE); + ptr::addr_of_mut!((*rdb).reparse_buffer.print_name_length).write(0); + + // Safe because we checked `MAX_AVAILABLE_PATH_BUFFER` + ptr::copy_nonoverlapping( + target_wchar.as_ptr().cast::(), + ptr::addr_of_mut!((*rdb).reparse_buffer.path_buffer).cast(), + target_wchar.len(), + ); + + // Set the total size of the data buffer + let size = target_len_in_bytes.wrapping_add(MOUNT_POINT_REPARSE_BUFFER_HEADER_SIZE + 2 * UNICODE_NULL_SIZE); + ptr::addr_of_mut!((*rdb).reparse_data_length).write(size); + size.wrapping_add(REPARSE_DATA_BUFFER_HEADER_SIZE) + }; + + helpers::set_reparse_point(file.as_raw_handle().cast(), rdb, u32::from(in_buffer_size)) +} + +pub fn delete(junction: &Path) -> io::Result<()> { + let file = helpers::open_reparse_point(junction, true)?; + helpers::delete_reparse_point(file.as_raw_handle().cast()) +} + +// Makes sure `align(ReparseDataBuffer) == 4` for struct `AlignAs` to be sound. +const _: () = { + const A: usize = std::mem::align_of::(); + if A != 4 { + let _ = [0; 0][A]; + } +}; + +type MaybeU8 = std::mem::MaybeUninit; +#[repr(align(4))] +struct AlignAs { + value: Vec, +} + +pub fn exists(junction: &Path) -> io::Result { + if !junction.exists() { + return Ok(false); + } + let file = helpers::open_reparse_point(junction, false)?; + // Allocate enough space to fit the maximum sized reparse data buffer + let mut data = AlignAs { + value: Vec::with_capacity(MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize), + }; + let rdb = data.value.as_mut_ptr().cast::(); + helpers::get_reparse_data_point(file.as_raw_handle().cast(), rdb)?; + // The reparse tag indicates if this is a junction or not + Ok(unsafe { (*rdb).reparse_tag } == IO_REPARSE_TAG_MOUNT_POINT) +} + +pub fn get_target(junction: &Path) -> io::Result { + if !junction.exists() { + return Err(io::Error::new(io::ErrorKind::NotFound, "`junction` does not exist")); + } + let file = helpers::open_reparse_point(junction, false)?; + let mut data = AlignAs { + value: Vec::with_capacity(MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize), + }; + let rdb = data.value.as_mut_ptr().cast::(); + helpers::get_reparse_data_point(file.as_raw_handle().cast(), rdb)?; + // SAFETY: rdb should be initialized now + let rdb = unsafe { &*rdb }; + if rdb.reparse_tag == IO_REPARSE_TAG_MOUNT_POINT { + let offset = rdb.reparse_buffer.substitute_name_offset / WCHAR_SIZE; + let len = rdb.reparse_buffer.substitute_name_length / WCHAR_SIZE; + let wide = unsafe { + let buf = rdb.reparse_buffer.path_buffer.as_ptr().add(offset as usize); + slice::from_raw_parts(buf, len as usize) + }; + // In case of "\??\C:\foo\bar" + let wide = wide.strip_prefix(&NON_INTERPRETED_PATH_PREFIX).unwrap_or(wide); + Ok(PathBuf::from(OsString::from_wide(wide))) + } else { + Err(io::Error::new(io::ErrorKind::Other, "not a reparse tag mount point")) + } +} diff --git a/vendor/junction/src/internals/helpers.rs b/vendor/junction/src/internals/helpers.rs new file mode 100644 index 000000000..4731dfca2 --- /dev/null +++ b/vendor/junction/src/internals/helpers.rs @@ -0,0 +1,229 @@ +#[macro_use] +mod utf16; + +use super::types::REPARSE_GUID_DATA_BUFFER_HEADER_SIZE; +use super::types::{ReparseDataBuffer, ReparseGuidDataBuffer}; + +use std::ffi::OsStr; +use std::fs::{File, OpenOptions}; +use std::io; +use std::mem::{self, MaybeUninit}; +use std::os::windows::ffi::OsStrExt; +use std::os::windows::fs::OpenOptionsExt; +use std::path::Path; +use std::ptr; + +use scopeguard::ScopeGuard; +use winapi::um::errhandlingapi::{GetLastError, SetLastError}; +use winapi::um::fileapi::GetFullPathNameW; +use winapi::um::handleapi::CloseHandle; +use winapi::um::ioapiset::DeviceIoControl; +use winapi::um::processthreadsapi::{GetCurrentProcess, OpenProcessToken}; +use winapi::um::securitybaseapi::AdjustTokenPrivileges; +use winapi::um::winbase::LookupPrivilegeValueW; +use winapi::um::winbase::{FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT}; +use winapi::um::winioctl::{FSCTL_DELETE_REPARSE_POINT, FSCTL_GET_REPARSE_POINT, FSCTL_SET_REPARSE_POINT}; +use winapi::um::winnt::*; + +pub static SE_RESTORE_NAME: [u16; 19] = utf16s!(b"SeRestorePrivilege\0"); +pub static SE_BACKUP_NAME: [u16; 18] = utf16s!(b"SeBackupPrivilege\0"); + +pub fn open_reparse_point(reparse_point: &Path, rdwr: bool) -> io::Result { + let access = if rdwr { + GENERIC_READ | GENERIC_WRITE + } else { + GENERIC_READ + }; + let mut opts = OpenOptions::new(); + opts.access_mode(access) + .share_mode(0) + .custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS); + match opts.open(reparse_point) { + Err(e) if e.kind() == io::ErrorKind::PermissionDenied => { + // Obtain privilege in case we don't have it yet + set_privilege(rdwr)?; + opts.open(reparse_point) + } + other => other, + } +} + +fn set_privilege(rdwr: bool) -> io::Result<()> { + const ERROR_NOT_ALL_ASSIGNED: u32 = 1300; + const TOKEN_PRIVILEGES_SIZE: u32 = mem::size_of::() as _; + unsafe { + let mut handle = ptr::null_mut(); + if OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &mut handle) == 0 { + return Err(io::Error::last_os_error()); + } + let handle = scopeguard::guard(handle, |h| { + CloseHandle(h); + }); + let mut tp: TOKEN_PRIVILEGES = mem::zeroed(); + let name = if rdwr { + SE_RESTORE_NAME.as_ptr() + } else { + SE_BACKUP_NAME.as_ptr() + }; + if LookupPrivilegeValueW(ptr::null(), name, &mut tp.Privileges[0].Luid) == 0 { + return Err(io::Error::last_os_error()); + } + tp.PrivilegeCount = 1; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + if AdjustTokenPrivileges( + *handle, + 0, + &mut tp, + TOKEN_PRIVILEGES_SIZE, + ptr::null_mut(), + ptr::null_mut(), + ) == 0 + { + return Err(io::Error::last_os_error()); + } + if GetLastError() == ERROR_NOT_ALL_ASSIGNED { + return Err(io::Error::from_raw_os_error(ERROR_NOT_ALL_ASSIGNED as i32)); + } + + let handle = ScopeGuard::into_inner(handle); + if CloseHandle(handle) == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } + } +} + +pub fn get_reparse_data_point(handle: HANDLE, rdb: *mut ReparseDataBuffer) -> io::Result<()> { + // Call DeviceIoControl to get the reparse point data + let mut bytes_returned: u32 = 0; + if unsafe { + DeviceIoControl( + handle, + FSCTL_GET_REPARSE_POINT, + ptr::null_mut(), + 0, + rdb.cast(), + MAXIMUM_REPARSE_DATA_BUFFER_SIZE, + &mut bytes_returned, + ptr::null_mut(), + ) + } == 0 + { + return Err(io::Error::last_os_error()); + } + Ok(()) +} + +pub fn set_reparse_point(handle: HANDLE, rdb: *mut ReparseDataBuffer, len: u32) -> io::Result<()> { + let mut bytes_returned: u32 = 0; + if unsafe { + DeviceIoControl( + handle, + FSCTL_SET_REPARSE_POINT, + rdb.cast(), + len, + ptr::null_mut(), + 0, + &mut bytes_returned, + ptr::null_mut(), + ) + } == 0 + { + return Err(io::Error::last_os_error()); + } + Ok(()) +} + +// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa364560(v=vs.85).aspx +pub fn delete_reparse_point(handle: HANDLE) -> io::Result<()> { + let mut rgdb: ReparseGuidDataBuffer = unsafe { mem::zeroed() }; + rgdb.reparse_tag = IO_REPARSE_TAG_MOUNT_POINT; + let mut bytes_returned: u32 = 0; + + if unsafe { + DeviceIoControl( + handle, + FSCTL_DELETE_REPARSE_POINT, + (&mut rgdb as *mut ReparseGuidDataBuffer).cast(), + u32::from(REPARSE_GUID_DATA_BUFFER_HEADER_SIZE), + ptr::null_mut(), + 0, + &mut bytes_returned, + ptr::null_mut(), + ) + } == 0 + { + return Err(io::Error::last_os_error()); + } + Ok(()) +} + +fn os_str_to_utf16(s: &OsStr) -> Vec { + s.encode_wide().chain(std::iter::once(0)).collect() +} + +type MaybeU16 = MaybeUninit; +// Returns the len of buf when success. +// Ref: . +pub fn get_full_path(target: &Path) -> io::Result> { + let path = os_str_to_utf16(target.as_os_str()); + let file_part = ptr::null_mut(); + const U16_UNINIT: MaybeU16 = MaybeU16::uninit(); + const ERROR_INSUFFICIENT_BUFFER: u32 = 122; + // Start off with a stack buf but then spill over to the heap if we end up + // needing more space. + // + // This initial size also works around `GetFullPathNameW` returning + // incorrect size hints for some short paths: + // https://github.com/dylni/normpath/issues/5 + let mut stack_buf: [MaybeU16; 512] = [U16_UNINIT; 512]; + let mut heap_buf: Vec = Vec::new(); + unsafe { + let mut n = stack_buf.len(); + loop { + let buf = if n <= stack_buf.len() { + &mut stack_buf[..] + } else { + let extra = n - heap_buf.len(); + heap_buf.reserve(extra); + // We used `reserve` and not `reserve_exact`, so in theory we + // may have gotten more than requested. If so, we'd like to use + // it... so long as we won't cause overflow. + n = heap_buf.capacity().min(u32::MAX as usize); + // Safety: MaybeUninit does not need initialization + heap_buf.set_len(n); + &mut heap_buf[..] + }; + + SetLastError(0); + let k = GetFullPathNameW( + path.as_ptr().cast::(), + n as u32, + maybe_slice_to_ptr(buf), + file_part, + ) as usize; + if k == 0 { + return Err(crate::io::Error::last_os_error()); + } + if GetLastError() == ERROR_INSUFFICIENT_BUFFER { + n = n.saturating_mul(2).min(u32::MAX as usize); + } else if k > n { + n = k; + } else { + // Safety: First `k` values are initialized. + let slice: &[u16] = maybe_slice_assume_init(&buf[..k]); + return Ok(slice.into()); + } + } + } +} + +unsafe fn maybe_slice_to_ptr(s: &mut [MaybeU16]) -> *mut u16 { + s.as_mut_ptr() as *mut u16 +} + +unsafe fn maybe_slice_assume_init(s: &[MaybeU16]) -> &[u16] { + // SAFETY: `MaybeUninit` and T are guaranteed to have the same layout + &*(s as *const [MaybeU16] as *const [u16]) +} diff --git a/vendor/junction/src/internals/helpers/utf16.rs b/vendor/junction/src/internals/helpers/utf16.rs new file mode 100644 index 000000000..57b75fb61 --- /dev/null +++ b/vendor/junction/src/internals/helpers/utf16.rs @@ -0,0 +1,15 @@ +// FIXME(const_generic) +/// Convert ASCII bytes to UTF-16 sequences. +macro_rules! utf16s { + ($src:expr) => {{ + const SRC: &[u8] = $src; + const N: usize = SRC.len(); + let mut i = 0; + let mut dst = [0u16; N]; + while i < N { + dst[i] = SRC[i] as u16; + i += 1; + } + dst + }}; +} diff --git a/vendor/junction/src/internals/types.rs b/vendor/junction/src/internals/types.rs new file mode 100644 index 000000000..b10bf2935 --- /dev/null +++ b/vendor/junction/src/internals/types.rs @@ -0,0 +1,105 @@ +use winapi::shared::guiddef; + +// NOTE: to use `size_of` operator, below structs should be packed. +/// Reparse Data Buffer header size = `sizeof(u32) + 2 * sizeof(u16)` +pub const REPARSE_DATA_BUFFER_HEADER_SIZE: u16 = 8; +/// Reparse GUID Data Buffer header size = `sizeof(u32) + 2*sizeof(u16) + sizeof(GUID)` +pub const REPARSE_GUID_DATA_BUFFER_HEADER_SIZE: u16 = 24; +/// MountPointReparseBuffer header size = `4 * sizeof(u16)` +pub const MOUNT_POINT_REPARSE_BUFFER_HEADER_SIZE: u16 = 8; + +type VarLenArr = [T; 1]; + +#[repr(C)] +#[derive(Debug)] +pub struct MountPointReparseBuffer { + /// Offset, in bytes, of the substitute name string in the `path_buffer` array. + /// Note that this offset must be divided by `sizeof(u16)` to get the array index. + pub substitute_name_offset: u16, + /// Length, in bytes, of the substitute name string. If this string is `NULL`-terminated, + /// it does not include space for the `UNICODE_NULL` character. + pub substitute_name_length: u16, + /// Offset, in bytes, of the print name string in the `path_buffer` array. + /// Note that this offset must be divided by `sizeof(u16)` to get the array index. + pub print_name_offset: u16, + /// Length, in bytes, of the print name string. If this string is `NULL`-terminated, + /// it does not include space for the `UNICODE_NULL` character. + pub print_name_length: u16, + /// A buffer containing the Unicode-encoded path string. The path string contains the + /// substitute name string and print name string. The substitute name and print name strings + /// can appear in any order in the path_buffer. (To locate the substitute name and print name + /// strings in the path_buffer, use the `substitute_name_offset`, `substitute_name_length`, + /// `print_name_offset`, and `print_name_length` members.) + pub path_buffer: VarLenArr, +} + +/// This structure contains reparse point data for a Microsoft reparse point. +/// +/// Read more: +/// * https://msdn.microsoft.com/en-us/windows/desktop/ff552012 +/// * https://www.pinvoke.net/default.aspx/Structures.REPARSE_DATA_BUFFER +#[repr(C)] +#[derive(Debug)] +pub struct ReparseDataBuffer { + /// Reparse point tag. Must be a Microsoft reparse point tag. + pub reparse_tag: u32, + /// Size, in bytes, of the reparse data in the `data_buffer` member. + /// Or the size of the `path_buffer` field, in bytes, plus 8 (= 4 * sizeof(u16)) + pub reparse_data_length: u16, + /// Reversed. It SHOULD be set to 0, and MUST be ignored. + pub reserved: u16, + pub reparse_buffer: MountPointReparseBuffer, +} + +#[repr(C)] +#[derive(Debug)] +pub struct GenericReparseBuffer { + /// Microsoft-defined data for the reparse point. + pub data_buffer: VarLenArr, +} + +/// Used by all third-party layered drivers to store data for a reparse point. +/// +/// Each reparse point contains one instance of a `ReparseGuidDataBuffer` structure. +/// +/// Read more: +/// * +#[repr(C)] +pub struct ReparseGuidDataBuffer { + /// Reparse point tag. This member identifies the structure of the user-defined + /// reparse data. + pub reparse_tag: u32, + /// The size of the reparse data in the `data_buffer` member, in bytes. This + /// value may vary with different tags and may vary between two uses of the + /// same tag. + pub reparse_data_length: u16, + /// Reserved; do not use. + pub reserved: u16, + /// A `GUID` that uniquely identifies the reparse point. When setting a reparse + /// point, the application must provide a non-`NULL` `GUID` in the `reparse_guid` + /// member. When retrieving a reparse point from the file system, `reparse_guid` + /// is the `GUID` assigned when the reparse point was set. + pub reparse_guid: guiddef::GUID, + /// The user-defined data for the reparse point. The contents are determined by + /// the reparse point implementer. The tag in the `reparse_tag` member and the + /// `GUID` in the `reparse_guid` member indicate how the data is to be interpreted. + pub generic: GenericReparseBuffer, +} + +impl std::fmt::Debug for ReparseGuidDataBuffer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ReparseGuidDataBuffer") + .field("reparse_tag", &self.reparse_tag) + .field("reparse_data_length", &self.reparse_data_length) + .field("reserved", &self.reserved) + .field( + "reparse_guid", + &format_args!( + "{}:{}:{}:{:?}", + self.reparse_guid.Data1, self.reparse_guid.Data2, self.reparse_guid.Data3, self.reparse_guid.Data4, + ), + ) + .field("generic", &self.generic.data_buffer) + .finish() + } +} diff --git a/vendor/junction/src/lib.rs b/vendor/junction/src/lib.rs new file mode 100644 index 000000000..5f86f792b --- /dev/null +++ b/vendor/junction/src/lib.rs @@ -0,0 +1,111 @@ +/*! +Library for working with NTFS junctions. + +Junction Points are a little known NTFS v5+ feature roughly equivalent to Unix +directory symbolic links. + +They are supported in Windows 2000 and onwards, where a directory +serves as a symbolic link to another directory on the computer. For example, +if the directory `D:\SYMLINK` specified `C:\WINNT\SYSTEM32` as its target, then +an application accessing `D:\SYMLINK\DRIVERS` would in reality be accessing +`C:\WINNT\SYSTEM32\DRIVERS`. +*/ +#![doc(html_root_url = "https://docs.rs/junction/~1")] +#![cfg(windows)] +#![deny(rust_2018_idioms)] + +mod internals; + +#[cfg(test)] +mod tests; + +use std::io; +use std::path::{Path, PathBuf}; + +/// Creates a junction point from the specified directory to the specified target directory. +/// +/// N.B. Only works on NTFS. +/// +/// # Example +/// +/// ```rust +/// use std::io; +/// use std::path::Path; +/// # use std::fs; +/// # use junction::create; +/// fn main() -> io::Result<()> { +/// let tmpdir = tempfile::tempdir()?; +/// let target = tmpdir.path().join("target"); +/// let junction = tmpdir.path().join("junction"); +/// # fs::create_dir_all(&target)?; +/// create(&target, &junction) +/// } +/// ``` +pub fn create(target: P, junction: Q) -> io::Result<()> +where + P: AsRef, + Q: AsRef, +{ + internals::create(target.as_ref(), junction.as_ref()) +} + +/// Deletes a `junction` reparse point from the specified file or directory. +/// +/// N.B. Only works on NTFS. +/// +/// This function does not delete the file or directory. Also it does nothing +/// if the `junction` point does not exist. +/// +/// # Example +/// +/// ```rust +/// use std::io; +/// use std::path::Path; +/// # use std::fs; +/// # use junction::{create, delete}; +/// fn main() -> io::Result<()> { +/// let tmpdir = tempfile::tempdir()?; +/// let target = tmpdir.path().join("target"); +/// let junction = tmpdir.path().join("junction"); +/// # fs::create_dir_all(&target)?; +/// create(&target, &junction)?; +/// delete(&junction) +/// } +/// ``` +pub fn delete>(junction: P) -> io::Result<()> { + internals::delete(junction.as_ref()) +} + +/// Determines whether the specified path exists and refers to a junction point. +/// +/// # Example +/// +/// ```rust +/// use std::io; +/// # use junction::exists; +/// fn main() -> io::Result<()> { +/// assert!(exists(r"C:\Users\Default User")?); +/// Ok(()) +/// } +/// ``` +pub fn exists>(junction: P) -> io::Result { + internals::exists(junction.as_ref()) +} + +/// Gets the target of the specified junction point. +/// +/// N.B. Only works on NTFS. +/// +/// # Example +/// +/// ```rust +/// use std::io; +/// # use junction::get_target; +/// fn main() -> io::Result<()> { +/// assert_eq!(get_target(r"C:\Users\Default User")?.to_str(), Some(r"C:\Users\Default")); +/// Ok(()) +/// } +/// ``` +pub fn get_target>(junction: P) -> io::Result { + internals::get_target(junction.as_ref()) +} diff --git a/vendor/junction/src/tests.rs b/vendor/junction/src/tests.rs new file mode 100644 index 000000000..961e5e450 --- /dev/null +++ b/vendor/junction/src/tests.rs @@ -0,0 +1,231 @@ +use std::fs::{self, File}; +use std::io::{self, Write}; +use std::os::windows::fs::symlink_file; +#[cfg(miri)] +use std::path::{Path, PathBuf}; + +#[cfg(not(miri))] +use tempfile::TempDir; + +#[cfg(miri)] +struct TempDir { + path: PathBuf, +} + +#[cfg(miri)] +impl TempDir { + fn path(&self) -> &Path { + self.path.as_path() + } +} + +// https://docs.microsoft.com/en-us/windows/desktop/debug/system-error-codes +const ERROR_NOT_A_REPARSE_POINT: i32 = 0x1126; +const ERROR_ALREADY_EXISTS: i32 = 0xb7; + +#[cfg(not(miri))] +fn create_tempdir() -> TempDir { + tempfile::Builder::new() + .prefix("junction-test-") + .tempdir_in("target/debug") + .unwrap() +} + +#[cfg(miri)] +fn create_tempdir() -> TempDir { + TempDir { + path: PathBuf::from("target/debug/junction-test"), + } +} + +#[test] +fn create_dir_all_with_junctions() { + let tmpdir = create_tempdir(); + let target = tmpdir.path().join("target"); + + let junction = tmpdir.path().join("junction"); + let b = junction.join("a/b"); + + fs::create_dir_all(&target).unwrap(); + + super::create(&target, &junction).unwrap(); + fs::create_dir_all(&b).unwrap(); + // the junction itself is not a directory, but `is_dir()` on a Path + // follows links + assert!(junction.is_dir()); + assert!(b.exists()); +} + +#[test] +fn create_recursive_rmdir() { + let tmpdir = create_tempdir(); + let d1 = tmpdir.path().join("d1"); // "d1" + let dt = d1.join("t"); // "d1/t" + let dtt = dt.join("t"); // "d1/t/t" + let d2 = tmpdir.path().join("d2"); // "d2" + let canary = d2.join("do_not_delete"); // "d2/do_not_delete" + + fs::create_dir_all(dtt).unwrap(); + fs::create_dir_all(&d2).unwrap(); + File::create(&canary).unwrap().write_all(b"foo").unwrap(); + + super::create(d2, dt.join("d2")).unwrap(); // "d1/t/d2" -> "d2" + + let _ = symlink_file(&canary, d1.join("canary")); // d1/canary -> d2/do_not_delete + fs::remove_dir_all(&d1).unwrap(); + + assert!(!d1.is_dir()); + assert!(canary.exists()); +} + +#[test] +fn create_recursive_rmdir_of_symlink() { + // test we do not recursively delete a symlink but only dirs. + let tmpdir = create_tempdir(); + let link = tmpdir.path().join("link"); + let dir = tmpdir.path().join("dir"); + let canary = dir.join("do_not_delete"); + fs::create_dir_all(&dir).unwrap(); + File::create(&canary).unwrap().write_all(b"foo").unwrap(); + super::create(&dir, &link).unwrap(); + fs::remove_dir_all(&link).unwrap(); + + assert!(!link.is_dir()); + assert!(canary.exists()); +} + +#[test] +fn create_directory_exist_before() { + let tmpdir = create_tempdir(); + + let target = tmpdir.path().join("target"); + let junction = tmpdir.path().join("junction"); + + fs::create_dir_all(&junction).unwrap(); + + match super::create(target, &junction) { + Err(ref e) if e.raw_os_error() == Some(ERROR_ALREADY_EXISTS) => {} + _ => panic!("directory exists before creating"), + } +} + +#[test] +fn create_target_no_exist() { + let tmpdir = create_tempdir(); + + let target = tmpdir.path().join("target"); + let junction = tmpdir.path().join("junction"); + + match super::create(target, junction) { + Ok(()) => {} + _ => panic!("junction should point to non exist target path"), + } +} + +#[test] +fn delete_junctions() { + let tmpdir = create_tempdir(); + + let non_existence_dir = tmpdir.path().join("non_existence_dir"); + match super::delete(non_existence_dir) { + Err(ref e) if e.kind() == io::ErrorKind::NotFound => {} + _ => panic!("target path does not exist or is not a directory"), + } + + let dir_not_junction = tmpdir.path().join("dir_not_junction"); + fs::create_dir_all(&dir_not_junction).unwrap(); + match super::delete(dir_not_junction) { + Err(ref e) if e.raw_os_error() == Some(ERROR_NOT_A_REPARSE_POINT) => {} + _ => panic!("target path is not a junction point"), + } + + let file = tmpdir.path().join("foo-file"); + File::create(&file).unwrap().write_all(b"foo").unwrap(); + match super::delete(&file) { + Err(ref e) if e.raw_os_error() == Some(ERROR_NOT_A_REPARSE_POINT) => {} + _ => panic!("target path is not a junction point"), + } +} + +#[test] +fn exists_verify() { + let tmpdir = create_tempdir(); + + // Check no such directory or file + let no_such_dir = tmpdir.path().join("no_such_dir"); + assert!(!super::exists(no_such_dir).unwrap()); + + // Target exists but not a junction + let no_such_file = tmpdir.path().join("file"); + File::create(&no_such_file).unwrap().write_all(b"foo").unwrap(); + match super::exists(&no_such_file) { + Err(ref e) if e.raw_os_error() == Some(ERROR_NOT_A_REPARSE_POINT) => {} + _ => panic!("target exists but not a junction"), + } + + let target = tmpdir.path().join("target"); + let junction = tmpdir.path().join("junction"); + let file = target.join("file"); + let junction_file = junction.join("file"); + + fs::create_dir_all(&target).unwrap(); + File::create(file).unwrap().write_all(b"foo").unwrap(); + + assert!( + !junction_file.exists(), + "file should not be located until junction created" + ); + assert!(!super::exists(&junction).unwrap(), "junction not created yet"); + + super::create(&target, &junction).unwrap(); + assert!(super::exists(&junction).unwrap(), "junction should exist now"); + assert_eq!(&super::get_target(&junction).unwrap(), &target); + assert!(junction_file.exists(), "file should be accessible via the junction"); + + super::delete(&junction).unwrap(); + match super::exists(&junction) { + Err(ref e) if e.raw_os_error() == Some(ERROR_NOT_A_REPARSE_POINT) => {} + _ => panic!("junction had been deleted"), + } + assert!( + !junction_file.exists(), + "file should not be located after junction deleted" + ); + assert!(junction.exists(), "directory should not be deleted"); +} + +#[test] +fn get_target_user_dirs() { + // junction + assert_eq!( + super::get_target(r"C:\Users\Default User").unwrap().to_str(), + Some(r"C:\Users\Default"), + ); + // junction with special permissions + assert_eq!( + super::get_target(r"C:\Documents and Settings\").unwrap().to_str(), + Some(r"C:\Users"), + ); + + let tmpdir = create_tempdir(); + + let non_existence_dir = tmpdir.path().join("non_existence_dir"); + match super::get_target(non_existence_dir) { + Err(ref e) if e.kind() == io::ErrorKind::NotFound => {} + _ => panic!("target path does not exist or is not a directory"), + } + + let dir_not_junction = tmpdir.path().join("dir_not_junction"); + fs::create_dir_all(&dir_not_junction).unwrap(); + match super::get_target(dir_not_junction) { + Err(ref e) if e.raw_os_error() == Some(ERROR_NOT_A_REPARSE_POINT) => {} + _ => panic!("target path is not a junction point"), + } + + let file = tmpdir.path().join("foo-file"); + File::create(&file).unwrap().write_all(b"foo").unwrap(); + match super::get_target(file) { + Err(ref e) if e.raw_os_error() == Some(ERROR_NOT_A_REPARSE_POINT) => {} + _ => panic!("target path is not a junction point"), + } +} -- cgit v1.2.3