use crate::error;
use crate::platform;
use crate::platform::ffi;
use std::ffi::OsStr;
use std::fmt;
use std::os::fd::AsRawFd;
use std::os::fd::BorrowedFd;
use std::os::fd::FromRawFd;
use std::os::fd::OwnedFd;
use std::path::Path;
use std::path::PathBuf;
pub const MAXIMUM_SYMLINKS_FOLLOWED: usize = 40;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Error {
InvalidRelativePath,
InvalidPathComponent,
CannotFollowSymlink,
NotFound,
AlreadyExists,
Conflict(EntryType),
SystemError(String, i32),
}
impl fmt::Display for Error {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidRelativePath => formatter.write_str("invalid relative path supplied to file descriptor operation"),
Self::InvalidPathComponent => {
formatter.write_str("an intermediate path component is missing or is not a directory")
}
Self::CannotFollowSymlink => formatter.write_str("cannot follow a symlink in a file descriptor operation"),
Self::NotFound => formatter.write_str("no such entry in file descriptor operation"),
Self::AlreadyExists => formatter.write_str("entry already exists in file descriptor operation"),
Self::Conflict(entry_type) => {
write!(
formatter,
"a {entry_type} entry is in the way of a file descriptor operation"
)
}
Self::SystemError(operation, errno) => write!(formatter, "{operation} returned error: {errno}"),
}
}
}
impl std::error::Error for Error {}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum EntryType {
Regular,
Directory,
Symlink,
Other,
}
impl fmt::Display for EntryType {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::Regular => "regular",
Self::Directory => "directory",
Self::Symlink => "symlink",
Self::Other => "other",
})
}
}
impl From<ffi::FdOpsEntryType> for EntryType {
fn from(entry_type: ffi::FdOpsEntryType) -> Self {
match entry_type {
ffi::FdOpsEntryType::Regular => Self::Regular,
ffi::FdOpsEntryType::Directory => Self::Directory,
ffi::FdOpsEntryType::Symlink => Self::Symlink,
ffi::FdOpsEntryType::Other => Self::Other,
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct FileStatus {
pub r#type: EntryType,
pub permissions: u16,
pub size: i64,
pub user_id: u32,
pub group_id: u32,
pub modification_seconds: i64,
pub modification_nanoseconds: i64,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum SymlinkPolicy {
Refuse,
FollowBeneath,
}
impl From<SymlinkPolicy> for ffi::FdOpsSymlinkPolicy {
fn from(policy: SymlinkPolicy) -> Self {
match policy {
SymlinkPolicy::Refuse => Self::Refuse,
SymlinkPolicy::FollowBeneath => Self::FollowBeneath,
}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct MkdirOptions {
pub permissions: Option<u16>,
pub make_intermediates: bool,
}
pub fn entry_type(fd: BorrowedFd<'_>, name: &OsStr) -> Result<Option<EntryType>, error::Error> {
platform::outcome(
ffi::cz_fd_ops_entry_type(fd.as_raw_fd(), component(name)?),
format!("read the type of {}", name.display()),
)
.map(|outcome| outcome.optional_fd_ops_entry_type())
}
pub fn open_directory(fd: BorrowedFd<'_>, name: &OsStr) -> Result<OwnedFd, error::Error> {
opened(
ffi::cz_fd_ops_open_directory(fd.as_raw_fd(), component(name)?),
format!("open the directory {}", name.display()),
)
}
pub fn make_directory(fd: BorrowedFd<'_>, name: &OsStr, permissions: Option<u16>) -> Result<(), error::Error> {
platform::outcome(
ffi::cz_fd_ops_make_directory(
fd.as_raw_fd(),
component(name)?,
permissions.is_some(),
permissions.unwrap_or_default(),
),
format!("make the directory {}", name.display()),
)
.map(drop)
}
pub fn unlink(fd: BorrowedFd<'_>, name: &OsStr) -> Result<(), error::Error> {
platform::outcome(
ffi::cz_fd_ops_unlink(fd.as_raw_fd(), component(name)?),
format!("unlink {}", name.display()),
)
.map(drop)
}
pub fn status_of(fd: BorrowedFd<'_>) -> Result<FileStatus, error::Error> {
platform::outcome(
ffi::cz_fd_ops_status_of(fd.as_raw_fd()),
"read the status of a descriptor",
)
.map(|outcome| outcome.fd_ops_file_status())
}
pub fn status(fd: BorrowedFd<'_>, name: &OsStr) -> Result<Option<FileStatus>, error::Error> {
platform::outcome(
ffi::cz_fd_ops_status(fd.as_raw_fd(), component(name)?),
format!("read the status of {}", name.display()),
)
.map(|outcome| outcome.optional_fd_ops_file_status())
}
pub fn status_relative_path(
fd: BorrowedFd<'_>,
relative_path: &Path,
symlinks: SymlinkPolicy,
) -> Result<Option<FileStatus>, error::Error> {
platform::outcome(
ffi::cz_fd_ops_status_relative_path(fd.as_raw_fd(), platform::path(relative_path)?, symlinks.into()),
format!("read the status of {}", relative_path.display()),
)
.map(|outcome| outcome.optional_fd_ops_file_status())
}
pub fn open_file(fd: BorrowedFd<'_>, name: &OsStr) -> Result<OwnedFd, error::Error> {
opened(
ffi::cz_fd_ops_open_file(fd.as_raw_fd(), component(name)?),
format!("open the file {}", name.display()),
)
}
pub fn open_file_relative_path(
fd: BorrowedFd<'_>,
relative_path: &Path,
symlinks: SymlinkPolicy,
) -> Result<OwnedFd, error::Error> {
opened(
ffi::cz_fd_ops_open_file_relative_path(fd.as_raw_fd(), platform::path(relative_path)?, symlinks.into()),
format!("open the file {}", relative_path.display()),
)
}
pub fn create_file(fd: BorrowedFd<'_>, name: &OsStr, permissions: Option<u16>) -> Result<OwnedFd, error::Error> {
opened(
ffi::cz_fd_ops_create_file(
fd.as_raw_fd(),
component(name)?,
permissions.is_some(),
permissions.unwrap_or_default(),
),
format!("create the file {}", name.display()),
)
}
pub fn make_symlink(fd: BorrowedFd<'_>, name: &OsStr, target: &str) -> Result<(), error::Error> {
platform::outcome(
ffi::cz_fd_ops_make_symlink(fd.as_raw_fd(), component(name)?, target),
format!("make the symlink {}", name.display()),
)
.map(drop)
}
pub fn read_symlink(fd: BorrowedFd<'_>, name: &OsStr) -> Result<String, error::Error> {
platform::outcome(
ffi::cz_fd_ops_read_symlink(fd.as_raw_fd(), component(name)?),
format!("read the symlink {}", name.display()),
)
.map(|outcome| outcome.text())
}
pub fn unlink_recursive(fd: BorrowedFd<'_>, filename: &OsStr) -> Result<(), error::Error> {
platform::outcome(
ffi::cz_fd_ops_unlink_recursive(fd.as_raw_fd(), component(filename)?),
format!("remove {}", filename.display()),
)
.map(drop)
}
pub fn enumerate(
fd: BorrowedFd<'_>,
body: impl FnMut(&Path, EntryType, BorrowedFd<'_>) -> Result<(), error::Error> + 'static,
) -> Result<(), error::Error> {
let body = platform::Enumerate::new(body);
let thrown = body.thrown.clone();
thrown
.outcome(ffi::cz_fd_ops_enumerate(fd.as_raw_fd(), body), "enumerate a directory")
.map(drop)
}
pub fn get_canonical_path(fd: BorrowedFd<'_>) -> Result<PathBuf, error::Error> {
platform::outcome(
ffi::cz_fd_ops_get_canonical_path(fd.as_raw_fd()),
"read the canonical path of a descriptor",
)
.map(|outcome| PathBuf::from(outcome.text()))
}
pub fn mkdir(
fd: BorrowedFd<'_>,
relative_path: &Path,
options: MkdirOptions,
completion: impl FnOnce(BorrowedFd<'_>) -> Result<(), error::Error> + 'static,
) -> Result<(), error::Error> {
let completion = platform::WithDescriptor::new(completion);
let thrown = completion.thrown.clone();
thrown
.outcome(
ffi::cz_fd_ops_mkdir(
fd.as_raw_fd(),
platform::path(relative_path)?,
options.permissions.is_some(),
options.permissions.unwrap_or_default(),
options.make_intermediates,
completion,
),
format!("make the directory {}", relative_path.display()),
)
.map(drop)
}
fn component(name: &OsStr) -> Result<&str, error::Error> {
platform::path(Path::new(name))
}
fn opened(outcome: ffi::CzOutcome, action: String) -> Result<OwnedFd, error::Error> {
platform::outcome(outcome, action).map(|outcome| unsafe { OwnedFd::from_raw_fd(outcome.int32()) })
}