Skip to content

Commit c7325fc

Browse files
committed
uefi: fs: Add file times plumbing
- Add FileTimes implementation. Signed-off-by: Ayush Singh <ayush@beagleboard.org>
1 parent 5ab6924 commit c7325fc

File tree

3 files changed

+144
-27
lines changed

3 files changed

+144
-27
lines changed

library/std/src/sys/fs/uefi.rs

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::hash::Hash;
77
use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom};
88
use crate::path::{Path, PathBuf};
99
use crate::sys::time::SystemTime;
10-
use crate::sys::unsupported;
10+
use crate::sys::{unsupported, unsupported_err};
1111

1212
#[expect(dead_code)]
1313
const FILE_PERMISSIONS_MASK: u64 = r_efi::protocols::file::READ_ONLY;
@@ -18,6 +18,7 @@ pub struct File(!);
1818
pub struct FileAttr {
1919
attr: u64,
2020
size: u64,
21+
times: FileTimes,
2122
}
2223

2324
pub struct ReadDir(!);
@@ -33,7 +34,11 @@ pub struct OpenOptions {
3334
}
3435

3536
#[derive(Copy, Clone, Debug, Default)]
36-
pub struct FileTimes {}
37+
pub struct FileTimes {
38+
accessed: Option<SystemTime>,
39+
modified: Option<SystemTime>,
40+
created: Option<SystemTime>,
41+
}
3742

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

6267
pub fn modified(&self) -> io::Result<SystemTime> {
63-
unsupported()
68+
self.times.modified.ok_or(unsupported_err())
6469
}
6570

6671
pub fn accessed(&self) -> io::Result<SystemTime> {
67-
unsupported()
72+
self.times.accessed.ok_or(unsupported_err())
6873
}
6974

7075
pub fn created(&self) -> io::Result<SystemTime> {
71-
unsupported()
76+
self.times.created.ok_or(unsupported_err())
7277
}
7378
}
7479

@@ -92,8 +97,13 @@ impl FilePermissions {
9297
}
9398

9499
impl FileTimes {
95-
pub fn set_accessed(&mut self, _t: SystemTime) {}
96-
pub fn set_modified(&mut self, _t: SystemTime) {}
100+
pub fn set_accessed(&mut self, t: SystemTime) {
101+
self.accessed = Some(t);
102+
}
103+
104+
pub fn set_modified(&mut self, t: SystemTime) {
105+
self.modified = Some(t);
106+
}
97107
}
98108

99109
impl FileType {
@@ -386,6 +396,7 @@ mod uefi_fs {
386396
use crate::path::Path;
387397
use crate::ptr::NonNull;
388398
use crate::sys::helpers;
399+
use crate::sys::time::{self, SystemTime};
389400

390401
pub(crate) struct File(NonNull<file::Protocol>);
391402

@@ -533,4 +544,23 @@ mod uefi_fs {
533544

534545
Ok(())
535546
}
547+
548+
/// EDK2 FAT driver uses EFI_UNSPECIFIED_TIMEZONE to represent localtime. So for proper
549+
/// conversion to SystemTime, we use the current time to get the timezone in such cases.
550+
#[expect(dead_code)]
551+
fn uefi_to_systemtime(mut time: r_efi::efi::Time) -> SystemTime {
552+
time.timezone = if time.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE {
553+
time::system_time_internal::now().unwrap().timezone
554+
} else {
555+
time.timezone
556+
};
557+
SystemTime::from_uefi(time)
558+
}
559+
560+
/// Convert to UEFI Time with the current timezone.
561+
#[expect(dead_code)]
562+
fn systemtime_to_uefi(time: SystemTime) -> r_efi::efi::Time {
563+
let now = time::system_time_internal::now().unwrap();
564+
time.to_uefi_loose(now.timezone, now.daylight)
565+
}
536566
}

library/std/src/sys/pal/uefi/tests.rs

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,20 @@ use crate::time::Duration;
88

99
const SECS_IN_MINUTE: u64 = 60;
1010

11+
const MAX_UEFI_TIME: Duration = from_uefi(r_efi::efi::Time {
12+
year: 9999,
13+
month: 12,
14+
day: 31,
15+
hour: 23,
16+
minute: 59,
17+
second: 59,
18+
nanosecond: 999_999_999,
19+
timezone: 1440,
20+
daylight: 0,
21+
pad1: 0,
22+
pad2: 0,
23+
});
24+
1125
#[test]
1226
fn align() {
1327
// UEFI ABI specifies that allocation alignment minimum is always 8. So this can be
@@ -44,7 +58,7 @@ fn systemtime_start() {
4458
};
4559
assert_eq!(from_uefi(&t), Duration::new(0, 0));
4660
assert_eq!(t, to_uefi(&from_uefi(&t), -1440, 0).unwrap());
47-
assert!(to_uefi(&from_uefi(&t), 0, 0).is_none());
61+
assert!(to_uefi(&from_uefi(&t), 0, 0).is_err());
4862
}
4963

5064
#[test]
@@ -64,7 +78,7 @@ fn systemtime_utc_start() {
6478
};
6579
assert_eq!(from_uefi(&t), Duration::new(1440 * SECS_IN_MINUTE, 0));
6680
assert_eq!(t, to_uefi(&from_uefi(&t), 0, 0).unwrap());
67-
assert!(to_uefi(&from_uefi(&t), -1440, 0).is_some());
81+
assert!(to_uefi(&from_uefi(&t), -1440, 0).is_err());
6882
}
6983

