use super::NamedWriteError;
use std::{fs, path::Path};
#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
use super::supported::WriterCustody;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PublicationMode {
Replace,
CreateNew,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct WriteOptions {
pub mode: PublicationMode,
pub permissions: u32,
}
pub fn write_with<T, E>(
path: &Path,
options: WriteOptions,
produce: impl FnOnce(&mut fs::File) -> Result<T, E>,
) -> Result<T, NamedWriteError<E>> {
#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
{
super::supported::commit_path_with_options(
path,
options,
|_, file| produce(file),
(WriterCustody::Writable, |_, _| Ok(())),
|_, _| Ok(()),
)
}
#[cfg(not(any(target_os = "linux", target_os = "android", target_vendor = "apple")))]
{
let _ = (path, options, produce);
Err(NamedWriteError::before(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"durable atomic file publication is unsupported",
)))
}
}
pub fn write_existing_parent_validated_with<T, E>(
path: &Path,
options: WriteOptions,
produce: impl FnOnce(&mut fs::File) -> Result<T, E>,
admit: impl FnOnce(&T) -> Result<(), E>,
) -> Result<T, NamedWriteError<E>> {
#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
{
use rustix::fs::{Mode, OFlags, open};
use std::os::fd::AsFd as _;
super::supported::native_permissions(options.permissions)
.map_err(NamedWriteError::before)?;
let (parent, name) =
super::supported::split_target(path).map_err(NamedWriteError::before)?;
let parent = open(
parent,
OFlags::RDONLY | OFlags::DIRECTORY | OFlags::NONBLOCK | OFlags::CLOEXEC,
Mode::empty(),
)
.map_err(|source| NamedWriteError::before(source.into()))?;
write_at_validated_with(parent.as_fd(), name, options, produce, admit)
}
#[cfg(not(any(target_os = "linux", target_os = "android", target_vendor = "apple")))]
{
let _ = (path, options, produce, admit);
Err(NamedWriteError::before(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"durable atomic file publication is unsupported",
)))
}
}
#[cfg(unix)]
pub fn write_at_validated_with<T, E>(
parent: std::os::fd::BorrowedFd<'_>,
name: &std::ffi::OsStr,
options: WriteOptions,
produce: impl FnOnce(&mut fs::File) -> Result<T, E>,
admit: impl FnOnce(&T) -> Result<(), E>,
) -> Result<T, NamedWriteError<E>> {
#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
{
let permissions = super::supported::native_permissions(options.permissions)
.map_err(NamedWriteError::before)?;
super::supported::commit_at_with_hook(
parent,
None,
name,
(options.mode, permissions),
|_, file| produce(file),
(WriterCustody::Writable, |_, value| admit(value)),
|_, _| Ok(()),
)
}
#[cfg(not(any(target_os = "linux", target_os = "android", target_vendor = "apple")))]
{
let _ = (parent, name, options, produce, admit);
Err(NamedWriteError::before(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"durable atomic file publication is unsupported",
)))
}
}
pub fn write_validated_with<T, E>(
path: &Path,
options: WriteOptions,
produce: impl FnOnce(&mut fs::File) -> Result<T, E>,
admit: impl FnOnce(&Path, &T) -> Result<(), E>,
) -> Result<T, NamedWriteError<E>> {
#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
{
super::supported::validate_target_path(path).map_err(NamedWriteError::before)?;
let absolute = std::path::absolute(path).map_err(NamedWriteError::before)?;
super::supported::commit_path_with_options(
&absolute,
options,
|_, file| produce(file),
(WriterCustody::ReadOnly, admit),
|_, _| Ok(()),
)
}
#[cfg(not(any(target_os = "linux", target_os = "android", target_vendor = "apple")))]
{
let _ = (path, options, produce, admit);
Err(NamedWriteError::before(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"durable atomic file publication is unsupported",
)))
}
}
#[cfg(all(
test,
any(target_os = "linux", target_os = "android", target_vendor = "apple")
))]
mod tests;