Get file size in uefi-rs

243 views Asked by At

I am making a basic uefi application that is supposed to load an elf kernel. I have gotten to the point that I have the fille loaded, and a buffer with the file info. But to actually read the file and do anything with it, I need to know the file size so I can make the buffer for it. I know uefi-rs has a FileInfo struct, but I do not know how to cast the buffer I have to this struct.

I have tried looking for solutions to similar problems, came across this Transmuting u8 buffer to struct in Rust. None of these solutions worked, I kept getting an error with the answers on that page because I cannot cast the thin u8 pointer to the fat FileInfo pointer.

This is my source code so far:

#![no_main]
#![no_std]
#![feature(abi_efiapi)]
#![allow(stable_features)]

#[macro_use]
extern crate alloc;

use elf_rs::{Elf, ElfFile};
use log::info;
use uefi::{prelude::{entry, BootServices, cstr16}, Handle, table::{SystemTable, Boot}, Status, Char16, proto::{loaded_image::LoadedImage, media::{file::{File, FileHandle, FileMode, FileAttribute, FileInfo}, fs::SimpleFileSystem}}, CStr16, data_types::Align};

fn load_file(path: &CStr16, boot_services: &BootServices) -> FileHandle {
    let loaded_image = boot_services.open_protocol_exclusive::<LoadedImage>(boot_services.image_handle()).unwrap();

    let mut file_system = boot_services.open_protocol_exclusive::<SimpleFileSystem>(loaded_image.device()).unwrap();

    let mut directory = file_system.open_volume().unwrap();

    directory.open(path, FileMode::Read, FileAttribute::READ_ONLY).unwrap()
}

#[entry]
fn main(image_handle: Handle, mut system_table: SystemTable<Boot>) -> Status {
    uefi_services::init(&mut system_table).unwrap();
    info!("Loading kernel...");
    let mut kernel = load_file(cstr16!("kernel.elf"), system_table.boot_services()).into_regular_file().unwrap();
    let mut small_buffer = vec![0u8; 0];
    let size = kernel.get_info::<FileInfo>(&mut small_buffer).err().unwrap().data().unwrap();
    let mut file_info = vec![0u8; size];
    kernel.get_info::<FileInfo>(&mut file_info);
    
    let info: FileInfo; //this is what I need


    let elf_buffer = vec![0u8; info.file_size().try_into().unwrap()];
    
    let elf = Elf::from_bytes(&mut elf_buffer).expect("Kernel loading failed!");

    info!("{:?} header: {:?}", elf, elf.elf_header());

    for p in elf.program_header_iter() {
        info!("{:x?}", p);
    }

    for s in elf.section_header_iter() {
        info!("{:x?}", s);
    }

    let s = elf.lookup_section(b".text");
    info!("s {:?}", s);

    system_table.boot_services().stall(100_000_000);
    Status::SUCCESS
}
1

There are 1 answers

0
Nicholas Bishop On

There are a few ways of doing this. Which one is right for you depends on how much control you need over the file reading and the memory being read into.

The very simplest option may be to use uefi::fs::FileSystem. That provides a metadata method to get file info, which includes the file size. Even better, if you just want the size in order to read the file into a vec, you can use FileSystem::read to get a Vec<u8>. Note that uefi::fs requires the alloc feature of the uefi crate to be enabled.

Alternatively you can use File::get_info, File::get_info_boxed, or file seeking to get the size. Here's an example of how:

#![no_main]
#![no_std]

extern crate alloc;

use alloc::vec;
use alloc::vec::Vec;
use uefi::prelude::*;
use uefi::proto::media::file::{File, FileAttribute, FileInfo, FileMode, RegularFile};
use uefi::proto::media::fs::SimpleFileSystem;
use uefi::table::boot::ScopedProtocol;
use uefi::{CStr16, Result};
use uefi_services::println;

/// Read via the `uefi::fs` module. Note that this requires the `alloc`
/// feature of the `uefi` crate to be enabled.
fn read_via_fs_module(path: &CStr16, simple_file_system: ScopedProtocol<SimpleFileSystem>) {
    let mut fs = uefi::fs::FileSystem::new(simple_file_system);

    let size = fs
        .metadata(path)
        .expect("failed to get metadata")
        .file_size();

    let vec = fs.read(path).expect("read failed");

    assert_eq!(vec.len(), usize::try_from(size).unwrap());

    println!(
        "successfully read {} into a Vec of length {}",
        path,
        vec.len()
    );
}

/// Read via the `SimpleFileSystem` protocol directly.
fn read_via_sfs(
    path: &CStr16,
    simple_file_system: &mut ScopedProtocol<SimpleFileSystem>,
) -> Result {
    let mut root = simple_file_system.open_volume()?;
    let mut file = root.open(
        path,
        FileMode::Read,
        // File attributes only matter when creating a file, so
        // use `empty`.
        FileAttribute::empty(),
    )?;

    // There are a few ways to get the length:

    // Option 1: `get_boxed_info`. This requires the `alloc` feature of
    // the `uefi` crate to be enabled.
    let size1 = file.get_boxed_info::<FileInfo>()?.file_size();

    // Option 2: `get_info` with a stack buffer that is sufficiently
    // large to hold the output. This call would fail if the buffer was
    // too small.
    let mut buf = [0; 128];
    let size2 = file.get_info::<FileInfo>(&mut buf).unwrap().file_size();

    // Option 3: `get_info` with a buffer allocated with the correct
    // size. We'll pass in an empty buffer initially to get the
    // size. Since the buffer is too small, we'll get an error -- and
    // that error will contain a `usize` with the required buffer size.
    let mut buf = Vec::new();
    let info_size = file
        .get_info::<FileInfo>(&mut buf)
        .unwrap_err()
        .data()
        .unwrap();
    buf.resize(info_size, 0);
    let size3 = file.get_info::<FileInfo>(&mut buf).unwrap().file_size();

    // Option 4: rather than using `FileInfo`, seek to the end of the
    // buffer. First we need to convert the file into a `RegularFile`
    // (the other type of file is a directory). We'd do that to read the
    // file anyway.
    let mut file = file.into_regular_file().unwrap();
    file.set_position(RegularFile::END_OF_FILE)?;
    let size4 = file.get_position()?;

    // Verify that all four methods of getting the size gave the same
    // value.
    assert_eq!(size1, size2);
    assert_eq!(size1, size3);
    assert_eq!(size1, size4);

    // Now we can create a buffer and read into it.
    let mut buf = vec![0; usize::try_from(size4).unwrap()];
    // Earlier we seeked to the end of the file. Seek back to the
    // beginning so we read the whole thing.
    file.set_position(0)?;
    file.read(&mut buf)?;

    println!("successfully read {} bytes from {}", size1, path);
    Ok(())
}

#[entry]
fn main(image: Handle, mut st: SystemTable<Boot>) -> Status {
    uefi_services::init(&mut st).unwrap();
    // Get the `SimpleFileSystem` protocol for the file system that the
    // current executable was loaded from, probably the ESP (EFI System
    // Partition).
    let mut simple_file_system = st.boot_services().get_image_file_system(image).unwrap();

    let path = cstr16!("kernel.elf");

    read_via_sfs(path, &mut simple_file_system).unwrap();

    read_via_fs_module(path, simple_file_system);
    Status::SUCCESS
}