Skip to content

Commit 0629cda

Browse files
committed
uefi: fs: Add file times plumbing
- Add FileTimes implementation. Signed-off-by: Ayush Singh <[email protected]>
1 parent 5b9007b commit 0629cda

File tree

3 files changed

+147
-27
lines changed

3 files changed

+147
-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: 56 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

@@ -104,12 +125,11 @@ pub(crate) mod system_time_internal {
104125
use crate::mem::MaybeUninit;
105126
use crate::ptr::NonNull;
106127

107-
const SECS_IN_MINUTE: u64 = 60;
108128
const SECS_IN_HOUR: u64 = SECS_IN_MINUTE * 60;
109129
const SECS_IN_DAY: u64 = SECS_IN_HOUR * 24;
110-
const TIMEZONE_DELTA: u64 = 1440 * SECS_IN_MINUTE;
130+
const SYSTEMTIME_TIMEZONE: u64 = 1440 * SECS_IN_MINUTE;
111131

112-
pub fn now() -> Option<SystemTime> {
132+
pub(crate) fn now() -> Option<Time> {
113133
let runtime_services: NonNull<RuntimeServices> = helpers::runtime_services()?;
114134
let mut t: MaybeUninit<Time> = MaybeUninit::uninit();
115135
let r = unsafe {
@@ -119,9 +139,7 @@ pub(crate) mod system_time_internal {
119139
return None;
120140
}
121141

122-
let t = unsafe { t.assume_init() };
123-
124-
Some(SystemTime::from_uefi(t))
142+
Some(unsafe { t.assume_init() })
125143
}
126144

127145
/// This algorithm is a modified form of the one described in the post
@@ -162,7 +180,7 @@ pub(crate) mod system_time_internal {
162180
+ (t.hour as u64) * SECS_IN_HOUR;
163181

164182
// Calculate the offset from 1/1/1900 at timezone -1440 min
165-
let adjusted_localtime_epoc: u64 = localtime_epoch + TIMEZONE_DELTA;
183+
let adjusted_localtime_epoc: u64 = localtime_epoch + SYSTEMTIME_TIMEZONE;
166184

167185
let epoch: u64 = if t.timezone == r_efi::efi::UNSPECIFIED_TIMEZONE {
168186
adjusted_localtime_epoc
@@ -180,16 +198,27 @@ pub(crate) mod system_time_internal {
180198
///
181199
/// The changes are to use 1900-01-01-00:00:00 with timezone -1440 as anchor instead of UNIX
182200
/// epoch used in the original algorithm.
183-
pub(crate) const fn to_uefi(dur: &Duration, timezone: i16, daylight: u8) -> Option<Time> {
201+
pub(crate) const fn to_uefi(dur: &Duration, timezone: i16, daylight: u8) -> Result<Time, i16> {
202+
const MIN_IN_HOUR: u64 = 60;
203+
const MIN_IN_DAY: u64 = MIN_IN_HOUR * 24;
204+
184205
// Check timzone validity
185206
assert!(timezone <= 1440 && timezone >= -1440);
186207

187208
// FIXME(#126043): use checked_sub_signed once stabilized
209+
// This cannot fail for valid SystemTime due to SYSTEMTIME_TIMEZONE
188210
let secs =
189211
dur.as_secs().checked_add_signed((-timezone as i64) * SECS_IN_MINUTE as i64).unwrap();
190212

191213
// Convert to seconds since 1900-01-01-00:00:00 in timezone.
192-
let Some(secs) = secs.checked_sub(TIMEZONE_DELTA) else { return None };
214+
let Some(secs) = secs.checked_sub(SYSTEMTIME_TIMEZONE) else {
215+
// If the current timezone cannot be used, find the closest timezone that will allow the
216+
// conversion to succeed.
217+
let delta = SYSTEMTIME_TIMEZONE - secs;
218+
let new_tz = timezone
219+
- (delta / SECS_IN_MINUTE + if delta % SECS_IN_MINUTE == 0 { 0 } else { 1 }) as i16;
220+
return Err(new_tz);
221+
};
193222

194223
let days = secs / SECS_IN_DAY;
195224
let remaining_secs = secs % SECS_IN_DAY;
@@ -212,9 +241,10 @@ pub(crate) mod system_time_internal {
212241
let minute = ((remaining_secs % SECS_IN_HOUR) / SECS_IN_MINUTE) as u8;
213242
let second = (remaining_secs % SECS_IN_MINUTE) as u8;
214243

215-
// Check Bounds
216-
if y >= 1900 && y <= 9999 {
217-
Some(Time {
244+
// At this point, invalid time will be greater than MAX representable time. It cannot be less
245+
// than minimum time since we already take care of that case above.
246+
if y <= 9999 {
247+
Ok(Time {
218248
year: y as u16,
219249
month: m as u8,
220250
day: d as u8,
@@ -228,7 +258,17 @@ pub(crate) mod system_time_internal {
228258
pad2: 0,
229259
})
230260
} else {
231-
None
261+
assert!(y == 10000);
262+
assert!(m == 1);
263+
264+
let delta = ((d - 1) as u64 * MIN_IN_DAY
265+
+ hour as u64 * MIN_IN_HOUR
266+
+ minute as u64
267+
+ if second == 0 { 0 } else { 1 }) as i16;
268+
let new_tz = timezone + delta;
269+
270+
assert!(new_tz <= 1440 && new_tz >= -1440);
271+
Err(new_tz)
232272
}
233273
}
234274
}

0 commit comments

Comments
 (0)