Skip to content

Commit 13ca978

Browse files
authored
Rollup merge of #121711 - ChrisDenton:junction, r=Mark-Simulacrum
Implement junction_point Implements #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.
2 parents bc3bc2b + 2283478 commit 13ca978

File tree

4 files changed

+86
-81
lines changed

4 files changed

+86
-81
lines changed

library/std/src/fs/tests.rs

+5-7
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,9 @@ use crate::os::unix::fs::symlink as symlink_dir;
2020
#[cfg(unix)]
2121
use crate::os::unix::fs::symlink as symlink_file;
2222
#[cfg(unix)]
23-
use crate::os::unix::fs::symlink as symlink_junction;
23+
use crate::os::unix::fs::symlink as junction_point;
2424
#[cfg(windows)]
25-
use crate::os::windows::fs::{symlink_dir, symlink_file, OpenOptionsExt};
26-
#[cfg(windows)]
27-
use crate::sys::fs::symlink_junction;
25+
use crate::os::windows::fs::{junction_point, symlink_dir, symlink_file, OpenOptionsExt};
2826
#[cfg(target_os = "macos")]
2927
use crate::sys::weak::weak;
3028

@@ -598,7 +596,7 @@ fn recursive_rmdir() {
598596
check!(fs::create_dir_all(&dtt));
599597
check!(fs::create_dir_all(&d2));
600598
check!(check!(File::create(&canary)).write(b"foo"));
601-
check!(symlink_junction(&d2, &dt.join("d2")));
599+
check!(junction_point(&d2, &dt.join("d2")));
602600
let _ = symlink_file(&canary, &d1.join("canary"));
603601
check!(fs::remove_dir_all(&d1));
604602

@@ -615,7 +613,7 @@ fn recursive_rmdir_of_symlink() {
615613
let canary = dir.join("do_not_delete");
616614
check!(fs::create_dir_all(&dir));
617615
check!(check!(File::create(&canary)).write(b"foo"));
618-
check!(symlink_junction(&dir, &link));
616+
check!(junction_point(&dir, &link));
619617
check!(fs::remove_dir_all(&link));
620618

621619
assert!(!link.is_dir());
@@ -1403,7 +1401,7 @@ fn create_dir_all_with_junctions() {
14031401

14041402
fs::create_dir(&target).unwrap();
14051403

1406-
check!(symlink_junction(&target, &junction));
1404+
check!(junction_point(&target, &junction));
14071405
check!(fs::create_dir_all(&b));
14081406
// the junction itself is not a directory, but `is_dir()` on a Path
14091407
// follows links

library/std/src/os/windows/fs.rs

+12
Original file line numberDiff line numberDiff line change
@@ -620,3 +620,15 @@ pub fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) -> io:
620620
pub fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) -> io::Result<()> {
621621
sys::fs::symlink_inner(original.as_ref(), link.as_ref(), true)
622622
}
623+
624+
/// Create a junction point.
625+
///
626+
/// The `link` path will be a directory junction pointing to the original path.
627+
/// If `link` is a relative path then it will be made absolute prior to creating the junction point.
628+
/// The `original` path must be a directory or a link to a directory, otherwise the junction point will be broken.
629+
///
630+
/// If either path is not a local file path then this will fail.
631+
#[unstable(feature = "junction_point", issue = "121709")]
632+
pub fn junction_point<P: AsRef<Path>, Q: AsRef<Path>>(original: P, link: Q) -> io::Result<()> {
633+
sys::fs::junction_point(original.as_ref(), link.as_ref())
634+
}

library/std/src/sys/pal/windows/c.rs

-11
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ pub type UINT = c_uint;
2525
pub type WCHAR = u16;
2626
pub type USHORT = c_ushort;
2727
pub type SIZE_T = usize;
28-
pub type WORD = u16;
2928
pub type CHAR = c_char;
3029
pub type ULONG = c_ulong;
3130

@@ -145,16 +144,6 @@ pub struct MOUNT_POINT_REPARSE_BUFFER {
145144
pub PrintNameLength: c_ushort,
146145
pub PathBuffer: WCHAR,
147146
}
148-
#[repr(C)]
149-
pub struct REPARSE_MOUNTPOINT_DATA_BUFFER {
150-
pub ReparseTag: DWORD,
151-
pub ReparseDataLength: DWORD,
152-
pub Reserved: WORD,
153-
pub ReparseTargetLength: WORD,
154-
pub ReparseTargetMaximumLength: WORD,
155-
pub Reserved1: WORD,
156-
pub ReparseTarget: WCHAR,
157-
}
158147

159148
#[repr(C)]
160149
pub struct SOCKADDR_STORAGE_LH {

library/std/src/sys/pal/windows/fs.rs

+69-63
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
use core::ptr::addr_of;
2+
13
use crate::os::windows::prelude::*;
24

35
use crate::borrow::Cow;
4-
use crate::ffi::{c_void, OsString};
6+
use crate::ffi::{c_void, OsStr, OsString};
57
use crate::fmt;
68
use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom};
79
use crate::mem::{self, MaybeUninit};
@@ -1446,75 +1448,79 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
14461448
Ok(size as u64)
14471449
}
14481450

