use std::io::{Read, Seek, SeekFrom, Write};
use std::os::fd::AsRawFd;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use crate::args::{MmapWorkloadConfig, WorkloadConfig};
pub(super) fn run_file_mutation_workload(config: &WorkloadConfig) -> Result<(), String> {
create_directory(&config.mkdir_path, config.directory_mode)?;
create_directory(&config.rmdir_path, config.directory_mode)?;
remove_directory(&config.rmdir_path)?;
std::fs::write(&config.rename_source_path, config.file_message.as_bytes())
.map_err(|error| error.to_string())?;
rename_path(&config.rename_source_path, &config.rename_target_path)?;
std::fs::write(&config.unlink_path, config.file_message.as_bytes())
.map_err(|error| error.to_string())?;
unlink_path(&config.unlink_path)?;
let truncate_source = config
.file_message
.as_bytes()
.iter()
.chain(config.file_message.as_bytes())
.copied()
.collect::<Vec<_>>();
std::fs::write(&config.truncate_path, truncate_source).map_err(|error| error.to_string())?;
let _truncate_file = std::fs::OpenOptions::new()
.write(true)
.truncate(true)
.open(&config.truncate_path)
.map_err(|error| error.to_string())?;
Ok(())
}
pub(super) struct FileEndpoint {
file: std::fs::File,
}
impl FileEndpoint {
pub(super) fn create(path: &Path) -> Result<Self, String> {
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create_new(true)
.open(path)
.map_err(|error| error.to_string())?;
Ok(Self { file })
}
pub(super) fn roundtrip(&mut self, message: &[u8]) -> Result<(), String> {
self.file
.write_all(message)
.map_err(|error| error.to_string())?;
self.file.flush().map_err(|error| error.to_string())?;
self.file
.seek(SeekFrom::Start(0))
.map_err(|error| error.to_string())?;
let mut observed = vec![0; message.len()];
self.file
.read_exact(&mut observed)
.map_err(|error| error.to_string())?;
if observed != message {
return Err("file observed unexpected payload".to_string());
}
Ok(())
}
}
pub(super) struct MmapSharedFile {
_file: std::fs::File,
}
impl MmapSharedFile {
pub(super) fn create(config: &MmapWorkloadConfig) -> Result<Self, String> {
let length = usize::try_from(config.length).map_err(|error| error.to_string())?;
let offset = libc::off_t::try_from(config.offset).map_err(|error| error.to_string())?;
let message = config.message.as_bytes();
if length == 0 {
return Err("mmap length must be positive".to_string());
}
if message.len() > length {
return Err(format!(
"mmap message length {} exceeds configured mapping length {}",
message.len(),
length
));
}
let file_length = config
.offset
.checked_add(config.length)
.ok_or_else(|| "mmap offset plus length overflowed".to_string())?;
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create_new(true)
.open(&config.path)
.map_err(|error| error.to_string())?;
file.set_len(file_length)
.map_err(|error| error.to_string())?;
let mapping = unsafe {
libc::mmap(
std::ptr::null_mut(),
length,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
file.as_raw_fd(),
offset,
)
};
if mapping == libc::MAP_FAILED {
return Err(std::io::Error::last_os_error().to_string());
}
let write_result = unsafe {
std::ptr::copy_nonoverlapping(message.as_ptr(), mapping.cast::<u8>(), message.len());
libc::msync(mapping, length, libc::MS_SYNC)
};
let sync_error = if write_result == 0 {
None
} else {
Some(std::io::Error::last_os_error().to_string())
};
let unmap_result = unsafe { libc::munmap(mapping, length) };
if unmap_result != 0 {
return Err(std::io::Error::last_os_error().to_string());
}
if let Some(error) = sync_error {
return Err(error);
}
let mut observed = vec![0; message.len()];
let mut observed_file =
std::fs::File::open(&config.path).map_err(|error| error.to_string())?;
observed_file
.seek(SeekFrom::Start(config.offset))
.map_err(|error| error.to_string())?;
observed_file
.read_exact(&mut observed)
.map_err(|error| error.to_string())?;
if observed != message {
return Err("mmap file observed unexpected payload".to_string());
}
Ok(Self { _file: file })
}
}
fn create_directory(path: &Path, mode: u32) -> Result<(), String> {
let raw_path = cstring_path(path)?;
let result = unsafe { libc::mkdirat(libc::AT_FDCWD, raw_path.as_ptr(), mode) };
if result == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error().to_string())
}
}
fn remove_directory(path: &Path) -> Result<(), String> {
let raw_path = cstring_path(path)?;
let result = unsafe {
libc::syscall(
libc::SYS_unlinkat,
libc::AT_FDCWD,
raw_path.as_ptr(),
libc::AT_REMOVEDIR,
)
};
if result == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error().to_string())
}
}
fn rename_path(source: &Path, target: &Path) -> Result<(), String> {
let raw_source = cstring_path(source)?;
let raw_target = cstring_path(target)?;
let result = unsafe {
libc::renameat(
libc::AT_FDCWD,
raw_source.as_ptr(),
libc::AT_FDCWD,
raw_target.as_ptr(),
)
};
if result == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error().to_string())
}
}
fn unlink_path(path: &Path) -> Result<(), String> {
let raw_path = cstring_path(path)?;
let result = unsafe { libc::syscall(libc::SYS_unlinkat, libc::AT_FDCWD, raw_path.as_ptr(), 0) };
if result == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error().to_string())
}
}
fn cstring_path(path: &Path) -> Result<std::ffi::CString, String> {
std::ffi::CString::new(path.as_os_str().as_bytes())
.map_err(|_| format!("path contains NUL byte: {}", path.display()))
}