diff --git a/.lock b/.lock new file mode 100644 index 00000000..e69de29b diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/crates.js b/crates.js new file mode 100644 index 00000000..08159ace --- /dev/null +++ b/crates.js @@ -0,0 +1 @@ +window.ALL_CRATES = ["junction"]; \ No newline at end of file diff --git a/help.html b/help.html new file mode 100644 index 00000000..47882ba4 --- /dev/null +++ b/help.html @@ -0,0 +1 @@ +
pub fn create<P, Q>(target: P, junction: Q) -> Result<()>
Creates a junction point from the specified directory to the specified target directory.
+N.B. Only works on NTFS.
+This function may error if the junction
path already exists.
use std::io;
+use std::path::Path;
+fn main() -> io::Result<()> {
+ let tmpdir = tempfile::tempdir()?;
+ let target = tmpdir.path().join("target");
+ let junction = tmpdir.path().join("junction");
+ create(&target, &junction)
+}
pub fn delete<P: AsRef<Path>>(junction: P) -> Result<()>
Deletes a junction
reparse point from the specified file or directory.
N.B. Only works on NTFS.
+This function delete the junction point only, leaving the target directory
+and its content as is. It does nothing if the junction
point does not exist.
use std::io;
+use std::path::Path;
+fn main() -> io::Result<()> {
+ let tmpdir = tempfile::tempdir()?;
+ let target = tmpdir.path().join("target");
+ let junction = tmpdir.path().join("junction");
+ create(&target, &junction)?;
+ delete(&junction)
+}
pub fn get_target<P: AsRef<Path>>(junction: P) -> Result<PathBuf>
Gets the target of the specified junction point.
+N.B. Only works on NTFS.
+use std::io;
+fn main() -> io::Result<()> {
+ assert_eq!(get_target(r"C:\Users\Default User")?.to_str(), Some(r"C:\Users\Default"));
+ Ok(())
+}
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
.
junction
reparse point from the specified file or directory.junction
reparse point from the specified file …\nDetermines whether the specified path exists and refers to …\nGets the target of the specified junction point.")
\ No newline at end of file
diff --git a/settings.html b/settings.html
new file mode 100644
index 00000000..f215a2bf
--- /dev/null
+++ b/settings.html
@@ -0,0 +1 @@
+1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +
mod c;
+mod cast;
+mod helpers;
+
+use std::ffi::OsString;
+use std::mem::size_of;
+use std::os::windows::ffi::OsStringExt;
+use std::os::windows::io::AsRawHandle;
+use std::path::{Path, PathBuf};
+use std::ptr::{addr_of_mut, copy_nonoverlapping};
+use std::{cmp, fs, io, slice};
+
+use cast::BytesAsReparseDataBuffer;
+
+/// 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] = helpers::utf16s(br"\??\");
+
+const WCHAR_SIZE: u16 = 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 = c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as u16
+ - c::REPARSE_DATA_BUFFER_HEADER_SIZE
+ - c::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 target = helpers::get_full_path(target)?;
+ fs::create_dir(junction)?;
+ let file = helpers::open_reparse_point(junction, true)?;
+ let target_len_in_bytes = {
+ // "\??\" + target
+ let len = NON_INTERPRETED_PATH_PREFIX.len().saturating_add(target.len());
+ 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 for buffer overflow.
+ if target_len_in_bytes > MAX_AVAILABLE_PATH_BUFFER {
+ return Err(io::Error::new(io::ErrorKind::InvalidInput, "`target` is too long"));
+ }
+ target_len_in_bytes
+ };
+
+ // Redefine the above char array into a ReparseDataBuffer we can work with
+ let mut data = BytesAsReparseDataBuffer::new();
+ let rdb = data.as_mut_ptr();
+ let in_buffer_size: u16 = unsafe {
+ // Set the type of reparse point we are creating
+ addr_of_mut!((*rdb).ReparseTag).write(c::IO_REPARSE_TAG_MOUNT_POINT);
+ addr_of_mut!((*rdb).Reserved).write(0);
+
+ // We write target at offset 0 of PathBuffer
+ addr_of_mut!((*rdb).ReparseBuffer.SubstituteNameOffset).write(0);
+ addr_of_mut!((*rdb).ReparseBuffer.SubstituteNameLength).write(target_len_in_bytes);
+
+ // We do not use PrintName. However let's set its offset correctly right after SubstituteName
+ addr_of_mut!((*rdb).ReparseBuffer.PrintNameOffset).write(target_len_in_bytes + UNICODE_NULL_SIZE);
+ addr_of_mut!((*rdb).ReparseBuffer.PrintNameLength).write(0);
+
+ // Safe because we checked `MAX_AVAILABLE_PATH_BUFFER`
+ copy_nonoverlapping(
+ NON_INTERPRETED_PATH_PREFIX.as_ptr(),
+ addr_of_mut!((*rdb).ReparseBuffer.PathBuffer).cast::<u16>(),
+ NON_INTERPRETED_PATH_PREFIX.len(),
+ );
+ // TODO: Do we need to write the NULL-terminator byte?
+ // It looks like libuv does that.
+ copy_nonoverlapping(
+ target.as_ptr(),
+ addr_of_mut!((*rdb).ReparseBuffer.PathBuffer)
+ .cast::<u16>()
+ .add(NON_INTERPRETED_PATH_PREFIX.len()),
+ target.len(),
+ );
+
+ // Set the total size of the data buffer
+ let size = target_len_in_bytes.wrapping_add(c::MOUNT_POINT_REPARSE_BUFFER_HEADER_SIZE + 2 * UNICODE_NULL_SIZE);
+ addr_of_mut!((*rdb).ReparseDataLength).write(size);
+ size.wrapping_add(c::REPARSE_DATA_BUFFER_HEADER_SIZE)
+ };
+
+ helpers::set_reparse_point(file.as_raw_handle() as isize, 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() as isize)
+}
+
+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 = BytesAsReparseDataBuffer::new();
+ // XXX: Could also use FindFirstFile to read the reparse point type
+ // Ref https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-point-tags
+ helpers::get_reparse_data_point(file.as_raw_handle() as isize, data.as_mut_ptr())?;
+ // SATETY: rdb should be initialized now
+ let rdb = unsafe { data.assume_init() };
+ // The reparse tag indicates if this is a junction or not
+ Ok(rdb.ReparseTag == c::IO_REPARSE_TAG_MOUNT_POINT)
+}
+
+pub fn get_target(junction: &Path) -> io::Result<PathBuf> {
+ // MSRV(1.63): use Path::try_exists instead
+ 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 = BytesAsReparseDataBuffer::new();
+ helpers::get_reparse_data_point(file.as_raw_handle() as isize, data.as_mut_ptr())?;
+ // SAFETY: rdb should be initialized now
+ let rdb = unsafe { data.assume_init() };
+ if rdb.ReparseTag == c::IO_REPARSE_TAG_MOUNT_POINT {
+ let offset = rdb.ReparseBuffer.SubstituteNameOffset / WCHAR_SIZE;
+ let len = rdb.ReparseBuffer.SubstituteNameLength / WCHAR_SIZE;
+ let wide = unsafe {
+ let buf = rdb.ReparseBuffer.PathBuffer.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"))
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +
#![allow(non_snake_case)]
+
+// MSRV(1.75): use `offset_of!` when stabilized.
+#[cfg(feature = "nightly")]
+mod nightly;
+
+use std::alloc::Layout;
+use std::os::raw::{c_ulong, c_ushort};
+use std::os::windows::io::RawHandle;
+
+pub use windows_sys::Win32::Foundation::{
+ CloseHandle, GetLastError, SetLastError, ERROR_INSUFFICIENT_BUFFER, FALSE, GENERIC_READ, GENERIC_WRITE, HANDLE,
+ INVALID_HANDLE_VALUE,
+};
+pub use windows_sys::Win32::Security::{
+ AdjustTokenPrivileges, LookupPrivilegeValueW, SE_PRIVILEGE_ENABLED, TOKEN_ADJUST_PRIVILEGES, TOKEN_PRIVILEGES,
+};
+// See more in <https://learn.microsoft.com/en-us/windows/win32/secauthz/privilege-constants>.
+pub use windows_sys::Win32::Security::{SE_BACKUP_NAME, SE_CREATE_SYMBOLIC_LINK_NAME, SE_RESTORE_NAME};
+pub use windows_sys::Win32::Storage::FileSystem::{
+ GetFullPathNameW, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
+ REPARSE_GUID_DATA_BUFFER,
+};
+pub use windows_sys::Win32::System::Ioctl::{
+ FSCTL_DELETE_REPARSE_POINT, FSCTL_GET_REPARSE_POINT, FSCTL_SET_REPARSE_POINT,
+};
+pub use windows_sys::Win32::System::SystemServices::IO_REPARSE_TAG_MOUNT_POINT;
+pub use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
+pub use windows_sys::Win32::System::IO::DeviceIoControl;
+
+// Makes sure layout of RawHandle and windows-sys's HANDLE are the same
+// for pointer casts between them.
+// CLIPPY: nonsense suggestions for assert!
+#[allow(clippy::unnecessary_operation)]
+const _: () = {
+ let std_layout = Layout::new::<RawHandle>();
+ let win_sys_layout = Layout::new::<HANDLE>();
+ // MSVR(Rust v1.57): use assert! instead
+ [(); 1][!(std_layout.size() == win_sys_layout.size()) as usize];
+ [(); 1][!(std_layout.align() == win_sys_layout.align()) as usize];
+};
+
+// NOTE: to use `size_of` operator, below structs should be packed.
+/// Reparse Data Buffer header size
+pub const REPARSE_DATA_BUFFER_HEADER_SIZE: u16 = 8;
+/// Reparse GUID Data Buffer header size
+pub const REPARSE_GUID_DATA_BUFFER_HEADER_SIZE: u16 = 24;
+/// MountPointReparseBuffer header size
+pub const MOUNT_POINT_REPARSE_BUFFER_HEADER_SIZE: u16 = 8;
+
+#[cfg(feature = "nightly")]
+const _: () = {
+ assert!(REPARSE_DATA_BUFFER_HEADER_SIZE == nightly::REPARSE_DATA_BUFFER_HEADER_SIZE);
+ assert!(REPARSE_GUID_DATA_BUFFER_HEADER_SIZE == nightly::REPARSE_GUID_DATA_BUFFER_HEADER_SIZE);
+ assert!(MOUNT_POINT_REPARSE_BUFFER_HEADER_SIZE == nightly::MOUNT_POINT_REPARSE_BUFFER_HEADER_SIZE);
+};
+
+type VarLenArr<T> = [T; 1];
+
+/// 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 REPARSE_DATA_BUFFER {
+ /// Reparse point tag. Must be a Microsoft reparse point tag.
+ pub ReparseTag: c_ulong,
+ // Size, in bytes, of the data after the Reserved member.
+ // This can be calculated by:
+ // MOUNT_POINT_REPARSE_BUFFER_HEADER_SIZE + SubstituteNameLength
+ // + PrintNameLength + (names.nul_terminated() ? 2 * sizeof(char) : 0);
+ pub ReparseDataLength: c_ushort,
+ /// Reversed. It SHOULD be set to 0, and MUST be ignored.
+ pub Reserved: c_ushort,
+ pub ReparseBuffer: MountPointReparseBuffer,
+}
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct MountPointReparseBuffer {
+ /// Offset, in bytes, of the substitute name string in the `PathBuffer` array.
+ /// Note that this offset must be divided by `sizeof(u16)` to get the array index.
+ pub SubstituteNameOffset: c_ushort,
+ /// 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 SubstituteNameLength: c_ushort,
+ /// Offset, in bytes, of the print name string in the `PathBuffer` array.
+ /// Note that this offset must be divided by `sizeof(u16)` to get the array index.
+ pub PrintNameOffset: c_ushort,
+ /// 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 PrintNameLength: c_ushort,
+ /// 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 PathBuffer. (To locate the substitute name and print name
+ /// strings in the PathBuffer, use the `SubstituteNameOffset`, `SubstituteNameLength`,
+ /// `PrintNameOffset`, and `PrintNameLength` members.)
+ pub PathBuffer: VarLenArr<c_ushort>,
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +
use std::alloc::{alloc, handle_alloc_error, Layout};
+use std::mem::align_of;
+
+use super::c::{MAXIMUM_REPARSE_DATA_BUFFER_SIZE, REPARSE_DATA_BUFFER};
+
+type MaybeU8 = std::mem::MaybeUninit<u8>;
+
+#[repr(align(4))]
+pub struct BytesAsReparseDataBuffer {
+ value: Box<[MaybeU8; MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize]>,
+}
+
+const _: () = {
+ let a = align_of::<BytesAsReparseDataBuffer>();
+ let b = align_of::<REPARSE_DATA_BUFFER>();
+ [(); 1][!((a % b) == 0) as usize]
+};
+
+impl BytesAsReparseDataBuffer {
+ pub fn new() -> Self {
+ type Raw = [MaybeU8; MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize];
+ const LAYOUT: Layout = Layout::new::<Raw>();
+ let boxed = unsafe {
+ let ptr = alloc(LAYOUT).cast::<Raw>();
+ if ptr.is_null() {
+ handle_alloc_error(LAYOUT);
+ }
+ Box::from_raw(ptr)
+ };
+ Self { value: boxed }
+ }
+
+ pub fn as_mut_ptr(&mut self) -> *mut REPARSE_DATA_BUFFER {
+ self.value.as_mut_ptr().cast::<REPARSE_DATA_BUFFER>()
+ }
+
+ pub unsafe fn assume_init(&mut self) -> &REPARSE_DATA_BUFFER {
+ &*self.as_mut_ptr()
+ }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +
mod utf16;
+
+use std::ffi::OsStr;
+use std::fs::{File, OpenOptions};
+use std::io;
+use std::mem::{size_of, zeroed, MaybeUninit};
+use std::os::windows::ffi::OsStrExt;
+use std::os::windows::fs::OpenOptionsExt;
+use std::path::Path;
+use std::ptr::{addr_of_mut, null, null_mut};
+
+pub(crate) use utf16::utf16s;
+
+use super::c;
+
+pub fn open_reparse_point(reparse_point: &Path, write: bool) -> io::Result<File> {
+ let access = c::GENERIC_READ | if write { c::GENERIC_WRITE } else { 0 };
+ // Set this flag to obtain a handle to a directory. Appropriate security checks
+ // still apply when this flag is used without SE_BACKUP_NAME and SE_RESTORE_NAME
+ // privileges.
+ // Ref <https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#directories>
+ let dir_attrs = c::FILE_FLAG_OPEN_REPARSE_POINT | c::FILE_FLAG_BACKUP_SEMANTICS;
+ let mut opts = OpenOptions::new();
+ opts.access_mode(access).share_mode(0).custom_flags(dir_attrs);
+ // Opens existing directory path
+ match opts.open(reparse_point) {
+ Err(e) if e.kind() == io::ErrorKind::PermissionDenied => {
+ set_privilege(write)?;
+ opts.open(reparse_point)
+ }
+ other => other,
+ }
+}
+
+fn set_privilege(write: bool) -> io::Result<()> {
+ const ERROR_NOT_ALL_ASSIGNED: u32 = 1300;
+ const TOKEN_PRIVILEGES_SIZE: u32 = size_of::<c::TOKEN_PRIVILEGES>() as _;
+ unsafe {
+ let mut handle: c::HANDLE = c::INVALID_HANDLE_VALUE;
+ if c::OpenProcessToken(c::GetCurrentProcess(), c::TOKEN_ADJUST_PRIVILEGES, &mut handle) == 0 {
+ return Err(io::Error::last_os_error());
+ }
+ let handle = scopeguard::guard(handle, |h| {
+ c::CloseHandle(h);
+ });
+ let name = if cfg!(feature = "unstable_admin") {
+ if write {
+ c::SE_RESTORE_NAME
+ } else {
+ c::SE_BACKUP_NAME
+ }
+ } else {
+ // FSCTL_SET_REPARSE_POINT requires SE_CREATE_SYMBOLIC_LINK_NAME privilege
+ // Ref <https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_set_reparse_point>
+ c::SE_CREATE_SYMBOLIC_LINK_NAME
+ };
+ let mut tp: c::TOKEN_PRIVILEGES = zeroed();
+ if c::LookupPrivilegeValueW(null(), name, &mut tp.Privileges[0].Luid) == 0 {
+ return Err(io::Error::last_os_error());
+ }
+ tp.Privileges[0].Attributes = c::SE_PRIVILEGE_ENABLED;
+ tp.PrivilegeCount = 1;
+
+ if c::AdjustTokenPrivileges(*handle, c::FALSE, &tp, TOKEN_PRIVILEGES_SIZE, null_mut(), null_mut()) == 0 {
+ return Err(io::Error::last_os_error());
+ }
+ if c::GetLastError() == ERROR_NOT_ALL_ASSIGNED {
+ return Err(io::Error::from_raw_os_error(ERROR_NOT_ALL_ASSIGNED as i32));
+ }
+ }
+ Ok(())
+}
+
+pub fn get_reparse_data_point(handle: c::HANDLE, rdb: *mut c::REPARSE_DATA_BUFFER) -> io::Result<()> {
+ // Call DeviceIoControl to get the reparse point data
+ let mut bytes_returned: u32 = 0;
+ if unsafe {
+ c::DeviceIoControl(
+ handle,
+ c::FSCTL_GET_REPARSE_POINT,
+ null_mut(),
+ 0,
+ rdb.cast(),
+ c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
+ &mut bytes_returned,
+ null_mut(),
+ )
+ } == 0
+ {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(())
+}
+
+pub fn set_reparse_point(handle: c::HANDLE, rdb: *mut c::REPARSE_DATA_BUFFER, len: u32) -> io::Result<()> {
+ let mut bytes_returned: u32 = 0;
+ if unsafe {
+ c::DeviceIoControl(
+ handle,
+ c::FSCTL_SET_REPARSE_POINT,
+ rdb.cast(),
+ len,
+ null_mut(),
+ 0,
+ &mut bytes_returned,
+ 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: c::HANDLE) -> io::Result<()> {
+ // TODO: Should we use REPARSE_DATA_BUFFER instead?
+ let mut rgdb: c::REPARSE_GUID_DATA_BUFFER = unsafe { zeroed() };
+ rgdb.ReparseTag = c::IO_REPARSE_TAG_MOUNT_POINT;
+ let mut bytes_returned: u32 = 0;
+
+ if unsafe {
+ c::DeviceIoControl(
+ handle,
+ c::FSCTL_DELETE_REPARSE_POINT,
+ addr_of_mut!(rgdb).cast(),
+ u32::from(c::REPARSE_GUID_DATA_BUFFER_HEADER_SIZE),
+ null_mut(),
+ 0,
+ &mut bytes_returned,
+ 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 canonical path without the terminating null character.
+// Ref: rust-lang/rust/blob/master/library/std/src/sys/windows/mod.rs#L198
+pub fn get_full_path(target: &Path) -> io::Result<Vec<u16>> {
+ let path = os_str_to_utf16(target.as_os_str());
+ let path = path.as_ptr().cast::<u16>();
+ const U16_UNINIT: MaybeU16 = MaybeU16::uninit();
+ // 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[..]
+ };
+
+ c::SetLastError(0);
+ let k = c::GetFullPathNameW(path, n as u32, maybe_slice_to_ptr(buf), null_mut()) as usize;
+ if k == 0 {
+ return Err(crate::io::Error::last_os_error());
+ }
+ if c::GetLastError() == c::ERROR_INSUFFICIENT_BUFFER {
+ n = n.saturating_mul(2).min(u32::MAX as usize);
+ } else if k > n {
+ n = k;
+ } else {
+ // TODO(perf): reduce an allocation by using `heap_buf.set_len(k)`
+ // Safety: First `k` values are initialized.
+ let slice: &[u16] = maybe_slice_assume_init(&buf[..k]);
+ return Ok(slice.into());
+ }
+ }
+ }
+}
+
+fn maybe_slice_to_ptr(s: &mut [MaybeU16]) -> *mut u16 {
+ // SAFETY: `MaybeUninit<T>` and T are guaranteed to have the same layout
+ s.as_mut_ptr() as *mut u16
+}
+
+fn maybe_slice_assume_init(s: &[MaybeU16]) -> &[u16] {
+ // SAFETY: `MaybeUninit<T>` and T are guaranteed to have the same layout
+ unsafe { &*(s as *const [MaybeU16] as *const [u16]) }
+}
+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +
/*!
+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.
+///
+/// # Error
+///
+/// This function may error if the `junction` path already exists.
+///
+/// # 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 delete the junction point only, leaving the target directory
+/// and its content as is. 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<()> {
+/// # #[cfg(feature = "unstable_admin")]
+/// 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<()> {
+/// # #[cfg(feature = "unstable_admin")]
+/// 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())
+}
+
fn:
) to \
+ restrict the search to a given item kind.","Accepted kinds are: fn
, mod
, struct
, \
+ enum
, trait
, type
, macro
, \
+ and const
.","Search functions by type signature (e.g., vec -> usize
or \
+ -> vec
or String, enum:Cow -> bool
)","You can look for items with an exact name by putting double quotes around \
+ your request: \"string\"
","Look for functions that accept or return \
+ slices and \
+ arrays by writing \
+ square brackets (e.g., -> [u8]
or [] -> Option
)","Look for items inside another one by searching for a path: vec::Vec
",].map(x=>""+x+"
").join("");const div_infos=document.createElement("div");addClass(div_infos,"infos");div_infos.innerHTML="${value.replaceAll(" ", " ")}
`}else{error[index]=value}});output+=`