Skip to content

Commit 2283478

Browse files
committed
Implement junction_point
1 parent 1e4f9e3 commit 2283478

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

@@ -142,16 +141,6 @@ pub struct MOUNT_POINT_REPARSE_BUFFER {
142141
pub PrintNameLength: c_ushort,
143142
pub PathBuffer: WCHAR,
144143
}
145-
#[repr(C)]
146-
pub struct REPARSE_MOUNTPOINT_DATA_BUFFER {
147-
pub ReparseTag: DWORD,
148-
pub ReparseDataLength: DWORD,
149-
pub Reserved: WORD,
150-
pub ReparseTargetLength: WORD,
151-
pub ReparseTargetMaximumLength: WORD,
152-
pub Reserved1: WORD,
153-
pub ReparseTarget: WCHAR,
154-
}
155144

156145
#[repr(C)]
157146
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)