-
Notifications
You must be signed in to change notification settings - Fork 13.5k
std: sys: pal: uefi: Overhaul Time #139806
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?
Conversation
library/std/src/sys/pal/uefi/time.rs
Outdated
} | ||
|
||
pub(crate) const fn checked_add(&self, dur: Duration) -> Self { | ||
let temp: u32 = self.usecs + dur.subsec_nanos(); |
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.
you're combining something called usecs
with something called nanos
that doesn't seem to be correct?
EDIT: it looks like usecs
should be called nanos thruout the file?
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.
I have renamed it to nanos
now. The reason for using nanos
instead of normal microseconds (which is uses in unix) is because nanoseconds are stored in the UEFI Time normally.
52207df
to
f4cd07a
Compare
@rustbot label +O-UEFI |
f4cd07a
to
451a576
Compare
This still doesn't solve the problem that Then, there is a design question: I think it might be more advantageous to convert the time and date directly into seconds (just like in the old version) as all of the operations use seconds, and it's unlikely that one would create a |
You mean they should fail for bounds outside of those defined here? Or should it fail when the values don't fit in the underlying data structure?
Ok, but should unspecified to unspecified calculation be fine? Currently the timezone on my qemu also shows up as unspecified, so I think they should be allowed.
Ok, I was thinking the same as all that to and fro conversion for everything was getting a bit too much. |
I think we should use the defined bounds (so years from 1900–9999, etc.).
I agree. Only unspecified-specified calculations are undefined.
😄 yeah... I'd maybe still choose a different timebase than UNIX time. You could e.g. use 1900-01-01-00:00,0[local] as base, which would allow you to use |
So how should conversion be done? UEFI -> Unix -> Anchor to 1900-01-01-00:00,0 ? |
The source for the algorithm you cite already does a conversion from a timebase of 0000-03-01 to 1970-01-01 by adding/subtracting an offset from the day count.
We'd simply need to find the offset for 1900-01-01, use it in the algorithm instead and adjust the UNIX_EPOCH constant accordingly. |
By my calculation (well, by inputting the time into the algorithm), the day offset is 693901 for 1900-01-01. |
36dd216
to
1e8af15
Compare
I have adjusted both conversion offsets and run some initial tests with time anchored to 1900-01-01. Additionally, I have added checks to ensure that the results of calculations are representable in UEFI Time. |
ping @joboet |
I don't think keeping track of the timezone in
|
So, the timestamp in
Well, that does explain the weird behavior I was observing on OVMF. I can see that I get unspecified timezone, even though I can see the timestamp is UTC. Why does
Do you have any idea what Project Mu does? If everyone disregards the spec, I guess we might as well. But we probably still need to check. |
Some RTCs always store the current time in UTC, and their drivers will adjust the returned time for the
Project Mu is based on EDK II and it appears to have not changed how FAT timestamps are handled (it also seems to have the same RTC drivers more or less AFAICT). |
@beetrees Sorry for the late reply. Was busy with college stuff. I have been pondering things a bit, and I am not sure why we need to do any modification to the time we get from UEFI in Rust. Let's look at Rust's Construct a new instanceThe only way to do this, is So any instance used in comparison and other things will also only come from the same way of construction. Note: If someone calls SetTime, and goes from Unspecified to UTC or vice versa, and then tries to copare to previous time, then I personally would consider it a user error. Internal UsageThere might be use cases where we do want to interpret SystemTime Timezone differently, but I feel like that should be on a case by case basis. The base |
A |
3fb7543
to
51e4cb2
Compare
Good point. I have updated the PR. |
library/std/src/sys/pal/uefi/time.rs
Outdated
// Check if can be represented in UEFI | ||
if temp.to_uefi(0, 0).is_some() { Some(temp) } else { None } |
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.
// Check if can be represented in UEFI | |
if temp.to_uefi(0, 0).is_some() { Some(temp) } else { None } | |
Some(temp) |
I don't think this check is necessary here. Many other platforms (e.g. many 32-bit Unix targets such as x86 FreeBSD) allow SystemTime
to have a greater range than the underlying system time representation. If a SystemTime
doesn't fit in the underlying system time representation, then any operations that need to convert to the underlying system representation (currently only File::set_times
) will fail with an InvalidInput
IO error. Additionally, what time's are representable in the UEFI time format will depend on the timezone, so this check disallow some representable times that are representable in the UEFI time format in some timezones but not UTC.
Could you also add a test to check that the minimum and maximum possible |
I discussed this extensively with @beetrees at the Rust All-Hands. We agreed that there are four design constraints that we should aim to satisfy. Two constraints are imposed by
two are imposed by the common practice on UEFI platforms
and one is just nice to have (especially because of things like file times)
Obviously, 1. and 3. conflict with each other, as valid UEFI times do not necessarily remain valid when converting to UTC. We settled on resolving this by making the conversion to UTC best-effort: If the time value is not representable in UTC, then the remaining minutes are stored in the time-zone field. That way, we can claim to be specification-conformant (modulo the interpretation of the unspecified timezone) and can blame potential time shifts on the UEFI implementation while still ensuring that things work most of the time. What are you're thoughts here, Ayush? |
I think the modulo unspecified timezone is wrong. According to spec, timezone should be
I am ok with it. I will try to update the PR as soon as I get time. |
51e4cb2
to
ac75938
Compare
After some experimenting, I think it would be better to use 1900/1/1 at timezone -1440 min as the anchor since that is the earliest UEFI time according to spec. |
library/std/src/sys/pal/uefi/time.rs
Outdated
/// When a Timezone is specified, the stored Duration is in UTC. If timezone is unspecified, then | ||
/// the timezone is assumed to be in UTC. | ||
/// | ||
/// UEFI SystemTime is stored as Duration from 1900-01-01-00:00:00 |
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.
This should mention the timezone that the lower bound is in.
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.
Added
library/std/src/sys/pal/uefi/time.rs
Outdated
let temp = Self(self.0.checked_add(*other)?); | ||
|
||
// Check if can be represented in UEFI | ||
if temp.to_uefi(0, 0).is_some() { Some(temp) } else { None } |
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.
This needs to be updated to reflect the new upper bound for SystemTime
(which is 9999-12-31 23:59:59.999999999 in timezone 1440). I'd suggest just having a MAX
constant and checking against that.
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.
Done
library/std/src/sys/pal/uefi/time.rs
Outdated
let temp = Self(self.0.checked_sub(*other)?); | ||
|
||
// Check if can be represented in UEFI | ||
if temp.to_uefi(0, 0).is_some() { Some(temp) } else { None } |
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.
let temp = Self(self.0.checked_sub(*other)?); | |
// Check if can be represented in UEFI | |
if temp.to_uefi(0, 0).is_some() { Some(temp) } else { None } | |
Some(Self(self.0.checked_sub(*other)?)) |
This check isn't needed as the minimum SystemTime
is represented as 0 and Duration
is unsigned.
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.
Removed
library/std/src/sys/pal/uefi/time.rs
Outdated
assert!(t.month <= 12); | ||
assert!(t.month != 0); |
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.
Could add validity checks for the other fields of Time
, not just month
.
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.
Added
library/std/src/sys/pal/uefi/time.rs
Outdated
assert!(timezone <= 1440); | ||
assert!(timezone >= -1440); | ||
|
||
let secs: u64 = if timezone == r_efi::efi::UNSPECIFIED_TIMEZONE { |
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.
timezone == r_efi::efi::UNSPECIFIED_TIMEZONE
cannot be true at the moment as the assertions check timezone
is between -1440 and 1440. Since it seems reasonable to have all SystemTime
's we convert to Time
have an actual timezone
, I think it's reasonable to just remove the if timezone == r_efi::efi::UNSPECIFIED_TIMEZONE {
branch.
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.
Well, actually that assumption is probably not going to hold true. I am guessing that we will want to keep the time in the same timezone as what was being used previously by the driver (like FileIO, Networking, etc). And this timezone can be unspecified.
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.
In that scenario, to make the assumption explicit, I'd expect us to pass the timezone back as UTC, or whatever else we've assumed the UNSPECIFIED_TIMEZONE
to be; e.g. for file created/modified/accessed timestamps we'll probably want to assume that UNSPECIFIED_TIMEZONE
is the same timezone as the timezone of GetTime()
as that's what the EDK II FAT driver assumes.
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.
Ok, I have removed the unspecified branch.
ac75938
to
c15d6ca
Compare
This comment has been minimized.
This comment has been minimized.
c15d6ca
to
130703e
Compare
assert!(t.day <= 31 && t.day != 0); | ||
|
||
assert!(t.second < 60); | ||
assert!(t.hour < 24); |
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.
missing minute check?
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.
Added
130703e
to
46af23b
Compare
Use a time representation with 1900-01-01-00:00:00 at timezone -1440 min as anchor. This is the earliest time supported in UEFI. Signed-off-by: Ayush Singh <[email protected]>
library/std/src/sys/pal/uefi/time.rs
Outdated
// Check timzone validity | ||
assert!(timezone <= 1440 && timezone >= -1440); | ||
|
||
// FIXME: use checked_sub_signed once stablized |
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.
// FIXME: use checked_sub_signed once stablized | |
// FIXME(#126043): use checked_sub_signed once stablized |
nit: The FIXME should link to the tracking issue.
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.
Added
library/std/src/sys/pal/uefi/time.rs
Outdated
// Convert to UTC | ||
let Some(secs) = secs.checked_sub(TIMEZONE_DELTA) else { return None }; |
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.
// Convert to UTC | |
let Some(secs) = secs.checked_sub(TIMEZONE_DELTA) else { return None }; | |
// Convert to seconds since 1900-01-01-00:00:00 in timezone. | |
let Some(secs) = secs.checked_sub(TIMEZONE_DELTA) else { return None }; |
This isn't converting to UTC as the previous statement converted from UTC to local time.
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.
Thanks for the catch.
46af23b
to
b560562
Compare
// Use 1 March as the start | ||
let (m_adj, overflow): (u32, bool) = (t.month as u32).overflowing_sub(3); | ||
let (carry, adjust): (u32, u32) = if overflow { (1, 12) } else { (0, 0) }; | ||
let y_adj: u32 = (t.year as u32) + YEAR_BASE - carry; | ||
let month_days: u32 = (m_adj.wrapping_add(adjust) * 62719 + 769) / 2048; | ||
let leap_days: u32 = y_adj / 4 - y_adj / 100 + y_adj / 400; | ||
let days: u32 = y_adj * 365 + leap_days + month_days + (t.day as u32 - 1) - 2472632; | ||
let days: u32 = y_adj * 365 + leap_days + month_days + (t.day as u32 - 1) - 2447065; |
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.
might be worth it to add a CONST
for the magic 2447065
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.
Well, I am not sure if it's useful to do that. The current code is almost a one-to-one translation of the algorithms I have linked in the comments, and thus is easier to reason about.
Besides, the offset in this particular case is derived by collapsing multiple calculations into a single one (the one in to_uefi is more straightforward). So not sure if there is any good descriptive name. And just calling it const MAGIC
is probably not any more useful/readable than current form.
library/std/src/sys/pal/uefi/time.rs
Outdated
|
||
const YEAR_BASE: u32 = 4800; /* Before min year, multiple of 400. */ | ||
|
||
// Calculate the number of days since 1/1/1970 | ||
// Calculate the number of days since 1/1/1900 |
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.
Why 1/1/1990? Might be worth it to add a comment?
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.
Added
let days = secs / SECS_IN_DAY; | ||
let remaining_secs = secs % SECS_IN_DAY; | ||
|
||
let z = days + 693901; |
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.
more magic constants (693901
), might be worth it to add CONST
values
b560562
to
221d82d
Compare
Add tests to ensure that extream system times are still representable. Signed-off-by: Ayush Singh <[email protected]>
221d82d
to
9e72276
Compare
Use UEFI time format for SystemTime.
All calculations and comparisons are being done using UnixTime since UEFI Time format does not seem good fit for those calculations.
I have tested the conversions and calculations, but I am not sure if there is a way to run unit tests for platform specific code in Rust source.
The only real benefit from using UEFI Time representation is that to and fro conversion will preserve
daylight
andtimezone
values.r? @joboet