#![forbid(unsafe_code)]
use std::sync::Mutex;
use widestring::U16CStr;
use windows_sys::Win32::Storage::FileSystem::{
FILE_ACCESS_RIGHTS, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_NORMAL, FILE_FLAGS_AND_ATTRIBUTES,
};
use winfsp::filesystem::{
DirBuffer, DirInfo, DirMarker, FileInfo, FileSecurity, FileSystemContext, OpenFileInfo,
VolumeInfo, WideNameInfo,
};
use winfsp::host::{FileSystemHost, VolumeParams};
use winfsp::winfsp_init_or_die;
use winfsp::FspError;
use crate::session::Session;
use crate::{ForensicFs, FsFileType, FsMetadata, FsTimestamp, MountOptions};
const STATUS_OBJECT_NAME_NOT_FOUND: i32 = 0xC000_0034u32 as i32;
const STATUS_IO_DEVICE_ERROR: i32 = 0xC000_0185u32 as i32;
const FILETIME_UNIX_OFFSET: u64 = 11_644_473_600;
fn not_found() -> FspError {
FspError::NTSTATUS(STATUS_OBJECT_NAME_NOT_FOUND)
}
fn io_error() -> FspError {
FspError::NTSTATUS(STATUS_IO_DEVICE_ERROR)
}
fn to_filetime(ts: FsTimestamp) -> u64 {
if ts.seconds <= 0 {
return 0;
}
(ts.seconds as u64 + FILETIME_UNIX_OFFSET) * 10_000_000 + u64::from(ts.nanoseconds) / 100
}
fn attributes(ft: FsFileType) -> FILE_FLAGS_AND_ATTRIBUTES {
match ft {
FsFileType::Directory => FILE_ATTRIBUTE_DIRECTORY,
_ => FILE_ATTRIBUTE_NORMAL,
}
}
pub struct WinForensicFs {
fs: Mutex<Box<dyn ForensicFs + Send>>,
root_ino: u64,
}
impl WinForensicFs {
fn new(fs: Box<dyn ForensicFs + Send>) -> Self {
let root_ino = fs.root_ino();
Self {
fs: Mutex::new(fs),
root_ino,
}
}
fn resolve(&self, path: &U16CStr) -> Option<u64> {
let path = path.to_string_lossy();
let mut ino = self.root_ino;
let mut fs = self.fs.lock().ok()?;
for comp in path.split(['\\', '/']).filter(|c| !c.is_empty()) {
ino = fs.lookup(ino, comp.as_bytes()).ok().flatten()?;
}
Some(ino)
}
fn fill(&self, ino: u64, fi: &mut FileInfo) -> Result<(), FspError> {
let meta: FsMetadata = {
let mut fs = self.fs.lock().map_err(|_| io_error())?;
fs.metadata(ino).map_err(|_| not_found())?
};
fi.file_attributes = attributes(meta.file_type);
fi.file_size = meta.size;
fi.allocation_size = meta.size.div_ceil(4096) * 4096;
fi.creation_time = to_filetime(meta.crtime);
fi.last_access_time = to_filetime(meta.atime);
fi.last_write_time = to_filetime(meta.mtime);
fi.change_time = to_filetime(meta.ctime);
fi.index_number = ino;
Ok(())
}
}
pub struct WinFile {
ino: u64,
dir_buffer: DirBuffer,
}
impl FileSystemContext for WinForensicFs {
type FileContext = WinFile;
fn get_security_by_name(
&self,
file_name: &U16CStr,
_security_descriptor: Option<&mut [std::ffi::c_void]>,
_reparse_point_resolver: impl FnOnce(&U16CStr) -> Option<FileSecurity>,
) -> Result<FileSecurity, FspError> {
let ino = self.resolve(file_name).ok_or_else(not_found)?;
let meta = {
let mut fs = self.fs.lock().map_err(|_| io_error())?;
fs.metadata(ino).map_err(|_| not_found())?
};
Ok(FileSecurity {
reparse: false,
sz_security_descriptor: 0,
attributes: attributes(meta.file_type),
})
}
fn open(
&self,
file_name: &U16CStr,
_create_options: u32,
_granted_access: FILE_ACCESS_RIGHTS,
file_info: &mut OpenFileInfo,
) -> Result<Self::FileContext, FspError> {
let ino = self.resolve(file_name).ok_or_else(not_found)?;
self.fill(ino, file_info.as_mut())?;
Ok(WinFile {
ino,
dir_buffer: DirBuffer::default(),
})
}
fn close(&self, _context: Self::FileContext) {}
fn get_file_info(
&self,
context: &Self::FileContext,
file_info: &mut FileInfo,
) -> Result<(), FspError> {
self.fill(context.ino, file_info)
}
fn read(
&self,
context: &Self::FileContext,
buffer: &mut [u8],
offset: u64,
) -> Result<u32, FspError> {
let data = {
let mut fs = self.fs.lock().map_err(|_| io_error())?;
fs.read_file_range(context.ino, offset, buffer.len() as u64)
.map_err(|_| io_error())?
};
let n = data.len().min(buffer.len());
buffer[..n].copy_from_slice(&data[..n]);
Ok(n as u32)
}
fn read_directory(
&self,
context: &Self::FileContext,
_pattern: Option<&U16CStr>,
marker: DirMarker,
buffer: &mut [u8],
) -> Result<u32, FspError> {
if let Ok(lock) = context.dir_buffer.acquire(marker.is_none(), None) {
let mut entries: Vec<(String, u64)> = {
let mut fs = self.fs.lock().map_err(|_| io_error())?;
fs.read_dir(context.ino)
.map_err(|_| io_error())?
.into_iter()
.filter(|e| e.name != b"." && e.name != b"..")
.map(|e| (String::from_utf8_lossy(&e.name).into_owned(), e.inode))
.collect()
};
entries.sort_by(|a, b| a.0.cmp(&b.0));
for (name, ino) in entries {
let mut dir_info: DirInfo<255> = DirInfo::new();
if self.fill(ino, dir_info.file_info_mut()).is_err() {
continue;
}
dir_info.set_name(name.as_str())?;
lock.write(&mut dir_info)?;
}
}
Ok(context.dir_buffer.read(marker, buffer))
}
fn get_volume_info(&self, out: &mut VolumeInfo) -> Result<(), FspError> {
out.total_size = 0;
out.free_size = 0;
Ok(())
}
}
pub fn mount_windows(
fs: Box<dyn ForensicFs + Send>,
mountpoint: &std::path::Path,
_session: Option<Session>,
options: &MountOptions,
) -> std::io::Result<()> {
let _init = winfsp_init_or_die();
let mut volume_params = VolumeParams::new();
volume_params
.sector_size(4096)
.sectors_per_allocation_unit(1)
.case_sensitive_search(true)
.case_preserved_names(true)
.unicode_on_disk(true)
.persistent_acls(false)
.filesystem_name(&options.fs_name);
let context = WinForensicFs::new(fs);
let mut host = FileSystemHost::new(volume_params, context)
.map_err(|e| std::io::Error::other(format!("WinFsp host: {e:?}")))?;
host.mount(mountpoint.as_os_str())
.map_err(|e| std::io::Error::other(format!("WinFsp mount: {e:?}")))?;
host.start()
.map_err(|e| std::io::Error::other(format!("WinFsp start: {e:?}")))?;
eprintln!("4n6mount: mounted at {} (WinFsp)", mountpoint.display());
loop {
std::thread::sleep(std::time::Duration::from_secs(3600));
}
}