Skip to content

Commit

Permalink
Replace chrono by the better maintained and no_std compatible time.
Browse files Browse the repository at this point in the history
  • Loading branch information
ColinFinck committed Mar 29, 2022
1 parent 11a784a commit d542663
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 93 deletions.
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,24 @@ arrayvec = { version = "0.7.1", default-features = false }
binread = { version = "2.1.1", features = ["const_generics"], default-features = false }
byteorder = { version = "1.4.3", default-features = false }
bitflags = "1.2.1"
chrono = { version = "0.4.19", optional = true }
derive_more = "0.99.16"
displaydoc = { version = "0.2.1", default-features = false }
enumn = "0.1.3"
memoffset = "0.6.4"
strum_macros = "0.21.1"
time = { version = "0.3.9", features = ["large-dates", "macros"], default-features = false, optional = true }

[dev-dependencies]
anyhow = "1.0"
time = { version = "0.3.9", features = ["formatting", "large-dates", "macros"], default-features = false }

[features]
default = ["std"]
std = ["arrayvec/std", "binread/std", "byteorder/std"]

[[example]]
name = "ntfs-shell"
required-features = ["chrono"]
required-features = ["time"]

[package.metadata.docs.rs]
all-features = true
Expand Down
24 changes: 18 additions & 6 deletions examples/ntfs-shell/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ use std::io;
use std::io::{BufReader, Read, Seek, Write};

use anyhow::{anyhow, bail, Context, Result};
use chrono::{DateTime, Utc};
use ntfs::attribute_value::NtfsAttributeValue;
use ntfs::indexes::NtfsFileNameIndex;
use ntfs::structured_values::{
NtfsAttributeList, NtfsFileName, NtfsFileNamespace, NtfsStandardInformation,
};
use ntfs::{Ntfs, NtfsAttribute, NtfsAttributeType, NtfsFile, NtfsReadSeek};
use time::format_description::FormatItem;
use time::macros::format_description;
use time::OffsetDateTime;

use sector_reader::SectorReader;

Expand Down Expand Up @@ -352,18 +354,28 @@ where
}

fn fileinfo_std(attribute: NtfsAttribute) -> Result<()> {
const TIME_FORMAT: &[FormatItem] =
format_description!("[year]-[month]-[day] [hour]:[minute]:[second] UTC");

println!();
println!("{:=^72}", " STANDARD INFORMATION ");

let std_info = attribute.resident_structured_value::<NtfsStandardInformation>()?;

println!("{:34}{:?}", "Attributes:", std_info.file_attributes());

let format = "%F %T UTC";
let atime = DateTime::<Utc>::from(std_info.access_time()).format(format);
let ctime = DateTime::<Utc>::from(std_info.creation_time()).format(format);
let mtime = DateTime::<Utc>::from(std_info.modification_time()).format(format);
let mmtime = DateTime::<Utc>::from(std_info.mft_record_modification_time()).format(format);
let atime = OffsetDateTime::from(std_info.access_time())
.format(TIME_FORMAT)
.unwrap();
let ctime = OffsetDateTime::from(std_info.creation_time())
.format(TIME_FORMAT)
.unwrap();
let mtime = OffsetDateTime::from(std_info.modification_time())
.format(TIME_FORMAT)
.unwrap();
let mmtime = OffsetDateTime::from(std_info.mft_record_modification_time())
.format(TIME_FORMAT)
.unwrap();
println!("{:34}{}", "Access Time:", atime);
println!("{:34}{}", "Creation Time:", ctime);
println!("{:34}{}", "Modification Time:", mtime);
Expand Down
127 changes: 42 additions & 85 deletions src/time.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,26 @@
// Copyright 2021 Colin Finck <colin@reactos.org>
// Copyright 2021-2022 Colin Finck <colin@reactos.org>
// SPDX-License-Identifier: MIT OR Apache-2.0

use binread::BinRead;
use derive_more::From;

#[cfg(any(feature = "chrono", feature = "std"))]
#[cfg(any(feature = "time", feature = "std"))]
use core::convert::TryFrom;

#[cfg(feature = "chrono")]
use {
crate::error::NtfsError,
chrono::{DateTime, Datelike, NaiveDate, Timelike, Utc},
};
#[cfg(feature = "time")]
use {crate::error::NtfsError, time::OffsetDateTime};

#[cfg(feature = "std")]
use std::time::{SystemTime, SystemTimeError};

/// Number of days between 0001-01-01 and 1601-01-01.
#[cfg(feature = "chrono")]
const DAYS_FROM_0001_TO_1601: i32 = 584389;

/// Difference in 100-nanosecond intervals between the Windows/NTFS epoch (1601-01-01) and the Unix epoch (1970-01-01).
#[cfg(feature = "std")]
const EPOCH_DIFFERENCE_IN_INTERVALS: i64 = 116_444_736_000_000_000;
#[cfg(any(feature = "time", feature = "std"))]
const EPOCH_DIFFERENCE_IN_INTERVALS: u64 = 116_444_736_000_000_000;

/// Number of 100-nanosecond intervals in a second.
#[cfg(any(feature = "chrono", feature = "std"))]
#[cfg(any(feature = "time", feature = "std"))]
const INTERVALS_PER_SECOND: u64 = 10_000_000;

/// Number of 100-nanosecond intervals in a day.
#[cfg(feature = "chrono")]
const INTERVALS_PER_DAY: u64 = 24 * 60 * 60 * INTERVALS_PER_SECOND;

/// An NTFS timestamp, used for expressing file times.
///
/// NTFS (and the Windows NT line of operating systems) represent time as an unsigned 64-bit integer
Expand All @@ -46,66 +35,33 @@ impl NtfsTime {
}
}

