use std::collections::HashMap;
use std::path::PathBuf;
use rustix::fs::{openat, Mode, OFlags, CWD};
use wireshift_core::op::CompletionPayload;
use wireshift_core::{Error, Result};
pub(crate) fn execute_openat(
dir: Option<std::fs::File>,
path: PathBuf,
flags: u32,
read: bool,
write: bool,
create: bool,
truncate: bool,
) -> Result<CompletionPayload> {
Ok(CompletionPayload::File(open_file_at(
dir, path, flags, read, write, create, truncate,
)?))
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn execute_openat_direct(
dir: Option<std::fs::File>,
path: PathBuf,
flags: u32,
read: bool,
write: bool,
create: bool,
truncate: bool,
slot: u32,
fixed_files: &mut HashMap<u32, std::fs::File>,
) -> Result<CompletionPayload> {
let file = open_file_at(dir, path, flags, read, write, create, truncate)?;
if fixed_files.insert(slot, file).is_some() {
return Err(Error::completion(
format!("direct descriptor slot {slot} was reused before close"),
"ensure each chained direct descriptor slot is closed exactly once",
));
}
Ok(CompletionPayload::Unit)
}
fn open_file_at(
dir: Option<std::fs::File>,
path: PathBuf,
flags: u32,
read: bool,
write: bool,
create: bool,
truncate: bool,
) -> Result<std::fs::File> {
if path.as_os_str().is_empty() {
return Err(Error::validation(
"openat path must not be empty",
"provide a non-empty filesystem path",
));
}
if dir.is_some() && path.is_absolute() {
return Err(Error::validation(
"openat path must be relative when a directory handle is provided",
"pass a relative path or omit the directory file descriptor",
));
}
let oflags = open_flags(flags, read, write, create, truncate)?;
let create_mode = if create {
Mode::from_raw_mode(0o600)
} else {
Mode::empty()
};
let file = if let Some(dir) = dir {
std::fs::File::from(openat(&dir, &path, oflags, create_mode).map_err(|error| {
Error::io(
"openat failed",
error.into(),
"ensure the relative path exists under the provided directory and permissions are correct",
)
})?)
} else {
std::fs::File::from(openat(CWD, &path, oflags, create_mode).map_err(|error| {
Error::io(
"openat failed",
error.into(),
"ensure the path exists and permissions are correct",
)
})?)
};
apply_open_hints(&file, flags)?;
Ok(file)
}
fn open_flags(
open_flags: u32,
read: bool,
write: bool,
create: bool,
truncate: bool,
) -> Result<OFlags> {
let mut flags = match (read, write) {
(true, true) => OFlags::RDWR,
(true, false) => OFlags::RDONLY,
(false, true) => OFlags::WRONLY,
(false, false) => {
return Err(Error::validation(
"openat must request read and/or write access",
"enable read_only or create a read-write open request",
));
}
};
if create {
flags |= OFlags::CREATE;
}
if truncate {
flags |= OFlags::TRUNC;
}
flags |= OFlags::from_bits_retain(open_flags & !sequential_hint_bit());
flags |= OFlags::CLOEXEC;
Ok(flags)
}
fn apply_open_hints(file: &std::fs::File, flags: u32) -> Result<()> {
if flags & sequential_hint_bit() == 0 {
return Ok(());
}
rustix::fs::fadvise(file, 0, None, rustix::fs::Advice::Sequential).map_err(|error| {
Error::io(
"posix_fadvise(SEQUENTIAL) failed after openat",
std::io::Error::from(error),
"remove OpenFlags::SEQUENTIAL or ensure the target filesystem supports fadvise",
)
})?;
Ok(())
}
const fn sequential_hint_bit() -> u32 {
1 << 31
}