1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use super::Status;
use log::warn;

/// This type is used when an UEFI operation has completed, but some non-fatal
/// problems (UEFI warnings) may have been encountered along the way
#[must_use]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Completion<T> {
    status: Status,
    result: T,
}

impl<T> Completion<T> {
    /// Build a completion from a non-error status and a function result
    pub fn new(status: Status, result: T) -> Self {
        if status.is_error() {
            built_with_error(status);
        }
        Self { status, result }
    }

    /// Extract the status of this completion
    pub fn status(&self) -> Status {
        self.status
    }

    /// Split this completion into its inner status and result data
    pub fn split(self) -> (Status, T) {
        (self.status, self.result)
    }

    /// Access the inner value, logging the warning if there is any
    pub fn log(self) -> T {
        if self.status != Status::SUCCESS {
            log_warning(self.status);
        }
        self.result
    }

    /// Assume that no warning occured, panic if not
    pub fn unwrap(self) -> T {
        if self.status != Status::SUCCESS {
            unwrap_failed(
                "Called `Completion::unwrap()` with a warning status",
                self.status,
            );
        }
        self.result
    }

    /// Assume that no warning occured, panic with provided message if not
    pub fn expect(self, msg: &str) -> T {
        if self.status != Status::SUCCESS {
            unwrap_failed(msg, self.status);
        }
        self.result
    }

    /// Transform the inner value without unwrapping the Completion
    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Completion<U> {
        Completion {
            status: self.status,
            result: f(self.result),
        }
    }

    /// Merge this completion with a success or warning status
    ///
    /// Since this type only has storage for one warning, if two warnings must
    /// be stored, one of them will be spilled into the logs.
    pub fn with_status(self, extra_status: Status) -> Self {
        if extra_status.is_success() {
            self
        } else {
            Completion::new(extra_status, self.log())
        }
    }
}

// Completions can be built from either a status or a payload

impl From<Status> for Completion<()> {
    fn from(status: Status) -> Self {
        Completion::new(status, ())
    }
}

impl<T> From<T> for Completion<T> {
    fn from(result: T) -> Self {
        Completion::new(Status::SUCCESS, result)
    }
}

// These are separate functions to reduce the code size of the methods

#[inline(never)]
#[cold]
fn built_with_error(error: Status) -> ! {
    panic!(
        "Completion was incorrectly built with error status: {:?}",
        error
    )
}

#[inline(never)]
#[cold]
fn unwrap_failed(msg: &str, warning: Status) -> ! {
    panic!("{}: {:?}", msg, warning)
}

#[inline(never)]
#[cold]
fn log_warning(warning: Status) {
    warn!("Encountered UEFI warning: {:?}", warning)
}