Using Rust to develop operating system (UEFI Abstract file system)

In the previous article, we introduced the basic use of rust in UEFI. In this chapter, we started to write the basic UEFI settings, mainly including File structure and UEFI compilation script to simplify the kernel File reading and qemu startup process

Build base exception

Results are provided for us in the standard library, but in the development of UEFI, results are divided into two types: results executed by UEFI service and user-defined results. In this section, we build our own results by imitating the io::Result of the standard library

Design thinking

In addition to meeting the basic use of our program, the Result we designed is compatible with the Result of UEFI service. Therefore, we can command two kinds of results again through type. Secondly, we need to be compatible with the error of UEFI service, but we still need to distinguish between UEFI exception and UEFI application exception. Therefore, we need to design an enumeration Repr to distinguish the two kinds of exceptions

According to the Result, we can easily define the following structure

// Represents the Result used by UEFI service
pub type UefiResult<T> = uefi::Result<T>;
// Represents the Result used by the UEFI application
pub type Result<T> = core::result::Result<T, Error>;

The Error of the second Result is our custom Error, which is divided into two kinds of UEFI execution errors and application custom errors, so we can use an enumeration to complete it

// It is used to represent the types of exceptions, including UEFI execution error and UEFI application error
#[derive(Debug)]
enum Repr {
    ///UEFI service error
    Uefi(Efi),
    ///UEFI application error
    Custom(Custom),
}

In the Repr, there are two structures, Efi and Custom, which are defined as follows

#[derive(Debug, Clone)]
struct Custom {
	///UEFI application execution error type
    kind: ErrorKind,
    ///UEFI application execution error description
    err_msg: String,
}

#[derive(Debug, Clone)]
struct Efi {
	///UEFI service execution result status code
    status: Status,
    ///The corresponding description of UEFI status. If you use the default prompt for None,
    err_msg: Option<String>,
}

// Used to indicate UEFI application exception type
#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
pub enum ErrorKind {
	///File not found
    NotFound,
    ///EOF read in read file
    UnexpectedEof,
    ///Invalid file
    InvalidFile,
    ///Invalid file mode
    InvalidFileMode,
    ///UEFI error with error code
    UefiErrorCode,
    //Terminate
    Interrupted,
}

The main difference between Custom and Efi is that kind is our Custom error type. Status indicates the status code of the execution error of UEFI service (status is actually the mapping between the enumeration of rule and C, but UEFI RS does not simply map to the enumeration of C because it will generate UB (undefined behavior), but adopts the newtype_enum macro for processing)

Next, we provide as? Str() method for the two structures. The implementation of ErrorKind is relatively simple. The following part of the code

impl ErrorKind {
    pub fn as_str(&self) -> &'static str {
        match *self {
            ErrorKind::NotFound => "entity not found",
            ErrorKind::UnexpectedEof => "unexpected end of file",
            ....
        }
  }
}

But there are some differences in as STR to implement Efi structure. We can't write such code

impl Efi {
    pub fn as_str(&self) -> &'static str {
        match self.status {
        	Status:: WARN_UNKNOWN_GLYPH => "The string contained characters that could not be rendered and were skipped."
        	.....
        }
     }
}

As mentioned above, if you look at the source Status simply, it's really like enumeration, but it's actually a structure, so we need to judge it as the specific value in Status

Take status:: warn ﹣ unknown ﹣ glyph as an example. After macro expansion, the result is pub struct warn ﹣ unknown ﹣ glyph (pub usize). Therefore, we need to deal with it in the way of structure

All Status results are listed for completeness

const ERROR_BIT: usize = 1 << (core::mem::size_of::<usize>() * 8 - 1);


