use super::NamedWriteError;
use std::{convert::Infallible, ffi::OsStr, fmt, fs::File, io, os::fd::BorrowedFd};
#[derive(Debug)]
pub enum RetainedWriteError<E> {
Create(io::Error),
Produce(E),
SyncFile(io::Error),
SyncDirectory(io::Error),
}
impl<E> fmt::Display for RetainedWriteError<E> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::Create(_) => "retained file creation failed",
Self::Produce(_) => "retained file production failed",
Self::SyncFile(_) => "retained file synchronization failed",
Self::SyncDirectory(_) => "retained directory synchronization failed",
})
}
}
impl<E: std::error::Error + 'static> std::error::Error for RetainedWriteError<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(match self {
Self::Create(source) | Self::SyncFile(source) | Self::SyncDirectory(source) => source,
Self::Produce(source) => source,
})
}
}
pub fn write_retained_at_with<T, E>(
parent: BorrowedFd<'_>,
name: &OsStr,
permissions: u32,
produce: impl FnOnce(&mut File) -> Result<T, E>,
) -> Result<T, RetainedWriteError<E>> {
let mut file =
create_retained_at(parent, name, permissions).map_err(RetainedWriteError::Create)?;
let output = produce(&mut file).map_err(RetainedWriteError::Produce)?;
file.sync_all().map_err(RetainedWriteError::SyncFile)?;
#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
rustix::fs::fsync(parent).map_err(|error| RetainedWriteError::SyncDirectory(error.into()))?;
Ok(output)
}
pub fn create_retained_at(
parent: BorrowedFd<'_>,
name: &OsStr,
permissions: u32,
) -> io::Result<File> {
#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
{
use rustix::fs::{OFlags, fsync, openat};
super::supported::validate_file_name(name)?;
let permissions = super::supported::native_permissions(permissions)?;
let file = File::from(openat(
parent,
name,
OFlags::WRONLY | OFlags::CREATE | OFlags::EXCL | OFlags::NOFOLLOW | OFlags::CLOEXEC,
permissions,
)?);
fsync(parent)?;
Ok(file)
}
#[cfg(not(any(target_os = "linux", target_os = "android", target_vendor = "apple")))]
{
let _ = (parent, name, permissions);
Err(io::Error::from(io::ErrorKind::Unsupported))
}
}
pub fn publish_retained_at(
parent: BorrowedFd<'_>,
stage: &OsStr,
target: &OsStr,
file: File,
) -> Result<(), NamedWriteError<Infallible>> {
#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
{
use super::supported::{publish_create_new, validate_file_name, verify_staging};
validate_file_name(stage).map_err(NamedWriteError::before)?;
validate_file_name(target).map_err(NamedWriteError::before)?;
verify_staging(&parent, None, stage, &file).map_err(NamedWriteError::before)?;
file.sync_all().map_err(NamedWriteError::before)?;
publish_create_new(&parent, stage, target, &file)?;
drop(file);
rustix::fs::fsync(parent).map_err(|source| NamedWriteError::AfterPublication {
source: source.into(),
})
}
#[cfg(not(any(target_os = "linux", target_os = "android", target_vendor = "apple")))]
{
let _ = (parent, stage, target, file);
Err(NamedWriteError::before(io::Error::from(
io::ErrorKind::Unsupported,
)))
}
}
#[cfg(all(
test,
any(target_os = "linux", target_os = "android", target_vendor = "apple")
))]
mod tests;