7084
#[test]
@@ -82,8 +96,44 @@ fn systemtime_end() {
8296
daylight: 0,
8397
pad2: 0,
8498
};
85-
assert!(to_uefi(&from_uefi(&t), 1440, 0).is_some());
86-
assert!(to_uefi(&from_uefi(&t), 1439, 0).is_none());
99+
assert!(to_uefi(&from_uefi(&t), 1440, 0).is_ok());
100+
assert!(to_uefi(&from_uefi(&t), 1439, 0).is_err());
101+
}
102+
103+
#[test]
104+
fn min_time() {
105+
let inp = Duration::from_secs(1440 * SECS_IN_MINUTE);
106+
let new_tz = to_uefi(&inp, 1440, 0).err().unwrap();
107+
assert_eq!(new_tz, 0);
108+
assert!(to_uefi(&inp, new_tz, 0).is_ok());
109+
110+
let inp = Duration::from_secs(1450 * SECS_IN_MINUTE);
111+
let new_tz = to_uefi(&inp, 1440, 0).err().unwrap();
112+
assert_eq!(new_tz, 10);
113+
assert!(to_uefi(&inp, new_tz, 0).is_ok());
114+
115+
let inp = Duration::from_secs(1450 * SECS_IN_MINUTE + 10);
116+
let new_tz = to_uefi(&inp, 1440, 0).err().unwrap();
117+
assert_eq!(new_tz, 9);
118+
assert!(to_uefi(&inp, new_tz, 0).is_ok());
119+
}
120+
121+
#[test]
122+
fn max_time() {
123+
let inp = MAX_UEFI_TIME;
124+
let new_tz = to_uefi(&inp, -1440, 0).err().unwrap();
125+
assert_eq!(new_tz, 1440);
126+
assert!(to_uefi(&inp, new_tz, 0).is_ok());
127+
128+
let inp = MAX_UEFI_TIME - Duration::from_secs(1440 * SECS_IN_MINUTE);
129+
let new_tz = to_uefi(&inp, -1440, 0).err().unwrap();
130+
assert_eq!(new_tz, 0);
131+
assert!(to_uefi(&inp, new_tz, 0).is_ok());
132+
133+
let inp = MAX_UEFI_TIME - Duration::from_secs(1440 * SECS_IN_MINUTE + 10);
134+
let new_tz = to_uefi(&inp, -1440, 0).err().unwrap();
135+
assert_eq!(new_tz, 0);
136+
assert!(to_uefi(&inp, new_tz, 0).is_ok());
87137
}
88138

89139
// UEFI IoSlice and IoSliceMut Tests

library/std/src/sys/pal/uefi/time.rs

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::time::Duration;
22

3+
const SECS_IN_MINUTE: u64 = 60;
4+
35
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
46
pub struct Instant(Duration);
57

@@ -70,13 +72,32 @@ impl SystemTime {
7072
Self(system_time_internal::from_uefi(&t))
7173
}
7274

