use std::path::Path;
pub const OWNER_ONLY_FILE_MODE: u32 = 0o600;
pub fn create_staging_file(
parent: &Path,
prefix: &str,
suffix: &str,
) -> std::io::Result<tempfile::NamedTempFile> {
for (label, part) in [("prefix", prefix), ("suffix", suffix)] {
if part.contains(['/', '\\']) {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("staging file {label} must not contain a path separator: {part:?}"),
));
}
}
let mut builder = tempfile::Builder::new();
builder.prefix(prefix).suffix(suffix);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt as _;
builder.permissions(std::fs::Permissions::from_mode(OWNER_ONLY_FILE_MODE));
}
builder.tempfile_in(parent)
}
pub fn sync_dir(dir: &Path) -> std::io::Result<()> {
#[cfg(unix)]
{
match std::fs::File::open(dir)?.sync_all() {
Err(err) if err.kind() == std::io::ErrorKind::InvalidInput => Ok(()),
other => other,
}
}
#[cfg(not(unix))]
{
let _ = dir;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_staging_file_lands_in_the_parent_it_was_given() {
let dir = tempfile::tempdir().unwrap();
let tmp = create_staging_file(dir.path(), "kv", ".tmp").unwrap();
assert_eq!(tmp.path().parent(), Some(dir.path()));
}
#[test]
fn the_name_carries_the_prefix_and_the_suffix() {
let dir = tempfile::tempdir().unwrap();
let tmp = create_staging_file(dir.path(), "barks", ".tmp").unwrap();
let name = tmp.path().file_name().unwrap().to_str().unwrap();
assert!(name.starts_with("barks"), "{name}");
assert!(name.ends_with(".tmp"), "{name}");
assert!(name.len() > "barks.tmp".len(), "no unique middle: {name}");
}
#[test]
fn a_path_separator_is_refused_in_either_argument() {
let dir = tempfile::tempdir().unwrap();
for (prefix, suffix) in [
("../escape", ".tmp"),
("kv", "/etc/passwd"),
("..\\escape", ".tmp"),
("kv", "\\tmp"),
] {
let err = create_staging_file(dir.path(), prefix, suffix)
.expect_err("a separator must not reach tempfile");
assert_eq!(
err.kind(),
std::io::ErrorKind::InvalidInput,
"{prefix:?} {suffix:?}: {err:?}"
);
}
}
#[test]
fn sync_dir_accepts_a_real_directory() {
let dir = tempfile::tempdir().unwrap();
sync_dir(dir.path()).unwrap();
}
#[test]
#[cfg(unix)]
fn sync_dir_reports_a_directory_that_is_not_there() {
let dir = tempfile::tempdir().unwrap();
let missing = dir.path().join("never-created");
let err = sync_dir(&missing).unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::NotFound, "{err:?}");
}
}