-
Notifications
You must be signed in to change notification settings - Fork 13.7k
uefi: fs: Add file times plumbing #138918
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,7 @@ use crate::hash::Hash; | |
use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; | ||
use crate::path::{Path, PathBuf}; | ||
use crate::sys::time::SystemTime; | ||
use crate::sys::unsupported; | ||
use crate::sys::{unsupported, unsupported_err}; | ||
|
||
#[expect(dead_code)] | ||
const FILE_PERMISSIONS_MASK: u64 = r_efi::protocols::file::READ_ONLY; | ||
|
@@ -18,6 +18,7 @@ pub struct File(!); | |
pub struct FileAttr { | ||
attr: u64, | ||
size: u64, | ||
times: FileTimes, | ||
} | ||
|
||
pub struct ReadDir(!); | ||
|
@@ -33,7 +34,11 @@ pub struct OpenOptions { | |
} | ||
|
||
#[derive(Copy, Clone, Debug, Default)] | ||
pub struct FileTimes {} | ||
pub struct FileTimes { | ||
accessed: Option<SystemTime>, | ||
modified: Option<SystemTime>, | ||
created: Option<SystemTime>, | ||
} | ||
|
||
#[derive(Clone, PartialEq, Eq, Debug)] | ||
// Bool indicates if file is readonly | ||
|
@@ -60,15 +65,15 @@ impl FileAttr { | |
} | ||
|
||
pub fn modified(&self) -> io::Result<SystemTime> { | ||
unsupported() | ||
self.times.modified.ok_or(unsupported_err()) | ||
} | ||
|
||
pub fn accessed(&self) -> io::Result<SystemTime> { | ||
unsupported() | ||
self.times.accessed.ok_or(unsupported_err()) | ||
} | ||
|
||
pub fn created(&self) -> io::Result<SystemTime> { | ||
unsupported() | ||
self.times.created.ok_or(unsupported_err()) | ||
} | ||
} | ||
|
||
|
@@ -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 = Some(t); | ||
} | ||
|
||
pub fn set_modified(&mut self, t: SystemTime) { | ||
self.modified = Some(t); | ||
} | ||
} | ||
|
||
impl FileType { | ||
|
@@ -386,6 +396,7 @@ mod uefi_fs { | |
use crate::path::Path; | ||
use crate::ptr::NonNull; | ||
use crate::sys::helpers; | ||
use crate::sys::time::{self, SystemTime}; | ||
|
||
pub(crate) struct File(NonNull<file::Protocol>); | ||
|
||
|
@@ -533,4 +544,23 @@ mod uefi_fs { | |
|
||
Ok(()) | ||
} | ||
|
||
/// EDK2 FAT driver uses EFI_UNSPECIFIED_TIMEZONE to represent localtime. So for proper | ||
/// conversion to SystemTime, we use the current time to get the timezone in such cases. | ||
#[expect(dead_code)] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why dead code? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, just to keep the PRs small to review, and maintain continuity in future PRs. Any interactions with these conversions will require implementing Also, the to and fro conversion are strongly related. So best to present them in the same PR. It might be 2026 by the time that I start upstreaming code that requires conversion from UEFI to systemtime. So I would probably forget a lot of the important information in the current discussion. Having a conversion function already present saves having to repeat the same discussion (or worse, letting wrong code be merged). |
||
fn uefi_to_systemtime(mut time: r_efi::efi::Time) -> SystemTime { | ||
time.timezone = if time.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE { | ||
time::system_time_internal::now().unwrap().timezone | ||
} else { | ||
time.timezone | ||
}; | ||
SystemTime::from_uefi(time) | ||
} | ||
|
||
/// Convert to UEFI Time with the current timezone. | ||
#[expect(dead_code)] | ||
fn systemtime_to_uefi(time: SystemTime) -> r_efi::efi::Time { | ||
let now = time::system_time_internal::now().unwrap(); | ||
time.to_uefi_loose(now.timezone, now.daylight) | ||
} | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we messed up in #139806. The earliest representable time in UEFI is in timezone +1440, not -1440: 1900-01-01-00:00:00+24:00 is 1899-12-31-00:00:00Z. Could you please check that all timezone conversions are correct:
|
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); | ||
|
||
|
@@ -70,13 +72,32 @@ 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> { | ||
system_time_internal::to_uefi(&self.0, timezone, daylight) | ||
pub(crate) const fn to_uefi( | ||
self, | ||
timezone: i16, | ||
daylight: u8, | ||
) -> Result<r_efi::efi::Time, i16> { | ||
// system_time_internal::to_uefi requires a valid timezone. In case of unspecified timezone, | ||
// we just pass 0 since it is assumed that no timezone related adjustments are required. | ||
if timezone == r_efi::efi::UNSPECIFIED_TIMEZONE { | ||
system_time_internal::to_uefi(&self.0, 0, daylight) | ||
} else { | ||
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() | ||
.map(Self::from_uefi) | ||
.unwrap_or_else(|| panic!("time not implemented on this platform")) | ||
} | ||
|
||
|
@@ -117,12 +138,11 @@ 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; | ||
const SYSTEMTIME_TIMEZONE: u64 = 1440 * SECS_IN_MINUTE; | ||
|
||
pub fn now() -> Option<SystemTime> { | ||
pub(crate) fn now() -> Option<Time> { | ||
let runtime_services: NonNull<RuntimeServices> = helpers::runtime_services()?; | ||
let mut t: MaybeUninit<Time> = MaybeUninit::uninit(); | ||
let r = unsafe { | ||
|
@@ -132,9 +152,7 @@ pub(crate) mod system_time_internal { | |
return None; | ||
} | ||
|
||
let t = unsafe { t.assume_init() }; | ||
|
||
Some(SystemTime::from_uefi(t)) | ||
Some(unsafe { t.assume_init() }) | ||
} | ||
|
||
/// This algorithm is a modified form of the one described in the post | ||
|
@@ -175,7 +193,7 @@ pub(crate) mod system_time_internal { | |
+ (t.hour as u64) * SECS_IN_HOUR; | ||
|
||
// Calculate the offset from 1/1/1900 at timezone -1440 min | ||
let adjusted_localtime_epoc: u64 = localtime_epoch + TIMEZONE_DELTA; | ||
let adjusted_localtime_epoc: u64 = localtime_epoch + SYSTEMTIME_TIMEZONE; | ||
|
||
let epoch: u64 = if t.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE { | ||
adjusted_localtime_epoc | ||
|
@@ -193,16 +211,24 @@ 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); | ||
|
||
// FIXME(#126043): use checked_sub_signed once stabilized | ||
// This cannot fail for valid SystemTime due to SYSTEMTIME_TIMEZONE | ||
let secs = | ||
dur.as_secs().checked_add_signed((-timezone as i64) * SECS_IN_MINUTE as i64).unwrap(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the wrong sign – you need to add the new timezone offset. Also, this will panic for positive timezones when the duration is very small – the overflow check should occur below. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The UEFI fields are in locatime. And it is given in the spec that |
||
|
||
// 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(SYSTEMTIME_TIMEZONE) else { | ||
let new_tz = | ||
(secs / SECS_IN_MINUTE - if secs % SECS_IN_MINUTE == 0 { 0 } else { 1 }) as i16; | ||
return Err(new_tz); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a usecase for |
||
}; | ||
|
||
let days = secs / SECS_IN_DAY; | ||
let remaining_secs = secs % SECS_IN_DAY; | ||
|
@@ -225,9 +251,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, | ||
|
@@ -241,7 +268,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) | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is returning a Result here enforced by higher-level APIs in Rust STD? If not, why not return
Option<SystemTime>
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, it is.