Files
CappuccinOS/src/drivers/fs/initramfs/mod.rs

782 lines
24 KiB
Rust
Executable File

pub mod compressors;
use core::{
fmt::{self, Debug},
ops::{Index, Range, RangeFrom, RangeFull},
};
use alloc::{boxed::Box, sync::Arc, vec::Vec};
use limine::ModuleRequest;
use crate::libs::math::ceil;
use super::vfs::{VFSDirectory, VFSFile, VFSFileSystem};
pub static MODULE_REQUEST: ModuleRequest = ModuleRequest::new(0);
pub fn init() {
// TODO: Put the module request stuff in another file?
if MODULE_REQUEST.get_response().get().is_none() {
crate::log_error!("Module request in none!");
return;
}
let module_response = MODULE_REQUEST.get_response().get().unwrap();
let mut initramfs = None;
let module_name = "initramfs.img";
for module in module_response.modules() {
let c_path = module.path.to_str();
if c_path.is_none() {
continue;
}
if !c_path.unwrap().to_str().unwrap().contains(module_name) {
continue;
}
initramfs = Some(module);
}
// End TODO
if initramfs.is_none() {
crate::log_error!("Initramfs was not found!");
return;
}
let initramfs = initramfs.unwrap();
crate::println!("Initramfs is located at: {:#018X?}", unsafe {
initramfs.base.as_ptr().unwrap()
..initramfs
.base
.as_ptr()
.unwrap()
.add(initramfs.length as usize)
});
let squashfs = Squashfs::new(initramfs.base.as_ptr().unwrap());
if squashfs.is_err() {
crate::log_error!("Initramfs in corrupt!");
return;
}
let squashfs = squashfs.unwrap();
crate::println!("{:X?}", squashfs);
crate::println!("{:?}", squashfs.superblock.features());
crate::println!(
"{:X?}",
squashfs
.open("/firstdir/seconddirbutlonger/yeah.txt")
.unwrap()
.read()
);
}
#[repr(C)]
#[derive(Debug)]
struct Squashfs<'a> {
superblock: SquashfsSuperblock,
data_table: &'a [u8],
inode_table: &'a [u8],
directory_table: &'a [u8],
fragment_table: Option<&'a [u8]>,
export_table: Option<&'a [u8]>,
id_table: &'a [u8],
xattr_table: Option<&'a [u8]>,
}
impl Squashfs<'_> {
fn new(ptr: *mut u8) -> Result<Squashfs<'static>, ()> {
crate::println!("Parsing initramfs fs at {:p}", ptr);
// bytes used from superblock
let length = unsafe { u64::from_le(*(ptr.add(40) as *const u64)) as usize };
let squashfs_data: &[u8] = unsafe { core::slice::from_raw_parts(ptr, length) };
let superblock = SquashfsSuperblock::new(&squashfs_data)?;
let data_table = &squashfs_data
[core::mem::size_of::<SquashfsSuperblock>()..superblock.inode_table as usize];
let inode_table =
&squashfs_data[superblock.inode_table as usize..superblock.dir_table as usize];
let directory_table =
&squashfs_data[superblock.dir_table as usize..superblock.frag_table as usize];
let mut fragment_table: Option<&[u8]> = None;
if superblock.frag_table != u64::MAX {
fragment_table = Some(
&squashfs_data[superblock.frag_table as usize..superblock.export_table as usize],
);
}
let mut export_table: Option<&[u8]> = None;
if superblock.export_table != u64::MAX {
export_table = Some(
&squashfs_data[superblock.export_table as usize..superblock.id_table as usize],
);
}
let mut id_table: &[u8] = &squashfs_data[superblock.id_table as usize..];
let mut xattr_table: Option<&[u8]> = None;
if superblock.xattr_table != u64::MAX {
id_table =
&squashfs_data[superblock.id_table as usize..superblock.xattr_table as usize];
xattr_table = Some(&squashfs_data[superblock.xattr_table as usize..]);
}
return Ok(Squashfs {
superblock,
data_table,
inode_table,
directory_table,
fragment_table,
export_table,
id_table,
xattr_table,
});
}
fn read_root_dir(&self) -> BasicDirectoryInode {
let root_inode_offset = self.superblock.root_inode_offset as usize;
let root_inode: BasicDirectoryInode = self
.read_inode(root_inode_offset as u32)
.try_into()
.expect("Failed to try_into");
return root_inode;
}
fn read_inode(&self, inode_num: u32) -> Inode {
let inode_table = &self.get_decompressed_table(&self.inode_table, (true, None));
let inode_offset = inode_num as usize;
let inode_file_type: InodeFileType = u16::from_le_bytes(
inode_table[inode_offset..(inode_offset + 2)]
.try_into()
.unwrap(),
)
.into();
match inode_file_type {
InodeFileType::BasicDirectory => {
return Inode::BasicDirectory(BasicDirectoryInode::from_bytes(
self,
&inode_table[inode_offset..],
));
}
InodeFileType::BasicFile => {
return Inode::BasicFile(BasicFileInode::from_bytes(
self,
&inode_table[inode_offset..],
));
}
_ => {
panic!("Unsupported or unknown inode file type {inode_file_type:?}!")
}
};
}
// metadata_block takes a tuple, the first element is whether the array is a metadata block,
// and the second element is a is_compressed override if the array is not a metadata block.
fn get_decompressed_table(
&self,
table: &[u8],
metadata_block: (bool, Option<bool>),
) -> Vec<u8> {
// the bottom 15 bits, I think the last bit indicates whether the data is uncompressed
let header = u16::from_le_bytes(table[0..2].try_into().unwrap());
let table_is_compressed = if !metadata_block.0 {
metadata_block.1.unwrap()
} else {
header & 0x8000 == 0
};
let table_size = header & 0x7FFF;
if table.len() >= 8192 {
panic!("Inode block is not less than 8KiB!");
}
let mut buffer: Vec<u8> = Vec::with_capacity(8192);
if table_is_compressed {
let bytes = if metadata_block.0 {
&table[2..]
} else {
&table
};
match self.superblock.compressor {
SquashfsCompressionType::GZIP => {
buffer.extend_from_slice(compressors::gzip::uncompress_data(bytes));
}
_ => {
crate::println!("Unsupported compression type")
}
}
} else {
unsafe {
core::ptr::copy_nonoverlapping(
table.as_ptr().add(2),
buffer.as_mut_ptr(),
table_size as usize,
);
buffer.set_len(table_size as usize);
}
}
return buffer;
}
}
impl<'a> VFSFileSystem for Squashfs<'a> {
fn open(&self, path: &str) -> Result<Box<dyn VFSFile + '_>, ()> {
let path_components: Vec<&str> = path.trim_start_matches('/').split('/').collect();
let mut current_dir = self.read_root_dir();
crate::println!("\033[94m{}\033[0m", "-".repeat(40));
for (i, &part) in path_components.iter().enumerate() {
crate::println!(
"{}\ncur: {current_dir:?}\n{part}\n{path}\n{0}",
"-".repeat(20)
);
let file = current_dir.find(part).ok_or(())?;
match file {
Inode::BasicDirectory(dir) => {
current_dir = dir;
}
Inode::BasicFile(file) => {
if i < path_components.len() - 1 {
return Err(());
}
return Ok(Box::new(file));
}
}
}
crate::println!("\033[94m{}\033[0m", "-".repeat(40));
return Err(());
}
fn read_dir(&self, path: &str) -> Result<Box<dyn VFSDirectory>, ()> {
unimplemented!()
}
}
#[derive(Clone, Copy, Debug)]
enum Inode<'a> {
BasicFile(BasicFileInode<'a>),
BasicDirectory(BasicDirectoryInode<'a>),
}
macro_rules! inode_enum_try_into {
($inode_type:ty, $inode_name:ident) => {
impl<'a> TryInto<$inode_type> for Inode<'a> {
type Error = ();
fn try_into(self) -> Result<$inode_type, Self::Error> {
match self {
Inode::$inode_name(inode) => {
return Ok(inode);
}
_ => {
return Err(());
}
}
}
}
};
}
inode_enum_try_into!(BasicFileInode<'a>, BasicFile);
inode_enum_try_into!(BasicDirectoryInode<'a>, BasicDirectory);
// TODO: can we remove the dependence on squahsfs??
#[repr(C)]
#[derive(Clone, Copy)]
struct InodeHeader<'a> {
squashfs: &'a Squashfs<'a>,
file_type: InodeFileType,
_reserved: [u16; 3],
mtime: u32,
inode_num: u32,
}
impl<'a> InodeHeader<'a> {
fn from_bytes(squashfs: &'a Squashfs, bytes: &[u8]) -> Self {
let file_type = u16::from_le_bytes(bytes[0..2].try_into().unwrap()).into();
let mtime = u32::from_le_bytes(bytes[8..12].try_into().unwrap());
let inode_num = u32::from_le_bytes(bytes[12..16].try_into().unwrap());
return Self {
squashfs,
file_type,
_reserved: [0; 3],
mtime,
inode_num,
};
}
}
impl<'a> Debug for InodeHeader<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("InodeHeader")
.field("file_type", &self.file_type)
.field("_reserved", &self._reserved)
.field("mtime", &self.mtime)
.field("inode_num", &self.inode_num)
.finish()
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
struct BasicDirectoryInode<'a> {
header: InodeHeader<'a>,
block_index: u32, // 4
link_count: u32, // 8
file_size: u16, // 10
block_offset: u16, // 12
parent_inode: u32, // 16
}
impl<'a> BasicDirectoryInode<'a> {
fn from_bytes(squashfs: &'a Squashfs, bytes: &[u8]) -> Self {
let header = InodeHeader::from_bytes(squashfs, bytes);
let block_index = u32::from_le_bytes(bytes[16..20].try_into().unwrap());
let link_count = u32::from_le_bytes(bytes[20..24].try_into().unwrap());
let file_size = u16::from_le_bytes(bytes[24..26].try_into().unwrap());
let block_offset = u16::from_le_bytes(bytes[26..28].try_into().unwrap());
let parent_inode = u32::from_le_bytes(bytes[28..32].try_into().unwrap());
return Self {
header,
block_index,
link_count,
file_size,
block_offset,
parent_inode,
};
}
fn entries(&self) -> Arc<[Inode]> {
let mut entries: Vec<Inode> = Vec::new();
let directory_table = &self.header.squashfs.get_decompressed_table(
self.header.squashfs.directory_table,
(
false,
Some(
!self
.header
.squashfs
.superblock
.features()
.uncompressed_data_blocks,
),
),
);
let directory_table_header =
DirectoryTableHeader::from_bytes(&directory_table[self.block_offset as usize..]);
// TODO: cheap hack, fix it when I have more hours of sleep.
let mut offset = self.block_offset as usize + core::mem::size_of::<DirectoryTableHeader>();
for _ in 0..directory_table_header.entry_count as usize {
let directroy_table_entry = DirectoryTableEntry::from_bytes(&directory_table[offset..]);
offset += 8 + directroy_table_entry.name.len();
let file_inode = self
.header
.squashfs
.read_inode(directroy_table_entry.offset as u32);
entries.push(file_inode);
}
return Arc::from(entries);
}
fn find(&self, name: &str) -> Option<Inode<'a>> {
crate::println!("Searching for file {name} in directory");
let directory_table = &self.header.squashfs.get_decompressed_table(
self.header.squashfs.directory_table,
(
false,
Some(
!self
.header
.squashfs
.superblock
.features()
.uncompressed_data_blocks,
),
),
);
let directory_table_header =
DirectoryTableHeader::from_bytes(&directory_table[self.block_offset as usize..]);
// TODO: cheap hack, fix it when I have more hours of sleep.
let mut offset = self.block_offset as usize + core::mem::size_of::<DirectoryTableHeader>();
for _ in 0..directory_table_header.entry_count as usize {
let directroy_table_entry = DirectoryTableEntry::from_bytes(&directory_table[offset..]);
offset += 8 + directroy_table_entry.name.len();
crate::println!("{}", directroy_table_entry.name);
if directroy_table_entry.name == name {
return Some(
self.header
.squashfs
.read_inode(directroy_table_entry.offset as u32),
);
}
}
return None;
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug)]
struct BasicFileInode<'a> {
header: InodeHeader<'a>,
block_start: u32, // 4
frag_idx: u32, // 8
block_offset: u32, // 12
file_size: u32, // 16
}
impl<'a> BasicFileInode<'a> {
fn from_bytes(squashfs: &'a Squashfs, bytes: &[u8]) -> Self {
let header = InodeHeader::from_bytes(squashfs, bytes);
let block_start = u32::from_le_bytes(bytes[16..20].try_into().unwrap());
let frag_idx = u32::from_le_bytes(bytes[20..24].try_into().unwrap());
let block_offset = u32::from_le_bytes(bytes[24..28].try_into().unwrap());
let file_size = u32::from_le_bytes(bytes[28..32].try_into().unwrap());
return Self {
header,
block_start,
frag_idx,
block_offset,
file_size,
};
}
}
impl<'a> VFSFile for BasicFileInode<'a> {
fn read(&self) -> Result<Arc<[u8]>, ()> {
// TODO: handle tail end packing (somehow?)
let block_count =
ceil(self.file_size as f64 / self.header.squashfs.superblock.block_size as f64)
as usize;
// TODO: is this really how you're supposed to do this?
let mut block_data: Vec<u8> = Vec::with_capacity(8192 * block_count);
unsafe {
core::ptr::copy_nonoverlapping(
self.header
.squashfs
.data_table
.as_ptr()
.add(self.block_offset as usize),
block_data.as_mut_ptr(),
self.file_size as usize,
);
block_data.set_len(self.file_size as usize);
}
return Ok(Arc::from(block_data));
}
}
#[repr(C)]
#[derive(Debug)]
struct DirectoryTableHeader {
entry_count: u32,
start: u32,
inode_num: u32,
}
impl DirectoryTableHeader {
fn from_bytes(bytes: &[u8]) -> Self {
// count is off by 1 entry
let entry_count = u32::from_le_bytes(bytes[0..4].try_into().unwrap()) + 1;
let start = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
let inode_num = u32::from_le_bytes(bytes[8..12].try_into().unwrap());
return Self {
entry_count,
start,
inode_num,
};
}
}
#[repr(C)]
#[derive(Debug)]
struct DirectoryTableEntry<'a> {
offset: u16,
inode_offset: i16,
inode_type: InodeFileType,
name_size: u16,
name: &'a str, // the file name length is name_size + 1 bytes
}
impl<'a> DirectoryTableEntry<'a> {
fn from_bytes(bytes: &'a [u8]) -> Self {
let offset = u16::from_le_bytes(bytes[0..2].try_into().unwrap());
let inode_offset = i16::from_le_bytes(bytes[2..4].try_into().unwrap());
let inode_type = u16::from_le_bytes(bytes[4..6].try_into().unwrap()).into();
let name_size = u16::from_le_bytes(bytes[6..8].try_into().unwrap());
let name = core::str::from_utf8(&bytes[8..((name_size as usize) + 1) + 8])
.expect("Failed to make DirectoryHeader name");
return Self {
offset,
inode_offset,
inode_type,
name_size,
name,
};
}
}
#[repr(u16)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum InodeFileType {
BasicDirectory = 1,
BasicFile = 2,
BasicSymlink = 3,
BasicBlockDevice = 4,
BasicCharDevice = 5,
BasicPipe = 6,
BasicSocked = 7,
ExtendedDirectory = 8,
ExtendedFile = 9,
ExtendedSymlink = 10,
ExtendedBlockDevice = 11,
ExtendedPipe = 12,
ExtendedSocked = 13,
}
impl Into<InodeFileType> for u16 {
fn into(self) -> InodeFileType {
match self {
1 => InodeFileType::BasicDirectory,
2 => InodeFileType::BasicFile,
3 => InodeFileType::BasicSymlink,
4 => InodeFileType::BasicBlockDevice,
5 => InodeFileType::BasicCharDevice,
6 => InodeFileType::BasicPipe,
7 => InodeFileType::BasicSocked,
8 => InodeFileType::ExtendedDirectory,
9 => InodeFileType::ExtendedFile,
10 => InodeFileType::ExtendedSymlink,
11 => InodeFileType::ExtendedBlockDevice,
12 => InodeFileType::ExtendedPipe,
13 => InodeFileType::ExtendedSocked,
_ => panic!("Unexpected Inode file type {self}!"),
}
}
}
#[repr(u16)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum SquashfsCompressionType {
GZIP = 1,
LZMA = 2,
LZO = 3,
XZ = 4,
LZ4 = 5,
ZSTD = 6,
}
#[repr(u16)]
enum SquashfsFlags {
UncompressedInodes = 0x0001,
UncompressedDataBlocks = 0x0002,
Reserved = 0x0004,
UncompressedFragments = 0x0008,
UnusedFragments = 0x0010,
FragmentsAlwaysPresent = 0x0020,
DeduplicatedData = 0x0040,
PresentNFSTable = 0x0080,
UncompressedXattrs = 0x0100,
NoXattrs = 0x0200,
PresentCompressorOptions = 0x0400,
UncompressedIDTable = 0x0800,
}
#[derive(Debug)]
struct SquashfsFeatures {
uncompressed_inodes: bool,
uncompressed_data_blocks: bool,
_reserved: bool,
uncompressed_fragments: bool,
unused_fragments: bool,
fragments_always_present: bool,
deduplicated_data: bool,
nfs_table_present: bool,
uncompressed_xattrs: bool,
no_xattrs: bool,
compressor_options_present: bool,
uncompressed_id_table: bool,
}
impl Into<SquashfsCompressionType> for u16 {
fn into(self) -> SquashfsCompressionType {
match self {
1 => SquashfsCompressionType::GZIP,
2 => SquashfsCompressionType::LZMA,
3 => SquashfsCompressionType::LZO,
4 => SquashfsCompressionType::XZ,
5 => SquashfsCompressionType::LZ4,
6 => SquashfsCompressionType::ZSTD,
_ => panic!("Unexpected Squashfs compression type!"),
}
}
}
#[repr(C, packed)]
#[derive(Clone, Copy, Debug)]
struct SquashfsSuperblock {
magic: u32, // 0x73717368
inode_count: u32, // 0x02
mod_time: u32, // varies
block_size: u32, // 0x20000
frag_count: u32, // 0x01
compressor: SquashfsCompressionType, // GZIP
block_log: u16, // 0x11
flags: u16, // 0xC0
id_count: u16, // 0x01
ver_major: u16, // 0x04
ver_minor: u16, // 0x00
root_inode_offset: u16, //
root_inode_block: u32, //
_reserved: u16, // 0x20
bytes_used: u64, // 0x0103
id_table: u64, // 0x00FB
xattr_table: u64, // 0xFFFFFFFFFFFFFFFF
inode_table: u64, // 0x7B
dir_table: u64, // 0xA4
frag_table: u64, // 0xD5
export_table: u64, // 0xED
}
impl SquashfsSuperblock {
fn new(bytes: &[u8]) -> Result<Self, ()> {
let superblock = Self {
magic: u32::from_le_bytes(bytes[0..4].try_into().unwrap()),
inode_count: u32::from_le_bytes(bytes[4..8].try_into().unwrap()),
mod_time: u32::from_le_bytes(bytes[8..12].try_into().unwrap()),
block_size: u32::from_le_bytes(bytes[12..16].try_into().unwrap()),
frag_count: u32::from_le_bytes(bytes[16..20].try_into().unwrap()),
compressor: u16::from_le_bytes(bytes[20..22].try_into().unwrap()).into(),
block_log: u16::from_le_bytes(bytes[22..24].try_into().unwrap()),
flags: u16::from_le_bytes(bytes[24..26].try_into().unwrap()),
id_count: u16::from_le_bytes(bytes[26..28].try_into().unwrap()),
ver_major: u16::from_le_bytes(bytes[28..30].try_into().unwrap()),
ver_minor: u16::from_le_bytes(bytes[30..32].try_into().unwrap()),
root_inode_offset: u16::from_le_bytes(bytes[32..34].try_into().unwrap()),
root_inode_block: u32::from_le_bytes(bytes[34..38].try_into().unwrap()),
_reserved: u16::from_le_bytes(bytes[38..40].try_into().unwrap()),
bytes_used: u64::from_le_bytes(bytes[40..48].try_into().unwrap()),
id_table: u64::from_le_bytes(bytes[48..56].try_into().unwrap()),
xattr_table: u64::from_le_bytes(bytes[56..64].try_into().unwrap()),
inode_table: u64::from_le_bytes(bytes[64..72].try_into().unwrap()),
dir_table: u64::from_le_bytes(bytes[72..80].try_into().unwrap()),
frag_table: u64::from_le_bytes(bytes[80..88].try_into().unwrap()),
export_table: u64::from_le_bytes(bytes[88..96].try_into().unwrap()),
};
if superblock.magic != 0x73717368 {
return Err(());
}
if superblock.ver_major != 4 || superblock.ver_minor != 0 {
return Err(());
}
if superblock.block_size > 1048576 {
return Err(());
}
if superblock.block_log > 20 {
return Err(());
}
if superblock.block_size != (1 << superblock.block_log) {
return Err(());
}
if superblock.block_size == 0 {
return Err(());
}
if ((superblock.block_size - 1) & superblock.block_size) != 0 {
return Err(());
}
return Ok(superblock);
}
fn features(&self) -> SquashfsFeatures {
let uncompressed_inodes = (self.flags & SquashfsFlags::UncompressedInodes as u16) != 0;
let uncompressed_data_blocks =
(self.flags & SquashfsFlags::UncompressedDataBlocks as u16) != 0;
let _reserved = (self.flags & SquashfsFlags::Reserved as u16) != 0;
let uncompressed_fragments =
(self.flags & SquashfsFlags::UncompressedFragments as u16) != 0;
let unused_fragments = (self.flags & SquashfsFlags::UnusedFragments as u16) != 0;
let fragments_always_present =
(self.flags & SquashfsFlags::FragmentsAlwaysPresent as u16) != 0;
let deduplicated_data = (self.flags & SquashfsFlags::DeduplicatedData as u16) != 0;
let nfs_table_present = (self.flags & SquashfsFlags::PresentNFSTable as u16) != 0;
let uncompressed_xattrs = (self.flags & SquashfsFlags::UncompressedXattrs as u16) != 0;
let no_xattrs = (self.flags & SquashfsFlags::NoXattrs as u16) != 0;
let compressor_options_present =
(self.flags & SquashfsFlags::PresentCompressorOptions as u16) != 0;
let uncompressed_id_table = (self.flags & SquashfsFlags::UncompressedIDTable as u16) != 0;
return SquashfsFeatures {
uncompressed_inodes,
uncompressed_data_blocks,
_reserved,
uncompressed_fragments,
unused_fragments,
fragments_always_present,
deduplicated_data,
nfs_table_present,
uncompressed_xattrs,
no_xattrs,
compressor_options_present,
uncompressed_id_table,
};
}
}