use crate::fs::{open, FollowSymlinks, OpenOptions};
use std::{
ffi::OsString,
fs, io,
os::windows::{ffi::OsStringExt, fs::OpenOptionsExt, io::AsRawHandle},
path::{Path, PathBuf},
ptr, slice,
};
#[allow(non_snake_case)]
mod c {
use winapi::ctypes::*;
pub(super) use winapi::{
shared::minwindef::*,
um::{ioapiset::*, winbase::*, winioctl::*, winnt::*},
};
#[repr(C)]
pub(super) struct REPARSE_DATA_BUFFER {
pub(super) ReparseTag: c_uint,
pub(super) ReparseDataLength: c_ushort,
pub(super) Reserved: c_ushort,
pub(super) rest: (),
}
#[repr(C)]
pub(super) struct SYMBOLIC_LINK_REPARSE_BUFFER {
pub(super) SubstituteNameOffset: c_ushort,
pub(super) SubstituteNameLength: c_ushort,
pub(super) PrintNameOffset: c_ushort,
pub(super) PrintNameLength: c_ushort,
pub(super) Flags: c_ulong,
pub(super) PathBuffer: WCHAR,
}
#[repr(C)]
pub struct MOUNT_POINT_REPARSE_BUFFER {
pub(super) SubstituteNameOffset: c_ushort,
pub(super) SubstituteNameLength: c_ushort,
pub(super) PrintNameOffset: c_ushort,
pub(super) PrintNameLength: c_ushort,
pub(super) PathBuffer: WCHAR,
}
pub(super) const SYMLINK_FLAG_RELATIVE: DWORD = 0x00000001;
pub(super) const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: usize = 16 * 1024;
}
fn cvt(i: i32) -> io::Result<i32> {
if i == 0 {
Err(io::Error::last_os_error())
} else {
Ok(i)
}
}
pub(crate) fn read_link_impl(start: &fs::File, path: &Path) -> io::Result<PathBuf> {
let mut opts = OpenOptions::new();
opts.access_mode(0);
opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT | c::FILE_FLAG_BACKUP_SEMANTICS);
opts.follow(FollowSymlinks::No);
let file = open(start, path, &opts)?;
read_link(&file)
}
fn reparse_point<'a>(
file: &fs::File,
space: &'a mut [u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE],
) -> io::Result<(c::DWORD, &'a c::REPARSE_DATA_BUFFER)> {
unsafe {
let mut bytes = 0;
cvt({
c::DeviceIoControl(
file.as_raw_handle(),
c::FSCTL_GET_REPARSE_POINT,
ptr::null_mut(),
0,
space.as_mut_ptr() as *mut _,
space.len() as c::DWORD,
&mut bytes,
ptr::null_mut(),
)
})?;
Ok((bytes, &*(space.as_ptr() as *const c::REPARSE_DATA_BUFFER)))
}
}
fn read_link(file: &fs::File) -> io::Result<PathBuf> {
let mut space = [0_u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
let (_bytes, buf) = reparse_point(file, &mut space)?;
unsafe {
let (path_buffer, subst_off, subst_len, relative) = match buf.ReparseTag {
c::IO_REPARSE_TAG_SYMLINK => {
let info: *const c::SYMBOLIC_LINK_REPARSE_BUFFER =
&buf.rest as *const _ as *const _;
(
&(*info).PathBuffer as *const _ as *const u16,
(*info).SubstituteNameOffset / 2,
(*info).SubstituteNameLength / 2,
(*info).Flags & c::SYMLINK_FLAG_RELATIVE != 0,
)
}
c::IO_REPARSE_TAG_MOUNT_POINT => {
let info: *const c::MOUNT_POINT_REPARSE_BUFFER = &buf.rest as *const _ as *const _;
(
&(*info).PathBuffer as *const _ as *const u16,
(*info).SubstituteNameOffset / 2,
(*info).SubstituteNameLength / 2,
false,
)
}
_ => {
return Err(io::Error::new(
io::ErrorKind::Other,
"Unsupported reparse point type",
));
}
};
let subst_ptr = path_buffer.offset(subst_off as isize);
let mut subst = slice::from_raw_parts(subst_ptr, subst_len as usize);
if !relative && subst.starts_with(&[92u16, 63u16, 63u16, 92u16]) {
subst = &subst[4..];
}
Ok(PathBuf::from(OsString::from_wide(subst)))
}
}