From 228347878ebd45d5cb7e6f424bad18eb0b92543a Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Tue, 27 Feb 2024 18:54:18 -0300 Subject: [PATCH] Implement junction_point --- library/std/src/fs/tests.rs | 12 +-- library/std/src/os/windows/fs.rs | 12 +++ library/std/src/sys/pal/windows/c.rs | 11 --- library/std/src/sys/pal/windows/fs.rs | 132 ++++++++++++++------------ 4 files changed, 86 insertions(+), 81 deletions(-) diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 5917bf8df029e..a65e78542bf20 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -20,11 +20,9 @@ use crate::os::unix::fs::symlink as symlink_dir; #[cfg(unix)] use crate::os::unix::fs::symlink as symlink_file; #[cfg(unix)] -use crate::os::unix::fs::symlink as symlink_junction; +use crate::os::unix::fs::symlink as junction_point; #[cfg(windows)] -use crate::os::windows::fs::{symlink_dir, symlink_file, OpenOptionsExt}; -#[cfg(windows)] -use crate::sys::fs::symlink_junction; +use crate::os::windows::fs::{junction_point, symlink_dir, symlink_file, OpenOptionsExt}; #[cfg(target_os = "macos")] use crate::sys::weak::weak; @@ -598,7 +596,7 @@ fn recursive_rmdir() { check!(fs::create_dir_all(&dtt)); check!(fs::create_dir_all(&d2)); check!(check!(File::create(&canary)).write(b"foo")); - check!(symlink_junction(&d2, &dt.join("d2"))); + check!(junction_point(&d2, &dt.join("d2"))); let _ = symlink_file(&canary, &d1.join("canary")); check!(fs::remove_dir_all(&d1)); @@ -615,7 +613,7 @@ fn recursive_rmdir_of_symlink() { let canary = dir.join("do_not_delete"); check!(fs::create_dir_all(&dir)); check!(check!(File::create(&canary)).write(b"foo")); - check!(symlink_junction(&dir, &link)); + check!(junction_point(&dir, &link)); check!(fs::remove_dir_all(&link)); assert!(!link.is_dir()); @@ -1403,7 +1401,7 @@ fn create_dir_all_with_junctions() { fs::create_dir(&target).unwrap(); - check!(symlink_junction(&target, &junction)); + check!(junction_point(&target, &junction)); check!(fs::create_dir_all(&b)); // the junction itself is not a directory, but `is_dir()` on a Path // follows links diff --git a/library/std/src/os/windows/fs.rs b/library/std/src/os/windows/fs.rs index e9d7a13e9d5b2..27947d91c99de 100644 --- a/library/std/src/os/windows/fs.rs +++ b/library/std/src/os/windows/fs.rs @@ -620,3 +620,15 @@ pub fn symlink_file, Q: AsRef>(original: P, link: Q) -> io: pub fn symlink_dir, Q: AsRef>(original: P, link: Q) -> io::Result<()> { sys::fs::symlink_inner(original.as_ref(), link.as_ref(), true) } + +/// Create a junction point. +/// +/// The `link` path will be a directory junction pointing to the original path. +/// If `link` is a relative path then it will be made absolute prior to creating the junction point. +/// The `original` path must be a directory or a link to a directory, otherwise the junction point will be broken. +/// +/// If either path is not a local file path then this will fail. +#[unstable(feature = "junction_point", issue = "121709")] +pub fn junction_point, Q: AsRef>(original: P, link: Q) -> io::Result<()> { + sys::fs::junction_point(original.as_ref(), link.as_ref()) +} diff --git a/library/std/src/sys/pal/windows/c.rs b/library/std/src/sys/pal/windows/c.rs index b007796722baf..abdbc0ad315d9 100644 --- a/library/std/src/sys/pal/windows/c.rs +++ b/library/std/src/sys/pal/windows/c.rs @@ -25,7 +25,6 @@ pub type UINT = c_uint; pub type WCHAR = u16; pub type USHORT = c_ushort; pub type SIZE_T = usize; -pub type WORD = u16; pub type CHAR = c_char; pub type ULONG = c_ulong; @@ -142,16 +141,6 @@ pub struct MOUNT_POINT_REPARSE_BUFFER { pub PrintNameLength: c_ushort, pub PathBuffer: WCHAR, } -#[repr(C)] -pub struct REPARSE_MOUNTPOINT_DATA_BUFFER { - pub ReparseTag: DWORD, - pub ReparseDataLength: DWORD, - pub Reserved: WORD, - pub ReparseTargetLength: WORD, - pub ReparseTargetMaximumLength: WORD, - pub Reserved1: WORD, - pub ReparseTarget: WCHAR, -} #[repr(C)] pub struct SOCKADDR_STORAGE_LH { diff --git a/library/std/src/sys/pal/windows/fs.rs b/library/std/src/sys/pal/windows/fs.rs index 3a9e7b4660b36..e92c5e80eac9c 100644 --- a/library/std/src/sys/pal/windows/fs.rs +++ b/library/std/src/sys/pal/windows/fs.rs @@ -1,7 +1,9 @@ +use core::ptr::addr_of; + use crate::os::windows::prelude::*; use crate::borrow::Cow; -use crate::ffi::{c_void, OsString}; +use crate::ffi::{c_void, OsStr, OsString}; use crate::fmt; use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom}; use crate::mem::{self, MaybeUninit}; @@ -1446,75 +1448,79 @@ pub fn copy(from: &Path, to: &Path) -> io::Result { Ok(size as u64) } -#[allow(dead_code)] -pub fn symlink_junction, Q: AsRef>( - original: P, - junction: Q, -) -> io::Result<()> { - symlink_junction_inner(original.as_ref(), junction.as_ref()) -} - -// Creating a directory junction on windows involves dealing with reparse -// points and the DeviceIoControl function, and this code is a skeleton of -// what can be found here: -// -// http://www.flexhex.com/docs/articles/hard-links.phtml -#[allow(dead_code)] -fn symlink_junction_inner(original: &Path, junction: &Path) -> io::Result<()> { - let d = DirBuilder::new(); - d.mkdir(junction)?; - +pub fn junction_point(original: &Path, link: &Path) -> io::Result<()> { + // Create and open a new directory in one go. let mut opts = OpenOptions::new(); + opts.create_new(true); opts.write(true); - opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT | c::FILE_FLAG_BACKUP_SEMANTICS); - let f = File::open(junction, &opts)?; - let h = f.as_inner().as_raw_handle(); - unsafe { - let mut data = - Align8([MaybeUninit::::uninit(); c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize]); - let data_ptr = data.0.as_mut_ptr(); - let data_end = data_ptr.add(c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize); - let db = data_ptr.cast::(); - // Zero the header to ensure it's fully initialized, including reserved parameters. - *db = mem::zeroed(); - let reparse_target_slice = { - let buf_start = ptr::addr_of_mut!((*db).ReparseTarget).cast::(); - // Compute offset in bytes and then divide so that we round down - // rather than hit any UB (admittedly this arithmetic should work - // out so that this isn't necessary) - let buf_len_bytes = usize::try_from(data_end.byte_offset_from(buf_start)).unwrap(); - let buf_len_wchars = buf_len_bytes / core::mem::size_of::(); - core::slice::from_raw_parts_mut(buf_start, buf_len_wchars) - }; - - // FIXME: this conversion is very hacky - let iter = br"\??\" - .iter() - .map(|x| *x as u16) - .chain(original.as_os_str().encode_wide()) - .chain(core::iter::once(0)); - let mut i = 0; - for c in iter { - if i >= reparse_target_slice.len() { - return Err(crate::io::const_io_error!( - crate::io::ErrorKind::InvalidFilename, - "Input filename is too long" - )); - } - reparse_target_slice[i] = c; - i += 1; + opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS | c::FILE_FLAG_POSIX_SEMANTICS); + opts.attributes(c::FILE_ATTRIBUTE_DIRECTORY); + + let d = File::open(link, &opts)?; + + // We need to get an absolute, NT-style path. + let path_bytes = original.as_os_str().as_encoded_bytes(); + let abs_path: Vec = if path_bytes.starts_with(br"\\?\") || path_bytes.starts_with(br"\??\") + { + // It's already an absolute path, we just need to convert the prefix to `\??\` + let bytes = unsafe { OsStr::from_encoded_bytes_unchecked(&path_bytes[4..]) }; + r"\??\".encode_utf16().chain(bytes.encode_wide()).collect() + } else { + // Get an absolute path and then convert the prefix to `\??\` + let abs_path = crate::path::absolute(original)?.into_os_string().into_encoded_bytes(); + if abs_path.len() > 0 && abs_path[1..].starts_with(br":\") { + let bytes = unsafe { OsStr::from_encoded_bytes_unchecked(&abs_path) }; + r"\??\".encode_utf16().chain(bytes.encode_wide()).collect() + } else if abs_path.starts_with(br"\\.\") { + let bytes = unsafe { OsStr::from_encoded_bytes_unchecked(&abs_path[4..]) }; + r"\??\".encode_utf16().chain(bytes.encode_wide()).collect() + } else if abs_path.starts_with(br"\\") { + let bytes = unsafe { OsStr::from_encoded_bytes_unchecked(&abs_path[2..]) }; + r"\??\UNC\".encode_utf16().chain(bytes.encode_wide()).collect() + } else { + return Err(io::const_io_error!(io::ErrorKind::InvalidInput, "path is not valid")); } - (*db).ReparseTag = c::IO_REPARSE_TAG_MOUNT_POINT; - (*db).ReparseTargetMaximumLength = (i * 2) as c::WORD; - (*db).ReparseTargetLength = ((i - 1) * 2) as c::WORD; - (*db).ReparseDataLength = (*db).ReparseTargetLength as c::DWORD + 12; + }; + // Defined inline so we don't have to mess about with variable length buffer. + #[repr(C)] + pub struct MountPointBuffer { + ReparseTag: u32, + ReparseDataLength: u16, + Reserved: u16, + SubstituteNameOffset: u16, + SubstituteNameLength: u16, + PrintNameOffset: u16, + PrintNameLength: u16, + PathBuffer: [MaybeUninit; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize], + } + let data_len = 12 + (abs_path.len() * 2); + if data_len > u16::MAX as usize { + return Err(io::const_io_error!( + io::ErrorKind::InvalidInput, + "`original` path is too long" + )); + } + let data_len = data_len as u16; + let mut header = MountPointBuffer { + ReparseTag: c::IO_REPARSE_TAG_MOUNT_POINT, + ReparseDataLength: data_len, + Reserved: 0, + SubstituteNameOffset: 0, + SubstituteNameLength: (abs_path.len() * 2) as u16, + PrintNameOffset: ((abs_path.len() + 1) * 2) as u16, + PrintNameLength: 0, + PathBuffer: [MaybeUninit::uninit(); c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize], + }; + unsafe { + let ptr = header.PathBuffer.as_mut_ptr(); + ptr.copy_from(abs_path.as_ptr().cast::>(), abs_path.len()); let mut ret = 0; cvt(c::DeviceIoControl( - h as *mut _, + d.as_raw_handle(), c::FSCTL_SET_REPARSE_POINT, - data_ptr.cast(), - (*db).ReparseDataLength + 8, + addr_of!(header).cast::(), + data_len as u32 + 8, ptr::null_mut(), 0, &mut ret,