#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl TryFrom<DateTime<Utc>> for NtfsTime {
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
impl TryFrom<OffsetDateTime> for NtfsTime {
type Error = NtfsError;

fn try_from(dt: DateTime<Utc>) -> Result<Self, Self::Error> {
// First do the time calculations, which safely fit into a u64.
let mut intervals = dt.hour() as u64;

intervals *= 60;
intervals += dt.minute() as u64;

intervals *= 60;
intervals += dt.second() as u64;

intervals *= INTERVALS_PER_SECOND;
intervals += dt.nanosecond() as u64 / 100;

// Now do checked arithmetics for the day calculations, which may
// exceed the lower bounds (years before 1601) or upper bounds
// (dates after approximately 28 May 60056).
let num_days_from_ce = dt.num_days_from_ce();
let num_days_from_1601 = num_days_from_ce
.checked_sub(DAYS_FROM_0001_TO_1601)
.ok_or(NtfsError::InvalidTime)?;
let intervals_days = INTERVALS_PER_DAY
.checked_mul(num_days_from_1601 as u64)
.ok_or(NtfsError::InvalidTime)?;
intervals = intervals
.checked_add(intervals_days)
.ok_or(NtfsError::InvalidTime)?;

Ok(Self(intervals))
fn try_from(dt: OffsetDateTime) -> Result<Self, Self::Error> {
let nanos_since_unix_epoch = dt.unix_timestamp_nanos();
let intervals_since_unix_epoch = nanos_since_unix_epoch / 100;
let intervals_since_windows_epoch =
intervals_since_unix_epoch + EPOCH_DIFFERENCE_IN_INTERVALS as i128;
let nt_timestamp =
u64::try_from(intervals_since_windows_epoch).map_err(|_| NtfsError::InvalidTime)?;

Ok(Self(nt_timestamp))
}
}

#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl From<NtfsTime> for DateTime<Utc> {
fn from(nt: NtfsTime) -> DateTime<Utc> {
let mut remainder = nt.nt_timestamp();

let nano = (remainder % INTERVALS_PER_SECOND) as u32 * 100;
remainder /= INTERVALS_PER_SECOND;
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
impl From<NtfsTime> for OffsetDateTime {
fn from(nt: NtfsTime) -> OffsetDateTime {
let intervals_since_windows_epoch = nt.nt_timestamp() as i128;
let intervals_since_unix_epoch =
intervals_since_windows_epoch - EPOCH_DIFFERENCE_IN_INTERVALS as i128;
let nanos_since_unix_epoch = intervals_since_unix_epoch * 100;

let sec = (remainder % 60) as u32;
remainder /= 60;

let min = (remainder % 60) as u32;
remainder /= 60;

let hour = (remainder % 24) as u32;
remainder /= 24;

let num_days_from_1601 = remainder as i32;
let num_days_from_ce = num_days_from_1601 + DAYS_FROM_0001_TO_1601;

let ndt =
NaiveDate::from_num_days_from_ce(num_days_from_ce).and_hms_nano(hour, min, sec, nano);
DateTime::<Utc>::from_utc(ndt, Utc)
OffsetDateTime::from_unix_timestamp_nanos(nanos_since_unix_epoch).unwrap()
}
}

Expand All @@ -120,7 +76,8 @@ impl TryFrom<SystemTime> for NtfsTime {
* INTERVALS_PER_SECOND
+ duration_since_unix_epoch.subsec_nanos() as u64 / 100;
let intervals_since_windows_epoch =
intervals_since_unix_epoch + EPOCH_DIFFERENCE_IN_INTERVALS as u64;
intervals_since_unix_epoch + EPOCH_DIFFERENCE_IN_INTERVALS;

Ok(Self(intervals_since_windows_epoch))
}
}
Expand All @@ -129,32 +86,32 @@ impl TryFrom<SystemTime> for NtfsTime {
pub(crate) mod tests {
use super::*;

#[cfg(feature = "chrono")]
use chrono::TimeZone;
#[cfg(feature = "time")]
use time::macros::datetime;

pub(crate) const NT_TIMESTAMP_2021_01_01: u64 = 132539328000000000u64;

#[cfg(feature = "chrono")]
#[cfg(feature = "time")]
#[test]
fn test_chrono() {
let dt = Utc.ymd(2013, 1, 5).and_hms(18, 15, 00);
fn test_offsetdatetime() {
let dt = datetime!(2013-01-05 18:15 UTC);
let nt = NtfsTime::try_from(dt).unwrap();
assert_eq!(*nt, 130018833000000000u64);
assert_eq!(nt.nt_timestamp(), 130018833000000000u64);

let dt2 = DateTime::<Utc>::from(nt);
let dt2 = OffsetDateTime::from(nt);
assert_eq!(dt, dt2);

let dt = Utc.ymd(1601, 1, 1).and_hms(0, 0, 0);
let dt = datetime!(1601-01-01 0:00 UTC);
let nt = NtfsTime::try_from(dt).unwrap();
assert_eq!(*nt, 0u64);
assert_eq!(nt.nt_timestamp(), 0u64);

let dt = Utc.ymd(1600, 12, 31).and_hms(23, 59, 59);
let dt = datetime!(1600-12-31 23:59:59 UTC);
assert!(NtfsTime::try_from(dt).is_err());

let dt = Utc.ymd(60056, 5, 28).and_hms(0, 0, 0);
let dt = datetime!(+60056-05-28 0:00 UTC);
assert!(NtfsTime::try_from(dt).is_ok());

let dt = Utc.ymd(60056, 5, 29).and_hms(0, 0, 0);
let dt = datetime!(+60056-05-29 0:00 UTC);
assert!(NtfsTime::try_from(dt).is_err());
}

Expand Down

0 comments on commit d542663

Please sign in to comment.