commit a4609d7f53cffe519179b79d76219a766a8debc1 Author: pjht Date: Tue Nov 1 12:41:57 2022 -0500 Initial commit diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..62d96b1 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,9 @@ +[unstable] +build-std-features = ["compiler-builtins-mem"] +build-std = ["core", "compiler_builtins", "alloc"] + +[build] +target = "x86_64-unknown-none.json" + +[install] +root = "../kernel/sysroot" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target diff --git a/2 b/2 new file mode 100644 index 0000000..58ba175 --- /dev/null +++ b/2 @@ -0,0 +1,61 @@ +#![no_std] +#![no_main] +#![feature(int_roundings)] +#![deny(unsafe_op_in_unsafe_fn)] + +use ext2::Ext2; +use std::fmt::Debug; +use std::loader::{Binary, Loader}; +use std::path::Path; +use std::syscalls::{get_initrd, new_process}; +use std::{ipc, prelude::*, str}; +use tar_no_std::TarArchiveRef; + +use ata::PRIMARY_SLAVE; + +mod ata; +mod ext2; + +main!({ + let primary_slave = PRIMARY_SLAVE.unwrap(); + let fs = Ext2::new(primary_slave).unwrap(); + print_all_files("/", &fs); + println!("Hello, syscall format world!"); + let initrd = TarArchiveRef::new(get_initrd()); + let test_proc = Binary::new( + initrd + .entries() + .find(|entry| entry.filename() == *"bin/test_proc") + .expect("test_proc not found") + .data(), + ) + .expect("test_proc not an ELF binary"); + let space = Loader::load(&test_proc); + let pid = new_process(test_proc.entry_point(), space).expect("Failed to create process"); + ipc::base::allow_protocol(0); + let id = ipc::rpc::send_call(pid, 0, 0, "Hello, RPC call world".as_bytes()); + let ret = loop { + ipc::rpc::process_messages(); + if let Some(ret) = ipc::rpc::get_return(id) { + dbg!(); + break ret; + } + }; + dbg!(&ret.retval); + println!("{}", str::from_utf8(&ret.retval).unwrap()); + loop {} +}); + +fn print_all_files + Debug>(path: P, fs: &Ext2) { + for entry in fs.read_dir(path).unwrap() { + let entry = entry.unwrap(); + if entry.metadata().file_type().is_dir() { + print_all_files(entry.path(), fs); + } else { + let mut file = fs.open(entry.path()).unwrap(); + let mut buf = String::new(); + file.read_to_string(&mut buf).unwrap(); + print!("{:?}:\n{}", entry.path(), buf); + } + } +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..21c57d2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,408 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "binread" +version = "2.2.0" +dependencies = [ + "binread_derive", + "rustversion", + "std", +] + +[[package]] +name = "binread_derive" +version = "2.1.0" +dependencies = [ + "either", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bit_field" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cobs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" + +[[package]] +name = "core2" +version = "0.4.0" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd42583b04998a5363558e5f9291ee5a5ff6b49944332103f251e7479a82aa7" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "derive-try-from-primitive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302ccf094df1151173bb6f5a2282fcd2f45accd5eae1bdf82dcbfefbc501ad5c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dev_driver_rpc" +version = "0.1.0" +dependencies = [ + "postcard", + "serde", + "spin", + "std", +] + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "elfloader" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a249d6a9d50f3bf5a3cb7bfd75e84989cad89c6c77b5996c8b084e844146ff04" +dependencies = [ + "bitflags", + "log", + "xmas-elf", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "init" +version = "0.1.0" +dependencies = [ + "binread", + "bitflags", + "dev_driver_rpc", + "itertools", + "postcard", + "serde", + "spin", + "std", + "tap", + "tar-no-std", + "x86_64 0.14.10 (git+https://github.com/pjht/x86_64)", +] + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "libc" +version = "0.2.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" + +[[package]] +name = "linked_list_allocator" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "636c3bc929db632724303109c88d5d559a2a60f62243bb041387f03fa081d94a" +dependencies = [ + "spinning_top", +] + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "once_cell" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" + +[[package]] +name = "postcard" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c2b180dc0bade59f03fd005cb967d3f1e5f69b13922dad0cd6e047cb8af2363" +dependencies = [ + "cobs", + "serde", +] + +[[package]] +name = "proc-macro2" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "spin" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spinning_top" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75adad84ee84b521fb2cca2d4fd0f1dab1d8d026bda3c5bea4ca63b5f9f9293c" +dependencies = [ + "lock_api", +] + +[[package]] +name = "std" +version = "0.1.0" +dependencies = [ + "core2", + "crossbeam-queue", + "derive-try-from-primitive", + "elfloader", + "hashbrown", + "linked_list_allocator", + "postcard", + "serde", + "spin", + "x86_64 0.14.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tar-no-std" +version = "0.1.7" +dependencies = [ + "arrayvec", + "bitflags", + "log", +] + +[[package]] +name = "unicode-ident" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "volatile" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ca98349dda8a60ae74e04fd90c7fb4d6a4fbe01e6d3be095478aa0b76f6c0c" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "x86_64" +version = "0.14.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "100555a863c0092238c2e0e814c1096c1e5cf066a309c696a87e907b5f8c5d69" +dependencies = [ + "bit_field", + "bitflags", + "rustversion", + "volatile", +] + +[[package]] +name = "x86_64" +version = "0.14.10" +source = "git+https://github.com/pjht/x86_64#5d941e68fa70779e07576d42a9f49bc89afdb9ed" +dependencies = [ + "bit_field", + "bitflags", + "rustversion", + "volatile", +] + +[[package]] +name = "xmas-elf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d29b4d8e7beaceb4e77447ba941a7600d23d0319ab52da0461abea214832d5a" +dependencies = [ + "zero", +] + +[[package]] +name = "zero" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f1bc8a6b2005884962297587045002d8cfb8dcec9db332f4ca216ddc5de82c5" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9eb6c49 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "init" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tar-no-std = { path = "../tar-no-std" } +std = { path = "../std" } +bitflags = "1.3.2" +spin = "0.9.4" +tap = "1.0.1" +x86_64 = { git = "https://github.com/pjht/x86_64", features = ["experimental"] } +binread = { version = "2.2.0", path = "../binread/binread", default-features = false } +itertools = { version = "0.10.3", default-features = false, features = ["use_alloc"] } +serde = { version = "1.0.144", default-features = false, features = ["alloc", "derive"] } +postcard = { version = "1.0.2", default-features = false, features = ["alloc"] } +dev_driver_rpc = { version = "0.1.0", path = "../dev_driver_rpc" } + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" + diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/src/ata.rs b/src/ata.rs new file mode 100644 index 0000000..c44abc9 --- /dev/null +++ b/src/ata.rs @@ -0,0 +1,412 @@ +use std::cmp::Ordering; + +use bitflags::bitflags; +use spin::{Lazy, Mutex}; +use std::io::{self, Read, Seek}; +use tap::Tap; +use x86_64::instructions::port::{ + PortRead, PortSafe, PortSafeReadOnly, PortSafeWriteOnly, PortWrite, +}; + +bitflags! { + pub struct RawError: u8 { + const AMNF = 0b0000_0001; + const TKZNF = 0b0000_0010; + const ABRT = 0b0000_0100; + const MCR = 0b0000_1000; + const IDNF = 0b0001_0000; + const MC = 0b0010_0000; + const UNC = 0b0100_0000; + const BBK = 0b1000_0000; + } +} + +impl From for Error { + fn from(err: RawError) -> Self { + if err.contains(RawError::AMNF) { + Self::AddressMarkNotFound + } else if err.contains(RawError::TKZNF) { + Self::TrackZeroNotFound + } else if err.contains(RawError::ABRT) { + Self::AbortedCommand + } else if err.contains(RawError::MCR) { + Self::MediaChangeRequest + } else if err.contains(RawError::IDNF) { + Self::IDMarkNotFound + } else if err.contains(RawError::MC) { + Self::MediaChanged + } else if err.contains(RawError::UNC) { + Self::UncorrectableData + } else if err.contains(RawError::BBK) { + Self::BadBlock + } else { + Self::Generic + } + } +} + +impl PortRead for RawError { + unsafe fn read_from_port(port: u16) -> Self { + unsafe { Self::from_bits_unchecked(u8::read_from_port(port)) } + } +} + +impl PortWrite for RawError { + unsafe fn write_to_port(port: u16, value: Self) { + unsafe { u8::write_to_port(port, value.bits) }; + } +} + +bitflags! { + pub struct Status: u8 { + const ERR = 0b0000_0001; + const DRQ = 0b0000_1000; + const SRV = 0b0001_0000; + const DF = 0b0010_0000; + const RDY = 0b0100_0000; + const BSY = 0b1000_0000; + } +} + +impl PortRead for Status { + unsafe fn read_from_port(port: u16) -> Self { + unsafe { Self::from_bits_unchecked(u8::read_from_port(port)) } + } +} + +impl PortWrite for Status { + unsafe fn write_to_port(port: u16, value: Self) { + unsafe { u8::write_to_port(port, value.bits) }; + } +} + +bitflags! { + pub struct DeviceControl: u8 { + const N_IEN = 0b0000_0010; + const SRST = 0b0000_0100; + const HOB = 0b1000_0000; + } +} + +impl PortRead for DeviceControl { + unsafe fn read_from_port(port: u16) -> Self { + unsafe { Self::from_bits_unchecked(u8::read_from_port(port)) } + } +} + +impl PortWrite for DeviceControl { + unsafe fn write_to_port(port: u16, value: Self) { + unsafe { u8::write_to_port(port, value.bits) }; + } +} + +bitflags! { + #[repr(transparent)] + pub struct DriveHead: u8 { + const DT0 = 0b0000_0001; + const DT1 = 0b0000_0010; + const DT2 = 0b0000_0100; + const DT3 = 0b0000_1000; + const DRV = 0b0001_0000; + const LBA = 0b0100_0000; + } +} + +impl DriveHead { + fn set_head_block(&mut self, data: u8) { + assert!(data < 16); + self.bits = (self.bits & 0xF0) | data; + } +} + +impl PortRead for DriveHead { + unsafe fn read_from_port(port: u16) -> Self { + unsafe { Self::from_bits_unchecked(u8::read_from_port(port)) } + } +} + +impl PortWrite for DriveHead { + unsafe fn write_to_port(port: u16, value: Self) { + unsafe { u8::write_to_port(port, value.bits) }; + } +} + +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum DriveNumber { + Master = 0, + Slave = 1, +} + +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq)] +#[allow(unused)] +enum Command { + Identify = 0xEC, + IdentifyPacket = 0xA1, + ReadSectors = 0x20, + WriteSectors = 0x30, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Error { + NonexistentDrive, + Generic, + AbortedCommand, + TrackZeroNotFound, + AddressMarkNotFound, + MediaChangeRequest, + IDMarkNotFound, + MediaChanged, + UncorrectableData, + BadBlock, +} + +impl From for io::Error { + fn from(_: Error) -> Self { + Self::new(io::ErrorKind::Other, "ATA read error") + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[allow(clippy::upper_case_acronyms)] +pub enum DriveKind { + ATA, + ATAPI, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum IdentifyError { + SATADrive, + Error(Error), +} + +impl From for IdentifyError { + fn from(err: Error) -> Self { + Self::Error(err) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Bus { + data: PortSafe, + error: PortSafeReadOnly, + sector_count: PortSafe, + sector_num_lba_lo: PortSafe, + cyl_low_lba_mid: PortSafe, + cyl_high_lba_hi: PortSafe, + drive_head: PortSafe, + status: PortSafeReadOnly, + command: PortSafeWriteOnly, + selected_drive: Option, // None represents the initial state with an unknown drive selected +} + +impl Bus { + /// SAFETY + /// io_base must be a valid base for the ATA bus's IO registers + const unsafe fn new(io_base: u16) -> Self { + unsafe { + Self { + data: PortSafe::new(io_base), + error: PortSafeReadOnly::new(io_base + 1), + sector_count: PortSafe::new(io_base + 2), + sector_num_lba_lo: PortSafe::new(io_base + 3), + cyl_low_lba_mid: PortSafe::new(io_base + 4), + cyl_high_lba_hi: PortSafe::new(io_base + 5), + drive_head: PortSafe::new(io_base + 6), + status: PortSafeReadOnly::new(io_base + 7), + command: PortSafeWriteOnly::new(io_base + 7), + selected_drive: None, + } + } + } + + pub fn select(&mut self, drive: DriveNumber, lba: bool, data: u8) { + if self.selected_drive != Some(drive) { + self.drive_head.write(DriveHead::empty().tap_mut(|v| { + v.set(DriveHead::DRV, drive == DriveNumber::Slave); + v.set(DriveHead::LBA, lba); + v.set_head_block(data); + })); + for _ in 0..14 { + self.status.read(); + } + self.selected_drive = Some(drive); + } + } + + pub fn read_data(&mut self) -> [u16; 256] { + let mut arr = [0; 256]; + arr.fill_with(|| self.data.read()); + arr + } + + fn send_command(&mut self, command: Command) -> Result<(), Error> { + self.command.write(command as u8); + while self.status.read().contains(Status::BSY) {} + while !(self.status.read().intersects(Status::DRQ | Status::ERR)) {} + self.error()?; + Ok(()) + } + + pub fn identify(&mut self) -> Result<(DriveKind, [u16; 256]), IdentifyError> { + self.command.write(Command::Identify as u8); + if self.status.read().bits() == 0 { + return Err(IdentifyError::Error(Error::NonexistentDrive)); + }; + while self.status.read().contains(Status::BSY) {} + while !(self.status.read().intersects(Status::DRQ | Status::ERR)) {} + if self.status.read().contains(Status::ERR) { + if self.cyl_low_lba_mid.read() == 0x14 && self.cyl_high_lba_hi.read() == 0xEB { + self.command.write(Command::IdentifyPacket as u8); + if self.status.read().bits() == 0 { + return Err(IdentifyError::Error(Error::NonexistentDrive)); + }; + while self.status.read().contains(Status::BSY) {} + while !(self.status.read().intersects(Status::DRQ | Status::ERR)) {} + self.error()?; + Ok((DriveKind::ATAPI, self.read_data())) + } else { + Err(IdentifyError::SATADrive) + } + } else { + Ok((DriveKind::ATA, self.read_data())) + } + } + + fn error(&mut self) -> Result<(), Error> { + if self.status.read().contains(Status::ERR) { + Err(self.error.read().into()) + } else { + Ok(()) + } + } +} + +#[allow(unused)] +// SAFETY +// This is safe because 0x1F0 is the standard primary bus base address +static PRIMARY_BUS: Mutex = Mutex::new(unsafe { Bus::new(0x1F0) }); +#[allow(unused)] +// SAFETY +// This is safe because 0x170 is the standard secondary bus base address +static SECONDARY_BUS: Mutex = Mutex::new(unsafe { Bus::new(0x170) }); + +#[allow(unused)] +pub static PRIMARY_MASTER: Lazy> = + Lazy::new(|| Device::new(&PRIMARY_BUS, DriveNumber::Master)); +#[allow(unused)] +pub static PRIMARY_SLAVE: Lazy> = + Lazy::new(|| Device::new(&PRIMARY_BUS, DriveNumber::Slave)); +#[allow(unused)] +pub static SECONDARY_MASTER: Lazy> = + Lazy::new(|| Device::new(&SECONDARY_BUS, DriveNumber::Master)); +#[allow(unused)] +pub static SECONDARY_SLAVE: Lazy> = + Lazy::new(|| Device::new(&SECONDARY_BUS, DriveNumber::Slave)); + +#[derive(Copy, Clone, Debug)] +pub struct Device { + bus: &'static Mutex, + drive_number: DriveNumber, + lba: bool, + pos: u64, + len: u64, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum NewDeviceError { + IdentifyError(IdentifyError), + ATAPIDevice, +} + +impl Device { + fn new(bus: &'static Mutex, drive_number: DriveNumber) -> Result { + bus.lock().select(drive_number, true, 0); + let id_result = bus + .lock() + .identify() + .map_err(NewDeviceError::IdentifyError)?; + if let (DriveKind::ATA, ident_data) = id_result { + Ok(Self { + bus, + drive_number, + lba: true, + pos: 0, + len: (u64::from(ident_data[60]) * 512), + }) + } else { + Err(NewDeviceError::ATAPIDevice) + } + } + + fn select(&self, data: u8) { + self.bus.lock().select(self.drive_number, self.lba, data); + } +} + +impl Read for Device { + #[allow(clippy::similar_names)] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let avail_len = u64::min(u64::min(self.len - self.pos, buf.len() as u64), 512 * 250); + if avail_len == 0 { + return Ok(0); + } + let sector = self.pos / 512; + assert!(sector < 0x1_0000_0000_0000); + let offset = (self.pos % 512) as usize; + let length = avail_len as usize + offset; + let num_sectors = length.div_ceil(512); + assert!(num_sectors < 256); + if sector < 0x1000_0000 { + self.select(((sector & 0xF00_0000) >> 24) as u8); + let mut bus = self.bus.lock(); + bus.sector_count.write(num_sectors as u8); + bus.sector_num_lba_lo.write(sector as u8); + bus.cyl_low_lba_mid.write((sector >> 8) as u8); + bus.cyl_high_lba_hi.write((sector >> 16) as u8); + bus.send_command(Command::ReadSectors)?; + } else { + unimplemented!(); + } + let mut byte_num = 0; + let mut i = 0; + for _ in 0..num_sectors { + for byte in self + .bus + .lock() + .read_data() + .into_iter() + .flat_map(u16::to_le_bytes) + { + if byte_num >= offset && byte_num < (offset + buf.len()) { + buf[i] = byte; + i += 1; + } + byte_num += 1; + } + } + self.pos += avail_len as u64; + Ok(avail_len as usize) + } +} + +impl Seek for Device { + fn seek(&mut self, pos: io::SeekFrom) -> io::Result { + match pos { + io::SeekFrom::Start(x) => self.pos = x, + io::SeekFrom::End(_) => { + return Err(io::Error::new( + io::ErrorKind::Other, + "End seek not implemented yet", + )) + } + io::SeekFrom::Current(x) => match x.cmp(&0) { + Ordering::Equal => (), + Ordering::Greater => self.pos += x.unsigned_abs(), + Ordering::Less => self.pos -= x.unsigned_abs(), + }, + }; + Ok(self.pos) + } +} diff --git a/src/ext2.rs b/src/ext2.rs new file mode 100644 index 0000000..1a69128 --- /dev/null +++ b/src/ext2.rs @@ -0,0 +1,163 @@ +mod block_group_table; +mod block_reader; +mod dir; +mod file; +mod metadata; +mod structs; + +use block_group_table::BlockGroupDescriptorTable; +use block_reader::BlockReader; +use std::io::{self, Read}; +use std::path::{Component, Path}; +use std::string::{String, ToString}; +use std::vec::Vec; +use structs::{Inode, Superblock}; + +pub use dir::{DirEntry, ReadDir}; +pub use file::File; +pub use metadata::{FileType, Metadata, Permissions}; +use std::fs::File as StdFile; + +#[derive(Debug)] +pub struct Ext2 { + reader: BlockReader, + descriptor_table: BlockGroupDescriptorTable, +} + +impl Ext2 { + pub fn new(mut disk: StdFile) -> io::Result { + let superblock = Superblock::read_from_disk(&mut disk)?; + let reader = BlockReader::new(disk, &superblock); + Ok(Self { + descriptor_table: BlockGroupDescriptorTable::new(&superblock, &reader)?, + reader, + }) + } + + fn path_inode>(&self, path: P) -> io::Result { + let path = path.as_ref(); + path.parent().map_or(Ok(2), |parent| { + self.read_dir_no_path(parent)? + .get_entry(path.file_name().ok_or_else(|| { + io::Error::new( + io::ErrorKind::Other, + "A path ending in .. or . was passed to path_inode", + ) + })?) + .map(|x| x.inode) + }) + } + + /// Returns an iterator over the entries within a directory. + /// + /// The iterator will yield instances of [`io::Result`]<[`DirEntry`]>. + /// New errors may be encountered after an iterator is initially constructed. + /// Entries for the current and parent directories (`.` and `..`) are + /// skipped. + /// + /// + /// # Errors + /// + /// This function will return an error in the following situations, but is not + /// limited to just these cases: + /// + /// * The provided `path` doesn't exist. + /// * The process lacks permissions to view the contents. + /// * The `path` points at a non-directory file. + #[allow(unused)] + pub fn read_dir>(&self, path: P) -> io::Result { + path.as_ref().components().try_fold( + ReadDir::read_inode(self, 2, Some("/".to_string().into()))?, + |dir, component| match component { + Component::Normal(os_str) => dir.get_entry(os_str)?.read_dir(), + _ => Ok(dir), + }, + ) + } + + /// Returns an iterator over the entries within a directory, without allocating the full path + /// for the directory on the heap. This saves unnecessary allocations when you will not use + /// `DirEntry::path`. + /// + /// The iterator will yield instances of [`io::Result`]<[`DirEntry`]>. + /// New errors may be encountered after an iterator is initially constructed. + /// Entries for the current and parent directories (`.` and `..`) are + /// skipped. + /// + /// + /// # Errors + /// + /// This function will return an error in the following situations, but is not + /// limited to just these cases: + /// + /// * The provided `path` doesn't exist. + /// * The process lacks permissions to view the contents. + /// * The `path` points at a non-directory file. + pub fn read_dir_no_path>(&self, path: P) -> io::Result { + path.as_ref().components().try_fold( + ReadDir::read_inode(self, 2, None)?, + |dir, component| match component { + Component::Normal(os_str) => dir.get_entry(os_str)?.read_dir(), + _ => Ok(dir), + }, + ) + } + + /// Attempts to open a file in read-only mode. + /// + /// # Errors + /// + /// This function will return an error if `path` does not already exist. + #[allow(unused)] + pub fn open>(&self, path: P) -> io::Result { + File::from_inode(Inode::read(self, self.path_inode(path)?)?, self) + } + + /// Read the entire contents of a file into a string. + /// + /// This is a convenience function for using [`File::open`] and [`read_to_string`] + /// with fewer imports and without an intermediate variable. + /// + /// [`read_to_string`]: core2::io::Read::read_to_string + /// + /// # Errors + /// + /// This function will return an error if `path` does not already exist. + /// Other errors may also be returned according to [`OpenOptions::open`]. + /// + /// It will also return an error if it encounters while reading an error + /// of a kind other than [`io::ErrorKind::Interrupted`], + /// or if the contents of the file are not valid UTF-8. + #[allow(unused)] + pub fn read_to_string>(&self, path: P) -> io::Result { + let mut string = String::new(); + self.open(path)?.read_to_string(&mut string)?; + Ok(string) + } + + /// Read the entire contents of a file into a bytes vector. + /// + /// This is a convenience function for using [`File::open`] and [`read_to_end`] + /// with fewer imports and without an intermediate variable. + /// + /// [`read_to_end`]: core2::io::Read::read_to_end + /// + /// # Errors + /// + /// This function will return an error if `path` does not already exist. + /// Other errors may also be returned according to [`OpenOptions::open`]. + /// + /// It will also return an error if it encounters while reading an error + /// of a kind other than [`io::ErrorKind::Interrupted`]. + #[allow(unused)] + pub fn read>(&self, path: P) -> io::Result> { + let mut bytes = Vec::new(); + self.open(path)?.read_to_end(&mut bytes)?; + Ok(bytes) + } + + #[allow(unused)] + pub fn metadata>(&self, path: P) -> io::Result { + Inode::read(self, self.path_inode(path)?).map(|x| Metadata::from_inode(&x)) + } +} diff --git a/src/ext2/block_group_table.rs b/src/ext2/block_group_table.rs new file mode 100644 index 0000000..6482f95 --- /dev/null +++ b/src/ext2/block_group_table.rs @@ -0,0 +1,83 @@ +use super::{ + block_reader::BlockReader, + structs::{BlockGroupDescriptor, Inode, Superblock}, +}; +use binread::prelude::*; +use std::vec::Vec; +use std::{dbg, io}; + +const INODE_SIZE: usize = 256; + +#[derive(Debug, BinRead)] +#[br(import(len: usize, block_group_inode_count: u32, block_size: usize))] +pub struct BlockGroupDescriptorTable { + #[br(count = len)] + table: Vec, + #[br(calc = block_group_inode_count)] + block_group_inode_count: u32, + #[br(calc = block_size)] + block_size: usize, +} + +impl BlockGroupDescriptorTable { + pub fn new(superblock: &Superblock, reader: &BlockReader) -> io::Result { + let num_block_groups = superblock + .total_blocks + .div_ceil(superblock.block_group_block_count) as usize; + let table_blocks = num_block_groups.div_ceil(reader.block_size / 32); + Ok(reader + .read_blocks(superblock.superblock_block + 1, table_blocks)? + .read_le_args(( + num_block_groups, + superblock.block_group_inode_count, + 1024 << superblock.block_size_raw, + )) + .expect("Parsing the block group descriptor table should never fail")) + } + + fn inode_group(&self, inode: u32) -> &BlockGroupDescriptor { + &self.table[((inode - 1) / self.block_group_inode_count) as usize] + } + + fn inode_group_offset(&self, inode: u32) -> usize { + ((inode - 1) % self.block_group_inode_count) as usize + } + + fn inode_block(&self, inode: u32) -> u32 { + (((self.inode_group_offset(inode) * INODE_SIZE) / self.block_size) as u32) + + self.inode_group(inode).inode_table_start + } + + fn inode_block_byte_start(&self, inode: u32) -> usize { + (self.inode_group_offset(inode) * INODE_SIZE) % self.block_size + } + + pub fn inode_loc(&self, inode: u32) -> InodeLoc { + // dbg!(self); + InodeLoc { + block: self.inode_block(inode), + byte_offset: self.inode_block_byte_start(inode), + num: inode, + } + } +} + +#[derive(Debug)] +pub struct InodeLoc { + block: u32, + byte_offset: usize, + num: u32, +} + +impl InodeLoc { + pub fn read(&self, reader: &BlockReader) -> io::Result { + // dbg!(self.num); + reader + .read_block_offset(self.block, self.byte_offset as u32)? + .read_le_args((self.num,)) + .map_err(|err| match err { + binread::Error::Io(_) => io::Error::new(io::ErrorKind::Other, "Binread IO error"), + _ => io::Error::new(io::ErrorKind::InvalidData, "Inode data was invalid"), + }) + } +} diff --git a/src/ext2/block_reader.rs b/src/ext2/block_reader.rs new file mode 100644 index 0000000..ddbd1b4 --- /dev/null +++ b/src/ext2/block_reader.rs @@ -0,0 +1,54 @@ +use std::dbg; +use std::vec::Vec; + +use super::structs::Superblock; +use std::fs::File as StdFile; +use std::io::{self, Cursor}; + +#[derive(Debug)] +pub struct BlockReader { + pub disk: StdFile, + pub block_size: usize, +} + +impl BlockReader { + pub fn new(disk: StdFile, superblock: &Superblock) -> Self { + Self { + disk, + block_size: 1024 << superblock.block_size_raw, + } + } + + pub fn read_block(&self, block: u32) -> io::Result>> { + self.read_blocks_offset(block, 0, 1) + } + + pub fn read_block_offset(&self, block: u32, offset: u32) -> io::Result>> { + self.read_blocks_offset(block, offset, 1) + } + + pub fn read_blocks(&self, block: u32, count: usize) -> io::Result>> { + self.read_blocks_offset(block, 0, count) + } + + pub fn read_blocks_offset( + &self, + block: u32, + offset: u32, + count: usize, + ) -> io::Result>> { + // dbg!(block, offset, count, self.block_size); + let start = self.block_to_byte_offset(block) + u64::from(offset); + let size = (self.block_size * count) - (offset as usize); + // dbg!(start, size); + let mut vec = Vec::new(); + vec.resize(size, 0); + self.disk.read_at(&mut vec, start)?; + // dbg!(&vec[0..16]); + Ok(Cursor::new(vec)) + } + + fn block_to_byte_offset(&self, block: u32) -> u64 { + u64::from(block) * (self.block_size as u64) + } +} diff --git a/src/ext2/dir.rs b/src/ext2/dir.rs new file mode 100644 index 0000000..403f6cd --- /dev/null +++ b/src/ext2/dir.rs @@ -0,0 +1,180 @@ +use super::{ + file::File, + metadata::{FileType, Metadata}, + structs::{DirEntryDisk, Inode}, + Ext2, +}; +use binread::BinReaderExt; +use std::io; +use std::path::PathBuf; +use std::string::String; + +/// Entries returned by the [`ReadDir`] iterator. +/// +/// An instance of `DirEntry` represents an entry inside of a directory on the +/// filesystem. Each entry can be inspected via methods to learn about the full +/// path or possibly other metadata through per-platform extension traits. +#[allow(unused)] +#[allow(clippy::module_name_repetitions)] +pub struct DirEntry<'a> { + pub(super) inode: u32, + pub(super) name: String, + pub(super) metadata: Metadata, + pub(super) directory_path: Option, + fs: &'a Ext2, +} + +impl<'a> core::fmt::Debug for DirEntry<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("DirEntry") + .field("inode", &self.inode) + .field("name", &self.name) + .field("metadata", &self.metadata) + .field("directory_path", &self.directory_path) + .finish() + } +} + +#[allow(unused)] +impl<'a> DirEntry<'a> { + /// Returns the bare file name of this directory entry without any other + /// leading path component. + pub fn file_name(&self) -> String { + self.name.clone() + } + + /// Returns the file type for the file that this entry points at. + /// + /// This function will not traverse symlinks if this entry points at a + /// symlink. + pub fn file_type(&self) -> FileType { + self.metadata.file_type() + } + + /// Returns the metadata for the file that this entry points at. + /// + /// This function will not traverse symlinks if this entry points at a + /// symlink. + pub fn metadata(&self) -> Metadata { + self.metadata + } + + /// Returns the full path to the file that this entry represents. + /// + /// The full path is created by joining the original path to `read_dir` + /// with the filename of this entry. + /// + /// # Panics + /// + /// This function will panic if this entry came either directly from a `ReadDir` made with + /// `Ext2::read_dir_no_path`, or indirectly from such a `ReadDir` by use of the + /// `DirEntry::read_dir` function. This is an explicit decision, so such a panic indicates a + /// definite bug in your code. + pub fn path(&self) -> PathBuf { + self.try_path().expect("Path was None") + } + + /// Returns the full path to the file that this entry represents. + /// + /// The full path is created by joining the original path to `read_dir` + /// with the filename of this entry. + pub fn try_path(&self) -> Option { + self.directory_path + .as_ref() + .map(|x| x.join(self.name.clone())) + } + + pub fn open(&self) -> io::Result { + File::from_inode(Inode::read(self.fs, self.inode)?, self.fs) + } + + pub fn read_dir(&self) -> io::Result> { + ReadDir::read_inode(self.fs, self.inode, self.try_path()) + } +} + +/// Iterator over the entries in a directory. +/// +/// This iterator is returned from the [`super::Ext2::read_dir`] function and +/// will yield instances of [`io::Result`]<[`DirEntry`]>. Through a [`DirEntry`] +/// information like the entry's path and possibly other metadata can be +/// learned. +/// +/// The order in which this iterator returns entries is platform and filesystem +/// dependent. +/// +/// # Errors +/// +/// This [`io::Result`] will be an `Err` if there's some sort of intermittent +/// IO error during iteration. +#[derive(Debug)] +#[allow(clippy::module_name_repetitions)] +pub struct ReadDir<'a> { + fs: &'a Ext2, + file: File<'a>, + directory_path: Option, +} + +impl<'a> ReadDir<'a> { + pub fn read_inode(fs: &'a Ext2, inode: u32, path: Option) -> io::Result> { + Ok(Self { + fs, + file: File::from_inode_dir(Inode::read(fs, inode)?, fs)?, + directory_path: path, + }) + } + + pub(super) fn get_entry(mut self, name: &str) -> io::Result> { + self.find(|entry| { + if let Ok(entry) = entry { + entry.name == name + } else { + true + } + }) + .unwrap_or_else(|| Err(io::Error::new(io::ErrorKind::NotFound, "File not found"))) + } + + fn next_impl(&mut self) -> io::Result>> { + let entry: DirEntryDisk = match self.file.read_le() { + Ok(entry) => entry, + Err(binread::Error::Io(io_error)) => { + return if io_error.kind() == io::ErrorKind::UnexpectedEof { + Ok(None) + } else { + Err(io_error) + } + } + Err(_) => { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "Directory entry data was invalid", + )) + } + }; + let name = String::from_utf8(entry.name).map_err(|_| { + io::Error::new( + io::ErrorKind::InvalidData, + "Non UTF-8 directory entry names are unspported", + ) + })?; + if name == "." || name == ".." || entry.inode == 0 { + return self.next_impl(); + } + let entry_inode = Inode::read(self.fs, entry.inode)?; + Ok(Some(DirEntry { + inode: entry.inode, + name, + metadata: Metadata::from_inode(&entry_inode), + directory_path: self.directory_path.clone(), + fs: self.fs, + })) + } +} + +impl<'a> Iterator for ReadDir<'a> { + type Item = io::Result>; + fn next(&mut self) -> Option { + self.next_impl().transpose() + } +} diff --git a/src/ext2/file.rs b/src/ext2/file.rs new file mode 100644 index 0000000..e9a4269 --- /dev/null +++ b/src/ext2/file.rs @@ -0,0 +1,133 @@ +use super::Ext2; +use super::{structs::Inode, Metadata}; +use core::{cmp::Ordering, str}; +use std::io::{self, Read}; +use std::string::String; + +/// A reference to an open file on the filesystem. +/// +/// An instance of a `File` can be read and/or written depending on what options +/// it was opened with. Files also implement [`Seek`] to alter the logical cursor +/// that the file contains internally. +/// +/// Files are automatically closed when they go out of scope. Errors detected +/// on closing are ignored by the implementation of `Drop`. +#[derive(Debug)] +pub struct File<'a> { + pos: u64, + inode: Inode, + fs: &'a Ext2, + block_size: usize, +} + +impl<'a> File<'a> { + pub(super) fn from_inode(inode: Inode, fs: &'a Ext2) -> io::Result { + // if !Metadata::from_inode(&inode).file_type().is_file() { + // return Err(io::Error::new(io::ErrorKind::Other, "Not a file")); + // } + Ok(Self { + pos: 0, + block_size: fs.reader.block_size, + fs, + inode, + }) + } + + /// This version of `from_inode` skips checks that the provided inode is a directory, not a file. It + /// should, only be used by `ReadDir::read_inode` to read the raw directory data. + pub(super) fn from_inode_dir(inode: Inode, fs: &'a Ext2) -> io::Result { + if !Metadata::from_inode(&inode).file_type().is_dir() { + return Err(io::Error::new(io::ErrorKind::Other, "Not a directory")); + } + Ok(Self { + pos: 0, + block_size: fs.reader.block_size, + fs, + inode, + }) + } + + /// Queries metadata about the underlying file. + #[allow(unused)] + pub fn metadata(&self) -> Metadata { + Metadata::from_inode(&self.inode) + } + + pub fn read_at(&mut self, buf: &mut [u8], pos: u64) -> io::Result { + let bytes_to_end = u64::from(self.inode.size_lower32).saturating_sub(pos); + if bytes_to_end == 0 { + return Ok(0); + } + let start_block = (pos / self.block_size as u64) as u32; + let offset = (pos % self.block_size as u64) as usize; + let length = core::cmp::min(bytes_to_end, buf.len() as u64); + self.fs + .reader + .read_block_offset( + self.inode + .log_to_phys_block(start_block) + .ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, "EOF reached"))?, + offset as u32, + )? + .read(buf)?; + let mut remaining_bytes = length.saturating_sub((self.block_size - offset) as u64); + for block in (start_block + 1)..=u32::MAX { + if remaining_bytes == 0 { + break; + }; + self.fs + .reader + .read_block( + self.inode.log_to_phys_block(block).ok_or_else(|| { + io::Error::new(io::ErrorKind::UnexpectedEof, "EOF reached") + })?, + )? + .read(buf)?; + remaining_bytes = remaining_bytes.saturating_sub(self.block_size as u64); + } + Ok((length - remaining_bytes) as usize) + } + + pub fn read_to_string(&mut self, buf: &mut String) -> io::Result { + unsafe { + let buf = buf.as_mut_vec(); + let ret = self.read_to_end(buf)?; + str::from_utf8(buf).map_err(|_| { + io::Error::new( + io::ErrorKind::InvalidData, + "stream did not contain valid UTF-8", + ) + })?; + Ok(ret) + } + } +} + +impl io::Seek for File<'_> { + fn seek(&mut self, pos: io::SeekFrom) -> io::Result { + match pos { + io::SeekFrom::Start(x) => self.pos = x, + io::SeekFrom::End(x) => match x.cmp(&0) { + Ordering::Equal => self.pos = u64::from(self.inode.size_lower32), + Ordering::Greater => unimplemented!(), + Ordering::Less => self.pos = u64::from(self.inode.size_lower32) - x.unsigned_abs(), + }, + io::SeekFrom::Current(x) => match x.cmp(&0) { + Ordering::Equal => (), + Ordering::Greater => self.pos += x.unsigned_abs(), + Ordering::Less => self.pos -= x.unsigned_abs(), + }, + }; + Ok(self.pos) + } +} + +impl io::Read for File<'_> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let res = self.read_at(buf, self.pos); + if let Ok(bytes_read) = res { + self.pos += bytes_read as u64; + } + res + } +} diff --git a/src/ext2/metadata.rs b/src/ext2/metadata.rs new file mode 100644 index 0000000..f52cc63 --- /dev/null +++ b/src/ext2/metadata.rs @@ -0,0 +1,252 @@ +#![allow(unused)] +use super::structs::Inode; + +#[derive(Copy, Clone, Debug)] +pub struct Permissions(u32); + +impl Permissions { + /// Returns `true` if these permissions describe a readonly (unwritable) file. + pub fn readonly(self) -> bool { + // check if any class (owner, group, others) has write permission + self.0 & 0o222 == 0 + } + + /// Modifies the readonly flag for this set of permissions. If the + /// `readonly` argument is `true`, using the resulting `Permission` will + /// update file permissions to forbid writing. Conversely, if it's `false`, + /// using the resulting `Permission` will update file permissions to allow + /// writing. + /// + /// This operation does **not** modify the filesystem. + pub fn set_readonly(mut self, readonly: bool) { + if readonly { + // remove write permission for all classes; equivalent to `chmod a-w ` + self.0 &= !0o222; + } else { + // add write permission for all classes; equivalent to `chmod a+w ` + self.0 |= 0o222; + } + } + + /// Returns the underlying raw `st_mode` bits that contain the standard + /// Unix permissions for this file. + pub fn mode(self) -> u32 { + self.0 as u32 + } + + /// Sets the underlying raw bits for this set of permissions. + pub fn set_mode(mut self, mode: u32) { + self.0 = mode; + } + + /// Creates a new instance of `Permissions` from the given set of Unix + /// permission bits. + pub fn from_mode(mode: u32) -> Self { + Self(mode) + } +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub enum FileType { + Fifo, + CharDev, + Directory, + BlockDev, + File, + SymLink, + Socket, +} + +impl FileType { + /// Tests whether this file type represents a directory. The + /// result is mutually exclusive to the results of + /// [`is_file`] and [`is_symlink`]; only zero or one of these + /// tests may pass. + pub fn is_dir(self) -> bool { + self == Self::Directory + } + + /// Tests whether this file type represents a regular file. + /// The result is mutually exclusive to the results of + /// [`is_dir`] and [`is_symlink`]; only zero or one of these + /// tests may pass. + pub fn is_file(self) -> bool { + self == Self::File + } + + /// Tests whether this file type represents a symbolic link. + /// The result is mutually exclusive to the results of + /// [`is_dir`] and [`is_file`]; only zero or one of these + /// tests may pass. + pub fn is_symlink(self) -> bool { + self == Self::SymLink + } + + /// Returns `true` if this file type is a fifo. + pub fn is_fifo(self) -> bool { + self == Self::Fifo + } + + /// Returns `true` if this file type is a block device. + pub fn is_block_device(self) -> bool { + self == Self::BlockDev + } + + /// Returns `true` if this file type is a char device. + pub fn is_char_device(self) -> bool { + self == Self::CharDev + } + + /// Returns `true` if this file type is a socket. + pub fn is_socket(self) -> bool { + self == Self::Socket + } +} + +#[derive(Copy, Clone, Debug)] +pub struct Metadata { + accessed: u32, + created: u32, + file_type: FileType, + length: u64, + modified: u32, + permissions: Permissions, + ino: u64, + nlink: u64, + uid: u32, + gid: u32, + // blksize: u64, + blocks: u64, +} + +impl Metadata { + pub(super) fn from_inode(inode: &Inode) -> Self { + let file_type = match (inode.type_perms & 0xF000) >> 12 { + 1 => FileType::Fifo, + 2 => FileType::CharDev, + 4 => FileType::Directory, + 6 => FileType::BlockDev, + 8 => FileType::File, + 0xA => FileType::SymLink, + 0xC => FileType::Socket, + x => panic!("Invalid inode file type {}", x), + }; + let permissions = Permissions(u32::from(inode.type_perms & 0xFFF)); + Self { + accessed: inode.last_access_time, + created: inode.creation_time, + file_type, + length: u64::from(inode.size_lower32), + modified: inode.last_modification_time, + permissions, + ino: u64::from(inode.number), + nlink: u64::from(inode.hard_links_count), + uid: u32::from(inode.uid), + gid: u32::from(inode.gid), + // blksize: inode.reader.block_size as u64, + blocks: u64::from(inode.sectors_used), + } + } + + /// Returns the last access time of this metadata. + /// + /// The returned value corresponds to the `atime` field of `stat` + pub fn accessed(&self) -> u32 { + self.accessed + } + + /// Returns the creation time listed in this metadata. + /// + /// The returned value corresponds to the `btime` field of `statx` on + /// Linux kernel starting from to 4.11, and the `birthtime` field of `stat` on + /// other Unix platforms. + pub fn created(&self) -> u32 { + self.created + } + + /// Returns the last modification time listed in this metadata. + /// + /// The returned value corresponds to the `mtime` field of `stat` + pub fn modified(&self) -> u32 { + self.modified + } + + /// Returns the file type for this metadata. + pub fn file_type(&self) -> FileType { + self.file_type + } + + /// Returns `true` if this metadata is for a directory. The + /// result is mutually exclusive to the result of + /// [`Metadata::is_file`], and will be false for symlink metadata + /// obtained from [`symlink_metadata`]. + pub fn is_dir(&self) -> bool { + self.file_type.is_dir() + } + + /// Returns `true` if this metadata is for a regular file. The + /// result is mutually exclusive to the result of + /// [`Metadata::is_dir`], and will be false for symlink metadata + /// obtained from [`symlink_metadata`]. + /// + /// When the goal is simply to read from (or write to) the source, the most0.227s + /// reliable way to test the source can be read (or written to) is to open + /// it. Only using `is_file` can break workflows like `diff <( prog_a )` on + /// a Unix-like system for example. See [`File::open`] or + /// [`OpenOptions::open`] for more information. + pub fn is_file(&self) -> bool { + self.file_type.is_file() + } + + /// Returns `true` if this metadata is for a symbolic link. + pub fn is_symlink(&self) -> bool { + self.file_type.is_symlink() + } + + /// Returns the size of the file, in bytes, this metadata is for. + pub fn len(&self) -> u64 { + self.length + } + + /// Returns the permissions of the file this metadata is for. + pub fn permissions(&self) -> Permissions { + self.permissions + } + + /// Returns the inode number. + pub fn ino(&self) -> u64 { + self.ino + } + + /// Returns the rights applied to this file. + pub fn mode(&self) -> u32 { + self.permissions.0 + } + + /// Returns the number of hard links pointing to this file. + pub fn nlink(&self) -> u64 { + self.nlink + } + + /// Returns the user ID of the owner of this file. + pub fn uid(&self) -> u32 { + self.uid + } + + /// Returns the group ID of the owner of this file. + pub fn gid(&self) -> u32 { + self.gid + } + + // /// Returns the block size for filesystem I/O. + // pub fn blksize(&self) -> u64 { + // self.blksize + // } + + /// Returns the number of blocks allocated to the file, in 512-byte units. + /// + /// Please note that this may be smaller than `st_size / 512` when the file has holes. + pub fn blocks(&self) -> u64 { + self.blocks + } +} diff --git a/src/ext2/structs.rs b/src/ext2/structs.rs new file mode 100644 index 0000000..becc2f8 --- /dev/null +++ b/src/ext2/structs.rs @@ -0,0 +1,159 @@ +use super::{block_reader::BlockReader, Ext2}; +use binread::prelude::*; +use itertools::Itertools; +use std::dbg; +use std::io::{self, Read, Seek, SeekFrom}; +use std::vec::Vec; + +use std::fs::File as StdFile; + +#[repr(u16)] +#[derive(Debug, BinRead)] +#[br(repr=u16)] +pub enum FSState { + Clean = 1, + HasErrors = 2, +} + +#[repr(u16)] +#[derive(Debug, BinRead)] +#[br(repr=u16)] +pub enum ErrorHandlingMethod { + Ignore = 1, + RemountRO = 2, + Panic = 3, +} + +#[derive(Debug, BinRead)] +#[allow(unused)] +pub struct Superblock { + pub total_inodes: u32, + pub total_blocks: u32, + pub superuser_blocks: u32, + pub unallocated_blocks: u32, + pub unallocated_inodes: u32, + pub superblock_block: u32, + pub block_size_raw: u32, + pub fragment_size_raw: u32, + pub block_group_block_count: u32, + pub block_group_fragment_count: u32, + pub block_group_inode_count: u32, + pub last_mount_time: u32, + pub last_written_time: u32, + pub times_mounted_since_last_check: u16, + pub max_mounts_before_check: u16, + pub ext2_sig: u16, + pub fs_state: FSState, + pub error_handling_method: ErrorHandlingMethod, + pub version_minor: u16, + pub last_check_time: u32, + pub max_interval_between_check: u32, + pub creation_os_id: u32, + pub version_major: u32, + pub reserved_uid: u16, + pub reserved_gid: u16, +} + +impl Superblock { + pub fn read_from_disk(disk: &mut StdFile) -> io::Result { + disk.seek(SeekFrom::Start(1024))?; + Ok(disk.read_le().unwrap()) + } +} + +#[derive(Debug, Clone, BinRead)] +#[allow(unused)] +pub struct BlockGroupDescriptor { + pub block_usage_block: u32, + pub inode_usage_block: u32, + pub inode_table_start: u32, + pub unallocated_blocks: u16, + pub unallocated_inodes: u16, + pub num_directories: u16, + pub reserved: [u8; 14], +} + +#[derive(Debug, BinRead)] +#[allow(unused)] +pub struct DirEntryDisk { + pub inode: u32, + pub entry_size: u16, + pub name_len: u8, + pub high_len_or_type: u8, + #[br(count = name_len)] + #[br(pad_after(i64::from(entry_size - u16::from(name_len) - 8)))] + pub name: Vec, +} + +#[derive(Debug, Clone, BinRead)] +#[br(import(number: u32))] +#[allow(unused)] +pub struct Inode { + pub type_perms: u16, + pub uid: u16, + pub size_lower32: u32, + pub last_access_time: u32, + pub creation_time: u32, + pub last_modification_time: u32, + pub deletion_time: u32, + pub gid: u16, + pub hard_links_count: u16, + pub sectors_used: u32, + pub flags: u32, + pub os_specific_1: u32, + pub direct_block_pointers: [u32; 12], + pub singly_indirect_block_pointer: u32, + pub doubly_indirect_block_pointer: u32, + pub triply_indirect_block_pointer: u32, + pub generation_number: u32, + pub file_acl: u32, + pub file_size_upper32_dir_acl: u32, + pub fragment_block_address: u32, + pub os_specific_2: [u8; 12], + #[br(calc = number)] + pub number: u32, +} + +impl Inode { + pub(super) fn read(fs: &Ext2, inode_num: u32) -> io::Result { + // dbg!(); + let ino = fs.descriptor_table.inode_loc(inode_num).read(&fs.reader); + // dbg!(); + ino + } + + #[allow(unused)] + pub fn pointer_block_blocks( + &self, + block: u32, + reader: &mut BlockReader, + ) -> io::Result>> { + Ok(reader + .read_block(block)? + .bytes() + .tuples::<(_, _, _, _)>() + .map(|raw| { + if let (Ok(b1), Ok(b2), Ok(b3), Ok(b4)) = raw { + Ok(u32::from_le_bytes([b1, b2, b3, b4])) + } else { + Err([raw.0, raw.1, raw.2, raw.3] + .into_iter() + .find_map(Result::err) + .expect("Not all bytes were Ok, yet all are not Err")) + } + }) + .filter(|x| if let &Ok(x) = x { x != 0 } else { true })) + } + + pub fn log_to_phys_block(&self, block: u32) -> Option { + if block < 12 { + if self.direct_block_pointers[block as usize] == 0 { + None + } else { + Some(self.direct_block_pointers[block as usize]) + } + } else { + unimplemented!() + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9f53c14 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,55 @@ +#![no_std] +#![no_main] +#![feature(int_roundings)] +#![deny(unsafe_op_in_unsafe_fn)] + +use ext2::Ext2; +use std::fmt::Debug; +use std::fs::File; +use std::loader::{Binary, Loader}; +use std::path::Path; +use std::prelude::*; +use std::syscalls::{get_initrd, new_process}; +use tar_no_std::TarArchiveRef; + +mod ata; +mod ext2; + +main!({ + // let primary_slave = PRIMARY_SLAVE.unwrap(); + let initrd = TarArchiveRef::new(get_initrd()); + let test_proc = Binary::new( + initrd + .entries() + .find(|entry| entry.filename() == *"bin/test_proc") + .expect("test_proc not found") + .data(), + ) + .expect("test_proc not an ELF binary"); + let space = Loader::load(&test_proc); + let pid = new_process(test_proc.entry_point(), space).expect("Failed to create process"); + let client = dev_driver_rpc::Client::new(pid); + let fd = client.open("sdb").unwrap(); + let sdb = File::from_pid_fd(pid, fd); + let fs = Ext2::new(sdb).unwrap(); + // dbg!(); + print_all_files("/", &fs); +}); + +fn print_all_files + Debug>(path: P, fs: &Ext2) { + // dbg!(); + for entry in fs.read_dir(path).unwrap() { + // dbg!(); + let entry = entry.unwrap(); + if entry.metadata().file_type().is_dir() { + print_all_files(entry.path(), fs); + } else { + // dbg!(); + let mut file = fs.open(entry.path()).unwrap(); + // dbg!(); + let mut buf = String::new(); + file.read_to_string(&mut buf).unwrap(); + print!("{:?}:\n{}", entry.path(), buf); + } + } +} diff --git a/x86_64-unknown-none.json b/x86_64-unknown-none.json new file mode 100644 index 0000000..74696c7 --- /dev/null +++ b/x86_64-unknown-none.json @@ -0,0 +1,14 @@ +{ + "llvm-target": "x86_64-unknown-none", + "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", + "arch": "x86_64", + "target-endian": "little", + "target-pointer-width": "64", + "target-c-int-width": "32", + "os": "none", + "executables": true, + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "panic-strategy": "abort", + "features": "-mmx,-sse,+soft-float" +}