Skip to content

Commit 0037af7

Browse files
authored
Handle UEFI warnings more cleanly (#60)
UEFI warnings represent non-fatal failures. We used to handle them as total failures, now we let the user choose how he wants to handle them.
1 parent e85ca03 commit 0037af7

File tree

24 files changed

+299
-117
lines changed

24 files changed

+299
-117
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ edition = "2018"
88

99
[dependencies]
1010
bitflags = "1"
11+
log = { version = "0.4", default-features = false }
1112
ucs2 = "0.1"
1213

1314
[workspace]

src/error/completion.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
use super::Status;
2+
use log::warn;
3+
4+
/// This type is used when an UEFI operation has completed, but some non-fatal
5+
/// problems may have been encountered along the way
6+
#[must_use]
7+
#[derive(Clone, Copy, Debug, PartialEq)]
8+
pub enum Completion<T> {
9+
/// The operation completed without problems
10+
Success(T),
11+
12+
/// The operation completed, but some non-fatal issues were encountered
13+
Warning(T, Status),
14+
}
15+
16+
impl<T> Completion<T> {
17+
/// Split the completion into a (status, value) pair
18+
pub fn split(self) -> (T, Status) {
19+
match self {
20+
Completion::Success(res) => (res, Status::SUCCESS),
21+
Completion::Warning(res, stat) => (res, stat),
22+
}
23+
}
24+
25+
/// Access the inner value, logging the warning if there is any
26+
pub fn log(self) -> T {
27+
match self {
28+
Completion::Success(res) => res,
29+
Completion::Warning(res, stat) => {
30+
log_warning(stat);
31+
res
32+
}
33+
}
34+
}
35+
36+
/// Assume that no warning occured, panic if not
37+
pub fn unwrap(self) -> T {
38+
match self {
39+
Completion::Success(res) => res,
40+
Completion::Warning(_, w) => {
41+
unwrap_failed("Called `Completion::unwrap()` on a `Warning` value", w)
42+
}
43+
}
44+
}
45+
46+
/// Assume that no warning occured, panic with provided message if not
47+
pub fn expect(self, msg: &str) -> T {
48+
match self {
49+
Completion::Success(res) => res,
50+
Completion::Warning(_, w) => unwrap_failed(msg, w),
51+
}
52+
}
53+
54+
/// Transform the inner value without unwrapping the Completion
55+
pub fn map<U>(self, f: impl Fn(T) -> U) -> Completion<U> {
56+
match self {
57+
Completion::Success(res) => Completion::Success(f(res)),
58+
Completion::Warning(res, stat) => Completion::Warning(f(res), stat),
59+
}
60+
}
61+
62+
/// Merge this completion with a success or warning status
63+
///
64+
/// Since this type only has storage for one warning, if two warnings must
65+
/// be stored, one of them will be spilled into the logs.
66+
///
67+
pub fn with_status(self, extra_status: Status) -> Self {
68+
match self {
69+
Completion::Success(res) => {
70+
if extra_status.is_success() {
71+
Completion::Success(res)
72+
} else {
73+
Completion::Warning(res, extra_status)
74+
}
75+
}
76+
Completion::Warning(res, stat) => {
77+
if extra_status.is_success() {
78+
Completion::Warning(res, stat)
79+
} else {
80+
log_warning(stat);
81+
Completion::Warning(res, extra_status)
82+
}
83+
}
84+
}
85+
}
86+
}
87+
88+
impl<T> From<T> for Completion<T> {
89+
fn from(res: T) -> Self {
90+
Completion::Success(res)
91+
}
92+
}
93+
94+
// These are separate functions to reduce the code size of the methods
95+
#[inline(never)]
96+
#[cold]
97+
fn unwrap_failed(msg: &str, warning: Status) -> ! {
98+
panic!("{}: {:?}", msg, warning)
99+
}
100+
101+
#[inline(never)]
102+
#[cold]
103+
fn log_warning(warning: Status) {
104+
warn!("Encountered UEFI warning: {:?}", warning)
105+
}

src/error/mod.rs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,55 @@
1-
use core::result;
2-
31
/// Definition of UEFI's standard status codes
42
mod status;
53
pub use self::status::Status;
64

5+
/// Completions are used to model operations which have completed, but may have
6+
/// encountered non-fatal errors ("warnings") along the way
7+
mod completion;
8+
pub use self::completion::Completion;
9+
710
/// Return type of many UEFI functions.
8-
pub type Result<T> = result::Result<T, Status>;
11+
pub type Result<T> = core::result::Result<Completion<T>, Status>;
12+
13+
/// Extension trait for Result which helps dealing with UEFI's warnings
14+
pub trait ResultExt<T> {
15+
/// Treat warnings as errors
16+
fn warning_as_error(self) -> core::result::Result<T, Status>;
17+
18+
/// Ignore warnings, keeping a trace of them in the logs
19+
fn log_warning(self) -> core::result::Result<T, Status>;
20+
21+
/// Expect success without warnings, panic otherwise
22+
fn unwrap_success(self) -> T;
23+
24+
/// Expect success without warnings, panic with provided message otherwise
25+
fn expect_success(self, msg: &str) -> T;
26+
27+
/// Transform the inner output, if any
28+
fn map_inner<U>(self, f: impl Fn(T) -> U) -> Result<U>;
29+
}
30+
31+
impl<T> ResultExt<T> for Result<T> {
32+
fn warning_as_error(self) -> core::result::Result<T, Status> {
33+
match self {
34+
Ok(Completion::Success(v)) => Ok(v),
35+
Ok(Completion::Warning(_, s)) => Err(s),
36+
Err(s) => Err(s),
37+
}
38+
}
39+
40+
fn log_warning(self) -> core::result::Result<T, Status> {
41+
self.map(|completion| completion.log())
42+
}
43+
44+
fn unwrap_success(self) -> T {
45+
self.unwrap().unwrap()
46+
}
47+
48+
fn expect_success(self, msg: &str) -> T {
49+
self.expect(msg).expect(msg)
50+
}
51+
52+
fn map_inner<U>(self, f: impl Fn(T) -> U) -> Result<U> {
53+
self.map(|completion| completion.map(f))
54+
}
55+
}

src/error/status.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::Result;
1+
use super::{Completion, Result};
22
use core::ops;
33
use ucs2;
44

@@ -129,9 +129,10 @@ impl Status {
129129
where
130130
F: FnOnce() -> T,
131131
{
132-
// FIXME: Is that the best way to handle warnings?
133132
if self.is_success() {
134-
Ok(f())
133+
Ok(Completion::Success(f()))
134+
} else if self.is_warning() {
135+
Ok(Completion::Warning(f(), self))
135136
} else {
136137
Err(self)
137138
}
@@ -146,7 +147,7 @@ impl Into<Result<()>> for Status {
146147
}
147148

148149
impl ops::Try for Status {
149-
type Ok = ();
150+
type Ok = Completion<()>;
150151
type Error = Status;
151152

152153
fn into_result(self) -> Result<()> {

src/lib.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
//! therefore all the network protocols will be unavailable.
2525
2626
#![feature(optin_builtin_traits)]
27-
#![feature(min_const_fn)]
28-
#![feature(tool_lints)]
2927
#![feature(try_trait)]
3028
#![no_std]
3129
// Enable some additional warnings and lints.
@@ -37,7 +35,7 @@ mod data_types;
3735
pub use self::data_types::{Event, Guid, Handle};
3836

3937
mod error;
40-
pub use self::error::{Result, Status};
38+
pub use self::error::{Completion, Result, ResultExt, Status};
4139

4240
pub mod table;
4341

src/prelude.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//!
33
//! This includes the system table types, `Status` codes, etc.
44
5-
pub use crate::Status;
5+
pub use crate::{ResultExt, Status};
66

77
// Import the basic table types.
88
pub use crate::table::{boot::BootServices, runtime::RuntimeServices, SystemTable};

src/proto/console/gop.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
//! but in practice there might be some extra padding used for efficiency.
2525
2626
use core::{ptr, slice};
27-
use crate::{Result, Status};
27+
use crate::{Completion, Result, Status};
2828

2929
/// Provides access to the video hardware's frame buffer.
3030
///
@@ -71,7 +71,7 @@ impl GraphicsOutput {
7171
}
7272

7373
/// Returns information about all available graphics modes.
74-
pub fn modes<'a>(&'a self) -> impl Iterator<Item = Mode> + 'a {
74+
pub fn modes<'a>(&'a self) -> impl Iterator<Item = Completion<Mode>> + 'a {
7575
ModeIter {
7676
gop: self,
7777
current: 0,
@@ -397,7 +397,7 @@ struct ModeIter<'a> {
397397
}
398398

399399
impl<'a> Iterator for ModeIter<'a> {
400-
type Item = Mode;
400+
type Item = Completion<Mode>;
401401

402402
fn next(&mut self) -> Option<Self::Item> {
403403
let index = self.current;

src/proto/console/pointer/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,8 @@ impl Pointer {
3737
let mut pointer_state = unsafe { mem::uninitialized() };
3838

3939
match (self.get_state)(self, &mut pointer_state) {
40-
Status::SUCCESS => Ok(Some(pointer_state)),
41-
Status::NOT_READY => Ok(None),
42-
error => Err(error),
40+
Status::NOT_READY => Ok(None.into()),
41+
other => other.into_with(|| Some(pointer_state)),
4342
}
4443
}
4544

src/proto/console/serial.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Abstraction over byte stream devices, also known as serial I/O devices.
22
33
use bitflags::bitflags;
4-
use crate::{Result, Status};
4+
use crate::{Completion, Result, Status};
55

66
/// Provides access to a serial I/O device.
77
///
@@ -83,14 +83,14 @@ impl Serial {
8383
/// Returns the number of bytes actually written to the device.
8484
/// In the case of a timeout, this number will be smaller than
8585
/// the buffer's size.
86+
///
87+
/// Unlike UEFI, we handle timeouts as warnings, not errors
8688
pub fn write(&mut self, data: &[u8]) -> Result<usize> {
8789
let mut buffer_size = data.len();
8890

89-
let status = (self.write)(self, &mut buffer_size, data.as_ptr());
90-
91-
match status {
92-
Status::SUCCESS | Status::TIMEOUT => Ok(buffer_size),
93-
err => Err(err),
91+
match (self.write)(self, &mut buffer_size, data.as_ptr()) {
92+
s @ Status::TIMEOUT => Ok(Completion::Warning(buffer_size, s)),
93+
other => other.into_with(|| buffer_size),
9494
}
9595
}
9696

@@ -99,14 +99,14 @@ impl Serial {
9999
/// Returns the number of bytes actually read from the device.
100100
/// In the case of a timeout or buffer overrun, this number will be smaller
101101
/// than the buffer's size.
102+
///
103+
/// Unlike UEFI, we handle timeouts as warnings, not errors
102104
pub fn read(&mut self, data: &mut [u8]) -> Result<usize> {
103105
let mut buffer_size = data.len();
104106

105-
let status = (self.read)(self, &mut buffer_size, data.as_mut_ptr());
106-
107-
match status {
108-
Status::SUCCESS | Status::TIMEOUT => Ok(buffer_size),
109-
err => Err(err),
107+
match (self.read)(self, &mut buffer_size, data.as_mut_ptr()) {
108+
s @ Status::TIMEOUT => Ok(Completion::Warning(buffer_size, s)),
109+
other => other.into_with(|| buffer_size),
110110
}
111111
}
112112

src/proto/console/text/input.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,8 @@ impl Input {
3434
let mut key = unsafe { mem::uninitialized() };
3535

3636
match (self.read_key_stroke)(self, &mut key) {
37-
Status::SUCCESS => Ok(Some(key)),
38-
Status::NOT_READY => Ok(None),
39-
error => Err(error),
37+
Status::NOT_READY => Ok(None.into()),
38+
other => other.into_with(|| Some(key)),
4039
}
4140
}
4241

src/proto/console/text/output.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use core::fmt;
2-
use crate::{Result, Status};
2+
use crate::prelude::*;
3+
use crate::{Completion, Result, Status};
34

45
/// Interface for text-based output devices.
56
///
@@ -53,7 +54,7 @@ impl Output {
5354

5455
/// Returns an iterator of all supported text modes.
5556
// TODO: fix the ugly lifetime parameter.
56-
pub fn modes<'a>(&'a mut self) -> impl Iterator<Item = OutputMode> + 'a {
57+
pub fn modes<'a>(&'a mut self) -> impl Iterator<Item = Completion<OutputMode>> + 'a {
5758
let max = self.data.max_mode;
5859
OutputModeIter {
5960
output: self,
@@ -74,8 +75,7 @@ impl Output {
7475
/// alternative to this method.
7576
fn query_mode(&self, index: i32) -> Result<(usize, usize)> {
7677
let (mut columns, mut rows) = (0, 0);
77-
(self.query_mode)(self, index, &mut columns, &mut rows)?;
78-
Ok((columns, rows))
78+
(self.query_mode)(self, index, &mut columns, &mut rows).into_with(|| (columns, rows))
7979
}
8080

8181
/// Sets a mode as current.
@@ -86,8 +86,8 @@ impl Output {
8686
/// Returns the the current text mode.
8787
pub fn current_mode(&self) -> Result<OutputMode> {
8888
let index = self.data.mode;
89-
let dims = self.query_mode(index)?;
90-
Ok(OutputMode { index, dims })
89+
self.query_mode(index)
90+
.map_inner(|dims| OutputMode { index, dims })
9191
}
9292

9393
/// Make the cursor visible or invisible.
@@ -148,7 +148,9 @@ impl fmt::Write for Output {
148148
buf[*i] = 0;
149149
*i = 0;
150150

151-
self.output_string(buf.as_ptr()).map_err(|_| fmt::Error)
151+
self.output_string(buf.as_ptr())
152+
.warning_as_error()
153+
.map_err(|_| fmt::Error)
152154
};
153155

154156
// This closure converts a character to UCS-2 and adds it to the buffer,
@@ -217,15 +219,15 @@ struct OutputModeIter<'a> {
217219
}
218220

219221
impl<'a> Iterator for OutputModeIter<'a> {
220-
type Item = OutputMode;
222+
type Item = Completion<OutputMode>;
221223

222224
fn next(&mut self) -> Option<Self::Item> {
223225
let index = self.current;
224226
if index < self.max {
225227
self.current += 1;
226228

227-
if let Ok(dims) = self.output.query_mode(index) {
228-
Some(OutputMode { index, dims })
229+
if let Ok(dims_completion) = self.output.query_mode(index) {
230+
Some(dims_completion.map(|dims| OutputMode { index, dims }))
229231
} else {
230232
self.next()
231233
}

0 commit comments

Comments
 (0)