use super::*;
use crate::test_support::{fixture, remove};
use std::path::PathBuf;
#[cfg(unix)]
fn mode_of(path: &Path) -> u32 {
use std::os::unix::fs::PermissionsExt;
return std::fs::symlink_metadata(path)
.expect("metadata")
.permissions()
.mode()
& 0o7777;
}
#[test]
fn a_private_file_is_created_once_with_its_exact_bytes() {
let root: PathBuf = fixture("private-write");
let path: PathBuf = root.join("record.json");
write_private_file(path.as_path(), b"{\"a\":1}\n").expect("first write");
assert_eq!(std::fs::read(&path).expect("read back"), b"{\"a\":1}\n");
#[cfg(unix)]
assert_eq!(mode_of(path.as_path()), 0o600);
let second: std::io::Error =
write_private_file(path.as_path(), b"other").expect_err("second write must fail");
assert_eq!(second.kind(), std::io::ErrorKind::AlreadyExists);
assert_eq!(std::fs::read(&path).expect("unchanged"), b"{\"a\":1}\n");
remove(root.as_path());
}
#[cfg(unix)]
#[test]
fn writers_refuse_a_planted_link() {
let root: PathBuf = fixture("private-link-write");
let target: PathBuf = root.join("target");
let link: PathBuf = root.join("record.json");
std::os::unix::fs::symlink(&target, &link).expect("plant link");
assert!(write_private_file(link.as_path(), b"x").is_err());
assert!(write_exclusive_synced(link.as_path(), b"x").is_err());
assert!(!target.exists(), "the link target must not be created");
remove(root.as_path());
}
#[test]
fn the_synced_writer_creates_an_exclusive_file() {
let root: PathBuf = fixture("private-synced");
let path: PathBuf = root.join("owner.json");
write_exclusive_synced(path.as_path(), b"abc").expect("write");
assert_eq!(std::fs::read(&path).expect("read"), b"abc");
#[cfg(unix)]
assert_eq!(mode_of(path.as_path()) & 0o077, 0, "no group or other bits");
assert!(write_exclusive_synced(path.as_path(), b"abc").is_err());
remove(root.as_path());
}
#[test]
fn a_private_directory_is_owner_only() {
let root: PathBuf = fixture("private-directory");
let directory: PathBuf = root.join("lock");
create_private_directory(directory.as_path()).expect("create");
assert!(directory.is_dir());
#[cfg(unix)]
assert_eq!(mode_of(directory.as_path()) & 0o077, 0);
assert!(
create_private_directory(directory.as_path()).is_err(),
"exists already"
);
assert!(
create_private_directory(root.join("a/b").as_path()).is_err(),
"no parent"
);
remove(root.as_path());
}
#[cfg(unix)]
#[test]
fn protection_sets_the_requested_mode() {
let root: PathBuf = fixture("private-protect");
let path: PathBuf = root.join("file");
std::fs::write(&path, b"x").expect("write");
protect_path(path.as_path(), 0o640).expect("protect");
assert_eq!(mode_of(path.as_path()), 0o640);
protect_path(path.as_path(), PRIVATE_FILE_MODE).expect("protect again");
assert_eq!(mode_of(path.as_path()), 0o600);
assert!(protect_path(root.join("missing").as_path(), 0o600).is_err());
remove(root.as_path());
}
#[test]
fn directory_sync_needs_the_directory() {
let root: PathBuf = fixture("private-sync");
sync_directory(root.as_path()).expect("sync");
#[cfg(unix)]
assert!(sync_directory(root.join("missing").as_path()).is_err());
remove(root.as_path());
}
#[test]
fn tree_removal_tolerates_an_absent_tree() {
let root: PathBuf = fixture("private-remove");
let tree: PathBuf = root.join("tree");
std::fs::create_dir_all(tree.join("inner")).expect("tree");
std::fs::write(tree.join("inner/file"), b"x").expect("file");
remove_tree(tree.as_path()).expect("remove");
assert!(!tree.exists());
remove_tree(tree.as_path()).expect("already absent");
remove(root.as_path());
}
#[cfg(unix)]
#[test]
fn tree_removal_reports_other_errors() {
let root: PathBuf = fixture("private-remove-error");
let file: PathBuf = root.join("file");
std::fs::write(&file, b"x").expect("file");
assert!(remove_tree(file.as_path()).is_err(), "a file is not a tree");
remove(root.as_path());
}
#[test]
fn reads_refuse_unsafe_and_oversized_paths() {
let root: PathBuf = fixture("private-read");
let path: PathBuf = root.join("record");
std::fs::write(&path, b"12345").expect("write");
assert_eq!(
read_regular_file(path.as_path(), 5).expect("at the limit"),
b"12345"
);
assert!(matches!(
read_regular_file(path.as_path(), 4),
Err(ReadRefusal::TooLarge)
));
assert!(matches!(
read_regular_file(root.join("missing").as_path(), 5),
Err(ReadRefusal::Missing)
));
assert!(matches!(
read_regular_file(root.as_path(), 5),
Err(ReadRefusal::NotRegular)
));
#[cfg(unix)]
{
let link: PathBuf = root.join("link");
std::os::unix::fs::symlink(&path, &link).expect("link");
assert!(matches!(
read_regular_file(link.as_path(), 5),
Err(ReadRefusal::NotRegular)
));
assert!(matches!(
read_regular_file(path.join("inner").as_path(), 5),
Err(ReadRefusal::Missing)
));
}
remove(root.as_path());
}
#[cfg(unix)]
#[test]
fn an_unreadable_file_is_an_io_refusal() {
use std::os::unix::fs::PermissionsExt;
let root: PathBuf = fixture("private-unreadable");
let path: PathBuf = root.join("record");
std::fs::write(&path, b"x").expect("write");
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o000)).expect("chmod");
if std::fs::read(&path).is_err() {
assert!(matches!(
read_regular_file(path.as_path(), 5),
Err(ReadRefusal::Io(_))
));
}
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600)).expect("restore");
remove(root.as_path());
}
#[test]
fn a_file_below_the_limit_is_read_whole() {
let root: PathBuf = fixture("private-read-small");
let path: PathBuf = root.join("record");
std::fs::write(&path, b"abc").expect("write");
assert_eq!(
read_regular_file(path.as_path(), 1024).expect("read"),
b"abc"
);
remove(root.as_path());
}
#[cfg(target_os = "linux")]
#[test]
fn the_read_is_capped_beyond_the_reported_size() {
let path: &Path = Path::new("/proc/self/stat");
assert_eq!(std::fs::metadata(path).expect("metadata").len(), 0);
assert!(matches!(
read_regular_file(path, 10),
Err(ReadRefusal::TooLarge)
));
let whole: Vec<u8> = read_regular_file(path, 4096).expect("whole record");
assert!(whole.len() > 10);
}