impl Efi {
    pub fn as_str(&self) -> &'static str {
        match self.status.0 {
            0 => "The operation completed successfully.",
            1 => "The string contained characters that could not be rendered and were skipped.",
            2 => "The handle was closed, but the file was not deleted.",
            3 => "The handle was closed, but the data to the file was not flushed properly.",
            4 => "The resulting buffer was too small, and the data was truncated.",
            5 => "The data has not been updated within the timeframe set by local policy.",
            6 => "The resulting buffer contains UEFI-compliant file system.",
            7 => "The operation will be processed across a system reset.",
            ERROR_BIT | 1 => "The image failed to load.",
            ERROR_BIT | 2 => "A parameter was incorrect.",
            ERROR_BIT | 3 => "The operation is not supported.",
            ERROR_BIT | 4 => "The buffer was not the proper size for the request.The buffer is not large enough to hold the requested data.",
            ERROR_BIT | 5 => "The required buffer size is returned in the appropriate parameter.",
            ERROR_BIT | 6 => "There is no data pending upon return.",
            ERROR_BIT | 7 => "The physical device reported an error while attempting the operation.",
            ERROR_BIT | 8 => "The device cannot be written to.",
            ERROR_BIT | 9 => "A resource has run out.",
            ERROR_BIT | 10 => "An inconstency was detected on the file system.",
            ERROR_BIT | 11 => "There is no more space on the file system.",
            ERROR_BIT | 12 => "The device does not contain any medium to perform the operation.",
            ERROR_BIT | 13 => "The medium in the device has changed since the last access.",
            ERROR_BIT | 14 => "The item was not found.",
            ERROR_BIT | 15 => "Access was denied.",
            ERROR_BIT | 16 => "The server was not found or did not respond to the request.",
            ERROR_BIT | 17 => "A mapping to a device does not exist.",
            ERROR_BIT | 18 => "The timeout time expired.",
            ERROR_BIT | 19 => "The protocol has not been started.",
            ERROR_BIT | 20 => "The protocol has already been started.",
            ERROR_BIT | 21 => "The operation was aborted.",
            ERROR_BIT | 22 => "An ICMP error occurred during the network operation.",
            ERROR_BIT | 23 => "A TFTP error occurred during the network operation.",
            ERROR_BIT | 24 => "A protocol error occurred during the network operation. The function encountered an internal version that was",
            ERROR_BIT | 25 => "incompatible with a version requested by the caller.",
            ERROR_BIT | 26 => "The function was not performed due to a security violation.",
            ERROR_BIT | 27 => "A CRC error was detected.",
            ERROR_BIT | 28 => "Beginning or end of media was reached",
            ERROR_BIT | 31 => "The end of the file was reached.",
            ERROR_BIT | 32 => "The language specified was invalid.The security status of the data is unknown or compromised and",
            ERROR_BIT | 33 => "the data must be updated or replaced to restore a valid security status.",
            ERROR_BIT | 34 => "There is an address conflict address allocation",
            ERROR_BIT | 35 => "A HTTP error occurred during the network operation.",
            _ => "Unknown status"
        }
    }
}

ERROR_BIT is defined in uefi-rs/src/result/status.rs, this structure is not pub, so we redefined this structure in our own files (only for reading, not for other purposes)

So we can define the structure of Error

#[derive(Debug)]
pub struct Error {
    repr: Repr,
}

Finally, we provide the matching method of Error

impl Error {
	///Create UEFI application exception according to given error type
    pub fn new(kind: ErrorKind, msg: &str) -> Error {
        Error { repr: Repr::Custom(Custom { kind, err_msg: msg.to_string() }) }
    }
	///Create UEFI application exception according to the given error type to support String. It is mainly convenient to use format!
    pub fn with_string(kind: ErrorKind, msg: String) -> Error {
        Error { repr: Repr::Custom(Custom { kind, err_msg: msg }) }
    }
    ///Create UEFI service error based on passed status code
    pub fn from_uefi_status(status: Status, msg: Option<&str>) -> Error {
        Error {
            repr: Repr::Uefi(Efi {
                status,
                err_msg: match msg {
                    Some(msg) => Some(msg.to_string()),
                    None => None,
                },
            })
        }
    }
	///Provide judgment of error type
    pub fn kind(&self) -> ErrorKind {
        match self.repr {
            Repr::Uefi(ref efi) => ErrorKind::UefiErrorCode,
            Repr::Custom(ref cu) => cu.kind,
        }
    }
}

In order to display Error information when an Error occurs, we need to implement Displaytrait for Error

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.repr {
            Repr::Custom(ref cu) => {
                write!(f, "{}: {}", cu.kind.as_str(), cu.err_msg)
            }
            Repr::Uefi(ref efi) => {
                match efi.err_msg {
                    None => write!(f, "got uefi status `{}` info: {}", efi.status.0, efi.as_str()),
                    Some(ref other) => write!(f, "got uefi status `{}` info: {}", efi.status.0, other),
                }
            }
        }
    }
}

After modification, the old Ok(T) and Err(T) cannot be used (one idea is to combine UefiResult and Result, so that it can be used as usual without testing the feasibility)

