summaryrefslogtreecommitdiffstats
path: root/vendor/junction
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:20:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:20:29 +0000
commit631cd5845e8de329d0e227aaa707d7ea228b8f8f (patch)
treea1b87c8f8cad01cf18f7c5f57a08f102771ed303 /vendor/junction
parentAdding debian version 1.69.0+dfsg1-1. (diff)
downloadrustc-631cd5845e8de329d0e227aaa707d7ea228b8f8f.tar.xz
rustc-631cd5845e8de329d0e227aaa707d7ea228b8f8f.zip
Merging upstream version 1.70.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/junction')
-rw-r--r--vendor/junction/.cargo-checksum.json1
-rw-r--r--vendor/junction/CHANGELOG.md66
-rw-r--r--vendor/junction/COPYRIGHT21
-rw-r--r--vendor/junction/Cargo.toml59
-rw-r--r--vendor/junction/README.md29
-rw-r--r--vendor/junction/src/internals.rs155
-rw-r--r--vendor/junction/src/internals/helpers.rs229
-rw-r--r--vendor/junction/src/internals/helpers/utf16.rs15
-rw-r--r--vendor/junction/src/internals/types.rs105
-rw-r--r--vendor/junction/src/lib.rs111
-rw-r--r--vendor/junction/src/tests.rs231
11 files changed, 1022 insertions, 0 deletions
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).
+
+<!--
+# Guiding Principles
+
+* Changelogs are for _humans_, not machines.
+* There should be an entry for every single version.
+* The same types of changes should be grouped.
+* Versions and sections should be linkable.
+* The latest version comes first.
+* The release date of each version is displayed.
+* Mention whether you follow Semantic Versioning.
+
+# Types of changes
+
+* `Added` for new features.
+* `Changed` for changes in existing functionality.
+* `Deprecated` for soon-to-be removed features.
+* `Removed` for now removed features.
+* `Fixed` for any bug fixes.
+* `Security` in case of vulnerabilities.
+-->
+
+## [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 <taolzu@gmail.com>"]
+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::<std::os::windows::io::RawHandle>();
+ let winapi_layout = Layout::new::<winapi::um::winnt::HANDLE>();
+ // 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::<u16>() 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<u16> = 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::<ReparseDataBuffer>();
+ 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::<u16>(),
+ 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::<ReparseDataBuffer>();
+ if A != 4 {
+ let _ = [0; 0][A];
+ }
+};
+
+type MaybeU8 = std::mem::MaybeUninit<u8>;
+#[repr(align(4))]
+struct AlignAs {
+ value: Vec<MaybeU8>,
+}
+
+pub fn exists(junction: &Path) -> io::Result<bool> {
+ 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::<ReparseDataBuffer>();
+ 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<PathBuf> {
+ 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::<ReparseDataBuffer>();
+ 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<File> {
+ 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::<TOKEN_PRIVILEGES>() 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<u16> {
+ s.encode_wide().chain(std::iter::once(0)).collect()
+}
+
+type MaybeU16 = MaybeUninit<u16>;
+// Returns the len of buf when success.
+// Ref: <rust-lang/rust/src/libstd/sys/windows/mod.rs#L106>.
+pub fn get_full_path(target: &Path) -> io::Result<Vec<u16>> {
+ 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<MaybeU16> = 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<u16> does not need initialization
+ heap_buf.set_len(n);
+ &mut heap_buf[..]
+ };
+
+ SetLastError(0);
+ let k = GetFullPathNameW(
+ path.as_ptr().cast::<u16>(),
+ 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<T>` 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> = [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<u16>,
+}
+
+/// 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<u8>,
+}
+
+/// 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:
+/// * <https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_reparse_guid_data_buffer>
+#[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<P, Q>(target: P, junction: Q) -> io::Result<()>
+where
+ P: AsRef<Path>,
+ Q: AsRef<Path>,
+{
+ 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<P: AsRef<Path>>(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<P: AsRef<Path>>(junction: P) -> io::Result<bool> {
+ 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<P: AsRef<Path>>(junction: P) -> io::Result<PathBuf> {
+ 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"),
+ }
+}