diff --git a/README.md b/README.md index 2936b6a..00f08fd 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,8 @@ Supported commands include: * `inflate , [,]` decompresses the a ZLIB compressed slice from the given source to the given destination. -* `mount ` to mount the UFS ramdisk. +* `mount ` to mount a UFS ramdisk or cpio miniroot. +* `umount` to unmount the ramdisk. * `ls ` to list a file or directory on the ramdisk. * `cat ` to display the contents of a file. * `copy ,` to copy the contents of a diff --git a/src/bldb.rs b/src/bldb.rs index d9da12f..dbbf664 100644 --- a/src/bldb.rs +++ b/src/bldb.rs @@ -47,7 +47,6 @@ use crate::mmu; use crate::ramdisk; use crate::result::Error; use crate::uart::{self, Uart}; -use crate::ufs; use alloc::boxed::Box; use core::fmt; use core::ops::Range; @@ -63,13 +62,13 @@ pub(crate) struct Config { pub(crate) gpios: &'static mut gpio::Gpios, pub(crate) loader_region: Range, pub(crate) page_table: mmu::LoaderPageTable, - pub(crate) ramdisk: Option>, + pub(crate) ramdisk: Option>, pub(crate) prompt: cons::Prompt, } impl Config { pub fn mount(&mut self, ramdisk: &'static [u8]) -> Result<(), Error> { - self.ramdisk = ramdisk::mount(ramdisk)?; + self.ramdisk = Some(ramdisk::mount(ramdisk)?); Ok(()) } } @@ -87,7 +86,7 @@ impl fmt::Debug for Config { writeln!( f, " ramdisk: {:?}", - self.ramdisk.as_ref().map(|_| "mounted") + self.ramdisk.as_ref().map(|fs| fs.as_str()) )?; writeln!(f, " prompt: {:?}", self.prompt)?; write!(f, "}}") diff --git a/src/cpio.rs b/src/cpio.rs new file mode 100644 index 0000000..05aaea7 --- /dev/null +++ b/src/cpio.rs @@ -0,0 +1,160 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! cpio miniroot support. + +use crate::io; +use crate::ramdisk; +use crate::result::{Error, Result}; +use crate::{print, println}; +use alloc::boxed::Box; +use core::slice; + +pub(crate) struct FileSystem { + sd: io::Sd, +} + +impl FileSystem { + pub(crate) fn try_new(bs: &[u8]) -> Result { + if bs.starts_with(b"070707") { + let sd = io::Sd::from_slice(bs); + Ok(FileSystem { sd }) + } else { + Err(Error::FsInvMagic) + } + } +} + +pub(crate) struct File { + data: io::Sd, +} + +impl File { + fn as_slice(&self) -> &[u8] { + let ptr = self.data.as_ptr(); + let len = self.data.len(); + unsafe { slice::from_raw_parts(ptr, len) } + } +} + +impl ramdisk::File for File { + fn file_type(&self) -> ramdisk::FileType { + ramdisk::FileType::Regular + } +} + +impl io::Read for File { + fn read(&self, offset: u64, dst: &mut [u8]) -> Result { + let s = self.as_slice(); + s.read(offset, dst) + } + + fn size(&self) -> usize { + self.data.len() + } +} + +impl ramdisk::FileSystem for FileSystem { + fn open(&self, path: &str) -> Result> { + let ptr: *const u8 = self.sd.as_ptr(); + let len = self.sd.len(); + let cpio = unsafe { core::slice::from_raw_parts(ptr, len) }; + let key = path.strip_prefix("/").unwrap_or(path); + for file in cpio_reader::iter_files(cpio) { + if file.name() == key { + let data = io::Sd::from_slice(file.file()); + return Ok(Box::new(File { data })); + } + } + Err(Error::FsNoFile) + } + + fn list(&self, path: &str) -> Result<()> { + let ptr: *const u8 = self.sd.as_ptr(); + let len = self.sd.len(); + let cpio = unsafe { core::slice::from_raw_parts(ptr, len) }; + let key = path.strip_prefix('/').unwrap_or(path); + for file in cpio_reader::iter_files(cpio) { + if file.name() == key { + lsfile(path, &file); + return Ok(()); + } + } + let mut found = false; + for file in cpio_reader::iter_files(cpio) { + if file.name().starts_with(key) { + lsfile(file.name(), &file); + found = true; + } + } + if found { Ok(()) } else { Err(Error::FsNoFile) } + } + + fn as_str(&self) -> &str { + "cpio" + } + + fn with_addr(&self, addr: usize) -> *const u8 { + self.sd.as_ptr().with_addr(addr) + } +} + +fn lsfile(path: &str, file: &cpio_reader::Entry) { + print!("#{ino:<4} ", ino = file.ino()); + print_mode(file.mode()); + println!( + " {nlink:<2} {uid:<3} {gid:<3} {size:>8} {path}", + nlink = file.nlink(), + uid = file.uid(), + gid = file.gid(), + size = file.file().len(), + ); +} + +fn first_char(mode: cpio_reader::Mode) -> char { + use cpio_reader::Mode; + match mode { + _ if mode.contains(Mode::DIRECTORY) => 'd', + _ if mode.contains(Mode::CHARACTER_SPECIAL_DEVICE) => 'c', + _ if mode.contains(Mode::BLOCK_SPECIAL_DEVICE) => 'b', + _ if mode.contains(Mode::SYMBOLIK_LINK) => 'l', + _ if mode.contains(Mode::SOCKET) => 's', + _ if mode.contains(Mode::NAMED_PIPE_FIFO) => 'f', + _ => '-', + } +} + +fn print_mode(mode: cpio_reader::Mode) { + use cpio_reader::Mode; + print!("{}", first_char(mode)); + let alt = |bit, t, f| { + if mode.contains(bit) { t } else { f } + }; + // For some reason, the cpio reader library appears to have + // the meaning of these bits mirrored with respect to the owner + // bits. + print!("{}", alt(Mode::WORLD_READABLE, 'r', '-')); + print!("{}", alt(Mode::WORLD_WRITABLE, 'w', '-')); + if !mode.contains(Mode::SUID) { + print!("{}", alt(Mode::WORLD_EXECUTABLE, 'x', '-')); + } else { + print!("{}", alt(Mode::WORLD_EXECUTABLE, 's', 'S')); + } + + print!("{}", alt(Mode::GROUP_READABLE, 'r', '-')); + print!("{}", alt(Mode::GROUP_WRITABLE, 'w', '-')); + if !mode.contains(Mode::SGID) { + print!("{}", alt(Mode::GROUP_EXECUTABLE, 'x', '-')); + } else { + print!("{}", alt(Mode::GROUP_EXECUTABLE, 's', 'S')); + } + + print!("{}", alt(Mode::USER_READABLE, 'r', '-')); + print!("{}", alt(Mode::USER_WRITABLE, 'w', '-')); + if !mode.contains(Mode::STICKY) { + print!("{}", alt(Mode::USER_EXECUTABLE, 'x', '-')); + } else { + print!("{}", alt(Mode::USER_EXECUTABLE, 't', 'T')); + } +} diff --git a/src/io.rs b/src/io.rs new file mode 100644 index 0000000..dcbcb6a --- /dev/null +++ b/src/io.rs @@ -0,0 +1,59 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use crate::result::Result; + +#[derive(Debug)] +pub(crate) struct Sd { + pub(crate) ptr: *const u8, + pub(crate) len: usize, +} + +impl Sd { + pub(crate) fn new(ptr: *const u8, len: usize) -> Sd { + Sd { ptr, len } + } + + pub(crate) fn from_slice(bs: &[u8]) -> Sd { + Sd::new(bs.as_ptr(), bs.len()) + } + + pub(crate) fn as_ptr(&self) -> *const u8 { + self.ptr + } + + pub(crate) fn len(&self) -> usize { + self.len + } + + pub(crate) fn subset(&self, offset: usize, len: usize) -> Sd { + assert!(offset + len <= self.len); + let ptr = self.ptr.wrapping_add(offset); + Sd { ptr, len } + } +} + +pub(crate) trait Read { + fn read(&self, off: u64, dst: &mut [u8]) -> Result; + fn size(&self) -> usize; +} + +impl Read for &[u8] { + fn read(&self, off: u64, dst: &mut [u8]) -> Result { + let off = off as usize; + if off > self.len() { + return Ok(0); + } + let bytes = &self[off..]; + let len = usize::min(bytes.len(), dst.len()); + if len > 0 { + dst[..len].copy_from_slice(&bytes[..len]); + } + Ok(len) + } + + fn size(&self) -> usize { + self.len() + } +} diff --git a/src/loader.rs b/src/loader.rs index 2e91b77..28f2f4d 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -7,11 +7,12 @@ extern crate alloc; +use crate::io::Read; use crate::mem; use crate::mmu::LoaderPageTable; use crate::println; +use crate::ramdisk::File; use crate::result::{Error, Result}; -use crate::ufs; use alloc::vec::Vec; use goblin::container::{Container, Ctx, Endian}; use goblin::elf::ProgramHeader; @@ -20,37 +21,12 @@ use goblin::elf::{self, Elf}; const PAGE_SIZE: usize = 4096; -pub(crate) trait Read { - fn read(&self, off: u64, dst: &mut [u8]) -> Result; -} - -impl Read for ufs::Inode<'_> { - fn read(&self, off: u64, dst: &mut [u8]) -> Result { - self.read(off, dst).map_err(|_| Error::FsRead) - } -} - -impl Read for &[u8] { - fn read(&self, off: u64, dst: &mut [u8]) -> Result { - let off = off as usize; - if off > self.len() { - return Err(Error::ElfTruncatedObj); - } - let bytes = &self[off..]; - let len = usize::min(bytes.len(), dst.len()); - if len > 0 { - dst[..len].copy_from_slice(&bytes[..len]); - } - Ok(0) - } -} - /// Loads an executable image contained in the given file /// creating virtual mappings as required. Returns the image's /// ELF entry point on success. pub(crate) fn load( page_table: &mut LoaderPageTable, - file: &ufs::Inode<'_>, + file: &dyn File, ) -> Result { let mut buf = [0u8; PAGE_SIZE]; file.read(0, &mut buf).map_err(|_| Error::FsRead)?; @@ -88,7 +64,7 @@ pub(crate) fn load_bytes( Ok(elf.entry) } -pub(crate) fn elfinfo(file: &T) -> Result<()> { +pub(crate) fn elfinfo(file: &dyn File) -> Result<()> { use goblin::elf; let mut buf = [0u8; PAGE_SIZE]; @@ -193,7 +169,7 @@ fn parse_program_headers( /// Loads the given ELF segment, creating virtual mappings for /// it as required. -fn load_segment( +fn load_segment( page_table: &mut LoaderPageTable, segment: &ProgramHeader, file: &T, @@ -226,8 +202,8 @@ fn load_segment( }; let filesz = segment.p_filesz as usize; let len = usize::min(filesz, dst.len()); - if len > 0 { - file.read(segment.p_offset, &mut dst[..len])?; + if len > 0 && file.read(segment.p_offset, &mut dst[..len])? != len { + return Err(Error::ElfTruncatedObj); } } let attrs = mem::Attrs::new_kernel( diff --git a/src/main.rs b/src/main.rs index 1301f86..2cee462 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,9 +18,11 @@ mod allocator; mod bldb; mod clock; mod cons; +mod cpio; mod cpuid; mod gpio; mod idt; +mod io; mod iomux; mod loader; mod mem; diff --git a/src/ramdisk.rs b/src/ramdisk.rs index d1baf8b..46c8908 100644 --- a/src/ramdisk.rs +++ b/src/ramdisk.rs @@ -4,67 +4,73 @@ //! Code for dealing with the UFS ramdisk. +use crate::cpio; +use crate::io; use crate::println; use crate::result::{Error, Result}; use crate::uart::Uart; use crate::ufs; +use alloc::boxed::Box; +use core::convert::TryInto; -pub fn mount( - ramdisk: &'static [u8], -) -> Result>> { - let fs = ufs::FileSystem::new(ramdisk)?; - if let Ok(ufs::State::Clean) = fs.state() { - let flags = fs.flags(); - println!("ramdisk mounted successfully (Clean, {flags:?})"); - Ok(Some(fs)) - } else { - println!("ramdisk mount failed: invalid state {:?}", fs.state()); - Err(Error::FsInvState) - } +/// The type of file, taken from the inode. +/// +/// Unix files can be one of a limited set of types; for +/// instance, directories are a type of file. The type +/// is encoded in the mode field of the inode; these are +/// the various types that are recognized. +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub enum FileType { + Unused, + Fifo, + Char, + Dir, + Block, + Regular, + SymLink, + ShadowInode, + Sock, + AttrDir, } -pub fn list(fs: &ufs::FileSystem<'_>, path: &str) -> Result<()> { - let path = path.as_bytes(); - let inode = fs.namei(path)?; - if inode.file_type() == ufs::FileType::Dir { - lsdir(fs, &ufs::Directory::new(&inode)); - } else { - lsfile(&inode, path); - } - Ok(()) +pub trait File: io::Read { + fn file_type(&self) -> FileType; +} + +pub trait FileSystem { + fn open(&self, path: &str) -> Result>; + fn list(&self, path: &str) -> Result<()>; + fn as_str(&self) -> &str; + fn with_addr(&self, addr: usize) -> *const u8; } -fn lsdir(fs: &ufs::FileSystem<'_>, dir: &ufs::Directory<'_>) { - for dentry in dir.iter() { - let ino = dentry.ino(); - match fs.inode(ino) { - Ok(file) => lsfile(&file, dentry.name()), - Err(e) => println!("ls: failed dir ent for ino #{ino}: {e:?}"), +pub fn mount(ramdisk: &'static [u8]) -> Result> { + mount_cpio(ramdisk).or_else(|_| { + let fs = ufs::FileSystem::new(ramdisk)?; + if let Ok(ufs::State::Clean) = fs.state() { + let flags = fs.flags(); + println!("ramdisk mounted successfully (Clean, {flags:?})"); + Ok(Box::new(fs)) + } else { + println!("ramdisk mount failed: invalid state {:?}", fs.state()); + Err(Error::FsInvState) } - } + }) +} + +pub fn mount_cpio(ramdisk: &'static [u8]) -> Result> { + let fs = Box::new(cpio::FileSystem::try_new(ramdisk)?); + println!("cpio miniroot mounted successfully"); + Ok(fs) } -fn lsfile(file: &ufs::Inode<'_>, name: &[u8]) { - println!( - "#{ino:<4} {mode:?} {nlink:<2} {uid:<3} {gid:<3} {size:>8} {name}", - mode = file.mode(), - ino = file.ino(), - nlink = file.nlink(), - uid = file.uid(), - gid = file.gid(), - size = file.size(), - name = unsafe { core::str::from_utf8_unchecked(name) } - ); +pub fn list(fs: &dyn FileSystem, path: &str) -> Result<()> { + fs.list(path) } -pub fn cat( - uart: &mut Uart, - fs: &ufs::FileSystem<'_>, - path: &str, -) -> Result<()> { - let path = path.as_bytes(); - let file = fs.namei(path)?; - if file.file_type() != ufs::FileType::Regular { +pub fn cat(uart: &mut Uart, fs: &dyn FileSystem, path: &str) -> Result<()> { + let file = fs.open(path)?; + if file.file_type() != FileType::Regular { println!("cat: not a regular file"); return Err(Error::BadArgs); } @@ -72,21 +78,16 @@ pub fn cat( let size = file.size(); while offset != size { let mut buf = [0u8; 1024]; - let nb = file.read(offset as u64, &mut buf)?; + let nb = file.read(offset.try_into().unwrap(), &mut buf)?; uart.putbs_crnl(&buf[..nb]); offset += nb; } Ok(()) } -pub fn copy( - fs: &ufs::FileSystem<'_>, - path: &str, - dst: &mut [u8], -) -> Result { - let path = path.as_bytes(); - let file = fs.namei(path)?; - if file.file_type() != ufs::FileType::Regular { +pub fn copy(fs: &dyn FileSystem, path: &str, dst: &mut [u8]) -> Result { + let file = fs.open(path)?; + if file.file_type() != FileType::Regular { println!("copy: not a regular file"); return Err(Error::BadArgs); } @@ -95,12 +96,11 @@ pub fn copy( Ok(nb) } -pub fn sha256(fs: &ufs::FileSystem<'_>, path: &str) -> Result<[u8; 32]> { +pub fn sha256(fs: &dyn FileSystem, path: &str) -> Result<[u8; 32]> { use sha2::{Digest, Sha256}; - let path = path.as_bytes(); - let file = fs.namei(path)?; - if file.file_type() != ufs::FileType::Regular { + let file = fs.open(path)?; + if file.file_type() != FileType::Regular { println!("sha256: can only sum regular files"); return Err(Error::BadArgs); } @@ -109,18 +109,10 @@ pub fn sha256(fs: &ufs::FileSystem<'_>, path: &str) -> Result<[u8; 32]> { let size = file.size(); while offset != size { let mut buf = [0u8; 1024]; - let nb = file.read(offset as u64, &mut buf)?; + let nb = file.read(offset.try_into().unwrap(), &mut buf)?; sum.update(&buf[..nb]); offset += nb; } let hash = sum.finalize(); Ok(hash.into()) } - -pub fn open<'a>( - fs: &'a ufs::FileSystem<'a>, - path: &str, -) -> Result> { - let path = path.as_bytes(); - fs.namei(path) -} diff --git a/src/repl/cat.rs b/src/repl/cat.rs index 4e63086..32f0764 100644 --- a/src/repl/cat.rs +++ b/src/repl/cat.rs @@ -16,6 +16,6 @@ pub fn run(config: &mut bldb::Config, env: &mut Vec) -> Result { }; let path = repl::popenv(env).as_string().map_err(usage)?; let fs = config.ramdisk.as_ref().ok_or(Error::FsNoRoot)?; - ramdisk::cat(&mut config.cons, fs, &path)?; + ramdisk::cat(&mut config.cons, fs.as_ref(), &path)?; Ok(Value::Nil) } diff --git a/src/repl/copy.rs b/src/repl/copy.rs index 9133a30..c62994f 100644 --- a/src/repl/copy.rs +++ b/src/repl/copy.rs @@ -20,6 +20,6 @@ pub fn run(config: &mut bldb::Config, env: &mut Vec) -> Result { .as_slice_mut(&config.page_table, 0) .and_then(|o| o.ok_or(Error::BadArgs)) .map_err(usage)?; - let len = ramdisk::copy(fs, &path, dst)?; + let len = ramdisk::copy(fs.as_ref(), &path, dst)?; Ok(Value::Slice(&dst[..len])) } diff --git a/src/repl/elfinfo.rs b/src/repl/elfinfo.rs index b8bbfb5..9273dcd 100644 --- a/src/repl/elfinfo.rs +++ b/src/repl/elfinfo.rs @@ -5,7 +5,6 @@ use crate::bldb; use crate::loader; use crate::println; -use crate::ramdisk; use crate::repl::{self, Value}; use crate::result::{Error, Result}; use alloc::vec::Vec; @@ -17,7 +16,7 @@ pub fn run(config: &mut bldb::Config, env: &mut Vec) -> Result { }; let path = repl::popenv(env).as_string().map_err(usage)?; let fs = config.ramdisk.as_ref().ok_or(Error::FsNoRoot)?; - let kernel = ramdisk::open(fs, &path)?; - loader::elfinfo(&kernel)?; + let kernel = fs.open(&path)?; + loader::elfinfo(kernel.as_ref())?; Ok(Value::Nil) } diff --git a/src/repl/list.rs b/src/repl/list.rs index 7be42fd..6e6bd52 100644 --- a/src/repl/list.rs +++ b/src/repl/list.rs @@ -15,6 +15,6 @@ pub fn run(config: &mut bldb::Config, env: &mut Vec) -> Result { return Err(Error::BadArgs); }; let fs = config.ramdisk.as_ref().ok_or(Error::FsNoRoot)?; - ramdisk::list(fs, &path)?; + ramdisk::list(fs.as_ref(), &path)?; Ok(Value::Nil) } diff --git a/src/repl/load.rs b/src/repl/load.rs index 6acfbb2..d4dd6f6 100644 --- a/src/repl/load.rs +++ b/src/repl/load.rs @@ -9,6 +9,28 @@ use crate::repl::{self, Value}; use crate::result::{Error, Result}; use alloc::vec::Vec; +pub fn loadcpio( + config: &mut bldb::Config, + env: &mut Vec, +) -> Result { + let usage = |error| { + println!("usage: loadcpio , "); + error + }; + let cpio = repl::popenv(env) + .as_slice(&config.page_table, 0) + .and_then(|o| o.ok_or(Error::BadArgs)) + .map_err(usage)?; + let path = repl::popenv(env).as_string().map_err(usage)?; + let src = cpio_reader::iter_files(cpio) + .find(|entry| entry.name() == path) + .ok_or(Error::CpioNoFile)? + .file(); + let entry = loader::load_bytes(&mut config.page_table, src)?; + let entry = entry.try_into().unwrap(); + Ok(Value::Pointer(src.as_ptr().with_addr(entry).cast_mut())) +} + pub fn loadmem( config: &mut bldb::Config, env: &mut Vec, @@ -27,15 +49,14 @@ pub fn loadmem( } pub fn run(config: &mut bldb::Config, env: &mut Vec) -> Result { - use crate::ramdisk; let usage = |error| { println!("usage: load "); error }; let path = repl::popenv(env).as_string().map_err(usage)?; let fs = config.ramdisk.as_ref().ok_or(Error::FsNoRoot)?; - let kernel = ramdisk::open(fs, &path)?; - let entry = loader::load(&mut config.page_table, &kernel)?; + let kernel = fs.open(&path)?; + let entry = loader::load(&mut config.page_table, kernel.as_ref())?; let entry = entry.try_into().unwrap(); - Ok(Value::Pointer(fs.data().with_addr(entry).cast_mut())) + Ok(Value::Pointer(fs.as_ref().with_addr(entry).cast_mut())) } diff --git a/src/repl/mod.rs b/src/repl/mod.rs index 533845a..ef66fb3 100644 --- a/src/repl/mod.rs +++ b/src/repl/mod.rs @@ -240,6 +240,7 @@ fn evalcmd( "inw" => pio::inw(config, env), "jfmt" => jfmt::run(config, env), "load" => load::run(config, env), + "loadcpio" => load::loadcpio(config, env), "loadmem" => load::loadmem(config, env), "ls" | "list" => list::run(config, env), "map" => vm::map(config, env), @@ -265,6 +266,7 @@ fn evalcmd( "sha256mem" => sha::mem(config, env), "spinner" => prompt::spinner(config, env), "unmap" => vm::unmap(config, env), + "umount" => mount::umount(config, env), "wrmsr" => msr::write(config, env), "wrsmn" => smn::write(config, env), _ => Err(Error::NoCommand), diff --git a/src/repl/mount.rs b/src/repl/mount.rs index aed1c1f..bfb053b 100644 --- a/src/repl/mount.rs +++ b/src/repl/mount.rs @@ -8,6 +8,11 @@ use crate::repl::{self, Value}; use crate::result::{Error, Result}; use alloc::vec::Vec; +pub fn umount(config: &mut bldb::Config, _env: &mut [Value]) -> Result { + config.ramdisk = None; + Ok(Value::Nil) +} + pub fn run(config: &mut bldb::Config, env: &mut Vec) -> Result { let usage = |error| { println!("usage: mount ,"); diff --git a/src/repl/reader.rs b/src/repl/reader.rs index a286b8b..1d72028 100644 --- a/src/repl/reader.rs +++ b/src/repl/reader.rs @@ -353,7 +353,8 @@ Supported commands include: * `inflate , [,]` decompresses the a ZLIB compressed slice from the given source to the given destination. -* `mount ` to mount the UFS ramdisk +* `mount ` to mount a UFS ramdisk or cpio miniroot. +* `umount` to unmount the ramdisk. * `ls ` to list a file or directory on the ramdisk * `cat ` to display the contents of a file * `copy ,` to copy the contents of a diff --git a/src/repl/sha.rs b/src/repl/sha.rs index 6b56405..f5cc5b1 100644 --- a/src/repl/sha.rs +++ b/src/repl/sha.rs @@ -34,6 +34,6 @@ pub fn run(config: &mut bldb::Config, env: &mut Vec) -> Result { } }; let fs = config.ramdisk.as_ref().ok_or(Error::FsNoRoot)?; - let hash = ramdisk::sha256(fs, &path)?; + let hash = ramdisk::sha256(fs.as_ref(), &path)?; Ok(Value::Sha256(hash)) } diff --git a/src/result.rs b/src/result.rs index e768149..5506675 100644 --- a/src/result.rs +++ b/src/result.rs @@ -19,6 +19,7 @@ pub(crate) enum Error { FsOffset, FsInvState, FsRead, + CpioNoFile, ElfTruncatedObj, ElfParseObject, ElfParseHeader, @@ -66,6 +67,7 @@ impl Error { Self::FsNoFile => "No such file or directory", Self::FsOffset => "Invalid file offset (exceeds maximum)", Self::FsRead => "Read error", + Self::CpioNoFile => "File not found in archive", Self::FsInvState => "Invalid UFS filesystem state", Self::ElfTruncatedObj => "ELF: Object truncated", Self::ElfParseObject => "ELF: Failed to parse object", diff --git a/src/ufs/dir.rs b/src/ufs/dir.rs index d5eb746..1356f17 100644 --- a/src/ufs/dir.rs +++ b/src/ufs/dir.rs @@ -13,14 +13,14 @@ pub const MAX_NAME_LEN: usize = 255; pub const PREFIX_LEN: usize = 8; /// Newtype around an inode representing a directory file. -pub struct Directory<'a> { - pub(super) inode: &'a Inode<'a>, +pub struct Directory { + pub(super) inode: Inode, } -impl<'a> Directory<'a> { +impl Directory { /// Creates a new directory from the given inode. Asserts /// that the inode refers to a directory. - pub fn new(inode: &'a Inode<'a>) -> Directory<'a> { + pub fn new(inode: Inode) -> Directory { let mode = inode.mode(); assert_eq!(mode.typ(), FileType::Dir); Directory { inode } @@ -28,14 +28,14 @@ impl<'a> Directory<'a> { /// Tries to create a new `Dirctory`` from the given inode. /// Returns `None`` if the inode's type is not a directory. - pub fn try_new(inode: &'a Inode<'a>) -> Option> { + pub fn try_new(inode: Inode) -> Option { let isdir = inode.mode().typ() == FileType::Dir; isdir.then(|| Self::new(inode)) } /// Returns an interator over the directory entries in this /// directory. - pub fn iter(&self) -> Iter<'_> { + pub fn iter(&self) -> Iter { Iter::new(self) } } @@ -43,14 +43,14 @@ impl<'a> Directory<'a> { /// A directory entry iterator. Iterates over the directory /// entries in the given directory. pub struct Iter<'a> { - inode: &'a Inode<'a>, + inode: &'a Inode, pos: u64, } -impl<'a> Iter<'a> { +impl Iter<'_> { /// Creates a new directory entry iterator for the given /// directory. - pub fn new(dir: &'a Directory<'a>) -> Iter<'a> { + pub fn new(dir: &Directory) -> Iter<'_> { let pos = 0; let inode = &dir.inode; Iter { inode, pos } diff --git a/src/ufs/mod.rs b/src/ufs/mod.rs index 0caf585..e8bc723 100644 --- a/src/ufs/mod.rs +++ b/src/ufs/mod.rs @@ -40,19 +40,24 @@ //! Unix''. ACM Transactions on Computer Systems 2, 3 (Aug. //! 1984), 181-197. https://doi.org/10.1145/989.990 +use crate::io; +use crate::println; +use crate::ramdisk::{self, FileType}; +use crate::result::{Error, Result}; + use core::cmp; use core::fmt::{self, Write}; use core::mem; use core::ops::Range; use core::ptr; +use alloc::boxed::Box; +use alloc::rc::Rc; use alloc::vec; use bitflags::bitflags; use bitstruct::bitstruct; use static_assertions::const_assert; -use crate::result::{Error, Result}; - /// The size of a "Device Block". That is, the size of a /// physical block on the underlying storage device, in bytes. pub const DEV_BLOCK_SIZE: usize = 512; @@ -403,7 +408,7 @@ const _FSL_SIZE: usize = (NDADDR + NIADDR - 1) * core::mem::size_of::(); /// The storage-resident version of an inode. #[repr(C, align(128))] -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct DInode { smode: u16, // 0: mode and type of file nlink: u16, // 2: number of links to file @@ -427,16 +432,19 @@ pub struct DInode { oeftflag: u32, // 124: extended attr directory ino, 0 = none } -#[derive(Debug)] -pub struct FileSystem<'a> { - sd: &'a [u8], +struct InnerFileSystem { + sd: io::Sd, sb: SuperBlock, } -impl<'a> FileSystem<'a> { - pub fn new(sd: &'a [u8]) -> Result> { +#[derive(Clone)] +pub struct FileSystem(Rc); + +impl FileSystem { + pub fn new(sd: &[u8]) -> Result { let sb = SuperBlock::read(sd)?; - Ok(FileSystem { sd, sb }) + let sd = io::Sd::from_slice(sd); + Ok(FileSystem(Rc::new(InnerFileSystem { sd, sb }))) } pub fn root_inode(&self) -> Inode { @@ -448,54 +456,60 @@ impl<'a> FileSystem<'a> { } pub fn fragsize(&self) -> usize { - self.sb.fsize as usize + self.0.sb.fsize as usize } /// Returns the filesystem state from the superblock. pub fn state(&self) -> Result { - self.sb.state() + self.0.sb.state() } /// Returns the filesystem flags from the superblock. pub fn flags(&self) -> Flags { - self.sb.flags() + self.0.sb.flags() } /// Returns the disk block number of a fragment. pub fn frags_to_sdblock(&self, fbno: usize) -> usize { - self.sb.fsbtodb(fbno) + self.0.sb.fsbtodb(fbno) } /// Returns the logical file block number for the given byte /// offset. pub fn logical_blockno(&self, offset: u64) -> usize { - self.sb.lblkno(offset) as usize + self.0.sb.lblkno(offset) as usize } /// Returns the number of inodes per fragment. #[allow(dead_code)] pub fn inodes_per_frag(&self) -> usize { - self.sb.inopf() as usize + self.0.sb.inopf() as usize } /// Returns the number of cylinder groups in the filesystem, /// as a Range, starting at zero. #[allow(dead_code)] pub fn cylgroups(&self) -> Range { - 0..self.sb.ncg + 0..self.0.sb.ncg } /// Returns the byte offset of the start of the data block /// region for the given cylinder group. #[allow(dead_code)] pub fn cylgroup_data_offset(&self, cylgrp: u32) -> usize { - self.sb.cgdmin(cylgrp) as usize * self.fragsize() + self.0.sb.cgdmin(cylgrp) as usize * self.fragsize() } /// Returns the number of indirect blocks spanned by a file /// system block. pub fn indir_span_per_block(&self) -> usize { - self.sb.nindir as usize + self.0.sb.nindir as usize + } + + /// Returns the offset of given inode, relative to the + /// start of the storage area. + pub fn inode_offset(&self, ino: u32) -> usize { + self.0.sb.inode_offset(ino) } /// Returns the logical fragment number in a block for a given @@ -507,16 +521,12 @@ impl<'a> FileSystem<'a> { /// Returns a the block size of the filesystem. pub fn blocksize(&self) -> usize { - self.sb.bsize as usize + self.0.sb.bsize as usize } /// Maps a file path name to an inode number, searching from /// some starting inode. - fn namex( - &'a self, - mut ip: Inode<'a>, - mut path: &[u8], - ) -> Result> { + fn namex(&self, mut ip: Inode, mut path: &[u8]) -> Result { // Split a '/' separated pathname into the first // componenet and remainder. If the path name is // empty, or contains only '/'s, returns None. @@ -531,7 +541,7 @@ impl<'a> FileSystem<'a> { if dirname.is_empty() { break; } - let dir = Directory::try_new(&ip).ok_or(Error::FsInvPath)?; + let dir = Directory::try_new(ip.clone()).ok_or(Error::FsInvPath)?; let mut tip = if let Some(entry) = dir.iter().find(|d| d.name() == dirname) { self.inode(entry.ino()) @@ -550,14 +560,20 @@ impl<'a> FileSystem<'a> { } /// Maps a file path name to an inode number. - pub fn namei(&self, path: &[u8]) -> Result> { + pub fn namei(&self, path: &[u8]) -> Result { self.namex(self.root_inode(), path) } + /// Returns a subset of the filesystem storage area + /// corresponding to the given length and offset. + fn subset(&self, offset: usize, len: usize) -> io::Sd { + self.0.sd.subset(offset, len) + } + /// Returns a pointer to the data area. /// Used for exposing provenance. pub fn data(&self) -> *const u8 { - self.sd.as_ptr() + self.0.sd.as_ptr() } } @@ -566,10 +582,32 @@ impl<'a> FileSystem<'a> { /// Note that UFS supports "holes"; block-sized and aligned /// spans of bytes within a file that are all zeroes are /// specially marked, and not backed by allocated blocks. -#[derive(Clone, Copy, Debug)] -pub enum Block<'a> { +enum Block { Hole, - Sd(&'a [u8]), + Sd(io::Sd), +} + +impl Block { + fn read(&self, offset: usize, dst: &mut [u8]) { + match self { + Self::Hole => dst.fill(0), + Self::Sd(sd) => { + let ptr = sd.as_ptr(); + let len = sd.len(); + if offset >= len { + return; + } + let count = cmp::min(dst.len(), len - offset); + unsafe { + ptr::copy( + ptr.wrapping_add(offset), + dst.as_mut_ptr(), + count, + ); + } + } + } + } } /// This block of constants provides the traditional Unix names @@ -584,27 +622,6 @@ const IFSHAD: u8 = 0o13; const IFSOCK: u8 = 0o14; const IFATTRDIR: u8 = 0o16; -/// The type of file, taken from the inode. -/// -/// Unix files can be one of a limited set of types; for -/// instance, directories are a type of file. The type -/// is encoded in the mode field of the inode; these are -/// the various types that are recognized. -#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] -#[repr(u8)] -pub enum FileType { - Unused = 0, - Fifo = IFIFO, - Char = IFCHR, - Dir = IFDIR, - Block = IFBLK, - Regular = IFREG, - SymLink = IFLNK, - ShadowInode = IFSHAD, - Sock = IFSOCK, - AttrDir = IFATTRDIR, -} - impl FileType { /// Returns a single character that represents the file /// type, such as 'd' for directories, or '-' for regular @@ -667,7 +684,18 @@ impl bitstruct::FromRaw for Mode { impl bitstruct::IntoRaw for Mode { fn into_raw(bits: FileType) -> u8 { - bits as u8 + match bits { + FileType::Fifo => IFIFO, + FileType::Char => IFCHR, + FileType::Dir => IFDIR, + FileType::Block => IFBLK, + FileType::Regular => IFREG, + FileType::SymLink => IFLNK, + FileType::ShadowInode => IFSHAD, + FileType::Sock => IFSOCK, + FileType::AttrDir => IFATTRDIR, + FileType::Unused => 0, + } } } @@ -709,18 +737,21 @@ impl fmt::Debug for Mode { /// An in-memory representation of an inode, that associates the /// inode with the underlying filesystem it came from and its /// inode number in that filesystem. -pub struct Inode<'a> { +#[derive(Clone)] +pub struct Inode { pub dinode: DInode, pub ino: u32, - pub fs: &'a FileSystem<'a>, + pub fs: FileSystem, } -impl<'a> Inode<'a> { +impl Inode { /// Returns a new inode from the given filesystem. - pub fn new(fs: &'a FileSystem<'a>, ino: u32) -> Result> { - let inoff = fs.sb.inode_offset(ino); - let p = fs.sd.as_ptr().wrapping_add(inoff).cast::(); + pub fn new(fs: &FileSystem, ino: u32) -> Result { + let inoff = fs.inode_offset(ino); + let src = fs.subset(inoff, mem::size_of::()); + let p = src.as_ptr().cast::(); let dinode = unsafe { ptr::read_unaligned(p) }; + let fs = fs.clone(); Ok(Inode { dinode, ino, fs }) } @@ -769,17 +800,10 @@ impl<'a> Inode<'a> { let n = core::cmp::min(buf.len(), self.size() - off); let mut nread = 0; while nread < n { - let frag_off: usize = off % fragsize; + let frag_off = off % fragsize; let m = cmp::min(n - nread, fragsize - frag_off); - match self.bmap(off as u64)? { - Block::Hole => { - buf[nread..nread + m].fill(0); - } - Block::Sd(bs) => { - buf[nread..nread + m] - .copy_from_slice(&bs[frag_off..frag_off + m]); - } - } + let block = self.bmap(off as u64)?; + block.read(frag_off, &mut buf[nread..nread + m]); off += m; nread += m; } @@ -789,12 +813,12 @@ impl<'a> Inode<'a> { /// Maps a byte offset in some file into a fragment-sized block /// from the the storage device. fn bmap(&self, off: u64) -> Result { - let fs = self.fs; - let lbn = fs.logical_blockno(off); + let fs = &self.fs; + let lbn = self.fs.logical_blockno(off); if lbn < NDADDR { let sdbn = self.dinode.dblocks[lbn] as usize; let offset = (sdbn + fs.logical_block_fragno(off)) * fs.fragsize(); - return Ok(Block::Sd(&fs.sd[offset..offset + fs.fragsize()])); + return Ok(Block::Sd(fs.subset(offset, fs.fragsize()))); } let mut lbn = lbn - NDADDR; let mut indir_span = 1; @@ -820,7 +844,9 @@ impl<'a> Inode<'a> { indir_span /= fs.indir_span_per_block(); let dboff = (lbn / indir_span) % fs.indir_span_per_block(); let dbaddr = dblockno * DEV_BLOCK_SIZE + dboff * 4; - let bs = &fs.sd[dbaddr..dbaddr + 4]; + let bs = unsafe { + core::ptr::read::<[u8; 4]>(fs.subset(dbaddr, 4).as_ptr().cast()) + }; nb = u32::from_ne_bytes([bs[0], bs[1], bs[2], bs[3]]); if nb == 0 { return Ok(Block::Hole); @@ -828,7 +854,7 @@ impl<'a> Inode<'a> { } let sdbn = nb as usize; let offset = (sdbn + fs.logical_block_fragno(off)) * fs.fragsize(); - Ok(Block::Sd(&self.fs.sd[offset..offset + fs.fragsize()])) + Ok(Block::Sd(self.fs.subset(offset, fs.fragsize()))) } pub fn mode(&self) -> Mode { @@ -836,7 +862,7 @@ impl<'a> Inode<'a> { } } -impl fmt::Debug for Inode<'_> { +impl fmt::Debug for Inode { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_fmt(format_args!("INODE: {} ({:?})\n", self.ino, self.mode()))?; f.write_fmt(format_args!("{:#x?}", self.dinode))?; @@ -844,6 +870,73 @@ impl fmt::Debug for Inode<'_> { } } +impl io::Read for Inode { + fn read(&self, offset: u64, dst: &mut [u8]) -> Result { + self.read(offset, dst) + } + + fn size(&self) -> usize { + self.size() + } +} + +impl ramdisk::File for Inode { + fn file_type(&self) -> FileType { + self.file_type() + } +} + +impl ramdisk::FileSystem for FileSystem { + fn open(&self, path: &str) -> Result> { + Ok(Box::new(self.namei(path.as_bytes())?)) + } + + fn list(&self, path: &str) -> Result<()> { + list(self, path, self.namei(path.as_bytes())?) + } + + fn as_str(&self) -> &str { + "UFS" + } + + fn with_addr(&self, addr: usize) -> *const u8 { + self.data().with_addr(addr) + } +} + +/// Lists a file, in a manner similar to `ls`. +pub fn list(fs: &FileSystem, path: &str, file: Inode) -> Result<()> { + if file.file_type() == FileType::Dir { + lsdir(fs, &Directory::new(file)); + } else { + lsfile(&file, path.as_bytes()); + } + Ok(()) +} + +fn lsdir(fs: &FileSystem, dir: &Directory) { + for dentry in dir.iter() { + let ino = dentry.ino(); + match fs.inode(ino) { + Ok(file) => lsfile(&file, dentry.name()), + Err(e) => println!("ls: failed dir ent for ino #{ino}: {e:?}"), + } + } +} + +fn lsfile(file: &Inode, name: &[u8]) { + println!( + "#{ino:<4} {mode:?} {nlink:<2} {uid:<3} {gid:<3} {size:>8} {name}", + mode = file.mode(), + ino = file.ino(), + nlink = file.nlink(), + uid = file.uid(), + gid = file.gid(), + size = file.size(), + name = unsafe { core::str::from_utf8_unchecked(name) } + ); +} + mod dir; pub use dir::Directory;