73-
#[expect(dead_code)]
74-
pub(crate) const fn to_uefi(self, timezone: i16, daylight: u8) -> Option<r_efi::efi::Time> {
75-
system_time_internal::to_uefi(&self.0, timezone, daylight)
75+
pub(crate) const fn to_uefi(
76+
self,
77+
timezone: i16,
78+
daylight: u8,
79+
) -> Result<r_efi::efi::Time, i16> {
80+
// system_time_internal::to_uefi requires a valid timezone. In case of unspecified timezone,
81+
// we just pass 0 since it is assumed that no timezone related adjustments are required.
82+
if timezone == r_efi::efi::UNSPECIFIED_TIMEZONE {
83+
system_time_internal::to_uefi(&self.0, 0, daylight)
84+
} else {
85+
system_time_internal::to_uefi(&self.0, timezone, daylight)
86+
}
87+
}
88+
89+
/// Create UEFI Time with the closest timezone (minute offset) that still allows the time to be
90+
/// represented.
91+
pub(crate) fn to_uefi_loose(self, timezone: i16, daylight: u8) -> r_efi::efi::Time {
92+
match self.to_uefi(timezone, daylight) {
93+
Ok(x) => x,
94+
Err(tz) => self.to_uefi(tz, daylight).unwrap(),
95+
}
7696
}
7797

7898
pub fn now() -> SystemTime {
7999
system_time_internal::now()
100+
.map(Self::from_uefi)
80101
.unwrap_or_else(|| panic!("time not implemented on this platform"))
81102
}
82103

@@ -117,12 +138,11 @@ pub(crate) mod system_time_internal {
117138
use crate::mem::MaybeUninit;
118139
use crate::ptr::NonNull;
119140

120-
const SECS_IN_MINUTE: u64 = 60;
121141
const SECS_IN_HOUR: u64 = SECS_IN_MINUTE * 60;
122142
const SECS_IN_DAY: u64 = SECS_IN_HOUR * 24;
123-
const TIMEZONE_DELTA: u64 = 1440 * SECS_IN_MINUTE;
143+
const SYSTEMTIME_TIMEZONE: u64 = 1440 * SECS_IN_MINUTE;
124144

125-
pub fn now() -> Option<SystemTime> {
145+
pub(crate) fn now() -> Option<Time> {
126146
let runtime_services: NonNull<RuntimeServices> = helpers::runtime_services()?;
127147
let mut t: MaybeUninit<Time> = MaybeUninit::uninit();
128148
let r = unsafe {
@@ -132,9 +152,7 @@ pub(crate) mod system_time_internal {
132152
return None;
133153
}
134154

135-
let t = unsafe { t.assume_init() };
136-
137-
Some(SystemTime::from_uefi(t))
155+
Some(unsafe { t.assume_init() })
138156
}
139157

140158
/// This algorithm is a modified form of the one described in the post
@@ -175,7 +193,7 @@ pub(crate) mod system_time_internal {
175193
+ (t.hour as u64) * SECS_IN_HOUR;
176194

177195
// Calculate the offset from 1/1/1900 at timezone -1440 min
178-
let adjusted_localtime_epoc: u64 = localtime_epoch + TIMEZONE_DELTA;
196+
let adjusted_localtime_epoc: u64 = localtime_epoch + SYSTEMTIME_TIMEZONE;
179197

180198
let epoch: u64 = if t.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE {
181199
adjusted_localtime_epoc
@@ -193,16 +211,24 @@ pub(crate) mod system_time_internal {
193211
///
194212
/// The changes are to use 1900-01-01-00:00:00 with timezone -1440 as anchor instead of UNIX
195213
/// epoch used in the original algorithm.
196-
pub(crate) const fn to_uefi(dur: &Duration, timezone: i16, daylight: u8) -> Option<Time> {
214+
pub(crate) const fn to_uefi(dur: &Duration, timezone: i16, daylight: u8) -> Result<Time, i16> {
215+
const MIN_IN_HOUR: u64 = 60;
216+
const MIN_IN_DAY: u64 = MIN_IN_HOUR * 24;
217+
197218
// Check timzone validity
198219
assert!(timezone <= 1440 && timezone >= -1440);
199220

200221
// FIXME(#126043): use checked_sub_signed once stabilized
222+
// This cannot fail for valid SystemTime due to SYSTEMTIME_TIMEZONE
201223
let secs =
202224
dur.as_secs().checked_add_signed((-timezone as i64) * SECS_IN_MINUTE as i64).unwrap();
203225

204226
// Convert to seconds since 1900-01-01-00:00:00 in timezone.
205-
let Some(secs) = secs.checked_sub(TIMEZONE_DELTA) else { return None };
227+
let Some(secs) = secs.checked_sub(SYSTEMTIME_TIMEZONE) else {
228+
let new_tz =
229+
(secs / SECS_IN_MINUTE - if secs % SECS_IN_MINUTE == 0 { 0 } else { 1 }) as i16;
230+
return Err(new_tz);
231+
};
206232

207233
let days = secs / SECS_IN_DAY;
208234
let remaining_secs = secs % SECS_IN_DAY;
@@ -225,9 +251,10 @@ pub(crate) mod system_time_internal {
225251
let minute = ((remaining_secs % SECS_IN_HOUR) / SECS_IN_MINUTE) as u8;
226252
let second = (remaining_secs % SECS_IN_MINUTE) as u8;
227253

228-
// Check Bounds
229-
if y >= 1900 && y <= 9999 {
230-
Some(Time {
254+
// At this point, invalid time will be greater than MAX representable time. It cannot be less
255+
// than minimum time since we already take care of that case above.
256+
if y <= 9999 {
257+
Ok(Time {
231258
year: y as u16,
232259
month: m as u8,
233260
day: d as u8,
@@ -241,7 +268,17 @@ pub(crate) mod system_time_internal {
241268
pad2: 0,
242269
})
243270
} else {
244-
None
271+
assert!(y == 10000);
272+
assert!(m == 1);
273+
274+
let delta = ((d - 1) as u64 * MIN_IN_DAY
275+
+ hour as u64 * MIN_IN_HOUR
276+
+ minute as u64
277+
+ if second == 0 { 0 } else { 1 }) as i16;
278+
let new_tz = timezone + delta;
279+
280+
assert!(new_tz <= 1440 && new_tz >= -1440);
281+
Err(new_tz)
245282
}
246283
}
247284
}

0 commit comments

Comments
 (0)