use std::ffi::c_void;
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
use std::ffi::{c_char, c_int, CString};
use crate::error::{Error, Result};
pub fn ptr_to_value(ptr: *mut c_void) -> Result<u64> {
Ok(ptr as usize as u64)
}
pub fn value_to_ptr(value: u64) -> Result<*mut c_void> {
Ok(value as usize as *mut c_void)
}
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
#[repr(C)]
struct Timespec {
tv_sec: i64,
tv_nsec: i64,
}
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
#[repr(C)]
struct Stat {
st_dev: i32,
st_mode: u16,
st_nlink: u16,
st_ino: u64,
st_uid: u32,
st_gid: u32,
st_rdev: i32,
_st_pad: u32,
st_atimespec: Timespec,
st_mtimespec: Timespec,
st_ctimespec: Timespec,
st_birthtimespec: Timespec,
st_size: i64,
st_blocks: i64,
st_blksize: i32,
st_flags: u32,
st_gen: u32,
st_lspare: i32,
st_qspare: [i64; 2],
}
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
extern "C" {
fn stat(path: *const c_char, buf: *mut Stat) -> c_int;
}
pub fn get_creation_date(path: &str) -> Result<i64> {
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
{
let cpath = CString::new(path).map_err(|_| Error::Invalid)?;
let mut st = Stat {
st_dev: 0,
st_mode: 0,
st_nlink: 0,
st_ino: 0,
st_uid: 0,
st_gid: 0,
st_rdev: 0,
_st_pad: 0,
st_atimespec: Timespec {
tv_sec: 0,
tv_nsec: 0,
},
st_mtimespec: Timespec {
tv_sec: 0,
tv_nsec: 0,
},
st_ctimespec: Timespec {
tv_sec: 0,
tv_nsec: 0,
},
st_birthtimespec: Timespec {
tv_sec: 0,
tv_nsec: 0,
},
st_size: 0,
st_blocks: 0,
st_blksize: 0,
st_flags: 0,
st_gen: 0,
st_lspare: 0,
st_qspare: [0, 0],
};
if unsafe { stat(cpath.as_ptr(), &mut st) } != 0 {
return Err(Error::NotFound);
}
let mut secs = st.st_birthtimespec.tv_sec;
if secs == 0 || secs == -1 {
secs = st.st_ctimespec.tv_sec;
}
if secs == 0 {
return Err(Error::NotFound);
}
Ok(secs)
}
#[cfg(not(any(target_os = "macos", target_os = "freebsd")))]
{
let meta = std::fs::metadata(path).map_err(|_| Error::NotFound)?;
let t = meta
.created()
.or_else(|_| meta.modified())
.map_err(|_| Error::NotFound)?;
let secs = t
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
if secs == 0 {
return Err(Error::NotFound);
}
Ok(secs)
}
}
#[cfg(test)]
mod tests {
use std::ffi::c_void;
use std::sync::atomic::{AtomicU64, Ordering};
use super::*;
use crate::error::{Error, Result};
#[test]
fn ptr_to_value_null_is_zero() {
assert_eq!(ptr_to_value(std::ptr::null_mut()).unwrap(), 0);
}
#[test]
fn ptr_value_round_trip() {
let value: u64 = 0x1234_5678_9abc_def0;
let ptr = value_to_ptr(value).unwrap();
assert_eq!(ptr_to_value(ptr).unwrap(), value);
assert!(!ptr.is_null());
}
#[test]
fn value_to_ptr_null_is_null() {
assert!(value_to_ptr(0).unwrap().is_null());
}
#[test]
fn value_to_ptr_accepts_any_u64() {
assert!(value_to_ptr(u64::MAX).is_ok());
assert!(value_to_ptr(1).is_ok());
}
#[test]
fn ptr_to_value_of_real_pointer_round_trips() {
let data = 42i32;
let raw = &data as *const i32 as *mut c_void;
let n = ptr_to_value(raw).unwrap();
let back = value_to_ptr(n).unwrap();
assert_eq!(back as *const i32 as *const i32, &data as *const i32);
}
fn temp_file_path(tag: &str) -> std::path::PathBuf {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
std::env::temp_dir().join(format!(
"quercus_core_qtutils_{}_{}_{}.tmp",
std::process::id(),
tag,
n
))
}
#[test]
fn creation_date_of_existing_file() {
let p = temp_file_path("exists");
std::fs::write(&p, b"hello").unwrap();
let secs = get_creation_date(p.to_str().unwrap());
match secs {
Ok(s) => assert!(s > 0, "creation time should be in the past"),
Err(e) => panic!("expected Ok, got {e:?}"),
}
if let Ok(meta) = std::fs::metadata(&p) {
if let Ok(created) = meta.created() {
let expected = created
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
assert_eq!(secs.unwrap(), expected);
}
}
let _ = std::fs::remove_file(&p);
}
#[test]
fn creation_date_matches_std_for_new_file() {
let p = temp_file_path("match");
std::fs::write(&p, b"data").unwrap();
let ours = get_creation_date(p.to_str().unwrap()).unwrap();
let expected = std::fs::metadata(&p)
.and_then(|m| m.created())
.map(|t| t.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as i64)
.unwrap_or(0);
assert!(expected > 0);
assert_eq!(ours, expected);
let _ = std::fs::remove_file(&p);
}
#[test]
fn creation_date_missing_file_is_not_found() {
let p = temp_file_path("missing");
let _ = std::fs::remove_file(&p);
let res = get_creation_date(p.to_str().unwrap());
assert!(matches!(res, Err(Error::NotFound)));
}
#[test]
fn creation_date_empty_path_is_not_found() {
assert!(matches!(get_creation_date(""), Err(Error::NotFound)));
}
#[test]
fn creation_date_returns_result_ok_type() {
let p = temp_file_path("typetest");
std::fs::write(&p, b"x").unwrap();
let res: Result<i64> = get_creation_date(p.to_str().unwrap());
assert!(res.is_ok());
let _ = std::fs::remove_file(&p);
}
#[test]
fn value_to_ptr_is_injective_over_u64() {
let a = value_to_ptr(1).unwrap();
let b = value_to_ptr(2).unwrap();
assert_ne!(a, b);
}
}