use crate::common::{
BackingFile, LoopbackDevice, Mount, single_mount, write_test_data,
};
use btrfs_uapi::{
device::{device_add, device_info, device_min_size},
filesystem::{
ResizeAmount, ResizeArgs, filesystem_info, label_get, label_set,
resize, sync,
},
};
use std::ffi::{CStr, CString};
#[test]
#[ignore = "requires elevated privileges"]
fn filesystem_info_basics() {
let (_td, mnt) = single_mount();
let info = filesystem_info(mnt.fd()).expect("filesystem_info failed");
assert!(!info.uuid.is_nil(), "uuid should not be nil");
assert_eq!(info.num_devices, 1);
assert_eq!(info.max_id, 1);
assert!(
info.sectorsize == 4096 || info.sectorsize == 16384,
"sectorsize should be 4096 or 16384, got {}",
info.sectorsize,
);
assert!(
info.nodesize == 4096 || info.nodesize == 16384,
"nodesize should be 4096 or 16384, got {}",
info.nodesize,
);
}
#[test]
#[ignore = "requires elevated privileges"]
fn filesystem_info_after_add() {
let td = tempfile::tempdir().unwrap();
let f1 = BackingFile::new(td.path(), "d1.img", 300_000_000);
f1.mkfs();
let lo1 = LoopbackDevice::new(f1);
let mnt = Mount::new(lo1, td.path());
let info1 = filesystem_info(mnt.fd()).expect("filesystem_info failed");
assert_eq!(info1.num_devices, 1);
assert_eq!(info1.max_id, 1);
let f2 = BackingFile::new(td.path(), "d2.img", 300_000_000);
let lo2 = LoopbackDevice::new(f2);
let dev2_cpath = CString::new(lo2.path().to_str().unwrap()).unwrap();
device_add(mnt.fd(), &dev2_cpath).expect("device_add failed");
let info2 =
filesystem_info(mnt.fd()).expect("filesystem_info after add failed");
assert_eq!(info2.num_devices, 2);
assert_eq!(info2.max_id, 2);
assert_eq!(
info1.uuid, info2.uuid,
"uuid should not change after adding a device"
);
}
#[test]
#[ignore = "requires elevated privileges"]
fn sync_basic() {
let (_td, mnt) = single_mount();
write_test_data(mnt.path(), "data.bin", 1_000_000);
sync(mnt.fd()).expect("sync failed");
sync(mnt.fd()).expect("second sync failed");
}
#[test]
#[ignore = "requires elevated privileges"]
fn label_get_set() {
let (_td, mnt) = single_mount();
let initial = label_get(mnt.fd()).expect("label_get failed");
assert!(
initial.to_bytes().is_empty(),
"initial label should be empty, got {initial:?}"
);
let test_label = CStr::from_bytes_with_nul(b"test-label\0").unwrap();
label_set(mnt.fd(), test_label).expect("label_set failed");
let got = label_get(mnt.fd()).expect("label_get after set failed");
assert_eq!(got.as_c_str(), test_label, "label should round-trip");
}
#[test]
#[ignore = "requires elevated privileges"]
fn label_max_length() {
let (_td, mnt) = single_mount();
let max_label = "a".repeat(255);
let max_cstr = CString::new(max_label.clone()).unwrap();
label_set(mnt.fd(), &max_cstr)
.expect("label_set with 255 bytes should succeed");
let got = label_get(mnt.fd()).expect("label_get failed");
assert_eq!(got.to_bytes().len(), 255);
let too_long = "b".repeat(256);
let too_long_cstr = CString::new(too_long).unwrap();
let err = label_set(mnt.fd(), &too_long_cstr);
assert!(err.is_err(), "label_set with 256 bytes should fail");
}
#[test]
#[ignore = "requires elevated privileges"]
fn label_clear() {
let (_td, mnt) = single_mount();
let test_label = CStr::from_bytes_with_nul(b"some-label\0").unwrap();
label_set(mnt.fd(), test_label).expect("label_set failed");
let got = label_get(mnt.fd()).expect("label_get failed");
assert_eq!(got.as_c_str(), test_label);
let empty = CStr::from_bytes_with_nul(b"\0").unwrap();
label_set(mnt.fd(), empty).expect("label_set empty failed");
let cleared = label_get(mnt.fd()).expect("label_get after clear failed");
assert!(
cleared.to_bytes().is_empty(),
"label should be empty after clearing, got {cleared:?}"
);
}
#[test]
#[ignore = "requires elevated privileges"]
fn resize_grow() {
let td = tempfile::tempdir().unwrap();
let f = BackingFile::new(td.path(), "disk.img", 200_000_000);
f.mkfs();
let lo = LoopbackDevice::new(f);
let mnt = Mount::new(lo, td.path());
let dev_before = device_info(mnt.fd(), 1).unwrap().unwrap();
mnt.loopback().backing_file().unwrap().resize(400_000_000);
mnt.loopback().refresh_size();
resize(mnt.fd(), ResizeArgs::new(ResizeAmount::Max).with_devid(1))
.expect("resize grow failed");
let dev_after = device_info(mnt.fd(), 1).unwrap().unwrap();
assert!(
dev_after.total_bytes > dev_before.total_bytes,
"device total_bytes should increase: before={}, after={}",
dev_before.total_bytes,
dev_after.total_bytes,
);
}
#[test]
#[ignore = "requires elevated privileges"]
fn resize_shrink() {
let td = tempfile::tempdir().unwrap();
let f = BackingFile::new(td.path(), "disk.img", 500_000_000);
f.mkfs();
let lo = LoopbackDevice::new(f);
let mnt = Mount::new(lo, td.path());
write_test_data(mnt.path(), "data.bin", 50_000_000);
sync(mnt.fd()).unwrap();
let dev = device_info(mnt.fd(), 1).unwrap().unwrap();
let min = device_min_size(mnt.fd(), 1).expect("device_min_size failed");
let target = min + (dev.total_bytes - min) / 2;
assert!(
target < dev.total_bytes,
"target {target} should be less than current size {}",
dev.total_bytes,
);
resize(
mnt.fd(),
ResizeArgs::new(ResizeAmount::Set(target)).with_devid(1),
)
.expect("resize shrink failed");
crate::common::verify_test_data(mnt.path(), "data.bin", 50_000_000);
}
#[test]
#[ignore = "requires elevated privileges"]
fn resize_shrink_below_minimum_fails() {
let td = tempfile::tempdir().unwrap();
let f = BackingFile::new(td.path(), "disk.img", 500_000_000);
f.mkfs();
let lo = LoopbackDevice::new(f);
let mnt = Mount::new(lo, td.path());
write_test_data(mnt.path(), "data.bin", 200_000_000);
sync(mnt.fd()).unwrap();
let result = resize(
mnt.fd(),
ResizeArgs::new(ResizeAmount::Set(1_000_000)).with_devid(1),
);
assert!(
result.is_err(),
"resize to 1MB should fail when data is present"
);
}