From 564d10e1be466b4e794773d0a7637c8494caf8d3 Mon Sep 17 00:00:00 2001 From: Mark Poliakov <mark@alnyan.me> Date: Tue, 23 Nov 2021 17:55:58 +0200 Subject: [PATCH] feature: simple ls(1p) --- Makefile | 1 + fs/macros/src/lib.rs | 13 +++++ fs/memfs/src/dir.rs | 13 +++-- fs/memfs/src/file.rs | 5 +- fs/memfs/src/lib.rs | 4 +- fs/memfs/src/tar.rs | 7 ++- fs/vfs/src/file.rs | 60 ++++++++++++++++++++++- fs/vfs/src/node.rs | 100 ++++++++++++++++++++++++++++++-------- kernel/src/syscall/arg.rs | 13 +++++ kernel/src/syscall/mod.rs | 83 ++++++++++++++++--------------- libsys/src/abi.rs | 1 + libsys/src/calls.rs | 18 ++++++- libsys/src/stat.rs | 95 +++++++++++++++++++++++++++++++----- user/Cargo.toml | 4 ++ user/src/ls/main.rs | 45 +++++++++++++++++ 15 files changed, 380 insertions(+), 82 deletions(-) create mode 100644 user/src/ls/main.rs diff --git a/Makefile b/Makefile index bbfd889..b73183a 100644 --- a/Makefile +++ b/Makefile @@ -96,6 +96,7 @@ initrd: 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 cd $(O)/rootfs && tar cf ../initrd.img `find -type f -printf "%P\n"` ifeq ($(MACH),orangepi3) $(MKIMAGE) \ diff --git a/fs/macros/src/lib.rs b/fs/macros/src/lib.rs index dde1281..c3c4c84 100644 --- a/fs/macros/src/lib.rs +++ b/fs/macros/src/lib.rs @@ -94,6 +94,18 @@ fn impl_inode_fn<T: ToTokens>(name: &str, behavior: T) -> ImplItem { #behavior } }, + "readdir" => quote! { + fn readdir( + &mut self, + _node: VnodeRef, + _pos: usize, + _entries: &mut [libsys::stat::DirectoryEntry] + ) -> + Result<usize, libsys::error::Errno> + { + #behavior + } + }, _ => panic!("TODO implement {:?}", name), }) } @@ -126,6 +138,7 @@ pub fn auto_inode(attr: TokenStream, input: TokenStream) -> TokenStream { 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 { diff --git a/fs/memfs/src/dir.rs b/fs/memfs/src/dir.rs index 5766e1a..fc1caea 100644 --- a/fs/memfs/src/dir.rs +++ b/fs/memfs/src/dir.rs @@ -1,6 +1,9 @@ use crate::{BlockAllocator, Bvec, FileInode}; use alloc::boxed::Box; -use libsys::{error::Errno, stat::Stat}; +use libsys::{ + error::Errno, + stat::{DirectoryEntry, OpenFlags, Stat}, +}; use vfs::{Vnode, VnodeImpl, VnodeKind, VnodeRef}; pub struct DirInode<A: BlockAllocator + Copy + 'static> { @@ -15,7 +18,7 @@ impl<A: BlockAllocator + Copy + 'static> VnodeImpl for DirInode<A> { name: &str, kind: VnodeKind, ) -> Result<VnodeRef, Errno> { - 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)))), @@ -32,7 +35,11 @@ impl<A: BlockAllocator + Copy + 'static> VnodeImpl for DirInode<A> { Ok(()) } - fn stat(&mut self, _at: VnodeRef, _stat: &mut Stat) -> Result<(), Errno> { + fn stat(&mut self, node: VnodeRef, stat: &mut Stat) -> Result<(), Errno> { + let props = node.props(); + stat.size = 0; + stat.blksize = 4096; + stat.mode = props.mode; Ok(()) } } diff --git a/fs/memfs/src/file.rs b/fs/memfs/src/file.rs index 1fe8068..13d31b6 100644 --- a/fs/memfs/src/file.rs +++ b/fs/memfs/src/file.rs @@ -35,10 +35,11 @@ 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> { + fn stat(&mut self, node: VnodeRef, stat: &mut Stat) -> Result<(), Errno> { + let props = node.props(); stat.size = self.data.size() as u64; stat.blksize = 4096; - stat.mode = 0o755; + stat.mode = props.mode; Ok(()) } } diff --git a/fs/memfs/src/lib.rs b/fs/memfs/src/lib.rs index 04be460..7e9e83c 100644 --- a/fs/memfs/src/lib.rs +++ b/fs/memfs/src/lib.rs @@ -69,7 +69,7 @@ impl<A: BlockAllocator + Copy + 'static> Ramfs<A> { fn create_node_initial(self: Rc<Self>, name: &str, tar: &Tar) -> VnodeRef { let kind = tar.node_kind(); - let node = Vnode::new(name, kind, Vnode::SEEKABLE); + let node = Vnode::new(name, kind, Vnode::SEEKABLE | Vnode::CACHE_READDIR); node.props_mut().mode = tar.mode(); node.set_fs(self.clone()); match kind { @@ -113,7 +113,7 @@ impl<A: BlockAllocator + Copy + 'static> Ramfs<A> { } unsafe fn load_tar(self: Rc<Self>, base: *const u8, size: usize) -> Result<VnodeRef, Errno> { - let root = Vnode::new("", VnodeKind::Directory, Vnode::SEEKABLE); + 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(); diff --git a/fs/memfs/src/tar.rs b/fs/memfs/src/tar.rs index 2211f65..5326a2f 100644 --- a/fs/memfs/src/tar.rs +++ b/fs/memfs/src/tar.rs @@ -82,7 +82,12 @@ impl Tar { } pub fn mode(&self) -> FileMode { - FileMode::from_bits(from_octal(&self.mode) as u32).unwrap() + 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] { diff --git a/fs/vfs/src/file.rs b/fs/vfs/src/file.rs index cd63be4..4c267b6 100644 --- a/fs/vfs/src/file.rs +++ b/fs/vfs/src/file.rs @@ -1,9 +1,10 @@ -use crate::{VnodeKind, VnodeRef}; +use crate::{VnodeKind, VnodeRef, Vnode}; use alloc::rc::Rc; use core::cell::RefCell; use core::cmp::min; use libsys::{ error::Errno, + stat::DirectoryEntry, traits::{Read, Seek, SeekDir, Write}, }; @@ -97,6 +98,9 @@ impl File { /// File has to be closed on execve() calls pub const CLOEXEC: u32 = 1 << 2; + pub const POS_CACHE_DOT: usize = usize::MAX - 1; + 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 { @@ -125,6 +129,60 @@ impl File { _ => todo!(), } } + + fn cache_readdir(inner: &mut NormalFile, entries: &mut [DirectoryEntry]) -> Result<usize, Errno> { + 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) + } + + pub fn readdir(&mut self, entries: &mut [DirectoryEntry]) -> Result<usize, Errno> { + 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 { diff --git a/fs/vfs/src/node.rs b/fs/vfs/src/node.rs index 1e066b9..d21b058 100644 --- a/fs/vfs/src/node.rs +++ b/fs/vfs/src/node.rs @@ -1,11 +1,11 @@ -use crate::{Ioctx, File, FileRef, Filesystem}; +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 libsys::{ error::Errno, ioctl::IoctlCmd, - stat::{AccessMode, FileMode, OpenFlags, Stat}, + stat::{AccessMode, DirectoryEntry, FileMode, OpenFlags, Stat}, }; /// Convenience type alias for [Rc<Vnode>] @@ -74,6 +74,13 @@ pub trait VnodeImpl { /// Resizes the file storage if necessary. fn write(&mut self, node: VnodeRef, pos: usize, data: &[u8]) -> Result<usize, Errno>; + fn readdir( + &mut self, + node: VnodeRef, + pos: usize, + data: &mut [DirectoryEntry], + ) -> Result<usize, Errno>; + /// Retrieves file status fn stat(&mut self, node: VnodeRef, stat: &mut Stat) -> Result<(), Errno>; @@ -97,6 +104,8 @@ impl Vnode { /// be seeked to arbitrary offsets pub const SEEKABLE: u32 = 1 << 0; + pub const CACHE_READDIR: u32 = 1 << 1; + /// 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 { @@ -127,6 +136,11 @@ impl Vnode { self.props.borrow_mut() } + /// Returns a borrowed reference to cached file properties + pub fn props(&self) -> Ref<VnodeProps> { + self.props.borrow() + } + /// Sets an associated [VnodeImpl] for the [Vnode] pub fn set_data(&self, data: Box<dyn VnodeImpl>) { *self.data.borrow_mut() = Some(data); @@ -163,6 +177,11 @@ impl Vnode { self.kind } + #[inline(always)] + pub const fn flags(&self) -> u32 { + self.flags + } + // Tree operations /// Attaches `child` vnode to `self` in in-memory tree. NOTE: does not @@ -235,6 +254,29 @@ impl Vnode { .cloned() } + pub(crate) fn for_each_entry<F: FnMut(usize, &VnodeRef)>( + &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<VnodeRef, Errno> { @@ -308,35 +350,55 @@ impl Vnode { /// Opens a vnode for access pub fn open(self: &VnodeRef, flags: OpenFlags) -> Result<FileRef, Errno> { - 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) + } } } diff --git a/kernel/src/syscall/arg.rs b/kernel/src/syscall/arg.rs index 937ec98..c64075d 100644 --- a/kernel/src/syscall/arg.rs +++ b/kernel/src/syscall/arg.rs @@ -62,6 +62,19 @@ pub fn struct_mut<'a, T>(base: usize) -> Result<&'a mut T, Errno> { Ok(unsafe { &mut *(bytes.as_mut_ptr() as *mut T) }) } +pub fn struct_buf_mut<'a, T>(base: usize, count: usize) -> Result<&'a mut [T], Errno> { + let layout = Layout::array::<T>(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) }) +} + pub fn option_struct_ref<'a, T>(base: usize) -> Result<Option<&'a T>, Errno> { if base == 0 { Ok(None) diff --git a/kernel/src/syscall/mod.rs b/kernel/src/syscall/mod.rs index c3b8f45..1896f48 100644 --- a/kernel/src/syscall/mod.rs +++ b/kernel/src/syscall/mod.rs @@ -2,8 +2,8 @@ use crate::arch::{machine, platform::exception::ExceptionFrame}; use crate::debug::Level; -use crate::proc::{self, elf, wait, Process, ProcessIo, Thread}; use crate::dev::timer::TimestampSource; +use crate::proc::{self, elf, wait, Process, ProcessIo, Thread}; use core::mem::size_of; use core::ops::DerefMut; use core::time::Duration; @@ -13,7 +13,9 @@ use libsys::{ ioctl::IoctlCmd, proc::{ExitCode, Pid}, signal::{Signal, SignalDestination}, - stat::{FdSet, AccessMode, FileDescriptor, FileMode, OpenFlags, Stat, AT_EMPTY_PATH}, + stat::{ + AccessMode, DirectoryEntry, FdSet, FileDescriptor, FileMode, OpenFlags, Stat, AT_EMPTY_PATH, + }, traits::{Read, Write}, }; use vfs::VnodeRef; @@ -62,7 +64,7 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> { let buf = arg::buf_mut(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); @@ -70,7 +72,7 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> { 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])?; @@ -88,7 +90,7 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> { let file = io.ioctx().open(at, path, mode, opts)?; Ok(u32::from(io.place_file(file)?) as usize) - }, + } SystemCall::Close => { let proc = Process::current(); let mut io = proc.io.lock(); @@ -96,7 +98,7 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> { io.close_file(fd)?; Ok(0) - }, + } SystemCall::FileStatus => { let at_fd = FileDescriptor::from_i32(args[0] as i32)?; let filename = arg::str_ref(args[1], args[2])?; @@ -107,7 +109,7 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> { let mut io = proc.io.lock(); find_at_node(&mut io, at_fd, filename, flags & AT_EMPTY_PATH != 0)?.stat(buf)?; Ok(0) - }, + } SystemCall::Ioctl => { let fd = FileDescriptor::from(args[0] as u32); let cmd = IoctlCmd::try_from(args[1] as u32)?; @@ -117,7 +119,7 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> { let node = io.file(fd)?.borrow().node().ok_or(Errno::InvalidFile)?; node.ioctl(cmd, args[2], args[3]) - }, + } SystemCall::Select => { let rfds = arg::option_struct_mut::<FdSet>(args[0])?; let wfds = arg::option_struct_mut::<FdSet>(args[1])?; @@ -128,7 +130,7 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> { }; 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])?; @@ -138,9 +140,18 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> { 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)?; + 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::<DirectoryEntry>(args[1], args[2])?; + + io.file(fd)?.borrow_mut().readdir(buf) + } // Process SystemCall::Clone => { @@ -151,7 +162,7 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> { Process::current() .new_user_thread(entry, stack, arg) .map(|e| e as usize) - }, + } SystemCall::Exec => { let node = { let proc = Process::current(); @@ -165,7 +176,7 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> { let file = node.open(OpenFlags::O_RDONLY)?; Process::execve(move |space| elf::load_elf(space, file), 0).unwrap(); panic!(); - }, + } SystemCall::Exit => { let status = ExitCode::from(args[0] as i32); let flags = args[1]; @@ -177,7 +188,7 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> { } unreachable!(); - }, + } SystemCall::WaitPid => { // TODO special "pid" values let pid = unsafe { Pid::from_raw(args[0] as u32) }; @@ -190,17 +201,15 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> { } e => e.map(|e| i32::from(e) as usize), } - }, + } SystemCall::WaitTid => { let tid = args[0] as u32; match Thread::waittid(tid) { - Ok(_) => { - Ok(0) - }, + Ok(_) => Ok(0), _ => todo!(), } - }, + } SystemCall::GetPid => Ok(Process::current().id().value() as usize), SystemCall::GetTid => Ok(Thread::current().id() as usize), SystemCall::Sleep => { @@ -214,15 +223,15 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> { } } res.map(|_| 0) - }, + } 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)?; @@ -235,11 +244,11 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> { _ => todo!(), }; Ok(0) - }, + } SystemCall::Yield => { proc::switch(); Ok(0) - }, + } SystemCall::GetSid => { // TODO handle kernel processes here? let pid = args[0] as u32; @@ -250,13 +259,13 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> { 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) + return Err(Errno::PermissionDenied); } proc }; Ok(proc.sid().value() as usize) - }, + } SystemCall::GetPgid => { // TODO handle kernel processes here? let pid = args[0] as u32; @@ -269,10 +278,8 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> { }; Ok(proc.pgid().value() as usize) - }, - SystemCall::GetPpid => { - Ok(Process::current().ppid().unwrap().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(); @@ -282,17 +289,13 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> { } todo!(); - }, + } 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!() - }; + let proc = if pid == 0 { current } else { todo!() }; if pgid == 0 { proc.set_pgid(proc.id()); @@ -301,13 +304,13 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> { } Ok(proc.pgid().value() as usize) - }, + } // System SystemCall::GetCpuTime => { let time = machine::local_timer().timestamp()?; Ok(time.as_nanos() as usize) - }, + } // Debugging SystemCall::DebugTrace => { @@ -316,9 +319,9 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> { print!(Level::Debug, "{}", buf); println!(Level::Debug, ""); Ok(args[1]) - }, + } // Handled elsewhere - SystemCall::Fork => unreachable!() + SystemCall::Fork => unreachable!(), } } diff --git a/libsys/src/abi.rs b/libsys/src/abi.rs index fad27ff..7fb3eb4 100644 --- a/libsys/src/abi.rs +++ b/libsys/src/abi.rs @@ -12,6 +12,7 @@ pub enum SystemCall { Ioctl = 6, Select = 7, Access = 8, + ReadDirectory = 9, // Process manipulation Fork = 32, Clone = 33, diff --git a/libsys/src/calls.rs b/libsys/src/calls.rs index 98fdb53..aae3c28 100644 --- a/libsys/src/calls.rs +++ b/libsys/src/calls.rs @@ -4,7 +4,7 @@ use crate::{ ioctl::IoctlCmd, proc::{ExitCode, Pid}, signal::{Signal, SignalDestination}, - stat::{AccessMode, FdSet, FileDescriptor, FileMode, OpenFlags, Stat}, + stat::{AccessMode, DirectoryEntry, FdSet, FileDescriptor, FileMode, OpenFlags, Stat}, }; use core::time::Duration; @@ -366,6 +366,20 @@ pub fn sys_getpgid(pid: Pid) -> Result<Pid, Errno> { #[inline(always)] pub fn sys_setpgid(pid: Pid, pgid: Pid) -> Result<Pid, Errno> { - Errno::from_syscall(unsafe { syscall!(SystemCall::SetPgid, argn!(pid.value()), argn!(pgid.value())) }).map(|e| unsafe { Pid::from_raw(e as u32) }) + 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<usize, Errno> { + Errno::from_syscall(unsafe { + syscall!( + SystemCall::ReadDirectory, + argn!(u32::from(fd)), + argp!(buf.as_mut_ptr()), + argn!(buf.len()) + ) + }) +} diff --git a/libsys/src/stat.rs b/libsys/src/stat.rs index a59be78..7ba0ecb 100644 --- a/libsys/src/stat.rs +++ b/libsys/src/stat.rs @@ -1,5 +1,5 @@ -use core::fmt; use crate::error::Errno; +use core::fmt; const AT_FDCWD: i32 = -2; pub const AT_EMPTY_PATH: u32 = 1 << 16; @@ -14,11 +14,16 @@ bitflags! { const O_CREAT = 1 << 4; const O_EXEC = 1 << 5; const O_CLOEXEC = 1 << 6; + const O_DIRECTORY = 1 << 7; } } bitflags! { pub struct FileMode: u32 { + const FILE_TYPE = 0xF << 12; + const S_IFREG = 0x8 << 12; + const S_IFDIR = 0x4 << 12; + const USER_READ = 1 << 8; const USER_WRITE = 1 << 7; const USER_EXEC = 1 << 6; @@ -42,26 +47,57 @@ bitflags! { #[derive(Clone, Default)] pub struct FdSet { - bits: [u64; 2] + 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 + set: &'a FdSet, } #[derive(Clone, Copy, Debug, Default)] #[repr(C)] pub struct Stat { - pub mode: u32, + 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] } @@ -93,10 +129,7 @@ impl FdSet { } pub fn iter(&self) -> impl Iterator<Item = FileDescriptor> + '_ { - FdSetIter { - idx: 0, - set: self - } + FdSetIter { idx: 0, set: self } } } @@ -131,13 +164,51 @@ impl fmt::Debug for FdSet { impl FileMode { /// Returns default permission set for directories - pub const fn default_dir() -> Self { - unsafe { Self::from_bits_unchecked(0o755) } + pub fn default_dir() -> Self { + unsafe { Self::from_bits_unchecked(0o755) | Self::S_IFDIR } } /// Returns default permission set for regular files - pub const fn default_reg() -> Self { - unsafe { Self::from_bits_unchecked(0o644) } + pub fn default_reg() -> Self { + unsafe { Self::from_bits_unchecked(0o644) | Self::S_IFREG } + } +} + +fn choose<T>(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_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', '-'), + ); + Ok(()) } } diff --git a/user/Cargo.toml b/user/Cargo.toml index 8cb69ab..91af3a7 100644 --- a/user/Cargo.toml +++ b/user/Cargo.toml @@ -17,6 +17,10 @@ path = "src/shell/main.rs" name = "fuzzy" path = "src/fuzzy/main.rs" +[[bin]] +name = "ls" +path = "src/ls/main.rs" + [dependencies] libusr = { path = "../libusr" } lazy_static = { version = "*", features = ["spin_no_std"] } diff --git a/user/src/ls/main.rs b/user/src/ls/main.rs new file mode 100644 index 0000000..91d8ebb --- /dev/null +++ b/user/src/ls/main.rs @@ -0,0 +1,45 @@ +#![no_std] +#![no_main] + +#[macro_use] +extern crate libusr; +#[macro_use] +extern crate alloc; + +use libusr::sys::{sys_readdir, sys_openat, sys_close, sys_fstatat, stat::{FileMode, OpenFlags, DirectoryEntry, Stat}}; +use alloc::{string::String, borrow::ToOwned}; + +#[no_mangle] +fn main() -> i32 { + let mut buffer = [DirectoryEntry::empty(); 16]; + let mut stat = Stat::default(); + let mut data = vec![]; + + let fd = sys_openat(None, "/", FileMode::default_dir(), OpenFlags::O_DIRECTORY | OpenFlags::O_RDONLY).unwrap(); + + loop { + let count = sys_readdir(fd, &mut buffer).unwrap(); + 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).unwrap(); + + + 0 +}