Simple FAT16/32 driver implementation
This commit is contained in:
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[build]
|
||||
target = "x86_64-unknown-uefi"
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/target
|
||||
/disk.img
|
||||
/OVMF_CODE.fd
|
||||
/OVMF_VARS.fd
|
||||
11
.zed/settings.json
Normal file
11
.zed/settings.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"lsp": {
|
||||
"rust-analyzer": {
|
||||
"initialization_options": {
|
||||
"cargo": {
|
||||
"allTargets": false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
164
Cargo.lock
generated
Normal file
164
Cargo.lock
generated
Normal file
@@ -0,0 +1,164 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "efi-loader"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"log",
|
||||
"uefi",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ptr_meta"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b9a0cf95a1196af61d4f1cbdab967179516d9a4a4312af1f31948f8f6224a79"
|
||||
dependencies = [
|
||||
"ptr_meta_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ptr_meta_derive"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ucs2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df79298e11f316400c57ec268f3c2c29ac3c4d4777687955cd3d4f3a35ce7eba"
|
||||
dependencies = [
|
||||
"bit_field",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uefi"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71fe9058b73ee2b6559524af9e33199c13b2485ddbf3ad1181b68051cdc50c17"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"log",
|
||||
"ptr_meta",
|
||||
"ucs2",
|
||||
"uefi-macros",
|
||||
"uefi-raw",
|
||||
"uguid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uefi-macros"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4687412b5ac74d245d5bfb1733ede50c31be19bf8a4b6a967a29b451bab49e67"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uefi-raw"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f64fe59e11af447d12fd60a403c74106eb104309f34b4c6dbce6e927d97da9d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"uguid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uguid"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c8352f8c05e47892e7eaf13b34abd76a7f4aeaf817b716e88789381927f199c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "efi-loader"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.11.0"
|
||||
log = "0.4.29"
|
||||
uefi = { version = "0.36.1", features = ["logger", "panic_handler", "log-debugcon", "alloc", "global_allocator"] }
|
||||
zerocopy = { version = "0.8.47", features = ["derive"] }
|
||||
|
||||
[workspace]
|
||||
7
README.md
Normal file
7
README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Setup
|
||||
- Install `qemu-system-x86_64`, `gdisk`
|
||||
- Install `cargo` (using [rustup](https://rustup.rs/) is the preferred cross-platform way to get Rust set up on your machine)
|
||||
- We also require `OVMF_CODE.fd` and `OVMF_VARS.fd` in the root directory to boot QEMU in UEFI mode. These files are omitted due to potential licensing issues (read: I haven't properly looked into it yet).
|
||||
- For creating a test disk image automatically, we require `mkfs.fat` and `mkfs.ext4` to be available.
|
||||
# Running
|
||||
Simply run `./run.sh` in the root directory to take care of setting up the environment and running QEMU with the required options.
|
||||
29
run.sh
Executable file
29
run.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_DIR=$(dirname "$(realpath "$0")")
|
||||
|
||||
cargo build
|
||||
mkdir -p "$PROJECT_DIR/target/esp/efi/boot"
|
||||
cp "$PROJECT_DIR/target/x86_64-unknown-uefi/debug/efi-loader.efi" "$PROJECT_DIR/target/esp/efi/boot/bootx64.efi"
|
||||
|
||||
if [[ ! -f "$PROJECT_DIR/disk.img" ]]; then
|
||||
dd if=/dev/zero of="$PROJECT_DIR/disk.img" bs=1M count=256
|
||||
sgdisk --zap-all --new=1::+64M --new=2:: disk.img
|
||||
echo Creating file systems on test disk, might require password
|
||||
DEVICE=$(sudo losetup -fP --show "$PROJECT_DIR/disk.img")
|
||||
sudo mkfs.fat -F32 ${DEVICE}p1
|
||||
sudo mkfs.ext4 ${DEVICE}p2
|
||||
sudo losetup -d $DEVICE
|
||||
fi
|
||||
|
||||
qemu-system-x86_64 -enable-kvm \
|
||||
-drive format=raw,file=fat:rw:target/esp,id=disk0,if=none \
|
||||
-drive format=raw,file=disk.img,id=disk1,if=none \
|
||||
-drive if=pflash,format=raw,readonly=on,file=OVMF_CODE.fd \
|
||||
-drive if=pflash,format=raw,readonly=on,file=OVMF_VARS.fd \
|
||||
-device ahci,id=ahci \
|
||||
-device ide-hd,bus=ahci.0,drive=disk0 \
|
||||
-device ide-hd,bus=ahci.1,drive=disk1 \
|
||||
--no-shutdown
|
||||
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
targets = ["x86_64-unknown-uefi"]
|
||||
33
src/fs/fat/cluster_chain.rs
Normal file
33
src/fs/fat/cluster_chain.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use core::{
|
||||
fmt::Debug,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::fs::fat::Cluster;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ClusterChain(Vec<Cluster>);
|
||||
impl Deref for ClusterChain {
|
||||
type Target = [Cluster];
|
||||
|
||||
fn deref(&self) -> &[Cluster] {
|
||||
&self.0[..]
|
||||
}
|
||||
}
|
||||
impl DerefMut for ClusterChain {
|
||||
fn deref_mut(&mut self) -> &mut [Cluster] {
|
||||
&mut self.0[..]
|
||||
}
|
||||
}
|
||||
impl From<Vec<Cluster>> for ClusterChain {
|
||||
fn from(value: Vec<Cluster>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl Debug for ClusterChain {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.write_fmt(format_args!("ClusterChain({:04X?})", self.0))
|
||||
}
|
||||
}
|
||||
289
src/fs/fat/directory.rs
Normal file
289
src/fs/fat/directory.rs
Normal file
@@ -0,0 +1,289 @@
|
||||
use core::{fmt::Display, ops::Index};
|
||||
|
||||
use alloc::{
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
use bitflags::bitflags;
|
||||
use zerocopy::{FromBytes, TryFromBytes};
|
||||
|
||||
use crate::fs::{
|
||||
Error,
|
||||
fat::{Cluster, FatFs, cluster_chain::ClusterChain},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, FromBytes)]
|
||||
pub struct DirectoryAttribute(u8);
|
||||
impl Display for DirectoryAttribute {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
let r = if self.contains(Self::READ_ONLY) {
|
||||
"R"
|
||||
} else {
|
||||
"W"
|
||||
};
|
||||
let d = if self.contains(Self::DIRECTORY) {
|
||||
"DIR"
|
||||
} else if self.contains(Self::LFN) {
|
||||
"LFN"
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
let h = if self.contains(Self::HIDDEN) {
|
||||
"H"
|
||||
} else {
|
||||
"-"
|
||||
};
|
||||
let s = if self.contains(Self::SYSTEM) {
|
||||
"S"
|
||||
} else {
|
||||
"-"
|
||||
};
|
||||
let v = if self.contains(Self::VOLUME_ID) {
|
||||
"V"
|
||||
} else {
|
||||
"-"
|
||||
};
|
||||
let a = if self.contains(Self::ARCHIVE) {
|
||||
"A"
|
||||
} else {
|
||||
"-"
|
||||
};
|
||||
f.write_fmt(format_args!("{d} {r}{h}{s}{v}{a}"))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl DirectoryAttribute {
|
||||
pub fn is_writable(&self) -> bool {
|
||||
!self.contains(Self::READ_ONLY)
|
||||
}
|
||||
pub fn is_hidden(&self) -> bool {
|
||||
self.contains(Self::HIDDEN)
|
||||
}
|
||||
pub fn is_system(&self) -> bool {
|
||||
self.contains(Self::SYSTEM)
|
||||
}
|
||||
pub fn is_directory(&self) -> bool {
|
||||
self.contains(Self::DIRECTORY)
|
||||
}
|
||||
pub fn is_archive(&self) -> bool {
|
||||
self.contains(Self::ARCHIVE)
|
||||
}
|
||||
pub fn is_volume_id(&self) -> bool {
|
||||
self.contains(Self::VOLUME_ID)
|
||||
}
|
||||
pub fn is_lfn(&self) -> bool {
|
||||
self.intersects(Self::LFN)
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
impl DirectoryAttribute: u8 {
|
||||
const NONE = 0;
|
||||
const READ_ONLY = 0x01;
|
||||
const HIDDEN = 0x02;
|
||||
const SYSTEM = 0x04;
|
||||
const VOLUME_ID = 0x08;
|
||||
const DIRECTORY = 0x10;
|
||||
const ARCHIVE = 0x20;
|
||||
const LFN = 0x0F;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct DirectoryList(Vec<DirectoryEntry>);
|
||||
impl Index<usize> for DirectoryList {
|
||||
type Output = DirectoryEntry;
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DirectoryListIter {
|
||||
list: DirectoryList,
|
||||
list_index: usize,
|
||||
}
|
||||
impl DirectoryListIter {
|
||||
pub fn from_chain(fs: &mut FatFs, cluster_chain: &ClusterChain) -> Self {
|
||||
let mut result = Self {
|
||||
list_index: 0,
|
||||
list: DirectoryList(Vec::with_capacity(32)),
|
||||
};
|
||||
result.buffer_entries_from_chain(fs, cluster_chain).unwrap();
|
||||
result
|
||||
}
|
||||
pub fn from_root(fs: &mut FatFs) -> Self {
|
||||
let mut result = Self {
|
||||
list_index: 0,
|
||||
list: DirectoryList(Vec::with_capacity(fs.boot_sector.root_entry_count as usize)),
|
||||
};
|
||||
result.buffer_entries_from_root(fs).unwrap();
|
||||
result
|
||||
}
|
||||
fn buffer_entries_from_chain(
|
||||
&mut self,
|
||||
fs: &mut FatFs,
|
||||
chain: &ClusterChain,
|
||||
) -> Result<(), Error<()>> {
|
||||
let mut sector_buffer = Vec::with_capacity(fs.sector_size_bytes());
|
||||
sector_buffer.resize(fs.sector_size_bytes(), 0);
|
||||
for cluster in chain.iter() {
|
||||
self.append_directory_list_from_cluster(fs, *cluster, sector_buffer.as_mut_slice())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn buffer_entries_from_root(&mut self, fs: &mut FatFs) -> Result<(), Error<()>> {
|
||||
let mut sector_buffer = Vec::with_capacity(fs.sector_size_bytes());
|
||||
sector_buffer.resize(fs.sector_size_bytes(), 0);
|
||||
fs.seek(fs.root_dir_sector());
|
||||
|
||||
for _ in 0..fs.root_dir_sectors {
|
||||
fs.partition.read(&mut sector_buffer)?;
|
||||
if !self.load_entries_from_bytes(sector_buffer.as_slice()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn append_directory_list_from_cluster(
|
||||
&mut self,
|
||||
fs: &mut FatFs,
|
||||
cluster: Cluster,
|
||||
buffer: &mut [u8],
|
||||
) -> Result<(), Error<()>> {
|
||||
fs.seek(cluster);
|
||||
fs.partition.read(buffer)?;
|
||||
|
||||
self.load_entries_from_bytes(buffer as &[u8]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_entries_from_bytes(&mut self, mut bytes: &[u8]) -> bool {
|
||||
while bytes.len() > 0 {
|
||||
let (entry, remainder) = DirectoryEntry::try_read_from_prefix(bytes).unwrap();
|
||||
bytes = remainder;
|
||||
if entry.is_end() {
|
||||
return false;
|
||||
}
|
||||
if entry.is_used() {
|
||||
self.list.0.push(entry);
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
impl Iterator for DirectoryListIter {
|
||||
type Item = DirectoryEntry;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.list_index >= self.list.0.len() {
|
||||
None
|
||||
} else {
|
||||
let result = self.list.0[self.list_index];
|
||||
self.list_index += 1;
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, FromBytes)]
|
||||
#[repr(packed, C)]
|
||||
pub struct DirectoryEntry {
|
||||
filename: [u8; 11],
|
||||
attributes: DirectoryAttribute,
|
||||
reserved_0: u8,
|
||||
ctime_100th: u8,
|
||||
ctime: u32,
|
||||
atime: u16,
|
||||
cluster_high: u16,
|
||||
mtime: u32,
|
||||
cluster_low: u16,
|
||||
file_size: u32,
|
||||
}
|
||||
impl DirectoryEntry {
|
||||
pub fn start_cluster(&self) -> Cluster {
|
||||
Cluster((self.cluster_high as u32) << 16 | self.cluster_low as u32)
|
||||
}
|
||||
pub fn is_end(&self) -> bool {
|
||||
self.filename[0] == 0
|
||||
}
|
||||
pub fn is_unused(&self) -> bool {
|
||||
self.filename[0] == 0xE5
|
||||
}
|
||||
pub fn is_used(&self) -> bool {
|
||||
!self.is_unused() && !self.is_end()
|
||||
}
|
||||
pub fn is_lfn(&self) -> bool {
|
||||
self.attributes.is_lfn()
|
||||
}
|
||||
pub fn decode_lfn(&self) -> (u8, String) {
|
||||
let mut concat = [0u8; 26];
|
||||
let mut result = [0u16; 13];
|
||||
unsafe {
|
||||
let data = core::mem::transmute::<*const DirectoryEntry, *const u8>(self as *const _);
|
||||
let temp = concat.as_mut_ptr();
|
||||
temp.byte_add(0)
|
||||
.copy_from_nonoverlapping(data.byte_add(1), 10);
|
||||
temp.byte_add(10)
|
||||
.copy_from_nonoverlapping(data.byte_add(14), 12);
|
||||
temp.byte_add(22)
|
||||
.copy_from_nonoverlapping(data.byte_add(28), 4);
|
||||
//String::from_utf16(unsafe { core::mem::transmute(concat.as_slice()) })
|
||||
}
|
||||
concat
|
||||
.chunks(2)
|
||||
.enumerate()
|
||||
.for_each(|(i, val)| result[i] = u16::from_le_bytes([val[0], val[1]]));
|
||||
let end = result.iter().position(|p| *p == 0).unwrap_or(13);
|
||||
(self.filename[0], String::from_utf16_lossy(&result[..end]))
|
||||
}
|
||||
pub fn name(&self) -> String {
|
||||
String::from_utf8_lossy(&self.filename).to_string()
|
||||
}
|
||||
pub fn attributes(&self) -> DirectoryAttribute {
|
||||
self.attributes
|
||||
}
|
||||
pub fn size(&self) -> u32 {
|
||||
self.file_size
|
||||
}
|
||||
}
|
||||
impl Display for DirectoryEntry {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
let end = if self.is_end() {
|
||||
"END "
|
||||
} else if self.is_used() {
|
||||
"USED"
|
||||
} else {
|
||||
"FREE"
|
||||
};
|
||||
|
||||
if self.attributes.is_lfn() {
|
||||
let (part, lfn) = self.decode_lfn();
|
||||
f.write_fmt(format_args!("{lfn:13} {} [{:02X}]", self.attributes, part))
|
||||
} else {
|
||||
let mut filename = self.filename.clone();
|
||||
if self.is_used() {
|
||||
f.write_fmt(format_args!(
|
||||
"{:13} {} {end} {}",
|
||||
self.name(),
|
||||
self.attributes,
|
||||
self.start_cluster()
|
||||
))
|
||||
} else {
|
||||
filename.iter_mut().for_each(|e| {
|
||||
if *e < 0x20 {
|
||||
*e = b' '
|
||||
} else if *e == 0xE5 {
|
||||
*e = b'_'
|
||||
}
|
||||
});
|
||||
f.write_fmt(format_args!(
|
||||
"{:13} {} {end} {}",
|
||||
self.name(),
|
||||
self.attributes,
|
||||
self.start_cluster()
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
78
src/fs/fat/fat_type.rs
Normal file
78
src/fs/fat/fat_type.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use alloc::vec::Vec;
|
||||
use zerocopy::FromBytes;
|
||||
|
||||
use crate::fs::fat::{Cluster, FatFs, Sector};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(packed, C)]
|
||||
pub struct Fat12;
|
||||
//impl Fat12 {
|
||||
// pub fn read_fat(_fat: &mut FatFs) -> Result<Vec<u16>, uefi::Error> {
|
||||
// unimplemented!()
|
||||
// }
|
||||
//}
|
||||
|
||||
#[derive(Debug, Copy, Clone, FromBytes)]
|
||||
#[repr(packed, C)]
|
||||
pub struct Fat16 {
|
||||
pub bios_drive_num: u8,
|
||||
reserved_0: u8,
|
||||
pub boot_signature: u16,
|
||||
pub volume_id: u32,
|
||||
pub volume_label: [u8; 11],
|
||||
pub fat_type_label: [u8; 8],
|
||||
}
|
||||
impl Fat16 {
|
||||
pub fn read_fat(fat: &mut FatFs) -> Result<Vec<Cluster>, uefi::Error> {
|
||||
let fat_len_bytes = fat.fat_size_bytes() as usize;
|
||||
let mut buffer = Vec::with_capacity(fat_len_bytes);
|
||||
buffer.resize(fat_len_bytes, 0u8);
|
||||
fat.seek(Sector(fat.first_fat_sector));
|
||||
fat.partition.read(&mut buffer)?;
|
||||
Ok(buffer
|
||||
.chunks_exact(2)
|
||||
.into_iter()
|
||||
.map(|val| Cluster::from_16(u16::from_ne_bytes([val[0], val[1]])))
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, FromBytes)]
|
||||
#[repr(packed, C)]
|
||||
pub struct Fat32 {
|
||||
pub table_size_32: u32,
|
||||
pub extended_flags: u16,
|
||||
pub fat_version: u16,
|
||||
pub root_cluster: u32,
|
||||
pub fat_info: u16,
|
||||
pub backup_bs_sector: u16,
|
||||
reserved_0: [u8; 12],
|
||||
pub drive_number: u8,
|
||||
reserved_1: u8,
|
||||
pub boot_signature: u8,
|
||||
pub volume_id: u32,
|
||||
pub volume_label: [u8; 11],
|
||||
pub fat_type_label: [u8; 8],
|
||||
}
|
||||
impl Fat32 {
|
||||
pub fn read_fat(fat: &mut FatFs) -> Result<Vec<Cluster>, uefi::Error> {
|
||||
let fat_len_bytes = fat.fat_size_bytes() as usize;
|
||||
let mut buffer = Vec::with_capacity(fat_len_bytes);
|
||||
buffer.resize(fat_len_bytes, 0u8);
|
||||
fat.seek(Sector(fat.first_fat_sector));
|
||||
fat.partition.read(&mut buffer)?;
|
||||
Ok(buffer
|
||||
.chunks_exact(4)
|
||||
.into_iter()
|
||||
.map(|val| Cluster::from_32(u32::from_ne_bytes([val[0], val[1], val[2], val[3]])))
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[allow(unused)]
|
||||
pub enum FatType {
|
||||
Fat12(Fat12),
|
||||
Fat16(Fat16),
|
||||
Fat32(Fat32),
|
||||
}
|
||||
568
src/fs/fat/mod.rs
Normal file
568
src/fs/fat/mod.rs
Normal file
@@ -0,0 +1,568 @@
|
||||
pub mod cluster_chain;
|
||||
pub mod directory;
|
||||
pub mod fat_type;
|
||||
|
||||
use core::fmt::{Debug, Display};
|
||||
|
||||
use alloc::{
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
use log::debug;
|
||||
use zerocopy::{FromBytes, TryFromBytes};
|
||||
|
||||
use crate::{
|
||||
Partition, Seek,
|
||||
fs::{
|
||||
BpbError, Error, Seekable,
|
||||
fat::{
|
||||
cluster_chain::ClusterChain,
|
||||
directory::{DirectoryAttribute, DirectoryEntry, DirectoryListIter},
|
||||
fat_type::{Fat12, Fat16, Fat32, FatType},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub struct LfnBuilder(Vec<(u8, String)>);
|
||||
impl LfnBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self(Vec::with_capacity(16))
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
pub fn add(&mut self, part: (u8, String)) {
|
||||
self.0.push(part);
|
||||
}
|
||||
pub fn build(mut self) -> String {
|
||||
self.0.sort_by_key(|e| e.0);
|
||||
self.0.into_iter().map(|e| e.1).collect::<Vec<_>>().join("")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default, FromBytes)]
|
||||
#[repr(packed)]
|
||||
pub struct FatBootSector {
|
||||
bootjmp: [u8; 3],
|
||||
oem_name: [u8; 8],
|
||||
bytes_per_sector: u16,
|
||||
sectors_per_cluster: u8,
|
||||
reserved_sector_count: u16,
|
||||
table_count: u8,
|
||||
root_entry_count: u16,
|
||||
total_sectors_16: u16,
|
||||
media_type: u8,
|
||||
table_size_16: u16,
|
||||
sectors_per_track: u16,
|
||||
head_side_count: u16,
|
||||
hidden_sector_count: u32,
|
||||
total_sectors_32: u32,
|
||||
}
|
||||
impl FatBootSector {
|
||||
pub fn cluster_size_bytes(&self) -> u32 {
|
||||
let bps = unsafe { (&raw const self.bytes_per_sector).read_unaligned() };
|
||||
bps as u32 * self.sectors_per_cluster as u32
|
||||
}
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<(FatBootSector, FatType), BpbError> {
|
||||
let (bpb, extended_fat_info) = FatBootSector::try_read_from_prefix(bytes).unwrap();
|
||||
let bps = unsafe { (&raw const bpb.bytes_per_sector).read_unaligned() as u32 };
|
||||
if ![512, 1024, 2048, 4096].contains(&bps) {
|
||||
return Err(BpbError::BytesPerSector);
|
||||
}
|
||||
|
||||
let total_sectors = if bpb.total_sectors_16 == 0 {
|
||||
bpb.total_sectors_32
|
||||
} else {
|
||||
bpb.total_sectors_16 as u32
|
||||
};
|
||||
let fat_size = bpb.table_size_16 as u32;
|
||||
let root_dir_sectors = (bpb.root_entry_count as u32 * 32 + (bps - 1)) / bps;
|
||||
let data_sectors = total_sectors - bpb.reserved_sector_count as u32
|
||||
+ bpb.table_count as u32 * fat_size
|
||||
+ root_dir_sectors;
|
||||
let total_clusters = data_sectors / bpb.sectors_per_cluster as u32;
|
||||
|
||||
let fat_type = if total_clusters < 4085 {
|
||||
FatType::Fat12(Fat12)
|
||||
} else if total_clusters < 65525 {
|
||||
let (fat, _) = Fat16::try_read_from_prefix(extended_fat_info).unwrap();
|
||||
FatType::Fat16(fat)
|
||||
} else {
|
||||
let (fat, _) = Fat32::try_read_from_prefix(extended_fat_info).unwrap();
|
||||
FatType::Fat32(fat)
|
||||
};
|
||||
|
||||
if bpb.sectors_per_cluster == 0
|
||||
|| (bpb.sectors_per_cluster & (bpb.sectors_per_cluster.wrapping_sub(1))) != 0
|
||||
{
|
||||
return Err(BpbError::SectorsPerCluster);
|
||||
}
|
||||
if bpb.reserved_sector_count == 0 {
|
||||
return Err(BpbError::ReservedSectorCount);
|
||||
}
|
||||
if bpb.table_count == 0 {
|
||||
return Err(BpbError::NumFats);
|
||||
}
|
||||
if bps * bpb.sectors_per_cluster as u32 == 0 {
|
||||
return Err(BpbError::ClusterSize);
|
||||
}
|
||||
if total_sectors == 0 {
|
||||
return Err(BpbError::TotalSectorCount);
|
||||
}
|
||||
|
||||
Ok((bpb, fat_type))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FatFs {
|
||||
partition: Partition,
|
||||
boot_sector: FatBootSector,
|
||||
fat_type: FatType,
|
||||
allocation_table: Option<FileAllocationTable>,
|
||||
root_directory: CachedFsEntry,
|
||||
|
||||
//total_sectors: u32,
|
||||
fat_size: u32,
|
||||
root_dir_sectors: u32,
|
||||
first_data_sector: u32,
|
||||
first_fat_sector: u32,
|
||||
//data_sectors: u32,
|
||||
//total_clusters: u32,
|
||||
}
|
||||
|
||||
impl FatFs {
|
||||
pub fn mount(mut partition: Partition) -> Result<FatFs, Error<BpbError>> {
|
||||
let mut sector = [0u8; 90];
|
||||
partition.seek(Seek::SetFromStart(0));
|
||||
if partition.read(&mut sector).is_err() {
|
||||
return Err(Error::Open);
|
||||
}
|
||||
|
||||
let (boot_sector, fat_type) = FatBootSector::from_bytes(§or)?;
|
||||
let bps = unsafe { (&raw const boot_sector.bytes_per_sector).read_unaligned() as u32 };
|
||||
|
||||
//let total_sectors = if boot_sector.total_sectors_16 == 0 {
|
||||
// boot_sector.total_sectors_32
|
||||
//} else {
|
||||
// boot_sector.total_sectors_16 as u32
|
||||
//};
|
||||
let fat_size = if boot_sector.table_size_16 == 0 {
|
||||
match fat_type {
|
||||
FatType::Fat32(fat) => fat.table_size_32,
|
||||
_ => boot_sector.table_size_16 as u32,
|
||||
}
|
||||
} else {
|
||||
boot_sector.table_size_16 as u32
|
||||
};
|
||||
let root_dir_sectors = (boot_sector.root_entry_count as u32 * 32 + (bps - 1)) / bps;
|
||||
//let data_sectors = total_sectors - boot_sector.reserved_sector_count as u32
|
||||
// + boot_sector.table_count as u32 * fat_size
|
||||
// + root_dir_sectors;
|
||||
//let total_clusters = data_sectors / boot_sector.sectors_per_cluster as u32;
|
||||
|
||||
Ok(Self {
|
||||
boot_sector,
|
||||
fat_type,
|
||||
partition,
|
||||
allocation_table: None,
|
||||
root_directory: CachedFsEntry::root(),
|
||||
|
||||
//total_sectors,
|
||||
fat_size,
|
||||
root_dir_sectors,
|
||||
first_data_sector: boot_sector.reserved_sector_count as u32
|
||||
+ (boot_sector.table_count as u32 * fat_size)
|
||||
+ root_dir_sectors,
|
||||
first_fat_sector: boot_sector.reserved_sector_count as u32,
|
||||
//data_sectors,
|
||||
//total_clusters,
|
||||
})
|
||||
}
|
||||
//pub fn is_fat12(&self) -> bool {
|
||||
// match self.fat_type {
|
||||
// FatType::Fat12(_) => true,
|
||||
// _ => false,
|
||||
// }
|
||||
//}
|
||||
pub fn is_fat16(&self) -> bool {
|
||||
match self.fat_type {
|
||||
FatType::Fat16(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
pub fn is_fat32(&self) -> bool {
|
||||
match self.fat_type {
|
||||
FatType::Fat32(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
pub fn sector_size_bytes(&self) -> usize {
|
||||
self.boot_sector.bytes_per_sector as usize
|
||||
}
|
||||
pub fn cluster_size_bytes(&self) -> usize {
|
||||
self.boot_sector.cluster_size_bytes() as usize
|
||||
}
|
||||
fn fat_size_bytes(&self) -> u32 {
|
||||
self.fat_size * self.boot_sector.bytes_per_sector as u32
|
||||
}
|
||||
fn read_allocation_table(&mut self) -> Result<(), uefi::Error> {
|
||||
self.allocation_table = Some(FileAllocationTable::from_fat(self)?);
|
||||
Ok(())
|
||||
}
|
||||
fn root_dir_sector(&self) -> Sector {
|
||||
Sector(self.first_data_sector - self.root_dir_sectors)
|
||||
}
|
||||
|
||||
pub fn open(&mut self, path: &str) -> Result<FileHandle, Error<()>> {
|
||||
if !path.starts_with("/") {
|
||||
return Err(Error::InvalidPath);
|
||||
}
|
||||
if self.allocation_table.is_none() {
|
||||
self.read_allocation_table()?;
|
||||
if let Some(fat) = &self.allocation_table {
|
||||
match self.fat_type {
|
||||
FatType::Fat32(fat32) => {
|
||||
self.root_directory.cluster_chain =
|
||||
fat.read_cluster_chain(Cluster::from_32(fat32.root_cluster));
|
||||
}
|
||||
FatType::Fat16(_) => {
|
||||
let children = CachedFsEntry::load_children_from_root_dir(self);
|
||||
self.root_directory.children = Some(children);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
let path_segments = path[1..].split("/").collect::<Vec<_>>();
|
||||
let mut path_index = 0;
|
||||
let mut current_entry = None;
|
||||
while path_index < path_segments.len() {
|
||||
if current_entry
|
||||
.as_ref()
|
||||
.unwrap_or(&self.root_directory)
|
||||
.children
|
||||
.is_none()
|
||||
{
|
||||
let cluster_chain = current_entry
|
||||
.as_ref()
|
||||
.unwrap_or(&self.root_directory)
|
||||
.cluster_chain
|
||||
.clone();
|
||||
let loaded_children = CachedFsEntry::load_children(&cluster_chain, self);
|
||||
current_entry
|
||||
.as_mut()
|
||||
.unwrap_or(&mut self.root_directory)
|
||||
.children = Some(loaded_children);
|
||||
}
|
||||
|
||||
if let Some(child) = current_entry
|
||||
.as_ref()
|
||||
.unwrap_or(&self.root_directory)
|
||||
.find_child_cached(path_segments[path_index])
|
||||
{
|
||||
debug!("{child}");
|
||||
current_entry = Some(child);
|
||||
path_index += 1;
|
||||
} else {
|
||||
return Err(Error::NotFound);
|
||||
}
|
||||
}
|
||||
|
||||
let entry = current_entry.ok_or(Error::NotFound)?;
|
||||
if entry.is_directory() {
|
||||
return Err(Error::Directory);
|
||||
}
|
||||
|
||||
Ok(entry.into())
|
||||
}
|
||||
|
||||
pub fn seek<S: Seekable<FatFs>>(&mut self, to: S) {
|
||||
let offset = to.to_byte_offset(self);
|
||||
self.partition.seek(Seek::SetFromStart(offset));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FileAllocationTable {
|
||||
cluster_chain: Vec<Cluster>,
|
||||
}
|
||||
|
||||
impl FileAllocationTable {
|
||||
pub fn from_fat(fat_fs: &mut FatFs) -> Result<FileAllocationTable, uefi::Error> {
|
||||
let cluster_chain = match fat_fs.fat_type {
|
||||
FatType::Fat32(_) => Fat32::read_fat(fat_fs)?,
|
||||
FatType::Fat16(_) => Fat16::read_fat(fat_fs)?,
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
Ok(Self { cluster_chain })
|
||||
}
|
||||
pub fn read_cluster_chain(&self, mut start_cluster: Cluster) -> ClusterChain {
|
||||
let mut buffer = Vec::with_capacity(16);
|
||||
while start_cluster.is_allocated() {
|
||||
let next_cluster = self.cluster_chain[start_cluster.masked() as usize];
|
||||
|
||||
if next_cluster.is_empty() {
|
||||
break;
|
||||
}
|
||||
buffer.push(start_cluster);
|
||||
start_cluster = next_cluster;
|
||||
}
|
||||
let chain: ClusterChain = buffer.into();
|
||||
chain
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CachedFsEntry {
|
||||
long_name: Option<String>,
|
||||
short_name: String,
|
||||
attributes: DirectoryAttribute,
|
||||
cluster_chain: ClusterChain,
|
||||
size: u32,
|
||||
children: Option<Vec<CachedFsEntry>>,
|
||||
root: bool,
|
||||
}
|
||||
impl CachedFsEntry {
|
||||
pub fn root() -> Self {
|
||||
Self {
|
||||
long_name: None,
|
||||
short_name: "".to_string(),
|
||||
attributes: DirectoryAttribute::DIRECTORY,
|
||||
children: None,
|
||||
cluster_chain: ClusterChain::default(),
|
||||
size: 0,
|
||||
root: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_child_cached(&self, name: &str) -> Option<CachedFsEntry> {
|
||||
if let Some(children) = &self.children {
|
||||
for child in children {
|
||||
let name_match = if let Some(long_name) = &child.long_name {
|
||||
long_name == name || child.short_name == name.to_ascii_uppercase()
|
||||
} else {
|
||||
child.short_name == name.to_ascii_uppercase()
|
||||
};
|
||||
if name_match {
|
||||
return Some(child.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn load_children(cluster_chain: &ClusterChain, fs: &mut FatFs) -> Vec<CachedFsEntry> {
|
||||
let mut children = Vec::new();
|
||||
let iter = DirectoryListIter::from_chain(fs, cluster_chain);
|
||||
let mut lfn_builder = LfnBuilder::new();
|
||||
|
||||
for dir in iter {
|
||||
if dir.is_lfn() {
|
||||
lfn_builder.add(dir.decode_lfn());
|
||||
} else {
|
||||
let mut lfn = None;
|
||||
if !lfn_builder.is_empty() {
|
||||
lfn = Some(lfn_builder.build());
|
||||
lfn_builder = LfnBuilder::new();
|
||||
}
|
||||
let mut entry = CachedFsEntry::from(dir, fs);
|
||||
entry.long_name = lfn;
|
||||
children.push(entry);
|
||||
}
|
||||
}
|
||||
children
|
||||
}
|
||||
fn load_children_from_root_dir(fs: &mut FatFs) -> Vec<CachedFsEntry> {
|
||||
let mut children = Vec::new();
|
||||
let iter = DirectoryListIter::from_root(fs);
|
||||
let mut lfn_builder = LfnBuilder::new();
|
||||
|
||||
for dir in iter {
|
||||
if dir.is_lfn() {
|
||||
lfn_builder.add(dir.decode_lfn());
|
||||
} else {
|
||||
let mut lfn = None;
|
||||
if !lfn_builder.is_empty() {
|
||||
lfn = Some(lfn_builder.build());
|
||||
lfn_builder = LfnBuilder::new();
|
||||
}
|
||||
let mut entry = CachedFsEntry::from(dir, fs);
|
||||
entry.long_name = lfn;
|
||||
children.push(entry);
|
||||
}
|
||||
}
|
||||
children
|
||||
}
|
||||
pub fn is_directory(&self) -> bool {
|
||||
self.root || self.attributes.is_directory()
|
||||
}
|
||||
}
|
||||
impl CachedFsEntry {
|
||||
pub fn from(value: DirectoryEntry, fs: &FatFs) -> Self {
|
||||
Self {
|
||||
attributes: value.attributes(),
|
||||
children: None,
|
||||
cluster_chain: fs
|
||||
.allocation_table
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.read_cluster_chain(value.start_cluster()),
|
||||
long_name: None,
|
||||
root: false,
|
||||
short_name: value.name(),
|
||||
size: value.size(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Display for CachedFsEntry {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.write_fmt(format_args!(
|
||||
"{} {}",
|
||||
self.attributes,
|
||||
self.long_name.as_ref().unwrap_or(&self.short_name)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FileHandle {
|
||||
chain: ClusterChain,
|
||||
length: u64,
|
||||
position: u64,
|
||||
}
|
||||
impl FileHandle {
|
||||
//pub fn seek(&mut self, seek: Seek) {
|
||||
// match seek {
|
||||
// Seek::Move(i) => self.position += i.cast_unsigned(),
|
||||
// Seek::SetFromStart(i) => self.position = i,
|
||||
// Seek::SetFromEnd(i) => self.position = self.length - i,
|
||||
// };
|
||||
// self.position = self.position.clamp(0, self.length);
|
||||
//}
|
||||
pub fn read(&mut self, buffer: &mut [u8], fs: &mut FatFs) -> Result<usize, Error<()>> {
|
||||
let max_copy = ((self.length - self.position) as usize).min(buffer.len());
|
||||
|
||||
let cluster_low = self.position as usize / fs.cluster_size_bytes();
|
||||
let cluster_high = (self.position as usize + max_copy) / fs.cluster_size_bytes();
|
||||
|
||||
let needed_clusters = if cluster_low == cluster_high {
|
||||
&[self.chain[cluster_low]]
|
||||
} else {
|
||||
&self.chain[cluster_low..=cluster_high]
|
||||
};
|
||||
let cluster_buffer_len = needed_clusters.len() * fs.cluster_size_bytes();
|
||||
let mut cluster_buffer = Vec::with_capacity(cluster_buffer_len);
|
||||
cluster_buffer.resize(cluster_buffer_len, 0u8);
|
||||
|
||||
for (i, cluster) in needed_clusters.iter().enumerate() {
|
||||
let slice =
|
||||
&mut cluster_buffer[i * fs.cluster_size_bytes()..(i + 1) * fs.cluster_size_bytes()];
|
||||
fs.seek(cluster);
|
||||
fs.partition.read(slice)?;
|
||||
}
|
||||
|
||||
let cluster_slice_start = self.position as usize % fs.cluster_size_bytes();
|
||||
buffer[0..max_copy]
|
||||
.copy_from_slice(&cluster_buffer[cluster_slice_start..cluster_slice_start + max_copy]);
|
||||
|
||||
Ok(max_copy)
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.length as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CachedFsEntry> for FileHandle {
|
||||
fn from(value: CachedFsEntry) -> Self {
|
||||
Self {
|
||||
chain: value.cluster_chain.clone(),
|
||||
length: value.size as u64,
|
||||
position: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Cluster(u32);
|
||||
impl Cluster {
|
||||
pub fn from_32(value: u32) -> Self {
|
||||
Cluster(value)
|
||||
}
|
||||
pub fn from_16(value: u16) -> Self {
|
||||
if value >= 0xfff8 {
|
||||
Cluster(0x0ffffff8)
|
||||
} else {
|
||||
Cluster(value as u32)
|
||||
}
|
||||
}
|
||||
//pub fn from_12(_value: u16) -> Self {
|
||||
// unimplemented!()
|
||||
//}
|
||||
|
||||
pub fn start_sector(&self, fs: &FatFs) -> Sector {
|
||||
Sector((self.0 - 2) * fs.boot_sector.sectors_per_cluster as u32 + fs.first_data_sector)
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.masked() == 0
|
||||
}
|
||||
pub fn is_end(&self) -> bool {
|
||||
self.masked() >= 0x0ffffff8
|
||||
}
|
||||
pub fn is_allocated(&self) -> bool {
|
||||
self.masked() >= 2 && !self.is_end()
|
||||
}
|
||||
pub fn masked(&self) -> u32 {
|
||||
self.0 & 0x0fffffff
|
||||
}
|
||||
//pub fn unmasked(&self) -> u32 {
|
||||
// self.0
|
||||
//}
|
||||
}
|
||||
// TODO: There must be a nicer way to handle both `Cluster` as well as `&Cluster`
|
||||
impl Seekable<FatFs> for Cluster {
|
||||
fn to_byte_offset(self, fs: &FatFs) -> u64 {
|
||||
self.start_sector(fs).byte_offset(fs)
|
||||
}
|
||||
}
|
||||
// TODO: There must be a nicer way to handle both `Cluster` as well as `&Cluster`
|
||||
impl Seekable<FatFs> for &Cluster {
|
||||
fn to_byte_offset(self, fs: &FatFs) -> u64 {
|
||||
self.start_sector(fs).byte_offset(fs)
|
||||
}
|
||||
}
|
||||
impl Display for Cluster {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
if self.is_allocated() {
|
||||
f.write_fmt(format_args!("Cluster 0x{:08X}", self.0))
|
||||
} else {
|
||||
f.write_str("No cluster")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Sector(u32);
|
||||
impl Sector {
|
||||
pub fn byte_offset(&self, fs: &FatFs) -> u64 {
|
||||
self.0 as u64 * fs.boot_sector.bytes_per_sector as u64
|
||||
}
|
||||
}
|
||||
// TODO: There must be a nicer way to handle both `Sector` as well as `&Sector`
|
||||
impl Seekable<FatFs> for Sector {
|
||||
fn to_byte_offset(self, fs: &FatFs) -> u64 {
|
||||
self.byte_offset(fs)
|
||||
}
|
||||
}
|
||||
// TODO: There must be a nicer way to handle both `Sector` as well as `&Sector`
|
||||
impl Seekable<FatFs> for &Sector {
|
||||
fn to_byte_offset(self, fs: &FatFs) -> u64 {
|
||||
self.byte_offset(fs)
|
||||
}
|
||||
}
|
||||
impl From<u32> for Sector {
|
||||
fn from(value: u32) -> Self {
|
||||
Sector(value)
|
||||
}
|
||||
}
|
||||
51
src/fs/mod.rs
Normal file
51
src/fs/mod.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use core::fmt::Debug;
|
||||
|
||||
pub mod fat;
|
||||
|
||||
pub enum Error<T> {
|
||||
Open,
|
||||
NotFound,
|
||||
Directory,
|
||||
InvalidPath,
|
||||
Uefi(uefi::Error),
|
||||
Other(T),
|
||||
}
|
||||
impl<T> Debug for Error<T>
|
||||
where
|
||||
T: Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
Self::Open => write!(f, "Open"),
|
||||
Self::NotFound => write!(f, "NotFound"),
|
||||
Self::Directory => write!(f, "Directory"),
|
||||
Self::InvalidPath => write!(f, "InvalidPath"),
|
||||
Self::Uefi(arg0) => f.debug_tuple("Uefi").field(arg0).finish(),
|
||||
Self::Other(arg0) => f.debug_tuple("Other").field(arg0).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BpbError {
|
||||
BytesPerSector,
|
||||
SectorsPerCluster,
|
||||
ReservedSectorCount,
|
||||
NumFats,
|
||||
ClusterSize,
|
||||
TotalSectorCount,
|
||||
}
|
||||
impl From<BpbError> for Error<BpbError> {
|
||||
fn from(value: BpbError) -> Self {
|
||||
Error::Other(value)
|
||||
}
|
||||
}
|
||||
impl<T> From<uefi::Error> for Error<T> {
|
||||
fn from(value: uefi::Error) -> Self {
|
||||
Error::Uefi(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Seekable<T> {
|
||||
fn to_byte_offset(self, fs: &T) -> u64;
|
||||
}
|
||||
1
src/loader/mod.rs
Normal file
1
src/loader/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub fn load_elf_at() {}
|
||||
257
src/main.rs
Normal file
257
src/main.rs
Normal file
@@ -0,0 +1,257 @@
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
mod fs;
|
||||
mod loader;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use core::{fmt::Display, time::Duration};
|
||||
use log::{debug, error, warn};
|
||||
use uefi::{
|
||||
Identify,
|
||||
boot::{OpenProtocolParams, ScopedProtocol, SearchType, locate_handle_buffer, open_protocol},
|
||||
prelude::*,
|
||||
proto::{
|
||||
ProtocolPointer,
|
||||
device_path::{
|
||||
DevicePath, DevicePathNodeEnum, DevicePathNodeIterator,
|
||||
media::{HardDrive, PartitionFormat},
|
||||
messaging::{Atapi, Iscsi, MasterSlave, NvmeNamespace, PrimarySecondary, Sata, Scsi},
|
||||
},
|
||||
media::{
|
||||
block::{BlockIO, BlockIOMedia},
|
||||
disk::DiskIo,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::fs::fat::FatFs;
|
||||
|
||||
enum Seek {
|
||||
SetFromStart(u64),
|
||||
SetFromEnd(u64),
|
||||
Move(i64),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Partition {
|
||||
part_offset: u64,
|
||||
media_id: u32,
|
||||
disk: ScopedProtocol<DiskIo>,
|
||||
position: u64,
|
||||
length: u64,
|
||||
}
|
||||
impl Partition {
|
||||
fn read(&mut self, buffer: &mut [u8]) -> Result<u64, uefi::Error> {
|
||||
if let Err(err) = self.disk.read_disk(self.media_id, self.position, buffer) {
|
||||
Err(err)
|
||||
} else {
|
||||
self.position += buffer.len() as u64;
|
||||
Ok(buffer.len() as u64)
|
||||
}
|
||||
}
|
||||
fn write(&mut self, buffer: &mut [u8]) -> Result<u64, uefi::Error> {
|
||||
if let Err(err) = self.disk.write_disk(self.media_id, self.position, buffer) {
|
||||
Err(err)
|
||||
} else {
|
||||
self.position += buffer.len() as u64;
|
||||
Ok(buffer.len() as u64)
|
||||
}
|
||||
}
|
||||
fn seek(&mut self, seek: Seek) {
|
||||
match seek {
|
||||
Seek::Move(i) => self.position += i.cast_unsigned(),
|
||||
Seek::SetFromStart(i) => self.position = i,
|
||||
Seek::SetFromEnd(i) => self.position = self.length - i,
|
||||
};
|
||||
//info!(
|
||||
// "Seek to 0x{:08X} (0x{:08X})",
|
||||
// self.position,
|
||||
// self.position + self.part_offset
|
||||
//);
|
||||
}
|
||||
}
|
||||
|
||||
struct BlockDevice<'a> {
|
||||
handle: &'a Handle,
|
||||
media: BlockDeviceMedia<'a>,
|
||||
raw_media: &'a BlockIOMedia,
|
||||
hd: &'a HardDrive,
|
||||
}
|
||||
impl<'a> BlockDevice<'a> {
|
||||
pub fn new(
|
||||
handle: &'a Handle,
|
||||
raw_media: &'a BlockIOMedia,
|
||||
path_iter: DevicePathNodeIterator<'a>,
|
||||
) -> Result<Self, uefi::Error> {
|
||||
let mut part: Option<&'a HardDrive> = None;
|
||||
let mut media: Option<BlockDeviceMedia<'a>> = None;
|
||||
for node in path_iter {
|
||||
if let Ok(node_enum) = node.as_enum() {
|
||||
match node_enum {
|
||||
DevicePathNodeEnum::MediaHardDrive(hd) => part = Some(hd),
|
||||
_ => {}
|
||||
}
|
||||
if let Ok(node_media) = BlockDeviceMedia::try_from(node_enum) {
|
||||
media = Some(node_media);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(part) = part.take()
|
||||
&& let Some(media) = media.take()
|
||||
{
|
||||
Ok(BlockDevice {
|
||||
handle,
|
||||
media,
|
||||
raw_media,
|
||||
hd: part,
|
||||
})
|
||||
} else {
|
||||
Err(uefi::Error::new(Status::NOT_FOUND, ()))
|
||||
}
|
||||
}
|
||||
fn open_disk(&self) -> uefi::Result<Partition> {
|
||||
open_proto::<DiskIo>(self.handle).map(|proto| Partition {
|
||||
part_offset: self.hd.partition_start() * self.raw_media.block_size() as u64,
|
||||
disk: proto,
|
||||
position: 0,
|
||||
length: self.hd.partition_size() * self.raw_media.block_size() as u64,
|
||||
media_id: self.raw_media.media_id(),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Display for BlockDevice<'_> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
let format = if self.hd.partition_format() == PartitionFormat::MBR {
|
||||
"MBR"
|
||||
} else {
|
||||
"GPT"
|
||||
};
|
||||
f.write_fmt(format_args!(
|
||||
"{}/{format}(0x{:X})",
|
||||
self.media,
|
||||
self.hd.partition_number()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum BlockDeviceMedia<'a> {
|
||||
Atapi(&'a Atapi),
|
||||
Nvme(&'a NvmeNamespace),
|
||||
Sata(&'a Sata),
|
||||
Scsi(&'a Scsi),
|
||||
Iscsi(&'a Iscsi),
|
||||
}
|
||||
impl Display for BlockDeviceMedia<'_> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
BlockDeviceMedia::Sata(sata) => f.write_fmt(format_args!(
|
||||
"Sata(0x{:X},0x{:X},0x{:X})",
|
||||
sata.hba_port_number(),
|
||||
sata.port_multiplier_port_number(),
|
||||
sata.logical_unit_number()
|
||||
)),
|
||||
BlockDeviceMedia::Atapi(atapi) => f.write_fmt(format_args!(
|
||||
"Atapi({},{},0x{:X})",
|
||||
if atapi.primary_secondary() == PrimarySecondary::PRIMARY {
|
||||
"Primary"
|
||||
} else {
|
||||
"Secondary"
|
||||
},
|
||||
if atapi.master_slave() == MasterSlave::MASTER {
|
||||
"Master"
|
||||
} else {
|
||||
"Slave"
|
||||
},
|
||||
atapi.logical_unit_number()
|
||||
)),
|
||||
BlockDeviceMedia::Nvme(nvme) => f.write_fmt(format_args!(
|
||||
"NVMe(0x{:X},0x{:X})",
|
||||
nvme.namespace_identifier(),
|
||||
nvme.ieee_extended_unique_identifier()
|
||||
)),
|
||||
BlockDeviceMedia::Scsi(_) => f.write_fmt(format_args!("SCSI(???)")),
|
||||
BlockDeviceMedia::Iscsi(_) => f.write_fmt(format_args!("iSCSI(???)")),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'a> TryFrom<DevicePathNodeEnum<'a>> for BlockDeviceMedia<'a> {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: DevicePathNodeEnum<'a>) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
DevicePathNodeEnum::MessagingAtapi(atapi) => Ok(Self::Atapi(atapi)),
|
||||
DevicePathNodeEnum::MessagingNvmeNamespace(nvme) => Ok(Self::Nvme(nvme)),
|
||||
DevicePathNodeEnum::MessagingSata(sata) => Ok(Self::Sata(sata)),
|
||||
DevicePathNodeEnum::MessagingScsi(scsi) => Ok(Self::Scsi(scsi)),
|
||||
DevicePathNodeEnum::MessagingIscsi(iscsi) => Ok(Self::Iscsi(iscsi)),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn open_proto<P: ProtocolPointer + ?Sized>(handle: &Handle) -> uefi::Result<ScopedProtocol<P>> {
|
||||
unsafe {
|
||||
open_protocol::<P>(
|
||||
OpenProtocolParams {
|
||||
handle: *handle,
|
||||
agent: boot::image_handle(),
|
||||
controller: None,
|
||||
},
|
||||
boot::OpenProtocolAttributes::GetProtocol,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[entry]
|
||||
fn main() -> Status {
|
||||
uefi::helpers::init().unwrap();
|
||||
|
||||
let all_block_io_handles =
|
||||
locate_handle_buffer(SearchType::ByProtocol(&BlockIO::GUID)).unwrap();
|
||||
for handle in all_block_io_handles.iter() {
|
||||
if let Ok(block_io) = open_proto::<BlockIO>(handle) {
|
||||
let media = block_io.media();
|
||||
if !media.is_logical_partition() || !media.is_media_present() {
|
||||
continue;
|
||||
}
|
||||
if let Ok(device_path) = open_proto::<DevicePath>(handle)
|
||||
&& let Ok(block_dev) = BlockDevice::new(handle, media, device_path.node_iter())
|
||||
&& let Ok(disk) = block_dev.open_disk()
|
||||
{
|
||||
match FatFs::mount(disk) {
|
||||
Ok(mut fat) => {
|
||||
if fat.is_fat32() {
|
||||
if let Ok(mut handle) = fat.open("/path/to/beemovie.txt") {
|
||||
let mut buffer = [0u8; 1000];
|
||||
let read = handle.read(&mut buffer, &mut fat).unwrap();
|
||||
debug!("Read {read} bytes");
|
||||
} else {
|
||||
warn!("FAT32 open failed");
|
||||
}
|
||||
} else if fat.is_fat16() {
|
||||
if let Ok(mut handle) = fat.open("/efi/boot/bootx64.efi") {
|
||||
let size = handle.len();
|
||||
let mut buffer = Vec::with_capacity(size);
|
||||
buffer.resize(size, 0u8);
|
||||
let read = handle.read(&mut buffer, &mut fat).unwrap();
|
||||
debug!("Read {read} bytes");
|
||||
} else {
|
||||
warn!("FAT16 open failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!("Failed to open BlockIO {handle:?}");
|
||||
}
|
||||
}
|
||||
|
||||
boot::stall(Duration::from_secs(60));
|
||||
Status::SUCCESS
|
||||
}
|
||||
Reference in New Issue
Block a user