pub fn ok<T>(t: T) -> UefiResult<Result<T>> {
    Ok(Completion::from(core::result::Result::Ok(t)))
}

pub fn err<T>(e: Error) -> UefiResult<Result<T>> {
    Ok(Completion::from(core::result::Result::Err(e)))
}

So we have finished the Result design

Encapsulate file operations

For the operation of files, we can refer to the standard library and abstract the operation of files into three trait s, namely read, write and seek
Read mainly provides file read operation, write mainly provides file write operation, Seek provides file pointer movement operation

pub trait Seek {
	///Specify file / read write location
    fn seek(&mut self, pos: SeekFrom) -> Result<()>;
}

pub trait Read {
	///Read as much file data as possible and fill it into 'but', and return the number of bytes read
	/// #Error
	///If the specified buffer is too small, return 'buffer' to 'small' and the required 'size`
    fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
}

pub trait Write {
	///Write the data in 'buf' to the file and return the number of bytes written
	///An error in the process of writing returns the number of bytes written
    fn write(&mut self, buf: &[u8]) -> usize;
	///Refresh all written data to the device
    fn flush(&mut self) -> Result<()>;
}

We have defined the basic functions of each trait, and we will enrich our trait functions in the future

In terms of structure, we need to separate File operation from File read-write operation. The main reason is that the open method provided by UEFI RS is also suitable for folder operation. Therefore, File is mainly used for folder and File operation. FileOperator is mainly used for File read-write operation. The definition of File is as follows

///For files and folders
pub struct File {
	//Root directory
    root: Directory,
    ///Buffer is used to store opened directory information
    buffer: Vec<u8>,
}

The new function of File only needs to get SimpleFileSystemProtocol from BootServices and read and open the root directory

impl File {
    ///The auxiliary operation of new function and try  new function
    fn _new(bt: &BootServices) -> UefiResult<Self> {
    	// Get SimpleFileSystemProtocol
        let f = unsafe { &mut *bt.locate_protocol::<SimpleFileSystem>().log_warning()?.get() };
        // Open root
        let mut volume = f.open_volume().log_warning()?;
        Ok(Completion::from(File {
            root: volume,
            buffer: vec![0_u8; DEFAULT_BUFFER_SIZE],
        }))
    }
 }

The new function here is just an auxiliary operation, because we need to provide the new and try new functions as follows

impl File {
    ///Create a File instance based on BootServices,
    /// # Panic
    ///When the attempt to read the root directory fails, errors in the file system, device drivers and other errors will cause panic
    pub fn new(bt: &BootServices) -> Self {
        match File::try_new(bt) {
            Ok(f) => f,
            Err(e) => {
                panic!("occur an error during open root folder! {}", e);
            }
        }
    }
    ///new method
    pub fn try_new(bt: &BootServices) -> Result<Self> {
        match Self::_new(bt).log_warning() {
            Ok(f) => Ok(f),
            Err(e) => Err(Error::from_uefi_status(e.status(), None)),
        }
    }
}

After the try ﹣ new function, we turn the underlying uefi service error into uefi application error, so we don't need to deal with nested results
Of course, we can also provide functions that can specify the buffer size

impl File {
    ///Auxiliary function of with buffer capacity
    fn capacity(bt: &BootServices, size: usize) -> UefiResult<Self> {
        let f = unsafe { &mut *bt.locate_protocol::<SimpleFileSystem>().log_warning()?.get() };
        let volume = f.open_volume().log_warning()?;
        Ok(Completion::from(File {
            root: volume,
            buffer: vec![0_u8; size],
        }))
    }

    ///Specify buffer size
    pub fn with_buffer_capacity(bt: &BootServices, size: usize) -> Result<Self> {
        match Self::capacity(bt, size).log_warning() {
            Ok(f) => Ok(f),
            Err(e) => Err(Error::from_uefi_status(e.status(), None))
        }
    }
 }

Open the specified file

The open function is complex, so it needs three auxiliary functions to complete. These three auxiliary functions will complete the following operations

  • Read the file information FileInfo in the root directory
  • Get the FileAttribute of the file
  • Open the file with the specified path according to the specified file mode and file properties

First, we complete the first step: read the file information FileInfo in the root directory

