use std::{
fs,
io::{ErrorKind, Write},
path::{Path, PathBuf}
};
#[cfg(windows)]
use windows_sys::Win32::Storage::FileSystem::GetDiskFreeSpaceExA;
use crate::{err::Error, ffi::path_to_cstring};
pub fn rndfile(
fname: impl AsRef<Path>,
size: u64
) -> Result<u64, std::io::Error> {
let mut i = super::iox::RngReader::with_lim(size);
let mut o = fs::File::create(fname)?;
std::io::copy(&mut i, &mut o)
}
#[allow(clippy::missing_errors_doc)]
pub fn gen_absdir(input: impl AsRef<str>) -> Result<PathBuf, Error> {
let pth = super::path::expabs(input)?;
if !pth.exists() {
std::fs::create_dir_all(&pth)?;
}
if !pth.is_dir() {
return Err(std::io::Error::other("Not a directory").into());
}
Ok(pth)
}
#[allow(clippy::missing_errors_doc)]
pub fn rm_containing(
pth: impl AsRef<Path>
) -> Result<PathBuf, std::io::Error> {
if let Some(parent) = pth.as_ref().parent() {
std::fs::remove_dir(parent)?;
Ok(parent.to_path_buf())
} else {
Err(std::io::Error::other("Parent not found"))
}
}
#[allow(clippy::missing_errors_doc)]
pub fn abspath<P>(pth: P) -> Result<PathBuf, Error>
where
P: AsRef<Path>
{
Ok(pth.as_ref().canonicalize()?)
}
pub fn run_if_outdated<F>(
srcfile: impl AsRef<Path>,
dstfile: impl AsRef<Path>,
f: F
) -> Result<bool, std::io::Error>
where
F: FnOnce(&Path, &Path)
{
if outdated(&srcfile, &dstfile)? {
f(srcfile.as_ref(), dstfile.as_ref());
Ok(true)
} else {
Ok(false)
}
}
pub fn outdated(
infile: impl AsRef<Path>,
outfile: impl AsRef<Path>
) -> Result<bool, std::io::Error> {
let in_md = fs::metadata(infile.as_ref())?;
let out_md = match fs::metadata(outfile.as_ref()) {
Ok(md) => md,
Err(err) if err.kind() == ErrorKind::NotFound => {
return Ok(true);
}
Err(e) => Err(e)?
};
let in_mtime = in_md.modified()?;
let out_mtime = out_md.modified()?;
Ok(in_mtime > out_mtime)
}
pub fn get_free_space(path: &Path) -> Result<u64, Error> {
#[cfg(unix)]
let res = get_free_space_unix(path);
#[cfg(windows)]
let res = get_free_space_win(path);
res
}
#[cfg(unix)]
fn get_free_space_unix(path: &Path) -> Result<u64, Error> {
if !path.is_dir() {
return Err(Error::IO("Not a directory".into()));
}
let cstr = path_to_cstring(path);
let cstr_path = cstr.as_bytes_with_nul();
let mut statfs = unsafe { std::mem::zeroed() };
let result =
unsafe { libc::statfs(cstr_path.as_ptr().cast::<i8>(), &raw mut statfs) };
if result == 0 {
#[cfg(target_os = "macos")]
let free = statfs.f_bavail * u64::from(statfs.f_bsize);
#[cfg(target_os = "linux")]
#[allow(clippy::cast_sign_loss)]
let free = statfs.f_bavail * statfs.f_bsize as u64;
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
let free = statfs.f_bavail as u64 * statfs.f_bsize;
Ok(free)
} else {
Err(Error::IO("statfs() failed".into()))
}
}
#[cfg(windows)]
fn get_free_space_win(path: &Path) -> Result<u64, Error> {
if !path.is_dir() {
return Err(Error::IO("Not a directory".into()));
}
let cstr = path_to_cstring(path);
let cstr_path = cstr.as_bytes_with_nul();
let mut free_bytes_available_to_caller: u64 = 0;
let mut total_bytes: u64 = 0;
let mut total_free_bytes: u64 = 0;
let result = unsafe {
GetDiskFreeSpaceExA(
cstr_path.as_ptr(),
&raw mut free_bytes_available_to_caller,
&raw mut total_bytes,
&raw mut total_free_bytes
)
};
if result != 0 {
Ok(free_bytes_available_to_caller)
} else {
Err(Error::IO(format!(
"GetDiskFreeSpaceExW failed with code: {}",
std::io::Error::last_os_error()
)))
}
}
pub fn if_not_exists<P, F>(pth: P, f: F)
where
P: AsRef<Path>,
F: FnOnce(&Path)
{
let pth = pth.as_ref();
if !pth.exists() {
f(pth);
}
}
pub enum TestFileContents {
Random,
U8Count,
U16Count,
U32Count,
U64Count,
CountedBlock { block_size: usize }
}
pub fn make_test_file<P>(
fname: P,
tfc: &TestFileContents,
size: u64
) -> Result<(), std::io::Error>
where
P: AsRef<Path>
{
match tfc {
TestFileContents::Random => {
rndfile(fname, size)?;
}
TestFileContents::U8Count => {
let mut o = fs::File::create(fname)?;
let mut buf: Vec<u8> = Vec::with_capacity(256);
for i in 0..256 {
buf.push(u8::try_from(i).unwrap());
}
let mut size = size;
while size > 0 {
let n = std::cmp::min(size, 256);
let n = n.try_into().unwrap();
o.write_all(&buf[..n])?;
size -= n as u64;
}
}
TestFileContents::U16Count => {
let mut o = fs::File::create(fname)?;
let mut c: u16 = 0;
let mut size = size;
while size > 0 {
let buf = c.to_be_bytes();
let n = std::cmp::min(size, buf.len() as u64);
let n: usize = n.try_into().unwrap();
o.write_all(&buf[..n])?;
c = c.wrapping_add(1);
size -= n as u64;
}
}
TestFileContents::U32Count => {
let mut o = fs::File::create(fname)?;
let mut c: u32 = 0;
let mut size = size;
while size > 0 {
let buf = c.to_be_bytes();
let n = std::cmp::min(size, buf.len() as u64);
let n: usize = n.try_into().unwrap();
o.write_all(&buf[..n])?;
c = c.wrapping_add(1);
size -= n as u64;
}
}
TestFileContents::U64Count => {
let mut o = fs::File::create(fname)?;
let mut c: u64 = 0;
let mut size = size;
while size > 0 {
let buf = c.to_be_bytes();
let n = std::cmp::min(size, buf.len() as u64);
let n: usize = n.try_into().unwrap();
o.write_all(&buf[..n])?;
c = c.wrapping_add(1);
size -= n as u64;
}
}
TestFileContents::CountedBlock { block_size } => {
assert!(*block_size >= std::mem::size_of::<u64>());
let mut o = fs::File::create(fname)?;
let mut c: u64 = 0;
let mut buf = vec![0u8; *block_size];
let mut size = size;
while size > 0 {
let cbuf = c.to_be_bytes();
buf[..8].copy_from_slice(&cbuf);
let n = std::cmp::min(size, buf.len() as u64);
let n: usize = n.try_into().unwrap();
o.write_all(&buf[..n])?;
c = c.wrapping_add(1);
size -= n as u64;
}
}
}
Ok(())
}