#![cfg(unix)]
use std::io::{Cursor, Read};
use std::path::Path;
use fstool::block::{BlockDevice, FileBackend};
use fstool::fs::{CloneCapability, FileMeta, FileSource, Filesystem};
use fstool::inspect;
use tempfile::NamedTempFile;
#[test]
fn ext4_clone_file_default_byte_copy() {
use fstool::fs::ext::{Ext, FormatOpts, FsKind};
let tmp = NamedTempFile::new().unwrap();
let opts = FormatOpts {
kind: FsKind::Ext4,
blocks_count: 8192,
inodes_count: 64,
journal_blocks: 1024,
sparse: true,
..FormatOpts::default()
};
let size = opts.blocks_count as u64 * opts.block_size as u64;
let body: Vec<u8> = (0..4321).map(|i| (i & 0xFF) as u8).collect();
{
let mut dev = FileBackend::create(tmp.path(), size).unwrap();
let mut fs: Box<dyn Filesystem> = Box::new(Ext::format_with(&mut dev, &opts).unwrap());
fs.create_file(
&mut dev,
Path::new("/src.bin"),
FileSource::Reader {
reader: Box::new(Cursor::new(body.clone())),
len: body.len() as u64,
},
FileMeta {
mode: 0o640,
uid: 1000,
gid: 1000,
mtime: 1_700_000_000,
atime: 1_700_000_000,
ctime: 1_700_000_000,
},
)
.unwrap();
assert_eq!(fs.clone_capability(), CloneCapability::None);
fs.clone_file(&mut dev, Path::new("/src.bin"), Path::new("/dst.bin"))
.unwrap();
fs.flush(&mut dev).unwrap();
dev.sync().unwrap();
}
let mut dev = FileBackend::open(tmp.path()).unwrap();
let mut fs = inspect::open(&mut dev).unwrap();
let mut got = Vec::new();
{
let mut r = fs.read_file(&mut dev, Path::new("/dst.bin")).unwrap();
r.read_to_end(&mut got).unwrap();
}
assert_eq!(got, body, "cloned content mismatch");
let attrs = fs.getattr(&mut dev, Path::new("/dst.bin")).unwrap();
assert_eq!(attrs.mode & 0o777, 0o640, "mode not preserved");
assert_eq!(attrs.uid, 1000, "uid not preserved");
assert_eq!(attrs.gid, 1000, "gid not preserved");
assert_eq!(attrs.mtime, 1_700_000_000, "mtime not preserved");
}
#[test]
fn clone_range_default_is_unsupported() {
use fstool::fs::ext::{Ext, FormatOpts, FsKind};
let tmp = NamedTempFile::new().unwrap();
let opts = FormatOpts {
kind: FsKind::Ext4,
blocks_count: 8192,
inodes_count: 64,
journal_blocks: 1024,
sparse: true,
..FormatOpts::default()
};
let size = opts.blocks_count as u64 * opts.block_size as u64;
let mut dev = FileBackend::create(tmp.path(), size).unwrap();
let mut fs: Box<dyn Filesystem> = Box::new(Ext::format_with(&mut dev, &opts).unwrap());
fs.create_file(
&mut dev,
Path::new("/a"),
FileSource::Zero(4096),
FileMeta::default(),
)
.unwrap();
fs.create_file(
&mut dev,
Path::new("/b"),
FileSource::Zero(4096),
FileMeta::default(),
)
.unwrap();
let err = fs
.clone_range(&mut dev, Path::new("/a"), 0, Path::new("/b"), 0, 4096)
.expect_err("clone_range default must reject");
assert!(
matches!(err, fstool::Error::Unsupported(_)),
"expected Unsupported, got: {err:?}"
);
}
#[test]
fn anyfs_clone_file_routes_through_default_fallback() {
use fstool::fs::ext::{Ext, FormatOpts, FsKind};
let tmp = NamedTempFile::new().unwrap();
let opts = FormatOpts {
kind: FsKind::Ext4,
blocks_count: 8192,
inodes_count: 64,
journal_blocks: 1024,
sparse: true,
..FormatOpts::default()
};
let size = opts.blocks_count as u64 * opts.block_size as u64;
let body = b"clone via AnyFs\n";
{
let mut dev = FileBackend::create(tmp.path(), size).unwrap();
let mut fs: Box<dyn Filesystem> = Box::new(Ext::format_with(&mut dev, &opts).unwrap());
fs.create_file(
&mut dev,
Path::new("/seed.txt"),
FileSource::Reader {
reader: Box::new(Cursor::new(body.to_vec())),
len: body.len() as u64,
},
FileMeta::default(),
)
.unwrap();
fs.flush(&mut dev).unwrap();
dev.sync().unwrap();
}
let mut dev = FileBackend::open(tmp.path()).unwrap();
let mut any = inspect::AnyFs::open(&mut dev).unwrap();
assert_eq!(any.clone_capability(), CloneCapability::None);
any.clone_file(&mut dev, "/seed.txt", "/clone.txt").unwrap();
any.flush(&mut dev).unwrap();
dev.sync().unwrap();
drop(dev);
let mut dev = FileBackend::open(tmp.path()).unwrap();
let mut fs = inspect::open(&mut dev).unwrap();
let mut got = Vec::new();
{
let mut r = fs.read_file(&mut dev, Path::new("/clone.txt")).unwrap();
r.read_to_end(&mut got).unwrap();
}
assert_eq!(got, body);
}