diff --git a/Cargo.lock b/Cargo.lock index eb4d3dd..1cf773c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -36,8 +36,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6419a5c75e40011b9fe0174db3fe24006ab122fbe1b7e9cc5974b338a755c76" [[package]] -name = "error" -version = "0.1.0" +name = "enum-repr" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad30c9c0fa1aaf1ae5010dab11f1117b15d35faf62cda4bbbc53b9987950f18" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "fallible-iterator" @@ -49,8 +56,8 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" name = "fat32" version = "0.1.0" dependencies = [ - "error", - "libcommon", + "fs-macros", + "libsys", "vfs", ] @@ -62,7 +69,7 @@ checksum = "99a40cabc11c8258822a593f5c51f2d9f4923e715ca9e2a0630cf77ae15f390b" dependencies = [ "endian-type-rs", "fallible-iterator", - "memoffset", + "memoffset 0.5.6", "num-derive", "num-traits", "rustc_version", @@ -70,6 +77,14 @@ dependencies = [ "unsafe_unwrap", ] +[[package]] +name = "fs-macros" +version = "0.1.0" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "kernel" version = "0.1.0" @@ -77,35 +92,54 @@ dependencies = [ "bitflags", "cfg-if", "cortex-a", - "error", "fdt-rs", - "libcommon", + "kernel-macros", + "libsys", "memfs", - "syscall", "tock-registers", "vfs", ] [[package]] -name = "libcommon" +name = "kernel-macros" version = "0.1.0" dependencies = [ - "error", + "quote", + "syn", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] + +[[package]] +name = "libsys" +version = "0.1.0" +dependencies = [ + "bitflags", + "enum-repr", ] [[package]] name = "libusr" version = "0.1.0" dependencies = [ - "syscall", + "lazy_static", + "libsys", + "memoffset 0.6.4", ] [[package]] name = "memfs" version = "0.1.0" dependencies = [ - "error", - "libcommon", + "fs-macros", + "libsys", "vfs", ] @@ -118,6 +152,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -144,9 +187,9 @@ version = "0.1.0" [[package]] name = "proc-macro2" -version = "1.0.29" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" dependencies = [ "unicode-xid", ] @@ -184,6 +227,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "static_assertions" version = "1.1.0" @@ -192,23 +241,15 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" +checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] -[[package]] -name = "syscall" -version = "0.1.0" -dependencies = [ - "bitflags", - "error", -] - [[package]] name = "tock-registers" version = "0.7.0" @@ -231,6 +272,8 @@ checksum = "1230ec65f13e0f9b28d789da20d2d419511893ea9dac2c1f4ef67b8b14e5da80" name = "user" version = "0.1.0" dependencies = [ + "lazy_static", + "libsys", "libusr", ] @@ -238,7 +281,6 @@ dependencies = [ name = "vfs" version = "0.1.0" dependencies = [ - "error", - "libcommon", - "syscall", + "fs-macros", + "libsys", ] diff --git a/Cargo.toml b/Cargo.toml index 183dc28..94aff48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,13 +9,13 @@ edition = "2018" [workspace] members = [ - "fs/vfs", - "fs/memfs", "fs/fat32", - "syscall", - "libcommon", + "fs/macros", + "fs/memfs", + "fs/vfs", "kernel", + "kernel/macros", + "libsys", "libusr", "user", - "error" ] diff --git a/Makefile b/Makefile index 4c58fcd..6739298 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,9 @@ endif CARGO_BUILD_OPTS=$(CARGO_COMMON_OPTS) \ --target=../etc/$(ARCH)-$(MACH).json +ifeq ($(VERBOSE),1) +CARGO_BUILD_OPTS+=--features verbose +endif ifneq ($(MACH),) CARGO_BUILD_OPTS+=--features mach_$(MACH) endif @@ -90,9 +93,15 @@ initrd: --target=../etc/$(ARCH)-osdev5.json \ -Z build-std=core,alloc,compiler_builtins \ $(CARGO_COMMON_OPTS) - mkdir -p $(O)/rootfs/bin + mkdir -p $(O)/rootfs/bin $(O)/rootfs/sbin $(O)/rootfs/dev + touch $(O)/rootfs/dev/.do_no_remove cp target/$(ARCH)-osdev5/$(PROFILE)/init $(O)/rootfs/init cp target/$(ARCH)-osdev5/$(PROFILE)/shell $(O)/rootfs/bin + cp target/$(ARCH)-osdev5/$(PROFILE)/fuzzy $(O)/rootfs/bin + cp target/$(ARCH)-osdev5/$(PROFILE)/ls $(O)/rootfs/bin + cp target/$(ARCH)-osdev5/$(PROFILE)/cat $(O)/rootfs/bin + cp target/$(ARCH)-osdev5/$(PROFILE)/hexd $(O)/rootfs/bin + cp target/$(ARCH)-osdev5/$(PROFILE)/login $(O)/rootfs/sbin cd $(O)/rootfs && tar cf ../initrd.img `find -type f -printf "%P\n"` ifeq ($(MACH),orangepi3) $(MKIMAGE) \ @@ -122,7 +131,7 @@ doc-open: clippy: cd kernel && cargo clippy $(CARGO_BUILD_OPTS) - cd init && cargo clippy \ + cd user && cargo clippy \ --target=../etc/$(ARCH)-osdev5.json \ -Zbuild-std=core,alloc,compiler_builtins $(CARGO_COMMON_OPTS) diff --git a/error/src/lib.rs b/error/src/lib.rs deleted file mode 100644 index 69a0188..0000000 --- a/error/src/lib.rs +++ /dev/null @@ -1,23 +0,0 @@ -#![no_std] - -#[derive(PartialEq, Debug, Clone, Copy)] -pub enum Errno { - AlreadyExists, - BadExecutable, - Busy, - DeviceError, - DoesNotExist, - EndOfFile, - Interrupt, - InvalidArgument, - InvalidFile, - InvalidOperation, - IsADirectory, - NotADirectory, - NotImplemented, - OutOfMemory, - ReadOnly, - TimedOut, - TooManyDescriptors, - WouldBlock, -} diff --git a/fs/fat32/Cargo.toml b/fs/fat32/Cargo.toml index cee876c..7938472 100644 --- a/fs/fat32/Cargo.toml +++ b/fs/fat32/Cargo.toml @@ -7,5 +7,5 @@ edition = "2021" [dependencies] vfs = { path = "../vfs" } -libcommon = { path = "../../libcommon" } -error = { path = "../../error" } +fs-macros = { path = "../macros" } +libsys = { path = "../../libsys" } diff --git a/fs/fat32/src/data.rs b/fs/fat32/src/data.rs index 469ac83..19df264 100644 --- a/fs/fat32/src/data.rs +++ b/fs/fat32/src/data.rs @@ -1,4 +1,4 @@ -use libcommon::{read_le16, read_le32}; +use libsys::mem::{read_le16, read_le32}; #[derive(Debug)] pub struct Bpb { diff --git a/fs/fat32/src/dir.rs b/fs/fat32/src/dir.rs index 4966d06..86c7547 100644 --- a/fs/fat32/src/dir.rs +++ b/fs/fat32/src/dir.rs @@ -1,7 +1,11 @@ use crate::{Bpb, FileInode}; use alloc::{borrow::ToOwned, boxed::Box, string::String}; -use error::Errno; -use libcommon::{read_le16, read_le32}; +use libsys::{ + error::Errno, + ioctl::IoctlCmd, + mem::{read_le16, read_le32}, + stat::{OpenFlags, Stat}, +}; use vfs::{BlockDevice, Vnode, VnodeImpl, VnodeKind, VnodeRef}; pub struct DirectoryInode { @@ -26,20 +30,8 @@ pub struct Dirent { pub cluster: u32, } +#[auto_inode] impl VnodeImpl for DirectoryInode { - fn create( - &mut self, - _parent: VnodeRef, - _name: &str, - _kind: VnodeKind, - ) -> Result { - todo!() - } - - fn remove(&mut self, _parent: VnodeRef, _name: &str) -> Result<(), Errno> { - todo!() - } - fn lookup(&mut self, parent: VnodeRef, name: &str) -> Result { let fs = parent.fs().unwrap(); let dirent = { @@ -72,30 +64,6 @@ impl VnodeImpl for DirectoryInode { } Ok(vnode) } - - fn open(&mut self, _node: VnodeRef) -> Result { - todo!() - } - - fn close(&mut self, _node: VnodeRef) -> Result<(), Errno> { - todo!() - } - - fn read(&mut self, _node: VnodeRef, _pos: usize, _data: &mut [u8]) -> Result { - todo!() - } - - fn write(&mut self, _node: VnodeRef, _pos: usize, _data: &[u8]) -> Result { - todo!() - } - - fn truncate(&mut self, _node: VnodeRef, _size: usize) -> Result<(), Errno> { - todo!() - } - - fn size(&mut self, _node: VnodeRef) -> Result { - todo!() - } } impl Iterator for FatIterator<'_> { diff --git a/fs/fat32/src/file.rs b/fs/fat32/src/file.rs index 9d8a1f5..a0819e0 100644 --- a/fs/fat32/src/file.rs +++ b/fs/fat32/src/file.rs @@ -1,5 +1,9 @@ use crate::Bpb; -use error::Errno; +use libsys::{ + stat::{Stat, OpenFlags}, + ioctl::IoctlCmd, + error::Errno +}; use vfs::{VnodeImpl, VnodeKind, VnodeRef}; pub struct FileInode { @@ -7,27 +11,15 @@ pub struct FileInode { pub size: u32, } +#[auto_inode] impl VnodeImpl for FileInode { - fn create(&mut self, _at: VnodeRef, _name: &str, _kind: VnodeKind) -> Result { - panic!() - } - - fn remove(&mut self, _parent: VnodeRef, _name: &str) -> Result<(), Errno> { - panic!() - } - - fn lookup(&mut self, _parent: VnodeRef, _name: &str) -> Result { - panic!() - } - - fn open(&mut self, _node: VnodeRef) -> Result { + fn open(&mut self, _node: VnodeRef, flags: OpenFlags) -> Result { + if flags & OpenFlags::O_ACCESS != OpenFlags::O_RDONLY { + return Err(Errno::ReadOnly); + } Ok(0) } - fn close(&mut self, _node: VnodeRef) -> Result<(), Errno> { - todo!() - } - fn read(&mut self, node: VnodeRef, pos: usize, data: &mut [u8]) -> Result { let size = self.size as usize; if pos >= size { @@ -60,16 +52,4 @@ impl VnodeImpl for FileInode { Ok(off) } - - fn write(&mut self, _node: VnodeRef, _pos: usize, _data: &[u8]) -> Result { - todo!() - } - - fn truncate(&mut self, _node: VnodeRef, _size: usize) -> Result<(), Errno> { - todo!() - } - - fn size(&mut self, _node: VnodeRef) -> Result { - todo!() - } } diff --git a/fs/fat32/src/lib.rs b/fs/fat32/src/lib.rs index 27e31ce..7561394 100644 --- a/fs/fat32/src/lib.rs +++ b/fs/fat32/src/lib.rs @@ -4,13 +4,18 @@ #[macro_use] extern crate std; +#[macro_use] +extern crate fs_macros; + extern crate alloc; use alloc::{boxed::Box, rc::Rc}; use core::any::Any; use core::cell::{Ref, RefCell}; -use error::Errno; -use libcommon::read_le32; +use libsys::{ + mem::read_le32, + error::Errno, +}; use vfs::{BlockDevice, Filesystem, Vnode, VnodeKind, VnodeRef}; pub mod dir; diff --git a/error/Cargo.toml b/fs/macros/Cargo.toml similarity index 52% rename from error/Cargo.toml rename to fs/macros/Cargo.toml index 31b8188..f73f66c 100644 --- a/error/Cargo.toml +++ b/fs/macros/Cargo.toml @@ -1,8 +1,13 @@ [package] -name = "error" +name = "fs-macros" version = "0.1.0" -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +syn = { version = "^1.0.81", features = ["full"] } +quote = "^1.0.10" + +[lib] +proc-macro = true diff --git a/fs/macros/src/lib.rs b/fs/macros/src/lib.rs new file mode 100644 index 0000000..91d79e9 --- /dev/null +++ b/fs/macros/src/lib.rs @@ -0,0 +1,162 @@ +extern crate proc_macro; +extern crate syn; +#[macro_use] +extern crate quote; + +use proc_macro::TokenStream; +use quote::ToTokens; +use std::collections::HashSet; +use syn::{parse_macro_input, ImplItem, ItemImpl, Ident}; + +fn impl_inode_fn(name: &str, behavior: T) -> ImplItem { + // TODO somehow know if current crate is vfs or not? + ImplItem::Verbatim(match name { + "create" => quote! { + fn create(&mut self, _at: VnodeRef, _name: &str, kind: VnodeKind) -> + Result + { + #behavior + } + }, + "remove" => quote! { + fn remove(&mut self, _at: VnodeRef, _name: &str) -> Result<(), libsys::error::Errno> { + #behavior + } + }, + "lookup" => quote! { + fn lookup(&mut self, _at: VnodeRef, _name: &str) -> + Result + { + #behavior + } + }, + "stat" => quote! { + fn stat(&mut self, _at: VnodeRef) -> + Result + { + #behavior + } + }, + "truncate" => quote! { + fn truncate(&mut self, _node: VnodeRef, _size: usize) -> + Result<(), libsys::error::Errno> + { + #behavior + } + }, + "size" => quote! { + fn size(&mut self, _node: VnodeRef) -> Result { + #behavior + } + }, + "read" => quote! { + fn read(&mut self, _node: VnodeRef, _pos: usize, _data: &mut [u8]) -> + Result + { + #behavior + } + }, + "write" => quote! { + fn write(&mut self, _node: VnodeRef, _pos: usize, _data: &[u8]) -> + Result + { + #behavior + } + }, + "open" => quote! { + fn open(&mut self, _node: VnodeRef, _flags: libsys::stat::OpenFlags) -> + Result + { + #behavior + } + }, + "close" => quote! { + fn close(&mut self, _node: VnodeRef) -> Result<(), libsys::error::Errno> { + #behavior + } + }, + "ioctl" => quote! { + fn ioctl( + &mut self, + _node: VnodeRef, + _cmd: libsys::ioctl::IoctlCmd, + _ptr: usize, + _len: usize) -> + Result + { + #behavior + } + }, + "is_ready" => quote! { + fn is_ready(&mut self, _node: VnodeRef, _write: bool) -> + Result + { + #behavior + } + }, + "readdir" => quote! { + fn readdir( + &mut self, + _node: VnodeRef, + _pos: usize, + _entries: &mut [libsys::stat::DirectoryEntry] + ) -> + Result + { + #behavior + } + }, + _ => panic!("TODO implement {:?}", name), + }) +} + +#[proc_macro_attribute] +pub fn auto_inode(attr: TokenStream, input: TokenStream) -> TokenStream { + let mut impl_item = parse_macro_input!(input as ItemImpl); + let mut missing = HashSet::::new(); + let behavior = if attr.is_empty() { + "unimplemented".to_string() + } else { + parse_macro_input!(attr as Ident).to_string() + }; + let behavior = match behavior.as_str() { + "unimplemented" => quote! { unimplemented!() }, + "panic" => quote! { panic!() }, + "error" => quote! { Err(libsys::error::Errno::NotImplemented) }, + _ => panic!("Unknown #[auto_inode] behavior: {:?}", behavior) + }; + + missing.insert("create".to_string()); + missing.insert("remove".to_string()); + missing.insert("lookup".to_string()); + missing.insert("open".to_string()); + missing.insert("close".to_string()); + missing.insert("truncate".to_string()); + missing.insert("read".to_string()); + missing.insert("write".to_string()); + missing.insert("stat".to_string()); + missing.insert("size".to_string()); + missing.insert("ioctl".to_string()); + missing.insert("is_ready".to_string()); + missing.insert("readdir".to_string()); + + for item in &impl_item.items { + match item { + ImplItem::Method(method) => { + let name = &method.sig.ident.to_string(); + if missing.contains(name) { + missing.remove(name); + } + } + _ => panic!("Unexpected impl item"), + } + } + + for item in &missing { + impl_item + .items + .push(impl_inode_fn(item, behavior.clone())); + } + + impl_item.to_token_stream().into() +} diff --git a/fs/memfs/Cargo.toml b/fs/memfs/Cargo.toml index 2c7d297..cbb1357 100644 --- a/fs/memfs/Cargo.toml +++ b/fs/memfs/Cargo.toml @@ -7,8 +7,8 @@ edition = "2021" [dependencies] vfs = { path = "../vfs" } -error = { path = "../../error" } -libcommon = { path = "../../libcommon" } +fs-macros = { path = "../macros" } +libsys = { path = "../../libsys" } [features] cow = [] diff --git a/fs/memfs/src/block.rs b/fs/memfs/src/block.rs index e3c9289..64241c9 100644 --- a/fs/memfs/src/block.rs +++ b/fs/memfs/src/block.rs @@ -1,6 +1,6 @@ use core::mem::{size_of, MaybeUninit}; use core::ops::{Deref, DerefMut}; -use error::Errno; +use libsys::error::Errno; pub const SIZE: usize = 4096; pub const ENTRY_COUNT: usize = SIZE / size_of::(); diff --git a/fs/memfs/src/bvec.rs b/fs/memfs/src/bvec.rs index f75039f..030daaa 100644 --- a/fs/memfs/src/bvec.rs +++ b/fs/memfs/src/bvec.rs @@ -2,7 +2,7 @@ use crate::{block, BlockAllocator, BlockRef}; use core::cmp::min; use core::mem::MaybeUninit; use core::ops::{Index, IndexMut}; -use error::Errno; +use libsys::error::Errno; const L0_BLOCKS: usize = 32; // 128K const L1_BLOCKS: usize = 8; // 16M diff --git a/fs/memfs/src/dir.rs b/fs/memfs/src/dir.rs index 552a6b6..b87e1d6 100644 --- a/fs/memfs/src/dir.rs +++ b/fs/memfs/src/dir.rs @@ -1,12 +1,13 @@ use crate::{BlockAllocator, Bvec, FileInode}; use alloc::boxed::Box; -use error::Errno; -use vfs::{OpenFlags, Stat, Vnode, VnodeImpl, VnodeKind, VnodeRef, IoctlCmd}; +use libsys::{error::Errno, stat::Stat}; +use vfs::{Vnode, VnodeImpl, VnodeKind, VnodeRef}; pub struct DirInode { alloc: A, } +#[auto_inode] impl VnodeImpl for DirInode { fn create( &mut self, @@ -14,7 +15,7 @@ impl VnodeImpl for DirInode { name: &str, kind: VnodeKind, ) -> Result { - let vnode = Vnode::new(name, kind, Vnode::SEEKABLE); + let vnode = Vnode::new(name, kind, Vnode::SEEKABLE | Vnode::CACHE_READDIR); match kind { VnodeKind::Directory => vnode.set_data(Box::new(DirInode { alloc: self.alloc })), VnodeKind::Regular => vnode.set_data(Box::new(FileInode::new(Bvec::new(self.alloc)))), @@ -31,42 +32,13 @@ impl VnodeImpl for DirInode { Ok(()) } - fn open(&mut self, _node: VnodeRef, _flags: OpenFlags) -> Result { - todo!() - } - - fn close(&mut self, _node: VnodeRef) -> Result<(), Errno> { - todo!() - } - - fn read(&mut self, _node: VnodeRef, _pos: usize, _data: &mut [u8]) -> Result { - todo!() - } - - fn write(&mut self, _node: VnodeRef, _pos: usize, _data: &[u8]) -> Result { - todo!() - } - - fn truncate(&mut self, _node: VnodeRef, _size: usize) -> Result<(), Errno> { - todo!() - } - - fn size(&mut self, _node: VnodeRef) -> Result { - todo!() - } - - fn stat(&mut self, _node: VnodeRef, _stat: &mut Stat) -> Result<(), Errno> { - todo!(); - } - - fn ioctl( - &mut self, - node: VnodeRef, - cmd: IoctlCmd, - ptr: usize, - len: usize, - ) -> Result { - todo!() + fn stat(&mut self, node: VnodeRef) -> Result { + let props = node.props(); + Ok(Stat { + size: 0, + blksize: 4096, + mode: props.mode, + }) } } diff --git a/fs/memfs/src/file.rs b/fs/memfs/src/file.rs index 9c3a2b1..c3b8e8d 100644 --- a/fs/memfs/src/file.rs +++ b/fs/memfs/src/file.rs @@ -1,29 +1,16 @@ use crate::{BlockAllocator, Bvec}; -use error::Errno; -use vfs::{OpenFlags, Stat, VnodeImpl, VnodeKind, VnodeRef, IoctlCmd}; +use libsys::{ + error::Errno, + stat::{OpenFlags, Stat}, +}; +use vfs::{VnodeImpl, VnodeKind, VnodeRef}; pub struct FileInode<'a, A: BlockAllocator + Copy + 'static> { data: Bvec<'a, A>, } +#[auto_inode] impl<'a, A: BlockAllocator + Copy + 'static> VnodeImpl for FileInode<'a, A> { - fn create( - &mut self, - _parent: VnodeRef, - _name: &str, - _kind: VnodeKind, - ) -> Result { - panic!() - } - - fn lookup(&mut self, _parent: VnodeRef, _name: &str) -> Result { - panic!() - } - - fn remove(&mut self, _parent: VnodeRef, _name: &str) -> Result<(), Errno> { - panic!() - } - fn open(&mut self, _node: VnodeRef, _mode: OpenFlags) -> Result { Ok(0) } @@ -48,21 +35,13 @@ impl<'a, A: BlockAllocator + Copy + 'static> VnodeImpl for FileInode<'a, A> { Ok(self.data.size()) } - fn stat(&mut self, _node: VnodeRef, stat: &mut Stat) -> Result<(), Errno> { - stat.size = self.data.size() as u64; - stat.blksize = 4096; - stat.mode = 0o755; - Ok(()) - } - - fn ioctl( - &mut self, - node: VnodeRef, - cmd: IoctlCmd, - ptr: usize, - len: usize, - ) -> Result { - todo!() + fn stat(&mut self, node: VnodeRef) -> Result { + let props = node.props(); + Ok(Stat { + size: self.data.size() as u64, + blksize: 4096, + mode: props.mode + }) } } diff --git a/fs/memfs/src/lib.rs b/fs/memfs/src/lib.rs index 22ad694..7e9e83c 100644 --- a/fs/memfs/src/lib.rs +++ b/fs/memfs/src/lib.rs @@ -11,19 +11,25 @@ extern crate alloc; #[macro_use] extern crate std; +#[macro_use] +extern crate fs_macros; + use alloc::{boxed::Box, rc::Rc}; use core::any::Any; use core::cell::{Ref, RefCell}; -use error::Errno; -use libcommon::*; -use vfs::{BlockDevice, FileMode, Filesystem, Vnode, VnodeKind, VnodeRef}; +use libsys::{ + error::Errno, + path::{path_component_left, path_component_right}, + stat::FileMode, +}; +use vfs::{BlockDevice, Filesystem, Vnode, VnodeKind, VnodeRef}; mod block; pub use block::{BlockAllocator, BlockRef}; mod bvec; use bvec::Bvec; mod tar; -use tar::TarIterator; +use tar::{TarIterator, Tar}; mod file; use file::FileInode; mod dir; @@ -61,8 +67,10 @@ impl Ramfs { Ok(res) } - fn create_node_initial(self: Rc, name: &str, kind: VnodeKind) -> VnodeRef { - let node = Vnode::new(name, kind, Vnode::SEEKABLE); + fn create_node_initial(self: Rc, name: &str, tar: &Tar) -> VnodeRef { + let kind = tar.node_kind(); + let node = Vnode::new(name, kind, Vnode::SEEKABLE | Vnode::CACHE_READDIR); + node.props_mut().mode = tar.mode(); node.set_fs(self.clone()); match kind { VnodeKind::Directory => node.set_data(Box::new(DirInode::new(self.alloc))), @@ -105,7 +113,10 @@ impl Ramfs { } unsafe fn load_tar(self: Rc, base: *const u8, size: usize) -> Result { - let root = self.clone().create_node_initial("", VnodeKind::Directory); + let root = Vnode::new("", VnodeKind::Directory, Vnode::SEEKABLE | Vnode::CACHE_READDIR); + root.set_fs(self.clone()); + root.set_data(Box::new(DirInode::new(self.alloc))); + root.props_mut().mode = FileMode::default_dir(); // 1. Create all the paths in TAR for block in TarIterator::new(base, base.add(size)) { @@ -114,7 +125,7 @@ impl Ramfs { let parent = self.clone().make_path(root.clone(), dirname, true)?; let node = self .clone() - .create_node_initial(basename, block.node_kind()); + .create_node_initial(basename, block); assert_eq!(node.kind(), block.node_kind()); parent.attach(node); } diff --git a/fs/memfs/src/tar.rs b/fs/memfs/src/tar.rs index 0cd4450..5326a2f 100644 --- a/fs/memfs/src/tar.rs +++ b/fs/memfs/src/tar.rs @@ -1,4 +1,4 @@ -use error::Errno; +use libsys::{error::Errno, stat::FileMode}; use vfs::VnodeKind; #[repr(packed)] @@ -81,6 +81,15 @@ impl Tar { } } + pub fn mode(&self) -> FileMode { + let t = match self.node_kind() { + VnodeKind::Regular => FileMode::S_IFREG, + VnodeKind::Directory => FileMode::S_IFDIR, + _ => todo!() + }; + FileMode::from_bits(from_octal(&self.mode) as u32).unwrap() | t + } + pub fn data(&self) -> &[u8] { unsafe { core::slice::from_raw_parts( diff --git a/fs/vfs/Cargo.toml b/fs/vfs/Cargo.toml index dd4bb6a..24ade6d 100644 --- a/fs/vfs/Cargo.toml +++ b/fs/vfs/Cargo.toml @@ -6,6 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -error = { path = "../../error" } -libcommon = { path = "../../libcommon" } -syscall = { path = "../../syscall" } +libsys = { path = "../../libsys" } +fs-macros = { path = "../macros" } diff --git a/fs/vfs/src/block.rs b/fs/vfs/src/block.rs index 01368c6..9e630fd 100644 --- a/fs/vfs/src/block.rs +++ b/fs/vfs/src/block.rs @@ -1,4 +1,4 @@ -use error::Errno; +use libsys::error::Errno; /// Block device interface pub trait BlockDevice { diff --git a/fs/vfs/src/char.rs b/fs/vfs/src/char.rs index 7022076..b4d2fe4 100644 --- a/fs/vfs/src/char.rs +++ b/fs/vfs/src/char.rs @@ -1,5 +1,5 @@ -use crate::{OpenFlags, Stat, VnodeImpl, VnodeKind, VnodeRef, IoctlCmd}; -use error::Errno; +use crate::{VnodeImpl, VnodeKind, VnodeRef}; +use libsys::{error::Errno, ioctl::IoctlCmd, stat::OpenFlags}; /// Generic character device trait pub trait CharDevice { @@ -16,7 +16,11 @@ pub trait CharDevice { /// will immediately return an error. fn write(&self, blocking: bool, data: &[u8]) -> Result; + /// Performs a TTY control request fn ioctl(&self, cmd: IoctlCmd, ptr: usize, lim: usize) -> Result; + + /// Returns `true` if the device is ready for an operation + fn is_ready(&self, write: bool) -> Result; } /// Wrapper struct to attach [VnodeImpl] implementation @@ -25,19 +29,8 @@ pub struct CharDeviceWrapper { device: &'static dyn CharDevice, } +#[auto_inode(error)] impl VnodeImpl for CharDeviceWrapper { - fn create(&mut self, _at: VnodeRef, _name: &str, _kind: VnodeKind) -> Result { - panic!(); - } - - fn remove(&mut self, _at: VnodeRef, _name: &str) -> Result<(), Errno> { - panic!(); - } - - fn lookup(&mut self, _at: VnodeRef, _name: &str) -> Result { - panic!(); - } - fn open(&mut self, _node: VnodeRef, _opts: OpenFlags) -> Result { Ok(0) } @@ -54,19 +47,17 @@ impl VnodeImpl for CharDeviceWrapper { self.device.write(true, data) } - fn truncate(&mut self, _node: VnodeRef, _size: usize) -> Result<(), Errno> { - panic!(); + fn is_ready(&mut self, _node: VnodeRef, write: bool) -> Result { + self.device.is_ready(write) } - fn size(&mut self, _node: VnodeRef) -> Result { - panic!(); - } - - fn stat(&mut self, _node: VnodeRef, _stat: &mut Stat) -> Result<(), Errno> { - todo!(); - } - - fn ioctl(&mut self, _node: VnodeRef, cmd: IoctlCmd, ptr: usize, len: usize) -> Result { + fn ioctl( + &mut self, + _node: VnodeRef, + cmd: IoctlCmd, + ptr: usize, + len: usize, + ) -> Result { self.device.ioctl(cmd, ptr, len) } } diff --git a/fs/vfs/src/file.rs b/fs/vfs/src/file.rs index 8eea546..a9da4a6 100644 --- a/fs/vfs/src/file.rs +++ b/fs/vfs/src/file.rs @@ -1,9 +1,12 @@ -use crate::{VnodeKind, VnodeRef}; +use crate::{VnodeKind, VnodeRef, Vnode}; use alloc::rc::Rc; use core::cell::RefCell; use core::cmp::min; -use error::Errno; -use libcommon::{Read, Seek, SeekDir, Write}; +use libsys::{ + error::Errno, + stat::DirectoryEntry, + traits::{Read, Seek, SeekDir, Write}, +}; struct NormalFile { vnode: VnodeRef, @@ -95,6 +98,11 @@ impl File { /// File has to be closed on execve() calls pub const CLOEXEC: u32 = 1 << 2; + /// Special position for cache-readdir: "." entry + pub const POS_CACHE_DOT: usize = usize::MAX - 1; + /// Special position for cache-readdir: ".." entry + pub const POS_CACHE_DOT_DOT: usize = usize::MAX; + /// Constructs a new file handle for a regular file pub fn normal(vnode: VnodeRef, pos: usize, flags: u32) -> FileRef { Rc::new(RefCell::new(Self { @@ -116,6 +124,69 @@ impl File { pub fn is_cloexec(&self) -> bool { self.flags & Self::CLOEXEC != 0 } + + /// Returns `true` if the file is ready for an operation + pub fn is_ready(&self, write: bool) -> Result { + match &self.inner { + FileInner::Normal(inner) => inner.vnode.is_ready(write), + _ => todo!(), + } + } + + fn cache_readdir(inner: &mut NormalFile, entries: &mut [DirectoryEntry]) -> Result { + let mut count = entries.len(); + let mut offset = 0usize; + + if inner.pos == Self::POS_CACHE_DOT { + if count == 0 { + return Ok(offset); + } + + entries[offset] = DirectoryEntry::from_str("."); + inner.pos = Self::POS_CACHE_DOT_DOT; + + offset += 1; + count -= 1; + } + + if inner.pos == Self::POS_CACHE_DOT_DOT { + if count == 0 { + return Ok(offset); + } + + entries[offset] = DirectoryEntry::from_str(".."); + inner.pos = 0; + + offset += 1; + count -= 1; + } + + if count == 0 { + return Ok(offset); + } + + let count = inner.vnode.for_each_entry(inner.pos, count, |i, e| { + entries[offset + i] = DirectoryEntry::from_str(e.name()); + }); + inner.pos += count; + Ok(offset + count) + } + + /// Reads directory entries into the target buffer + pub fn readdir(&mut self, entries: &mut [DirectoryEntry]) -> Result { + match &mut self.inner { + FileInner::Normal(inner) => { + assert_eq!(inner.vnode.kind(), VnodeKind::Directory); + + if inner.vnode.flags() & Vnode::CACHE_READDIR != 0 { + Self::cache_readdir(inner, entries) + } else { + todo!(); + } + }, + _ => todo!(), + } + } } impl Drop for File { @@ -133,11 +204,13 @@ impl Drop for File { mod tests { use super::*; use crate::{Vnode, VnodeImpl, VnodeKind, VnodeRef}; + use libsys::{stat::OpenFlags, ioctl::IoctlCmd, stat::Stat}; use alloc::boxed::Box; use alloc::rc::Rc; struct DummyInode; + #[auto_inode] impl VnodeImpl for DummyInode { fn create( &mut self, @@ -150,25 +223,17 @@ mod tests { Ok(node) } - fn remove(&mut self, _at: VnodeRef, _name: &str) -> Result<(), Errno> { - Err(Errno::NotImplemented) - } - - fn lookup(&mut self, _at: VnodeRef, _name: &str) -> Result { - todo!() - } - - fn open(&mut self, _node: VnodeRef) -> Result { + fn open(&mut self, _node: VnodeRef, _flags: OpenFlags) -> Result { Ok(0) } fn close(&mut self, _node: VnodeRef) -> Result<(), Errno> { - Err(Errno::NotImplemented) + Ok(()) } fn read(&mut self, _node: VnodeRef, pos: usize, data: &mut [u8]) -> Result { #[cfg(test)] - println!("read {}", pos); + println!("read {} at {}", data.len(), pos); let len = 123; if pos >= len { return Ok(0); @@ -183,41 +248,24 @@ mod tests { fn write(&mut self, _node: VnodeRef, _pos: usize, _data: &[u8]) -> Result { Err(Errno::NotImplemented) } - - fn truncate(&mut self, _node: VnodeRef, _size: usize) -> Result<(), Errno> { - Err(Errno::NotImplemented) - } - - fn size(&mut self, _node: VnodeRef) -> Result { - Err(Errno::NotImplemented) - } } #[test] fn test_normal_read() { let node = Vnode::new("", VnodeKind::Regular, 0); node.set_data(Box::new(DummyInode {})); - let mut file = node.open().unwrap(); - - match &file.inner { - FileInner::Normal(inner) => { - assert!(Rc::ptr_eq(&inner.vnode, &node)); - assert_eq!(inner.pos, 0); - } - _ => panic!("Invalid file.inner"), - } - + let mut file = node.open(OpenFlags::O_RDONLY).unwrap(); let mut buf = [0u8; 4096]; - assert_eq!(file.read(&mut buf[0..32]).unwrap(), 32); + assert_eq!(file.borrow_mut().read(&mut buf[0..32]).unwrap(), 32); for i in 0..32 { assert_eq!((i & 0xFF) as u8, buf[i]); } - assert_eq!(file.read(&mut buf[0..64]).unwrap(), 64); + assert_eq!(file.borrow_mut().read(&mut buf[0..64]).unwrap(), 64); for i in 0..64 { assert_eq!(((i + 32) & 0xFF) as u8, buf[i]); } - assert_eq!(file.read(&mut buf[0..64]).unwrap(), 27); + assert_eq!(file.borrow_mut().read(&mut buf[0..64]).unwrap(), 27); for i in 0..27 { assert_eq!(((i + 96) & 0xFF) as u8, buf[i]); } diff --git a/fs/vfs/src/fs.rs b/fs/vfs/src/fs.rs index 4b60358..a086b10 100644 --- a/fs/vfs/src/fs.rs +++ b/fs/vfs/src/fs.rs @@ -2,7 +2,7 @@ use crate::{BlockDevice, VnodeRef}; use alloc::rc::Rc; use core::any::Any; use core::cell::Ref; -use error::Errno; +use libsys::error::Errno; /// General filesystem interface pub trait Filesystem { diff --git a/fs/vfs/src/ioctx.rs b/fs/vfs/src/ioctx.rs index a736c06..f55b3c7 100644 --- a/fs/vfs/src/ioctx.rs +++ b/fs/vfs/src/ioctx.rs @@ -1,19 +1,28 @@ -use crate::{FileMode, FileRef, OpenFlags, VnodeKind, VnodeRef}; -use error::Errno; -use libcommon::{path_component_left, path_component_right}; +use crate::{FileRef, VnodeKind, VnodeRef}; +use libsys::{ + error::Errno, + path::{path_component_left, path_component_right}, + stat::{FileMode, GroupId, OpenFlags, UserId}, +}; /// I/O context structure #[derive(Clone)] pub struct Ioctx { root: VnodeRef, cwd: VnodeRef, + /// Process user ID + pub uid: UserId, + /// Process group ID + pub gid: GroupId, } impl Ioctx { /// Creates a new I/O context with given root node - pub fn new(root: VnodeRef) -> Self { + pub fn new(root: VnodeRef, uid: UserId, gid: GroupId) -> Self { Self { cwd: root.clone(), + uid, + gid, root, } } @@ -38,6 +47,11 @@ impl Ioctx { } } + while let Some(target) = at.target() { + assert!(at.kind() == VnodeKind::Directory); + at = target; + } + if element.is_empty() && rest.is_empty() { return Ok(at); } @@ -110,6 +124,16 @@ impl Ioctx { node.open(opts) } + + /// Changes current working directory of the process + pub fn chdir(&mut self, path: &str) -> Result<(), Errno> { + let node = self.find(None, path, true)?; + if !node.is_directory() { + return Err(Errno::NotADirectory); + } + self.cwd = node; + Ok(()) + } } #[cfg(test)] @@ -117,9 +141,11 @@ mod tests { use super::*; use crate::{Vnode, VnodeImpl, VnodeKind}; use alloc::{boxed::Box, rc::Rc}; + use libsys::{ioctl::IoctlCmd, stat::OpenFlags, stat::Stat}; pub struct DummyInode; + #[auto_inode] impl VnodeImpl for DummyInode { fn create( &mut self, @@ -132,37 +158,9 @@ mod tests { Ok(vnode) } - fn remove(&mut self, _at: VnodeRef, _name: &str) -> Result<(), Errno> { - todo!() - } - fn lookup(&mut self, _at: VnodeRef, _name: &str) -> Result { Err(Errno::DoesNotExist) } - - fn open(&mut self, _node: VnodeRef) -> Result { - todo!() - } - - fn close(&mut self, _node: VnodeRef) -> Result<(), Errno> { - todo!() - } - - fn read(&mut self, _node: VnodeRef, _pos: usize, _data: &mut [u8]) -> Result { - todo!() - } - - fn write(&mut self, _node: VnodeRef, _pos: usize, _data: &[u8]) -> Result { - todo!() - } - - fn truncate(&mut self, _node: VnodeRef, _size: usize) -> Result<(), Errno> { - todo!() - } - - fn size(&mut self, _node: VnodeRef) -> Result { - todo!() - } } #[test] diff --git a/fs/vfs/src/lib.rs b/fs/vfs/src/lib.rs index d02b0f4..81bbf7f 100644 --- a/fs/vfs/src/lib.rs +++ b/fs/vfs/src/lib.rs @@ -7,10 +7,13 @@ #[macro_use] extern crate std; +#[macro_use] +extern crate fs_macros; + extern crate alloc; -pub use syscall::stat::{FileMode, OpenFlags, Stat}; -pub use syscall::ioctl::IoctlCmd; +// pub use libsys::stat::{FileMode, OpenFlags, Stat}; +// pub use libsys::ioctl::IoctlCmd; mod block; pub use block::BlockDevice; diff --git a/fs/vfs/src/node.rs b/fs/vfs/src/node.rs index fb37013..0e87b37 100644 --- a/fs/vfs/src/node.rs +++ b/fs/vfs/src/node.rs @@ -1,8 +1,12 @@ -use crate::{File, FileMode, FileRef, Filesystem, IoctlCmd, OpenFlags, Stat}; +use crate::{File, FileRef, Filesystem, Ioctx}; use alloc::{borrow::ToOwned, boxed::Box, rc::Rc, string::String, vec::Vec}; -use core::cell::{RefCell, RefMut}; +use core::cell::{RefCell, RefMut, Ref}; use core::fmt; -use error::Errno; +use libsys::{ + error::Errno, + ioctl::IoctlCmd, + stat::{AccessMode, DirectoryEntry, FileMode, OpenFlags, Stat}, +}; /// Convenience type alias for [Rc] pub type VnodeRef = Rc; @@ -27,7 +31,8 @@ pub(crate) struct TreeNode { /// File property cache struct pub struct VnodeProps { - mode: FileMode, + /// Node permissions and type + pub mode: FileMode, } /// Virtual filesystem node struct, generalizes access to @@ -70,12 +75,24 @@ pub trait VnodeImpl { /// Resizes the file storage if necessary. fn write(&mut self, node: VnodeRef, pos: usize, data: &[u8]) -> Result; + /// Read directory entries into target buffer + fn readdir( + &mut self, + node: VnodeRef, + pos: usize, + data: &mut [DirectoryEntry], + ) -> Result; + /// Retrieves file status - fn stat(&mut self, node: VnodeRef, stat: &mut Stat) -> Result<(), Errno>; + fn stat(&mut self, node: VnodeRef) -> Result; /// Reports the size of this filesystem object in bytes fn size(&mut self, node: VnodeRef) -> Result; + /// Returns `true` if node is ready for an operation + fn is_ready(&mut self, node: VnodeRef, write: bool) -> Result; + + /// Performs filetype-specific request fn ioctl( &mut self, node: VnodeRef, @@ -90,6 +107,11 @@ impl Vnode { /// be seeked to arbitrary offsets pub const SEEKABLE: u32 = 1 << 0; + /// If set, readdir() uses only in-memory node tree + pub const CACHE_READDIR: u32 = 1 << 1; + /// If set, stat() uses only in-memory stat data + pub const CACHE_STAT: u32 = 1 << 2; + /// Constructs a new [Vnode], wrapping it in [Rc]. The resulting node /// then needs to have [Vnode::set_data()] called on it to be usable. pub fn new(name: &str, kind: VnodeKind, flags: u32) -> VnodeRef { @@ -115,6 +137,16 @@ impl Vnode { &self.name } + /// Returns a borrowed reference to cached file properties + pub fn props_mut(&self) -> RefMut { + self.props.borrow_mut() + } + + /// Returns a borrowed reference to cached file properties + pub fn props(&self) -> Ref { + self.props.borrow() + } + /// Sets an associated [VnodeImpl] for the [Vnode] pub fn set_data(&self, data: Box) { *self.data.borrow_mut() = Some(data); @@ -151,6 +183,12 @@ impl Vnode { self.kind } + /// Returns flags of the vnode + #[inline(always)] + pub const fn flags(&self) -> u32 { + self.flags + } + // Tree operations /// Attaches `child` vnode to `self` in in-memory tree. NOTE: does not @@ -223,6 +261,29 @@ impl Vnode { .cloned() } + pub(crate) fn for_each_entry( + &self, + offset: usize, + limit: usize, + mut f: F, + ) -> usize { + assert!(self.is_directory()); + let mut count = 0; + for (index, item) in self + .tree + .borrow() + .children + .iter() + .skip(offset) + .take(limit) + .enumerate() + { + f(index, item); + count += 1; + } + count + } + /// Looks up a child `name` in `self`. Will first try looking up a cached /// vnode and will load it from disk if it's missing. pub fn lookup_or_load(self: &VnodeRef, name: &str) -> Result { @@ -296,35 +357,55 @@ impl Vnode { /// Opens a vnode for access pub fn open(self: &VnodeRef, flags: OpenFlags) -> Result { - if self.kind == VnodeKind::Directory { - return Err(Errno::IsADirectory); + let mut open_flags = 0; + if flags.contains(OpenFlags::O_DIRECTORY) { + if self.kind != VnodeKind::Directory { + return Err(Errno::NotADirectory); + } + if flags & OpenFlags::O_ACCESS != OpenFlags::O_RDONLY { + return Err(Errno::IsADirectory); + } + + open_flags = File::READ; + } else { + if self.kind == VnodeKind::Directory { + return Err(Errno::IsADirectory); + } + + match flags & OpenFlags::O_ACCESS { + OpenFlags::O_RDONLY => open_flags |= File::READ, + OpenFlags::O_WRONLY => open_flags |= File::WRITE, + OpenFlags::O_RDWR => open_flags |= File::READ | File::WRITE, + _ => unimplemented!(), + } } - let mut open_flags = 0; - match flags & OpenFlags::O_ACCESS { - OpenFlags::O_RDONLY => open_flags |= File::READ, - OpenFlags::O_WRONLY => open_flags |= File::WRITE, - OpenFlags::O_RDWR => open_flags |= File::READ | File::WRITE, - _ => unimplemented!(), - } if flags.contains(OpenFlags::O_CLOEXEC) { open_flags |= File::CLOEXEC; } - if let Some(ref mut data) = *self.data() { - let pos = data.open(self.clone(), flags)?; - Ok(File::normal(self.clone(), pos, open_flags)) + if self.kind == VnodeKind::Directory && self.flags & Vnode::CACHE_READDIR != 0 { + Ok(File::normal(self.clone(), File::POS_CACHE_DOT, open_flags)) } else { - Err(Errno::NotImplemented) + if let Some(ref mut data) = *self.data() { + let pos = data.open(self.clone(), flags)?; + Ok(File::normal(self.clone(), pos, open_flags)) + } else { + Err(Errno::NotImplemented) + } } } /// Closes a vnode pub fn close(self: &VnodeRef) -> Result<(), Errno> { - if let Some(ref mut data) = *self.data() { - data.close(self.clone()) + if self.kind == VnodeKind::Directory && self.flags & Vnode::CACHE_READDIR != 0 { + Ok(()) } else { - Err(Errno::NotImplemented) + if let Some(ref mut data) = *self.data() { + data.close(self.clone()) + } else { + Err(Errno::NotImplemented) + } } } @@ -377,9 +458,16 @@ impl Vnode { } /// Reports file status - pub fn stat(self: &VnodeRef, stat: &mut Stat) -> Result<(), Errno> { - if let Some(ref mut data) = *self.data() { - data.stat(self.clone(), stat) + pub fn stat(self: &VnodeRef) -> Result { + if self.flags & Self::CACHE_STAT != 0 { + let props = self.props(); + Ok(Stat { + blksize: 0, + size: 0, + mode: props.mode + }) + } else if let Some(ref mut data) = *self.data() { + data.stat(self.clone()) } else { Err(Errno::NotImplemented) } @@ -393,6 +481,48 @@ impl Vnode { Err(Errno::NotImplemented) } } + + /// Returns `true` if the node is ready for operation + pub fn is_ready(self: &VnodeRef, write: bool) -> Result { + if let Some(ref mut data) = *self.data() { + data.is_ready(self.clone(), write) + } else { + Err(Errno::NotImplemented) + } + } + + /// Checks if given [Ioctx] has `access` permissions to the vnode + pub fn check_access(&self, _ioctx: &Ioctx, access: AccessMode) -> Result<(), Errno> { + let props = self.props.borrow(); + let mode = props.mode; + + if access.contains(AccessMode::F_OK) { + if access.intersects(AccessMode::R_OK | AccessMode::W_OK | AccessMode::X_OK) { + return Err(Errno::InvalidArgument); + } + return Ok(()); + } else { + if access.contains(AccessMode::F_OK) { + return Err(Errno::InvalidArgument); + } + + // Check user + if access.contains(AccessMode::R_OK) && !mode.contains(FileMode::USER_READ) { + return Err(Errno::PermissionDenied); + } + if access.contains(AccessMode::W_OK) && !mode.contains(FileMode::USER_WRITE) { + return Err(Errno::PermissionDenied); + } + if access.contains(AccessMode::X_OK) && !mode.contains(FileMode::USER_EXEC) { + return Err(Errno::PermissionDenied); + } + + // TODO check group + // TODO check other + + return Ok(()); + } + } } impl fmt::Debug for Vnode { @@ -405,8 +535,10 @@ impl fmt::Debug for Vnode { mod tests { use super::*; + use libsys::{ioctl::IoctlCmd, stat::OpenFlags, stat::Stat}; pub struct DummyInode; + #[auto_inode] impl VnodeImpl for DummyInode { fn create( &mut self, @@ -426,30 +558,6 @@ mod tests { fn lookup(&mut self, _at: VnodeRef, _name: &str) -> Result { Err(Errno::DoesNotExist) } - - fn open(&mut self, _node: VnodeRef) -> Result { - Err(Errno::NotImplemented) - } - - fn close(&mut self, _node: VnodeRef) -> Result<(), Errno> { - Err(Errno::NotImplemented) - } - - fn read(&mut self, _node: VnodeRef, _pos: usize, _data: &mut [u8]) -> Result { - Err(Errno::NotImplemented) - } - - fn write(&mut self, _node: VnodeRef, _pos: usize, _data: &[u8]) -> Result { - Err(Errno::NotImplemented) - } - - fn truncate(&mut self, _node: VnodeRef, _size: usize) -> Result<(), Errno> { - Err(Errno::NotImplemented) - } - - fn size(&mut self, _node: VnodeRef) -> Result { - Err(Errno::NotImplemented) - } } #[test] @@ -469,10 +577,13 @@ mod tests { root.set_data(Box::new(DummyInode {})); - let node = root.mkdir("test", FileMode::default_dir()).unwrap(); + let node = root + .create("test", FileMode::default_dir(), VnodeKind::Directory) + .unwrap(); assert_eq!( - root.mkdir("test", FileMode::default_dir()).unwrap_err(), + root.create("test", FileMode::default_dir(), VnodeKind::Directory) + .unwrap_err(), Errno::AlreadyExists ); diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 0675dec..f5b3402 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -10,23 +10,24 @@ name = "kernel" test = false [dependencies] -error = { path = "../error" } vfs = { path = "../fs/vfs" } memfs = { path = "../fs/memfs" } -libcommon = { path = "../libcommon" } -syscall = { path = "../syscall" } +libsys = { path = "../libsys" } cfg-if = "1.x.x" tock-registers = "0.7.x" fdt-rs = { version = "0.x.x", default-features = false } bitflags = "^1.3.0" +kernel-macros = { path = "macros" } [target.'cfg(target_arch = "aarch64")'.dependencies] cortex-a = { version = "6.x.x" } [features] +default = ["aggressive_syscall"] pl011 = [] pl031 = [] verbose = [] +aggressive_syscall = [] mach_qemu = ["pl011", "pl031"] mach_orangepi3 = [] diff --git a/libcommon/Cargo.toml b/kernel/macros/Cargo.toml similarity index 65% rename from libcommon/Cargo.toml rename to kernel/macros/Cargo.toml index a49a495..fed4cd7 100644 --- a/libcommon/Cargo.toml +++ b/kernel/macros/Cargo.toml @@ -1,9 +1,13 @@ [package] -name = "libcommon" +name = "kernel-macros" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +proc-macro = true + [dependencies] -error = { path = "../error" } +syn = "^1.0.81" +quote = "^1.0.10" diff --git a/kernel/macros/src/lib.rs b/kernel/macros/src/lib.rs new file mode 100644 index 0000000..6235eb5 --- /dev/null +++ b/kernel/macros/src/lib.rs @@ -0,0 +1,42 @@ +extern crate proc_macro; +extern crate syn; +#[macro_use] +extern crate quote; + +use proc_macro::TokenStream; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro_derive(TtyCharDevice)] +pub fn derive_tty_char_device(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + + if !ast.generics.params.is_empty() { + panic!( + "Derived TtyCharDevice cannot have generic parameters: {:?}", + ast.ident + ); + } + let ident = ast.ident; + + quote! { + impl vfs::CharDevice for #ident { + fn read(&self, blocking: bool, data: &mut [u8]) -> Result { + assert!(blocking); + crate::dev::tty::TtyDevice::line_read(self, data) + } + fn write(&self, blocking: bool, data: &[u8]) -> Result { + assert!(blocking); + crate::dev::tty::TtyDevice::line_write(self, data) + } + fn ioctl(&self, cmd: libsys::ioctl::IoctlCmd, ptr: usize, len: usize) -> + Result + { + crate::dev::tty::TtyDevice::tty_ioctl(self, cmd, ptr, len) + } + fn is_ready(&self, write: bool) -> Result { + crate::dev::tty::TtyDevice::is_ready(self, write) + } + } + } + .into() +} diff --git a/kernel/src/arch/aarch64/boot/mod.rs b/kernel/src/arch/aarch64/boot/mod.rs index 18e97f5..d42b224 100644 --- a/kernel/src/arch/aarch64/boot/mod.rs +++ b/kernel/src/arch/aarch64/boot/mod.rs @@ -14,7 +14,7 @@ use crate::config::{ConfigKey, CONFIG}; use crate::dev::fdt::find_prop; use crate::dev::{fdt::DeviceTree, irq::IntSource, Device}; use crate::fs::devfs; -use error::Errno; +use libsys::error::Errno; //use crate::debug::Level; use crate::mem::{ self, heap, diff --git a/kernel/src/arch/aarch64/context.S b/kernel/src/arch/aarch64/context.S index b022526..71858c5 100644 --- a/kernel/src/arch/aarch64/context.S +++ b/kernel/src/arch/aarch64/context.S @@ -32,27 +32,6 @@ __aa64_ctx_enter_kernel: eret __aa64_ctx_enter_from_fork: - // stack.push(frame.x[18]); - // stack.push(frame.x[17]); - // stack.push(frame.x[16]); - // stack.push(frame.x[15]); - // stack.push(frame.x[14]); - // stack.push(frame.x[13]); - // stack.push(frame.x[12]); - // stack.push(frame.x[11]); - // stack.push(frame.x[10]); - // stack.push(frame.x[9]); - // stack.push(frame.x[8]); - // stack.push(frame.x[7]); - // stack.push(frame.x[6]); - // stack.push(frame.x[5]); - // stack.push(frame.x[4]); - // stack.push(frame.x[3]); - // stack.push(frame.x[2]); - // stack.push(frame.x[1]); - - // stack.push(frame.elr_el1 as usize); - // stack.push(frame.sp_el0 as usize); ldp x0, x1, [sp, #16 * 0] msr sp_el0, x0 msr elr_el1, x1 @@ -82,7 +61,8 @@ __aa64_ctx_switch: stp x27, x28, [sp, #16 * 4] stp x29, x30, [sp, #16 * 5] mrs x19, TTBR0_EL1 - stp x19, xzr, [sp, #16 * 6] + mrs x20, TPIDR_EL0 + stp x19, x20, [sp, #16 * 6] mov x19, sp str x19, [x1] @@ -90,8 +70,9 @@ __aa64_ctx_switch_to: ldr x0, [x0] mov sp, x0 - ldp x19, xzr, [sp, #16 * 6] + ldp x19, x20, [sp, #16 * 6] msr TTBR0_EL1, x19 + msr TPIDR_EL0, x20 ldp x19, x20, [sp, #16 * 0] ldp x21, x22, [sp, #16 * 1] ldp x23, x24, [sp, #16 * 2] diff --git a/kernel/src/arch/aarch64/context.rs b/kernel/src/arch/aarch64/context.rs index b86f0bd..d639758 100644 --- a/kernel/src/arch/aarch64/context.rs +++ b/kernel/src/arch/aarch64/context.rs @@ -18,7 +18,7 @@ pub struct Context { /// Thread's kernel stack pointer pub k_sp: usize, // 0x00 - stack_base_phys: usize, + stack_base: usize, stack_page_count: usize, } @@ -35,7 +35,7 @@ impl Context { Self { k_sp: stack.sp, - stack_base_phys: stack.bp, + stack_base: stack.bp, stack_page_count: 8, } } @@ -67,7 +67,7 @@ impl Context { stack.push(frame.sp_el0 as usize); // Setup common - stack.push(0); + stack.push(0); // tpidr_el0 stack.push(ttbr0); stack.push(__aa64_ctx_enter_from_fork as usize); // x30/lr stack.push(frame.x[29]); // x29 @@ -85,7 +85,7 @@ impl Context { Self { k_sp: stack.sp, - stack_base_phys: stack.bp, + stack_base: stack.bp, stack_page_count: 8, } } @@ -96,7 +96,7 @@ impl Context { stack.push(entry); stack.push(arg); - stack.push(/* ttbr0 */ 0); + stack.push(0); stack.push(ustack); stack.setup_common(__aa64_ctx_enter_user as usize, ttbr0); @@ -104,11 +104,39 @@ impl Context { Self { k_sp: stack.sp, - stack_base_phys: stack.bp, + stack_base: stack.bp, stack_page_count: 8, } } + /// Constructs an uninitialized thread context + pub fn empty() -> Self { + let stack = Stack::new(8); + Self { + k_sp: stack.sp, + stack_base: stack.bp, + stack_page_count: 8 + } + } + + /// Sets up a context for signal entry + /// + /// # Safety + /// + /// Unsafe: may clobber an already active context + pub unsafe fn setup_signal_entry(&mut self, entry: usize, arg: usize, ttbr0: usize, ustack: usize) { + let mut stack = Stack::from_base_size(self.stack_base, self.stack_page_count); + + stack.push(entry); + stack.push(arg); + stack.push(0); + stack.push(ustack); + + stack.setup_common(__aa64_ctx_enter_user as usize, ttbr0); + + self.k_sp = stack.sp; + } + /// Performs initial thread entry /// /// # Safety @@ -140,21 +168,28 @@ impl Stack { } } + pub unsafe fn from_base_size(bp: usize, page_count: usize) -> Stack { + Stack { + bp, + sp: bp + page_count * mem::PAGE_SIZE + } + } + pub fn setup_common(&mut self, entry: usize, ttbr: usize) { - self.push(0); + self.push(0); // tpidr_el0 self.push(ttbr); - self.push(entry); // x30/lr - self.push(0); // x29 - self.push(0); // x28 - self.push(0); // x27 - self.push(0); // x26 - self.push(0); // x25 - self.push(0); // x24 - self.push(0); // x23 - self.push(0); // x22 - self.push(0); // x21 - self.push(0); // x20 - self.push(0); // x19 + self.push(entry); // x30/lr + self.push(0); // x29 + self.push(0); // x28 + self.push(0); // x27 + self.push(0); // x26 + self.push(0); // x25 + self.push(0); // x24 + self.push(0); // x23 + self.push(0); // x22 + self.push(0); // x21 + self.push(0); // x20 + self.push(0); // x19 } pub fn push(&mut self, value: usize) { diff --git a/kernel/src/arch/aarch64/cpu.rs b/kernel/src/arch/aarch64/cpu.rs index 955af8d..69b89c6 100644 --- a/kernel/src/arch/aarch64/cpu.rs +++ b/kernel/src/arch/aarch64/cpu.rs @@ -1,30 +1,28 @@ #![allow(missing_docs)] -use cortex_a::registers::{TPIDR_EL1, MPIDR_EL1}; -use tock_registers::interfaces::{Readable, Writeable}; -use core::ptr::null_mut; -use core::mem::MaybeUninit; -use core::sync::atomic::{Ordering, AtomicUsize}; -use crate::proc::{Scheduler, process::Context}; +use crate::proc::Scheduler; use crate::util::InitOnce; +use core::mem::MaybeUninit; +use core::ptr::null_mut; +use core::sync::atomic::{AtomicUsize, Ordering}; +use cortex_a::registers::{MPIDR_EL1, TPIDR_EL1}; +use tock_registers::interfaces::{Readable, Writeable}; #[repr(C)] pub struct Cpu { - active_context: *mut Context, // 0x00 - counter: AtomicUsize, // 0x08 + counter: AtomicUsize, // 0x08 id: usize, - scheduler: Scheduler + scheduler: Scheduler, } impl Cpu { pub fn new(id: usize) -> Self { Self { - active_context: null_mut(), counter: AtomicUsize::new(0), id, - scheduler: Scheduler::new() + scheduler: Scheduler::new(), } } @@ -46,7 +44,9 @@ impl Cpu { } pub unsafe fn cpus() -> impl Iterator { - CPUS[..CPU_COUNT.load(Ordering::Acquire)].iter_mut().map(|c| c.assume_init_mut()) + CPUS[..CPU_COUNT.load(Ordering::Acquire)] + .iter_mut() + .map(|c| c.assume_init_mut()) } pub unsafe fn by_index(idx: usize) -> &'static mut Cpu { diff --git a/kernel/src/arch/aarch64/exception.rs b/kernel/src/arch/aarch64/exception.rs index bbc0917..0a89091 100644 --- a/kernel/src/arch/aarch64/exception.rs +++ b/kernel/src/arch/aarch64/exception.rs @@ -3,11 +3,11 @@ use crate::arch::machine; use crate::debug::Level; use crate::dev::irq::{IntController, IrqContext}; -use crate::proc::{sched, Process}; use crate::mem; +use crate::proc::{sched, Thread}; use crate::syscall; -use ::syscall::abi; use cortex_a::registers::{ESR_EL1, FAR_EL1}; +use libsys::{abi::SystemCall, signal::Signal}; use tock_registers::interfaces::Readable; /// Trapped SIMD/FP functionality @@ -76,7 +76,7 @@ fn dump_data_abort(level: Level, esr: u64, far: u64) { } else { print!(level, " at UNKNOWN"); } - println!(level, ""); + println!(level, "\x1B[0m"); } #[no_mangle] @@ -88,15 +88,21 @@ extern "C" fn __aa64_exc_sync_handler(exc: &mut ExceptionFrame) { match err_code { EC_DATA_ABORT_EL0 | EC_DATA_ABORT_ELX => { let far = FAR_EL1.get() as usize; + let iss = esr & 0x1FFFFFF; + // TODO handle scenarios when sheduler is not yet initialized + if iss & (1 << 6) != 0 && far < mem::KERNEL_OFFSET { + let thread = Thread::current(); + let proc = thread.owner().unwrap(); - if far < mem::KERNEL_OFFSET { - let proc = Process::current(); - - if let Err(e) = proc.manipulate_space(|space| space.try_cow_copy(far)) { + if proc + .manipulate_space(|space| space.try_cow_copy(far)) + .is_err() + { // Kill program + errorln!("Data abort from {:#x}", exc.elr_el1); dump_data_abort(Level::Error, esr, far as u64); - panic!("CoW copy returned {:?}", e); + proc.enter_fault_signal(thread, Signal::SegmentationFault); } unsafe { @@ -110,34 +116,47 @@ extern "C" fn __aa64_exc_sync_handler(exc: &mut ExceptionFrame) { } errorln!("Unresolved data abort"); + errorln!("Data abort from {:#x}", exc.elr_el1); dump_data_abort(Level::Error, esr, far as u64); } EC_SVC_AA64 => { - unsafe { - if exc.x[8] == abi::SYS_FORK { - match syscall::sys_fork(exc) { - Ok(pid) => exc.x[0] = pid.value() as usize, - Err(err) => { - warnln!("fork() syscall failed: {:?}", err); - exc.x[0] = usize::MAX; - } - } - return; - } + let num = SystemCall::from_repr(exc.x[8]); + if num.is_none() { + todo!(); + } + let num = num.unwrap(); - match syscall::syscall(exc.x[8], &exc.x[..6]) { - Ok(val) => exc.x[0] = val, + if num == SystemCall::Fork { + match unsafe { syscall::sys_fork(exc) } { + Ok(pid) => exc.x[0] = pid.value() as usize, Err(err) => { - warnln!("syscall {} failed: {:?}", exc.x[8], err); - exc.x[0] = usize::MAX + exc.x[0] = err.to_negative_isize() as usize; } } + return; + } + + match syscall::syscall(num, &exc.x[..6]) { + Ok(val) => exc.x[0] = val, + Err(err) => { + exc.x[0] = err.to_negative_isize() as usize; + } } + return; } _ => {} } + // if sched::is_ready() { + // let thread = Thread::current(); + // errorln!( + // "Unhandled exception in thread {}, {:?}", + // thread.id(), + // thread.owner().map(|e| e.id()) + // ); + // } + errorln!( "Unhandled exception at ELR={:#018x}, ESR={:#010x}", exc.elr_el1, diff --git a/kernel/src/arch/aarch64/irq/gic/mod.rs b/kernel/src/arch/aarch64/irq/gic/mod.rs index d3a056e..dc7eb96 100644 --- a/kernel/src/arch/aarch64/irq/gic/mod.rs +++ b/kernel/src/arch/aarch64/irq/gic/mod.rs @@ -7,7 +7,7 @@ use crate::dev::{ use crate::mem::virt::{DeviceMemory, DeviceMemoryIo}; use crate::sync::IrqSafeSpinLock; use crate::util::InitOnce; -use error::Errno; +use libsys::error::Errno; mod gicc; use gicc::Gicc; @@ -30,7 +30,6 @@ pub struct Gic { gicd: InitOnce, gicd_base: usize, gicc_base: usize, - scheduler_irq: IrqNumber, table: IrqSafeSpinLock<[Option<&'static (dyn IntSource + Sync)>; MAX_IRQ]>, } @@ -89,23 +88,27 @@ impl IntController for Gic { return; } +//<<<<<<< HEAD if irq_number == 1 { gicc.clear_irq(irq_number as u32, ic); debugln!("Received IPI"); loop {} } - - if self.scheduler_irq.0 == irq_number { - use crate::proc::sched; - use cortex_a::registers::{CNTP_TVAL_EL0, CNTP_CTL_EL0}; - use tock_registers::interfaces::Writeable; - use crate::arch::platform::cpu::Cpu; - gicc.clear_irq(irq_number as u32, ic); - CNTP_TVAL_EL0.set(1000000); - CNTP_CTL_EL0.write(CNTP_CTL_EL0::ENABLE::SET); - sched::switch(false); - return; - } +// +// if self.scheduler_irq.0 == irq_number { +// use crate::proc::sched; +// use cortex_a::registers::{CNTP_TVAL_EL0, CNTP_CTL_EL0}; +// use tock_registers::interfaces::Writeable; +// use crate::arch::platform::cpu::Cpu; +// gicc.clear_irq(irq_number as u32, ic); +// CNTP_TVAL_EL0.set(1000000); +// CNTP_CTL_EL0.write(CNTP_CTL_EL0::ENABLE::SET); +// sched::switch(false); +// return; +// } +//======= + gicc.clear_irq(irq_number as u32, ic); +//>>>>>>> feat/thread { // TODO make timer interrupt a special case and drop table lock @@ -118,8 +121,6 @@ impl IntController for Gic { } } } - - gicc.clear_irq(irq_number as u32, ic); } fn register_handler( @@ -157,13 +158,12 @@ impl Gic { /// # Safety /// /// Does not perform `gicd_base` and `gicc_base` validation. - pub const unsafe fn new(gicd_base: usize, gicc_base: usize, scheduler_irq: IrqNumber) -> Self { + pub const unsafe fn new(gicd_base: usize, gicc_base: usize) -> Self { Self { gicc: InitOnce::new(), gicd: InitOnce::new(), gicd_base, gicc_base, - scheduler_irq, table: IrqSafeSpinLock::new([None; MAX_IRQ]), } } diff --git a/kernel/src/arch/aarch64/mach_orangepi3/gpio.rs b/kernel/src/arch/aarch64/mach_orangepi3/gpio.rs index 06db644..0fb90e7 100644 --- a/kernel/src/arch/aarch64/mach_orangepi3/gpio.rs +++ b/kernel/src/arch/aarch64/mach_orangepi3/gpio.rs @@ -12,7 +12,7 @@ use crate::dev::{ use crate::mem::virt::DeviceMemoryIo; use crate::sync::IrqSafeSpinLock; use crate::util::InitOnce; -use error::Errno; +use libsys::error::Errno; use tock_registers::interfaces::{Readable, Writeable}; use tock_registers::register_structs; use tock_registers::registers::ReadWrite; diff --git a/kernel/src/arch/aarch64/mach_orangepi3/mod.rs b/kernel/src/arch/aarch64/mach_orangepi3/mod.rs index 2e707b0..6d1d347 100644 --- a/kernel/src/arch/aarch64/mach_orangepi3/mod.rs +++ b/kernel/src/arch/aarch64/mach_orangepi3/mod.rs @@ -13,7 +13,7 @@ use crate::dev::{ }; use crate::fs::devfs::{self, CharDeviceType}; use crate::mem::phys; -use error::Errno; +use libsys::error::Errno; mod gpio; mod rtc; diff --git a/kernel/src/arch/aarch64/mach_orangepi3/rtc.rs b/kernel/src/arch/aarch64/mach_orangepi3/rtc.rs index 01eaf1b..e65b85b 100644 --- a/kernel/src/arch/aarch64/mach_orangepi3/rtc.rs +++ b/kernel/src/arch/aarch64/mach_orangepi3/rtc.rs @@ -7,7 +7,7 @@ use crate::dev::{ use crate::mem::virt::DeviceMemoryIo; use crate::sync::IrqSafeSpinLock; use crate::util::InitOnce; -use error::Errno; +use libsys::error::Errno; use tock_registers::{ interfaces::{Readable, Writeable}, register_bitfields, register_structs, diff --git a/kernel/src/arch/aarch64/mach_orangepi3/uart.rs b/kernel/src/arch/aarch64/mach_orangepi3/uart.rs index 8fbc719..efa53cc 100644 --- a/kernel/src/arch/aarch64/mach_orangepi3/uart.rs +++ b/kernel/src/arch/aarch64/mach_orangepi3/uart.rs @@ -8,11 +8,10 @@ use crate::dev::{ use crate::mem::virt::DeviceMemoryIo; use crate::sync::IrqSafeSpinLock; use crate::util::InitOnce; -use error::Errno; +use libsys::error::Errno; use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; use tock_registers::registers::{Aliased, ReadOnly, ReadWrite}; use tock_registers::{register_bitfields, register_structs}; -use vfs::{CharDevice, IoctlCmd}; register_bitfields! [ u32, @@ -78,6 +77,7 @@ struct UartInner { regs: DeviceMemoryIo, } +#[derive(TtyCharDevice)] pub(super) struct Uart { inner: InitOnce>, ring: CharRing<16>, @@ -123,22 +123,6 @@ impl SerialDevice for Uart { } } -impl CharDevice for Uart { - fn read(&self, blocking: bool, data: &mut [u8]) -> Result { - assert!(blocking); - self.line_read(data) - } - - fn write(&self, blocking: bool, data: &[u8]) -> Result { - assert!(blocking); - self.line_write(data) - } - - fn ioctl(&self, cmd: IoctlCmd, ptr: usize, len: usize) -> Result { - self.tty_ioctl(cmd, ptr, len) - } -} - impl TtyDevice<16> for Uart { fn ring(&self) -> &CharRing<16> { &self.ring diff --git a/kernel/src/arch/aarch64/mach_orangepi3/wdog.rs b/kernel/src/arch/aarch64/mach_orangepi3/wdog.rs index 3bca2dd..5178b9d 100644 --- a/kernel/src/arch/aarch64/mach_orangepi3/wdog.rs +++ b/kernel/src/arch/aarch64/mach_orangepi3/wdog.rs @@ -2,7 +2,7 @@ use crate::dev::Device; use crate::mem::virt::DeviceMemoryIo; use crate::sync::IrqSafeSpinLock; use crate::util::InitOnce; -use error::Errno; +use libsys::error::Errno; use tock_registers::{ interfaces::Writeable, register_bitfields, register_structs, registers::ReadWrite, }; diff --git a/kernel/src/arch/aarch64/mach_qemu/mod.rs b/kernel/src/arch/aarch64/mach_qemu/mod.rs index a359e32..0c0e972 100644 --- a/kernel/src/arch/aarch64/mach_qemu/mod.rs +++ b/kernel/src/arch/aarch64/mach_qemu/mod.rs @@ -13,7 +13,7 @@ use crate::dev::{ }; use crate::fs::devfs::{self, CharDeviceType}; use crate::mem::phys; -use error::Errno; +use libsys::error::Errno; pub use gic::IrqNumber; @@ -84,6 +84,6 @@ pub fn ipi_sender() -> &'static Gic { static UART0: Pl011 = unsafe { Pl011::new(UART0_BASE, UART0_IRQ) }; static RTC: Pl031 = unsafe { Pl031::new(RTC_BASE, RTC_IRQ) }; -static GIC: Gic = unsafe { Gic::new(GICD_BASE, GICC_BASE, LOCAL_TIMER_IRQ) }; +static GIC: Gic = unsafe { Gic::new(GICD_BASE, GICC_BASE) }; static PCIE: GenericPcieHost = unsafe { GenericPcieHost::new(ECAM_BASE, 8) }; static LOCAL_TIMER: GenericTimer = GenericTimer::new(LOCAL_TIMER_IRQ); diff --git a/kernel/src/arch/aarch64/mach_rpi3/emmc.rs b/kernel/src/arch/aarch64/mach_rpi3/emmc.rs index b96835a..68119ea 100644 --- a/kernel/src/arch/aarch64/mach_rpi3/emmc.rs +++ b/kernel/src/arch/aarch64/mach_rpi3/emmc.rs @@ -7,7 +7,7 @@ use crate::dev::Device; use crate::mem::virt::DeviceMemoryIo; use crate::sync::IrqSafeSpinLock; use crate::util::InitOnce; -use error::Errno; +use libsys::error::Errno; use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; use tock_registers::registers::{ReadOnly, ReadWrite}; use tock_registers::{register_bitfields, register_structs}; diff --git a/kernel/src/arch/aarch64/mach_rpi3/irqchip.rs b/kernel/src/arch/aarch64/mach_rpi3/irqchip.rs index 0e77072..dbb903f 100644 --- a/kernel/src/arch/aarch64/mach_rpi3/irqchip.rs +++ b/kernel/src/arch/aarch64/mach_rpi3/irqchip.rs @@ -7,7 +7,7 @@ use crate::sync::IrqSafeSpinLock; use crate::util::InitOnce; use core::fmt; use cortex_a::registers::MPIDR_EL1; -use error::Errno; +use libsys::error::Errno; use tock_registers::interfaces::{Readable, Writeable}; use tock_registers::register_structs; diff --git a/kernel/src/arch/aarch64/mach_rpi3/mailbox.rs b/kernel/src/arch/aarch64/mach_rpi3/mailbox.rs index bdfa1e1..359b6d2 100644 --- a/kernel/src/arch/aarch64/mach_rpi3/mailbox.rs +++ b/kernel/src/arch/aarch64/mach_rpi3/mailbox.rs @@ -2,7 +2,7 @@ use crate::dev::Device; use crate::mem::{self, virt::DeviceMemoryIo}; use crate::sync::IrqSafeSpinLock; use crate::util::InitOnce; -use error::Errno; +use libsys::error::Errno; use tock_registers::interfaces::{Readable, Writeable}; use tock_registers::registers::{ReadOnly, WriteOnly}; diff --git a/kernel/src/arch/aarch64/mach_rpi3/mod.rs b/kernel/src/arch/aarch64/mach_rpi3/mod.rs index 91cbf7c..a7c6d79 100644 --- a/kernel/src/arch/aarch64/mach_rpi3/mod.rs +++ b/kernel/src/arch/aarch64/mach_rpi3/mod.rs @@ -5,7 +5,7 @@ use crate::dev::{ Device, }; use crate::mem::phys; -use error::Errno; +use libsys::error::Errno; pub mod irqchip; pub use irqchip::{Bcm283xIrqchip, IrqNumber}; diff --git a/kernel/src/arch/aarch64/smp.rs b/kernel/src/arch/aarch64/smp.rs index b7b6bda..c55e9fb 100644 --- a/kernel/src/arch/aarch64/smp.rs +++ b/kernel/src/arch/aarch64/smp.rs @@ -10,7 +10,7 @@ use crate::mem::{ phys::{self, PageUsage}, }; use cortex_a::registers::MPIDR_EL1; -use error::Errno; +use libsys::error::Errno; use fdt_rs::prelude::*; use tock_registers::interfaces::Readable; diff --git a/kernel/src/arch/aarch64/timer.rs b/kernel/src/arch/aarch64/timer.rs index 57408ae..96f51dd 100644 --- a/kernel/src/arch/aarch64/timer.rs +++ b/kernel/src/arch/aarch64/timer.rs @@ -8,7 +8,7 @@ use crate::dev::{ }; use core::time::Duration; use cortex_a::registers::{CNTFRQ_EL0, CNTPCT_EL0, CNTP_CTL_EL0, CNTP_TVAL_EL0}; -use error::Errno; +use libsys::error::Errno; use tock_registers::interfaces::{Readable, Writeable}; /// Generic timer struct @@ -34,15 +34,9 @@ impl IntSource for GenericTimer { fn handle_irq(&self) -> Result<(), Errno> { CNTP_TVAL_EL0.set(TIMER_TICK); CNTP_CTL_EL0.write(CNTP_CTL_EL0::ENABLE::SET); - todo!(); -//<<<<<<< HEAD -// use crate::proc; -// proc::wait::tick(); -// proc::switch(); -//======= -// use crate::proc::sched; -// sched::switch(false); -//>>>>>>> 61fa9d1 (feat: dirty smp) + use crate::proc::{wait, sched}; + wait::tick(); + sched::switch(false); Ok(()) } diff --git a/kernel/src/debug.rs b/kernel/src/debug.rs index c94d583..26e6b83 100644 --- a/kernel/src/debug.rs +++ b/kernel/src/debug.rs @@ -13,6 +13,7 @@ use crate::dev::serial::SerialDevice; use crate::sync::IrqSafeSpinLock; +use libsys::debug::TraceLevel; use core::fmt; /// Kernel logging levels @@ -28,6 +29,18 @@ pub enum Level { Error, } +impl From for Level { + #[inline(always)] + fn from(l: TraceLevel) -> Self { + match l { + TraceLevel::Debug => Self::Debug, + TraceLevel::Info => Self::Info, + TraceLevel::Warn => Self::Warn, + TraceLevel::Error => Self::Error, + } + } +} + struct SerialOutput { inner: &'static T, } diff --git a/kernel/src/dev/fdt.rs b/kernel/src/dev/fdt.rs index e81deb7..b0d2aaa 100644 --- a/kernel/src/dev/fdt.rs +++ b/kernel/src/dev/fdt.rs @@ -1,6 +1,5 @@ //! Device tree facilities use crate::debug::Level; -use error::Errno; use fdt_rs::prelude::*; use fdt_rs::{ base::DevTree, @@ -8,7 +7,7 @@ use fdt_rs::{ iters::DevTreeIndexCompatibleNodeIter, DevTreeIndex, DevTreeIndexNode, DevTreeIndexProp, }, }; -use libcommon::path_component_left; +use libsys::{error::Errno, path::path_component_left}; #[repr(align(16))] struct Wrap { diff --git a/kernel/src/dev/gpio.rs b/kernel/src/dev/gpio.rs index 7cf11fe..d79072b 100644 --- a/kernel/src/dev/gpio.rs +++ b/kernel/src/dev/gpio.rs @@ -1,7 +1,7 @@ //! GPIO and pin control interfaces use crate::dev::Device; -use error::Errno; +use libsys::error::Errno; /// Pin function mode pub enum PinMode { diff --git a/kernel/src/dev/irq.rs b/kernel/src/dev/irq.rs index 373c49f..b7e989f 100644 --- a/kernel/src/dev/irq.rs +++ b/kernel/src/dev/irq.rs @@ -2,7 +2,7 @@ use crate::arch::platform::smp::NodeAddress; use crate::dev::Device; use core::marker::PhantomData; -use error::Errno; +use libsys::error::Errno; /// Token to indicate the local core is running in IRQ context pub struct IrqContext<'irq_context> { diff --git a/kernel/src/dev/mod.rs b/kernel/src/dev/mod.rs index b0e9dae..fdaf7a8 100644 --- a/kernel/src/dev/mod.rs +++ b/kernel/src/dev/mod.rs @@ -1,6 +1,6 @@ //! Module for device interfaces and drivers -use error::Errno; +use libsys::error::Errno; // Device classes pub mod fdt; diff --git a/kernel/src/dev/pci/mod.rs b/kernel/src/dev/pci/mod.rs index 142c75f..922cbb1 100644 --- a/kernel/src/dev/pci/mod.rs +++ b/kernel/src/dev/pci/mod.rs @@ -2,7 +2,7 @@ use crate::dev::Device; use core::fmt; -use error::Errno; +use libsys::error::Errno; pub mod pcie; diff --git a/kernel/src/dev/pci/pcie/gpex.rs b/kernel/src/dev/pci/pcie/gpex.rs index ee215d2..c988aab 100644 --- a/kernel/src/dev/pci/pcie/gpex.rs +++ b/kernel/src/dev/pci/pcie/gpex.rs @@ -6,7 +6,7 @@ use crate::dev::{ }; use crate::mem::virt::DeviceMemory; use crate::util::InitOnce; -use error::Errno; +use libsys::error::Errno; /// GPEX host controller struct pub struct GenericPcieHost { diff --git a/kernel/src/dev/rtc/pl031.rs b/kernel/src/dev/rtc/pl031.rs index cf6e6b6..429c5ca 100644 --- a/kernel/src/dev/rtc/pl031.rs +++ b/kernel/src/dev/rtc/pl031.rs @@ -8,7 +8,7 @@ use crate::dev::{ use crate::mem::virt::DeviceMemoryIo; use crate::sync::IrqSafeSpinLock; use crate::util::InitOnce; -use error::Errno; +use libsys::error::Errno; use tock_registers::{ interfaces::{ReadWriteable, Readable, Writeable}, register_bitfields, register_structs, diff --git a/kernel/src/dev/sd.rs b/kernel/src/dev/sd.rs index fe60b98..b5cdd04 100644 --- a/kernel/src/dev/sd.rs +++ b/kernel/src/dev/sd.rs @@ -1,6 +1,6 @@ //! SD host controller interface and card operation facilities use crate::dev::Device; -use error::Errno; +use libsys::error::Errno; use vfs::BlockDevice; /// Generic SD/MMC host controller interface diff --git a/kernel/src/dev/serial/mod.rs b/kernel/src/dev/serial/mod.rs index 2f84e84..ed7f120 100644 --- a/kernel/src/dev/serial/mod.rs +++ b/kernel/src/dev/serial/mod.rs @@ -1,7 +1,7 @@ //! Module for serial device drivers use crate::dev::Device; -use error::Errno; +use libsys::error::Errno; #[cfg(feature = "pl011")] pub mod pl011; diff --git a/kernel/src/dev/serial/pl011.rs b/kernel/src/dev/serial/pl011.rs index 29aa1c4..a0eb947 100644 --- a/kernel/src/dev/serial/pl011.rs +++ b/kernel/src/dev/serial/pl011.rs @@ -10,14 +10,12 @@ use crate::dev::{ use crate::mem::virt::DeviceMemoryIo; use crate::sync::IrqSafeSpinLock; use crate::util::InitOnce; -use core::fmt; -use error::Errno; +use libsys::error::Errno; use tock_registers::{ interfaces::{ReadWriteable, Readable, Writeable}, register_bitfields, register_structs, registers::{ReadOnly, ReadWrite, WriteOnly}, }; -use vfs::{CharDevice, IoctlCmd}; register_bitfields! { u32, @@ -78,6 +76,7 @@ struct Pl011Inner { } /// Device struct for PL011 +#[derive(TtyCharDevice)] pub struct Pl011 { inner: InitOnce>, ring: CharRing<16>, @@ -117,16 +116,16 @@ impl Pl011Inner { } } -impl fmt::Write for Pl011Inner { - fn write_str(&mut self, s: &str) -> fmt::Result { - for &c in s.as_bytes() { - unsafe { - self.send(c); - } - } - Ok(()) - } -} +// impl fmt::Write for Pl011Inner { +// fn write_str(&mut self, s: &str) -> fmt::Result { +// for &c in s.as_bytes() { +// unsafe { +// self.send(c); +// } +// } +// Ok(()) +// } +// } impl IntSource for Pl011 { fn handle_irq(&self) -> Result<(), Errno> { @@ -167,21 +166,21 @@ impl SerialDevice for Pl011 { } } -impl CharDevice for Pl011 { - fn read(&self, blocking: bool, data: &mut [u8]) -> Result { - assert!(blocking); - self.line_read(data) - } - - fn write(&self, blocking: bool, data: &[u8]) -> Result { - assert!(blocking); - self.line_write(data) - } - - fn ioctl(&self, cmd: IoctlCmd, ptr: usize, len: usize) -> Result { - self.tty_ioctl(cmd, ptr, len) - } -} +// impl CharDevice for Pl011 { +// fn read(&self, blocking: bool, data: &mut [u8]) -> Result { +// assert!(blocking); +// self.line_read(data) +// } +// +// fn write(&self, blocking: bool, data: &[u8]) -> Result { +// assert!(blocking); +// self.line_write(data) +// } +// +// fn ioctl(&self, cmd: IoctlCmd, ptr: usize, len: usize) -> Result { +// self.tty_ioctl(cmd, ptr, len) +// } +// } impl TtyDevice<16> for Pl011 { fn ring(&self) -> &CharRing<16> { diff --git a/kernel/src/dev/timer.rs b/kernel/src/dev/timer.rs index 4494fda..3d24525 100644 --- a/kernel/src/dev/timer.rs +++ b/kernel/src/dev/timer.rs @@ -2,7 +2,7 @@ use crate::dev::Device; use core::time::Duration; -use error::Errno; +use libsys::error::Errno; /// Interface for generic timestamp source pub trait TimestampSource: Device { diff --git a/kernel/src/dev/tty.rs b/kernel/src/dev/tty.rs index 6a87eba..2d9300f 100644 --- a/kernel/src/dev/tty.rs +++ b/kernel/src/dev/tty.rs @@ -1,15 +1,16 @@ //! Teletype (TTY) device facilities use crate::dev::serial::SerialDevice; -use crate::proc::wait::Wait; +use crate::proc::{Process, wait::{Wait, WAIT_SELECT}}; use crate::sync::IrqSafeSpinLock; -use error::Errno; -use syscall::{ +use libsys::error::Errno; +use libsys::{ termios::{Termios, TermiosIflag, TermiosLflag, TermiosOflag}, + proc::Pid, + signal::Signal, ioctl::IoctlCmd }; use core::mem::size_of; -use crate::syscall::arg::validate_user_ptr_struct; -use vfs::CharDevice; +use crate::syscall::arg; #[derive(Debug)] struct CharRingInner { @@ -17,6 +18,7 @@ struct CharRingInner { wr: usize, data: [u8; N], flags: u8, + fg_pgid: Option, } /// Ring buffer for TTYs @@ -27,26 +29,45 @@ pub struct CharRing { inner: IrqSafeSpinLock>, } +/// Generic teletype device interface pub trait TtyDevice: SerialDevice { + /// Returns a reference to character device's ring buffer fn ring(&self) -> &CharRing; - fn tty_ioctl(&self, cmd: IoctlCmd, ptr: usize, len: usize) -> Result { + /// Returns `true` if the TTY is ready for an operation + fn is_ready(&self, write: bool) -> Result { + let ring = self.ring(); + if write { + todo!() + } else { + Ok(ring.is_readable()) + } + } + + /// Performs a TTY control request + fn tty_ioctl(&self, cmd: IoctlCmd, ptr: usize, _len: usize) -> Result { match cmd { IoctlCmd::TtyGetAttributes => { // TODO validate size - let res = validate_user_ptr_struct::(ptr)?; + let res = arg::struct_mut::(ptr)?; *res = self.ring().config.lock().clone(); Ok(size_of::()) }, IoctlCmd::TtySetAttributes => { - let src = validate_user_ptr_struct::(ptr)?; + let src = arg::struct_ref::(ptr)?; *self.ring().config.lock() = src.clone(); Ok(size_of::()) }, + IoctlCmd::TtySetPgrp => { + let src = arg::struct_ref::(ptr)?; + self.ring().inner.lock().fg_pgid = Some(unsafe { Pid::from_raw(*src) }); + Ok(0) + }, _ => Err(Errno::InvalidArgument) } } + /// Processes and writes output an output byte fn line_send(&self, byte: u8) -> Result<(), Errno> { let config = self.ring().config.lock(); @@ -57,10 +78,19 @@ pub trait TtyDevice: SerialDevice { self.send(byte) } + /// Receives input bytes and processes them fn recv_byte(&self, mut byte: u8) { let ring = self.ring(); let config = ring.config.lock(); + if byte == b'@' { + use crate::mem::phys; + let stat = phys::statistics(); + debugln!("Physical memory stats:"); + debugln!("{:#?}", stat); + return; + } + if byte == b'\r' && config.iflag.contains(TermiosIflag::ICRNL) { byte = b'\n'; } @@ -89,6 +119,19 @@ pub trait TtyDevice: SerialDevice { } } + if byte == 0x3 && config.lflag.contains(TermiosLflag::ISIG) { + drop(config); + let pgid = ring.inner.lock().fg_pgid; + if let Some(pgid) = pgid { + // TODO send to pgid + let proc = Process::get(pgid); + if let Some(proc) = proc { + proc.set_signal(Signal::Interrupt); + } + } + return; + } + self.ring().putc(byte, false).ok(); } @@ -97,7 +140,7 @@ pub trait TtyDevice: SerialDevice { let ring = self.ring(); let mut config = ring.config.lock(); - if data.len() == 0 { + if data.is_empty() { return Ok(0); } @@ -105,7 +148,7 @@ pub trait TtyDevice: SerialDevice { drop(config); let byte = ring.getc()?; data[0] = byte; - return Ok(1); + Ok(1) } else { let mut rem = data.len(); let mut off = 0; @@ -132,7 +175,7 @@ pub trait TtyDevice: SerialDevice { let idx = data[..off].iter().rposition(|&ch| ch == b' ').unwrap_or(0); let len = off; - for i in idx..len { + for _ in idx..len { self.raw_write(b"\x1B[D \x1B[D").ok(); off -= 1; rem += 1; @@ -165,6 +208,7 @@ pub trait TtyDevice: SerialDevice { } } + /// Processes and writes string bytes fn line_write(&self, data: &[u8]) -> Result { for &byte in data.iter() { self.line_send(byte)?; @@ -172,6 +216,7 @@ pub trait TtyDevice: SerialDevice { Ok(data.len()) } + /// Writes string bytes without any processing fn raw_write(&self, data: &[u8]) -> Result { for &byte in data.iter() { self.send(byte)?; @@ -209,14 +254,48 @@ impl CharRing { pub const fn new() -> Self { Self { inner: IrqSafeSpinLock::new(CharRingInner { + fg_pgid: None, rd: 0, wr: 0, data: [0; N], flags: 0, }), config: IrqSafeSpinLock::new(Termios::new()), - wait_read: Wait::new(), - wait_write: Wait::new(), + wait_read: Wait::new("tty_read"), + wait_write: Wait::new("tty_write"), + } + } + + /// Returns `true` if a character/line is available for reception + pub fn is_readable(&self) -> bool { + let inner = self.inner.lock(); + let config = self.config.lock(); + if config.lflag.contains(TermiosLflag::ICANON) { + // TODO optimize this somehow? + let mut rd = inner.rd; + let mut count = 0usize; + loop { + let readable = if rd <= inner.wr { + (inner.wr - rd) > 0 + } else { + (inner.wr + (N - rd)) > 0 + }; + + if !readable { + break; + } + + let byte = inner.data[rd]; + if byte == b'\n' || byte == config.chars.eof { + count += 1; + } + + rd = (rd + 1) % N; + } + + count != 0 || inner.flags != 0 + } else { + inner.is_readable() || inner.flags != 0 } } @@ -232,15 +311,10 @@ impl CharRing { break; } } - if lock.flags != 0 { - if lock.flags & (1 << 0) != 0 { - lock.flags &= !(1 << 0); - return Err(Errno::EndOfFile); - } - todo!(); - } let byte = lock.read_unchecked(); + drop(lock); self.wait_write.wakeup_one(); + WAIT_SELECT.wakeup_all(); Ok(byte) } @@ -251,7 +325,9 @@ impl CharRing { todo!() } lock.write_unchecked(ch); + drop(lock); self.wait_read.wakeup_one(); + WAIT_SELECT.wakeup_all(); Ok(()) } } diff --git a/kernel/src/fs/devfs.rs b/kernel/src/fs/devfs.rs index 3de2ad1..3cb59c7 100644 --- a/kernel/src/fs/devfs.rs +++ b/kernel/src/fs/devfs.rs @@ -2,7 +2,7 @@ use crate::util::InitOnce; use alloc::boxed::Box; use core::sync::atomic::{AtomicUsize, Ordering}; -use error::Errno; +use libsys::{stat::FileMode, error::Errno}; use vfs::{CharDevice, CharDeviceWrapper, Vnode, VnodeKind, VnodeRef}; /// Possible character device kinds @@ -16,7 +16,9 @@ static DEVFS_ROOT: InitOnce = InitOnce::new(); /// Initializes devfs pub fn init() { - DEVFS_ROOT.init(Vnode::new("", VnodeKind::Directory, 0)); + let node = Vnode::new("", VnodeKind::Directory, Vnode::CACHE_READDIR | Vnode::CACHE_STAT); + node.props_mut().mode = FileMode::default_dir(); + DEVFS_ROOT.init(node); } /// Returns devfs root node reference @@ -27,7 +29,8 @@ pub fn root() -> &'static VnodeRef { fn _add_char_device(dev: &'static dyn CharDevice, name: &str) -> Result<(), Errno> { infoln!("Add char device: {}", name); - let node = Vnode::new(name, VnodeKind::Char, 0); + let node = Vnode::new(name, VnodeKind::Char, Vnode::CACHE_STAT); + node.props_mut().mode = FileMode::from_bits(0o600).unwrap() | FileMode::S_IFCHR; node.set_data(Box::new(CharDeviceWrapper::new(dev))); DEVFS_ROOT.get().attach(node); diff --git a/kernel/src/fs/mod.rs b/kernel/src/fs/mod.rs index 2773a42..acfa1b8 100644 --- a/kernel/src/fs/mod.rs +++ b/kernel/src/fs/mod.rs @@ -3,6 +3,8 @@ use crate::mem::{ self, phys::{self, PageUsage}, }; +use libsys::{error::Errno, stat::MountOptions}; +use vfs::VnodeRef; use memfs::BlockAllocator; pub mod devfs; @@ -25,3 +27,14 @@ unsafe impl BlockAllocator for MemfsBlockAlloc { phys::free_page(phys).unwrap(); } } + +/// Creates a filesystem instance based on `options` +pub fn create_filesystem(options: &MountOptions) -> Result { + let fs_name = options.fs.unwrap(); + + if fs_name == "devfs" { + Ok(devfs::root().clone()) + } else { + todo!(); + } +} diff --git a/kernel/src/init.rs b/kernel/src/init.rs index 0b5f408..db7789f 100644 --- a/kernel/src/init.rs +++ b/kernel/src/init.rs @@ -4,8 +4,9 @@ use crate::config::{ConfigKey, CONFIG}; use crate::fs::{devfs, MemfsBlockAlloc}; use crate::mem; use crate::proc::{elf, Process}; +use libsys::stat::{FileDescriptor, OpenFlags, UserId, GroupId}; use memfs::Ramfs; -use vfs::{Filesystem, Ioctx, OpenFlags}; +use vfs::{Filesystem, Ioctx}; /// Kernel init process function #[inline(never)] @@ -28,7 +29,7 @@ pub extern "C" fn init_fn(_arg: usize) -> ! { unsafe { Ramfs::open(initrd_start as *mut u8, initrd_size, MemfsBlockAlloc {}).unwrap() }; let root = fs.root().unwrap(); - let ioctx = Ioctx::new(root); + let ioctx = Ioctx::new(root, UserId::root(), GroupId::root()); let node = ioctx.find(None, "/init", true).unwrap(); let file = node.open(OpenFlags::O_RDONLY | OpenFlags::O_EXEC).unwrap(); @@ -50,13 +51,14 @@ pub extern "C" fn init_fn(_arg: usize) -> ! { let stdout = tty_node.open(OpenFlags::O_WRONLY).unwrap(); let stderr = stdout.clone(); - io.set_file(0, stdin).unwrap(); - io.set_file(1, stdout).unwrap(); - io.set_file(2, stderr).unwrap(); + io.set_file(FileDescriptor::STDIN, stdin).unwrap(); + io.set_file(FileDescriptor::STDOUT, stdout).unwrap(); + io.set_file(FileDescriptor::STDERR, stderr).unwrap(); + io.set_ctty(tty_node); } drop(cfg); - Process::execve(|space| elf::load_elf(space, file), 0).unwrap(); + Process::execve(|space| elf::load_elf(space, file), &["/init"]).unwrap(); panic!("Unreachable"); } diff --git a/kernel/src/main.rs b/kernel/src/main.rs index a9afcc1..47df56f 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -19,6 +19,8 @@ #![no_main] #![warn(missing_docs)] +#[macro_use] +extern crate kernel_macros; #[macro_use] extern crate cfg_if; #[macro_use] diff --git a/kernel/src/mem/mod.rs b/kernel/src/mem/mod.rs index 878494c..3f2433e 100644 --- a/kernel/src/mem/mod.rs +++ b/kernel/src/mem/mod.rs @@ -26,73 +26,3 @@ pub fn kernel_end_phys() -> usize { } unsafe { &__kernel_end as *const _ as usize - KERNEL_OFFSET } } - -/// See memcpy(3p). -/// -/// # Safety -/// -/// Unsafe: writes to arbitrary memory locations, performs no pointer -/// validation. -#[no_mangle] -pub unsafe extern "C" fn memcpy(dst: *mut u8, src: *mut u8, mut len: usize) -> *mut u8 { - while len != 0 { - len -= 1; - *dst.add(len) = *src.add(len); - } - dst -} - -/// See memcmp(3p). -/// -/// # Safety -/// -/// Unsafe: performs reads from arbitrary memory locations, performs no -/// pointer validation. -#[no_mangle] -pub unsafe extern "C" fn memcmp(a: *mut u8, b: *mut u8, mut len: usize) -> isize { - while len != 0 { - len -= 1; - if *a.add(len) < *b.add(len) { - return -1; - } - if *a.add(len) > *b.add(len) { - return 1; - } - } - 0 -} - -/// See memmove(3p) -/// -/// # Safety -/// -/// Unsafe: writes to arbitrary memory locations, performs no pointer -/// validation. -#[no_mangle] -pub unsafe extern "C" fn memmove(dst: *mut u8, src: *mut u8, len: usize) -> *mut u8 { - if dst < src { - for i in 0..len { - *dst.add(i) = *src.add(i); - } - } else { - for i in 0..len { - *dst.add(len - (i + 1)) = *src.add(len - (i + 1)); - } - } - dst -} - -/// See memset(3p) -/// -/// # Safety -/// -/// Unsafe: writes to arbitrary memory locations, performs no pointer -/// validation. -#[no_mangle] -pub unsafe extern "C" fn memset(buf: *mut u8, val: u8, mut len: usize) -> *mut u8 { - while len != 0 { - len -= 1; - *buf.add(len) = val; - } - buf -} diff --git a/kernel/src/mem/phys/manager.rs b/kernel/src/mem/phys/manager.rs index 0b3fdc4..de4ba15 100644 --- a/kernel/src/mem/phys/manager.rs +++ b/kernel/src/mem/phys/manager.rs @@ -1,8 +1,8 @@ -use super::{PageInfo, PageUsage}; -use crate::mem::{memcpy, memset, virtualize, PAGE_SIZE}; +use super::{PageInfo, PageUsage, PageStatistics}; +use crate::mem::{virtualize, PAGE_SIZE}; use crate::sync::IrqSafeSpinLock; use core::mem; -use error::Errno; +use libsys::{error::Errno, mem::memcpy}; pub unsafe trait Manager { fn alloc_page(&mut self, pu: PageUsage) -> Result; @@ -10,11 +10,14 @@ pub unsafe trait Manager { fn free_page(&mut self, page: usize) -> Result<(), Errno>; fn copy_cow_page(&mut self, src: usize) -> Result; fn fork_page(&mut self, src: usize) -> Result; + fn statistics(&self) -> PageStatistics; // TODO status() } pub struct SimpleManager { pages: &'static mut [PageInfo], + stats: PageStatistics, base_index: usize, + last_index: usize, } impl SimpleManager { pub(super) unsafe fn initialize(base: usize, at: usize, count: usize) -> Self { @@ -32,6 +35,15 @@ impl SimpleManager { } Self { base_index: base / PAGE_SIZE, + last_index: 0, + stats: PageStatistics { + available: 0, + kernel: 0, + kernel_heap: 0, + paging: 0, + user_private: 0, + filesystem: 0 + }, pages, } } @@ -39,6 +51,7 @@ impl SimpleManager { let page = &mut self.pages[self.page_index(addr)]; assert!(page.refcount == 0 && page.usage == PageUsage::Reserved); page.usage = PageUsage::Available; + self.stats.available += 1; } fn page_index(&self, page: usize) -> usize { @@ -46,21 +59,61 @@ impl SimpleManager { } fn alloc_single_index(&mut self, pu: PageUsage) -> Result { - for index in 0..self.pages.len() { + for index in self.last_index..self.pages.len() { let page = &mut self.pages[index]; if page.usage == PageUsage::Available { page.usage = pu; page.refcount = 1; + self.last_index = index; + return Ok(index); + } + } + for index in 0..self.last_index { + let page = &mut self.pages[index]; + if page.usage == PageUsage::Available { + page.usage = pu; + page.refcount = 1; + self.last_index = index; return Ok(index); } } Err(Errno::OutOfMemory) } + + fn update_stats_alloc(&mut self, pu: PageUsage, count: usize) { + let field = match pu { + PageUsage::Kernel => &mut self.stats.kernel, + PageUsage::KernelHeap => &mut self.stats.kernel_heap, + PageUsage::Paging => &mut self.stats.paging, + PageUsage::UserPrivate => &mut self.stats.user_private, + PageUsage::Filesystem => &mut self.stats.filesystem, + _ => panic!("TODO {:?}", pu), + }; + *field += count; + self.stats.available -= count; + } + + // fn update_stats_free(&mut self, pu: PageUsage, count: usize) { + // let field = match pu { + // PageUsage::Kernel => &mut self.stats.kernel, + // PageUsage::KernelHeap => &mut self.stats.kernel_heap, + // PageUsage::Paging => &mut self.stats.paging, + // PageUsage::UserPrivate => &mut self.stats.user_private, + // PageUsage::Filesystem => &mut self.stats.filesystem, + // _ => panic!("TODO {:?}", pu), + // }; + // *field -= count; + // self.stats.available += count; + // } } unsafe impl Manager for SimpleManager { fn alloc_page(&mut self, pu: PageUsage) -> Result { - self.alloc_single_index(pu) - .map(|r| (self.base_index + r) * PAGE_SIZE) + let res = self.alloc_single_index(pu) + .map(|r| (self.base_index + r) * PAGE_SIZE); + if res.is_ok() { + self.update_stats_alloc(pu, 1); + } + res } fn alloc_contiguous_pages(&mut self, pu: PageUsage, count: usize) -> Result { 'l0: for i in 0..self.pages.len() { @@ -75,6 +128,7 @@ unsafe impl Manager for SimpleManager { page.usage = pu; page.refcount = 1; } + self.update_stats_alloc(pu, count); return Ok((self.base_index + i) * PAGE_SIZE); } Err(Errno::OutOfMemory) @@ -91,31 +145,40 @@ unsafe impl Manager for SimpleManager { assert_eq!(page.refcount, 1); page.usage = PageUsage::Available; page.refcount = 0; + + self.last_index = index; } + + // FIXME + // self.update_stats_free(usage, 1); + Ok(()) } fn copy_cow_page(&mut self, src: usize) -> Result { let src_index = self.page_index(src); - let page = &mut self.pages[src_index]; - let usage = page.usage; - if usage != PageUsage::UserPrivate { - panic!("CoW not available for non-UserPrivate pages: {:?}", usage); - } + let (usage, refcount) = { + let page = &mut self.pages[src_index]; + let usage = page.usage; + if usage != PageUsage::UserPrivate { + panic!("CoW not available for non-UserPrivate pages: {:?}", usage); + } + let count = page.refcount; + if count > 1 { + page.refcount -= 1; + } + (usage, count) + }; - if page.refcount > 1 { - page.refcount -= 1; - drop(page); + if refcount == 0 { + Ok(src) + } else { let dst_index = self.alloc_single_index(usage)?; let dst = (self.base_index + dst_index) * PAGE_SIZE; unsafe { memcpy(virtualize(dst) as *mut u8, virtualize(src) as *mut u8, 4096); } Ok(dst) - } else { - assert_eq!(page.refcount, 1); - // No additional operations needed - Ok(src) } } @@ -130,6 +193,10 @@ unsafe impl Manager for SimpleManager { } Ok(src) } + + fn statistics(&self) -> PageStatistics { + self.stats.clone() + } } pub(super) static MANAGER: IrqSafeSpinLock> = IrqSafeSpinLock::new(None); diff --git a/kernel/src/mem/phys/mod.rs b/kernel/src/mem/phys/mod.rs index 4a395ac..f90b072 100644 --- a/kernel/src/mem/phys/mod.rs +++ b/kernel/src/mem/phys/mod.rs @@ -3,7 +3,7 @@ use crate::config::{ConfigKey, CONFIG}; use crate::mem::PAGE_SIZE; use core::mem::size_of; -use error::Errno; +use libsys::error::Errno; mod manager; mod reserved; @@ -32,6 +32,18 @@ pub enum PageUsage { Filesystem, } +/// Represents counts of allocated/available pages +#[allow(missing_docs)] +#[derive(Clone, Debug)] +pub struct PageStatistics { + pub available: usize, + pub kernel: usize, + pub kernel_heap: usize, + pub paging: usize, + pub user_private: usize, + pub filesystem: usize, +} + /// Data structure representing a single physical memory page pub struct PageInfo { refcount: usize, @@ -66,18 +78,57 @@ impl Iterator for SimpleMemoryIterator { } } +#[cfg(feature = "verbose")] +fn trace_alloc(loc: &core::panic::Location, pu: PageUsage, base: usize, count: usize) { + use crate::debug::Level; + println!( + Level::Debug, + "\x1B[36;1m[phys/alloc] {}:{} {:?} {:#x}..{:#x}\x1B[0m", + loc.file(), + loc.line(), + pu, + base, + base + count * PAGE_SIZE + ); +} + +#[cfg(feature = "verbose")] +fn trace_free(loc: &core::panic::Location, page: usize) { + use crate::debug::Level; + println!( + Level::Debug, + "\x1B[36;1m[phys/free] {}:{} {:#x}..{:#x}\x1B[0m", + loc.file(), + loc.line(), + page, + page + PAGE_SIZE + ); +} + /// Allocates a contiguous range of `count` physical memory pages. +#[cfg_attr(feature = "verbose", track_caller)] pub fn alloc_contiguous_pages(pu: PageUsage, count: usize) -> Result { - MANAGER + let res = MANAGER .lock() .as_mut() .unwrap() - .alloc_contiguous_pages(pu, count) + .alloc_contiguous_pages(pu, count); + #[cfg(feature = "verbose")] + if let Ok(base) = res { + trace_alloc(&core::panic::Location::caller(), pu, base, count); + } + res } /// Allocates a single physical memory page. +#[cfg_attr(feature = "verbose", track_caller)] pub fn alloc_page(pu: PageUsage) -> Result { - MANAGER.lock().as_mut().unwrap().alloc_page(pu) + let res = MANAGER.lock().as_mut().unwrap().alloc_page(pu); + #[cfg(feature = "verbose")] + if let Ok(base) = res { + trace_alloc(&core::panic::Location::caller(), pu, base, 1); + } + res } /// Releases a single physical memory page back for further allocation. @@ -85,10 +136,26 @@ pub fn alloc_page(pu: PageUsage) -> Result { /// # Safety /// /// Unsafe: accepts arbitrary `page` arguments +#[cfg_attr(feature = "verbose", track_caller)] pub unsafe fn free_page(page: usize) -> Result<(), Errno> { + #[cfg(feature = "verbose")] + { + trace_free(&core::panic::Location::caller(), page); + } MANAGER.lock().as_mut().unwrap().free_page(page) } +/// Returns current statistics for page allocation +pub fn statistics() -> PageStatistics { + MANAGER.lock().as_ref().unwrap().statistics() +} + +/// Clones the source page. +/// +/// If returned address is the same as `page`, this means +/// `page`'s refcount has increased and the page is Copy-on-Write. +/// This case has to be handled accordingly +/// /// # Safety /// /// Unsafe: accepts arbitrary `page` arguments @@ -96,6 +163,12 @@ pub unsafe fn fork_page(page: usize) -> Result { MANAGER.lock().as_mut().unwrap().fork_page(page) } +/// Copies a Copy-on-Write page. If refcount is already 1, +/// page does not need to be copied and the same address is returned. +/// +/// # Safety +/// +/// Unsafe: accepts arbitrary `page` arguments pub unsafe fn copy_cow_page(page: usize) -> Result { MANAGER.lock().as_mut().unwrap().copy_cow_page(page) } diff --git a/kernel/src/mem/virt/fixed.rs b/kernel/src/mem/virt/fixed.rs index 1327828..5275576 100644 --- a/kernel/src/mem/virt/fixed.rs +++ b/kernel/src/mem/virt/fixed.rs @@ -5,7 +5,7 @@ use crate::mem::{ virt::{Entry, MapAttributes, Table}, }; use cortex_a::asm::barrier::{self, dsb, isb}; -use error::Errno; +use libsys::error::Errno; const DEVICE_MAP_OFFSET: usize = mem::KERNEL_OFFSET + (256usize << 30); diff --git a/kernel/src/mem/virt/mod.rs b/kernel/src/mem/virt/mod.rs index 97b8639..5e879e5 100644 --- a/kernel/src/mem/virt/mod.rs +++ b/kernel/src/mem/virt/mod.rs @@ -4,7 +4,7 @@ use core::marker::PhantomData; use core::ops::Deref; use cortex_a::asm::barrier::{self, dsb, isb}; use cortex_a::registers::TTBR0_EL1; -use error::Errno; +use libsys::error::Errno; use tock_registers::interfaces::Writeable; pub mod table; diff --git a/kernel/src/mem/virt/table.rs b/kernel/src/mem/virt/table.rs index 3f099b8..8ec0d4b 100644 --- a/kernel/src/mem/virt/table.rs +++ b/kernel/src/mem/virt/table.rs @@ -5,7 +5,7 @@ use crate::mem::{ phys::{self, PageUsage}, }; use core::ops::{Index, IndexMut}; -use error::Errno; +use libsys::{error::Errno, mem::memset}; /// Transparent wrapper structure representing a single /// translation table entry @@ -38,6 +38,7 @@ bitflags! { /// This page is used for device-MMIO mapping and uses MAIR attribute #1 const DEVICE = 1 << 2; + /// Pages marked with this bit are Copy-on-Write const EX_COW = 1 << 55; /// UXN bit -- if set, page may not be used for instruction fetching from EL0 @@ -204,11 +205,33 @@ impl Space { } else { l2_table[l2i] = Entry::table(phys, flags | MapAttributes::ACCESS); #[cfg(feature = "verbose")] - debugln!("Map {:#x} -> {:#x}", virt, phys); + debugln!("{:#p} Map {:#x} -> {:#x}, {:?}", self, virt, phys, flags); Ok(()) } } + /// Translates a virtual address into a corresponding physical one. + /// + /// Only works for 4K pages atm. + // TODO extract attributes + pub fn translate(&mut self, virt: usize) -> Result { + let l0i = virt >> 30; + let l1i = (virt >> 21) & 0x1FF; + let l2i = (virt >> 12) & 0x1FF; + + let l1_table = self.0.next_level_table(l0i).ok_or(Errno::DoesNotExist)?; + let l2_table = l1_table.next_level_table(l1i).ok_or(Errno::DoesNotExist)?; + + let entry = l2_table[l2i]; + if entry.is_present() { + Ok(unsafe { entry.address_unchecked() }) + } else { + Err(Errno::DoesNotExist) + } + } + + /// Attempts to resolve a page fault at `virt` address by copying the + /// underlying Copy-on-Write mapping (if any is present) pub fn try_cow_copy(&mut self, virt: usize) -> Result<(), Errno> { let virt = virt & !0xFFF; let l0i = virt >> 30; @@ -227,7 +250,11 @@ impl Space { let src_phys = unsafe { entry.address_unchecked() }; if !entry.is_cow() { - warnln!("Entry is not marked as CoW: {:#x}, points to {:#x}", virt, src_phys); + warnln!( + "Entry is not marked as CoW: {:#x}, points to {:#x}", + virt, + src_phys + ); return Err(Errno::DoesNotExist); } @@ -240,6 +267,71 @@ impl Space { Ok(()) } + /// Allocates a contiguous region from the address space and maps + /// physical pages to it + pub fn allocate( + &mut self, + start: usize, + end: usize, + len: usize, + flags: MapAttributes, + usage: PageUsage, + ) -> Result { + 'l0: for page in (start..end).step_by(0x1000) { + for i in 0..len { + if self.translate(page + i * 0x1000).is_ok() { + continue 'l0; + } + } + + for i in 0..len { + let phys = phys::alloc_page(usage).unwrap(); + self.map(page + i * 0x1000, phys, flags).unwrap(); + } + return Ok(page); + } + Err(Errno::OutOfMemory) + } + + /// Removes a single 4K page mapping from the table and + /// releases the underlying physical memory + pub fn unmap_single(&mut self, page: usize) -> Result<(), Errno> { + let l0i = page >> 30; + let l1i = (page >> 21) & 0x1FF; + let l2i = (page >> 12) & 0x1FF; + + let l1_table = self.0.next_level_table(l0i).ok_or(Errno::DoesNotExist)?; + let l2_table = l1_table.next_level_table(l1i).ok_or(Errno::DoesNotExist)?; + + let entry = l2_table[l2i]; + + if !entry.is_present() { + return Err(Errno::DoesNotExist); + } + + let phys = unsafe { entry.address_unchecked() }; + unsafe { + phys::free_page(phys)?; + } + l2_table[l2i] = Entry::invalid(); + + unsafe { + asm!("tlbi vaae1, {}", in(reg) page); + } + + // TODO release paging structure memory + + Ok(()) + } + + /// Releases a range of virtual pages and their corresponding physical pages + pub fn free(&mut self, start: usize, len: usize) -> Result<(), Errno> { + for i in 0..len { + self.unmap_single(start + i * 0x1000)?; + } + Ok(()) + } + /// Performs a copy of the address space, cloning data owned by it pub fn fork(&mut self) -> Result<&'static mut Self, Errno> { let res = Self::alloc_empty()?; @@ -264,13 +356,20 @@ impl Space { todo!(); // res.map(virt_addr, dst_phys, flags)?; } else { - // TODO only apply CoW to writable pages - flags |= MapAttributes::AP_BOTH_READONLY | MapAttributes::EX_COW; - l2_table[l2i].set_cow(); - unsafe { - asm!("tlbi vaae1, {}", in(reg) virt_addr); + let writable = flags & MapAttributes::AP_BOTH_READONLY + == MapAttributes::AP_BOTH_READWRITE; + + if writable { + flags |= + MapAttributes::AP_BOTH_READONLY | MapAttributes::EX_COW; + l2_table[l2i].set_cow(); + + unsafe { + asm!("tlbi vaae1, {}", in(reg) virt_addr); + } } - res.map(virt_addr, dst_phys, flags); + + res.map(virt_addr, dst_phys, flags)?; } } } @@ -280,6 +379,13 @@ impl Space { Ok(res) } + /// Releases all the mappings from the address space. Frees all + /// memory pages referenced by this space as well as those used for + /// its paging tables. + /// + /// # Safety + /// + /// Unsafe: may invalidate currently active address space pub unsafe fn release(space: &mut Self) { for l0i in 0..512 { let l0_entry = space.0[l0i]; @@ -288,8 +394,7 @@ impl Space { } assert!(l0_entry.is_table()); - let l1_table = - unsafe { &mut *(mem::virtualize(l0_entry.address_unchecked()) as *mut Table) }; + let l1_table = &mut *(mem::virtualize(l0_entry.address_unchecked()) as *mut Table); for l1i in 0..512 { let l1_entry = l1_table[l1i]; @@ -297,8 +402,7 @@ impl Space { continue; } assert!(l1_entry.is_table()); - let l2_table = - unsafe { &mut *(mem::virtualize(l1_entry.address_unchecked()) as *mut Table) }; + let l2_table = &mut *(mem::virtualize(l1_entry.address_unchecked()) as *mut Table); for l2i in 0..512 { let entry = l2_table[l2i]; @@ -307,20 +411,17 @@ impl Space { } assert!(entry.is_table()); - unsafe { - phys::free_page(unsafe { entry.address_unchecked() }); - } - } - unsafe { - phys::free_page(unsafe { l1_entry.address_unchecked() }); + phys::free_page(entry.address_unchecked()).unwrap(); } + phys::free_page(l1_entry.address_unchecked()).unwrap(); } - unsafe { - phys::free_page(unsafe { l0_entry.address_unchecked() }); - } - } - unsafe { - mem::memset(space as *mut Space as *mut u8, 0, 4096); + phys::free_page(l0_entry.address_unchecked()).unwrap(); } + memset(space as *mut Space as *mut u8, 0, 4096); + } + + /// Returns the physical address of this structure + pub fn address_phys(&mut self) -> usize { + (self as *mut _ as usize) - mem::KERNEL_OFFSET } } diff --git a/kernel/src/proc/elf.rs b/kernel/src/proc/elf.rs index 2f6c3d9..cfe3125 100644 --- a/kernel/src/proc/elf.rs +++ b/kernel/src/proc/elf.rs @@ -5,8 +5,10 @@ use crate::mem::{ virt::{MapAttributes, Space}, }; use core::mem::{size_of, MaybeUninit}; -use error::Errno; -use libcommon::{Read, Seek, SeekDir}; +use libsys::{ + error::Errno, + traits::{Read, Seek, SeekDir} +}; use vfs::FileRef; trait Elf { diff --git a/kernel/src/proc/io.rs b/kernel/src/proc/io.rs index 2267a10..c7ff1f9 100644 --- a/kernel/src/proc/io.rs +++ b/kernel/src/proc/io.rs @@ -1,12 +1,13 @@ //! Process file descriptors and I/O context use alloc::collections::BTreeMap; -use error::Errno; -use vfs::{FileRef, Ioctx}; +use libsys::{error::Errno, stat::{FileDescriptor, UserId, GroupId}}; +use vfs::{FileRef, Ioctx, VnodeRef, VnodeKind}; /// Process I/O context. Contains file tables, root/cwd info etc. pub struct ProcessIo { ioctx: Option, - files: BTreeMap, + files: BTreeMap, + ctty: Option, } impl ProcessIo { @@ -21,9 +22,76 @@ impl ProcessIo { Ok(dst) } + /// Sets controlling terminal for the process + pub fn set_ctty(&mut self, node: VnodeRef) { + assert_eq!(node.kind(), VnodeKind::Char); + self.ctty = Some(node); + } + + /// Returns current controlling terminal of the process + pub fn ctty(&mut self) -> Option { + self.ctty.clone() + } + + /// Returns user ID of the process + #[inline(always)] + pub fn uid(&self) -> UserId { + self.ioctx.as_ref().unwrap().uid + } + + /// Returns group ID of the process + #[inline(always)] + pub fn gid(&self) -> GroupId { + self.ioctx.as_ref().unwrap().gid + } + + /// Changes (if permitted) user ID of the process + #[inline(always)] + pub fn set_uid(&mut self, uid: UserId) -> Result<(), Errno> { + let old_uid = self.uid(); + if old_uid == uid { + Ok(()) + } else if !old_uid.is_root() { + Err(Errno::PermissionDenied) + } else { + self.ioctx.as_mut().unwrap().uid = uid; + Ok(()) + } + } + + /// Changes (if permitted) group ID of the process + #[inline(always)] + pub fn set_gid(&mut self, gid: GroupId) -> Result<(), Errno> { + let old_gid = self.gid(); + if old_gid == gid { + Ok(()) + } else if !old_gid.is_root() { + Err(Errno::PermissionDenied) + } else { + self.ioctx.as_mut().unwrap().gid = gid; + Ok(()) + } + } + + /// Clones a file descriptor into an available slot or, if specified, requested one + pub fn duplicate_file(&mut self, src: FileDescriptor, dst: Option) -> Result { + let file_ref = self.file(src)?; + if let Some(dst) = dst { + let idx = u32::from(dst); + if self.files.get(&idx).is_some() { + return Err(Errno::AlreadyExists); + } + + self.files.insert(idx, file_ref); + Ok(dst) + } else { + self.place_file(file_ref) + } + } + /// Returns [File] struct referred to by file descriptor `idx` - pub fn file(&mut self, idx: usize) -> Result { - self.files.get(&idx).cloned().ok_or(Errno::InvalidFile) + pub fn file(&mut self, fd: FileDescriptor) -> Result { + self.files.get(&u32::from(fd)).cloned().ok_or(Errno::InvalidFile) } /// Returns [Ioctx] structure reference of this I/O context @@ -32,19 +100,19 @@ impl ProcessIo { } /// Allocates a file descriptor and associates a [File] struct with it - pub fn place_file(&mut self, file: FileRef) -> Result { + pub fn place_file(&mut self, file: FileRef) -> Result { for idx in 0..64 { if self.files.get(&idx).is_none() { self.files.insert(idx, file); - return Ok(idx); + return Ok(FileDescriptor::from(idx)); } } Err(Errno::TooManyDescriptors) } /// Performs [File] close and releases its associated file descriptor `idx` - pub fn close_file(&mut self, idx: usize) -> Result<(), Errno> { - let res = self.files.remove(&idx); + pub fn close_file(&mut self, idx: FileDescriptor) -> Result<(), Errno> { + let res = self.files.remove(&u32::from(idx)); assert!(res.is_some()); Ok(()) } @@ -54,12 +122,14 @@ impl ProcessIo { Self { files: BTreeMap::new(), ioctx: None, + ctty: None, } } /// Assigns a descriptor number to an open file. If the number is not available, /// returns [Errno::AlreadyExists]. - pub fn set_file(&mut self, idx: usize, file: FileRef) -> Result<(), Errno> { + pub fn set_file(&mut self, idx: FileDescriptor, file: FileRef) -> Result<(), Errno> { + let idx = u32::from(idx); if self.files.get(&idx).is_none() { self.files.insert(idx, file); Ok(()) diff --git a/kernel/src/proc/mod.rs b/kernel/src/proc/mod.rs index 874a4f9..cb7ced0 100644 --- a/kernel/src/proc/mod.rs +++ b/kernel/src/proc/mod.rs @@ -9,10 +9,14 @@ use alloc::{ }; use core::sync::atomic::{AtomicUsize, Ordering}; use crate::arch::platform::cpu::{self, Cpu}; +use libsys::proc::Pid; pub mod elf; +pub mod thread; +pub use thread::{Thread, ThreadRef, State as ThreadState}; +pub(self) use thread::Context; pub mod process; -pub use process::{Pid, Process, ProcessRef, State as ProcessState}; +pub use process::{Process, ProcessRef, ProcessState}; pub mod io; pub use io::ProcessIo; @@ -22,6 +26,7 @@ pub mod sched; pub use sched::Scheduler; //pub(self) use sched::SCHED; +//<<<<<<< HEAD // <<<<<<< HEAD // macro_rules! spawn { // (fn ($dst_arg:ident : usize) $body:block, $src_arg:expr) => {{ @@ -49,10 +54,10 @@ pub use sched::Scheduler; // SCHED.switch(false); //} -/// -pub fn process(id: Pid) -> ProcessRef { - PROCESSES.lock().get(&id).unwrap().clone() -} +// /// +// pub fn process(id: Pid) -> ProcessRef { +// PROCESSES.lock().get(&id).unwrap().clone() +// } macro_rules! spawn { (fn ($dst_arg:ident : usize) $body:block, $src_arg:expr) => {{ @@ -74,17 +79,22 @@ macro_rules! spawn { (fn () $body:block) => (spawn!(fn (_arg: usize) $body, 0usize)) } -/// Global list of all processes in the system +// /// Global list of all processes in the system +// // ======= +// /// Performs a task switch. +// /// +// /// See [Scheduler::switch] +// pub fn switch() { +// SCHED.switch(false); +// } + +// >>>>>>> feat/thread pub(self) static PROCESSES: IrqSafeSpinLock> = IrqSafeSpinLock::new(BTreeMap::new()); -/// Sets up initial process and enters it. -/// -/// See [Scheduler::enter] -/// -/// # Safety -/// -/// Unsafe: May only be called once. +pub(self) static THREADS: IrqSafeSpinLock> = + IrqSafeSpinLock::new(BTreeMap::new()); + pub unsafe fn enter(is_bsp: bool) -> ! { static COUNTER: AtomicUsize = AtomicUsize::new(0); @@ -97,24 +107,8 @@ pub unsafe fn enter(is_bsp: bool) -> ! { } if is_bsp { - sched::enqueue(Process::new_kernel(init::init_fn, 0).unwrap().id()); - } else { - spawn!(fn () { - loop {} - }); + Process::new_kernel(init::init_fn, 0).unwrap().enqueue(); } - // if let Some((start, end)) = initrd { - // let initrd = Box::into_raw(Box::new((mem::virtualize(start), mem::virtualize(end)))); - - // spawn!(fn (initrd_ptr: usize) { - // loop {} - // // debugln!("Running kernel init process"); - - // // let (start, _end) = unsafe { *(initrd_ptr as *const (usize, usize)) }; - // // Process::execve(|space| elf::load_elf(space, start as *const u8), 0).unwrap(); - // // panic!("This code should not run"); - // }, initrd as usize); - // } sched.enter(); } diff --git a/kernel/src/proc/process.rs b/kernel/src/proc/process.rs index 891dcab..6abd544 100644 --- a/kernel/src/proc/process.rs +++ b/kernel/src/proc/process.rs @@ -5,340 +5,410 @@ use crate::mem::{ phys::{self, PageUsage}, virt::{MapAttributes, Space}, }; -use crate::proc::{wait::Wait, ProcessIo, PROCESSES, sched}; +use crate::proc::{ + wait::Wait, Context, ProcessIo, Thread, ThreadRef, ThreadState, PROCESSES, sched, +}; use crate::sync::IrqSafeSpinLock; -use alloc::rc::Rc; -use core::cell::UnsafeCell; -use core::fmt; +use alloc::{rc::Rc, vec::Vec}; use core::sync::atomic::{AtomicU32, Ordering}; -use error::Errno; - -pub use crate::arch::platform::context::{self, Context}; +use libsys::{ + error::Errno, + mem::memcpy, + proc::{ExitCode, Pid}, + signal::Signal, + ProgramArgs, +}; /// Wrapper type for a process struct reference pub type ProcessRef = Rc; -/// Wrapper type for process exit code -#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug)] -#[repr(transparent)] -pub struct ExitCode(i32); - -/// Wrapper type for process ID -#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq)] -#[repr(transparent)] -pub struct Pid(u32); - /// List of possible process states #[derive(Clone, Copy, Debug, PartialEq)] -pub enum State { - /// Process is ready to be executed and/or is scheduled for it - Ready, - /// Process is currently running or is in system call/interrupt handler - Running, +pub enum ProcessState { + /// Process is alive + Active, /// Process has finished execution and is waiting to be reaped Finished, - /// Process is waiting for some external event - Waiting, } struct ProcessInner { space: Option<&'static mut Space>, - state: State, + state: ProcessState, id: Pid, - wait_flag: bool, + pgid: Pid, + ppid: Option, + sid: Pid, exit: Option, + threads: Vec, } /// Structure describing an operating system process #[allow(dead_code)] pub struct Process { - ctx: UnsafeCell, inner: IrqSafeSpinLock, exit_wait: Wait, + signal_state: AtomicU32, /// Process I/O context pub io: IrqSafeSpinLock, - cpu: AtomicU32, -} - -impl From for ExitCode { - fn from(f: i32) -> Self { - Self(f) - } -} - -impl From<()> for ExitCode { - fn from(_: ()) -> Self { - Self(0) - } -} - -impl From for i32 { - fn from(f: ExitCode) -> Self { - f.0 - } -} - -impl Pid { - /// Kernel idle process always has PID of zero - pub const IDLE: Self = Self(Self::KERNEL_BIT); - - const KERNEL_BIT: u32 = 1 << 31; - - /// Constructs an instance of user-space PID - pub const fn user(id: u32) -> Self { - assert!(id < 256, "PID is too high"); - Self(id) - } - - /// Allocates a new kernel-space PID - pub fn new_kernel() -> Self { - static LAST: AtomicU32 = AtomicU32::new(0); - let id = LAST.fetch_add(1, Ordering::Relaxed); - assert!(id & Self::KERNEL_BIT == 0, "Out of kernel PIDs"); - Self(id | Self::KERNEL_BIT) - } - - /// Allocates a new user-space PID. - /// - /// First user PID is #1. - pub fn new_user() -> Self { - static LAST: AtomicU32 = AtomicU32::new(1); - let id = LAST.fetch_add(1, Ordering::Relaxed); - assert!(id < 256, "Out of user PIDs"); - Self(id) - } - - /// Returns `true` if this PID belongs to a kernel process - pub fn is_kernel(self) -> bool { - self.0 & Self::KERNEL_BIT != 0 - } - - /// Returns address space ID of a user-space process. - /// - /// Panics if called on kernel process PID. - pub fn asid(self) -> u8 { - assert!(!self.is_kernel()); - self.0 as u8 - } - - /// Returns bit value of this pid - pub const fn value(self) -> u32 { - self.0 - } - - pub const unsafe fn from_raw(num: u32) -> Self { - Self(num) - } -} - -impl fmt::Display for Pid { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "Pid(#{}{})", - if self.is_kernel() { "K" } else { "U" }, - self.0 & !Self::KERNEL_BIT - ) - } } impl Process { const USTACK_VIRT_TOP: usize = 0x100000000; const USTACK_PAGES: usize = 4; - const CPU_NONE: u32 = u32::MAX; - - /// Returns currently executing process - pub fn current() -> ProcessRef { - sched::current_process() - } - - pub fn get(pid: Pid) -> Option { - PROCESSES.lock().get(&pid).cloned() - } - - /// Schedules an initial thread for execution - /// - /// # Safety - /// - /// Unsafe: only allowed to be called once, repeated calls - /// will generate undefined behavior - pub unsafe fn enter(cpu: u32, proc: ProcessRef) -> ! { - // FIXME use some global lock to guarantee atomicity of thread entry? - proc.inner.lock().state = State::Running; - proc.cpu.store(cpu, Ordering::SeqCst); - let ctx = proc.ctx.get(); - - // I don't think this is bad: process can't be dropped fully unless - // it's been reaped (and this function won't run for such process) - // drop(proc); - (&mut *ctx).enter() - } - - #[inline] - pub fn manipulate_space Result<(), Errno>>( - &self, - f: F, - ) -> Result<(), Errno> { - f(self.inner.lock().space.as_mut().unwrap()) - } - - /// Schedules a next thread for execution - /// - /// # Safety - /// - /// Unsafe: - /// - /// * Does not ensure src and dst threads are not the same thread - /// * Does not ensure src is actually current context - pub unsafe fn switch(cpu: u32, src: ProcessRef, dst: ProcessRef, discard: bool) { - { - let mut src_lock = src.inner.lock(); - let mut dst_lock = dst.inner.lock(); - - if !discard { - assert_eq!(src_lock.state, State::Running); - src_lock.state = State::Ready; - } - assert!(dst_lock.state == State::Ready || dst_lock.state == State::Waiting); - dst_lock.state = State::Running; - - src.cpu.store(Self::CPU_NONE, Ordering::SeqCst); - dst.cpu.store(cpu, Ordering::SeqCst); - } - - let src_ctx = src.ctx.get(); - let dst_ctx = dst.ctx.get(); - - // See "drop" note in Process::enter() - // drop(src); - // drop(dst); - - (&mut *src_ctx).switch(&mut *dst_ctx); - } - - /// Suspends current process with a "waiting" status - pub fn enter_wait(&self) { - let drop = { - let mut lock = self.inner.lock(); - let drop = lock.state == State::Running; - lock.state = State::Waiting; - sched::dequeue(lock.id); - // SCHED.dequeue(lock.id); - drop - }; - if drop { - sched::switch(true); - // todo!(); - // SCHED.switch(true); - } - } - - /// Changes process wait condition status - pub fn set_wait_flag(&self, v: bool) { - self.inner.lock().wait_flag = v; - } - - /// Returns `true` if process wait condition has not been reached - pub fn wait_flag(&self) -> bool { - self.inner.lock().wait_flag - } /// Returns the process ID + #[inline] pub fn id(&self) -> Pid { self.inner.lock().id } - /// - pub fn cpu(&self) -> u32 { - self.cpu.load(Ordering::SeqCst) + /// Returns the process session ID + #[inline] + pub fn sid(&self) -> Pid { + self.inner.lock().sid + } + + /// Returns parent's [Pid] + #[inline] + pub fn pgid(&self) -> Pid { + self.inner.lock().pgid + } + + /// Returns parent's [Pid] + #[inline] + pub fn ppid(&self) -> Option { + self.inner.lock().ppid + } + + /// Sets a new group id for the process + pub fn set_pgid(&self, pgid: Pid) { + self.inner.lock().pgid = pgid; + } + + /// Sets a new session id for the process + pub fn set_sid(&self, sid: Pid) { + self.inner.lock().sid = sid; + } + + /// Returns [Rc]-reference to current process + #[inline] + pub fn current() -> ProcessRef { + Thread::current().owner().unwrap() + } + + /// Executes a closure performing manipulations on the process address space + #[inline] + pub fn manipulate_space(&self, f: F) -> R + where + F: FnOnce(&mut Space) -> R, + { + f(self.inner.lock().space.as_mut().unwrap()) } /// Creates a new kernel process pub fn new_kernel(entry: extern "C" fn(usize) -> !, arg: usize) -> Result { - let id = Pid::new_kernel(); + let id = new_kernel_pid(); + let thread = Thread::new_kernel(Some(id), entry, arg)?; + let mut inner = ProcessInner { + threads: Vec::new(), + id, + pgid: id, + ppid: None, + sid: id, + exit: None, + space: None, + state: ProcessState::Active, + }; + inner.threads.push(thread.id()); + let res = Rc::new(Self { - ctx: UnsafeCell::new(Context::kernel(entry as usize, arg)), + exit_wait: Wait::new("process_exit"), io: IrqSafeSpinLock::new(ProcessIo::new()), - exit_wait: Wait::new(), - inner: IrqSafeSpinLock::new(ProcessInner { - id, - exit: None, - space: None, - wait_flag: false, - state: State::Ready, - }), - cpu: AtomicU32::new(Self::CPU_NONE), + signal_state: AtomicU32::new(0), + inner: IrqSafeSpinLock::new(inner), }); - debugln!("New kernel process: {}", id); - assert!(PROCESSES.lock().insert(id, res.clone()).is_none()); + debugln!("New kernel process: {:?}", id); + let prev = PROCESSES.lock().insert(id, res.clone()); + assert!(prev.is_none()); Ok(res) } + /// Adds all of the process threads to scheduler queue + pub fn enqueue(&self) { + let inner = self.inner.lock(); + for &tid in inner.threads.iter() { + sched::enqueue(tid); + } + } + + /// Returns process (if any) to which `pid` refers + pub fn get(pid: Pid) -> Option { + PROCESSES.lock().get(&pid).cloned() + } + +// <<<<<<< HEAD + // /// Schedules an initial thread for execution + // /// + // /// # Safety + // /// + // /// Unsafe: only allowed to be called once, repeated calls + // /// will generate undefined behavior + // pub unsafe fn enter(cpu: u32, proc: ProcessRef) -> ! { + // // FIXME use some global lock to guarantee atomicity of thread entry? + // proc.inner.lock().state = State::Running; + // proc.cpu.store(cpu, Ordering::SeqCst); + // let ctx = proc.ctx.get(); + + // // I don't think this is bad: process can't be dropped fully unless + // // it's been reaped (and this function won't run for such process) + // // drop(proc); + // (&mut *ctx).enter() + // } +// ======= + /// Sets a pending signal for a process + pub fn set_signal(&self, signal: Signal) { + todo!(); + // let mut lock = self.inner.lock(); + // let ttbr0 = lock.space.as_mut().unwrap().address_phys() | ((lock.id.asid() as usize) << 48); + // let main_thread = Thread::get(lock.threads[0]).unwrap(); + // drop(lock); + + // // TODO check that `signal` is not a fault signal + // // it is illegal to call this function with + // // fault signals + // match main_thread.state() { + // ThreadState::Running => { + // main_thread.enter_signal(signal, ttbr0); + // } + // ThreadState::Waiting => { + // main_thread.clone().setup_signal(signal, ttbr0); + // main_thread.interrupt_wait(true); + // } + // ThreadState::Ready => { + // main_thread.clone().setup_signal(signal, ttbr0); + // main_thread.interrupt_wait(false); + // } + // ThreadState::Finished => { + // // TODO report error back + // todo!() + // } + // } + } + + /// Immediately delivers a signal to requested thread + pub fn enter_fault_signal(&self, thread: ThreadRef, signal: Signal) { + todo!(); + // let mut lock = self.inner.lock(); + // let ttbr0 = lock.space.as_mut().unwrap().address_phys() | ((lock.id.asid() as usize) << 48); + // thread.enter_signal(signal, ttbr0); + } + + // /// Schedules a next thread for execution + // /// + // /// # Safety + // /// + // /// Unsafe: + // /// + // /// * Does not ensure src and dst threads are not the same thread + // /// * Does not ensure src is actually current context + // pub unsafe fn switch(cpu: u32, src: ProcessRef, dst: ProcessRef, discard: bool) { + // { + // let mut src_lock = src.inner.lock(); + // let mut dst_lock = dst.inner.lock(); + + // if !discard { + // assert_eq!(src_lock.state, State::Running); + // src_lock.state = State::Ready; + // } + // assert!(dst_lock.state == State::Ready || dst_lock.state == State::Waiting); + // dst_lock.state = State::Running; + + // src.cpu.store(Self::CPU_NONE, Ordering::SeqCst); + // dst.cpu.store(cpu, Ordering::SeqCst); + // } + + // let src_ctx = src.ctx.get(); + // let dst_ctx = dst.ctx.get(); + + // // See "drop" note in Process::enter() + // // drop(src); + // // drop(dst); + + // (&mut *src_ctx).switch(&mut *dst_ctx); + // } + + // /// Suspends current process with a "waiting" status + // pub fn enter_wait(&self) { + // let drop = { + // let mut lock = self.inner.lock(); + // let drop = lock.state == State::Running; + // lock.state = State::Waiting; + // sched::dequeue(lock.id); + // // SCHED.dequeue(lock.id); + // drop + // }; + // if drop { + // sched::switch(true); + // // todo!(); + // // SCHED.switch(true); + // } + // } + + /// Crates a new thread in the process + pub fn new_user_thread(&self, entry: usize, stack: usize, arg: usize) -> Result { + let mut lock = self.inner.lock(); + + let space_phys = lock.space.as_mut().unwrap().address_phys(); + let ttbr0 = space_phys | ((lock.id.asid() as usize) << 48); + + let thread = Thread::new_user(lock.id, entry, stack, arg, ttbr0)?; + let tid = thread.id(); + lock.threads.push(tid); + sched::enqueue(tid); + + Ok(tid) + } + + // /// Creates a new kernel process + // pub fn new_kernel(entry: extern "C" fn(usize) -> !, arg: usize) -> Result { + // let id = Pid::new_kernel(); + // let res = Rc::new(Self { + // ctx: UnsafeCell::new(Context::kernel(entry as usize, arg)), + // io: IrqSafeSpinLock::new(ProcessIo::new()), + // exit_wait: Wait::new(), + // inner: IrqSafeSpinLock::new(ProcessInner { + // id, + // exit: None, + // space: None, + // wait_flag: false, + // state: State::Ready, + // }), + // cpu: AtomicU32::new(Self::CPU_NONE), + // }); + // debugln!("New kernel process: {}", id); + // assert!(PROCESSES.lock().insert(id, res.clone()).is_none()); + // Ok(res) + // } + /// Creates a "fork" of the process, cloning its address space and /// resources pub fn fork(&self, frame: &mut ExceptionFrame) -> Result { let src_io = self.io.lock(); let mut src_inner = self.inner.lock(); - let dst_id = Pid::new_user(); + let dst_id = new_user_pid(); let dst_space = src_inner.space.as_mut().unwrap().fork()?; let dst_space_phys = (dst_space as *mut _ as usize) - mem::KERNEL_OFFSET; let dst_ttbr0 = dst_space_phys | ((dst_id.asid() as usize) << 48); + let mut threads = Vec::new(); + let tid = Thread::fork(Some(dst_id), frame, dst_ttbr0)?.id(); + threads.push(tid); + let dst = Rc::new(Self { - ctx: UnsafeCell::new(Context::fork(frame, dst_ttbr0)), + exit_wait: Wait::new("process_exit"), io: IrqSafeSpinLock::new(src_io.fork()?), - exit_wait: Wait::new(), + signal_state: AtomicU32::new(0), inner: IrqSafeSpinLock::new(ProcessInner { - id: dst_id, + threads, exit: None, space: Some(dst_space), - state: State::Ready, - wait_flag: false, + state: ProcessState::Active, + id: dst_id, + pgid: src_inner.pgid, + ppid: Some(src_inner.id), + sid: src_inner.sid, }), - cpu: AtomicU32::new(Self::CPU_NONE) }); - debugln!("Process {} forked into {}", src_inner.id, dst_id); + + debugln!("Process {:?} forked into {:?}", src_inner.id, dst_id); assert!(PROCESSES.lock().insert(dst_id, dst).is_none()); - sched::enqueue(dst_id); + + sched::enqueue(tid); // SCHED.enqueue(dst_id); Ok(dst_id) } /// Terminates a process. - pub fn exit>(&self, status: I) { - let status = status.into(); - let drop = { - let mut lock = self.inner.lock(); - let drop = lock.state == State::Running; - infoln!("Process {} is exiting: {:?}", lock.id, status); - assert!(lock.exit.is_none()); - lock.exit = Some(status); - lock.state = State::Finished; + pub fn exit(self: ProcessRef, status: ExitCode) { + let thread = Thread::current(); + let mut lock = self.inner.lock(); + let is_running = thread.owner_id().map(|e| e == lock.id).unwrap_or(false); - if let Some(space) = lock.space.take() { - unsafe { - Space::release(space); - asm!("tlbi aside1, {}", in(reg) ((lock.id.asid() as usize) << 48)); - } + infoln!("Process {:?} is exiting: {:?}", lock.id, status); + assert!(lock.exit.is_none()); + lock.exit = Some(status); + lock.state = ProcessState::Finished; + + for &tid in lock.threads.iter() { + Thread::get(tid).unwrap().terminate(status); + sched::dequeue(tid); + // SCHED.dequeue(tid); + } + + if let Some(space) = lock.space.take() { + unsafe { + Space::release(space); + asm!("tlbi aside1, {}", in(reg) ((lock.id.asid() as usize) << 48)); } + } - self.io.lock().handle_exit(); + // TODO when exiting from signal handler interrupting an IO operation + // deadlock is achieved + self.io.lock().handle_exit(); + + drop(lock); - sched::dequeue(lock.id); - drop - }; self.exit_wait.wakeup_all(); - if drop { + + if is_running { sched::switch(true); panic!("This code should never run"); } } + /// Terminates a thread of the process. If the thread is the only + /// one remaining, process itself is exited (see [Process::exit]) + pub fn exit_thread(thread: ThreadRef, status: ExitCode) { + let switch = { + let switch = thread.state() == ThreadState::Running; + let process = thread.owner().unwrap(); + let mut lock = process.inner.lock(); + let tid = thread.id(); + + if lock.threads.len() == 1 { + // TODO call Process::exit instead? + drop(lock); + process.exit(status); + return; + } + + lock.threads.retain(|&e| e != tid); + + thread.terminate(status); + todo!(); + // SCHED.dequeue(tid); + debugln!("Thread {} terminated", tid); + + switch + }; + + if switch { + // TODO retain thread ID in process "finished" list and + // drop it when process finishes + // SCHED.switch(true); + todo!(); + panic!("This code should not run"); + } else { + // Can drop this thread: it's not running + todo!(); + } + } + fn collect(&self) -> Option { let lock = self.inner.lock(); - if lock.state == State::Finished { + if lock.state == ProcessState::Finished { lock.exit } else { None @@ -357,7 +427,6 @@ impl Process { if let Some(r) = proc.collect() { // TODO drop the process struct itself PROCESSES.lock().remove(&proc.id()); - debugln!("pid {} has {} refs", proc.id(), Rc::strong_count(&proc)); return Ok(r); } @@ -365,43 +434,160 @@ impl Process { } } - /// Loads a new program into current process address space - pub fn execve Result>( - loader: F, - arg: usize, - ) -> Result<(), Errno> { - unsafe { - // Run with interrupts disabled - asm!("msr daifset, #2"); + fn write_paged(space: &mut Space, dst: usize, src: T) -> Result<(), Errno> { + let size = core::mem::size_of::(); + if (size + (dst % 4096)) > 4096 { + todo!("Object crossed page boundary"); } - let proc = sched::current_process(); - let mut lock = proc.inner.lock(); - if lock.id.is_kernel() { - let mut proc_lock = PROCESSES.lock(); - let old_pid = lock.id; - assert!( - proc_lock.remove(&old_pid).is_some(), - "Failed to downgrade kernel process (remove kernel pid)" - ); - lock.id = Pid::new_user(); - debugln!( - "Process downgrades from kernel to user: {} -> {}", - old_pid, - lock.id - ); - assert!(proc_lock.insert(lock.id, proc.clone()).is_none()); - unsafe { - use crate::arch::platform::cpu::Cpu; - Cpu::get().scheduler().hack_current_pid(lock.id); - } + let page_virt = dst & !4095; + let page_phys = if let Ok(phys) = space.translate(dst) { + phys } else { - // Invalidate user ASID - let input = (lock.id.asid() as usize) << 48; - unsafe { - asm!("tlbi aside1, {}", in(reg) input); - } + let page = phys::alloc_page(PageUsage::UserPrivate)?; + let flags = MapAttributes::SH_OUTER + | MapAttributes::NOT_GLOBAL + | MapAttributes::UXN + | MapAttributes::PXN + | MapAttributes::AP_BOTH_READONLY; + space.map(page_virt, page, flags)?; + page + }; + + unsafe { + core::ptr::write((mem::virtualize(page_phys) + (dst % 4096)) as *mut T, src); } + Ok(()) + } + + fn write_paged_bytes(space: &mut Space, dst: usize, src: &[u8]) -> Result<(), Errno> { + if (src.len() + (dst % 4096)) > 4096 { + todo!("Object crossed page boundary"); + } + let page_virt = dst & !4095; + let page_phys = if let Ok(phys) = space.translate(dst) { + phys + } else { + let page = phys::alloc_page(PageUsage::UserPrivate)?; + let flags = MapAttributes::SH_OUTER + | MapAttributes::NOT_GLOBAL + | MapAttributes::UXN + | MapAttributes::PXN + | MapAttributes::AP_BOTH_READONLY; + space.map(page_virt, page, flags)?; + page + }; + + unsafe { + memcpy( + (mem::virtualize(page_phys) + (dst % 4096)) as *mut u8, + src.as_ptr(), + src.len(), + ); + } + Ok(()) + } + + fn store_arguments(space: &mut Space, argv: &[&str]) -> Result { + let mut offset = 0usize; + // TODO vmalloc? + let base = 0x60000000; + + // 1. Store program argument string bytes + for arg in argv.iter() { + Self::write_paged_bytes(space, base + offset, arg.as_bytes())?; + offset += arg.len(); + } + // Align + offset = (offset + 15) & !15; + let argv_offset = offset; + + // 2. Store arg pointers + let mut data_offset = 0usize; + for arg in argv.iter() { + // XXX this is really unsafe and I am not really sure ABI will stay like this XXX + Self::write_paged(space, base + offset + 0, base + data_offset)?; + Self::write_paged(space, base + offset + 8, arg.len())?; + offset += 16; + data_offset += arg.len(); + } + + // 3. Store ProgramArgs + let data = ProgramArgs { + argc: argv.len(), + argv: base + argv_offset, + storage: base, + size: offset + core::mem::size_of::(), + }; + Self::write_paged(space, base + offset, data)?; + + Ok(base + offset) + } + + /// Loads a new program into current process address space + pub fn execve Result>( + loader: F, + argv: &[&str], + ) -> Result<(), Errno> { + unsafe { + // Run with interrupts disabled + asm!("msr daifset, #2"); + } + +// <<<<<<< HEAD +// let proc = sched::current_process(); +// let mut lock = proc.inner.lock(); +// if lock.id.is_kernel() { +// let mut proc_lock = PROCESSES.lock(); +// let old_pid = lock.id; +// assert!( +// proc_lock.remove(&old_pid).is_some(), +// "Failed to downgrade kernel process (remove kernel pid)" +// ); +// lock.id = Pid::new_user(); +// debugln!( +// "Process downgrades from kernel to user: {} -> {}", +// old_pid, +// lock.id +// ); +// assert!(proc_lock.insert(lock.id, proc.clone()).is_none()); +// unsafe { +// use crate::arch::platform::cpu::Cpu; +// Cpu::get().scheduler().hack_current_pid(lock.id); +// } +// } else { +// // Invalidate user ASID +// let input = (lock.id.asid() as usize) << 48; +// unsafe { +// asm!("tlbi aside1, {}", in(reg) input); +// } +// ======= + let proc = Process::current(); + let mut process_lock = proc.inner.lock(); + + if process_lock.threads.len() != 1 { + todo!(); +// >>>>>>> feat/thread + } + + let thread = Thread::get(process_lock.threads[0]).unwrap(); + + if process_lock.id.is_kernel() { + let mut processes = PROCESSES.lock(); + let old_pid = process_lock.id; + let new_pid = new_user_pid(); + debugln!("Downgrading process {:?} -> {:?}", old_pid, new_pid); + + let r = processes.remove(&old_pid); + assert!(r.is_some()); + process_lock.id = new_pid; + process_lock.pgid = new_pid; + process_lock.sid = new_pid; + let r = processes.insert(new_pid, proc.clone()); + assert!(r.is_none()); + } + + thread.set_owner(process_lock.id); proc.io.lock().handle_cloexec(); @@ -422,25 +608,26 @@ impl Process { } let entry = loader(new_space)?; + let arg = Self::store_arguments(new_space, argv)?; debugln!("Will now enter at {:#x}", entry); // TODO drop old address space - lock.space = Some(new_space); + process_lock.space = Some(new_space); unsafe { // TODO drop old context - let ctx = proc.ctx.get(); + let ctx = thread.ctx.get(); + let asid = (process_lock.id.asid() as usize) << 48; + asm!("tlbi aside1, {}", in(reg) asid); ctx.write(Context::user( entry, arg, - new_space_phys | ((lock.id.asid() as usize) << 48), + new_space_phys | asid, Self::USTACK_VIRT_TOP, )); - assert_eq!(lock.state, State::Running); - - drop(lock); + drop(process_lock); (*ctx).enter(); } @@ -449,6 +636,23 @@ impl Process { impl Drop for Process { fn drop(&mut self) { - debugln!("Dropping process {}", self.id()); + debugln!("Dropping process {:?}", self.id()); } } + +/// Allocates a new kernel-space PID +pub fn new_kernel_pid() -> Pid { + static LAST: AtomicU32 = AtomicU32::new(0); + let id = LAST.fetch_add(1, Ordering::Relaxed); + Pid::kernel(id) +} + +/// Allocates a new user-space PID. +/// +/// First user PID is #1. +pub fn new_user_pid() -> Pid { + static LAST: AtomicU32 = AtomicU32::new(1); + let id = LAST.fetch_add(1, Ordering::Relaxed); + assert!(id < 256, "Out of user PIDs"); + Pid::user(id) +} diff --git a/kernel/src/proc/sched.rs b/kernel/src/proc/sched.rs index 1674a91..7f8c3d7 100644 --- a/kernel/src/proc/sched.rs +++ b/kernel/src/proc/sched.rs @@ -1,5 +1,5 @@ //! -use crate::proc::{Pid, Process, ProcessRef, PROCESSES}; +use crate::proc::{Thread, ThreadRef, THREADS}; use crate::util::InitOnce; use alloc::{collections::VecDeque, rc::Rc}; use crate::sync::{IrqSafeSpinLock, IrqSafeSpinLockGuard}; @@ -9,9 +9,9 @@ use core::ops::Deref; use tock_registers::interfaces::Readable; struct SchedulerInner { - queue: VecDeque, - idle: Option, - current: Option, + queue: VecDeque, + idle: Option, + current: Option, } /// Process scheduler state and queues @@ -27,7 +27,7 @@ impl SchedulerInner { current: None, }; - this.idle = Some(Process::new_kernel(idle_fn, 0).unwrap().id()); + this.idle = Some(Thread::new_kernel(None, idle_fn, 0).unwrap().id()); this } @@ -61,13 +61,13 @@ impl Scheduler { } /// Schedules a thread for execution - pub fn enqueue(&self, pid: Pid) { - self.inner.get().lock().queue.push_back(pid); + pub fn enqueue(&self, tid: u32) { + self.inner.get().lock().queue.push_back(tid); } - /// Removes given `pid` from execution queue - pub fn dequeue(&self, pid: Pid) { - self.inner.get().lock().queue.retain(|&p| p != pid) + /// Removes given `tid` from execution queue + pub fn dequeue(&self, tid: u32) { + self.inner.get().lock().queue.retain(|&p| p != tid) } /// Performs initial process entry. @@ -85,10 +85,11 @@ impl Scheduler { }; inner.current = Some(id); - PROCESSES.lock().get(&id).unwrap().clone() + THREADS.lock().get(&id).unwrap().clone() }; - Process::enter((MPIDR_EL1.get() & 0xF) as u32, thread) + asm!("msr daifset, #2"); + Thread::enter((MPIDR_EL1.get() & 0xF) as u32, thread) } /// This hack is required to be called from execve() when downgrading current @@ -97,8 +98,14 @@ impl Scheduler { /// # Safety /// /// Unsafe: only allowed to be called from Process::execve() - pub unsafe fn hack_current_pid(&self, new: Pid) { - self.inner.get().lock().current = Some(new); + pub unsafe fn hack_current_tid(&self, old: u32, new: u32) { + let mut lock = self.inner.get().lock(); + match lock.current { + Some(t) if t == old => { + lock.current = Some(new); + } + _ => {} + } } /// Switches to the next task scheduled for execution. If there're @@ -108,7 +115,11 @@ impl Scheduler { let mut inner = self.inner.get().lock(); let current = inner.current.unwrap(); +//<<<<<<< HEAD if !discard && current != inner.idle.unwrap() { +//======= +// if !discard && current != 0 { +//>>>>>>> feat/thread // Put the process into the back of the queue if !enqueue_somewhere_else((MPIDR_EL1.get() & 0xF) as usize, current, &sched_lock) { inner.queue.push_back(current); @@ -123,7 +134,7 @@ impl Scheduler { inner.current = Some(next); let (from, to) = { - let lock = PROCESSES.lock(); + let lock = THREADS.lock(); ( lock.get(¤t).unwrap().clone(), lock.get(&next).unwrap().clone(), @@ -135,23 +146,42 @@ impl Scheduler { if !Rc::ptr_eq(&from, &to) { unsafe { +//<<<<<<< HEAD drop(sched_lock); - Process::switch((MPIDR_EL1.get() & 0xF) as u32, from, to, discard); +// Process::switch((MPIDR_EL1.get() & 0xF) as u32, from, to, discard); +//======= + asm!("msr daifset, #2"); + Thread::switch((MPIDR_EL1.get() & 0xF) as u32, from, to, discard); +//>>>>>>> feat/thread } } } - /// Returns a Rc-reference to currently running process - pub fn current_process(&self) -> ProcessRef { + /// Returns a [Rc]-reference to currently running Thread + pub fn current_thread(&self) -> ThreadRef { let inner = self.inner.get().lock(); - let current = inner.current.unwrap(); - PROCESSES.lock().get(¤t).unwrap().clone() + let id = inner.current.unwrap(); + THREADS.lock().get(&id).unwrap().clone() } + + // /// Returns a Rc-reference to currently running process + // pub fn current_process(&self) -> ProcessRef { + // let inner = self.inner.get().lock(); + // let current = inner.current.unwrap(); + // PROCESSES.lock().get(¤t).unwrap().clone() + // } } +// <<<<<<< HEAD +// // pub fn is_ready() -> bool { +// // SCHED.inner.is_initialized() +// // } +// ======= +// /// Returns `true` if the scheduler has been initialized // pub fn is_ready() -> bool { // SCHED.inner.is_initialized() // } +// >>>>>>> feat/thread #[inline(never)] extern "C" fn idle_fn(_a: usize) -> ! { @@ -160,6 +190,11 @@ extern "C" fn idle_fn(_a: usize) -> ! { } } +pub fn current_thread() -> ThreadRef { + let guard = SCHED_LOCK.lock(); + unsafe { Cpu::get().scheduler().current_thread() } +} + /// Performs a task switch. /// /// See [Scheduler::switch] @@ -170,16 +205,17 @@ pub fn switch(discard: bool) { } /// -pub fn enqueue_to(cpu: usize, pid: Pid) { - let _lock = SCHED_LOCK.lock(); - debugln!("Queue {} to cpu{}", pid, cpu); - unsafe { - cpu::by_index(cpu).scheduler().enqueue(pid) - } +pub fn enqueue_to(cpu: usize, tid: u32) { + todo!() + //let _lock = SCHED_LOCK.lock(); + //debugln!("Queue {} to cpu{}", pid, cpu); + //unsafe { + // cpu::by_index(cpu).scheduler().enqueue(pid) + //} } /// -pub fn enqueue(pid: Pid) { +pub fn enqueue(tid: u32) { let _lock = SCHED_LOCK.lock(); let mut min_idx = 0; let mut min_cnt = usize::MAX; @@ -193,12 +229,12 @@ pub fn enqueue(pid: Pid) { // debugln!("Queue {} to cpu{}", pid, min_idx); unsafe { - cpu::by_index(min_idx).scheduler().enqueue(pid) + cpu::by_index(min_idx).scheduler().enqueue(tid) } } /// -pub fn enqueue_somewhere_else(ignore: usize, pid: Pid, _guard: &IrqSafeSpinLockGuard<()>) -> bool { +pub fn enqueue_somewhere_else(ignore: usize, tid: u32, _guard: &IrqSafeSpinLockGuard<()>) -> bool { let mut min_idx = 0; //let mut min_cnt = usize::MAX; static mut LAST: usize = 0; @@ -222,28 +258,22 @@ pub fn enqueue_somewhere_else(ignore: usize, pid: Pid, _guard: &IrqSafeSpinLockG false } else { unsafe { - cpu::by_index(min_idx).scheduler().enqueue(pid) + cpu::by_index(min_idx).scheduler().enqueue(tid) } true } } /// -pub fn dequeue(pid: Pid) { +pub fn dequeue(tid: u32) { // TODO process can be rescheduled to other CPU between scheduler locks let lock = SCHED_LOCK.lock(); - let cpu_id = PROCESSES.lock().get(&pid).unwrap().cpu(); + let cpu_id = Thread::get(tid).unwrap().cpu(); unsafe { - cpu::by_index(cpu_id as usize).scheduler().dequeue(pid); + cpu::by_index(cpu_id as usize).scheduler().dequeue(tid); } } -/// -pub fn current_process() -> ProcessRef { - let _lock = SCHED_LOCK.lock(); - unsafe { Cpu::get().scheduler().current_process() } -} - static SCHED_LOCK: IrqSafeSpinLock<()> = IrqSafeSpinLock::new(()); // TODO maybe move this into a per-CPU struct diff --git a/kernel/src/proc/thread.rs b/kernel/src/proc/thread.rs new file mode 100644 index 0000000..6ff947f --- /dev/null +++ b/kernel/src/proc/thread.rs @@ -0,0 +1,423 @@ +//! Facilities for controlling threads - smallest units of +//! execution in the operating system +use crate::arch::aarch64::exception::ExceptionFrame; +use crate::proc::{ + wait::{Wait, WaitStatus}, + Process, ProcessRef, sched, THREADS, +}; +use crate::sync::IrqSafeSpinLock; +use crate::util::InitOnce; +use alloc::rc::Rc; +use core::cell::UnsafeCell; +use core::sync::atomic::{AtomicU32, Ordering}; +use libsys::{ + error::Errno, + proc::{ExitCode, Pid}, + signal::Signal, +}; + +pub use crate::arch::platform::context::{self, Context}; + +/// Convenience wrapper for [Thread] references +pub type ThreadRef = Rc; + +/// List of possible process states +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum State { + /// Process is ready to be executed and/or is scheduled for it + Ready, + /// Process is currently running or is in system call/interrupt handler + Running, + /// Process has finished execution and is waiting to be reaped + Finished, + /// Process is waiting for some external event + Waiting, +} + +struct ThreadInner { + id: u32, + state: State, + owner: Option, + pending_wait: Option<&'static Wait>, + wait_status: WaitStatus, + signal_entry: usize, + signal_stack: usize, +} + +/// Thread control data +pub struct Thread { + inner: IrqSafeSpinLock, + exit_wait: Wait, + exit_status: InitOnce, + pub(super) ctx: UnsafeCell, + signal_ctx: UnsafeCell, + signal_pending: AtomicU32, + cpu: AtomicU32, +} + +impl Thread { + const CPU_NONE: u32 = u32::MAX; + + /// Returns currently active thread [Rc]-reference + #[inline] + pub fn current() -> ThreadRef { + sched::current_thread() + } + + /// Returns a reference to thread `tid`, if it exists + #[inline] + pub fn get(tid: u32) -> Option { + THREADS.lock().get(&tid).cloned() + } + + /// Returns the owner process + #[inline] + pub fn owner(&self) -> Option { + self.inner.lock().owner.and_then(Process::get) + } + + /// Returns [Pid] of the owner process + pub fn owner_id(&self) -> Option { + self.inner.lock().owner + } + + /// Creates a new kernel process + pub fn new_kernel( + owner: Option, + entry: extern "C" fn(usize) -> !, + arg: usize, + ) -> Result { + let id = new_tid(); + + let res = Rc::new(Self { + cpu: AtomicU32::new(Self::CPU_NONE), + ctx: UnsafeCell::new(Context::kernel(entry as usize, arg)), + signal_ctx: UnsafeCell::new(Context::empty()), + signal_pending: AtomicU32::new(0), + exit_wait: Wait::new("thread_exit"), + exit_status: InitOnce::new(), + inner: IrqSafeSpinLock::new(ThreadInner { + signal_entry: 0, + signal_stack: 0, + id, + owner, + pending_wait: None, + wait_status: WaitStatus::Done, + state: State::Ready, + }), + }); + debugln!("New kernel thread: {:?}", id); + assert!(THREADS.lock().insert(id, res.clone()).is_none()); + Ok(res) + } + + /// Creates a new userspace process + pub fn new_user( + owner: Pid, + entry: usize, + stack: usize, + arg: usize, + ttbr0: usize, + ) -> Result { + let id = new_tid(); + + let res = Rc::new(Self { + cpu: AtomicU32::new(Self::CPU_NONE), + ctx: UnsafeCell::new(Context::user(entry, arg, ttbr0, stack)), + signal_ctx: UnsafeCell::new(Context::empty()), + signal_pending: AtomicU32::new(0), + exit_wait: Wait::new("thread_exit"), + exit_status: InitOnce::new(), + inner: IrqSafeSpinLock::new(ThreadInner { + signal_entry: 0, + signal_stack: 0, + id, + owner: Some(owner), + pending_wait: None, + wait_status: WaitStatus::Done, + state: State::Ready, + }), + }); + debugln!("New userspace thread: {:?}", id); + assert!(THREADS.lock().insert(id, res.clone()).is_none()); + Ok(res) + } + + /// Creates a fork thread cloning `frame` context + pub fn fork( + owner: Option, + frame: &ExceptionFrame, + ttbr0: usize, + ) -> Result { + let id = new_tid(); + + let res = Rc::new(Self { + cpu: AtomicU32::new(Self::CPU_NONE), + ctx: UnsafeCell::new(Context::fork(frame, ttbr0)), + signal_ctx: UnsafeCell::new(Context::empty()), + signal_pending: AtomicU32::new(0), + exit_wait: Wait::new("thread_exit"), + exit_status: InitOnce::new(), + inner: IrqSafeSpinLock::new(ThreadInner { + signal_entry: 0, + signal_stack: 0, + id, + owner, + pending_wait: None, + wait_status: WaitStatus::Done, + state: State::Ready, + }), + }); + debugln!("Forked new user thread: {:?}", id); + assert!(THREADS.lock().insert(id, res.clone()).is_none()); + Ok(res) + } + + /// Returns the thread ID + #[inline] + pub fn id(&self) -> u32 { + self.inner.lock().id + } + + /// Schedules an initial thread for execution + /// + /// # Safety + /// + /// Unsafe: only allowed to be called once, repeated calls + /// will generate undefined behavior + pub unsafe fn enter(cpu: u32, thread: ThreadRef) -> ! { + // FIXME use some global lock to guarantee atomicity of thread entry? + thread.inner.lock().state = State::Running; + thread.cpu.store(cpu, Ordering::SeqCst); + thread.current_context().enter() + } + + /// + pub fn cpu(&self) -> u32 { + self.cpu.load(Ordering::SeqCst) + } + + /// Schedules a next thread for execution + /// + /// # Safety + /// + /// Unsafe: + /// + /// * Does not ensure src and dst threads are not the same thread + /// * Does not ensure src is actually current context + pub unsafe fn switch(cpu: u32, src: ThreadRef, dst: ThreadRef, discard: bool) { + { + let mut src_lock = src.inner.lock(); + let mut dst_lock = dst.inner.lock(); + + if !discard { + assert_eq!(src_lock.state, State::Running); + src_lock.state = State::Ready; + } + // assert!(dst_lock.state == State::Ready || dst_lock.state == State::Waiting); + dst_lock.state = State::Running; + + src.cpu.store(Self::CPU_NONE, Ordering::SeqCst); + dst.cpu.store(cpu, Ordering::SeqCst); + } + + let src_ctx = src.current_context(); + let dst_ctx = dst.current_context(); + + (&mut *src_ctx).switch(&mut *dst_ctx); + } + + #[allow(clippy::mut_from_ref)] + fn current_context(&self) -> &mut Context { + if self.signal_pending.load(Ordering::Acquire) != 0 { + unsafe { &mut *self.signal_ctx.get() } + } else { + unsafe { &mut *self.ctx.get() } + } + } + + /// Suspends current process with a "waiting" status + pub fn enter_wait(&self) { + let drop = { + let mut lock = self.inner.lock(); + let drop = lock.state == State::Running; + lock.state = State::Waiting; + sched::dequeue(lock.id); + // SCHED.dequeue(lock.id); + drop + }; + if drop { + sched::switch(true); + // SCHED.switch(true); + } + } + + /// Changes process wait condition status + pub fn setup_wait(&self, wait: *const Wait) { + let mut lock = self.inner.lock(); + // FIXME this is not cool + lock.pending_wait = Some(unsafe { &*wait }); + lock.wait_status = WaitStatus::Pending; + } + + /// Suspends current thread until thread `tid` terminates + pub fn waittid(tid: u32) -> Result<(), Errno> { + loop { + let thread = THREADS + .lock() + .get(&tid) + .cloned() + .ok_or(Errno::DoesNotExist)?; + + if thread.state() == State::Finished { + // TODO remove thread from its parent? + return Ok(()); + } + + thread.exit_wait.wait(None)?; + } + } + + /// Updates pending wait status + pub fn set_wait_status(&self, status: WaitStatus) { + let mut lock = self.inner.lock(); + lock.wait_status = status; + } + + /// Resets wait channel back to initial state + pub fn reset_wait(&self) { + let mut lock = self.inner.lock(); + lock.pending_wait = None; + lock.wait_status = WaitStatus::Done; + } + + /// Returns status of the thread's pending wait + pub fn wait_status(&self) -> WaitStatus { + self.inner.lock().wait_status + } + + /// Switches current thread back from signal handler + pub fn return_from_signal(&self) { + if self.signal_pending.load(Ordering::Acquire) == 0 { + panic!("TODO handle cases when returning from no signal"); + } + self.signal_pending.store(0, Ordering::Release); + + let src_ctx = self.signal_ctx.get(); + let dst_ctx = self.ctx.get(); + + assert_eq!(self.inner.lock().state, State::Running); + + unsafe { + (&mut *src_ctx).switch(&mut *dst_ctx); + } + } + + /// Returns the thread state + #[inline] + pub fn state(&self) -> State { + self.inner.lock().state + } + + /// Sets the thread's owner process ID + pub fn set_owner(&self, pid: Pid) { + self.inner.lock().owner = Some(pid); + } + + /// Sets up values needed for signal entry + pub fn set_signal_entry(&self, entry: usize, stack: usize) { + let mut lock = self.inner.lock(); + lock.signal_entry = entry; + lock.signal_stack = stack; + } + + /// Sets up a context for signal handler + pub fn setup_signal(self: ThreadRef, signal: Signal, ttbr0: usize) { + if self + .signal_pending + .compare_exchange_weak(0, signal as u32, Ordering::SeqCst, Ordering::Relaxed) + .is_err() + { + panic!("Already handling a signal (maybe handle this case)"); + } + + let lock = self.inner.lock(); + if lock.signal_entry == 0 || lock.signal_stack == 0 { + drop(lock); + Process::exit_thread(self, ExitCode::from(-1)); + return; + } + + let signal_ctx = unsafe { &mut *self.signal_ctx.get() }; + + debugln!( + "Signal entry: tid={}, pc={:#x}, sp={:#x}, ttbr0={:#x}", + lock.id, + lock.signal_entry, + lock.signal_stack, + ttbr0 + ); + + unsafe { + signal_ctx.setup_signal_entry( + lock.signal_entry, + signal as usize, + ttbr0, + lock.signal_stack, + ); + } + } + + /// Switches process main thread to a signal handler + pub fn enter_signal(self: ThreadRef, signal: Signal, ttbr0: usize) { + let src_ctx = self.ctx.get(); + let signal_ctx = unsafe { &mut *self.signal_ctx.get() }; + + assert_eq!(self.state(), State::Running); + self.setup_signal(signal, ttbr0); + + unsafe { + (&mut *src_ctx).switch(signal_ctx); + } + } + + /// Interrupts pending wait (from signal routines) + pub fn interrupt_wait(&self, enqueue: bool) { + let mut lock = self.inner.lock(); + let tid = lock.id; + let wait = lock.pending_wait.take(); + drop(lock); + if let Some(wait) = wait { + wait.abort(tid, enqueue); + } + } + + /// Cleans up any resources of the thread and aborts + /// pending wait, if any + pub fn terminate(&self, status: ExitCode) { + let mut lock = self.inner.lock(); + lock.state = State::Finished; + let tid = lock.id; + let wait = lock.pending_wait.take(); + drop(lock); + if let Some(wait) = wait { + wait.abort(tid, false); + } + self.exit_status.init(status); + self.exit_wait.wakeup_all(); + } +} + +impl Drop for Thread { + fn drop(&mut self) { + debugln!("Dropping process {:?}", self.id()); + } +} + +/// Allocates a new thread ID +pub fn new_tid() -> u32 { + static LAST: AtomicU32 = AtomicU32::new(1); + let id = LAST.fetch_add(1, Ordering::Relaxed); + assert!(id < 256, "Out of user TIDs"); + id +} diff --git a/kernel/src/proc/wait.rs b/kernel/src/proc/wait.rs index a14d960..7e1012d 100644 --- a/kernel/src/proc/wait.rs +++ b/kernel/src/proc/wait.rs @@ -2,24 +2,40 @@ use crate::arch::machine; use crate::dev::timer::TimestampSource; -use crate::proc::{self, sched, Pid, Process}; +use crate::proc::{self, sched, Thread, ThreadRef}; use crate::sync::IrqSafeSpinLock; use alloc::collections::LinkedList; use core::time::Duration; -use error::Errno; +use libsys::{error::Errno, stat::FdSet}; /// Wait channel structure. Contains a queue of processes /// waiting for some event to happen. pub struct Wait { - queue: IrqSafeSpinLock>, + queue: IrqSafeSpinLock>, + #[allow(dead_code)] + name: &'static str +} + +/// Status of a (possibly) pending wait +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum WaitStatus { + /// In progress + Pending, + /// Wait was interrupted by a signal + Interrupted, + /// Channel reported data available + Done, } struct Timeout { - pid: Pid, + tid: u32, deadline: Duration, } static TICK_LIST: IrqSafeSpinLock> = IrqSafeSpinLock::new(LinkedList::new()); +/// Global wait channel for blocking on select. Gets notified +/// of ANY I/O operations available, so not very efficient. +pub static WAIT_SELECT: Wait = Wait::new("select"); /// Checks for any timed out wait channels and interrupts them pub fn tick() { @@ -29,9 +45,14 @@ pub fn tick() { while let Some(item) = cursor.current() { if time > item.deadline { - let pid = item.pid; + let tid = item.tid; cursor.remove_current(); - sched::enqueue(pid); + todo!(); +//<<<<<<< HEAD +// sched::enqueue(pid); +//======= +// SCHED.enqueue(tid); +//>>>>>>> feat/thread } else { cursor.move_next(); } @@ -41,7 +62,7 @@ pub fn tick() { /// Suspends current process for given duration pub fn sleep(timeout: Duration, remaining: &mut Duration) -> Result<(), Errno> { // Dummy wait descriptor which will never receive notifications - static SLEEP_NOTIFY: Wait = Wait::new(); + static SLEEP_NOTIFY: Wait = Wait::new("sleep"); let deadline = machine::local_timer().timestamp()? + timeout; match SLEEP_NOTIFY.wait(Some(deadline)) { Err(Errno::Interrupt) => { @@ -54,11 +75,92 @@ pub fn sleep(timeout: Duration, remaining: &mut Duration) -> Result<(), Errno> { } } +/// Suspends current process until some file descriptor +/// signals data available +pub fn select( + thread: ThreadRef, + mut rfds: Option<&mut FdSet>, + mut wfds: Option<&mut FdSet>, + timeout: Option, +) -> Result { + if wfds.is_none() && rfds.is_none() { + todo!(); + } + let read = rfds.as_deref().map(FdSet::clone); + let write = wfds.as_deref().map(FdSet::clone); + rfds.as_deref_mut().map(FdSet::reset); + wfds.as_deref_mut().map(FdSet::reset); + + let deadline = timeout.map(|v| v + machine::local_timer().timestamp().unwrap()); + let proc = thread.owner().unwrap(); + let mut io = proc.io.lock(); + + loop { + if let Some(read) = &read { + for fd in read.iter() { + let file = io.file(fd)?; + if file.borrow().is_ready(false)? { + rfds.as_mut().unwrap().set(fd); + return Ok(1); + } + } + } + if let Some(write) = &write { + for fd in write.iter() { + let file = io.file(fd)?; + if file.borrow().is_ready(true)? { + wfds.as_mut().unwrap().set(fd); + return Ok(1); + } + } + } + + // Suspend + match WAIT_SELECT.wait(deadline) { + Err(Errno::TimedOut) => return Ok(0), + Err(e) => return Err(e), + Ok(_) => {} + } + } +} + impl Wait { /// Constructs a new wait channel - pub const fn new() -> Self { + pub const fn new(name: &'static str) -> Self { Self { queue: IrqSafeSpinLock::new(LinkedList::new()), + name + } + } + + /// Interrupt wait pending on the channel + pub fn abort(&self, tid: u32, enqueue: bool) { + let mut queue = self.queue.lock(); + let mut tick_lock = TICK_LIST.lock(); + let mut cursor = tick_lock.cursor_front_mut(); + while let Some(item) = cursor.current() { + if tid == item.tid { + cursor.remove_current(); + break; + } else { + cursor.move_next(); + } + } + + let mut cursor = queue.cursor_front_mut(); + while let Some(item) = cursor.current() { + if tid == *item { + cursor.remove_current(); + let thread = Thread::get(tid).unwrap(); + thread.set_wait_status(WaitStatus::Interrupted); + if enqueue { + sched::enqueue(tid); + // SCHED.enqueue(tid); + } + break; + } else { + cursor.move_next(); + } } } @@ -67,12 +169,12 @@ impl Wait { let mut queue = self.queue.lock(); let mut count = 0; while limit != 0 && !queue.is_empty() { - let pid = queue.pop_front(); - if let Some(pid) = pid { + let tid = queue.pop_front(); + if let Some(tid) = tid { let mut tick_lock = TICK_LIST.lock(); let mut cursor = tick_lock.cursor_front_mut(); while let Some(item) = cursor.current() { - if pid == item.pid { + if tid == item.tid { cursor.remove_current(); break; } else { @@ -81,8 +183,8 @@ impl Wait { } drop(tick_lock); - proc::process(pid).set_wait_flag(false); - sched::enqueue(pid); + Thread::get(tid).unwrap().set_wait_status(WaitStatus::Done); + sched::enqueue(tid); } limit -= 1; @@ -104,26 +206,33 @@ impl Wait { /// Suspends current process until event is signalled or /// (optional) deadline is reached pub fn wait(&self, deadline: Option) -> Result<(), Errno> { - let proc = Process::current(); + let thread = Thread::current(); //let deadline = timeout.map(|t| machine::local_timer().timestamp().unwrap() + t); let mut queue_lock = self.queue.lock(); - queue_lock.push_back(proc.id()); - proc.set_wait_flag(true); + queue_lock.push_back(thread.id()); + thread.setup_wait(self); + if let Some(deadline) = deadline { TICK_LIST.lock().push_back(Timeout { - pid: proc.id(), + tid: thread.id(), deadline, }); } loop { - if !proc.wait_flag() { - return Ok(()); - } + match thread.wait_status() { + WaitStatus::Pending => {} + WaitStatus::Done => { + return Ok(()); + } + WaitStatus::Interrupted => { + return Err(Errno::Interrupt); + } + }; drop(queue_lock); - proc.enter_wait(); + thread.enter_wait(); queue_lock = self.queue.lock(); if let Some(deadline) = deadline { @@ -131,7 +240,7 @@ impl Wait { let mut cursor = queue_lock.cursor_front_mut(); while let Some(&mut item) = cursor.current() { - if proc.id() == item { + if thread.id() == item { cursor.remove_current(); break; } else { diff --git a/kernel/src/syscall/arg.rs b/kernel/src/syscall/arg.rs index 51c46b0..88a4eae 100644 --- a/kernel/src/syscall/arg.rs +++ b/kernel/src/syscall/arg.rs @@ -1,65 +1,190 @@ //! System call argument ABI helpers use crate::mem; -use core::mem::size_of; -use error::Errno; +use core::alloc::Layout; +use libsys::error::Errno; +use crate::proc::Process; -fn translate(virt: usize) -> Option { - let mut res: usize; - unsafe { - asm!("at s1e1r, {}; mrs {}, par_el1", in(reg) virt, out(reg) res); - } - if res & 1 == 0 { - Some(res & !(0xFFF | (0xFF << 56))) - } else { - None +// TODO _mut() versions checking whether pages are actually writable + +macro_rules! invalid_memory { + ($($args:tt)+) => { + warnln!($($args)+); + #[cfg(feature = "aggressive_syscall")] + { + use libsys::signal::Signal; + use crate::proc::Thread; + + let thread = Thread::current(); + let proc = thread.owner().unwrap(); + proc.enter_fault_signal(thread, Signal::SegmentationFault); + } + return Err(Errno::InvalidArgument); } } -/// Unwraps a slim structure pointer -pub fn validate_user_ptr_struct<'a, T>(base: usize) -> Result<&'a mut T, Errno> { - let bytes = validate_user_ptr(base, size_of::())?; +#[inline(always)] +fn is_el0_accessible(virt: usize, write: bool) -> bool { + let mut res: usize; + unsafe { + if write { + asm!("at s1e0w, {}; mrs {}, par_el1", in(reg) virt, out(reg) res); + } else { + asm!("at s1e0r, {}; mrs {}, par_el1", in(reg) virt, out(reg) res); + } + } + res & 1 == 0 +} + +/// Checks given argument and interprets it as a `T` reference +pub fn struct_ref<'a, T>(base: usize) -> Result<&'a T, Errno> { + let layout = Layout::new::(); + if base % layout.align() != 0 { + invalid_memory!( + "Structure pointer is misaligned: base={:#x}, expected {:?}", + base, + layout + ); + } + let bytes = buf_ref(base, layout.size())?; + Ok(unsafe { &*(bytes.as_ptr() as *const T) }) +} + +/// Checks given argument and interprets it as a `T` mutable reference +pub fn struct_mut<'a, T>(base: usize) -> Result<&'a mut T, Errno> { + let layout = Layout::new::(); + if base % layout.align() != 0 { + invalid_memory!( + "Structure pointer is misaligned: base={:#x}, expected {:?}", + base, + layout + ); + } + let bytes = buf_mut(base, layout.size())?; Ok(unsafe { &mut *(bytes.as_mut_ptr() as *mut T) }) } -/// Unwraps an user buffer reference -pub fn validate_user_ptr<'a>(base: usize, len: usize) -> Result<&'a mut [u8], Errno> { +/// Checks given argument and interprets it as a `T` array buffer of size `count` +pub fn struct_buf_ref<'a, T>(base: usize, count: usize) -> Result<&'a [T], Errno> { + let layout = Layout::array::(count).unwrap(); + if base % layout.align() != 0 { + invalid_memory!( + "Structure pointer is misaligned: base={:#x}, expected {:?}", + base, + layout + ); + } + let bytes = buf_ref(base, layout.size())?; + Ok(unsafe { core::slice::from_raw_parts(bytes.as_ptr() as *const T, count) }) +} + +/// Checks given argument and interprets it as a `T` array buffer of size `count` +pub fn struct_buf_mut<'a, T>(base: usize, count: usize) -> Result<&'a mut [T], Errno> { + let layout = Layout::array::(count).unwrap(); + if base % layout.align() != 0 { + invalid_memory!( + "Structure pointer is misaligned: base={:#x}, expected {:?}", + base, + layout + ); + } + let bytes = buf_mut(base, layout.size())?; + Ok(unsafe { core::slice::from_raw_parts_mut(bytes.as_mut_ptr() as *mut T, count) }) +} + +/// Checks given argument and interprets it as a `Option<&'a T>` +pub fn option_struct_ref<'a, T>(base: usize) -> Result, Errno> { + if base == 0 { + Ok(None) + } else { + struct_ref(base).map(Some) + } +} + +/// Checks given argument and interprets it as a `Option<&'a mut T>` +pub fn option_struct_mut<'a, T>(base: usize) -> Result, Errno> { + if base == 0 { + Ok(None) + } else { + struct_mut(base).map(Some) + } +} + +/// Validates that the argument pointer is accessible for requested operation +/// for current process +pub fn validate_ptr(base: usize, len: usize, write: bool) -> Result<(), Errno> { if base > mem::KERNEL_OFFSET || base + len > mem::KERNEL_OFFSET { - warnln!( + invalid_memory!( "User region refers to kernel memory: base={:#x}, len={:#x}", base, len ); - return Err(Errno::InvalidArgument); } + let process = Process::current(); + for i in (base / mem::PAGE_SIZE)..((base + len + mem::PAGE_SIZE - 1) / mem::PAGE_SIZE) { - if translate(i * mem::PAGE_SIZE).is_none() { - warnln!( - "User region refers to unmapped memory: base={:#x}, len={:#x} (page {:#x})", + if !is_el0_accessible(i * mem::PAGE_SIZE, write) { + // It's possible a CoW page hasn't yet been cloned when trying + // a write access + let res = if write { + process.manipulate_space(|space| { + space.try_cow_copy(i * mem::PAGE_SIZE) + }) + } else { + Err(Errno::DoesNotExist) + }; + + if res.is_ok() { + continue; + } + + invalid_memory!( + "User region refers to inaccessible/unmapped memory: base={:#x}, len={:#x} (page {:#x}, write={})", base, len, - i * mem::PAGE_SIZE + i * mem::PAGE_SIZE, + write ); - return Err(Errno::InvalidArgument); } } + Ok(()) +} + +/// Checks given argument and interprets it as a byte buffer +pub fn buf_ref<'a>(base: usize, len: usize) -> Result<&'a [u8], Errno> { + validate_ptr(base, len, false)?; + Ok(unsafe { core::slice::from_raw_parts(base as *const u8, len) }) +} + +/// Checks given argument and interprets it as a mutable byte buffer +pub fn buf_mut<'a>(base: usize, len: usize) -> Result<&'a mut [u8], Errno> { + validate_ptr(base, len, true)?; Ok(unsafe { core::slice::from_raw_parts_mut(base as *mut u8, len) }) } -/// Unwraps a nullable user buffer reference -pub fn validate_user_ptr_null<'a>(base: usize, len: usize) -> Result, Errno> { +/// Checks possibly NULL given argument and interprets it as a byte buffer +pub fn option_buf_ref<'a>(base: usize, len: usize) -> Result, Errno> { if base == 0 { Ok(None) } else { - validate_user_ptr(base, len).map(Some) + buf_ref(base, len).map(Some) + } +} + +/// Checks possibly NULL given argument and interprets it as a mutable byte buffer +pub fn option_buf_mut<'a>(base: usize, len: usize) -> Result, Errno> { + if base == 0 { + Ok(None) + } else { + buf_mut(base, len).map(Some) } } /// Unwraps user string argument -pub fn validate_user_str<'a>(base: usize, len: usize) -> Result<&'a str, Errno> { - let bytes = validate_user_ptr(base, len)?; +pub fn str_ref<'a>(base: usize, len: usize) -> Result<&'a str, Errno> { + let bytes = buf_ref(base, len)?; core::str::from_utf8(bytes).map_err(|_| { warnln!( "User string contains invalid UTF-8 characters: base={:#x}, len={:#x}", @@ -69,49 +194,3 @@ pub fn validate_user_str<'a>(base: usize, len: usize) -> Result<&'a str, Errno> Errno::InvalidArgument }) } -// if base > mem::KERNEL_OFFSET { -// warnln!("User string refers to kernel memory: base={:#x}", base); -// return Err(Errno::InvalidArgument); -// } -// -// let base_ptr = base as *const u8; -// let mut len = 0; -// let mut page_valid = false; -// loop { -// if len == limit { -// warnln!("User string exceeded limit: base={:#x}", base); -// return Err(Errno::InvalidArgument); -// } -// -// if (base + len) % mem::PAGE_SIZE == 0 { -// page_valid = false; -// } -// -// if !page_valid && translate((base + len) & !0xFFF).is_none() { -// warnln!( -// "User string refers to unmapped memory: base={:#x}, off={:#x}", -// base, -// len -// ); -// return Err(Errno::InvalidArgument); -// } -// -// page_valid = true; -// -// let byte = unsafe { *base_ptr.add(len) }; -// if byte == 0 { -// break; -// } -// -// len += 1; -// } -// -// let slice = unsafe { core::slice::from_raw_parts(base_ptr, len) }; -// core::str::from_utf8(slice).map_err(|_| { -// warnln!( -// "User string contains invalid UTF-8 characters: base={:#x}", -// base -// ); -// Errno::InvalidArgument -// }) -// } diff --git a/kernel/src/syscall/mod.rs b/kernel/src/syscall/mod.rs index aa3d68a..66568c9 100644 --- a/kernel/src/syscall/mod.rs +++ b/kernel/src/syscall/mod.rs @@ -1,21 +1,30 @@ //! System call implementation -use crate::arch::platform::exception::ExceptionFrame; +use crate::arch::{machine, platform::exception::ExceptionFrame}; +use crate::mem::{virt::MapAttributes, phys::PageUsage}; use crate::debug::Level; -use crate::proc::{elf, wait, Pid, Process, ProcessIo}; +use crate::dev::timer::TimestampSource; +use crate::fs::create_filesystem; +use crate::proc::{self, sched, elf, wait, Process, ProcessIo, Thread}; use core::mem::size_of; use core::ops::DerefMut; use core::time::Duration; -use error::Errno; -use libcommon::{Read, Write}; -use syscall::{ - abi, - stat::{AT_EMPTY_PATH, AT_FDCWD}, +use libsys::{ + abi::SystemCall, + debug::TraceLevel, + error::Errno, + ioctl::IoctlCmd, + proc::{ExitCode, Pid, MemoryAccess}, + signal::{Signal, SignalDestination}, + stat::{ + AccessMode, DirectoryEntry, FdSet, FileDescriptor, FileMode, GroupId, MountOptions, + OpenFlags, Stat, UserId, AT_EMPTY_PATH, + }, + traits::{Read, Write}, }; -use vfs::{FileMode, OpenFlags, Stat, VnodeRef, IoctlCmd}; +use vfs::VnodeRef; pub mod arg; -pub use arg::*; /// Creates a "fork" process from current one using its register frame. /// See [Process::fork()]. @@ -30,11 +39,11 @@ pub unsafe fn sys_fork(regs: &mut ExceptionFrame) -> Result { fn find_at_node>( io: &mut T, - at_fd: usize, + at_fd: Option, filename: &str, empty_path: bool, ) -> Result { - let at = if at_fd as i32 != AT_FDCWD { + let at = if let Some(at_fd) = at_fd { io.file(at_fd)?.borrow().node() } else { None @@ -48,95 +57,66 @@ fn find_at_node>( } /// Main system call dispatcher function -pub fn syscall(num: usize, args: &[usize]) -> Result { +pub fn syscall(num: SystemCall, args: &[usize]) -> Result { match num { - // Process management system calls - abi::SYS_EXIT => { - Process::current().exit(args[0] as i32); - unreachable!(); - } + // I/O + SystemCall::Read => { + let proc = Process::current(); + let fd = FileDescriptor::from(args[0] as u32); + let mut io = proc.io.lock(); + let buf = arg::buf_mut(args[1], args[2])?; - // I/O system calls - abi::SYS_OPENAT => { - let at_fd = args[0]; - let path = validate_user_str(args[1], args[2])?; + io.file(fd)?.borrow_mut().read(buf) + } + SystemCall::Write => { + let proc = Process::current(); + let fd = FileDescriptor::from(args[0] as u32); + let mut io = proc.io.lock(); + let buf = arg::buf_ref(args[1], args[2])?; + + io.file(fd)?.borrow_mut().write(buf) + } + SystemCall::Open => { + let at_fd = FileDescriptor::from_i32(args[0] as i32)?; + let path = arg::str_ref(args[1], args[2])?; let mode = FileMode::from_bits(args[3] as u32).ok_or(Errno::InvalidArgument)?; let opts = OpenFlags::from_bits(args[4] as u32).ok_or(Errno::InvalidArgument)?; let proc = Process::current(); let mut io = proc.io.lock(); - let at = if at_fd as i32 == AT_FDCWD { - None + let at = if let Some(fd) = at_fd { + io.file(fd)?.borrow().node() } else { - io.file(at_fd)?.borrow().node() + None }; let file = io.ioctx().open(at, path, mode, opts)?; - io.place_file(file) + Ok(u32::from(io.place_file(file)?) as usize) } - abi::SYS_READ => { + SystemCall::Close => { let proc = Process::current(); let mut io = proc.io.lock(); - let buf = validate_user_ptr(args[1], args[2])?; - - io.file(args[0])?.borrow_mut().read(buf) - } - abi::SYS_WRITE => { - let proc = Process::current(); - let mut io = proc.io.lock(); - let buf = validate_user_ptr(args[1], args[2])?; - - io.file(args[0])?.borrow_mut().write(buf) - } - abi::SYS_FSTATAT => { - let at_fd = args[0]; - let filename = validate_user_str(args[1], args[2])?; - let buf = validate_user_ptr_struct::(args[3])?; - let flags = args[4] as u32; - - let proc = Process::current(); - let mut io = proc.io.lock(); - find_at_node(&mut io, at_fd, filename, flags & AT_EMPTY_PATH != 0)?.stat(buf)?; - Ok(0) - } - abi::SYS_CLOSE => { - let proc = Process::current(); - let mut io = proc.io.lock(); - let fd = args[0]; + let fd = FileDescriptor::from(args[0] as u32); io.close_file(fd)?; Ok(0) } - abi::SYS_EXECVE => { - let node = { - let proc = Process::current(); - let mut io = proc.io.lock(); - let filename = validate_user_str(args[0], args[1])?; - // TODO argv, envp array passing ABI? - let node = io.ioctx().find(None, filename, true)?; - drop(io); - node - }; - let file = node.open(OpenFlags::O_RDONLY)?; - Process::execve(|space| elf::load_elf(space, file), 0).unwrap(); - panic!(); - } - abi::SYS_WAITPID => { - // TODO special "pid" values - let pid = unsafe { Pid::from_raw(args[0] as u32) }; - let status = validate_user_ptr_struct::(args[1])?; + SystemCall::FileStatus => { + let at_fd = FileDescriptor::from_i32(args[0] as i32)?; + let filename = arg::str_ref(args[1], args[2])?; + let buf = arg::struct_mut::(args[3])?; + let flags = args[4] as u32; - match Process::waitpid(pid) { - Ok(exit) => { - *status = i32::from(exit); - Ok(0) - }, - _ => todo!() - } + let proc = Process::current(); + let mut io = proc.io.lock(); + let stat = + find_at_node(&mut io, at_fd, filename, flags & AT_EMPTY_PATH != 0)?.stat()?; + *buf = stat; + Ok(0) } - abi::SYS_IOCTL => { - let fd = args[0]; + SystemCall::Ioctl => { + let fd = FileDescriptor::from(args[0] as u32); let cmd = IoctlCmd::try_from(args[1] as u32)?; let proc = Process::current(); @@ -144,20 +124,198 @@ pub fn syscall(num: usize, args: &[usize]) -> Result { let node = io.file(fd)?.borrow().node().ok_or(Errno::InvalidFile)?; node.ioctl(cmd, args[2], args[3]) - }, - - // Extra system calls - abi::SYS_EX_DEBUG_TRACE => { - let buf = validate_user_ptr(args[0], args[1])?; - print!(Level::Debug, "[trace] "); - for &byte in buf.iter() { - print!(Level::Debug, "{}", byte as char); - } - println!(Level::Debug, ""); - Ok(args[1]) } - abi::SYS_EX_NANOSLEEP => { - let rem_buf = validate_user_ptr_null(args[1], size_of::() * 2)?; + SystemCall::Select => { + let rfds = arg::option_struct_mut::(args[0])?; + let wfds = arg::option_struct_mut::(args[1])?; + let timeout = if args[2] == 0 { + None + } else { + Some(Duration::from_nanos(args[2] as u64)) + }; + + wait::select(Thread::current(), rfds, wfds, timeout) + } + SystemCall::Access => { + let at_fd = FileDescriptor::from_i32(args[0] as i32)?; + let path = arg::str_ref(args[1], args[2])?; + let mode = AccessMode::from_bits(args[3] as u32).ok_or(Errno::InvalidArgument)?; + let flags = args[4] as u32; + + let proc = Process::current(); + let mut io = proc.io.lock(); + + find_at_node(&mut io, at_fd, path, flags & AT_EMPTY_PATH != 0)? + .check_access(io.ioctx(), mode)?; + Ok(0) + } + SystemCall::ReadDirectory => { + let proc = Process::current(); + let fd = FileDescriptor::from(args[0] as u32); + let mut io = proc.io.lock(); + let buf = arg::struct_buf_mut::(args[1], args[2])?; + + io.file(fd)?.borrow_mut().readdir(buf) + } + SystemCall::GetUserId => { + let proc = Process::current(); + let uid = proc.io.lock().uid(); + Ok(u32::from(uid) as usize) + } + SystemCall::GetGroupId => { + let proc = Process::current(); + let gid = proc.io.lock().gid(); + Ok(u32::from(gid) as usize) + } + SystemCall::DuplicateFd => { + let src = FileDescriptor::from(args[0] as u32); + let dst = FileDescriptor::from_i32(args[1] as i32)?; + + let proc = Process::current(); + let mut io = proc.io.lock(); + + let res = io.duplicate_file(src, dst)?; + + Ok(u32::from(res) as usize) + } + SystemCall::SetUserId => { + let uid = UserId::from(args[0] as u32); + let proc = Process::current(); + proc.io.lock().set_uid(uid)?; + Ok(0) + } + SystemCall::SetGroupId => { + let gid = GroupId::from(args[0] as u32); + let proc = Process::current(); + proc.io.lock().set_gid(gid)?; + Ok(0) + } + SystemCall::SetCurrentDirectory => { + let path = arg::str_ref(args[0], args[1])?; + let proc = Process::current(); + proc.io.lock().ioctx().chdir(path)?; + Ok(0) + } + SystemCall::GetCurrentDirectory => { + todo!() + } + SystemCall::Seek => { + todo!() + } + SystemCall::MapMemory => { + let len = args[1]; + if len == 0 || (len & 0xFFF) != 0 { + return Err(Errno::InvalidArgument); + } + let acc = MemoryAccess::from_bits(args[2] as u32).ok_or(Errno::InvalidArgument)?; + let _flags = MemoryAccess::from_bits(args[3] as u32).ok_or(Errno::InvalidArgument)?; + + let mut attrs = MapAttributes::NOT_GLOBAL | MapAttributes::SH_OUTER | MapAttributes::PXN; + if !acc.contains(MemoryAccess::READ) { + return Err(Errno::NotImplemented); + } + if acc.contains(MemoryAccess::WRITE) { + if acc.contains(MemoryAccess::EXEC) { + return Err(Errno::PermissionDenied); + } + attrs |= MapAttributes::AP_BOTH_READWRITE; + } else { + attrs |= MapAttributes::AP_BOTH_READONLY; + } + if !acc.contains(MemoryAccess::EXEC) { + attrs |= MapAttributes::UXN; + } + + // TODO don't ignore flags + let usage = PageUsage::UserPrivate; + + let proc = Process::current(); + + proc.manipulate_space(move |space| { + space.allocate(0x100000000, 0xF00000000, len / 4096, attrs, usage) + }) + } + SystemCall::UnmapMemory => { + let addr = args[0]; + let len = args[1]; + + if addr == 0 || len == 0 || addr & 0xFFF != 0 || len & 0xFFF != 0 { + return Err(Errno::InvalidArgument); + } + + let proc = Process::current(); + proc.manipulate_space(move |space| { + space.free(addr, len / 4096) + })?; + Ok(0) + } + + // Process + SystemCall::Clone => { + let entry = args[0]; + let stack = args[1]; + let arg = args[2]; + + Process::current() + .new_user_thread(entry, stack, arg) + .map(|e| e as usize) + } + SystemCall::Exec => { + let filename = arg::str_ref(args[0], args[1])?; + let argv = arg::struct_buf_ref::<&str>(args[2], args[3])?; + // Validate each argument as well + for item in argv.iter() { + arg::validate_ptr(item.as_ptr() as usize, item.len(), false)?; + } + let node = { + let proc = Process::current(); + let mut io = proc.io.lock(); + // TODO argv, envp array passing ABI? + let node = io.ioctx().find(None, filename, true)?; + drop(io); + node + }; + let file = node.open(OpenFlags::O_RDONLY)?; + Process::execve(move |space| elf::load_elf(space, file), argv).unwrap(); + panic!(); + } + SystemCall::Exit => { + let status = ExitCode::from(args[0] as i32); + let flags = args[1]; + + if flags & (1 << 0) != 0 { + Process::exit_thread(Thread::current(), status); + } else { + Process::current().exit(status); + } + + unreachable!(); + } + SystemCall::WaitPid => { + // TODO special "pid" values + let pid = unsafe { Pid::from_raw(args[0] as u32) }; + let status = arg::struct_mut::(args[1])?; + + match Process::waitpid(pid) { + Ok(exit) => { + *status = i32::from(exit); + Ok(0) + } + e => e.map(|e| i32::from(e) as usize), + } + } + SystemCall::WaitTid => { + let tid = args[0] as u32; + + match Thread::waittid(tid) { + Ok(_) => Ok(0), + _ => todo!(), + } + } + SystemCall::GetPid => Ok(Process::current().id().value() as usize), + SystemCall::GetTid => Ok(Thread::current().id() as usize), + SystemCall::Sleep => { + let rem_buf = arg::option_buf_ref(args[1], size_of::() * 2)?; let mut rem = Duration::new(0, 0); let res = wait::sleep(Duration::from_nanos(args[0] as u64), &mut rem); if res == Err(Errno::Interrupt) { @@ -168,6 +326,125 @@ pub fn syscall(num: usize, args: &[usize]) -> Result { } res.map(|_| 0) } - _ => panic!("Undefined system call: {}", num), + SystemCall::SetSignalEntry => { + Thread::current().set_signal_entry(args[0], args[1]); + Ok(0) + } + SystemCall::SignalReturn => { + Thread::current().return_from_signal(); + unreachable!(); + } + SystemCall::SendSignal => { + let target = SignalDestination::from(args[0] as isize); + let signal = Signal::try_from(args[1] as u32)?; + + match target { + SignalDestination::This => Process::current().set_signal(signal), + SignalDestination::Process(pid) => Process::get(pid) + .ok_or(Errno::DoesNotExist)? + .set_signal(signal), + _ => todo!(), + }; + Ok(0) + } + SystemCall::Yield => { + sched::switch(false); + Ok(0) + } + SystemCall::GetSid => { + // TODO handle kernel processes here? + let pid = args[0] as u32; + let current = Process::current(); + let proc = if pid == 0 { + current + } else { + let pid = unsafe { Pid::from_raw(pid) }; + let proc = Process::get(pid).ok_or(Errno::DoesNotExist)?; + if proc.sid() != current.sid() { + return Err(Errno::PermissionDenied); + } + proc + }; + + Ok(proc.sid().value() as usize) + } + SystemCall::GetPgid => { + // TODO handle kernel processes here? + let pid = args[0] as u32; + let current = Process::current(); + let proc = if pid == 0 { + current + } else { + let pid = unsafe { Pid::from_raw(pid) }; + Process::get(pid).ok_or(Errno::DoesNotExist)? + }; + + Ok(proc.pgid().value() as usize) + } + SystemCall::GetPpid => Ok(Process::current().ppid().unwrap().value() as usize), + SystemCall::SetSid => { + let proc = Process::current(); + let mut io = proc.io.lock(); + + if let Some(_ctty) = io.ctty() { + todo!(); + } + + let id = proc.id(); + proc.set_sid(id); + Ok(id.value() as usize) + } + SystemCall::SetPgid => { + let pid = args[0] as u32; + let pgid = args[1] as u32; + + let current = Process::current(); + let proc = if pid == 0 { current } else { todo!() }; + + if pgid == 0 { + proc.set_pgid(proc.id()); + } else { + todo!(); + } + + Ok(proc.pgid().value() as usize) + } + + // System + SystemCall::GetCpuTime => { + let time = machine::local_timer().timestamp()?; + Ok(time.as_nanos() as usize) + } + SystemCall::Mount => { + let target = arg::str_ref(args[0], args[1])?; + let options = arg::struct_ref::(args[2])?; + + let proc = Process::current(); + let mut io = proc.io.lock(); + + debugln!("mount(target={:?}, options={:#x?})", target, options); + + let target_node = io.ioctx().find(None, target, true)?; + let root = create_filesystem(options)?; + + target_node.mount(root)?; + + Ok(0) + } + + // Debugging + SystemCall::DebugTrace => { + let level = TraceLevel::from_repr(args[0]) + .map(Level::from) + .ok_or(Errno::InvalidArgument)?; + let buf = arg::str_ref(args[1], args[2])?; + let thread = Thread::current(); + let proc = thread.owner().unwrap(); + println!(level, "[trace {:?}:{}] {}", proc.id(), thread.id(), buf); + Ok(args[1]) + } + + // Handled elsewhere + SystemCall::Fork => unreachable!(), } } diff --git a/libcommon/src/lib.rs b/libcommon/src/lib.rs deleted file mode 100644 index 961a822..0000000 --- a/libcommon/src/lib.rs +++ /dev/null @@ -1,49 +0,0 @@ -#![no_std] - -use error::Errno; - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum SeekDir { - Set, - End, - Current, -} - -pub trait Read { - fn read(&mut self, data: &mut [u8]) -> Result; -} - -pub trait Seek { - fn seek(&mut self, off: isize, whence: SeekDir) -> Result; -} - -pub trait Write { - fn write(&mut self, data: &[u8]) -> Result; -} - -pub fn path_component_left(path: &str) -> (&str, &str) { - if let Some((left, right)) = path.split_once('/') { - (left, right.trim_start_matches('/')) - } else { - (path, "") - } -} - -pub fn path_component_right(path: &str) -> (&str, &str) { - if let Some((left, right)) = path.trim_end_matches('/').rsplit_once('/') { - (left.trim_end_matches('/'), right) - } else { - ("", path) - } -} - -pub fn read_le32(src: &[u8]) -> u32 { - (src[0] as u32) | ((src[1] as u32) << 8) | ((src[2] as u32) << 16) | ((src[3] as u32) << 24) -} - -pub fn read_le16(src: &[u8]) -> u16 { - (src[0] as u16) | ((src[1] as u16) << 8) -} - -#[cfg(test)] -mod tests {} diff --git a/libcommon/src/sync.rs b/libcommon/src/sync.rs deleted file mode 100644 index e69de29..0000000 diff --git a/syscall/Cargo.toml b/libsys/Cargo.toml similarity index 81% rename from syscall/Cargo.toml rename to libsys/Cargo.toml index adfa386..f2a50f4 100644 --- a/syscall/Cargo.toml +++ b/libsys/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "syscall" +name = "libsys" version = "0.1.0" edition = "2021" @@ -7,7 +7,7 @@ edition = "2021" [dependencies] bitflags = "^1.3.0" -error = { path = "../error" } +enum-repr = "^0.2.6" [features] user = [] diff --git a/libsys/src/abi.rs b/libsys/src/abi.rs new file mode 100644 index 0000000..1a758d0 --- /dev/null +++ b/libsys/src/abi.rs @@ -0,0 +1,51 @@ +use enum_repr::EnumRepr; + +#[EnumRepr(type = "usize")] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum SystemCall { + // I/O + Read = 1, + Write = 2, + Open = 3, + Close = 4, + FileStatus = 5, + Ioctl = 6, + Select = 7, + Access = 8, + ReadDirectory = 9, + GetUserId = 10, + GetGroupId = 11, + DuplicateFd = 12, + SetUserId = 13, + SetGroupId = 14, + SetCurrentDirectory = 15, + GetCurrentDirectory = 16, + Seek = 17, + MapMemory = 18, + UnmapMemory = 19, + + // Process manipulation + Fork = 32, + Clone = 33, + Exec = 34, + Exit = 35, + WaitPid = 36, + WaitTid = 37, + GetPid = 38, + GetTid = 39, + Sleep = 40, + SetSignalEntry = 41, + SignalReturn = 42, + SendSignal = 43, + Yield = 44, + GetSid = 45, + GetPgid = 46, + GetPpid = 47, + SetSid = 48, + SetPgid = 49, + // System + GetCpuTime = 64, + Mount = 65, + // Debugging + DebugTrace = 128 +} diff --git a/libsys/src/calls.rs b/libsys/src/calls.rs new file mode 100644 index 0000000..d0ba0bb --- /dev/null +++ b/libsys/src/calls.rs @@ -0,0 +1,476 @@ +use crate::abi::SystemCall; +use crate::{ + debug::TraceLevel, + error::Errno, + ioctl::IoctlCmd, + proc::{ExitCode, MemoryAccess, MemoryMap, Pid}, + signal::{Signal, SignalDestination}, + stat::{ + AccessMode, DirectoryEntry, FdSet, FileDescriptor, FileMode, GroupId, MountOptions, + OpenFlags, Stat, UserId, + }, +}; +use core::time::Duration; + +// TODO document the syscall ABI + +// TODO move this to libusr +macro_rules! syscall { + ($num:expr) => {{ + let mut res: usize; + asm!("svc #0", out("x0") res, in("x8") $num.repr(), options(nostack)); + res + }}; + ($num:expr, $a0:expr) => {{ + let mut res: usize = $a0; + asm!("svc #0", + inout("x0") res, + in("x8") $num.repr(), options(nostack)); + res + }}; + ($num:expr, $a0:expr, $a1:expr) => {{ + let mut res: usize = $a0; + asm!("svc #0", + inout("x0") res, in("x1") $a1, + in("x8") $num.repr(), options(nostack)); + res + }}; + ($num:expr, $a0:expr, $a1:expr, $a2:expr) => {{ + let mut res: usize = $a0; + asm!("svc #0", + inout("x0") res, in("x1") $a1, in("x2") $a2, + in("x8") $num.repr(), options(nostack)); + res + }}; + ($num:expr, $a0:expr, $a1:expr, $a2:expr, $a3:expr) => {{ + let mut res: usize = $a0; + asm!("svc #0", + inout("x0") res, in("x1") $a1, in("x2") $a2, + in("x3") $a3, in("x8") $num.repr(), options(nostack)); + res + }}; + ($num:expr, $a0:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr) => {{ + let mut res: usize = $a0; + asm!("svc #0", + inout("x0") res, in("x1") $a1, in("x2") $a2, + in("x3") $a3, in("x4") $a4, in("x8") $num.repr(), options(nostack)); + res + }}; +} + +/// Integer/size argument +macro_rules! argn { + ($a:expr) => { + $a as usize + }; +} +/// Pointer/base argument +macro_rules! argp { + ($a:expr) => { + $a as usize + }; +} +// /// Immutable pointer/base argument +// macro_rules! argpi { +// ($a:expr) => ($a as *const core::ffi::c_void as usize) +// } + +/// # Safety +/// +/// System call +#[inline(always)] +pub fn sys_exit(code: ExitCode) -> ! { + unsafe { + syscall!(SystemCall::Exit, argn!(i32::from(code)), argn!(0)); + } + unreachable!(); +} + +/// # Safety +/// +/// System call +#[inline(always)] +pub fn sys_close(fd: FileDescriptor) -> Result<(), Errno> { + Errno::from_syscall_unit(unsafe { syscall!(SystemCall::Close, argn!(u32::from(fd))) }) +} + +/// # Safety +/// +/// System call +#[inline(always)] +pub fn sys_ex_nanosleep(ns: u64, rem: &mut [u64; 2]) -> Result<(), Errno> { + Errno::from_syscall_unit(unsafe { + syscall!(SystemCall::Sleep, argn!(ns), argp!(rem.as_mut_ptr())) + }) +} + +/// # Safety +/// +/// System call +#[inline(always)] +pub fn sys_ex_debug_trace(level: TraceLevel, msg: &[u8]) -> Result<(), Errno> { + Errno::from_syscall_unit(unsafe { + syscall!( + SystemCall::DebugTrace, + argn!(level.repr()), + argp!(msg.as_ptr()), + argn!(msg.len()) + ) + }) +} + +/// # Safety +/// +/// System call +#[inline(always)] +pub fn sys_openat( + at: Option, + pathname: &str, + mode: FileMode, + flags: OpenFlags, +) -> Result { + Errno::from_syscall(unsafe { + syscall!( + SystemCall::Open, + argn!(FileDescriptor::into_i32(at)), + argp!(pathname.as_ptr()), + argn!(pathname.len()), + argn!(mode.bits()), + argn!(flags.bits()) + ) + }) + .map(|e| FileDescriptor::from(e as u32)) +} + +/// # Safety +/// +/// System call +#[inline(always)] +pub fn sys_read(fd: FileDescriptor, data: &mut [u8]) -> Result { + Errno::from_syscall(unsafe { + syscall!( + SystemCall::Read, + argn!(u32::from(fd)), + argp!(data.as_mut_ptr()), + argn!(data.len()) + ) + }) +} + +#[inline(always)] +pub fn sys_write(fd: FileDescriptor, data: &[u8]) -> Result { + Errno::from_syscall(unsafe { + syscall!( + SystemCall::Write, + argn!(u32::from(fd)), + argp!(data.as_ptr()), + argn!(data.len()) + ) + }) +} + +/// # Safety +/// +/// System call +#[inline(always)] +pub fn sys_fstatat( + at: Option, + pathname: &str, + statbuf: &mut Stat, + flags: u32, +) -> Result<(), Errno> { + Errno::from_syscall_unit(unsafe { + syscall!( + SystemCall::FileStatus, + argn!(FileDescriptor::into_i32(at)), + argp!(pathname.as_ptr()), + argn!(pathname.len()), + argp!(statbuf as *mut Stat), + argn!(flags) + ) + }) +} + +/// # Safety +/// +/// System call +#[inline(always)] +pub unsafe fn sys_fork() -> Result, Errno> { + Errno::from_syscall(syscall!(SystemCall::Fork)).map(|res| { + if res != 0 { + Some(unsafe { Pid::from_raw(res as u32) }) + } else { + None + } + }) +} + +/// # Safety +/// +/// System call +#[inline(always)] +pub fn sys_execve(pathname: &str, argv: &[&str]) -> Result<(), Errno> { + Errno::from_syscall_unit(unsafe { + syscall!( + SystemCall::Exec, + argp!(pathname.as_ptr()), + argn!(pathname.len()), + argp!(argv.as_ptr()), + argn!(argv.len()) + ) + }) +} + +/// # Safety +/// +/// System call +#[inline(always)] +pub fn sys_waitpid(pid: Pid, status: &mut i32) -> Result<(), Errno> { + Errno::from_syscall_unit(unsafe { + syscall!( + SystemCall::WaitPid, + argn!(pid.value()), + argp!(status as *mut i32) + ) + }) +} + +/// # Safety +/// +/// System call +#[inline(always)] +pub fn sys_ioctl( + fd: FileDescriptor, + cmd: IoctlCmd, + ptr: usize, + len: usize, +) -> Result { + Errno::from_syscall(unsafe { + syscall!( + SystemCall::Ioctl, + argn!(u32::from(fd)), + argn!(cmd), + argn!(ptr), + argn!(len) + ) + }) +} + +#[inline(always)] +pub fn sys_ex_getcputime() -> Result { + Errno::from_syscall(unsafe { syscall!(SystemCall::GetCpuTime) }) + .map(|e| Duration::from_nanos(e as u64)) +} + +#[inline(always)] +pub fn sys_ex_signal(entry: usize, stack: usize) -> Result<(), Errno> { + Errno::from_syscall_unit(unsafe { + syscall!(SystemCall::SetSignalEntry, argn!(entry), argn!(stack)) + }) +} + +#[inline(always)] +pub fn sys_ex_sigreturn() -> ! { + unsafe { + syscall!(SystemCall::SignalReturn); + } + unreachable!(); +} + +#[inline(always)] +pub fn sys_ex_kill(pid: SignalDestination, signum: Signal) -> Result<(), Errno> { + Errno::from_syscall_unit(unsafe { + syscall!( + SystemCall::SendSignal, + argn!(isize::from(pid)), + argn!(signum as u32) + ) + }) +} + +#[inline(always)] +pub fn sys_ex_clone(entry: usize, stack: usize, arg: usize) -> Result { + Errno::from_syscall(unsafe { + syscall!(SystemCall::Clone, argn!(entry), argn!(stack), argn!(arg)) + }) +} + +#[inline(always)] +pub fn sys_ex_thread_exit(status: ExitCode) -> ! { + unsafe { + syscall!(SystemCall::Exit, argn!(i32::from(status)), argn!(1)); + } + unreachable!(); +} + +#[inline(always)] +pub fn sys_ex_thread_wait(tid: u32) -> Result { + Errno::from_syscall(unsafe { syscall!(SystemCall::WaitTid, argn!(tid)) }) + .map(|_| ExitCode::from(0)) +} + +#[inline(always)] +pub fn sys_ex_yield() { + unsafe { + syscall!(SystemCall::Yield); + } +} + +#[inline(always)] +pub fn sys_select( + read_fds: Option<&mut FdSet>, + write_fds: Option<&mut FdSet>, + timeout: u64, +) -> Result { + Errno::from_syscall(unsafe { + syscall!( + SystemCall::Select, + argp!(read_fds + .map(|e| e as *mut _) + .unwrap_or(core::ptr::null_mut())), + argp!(write_fds + .map(|e| e as *mut _) + .unwrap_or(core::ptr::null_mut())), + argn!(timeout) + ) + }) +} + +#[inline(always)] +pub fn sys_faccessat( + fd: Option, + name: &str, + mode: AccessMode, + flags: u32, +) -> Result<(), Errno> { + Errno::from_syscall_unit(unsafe { + syscall!( + SystemCall::Access, + argn!(FileDescriptor::into_i32(fd)), + argp!(name.as_ptr()), + argn!(name.len()), + argn!(mode.bits()), + argn!(flags) + ) + }) +} + +#[inline(always)] +pub fn sys_ex_gettid() -> u32 { + unsafe { syscall!(SystemCall::GetTid) as u32 } +} + +#[inline(always)] +pub fn sys_getpid() -> Pid { + unsafe { Pid::from_raw(syscall!(SystemCall::GetPid) as u32) } +} + +#[inline(always)] +pub fn sys_getpgid(pid: Pid) -> Result { + Errno::from_syscall(unsafe { syscall!(SystemCall::GetPgid, argn!(pid.value())) }) + .map(|e| unsafe { Pid::from_raw(e as u32) }) +} + +#[inline(always)] +pub fn sys_setpgid(pid: Pid, pgid: Pid) -> Result { + Errno::from_syscall(unsafe { + syscall!(SystemCall::SetPgid, argn!(pid.value()), argn!(pgid.value())) + }) + .map(|e| unsafe { Pid::from_raw(e as u32) }) +} + +#[inline(always)] +pub fn sys_readdir(fd: FileDescriptor, buf: &mut [DirectoryEntry]) -> Result { + Errno::from_syscall(unsafe { + syscall!( + SystemCall::ReadDirectory, + argn!(u32::from(fd)), + argp!(buf.as_mut_ptr()), + argn!(buf.len()) + ) + }) +} + +#[inline(always)] +pub fn sys_getuid() -> UserId { + UserId::from(unsafe { syscall!(SystemCall::GetUserId) as u32 }) +} + +#[inline(always)] +pub fn sys_getgid() -> GroupId { + GroupId::from(unsafe { syscall!(SystemCall::GetGroupId) as u32 }) +} + +#[inline(always)] +pub fn sys_setsid() -> Result { + Errno::from_syscall(unsafe { syscall!(SystemCall::SetSid) }) + .map(|e| unsafe { Pid::from_raw(e as u32) }) +} + +#[inline(always)] +pub fn sys_mount(target: &str, options: &MountOptions) -> Result<(), Errno> { + Errno::from_syscall_unit(unsafe { + syscall!( + SystemCall::Mount, + argp!(target.as_ptr()), + argn!(target.len()), + argp!(options as *const _) + ) + }) +} + +#[inline(always)] +pub fn sys_dup(src: FileDescriptor, dst: Option) -> Result { + Errno::from_syscall(unsafe { + syscall!( + SystemCall::DuplicateFd, + argn!(u32::from(src)), + argn!(FileDescriptor::into_i32(dst)) + ) + }) + .map(|e| FileDescriptor::from(e as u32)) +} + +#[inline(always)] +pub fn sys_setuid(uid: UserId) -> Result<(), Errno> { + Errno::from_syscall_unit(unsafe { syscall!(SystemCall::SetUserId, u32::from(uid) as usize) }) +} + +#[inline(always)] +pub fn sys_setgid(gid: GroupId) -> Result<(), Errno> { + Errno::from_syscall_unit(unsafe { syscall!(SystemCall::SetGroupId, u32::from(gid) as usize) }) +} + +#[inline(always)] +pub fn sys_chdir(path: &str) -> Result<(), Errno> { + Errno::from_syscall_unit(unsafe { + syscall!( + SystemCall::SetCurrentDirectory, + argp!(path.as_ptr()), + argn!(path.len()) + ) + }) +} + +#[inline(always)] +pub fn sys_mmap( + hint: usize, + len: usize, + acc: MemoryAccess, + flags: MemoryMap, +) -> Result { + Errno::from_syscall(unsafe { + syscall!( + SystemCall::MapMemory, + argn!(hint), + argn!(len), + argn!(acc.bits()), + argn!(flags.bits()) + ) + }) +} + +#[inline(always)] +pub unsafe fn sys_munmap(addr: usize, len: usize) -> Result<(), Errno> { + Errno::from_syscall_unit(syscall!(SystemCall::UnmapMemory, argn!(addr), argn!(len))) +} diff --git a/libsys/src/debug.rs b/libsys/src/debug.rs new file mode 100644 index 0000000..cafc5d9 --- /dev/null +++ b/libsys/src/debug.rs @@ -0,0 +1,10 @@ +use enum_repr::EnumRepr; + +#[EnumRepr(type = "usize")] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum TraceLevel { + Debug = 1, + Info = 2, + Warn = 3, + Error = 4, +} diff --git a/libsys/src/error.rs b/libsys/src/error.rs new file mode 100644 index 0000000..1788d38 --- /dev/null +++ b/libsys/src/error.rs @@ -0,0 +1,53 @@ +#[derive(PartialEq, Debug, Clone, Copy)] +#[repr(u32)] +pub enum Errno { + AlreadyExists, + BadExecutable, + Busy, + DeviceError, + DoesNotExist, + EndOfFile, + Interrupt, + InvalidArgument, + InvalidFile, + InvalidOperation, + IsADirectory, + NotADirectory, + NotImplemented, + OutOfMemory, + PermissionDenied, + ReadOnly, + TimedOut, + TooManyDescriptors, + WouldBlock, +} + +impl Errno { + pub const fn to_negative_isize(self) -> isize { + -(self as isize) + } + + pub fn from_syscall(u: usize) -> Result { + let i = u as isize; + if i < 0 { + Err(Self::from((-i) as usize)) + } else { + Ok(u) + } + } + + pub fn from_syscall_unit(u: usize) -> Result<(), Self> { + let i = u as isize; + if i < 0 { + Err(Self::from((-i) as usize)) + } else { + Ok(()) + } + } +} + +impl From for Errno { + fn from(u: usize) -> Errno { + unsafe { core::mem::transmute(u as u32) } + } +} diff --git a/syscall/src/ioctl.rs b/libsys/src/ioctl.rs similarity index 81% rename from syscall/src/ioctl.rs rename to libsys/src/ioctl.rs index 3f308d6..91cb058 100644 --- a/syscall/src/ioctl.rs +++ b/libsys/src/ioctl.rs @@ -1,12 +1,13 @@ use core::convert::TryFrom; -use error::Errno; - +use crate::error::Errno; #[derive(Clone, Copy, Debug)] #[repr(u32)] +#[non_exhaustive] pub enum IoctlCmd { TtySetAttributes = 1, TtyGetAttributes = 2, + TtySetPgrp = 3, } impl TryFrom for IoctlCmd { @@ -17,6 +18,7 @@ impl TryFrom for IoctlCmd { match u { 1 => Ok(Self::TtySetAttributes), 2 => Ok(Self::TtyGetAttributes), + 3 => Ok(Self::TtySetPgrp), _ => Err(Errno::InvalidArgument) } } diff --git a/libsys/src/lib.rs b/libsys/src/lib.rs new file mode 100644 index 0000000..ddafbff --- /dev/null +++ b/libsys/src/lib.rs @@ -0,0 +1,30 @@ +#![feature(asm, const_panic)] +#![no_std] + +#[macro_use] +extern crate bitflags; + +pub mod abi; +pub mod debug; +pub mod error; +pub mod ioctl; +pub mod mem; +pub mod path; +pub mod proc; +pub mod signal; +pub mod stat; +pub mod termios; +pub mod traits; + +#[derive(Debug)] +pub struct ProgramArgs { + pub argv: usize, + pub argc: usize, + pub storage: usize, + pub size: usize +} + +#[cfg(feature = "user")] +pub mod calls; +#[cfg(feature = "user")] +pub use calls::*; diff --git a/libusr/src/mem.rs b/libsys/src/mem.rs similarity index 69% rename from libusr/src/mem.rs rename to libsys/src/mem.rs index 1b9f19a..b67d35e 100644 --- a/libusr/src/mem.rs +++ b/libsys/src/mem.rs @@ -1,3 +1,11 @@ +pub fn read_le32(src: &[u8]) -> u32 { + (src[0] as u32) | ((src[1] as u32) << 8) | ((src[2] as u32) << 16) | ((src[3] as u32) << 24) +} + +pub fn read_le16(src: &[u8]) -> u16 { + (src[0] as u16) | ((src[1] as u16) << 8) +} + /// See memcpy(3p). /// /// # Safety @@ -5,7 +13,7 @@ /// Unsafe: writes to arbitrary memory locations, performs no pointer /// validation. #[no_mangle] -pub unsafe extern "C" fn memcpy(dst: *mut u8, src: *mut u8, mut len: usize) -> *mut u8 { +pub unsafe extern "C" fn memcpy(dst: *mut u8, src: *const u8, mut len: usize) -> *mut u8 { while len != 0 { len -= 1; *dst.add(len) = *src.add(len); @@ -20,15 +28,16 @@ pub unsafe extern "C" fn memcpy(dst: *mut u8, src: *mut u8, mut len: usize) -> * /// Unsafe: performs reads from arbitrary memory locations, performs no /// pointer validation. #[no_mangle] -pub unsafe extern "C" fn memcmp(a: *mut u8, b: *mut u8, mut len: usize) -> isize { - while len != 0 { - len -= 1; - if *a.add(len) < *b.add(len) { +pub unsafe extern "C" fn memcmp(a: *mut u8, b: *mut u8, len: usize) -> isize { + let mut off = 0; + while off != len { + if *a.add(off) < *b.add(off) { return -1; } - if *a.add(len) > *b.add(len) { + if *a.add(off) > *b.add(off) { return 1; } + off += 1; } 0 } diff --git a/libsys/src/path.rs b/libsys/src/path.rs new file mode 100644 index 0000000..b64ecef --- /dev/null +++ b/libsys/src/path.rs @@ -0,0 +1,15 @@ +pub fn path_component_left(path: &str) -> (&str, &str) { + if let Some((left, right)) = path.split_once('/') { + (left, right.trim_start_matches('/')) + } else { + (path, "") + } +} + +pub fn path_component_right(path: &str) -> (&str, &str) { + if let Some((left, right)) = path.trim_end_matches('/').rsplit_once('/') { + (left.trim_end_matches('/'), right) + } else { + ("", path) + } +} diff --git a/libsys/src/proc.rs b/libsys/src/proc.rs new file mode 100644 index 0000000..fe76473 --- /dev/null +++ b/libsys/src/proc.rs @@ -0,0 +1,134 @@ +use crate::error::Errno; +use core::convert::TryFrom; +use core::fmt; + +/// Wrapper type for process exit code +#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug)] +#[repr(transparent)] +pub struct ExitCode(i32); + +/// Wrapper type for process ID +#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq)] +#[repr(transparent)] +pub struct Pid(u32); + +#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug)] +#[repr(transparent)] +pub struct Pgid(u32); + +bitflags! { + pub struct MemoryAccess: u32 { + const READ = 1 << 0; + const WRITE = 1 << 1; + const EXEC = 1 << 2; + } +} + +bitflags! { + pub struct MemoryMap: u32 { + const BACKEND = 0x3 << 0; + const ANONYMOUS = 1 << 0; + + const SHARING = 0x3 << 2; + const PRIVATE = 1 << 2; + } +} + +impl From for ExitCode { + fn from(f: i32) -> Self { + Self(f) + } +} + +impl From<()> for ExitCode { + fn from(_: ()) -> Self { + Self(0) + } +} + +impl From for i32 { + fn from(f: ExitCode) -> Self { + f.0 + } +} + +impl Pid { + /// Kernel idle process always has PID of zero + pub const IDLE: Self = Self(Self::KERNEL_BIT); + + const KERNEL_BIT: u32 = 1 << 31; + + /// Constructs an instance of user-space PID + pub const fn user(id: u32) -> Self { + assert!(id < 256, "PID is too high"); + Self(id) + } + + /// Constructs an instance of kernel-space PID + pub const fn kernel(id: u32) -> Self { + assert!(id & Self::KERNEL_BIT == 0, "PID is too high"); + Self(id | Self::KERNEL_BIT) + } + + /// Returns `true` if this PID belongs to a kernel process + pub fn is_kernel(self) -> bool { + self.0 & Self::KERNEL_BIT != 0 + } + + /// Returns address space ID of a user-space process. + /// + /// Panics if called on kernel process PID. + pub fn asid(self) -> u8 { + assert!(!self.is_kernel()); + self.0 as u8 + } + + /// Returns bit value of this pid + pub const fn value(self) -> u32 { + self.0 + } + + /// Constructs [Pid] from raw [u32] value + /// + /// # Safety + /// + /// Unsafe: does not check `num` + pub const unsafe fn from_raw(num: u32) -> Self { + Self(num) + } +} + +impl fmt::Debug for Pid { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Pid(#{}{})", + if self.is_kernel() { "K" } else { "U" }, + self.0 & !Self::KERNEL_BIT + ) + } +} + +impl TryFrom for Pgid { + type Error = Errno; + + fn try_from(pid: Pid) -> Result { + if pid.is_kernel() { + Err(Errno::InvalidArgument) + } else { + Ok(Pgid(pid.0)) + } + } +} + +impl From for Pgid { + fn from(p: u32) -> Pgid { + Self(p) + } +} + +impl From for u32 { + fn from(p: Pgid) -> u32 { + p.0 + } +} diff --git a/libsys/src/signal.rs b/libsys/src/signal.rs new file mode 100644 index 0000000..6d2dc3c --- /dev/null +++ b/libsys/src/signal.rs @@ -0,0 +1,63 @@ +use crate::error::Errno; +use crate::proc::{Pid, Pgid}; + +#[derive(Clone, Copy, PartialEq, Debug)] +#[repr(u32)] +pub enum Signal { + Interrupt = 2, + IllegalInstruction = 4, + FloatError = 8, + Kill = 9, + SegmentationFault = 11, + InvalidSystemCall = 31 +} + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum SignalDestination { + Group(Pgid), + Process(Pid), + All, + This +} + +impl From for SignalDestination { + fn from(num: isize) -> Self { + if num > 0 { + Self::Process(Pid::user(num as u32)) + } else if num == 0 { + Self::This + } else if num == -1 { + Self::All + } else { + Self::Group(Pgid::from((-num) as u32)) + } + } +} + +impl From for isize { + fn from(p: SignalDestination) -> isize { + match p { + SignalDestination::Process(pid) => pid.value() as isize, + SignalDestination::Group(pgid) => -(u32::from(pgid) as isize), + SignalDestination::This => 0, + SignalDestination::All => -1 + } + } +} + +impl TryFrom for Signal { + type Error = Errno; + + #[inline] + fn try_from(u: u32) -> Result { + match u { + 2 => Ok(Self::Interrupt), + 4 => Ok(Self::IllegalInstruction), + 8 => Ok(Self::FloatError), + 9 => Ok(Self::Kill), + 11 => Ok(Self::SegmentationFault), + 31 => Ok(Self::InvalidSystemCall), + _ => Err(Errno::InvalidArgument) + } + } +} diff --git a/libsys/src/stat.rs b/libsys/src/stat.rs new file mode 100644 index 0000000..8cc3ac8 --- /dev/null +++ b/libsys/src/stat.rs @@ -0,0 +1,315 @@ +// TODO split up this file +use crate::error::Errno; +use core::fmt; + +const AT_FDCWD: i32 = -2; +pub const AT_EMPTY_PATH: u32 = 1 << 16; + +bitflags! { + pub struct OpenFlags: u32 { + const O_RDONLY = 1; + const O_WRONLY = 2; + const O_RDWR = 3; + const O_ACCESS = 0x7; + + const O_CREAT = 1 << 4; + const O_EXEC = 1 << 5; + const O_CLOEXEC = 1 << 6; + const O_DIRECTORY = 1 << 7; + const O_CTTY = 1 << 8; + } +} + +bitflags! { + pub struct FileMode: u32 { + const FILE_TYPE = 0xF << 12; + const S_IFREG = 0x8 << 12; + const S_IFDIR = 0x4 << 12; + const S_IFCHR = 0x2 << 12; + + const USER_READ = 1 << 8; + const USER_WRITE = 1 << 7; + const USER_EXEC = 1 << 6; + const GROUP_READ = 1 << 5; + const GROUP_WRITE = 1 << 4; + const GROUP_EXEC = 1 << 3; + const OTHER_READ = 1 << 2; + const OTHER_WRITE = 1 << 1; + const OTHER_EXEC = 1 << 0; + } +} + +bitflags! { + pub struct AccessMode: u32 { + const R_OK = 1 << 0; + const W_OK = 1 << 1; + const X_OK = 1 << 2; + const F_OK = 1 << 3; + } +} + +#[derive(Clone, Debug)] +pub struct MountOptions<'a> { + pub device: Option<&'a str>, + pub fs: Option<&'a str>, + // TODO flags etc. +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +pub struct UserId(u32); + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +pub struct GroupId(u32); + +impl UserId { + pub const fn root() -> Self { + Self(0) + } + + pub const fn is_root(self) -> bool { + self.0 == 0 + } +} + +impl From for UserId { + #[inline(always)] + fn from(v: u32) -> Self { + Self(v) + } +} + +impl From for u32 { + #[inline(always)] + fn from(v: UserId) -> u32 { + v.0 + } +} + +impl GroupId { + pub const fn root() -> Self { + Self(0) + } + + pub const fn is_root(self) -> bool { + self.0 == 0 + } +} + +impl From for GroupId { + #[inline(always)] + fn from(v: u32) -> Self { + Self(v) + } +} + +impl From for u32 { + #[inline(always)] + fn from(v: GroupId) -> u32 { + v.0 + } +} + +#[derive(Clone, Default)] +pub struct FdSet { + bits: [u64; 2], +} + +#[derive(Clone, Copy, Debug)] +#[repr(transparent)] +pub struct FileDescriptor(u32); + +#[derive(Clone, Copy)] +pub struct DirectoryEntry { + name: [u8; 64], +} + +struct FdSetIter<'a> { + idx: u32, + set: &'a FdSet, +} + +#[derive(Clone, Copy, Debug, Default)] +#[repr(C)] +pub struct Stat { + pub mode: FileMode, + pub size: u64, + pub blksize: u32, +} + +impl DirectoryEntry { + pub const fn empty() -> Self { + Self { name: [0; 64] } + } + + pub fn from_str(i: &str) -> DirectoryEntry { + let mut res = DirectoryEntry { name: [0; 64] }; + let bytes = i.as_bytes(); + res.name[..bytes.len()].copy_from_slice(bytes); + res + } + + pub fn as_str(&self) -> &str { + let zero = self.name.iter().position(|&c| c == 0).unwrap(); + core::str::from_utf8(&self.name[..zero]).unwrap() + } +} + +impl fmt::Debug for DirectoryEntry { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("DirectoryEntry") + .field("name", &self.as_str()) + .finish() + } +} + +impl FdSet { + pub const fn empty() -> Self { + Self { bits: [0; 2] } + } + + #[inline] + pub fn reset(&mut self) { + self.bits.fill(0); + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.bits.iter().any(|&x| x != 0) + } + + #[inline] + pub fn set(&mut self, fd: FileDescriptor) { + self.bits[(fd.0 as usize) / 64] |= 1 << (fd.0 % 64); + } + + #[inline] + pub fn clear(&mut self, fd: FileDescriptor) { + self.bits[(fd.0 as usize) / 64] &= !(1 << (fd.0 % 64)); + } + + #[inline] + pub fn is_set(&self, fd: FileDescriptor) -> bool { + self.bits[(fd.0 as usize) / 64] & (1 << (fd.0 % 64)) != 0 + } + + pub fn iter(&self) -> impl Iterator + '_ { + FdSetIter { idx: 0, set: self } + } +} + +impl Iterator for FdSetIter<'_> { + type Item = FileDescriptor; + + fn next(&mut self) -> Option { + while self.idx < 128 { + if self.set.is_set(FileDescriptor(self.idx)) { + let res = self.idx; + self.idx += 1; + return Some(FileDescriptor::from(res)); + } + self.idx += 1; + } + None + } +} + +impl fmt::Debug for FdSet { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "FdSet {{ ")?; + for (count, fd) in self.iter().enumerate() { + if count != 0 { + write!(f, ", ")?; + } + write!(f, "{:?}", fd)?; + } + write!(f, " }}") + } +} + +impl FileMode { + /// Returns default permission set for directories + pub fn default_dir() -> Self { + unsafe { Self::from_bits_unchecked(0o755) | Self::S_IFDIR } + } + + /// Returns default permission set for regular files + pub fn default_reg() -> Self { + unsafe { Self::from_bits_unchecked(0o644) | Self::S_IFREG } + } +} + +fn choose(q: bool, a: T, b: T) -> T { + if q { a } else { b } +} + +impl Default for FileMode { + fn default() -> Self { + unsafe { Self::from_bits_unchecked(0) } + } +} + +impl fmt::Display for FileMode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}{}{}{}{}{}{}{}{}{}", + // File type + match *self & Self::FILE_TYPE { + Self::S_IFCHR => 'c', + Self::S_IFDIR => 'd', + Self::S_IFREG => '-', + _ => '?' + }, + // User + choose(self.contains(Self::USER_READ), 'r', '-'), + choose(self.contains(Self::USER_WRITE), 'w', '-'), + choose(self.contains(Self::USER_EXEC), 'x', '-'), + // Group + choose(self.contains(Self::GROUP_READ), 'r', '-'), + choose(self.contains(Self::GROUP_WRITE), 'w', '-'), + choose(self.contains(Self::GROUP_EXEC), 'x', '-'), + // Other + choose(self.contains(Self::OTHER_READ), 'r', '-'), + choose(self.contains(Self::OTHER_WRITE), 'w', '-'), + choose(self.contains(Self::OTHER_EXEC), 'x', '-'), + ) + } +} + +impl FileDescriptor { + pub const STDIN: Self = Self(0); + pub const STDOUT: Self = Self(1); + pub const STDERR: Self = Self(2); + + pub fn from_i32(u: i32) -> Result, Errno> { + if u >= 0 { + Ok(Some(Self(u as u32))) + } else if u == AT_FDCWD { + Ok(None) + } else { + Err(Errno::InvalidArgument) + } + } + + pub fn into_i32(u: Option) -> i32 { + if let Some(u) = u { + u.0 as i32 + } else { + AT_FDCWD + } + } +} + +impl From for FileDescriptor { + fn from(u: u32) -> Self { + Self(u) + } +} + +impl From for u32 { + fn from(u: FileDescriptor) -> u32 { + u.0 + } +} diff --git a/syscall/src/termios.rs b/libsys/src/termios.rs similarity index 100% rename from syscall/src/termios.rs rename to libsys/src/termios.rs diff --git a/libsys/src/traits.rs b/libsys/src/traits.rs new file mode 100644 index 0000000..c586b0d --- /dev/null +++ b/libsys/src/traits.rs @@ -0,0 +1,28 @@ +use crate::error::Errno; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum SeekDir { + Set, + End, + Current, +} + +pub trait Read { + fn read(&mut self, data: &mut [u8]) -> Result; +} + +pub trait Seek { + fn seek(&mut self, off: isize, whence: SeekDir) -> Result; +} + +pub trait Write { + fn write(&mut self, data: &[u8]) -> Result; +} + +pub trait RandomRead { + fn pread(&mut self, pos: usize, data: &mut [u8]) -> Result; +} + +pub trait RandomWrite { + fn pwrite(&mut self, pos: usize, data: &[u8]) -> Result; +} diff --git a/libusr/Cargo.toml b/libusr/Cargo.toml index 26a72d1..0299b4e 100644 --- a/libusr/Cargo.toml +++ b/libusr/Cargo.toml @@ -6,4 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -syscall = { path = "../syscall", features = ["user"] } +libsys = { path = "../libsys", features = ["user"] } +lazy_static = { version = "^1.4.0", features = ["spin_no_std"] } +memoffset = "^0.6.4" diff --git a/libusr/src/allocator.rs b/libusr/src/allocator.rs new file mode 100644 index 0000000..7f4f65a --- /dev/null +++ b/libusr/src/allocator.rs @@ -0,0 +1,262 @@ +use core::alloc::{GlobalAlloc, Layout}; +use core::mem::{size_of, MaybeUninit}; +use core::ptr::null_mut; +use libsys::{ + calls::{sys_mmap, sys_munmap}, + error::Errno, + proc::{MemoryAccess, MemoryMap}, +}; +use memoffset::offset_of; + +use crate::trace_debug; + +struct Allocator; + +const BLOCK_MAGIC: u32 = 0xBADB10C0; +const BLOCK_MAGIC_MASK: u32 = 0xFFFFFFF0; +const BLOCK_ALLOC: u32 = 1 << 0; +const SMALL_ZONE_ELEM: usize = 256; +const SMALL_ZONE_SIZE: usize = 6 * 0x1000; +const MID_ZONE_ELEM: usize = 2048; +const MID_ZONE_SIZE: usize = 24 * 0x1000; +const LARGE_ZONE_ELEM: usize = 8192; +const LARGE_ZONE_SIZE: usize = 48 * 0x1000; + +struct ZoneList { + prev: *mut ZoneList, + next: *mut ZoneList, +} + +#[repr(C)] +struct Zone { + size: usize, + list: ZoneList, +} + +#[repr(C)] +struct Block { + prev: *mut Block, + next: *mut Block, + flags: u32, + size: u32, +} + +static mut SMALL_ZONE_LIST: MaybeUninit = MaybeUninit::uninit(); +static mut MID_ZONE_LIST: MaybeUninit = MaybeUninit::uninit(); +static mut LARGE_ZONE_LIST: MaybeUninit = MaybeUninit::uninit(); + +impl ZoneList { + fn init(&mut self) { + self.prev = self; + self.next = self; + } + + unsafe fn init_uninit(list: &mut MaybeUninit) { + list.assume_init_mut().init() + } + + fn add(&mut self, new: *mut ZoneList) { + let new = unsafe { &mut *new }; + let next = unsafe { &mut *self.next }; + + next.prev = new; + new.next = next; + new.prev = self; + self.next = new; + } + + fn del(&mut self) { + let prev = unsafe { &mut *self.prev }; + let next = unsafe { &mut *self.next }; + + next.prev = prev; + prev.next = next; + } +} + +impl Zone { + fn alloc(size: usize) -> Result<*mut Self, Errno> { + let pages = sys_mmap( + 0, + size, + MemoryAccess::READ | MemoryAccess::WRITE, + MemoryMap::ANONYMOUS | MemoryMap::PRIVATE, + )?; + trace_debug!("Zone::alloc({}) => {:#x}", size, pages); + + let zone_ptr = pages as *mut Zone; + let head_ptr = (pages + size_of::()) as *mut Block; + + let zone = unsafe { &mut *zone_ptr }; + let head = unsafe { &mut *head_ptr }; + zone.list.init(); + zone.size = size - size_of::(); + + head.size = (size - (size_of::() + size_of::())) as u32; + head.flags = BLOCK_MAGIC; + head.prev = null_mut(); + head.next = null_mut(); + + Ok(zone) + } + + unsafe fn free(zone: *mut Self) { + trace_debug!("Zone::free({:p})", zone); + sys_munmap(zone as usize, (&*zone).size + size_of::()) + .expect("Failed to unmap heap pages"); + } + + fn get(item: *mut ZoneList) -> *mut Zone { + ((item as usize) - offset_of!(Zone, list)) as *mut Zone + } +} + +unsafe fn zone_alloc(zone: &mut Zone, size: usize) -> *mut u8 { + assert_eq!(size & 15, 0); + + let begin = ((zone as *mut _ as usize) + size_of::()) as *mut Block; + + let mut block = begin; + while !block.is_null() { + let block_ref = &mut *block; + if block_ref.flags & BLOCK_ALLOC != 0 { + block = block_ref.next; + continue; + } + + if size == block_ref.size as usize { + block_ref.flags |= BLOCK_ALLOC; + let ptr = block.add(1) as *mut u8; + // TODO fill with zeros + return ptr; + } else if block_ref.size as usize >= size + size_of::() { + let cur_next = block_ref.next; + let cur_next_ref = &mut *cur_next; + let new_block = ((block as usize) + size_of::() + size) as *mut Block; + let new_block_ref = &mut *new_block; + + if !cur_next.is_null() { + cur_next_ref.prev = new_block; + } + new_block_ref.next = cur_next; + new_block_ref.prev = block; + new_block_ref.size = ((block_ref.size as usize) - size_of::() - size) as u32; + new_block_ref.flags = BLOCK_MAGIC; + block_ref.next = new_block; + block_ref.size = size as u32; + block_ref.flags |= BLOCK_ALLOC; + + let ptr = block.add(1) as *mut u8; + // TODO fill with zeros + return ptr; + } + + block = block_ref.next; + } + + null_mut() +} + +unsafe fn alloc_from(list: &mut ZoneList, zone_size: usize, size: usize) -> *mut u8 { + loop { + let mut zone = list.next; + while zone != list { + let ptr = zone_alloc(&mut *Zone::get(zone), size); + if !ptr.is_null() { + return ptr; + } + zone = (&mut *zone).next; + } + + let zone = match Zone::alloc(zone_size) { + Ok(zone) => zone, + Err(e) => { + trace_debug!("Zone alloc failed: {:?}", e); + return null_mut(); + } + }; + list.add(&mut (&mut *zone).list); + } +} + +unsafe impl GlobalAlloc for Allocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + assert!(layout.align() < 16); + let size = (layout.size() + 15) & !15; + trace_debug!("alloc({:?})", layout); + if size <= SMALL_ZONE_ELEM { + alloc_from(SMALL_ZONE_LIST.assume_init_mut(), SMALL_ZONE_SIZE, size) + } else if size <= MID_ZONE_ELEM { + alloc_from(MID_ZONE_LIST.assume_init_mut(), MID_ZONE_SIZE, size) + } else if size <= LARGE_ZONE_ELEM { + alloc_from(LARGE_ZONE_LIST.assume_init_mut(), LARGE_ZONE_SIZE, size) + } else { + todo!(); + } + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + trace_debug!("free({:p}, {:?})", ptr, layout); + assert!(!ptr.is_null()); + let mut block = ptr.sub(size_of::()) as *mut Block; + let mut block_ref = &mut *block; + + if block_ref.flags & BLOCK_MAGIC_MASK != BLOCK_MAGIC { + panic!("Heap block is malformed: block={:p}, ptr={:p}", block, ptr); + } + if block_ref.flags & BLOCK_ALLOC == 0 { + panic!( + "Double free error in heap: block={:p}, ptr={:p}", + block, ptr + ); + } + + block_ref.flags &= !BLOCK_ALLOC; + let prev = block_ref.prev; + let next = block_ref.next; + let prev_ref = &mut *prev; + let next_ref = &mut *next; + + if !prev.is_null() && prev_ref.flags & BLOCK_ALLOC == 0 { + block_ref.flags = 0; + prev_ref.next = next; + if !next.is_null() { + next_ref.prev = prev; + } + prev_ref.size += (block_ref.size as usize + size_of::()) as u32; + + block = prev; + block_ref = &mut *block; + } + + if !next.is_null() && next_ref.flags & BLOCK_ALLOC == 0 { + next_ref.flags = 0; + if !next_ref.next.is_null() { + (&mut *(next_ref.next)).prev = block; + } + block_ref.next = next_ref.next; + block_ref.size += (next_ref.size as usize + size_of::()) as u32; + } + + if block_ref.prev.is_null() && block_ref.next.is_null() { + let zone = (block as usize - size_of::()) as *mut Zone; + assert_eq!((zone as usize) & 0xFFF, 0); + (&mut *zone).list.del(); + Zone::free(zone); + } + } +} + +#[alloc_error_handler] +fn alloc_error_handler(_layout: Layout) -> ! { + loop {} +} + +#[global_allocator] +static ALLOC: Allocator = Allocator; + +pub unsafe fn init() { + ZoneList::init_uninit(&mut SMALL_ZONE_LIST); + ZoneList::init_uninit(&mut MID_ZONE_LIST); + ZoneList::init_uninit(&mut LARGE_ZONE_LIST); +} diff --git a/libusr/src/env.rs b/libusr/src/env.rs new file mode 100644 index 0000000..ce81bc6 --- /dev/null +++ b/libusr/src/env.rs @@ -0,0 +1,21 @@ +use libsys::{debug::TraceLevel, ProgramArgs}; +use alloc::vec::Vec; +use crate::trace; + +static mut PROGRAM_ARGS: Vec<&'static str> = Vec::new(); + +pub fn args() -> &'static [&'static str] { + unsafe { &PROGRAM_ARGS } +} + +pub(crate) unsafe fn setup_env(arg: &ProgramArgs) { + for i in 0..arg.argc { + let base = core::ptr::read((arg.argv + i * 16 + 0) as *const *const u8); + let len = core::ptr::read((arg.argv + i * 16 + 8) as *const usize); + + let string = core::str::from_utf8(core::slice::from_raw_parts(base, len)).unwrap(); + PROGRAM_ARGS.push(string); + } + + trace!(TraceLevel::Debug, "args = {:?}", PROGRAM_ARGS); +} diff --git a/libusr/src/file.rs b/libusr/src/file.rs new file mode 100644 index 0000000..ef161d3 --- /dev/null +++ b/libusr/src/file.rs @@ -0,0 +1,35 @@ +use crate::io::{AsRawFd, Error, Read}; +use libsys::{ + calls::{sys_openat, sys_read, sys_close}, + stat::{FileDescriptor, FileMode, OpenFlags}, +}; + +pub struct File { + fd: FileDescriptor, +} + +impl File { + pub fn open(path: &str) -> Result { + let fd = sys_openat(None, path, FileMode::default_reg(), OpenFlags::O_RDONLY) + .map_err(Error::from)?; + Ok(File { fd }) + } +} + +impl AsRawFd for File { + fn as_raw_fd(&self) -> FileDescriptor { + self.fd + } +} + +impl Drop for File { + fn drop(&mut self) { + sys_close(self.fd).ok(); + } +} + +impl Read for File { + fn read(&mut self, bytes: &mut [u8]) -> Result { + sys_read(self.fd, bytes).map_err(Error::from) + } +} diff --git a/libusr/src/io.rs b/libusr/src/io.rs deleted file mode 100644 index e7b9ae7..0000000 --- a/libusr/src/io.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::sys::{self, Stat}; -use core::fmt; -use syscall::stat::AT_FDCWD; - -// TODO populate this type -pub struct Error; - -pub fn stat(pathname: &str) -> Result { - let mut buf = Stat::default(); - let res = unsafe { sys::sys_fstatat(AT_FDCWD, pathname, &mut buf, 0) }; - if res != 0 { - todo!(); - } - Ok(buf) -} - -// print!/println! group - -#[macro_export] -macro_rules! print { - ($($args:tt)+) => ($crate::io::_print($crate::sys::STDOUT_FILENO, format_args!($($args)+))) -} - -#[macro_export] -macro_rules! println { - ($($args:tt)+) => (print!("{}\n", format_args!($($args)+))) -} - -#[macro_export] -macro_rules! eprint { - ($($args:tt)+) => ($crate::io::_print($crate::sys::STDERR_FILENO, format_args!($($args)+))) -} - -#[macro_export] -macro_rules! eprintln { - ($($args:tt)+) => (eprint!("{}\n", format_args!($($args)+))) -} - -struct BufferWriter<'a> { - buf: &'a mut [u8], - pos: usize, -} - -impl fmt::Write for BufferWriter<'_> { - fn write_str(&mut self, s: &str) -> fmt::Result { - for byte in s.bytes() { - self.buf[self.pos] = byte; - self.pos += 1; - } - Ok(()) - } -} - -pub fn _print(fd: i32, args: fmt::Arguments) { - use core::fmt::Write; - static mut BUFFER: [u8; 4096] = [0; 4096]; - let mut writer = BufferWriter { - buf: unsafe { &mut BUFFER }, - pos: 0, - }; - writer.write_fmt(args).ok(); - unsafe { - sys::sys_write(fd, &BUFFER[..writer.pos]); - } -} diff --git a/libusr/src/io/error.rs b/libusr/src/io/error.rs new file mode 100644 index 0000000..64b570a --- /dev/null +++ b/libusr/src/io/error.rs @@ -0,0 +1,36 @@ +use libsys::error::Errno; + +#[derive(Debug)] +pub struct Error { + #[allow(dead_code)] + repr: Repr, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum ErrorKind { + NotFound, + PermissionDenied, + InvalidData, +} + +#[derive(Debug)] +enum Repr { + Os(Errno), + Simple(ErrorKind), +} + +impl Error { + pub const fn new(kind: ErrorKind) -> Self { + Self { + repr: Repr::Simple(kind), + } + } +} + +impl From for Error { + fn from(e: Errno) -> Self { + Self { + repr: Repr::Os(e) + } + } +} diff --git a/libusr/src/io/mod.rs b/libusr/src/io/mod.rs new file mode 100644 index 0000000..d0c97d5 --- /dev/null +++ b/libusr/src/io/mod.rs @@ -0,0 +1,44 @@ +use libsys::{ + calls::{sys_fstatat, sys_ioctl}, + stat::{FileDescriptor, Stat}, + ioctl::IoctlCmd, + error::Errno, + proc::Pid +}; +use core::mem::size_of; +use core::fmt; + +mod error; +pub use error::{Error, ErrorKind}; +mod writer; +pub use writer::{_print}; +mod stdio; +pub use stdio::{stderr, stdin, stdout, Stderr, Stdin, Stdout}; + +pub trait Read { + fn read(&mut self, bytes: &mut [u8]) -> Result; +} + +pub trait Write { + fn write(&mut self, bytes: &[u8]) -> Result; + fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<(), Error>; +} + +pub trait AsRawFd { + fn as_raw_fd(&self) -> FileDescriptor; +} + +pub fn tcgetpgrp(_fd: FileDescriptor) -> Result { + todo!() +} + +pub fn tcsetpgrp(fd: FileDescriptor, pgid: Pid) -> Result<(), Errno> { + sys_ioctl(fd, IoctlCmd::TtySetPgrp, &pgid as *const _ as usize, size_of::()).map(|_| ()) +} + +pub fn stat(pathname: &str) -> Result { + let mut buf = Stat::default(); + // TODO error handling + sys_fstatat(None, pathname, &mut buf, 0).unwrap(); + Ok(buf) +} diff --git a/libusr/src/io/stdio.rs b/libusr/src/io/stdio.rs new file mode 100644 index 0000000..17f8afe --- /dev/null +++ b/libusr/src/io/stdio.rs @@ -0,0 +1,138 @@ +use crate::io::{Error, Read, Write}; +use crate::sync::Mutex; +use core::fmt; +use libsys::{ + calls::{sys_read, sys_write}, + stat::FileDescriptor, +}; + +struct InputInner { + fd: FileDescriptor, +} +struct OutputInner { + fd: FileDescriptor, +} + +//pub struct StdinLock<'a> { +// lock: MutexGuard<'a, InputInner> +//} +// +//pub struct StdoutLock<'a> { +// lock: MutexGuard<'a, OutputInner> +//} +// +//pub struct StderrLock<'a> { +// lock: MutexGuard<'a, OutputInner> +//} + +pub struct Stdin { + inner: &'static Mutex, +} + +pub struct Stdout { + inner: &'static Mutex, +} + +pub struct Stderr { + inner: &'static Mutex, +} + +// STDIN + +impl Read for InputInner { + fn read(&mut self, bytes: &mut [u8]) -> Result { + sys_read(self.fd, bytes).map_err(Error::from) + } +} + +impl Read for Stdin { + fn read(&mut self, bytes: &mut [u8]) -> Result { + self.inner.lock().read(bytes) + } +} + +// impl Stdin { +// pub fn lock(&self) -> StdinLock { +// StdinLock { +// lock: self.inner.lock() +// } +// } +// } + +// STDOUT/STDERR + +impl fmt::Write for OutputInner { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.write(s.as_bytes()).map(|_| ()).map_err(|_| todo!()) + } +} + +impl Write for OutputInner { + fn write(&mut self, bytes: &[u8]) -> Result { + sys_write(self.fd, bytes).map_err(Error::from) + } + + fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<(), Error> { + fmt::Write::write_fmt(self, args).map_err(|_| todo!()) + } +} + +impl Write for Stdout { + fn write(&mut self, bytes: &[u8]) -> Result { + self.inner.lock().write(bytes) + } + + fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<(), Error> { + self.inner.lock().write_fmt(args) + } +} + +impl Write for Stderr { + fn write(&mut self, bytes: &[u8]) -> Result { + self.inner.lock().write(bytes) + } + + fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<(), Error> { + self.inner.lock().write_fmt(args) + } +} + +// impl Stdout { +// pub fn lock(&self) -> StdoutLock { +// StdoutLock { +// lock: self.inner.lock() +// } +// } +// } +// +// impl Stderr { +// pub fn lock(&self) -> StderrLock { +// StderrLock { +// lock: self.inner.lock() +// } +// } +// } + +lazy_static! { + static ref STDIN: Mutex = Mutex::new(InputInner { + fd: FileDescriptor::STDIN + }); + static ref STDOUT: Mutex = Mutex::new(OutputInner { + fd: FileDescriptor::STDOUT + }); + static ref STDERR: Mutex = Mutex::new(OutputInner { + fd: FileDescriptor::STDOUT + }); +} + +pub fn stdin() -> Stdin { + Stdin { inner: &STDIN } +} + +pub fn stdout() -> Stdout { + Stdout { inner: &STDOUT } +} + +pub fn stderr() -> Stderr { + Stderr { inner: &STDERR } +} diff --git a/libusr/src/io/writer.rs b/libusr/src/io/writer.rs new file mode 100644 index 0000000..9ddcc08 --- /dev/null +++ b/libusr/src/io/writer.rs @@ -0,0 +1,26 @@ +use core::fmt; +use crate::io::Write; + +#[macro_export] +macro_rules! print { + ($($args:tt)+) => ($crate::io::_print($crate::io::stdout, format_args!($($args)+))) +} + +#[macro_export] +macro_rules! println { + ($($args:tt)+) => (print!("{}\n", format_args!($($args)+))) +} + +#[macro_export] +macro_rules! eprint { + ($($args:tt)+) => ($crate::io::_print($crate::io::stderr, format_args!($($args)+))) +} + +#[macro_export] +macro_rules! eprintln { + ($($args:tt)+) => (eprint!("{}\n", format_args!($($args)+))) +} + +pub fn _print(out: fn() -> T, args: fmt::Arguments) { + out().write_fmt(args).expect("stdout/stderr write failed"); +} diff --git a/libusr/src/lib.rs b/libusr/src/lib.rs index ee8532b..cc52cd4 100644 --- a/libusr/src/lib.rs +++ b/libusr/src/lib.rs @@ -1,34 +1,46 @@ #![feature(asm, alloc_error_handler)] #![no_std] +#[macro_use] +extern crate lazy_static; + +extern crate alloc; + use core::panic::PanicInfo; +use libsys::{debug::TraceLevel, ProgramArgs, proc::ExitCode}; +mod allocator; +pub mod env; +pub mod file; pub mod io; -pub mod mem; pub mod os; - -pub mod sys { - pub use syscall::calls::*; - pub use syscall::stat::*; - pub use syscall::termios; -} +pub mod sys; +pub mod sync; +pub mod thread; +pub mod signal; #[link_section = ".text._start"] #[no_mangle] -extern "C" fn _start(_arg: usize) -> ! { +extern "C" fn _start(arg: &'static ProgramArgs) -> ! { extern "Rust" { fn main() -> i32; } + unsafe { - sys::sys_exit(main()); + allocator::init(); + thread::init_main(); + env::setup_env(arg); } + + let res = unsafe { main() }; + sys::sys_exit(ExitCode::from(res)); } #[panic_handler] fn panic_handler(pi: &PanicInfo) -> ! { - // TODO formatted messages - trace!("Panic ocurred: {}", pi); - unsafe { - sys::sys_exit(-1); - } + // TODO unwind to send panic argument back to parent thread + // TODO print to stdout/stderr (if available) + let thread = thread::current(); + trace!(TraceLevel::Error, "{:?} panicked: {:?}", thread, pi); + sys::sys_exit(ExitCode::from(-1)); } diff --git a/libusr/src/os.rs b/libusr/src/os.rs index 76058da..3c9a592 100644 --- a/libusr/src/os.rs +++ b/libusr/src/os.rs @@ -1,42 +1,16 @@ +use libsys::debug::TraceLevel; use crate::sys; use core::fmt; -use core::mem::{size_of, MaybeUninit}; -use syscall::{ioctl::IoctlCmd, termios::Termios}; - -pub fn get_tty_attrs(fd: u32) -> Result { - let mut termios = MaybeUninit::::uninit(); - let res = unsafe { - sys::sys_ioctl( - fd, - IoctlCmd::TtyGetAttributes, - termios.as_mut_ptr() as usize, - size_of::(), - ) - }; - if res != size_of::() as isize { - return Err("Failed"); - } - Ok(unsafe { termios.assume_init() }) -} - -pub fn set_tty_attrs(fd: u32, attrs: &Termios) -> Result<(), &'static str> { - let res = unsafe { - sys::sys_ioctl( - fd, - IoctlCmd::TtySetAttributes, - attrs as *const _ as usize, - size_of::(), - ) - }; - if res != size_of::() as isize { - return Err("Failed"); - } - Ok(()) -} #[macro_export] macro_rules! trace { - ($($args:tt)+) => ($crate::os::_trace(format_args!($($args)+))) + ($level:expr, $($args:tt)+) => ($crate::os::_trace($level, format_args!($($args)+))) +} + + +#[macro_export] +macro_rules! trace_debug { + ($($args:tt)+) => ($crate::os::_trace($crate::sys::debug::TraceLevel::Debug, format_args!($($args)+))) } struct BufferWriter<'a> { @@ -54,7 +28,7 @@ impl fmt::Write for BufferWriter<'_> { } } -pub fn _trace(args: fmt::Arguments) { +pub fn _trace(level: TraceLevel, args: fmt::Arguments) { use core::fmt::Write; static mut BUFFER: [u8; 4096] = [0; 4096]; let mut writer = BufferWriter { @@ -62,7 +36,5 @@ pub fn _trace(args: fmt::Arguments) { pos: 0, }; writer.write_fmt(args).ok(); - unsafe { - sys::sys_ex_debug_trace(&BUFFER[..writer.pos]); - } + sys::sys_ex_debug_trace(level, unsafe { &BUFFER[..writer.pos] }).ok(); } diff --git a/libusr/src/signal.rs b/libusr/src/signal.rs new file mode 100644 index 0000000..8417122 --- /dev/null +++ b/libusr/src/signal.rs @@ -0,0 +1,42 @@ +use crate::trace; +use libsys::{ + debug::TraceLevel, + calls::{sys_ex_sigreturn, sys_exit}, + proc::ExitCode, + signal::Signal, +}; + +#[derive(Clone, Copy)] +pub enum SignalHandler { + Func(fn(Signal) -> ()), + Ignore, + Terminate, +} + +// TODO per-thread signal handler table +static mut SIGNAL_HANDLERS: [SignalHandler; 32] = [SignalHandler::Terminate; 32]; + +pub fn set_handler(sig: Signal, handler: SignalHandler) -> SignalHandler { + unsafe { + let old = SIGNAL_HANDLERS[sig as usize]; + SIGNAL_HANDLERS[sig as usize] = handler; + old + } +} + +#[inline(never)] +pub(crate) extern "C" fn signal_handler(arg: Signal) -> ! { + // TODO tpidr_el0 is invalidated when entering signal context + trace!(TraceLevel::Debug, "Entered signal handler: arg={:?}", arg); + let no = arg as usize; + if no >= 32 { + panic!("Undefined signal number: {}", no); + } + match unsafe { SIGNAL_HANDLERS[no] } { + SignalHandler::Func(f) => f(arg), + SignalHandler::Ignore => (), + SignalHandler::Terminate => sys_exit(ExitCode::from(-1)), + } + + sys_ex_sigreturn(); +} diff --git a/libusr/src/sync.rs b/libusr/src/sync.rs new file mode 100644 index 0000000..e04b5c9 --- /dev/null +++ b/libusr/src/sync.rs @@ -0,0 +1,54 @@ +use core::cell::UnsafeCell; +use core::ops::{Deref, DerefMut}; +use crate::sys::RawMutex; + +pub struct Mutex { + inner: RawMutex, + data: UnsafeCell +} + +pub struct MutexGuard<'a, T> { + data: &'a mut T, + lock: &'a RawMutex, +} + +impl Mutex { + pub fn new(t: T) -> Self { + Self { + inner: RawMutex::new(), + data: UnsafeCell::new(t) + } + } + + pub fn lock(&self) -> MutexGuard<'_, T> { + unsafe { + self.inner.lock(); + MutexGuard { + data: (&mut *self.data.get()), + lock: &self.inner + } + } + } +} + +impl<'a, T> Drop for MutexGuard<'a, T> { + fn drop(&mut self) { + unsafe { self.lock.release(); } + } +} + +impl<'a, T> Deref for MutexGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &T { + self.data + } +} + +impl<'a, T> DerefMut for MutexGuard<'a, T> { + fn deref_mut(&mut self) -> &mut T { + self.data + } +} + +unsafe impl Sync for Mutex {} diff --git a/libusr/src/sys/mod.rs b/libusr/src/sys/mod.rs new file mode 100644 index 0000000..06ac158 --- /dev/null +++ b/libusr/src/sys/mod.rs @@ -0,0 +1,38 @@ +pub use libsys::signal::{Signal, SignalDestination}; +pub use libsys::proc::{self, ExitCode}; +pub use libsys::termios; +pub use libsys::abi; +pub use libsys::calls::*; +pub use libsys::stat::{self, AccessMode, FileDescriptor}; +pub use libsys::error::Errno; +pub use libsys::debug; + +use core::sync::atomic::{Ordering, AtomicBool}; + +// TODO replace with a proper mutex impl +pub(crate) struct RawMutex { + inner: AtomicBool +} + +impl RawMutex { + pub const fn new() -> Self { + Self { inner: AtomicBool::new(false) } + } + + #[inline] + unsafe fn try_lock(&self) -> bool { + self.inner.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed).is_ok() + } + + #[inline] + pub unsafe fn lock(&self) { + while !self.try_lock() { + sys_ex_yield(); + } + } + + #[inline] + pub unsafe fn release(&self) { + self.inner.store(false, Ordering::Release); + } +} diff --git a/libusr/src/thread.rs b/libusr/src/thread.rs new file mode 100644 index 0000000..266c11a --- /dev/null +++ b/libusr/src/thread.rs @@ -0,0 +1,143 @@ +use crate::signal; +use alloc::{boxed::Box, sync::Arc, vec}; +use core::any::Any; +use core::cell::UnsafeCell; +use core::fmt; +use core::mem::MaybeUninit; +use libsys::{ + calls::{sys_ex_clone, sys_ex_gettid, sys_ex_signal, sys_ex_thread_exit, sys_ex_thread_wait}, + proc::ExitCode, +}; + +struct NativeData +where + F: FnOnce() -> T, + F: Send + 'static, + T: Send + 'static, +{ + closure: F, + result: ThreadPacket, + stack: usize, +} + +#[derive(Clone)] +pub struct Thread { + id: u32, +} + +pub type ThreadResult = Result>; +pub type ThreadPacket = Arc>>>; + +pub struct JoinHandle { + native: u32, + result: ThreadPacket, +} + +impl Thread { + pub const fn id(&self) -> u32 { + self.id + } +} + +impl fmt::Debug for Thread { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Thread") + .field("id", &self.id) + .finish_non_exhaustive() + } +} + +impl JoinHandle { + pub fn join(self) -> ThreadResult { + sys_ex_thread_wait(self.native).unwrap(); + unsafe { + Arc::try_unwrap(self.result) + .unwrap() + .into_inner() + .assume_init() + } + } +} + +unsafe fn init_common(signal_stack_pointer: *mut u8) { + let tid = sys_ex_gettid() as u64; + asm!("msr tpidr_el0, {:x}", in(reg) tid); + + // thread::current() should be valid at this point + + sys_ex_signal( + signal::signal_handler as usize, + signal_stack_pointer as usize, + ) + .unwrap(); +} + +pub(crate) unsafe fn init_main() { + #[repr(align(16))] + struct StackWrapper { + data: [u8; 8192], + } + static mut STACK: StackWrapper = StackWrapper { data: [0; 8192] }; + init_common(STACK.data.as_mut_ptr().add(8192)) +} + +pub fn current() -> Thread { + let mut id: u64; + unsafe { + asm!("mrs {:x}, tpidr_el0", out(reg) id); + } + + Thread { id: id as u32 } +} + +pub fn spawn(f: F) -> JoinHandle +where + F: FnOnce() -> T, + F: Send + 'static, + T: Send + 'static, +{ + let stack = vec![0u8; 8192].leak(); + let result = Arc::new(UnsafeCell::new(MaybeUninit::uninit())); + + #[inline(never)] + extern "C" fn thread_entry(data: *mut NativeData) -> ! + where + F: FnOnce() -> T, + F: Send + 'static, + T: Send + 'static, + { + let (_stack, _len) = { + // Setup signal handling + let mut signal_stack = vec![0u8; 8192]; + + unsafe { + init_common(signal_stack.as_mut_ptr().add(signal_stack.len())); + } + + let data: Box> = unsafe { Box::from_raw(data) }; + let res = (data.closure)(); + + unsafe { + (&mut *data.result.get()).write(Ok(res)); + } + + (data.stack, 8192) + }; + + // TODO free stack + sys_ex_thread_exit(ExitCode::from(0)); + } + + let native = { + let stack = stack.as_mut_ptr() as usize + stack.len(); + let data: *mut NativeData = Box::into_raw(Box::new(NativeData { + closure: f, + stack, + result: result.clone(), + })); + + sys_ex_clone(thread_entry:: as usize, stack, data as usize).unwrap() as u32 + }; + + JoinHandle { native, result } +} diff --git a/syscall/src/abi.rs b/syscall/src/abi.rs deleted file mode 100644 index 3cff16c..0000000 --- a/syscall/src/abi.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub const SYS_EX_DEBUG_TRACE: usize = 128; -pub const SYS_EX_NANOSLEEP: usize = 129; - -pub const SYS_EXIT: usize = 1; -pub const SYS_READ: usize = 2; -pub const SYS_WRITE: usize = 3; -pub const SYS_OPENAT: usize = 4; -pub const SYS_FSTATAT: usize = 5; -pub const SYS_CLOSE: usize = 6; -pub const SYS_FORK: usize = 7; -pub const SYS_EXECVE: usize = 8; -pub const SYS_WAITPID: usize = 9; -pub const SYS_IOCTL: usize = 10; diff --git a/syscall/src/calls.rs b/syscall/src/calls.rs deleted file mode 100644 index 8e347c6..0000000 --- a/syscall/src/calls.rs +++ /dev/null @@ -1,200 +0,0 @@ -use crate::abi; -use crate::{ - ioctl::IoctlCmd, - stat::{FileMode, OpenFlags, Stat}, -}; -use core::mem::size_of; - -// TODO document the syscall ABI - -macro_rules! syscall { - ($num:expr) => {{ - let mut res: usize; - asm!("svc #0", out("x0") res, in("x8") $num, options(nostack)); - res - }}; - ($num:expr, $a0:expr) => {{ - let mut res: usize = $a0; - asm!("svc #0", - inout("x0") res, - in("x8") $num, options(nostack)); - res - }}; - ($num:expr, $a0:expr, $a1:expr) => {{ - let mut res: usize = $a0; - asm!("svc #0", - inout("x0") res, in("x1") $a1, - in("x8") $num, options(nostack)); - res - }}; - ($num:expr, $a0:expr, $a1:expr, $a2:expr) => {{ - let mut res: usize = $a0; - asm!("svc #0", - inout("x0") res, in("x1") $a1, in("x2") $a2, - in("x8") $num, options(nostack)); - res - }}; - ($num:expr, $a0:expr, $a1:expr, $a2:expr, $a3:expr) => {{ - let mut res: usize = $a0; - asm!("svc #0", - inout("x0") res, in("x1") $a1, in("x2") $a2, - in("x3") $a3, in("x8") $num, options(nostack)); - res - }}; - ($num:expr, $a0:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr) => {{ - let mut res: usize = $a0; - asm!("svc #0", - inout("x0") res, in("x1") $a1, in("x2") $a2, - in("x3") $a3, in("x4") $a4, in("x8") $num, options(nostack)); - res - }}; -} - -/// Integer/size argument -macro_rules! argn { - ($a:expr) => { - $a as usize - }; -} -/// Pointer/base argument -macro_rules! argp { - ($a:expr) => { - $a as usize - }; -} -// /// Immutable pointer/base argument -// macro_rules! argpi { -// ($a:expr) => ($a as *const core::ffi::c_void as usize) -// } - -/// # Safety -/// -/// System call -#[inline(always)] -pub unsafe fn sys_exit(status: i32) -> ! { - syscall!(abi::SYS_EXIT, argn!(status)); - unreachable!(); -} - -/// # Safety -/// -/// System call -#[inline(always)] -pub unsafe fn sys_close(fd: i32) -> i32 { - syscall!(abi::SYS_CLOSE, argn!(fd)) as i32 -} - -/// # Safety -/// -/// System call -#[inline(always)] -pub unsafe fn sys_ex_nanosleep(ns: u64, rem: &mut [u64; 2]) -> i32 { - syscall!(abi::SYS_EX_NANOSLEEP, argn!(ns), argp!(rem.as_mut_ptr())) as i32 -} - -/// # Safety -/// -/// System call -#[inline(always)] -pub unsafe fn sys_ex_debug_trace(msg: &[u8]) -> usize { - syscall!( - abi::SYS_EX_DEBUG_TRACE, - argp!(msg.as_ptr()), - argn!(msg.len()) - ) -} - -/// # Safety -/// -/// System call -#[inline(always)] -pub unsafe fn sys_openat(at: i32, pathname: &str, mode: FileMode, flags: OpenFlags) -> i32 { - syscall!( - abi::SYS_OPENAT, - argn!(at), - argp!(pathname.as_ptr()), - argn!(pathname.len()), - argn!(mode.bits()), - argn!(flags.bits()) - ) as i32 -} - -/// # Safety -/// -/// System call -#[inline(always)] -pub unsafe fn sys_read(fd: i32, data: &mut [u8]) -> isize { - syscall!( - abi::SYS_READ, - argn!(fd), - argp!(data.as_mut_ptr()), - argn!(data.len()) - ) as isize -} - -/// # Safety -/// -/// System call -#[inline(always)] -pub unsafe fn sys_write(fd: i32, data: &[u8]) -> isize { - syscall!( - abi::SYS_WRITE, - argn!(fd), - argp!(data.as_ptr()), - argn!(data.len()) - ) as isize -} - -/// # Safety -/// -/// System call -#[inline(always)] -pub unsafe fn sys_fstatat(at: i32, pathname: &str, statbuf: &mut Stat, flags: u32) -> i32 { - syscall!( - abi::SYS_FSTATAT, - argn!(at), - argp!(pathname.as_ptr()), - argn!(pathname.len()), - argp!(statbuf as *mut Stat), - argn!(flags) - ) as i32 -} - -/// # Safety -/// -/// System call -#[inline(always)] -pub unsafe fn sys_fork() -> i32 { - syscall!(abi::SYS_FORK) as i32 -} - -/// # Safety -/// -/// System call -#[inline(always)] -pub unsafe fn sys_execve(pathname: &str) -> i32 { - syscall!( - abi::SYS_EXECVE, - argp!(pathname.as_ptr()), - argn!(pathname.len()) - ) as i32 -} - -/// # Safety -/// -/// System call -#[inline(always)] -pub unsafe fn sys_waitpid(pid: u32, status: &mut i32) -> i32 { - syscall!(abi::SYS_WAITPID, argn!(pid), argp!(status as *mut i32)) as i32 -} - -#[inline(always)] -pub unsafe fn sys_ioctl(fd: u32, cmd: IoctlCmd, ptr: usize, len: usize) -> isize { - syscall!( - abi::SYS_IOCTL, - argn!(fd), - argn!(cmd), - argn!(ptr), - argn!(len) - ) as isize -} diff --git a/syscall/src/lib.rs b/syscall/src/lib.rs deleted file mode 100644 index 1b8eb00..0000000 --- a/syscall/src/lib.rs +++ /dev/null @@ -1,15 +0,0 @@ -#![feature(asm)] -#![no_std] - -#[macro_use] -extern crate bitflags; - -pub mod abi; -pub mod stat; -pub mod ioctl; -pub mod termios; - -#[cfg(feature = "user")] -pub mod calls; -#[cfg(feature = "user")] -pub use calls::*; diff --git a/syscall/src/stat.rs b/syscall/src/stat.rs deleted file mode 100644 index 733f77f..0000000 --- a/syscall/src/stat.rs +++ /dev/null @@ -1,53 +0,0 @@ -pub const AT_FDCWD: i32 = -2; -pub const AT_EMPTY_PATH: u32 = 1 << 16; - -pub const STDIN_FILENO: i32 = 0; -pub const STDOUT_FILENO: i32 = 1; -pub const STDERR_FILENO: i32 = 2; - -bitflags! { - pub struct OpenFlags: u32 { - const O_RDONLY = 1; - const O_WRONLY = 2; - const O_RDWR = 3; - const O_ACCESS = 0x7; - - const O_CREAT = 1 << 4; - const O_EXEC = 1 << 5; - const O_CLOEXEC = 1 << 6; - } -} - -bitflags! { - pub struct FileMode: u32 { - const USER_READ = 1 << 8; - const USER_WRITE = 1 << 7; - const USER_EXEC = 1 << 6; - const GROUP_READ = 1 << 5; - const GROUP_WRITE = 1 << 4; - const GROUP_EXEC = 1 << 3; - const OTHER_READ = 1 << 2; - const OTHER_WRITE = 1 << 1; - const OTHER_EXEC = 1 << 0; - } -} - -#[derive(Clone, Copy, Debug, Default)] -#[repr(C)] -pub struct Stat { - pub mode: u32, - pub size: u64, - pub blksize: u32, -} - -impl FileMode { - /// Returns default permission set for directories - pub const fn default_dir() -> Self { - unsafe { Self::from_bits_unchecked(0o755) } - } - - /// Returns default permission set for regular files - pub const fn default_reg() -> Self { - unsafe { Self::from_bits_unchecked(0o644) } - } -} diff --git a/user/Cargo.toml b/user/Cargo.toml index 2b8da15..910a7e6 100644 --- a/user/Cargo.toml +++ b/user/Cargo.toml @@ -11,7 +11,29 @@ path = "src/init/main.rs" [[bin]] name = "shell" -path = "src/shell/main.rs" +path = "src/bin/shell.rs" + +[[bin]] +name = "fuzzy" +path = "src/bin/fuzzy.rs" + +[[bin]] +name = "ls" +path = "src/bin/ls.rs" + +[[bin]] +name = "cat" +path = "src/bin/cat.rs" + +[[bin]] +name = "hexd" +path = "src/bin/hexd.rs" + +[[bin]] +name = "login" +path = "src/sbin/login.rs" [dependencies] libusr = { path = "../libusr" } +libsys = { path = "../libsys" } +lazy_static = { version = "*", features = ["spin_no_std"] } diff --git a/user/src/bin/cat.rs b/user/src/bin/cat.rs new file mode 100644 index 0000000..6360799 --- /dev/null +++ b/user/src/bin/cat.rs @@ -0,0 +1,46 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate libusr; + +use libusr::io::{self, Read, Write}; +use libusr::file::File; + +fn do_cat(mut fd: F) -> Result<(), io::Error> { + let mut buf = [0; 4096]; + let mut out = io::stdout(); + + loop { + let count = fd.read(&mut buf)?; + if count == 0 { + break; + } + + out.write(&buf[..count])?; + } + + Ok(()) +} + +#[no_mangle] +fn main() -> i32 { + let args = libusr::env::args(); + let mut res = 0; + + if args.len() == 1 { + if let Err(e) = do_cat(io::stdin()) { + eprintln!("{}: {:?}", ".", e); + res = -1; + } + } else { + for arg in &args[1..] { + if let Err(e) = File::open(arg).map(do_cat) { + eprintln!("{}: {:?}", arg, e); + res = -1; + } + } + } + + res +} diff --git a/user/src/bin/fuzzy.rs b/user/src/bin/fuzzy.rs new file mode 100644 index 0000000..36a2629 --- /dev/null +++ b/user/src/bin/fuzzy.rs @@ -0,0 +1,139 @@ +#![feature(asm)] +#![no_std] +#![no_main] + +#![allow(unused_macros)] +#![allow(dead_code)] + +#[macro_use] +extern crate libusr; + +use libusr::sys::{abi::SystemCall, stat::Stat}; + +static mut STATE: u64 = 0; + +macro_rules! syscall { + ($num:expr) => {{ + let mut res: usize; + asm!("svc #0", out("x0") res, in("x8") $num, options(nostack)); + res + }}; + ($num:expr, $a0:expr) => {{ + let mut res: usize = $a0; + asm!("svc #0", + inout("x0") res, + in("x8") $num, options(nostack)); + res + }}; + ($num:expr, $a0:expr, $a1:expr) => {{ + let mut res: usize = $a0; + asm!("svc #0", + inout("x0") res, in("x1") $a1, + in("x8") $num, options(nostack)); + res + }}; + ($num:expr, $a0:expr, $a1:expr, $a2:expr) => {{ + let mut res: usize = $a0; + asm!("svc #0", + inout("x0") res, in("x1") $a1, in("x2") $a2, + in("x8") $num, options(nostack)); + res + }}; + ($num:expr, $a0:expr, $a1:expr, $a2:expr, $a3:expr) => {{ + let mut res: usize = $a0; + asm!("svc #0", + inout("x0") res, in("x1") $a1, in("x2") $a2, + in("x3") $a3, in("x8") $num, options(nostack)); + res + }}; + ($num:expr, $a0:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr) => {{ + let mut res: usize = $a0; + asm!("svc #0", + inout("x0") res, in("x1") $a1, in("x2") $a2, + in("x3") $a3, in("x4") $a4, in("x8") $num, options(nostack)); + res + }}; +} + +/// Integer/size argument +macro_rules! argn { + ($a:expr) => { + $a as usize + }; +} +/// Pointer/base argument +macro_rules! argp { + ($a:expr) => { + $a as usize + }; +} + +fn random_set_seed(seed: u64) { + unsafe { STATE = seed; } +} + +fn random_u64() -> u64 { + let mut x = unsafe { STATE }; + x ^= x << 13; + x ^= x >> 7; + x ^= x << 17; + unsafe { + STATE = x; + } + x +} + +fn random_ascii_char() -> u8 { + ((random_u64() % (0x7F - 0x20)) as u8) + 0x20 +} + +fn random_str_range(buf: &mut [u8], min: usize, max: usize) -> &str { + let max = core::cmp::min(buf.len(), max); + assert!(max > min); + let len = ((random_u64() as usize) % (max - min)) + min; + for c in buf[..len].iter_mut() { + *c = random_ascii_char(); + } + core::str::from_utf8(&buf[..len]).unwrap() +} + +fn random_str(buf: &mut [u8]) -> &str { + random_str_range(buf, 0, buf.len()) +} + +fn random_bytes(buf: &mut [u8]) { + for byte in buf.iter_mut() { + *byte = (random_u64() & 0xFF) as u8; + } +} + +#[no_mangle] +fn main() -> i32 { + let seed = libusr::sys::sys_ex_getcputime().unwrap().as_nanos() as u64 / 13; + println!("Using seed: {:#x}", seed); + random_set_seed(seed); + + let mut buf = [0; 256]; + + // Test sys_ex_getcputime() + let mut prev_time = libusr::sys::sys_ex_getcputime().unwrap().as_nanos(); + for _ in 0..1000 { + let t = libusr::sys::sys_ex_getcputime().unwrap().as_nanos(); + assert!(t >= prev_time); + prev_time = t; + } + + // Test non-utf8 input fed into syscalls expecting strings + // let old_signal = signal::set_handler(Signal::InvalidSystemCall, SignalHandler::Ignore); + for _ in 0..10000 { + random_bytes(&mut buf); + let mut stat = Stat::default(); + + unsafe { + syscall!(SystemCall::FileStatus.repr(), (-2i32) as usize, buf.as_mut_ptr() as usize, buf.len(), (&mut stat) as *mut _ as usize); + } + } + // signal::set_handler(Signal::InvalidSystemCall, old_signal); + + 0 +} diff --git a/user/src/bin/hexd.rs b/user/src/bin/hexd.rs new file mode 100644 index 0000000..536463c --- /dev/null +++ b/user/src/bin/hexd.rs @@ -0,0 +1,69 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate libusr; + +use libusr::io::{self, Read}; +use libusr::file::File; + +fn line_print(off: usize, line: &[u8]) { + print!("{:08x}: ", off); + for i in 0..16 { + if i < line.len() { + print!("{:02x}", line[i]); + } else { + print!(" "); + } + if i % 2 != 0 { + print!(" "); + } + } + print!("| "); + for &b in line.iter() { + if b.is_ascii() && !b.is_ascii_control() { + print!("{}", b as char); + } else { + print!("."); + } + } + println!(""); +} + +fn do_hexd(mut fd: F) -> Result<(), io::Error> { + let mut buf = [0; 16]; + let mut off = 0; + loop { + let count = fd.read(&mut buf)?; + if count == 0 { + break; + } + + line_print(off, &buf[..count]); + off += count; + } + + Ok(()) +} + +#[no_mangle] +fn main() -> i32 { + let args = libusr::env::args(); + let mut res = 0; + + if args.len() == 1 { + if let Err(e) = do_hexd(io::stdin()) { + eprintln!("{}: {:?}", ".", e); + res = -1; + } + } else { + for arg in &args[1..] { + if let Err(e) = File::open(arg).map(do_hexd) { + eprintln!("{}: {:?}", arg, e); + res = -1; + } + } + } + + res +} diff --git a/user/src/bin/ls.rs b/user/src/bin/ls.rs new file mode 100644 index 0000000..ef35207 --- /dev/null +++ b/user/src/bin/ls.rs @@ -0,0 +1,73 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate libusr; +#[macro_use] +extern crate alloc; + +use alloc::borrow::ToOwned; +use libusr::sys::{ + stat::{DirectoryEntry, FileMode, OpenFlags, Stat}, + sys_close, sys_fstatat, sys_openat, sys_readdir, Errno, +}; + +fn list_directory(path: &str) -> Result<(), Errno> { + let mut buffer = [DirectoryEntry::empty(); 8]; + let mut stat = Stat::default(); + let mut data = vec![]; + + let fd = sys_openat( + None, + path, + FileMode::default_dir(), + OpenFlags::O_DIRECTORY | OpenFlags::O_RDONLY, + )?; + + loop { + let count = sys_readdir(fd, &mut buffer)?; + if count == 0 { + break; + } + + buffer.iter().take(count).for_each(|e| { + data.push(e.as_str().to_owned()); + }); + } + + data.sort(); + + data.iter().for_each(|item| { + let stat = sys_fstatat(Some(fd), item, &mut stat, 0).map(|_| &stat); + if let Ok(stat) = stat { + print!("{} ", stat.mode); + } else { + print!("?????????? "); + } + println!("{}", item); + }); + + sys_close(fd) +} + +#[no_mangle] +fn main() -> i32 { + let args = libusr::env::args(); + let mut res = 0; + + if args.len() == 1 { + if let Err(e) = list_directory(".") { + eprintln!("{}: {:?}", ".", e); + res = -1; + } + } else { + for arg in &args[1..] { + if let Err(e) = list_directory(arg) { + eprintln!("{}: {:?}", arg, e); + res = -1; + } + } + } + + res +} diff --git a/user/src/bin/shell.rs b/user/src/bin/shell.rs new file mode 100644 index 0000000..ecd6319 --- /dev/null +++ b/user/src/bin/shell.rs @@ -0,0 +1,113 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate libusr; +extern crate alloc; + +use alloc::{borrow::ToOwned, vec::Vec}; +use libusr::io::{self, Read}; +use libusr::signal::{self, SignalHandler}; +use libusr::sys::{ + proc::Pid, sys_chdir, sys_execve, sys_exit, sys_faccessat, sys_fork, sys_getpgid, sys_setpgid, + sys_waitpid, AccessMode, Errno, ExitCode, FileDescriptor, Signal, +}; + +struct Builtin { + func: fn(&[&str]) -> ExitCode, + name: &'static str, +} + +fn cmd_cd(args: &[&str]) -> ExitCode { + if args.len() != 2 { + eprintln!("Usage: cd DIR"); + ExitCode::from(-1) + } else { + if let Err(err) = sys_chdir(args[1]) { + eprintln!("{}: {:?}", args[1], err); + ExitCode::from(-1) + } else { + ExitCode::from(0) + } + } +} + +static BUILTINS: [Builtin; 1] = [Builtin { + name: "cd", + func: cmd_cd, +}]; + +fn readline<'a, F: Read>(f: &mut F, bytes: &'a mut [u8]) -> Result, io::Error> { + let size = f.read(bytes)?; + Ok(if size == 0 { + None + } else { + Some( + core::str::from_utf8(&bytes[..size]) + .unwrap() + .trim_end_matches('\n'), + ) + }) +} + +fn execute(line: &str) -> Result { + // TODO proper arg handling + let args: Vec<&str> = line.split(' ').collect(); + let cmd = args[0]; + + for item in BUILTINS.iter() { + if item.name == cmd { + return Ok((item.func)(&args)); + } + } + + let filename = "/bin/".to_owned() + cmd; + sys_faccessat(None, &filename, AccessMode::X_OK, 0)?; + + if let Some(pid) = unsafe { sys_fork()? } { + let mut status = 0; + sys_waitpid(pid, &mut status)?; + let pgid = sys_getpgid(unsafe { Pid::from_raw(0) }).unwrap(); + io::tcsetpgrp(FileDescriptor::STDIN, pgid).unwrap(); + Ok(ExitCode::from(status)) + } else { + let pgid = sys_setpgid(unsafe { Pid::from_raw(0) }, unsafe { Pid::from_raw(0) }).unwrap(); + io::tcsetpgrp(FileDescriptor::STDIN, pgid).unwrap(); + sys_execve(&filename, &args).unwrap(); + sys_exit(ExitCode::from(-1)); + } +} + +#[no_mangle] +fn main() -> i32 { + let mut buf = [0; 256]; + let mut stdin = io::stdin(); + + signal::set_handler(Signal::Interrupt, SignalHandler::Ignore); + let pgid = sys_setpgid(unsafe { Pid::from_raw(0) }, unsafe { Pid::from_raw(0) }).unwrap(); + io::tcsetpgrp(FileDescriptor::STDIN, pgid).unwrap(); + + loop { + print!("> "); + match readline(&mut stdin, &mut buf) { + Ok(line) => { + if line.is_none() { + break; + } + let line = line.unwrap().trim_start_matches(' '); + if line.is_empty() { + continue; + } + + if let Err(e) = execute(line) { + eprintln!("{}: {:?}", line.split(' ').next().unwrap(), e); + } + } + Err(_) => { + println!("Interrupt!"); + continue; + } + } + } + 0 +} diff --git a/user/src/init/main.rs b/user/src/init/main.rs index df9d5e6..b7b1448 100644 --- a/user/src/init/main.rs +++ b/user/src/init/main.rs @@ -5,22 +5,31 @@ #[macro_use] extern crate libusr; +use libusr::sys::{stat::MountOptions, sys_execve, sys_fork, sys_mount, sys_waitpid}; + #[no_mangle] fn main() -> i32 { - let pid = unsafe { libusr::sys::sys_fork() }; - if pid < 0 { - eprintln!("fork() failed"); - return -1; - } else if pid == 0 { - return unsafe { libusr::sys::sys_execve("/bin/shell") }; - } else { + sys_mount( + "/dev", + &MountOptions { + device: None, + fs: Some("devfs"), + }, + ) + .expect("Failed to mount devfs"); + + if let Some(pid) = unsafe { sys_fork().unwrap() } { let mut status = 0; - let res = unsafe { libusr::sys::sys_waitpid(pid as u32, &mut status) }; - if res == 0 { - println!("Process {} exited with status {}", pid, status); - } else { - eprintln!("waitpid() failed"); + sys_waitpid(pid, &mut status).unwrap(); + println!("Process {:?} exited with status {}", pid, status); + + loop { + unsafe { + asm!("nop"); + } } + } else { + sys_execve("/sbin/login", &["/sbin/login", "/dev/ttyS0"]).unwrap(); + loop {} } - loop {} } diff --git a/user/src/sbin/login.rs b/user/src/sbin/login.rs new file mode 100644 index 0000000..3de22b2 --- /dev/null +++ b/user/src/sbin/login.rs @@ -0,0 +1,147 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate libusr; + +use libsys::{ + calls::{ + sys_close, sys_dup, sys_fork, sys_getgid, sys_getpgid, sys_getuid, sys_ioctl, sys_openat, + sys_read, sys_setgid, sys_setpgid, sys_setsid, sys_setuid, sys_waitpid, sys_execve + }, + error::Errno, + ioctl::IoctlCmd, + proc::Pid, + stat::{FileDescriptor, FileMode, GroupId, OpenFlags, UserId}, + termios::{Termios, TermiosLflag}, +}; +use libusr::{env, io}; + +struct HiddenInput { + fd: FileDescriptor, + termios: Termios, +} + +impl HiddenInput { + fn open(fd: FileDescriptor) -> Result { + use core::mem::{size_of, MaybeUninit}; + let mut termios: MaybeUninit = MaybeUninit::uninit(); + sys_ioctl( + fd, + IoctlCmd::TtyGetAttributes, + termios.as_mut_ptr() as usize, + size_of::(), + )?; + let termios = unsafe { termios.assume_init() }; + + let mut new_termios = termios.clone(); + new_termios.lflag &= !(TermiosLflag::ECHO | TermiosLflag::ECHOK | TermiosLflag::ECHOE); + sys_ioctl( + fd, + IoctlCmd::TtySetAttributes, + &new_termios as *const _ as usize, + size_of::(), + )?; + + Ok(Self { fd, termios }) + } + + fn readline<'a>(&mut self, buf: &'a mut [u8]) -> Result<&'a str, Errno> { + readline(self.fd, buf) + } +} + +impl Drop for HiddenInput { + fn drop(&mut self) { + use core::mem::size_of; + sys_ioctl( + self.fd, + IoctlCmd::TtySetAttributes, + &self.termios as *const _ as usize, + size_of::(), + ) + .ok(); + } +} + +fn readline(fd: FileDescriptor, buf: &mut [u8]) -> Result<&str, Errno> { + let len = sys_read(fd, buf)?; + + if len == 0 { + Ok("") + } else { + Ok(core::str::from_utf8(&buf[..len - 1]).unwrap()) + } +} + +fn login_as(uid: UserId, gid: GroupId, shell: &str) -> Result<(), Errno> { + if let Some(pid) = unsafe { sys_fork() }? { + let mut status = 0; + sys_waitpid(pid, &mut status).ok(); + let pgid = sys_getpgid(unsafe { Pid::from_raw(0) }).unwrap(); + io::tcsetpgrp(FileDescriptor::STDIN, pgid).unwrap(); + Ok(()) + } else { + sys_setuid(uid).expect("setuid failed"); + sys_setgid(gid).expect("setgid failed"); + let pgid = sys_setpgid(unsafe { Pid::from_raw(0) }, unsafe { Pid::from_raw(0) }).unwrap(); + io::tcsetpgrp(FileDescriptor::STDIN, pgid).unwrap(); + sys_execve(shell, &[shell]).expect("execve() failed"); + panic!(); + } +} + +// TODO baud rate and misc port settings +#[no_mangle] +fn main() -> i32 { + if !sys_getuid().is_root() || !sys_getgid().is_root() { + panic!("This program must be run as root"); + } + + let args = env::args(); + if args.len() != 2 { + panic!("Usage: {} TTY", args[0]); + } + + sys_setsid().expect("setsid() failed"); + + // Close controlling terminal + // NOTE this will invalidate rust-side Stdin, Stdout, Stderr + // until replacement is re-opened using the specified TTY + sys_close(FileDescriptor::STDERR).unwrap(); + sys_close(FileDescriptor::STDOUT).unwrap(); + sys_close(FileDescriptor::STDIN).unwrap(); + + sys_openat( + None, + args[1], + FileMode::default_reg(), + OpenFlags::O_RDONLY | OpenFlags::O_CTTY, + ) + .expect("Failed to open stdin"); + sys_openat( + None, + args[1], + FileMode::default_reg(), + OpenFlags::O_WRONLY | OpenFlags::O_CTTY, + ) + .expect("Failed to open stdout"); + sys_dup(FileDescriptor::STDOUT, Some(FileDescriptor::STDERR)).expect("Failed to open stderr"); + + let mut user_buf = [0; 128]; + let mut password_buf = [0; 128]; + loop { + print!("login: "); + let username = readline(FileDescriptor::STDIN, &mut user_buf).expect("Login read failed"); + print!("password: "); + let password = { + let mut input = HiddenInput::open(FileDescriptor::STDIN).unwrap(); + input.readline(&mut password_buf) + } + .expect("Password read failed"); + + if username == "root" && password == "toor" { + login_as(UserId::from(0), GroupId::from(0), "/bin/shell").unwrap(); + } + } +} diff --git a/user/src/shell/main.rs b/user/src/shell/main.rs deleted file mode 100644 index b327102..0000000 --- a/user/src/shell/main.rs +++ /dev/null @@ -1,35 +0,0 @@ -#![no_std] -#![no_main] - -#[macro_use] -extern crate libusr; - -fn readline(fd: i32, buf: &mut [u8]) -> Result<&str, ()> { - let count = unsafe { libusr::sys::sys_read(fd, buf) }; - if count >= 0 { - core::str::from_utf8(&buf[..count as usize]).map_err(|_| ()) - } else { - Err(()) - } -} - -#[no_mangle] -fn main() -> i32 { - let mut buf = [0; 512]; - loop { - print!("> "); - let line = readline(libusr::sys::STDIN_FILENO, &mut buf).unwrap(); - if line.is_empty() { - break; - } - let line = line.trim_end_matches('\n'); - - println!(":: {:?}", line); - - if line == "quit" || line == "exit" { - break; - } - } - - return 0; -}