uefi 0.37.0

This crate makes it easy to develop Rust software that leverages safe, convenient, and performant abstractions for UEFI functionality.
Documentation
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
// SPDX-License-Identifier: MIT OR Apache-2.0

//! This module provides the [`FileHandle`] structure as well as the more
//! specific [`RegularFile`] and [`Directory`] structures.
//!
//! This module also provides the [`File`] trait for opening, querying,
//! creating, reading, and writing files.
//!
//! Usually a file system implementation will return a "root" directory, representing
//! `/` on that volume. With that directory, it is possible to enumerate and open
//! all the other files on that volume.

mod dir;
mod info;
mod regular;

use crate::{CStr16, Result, Status, StatusExt};
use core::ffi::c_void;
use core::fmt::Debug;
use core::{mem, ptr};
use uefi_raw::protocol::file_system::FileProtocolV1;

#[cfg(feature = "alloc")]
use {crate::mem::make_boxed, alloc::boxed::Box};

pub use dir::Directory;
pub use info::{
    FileInfo, FileInfoCreationError, FileProtocolInfo, FileSystemInfo, FileSystemVolumeLabel,
    FromUefi,
};
pub use regular::RegularFile;
pub use uefi_raw::protocol::file_system::FileAttribute;

/// Common interface to `FileHandle`, `RegularFile`, and `Directory`.
///
/// `File` contains all functionality that is safe to perform on any type of
/// file handle.
pub trait File: Sized {
    /// Access the underlying file handle.
    #[doc(hidden)]
    fn handle(&mut self) -> &mut FileHandle;

    /// Try to open a file relative to this file.
    ///
    /// # Arguments
    /// * `filename`    Path of file to open, relative to this file
    /// * `open_mode`   The mode to open the file with
    /// * `attributes`  Only valid when `FILE_MODE_CREATE` is used as a mode
    ///
    /// # Errors
    ///
    /// See section `EFI_FILE_PROTOCOL.Open()` in the UEFI Specification for more details.
    /// Note that [`INVALID_PARAMETER`] is not listed in the specification as one of the
    /// errors returned by this function, but some implementations (such as EDK2) perform
    /// additional validation and may return that status for invalid inputs.
    ///
    /// [`INVALID_PARAMETER`]: Status::INVALID_PARAMETER
    ///
    /// * [`Status::INVALID_PARAMETER`]
    /// * [`Status::NOT_FOUND`]
    /// * [`Status::NO_MEDIA`]
    /// * [`Status::MEDIA_CHANGED`]
    /// * [`Status::DEVICE_ERROR`]
    /// * [`Status::VOLUME_CORRUPTED`]
    /// * [`Status::WRITE_PROTECTED`]
    /// * [`Status::ACCESS_DENIED`]
    /// * [`Status::OUT_OF_RESOURCES`]
    /// * [`Status::VOLUME_FULL`]
    fn open(
        &mut self,
        filename: &CStr16,
        open_mode: FileMode,
        attributes: FileAttribute,
    ) -> Result<FileHandle> {
        let mut ptr = ptr::null_mut();

        unsafe {
            (self.imp().open)(
                self.imp(),
                &mut ptr,
                filename.as_ptr().cast(),
                uefi_raw::protocol::file_system::FileMode::from_bits_truncate(open_mode as u64),
                attributes,
            )
        }
        .to_result_with_val(|| unsafe { FileHandle::new(ptr) })
    }

    /// Close this file handle. Same as dropping this structure.
    fn close(self) {}

    /// Closes and deletes this file
    ///
    /// # Warnings
    ///
    /// See section `EFI_FILE_PROTOCOL.Delete()` in the UEFI Specification for more details.
    ///
    /// * [`Status::WARN_DELETE_FAILURE`]
    fn delete(mut self) -> Result {
        let result = unsafe { (self.imp().delete)(self.imp()) }.to_result();
        mem::forget(self);
        result
    }

