use std::fs;
use std::io;
use std::path::Path;
use std::time::SystemTime;
use filetime::FileTime;
#[cfg(unix)]
use std::os::unix::io::RawFd;
#[cfg(windows)]
use libc;
pub fn set_file_stat(
path: &Path,
mtime: SystemTime,
uid: u32,
gid: u32,
mode: u32,
) -> io::Result<()> {
if !is_reg_file(path) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"set_file_stat: not a regular file",
));
}
let atime = FileTime::from_system_time(SystemTime::now());
let ft_mtime = FileTime::from_system_time(mtime);
filetime::set_file_times(path, atime, ft_mtime)?;
#[cfg(unix)]
{
use nix::unistd::{chown, Gid, Uid};
chown(path, Some(Uid::from_raw(uid)), Some(Gid::from_raw(gid))).map_err(io::Error::from)?;
}
#[cfg(not(unix))]
let _ = (uid, gid);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(path, fs::Permissions::from_mode(mode & 0o7777))?;
}
#[cfg(windows)]
{
let readonly = (mode & 0o200) == 0;
let mut perms = fs::metadata(path)?.permissions();
perms.set_readonly(readonly);
fs::set_permissions(path, perms)?;
}
#[cfg(not(any(unix, windows)))]
let _ = mode;
Ok(())
}
#[cfg(unix)]
pub fn is_reg_fd(fd: RawFd) -> bool {
if fd < 0 {
return false;
}
use nix::sys::stat::{fstat, SFlag};
use std::os::unix::io::BorrowedFd;
match fstat(unsafe { BorrowedFd::borrow_raw(fd) }) {
Ok(stat) => {
(stat.st_mode as u32) & (SFlag::S_IFMT.bits() as u32) == SFlag::S_IFREG.bits() as u32
}
Err(_) => false,
}
}
#[cfg(windows)]
pub fn is_reg_fd(fd: i32) -> bool {
if fd < 3 {
return false;
}
unsafe {
let mut stat_buf = std::mem::zeroed::<libc::stat>();
if libc::fstat(fd, &mut stat_buf) != 0 {
return false;
}
(stat_buf.st_mode as u32) & (libc::S_IFMT as u32) == (libc::S_IFREG as u32)
}
}
pub fn is_reg_file(path: &Path) -> bool {
fs::metadata(path)
.map(|m| m.file_type().is_file())
.unwrap_or(false)
}
pub fn is_directory(path: &Path) -> bool {
fs::metadata(path)
.map(|m| m.file_type().is_dir())
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::time::Duration;
use tempfile::TempDir;
#[test]
fn is_reg_file_returns_true_for_regular_file() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("file.txt");
File::create(&path).unwrap();
assert!(is_reg_file(&path));
}
#[test]
fn is_reg_file_returns_false_for_directory() {
let dir = TempDir::new().unwrap();
assert!(!is_reg_file(dir.path()));
}
#[test]
fn is_reg_file_returns_false_for_nonexistent_path() {
assert!(!is_reg_file(Path::new(
"/nonexistent/__lz4_test_path__.txt"
)));
}
#[test]
fn is_directory_returns_true_for_directory() {
let dir = TempDir::new().unwrap();
assert!(is_directory(dir.path()));
}
#[test]
fn is_directory_returns_false_for_regular_file() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("file.txt");
File::create(&path).unwrap();
assert!(!is_directory(&path));
}
#[test]
fn is_directory_returns_false_for_nonexistent_path() {
assert!(!is_directory(Path::new("/nonexistent/__lz4_test_dir__")));
}
#[cfg(unix)]
#[test]
fn is_reg_fd_stdin_is_not_regular_file() {
assert!(!is_reg_fd(0));
}
#[cfg(unix)]
#[test]
fn is_reg_fd_returns_true_for_file_fd() {
use std::os::unix::io::IntoRawFd;
let dir = TempDir::new().unwrap();
let path = dir.path().join("fd_test.bin");
let f = File::create(&path).unwrap();
let fd = f.into_raw_fd();
assert!(is_reg_fd(fd));
let _ = nix::unistd::close(fd);
}
#[test]
fn set_file_stat_errors_on_nonexistent_file() {
let result = set_file_stat(
Path::new("/nonexistent/__lz4_set_stat__.txt"),
SystemTime::now(),
0,
0,
0o644,
);
assert!(result.is_err());
}
#[test]
fn set_file_stat_errors_on_directory() {
let dir = TempDir::new().unwrap();
let result = set_file_stat(dir.path(), SystemTime::now(), 0, 0, 0o755);
assert!(result.is_err());
}
#[test]
fn set_file_stat_mtime_roundtrip() {
let dir = TempDir::new().unwrap();
let path = dir.path().join("mtime_test.txt");
File::create(&path).unwrap();
let target_mtime = SystemTime::now() - Duration::from_secs(3600);
#[cfg(unix)]
let (uid, gid) = {
use std::os::unix::fs::MetadataExt;
let m = fs::metadata(&path).unwrap();
(m.uid(), m.gid())
};
#[cfg(not(unix))]
let (uid, gid) = (0u32, 0u32);
set_file_stat(&path, target_mtime, uid, gid, 0o644).unwrap();
let actual_mtime = fs::metadata(&path).unwrap().modified().unwrap();
let diff = if actual_mtime >= target_mtime {
actual_mtime.duration_since(target_mtime).unwrap()
} else {
target_mtime.duration_since(actual_mtime).unwrap()
};
assert!(
diff < Duration::from_secs(1),
"mtime deviation {diff:?} exceeds 1-second tolerance"
);
}
}