Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement junction_point #121711

Merged
merged 1 commit into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading