Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions library/std/src/sys/fs/uefi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ pub struct File(!);
pub struct FileAttr {
attr: u64,
size: u64,
created: r_efi::efi::Time,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UEFI allows creation time to be set, so created should be part of the FileTimes struct (a future PR can then use it to implement set_created in an extension trait like on Windows and Apple platforms).

times: FileTimes,
}

pub struct ReadDir(!);
Expand All @@ -33,7 +35,10 @@ pub struct OpenOptions {
}

#[derive(Copy, Clone, Debug, Default)]
pub struct FileTimes {}
pub struct FileTimes {
accessed: r_efi::efi::Time,
modified: r_efi::efi::Time,
}

#[derive(Clone, PartialEq, Eq, Debug)]
// Bool indicates if file is readonly
Expand All @@ -60,15 +65,15 @@ impl FileAttr {
}

pub fn modified(&self) -> io::Result<SystemTime> {
unsupported()
Ok(SystemTime::from_uefi(self.times.modified))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FAT timestamps are always stored in local time in the system timezone, and the EDK II FAT driver uses EFI_UNSPECIFIED_TIMEZONE to represent this. Therefore, if the modified/accessed/created EFI time has a timezone of unspecified, this conversion should use the timezone from the runtime services GetTime in order to be compatible.

}

pub fn accessed(&self) -> io::Result<SystemTime> {
unsupported()
Ok(SystemTime::from_uefi(self.times.accessed))
}

pub fn created(&self) -> io::Result<SystemTime> {
unsupported()
Ok(SystemTime::from_uefi(self.created))
}
}

Expand All @@ -92,8 +97,13 @@ impl FilePermissions {
}

impl FileTimes {
pub fn set_accessed(&mut self, _t: SystemTime) {}
pub fn set_modified(&mut self, _t: SystemTime) {}
pub fn set_accessed(&mut self, t: SystemTime) {
self.accessed = t.to_uefi_loose(self.accessed.timezone, self.accessed.daylight);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.timezone and .daylight will always be zero here (unless set_accessed/set_modified was called previously and that time had to have it's timezone adjusted to fit in the range of EFI_TIME). The EDK II FAT driver will ignore the timezone when setting the time.. As FAT stores timestamps in the system local time, this conversion should use the current system timezone from the runtime services GetTime in order to be compatible with that driver's behaviour. This conversion should be done in the File::set_times implementation to have consistent behaviour in the (unlikely) event the timezone is changed between the call to set_accessed and the actual setting of the file times in File::set_times (which I believe isn't being implemented in this PR); therefore the FileTimes struct should look something like struct FileTimes { accessed: SystemTime, modified: SystemTime, created: SystemTime }.

}

pub fn set_modified(&mut self, t: SystemTime) {
self.modified = t.to_uefi_loose(self.modified.timezone, self.modified.daylight);
}
}

impl FileType {
Expand Down
58 changes: 54 additions & 4 deletions library/std/src/sys/pal/uefi/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ use crate::time::Duration;

const SECS_IN_MINUTE: u64 = 60;

const MAX_UEFI_TIME: Duration = from_uefi(r_efi::efi::Time {
year: 9999,
month: 12,
day: 31,
hour: 23,
minute: 59,
second: 59,
nanosecond: 999_999_999,
timezone: 1440,
daylight: 0,
pad1: 0,
pad2: 0,
});

#[test]
fn align() {
// UEFI ABI specifies that allocation alignment minimum is always 8. So this can be
Expand Down Expand Up @@ -44,7 +58,7 @@ fn systemtime_start() {
};
assert_eq!(from_uefi(&t), Duration::new(0, 0));
assert_eq!(t, to_uefi(&from_uefi(&t), -1440, 0).unwrap());
assert!(to_uefi(&from_uefi(&t), 0, 0).is_none());
assert!(to_uefi(&from_uefi(&t), 0, 0).is_err());
}

#[test]
Expand All @@ -64,7 +78,7 @@ fn systemtime_utc_start() {
};
assert_eq!(from_uefi(&t), Duration::new(1440 * SECS_IN_MINUTE, 0));
assert_eq!(t, to_uefi(&from_uefi(&t), 0, 0).unwrap());
assert!(to_uefi(&from_uefi(&t), -1440, 0).is_some());
assert!(to_uefi(&from_uefi(&t), -1440, 0).is_err());
}

#[test]
Expand All @@ -82,8 +96,44 @@ fn systemtime_end() {
daylight: 0,
pad2: 0,
};
assert!(to_uefi(&from_uefi(&t), 1440, 0).is_some());
assert!(to_uefi(&from_uefi(&t), 1439, 0).is_none());
assert!(to_uefi(&from_uefi(&t), 1440, 0).is_ok());
assert!(to_uefi(&from_uefi(&t), 1439, 0).is_err());
}

#[test]
fn min_time() {
let inp = Duration::from_secs(1440 * SECS_IN_MINUTE);
let new_tz = to_uefi(&inp, 1440, 0).err().unwrap();
assert_eq!(new_tz, 0);
assert!(to_uefi(&inp, new_tz, 0).is_ok());

let inp = Duration::from_secs(1450 * SECS_IN_MINUTE);
let new_tz = to_uefi(&inp, 1440, 0).err().unwrap();
assert_eq!(new_tz, 10);
assert!(to_uefi(&inp, new_tz, 0).is_ok());

let inp = Duration::from_secs(1450 * SECS_IN_MINUTE + 10);
let new_tz = to_uefi(&inp, 1440, 0).err().unwrap();
assert_eq!(new_tz, 9);
assert!(to_uefi(&inp, new_tz, 0).is_ok());
}

#[test]
fn max_time() {
let inp = MAX_UEFI_TIME;
let new_tz = to_uefi(&inp, -1440, 0).err().unwrap();
assert_eq!(new_tz, 1440);
assert!(to_uefi(&inp, new_tz, 0).is_ok());

let inp = MAX_UEFI_TIME - Duration::from_secs(1440 * SECS_IN_MINUTE);
let new_tz = to_uefi(&inp, -1440, 0).err().unwrap();
assert_eq!(new_tz, 0);
assert!(to_uefi(&inp, new_tz, 0).is_ok());

let inp = MAX_UEFI_TIME - Duration::from_secs(1440 * SECS_IN_MINUTE + 10);
let new_tz = to_uefi(&inp, -1440, 0).err().unwrap();
assert_eq!(new_tz, 0);
assert!(to_uefi(&inp, new_tz, 0).is_ok());
}

// UEFI IoSlice and IoSliceMut Tests
Expand Down
49 changes: 40 additions & 9 deletions library/std/src/sys/pal/uefi/time.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::time::Duration;

const SECS_IN_MINUTE: u64 = 60;

#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct Instant(Duration);

Expand Down Expand Up @@ -70,11 +72,23 @@ impl SystemTime {
Self(system_time_internal::from_uefi(&t))
}

#[expect(dead_code)]
pub(crate) const fn to_uefi(self, timezone: i16, daylight: u8) -> Option<r_efi::efi::Time> {
pub(crate) const fn to_uefi(
self,
timezone: i16,
daylight: u8,
) -> Result<r_efi::efi::Time, i16> {
system_time_internal::to_uefi(&self.0, timezone, daylight)
}

/// Create UEFI Time with the closest timezone (minute offset) that still allows the time to be
/// represented.
pub(crate) fn to_uefi_loose(self, timezone: i16, daylight: u8) -> r_efi::efi::Time {
match self.to_uefi(timezone, daylight) {
Ok(x) => x,
Err(tz) => self.to_uefi(tz, daylight).unwrap(),
}
}

pub fn now() -> SystemTime {
system_time_internal::now()
.unwrap_or_else(|| panic!("time not implemented on this platform"))
Expand Down Expand Up @@ -117,7 +131,6 @@ pub(crate) mod system_time_internal {
use crate::mem::MaybeUninit;
use crate::ptr::NonNull;

const SECS_IN_MINUTE: u64 = 60;
const SECS_IN_HOUR: u64 = SECS_IN_MINUTE * 60;
const SECS_IN_DAY: u64 = SECS_IN_HOUR * 24;
const TIMEZONE_DELTA: u64 = 1440 * SECS_IN_MINUTE;
Expand Down Expand Up @@ -193,7 +206,10 @@ pub(crate) mod system_time_internal {
///
/// The changes are to use 1900-01-01-00:00:00 with timezone -1440 as anchor instead of UNIX
/// epoch used in the original algorithm.
pub(crate) const fn to_uefi(dur: &Duration, timezone: i16, daylight: u8) -> Option<Time> {
pub(crate) const fn to_uefi(dur: &Duration, timezone: i16, daylight: u8) -> Result<Time, i16> {
const MIN_IN_HOUR: u64 = 60;
const MIN_IN_DAY: u64 = MIN_IN_HOUR * 24;

// Check timzone validity
assert!(timezone <= 1440 && timezone >= -1440);

Expand All @@ -202,7 +218,11 @@ pub(crate) mod system_time_internal {
dur.as_secs().checked_add_signed((-timezone as i64) * SECS_IN_MINUTE as i64).unwrap();

// Convert to seconds since 1900-01-01-00:00:00 in timezone.
let Some(secs) = secs.checked_sub(TIMEZONE_DELTA) else { return None };
let Some(secs) = secs.checked_sub(TIMEZONE_DELTA) else {
let new_tz =
(secs / SECS_IN_MINUTE - if secs % SECS_IN_MINUTE == 0 { 0 } else { 1 }) as i16;
return Err(new_tz);
};

let days = secs / SECS_IN_DAY;
let remaining_secs = secs % SECS_IN_DAY;
Expand All @@ -225,9 +245,10 @@ pub(crate) mod system_time_internal {
let minute = ((remaining_secs % SECS_IN_HOUR) / SECS_IN_MINUTE) as u8;
let second = (remaining_secs % SECS_IN_MINUTE) as u8;

// Check Bounds
if y >= 1900 && y <= 9999 {
Some(Time {
// At this point, invalid time will be greater than MAX representable time. It cannot be less
// than minimum time since we already take care of that case above.
if y <= 9999 {
Ok(Time {
year: y as u16,
month: m as u8,
day: d as u8,
Expand All @@ -241,7 +262,17 @@ pub(crate) mod system_time_internal {
pad2: 0,
})
} else {
None
assert!(y == 10000);
assert!(m == 1);

let delta = ((d - 1) as u64 * MIN_IN_DAY
+ hour as u64 * MIN_IN_HOUR
+ minute as u64
+ if second == 0 { 0 } else { 1 }) as i16;
let new_tz = timezone + delta;

assert!(new_tz <= 1440 && new_tz >= -1440);
Err(new_tz)
}
}
}
Expand Down
Loading