Add time to dir entry (#215)

* Add creation time to dir entry

* Update and publish fuse script

* Change boot offset in doc

* Change creation time into last modified time

* Add sort parameter to list command

* Add error for missing sort key

* Add shortcut params for sort
This commit is contained in:
Vincent Ollivier 2021-07-26 22:35:32 +02:00 committed by GitHub
parent 1575b40f9c
commit bbcfc66e18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 247 additions and 51 deletions

View File

@ -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

110
run/moros-fuse.py Normal file
View File

@ -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)

9
run/moros-fuse.sh Normal file
View File

@ -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

View File

@ -1,6 +1,7 @@
use crate::sys;
use core::fmt;
#[derive(Clone, Copy)]
pub struct Style {
foreground: Option<usize>,
background: Option<usize>,

View File

@ -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() {

View File

@ -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 {
&current_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
}
}