use super::{CreateMode, CreateOptions};
use crate::document::{DocumentError, DocumentResult};
use std::fs;
use std::path::Path;
pub(super) fn guard_mutation(path: &Path, operation: &str) -> DocumentResult<fs::Metadata> {
let metadata = fs::symlink_metadata(path).map_err(|error| DocumentError::IoError {
detail: format!("{operation} preflight `{}`: {error}", path.display()),
})?;
if metadata.file_type().is_symlink() {
return Err(DocumentError::UnsupportedOperation {
format: "filesystem".to_string(),
operation: operation.to_string(),
detail: format!("refusing to mutate symlink `{}`", path.display()),
});
}
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
if metadata.nlink() > 1 {
return Err(DocumentError::UnsupportedOperation {
format: "filesystem".to_string(),
operation: operation.to_string(),
detail: format!("refusing to mutate hardlinked file `{}`", path.display()),
});
}
}
Ok(metadata)
}
fn document_io_error(operation: &str, error: crate::atomic_file::AtomicError) -> DocumentError {
DocumentError::IoError {
detail: format!("{operation} {error}"),
}
}
pub(super) fn write_atomic(path: &Path, bytes: &[u8], operation: &str) -> DocumentResult<()> {
let metadata = guard_mutation(path, operation)?;
crate::atomic_file::install(
path,
crate::atomic_file::AtomicInstall::replacing(bytes)
.with_permissions(Some(metadata.permissions())),
)
.map_err(|error| document_io_error(operation, error))
}
pub(super) fn write_atomic_create(
path: &Path,
bytes: &[u8],
options: CreateOptions,
) -> DocumentResult<()> {
let operation = "create";
let mut existing_permissions = None;
match fs::symlink_metadata(path) {
Ok(_) => match options.mode {
CreateMode::NewOnly => {
return Err(DocumentError::AlreadyExists {
path: path.display().to_string(),
});
}
CreateMode::Replace => {
let metadata = guard_mutation(path, operation)?;
existing_permissions = Some(metadata.permissions());
}
},
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Err(error) => {
return Err(DocumentError::IoError {
detail: format!("create preflight `{}`: {error}", path.display()),
});
}
}
let unix_mode = options.effective_unix_mode(existing_permissions.is_some());
let preserved = unix_mode
.is_none()
.then(|| existing_permissions.clone())
.flatten();
let mut request = crate::atomic_file::AtomicInstall::replacing(bytes)
.with_permissions(preserved)
.with_unix_mode(unix_mode);
if options.mode == CreateMode::NewOnly {
request = request.new_only();
}
crate::atomic_file::install(path, request).map_err(|error| {
if error.target_exists() {
return DocumentError::AlreadyExists {
path: path.display().to_string(),
};
}
document_io_error(operation, error)
})
}