diff --git a/doc/filesystem.md b/doc/filesystem.md index dd6d828e8..6914b3117 100644 --- a/doc/filesystem.md +++ b/doc/filesystem.md @@ -5,7 +5,7 @@ A hard drive is separated in blocks of 512 bytes, grouped into 4 areas: +------------+ - | Boot | (2048 blocks) + | Boot | (4096 blocks) +------------+ | Superblock | (2 blocks) +------------+ @@ -103,16 +103,17 @@ Structure: A directory entry represents a file or a directory contained inside a directory. Each entry use a variable number of bytes that must fit inside the data of one block. Those bytes represent the kind of entry (file or dir), the -address of the first block, the filesize (max 4GB), the length of the filename, -and the filename (max 255 chars) of the entry. +address of the first block, the filesize (max 4GB), the last modified time in +seconds since Unix Epoch, the length of the filename, and the filename (max +255 chars) of the entry. Structure: - 0 1 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 m - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // +-+ - |k| addr | size |n| name buffer | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // +-+ + 0 1 2 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 m + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // +-+ + |k| addr | size | time |n| name buffer | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // +-+ k = kind of entry n = length of name buffer diff --git a/run/moros-fuse.py b/run/moros-fuse.py new file mode 100644 index 000000000..6ad1c23f0 --- /dev/null +++ b/run/moros-fuse.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python + +from errno import ENOENT +from fuse import FUSE, FuseOSError, Operations, LoggingMixIn +from stat import S_IFDIR, S_IFREG + +class MorosFuse(Operations): + chmod = None + chown = None + create = None + mkdir = None + readlink = None + rename = None + rmdir = None + symlink = None + truncate = None + unlink = None + utimens = None + write = None + + def __init__(self, path): + self.image = open(path, "rb") + self.image_offset = 4096 + self.block_size = 512 + addr = self.image_offset * self.block_size + self.image.seek(addr) + block = self.image.read(self.block_size) + + def destroy(self, path): + self.image.close() + return + + def getattr(self, path, fh=None): + (kind, addr, size, time, name) = self.__scan(path) + if addr == 0: + raise FuseOSError(ENOENT) + mode = S_IFDIR | 0o755 if kind == 0 else S_IFREG | 0o644 + return { "st_atime": 0, "st_mtime": time, "st_uid": 0, "st_gid": 0, "st_mode": mode, "st_size": size } + + def read(self, path, size, offset, fh): + (kind, next_block_addr, size, time, name) = self.__scan(path) + res = b"" + while next_block_addr != 0: + self.image.seek(next_block_addr) + next_block_addr = int.from_bytes(self.image.read(4), "big") * self.block_size + if offset < self.block_size - 4: + buf = self.image.read(min(self.block_size - 4, size)) + res = b"".join([res, buf[offset:]]) + offset = 0 + else: + offset -= self.block_size - 4 + size -= self.block_size - 4 + return res + + def readdir(self, path, fh): + files = [".", ".."] + (_, next_block_addr, _, _, _) = self.__scan(path) + while next_block_addr != 0: + self.image.seek(next_block_addr) + next_block_addr = int.from_bytes(self.image.read(4), "big") + offset = 4 + while offset < self.block_size: + kind = int.from_bytes(self.image.read(1), "big") + addr = int.from_bytes(self.image.read(4), "big") * self.block_size + if addr == 0: + break + size = int.from_bytes(self.image.read(4), "big") + time = int.from_bytes(self.image.read(8), "big") + n = int.from_bytes(self.image.read(1), "big") + name = self.image.read(n).decode("utf-8") + offset += 1 + 4 + 4 + 8 + 1 + n + files.append(name) + return files + + def __scan(self, path): + dirs = path[1:].split("/") + d = dirs.pop(0) + next_block_addr = (self.image_offset + 2 + self.block_size) * self.block_size + if d == "": + return (0, next_block_addr, 0, 0, d) + while next_block_addr != 0: + self.image.seek(next_block_addr) + next_block_addr = int.from_bytes(self.image.read(4), "big") + offset = 4 + while offset < self.block_size: + kind = int.from_bytes(self.image.read(1), "big") + addr = int.from_bytes(self.image.read(4), "big") * self.block_size + if addr == 0: + break + size = int.from_bytes(self.image.read(4), "big") + time = int.from_bytes(self.image.read(8), "big") + n = int.from_bytes(self.image.read(1), "big") + name = self.image.read(n).decode("utf-8") + offset += 1 + 4 + 4 + 1 + n + if name == d: + if len(dirs) == 0: + return (kind, addr, size, time, name) + else: + next_block_addr = addr + d = dirs.pop(0) + break + return (0, 0, 0, 0, "") + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('image') + parser.add_argument('mount') + args = parser.parse_args() + fuse = FUSE(MorosFuse(args.image), args.mount, ro=True, foreground=True, allow_other=True) diff --git a/run/moros-fuse.sh b/run/moros-fuse.sh new file mode 100644 index 000000000..fbb7e4eb1 --- /dev/null +++ b/run/moros-fuse.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +img="disk.img" +path="/tmp/moros" + +# pip install fusepy +mkdir -p $path +echo "Mounting $img in $path" +python run/moros-fuse.py $img $path diff --git a/src/api/console.rs b/src/api/console.rs index 3227f1c72..20bfeb9da 100644 --- a/src/api/console.rs +++ b/src/api/console.rs @@ -1,6 +1,7 @@ use crate::sys; use core::fmt; +#[derive(Clone, Copy)] pub struct Style { foreground: Option, background: Option, diff --git a/src/sys/fs.rs b/src/sys/fs.rs index eec22e1b4..56192f65a 100644 --- a/src/sys/fs.rs +++ b/src/sys/fs.rs @@ -59,6 +59,7 @@ pub struct File { name: String, addr: u32, size: u32, + time: u64, dir: Dir, // TODO: Replace with `parent: Some(Dir)` and also add it to `Dir` offset: u32, } @@ -192,7 +193,7 @@ impl File { block.write(); } self.size = self.offset; - self.dir.update_entry_size(&self.name, self.size); + self.dir.update_entry(&self.name, self.size); Ok(bytes) } @@ -371,13 +372,14 @@ pub struct DirEntry { kind: FileType, addr: u32, size: u32, + time: u64, name: String, } impl DirEntry { - pub fn new(dir: Dir, kind: FileType, addr: u32, size: u32, name: &str) -> Self { + pub fn new(dir: Dir, kind: FileType, addr: u32, size: u32, time: u64, name: &str) -> Self { let name = String::from(name); - Self { dir, kind, addr, size, name } + Self { dir, kind, addr, size, time, name } } pub fn is_dir(&self) -> bool { @@ -392,6 +394,10 @@ impl DirEntry { self.size } + pub fn time(&self) -> u64 { + self.time + } + pub fn name(&self) -> String { self.name.clone() } @@ -407,13 +413,14 @@ impl DirEntry { name: self.name.clone(), addr: self.addr, size: self.size, + time: self.time, dir: self.dir, offset: 0, } } pub fn len(&self) -> usize { - 1 + 4 + 4 + 1 + self.name.len() + 1 + 4 + 4 + 8 + 1 + self.name.len() } } @@ -509,28 +516,37 @@ impl Dir { let entry_kind = kind; let entry_size = 0; + let entry_time = sys::clock::realtime() as u64; let entry_addr = new_block.addr(); let entry_name = name.as_bytes(); let n = entry_name.len(); let i = read_dir.data_offset; let data = read_dir.block.data_mut(); - data[i + 0] = entry_kind as u8; - data[i + 1] = entry_addr.get_bits(24..32) as u8; - data[i + 2] = entry_addr.get_bits(16..24) as u8; - data[i + 3] = entry_addr.get_bits(8..16) as u8; - data[i + 4] = entry_addr.get_bits(0..8) as u8; - data[i + 5] = entry_size.get_bits(24..32) as u8; - data[i + 6] = entry_size.get_bits(16..24) as u8; - data[i + 7] = entry_size.get_bits(8..16) as u8; - data[i + 8] = entry_size.get_bits(0..8) as u8; - data[i + 9] = n as u8; + data[i + 0] = entry_kind as u8; + data[i + 1] = entry_addr.get_bits(24..32) as u8; + data[i + 2] = entry_addr.get_bits(16..24) as u8; + data[i + 3] = entry_addr.get_bits(8..16) as u8; + data[i + 4] = entry_addr.get_bits(0..8) as u8; + data[i + 5] = entry_size.get_bits(24..32) as u8; + data[i + 6] = entry_size.get_bits(16..24) as u8; + data[i + 7] = entry_size.get_bits(8..16) as u8; + data[i + 8] = entry_size.get_bits(0..8) as u8; + data[i + 9] = entry_time.get_bits(56..64) as u8; + data[i + 10] = entry_time.get_bits(48..56) as u8; + data[i + 11] = entry_time.get_bits(40..48) as u8; + data[i + 12] = entry_time.get_bits(32..40) as u8; + data[i + 13] = entry_time.get_bits(24..32) as u8; + data[i + 14] = entry_time.get_bits(16..24) as u8; + data[i + 15] = entry_time.get_bits(8..16) as u8; + data[i + 16] = entry_time.get_bits(0..8) as u8; + data[i + 17] = n as u8; for j in 0..n { - data[i + 10 + j] = entry_name[j]; + data[i + 18 + j] = entry_name[j]; } read_dir.block.write(); - Some(DirEntry::new(*self, kind, entry_addr, entry_size, name)) + Some(DirEntry::new(*self, kind, entry_addr, entry_size, entry_time, name)) } // Deleting an entry is done by setting the entry address to 0 @@ -564,16 +580,25 @@ impl Dir { Err(()) } - fn update_entry_size(&mut self, name: &str, size: u32) { + fn update_entry(&mut self, name: &str, size: u32) { let mut read_dir = self.read(); for entry in &mut read_dir { if entry.name == name { + let time = sys::clock::realtime() as u64; let data = read_dir.block.data_mut(); let i = read_dir.data_offset - entry.len(); - data[i + 5] = size.get_bits(24..32) as u8; - data[i + 6] = size.get_bits(16..24) as u8; - data[i + 7] = size.get_bits(8..16) as u8; - data[i + 8] = size.get_bits(0..8) as u8; + data[i + 5] = size.get_bits(24..32) as u8; + data[i + 6] = size.get_bits(16..24) as u8; + data[i + 7] = size.get_bits(8..16) as u8; + data[i + 8] = size.get_bits(0..8) as u8; + data[i + 9] = time.get_bits(56..64) as u8; + data[i + 10] = time.get_bits(48..56) as u8; + data[i + 11] = time.get_bits(40..48) as u8; + data[i + 12] = time.get_bits(32..40) as u8; + data[i + 13] = time.get_bits(24..32) as u8; + data[i + 14] = time.get_bits(16..24) as u8; + data[i + 15] = time.get_bits(8..16) as u8; + data[i + 16] = time.get_bits(0..8) as u8; read_dir.block.write(); break; } @@ -624,15 +649,26 @@ impl Iterator for ReadDir { 1 => FileType::File, _ => break, }; - let entry_addr = (data[i + 1] as u32) << 24 - | (data[i + 2] as u32) << 16 - | (data[i + 3] as u32) << 8 - | (data[i + 4] as u32); - let entry_size = (data[i + 5] as u32) << 24 - | (data[i + 6] as u32) << 16 - | (data[i + 7] as u32) << 8 - | (data[i + 8] as u32); - i += 9; + + let entry_addr = (data[i + 1] as u32) << 24 + | (data[i + 2] as u32) << 16 + | (data[i + 3] as u32) << 8 + | (data[i + 4] as u32); + + let entry_size = (data[i + 5] as u32) << 24 + | (data[i + 6] as u32) << 16 + | (data[i + 7] as u32) << 8 + | (data[i + 8] as u32); + + let entry_time = (data[i + 9] as u64) << 56 + | (data[i + 10] as u64) << 48 + | (data[i + 11] as u64) << 40 + | (data[i + 12] as u64) << 32 + | (data[i + 13] as u64) << 24 + | (data[i + 14] as u64) << 16 + | (data[i + 15] as u64) << 8 + | (data[i + 16] as u64); + i += 17; let mut n = data[i]; if n == 0 || n as usize >= data.len() - i { @@ -658,7 +694,7 @@ impl Iterator for ReadDir { continue; } - return Some(DirEntry::new(self.dir, entry_kind, entry_addr, entry_size, &entry_name)); + return Some(DirEntry::new(self.dir, entry_kind, entry_addr, entry_size, entry_time, &entry_name)); } match self.block.next() { diff --git a/src/usr/list.rs b/src/usr/list.rs index 7be3ade2b..38eadcc55 100644 --- a/src/usr/list.rs +++ b/src/usr/list.rs @@ -1,31 +1,70 @@ use crate::{sys, usr}; +use crate::api::console::Style; +use alloc::string::ToString; use alloc::vec::Vec; +use time::OffsetDateTime; pub fn main(args: &[&str]) -> usr::shell::ExitCode { - let current_dir = sys::process::dir(); - let mut pathname = if args.len() == 2 && !args[1].is_empty() { - args[1] - } else { - ¤t_dir - }; + let mut path: &str = &sys::process::dir(); + let mut sort = "name"; + + let mut i = 1; + let n = args.len(); + while i < n { + match args[i] { + "--sort" => { + if i + 1 < n { + sort = args[i + 1]; + i += 1; + } else { + println!("Missing sort key"); + return usr::shell::ExitCode::CommandError; + } + }, + "-t" => sort = "time", + "-s" => sort = "size", + "-n" => sort = "name", + _ => path = args[i], + } + i += 1; + } // The commands `list /usr/alice/` and `list /usr/alice` are equivalent, // but `list /` should not be modified. - if pathname.len() > 1 { - pathname = pathname.trim_end_matches('/'); + if path.len() > 1 { + path = path.trim_end_matches('/'); } - if let Some(dir) = sys::fs::Dir::open(pathname) { + if let Some(dir) = sys::fs::Dir::open(path) { let mut files: Vec<_> = dir.read().collect(); - files.sort_by_key(|f| f.name()); + match sort { + "name" => files.sort_by_key(|f| f.name()), + "size" => files.sort_by_key(|f| f.size()), + "time" => files.sort_by_key(|f| f.time()), + _ => { + println!("Invalid sort key '{}'", sort); + return usr::shell::ExitCode::CommandError; + } + } + + let mut max_size = 0; + for file in &files { + max_size = core::cmp::max(max_size, file.size()); + } + let width = max_size.to_string().len(); + + let csi_color = Style::color("Blue"); + let csi_reset = Style::reset(); for file in files { - println!("{}", file.name()); + let date = OffsetDateTime::from_unix_timestamp(file.time() as i64); + let color = if file.is_dir() { csi_color } else { csi_reset }; + println!("{:width$} {} {}{}{}", file.size(), date.format("%F %H:%M:%S"), color, file.name(), csi_reset, width = width); } usr::shell::ExitCode::CommandSuccessful } else { - println!("Dir not found '{}'", pathname); + println!("Dir not found '{}'", path); usr::shell::ExitCode::CommandError } }