use std::io::Read;
use std::path::Path;
use crate::Error;
pub(crate) fn on_disk_bytes(path: &Path) -> Result<u64, Error> {
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
let meta = std::fs::metadata(path).map_err(|source| Error::Io {
context: "stat",
source,
})?;
Ok(meta.blocks().saturating_mul(512))
}
#[cfg(windows)]
{
use std::os::windows::ffi::OsStrExt;
use windows_sys::Win32::Storage::FileSystem::GetCompressedFileSizeW;
let wide: Vec<u16> = path
.as_os_str()
.encode_wide()
.chain(std::iter::once(0))
.collect();
let mut high: u32 = 0;
let low = unsafe { GetCompressedFileSizeW(wide.as_ptr(), &mut high) };
if low == u32::MAX {
let err = std::io::Error::last_os_error();
if err.raw_os_error().unwrap_or(0) != 0 {
return Err(Error::Io {
context: "GetCompressedFileSizeW",
source: err,
});
}
}
Ok(((high as u64) << 32) | low as u64)
}
#[cfg(not(any(unix, windows)))]
{
let meta = std::fs::metadata(path).map_err(|source| Error::Io {
context: "stat",
source,
})?;
Ok(meta.len())
}
}
pub(crate) fn magic_prefix(path: &Path) -> Result<[u8; 4], Error> {
let mut file = std::fs::File::open(path).map_err(|source| Error::Io {
context: "open",
source,
})?;
let mut buf = [0u8; 4];
file.read(&mut buf).map_err(|source| Error::Io {
context: "read",
source,
})?;
Ok(buf)
}
pub(crate) fn readback_matches(path: &Path, expected: &[u8]) -> Result<bool, Error> {
let mut file = std::fs::File::open(path).map_err(|source| Error::Io {
context: "read-back",
source,
})?;
let mut buf = [0u8; 64 * 1024];
let mut off = 0usize;
loop {
let n = file.read(&mut buf).map_err(|source| Error::Io {
context: "read-back",
source,
})?;
if n == 0 {
break;
}
if off + n > expected.len() || buf[..n] != expected[off..off + n] {
return Ok(false);
}
off += n;
}
Ok(off == expected.len())
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
fn scratch(tag: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("decmpfs-verify-{tag}-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
dir
}
#[test]
fn readback_matches_identical_content() {
let dir = scratch("rb-eq");
let path = dir.join("f");
let content = vec![0xABu8; 200 * 1024];
std::fs::write(&path, &content).unwrap();
assert!(readback_matches(&path, &content).unwrap());
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn readback_detects_a_differing_byte() {
let dir = scratch("rb-diff");
let path = dir.join("f");
let content = vec![0x11u8; 100 * 1024];
let mut on_disk = content.clone();
on_disk[80 * 1024] = 0x22;
std::fs::write(&path, &on_disk).unwrap();
assert!(!readback_matches(&path, &content).unwrap());
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn readback_detects_a_short_file() {
let dir = scratch("rb-short");
let path = dir.join("f");
std::fs::write(&path, vec![0x33u8; 4096]).unwrap();
assert!(!readback_matches(&path, &vec![0x33u8; 8192]).unwrap());
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn readback_detects_a_long_file() {
let dir = scratch("rb-long");
let path = dir.join("f");
std::fs::write(&path, vec![0x44u8; 8192]).unwrap();
assert!(!readback_matches(&path, &vec![0x44u8; 4096]).unwrap());
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn readback_errors_on_a_missing_path() {
assert!(readback_matches(std::path::Path::new("/no/such/rb/x"), b"x").is_err());
}
#[test]
fn measures_allocation_and_reads_magic() {
let dir = std::env::temp_dir().join(format!("decmpfs-verify-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("f");
std::fs::write(&path, vec![0x7f; 9000]).unwrap();
assert!(
on_disk_bytes(&path).unwrap() > 0,
"allocated bytes reported"
);
assert_eq!(magic_prefix(&path).unwrap(), [0x7f; 4]);
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn errors_on_a_missing_path() {
let p = std::path::Path::new("/no/such/verify/x");
assert!(on_disk_bytes(p).is_err());
assert!(magic_prefix(p).is_err());
}
#[cfg(unix)]
#[test]
fn magic_prefix_errors_when_the_read_fails_after_a_successful_open() {
let dir = std::env::temp_dir().join(format!("decmpfs-readfail-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
assert!(magic_prefix(&dir).is_err(), "read of a directory fd errors");
std::fs::remove_dir_all(&dir).ok();
}
}