    /// Queries some information about a file
    ///
    /// The information will be written into a user-provided buffer.
    /// If the buffer is too small, the required buffer size will be returned as part of the error.
    ///
    /// The buffer must be aligned on an `<Info as Align>::alignment()` boundary.
    ///
    /// # Arguments
    /// * `buffer`  Buffer that the information should be written into
    ///
    /// # Errors
    ///
    /// See section `EFI_FILE_PROTOCOL.GetInfo()` in the UEFI Specification for more details.
    ///
    /// * [`Status::UNSUPPORTED`]
    /// * [`Status::NO_MEDIA`]
    /// * [`Status::DEVICE_ERROR`]
    /// * [`Status::VOLUME_CORRUPTED`]
    /// * [`Status::BUFFER_TOO_SMALL`]
    fn get_info<'buf, Info: FileProtocolInfo + ?Sized>(
        &mut self,
        buffer: &'buf mut [u8],
    ) -> Result<&'buf mut Info, Option<usize>> {
        let mut buffer_size = buffer.len();
        Info::assert_aligned(buffer);
        unsafe {
            (self.imp().get_info)(
                self.imp(),
                &Info::GUID,
                &mut buffer_size,
                buffer.as_mut_ptr().cast(),
            )
        }
        .to_result_with(
            || unsafe { Info::from_uefi(buffer.as_mut_ptr().cast::<c_void>()) },
            |s| {
                if s == Status::BUFFER_TOO_SMALL {
                    Some(buffer_size)
                } else {
                    None
                }
            },
        )
    }

    /// Sets some information about a file
    ///
    /// There are various restrictions on the information that may be modified using this method.
    /// The simplest one is that it is usually not possible to call it on read-only media. Further
    /// restrictions specific to a given information type are described in the corresponding
    /// `FileProtocolInfo` type documentation.
    ///
    /// # Arguments
    /// * `info`  Info that should be set for the file
    ///
    /// # Errors
    ///
    /// See section `EFI_FILE_PROTOCOL.SetInfo()` in the UEFI Specification for more details.
    ///
    /// * [`Status::UNSUPPORTED`]
    /// * [`Status::NO_MEDIA`]
    /// * [`Status::DEVICE_ERROR`]
    /// * [`Status::VOLUME_CORRUPTED`]
    /// * [`Status::WRITE_PROTECTED`]
    /// * [`Status::ACCESS_DENIED`]
    /// * [`Status::VOLUME_FULL`]
    /// * [`Status::BAD_BUFFER_SIZE`]
    fn set_info<Info: FileProtocolInfo + ?Sized>(&mut self, info: &Info) -> Result {
        let info_ptr = ptr::from_ref(info).cast::<c_void>();
        let info_size = size_of_val(info);
        unsafe { (self.imp().set_info)(self.imp(), &Info::GUID, info_size, info_ptr).to_result() }
    }

    /// Flushes all modified data associated with the file handle to the device
    ///
    /// # Errors
    ///
    /// See section `EFI_FILE_PROTOCOL.Flush()` in the UEFI Specification for more details.
    ///
    /// * [`Status::NO_MEDIA`]
    /// * [`Status::DEVICE_ERROR`]
    /// * [`Status::VOLUME_CORRUPTED`]
    /// * [`Status::WRITE_PROTECTED`]
    /// * [`Status::ACCESS_DENIED`]
    /// * [`Status::VOLUME_FULL`]
    fn flush(&mut self) -> Result {
        unsafe { (self.imp().flush)(self.imp()) }.to_result()
    }

    /// Read the dynamically allocated info for a file.
    #[cfg(feature = "alloc")]
    fn get_boxed_info<Info: FileProtocolInfo + ?Sized + Debug>(&mut self) -> Result<Box<Info>> {
        let fetch_data_fn = |buf| self.get_info::<Info>(buf);
        let file_info = make_boxed::<Info, _>(fetch_data_fn)?;
        Ok(file_info)
    }

    /// Returns if the underlying file is a regular file.
    /// The result is an error if the underlying file was already closed or deleted.
    ///
    /// UEFI file system protocol only knows "regular files" and "directories".
    fn is_regular_file(&self) -> Result<bool>;

    /// Returns if the underlying file is a directory.
    /// The result is an error if the underlying file was already closed or deleted.
    ///
    /// UEFI file system protocol only knows "regular files" and "directories".
    fn is_directory(&self) -> Result<bool>;
}