impl File{
	///Read root information
    fn read_entry(&mut self) -> Result<&mut FileInfo> {
        return match self.root.read_entry(self.buffer.as_mut_slice()).log_warning() {
            Ok(info) => {
                if let Some(f) = info { Ok(f) } else { Err(Error::new(ErrorKind::NotFound, "the file info header not found!")) }
            }
            Err(e) => Err(Error::from_uefi_status(e.status(), None))
        };
    }
}

Step 2: get the file attribute FileAttribute

impl File{
	///Read root properties
    fn root_attribute(&mut self) -> Result<FileAttribute> {
        match self.read_entry() {
            Ok(info) => Ok(info.attribute()),
            Err(e) => Err(e),
        }
    }
}

The third step:

impl File{
	fn _open(&mut self, filename: &str, mode: FileMode, mut attr: FileAttribute) -> UefiResult<Result<FileOperator>> {
		// If the specified mode is write mode, you need to change the property value
        if let FileMode::CreateReadWrite = mode {
            attr = FileAttribute::VALID_ATTR;
        }

        return match self.root.open(filename, mode, attr).log_warning() {
            Ok(handle) => {
            	// Only files are processed here. If a folder is specified, ErrorKind::InvalidFile is returned
                match handle.into_type().log_warning()? {
                    FileType::Dir(_) => {
                        return err(Error::new(ErrorKind::InvalidFile, "except file found folder, if you want create folder please use `mkdir` method if you want read folder please use `walk` method"));
                    }
                    FileType::Regular(file) => {
                        ok(FileOperator { file, current: 0 })
                    }
                }
            }
            Err(e) => {
                err(Error::from_uefi_status(e.status(), None))
            }
        };
    }
}

Finally, we define the open function

    pub fn open(&mut self, filename: &str, mode: &str) -> UefiResult<Result<FileOperator>> {
    	// Get file properties
        let attr = self.root_attribute().unwrap();
        let f_mode = match mode {
            "r" => FileMode::Read,
            "w" => FileMode::ReadWrite,
            "c" => FileMode::CreateReadWrite,
            other => return err(Error::new(ErrorKind::InvalidFileMode, format!("No Support mode: `{}`", other.clone()).as_str())),
        };
        self._open(filename, f_mode, attr)
    }

It is worth noting that the contents of the root attribute, open and open functions cannot be written in one function. The compiler will prompt a variable borrowing, an immutable borrowing, and FileInfo not implementation copy trait and other errors
We notice that the return value of open is FileOperator, which is defined as follows

pub struct FileOperator {
	///Documented instance
    file: RegularFile,
    ///File pointer location
    current: u64,
}

FileOperator will implement Read,Write,Seek and other trait s. The implementation is relatively simple, which will not be explained here

Seek

///Sets the location of the cursor for this file handle to the specified absolute location.
///Allow 'End' to set the cursor beyond the End of the file, which will trigger file growth on the next write.
///
///* ` SeekFrom::Start(size) ` move cursor to file start position ` size 'bytes
///* ` SeekFrom::End(size) ` move the cursor to the size set for this object plus the specified 'size' bytes
///* ` SeekFrom::Current(size) ` move the cursor to the current position plus the specified 'size' bytes
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SeekFrom {
    Start(u64),
    End(u64),
    Current(u64),
}

impl Seek for FileOperator {
    fn seek(&mut self, pos: SeekFrom) -> Result<()> {
        let result = match pos {
            SeekFrom::Start(p) => self.file.set_position(p).log_warning(),
            SeekFrom::End(p) => self.file.set_position(RegularFile::END_OF_FILE + p).log_warning(),
            SeekFrom::Current(p) => self.file.set_position(self.current + p).log_warning(),
        };
        match result {
            Ok(_) => Ok(()),
            Err(e) => Err(Error::from_uefi_status(e.status(), None))
        }
    }
}

Read

impl Read for FileOperator {
    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
        match self.file.read(buf).log_warning() {
            Ok(size) => {
                self.current += size as u64;
                Ok(size)
            }
            Err(e) => {
                match e.data() {
                    Some(size) => Err(Error::from_uefi_status(e.status(), Some(format!("buffer to small need {}", size).as_str()))),
                    None => Err(Error::from_uefi_status(e.status(), None))
                }
            }
        }
    }
}

Write

impl Write for FileOperator {
    fn write(&mut self, buf: &[u8]) -> usize {
        match self.file.write(buf).log_warning() {
            Ok(_) => buf.len(),
            Err(size) => *size.data()
        }
    }

