use std::path::{Path, PathBuf};
use std::sync::Once;
use libpfs3::ondisk::*;
use libpfs3::volume::Volume;
fn fixtures_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
}
fn open_small() -> Volume {
Volume::open_rdb(&fixtures_dir().join("small.hdf")).unwrap()
}
static EXTRACT_PFS: Once = Once::new();
fn open_pfs() -> Volume {
let hdf = fixtures_dir().join("pfs.hdf");
EXTRACT_PFS.call_once(|| {
if !hdf.exists() {
let archive = fixtures_dir().join("pfs.7z");
assert!(archive.exists(), "pfs.7z fixture missing");
sevenz_rust::decompress_file(&archive, &fixtures_dir())
.expect("failed to extract pfs.7z");
}
});
Volume::open_rdb(&hdf).unwrap()
}
macro_rules! tests_for_image {
($mod_name:ident, $opener:ident) => {
mod $mod_name {
use super::*;
#[test]
fn open_and_validate() {
let vol = $opener();
assert_eq!(vol.rootblock.disktype, ID_PFS_DISK);
assert_eq!(vol.block_size(), 512);
assert_eq!(vol.rootblock.reserved_blksize, 1024);
assert!(vol.rootblock.is_splitted_anodes());
assert!(vol.rootblock.has_extension());
assert!(vol.rootblock_ext.is_some());
assert!(!vol.name().is_empty());
assert!(vol.total_blocks() > 0);
assert!(vol.free_blocks() > 0);
assert!(vol.free_blocks() < vol.total_blocks());
assert!(vol.rootblock.firstreserved < vol.rootblock.lastreserved);
}
#[test]
fn flags_string_nonempty() {
let vol = $opener();
let flags = vol.rootblock.flags_string();
assert!(flags.contains("HARDDISK"));
assert!(flags.contains("SPLITTED_ANODES"));
}
#[test]
fn list_root_nonempty() {
let mut vol = $opener();
let entries = vol.list_dir("/").unwrap();
assert!(!entries.is_empty());
for e in &entries {
assert!(!e.name.is_empty());
}
}
#[test]
fn root_has_files_and_dirs() {
let mut vol = $opener();
let entries = vol.list_dir("/").unwrap();
assert!(entries.iter().any(|e| e.is_file()));
assert!(entries.iter().any(|e| e.is_dir()));
}
#[test]
fn list_first_subdir() {
let mut vol = $opener();
let entries = vol.list_dir("/").unwrap();
for dir in entries.iter().filter(|e| e.is_dir()) {
let sub = vol.list_dir(&dir.name).unwrap();
if sub.iter().any(|e| e.is_file()) {
return; }
}
panic!("no subdirectory with files found");
}
#[test]
fn file_entry_properties() {
let mut vol = $opener();
let entries = vol.list_dir("/").unwrap();
let file = entries.iter().find(|e| e.is_file()).unwrap();
assert!(file.entry_type < 0);
assert!(file.file_size() > 0);
assert!(file.creation_day > 0);
}
#[test]
fn dir_entry_properties() {
let mut vol = $opener();
let entries = vol.list_dir("/").unwrap();
let dir = entries.iter().find(|e| e.is_dir()).unwrap();
assert_eq!(dir.entry_type, ST_USERDIR);
}
#[test]
fn lookup_root_is_none() {
let mut vol = $opener();
assert!(vol.lookup("/").unwrap().is_none());
}
#[test]
fn lookup_first_file() {
let mut vol = $opener();
let entries = vol.list_dir("/").unwrap();
let file = entries.iter().find(|e| e.is_file()).unwrap();
let found = vol.lookup(&file.name).unwrap().unwrap();
assert_eq!(found.name, file.name);
assert_eq!(found.file_size(), file.file_size());
}
#[test]
fn lookup_case_insensitive() {
let mut vol = $opener();
let entries = vol.list_dir("/").unwrap();
let file = entries.iter().find(|e| e.is_file()).unwrap();
let upper = file.name.to_ascii_uppercase();
let found = vol.lookup(&upper).unwrap().unwrap();
assert_eq!(found.name, file.name);
}
#[test]
fn lookup_nonexistent() {
let mut vol = $opener();
assert!(vol.lookup("__nonexistent_file_42__").is_err());
}
#[test]
fn lookup_nested_file() {
let mut vol = $opener();
let entries = vol.list_dir("/").unwrap();
for dir in entries.iter().filter(|e| e.is_dir()) {
let sub = vol.list_dir(&dir.name).unwrap();
if let Some(file) = sub.iter().find(|e| e.is_file()) {
let path = format!("{}/{}", dir.name, file.name);
let found = vol.lookup(&path).unwrap().unwrap();
assert_eq!(found.name, file.name);
return;
}
}
panic!("no nested file found");
}
#[test]
fn read_first_file_size_matches() {
let mut vol = $opener();
let entries = vol.list_dir("/").unwrap();
let file = entries.iter().find(|e| e.is_file()).unwrap();
let data = vol.read_file(&file.name).unwrap();
assert_eq!(data.len() as u64, file.file_size());
}
#[test]
fn read_nested_file() {
let mut vol = $opener();
let entries = vol.list_dir("/").unwrap();
for dir in entries.iter().filter(|e| e.is_dir()) {
let sub = vol.list_dir(&dir.name).unwrap();
if let Some(file) = sub.iter().find(|e| e.is_file()) {
let path = format!("{}/{}", dir.name, file.name);
let data = vol.read_file(&path).unwrap();
assert_eq!(data.len() as u64, file.file_size());
return;
}
}
panic!("no nested file found");
}
#[test]
fn anode_chain_rootdir() {
let mut vol = $opener();
let blocks = vol.validate_anode_chain(ANODE_ROOTDIR).unwrap();
assert!(!blocks.is_empty());
}
#[test]
fn anode_chain_matches_file_size() {
let mut vol = $opener();
let entries = vol.list_dir("/").unwrap();
let file = entries.iter().find(|e| e.is_file()).unwrap();
let blocks = vol.validate_anode_chain(file.anode).unwrap();
let expected = (file.file_size() + 511) / 512;
assert_eq!(blocks.len() as u64, expected);
}
#[test]
fn read_dir_as_file_fails() {
let mut vol = $opener();
let entries = vol.list_dir("/").unwrap();
let dir = entries.iter().find(|e| e.is_dir()).unwrap();
assert!(vol.read_file(&dir.name).is_err());
}
#[test]
fn list_file_as_dir_fails() {
let mut vol = $opener();
let entries = vol.list_dir("/").unwrap();
let file = entries.iter().find(|e| e.is_file()).unwrap();
assert!(vol.list_dir(&file.name).is_err());
}
}
};
}
tests_for_image!(small, open_small);
tests_for_image!(pfs_real, open_pfs);
mod small_exact {
use super::*;
#[test]
fn volume_name() {
assert_eq!(open_small().name(), "TestVol");
}
#[test]
fn total_blocks() {
assert_eq!(open_small().total_blocks(), 512);
}
#[test]
fn root_entry_count() {
assert_eq!(open_small().list_dir("/").unwrap().len(), 3);
}
#[test]
fn hello_content() {
assert_eq!(
open_small().read_file("hello.txt").unwrap(),
b"Hello from PFS3!\n"
);
}
#[test]
fn binary_content() {
let data = open_small().read_file("test.bin").unwrap();
let expected: Vec<u8> = (0..=255u8).cycle().take(1024).collect();
assert_eq!(data, expected);
}
#[test]
fn nested_content() {
assert_eq!(
open_small().read_file("SubDir/nested.txt").unwrap(),
b"Nested file content.\n"
);
}
}
mod pfs_exact {
use super::*;
#[test]
fn volume_name() {
assert_eq!(open_pfs().name(), "PFS3AIO Volume");
}
#[test]
fn total_blocks() {
assert_eq!(open_pfs().total_blocks(), 15876);
}
#[test]
fn has_deldir() {
assert!(open_pfs().rootblock.has_flag(MODE_DELDIR));
}
#[test]
fn root_entry_count() {
assert_eq!(open_pfs().list_dir("/").unwrap().len(), 13);
}
#[test]
fn libs_count() {
assert_eq!(open_pfs().list_dir("Libs").unwrap().len(), 8);
}
#[test]
fn startup_sequence_content() {
let data = open_pfs().read_file("S/Startup-Sequence").unwrap();
assert!(String::from_utf8_lossy(&data).contains("xSysInfo"));
}
#[test]
fn library_size() {
let e = open_pfs().lookup("Libs/68060.library").unwrap().unwrap();
assert_eq!(e.file_size(), 65476);
}
#[test]
fn sysinfo_guide_size() {
let e = open_pfs().lookup("SysInfo/SysInfo.guide").unwrap().unwrap();
assert_eq!(e.file_size(), 39019);
}
}
#[test]
fn latin1_ascii() {
assert_eq!(libpfs3::util::latin1_to_string(b"hello"), "hello");
}
#[test]
fn latin1_umlaut() {
assert_eq!(libpfs3::util::latin1_to_string(b"\xe4\xf6\xfc"), "äöü");
}
#[test]
fn name_eq_ci() {
assert!(libpfs3::util::name_eq_ci("Hello", "hello"));
assert!(!libpfs3::util::name_eq_ci("foo", "bar"));
assert!(!libpfs3::util::name_eq_ci("foo", "fooo"));
}
#[test]
fn amiga_epoch() {
use std::time::UNIX_EPOCH;
assert_eq!(
libpfs3::util::amiga_to_systime(0, 0, 0)
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),
2922 * 86400
);
}
#[test]
fn protection_bits() {
assert_eq!(
libpfs3::util::amiga_protection_to_mode(0, false) & 0o777,
0o777
);
assert_eq!(
libpfs3::util::amiga_protection_to_mode(0x0F, false) & 0o777,
0o000
);
}
#[test]
fn open_nonexistent() {
assert!(Volume::open_rdb(Path::new("/nonexistent.hdf")).is_err());
}
mod mkfs {
use super::*;
use libpfs3::format::{self, FormatOptions};
use libpfs3::io::FileBlockDevice;
use std::sync::atomic::{AtomicU32, Ordering};
static COUNTER: AtomicU32 = AtomicU32::new(0);
fn temp_image(size_blocks: u64) -> (PathBuf, FileBlockDevice) {
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir().join(format!("pfs3_test_mkfs_{}.img", n));
let dev = FileBlockDevice::create(&path, 512, size_blocks).unwrap();
(path, dev)
}
#[test]
fn format_and_open() {
let (path, dev) = temp_image(8192); let opts = FormatOptions {
volume_name: "TestFmt".into(),
enable_deldir: false,
};
let result = format::format_with_size(&dev, 8192, &opts).unwrap();
assert_eq!(result.volume_name, "TestFmt");
assert_eq!(result.total_blocks, 8192);
assert!(result.data_blocks > 0);
drop(dev);
let vol = Volume::open(&path, 0).unwrap();
assert_eq!(vol.name(), "TestFmt");
assert_eq!(vol.rootblock.disktype, ID_PFS_DISK);
assert_eq!(vol.total_blocks(), 8192);
std::fs::remove_file(&path).ok();
}
#[test]
fn format_empty_root() {
let (path, dev) = temp_image(4096); let opts = FormatOptions {
volume_name: "Empty".into(),
enable_deldir: false,
};
format::format_with_size(&dev, 4096, &opts).unwrap();
drop(dev);
let mut vol = Volume::open(&path, 0).unwrap();
let entries = vol.list_dir("/").unwrap();
assert!(entries.is_empty());
std::fs::remove_file(&path).ok();
}
#[test]
fn format_rootblock_flags() {
let (path, dev) = temp_image(8192);
let opts = FormatOptions {
volume_name: "Flags".into(),
enable_deldir: false,
};
format::format_with_size(&dev, 8192, &opts).unwrap();
drop(dev);
let vol = Volume::open(&path, 0).unwrap();
assert!(vol.rootblock.has_flag(MODE_HARDDISK));
assert!(vol.rootblock.has_flag(MODE_SPLITTED_ANODES));
assert!(vol.rootblock.has_flag(MODE_EXTENSION));
assert!(vol.rootblock.has_flag(MODE_LONGFN));
assert!(vol.rootblock.has_flag(MODE_DATESTAMP));
assert!(vol.rootblock_ext.is_some());
std::fs::remove_file(&path).ok();
}
#[test]
fn format_check_passes() {
let (path, dev) = temp_image(8192);
let opts = FormatOptions {
volume_name: "Check".into(),
enable_deldir: false,
};
format::format_with_size(&dev, 8192, &opts).unwrap();
drop(dev);
let mut vol = Volume::open(&path, 0).unwrap();
let blocks = vol.validate_anode_chain(ANODE_ROOTDIR).unwrap();
assert!(!blocks.is_empty());
assert!(vol.free_blocks() > 0);
assert!(vol.free_blocks() < vol.total_blocks());
std::fs::remove_file(&path).ok();
}
#[test]
fn format_various_sizes() {
for &blocks in &[256u64, 1024, 4096, 16384] {
let (path, dev) = temp_image(blocks);
let opts = FormatOptions {
volume_name: "Size".into(),
enable_deldir: false,
};
let result = format::format_with_size(&dev, blocks, &opts).unwrap();
assert_eq!(result.total_blocks, blocks);
assert!(result.data_blocks > 0);
drop(dev);
let vol = Volume::open(&path, 0).unwrap();
assert_eq!(vol.total_blocks() as u64, blocks);
std::fs::remove_file(&path).ok();
}
}
}
mod writer_tests {
use super::*;
use libpfs3::format::{self, FormatOptions};
use libpfs3::io::FileBlockDevice;
use libpfs3::writer::Writer;
use std::sync::atomic::{AtomicU32, Ordering};
static COUNTER: AtomicU32 = AtomicU32::new(100);
fn fresh_volume(blocks: u64) -> (PathBuf, Writer) {
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir().join(format!("pfs3_test_writer_{}.img", n));
let dev = FileBlockDevice::create(&path, 512, blocks).unwrap();
let opts = FormatOptions {
volume_name: "WrTest".into(),
enable_deldir: false,
};
format::format_with_size(&dev, blocks, &opts).unwrap();
drop(dev);
let dev = FileBlockDevice::open_rw(&path, 512, 0, 0).unwrap();
let vol = Volume::from_device(Box::new(dev)).unwrap();
let w = Writer::open(vol).unwrap();
(path, w)
}
fn reopen(path: &Path) -> Volume {
Volume::open(path, 0).unwrap()
}
#[test]
fn write_and_read_file() {
let (path, mut w) = fresh_volume(4096);
w.write_file("test.txt", b"Hello PFS3!").unwrap();
let vol = w.into_volume();
drop(vol);
let mut vol = reopen(&path);
assert_eq!(vol.read_file("test.txt").unwrap(), b"Hello PFS3!");
std::fs::remove_file(&path).ok();
}
#[test]
fn write_binary_file() {
let (path, mut w) = fresh_volume(4096);
let data: Vec<u8> = (0..=255u8).cycle().take(2048).collect();
w.write_file("binary.dat", &data).unwrap();
drop(w);
let mut vol = reopen(&path);
assert_eq!(vol.read_file("binary.dat").unwrap(), data);
std::fs::remove_file(&path).ok();
}
#[test]
fn create_dir_and_write_nested() {
let (path, mut w) = fresh_volume(4096);
w.create_dir("SubDir").unwrap();
w.write_file("SubDir/inner.txt", b"nested!").unwrap();
drop(w);
let mut vol = reopen(&path);
let entries = vol.list_dir("SubDir").unwrap();
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].name, "inner.txt");
assert_eq!(vol.read_file("SubDir/inner.txt").unwrap(), b"nested!");
std::fs::remove_file(&path).ok();
}
#[test]
fn delete_file() {
let (path, mut w) = fresh_volume(4096);
w.write_file("a.txt", b"aaa").unwrap();
w.write_file("b.txt", b"bbb").unwrap();
w.delete("a.txt").unwrap();
drop(w);
let mut vol = reopen(&path);
let entries = vol.list_dir("/").unwrap();
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].name, "b.txt");
assert_eq!(vol.read_file("b.txt").unwrap(), b"bbb");
std::fs::remove_file(&path).ok();
}
#[test]
fn delete_empty_dir() {
let (path, mut w) = fresh_volume(4096);
w.create_dir("EmptyDir").unwrap();
w.delete("EmptyDir").unwrap();
drop(w);
let mut vol = reopen(&path);
assert!(vol.list_dir("/").unwrap().is_empty());
std::fs::remove_file(&path).ok();
}
#[test]
fn multiple_files() {
let (path, mut w) = fresh_volume(8192);
for i in 0..10 {
w.write_file(
&format!("file_{}.txt", i),
format!("content {}", i).as_bytes(),
)
.unwrap();
}
drop(w);
let mut vol = reopen(&path);
let entries = vol.list_dir("/").unwrap();
assert_eq!(entries.len(), 10);
for i in 0..10 {
let data = vol.read_file(&format!("file_{}.txt", i)).unwrap();
assert_eq!(String::from_utf8_lossy(&data), format!("content {}", i));
}
std::fs::remove_file(&path).ok();
}
#[test]
fn check_after_writes() {
let (path, mut w) = fresh_volume(4096);
w.write_file("test.txt", b"data").unwrap();
w.create_dir("Dir").unwrap();
w.write_file("Dir/sub.txt", b"sub").unwrap();
drop(w);
let mut vol = reopen(&path);
let entries = vol.list_dir("/").unwrap();
for e in &entries {
vol.validate_anode_chain(e.anode).unwrap();
}
assert!(vol.free_blocks() > 0);
std::fs::remove_file(&path).ok();
}
}
mod overwrite_tests {
use super::*;
use libpfs3::format::{self, FormatOptions};
use libpfs3::io::FileBlockDevice;
use libpfs3::writer::Writer;
use std::sync::atomic::{AtomicU32, Ordering};
static COUNTER: AtomicU32 = AtomicU32::new(300);
fn fresh_writer(blocks: u64) -> (PathBuf, Writer) {
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir().join(format!("pfs3_test_overwrite_{}.img", n));
let dev = FileBlockDevice::create(&path, 512, blocks).unwrap();
format::format_with_size(
&dev,
blocks,
&FormatOptions {
volume_name: "OvrTest".into(),
enable_deldir: false,
},
)
.unwrap();
drop(dev);
let dev = FileBlockDevice::open_rw(&path, 512, 0, 0).unwrap();
let vol = Volume::from_device(Box::new(dev)).unwrap();
(path, Writer::open(vol).unwrap())
}
fn reopen(path: &Path) -> Volume {
Volume::open(path, 0).unwrap()
}
#[test]
fn overwrite_same_size() {
let (path, mut w) = fresh_writer(4096);
w.write_file("test.txt", b"hello world!").unwrap();
let anode = w
.vol
.list_dir_by_anode(ANODE_ROOTDIR)
.unwrap()
.iter()
.find(|e| e.name == "test.txt")
.unwrap()
.anode;
w.overwrite_file_in(ANODE_ROOTDIR, "test.txt", anode, b"HELLO WORLD!")
.unwrap();
drop(w);
let mut vol = reopen(&path);
assert_eq!(vol.read_file("test.txt").unwrap(), b"HELLO WORLD!");
assert_eq!(vol.lookup("test.txt").unwrap().unwrap().file_size(), 12);
std::fs::remove_file(&path).ok();
}
#[test]
fn overwrite_grow() {
let (path, mut w) = fresh_writer(4096);
w.write_file("test.txt", b"small").unwrap();
let anode = w
.vol
.list_dir_by_anode(ANODE_ROOTDIR)
.unwrap()
.iter()
.find(|e| e.name == "test.txt")
.unwrap()
.anode;
let big_data: Vec<u8> = (0..2048u16).flat_map(|i| i.to_le_bytes()).collect();
w.overwrite_file_in(ANODE_ROOTDIR, "test.txt", anode, &big_data)
.unwrap();
drop(w);
let mut vol = reopen(&path);
assert_eq!(vol.read_file("test.txt").unwrap(), big_data);
assert_eq!(
vol.lookup("test.txt").unwrap().unwrap().file_size(),
big_data.len() as u64
);
std::fs::remove_file(&path).ok();
}
#[test]
fn overwrite_shrink() {
let (path, mut w) = fresh_writer(4096);
let big_data: Vec<u8> = vec![0xAB; 2048];
w.write_file("test.txt", &big_data).unwrap();
let free_before = w.vol.rootblock.blocksfree;
let anode = w
.vol
.list_dir_by_anode(ANODE_ROOTDIR)
.unwrap()
.iter()
.find(|e| e.name == "test.txt")
.unwrap()
.anode;
w.overwrite_file_in(ANODE_ROOTDIR, "test.txt", anode, b"tiny")
.unwrap();
let free_after = w.vol.rootblock.blocksfree;
assert!(free_after > free_before);
drop(w);
let mut vol = reopen(&path);
assert_eq!(vol.read_file("test.txt").unwrap(), b"tiny");
assert_eq!(vol.lookup("test.txt").unwrap().unwrap().file_size(), 4);
std::fs::remove_file(&path).ok();
}
#[test]
fn overwrite_anode_stable() {
let (path, mut w) = fresh_writer(4096);
w.write_file("test.txt", b"original").unwrap();
let anode_before = w
.vol
.list_dir_by_anode(ANODE_ROOTDIR)
.unwrap()
.iter()
.find(|e| e.name == "test.txt")
.unwrap()
.anode;
w.overwrite_file_in(ANODE_ROOTDIR, "test.txt", anode_before, b"replaced!")
.unwrap();
let anode_after = w
.vol
.list_dir_by_anode(ANODE_ROOTDIR)
.unwrap()
.iter()
.find(|e| e.name == "test.txt")
.unwrap()
.anode;
assert_eq!(anode_before, anode_after);
drop(w);
let mut vol = reopen(&path);
assert_eq!(vol.read_file("test.txt").unwrap(), b"replaced!");
std::fs::remove_file(&path).ok();
}
#[test]
fn overwrite_multiple_times() {
let (path, mut w) = fresh_writer(4096);
w.write_file("test.txt", b"v1").unwrap();
let anode = w
.vol
.list_dir_by_anode(ANODE_ROOTDIR)
.unwrap()
.iter()
.find(|e| e.name == "test.txt")
.unwrap()
.anode;
for content in &[
&vec![0u8; 4096][..],
b"small" as &[u8],
&vec![0xFFu8; 1500],
&vec![0xFFu8; 1500],
] {
w.overwrite_file_in(ANODE_ROOTDIR, "test.txt", anode, content)
.unwrap();
}
drop(w);
let mut vol = reopen(&path);
let data = vol.read_file("test.txt").unwrap();
assert_eq!(data.len(), 1500);
assert!(data.iter().all(|&b| b == 0xFF));
vol.validate_anode_chain(anode).unwrap();
std::fs::remove_file(&path).ok();
}
#[test]
fn overwrite_check_clean() {
let (path, mut w) = fresh_writer(4096);
w.write_file("a.txt", b"aaa").unwrap();
w.write_file("b.txt", &vec![0u8; 2000]).unwrap();
let anode_a = w
.vol
.list_dir_by_anode(ANODE_ROOTDIR)
.unwrap()
.iter()
.find(|e| e.name == "a.txt")
.unwrap()
.anode;
let anode_b = w
.vol
.list_dir_by_anode(ANODE_ROOTDIR)
.unwrap()
.iter()
.find(|e| e.name == "b.txt")
.unwrap()
.anode;
w.overwrite_file_in(ANODE_ROOTDIR, "a.txt", anode_a, &vec![0xAA; 3000])
.unwrap();
w.overwrite_file_in(ANODE_ROOTDIR, "b.txt", anode_b, b"b")
.unwrap();
drop(w);
let mut vol = reopen(&path);
vol.validate_anode_chain(anode_a).unwrap();
vol.validate_anode_chain(anode_b).unwrap();
let bitmap_free = vol.bitmap_count_free().unwrap();
assert_eq!(bitmap_free, vol.free_blocks());
std::fs::remove_file(&path).ok();
}
}
mod link_tests {
use super::*;
use libpfs3::format::{self, FormatOptions};
use libpfs3::io::FileBlockDevice;
use libpfs3::writer::Writer;
use std::sync::atomic::{AtomicU32, Ordering};
static COUNTER: AtomicU32 = AtomicU32::new(200);
fn fresh_writer(blocks: u64) -> (PathBuf, Writer) {
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir().join(format!("pfs3_test_link_{}.img", n));
let dev = FileBlockDevice::create(&path, 512, blocks).unwrap();
format::format_with_size(
&dev,
blocks,
&FormatOptions {
volume_name: "LinkTest".into(),
enable_deldir: false,
},
)
.unwrap();
drop(dev);
let dev = FileBlockDevice::open_rw(&path, 512, 0, 0).unwrap();
let vol = Volume::from_device(Box::new(dev)).unwrap();
(path, Writer::open(vol).unwrap())
}
fn reopen(path: &Path) -> Volume {
Volume::open(path, 0).unwrap()
}
#[test]
fn create_and_read_softlink() {
let (path, mut w) = fresh_writer(4096);
w.create_softlink("mylink", "target/path").unwrap();
drop(w);
let mut vol = reopen(&path);
let entries = vol.list_dir("/").unwrap();
assert_eq!(entries.len(), 1);
assert!(entries[0].is_softlink());
assert_eq!(entries[0].name, "mylink");
let data = vol
.read_file_data(entries[0].anode, entries[0].file_size())
.unwrap();
assert_eq!(String::from_utf8_lossy(&data), "target/path");
std::fs::remove_file(&path).ok();
}
#[test]
fn create_hardlink() {
let (path, mut w) = fresh_writer(4096);
w.write_file("original.txt", b"data").unwrap();
let entries = w.vol.list_dir_by_anode(ANODE_ROOTDIR).unwrap();
let orig = entries.iter().find(|e| e.name == "original.txt").unwrap();
let orig_anode = orig.anode;
w.create_hardlink("link.txt", orig_anode).unwrap();
drop(w);
let mut vol = reopen(&path);
let entries = vol.list_dir("/").unwrap();
assert_eq!(entries.len(), 2);
let orig = entries.iter().find(|e| e.name == "original.txt").unwrap();
let link = entries.iter().find(|e| e.name == "link.txt").unwrap();
assert_eq!(orig.anode, link.anode);
std::fs::remove_file(&path).ok();
}
}