diff options
| author | Guillaume Boisseau <Nadrieril@users.noreply.github.com> | 2024-03-09 21:40:09 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-03-09 21:40:09 +0100 |
| commit | 13ca978f914a2ffb212698e7fd69aaf10611fb8e (patch) | |
| tree | 1803f8a6df3393ec2a435eb33a2b982ffaf4f583 | |
| parent | bc3bc2ba6b9e762a8eace7a9e9e9d18ed9f0d75b (diff) | |
| parent | 228347878ebd45d5cb7e6f424bad18eb0b92543a (diff) | |
| download | rust-13ca978f914a2ffb212698e7fd69aaf10611fb8e.tar.gz rust-13ca978f914a2ffb212698e7fd69aaf10611fb8e.zip | |
Rollup merge of #121711 - ChrisDenton:junction, r=Mark-Simulacrum
Implement junction_point Implements https://github.com/rust-lang/rust/issues/121709 We already had a private implementation that we use for tests so we could just make that public. Except it was very hacky as it was only ever intended for use in testing. I've made an improved version that at least handles path conversion correctly and has less need for things like the `Align8` hack. There's still room for further improvement though.
| -rw-r--r-- | library/std/src/fs/tests.rs | 12 | ||||
| -rw-r--r-- | library/std/src/os/windows/fs.rs | 12 | ||||
| -rw-r--r-- | library/std/src/sys/pal/windows/c.rs | 11 | ||||
| -rw-r--r-- | 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 5917bf8df02..a65e78542bf 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 e9d7a13e9d5..27947d91c99 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<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) -> io: pub fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(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<P: AsRef<Path>, Q: AsRef<Path>>(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 584e17cd196..2fc6598d876 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; @@ -145,16 +144,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 3a9e7b4660b..e92c5e80eac 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<u64> { Ok(size as u64) } -#[allow(dead_code)] -pub fn symlink_junction<P: AsRef<Path>, Q: AsRef<Path>>( - 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::<u8>::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::<c::REPARSE_MOUNTPOINT_DATA_BUFFER>(); - // 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::<c::WCHAR>(); - // 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::<c::WCHAR>(); - 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<u16> = 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<u16>; 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::<MaybeUninit<u16>>(), 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::<c_void>(), + data_len as u32 + 8, ptr::null_mut(), 0, &mut ret, |