// Internal File helper methods to access the function pointer table.
trait FileInternal: File {
    fn imp(&mut self) -> &mut FileProtocolV1 {
        unsafe { &mut *self.handle().0 }
    }
}

impl<T: File> FileInternal for T {}

/// An opaque handle to some contiguous block of data on a volume.
///
/// A `FileHandle` is just a wrapper around a UEFI file handle. Under the hood, it can either be a
/// `RegularFile` or a `Directory`; use the `into_type()` or the unsafe
/// `{RegularFile, Directory}::new()` methods to perform the conversion.
///
/// Dropping this structure will result in the file handle being closed.
#[repr(transparent)]
#[derive(Debug)]
pub struct FileHandle(*mut FileProtocolV1);

impl FileHandle {
    pub(super) const unsafe fn new(ptr: *mut FileProtocolV1) -> Self {
        Self(ptr)
    }

    /// Converts `File` into a more specific subtype based on if it is a
    /// directory or not. Wrapper around [Self::is_regular_file].
    pub fn into_type(self) -> Result<FileType> {
        use FileType::*;

        self.is_regular_file().map(|is_file| {
            if is_file {
                unsafe { Regular(RegularFile::new(self)) }
            } else {
                unsafe { Dir(Directory::new(self)) }
            }
        })
    }

    /// If the handle represents a directory, convert it into a
    /// [`Directory`]. Otherwise returns `None`.
    #[must_use]
    pub fn into_directory(self) -> Option<Directory> {
        if let Ok(FileType::Dir(dir)) = self.into_type() {
            Some(dir)
        } else {
            None
        }
    }

    /// If the handle represents a regular file, convert it into a
    /// [`RegularFile`]. Otherwise returns `None`.
    #[must_use]
    pub fn into_regular_file(self) -> Option<RegularFile> {
        if let Ok(FileType::Regular(regular)) = self.into_type() {
            Some(regular)
        } else {
            None
        }
    }
}

impl File for FileHandle {
    #[inline]
    fn handle(&mut self) -> &mut FileHandle {
        self
    }

    fn is_regular_file(&self) -> Result<bool> {
        let this = unsafe { self.0.as_mut().unwrap() };

        // - get_position fails with EFI_UNSUPPORTED on directories
        // - result is an error if the underlying file was already closed or deleted.
        let mut pos = 0;
        match unsafe { (this.get_position)(this, &mut pos) } {
            Status::SUCCESS => Ok(true),
            Status::UNSUPPORTED => Ok(false),
            s => Err(s.into()),
        }
    }

    fn is_directory(&self) -> Result<bool> {
        self.is_regular_file().map(|b| !b)
    }
}

impl Drop for FileHandle {
    fn drop(&mut self) {
        let result: Result = unsafe { (self.imp().close)(self.imp()) }.to_result();
        // The spec says this always succeeds.
        result.expect("Failed to close file");
    }
}

/// Disambiguate the file type. Returned by `File::into_type()`.
#[derive(Debug)]
pub enum FileType {
    /// The file was a regular (data) file.
    Regular(RegularFile),
    /// The file was a directory.
    Dir(Directory),
}

/// Usage flags describing what is possible to do with the file.
///
/// SAFETY: Using a repr(C) enum is safe here because this type is only sent to
///         the UEFI implementation, and never received from it.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[repr(u64)]
pub enum FileMode {
    /// The file can be read from
    Read = 1,

    /// The file can be read from and written to
    ReadWrite = 2 | 1,

    /// The file can be read, written, and will be created if it does not exist
    CreateReadWrite = (1 << 63) | 2 | 1,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::runtime::Time;
    use crate::{CString16, Guid, Identify};
    use ::alloc::vec;
    use uefi_raw::protocol::file_system::FileProtocolRevision;

