easy_fuser 0.7.1

A flexible and idiomatic Fuse implementation for Rust
Documentation
use super::*;
use std::os::unix::fs::MetadataExt;

#[test]
fn lookup_prefers_upper_then_ordered_lowers_and_merges_directories() {
    let temp = tempfile::tempdir().unwrap();
    let upper = temp.path().join("upper");
    let high = temp.path().join("high");
    let low = temp.path().join("low");
    fs::create_dir_all(upper.join("dir")).unwrap();
    fs::create_dir_all(high.join("dir")).unwrap();
    fs::create_dir_all(low.join("dir")).unwrap();
    fs::write(upper.join("dir/upper-only"), b"upper").unwrap();
    fs::write(upper.join("dir/shared"), b"upper").unwrap();
    fs::write(high.join("dir/shared"), b"high").unwrap();
    fs::write(low.join("dir/shared"), b"low").unwrap();
    fs::write(high.join("dir/layer-priority"), b"high").unwrap();
    fs::write(low.join("dir/layer-priority"), b"low").unwrap();
    fs::write(low.join("dir/low-only"), b"low").unwrap();
    let overlay = OverlayFs::new(upper.as_path(), [high.as_path(), low.as_path()]).unwrap();

    let shared = overlay.resolve(Path::new("dir/shared")).unwrap().unwrap();
    assert_eq!(shared.path, upper.join("dir/shared"));
    assert_eq!(fs::read(shared.path).unwrap(), b"upper");
    let prioritized = overlay
        .resolve(Path::new("dir/layer-priority"))
        .unwrap()
        .unwrap();
    assert_eq!(prioritized.path, high.join("dir/layer-priority"));
    let entries = overlay.readdir_entries(Path::new("dir")).unwrap();
    let names = entries
        .into_iter()
        .map(|(name, _)| name)
        .collect::<HashSet<_>>();
    assert!(names.contains(OsStr::new("upper-only")));
    assert!(names.contains(OsStr::new("shared")));
    assert!(names.contains(OsStr::new("layer-priority")));
    assert!(names.contains(OsStr::new("low-only")));
}

#[test]
fn copy_up_changes_only_the_upper_layer() {
    let temp = tempfile::tempdir().unwrap();
    let upper = temp.path().join("upper");
    let lower = temp.path().join("lower");
    fs::create_dir(&upper).unwrap();
    fs::create_dir(&lower).unwrap();
    fs::write(lower.join("config"), b"base").unwrap();
    let overlay = OverlayFs::new(upper.as_path(), [lower.as_path()]).unwrap();

    let copied = overlay.copy_up(Path::new("config")).unwrap();
    fs::write(&copied, b"changed").unwrap();

    assert_eq!(fs::read(&copied).unwrap(), b"changed");
    assert_eq!(fs::read(lower.join("config")).unwrap(), b"base");
}

#[test]
fn linking_a_lower_file_copies_it_up_and_creates_an_upper_hard_link() {
    let temp = tempfile::tempdir().unwrap();
    let upper = temp.path().join("upper");
    let lower = temp.path().join("lower");
    fs::create_dir(&upper).unwrap();
    fs::create_dir(&lower).unwrap();
    fs::write(lower.join("source"), b"value").unwrap();
    let overlay = OverlayFs::new(upper.as_path(), [lower.as_path()]).unwrap();

    overlay
        .link_path(Path::new("source"), Path::new("linked"))
        .unwrap();

    let source = fs::metadata(upper.join("source")).unwrap();
    let linked = fs::metadata(upper.join("linked")).unwrap();
    assert_eq!(source.ino(), linked.ino());
    assert_eq!(fs::read(lower.join("source")).unwrap(), b"value");
}

#[test]
fn unlink_persists_a_whiteout_across_reopen() {
    let temp = tempfile::tempdir().unwrap();
    let upper = temp.path().join("upper");
    let lower = temp.path().join("lower");
    fs::create_dir(&upper).unwrap();
    fs::create_dir(&lower).unwrap();
    fs::write(lower.join("removed"), b"lower").unwrap();

    {
        let overlay = OverlayFs::new(upper.as_path(), [lower.as_path()]).unwrap();
        overlay.unlink_path(Path::new("removed")).unwrap();
        assert!(overlay.resolve(Path::new("removed")).unwrap().is_none());
    }

    let reopened = OverlayFs::new(upper.as_path(), [lower.as_path()]).unwrap();
    assert!(reopened.resolve(Path::new("removed")).unwrap().is_none());
    assert!(lower.join("removed").exists());
}

#[test]
fn renaming_a_lower_file_copies_it_up_and_hides_the_old_name() {
    let temp = tempfile::tempdir().unwrap();
    let upper = temp.path().join("upper");
    let lower = temp.path().join("lower");
    fs::create_dir(&upper).unwrap();
    fs::create_dir(&lower).unwrap();
    fs::write(lower.join("old"), b"value").unwrap();
    let overlay = OverlayFs::new(upper.as_path(), [lower.as_path()]).unwrap();

    overlay
        .rename_path(Path::new("old"), Path::new("new"), RenameFlags::empty())
        .unwrap();

    assert!(overlay.resolve(Path::new("old")).unwrap().is_none());
    assert_eq!(fs::read(upper.join("new")).unwrap(), b"value");
    assert_eq!(fs::read(lower.join("old")).unwrap(), b"value");
}

#[test]
fn upper_directory_over_a_lower_file_can_be_renamed() {
    let temp = tempfile::tempdir().unwrap();
    let upper = temp.path().join("upper");
    let lower = temp.path().join("lower");
    fs::create_dir_all(upper.join("entry")).unwrap();
    fs::create_dir(&lower).unwrap();
    fs::write(lower.join("entry"), b"lower file").unwrap();
    let overlay = OverlayFs::new(upper.as_path(), [lower.as_path()]).unwrap();

    overlay
        .rename_path(Path::new("entry"), Path::new("moved"), RenameFlags::empty())
        .unwrap();

    assert!(upper.join("moved").is_dir());
    assert!(overlay.resolve(Path::new("entry")).unwrap().is_none());
    assert_eq!(fs::read(lower.join("entry")).unwrap(), b"lower file");
}

#[test]
fn paths_cannot_escape_the_overlay_root() {
    let temp = tempfile::tempdir().unwrap();
    let upper = temp.path().join("upper");
    fs::create_dir(&upper).unwrap();
    let overlay = OverlayFs::new(upper.as_path(), std::iter::empty::<&Path>()).unwrap();

    assert!(overlay.normalize_path(Path::new("../outside")).is_err());
    assert!(overlay.normalize_path(Path::new("/outside")).is_err());
    assert!(overlay.normalize_path(Path::new(CONTROL_DIR)).is_err());
    assert!(
        overlay
            .normalize_path(Path::new(".easy_fuser_overlay_tmp.entry"))
            .is_err()
    );
}