Handle pre-epoch file timestamps consistently and record Rust audit

This commit is contained in:
karl committed 2026-10-13 17:02:16 +02:00
1 parent 59b2fc39b2
commit 14adbbc616
7 files changed
+262 -46

No files matched your search

+39 -3
View File
@@ -14,7 +14,7 @@ use std::{
path::{Path, PathBuf},
process::{Command, Stdio},
thread,
time::{Duration, Instant, UNIX_EPOCH},
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
@@ -28,6 +28,26 @@ pub enum MediaKind {
mod tests {
use super::*;
#[test]
fn signed_unix_time_preserves_epoch_and_storage_boundaries() -> Result<()> {
// Windows SystemTime has 100 ns precision; storage conversion itself
// must still handle every signed nanosecond boundary exactly.
assert_eq!(unix_time_ns(UNIX_EPOCH)?, 0);
assert_eq!(unix_time_ns(UNIX_EPOCH + Duration::from_nanos(100))?, 100);
assert_eq!(unix_time_ns(UNIX_EPOCH - Duration::from_nanos(100))?, -100);
assert_eq!(
signed_duration_ns(Duration::from_nanos(i64::MAX as u64), false)?,
i64::MAX
);
assert_eq!(
signed_duration_ns(Duration::from_nanos(i64::MAX as u64 + 1), true)?,
i64::MIN
);
assert!(signed_duration_ns(Duration::from_nanos(i64::MAX as u64 + 1), false).is_err());
assert!(signed_duration_ns(Duration::from_nanos(i64::MAX as u64 + 2), true).is_err());
Ok(())
}
fn fixture(duration_ms: u32) -> MediaProbe {
MediaProbe {
duration_ms,
@@ -414,9 +434,25 @@ impl MediaIdentity {
Ok(())
}
}
/// Convert a filesystem timestamp to signed nanoseconds since the Unix epoch.
/// Times outside the signed 64-bit storage range are rejected explicitly.
pub fn unix_time_ns(time: SystemTime) -> Result<i64> {
match time.duration_since(UNIX_EPOCH) {
Ok(duration) => signed_duration_ns(duration, false),
Err(error) => signed_duration_ns(error.duration(), true),
}
}
fn signed_duration_ns(duration: Duration, before_epoch: bool) -> Result<i64> {
// Duration's u64 seconds fit in i128 nanoseconds, including i64::MIN's
// magnitude, which cannot first be represented as positive i64.
let nanos = duration.as_nanos() as i128;
i64::try_from(if before_epoch { -nanos } else { nanos })
.context("File timestamp exceeds supported nanosecond range")
}
fn modified_ns(metadata: &fs::Metadata) -> Result<i64> {
i64::try_from(metadata.modified()?.duration_since(UNIX_EPOCH)?.as_nanos())
.context("Unsupported media timestamp")
unix_time_ns(metadata.modified()?)
}
#[derive(Debug, Clone, Serialize)]