Handle pre-epoch file timestamps consistently and record Rust audit
This commit is contained in:
1 parent
59b2fc39b2
commit
14adbbc616
7 files changed
+262
-46
No files matched your search
@@ -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)]
|
||||
|
||||
Reference in new issue
Block a user