    fn flush(&mut self) -> Result<()> {
        match self.file.flush().log_warning() {
            Ok(()) => Ok(()),
            Err(e) => Err(Error::from_uefi_status(e.status(), None))
        }
    }
}

These methods are mainly reused by SimpleFileSystem. Our main focus is to design and provide convenient APIs. Let's design the read API first

Expand Read trait

Read  transact mainly reads the specified number of bytes and fills them into the given slice. The following is the read  transact flowchart

///Read the specified number of bytes
///Fill the read data into 'buf'
fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<()> {
   // Is buf filled?
    while !buf.is_empty() {
    	// Read and fill in data
        match self.read(buf){
            Ok(0) => break,
            Ok(n) => {
                let tmp = buf;
                buf = &mut tmp[n..];
            }
            Err(e) => return Err(e),
        }
    }
    // Is buf empty?
    if !buf.is_empty() {
        Err(Error::new(ErrorKind::UnexpectedEof, "failed to fill whole buffer"))
    } else {
        Ok(())
    }
}

Read ﹣ to ﹣ end reads all bytes until it encounters EOF, and fills the read data into buf. The following is the flow chart of read ﹣ to ﹣ end

The difficulty in read ﹣ to ﹣ end is the expansion operation. The expansion operation has two steps in total

  1. Use GlobalAllocator to request new memory space
  2. The newly requested memory space will be initialized (all set to 0)

Secondly, we need to record the number of bytes read each time in order to accurately express the size of the buffer. When expanding, we only focus on the size of the buffer, not the size, but only the size of the buffer when users use it (that is, the number of bytes read from the file)
So we need a new data structure to complete this operation. It will complete 2 operations

  1. Save the read and record the number of bytes read each time
  2. Set buffer size after reading
    We can get this structure
struct Guard<'a> {
    buf: &'a mut Vec<u8>,
    len: usize,
}

Guard will complete the above two operations. The time to set the buffer size should be at the end of guard's life cycle. Therefore, the buffer size needs to be set before guard is dropped, so we can override the Drop track

impl Drop for Guard<'_> {
    fn drop(&mut self) {
        unsafe {
            self.buf.set_len(self.len);
        }
    }
}

Now we have another expansion problem to solve. In the alloc library, we have implemented the function reserve for Vec. After the expansion, we need to reset the buffer capacity
Then initialize the newly applied memory space, so we also need a data structure Initializer for initialization, which is defined as follows

#[derive(Debug)]
pub struct Initializer(bool);

The bool value indicates whether the initialization operation is needed. Then we need to provide two initialization functions, zero and nop. The zero function is used to indicate that the memory is initialized, and nop means that the memory is not initialized. Therefore, the nop function is an unsafe operation

impl Initializer {
    ///Indicates that the buffer needs to be initialized
    #[inline]
    pub fn zeroing() -> Initializer {
        Initializer(true)
    }
    ///Indicates that the buffer will not be initialized
    #[inline]
    pub unsafe fn nop() -> Initializer {
        Initializer(false)
    }
    ///Indicates whether the buffer should be initialized
    #[inline]
    pub fn should_initialize(&self) -> bool {
        self.0
    }
}

Finally, we implement the initialization function initialize

    ///Buffers are initialized if needed (set to 0 based on buffer length)
    #[inline]
    pub fn initialize(&self, buf: &mut [u8]) {
        if self.should_initialize() {
            unsafe { ptr::write_bytes(buf.as_mut_ptr(), 0, buf.len()) }
        }
    }

The initialization function is relatively simple, which is not covered here

So our expansion operation can be written in this way. Here is the pseudo code

let r = Initializer::zeroing();
let mut g = Initialization Guard;
if Insufficient buffer capacity {
    // Capacity expansion
    g.buf.reserve(Bytes to be expanded);
    // Get the maximum buffer capacity after expansion
    let capacity = g.buf.capacity();
    unsafe {
        // Set buffer capacity
        g.buf.set_len(capacity);
        // To initialize the newly expanded memory of the buffer, only the newly added memory needs to be initialized
        r.initializer().initialize(&mut g.buf[g.len..]);
    }
}

The judging condition of insufficient buffer capacity is that the length of buffer in Guard is equal to the length of buffer
Accordingly, each time we read, we only need to read the data in the newly added memory, so we can write the read operation as follows

