Skip to content

Commit

Permalink
Implement junction_point
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisDenton committed Feb 27, 2024
1 parent 1e4f9e3 commit 2283478
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 81 deletions.
12 changes: 5 additions & 7 deletions library/std/src/fs/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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));

Expand All @@ -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());
Expand Down Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions library/std/src/os/windows/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
11 changes: 0 additions & 11 deletions library/std/src/sys/pal/windows/c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 {
Expand Down
132 changes: 69 additions & 63 deletions library/std/src/sys/pal/windows/fs.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 2283478

Please sign in to comment.