Skip to content

Commit

Permalink
Use the junction crate in bootstrap instead of manually creating th…
Browse files Browse the repository at this point in the history
…e junction
  • Loading branch information
thomcc committed Apr 5, 2023
1 parent 12dff54 commit 42e38e8
Show file tree
Hide file tree
Showing 3 changed files with 15 additions and 115 deletions.
11 changes: 11 additions & 0 deletions src/bootstrap/Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ dependencies = [
"hex",
"ignore",
"is-terminal",
"junction",
"libc",
"object",
"once_cell",
Expand Down Expand Up @@ -349,6 +350,16 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"

[[package]]
name = "junction"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca39ef0d69b18e6a2fd14c2f0a1d593200f4a4ed949b240b5917ab51fac754cb"
dependencies = [
"scopeguard",
"winapi",
]

[[package]]
name = "lazy_static"
version = "1.4.0"
Expand Down
3 changes: 3 additions & 0 deletions src/bootstrap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ sysinfo = { version = "0.26.0", optional = true }
[target.'cfg(not(target_os = "solaris"))'.dependencies]
fd-lock = "3.0.8"

[target.'cfg(windows)'.dependencies.junction]
version = "1.0.0"

[target.'cfg(windows)'.dependencies.windows]
version = "0.46.0"
features = [
Expand Down
116 changes: 1 addition & 115 deletions src/bootstrap/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,123 +146,9 @@ pub fn symlink_dir(config: &Config, src: &Path, dest: &Path) -> io::Result<()> {
fs::symlink(src, dest)
}

// 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
#[cfg(windows)]
fn symlink_dir_inner(target: &Path, junction: &Path) -> io::Result<()> {
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;

use windows::{
core::PCWSTR,
Win32::Foundation::{CloseHandle, HANDLE},
Win32::Storage::FileSystem::{
CreateFileW, FILE_ACCESS_FLAGS, FILE_FLAG_BACKUP_SEMANTICS,
FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE,
MAXIMUM_REPARSE_DATA_BUFFER_SIZE, OPEN_EXISTING,
},
Win32::System::Ioctl::FSCTL_SET_REPARSE_POINT,
Win32::System::SystemServices::{GENERIC_WRITE, IO_REPARSE_TAG_MOUNT_POINT},
Win32::System::IO::DeviceIoControl,
};

#[allow(non_snake_case)]
#[repr(C)]
struct REPARSE_MOUNTPOINT_DATA_BUFFER {
ReparseTag: u32,
ReparseDataLength: u32,
Reserved: u16,
ReparseTargetLength: u16,
ReparseTargetMaximumLength: u16,
Reserved1: u16,
ReparseTarget: u16,
}

fn to_u16s<S: AsRef<OsStr>>(s: S) -> io::Result<Vec<u16>> {
Ok(s.as_ref().encode_wide().chain(Some(0)).collect())
}

// We're using low-level APIs to create the junction, and these are more
// picky about paths. For example, forward slashes cannot be used as a
// path separator, so we should try to canonicalize the path first.
let target = fs::canonicalize(target)?;

fs::create_dir(junction)?;

let path = to_u16s(junction)?;

let h = unsafe {
CreateFileW(
PCWSTR(path.as_ptr()),
FILE_ACCESS_FLAGS(GENERIC_WRITE),
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
None,
OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
HANDLE::default(),
)
}
.map_err(|_| io::Error::last_os_error())?;

unsafe {
#[repr(C, align(8))]
struct Align8<T>(T);
let mut data = Align8([0u8; MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize]);
let db = data.0.as_mut_ptr() as *mut REPARSE_MOUNTPOINT_DATA_BUFFER;
let end = db.cast::<u8>().add(MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize);
let reparse_target_slice = {
let buf_start = core::ptr::addr_of_mut!((*db).ReparseTarget).cast::<u16>();
// 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(end.offset_from(buf_start.cast::<u8>())).unwrap();
let buf_len_wchars = buf_len_bytes / core::mem::size_of::<u16>();
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(path.iter().copied())
.chain(core::iter::once(0));
let mut i = 0;
for c in iter {
if i >= reparse_target_slice.len() {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("path too long for reparse target: {target:?}"),
));
}
reparse_target_slice[i] = c;
i += 1;
}
(*db).ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
(*db).ReparseTargetMaximumLength = (i * 2) as u16;
(*db).ReparseTargetLength = ((i - 1) * 2) as u16;
(*db).ReparseDataLength = ((*db).ReparseTargetLength + 12) as u32;

let mut ret = 0u32;
DeviceIoControl(
h,
FSCTL_SET_REPARSE_POINT,
Some(db.cast()),
(*db).ReparseDataLength + 8,
None,
0,
Some(&mut ret),
None,
)
.ok()
.map_err(|_| io::Error::last_os_error())?;
}

unsafe { CloseHandle(h) };
Ok(())
junction::create(&target, &junction)
}
}

Expand Down

0 comments on commit 42e38e8

Please sign in to comment.