Use the junction
crate in bootstrap instead of manually creating the junction
This commit is contained in:
parent
12dff54a6a
commit
42e38e8949
src/bootstrap
@ -45,6 +45,7 @@ dependencies = [
|
||||
"hex",
|
||||
"ignore",
|
||||
"is-terminal",
|
||||
"junction",
|
||||
"libc",
|
||||
"object",
|
||||
"once_cell",
|
||||
@ -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"
|
||||
|
@ -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 = [
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user