pub(crate) mod c;
pub(crate) mod cast;
pub(crate) mod helpers;
use std::ffi::OsString;
use std::mem::size_of;
use std::os::windows::ffi::OsStringExt;
use std::os::windows::io::AsRawHandle;
use std::path::{Path, PathBuf};
use std::ptr::{addr_of_mut, copy_nonoverlapping};
use std::{cmp, fs, io, slice};
use cast::BytesAsReparseDataBuffer;
const NT_PREFIX: [u16; 4] = helpers::utf16s(br"\??\");
const VERBATIM_PREFIX: [u16; 4] = helpers::utf16s(br"\\?\");
pub(crate) const WCHAR_SIZE: u16 = size_of::<u16>() as _;
pub fn create(target: &Path, junction: &Path) -> io::Result<()> {
const UNICODE_NULL_SIZE: u16 = WCHAR_SIZE;
const MAX_PATH_BUFFER: u16 = c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as u16
- c::REPARSE_DATA_BUFFER_HEADER_SIZE
- c::MOUNT_POINT_REPARSE_BUFFER_HEADER_SIZE;
let target = helpers::get_full_path(target)?;
let target = target.strip_prefix(VERBATIM_PREFIX.as_slice()).unwrap_or(&target);
fs::create_dir(junction)?;
let file = helpers::open_reparse_point(junction, true)?;
let substitute_len_in_bytes = {
let len = NT_PREFIX.len().saturating_add(target.len());
let min_len = cmp::min(len, u16::MAX as usize) as u16;
min_len.saturating_mul(WCHAR_SIZE)
};
let print_name_len_in_bytes = {
let min_len = cmp::min(target.len(), u16::MAX as usize) as u16;
min_len.saturating_mul(WCHAR_SIZE)
};
let total_path_buffer = substitute_len_in_bytes
.saturating_add(UNICODE_NULL_SIZE)
.saturating_add(print_name_len_in_bytes)
.saturating_add(UNICODE_NULL_SIZE);
if total_path_buffer > MAX_PATH_BUFFER {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "`target` is too long"));
}
let mut data = BytesAsReparseDataBuffer::new();
let rdb = data.as_mut_ptr();
let in_buffer_size: u16 = unsafe {
addr_of_mut!((*rdb).ReparseTag).write(c::IO_REPARSE_TAG_MOUNT_POINT);
addr_of_mut!((*rdb).Reserved).write(0);
addr_of_mut!((*rdb).ReparseBuffer.SubstituteNameOffset).write(0);
addr_of_mut!((*rdb).ReparseBuffer.SubstituteNameLength).write(substitute_len_in_bytes);
addr_of_mut!((*rdb).ReparseBuffer.PrintNameOffset).write(substitute_len_in_bytes + UNICODE_NULL_SIZE);
addr_of_mut!((*rdb).ReparseBuffer.PrintNameLength).write(print_name_len_in_bytes);
let mut path_buffer_ptr: *mut u16 = addr_of_mut!((*rdb).ReparseBuffer.PathBuffer).cast();
copy_nonoverlapping(NT_PREFIX.as_ptr(), path_buffer_ptr, NT_PREFIX.len());
path_buffer_ptr = path_buffer_ptr.add(NT_PREFIX.len());
copy_nonoverlapping(target.as_ptr(), path_buffer_ptr, target.len());
path_buffer_ptr = path_buffer_ptr.add(target.len());
path_buffer_ptr.write(0);
path_buffer_ptr = path_buffer_ptr.add(1);
copy_nonoverlapping(target.as_ptr(), path_buffer_ptr, target.len());
path_buffer_ptr = path_buffer_ptr.add(target.len());
path_buffer_ptr.write(0);
let size = c::MOUNT_POINT_REPARSE_BUFFER_HEADER_SIZE
+ substitute_len_in_bytes
+ UNICODE_NULL_SIZE
+ print_name_len_in_bytes
+ UNICODE_NULL_SIZE;
addr_of_mut!((*rdb).ReparseDataLength).write(size);
size.wrapping_add(c::REPARSE_DATA_BUFFER_HEADER_SIZE)
};
helpers::set_reparse_point(file.as_raw_handle(), rdb, u32::from(in_buffer_size))
}
pub fn delete(junction: &Path) -> io::Result<()> {
let file = helpers::open_reparse_point(junction, true)?;
helpers::delete_reparse_point(file.as_raw_handle())
}
pub fn exists(junction: &Path) -> io::Result<bool> {
if !junction.exists() {
return Ok(false);
}
let file = helpers::open_reparse_point(junction, false)?;
let mut data = BytesAsReparseDataBuffer::new();
helpers::get_reparse_data_point(file.as_raw_handle(), data.as_mut_ptr())?;
let rdb = unsafe { data.assume_init() };
Ok(rdb.ReparseTag == c::IO_REPARSE_TAG_MOUNT_POINT)
}
pub fn get_target(junction: &Path) -> io::Result<PathBuf> {
if !junction.exists() {
return Err(io::Error::new(io::ErrorKind::NotFound, "`junction` does not exist"));
}
let file = helpers::open_reparse_point(junction, false)?;
let mut data = BytesAsReparseDataBuffer::new();
helpers::get_reparse_data_point(file.as_raw_handle(), data.as_mut_ptr())?;
let rdb = unsafe { data.assume_init() };
if rdb.ReparseTag == c::IO_REPARSE_TAG_MOUNT_POINT {
let offset = rdb.ReparseBuffer.SubstituteNameOffset / WCHAR_SIZE;
let len = rdb.ReparseBuffer.SubstituteNameLength / WCHAR_SIZE;
let wide = unsafe {
let buf = rdb.ReparseBuffer.PathBuffer.as_ptr().add(offset as usize);
slice::from_raw_parts(buf, len as usize)
};
let wide = wide.strip_prefix(&NT_PREFIX).unwrap_or(wide);
Ok(PathBuf::from(OsString::from_wide(wide)))
} else {
Err(io::Error::new(io::ErrorKind::Other, "not a reparse tag mount point"))
}
}