blob: 1ec68b7cc1b1759540ed5eee0bb2d89850b648c9 [file] [log] [blame] [edit]
mod c;
mod cast;
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;
/// This prefix indicates to NTFS that the path is to be treated as a non-interpreted
/// path in the virtual file system.
const NON_INTERPRETED_PATH_PREFIX: [u16; 4] = helpers::utf16s(br"\??\");
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_AVAILABLE_PATH_BUFFER: u16 = c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as u16
- c::REPARSE_DATA_BUFFER_HEADER_SIZE
- c::MOUNT_POINT_REPARSE_BUFFER_HEADER_SIZE
- 2 * UNICODE_NULL_SIZE;
// 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 = helpers::get_full_path(target)?;
fs::create_dir(junction)?;
let file = helpers::open_reparse_point(junction, true)?;
let target_len_in_bytes = {
// "\??\" + target
let len = NON_INTERPRETED_PATH_PREFIX.len().saturating_add(target.len());
let min_len = cmp::min(len, u16::MAX as usize) as u16;
// Len without `UNICODE_NULL` at the end
let target_len_in_bytes = min_len.saturating_mul(WCHAR_SIZE);
// Check for buffer overflow.
if target_len_in_bytes > MAX_AVAILABLE_PATH_BUFFER {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "`target` is too long"));
}
target_len_in_bytes
};
// Redefine the above char array into a ReparseDataBuffer we can work with
let mut data = BytesAsReparseDataBuffer::new();
let rdb = data.as_mut_ptr();
let in_buffer_size: u16 = unsafe {
// Set the type of reparse point we are creating
addr_of_mut!((*rdb).ReparseTag).write(c::IO_REPARSE_TAG_MOUNT_POINT);
addr_of_mut!((*rdb).Reserved).write(0);
// We write target at offset 0 of PathBuffer
addr_of_mut!((*rdb).ReparseBuffer.SubstituteNameOffset).write(0);
addr_of_mut!((*rdb).ReparseBuffer.SubstituteNameLength).write(target_len_in_bytes);
// We do not use PrintName. However let's set its offset correctly right after SubstituteName
addr_of_mut!((*rdb).ReparseBuffer.PrintNameOffset).write(target_len_in_bytes + UNICODE_NULL_SIZE);
addr_of_mut!((*rdb).ReparseBuffer.PrintNameLength).write(0);
let mut path_buffer_ptr: *mut u16 = addr_of_mut!((*rdb).ReparseBuffer.PathBuffer).cast();
// Safe because we checked `MAX_AVAILABLE_PATH_BUFFER`
copy_nonoverlapping(
NON_INTERPRETED_PATH_PREFIX.as_ptr(),
path_buffer_ptr,
NON_INTERPRETED_PATH_PREFIX.len(),
);
// TODO: Do we need to write the NULL-terminator byte?
// It looks like libuv does that.
path_buffer_ptr = path_buffer_ptr.add(NON_INTERPRETED_PATH_PREFIX.len());
copy_nonoverlapping(target.as_ptr(), path_buffer_ptr, target.len());
// Set the total size of the data buffer
let size = target_len_in_bytes.wrapping_add(c::MOUNT_POINT_REPARSE_BUFFER_HEADER_SIZE + 2 * 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() as isize, 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() as isize)
}
pub fn exists(junction: &Path) -> io::Result<bool> {
if !junction.exists() {
return Ok(false);
}
let file = helpers::open_reparse_point(junction, false)?;
// Allocate enough space to fit the maximum sized reparse data buffer
let mut data = BytesAsReparseDataBuffer::new();
// XXX: Could also use FindFirstFile to read the reparse point type
// Ref https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-point-tags
helpers::get_reparse_data_point(file.as_raw_handle() as isize, data.as_mut_ptr())?;
// SATETY: rdb should be initialized now
let rdb = unsafe { data.assume_init() };
// The reparse tag indicates if this is a junction or not
Ok(rdb.ReparseTag == c::IO_REPARSE_TAG_MOUNT_POINT)
}
pub fn get_target(junction: &Path) -> io::Result<PathBuf> {
// MSRV(1.63): use Path::try_exists instead
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() as isize, data.as_mut_ptr())?;
// SAFETY: rdb should be initialized now
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)
};
// In case of "\??\C:\foo\bar"
let wide = wide.strip_prefix(&NON_INTERPRETED_PATH_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"))
}
}