use crate::atomic::{durable_sync_with, SyncLevel, SyncPolicy};
use crate::error::{FsError, Result};
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct AppendOptions {
pub private: bool,
pub durable: bool,
}
impl Default for AppendOptions {
fn default() -> Self {
Self {
private: false,
durable: true,
}
}
}
impl AppendOptions {
pub fn private() -> Self {
Self {
private: true,
..Self::default()
}
}
#[must_use]
pub fn durable(mut self, durable: bool) -> Self {
self.durable = durable;
self
}
}
pub fn append_record(
path: &Path,
bytes: &[u8],
options: AppendOptions,
) -> Result<Option<SyncLevel>> {
let name = path
.file_name()
.ok_or_else(|| FsError::BadName(path.display().to_string()))?;
crate::atomic::reject_reserved_name(&name.to_string_lossy())?;
match std::fs::symlink_metadata(path) {
Ok(meta) if meta.file_type().is_symlink() => {
return Err(FsError::Symlink(path.to_path_buf()))
}
Ok(meta) if !meta.is_file() => return Err(FsError::NotAFile(path.to_path_buf())),
_ => {}
}
let (mut file, created) = open_append(path, options)?;
file.write_all(bytes).map_err(|e| FsError::io(path, e))?;
if !options.durable {
return Ok(None);
}
let level = durable_sync_with(&file, SyncPolicy::Fallback).map_err(|e| FsError::io(path, e))?;
if created {
let parent = match path.parent() {
Some(p) if !p.as_os_str().is_empty() => p,
_ => Path::new("."),
};
crate::atomic::try_sync_dir(parent).map_err(|source| FsError::DirSyncFailed {
path: path.to_path_buf(),
source,
})?;
}
Ok(Some(level))
}
pub fn open_private_append(path: &Path) -> Result<std::fs::File> {
let name = path
.file_name()
.ok_or_else(|| FsError::BadName(path.display().to_string()))?;
crate::atomic::reject_reserved_name(&name.to_string_lossy())?;
match std::fs::symlink_metadata(path) {
Ok(meta) if meta.file_type().is_symlink() => {
return Err(FsError::Symlink(path.to_path_buf()))
}
Ok(meta) if !meta.is_file() => return Err(FsError::NotAFile(path.to_path_buf())),
_ => {}
}
open_append(path, AppendOptions::private()).map(|(file, _)| file)
}
fn open_append(path: &Path, options: AppendOptions) -> Result<(std::fs::File, bool)> {
if options.private {
match crate::private::create_private_new(path) {
Ok(created) => {
drop(created);
let file = append_handle(path)?;
return Ok((file, true));
}
Err(e) if e.kind() == Some(std::io::ErrorKind::AlreadyExists) => {}
Err(e) => return Err(e),
}
return Ok((append_handle(path)?, false));
}
let existed = path.exists();
let file = append_handle(path)?;
Ok((file, !existed))
}
fn append_handle(path: &Path) -> Result<std::fs::File> {
let mut open = OpenOptions::new();
open.append(true).create(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
open.custom_flags(libc::O_NOFOLLOW | libc::O_CLOEXEC);
}
open.open(path).map_err(|e| FsError::io(path, e))
}