let start_len = Initial buffer size
// Read data to expansion section
let r = FileOperator Example
let ret:Option<usize>;
//  Just read the new memory
match r.read(&mut g.buf[g.len..]){
	// No data read
    Ok(0) => {
    	// Read memory size = size after read - size before read 
        ret = Ok(g.len - start_len);
        break;
    }
    // Record the number of bytes read each time
    Ok(n) => g.len += n,
    Err(e) => {
        ret = Err(e);
        break;
    }
}

In this way, we can form the read ﹐ to ﹐ end ﹐ with ﹐ reservation function

fn read_to_end_with_reservation<R, F>(r: &mut R, buf: &mut Vec<u8>, mut reservation_size: F) -> Result<usize> 
		where R: Read + ?Sized, F: FnMut(&R) -> usize
{
    let start_len = buf.len();
    let mut g = Guard { len: buf.len(), buf };
    let ret:Result<usize>;
    loop {
        // Expansion is required when the length of buffer is equal to the length of buffer
        if g.len == g.buf.len() {
            // Capacity expansion
            g.buf.reserve(reservation_size(r));
            // Get the maximum buffer capacity after expansion
            let capacity = g.buf.capacity();
            unsafe {
                // Set buffer capacity
                g.buf.set_len(capacity);
                // To initialize the newly expanded memory of the buffer, only the newly added memory needs to be initialized
                r.initializer().initialize(&mut g.buf[g.len..]);
            }
        }
        // Read data to expansion section
        match r.read(&mut g.buf[g.len..]){
        	// No data read
            Ok(0) => {
            	// //Read memory size = size after read - size before read 
                ret = Ok(g.len - start_len);
                break;
            }
            // Record the number of bytes read each time
            Ok(n) => g.len += n,
            Err(e) => {
                ret = Err(e);
                break;
            }
        }
    }

    ret
}

Read ﹣ to ﹣ end ﹣ with ﹣ reservation is the auxiliary function of read ﹣ to ﹣ end. The generic parameter f: fnmut - > use is used to specify the size of the expansion

Corresponding to our Readtrait, we need to add the initialization function (including the previously written read_exact function)

pub trait Read {
    ///Read as much file data as possible and fill it into 'but', and return the number of bytes read
    /// #Error
    ///If the specified buffer is too small, return 'buffer' to 'small' and the required 'size`
    fn read(&mut self, buf: &mut [u8]) -> Result<usize>;

    ///Read the specified number of bytes
    ///Fill the read data into 'buf'
    fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<()> {
        while !buf.is_empty() {
            match self.read(buf) {
                Ok(0) => break,
                Ok(n) => {
                    let tmp = buf;
                    buf = &mut tmp[n..];
                }
                Err(e) => return Err(e),
            }
        }
        if !buf.is_empty() {
            Err(Error::new(ErrorKind::UnexpectedEof, "failed to fill whole buffer"))
        } else {
            Ok(())
        }
    }

    #[inline]
    unsafe fn initializer(&self) -> Initializer {
        Initializer::zeroing()
    }
}

Then we can define the read ﹐ to ﹐ end function

fn read_to_end<R: Read + ?Sized>(r: &mut R, buf: &mut Vec<u8>) -> Result<usize> {
    read_to_end_with_reservation(r, buf, |_| 32)
}

Then add the read ﹐ to ﹐ end function to Read trait

pub trait Read {
	....
    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
        read_to_end(self, buf)
    }
    ...
}

Extend Write trait

For the time being, we only provide one write all function in write trace, which is enough. For the time being, we will not do too many complex operations in uefi environment

pub trait Write {
	fn write_all(&mut self, mut buf: &[u8]) -> Result<()> {
        while !buf.is_empty() {
            match self.write(buf) {
                0 => {
                    return Err(Error::new(ErrorKind::WriteZero, "failed to write whole buffer"));
                }
                n => buf = &buf[n..],
            }
        }
        Ok(())
    }
}

The implementation of the write all function is simple, just loop in

If you have read the code of std::io, you will know that this is the modified version of the official library, which is applicable to our current uefi environment

What is the next step?

In the next article, we need to recognize and analyze elf files, because our kernel is compiled with ELF files. With the convenient api provided in this chapter, it will be more convenient to parse elf files

Published 19 original articles · praised 1 · visited 1007
Private letter follow

Keywords: network Attribute

Added by jackpf on Fri, 21 Feb 2020 07:36:34 +0200