1449-
#[allow(dead_code)]
1450-
pub fn symlink_junction<P: AsRef<Path>, Q: AsRef<Path>>(
1451-
original: P,
1452-
junction: Q,
1453-
) -> io::Result<()> {
1454-
symlink_junction_inner(original.as_ref(), junction.as_ref())
1455-
}
1456-
1457-
// Creating a directory junction on windows involves dealing with reparse
1458-
// points and the DeviceIoControl function, and this code is a skeleton of
1459-
// what can be found here:
1460-
//
1461-
// http://www.flexhex.com/docs/articles/hard-links.phtml
1462-
#[allow(dead_code)]
1463-
fn symlink_junction_inner(original: &Path, junction: &Path) -> io::Result<()> {
1464-
let d = DirBuilder::new();
1465-
d.mkdir(junction)?;
1466-
1451+
pub fn junction_point(original: &Path, link: &Path) -> io::Result<()> {
1452+
// Create and open a new directory in one go.
14671453
let mut opts = OpenOptions::new();
1454+
opts.create_new(true);
14681455
opts.write(true);
1469-
opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT | c::FILE_FLAG_BACKUP_SEMANTICS);
1470-
let f = File::open(junction, &opts)?;
1471-
let h = f.as_inner().as_raw_handle();
1472-
unsafe {
1473-
let mut data =
1474-
Align8([MaybeUninit::<u8>::uninit(); c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize]);
1475-
let data_ptr = data.0.as_mut_ptr();
1476-
let data_end = data_ptr.add(c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize);
1477-
let db = data_ptr.cast::<c::REPARSE_MOUNTPOINT_DATA_BUFFER>();
1478-
// Zero the header to ensure it's fully initialized, including reserved parameters.
1479-
*db = mem::zeroed();
1480-
let reparse_target_slice = {
1481-
let buf_start = ptr::addr_of_mut!((*db).ReparseTarget).cast::<c::WCHAR>();
1482-
// Compute offset in bytes and then divide so that we round down
1483-
// rather than hit any UB (admittedly this arithmetic should work
1484-
// out so that this isn't necessary)
1485-
let buf_len_bytes = usize::try_from(data_end.byte_offset_from(buf_start)).unwrap();
1486-
let buf_len_wchars = buf_len_bytes / core::mem::size_of::<c::WCHAR>();
1487-
core::slice::from_raw_parts_mut(buf_start, buf_len_wchars)
1488-
};
1489-
1490-
// FIXME: this conversion is very hacky
1491-
let iter = br"\??\"
1492-
.iter()
1493-
.map(|x| *x as u16)
1494-
.chain(original.as_os_str().encode_wide())
1495-
.chain(core::iter::once(0));
1496-
let mut i = 0;
1497-
for c in iter {
1498-
if i >= reparse_target_slice.len() {
1499-
return Err(crate::io::const_io_error!(
1500-
crate::io::ErrorKind::InvalidFilename,
1501-
"Input filename is too long"
1502-
));
1503-
}
1504-
reparse_target_slice[i] = c;
1505-
i += 1;
1456+
opts.custom_flags(c::FILE_FLAG_BACKUP_SEMANTICS | c::FILE_FLAG_POSIX_SEMANTICS);
1457+
opts.attributes(c::FILE_ATTRIBUTE_DIRECTORY);
1458+
1459+
let d = File::open(link, &opts)?;
1460+
1461+
// We need to get an absolute, NT-style path.
1462+
let path_bytes = original.as_os_str().as_encoded_bytes();
1463+
let abs_path: Vec<u16> = if path_bytes.starts_with(br"\\?\") || path_bytes.starts_with(br"\??\")
1464+
{
1465+
// It's already an absolute path, we just need to convert the prefix to `\??\`
1466+
let bytes = unsafe { OsStr::from_encoded_bytes_unchecked(&path_bytes[4..]) };
1467+
r"\??\".encode_utf16().chain(bytes.encode_wide()).collect()
1468+
} else {
1469+
// Get an absolute path and then convert the prefix to `\??\`
1470+
let abs_path = crate::path::absolute(original)?.into_os_string().into_encoded_bytes();
1471+
if abs_path.len() > 0 && abs_path[1..].starts_with(br":\") {
1472+
let bytes = unsafe { OsStr::from_encoded_bytes_unchecked(&abs_path) };
1473+
r"\??\".encode_utf16().chain(bytes.encode_wide()).collect()
1474+
} else if abs_path.starts_with(br"\\.\") {
1475+
let bytes = unsafe { OsStr::from_encoded_bytes_unchecked(&abs_path[4..]) };
1476+
r"\??\".encode_utf16().chain(bytes.encode_wide()).collect()
1477+
} else if abs_path.starts_with(br"\\") {
1478+
let bytes = unsafe { OsStr::from_encoded_bytes_unchecked(&abs_path[2..]) };
1479+
r"\??\UNC\".encode_utf16().chain(bytes.encode_wide()).collect()
1480+
} else {
1481+
return Err(io::const_io_error!(io::ErrorKind::InvalidInput, "path is not valid"));
15061482
}
1507-
(*db).ReparseTag = c::IO_REPARSE_TAG_MOUNT_POINT;
1508-
(*db).ReparseTargetMaximumLength = (i * 2) as c::WORD;
1509-
(*db).ReparseTargetLength = ((i - 1) * 2) as c::WORD;
1510-
(*db).ReparseDataLength = (*db).ReparseTargetLength as c::DWORD + 12;
1483+
};
1484+
// Defined inline so we don't have to mess about with variable length buffer.
1485+
#[repr(C)]
1486+
pub struct MountPointBuffer {
1487+
ReparseTag: u32,
1488+
ReparseDataLength: u16,
1489+
Reserved: u16,
1490+
SubstituteNameOffset: u16,
1491+
SubstituteNameLength: u16,
1492+
PrintNameOffset: u16,
1493+
PrintNameLength: u16,
1494+
PathBuffer: [MaybeUninit<u16>; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize],
1495+
}
1496+
let data_len = 12 + (abs_path.len() * 2);
1497+
if data_len > u16::MAX as usize {
1498+
return Err(io::const_io_error!(
1499+
io::ErrorKind::InvalidInput,
1500+
"`original` path is too long"
1501+
));
1502+
}
1503+
let data_len = data_len as u16;
1504+
let mut header = MountPointBuffer {
1505+
ReparseTag: c::IO_REPARSE_TAG_MOUNT_POINT,
1506+
ReparseDataLength: data_len,
1507+
Reserved: 0,
1508+
SubstituteNameOffset: 0,
1509+
SubstituteNameLength: (abs_path.len() * 2) as u16,
1510+
PrintNameOffset: ((abs_path.len() + 1) * 2) as u16,
1511+
PrintNameLength: 0,
1512+
PathBuffer: [MaybeUninit::uninit(); c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize],
1513+
};
1514+
unsafe {
1515+
let ptr = header.PathBuffer.as_mut_ptr();
1516+
ptr.copy_from(abs_path.as_ptr().cast::<MaybeUninit<u16>>(), abs_path.len());
15111517

15121518
let mut ret = 0;
15131519
cvt(c::DeviceIoControl(
1514-
h as *mut _,
1520+
d.as_raw_handle(),
15151521
c::FSCTL_SET_REPARSE_POINT,
1516-
data_ptr.cast(),
1517-
(*db).ReparseDataLength + 8,
1522+
addr_of!(header).cast::<c_void>(),
1523+
data_len as u32 + 8,
15181524
ptr::null_mut(),
15191525
0,
15201526
&mut ret,

0 commit comments

Comments
 (0)