    // Test `get_boxed_info` by setting up a fake file, which is mostly
    // just function pointers. Most of the functions can be empty, only
    // get_info is actually implemented to return useful data.
    #[test]
    fn test_get_boxed_info() {
        let mut file_impl = FileProtocolV1 {
            revision: FileProtocolRevision::REVISION_1,
            open: stub_open,
            close: stub_close,
            delete: stub_delete,
            read: stub_read,
            write: stub_write,
            get_position: stub_get_position,
            set_position: stub_set_position,
            get_info: stub_get_info,
            set_info: stub_set_info,
            flush: stub_flush,
        };
        let file_handle = FileHandle(&mut file_impl);

        let mut file = unsafe { RegularFile::new(file_handle) };
        let info = file.get_boxed_info::<FileInfo>().unwrap();
        assert_eq!(info.file_size(), 123);
        assert_eq!(info.file_name(), CString16::try_from("test_file").unwrap());
    }

    unsafe extern "efiapi" fn stub_get_info(
        _this: *mut FileProtocolV1,
        information_type: *const Guid,
        buffer_size: *mut usize,
        buffer: *mut c_void,
    ) -> Status {
        assert_eq!(unsafe { *information_type }, FileInfo::GUID);

        // Use a temporary buffer to get some file info, then copy that
        // data to the output buffer.
        let mut tmp = vec![0; 128];
        let file_size = 123;
        let physical_size = 456;
        let time = Time::invalid();
        let info = FileInfo::new(
            &mut tmp,
            file_size,
            physical_size,
            time,
            time,
            time,
            FileAttribute::empty(),
            &CString16::try_from("test_file").unwrap(),
        )
        .unwrap();
        let required_size = size_of_val(info);
        if unsafe { *buffer_size } < required_size {
            unsafe {
                *buffer_size = required_size;
            }
            Status::BUFFER_TOO_SMALL
        } else {
            unsafe {
                ptr::copy_nonoverlapping(
                    core::ptr::from_ref::<FileInfo>(info).cast(),
                    buffer,
                    required_size,
                );
            }
            unsafe {
                *buffer_size = required_size;
            }
            Status::SUCCESS
        }
    }

    const extern "efiapi" fn stub_open(
        _this: *mut FileProtocolV1,
        _new_handle: *mut *mut FileProtocolV1,
        _filename: *const uefi_raw::Char16,
        _open_mode: uefi_raw::protocol::file_system::FileMode,
        _attributes: FileAttribute,
    ) -> Status {
        Status::UNSUPPORTED
    }

    const extern "efiapi" fn stub_close(_this: *mut FileProtocolV1) -> Status {
        Status::SUCCESS
    }

    const extern "efiapi" fn stub_delete(_this: *mut FileProtocolV1) -> Status {
        Status::UNSUPPORTED
    }

    const extern "efiapi" fn stub_read(
        _this: *mut FileProtocolV1,
        _buffer_size: *mut usize,
        _buffer: *mut c_void,
    ) -> Status {
        Status::UNSUPPORTED
    }

    const extern "efiapi" fn stub_write(
        _this: *mut FileProtocolV1,
        _buffer_size: *mut usize,
        _buffer: *const c_void,
    ) -> Status {
        Status::UNSUPPORTED
    }

    const extern "efiapi" fn stub_get_position(
        _this: *const FileProtocolV1,
        _position: *mut u64,
    ) -> Status {
        Status::UNSUPPORTED
    }

    const extern "efiapi" fn stub_set_position(
        _this: *mut FileProtocolV1,
        _position: u64,
    ) -> Status {
        Status::UNSUPPORTED
    }

    const extern "efiapi" fn stub_set_info(
        _this: *mut FileProtocolV1,
        _information_type: *const Guid,
        _buffer_size: usize,
        _buffer: *const c_void,
    ) -> Status {
        Status::UNSUPPORTED
    }

    const extern "efiapi" fn stub_flush(_this: *mut FileProtocolV1) -> Status {
        Status::UNSUPPORTED
    }
}