From 731b97262bb2c276fed034e01d05690cb5d70000 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 29 Dec 2021 16:11:54 -0700 Subject: [PATCH] RedoxFS 0.5.0 Significant re-design to use copy-on-write and hashes for all data --- Cargo.lock | 284 +++++++++- Cargo.toml | 38 +- DESIGN.md | 37 -- src/allocator.rs | 271 ++++++++++ src/archive.rs | 130 +++-- src/bin/ar.rs | 16 +- src/bin/mkfs.rs | 77 ++- src/bin/mount.rs | 185 ++++--- src/block.rs | 207 +++++++ src/dir.rs | 112 ++++ src/disk/cache.rs | 10 +- src/disk/file.rs | 12 +- src/disk/mod.rs | 15 +- src/disk/sparse.rs | 15 +- src/filesystem.rs | 768 ++++++++------------------ src/header.rs | 195 +++++-- src/key.rs | 92 ++++ src/lib.rs | 33 +- src/mount/fuse.rs | 403 +++++++------- src/mount/redox/mod.rs | 8 +- src/mount/redox/resource.rs | 414 +++++++------- src/mount/redox/scheme.rs | 503 +++++++++-------- src/node.rs | 321 +++++++---- src/tests.rs | 10 +- src/transaction.rs | 1015 +++++++++++++++++++++++++++++++++++ src/tree.rs | 161 ++++++ test.sh | 59 ++ 27 files changed, 3813 insertions(+), 1578 deletions(-) delete mode 100644 DESIGN.md create mode 100644 src/allocator.rs create mode 100644 src/block.rs create mode 100644 src/dir.rs create mode 100644 src/key.rs create mode 100644 src/transaction.rs create mode 100644 src/tree.rs create mode 100755 test.sh diff --git a/Cargo.lock b/Cargo.lock index f792778..92d46e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,11 +2,77 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "argon2" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25df3c03f1040d0069fcd3907e24e36d59f9b6fa07ba49be0eb25a794f036ba7" +dependencies = [ + "base64ct", + "blake2", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "base64ct" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b4d9b1225d28d360ec6a231d65af1fd99a2a095154c8040689617290569c5c" + [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "blake2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] [[package]] name = "cfg-if" @@ -14,6 +80,58 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log 0.4.14", + "regex", + "termcolor", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -33,11 +151,47 @@ dependencies = [ "time", ] +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "libc" -version = "0.2.97" +version = "0.2.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" +checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" [[package]] name = "log" @@ -57,11 +211,29 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "pkg-config" -version = "0.3.19" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" +checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" [[package]] name = "rand" @@ -112,24 +284,97 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ "bitflags", ] +[[package]] +name = "redox_termios" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" +dependencies = [ + "redox_syscall", +] + [[package]] name = "redoxfs" -version = "0.4.4" +version = "0.5.0" dependencies = [ + "aes", + "argon2", + "base64ct", + "env_logger", "fuse", + "getrandom", "libc", + "log 0.4.14", "redox_syscall", + "seahash", + "simple_endian", + "termion", "time", "uuid", ] +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "simple_endian" +version = "0.2.1" +source = "git+https://github.com/michalfita/simple-endian-rs.git?rev=7210f40881d16f7f2e3d8d40f6381fa222843caa#7210f40881d16f7f2e3d8d40f6381fa222843caa" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "termion" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" +dependencies = [ + "libc", + "numtoa", + "redox_syscall", + "redox_termios", +] + [[package]] name = "thread-scoped" version = "1.0.2" @@ -147,6 +392,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + [[package]] name = "uuid" version = "0.5.1" @@ -156,6 +407,12 @@ dependencies = [ "rand 0.3.23", ] +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -178,6 +435,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index e35f3e4..ff58b5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,10 +2,11 @@ name = "redoxfs" description = "The Redox Filesystem" repository = "https://gitlab.redox-os.org/redox-os/redoxfs" -version = "0.4.4" +version = "0.5.0" license-file = "LICENSE" readme = "README.md" authors = ["Jeremy Soller "] +edition = "2018" [lib] name = "redoxfs" @@ -30,17 +31,44 @@ doc = false required-features = ["std"] [dependencies] +aes = { version = "=0.7.5", default-features = false } +argon2 = { version = "0.3.4", default-features = false, features = ["alloc"] } +# newer versions require edition 2021 +base64ct = { version = "=1.1.1", default-features = false } +env_logger = { version = "0.9.0", optional = true } +getrandom = { version = "0.2.5", optional = true } +log = { version = "0.4.14", default-features = false } redox_syscall = "0.2.9" +seahash = { version = "4.1.0", default-features = false } +termion = { version = "1.5.6", optional = true } +uuid = { version = "0.5", default-features = false } -[dependencies.uuid] -version = "0.5" +[dependencies.simple_endian] +# https://github.com/rexlunae/simple-endian-rs/pull/5 +git = "https://github.com/michalfita/simple-endian-rs.git" +rev = "7210f40881d16f7f2e3d8d40f6381fa222843caa" default-features = false +features = [ + "bitwise", "comparisons", "format", "math_ops", "neg_ops", "shift_ops", + "both_endian", "float_impls", "integer_impls", "byte_impls" +] [features] default = ["std"] -std = ["fuse", "time", "uuid/v4"] +force-soft = [ + "aes/force-soft" +] +std = [ + "env_logger", + "fuse", + "getrandom", + "libc", + "termion", + "time", + "uuid/v4" +] [target.'cfg(not(target_os = "redox"))'.dependencies] fuse = { version = "0.3", optional = true } -libc = "0.2" +libc = { version = "0.2", optional = true } time = { version = "0.1", optional = true } diff --git a/DESIGN.md b/DESIGN.md deleted file mode 100644 index 99a9935..0000000 --- a/DESIGN.md +++ /dev/null @@ -1,37 +0,0 @@ -# RedoxFS Design Document - -## Structures - -### Header -The header is the entry point for the filesystem. When mounting a disk or image, it should be scanned for a block starting with the 8-byte signature, within the first megabyte: -```rust -"RedoxFS\0" -``` - -The header stores the filesystem version, disk identifier, disk size, root block pointer, and free block pointer. - -```rust -#[repr(packed)] -pub struct Header { - pub signature: [u8; 8], - pub version: u64, - pub uuid: [u8; 16], - pub size: u64, - pub root: u64, - pub free: u64, -} -``` - -The root and free block pointers point to a Node that identifies - -### Node - -```rust -#[repr(packed)] -pub struct Node { - pub name: [u8; 256], - pub mode: u64, - pub next: u64, - pub extents: [Extent; 15], -} -``` diff --git a/src/allocator.rs b/src/allocator.rs new file mode 100644 index 0000000..c3a3542 --- /dev/null +++ b/src/allocator.rs @@ -0,0 +1,271 @@ +use alloc::vec::Vec; +use core::{fmt, mem, ops, slice}; +use simple_endian::*; + +use crate::BlockPtr; + +pub const ALLOC_LIST_ENTRIES: usize = 255; + +#[derive(Clone, Default)] +pub struct Allocator { + levels: Vec>, +} + +impl Allocator { + pub fn levels(&self) -> &Vec> { + &self.levels + } + + pub fn free(&self) -> u64 { + let mut free = 0; + for level in 0..self.levels.len() { + let level_size = 1 << level; + free += self.levels[level].len() as u64 * level_size; + } + free + } + + pub fn allocate(&mut self) -> Option { + // First, find the lowest level with a free block + let mut addr_opt = None; + let mut level = 0; + while level < self.levels.len() { + if !self.levels[level].is_empty() { + addr_opt = self.levels[level].pop(); + break; + } + level += 1; + } + + // Next, if a free block was found, split it up until you have a usable level 0 block + let addr = addr_opt?; + while level > 0 { + level -= 1; + let level_size = 1 << level; + self.levels[level].push(addr + level_size); + } + + Some(addr) + } + + pub fn allocate_exact(&mut self, exact_addr: u64) -> Option { + let mut addr_opt = None; + + // Go from the highest to the lowest level + for level in (0..self.levels.len()).rev() { + let level_size = 1 << level; + + // Split higher block if found + if let Some(addr) = addr_opt.take() { + self.levels[level].push(addr); + self.levels[level].push(addr + level_size); + } + + // Look for matching block and remove it + for i in 0..self.levels[level].len() { + let start = self.levels[level][i]; + if start <= exact_addr { + let end = start + level_size; + if end > exact_addr { + self.levels[level].remove(i); + addr_opt = Some(start); + break; + } + } + } + } + + addr_opt + } + + pub fn deallocate(&mut self, mut addr: u64) { + // See if block matches with a sibling - if so, join them into a larger block, and populate + // this all the way to the top level + let mut level = 0; + loop { + while level >= self.levels.len() { + self.levels.push(Vec::new()); + } + + let level_size = 1 << level; + let next_size = level_size << 1; + + let mut found = false; + let mut i = 0; + while i < self.levels[level].len() { + let level_addr = self.levels[level][i]; + if addr % next_size == 0 && addr + level_size == level_addr { + self.levels[level].remove(i); + found = true; + break; + } else if level_addr % next_size == 0 && level_addr + level_size == addr { + self.levels[level].remove(i); + addr = level_addr; + found = true; + break; + } + i += 1; + } + + if !found { + self.levels[level].push(addr); + return; + } + + level += 1; + } + } +} + +#[repr(packed)] +pub struct AllocEntry { + addr: u64le, + count: i64le, +} + +impl AllocEntry { + pub fn new(addr: u64, count: i64) -> Self { + Self { + addr: addr.into(), + count: count.into(), + } + } + + pub fn addr(&self) -> u64 { + { self.addr }.to_native() + } + + pub fn count(&self) -> i64 { + { self.count }.to_native() + } + + pub fn is_null(&self) -> bool { + self.count() == 0 + } +} + +impl Clone for AllocEntry { + fn clone(&self) -> Self { + Self { + addr: self.addr, + count: self.count, + } + } +} + +impl Copy for AllocEntry {} + +impl Default for AllocEntry { + fn default() -> Self { + Self { + addr: 0.into(), + count: 0.into(), + } + } +} + +impl fmt::Debug for AllocEntry { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let addr = self.addr(); + let count = self.count(); + f.debug_struct("AllocEntry") + .field("addr", &addr) + .field("count", &count) + .finish() + } +} + +/// Alloc log node +#[repr(packed)] +pub struct AllocList { + pub prev: BlockPtr, + pub entries: [AllocEntry; ALLOC_LIST_ENTRIES], +} + +impl Default for AllocList { + fn default() -> Self { + Self { + prev: BlockPtr::default(), + entries: [AllocEntry::default(); ALLOC_LIST_ENTRIES], + } + } +} + +impl fmt::Debug for AllocList { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let prev = self.prev; + let entries: Vec<&AllocEntry> = self + .entries + .iter() + .filter(|entry| entry.count() > 0) + .collect(); + f.debug_struct("AllocList") + .field("prev", &prev) + .field("entries", &entries) + .finish() + } +} + +impl ops::Deref for AllocList { + type Target = [u8]; + fn deref(&self) -> &[u8] { + unsafe { + slice::from_raw_parts( + self as *const AllocList as *const u8, + mem::size_of::(), + ) as &[u8] + } + } +} + +impl ops::DerefMut for AllocList { + fn deref_mut(&mut self) -> &mut [u8] { + unsafe { + slice::from_raw_parts_mut( + self as *mut AllocList as *mut u8, + mem::size_of::(), + ) as &mut [u8] + } + } +} + +#[test] +fn alloc_node_size_test() { + assert_eq!(mem::size_of::(), crate::BLOCK_SIZE as usize); +} + +#[test] +fn allocator_test() { + let mut alloc = Allocator::default(); + + assert_eq!(alloc.allocate(), None); + + alloc.deallocate(1); + assert_eq!(alloc.allocate(), Some(1)); + assert_eq!(alloc.allocate(), None); + + for addr in 1023..2048 { + alloc.deallocate(addr); + } + + assert_eq!(alloc.levels.len(), 11); + for level in 0..alloc.levels.len() { + if level == 0 { + assert_eq!(alloc.levels[level], [1023]); + } else if level == 10 { + assert_eq!(alloc.levels[level], [1024]); + } else { + assert_eq!(alloc.levels[level], []); + } + } + + for addr in 1023..2048 { + assert_eq!(alloc.allocate(), Some(addr)); + } + assert_eq!(alloc.allocate(), None); + + assert_eq!(alloc.levels.len(), 11); + for level in 0..alloc.levels.len() { + assert_eq!(alloc.levels[level], []); + } +} diff --git a/src/archive.rs b/src/archive.rs index 1483e19..b8401c1 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -4,16 +4,16 @@ use std::os::unix::ffi::OsStrExt; use std::os::unix::fs::MetadataExt; use std::path::Path; -use crate::{Disk, Extent, FileSystem, Node, BLOCK_SIZE}; +use crate::{Disk, FileSystem, Node, Transaction, TreePtr, BLOCK_SIZE}; fn syscall_err(err: syscall::Error) -> io::Error { io::Error::from_raw_os_error(err.errno) } pub fn archive_at>( - fs: &mut FileSystem, + tx: &mut Transaction, parent_path: P, - parent_block: u64, + parent_ptr: TreePtr, ) -> io::Result<()> { for entry_res in fs::read_dir(parent_path)? { let entry = entry_res?; @@ -38,44 +38,60 @@ pub fn archive_at>( )); }; - let mode = mode_type | (metadata.mode() as u16 & Node::MODE_PERM); - let mut node = fs - .create_node( - mode, - &name, - parent_block, - metadata.ctime() as u64, - metadata.ctime_nsec() as u32, - ) - .map_err(syscall_err)?; - node.1.uid = metadata.uid(); - node.1.gid = metadata.gid(); - fs.write_at(node.0, &node.1).map_err(syscall_err)?; + let node_ptr; + { + let mode = mode_type | (metadata.mode() as u16 & Node::MODE_PERM); + let mut node = tx + .create_node( + parent_ptr, + &name, + mode, + metadata.ctime() as u64, + metadata.ctime_nsec() as u32, + ) + .map_err(syscall_err)?; + + node_ptr = node.ptr(); + + if node.data().uid() != metadata.uid() || node.data().gid() != metadata.gid() { + node.data_mut().set_uid(metadata.uid()); + node.data_mut().set_gid(metadata.gid()); + tx.sync_tree(node).map_err(syscall_err)?; + } + } let path = entry.path(); if file_type.is_dir() { - archive_at(fs, path, node.0)?; + archive_at(tx, path, node_ptr)?; } else if file_type.is_file() { let data = fs::read(path)?; - fs.write_node( - node.0, - 0, - &data, - metadata.mtime() as u64, - metadata.mtime_nsec() as u32, - ) - .map_err(syscall_err)?; + let count = tx + .write_node( + node_ptr, + 0, + &data, + metadata.mtime() as u64, + metadata.mtime_nsec() as u32, + ) + .map_err(syscall_err)?; + if count != data.len() { + panic!("file write count {} != {}", count, data.len()); + } } else if file_type.is_symlink() { let destination = fs::read_link(path)?; let data = destination.as_os_str().as_bytes(); - fs.write_node( - node.0, - 0, - &data, - metadata.mtime() as u64, - metadata.mtime_nsec() as u32, - ) - .map_err(syscall_err)?; + let count = tx + .write_node( + node_ptr, + 0, + data, + metadata.mtime() as u64, + metadata.mtime_nsec() as u32, + ) + .map_err(syscall_err)?; + if count != data.len() { + panic!("symlink write count {} != {}", count, data.len()); + } } else { return Err(io::Error::new( io::ErrorKind::Other, @@ -88,19 +104,43 @@ pub fn archive_at>( } pub fn archive>(fs: &mut FileSystem, parent_path: P) -> io::Result { - let root_block = fs.header.1.root; - archive_at(fs, parent_path, root_block)?; + let end_block = fs + .tx(|tx| { + // Archive_at root node + archive_at(tx, parent_path, TreePtr::root()) + .map_err(|err| syscall::Error::new(err.raw_os_error().unwrap()))?; + + // Squash alloc log + tx.sync(true)?; + + let mut end_block = tx.header.size() / BLOCK_SIZE; + /* TODO: Cut off any free blocks at the end of the filesystem + let mut end_changed = true; + while end_changed { + end_changed = false; + + let allocator = fs.allocator(); + let levels = allocator.levels(); + for level in 0..levels.len() { + let level_size = 1 << level; + for &block in levels[level].iter() { + if block < end_block && block + level_size >= end_block { + end_block = block; + end_changed = true; + } + } + } + } + */ - let free_block = fs.header.1.free; - let mut free = fs.node(free_block).map_err(syscall_err)?; - let end_block = free.1.extents[0].block; - let end_size = end_block * BLOCK_SIZE; - free.1.extents[0] = Extent::default(); - fs.write_at(free.0, &free.1).map_err(syscall_err)?; + // Update header + tx.header.size = (end_block * BLOCK_SIZE).into(); + tx.header_changed = true; + tx.sync(false)?; - fs.header.1.size = end_size; - let header = fs.header; - fs.write_at(header.0, &header.1).map_err(syscall_err)?; + Ok(end_block) + }) + .map_err(syscall_err)?; - Ok(header.0 * BLOCK_SIZE + end_size) + Ok((fs.block + end_block) * BLOCK_SIZE) } diff --git a/src/bin/ar.rs b/src/bin/ar.rs index a4ac009..07e3cdd 100644 --- a/src/bin/ar.rs +++ b/src/bin/ar.rs @@ -10,6 +10,8 @@ use redoxfs::{archive, DiskSparse, FileSystem}; use uuid::Uuid; fn main() { + env_logger::init(); + let mut args = env::args().skip(1); let disk_path = if let Some(path) = args.next() { @@ -30,7 +32,7 @@ fn main() { let bootloader_path_opt = args.next(); - let disk = match DiskSparse::create(&disk_path) { + let disk = match DiskSparse::create(&disk_path, 1024 * 1024 * 1024 /*TODO: make argument*/) { Ok(disk) => disk, Err(err) => { println!("redoxfs-ar: failed to open image {}: {}", disk_path, err); @@ -62,7 +64,13 @@ fn main() { }; let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - match FileSystem::create_reserved(disk, &bootloader, ctime.as_secs(), ctime.subsec_nanos()) { + match FileSystem::create_reserved( + disk, + None, + &bootloader, + ctime.as_secs(), + ctime.subsec_nanos(), + ) { Ok(mut fs) => { let size = match archive(&mut fs, &folder_path) { Ok(ok) => ok, @@ -80,12 +88,12 @@ fn main() { process::exit(1); } - let uuid = Uuid::from_bytes(&fs.header.1.uuid).unwrap(); + let uuid = Uuid::from_bytes(&fs.header.uuid()).unwrap(); println!( "redoxfs-ar: created filesystem on {}, reserved {} blocks, size {} MB, uuid {}", disk_path, fs.block, - fs.header.1.size / 1000 / 1000, + fs.header.size() / 1000 / 1000, uuid.hyphenated() ); } diff --git a/src/bin/mkfs.rs b/src/bin/mkfs.rs index e3b9690..0365f25 100644 --- a/src/bin/mkfs.rs +++ b/src/bin/mkfs.rs @@ -2,28 +2,47 @@ extern crate redoxfs; extern crate uuid; use std::io::Read; -use std::{env, fs, process, time}; +use std::{env, fs, io, process, time}; use redoxfs::{DiskFile, FileSystem}; +use termion::input::TermRead; use uuid::Uuid; +fn usage() -> ! { + eprintln!("redoxfs-mkfs [--encrypt] DISK [BOOTLOADER]"); + process::exit(1); +} + fn main() { - let mut args = env::args().skip(1); + env_logger::init(); + + let mut encrypt = false; + let mut disk_path_opt = None; + let mut bootloader_path_opt = None; + for arg in env::args().skip(1) { + if arg == "--encrypt" { + encrypt = true; + } else if disk_path_opt.is_none() { + disk_path_opt = Some(arg); + } else if bootloader_path_opt.is_none() { + bootloader_path_opt = Some(arg); + } else { + eprintln!("redoxfs-mkfs: too many arguments provided"); + usage(); + } + } - let disk_path = if let Some(path) = args.next() { + let disk_path = if let Some(path) = disk_path_opt { path } else { - println!("redoxfs-mkfs: no disk image provided"); - println!("redoxfs-mkfs DISK [BOOTLOADER]"); - process::exit(1); + eprintln!("redoxfs-mkfs: no disk image provided"); + usage(); }; - let bootloader_path_opt = args.next(); - let disk = match DiskFile::open(&disk_path) { Ok(disk) => disk, Err(err) => { - println!("redoxfs-mkfs: failed to open image {}: {}", disk_path, err); + eprintln!("redoxfs-mkfs: failed to open image {}: {}", disk_path, err); process::exit(1); } }; @@ -34,7 +53,7 @@ fn main() { Ok(mut file) => match file.read_to_end(&mut bootloader) { Ok(_) => (), Err(err) => { - println!( + eprintln!( "redoxfs-mkfs: failed to read bootloader {}: {}", bootloader_path, err ); @@ -42,7 +61,7 @@ fn main() { } }, Err(err) => { - println!( + eprintln!( "redoxfs-mkfs: failed to open bootloader {}: {}", bootloader_path, err ); @@ -51,22 +70,48 @@ fn main() { } }; + let password_opt = if encrypt { + eprint!("redoxfs-mkfs: password: "); + + let password = io::stdin() + .read_passwd(&mut io::stderr()) + .unwrap() + .unwrap_or(String::new()); + + eprintln!(); + + if password.is_empty() { + eprintln!("redoxfs-mkfs: empty password, giving up"); + process::exit(1); + } + + Some(password) + } else { + None + }; + let ctime = time::SystemTime::now() .duration_since(time::UNIX_EPOCH) .unwrap(); - match FileSystem::create_reserved(disk, &bootloader, ctime.as_secs(), ctime.subsec_nanos()) { + match FileSystem::create_reserved( + disk, + password_opt.as_ref().map(|x| x.as_bytes()), + &bootloader, + ctime.as_secs(), + ctime.subsec_nanos(), + ) { Ok(filesystem) => { - let uuid = Uuid::from_bytes(&filesystem.header.1.uuid).unwrap(); - println!( + let uuid = Uuid::from_bytes(&filesystem.header.uuid()).unwrap(); + eprintln!( "redoxfs-mkfs: created filesystem on {}, reserved {} blocks, size {} MB, uuid {}", disk_path, filesystem.block, - filesystem.header.1.size / 1000 / 1000, + filesystem.header.size() / 1000 / 1000, uuid.hyphenated() ); } Err(err) => { - println!( + eprintln!( "redoxfs-mkfs: failed to create filesystem on {}: {}", disk_path, err ); diff --git a/src/bin/mount.rs b/src/bin/mount.rs index 3212e6b..cd9d103 100644 --- a/src/bin/mount.rs +++ b/src/bin/mount.rs @@ -11,11 +11,12 @@ extern crate uuid; use std::env; use std::fs::File; -use std::io::{Read, Write}; +use std::io::{self, Read, Write}; use std::os::unix::io::{FromRawFd, RawFd}; use std::process; use redoxfs::{mount, DiskCache, DiskFile, FileSystem}; +use termion::input::TermRead; use uuid::Uuid; #[cfg(target_os = "redox")] @@ -42,9 +43,7 @@ fn setsig() { #[cfg(not(target_os = "redox"))] // on linux, this is implemented properly, so no need for this unscrupulous nonsense! -fn setsig() { - () -} +fn setsig() {} #[cfg(not(target_os = "redox"))] fn fork() -> isize { @@ -57,8 +56,11 @@ fn pipe(pipes: &mut [i32; 2]) -> isize { } #[cfg(not(target_os = "redox"))] -fn capability_mode() { - () +fn capability_mode() {} + +#[cfg(not(target_os = "redox"))] +fn bootloader_password() -> Option> { + None } #[cfg(target_os = "redox")] @@ -76,6 +78,37 @@ fn capability_mode() { syscall::setrens(0, 0).expect("redoxfs: failed to enter null namespace"); } +#[cfg(target_os = "redox")] +fn bootloader_password() -> Option> { + let addr_env = env::var_os("REDOXFS_PASSWORD_ADDR")?; + let size_env = env::var_os("REDOXFS_PASSWORD_SIZE")?; + + let addr = usize::from_str_radix( + addr_env.to_str().expect("REDOXFS_PASSWORD_ADDR not valid"), + 16, + ) + .expect("failed to parse REDOXFS_PASSWORD_ADDR"); + + let size = usize::from_str_radix( + size_env.to_str().expect("REDOXFS_PASSWORD_SIZE not valid"), + 16, + ) + .expect("failed to parse REDOXFS_PASSWORD_SIZE"); + + let mut password = Vec::with_capacity(size); + unsafe { + let password_map = syscall::physmap(addr, size, syscall::PhysmapFlags::empty()) + .expect("failed to map REDOXFS_PASSWORD"); + + for i in 0..size { + password.push(*((password_map + i) as *const u8)); + } + + let _ = syscall::physunmap(password_map); + } + Some(password) +} + fn usage() { println!("redoxfs [--uuid] [disk or uuid] [mountpoint] [block in hex]"); } @@ -85,35 +118,87 @@ enum DiskId { Uuid(Uuid), } -fn filesystem_by_path(path: &str, block_opt: Option) -> Option<(String, FileSystem>)> { +fn filesystem_by_path( + path: &str, + block_opt: Option, +) -> Option<(String, FileSystem>)> { println!("redoxfs: opening {}", path); - match DiskFile::open(&path).map(|image| DiskCache::new(image)) { - Ok(disk) => match redoxfs::FileSystem::open(disk, block_opt) { - Ok(filesystem) => { - println!( - "redoxfs: opened filesystem on {} with uuid {}", - path, - Uuid::from_bytes(&filesystem.header.1.uuid) - .unwrap() - .hyphenated() - ); + let attempts = 10; + for attempt in 0..=attempts { + let password_opt = if attempt > 0 { + eprint!("redoxfs: password: "); + + let password = io::stdin() + .read_passwd(&mut io::stderr()) + .unwrap() + .unwrap_or(String::new()); - return Some((path.to_string(), filesystem)); + eprintln!(); + + if password.is_empty() { + eprintln!("redoxfs: empty password, giving up"); + + // Password is empty, exit loop + break; } - Err(err) => println!("redoxfs: failed to open filesystem {}: {}", path, err), - }, - Err(err) => println!("redoxfs: failed to open image {}: {}", path, err), + + Some(password.into_bytes()) + } else { + bootloader_password() + }; + + match DiskFile::open(&path).map(|image| DiskCache::new(image)) { + Ok(disk) => match redoxfs::FileSystem::open( + disk, + password_opt.as_ref().map(|x| x.as_slice()), + block_opt, + true, + ) { + Ok(filesystem) => { + println!( + "redoxfs: opened filesystem on {} with uuid {}", + path, + Uuid::from_bytes(&filesystem.header.uuid()) + .unwrap() + .hyphenated() + ); + + return Some((path.to_string(), filesystem)); + } + Err(err) => match err.errno { + syscall::ENOKEY => { + if password_opt.is_some() { + println!("redoxfs: incorrect password ({}/{})", attempt, attempts); + } + } + _ => { + println!("redoxfs: failed to open filesystem {}: {}", path, err); + break; + } + }, + }, + Err(err) => { + println!("redoxfs: failed to open image {}: {}", path, err); + break; + } + } } None } #[cfg(not(target_os = "redox"))] -fn filesystem_by_uuid(_uuid: &Uuid, _block_opt: Option) -> Option<(String, FileSystem>)> { +fn filesystem_by_uuid( + _uuid: &Uuid, + _block_opt: Option, +) -> Option<(String, FileSystem>)> { None } #[cfg(target_os = "redox")] -fn filesystem_by_uuid(uuid: &Uuid, block_opt: Option) -> Option<(String, FileSystem>)> { +fn filesystem_by_uuid( + uuid: &Uuid, + block_opt: Option, +) -> Option<(String, FileSystem>)> { use std::fs; match fs::read_dir(":") { @@ -128,16 +213,21 @@ fn filesystem_by_uuid(uuid: &Uuid, block_opt: Option) -> Option<(String, Fi Ok(entries) => { for entry_res in entries { if let Ok(entry) = entry_res { - if let Ok(path) = entry.path().into_os_string().into_string() { + if let Ok(path) = + entry.path().into_os_string().into_string() + { println!("redoxfs: found path {}", path); - if let Some((path, filesystem)) = filesystem_by_path(&path, block_opt) { - if &filesystem.header.1.uuid == uuid.as_bytes() { + if let Some((path, filesystem)) = + filesystem_by_path(&path, block_opt) + { + if &filesystem.header.uuid() == uuid.as_bytes() + { println!( "redoxfs: filesystem on {} matches uuid {}", path, uuid.hyphenated() ); - return Some((path, filesystem)) + return Some((path, filesystem)); } else { println!( "redoxfs: filesystem on {} does not match uuid {}", @@ -171,12 +261,8 @@ fn daemon(disk_id: &DiskId, mountpoint: &str, block_opt: Option, mut write: setsig(); let filesystem_opt = match *disk_id { - DiskId::Path(ref path) => { - filesystem_by_path(path, block_opt) - } - DiskId::Uuid(ref uuid) => { - filesystem_by_uuid(uuid, block_opt) - } + DiskId::Path(ref path) => filesystem_by_path(path, block_opt), + DiskId::Uuid(ref uuid) => filesystem_by_uuid(uuid, block_opt), }; if let Some((path, filesystem)) = filesystem_opt { @@ -215,24 +301,9 @@ fn daemon(disk_id: &DiskId, mountpoint: &str, block_opt: Option, mut write: process::exit(1); } -fn print_uuid(path: &str) { - match DiskFile::open(&path).map(|image| DiskCache::new(image)) { - Ok(disk) => match redoxfs::FileSystem::open(disk, None) { - Ok(filesystem) => { - println!( - "{}", - Uuid::from_bytes(&filesystem.header.1.uuid) - .unwrap() - .hyphenated() - ); - } - Err(err) => println!("redoxfs: failed to open filesystem {}: {}", path, err), - }, - Err(err) => println!("redoxfs: failed to open image {}: {}", path, err), - } -} - fn main() { + env_logger::init(); + let mut args = env::args().skip(1); let disk_id = match args.next() { @@ -255,18 +326,6 @@ fn main() { }; DiskId::Uuid(uuid) - } else if arg == "--get-uuid" { - match args.next() { - Some(arg) => { - print_uuid(&arg); - process::exit(1); - } - None => { - println!("redoxfs: no disk provided"); - usage(); - process::exit(1); - } - }; } else { DiskId::Path(arg) } @@ -313,7 +372,7 @@ fn main() { drop(write); let mut res = [0]; - read.read(&mut res).unwrap(); + read.read_exact(&mut res).unwrap(); process::exit(res[0] as i32); } else { diff --git a/src/block.rs b/src/block.rs new file mode 100644 index 0000000..bc1fabb --- /dev/null +++ b/src/block.rs @@ -0,0 +1,207 @@ +use core::{fmt, marker::PhantomData, mem, ops, slice}; +use simple_endian::*; + +use crate::BLOCK_SIZE; + +#[derive(Clone, Copy, Debug, Default)] +pub struct BlockData { + addr: u64, + data: T, +} + +impl BlockData { + pub fn new(addr: u64, data: T) -> Self { + Self { addr, data } + } + + pub fn addr(&self) -> u64 { + self.addr + } + + #[must_use = "don't forget to de-allocate old block address"] + pub fn swap_addr(&mut self, addr: u64) -> u64 { + let old = self.addr; + self.addr = addr; + old + } + + pub fn data(&self) -> &T { + &self.data + } + + pub fn data_mut(&mut self) -> &mut T { + &mut self.data + } + + pub fn into_data(self) -> T { + self.data + } +} + +impl> BlockData { + pub fn create_ptr(&self) -> BlockPtr { + BlockPtr { + addr: self.addr.into(), + hash: seahash::hash(self.data.deref()).into(), + phantom: PhantomData, + } + } +} + +#[repr(packed)] +pub struct BlockList { + pub ptrs: [BlockPtr; 256], +} + +impl BlockList { + pub fn is_empty(&self) -> bool { + for ptr in self.ptrs.iter() { + if !ptr.is_null() { + return false; + } + } + true + } +} + +impl Default for BlockList { + fn default() -> Self { + Self { + ptrs: [BlockPtr::default(); 256], + } + } +} + +impl ops::Deref for BlockList { + type Target = [u8]; + fn deref(&self) -> &[u8] { + unsafe { + slice::from_raw_parts( + self as *const BlockList as *const u8, + mem::size_of::>(), + ) as &[u8] + } + } +} + +impl ops::DerefMut for BlockList { + fn deref_mut(&mut self) -> &mut [u8] { + unsafe { + slice::from_raw_parts_mut( + self as *mut BlockList as *mut u8, + mem::size_of::>(), + ) as &mut [u8] + } + } +} + +#[repr(packed)] +pub struct BlockPtr { + addr: u64le, + hash: u64le, + phantom: PhantomData, +} + +impl BlockPtr { + pub fn addr(&self) -> u64 { + { self.addr }.to_native() + } + + pub fn hash(&self) -> u64 { + { self.hash }.to_native() + } + + pub fn is_null(&self) -> bool { + self.addr() == 0 + } + + /// Cast BlockPtr to another type + /// + /// # Safety + /// Unsafe because it can be used to transmute types + pub unsafe fn cast(self) -> BlockPtr { + BlockPtr { + addr: self.addr, + hash: self.hash, + phantom: PhantomData, + } + } + + #[must_use = "the returned pointer should usually be deallocated"] + pub fn clear(&mut self) -> BlockPtr { + let mut ptr = Self::default(); + mem::swap(self, &mut ptr); + ptr + } +} + +impl Clone for BlockPtr { + fn clone(&self) -> Self { + Self { + addr: self.addr, + hash: self.hash, + phantom: PhantomData, + } + } +} + +impl Copy for BlockPtr {} + +impl Default for BlockPtr { + fn default() -> Self { + Self { + addr: 0.into(), + hash: 0.into(), + phantom: PhantomData, + } + } +} + +impl fmt::Debug for BlockPtr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let addr = self.addr(); + let hash = self.hash(); + f.debug_struct("BlockPtr") + .field("addr", &addr) + .field("hash", &hash) + .finish() + } +} + +#[repr(packed)] +pub struct BlockRaw([u8; BLOCK_SIZE as usize]); + +impl Clone for BlockRaw { + fn clone(&self) -> Self { + Self(self.0) + } +} + +impl Default for BlockRaw { + fn default() -> Self { + Self([0; BLOCK_SIZE as usize]) + } +} + +impl ops::Deref for BlockRaw { + type Target = [u8]; + fn deref(&self) -> &[u8] { + &self.0 + } +} + +impl ops::DerefMut for BlockRaw { + fn deref_mut(&mut self) -> &mut [u8] { + &mut self.0 + } +} + +#[test] +fn block_list_size_test() { + assert_eq!(mem::size_of::>(), BLOCK_SIZE as usize); +} + +#[test] +fn block_raw_size_test() { + assert_eq!(mem::size_of::(), BLOCK_SIZE as usize); +} diff --git a/src/dir.rs b/src/dir.rs new file mode 100644 index 0000000..20cd74f --- /dev/null +++ b/src/dir.rs @@ -0,0 +1,112 @@ +use core::{mem, ops, slice, str}; + +use crate::{Node, TreePtr}; + +#[repr(packed)] +pub struct DirEntry { + node_ptr: TreePtr, + name: [u8; 252], +} + +impl DirEntry { + pub fn new(node_ptr: TreePtr, name: &str) -> Option { + let mut entry = DirEntry { + node_ptr, + ..Default::default() + }; + + if name.len() > entry.name.len() { + return None; + } + + entry.name[..name.len()].copy_from_slice(name.as_bytes()); + + Some(entry) + } + + pub fn node_ptr(&self) -> TreePtr { + self.node_ptr + } + + pub fn name(&self) -> Option<&str> { + let mut len = 0; + while len < self.name.len() { + if self.name[len] == 0 { + break; + } + len += 1; + } + //TODO: report utf8 error? + str::from_utf8(&self.name[..len]).ok() + } +} + +impl Clone for DirEntry { + fn clone(&self) -> Self { + Self { + node_ptr: self.node_ptr, + name: self.name, + } + } +} + +impl Copy for DirEntry {} + +impl Default for DirEntry { + fn default() -> Self { + Self { + node_ptr: TreePtr::default(), + name: [0; 252], + } + } +} + +#[repr(packed)] +pub struct DirList { + pub entries: [DirEntry; 16], +} + +impl DirList { + pub fn is_empty(&self) -> bool { + for entry in self.entries.iter() { + if !entry.node_ptr().is_null() { + return false; + } + } + true + } +} + +impl Default for DirList { + fn default() -> Self { + Self { + entries: [DirEntry::default(); 16], + } + } +} + +impl ops::Deref for DirList { + type Target = [u8]; + fn deref(&self) -> &[u8] { + unsafe { + slice::from_raw_parts( + self as *const DirList as *const u8, + mem::size_of::(), + ) as &[u8] + } + } +} + +impl ops::DerefMut for DirList { + fn deref_mut(&mut self) -> &mut [u8] { + unsafe { + slice::from_raw_parts_mut(self as *mut DirList as *mut u8, mem::size_of::()) + as &mut [u8] + } + } +} + +#[test] +fn dir_list_size_test() { + assert_eq!(mem::size_of::(), crate::BLOCK_SIZE as usize); +} diff --git a/src/disk/cache.rs b/src/disk/cache.rs index 400bc4b..98f8af1 100644 --- a/src/disk/cache.rs +++ b/src/disk/cache.rs @@ -2,8 +2,8 @@ use std::collections::{HashMap, VecDeque}; use std::{cmp, ptr}; use syscall::error::Result; -use disk::Disk; -use BLOCK_SIZE; +use crate::disk::Disk; +use crate::BLOCK_SIZE; fn copy_memory(src: &[u8], dest: &mut [u8]) -> usize { let len = cmp::min(src.len(), dest.len()); @@ -21,7 +21,7 @@ pub struct DiskCache { impl DiskCache { pub fn new(inner: T) -> Self { DiskCache { - inner: inner, + inner, cache: HashMap::new(), order: VecDeque::new(), size: 65536, // 256 MB cache @@ -40,7 +40,7 @@ impl DiskCache { } impl Disk for DiskCache { - fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result { + unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result { // println!("Cache read at {}", block); let mut read = 0; @@ -80,7 +80,7 @@ impl Disk for DiskCache { Ok(read) } - fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result { + unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result { //TODO: Write only blocks that have changed // println!("Cache write at {}", block); diff --git a/src/disk/file.rs b/src/disk/file.rs index 6014643..d65c2a0 100644 --- a/src/disk/file.rs +++ b/src/disk/file.rs @@ -3,8 +3,8 @@ use std::io::{Read, Seek, SeekFrom, Write}; use std::path::Path; use syscall::error::{Error, Result, EIO}; -use disk::Disk; -use BLOCK_SIZE; +use crate::disk::Disk; +use crate::BLOCK_SIZE; macro_rules! try_disk { ($expr:expr) => { @@ -25,7 +25,7 @@ pub struct DiskFile { impl DiskFile { pub fn open>(path: P) -> Result { let file = try_disk!(OpenOptions::new().read(true).write(true).open(path)); - Ok(DiskFile { file: file }) + Ok(DiskFile { file }) } pub fn create>(path: P, size: u64) -> Result { @@ -35,18 +35,18 @@ impl DiskFile { .create(true) .open(path)); try_disk!(file.set_len(size)); - Ok(DiskFile { file: file }) + Ok(DiskFile { file }) } } impl Disk for DiskFile { - fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result { + unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result { try_disk!(self.file.seek(SeekFrom::Start(block * BLOCK_SIZE))); let count = try_disk!(self.file.read(buffer)); Ok(count) } - fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result { + unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result { try_disk!(self.file.seek(SeekFrom::Start(block * BLOCK_SIZE))); let count = try_disk!(self.file.write(buffer)); Ok(count) diff --git a/src/disk/mod.rs b/src/disk/mod.rs index c3aeb9b..e0db25e 100644 --- a/src/disk/mod.rs +++ b/src/disk/mod.rs @@ -16,7 +16,18 @@ mod sparse; /// A disk pub trait Disk { - fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result; - fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result; + /// Read blocks from disk + /// + /// # Safety + /// Unsafe to discourage use, use filesystem wrappers instead + unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result; + + /// Write blocks from disk + /// + /// # Safety + /// Unsafe to discourage use, use filesystem wrappers instead + unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result; + + /// Get size of disk in bytes fn size(&mut self) -> Result; } diff --git a/src/disk/sparse.rs b/src/disk/sparse.rs index 8ed79bd..ae12192 100644 --- a/src/disk/sparse.rs +++ b/src/disk/sparse.rs @@ -4,8 +4,8 @@ use std::path::Path; use std::u64; use syscall::error::{Error, Result, EIO}; -use disk::Disk; -use BLOCK_SIZE; +use crate::disk::Disk; +use crate::BLOCK_SIZE; macro_rules! try_disk { ($expr:expr) => { @@ -21,33 +21,34 @@ macro_rules! try_disk { pub struct DiskSparse { pub file: File, + pub max_size: u64, } impl DiskSparse { - pub fn create>(path: P) -> Result { + pub fn create>(path: P, max_size: u64) -> Result { let file = try_disk!(OpenOptions::new() .read(true) .write(true) .create(true) .open(path)); - Ok(DiskSparse { file }) + Ok(DiskSparse { file, max_size }) } } impl Disk for DiskSparse { - fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result { + unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result { try_disk!(self.file.seek(SeekFrom::Start(block * BLOCK_SIZE))); let count = try_disk!(self.file.read(buffer)); Ok(count) } - fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result { + unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result { try_disk!(self.file.seek(SeekFrom::Start(block * BLOCK_SIZE))); let count = try_disk!(self.file.write(buffer)); Ok(count) } fn size(&mut self) -> Result { - Ok(u64::MAX) + Ok(self.max_size) } } diff --git a/src/filesystem.rs b/src/filesystem.rs index 92fc615..c1b9760 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -1,37 +1,96 @@ -use alloc::vec::Vec; -use core::cmp::min; +use aes::{Aes128, BlockDecrypt, BlockEncrypt}; +use alloc::{collections::VecDeque, vec::Vec}; +use syscall::error::{Error, Result, EKEYREJECTED, ENOENT, ENOKEY, ENOSPC}; -use syscall::error::{Error, Result, EEXIST, EINVAL, EISDIR, ENOENT, ENOSPC, ENOTDIR, ENOTEMPTY}; - -use {Disk, Extent, Header, Node, BLOCK_SIZE}; +use crate::{ + AllocEntry, AllocList, Allocator, BlockData, Disk, Header, Key, KeySlot, Node, Salt, + Transaction, TreeList, BLOCK_SIZE, HEADER_RING, +}; /// A file system pub struct FileSystem { + //TODO: make private pub disk: D, + //TODO: make private pub block: u64, - pub header: (u64, Header), + //TODO: make private + pub header: Header, + pub(crate) allocator: Allocator, + pub(crate) aes_opt: Option, + aes_blocks: Vec, } impl FileSystem { /// Open a file system on a disk - pub fn open(mut disk: D, block_opt: Option) -> Result { - for block in block_opt.map_or(0..65536, |x| x..x + 1) { - let mut header = (0, Header::default()); - disk.read_at(block + header.0, &mut header.1)?; - - if header.1.valid() { - let mut root = (header.1.root, Node::default()); - disk.read_at(block + root.0, &mut root.1)?; - - let mut free = (header.1.free, Node::default()); - disk.read_at(block + free.0, &mut free.1)?; - - return Ok(FileSystem { - disk: disk, - block: block, - header: header, - }); + pub fn open( + mut disk: D, + password_opt: Option<&[u8]>, + block_opt: Option, + squash: bool, + ) -> Result { + for ring_block in block_opt.map_or(0..65536, |x| x..x + 1) { + let mut header = Header::default(); + unsafe { disk.read_at(ring_block, &mut header)? }; + + // Skip invalid headers + if !header.valid() { + continue; + } + + let block = ring_block - (header.generation() % HEADER_RING); + for i in 0..HEADER_RING { + let mut other_header = Header::default(); + unsafe { disk.read_at(block + i, &mut other_header)? }; + + // Skip invalid headers + if !other_header.valid() { + continue; + } + + // If this is a newer header, use it + if other_header.generation() > header.generation() { + header = other_header; + } } + + let aes_opt = match password_opt { + Some(password) => { + if !header.encrypted() { + // Header not encrypted but password provided + return Err(Error::new(EKEYREJECTED)); + } + match header.aes(password) { + Some(aes) => Some(aes), + None => { + // Header encrypted with a different password + return Err(Error::new(ENOKEY)); + } + } + } + None => { + if header.encrypted() { + // Header encrypted but no password provided + return Err(Error::new(ENOKEY)); + } + None + } + }; + + let mut fs = FileSystem { + disk, + block, + header, + allocator: Allocator::default(), + aes_opt, + aes_blocks: Vec::with_capacity(BLOCK_SIZE as usize / aes::BLOCK_SIZE), + }; + + unsafe { fs.reset_allocator()? }; + + // Squash allocations and sync + Transaction::new(&mut fs).commit(squash)?; + + return Ok(fs); } Err(Error::new(ENOENT)) @@ -39,8 +98,13 @@ impl FileSystem { /// Create a file system on a disk #[cfg(feature = "std")] - pub fn create(disk: D, ctime: u64, ctime_nsec: u32) -> Result { - Self::create_reserved(disk, &[], ctime, ctime_nsec) + pub fn create( + disk: D, + password_opt: Option<&[u8]>, + ctime: u64, + ctime_nsec: u32, + ) -> Result { + Self::create_reserved(disk, password_opt, &[], ctime, ctime_nsec) } /// Create a file system on a disk, with reserved data at the beginning @@ -49,6 +113,7 @@ impl FileSystem { #[cfg(feature = "std")] pub fn create_reserved( mut disk: D, + password_opt: Option<&[u8]>, reserved: &[u8], ctime: u64, ctime_nsec: u32, @@ -56,20 +121,7 @@ impl FileSystem { let size = disk.size()?; let block_offset = (reserved.len() as u64 + BLOCK_SIZE - 1) / BLOCK_SIZE; - if size >= (block_offset + 4) * BLOCK_SIZE { - let mut free = (2, Node::new(Node::MODE_FILE, "free", 0, ctime, ctime_nsec)?); - free.1.extents[0] = Extent::new(4, size - (block_offset + 4) * BLOCK_SIZE); - disk.write_at(block_offset + free.0, &free.1)?; - - let root = ( - 1, - Node::new(Node::MODE_DIR | 0o755, "root", 0, ctime, ctime_nsec)?, - ); - disk.write_at(block_offset + root.0, &root.1)?; - - let header = (0, Header::new(size, root.0, free.0)); - disk.write_at(block_offset + header.0, &header.1)?; - + if size >= (block_offset + HEADER_RING + 4) * BLOCK_SIZE { for block in 0..block_offset as usize { let mut data = [0; BLOCK_SIZE as usize]; @@ -79,575 +131,173 @@ impl FileSystem { i += 1; } - disk.write_at(block as u64, &data)?; - } - - Ok(FileSystem { - disk: disk, - block: block_offset, - header: header, - }) - } else { - Err(Error::new(ENOSPC)) - } - } - - /// Read at a certain spot in the disk, returning data into buffer - pub fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result { - self.disk.read_at(self.block + block, buffer) - } - - pub fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result { - self.disk.write_at(self.block + block, buffer) - } - - pub fn allocate(&mut self, length: u64) -> Result { - //TODO: traverse next pointer - let free_block = self.header.1.free; - let mut free = self.node(free_block)?; - let mut block_option = None; - for extent in free.1.extents.iter_mut() { - if extent.length / BLOCK_SIZE >= length { - block_option = Some(extent.block); - extent.length -= length * BLOCK_SIZE; - extent.block += length; - break; - } - } - if let Some(block) = block_option { - self.write_at(free.0, &free.1)?; - Ok(block) - } else { - Err(Error::new(ENOSPC)) - } - } - - pub fn deallocate(&mut self, block: u64, length: u64) -> Result<()> { - let free_block = self.header.1.free; - self.insert_blocks(block, length, free_block) - } - - pub fn node(&mut self, block: u64) -> Result<(u64, Node)> { - let mut node = Node::default(); - self.read_at(block, &mut node)?; - Ok((block, node)) - } - - pub fn child_nodes( - &mut self, - children: &mut Vec<(u64, Node)>, - parent_block: u64, - ) -> Result<()> { - if parent_block == 0 { - return Ok(()); - } - - let parent = self.node(parent_block)?; - for extent in parent.1.extents.iter() { - for (block, size) in extent.blocks() { - if size >= BLOCK_SIZE { - children.push(self.node(block)?); + unsafe { + disk.write_at(block as u64, &data)?; } } - } - - self.child_nodes(children, parent.1.next) - } - - pub fn find_node(&mut self, name: &str, parent_block: u64) -> Result<(u64, Node)> { - if parent_block == 0 { - return Err(Error::new(ENOENT)); - } - let parent = self.node(parent_block)?; - for extent in parent.1.extents.iter() { - for (block, size) in extent.blocks() { - if size >= BLOCK_SIZE { - let child = self.node(block)?; + let mut header = Header::new(size); - let mut matches = false; - if let Ok(child_name) = child.1.name() { - if child_name == name { - matches = true; - } - } - - if matches { - return Ok(child); - } + let aes_opt = match password_opt { + Some(password) => { + //TODO: handle errors + header.key_slots[0] = + KeySlot::new(password, Salt::new().unwrap(), Key::new().unwrap()).unwrap(); + Some(header.key_slots[0].key(password).unwrap().into_aes()) } - } - } + None => None, + }; - self.find_node(name, parent.1.next) - } - - //TODO: Accept length in units of BLOCK_SIZE to match remove_blocks - pub fn insert_blocks(&mut self, block: u64, length: u64, parent_block: u64) -> Result<()> { - if parent_block == 0 { - return Err(Error::new(ENOSPC)); - } - - let mut inserted = false; - let mut parent = self.node(parent_block)?; - for extent in parent.1.extents.iter_mut() { - if extent.length == 0 { - //New extent - inserted = true; - extent.block = block; - extent.length = length; - break; - } else if length % BLOCK_SIZE == 0 && extent.block == block + length / BLOCK_SIZE { - //At beginning - inserted = true; - extent.block = block; - extent.length += length; - break; - } else if extent.length % BLOCK_SIZE == 0 - && extent.block + extent.length / BLOCK_SIZE == block - { - //At end - inserted = true; - extent.length += length; - break; - } - } - - if inserted { - self.write_at(parent.0, &parent.1)?; - Ok(()) - } else { - if parent.1.next == 0 { - let next = self.allocate(1)?; - // Could be mutated by self.allocate if free block - if parent.0 == self.header.1.free { - self.read_at(parent.0, &mut parent.1)?; - } - parent.1.next = next; - self.write_at(parent.0, &parent.1)?; - self.write_at(parent.1.next, &Node::default())?; - } - - self.insert_blocks(block, length, parent.1.next) - } - } - - pub fn create_node( - &mut self, - mode: u16, - name: &str, - parent_block: u64, - ctime: u64, - ctime_nsec: u32, - ) -> Result<(u64, Node)> { - if name.contains(':') { - Err(Error::new(EINVAL)) - } else if self.find_node(name, parent_block).is_ok() { - Err(Error::new(EEXIST)) - } else { - let node_data = Node::new(mode, name, parent_block, ctime, ctime_nsec)?; - let node = (self.allocate(1)?, node_data); - self.write_at(node.0, &node.1)?; - - self.insert_blocks(node.0, BLOCK_SIZE, parent_block)?; - - Ok(node) - } - } + let mut fs = FileSystem { + disk, + block: block_offset, + header, + allocator: Allocator::default(), + aes_opt, + aes_blocks: Vec::with_capacity(BLOCK_SIZE as usize / aes::BLOCK_SIZE), + }; - pub fn remove_blocks(&mut self, block: u64, length: u64, parent_block: u64) -> Result<()> { - if parent_block == 0 { - return Err(Error::new(ENOENT)); - } + fs.tx(|tx| unsafe { + let tree = BlockData::new(HEADER_RING + 1, TreeList::default()); - let mut removed = false; - let mut replace_option = None; - let mut parent = self.node(parent_block)?; - for extent in parent.1.extents.iter_mut() { - if block >= extent.block && block + length <= extent.block + extent.length / BLOCK_SIZE - { - //Inside - removed = true; - - let left = Extent::new(extent.block, (block - extent.block) * BLOCK_SIZE); - let right = Extent::new( - block + length, - ((extent.block + extent.length / BLOCK_SIZE) - (block + length)) * BLOCK_SIZE, - ); + let mut alloc = BlockData::new(HEADER_RING + 2, AllocList::default()); + let alloc_free = size / BLOCK_SIZE - (block_offset + HEADER_RING + 4); + alloc.data_mut().entries[0] = AllocEntry::new(HEADER_RING + 4, alloc_free as i64); - if left.length > 0 { - *extent = left; + tx.header.tree = tx.write_block(tree)?; + tx.header.alloc = tx.write_block(alloc)?; + tx.header_changed = true; - if right.length > 0 { - replace_option = Some(right); - } - } else if right.length > 0 { - *extent = right; - } else { - *extent = Extent::default(); - } + Ok(()) + })?; - break; + unsafe { + fs.reset_allocator()?; } - } - if removed { - self.write_at(parent.0, &parent.1)?; + fs.tx(|tx| unsafe { + let mut root = BlockData::new( + HEADER_RING + 3, + Node::new(Node::MODE_DIR | 0o755, 0, 0, ctime, ctime_nsec), + ); + root.data_mut().set_links(1); + let root_ptr = tx.write_block(root)?; + assert_eq!(tx.insert_tree(root_ptr)?.id(), 1); + Ok(()) + })?; - if let Some(replace) = replace_option { - self.insert_blocks(replace.block, replace.length, parent_block)?; - } + // Make sure everything is synced and squash allocations + Transaction::new(&mut fs).commit(true)?; - Ok(()) + Ok(fs) } else { - self.remove_blocks(block, length, parent.1.next) + Err(Error::new(ENOSPC)) } } - pub fn remove_node(&mut self, mode: u16, name: &str, parent_block: u64) -> Result<()> { - let node = self.find_node(name, parent_block)?; - if node.1.mode & Node::MODE_TYPE == mode { - if node.1.is_dir() { - let mut children = Vec::new(); - self.child_nodes(&mut children, node.0)?; - if !children.is_empty() { - return Err(Error::new(ENOTEMPTY)); - } - } - - self.node_set_len(node.0, 0)?; - self.remove_blocks(node.0, 1, parent_block)?; - self.write_at(node.0, &Node::default())?; - self.deallocate(node.0, BLOCK_SIZE)?; - - Ok(()) - } else if node.1.is_dir() { - Err(Error::new(EISDIR)) - } else { - Err(Error::new(ENOTDIR)) - } + /// Start a filesystem transaction, required for making any changes + pub fn tx) -> Result, T>(&mut self, f: F) -> Result { + let mut tx = Transaction::new(self); + let t = f(&mut tx)?; + tx.commit(false)?; + Ok(t) } - // TODO: modification time - fn node_ensure_len(&mut self, block: u64, mut length: u64) -> Result<()> { - if block == 0 { - return Err(Error::new(ENOENT)); - } - - let mut changed = false; - - let mut node = self.node(block)?; - for extent in node.1.extents.iter_mut() { - if extent.length >= length { - length = 0; - break; - } else { - changed = true; - let allocated = ((extent.length + BLOCK_SIZE - 1) / BLOCK_SIZE) * BLOCK_SIZE; - if allocated >= length { - extent.length = length; - length = 0; - break; - } else { - extent.length = allocated; - length -= allocated; - } - } - } - - if changed { - self.write_at(node.0, &node.1)?; - } - - if length > 0 { - if node.1.next > 0 { - self.node_ensure_len(node.1.next, length) - } else { - let new_block = self.allocate((length + BLOCK_SIZE - 1) / BLOCK_SIZE)?; - self.insert_blocks(new_block, length, block)?; - Ok(()) - } - } else { - Ok(()) - } + pub fn allocator(&self) -> &Allocator { + &self.allocator } - //TODO: modification time - pub fn node_set_len(&mut self, block: u64, mut length: u64) -> Result<()> { - if block == 0 { - return Err(Error::new(ENOENT)); - } - - let mut changed = false; - - let mut node = self.node(block)?; - for extent in node.1.extents.iter_mut() { - if extent.length > length { - let start = (length + BLOCK_SIZE - 1) / BLOCK_SIZE; - let end = (extent.length + BLOCK_SIZE - 1) / BLOCK_SIZE; - if end > start { - self.deallocate(extent.block + start, (end - start) * BLOCK_SIZE)?; - } - extent.length = length; - changed = true; - length = 0; - } else { - length -= extent.length; + /// Reset allocator to state stored on disk + /// + /// # Safety + /// Unsafe, it must only be called when openning the filesystem + unsafe fn reset_allocator(&mut self) -> Result<()> { + self.allocator = Allocator::default(); + + // To avoid having to update all prior alloc blocks, there is only a previous pointer + // This means we need to roll back all allocations. Currently we do this by reading the + // alloc log into a buffer to reverse it. + let mut allocs = VecDeque::new(); + self.tx(|tx| { + let mut alloc_ptr = tx.header.alloc; + while !alloc_ptr.is_null() { + let alloc = tx.read_block(alloc_ptr)?; + alloc_ptr = alloc.data().prev; + allocs.push_front(alloc); } - } - - if changed { - self.write_at(node.0, &node.1)?; - } - - if node.1.next > 0 { - self.node_set_len(node.1.next, length) - } else { Ok(()) - } - } - - fn node_extents( - &mut self, - block: u64, - mut offset: u64, - mut len: usize, - extents: &mut Vec, - ) -> Result<()> { - if block == 0 { - return Ok(()); - } - - let node = self.node(block)?; - for extent in node.1.extents.iter() { - let mut push_extent = Extent::default(); - for (block, size) in extent.blocks() { - if offset == 0 { - if push_extent.block == 0 { - push_extent.block = block; - } - if len as u64 >= size { - push_extent.length += size; - len -= size as usize; - } else if len > 0 { - push_extent.length += len as u64; - len = 0; - break; - } else { - break; + })?; + + for alloc in allocs { + for entry in alloc.data().entries.iter() { + let addr = entry.addr(); + let count = entry.count(); + if count < 0 { + for i in 0..-count { + //TODO: replace assert with error? + assert_eq!( + self.allocator.allocate_exact(addr + i as u64), + Some(addr + i as u64) + ); } } else { - offset -= 1; + for i in 0..count { + self.allocator.deallocate(addr + i as u64); + } } } - if push_extent.length > 0 { - extents.push(push_extent); - } - if len == 0 { - break; - } } - if len > 0 { - self.node_extents(node.1.next, offset, len, extents) - } else { - Ok(()) - } + Ok(()) } - pub fn read_node( - &mut self, - block: u64, - offset: u64, - buf: &mut [u8], - atime: u64, - atime_nsec: u32, - ) -> Result { - let block_offset = offset / BLOCK_SIZE; - let mut byte_offset = (offset % BLOCK_SIZE) as usize; - - let mut extents = Vec::new(); - self.node_extents(block, block_offset, byte_offset + buf.len(), &mut extents)?; - - let mut i = 0; - for extent in extents.iter() { - let mut block = extent.block; - let mut length = extent.length; - - if byte_offset > 0 && length > 0 { - let mut sector = [0; BLOCK_SIZE as usize]; - self.read_at(block, &mut sector)?; - - let sector_size = min(sector.len() as u64, length) as usize; - for (s_b, b) in sector[byte_offset..sector_size] - .iter() - .zip(buf[i..].iter_mut()) - { - *b = *s_b; - i += 1; - } - - block += 1; - length -= sector_size as u64; + pub(crate) fn decrypt(&mut self, data: &mut [u8]) -> bool { + if let Some(ref aes) = self.aes_opt { + assert_eq!(data.len() % aes::BLOCK_SIZE, 0); - byte_offset = 0; + self.aes_blocks.clear(); + for i in 0..data.len() / aes::BLOCK_SIZE { + self.aes_blocks.push(aes::Block::clone_from_slice( + &data[i * aes::BLOCK_SIZE..(i + 1) * aes::BLOCK_SIZE], + )); } - let length_aligned = - ((min(length, (buf.len() - i) as u64) / BLOCK_SIZE) * BLOCK_SIZE) as usize; + aes.decrypt_blocks(&mut self.aes_blocks); - if length_aligned > 0 { - let extent_buf = &mut buf[i..i + length_aligned]; - self.read_at(block, extent_buf)?; - i += length_aligned; - block += (length_aligned as u64) / BLOCK_SIZE; - length -= length_aligned as u64; + for i in 0..data.len() / aes::BLOCK_SIZE { + data[i * aes::BLOCK_SIZE..(i + 1) * aes::BLOCK_SIZE] + .copy_from_slice(&self.aes_blocks[i]); } + self.aes_blocks.clear(); - if length > 0 { - let mut sector = [0; BLOCK_SIZE as usize]; - self.read_at(block, &mut sector)?; - - let sector_size = min(sector.len() as u64, length) as usize; - for (s_b, b) in sector[..sector_size].iter().zip(buf[i..].iter_mut()) { - *b = *s_b; - i += 1; - } - - block += 1; - length -= sector_size as u64; - } - - assert_eq!(length, 0); - assert_eq!( - block, - extent.block + (extent.length + BLOCK_SIZE - 1) / BLOCK_SIZE - ); - } - - if i > 0 { - let mut node = self.node(block)?; - if atime > node.1.atime || (atime == node.1.atime && atime_nsec > node.1.atime_nsec) { - let is_old = atime - node.1.atime > 3600; // Last read was more than a day ago - node.1.atime = atime; - node.1.atime_nsec = atime_nsec; - if is_old { - self.write_at(node.0, &node.1)?; - } - } + true + } else { + false } - - Ok(i) } - pub fn write_node( - &mut self, - block: u64, - offset: u64, - buf: &[u8], - mtime: u64, - mtime_nsec: u32, - ) -> Result { - let block_offset = offset / BLOCK_SIZE; - let mut byte_offset = (offset % BLOCK_SIZE) as usize; - - self.node_ensure_len( - block, - block_offset as u64 * BLOCK_SIZE + (byte_offset + buf.len()) as u64, - )?; - - let mut extents = Vec::new(); - self.node_extents(block, block_offset, byte_offset + buf.len(), &mut extents)?; - - let mut i = 0; - for extent in extents.iter() { - let mut block = extent.block; - let mut length = extent.length; - - if byte_offset > 0 && length > 0 { - let mut sector = [0; BLOCK_SIZE as usize]; - self.read_at(block, &mut sector)?; - - let sector_size = min(sector.len() as u64, length) as usize; - for (s_b, b) in sector[byte_offset..sector_size] - .iter_mut() - .zip(buf[i..].iter()) - { - *s_b = *b; - i += 1; - } + pub(crate) fn encrypt(&mut self, data: &mut [u8]) -> bool { + if let Some(ref aes) = self.aes_opt { + assert_eq!(data.len() % aes::BLOCK_SIZE, 0); - self.write_at(block, §or)?; - - block += 1; - length -= sector_size as u64; - - byte_offset = 0; - } - - let length_aligned = - ((min(length, (buf.len() - i) as u64) / BLOCK_SIZE) * BLOCK_SIZE) as usize; - - if length_aligned > 0 { - let extent_buf = &buf[i..i + length_aligned]; - self.write_at(block, extent_buf)?; - i += length_aligned; - block += (length_aligned as u64) / BLOCK_SIZE; - length -= length_aligned as u64; - } - - if length > 0 { - let mut sector = [0; BLOCK_SIZE as usize]; - self.read_at(block, &mut sector)?; - - let sector_size = min(sector.len() as u64, length) as usize; - for (s_b, b) in sector[..sector_size].iter_mut().zip(buf[i..].iter()) { - *s_b = *b; - i += 1; - } - - self.write_at(block, §or)?; - - block += 1; - length -= sector_size as u64; + self.aes_blocks.clear(); + for i in 0..data.len() / aes::BLOCK_SIZE { + self.aes_blocks.push(aes::Block::clone_from_slice( + &data[i * aes::BLOCK_SIZE..(i + 1) * aes::BLOCK_SIZE], + )); } - assert_eq!(length, 0); - assert_eq!( - block, - extent.block + (extent.length + BLOCK_SIZE - 1) / BLOCK_SIZE - ); - } + aes.encrypt_blocks(&mut self.aes_blocks); - if i > 0 { - let mut node = self.node(block)?; - if mtime > node.1.mtime || (mtime == node.1.mtime && mtime_nsec > node.1.mtime_nsec) { - node.1.mtime = mtime; - node.1.mtime_nsec = mtime_nsec; - self.write_at(node.0, &node.1)?; + for i in 0..data.len() / aes::BLOCK_SIZE { + data[i * aes::BLOCK_SIZE..(i + 1) * aes::BLOCK_SIZE] + .copy_from_slice(&self.aes_blocks[i]); } - } - - Ok(i) - } - - pub fn node_len(&mut self, block: u64) -> Result { - if block == 0 { - return Err(Error::new(ENOENT)); - } - - let mut size = 0; - - let node = self.node(block)?; - for extent in node.1.extents.iter() { - size += extent.length; - } + self.aes_blocks.clear(); - if node.1.next > 0 { - size += self.node_len(node.1.next)?; - Ok(size) + true } else { - Ok(size) + false } } } diff --git a/src/header.rs b/src/header.rs index 7018180..652c686 100644 --- a/src/header.rs +++ b/src/header.rs @@ -1,7 +1,13 @@ use core::ops::{Deref, DerefMut}; use core::{fmt, mem, slice}; +use simple_endian::*; -use {BLOCK_SIZE, SIGNATURE, VERSION}; +use aes::{Aes128, BlockDecrypt, BlockEncrypt}; +use uuid::Uuid; + +use crate::{AllocList, BlockPtr, KeySlot, Tree, BLOCK_SIZE, SIGNATURE, VERSION}; + +pub const HEADER_RING: u64 = 256; /// The header of the filesystem #[derive(Clone, Copy)] @@ -10,48 +16,142 @@ pub struct Header { /// Signature, should be SIGNATURE pub signature: [u8; 8], /// Version, should be VERSION - pub version: u64, + pub version: u64le, /// Disk ID, a 128-bit unique identifier pub uuid: [u8; 16], /// Disk size, in number of BLOCK_SIZE sectors - pub size: u64, - /// Block of root node - pub root: u64, - /// Block of free space node - pub free: u64, + pub size: u64le, + /// Generation of header + pub generation: u64le, + /// Block of first tree node + pub tree: BlockPtr, + /// Block of last alloc node + pub alloc: BlockPtr, + /// Key slots + pub key_slots: [KeySlot; 64], /// Padding - pub padding: [u8; BLOCK_SIZE as usize - 56], + pub padding: [u8; BLOCK_SIZE as usize - 2152], + /// encrypted hash of header data without hash, set to hash and padded if disk is not encrypted + pub encrypted_hash: [u8; 16], + /// hash of header data without hash + pub hash: u64le, } impl Header { - pub fn default() -> Header { - Header { - signature: [0; 8], - version: 0, - uuid: [0; 16], - size: 0, - root: 0, - free: 0, - padding: [0; BLOCK_SIZE as usize - 56], - } - } - #[cfg(feature = "std")] - pub fn new(size: u64, root: u64, free: u64) -> Header { - let uuid = uuid::Uuid::new_v4(); - Header { + pub fn new(size: u64) -> Header { + let uuid = Uuid::new_v4(); + let mut header = Header { signature: *SIGNATURE, - version: VERSION, + version: VERSION.into(), uuid: *uuid.as_bytes(), - size: size, - root: root, - free: free, - padding: [0; BLOCK_SIZE as usize - 56], - } + size: size.into(), + ..Default::default() + }; + header.update_hash(None); + header } pub fn valid(&self) -> bool { - &self.signature == SIGNATURE && self.version == VERSION + if &self.signature != SIGNATURE { + // Signature does not match + return false; + } + + if { self.version }.to_native() != VERSION { + // Version does not match + return false; + } + + if { self.hash }.to_native() != self.create_hash() { + // Hash does not match + return false; + } + + // All tests passed, header is valid + true + } + + pub fn uuid(&self) -> [u8; 16] { + self.uuid + } + + pub fn size(&self) -> u64 { + { self.size }.to_native() + } + + pub fn generation(&self) -> u64 { + { self.generation }.to_native() + } + + fn create_hash(&self) -> u64 { + // Calculate part of header to hash (everything before the hashes) + let end = mem::size_of_val(self) + - mem::size_of_val(&{ self.hash }) + - mem::size_of_val(&{ self.encrypted_hash }); + seahash::hash(&self[..end]) + } + + fn create_encrypted_hash(&self, aes_opt: Option<&Aes128>) -> [u8; 16] { + let mut encrypted_hash = [0; 16]; + for (i, b) in { self.hash }.to_native().to_le_bytes().iter().enumerate() { + encrypted_hash[i] = *b; + } + if let Some(aes) = aes_opt { + let mut block = aes::Block::from(encrypted_hash); + aes.encrypt_block(&mut block); + encrypted_hash = block.into(); + } + encrypted_hash + } + + pub fn encrypted(&self) -> bool { + (self.encrypted_hash) != self.create_encrypted_hash(None) + } + + pub fn aes(&self, password: &[u8]) -> Option { + let hash = self.create_encrypted_hash(None); + for slot in self.key_slots.iter() { + //TODO: handle errors + let aes = slot.key(password).unwrap().into_aes(); + let mut block = aes::Block::from(self.encrypted_hash); + aes.decrypt_block(&mut block); + if block == aes::Block::from(hash) { + return Some(aes); + } + } + None + } + fn update_hash(&mut self, aes_opt: Option<&Aes128>) { + self.hash = self.create_hash().into(); + // Make sure to do this second, it relies on the hash being up to date + self.encrypted_hash = self.create_encrypted_hash(aes_opt); + } + + pub fn update(&mut self, aes_opt: Option<&Aes128>) -> u64 { + let mut generation = self.generation(); + generation += 1; + self.generation = generation.into(); + self.update_hash(aes_opt); + generation + } +} + +impl Default for Header { + fn default() -> Self { + Self { + signature: [0; 8], + version: 0.into(), + uuid: [0; 16], + size: 0.into(), + generation: 0.into(), + tree: BlockPtr::::default(), + alloc: BlockPtr::::default(), + key_slots: [KeySlot::default(); 64], + padding: [0; BLOCK_SIZE as usize - 2152], + encrypted_hash: [0; 16], + hash: 0.into(), + } } } @@ -61,15 +161,19 @@ impl fmt::Debug for Header { let version = self.version; let uuid = self.uuid; let size = self.size; - let root = self.root; - let free = self.free; + let generation = self.generation; + let tree = self.tree; + let alloc = self.alloc; + let hash = self.hash; f.debug_struct("Header") .field("signature", &signature) .field("version", &version) .field("uuid", &uuid) .field("size", &size) - .field("root", &root) - .field("free", &free) + .field("generation", &generation) + .field("tree", &tree) + .field("alloc", &alloc) + .field("hash", &hash) .finish() } } @@ -93,7 +197,30 @@ impl DerefMut for Header { } } +#[test] +fn header_not_valid_test() { + assert_eq!(Header::default().valid(), false); +} + #[test] fn header_size_test() { assert_eq!(mem::size_of::
(), BLOCK_SIZE as usize); } + +#[test] +fn header_hash_test() { + let mut header = Header::default(); + assert_eq!(header.create_hash(), 0xe81ffcb86026ff96); + header.update_hash(None); + assert_eq!({ header.hash }.to_native(), 0xe81ffcb86026ff96); + assert_eq!( + header.encrypted_hash, + [0x96, 0xff, 0x26, 0x60, 0xb8, 0xfc, 0x1f, 0xe8, 0, 0, 0, 0, 0, 0, 0, 0] + ); +} + +#[cfg(feature = "std")] +#[test] +fn header_valid_test() { + assert_eq!(Header::new(0).valid(), true); +} diff --git a/src/key.rs b/src/key.rs new file mode 100644 index 0000000..9c18623 --- /dev/null +++ b/src/key.rs @@ -0,0 +1,92 @@ +use aes::{Aes128, BlockDecrypt, BlockEncrypt, NewBlockCipher}; + +// The raw key, keep secret! +#[repr(transparent)] +pub struct Key([u8; 16]); + +impl Key { + /// Generate a random key + #[cfg(feature = "std")] + pub fn new() -> Result { + let mut bytes = [0; 16]; + getrandom::getrandom(&mut bytes)?; + Ok(Self(bytes)) + } + + pub fn into_aes(self) -> Aes128 { + Aes128::new(&aes::Block::from(self.0)) + } +} + +/// The encrypted key, encrypted with AES using the salt and password +#[derive(Clone, Copy, Default)] +#[repr(transparent)] +pub struct EncryptedKey([u8; 16]); + +/// Salt used to prevent rainbow table attacks on the encryption password +#[derive(Clone, Copy, Default)] +#[repr(transparent)] +pub struct Salt([u8; 16]); + +impl Salt { + /// Generate a random salt + #[cfg(feature = "std")] + pub fn new() -> Result { + let mut bytes = [0; 16]; + getrandom::getrandom(&mut bytes)?; + Ok(Self(bytes)) + } +} + +/// The key slot, containing the salt and encrypted key that are used with one password +#[derive(Clone, Copy, Default)] +#[repr(packed)] +pub struct KeySlot { + salt: Salt, + encrypted_key: EncryptedKey, +} + +impl KeySlot { + /// Get the password AES key (generated from the password and salt, encrypts the real key) + pub fn password_aes(password: &[u8], salt: &Salt) -> Result { + let mut key = Key([0; 16]); + + let mut params_builder = argon2::ParamsBuilder::new(); + params_builder.output_len(key.0.len())?; + + let argon2 = argon2::Argon2::new( + argon2::Algorithm::Argon2id, + argon2::Version::V0x13, + params_builder.params()?, + ); + + argon2.hash_password_into(password, &salt.0, &mut key.0)?; + + Ok(key.into_aes()) + } + + /// Create a new key slot from a password, salt, and encryption key + pub fn new(password: &[u8], salt: Salt, key: Key) -> Result { + let password_aes = Self::password_aes(password, &salt)?; + + // Encrypt the real AES key + let mut block = aes::Block::from(key.0); + password_aes.encrypt_block(&mut block); + + Ok(Self { + salt, + encrypted_key: EncryptedKey(block.into()), + }) + } + + /// Get the encryption key from this key slot + pub fn key(&self, password: &[u8]) -> Result { + let password_aes = Self::password_aes(password, &self.salt)?; + + // Decrypt the real AES key + let mut block = aes::Block::from(self.encrypted_key.0); + password_aes.decrypt_block(&mut block); + + Ok(Key(block.into())) + } +} diff --git a/src/lib.rs b/src/lib.rs index 752c40a..03deee8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,42 +1,47 @@ #![crate_name = "redoxfs"] #![crate_type = "lib"] #![cfg_attr(not(feature = "std"), no_std)] +// Used often in generating redox_syscall errors +#![allow(clippy::or_fun_call)] extern crate alloc; -#[cfg(feature = "std")] -extern crate core; - -extern crate syscall; -extern crate uuid; use core::sync::atomic::AtomicUsize; pub const BLOCK_SIZE: u64 = 4096; -pub const SIGNATURE: &'static [u8; 8] = b"RedoxFS\0"; -pub const VERSION: u64 = 4; +pub const SIGNATURE: &[u8; 8] = b"RedoxFS\0"; +pub const VERSION: u64 = 5; pub static IS_UMT: AtomicUsize = AtomicUsize::new(0); +pub use self::allocator::{AllocEntry, AllocList, Allocator, ALLOC_LIST_ENTRIES}; #[cfg(feature = "std")] pub use self::archive::{archive, archive_at}; -pub use self::disk::Disk; -#[cfg(feature = "std")] -pub use self::disk::{DiskCache, DiskFile, DiskSparse}; -pub use self::extent::Extent; +pub use self::block::{BlockData, BlockList, BlockPtr, BlockRaw}; +pub use self::dir::{DirEntry, DirList}; +pub use self::disk::*; pub use self::filesystem::FileSystem; -pub use self::header::Header; +pub use self::header::{Header, HEADER_RING}; +pub use self::key::{Key, KeySlot, Salt}; #[cfg(feature = "std")] pub use self::mount::mount; -pub use self::node::Node; +pub use self::node::{Node, NodeLevel}; +pub use self::transaction::Transaction; +pub use self::tree::{Tree, TreeData, TreeList, TreePtr}; +mod allocator; #[cfg(feature = "std")] mod archive; +mod block; +mod dir; mod disk; -mod extent; mod filesystem; mod header; +mod key; #[cfg(feature = "std")] mod mount; mod node; +mod transaction; +mod tree; #[cfg(all(feature = "std", test))] mod tests; diff --git a/src/mount/fuse.rs b/src/mount/fuse.rs index 2a79333..a6e788a 100644 --- a/src/mount/fuse.rs +++ b/src/mount/fuse.rs @@ -8,10 +8,7 @@ use std::os::unix::ffi::OsStrExt; use std::path::Path; use std::time::{SystemTime, UNIX_EPOCH}; -use disk::Disk; -use filesystem; -use node::Node; -use BLOCK_SIZE; +use crate::{filesystem, Disk, Node, TreeData, TreePtr, BLOCK_SIZE}; use self::fuse::{ FileAttr, FileType, Filesystem, ReplyAttr, ReplyCreate, ReplyData, ReplyDirectory, ReplyEmpty, @@ -51,7 +48,7 @@ where }, )?; - let res = callback(&mountpoint); + let res = callback(mountpoint); session.run()?; @@ -62,41 +59,48 @@ pub struct Fuse { pub fs: filesystem::FileSystem, } -fn node_attr(node: &(u64, Node)) -> FileAttr { +fn node_attr(node: &TreeData) -> FileAttr { FileAttr { - ino: node.0, - size: node.1.extents[0].length, + ino: node.id() as u64, + size: node.data().size(), // Blocks is in 512 byte blocks, not in our block size - blocks: (node.1.extents[0].length + BLOCK_SIZE - 1) / BLOCK_SIZE * (BLOCK_SIZE / 512), - atime: NULL_TIME, + blocks: (node.data().size() + BLOCK_SIZE - 1) / BLOCK_SIZE * (BLOCK_SIZE / 512), + atime: Timespec { + sec: node.data().atime().0 as i64, + nsec: node.data().atime().1 as i32, + }, mtime: Timespec { - sec: node.1.mtime as i64, - nsec: node.1.mtime_nsec as i32, + sec: node.data().mtime().0 as i64, + nsec: node.data().mtime().1 as i32, }, ctime: Timespec { - sec: node.1.ctime as i64, - nsec: node.1.ctime_nsec as i32, + sec: node.data().ctime().0 as i64, + nsec: node.data().ctime().1 as i32, }, crtime: NULL_TIME, - kind: if node.1.is_dir() { + kind: if node.data().is_dir() { FileType::Directory - } else if node.1.is_symlink() { + } else if node.data().is_symlink() { FileType::Symlink } else { FileType::RegularFile }, - perm: node.1.mode & Node::MODE_PERM, - nlink: 1, - uid: node.1.uid, - gid: node.1.gid, + perm: node.data().mode() & Node::MODE_PERM, + nlink: node.data().links(), + uid: node.data().uid(), + gid: node.data().gid(), rdev: 0, flags: 0, } } impl Filesystem for Fuse { - fn lookup(&mut self, _req: &Request, parent_block: u64, name: &OsStr, reply: ReplyEntry) { - match self.fs.find_node(name.to_str().unwrap(), parent_block) { + fn lookup(&mut self, _req: &Request, parent_id: u64, name: &OsStr, reply: ReplyEntry) { + let parent_ptr = TreePtr::new(parent_id as u32); + match self + .fs + .tx(|tx| tx.find_node(parent_ptr, name.to_str().unwrap())) + { Ok(node) => { reply.entry(&TTL, &node_attr(&node), 0); } @@ -106,8 +110,9 @@ impl Filesystem for Fuse { } } - fn getattr(&mut self, _req: &Request, block: u64, reply: ReplyAttr) { - match self.fs.node(block) { + fn getattr(&mut self, _req: &Request, node_id: u64, reply: ReplyAttr) { + let node_ptr = TreePtr::::new(node_id as u32); + match self.fs.tx(|tx| tx.read_tree(node_ptr)) { Ok(node) => { reply.attr(&TTL, &node_attr(&node)); } @@ -120,7 +125,7 @@ impl Filesystem for Fuse { fn setattr( &mut self, _req: &Request, - block: u64, + node_id: u64, mode: Option, uid: Option, gid: Option, @@ -134,86 +139,57 @@ impl Filesystem for Fuse { _flags: Option, reply: ReplyAttr, ) { + let node_ptr = TreePtr::::new(node_id as u32); + + let mut node = match self.fs.tx(|tx| tx.read_tree(node_ptr)) { + Ok(ok) => ok, + Err(err) => { + reply.error(err.errno as i32); + return; + } + }; + let mut node_changed = false; + if let Some(mode) = mode { - match self.fs.node(block) { - Ok(mut node) => { - if node.1.mode & Node::MODE_PERM != mode as u16 & Node::MODE_PERM { - // println!("Chmod {:?}:{:o}:{:o}", node.1.name(), node.1.mode, mode); - node.1.mode = - (node.1.mode & Node::MODE_TYPE) | (mode as u16 & Node::MODE_PERM); - if let Err(err) = self.fs.write_at(node.0, &node.1) { - reply.error(err.errno as i32); - return; - } - } - } - Err(err) => { - reply.error(err.errno as i32); - return; - } + if node.data().mode() & Node::MODE_PERM != mode as u16 & Node::MODE_PERM { + let new_mode = + (node.data().mode() & Node::MODE_TYPE) | (mode as u16 & Node::MODE_PERM); + node.data_mut().set_mode(new_mode); + node_changed = true; } } if let Some(uid) = uid { - match self.fs.node(block) { - Ok(mut node) => { - if node.1.uid != uid { - node.1.uid = uid; - if let Err(err) = self.fs.write_at(node.0, &node.1) { - reply.error(err.errno as i32); - return; - } - } - } - Err(err) => { - reply.error(err.errno as i32); - return; - } + if node.data().uid() != uid { + node.data_mut().set_uid(uid); + node_changed = true; } } if let Some(gid) = gid { - match self.fs.node(block) { - Ok(mut node) => { - if node.1.gid != gid { - node.1.gid = gid; - if let Err(err) = self.fs.write_at(node.0, &node.1) { - reply.error(err.errno as i32); - return; - } - } - } - Err(err) => { - reply.error(err.errno as i32); - return; - } + if node.data().gid() != gid { + node.data_mut().set_gid(gid); + node_changed = true; } } - if let Some(size) = size { - if let Err(err) = self.fs.node_set_len(block, size) { - reply.error(err.errno as i32); - return; - } + if let Some(atime) = atime { + node.data_mut() + .set_atime(atime.sec as u64, atime.nsec as u32); + node_changed = true; } - let need_update = atime.is_some() || mtime.is_some(); - if need_update { - match self.fs.node(block) { - Ok(mut node) => { - if let Some(atime) = atime { - node.1.atime = atime.sec as u64; - node.1.atime_nsec = atime.nsec as u32; - } - - if let Some(mtime) = mtime { - node.1.mtime = mtime.sec as u64; - node.1.mtime_nsec = mtime.nsec as u32; - } + if let Some(mtime) = mtime { + node.data_mut() + .set_mtime(mtime.sec as u64, mtime.nsec as u32); + node_changed = true; + } - if let Err(err) = self.fs.write_at(node.0, &node.1) { - reply.error(err.errno as i32); - return; + if let Some(size) = size { + match self.fs.tx(|tx| tx.truncate_node_inner(&mut node, size)) { + Ok(ok) => { + if ok { + node_changed = true; } } Err(err) => { @@ -223,34 +199,40 @@ impl Filesystem for Fuse { } } - match self.fs.node(block) { - Ok(node) => { - reply.attr(&TTL, &node_attr(&node)); - } - Err(err) => { + let attr = node_attr(&node); + + if node_changed { + if let Err(err) = self.fs.tx(|tx| tx.sync_tree(node)) { reply.error(err.errno as i32); + return; } } + + reply.attr(&TTL, &attr); } fn read( &mut self, _req: &Request, - block: u64, + node_id: u64, _fh: u64, offset: i64, size: u32, reply: ReplyData, ) { + let node_ptr = TreePtr::::new(node_id as u32); + let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); let mut data = vec![0; size as usize]; - match self.fs.read_node( - block, - cmp::max(0, offset) as u64, - &mut data, - atime.as_secs(), - atime.subsec_nanos(), - ) { + match self.fs.tx(|tx| { + tx.read_node( + node_ptr, + cmp::max(0, offset) as u64, + &mut data, + atime.as_secs(), + atime.subsec_nanos(), + ) + }) { Ok(count) => { reply.data(&data[..count]); } @@ -263,21 +245,25 @@ impl Filesystem for Fuse { fn write( &mut self, _req: &Request, - block: u64, + node_id: u64, _fh: u64, offset: i64, data: &[u8], _flags: u32, reply: ReplyWrite, ) { + let node_ptr = TreePtr::::new(node_id as u32); + let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - match self.fs.write_node( - block, - cmp::max(0, offset) as u64, - &data, - mtime.as_secs(), - mtime.subsec_nanos(), - ) { + match self.fs.tx(|tx| { + tx.write_node( + node_ptr, + cmp::max(0, offset) as u64, + data, + mtime.as_secs(), + mtime.subsec_nanos(), + ) + }) { Ok(count) => { reply.written(count as u32); } @@ -298,23 +284,25 @@ impl Filesystem for Fuse { fn readdir( &mut self, _req: &Request, - parent_block: u64, + parent_id: u64, _fh: u64, offset: i64, mut reply: ReplyDirectory, ) { + let parent_ptr = TreePtr::new(parent_id as u32); let mut children = Vec::new(); - match self.fs.child_nodes(&mut children, parent_block) { + match self.fs.tx(|tx| tx.child_nodes(parent_ptr, &mut children)) { Ok(()) => { let mut i; let skip; if offset == 0 { skip = 0; i = 0; - reply.add(parent_block - self.fs.header.0, i, FileType::Directory, "."); + reply.add(parent_id, i, FileType::Directory, "."); i += 1; reply.add( - parent_block - self.fs.header.0, + //TODO: get parent? + parent_id, i, FileType::Directory, "..", @@ -326,15 +314,24 @@ impl Filesystem for Fuse { } for child in children.iter().skip(skip) { + //TODO: make it possible to get file type from directory entry + let node = match self.fs.tx(|tx| tx.read_tree(child.node_ptr())) { + Ok(ok) => ok, + Err(err) => { + reply.error(err.errno as i32); + return; + } + }; + let full = reply.add( - child.0 - self.fs.header.0, + child.node_ptr().id() as u64, i, - if child.1.is_dir() { + if node.data().is_dir() { FileType::Directory } else { FileType::RegularFile }, - child.1.name().unwrap(), + child.name().unwrap(), ); if full { @@ -354,20 +351,23 @@ impl Filesystem for Fuse { fn create( &mut self, _req: &Request, - parent_block: u64, + parent_id: u64, name: &OsStr, mode: u32, flags: u32, reply: ReplyCreate, ) { + let parent_ptr = TreePtr::::new(parent_id as u32); let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - match self.fs.create_node( - Node::MODE_FILE | (mode as u16 & Node::MODE_PERM), - name.to_str().unwrap(), - parent_block, - ctime.as_secs(), - ctime.subsec_nanos(), - ) { + match self.fs.tx(|tx| { + tx.create_node( + parent_ptr, + name.to_str().unwrap(), + Node::MODE_FILE | (mode as u16 & Node::MODE_PERM), + ctime.as_secs(), + ctime.subsec_nanos(), + ) + }) { Ok(node) => { // println!("Create {:?}:{:o}:{:o}", node.1.name(), node.1.mode, mode); reply.created(&TTL, &node_attr(&node), 0, 0, flags); @@ -381,19 +381,22 @@ impl Filesystem for Fuse { fn mkdir( &mut self, _req: &Request, - parent_block: u64, + parent_id: u64, name: &OsStr, mode: u32, reply: ReplyEntry, ) { + let parent_ptr = TreePtr::::new(parent_id as u32); let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - match self.fs.create_node( - Node::MODE_DIR | (mode as u16 & Node::MODE_PERM), - name.to_str().unwrap(), - parent_block, - ctime.as_secs(), - ctime.subsec_nanos(), - ) { + match self.fs.tx(|tx| { + tx.create_node( + parent_ptr, + name.to_str().unwrap(), + Node::MODE_DIR | (mode as u16 & Node::MODE_PERM), + ctime.as_secs(), + ctime.subsec_nanos(), + ) + }) { Ok(node) => { // println!("Mkdir {:?}:{:o}:{:o}", node.1.name(), node.1.mode, mode); reply.entry(&TTL, &node_attr(&node), 0); @@ -404,10 +407,11 @@ impl Filesystem for Fuse { } } - fn rmdir(&mut self, _req: &Request, parent_block: u64, name: &OsStr, reply: ReplyEmpty) { + fn rmdir(&mut self, _req: &Request, parent_id: u64, name: &OsStr, reply: ReplyEmpty) { + let parent_ptr = TreePtr::::new(parent_id as u32); match self .fs - .remove_node(Node::MODE_DIR, name.to_str().unwrap(), parent_block) + .tx(|tx| tx.remove_node(parent_ptr, name.to_str().unwrap(), Node::MODE_DIR)) { Ok(()) => { reply.ok(); @@ -418,10 +422,11 @@ impl Filesystem for Fuse { } } - fn unlink(&mut self, _req: &Request, parent_block: u64, name: &OsStr, reply: ReplyEmpty) { + fn unlink(&mut self, _req: &Request, parent_id: u64, name: &OsStr, reply: ReplyEmpty) { + let parent_ptr = TreePtr::::new(parent_id as u32); match self .fs - .remove_node(Node::MODE_FILE, name.to_str().unwrap(), parent_block) + .tx(|tx| tx.remove_node(parent_ptr, name.to_str().unwrap(), Node::MODE_FILE)) { Ok(()) => { reply.ok(); @@ -433,52 +438,44 @@ impl Filesystem for Fuse { } fn statfs(&mut self, _req: &Request, _ino: u64, reply: ReplyStatfs) { - let free = self.fs.header.1.free; - match self.fs.node_len(free) { - Ok(free_size) => { - let bsize = BLOCK_SIZE; - let blocks = self.fs.header.1.size / bsize; - let bfree = free_size / bsize; - reply.statfs(blocks, bfree, bfree, 0, 0, bsize as u32, 256, 0); - } - Err(err) => { - reply.error(err.errno as i32); - } - } + let bsize = BLOCK_SIZE; + let blocks = self.fs.header.size() / bsize; + let bfree = self.fs.allocator().free(); + reply.statfs(blocks, bfree, bfree, 0, 0, bsize as u32, 256, 0); } fn symlink( &mut self, _req: &Request, - parent_block: u64, + parent_id: u64, name: &OsStr, link: &Path, reply: ReplyEntry, ) { + let parent_ptr = TreePtr::::new(parent_id as u32); let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - match self.fs.create_node( - Node::MODE_SYMLINK | 0o777, - name.to_str().unwrap(), - parent_block, - ctime.as_secs(), - ctime.subsec_nanos(), - ) { + match self.fs.tx(|tx| { + let node = tx.create_node( + parent_ptr, + name.to_str().unwrap(), + Node::MODE_SYMLINK | 0o777, + ctime.as_secs(), + ctime.subsec_nanos(), + )?; + + let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + tx.write_node( + node.ptr(), + 0, + link.as_os_str().as_bytes(), + mtime.as_secs(), + mtime.subsec_nanos(), + )?; + + Ok(node) + }) { Ok(node) => { - let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - match self.fs.write_node( - node.0, - 0, - link.as_os_str().as_bytes(), - mtime.as_secs(), - mtime.subsec_nanos(), - ) { - Ok(_count) => { - reply.entry(&TTL, &node_attr(&node), 0); - } - Err(err) => { - reply.error(err.errno as i32); - } - } + reply.entry(&TTL, &node_attr(&node), 0); } Err(error) => { reply.error(error.errno as i32); @@ -486,13 +483,19 @@ impl Filesystem for Fuse { } } - fn readlink(&mut self, _req: &Request, ino: u64, reply: ReplyData) { + fn readlink(&mut self, _req: &Request, node_id: u64, reply: ReplyData) { + let node_ptr = TreePtr::::new(node_id as u32); let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); let mut data = vec![0; 4096]; - match self - .fs - .read_node(ino, 0, &mut data, atime.as_secs(), atime.subsec_nanos()) - { + match self.fs.tx(|tx| { + tx.read_node( + node_ptr, + 0, + &mut data, + atime.as_secs(), + atime.subsec_nanos(), + ) + }) { Ok(count) => { reply.data(&data[..count]); } @@ -506,39 +509,27 @@ impl Filesystem for Fuse { &mut self, _req: &Request, orig_parent: u64, - name: &OsStr, + orig_name: &OsStr, new_parent: u64, new_name: &OsStr, reply: ReplyEmpty, ) { - let rename_inner = |fs: &mut filesystem::FileSystem| -> syscall::Result<()> { - let mut orig = fs.find_node(name.to_str().unwrap(), orig_parent)?; - - if new_parent != orig_parent { - fs.remove_blocks(orig.0, 1, orig_parent)?; - } - - if let Ok(node) = fs.find_node(new_name.to_str().unwrap(), new_parent) { - if node.0 != orig.0 { - fs.node_set_len(node.0, 0)?; - fs.remove_blocks(node.0, 1, new_parent)?; - fs.write_at(node.0, &Node::default())?; - fs.deallocate(node.0, BLOCK_SIZE)?; - } - } - - orig.1.set_name(&new_name.to_str().unwrap())?; - orig.1.parent = new_parent; - fs.write_at(orig.0, &orig.1)?; - - if new_parent != orig_parent { - fs.insert_blocks(orig.0, BLOCK_SIZE, new_parent)?; - } - + let orig_parent_ptr = TreePtr::::new(orig_parent as u32); + let orig_name = orig_name.to_str().expect("name is not utf-8"); + let new_parent_ptr = TreePtr::::new(new_parent as u32); + let new_name = new_name.to_str().expect("name is not utf-8"); + + // TODO: improve performance + match self.fs.tx(|tx| { + let node = tx.find_node(orig_parent_ptr, orig_name)?; + tx.link_node(new_parent_ptr, new_name, node.ptr())?; + tx.remove_node( + orig_parent_ptr, + orig_name, + node.data().mode() & Node::MODE_TYPE, + )?; Ok(()) - }; - - match rename_inner(&mut self.fs) { + }) { Ok(()) => reply.ok(), Err(err) => reply.error(err.errno as i32), } diff --git a/src/mount/redox/mod.rs b/src/mount/redox/mod.rs index 8decac6..b5b2b73 100644 --- a/src/mount/redox/mod.rs +++ b/src/mount/redox/mod.rs @@ -2,11 +2,9 @@ use std::fs::File; use std::io::{self, Read, Write}; use std::path::Path; use std::sync::atomic::Ordering; -use syscall::{Packet, Scheme}; +use syscall::{Packet, SchemeMut}; -use disk::Disk; -use filesystem::FileSystem; -use IS_UMT; +use crate::{Disk, FileSystem, IS_UMT}; use self::scheme::FileScheme; @@ -26,7 +24,7 @@ where let mounted_path = format!("{}:", mountpoint.display()); let res = callback(Path::new(&mounted_path)); - let scheme = FileScheme::new(format!("{}", mountpoint.display()), filesystem); + let mut scheme = FileScheme::new(format!("{}", mountpoint.display()), filesystem); loop { if IS_UMT.load(Ordering::SeqCst) > 0 { break Ok(res); diff --git a/src/mount/redox/resource.rs b/src/mount/redox/resource.rs index d77e900..2f18ff6 100644 --- a/src/mount/redox/resource.rs +++ b/src/mount/redox/resource.rs @@ -10,57 +10,151 @@ use syscall::flag::{ PROT_READ, PROT_WRITE, SEEK_CUR, SEEK_END, SEEK_SET, }; -use disk::Disk; -use filesystem::FileSystem; +use crate::{Disk, Node, Transaction, TreePtr}; pub trait Resource { - fn block(&self) -> u64; - fn dup(&self) -> Result>>; + fn node_ptr(&self) -> TreePtr; + + fn uid(&self) -> u32; + + fn dup(&self) -> Result>>; + fn set_path(&mut self, path: &str); - fn read(&mut self, buf: &mut [u8], fs: &mut FileSystem) -> Result; - fn write(&mut self, buf: &[u8], fs: &mut FileSystem) -> Result; - fn seek(&mut self, offset: isize, whence: usize, fs: &mut FileSystem) -> Result; - fn fmap(&mut self, map: &Map, fs: &mut FileSystem) -> Result; - fn funmap(&mut self, address: usize, fs: &mut FileSystem) -> Result; - fn fchmod(&mut self, mode: u16, fs: &mut FileSystem) -> Result; - fn fchown(&mut self, uid: u32, gid: u32, fs: &mut FileSystem) -> Result; + + fn read(&mut self, buf: &mut [u8], tx: &mut Transaction) -> Result; + + fn write(&mut self, buf: &[u8], tx: &mut Transaction) -> Result; + + fn seek(&mut self, offset: isize, whence: usize, tx: &mut Transaction) -> Result; + + fn fmap(&mut self, map: &Map, tx: &mut Transaction) -> Result; + + fn funmap(&mut self, address: usize, tx: &mut Transaction) -> Result; + + fn fchmod(&mut self, mode: u16, tx: &mut Transaction) -> Result { + let mut node = tx.read_tree(self.node_ptr())?; + + if node.data().uid() == self.uid() || self.uid() == 0 { + let old_mode = node.data().mode(); + let new_mode = (old_mode & !MODE_PERM) | (mode & MODE_PERM); + if old_mode != new_mode { + node.data_mut().set_mode(new_mode); + tx.sync_tree(node)?; + } + + Ok(0) + } else { + Err(Error::new(EPERM)) + } + } + + fn fchown(&mut self, uid: u32, gid: u32, tx: &mut Transaction) -> Result { + let mut node = tx.read_tree(self.node_ptr())?; + + let old_uid = node.data().uid(); + if old_uid == self.uid() || self.uid() == 0 { + let mut node_changed = false; + + if uid as i32 != -1 { + if uid != old_uid { + node.data_mut().set_uid(uid); + node_changed = true; + } + } + + if gid as i32 != -1 { + let old_gid = node.data().gid(); + if gid != old_gid { + node.data_mut().set_gid(gid); + node_changed = true; + } + } + + if node_changed { + tx.sync_tree(node)?; + } + + Ok(0) + } else { + Err(Error::new(EPERM)) + } + } + fn fcntl(&mut self, cmd: usize, arg: usize) -> Result; - fn path(&self, buf: &mut [u8]) -> Result; - fn stat(&self, _stat: &mut Stat, fs: &mut FileSystem) -> Result; - fn sync(&mut self, fs: &mut FileSystem) -> Result; - fn truncate(&mut self, len: usize, fs: &mut FileSystem) -> Result; - fn utimens(&mut self, times: &[TimeSpec], fs: &mut FileSystem) -> Result; + + fn path(&self) -> &str; + + fn stat(&self, stat: &mut Stat, tx: &mut Transaction) -> Result { + let node = tx.read_tree(self.node_ptr())?; + + let ctime = node.data().ctime(); + let mtime = node.data().mtime(); + let atime = node.data().atime(); + *stat = Stat { + st_dev: 0, // TODO + st_ino: node.id() as u64, + st_mode: node.data().mode(), + st_nlink: node.data().links(), + st_uid: node.data().uid(), + st_gid: node.data().gid(), + st_size: node.data().size(), + st_mtime: mtime.0, + st_mtime_nsec: mtime.1, + st_atime: atime.0, + st_atime_nsec: atime.1, + st_ctime: ctime.0, + st_ctime_nsec: ctime.1, + ..Default::default() + }; + + Ok(0) + } + + fn sync(&mut self, tx: &mut Transaction) -> Result; + + fn truncate(&mut self, len: usize, tx: &mut Transaction) -> Result; + + fn utimens(&mut self, times: &[TimeSpec], tx: &mut Transaction) -> Result; } pub struct DirResource { path: String, - block: u64, + node_ptr: TreePtr, data: Option>, seek: isize, uid: u32, } impl DirResource { - pub fn new(path: String, block: u64, data: Option>, uid: u32) -> DirResource { + pub fn new( + path: String, + node_ptr: TreePtr, + data: Option>, + uid: u32, + ) -> DirResource { DirResource { - path: path, - block: block, - data: data, + path, + node_ptr, + data, seek: 0, - uid: uid, + uid, } } } impl Resource for DirResource { - fn block(&self) -> u64 { - self.block + fn node_ptr(&self) -> TreePtr { + self.node_ptr + } + + fn uid(&self) -> u32 { + self.uid } - fn dup(&self) -> Result>> { + fn dup(&self) -> Result>> { Ok(Box::new(DirResource { path: self.path.clone(), - block: self.block, + node_ptr: self.node_ptr, data: self.data.clone(), seek: self.seek, uid: self.uid, @@ -71,7 +165,7 @@ impl Resource for DirResource { self.path = path.to_string(); } - fn read(&mut self, buf: &mut [u8], _fs: &mut FileSystem) -> Result { + fn read(&mut self, buf: &mut [u8], _tx: &mut Transaction) -> Result { let data = self.data.as_ref().ok_or(Error::new(EISDIR))?; let size = data.len() as isize; let mut i = 0; @@ -83,11 +177,11 @@ impl Resource for DirResource { Ok(i) } - fn write(&mut self, _buf: &[u8], _fs: &mut FileSystem) -> Result { + fn write(&mut self, _buf: &[u8], _tx: &mut Transaction) -> Result { Err(Error::new(EBADF)) } - fn seek(&mut self, offset: isize, whence: usize, _fs: &mut FileSystem) -> Result { + fn seek(&mut self, offset: isize, whence: usize, _tx: &mut Transaction) -> Result { let data = self.data.as_ref().ok_or(Error::new(EBADF))?; let size = data.len() as isize; self.seek = match whence { @@ -99,108 +193,47 @@ impl Resource for DirResource { Ok(self.seek) } - fn fmap(&mut self, _map: &Map, _fs: &mut FileSystem) -> Result { + fn fmap(&mut self, _map: &Map, _tx: &mut Transaction) -> Result { Err(Error::new(EBADF)) } - fn funmap(&mut self, _address: usize, _fs: &mut FileSystem) -> Result { + fn funmap(&mut self, _address: usize, _tx: &mut Transaction) -> Result { Err(Error::new(EBADF)) } - fn fchmod(&mut self, mode: u16, fs: &mut FileSystem) -> Result { - let mut node = fs.node(self.block)?; - - if node.1.uid == self.uid || self.uid == 0 { - node.1.mode = (node.1.mode & !MODE_PERM) | (mode & MODE_PERM); - - fs.write_at(node.0, &node.1)?; - - Ok(0) - } else { - Err(Error::new(EPERM)) - } - } - - fn fchown(&mut self, uid: u32, gid: u32, fs: &mut FileSystem) -> Result { - let mut node = fs.node(self.block)?; - - if node.1.uid == self.uid || self.uid == 0 { - if uid as i32 != -1 { - node.1.uid = uid; - } - - if gid as i32 != -1 { - node.1.gid = gid; - } - - fs.write_at(node.0, &node.1)?; - - Ok(0) - } else { - Err(Error::new(EPERM)) - } - } - fn fcntl(&mut self, _cmd: usize, _arg: usize) -> Result { Err(Error::new(EBADF)) } - fn path(&self, buf: &mut [u8]) -> Result { - let path = self.path.as_bytes(); - - let mut i = 0; - while i < buf.len() && i < path.len() { - buf[i] = path[i]; - i += 1; - } - - Ok(i) - } - - fn stat(&self, stat: &mut Stat, fs: &mut FileSystem) -> Result { - let node = fs.node(self.block)?; - - *stat = Stat { - st_dev: 0, // TODO - st_ino: node.0, - st_mode: node.1.mode, - st_nlink: 1, - st_uid: node.1.uid, - st_gid: node.1.gid, - st_size: fs.node_len(self.block)?, - st_mtime: node.1.mtime, - st_mtime_nsec: node.1.mtime_nsec, - st_atime: node.1.atime, - st_atime_nsec: node.1.atime_nsec, - st_ctime: node.1.ctime, - st_ctime_nsec: node.1.ctime_nsec, - ..Default::default() - }; - - Ok(0) + fn path(&self) -> &str { + &self.path } - fn sync(&mut self, _fs: &mut FileSystem) -> Result { + fn sync(&mut self, _tx: &mut Transaction) -> Result { Err(Error::new(EBADF)) } - fn truncate(&mut self, _len: usize, _fs: &mut FileSystem) -> Result { + fn truncate(&mut self, _len: usize, _tx: &mut Transaction) -> Result { Err(Error::new(EBADF)) } - fn utimens(&mut self, _times: &[TimeSpec], _fs: &mut FileSystem) -> Result { + fn utimens(&mut self, _times: &[TimeSpec], _tx: &mut Transaction) -> Result { Err(Error::new(EBADF)) } } pub struct Fmap { - block: u64, + node_ptr: TreePtr, offset: usize, flags: MapFlags, data: &'static mut [u8], } impl Fmap { - pub unsafe fn new(block: u64, map: &Map, fs: &mut FileSystem) -> Result { + pub unsafe fn new( + node_ptr: TreePtr, + map: &Map, + tx: &mut Transaction, + ) -> Result { extern "C" { fn memalign(align: usize, size: usize) -> *mut u8; fn free(ptr: *mut u8); @@ -216,8 +249,8 @@ impl Fmap { // Read buffer from disk let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); let buf = slice::from_raw_parts_mut(address, map.size); - let count = match fs.read_node( - block, + let count = match tx.read_node( + node_ptr, map.offset as u64, buf, atime.as_secs(), @@ -236,18 +269,18 @@ impl Fmap { } Ok(Self { - block, + node_ptr, offset: map.offset, flags: map.flags, data: buf, }) } - pub fn sync(&mut self, fs: &mut FileSystem) -> Result<()> { + pub fn sync(&mut self, tx: &mut Transaction) -> Result<()> { if self.flags & PROT_WRITE == PROT_WRITE { let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - fs.write_node( - self.block, + tx.write_node( + self.node_ptr, self.offset as u64, &self.data, mtime.as_secs(), @@ -272,7 +305,7 @@ impl Drop for Fmap { pub struct FileResource { path: String, - block: u64, + node_ptr: TreePtr, flags: usize, seek: isize, uid: u32, @@ -280,10 +313,10 @@ pub struct FileResource { } impl FileResource { - pub fn new(path: String, block: u64, flags: usize, uid: u32) -> FileResource { + pub fn new(path: String, node_ptr: TreePtr, flags: usize, uid: u32) -> FileResource { FileResource { path, - block, + node_ptr, flags, seek: 0, uid, @@ -293,14 +326,18 @@ impl FileResource { } impl Resource for FileResource { - fn block(&self) -> u64 { - self.block + fn node_ptr(&self) -> TreePtr { + self.node_ptr } - fn dup(&self) -> Result>> { + fn uid(&self) -> u32 { + self.uid + } + + fn dup(&self) -> Result>> { Ok(Box::new(FileResource { path: self.path.clone(), - block: self.block, + node_ptr: self.node_ptr, flags: self.flags, seek: self.seek, uid: self.uid, @@ -312,11 +349,11 @@ impl Resource for FileResource { self.path = path.to_string(); } - fn read(&mut self, buf: &mut [u8], fs: &mut FileSystem) -> Result { + fn read(&mut self, buf: &mut [u8], tx: &mut Transaction) -> Result { if self.flags & O_ACCMODE == O_RDWR || self.flags & O_ACCMODE == O_RDONLY { let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - let count = fs.read_node( - self.block, + let count = tx.read_node( + self.node_ptr, self.seek as u64, buf, atime.as_secs(), @@ -329,14 +366,15 @@ impl Resource for FileResource { } } - fn write(&mut self, buf: &[u8], fs: &mut FileSystem) -> Result { + fn write(&mut self, buf: &[u8], tx: &mut Transaction) -> Result { if self.flags & O_ACCMODE == O_RDWR || self.flags & O_ACCMODE == O_WRONLY { if self.flags & O_APPEND == O_APPEND { - self.seek = fs.node_len(self.block)? as isize; + let node = tx.read_tree(self.node_ptr)?; + self.seek = node.data().size() as isize; } let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - let count = fs.write_node( - self.block, + let count = tx.write_node( + self.node_ptr, self.seek as u64, buf, mtime.as_secs(), @@ -349,18 +387,20 @@ impl Resource for FileResource { } } - fn seek(&mut self, offset: isize, whence: usize, fs: &mut FileSystem) -> Result { - let size = fs.node_len(self.block)? as isize; + fn seek(&mut self, offset: isize, whence: usize, tx: &mut Transaction) -> Result { self.seek = match whence { SEEK_SET => max(0, offset), SEEK_CUR => max(0, self.seek + offset), - SEEK_END => max(0, size + offset), + SEEK_END => { + let node = tx.read_tree(self.node_ptr)?; + max(0, node.data().size() as isize + offset) + } _ => return Err(Error::new(EINVAL)), }; Ok(self.seek) } - fn fmap(&mut self, map: &Map, fs: &mut FileSystem) -> Result { + fn fmap(&mut self, map: &Map, tx: &mut Transaction) -> Result { let accmode = self.flags & O_ACCMODE; if map.flags.contains(PROT_READ) && !(accmode == O_RDWR || accmode == O_RDONLY) { return Err(Error::new(EBADF)); @@ -370,15 +410,15 @@ impl Resource for FileResource { } //TODO: PROT_EXEC? - let map = unsafe { Fmap::new(self.block, map, fs)? }; + let map = unsafe { Fmap::new(self.node_ptr, map, tx)? }; let address = map.data.as_ptr() as usize; self.fmaps.insert(address, map); Ok(address) } - fn funmap(&mut self, address: usize, fs: &mut FileSystem) -> Result { + fn funmap(&mut self, address: usize, tx: &mut Transaction) -> Result { if let Some(mut fmap) = self.fmaps.remove(&address) { - fmap.sync(fs)?; + fmap.sync(tx)?; Ok(0) } else { @@ -386,40 +426,6 @@ impl Resource for FileResource { } } - fn fchmod(&mut self, mode: u16, fs: &mut FileSystem) -> Result { - let mut node = fs.node(self.block)?; - - if node.1.uid == self.uid || self.uid == 0 { - node.1.mode = (node.1.mode & !MODE_PERM) | (mode & MODE_PERM); - - fs.write_at(node.0, &node.1)?; - - Ok(0) - } else { - Err(Error::new(EPERM)) - } - } - - fn fchown(&mut self, uid: u32, gid: u32, fs: &mut FileSystem) -> Result { - let mut node = fs.node(self.block)?; - - if node.1.uid == self.uid || self.uid == 0 { - if uid as i32 != -1 { - node.1.uid = uid; - } - - if gid as i32 != -1 { - node.1.gid = gid; - } - - fs.write_at(node.0, &node.1)?; - - Ok(0) - } else { - Err(Error::new(EPERM)) - } - } - fn fcntl(&mut self, cmd: usize, arg: usize) -> Result { match cmd { F_GETFL => Ok(self.flags), @@ -431,69 +437,57 @@ impl Resource for FileResource { } } - fn path(&self, buf: &mut [u8]) -> Result { - let path = self.path.as_bytes(); - - let mut i = 0; - while i < buf.len() && i < path.len() { - buf[i] = path[i]; - i += 1; - } - - Ok(i) - } - - fn stat(&self, stat: &mut Stat, fs: &mut FileSystem) -> Result { - let node = fs.node(self.block)?; - - *stat = Stat { - st_dev: 0, // TODO - st_ino: node.0, - st_mode: node.1.mode, - st_nlink: 1, - st_uid: node.1.uid, - st_gid: node.1.gid, - st_size: fs.node_len(self.block)?, - st_mtime: node.1.mtime, - st_mtime_nsec: node.1.mtime_nsec, - st_atime: node.1.atime, - st_atime_nsec: node.1.atime_nsec, - st_ctime: node.1.ctime, - st_ctime_nsec: node.1.ctime_nsec, - ..Default::default() - }; - - Ok(0) + fn path(&self) -> &str { + &self.path } - fn sync(&mut self, fs: &mut FileSystem) -> Result { + fn sync(&mut self, tx: &mut Transaction) -> Result { for fmap in self.fmaps.values_mut() { - fmap.sync(fs)?; + fmap.sync(tx)?; } Ok(0) } - fn truncate(&mut self, len: usize, fs: &mut FileSystem) -> Result { + fn truncate(&mut self, len: usize, tx: &mut Transaction) -> Result { if self.flags & O_ACCMODE == O_RDWR || self.flags & O_ACCMODE == O_WRONLY { - fs.node_set_len(self.block, len as u64)?; + let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + tx.truncate_node( + self.node_ptr, + len as u64, + mtime.as_secs(), + mtime.subsec_nanos(), + )?; Ok(0) } else { Err(Error::new(EBADF)) } } - fn utimens(&mut self, times: &[TimeSpec], fs: &mut FileSystem) -> Result { - let mut node = fs.node(self.block)?; + fn utimens(&mut self, times: &[TimeSpec], tx: &mut Transaction) -> Result { + let mut node = tx.read_tree(self.node_ptr)?; - if node.1.uid == self.uid || self.uid == 0 { + if node.data().uid() == self.uid || self.uid == 0 { if let &[atime, mtime] = times { - node.1.mtime = mtime.tv_sec as u64; - node.1.mtime_nsec = mtime.tv_nsec as u32; - node.1.atime = atime.tv_sec as u64; - node.1.atime_nsec = atime.tv_nsec as u32; - - fs.write_at(node.0, &node.1)?; + let mut node_changed = false; + + let old_mtime = node.data().mtime(); + let new_mtime = (mtime.tv_sec as u64, mtime.tv_nsec as u32); + if old_mtime != new_mtime { + node.data_mut().set_mtime(new_mtime.0, new_mtime.1); + node_changed = true; + } + + let old_atime = node.data().atime(); + let new_atime = (atime.tv_sec as u64, atime.tv_nsec as u32); + if old_atime != new_atime { + node.data_mut().set_atime(new_atime.0, new_atime.1); + node_changed = true; + } + + if node_changed { + tx.sync_tree(node)?; + } } Ok(0) } else { diff --git a/src/mount/redox/scheme.rs b/src/mount/redox/scheme.rs index dd0e6fb..1640fe8 100644 --- a/src/mount/redox/scheme.rs +++ b/src/mount/redox/scheme.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::collections::BTreeMap; use std::str; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -13,62 +12,65 @@ use syscall::flag::{ EventFlags, MODE_PERM, O_ACCMODE, O_CREAT, O_DIRECTORY, O_EXCL, O_NOFOLLOW, O_RDONLY, O_RDWR, O_STAT, O_SYMLINK, O_TRUNC, O_WRONLY, }; -use syscall::scheme::Scheme; +use syscall::scheme::SchemeMut; -use disk::Disk; -use filesystem::FileSystem; -use node::Node; -use BLOCK_SIZE; +use crate::{Disk, FileSystem, Node, Transaction, TreeData, TreePtr, BLOCK_SIZE}; use super::resource::{DirResource, FileResource, Resource}; pub struct FileScheme { name: String, - fs: RefCell>, + fs: FileSystem, next_id: AtomicUsize, - files: RefCell>>>, - fmap: RefCell>, + files: BTreeMap>>, + fmap: BTreeMap, } impl FileScheme { pub fn new(name: String, fs: FileSystem) -> FileScheme { FileScheme { name: name, - fs: RefCell::new(fs), + fs: fs, next_id: AtomicUsize::new(1), - files: RefCell::new(BTreeMap::new()), - fmap: RefCell::new(BTreeMap::new()), + files: BTreeMap::new(), + fmap: BTreeMap::new(), } } fn resolve_symlink( - &self, - fs: &mut FileSystem, + scheme_name: &str, + tx: &mut Transaction, uid: u32, gid: u32, - url: &[u8], - node: (u64, Node), - nodes: &mut Vec<(u64, Node)>, + url: &str, + node: TreeData, + nodes: &mut Vec<(TreeData, String)>, ) -> Result> { + let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let mut node = node; for _ in 0..32 { // XXX What should the limit be? - let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); let mut buf = [0; 4096]; - let count = fs.read_node(node.0, 0, &mut buf, atime.as_secs(), atime.subsec_nanos())?; - let scheme = format!("{}:", &self.name); - let canon = canonicalize( - &format!("{}{}", scheme, str::from_utf8(url).unwrap()).as_bytes(), - &buf[0..count], - ); + let count = tx.read_node( + node.ptr(), + 0, + &mut buf, + atime.as_secs(), + atime.subsec_nanos(), + )?; + let scheme = format!("{}:", scheme_name); + let canon = canonicalize(&format!("{}{}", scheme, url).as_bytes(), &buf[0..count]); let path = str::from_utf8(&canon[scheme.len()..]) .unwrap_or("") .trim_matches('/'); nodes.clear(); - if let Some(next_node) = self.path_nodes(fs, path, uid, gid, nodes)? { - if !next_node.1.is_symlink() { + if let Some((next_node, next_node_name)) = + Self::path_nodes(scheme_name, tx, path, uid, gid, nodes)? + { + if !next_node.data().is_symlink() { if canon.starts_with(scheme.as_bytes()) { - nodes.push(next_node); + nodes.push((next_node, next_node_name)); return Ok(canon[scheme.len()..].to_vec()); } else { return Err(Error::new(EXDEV)); @@ -83,47 +85,51 @@ impl FileScheme { } fn path_nodes( - &self, - fs: &mut FileSystem, + scheme_name: &str, + tx: &mut Transaction, path: &str, uid: u32, gid: u32, - nodes: &mut Vec<(u64, Node)>, - ) -> Result> { + nodes: &mut Vec<(TreeData, String)>, + ) -> Result, String)>> { let mut parts = path.split('/').filter(|part| !part.is_empty()); - let mut part_opt = None; - let mut block = fs.header.1.root; + let mut part_opt: Option<&str> = None; + let mut node_ptr = TreePtr::root(); + let mut node_name = String::new(); loop { let node_res = match part_opt { - None => fs.node(block), - Some(part) => fs.find_node(part, block), + None => tx.read_tree(node_ptr), + Some(part) => { + node_name = part.to_string(); + tx.find_node(node_ptr, part) + } }; part_opt = parts.next(); - if part_opt.is_some() { + if let Some(part) = part_opt { let node = node_res?; - if !node.1.permission(uid, gid, Node::MODE_EXEC) { + if !node.data().permission(uid, gid, Node::MODE_EXEC) { return Err(Error::new(EACCES)); } - if node.1.is_symlink() { - let mut url = Vec::new(); - url.extend_from_slice(self.name.as_bytes()); - url.push(b':'); - for i in nodes.iter() { - url.push(b'/'); - url.extend_from_slice(&i.1.name); + if node.data().is_symlink() { + let mut url = String::new(); + url.push_str(scheme_name); + url.push(':'); + for (_parent, parent_name) in nodes.iter() { + url.push('/'); + url.push_str(&parent_name); } - self.resolve_symlink(fs, uid, gid, &url, node, nodes)?; - block = nodes.last().unwrap().0; - } else if !node.1.is_dir() { + Self::resolve_symlink(scheme_name, tx, uid, gid, &url, node, nodes)?; + node_ptr = nodes.last().unwrap().0.ptr(); + } else if !node.data().is_dir() { return Err(Error::new(ENOTDIR)); } else { - block = node.0; - nodes.push(node); + node_ptr = node.ptr(); + nodes.push((node, part.to_string())); } } else { match node_res { - Ok(node) => return Ok(Some(node)), + Ok(node) => return Ok(Some((node, node_name))), Err(err) => match err.errno { ENOENT => return Ok(None), _ => return Err(err), @@ -204,96 +210,110 @@ pub fn canonicalize(current: &[u8], path: &[u8]) -> Vec { } } -impl Scheme for FileScheme { - fn open(&self, url: &str, flags: usize, uid: u32, gid: u32) -> Result { +impl SchemeMut for FileScheme { + fn open(&mut self, url: &str, flags: usize, uid: u32, gid: u32) -> Result { let path = url.trim_matches('/'); // println!("Open '{}' {:X}", path, flags); - let mut fs = self.fs.borrow_mut(); - + //TODO: try to move things into one transaction + let scheme_name = &self.name; let mut nodes = Vec::new(); - let node_opt = self.path_nodes(&mut fs, path, uid, gid, &mut nodes)?; - let resource: Box> = match node_opt { - Some(node) => { + let node_opt = self + .fs + .tx(|tx| Self::path_nodes(scheme_name, tx, path, uid, gid, &mut nodes))?; + let resource: Box> = match node_opt { + Some((node, _node_name)) => { if flags & (O_CREAT | O_EXCL) == O_CREAT | O_EXCL { return Err(Error::new(EEXIST)); - } else if node.1.is_dir() { + } else if node.data().is_dir() { if flags & O_ACCMODE == O_RDONLY { - if !node.1.permission(uid, gid, Node::MODE_READ) { - // println!("dir not readable {:o}", node.1.mode); + if !node.data().permission(uid, gid, Node::MODE_READ) { + // println!("dir not readable {:o}", node.data().mode); return Err(Error::new(EACCES)); } let mut children = Vec::new(); - fs.child_nodes(&mut children, node.0)?; + self.fs.tx(|tx| tx.child_nodes(node.ptr(), &mut children))?; let mut data = Vec::new(); for child in children.iter() { - if let Ok(name) = child.1.name() { + if let Some(child_name) = child.name() { if !data.is_empty() { data.push(b'\n'); } - data.extend_from_slice(&name.as_bytes()); + data.extend_from_slice(&child_name.as_bytes()); } } - Box::new(DirResource::new(path.to_string(), node.0, Some(data), uid)) + Box::new(DirResource::new( + path.to_string(), + node.ptr(), + Some(data), + uid, + )) } else if flags & O_WRONLY == O_WRONLY { // println!("{:X} & {:X}: EISDIR {}", flags, O_DIRECTORY, path); return Err(Error::new(EISDIR)); } else { - Box::new(DirResource::new(path.to_string(), node.0, None, uid)) + Box::new(DirResource::new(path.to_string(), node.ptr(), None, uid)) } - } else if node.1.is_symlink() + } else if node.data().is_symlink() && !(flags & O_STAT == O_STAT && flags & O_NOFOLLOW == O_NOFOLLOW) && flags & O_SYMLINK != O_SYMLINK { let mut resolve_nodes = Vec::new(); - let resolved = self.resolve_symlink( - &mut fs, - uid, - gid, - url.as_bytes(), - node, - &mut resolve_nodes, - )?; - drop(fs); + let resolved = self.fs.tx(|tx| { + Self::resolve_symlink( + scheme_name, + tx, + uid, + gid, + url, + node, + &mut resolve_nodes, + ) + })?; let resolved_utf8 = str::from_utf8(&resolved).map_err(|_| Error::new(EINVAL))?; return self.open(resolved_utf8, flags, uid, gid); - } else if !node.1.is_symlink() && flags & O_SYMLINK == O_SYMLINK { + } else if !node.data().is_symlink() && flags & O_SYMLINK == O_SYMLINK { return Err(Error::new(EINVAL)); } else { + let node_ptr = node.ptr(); + if flags & O_DIRECTORY == O_DIRECTORY { // println!("{:X} & {:X}: ENOTDIR {}", flags, O_DIRECTORY, path); return Err(Error::new(ENOTDIR)); } if (flags & O_ACCMODE == O_RDONLY || flags & O_ACCMODE == O_RDWR) - && !node.1.permission(uid, gid, Node::MODE_READ) + && !node.data().permission(uid, gid, Node::MODE_READ) { - // println!("file not readable {:o}", node.1.mode); + // println!("file not readable {:o}", node.data().mode); return Err(Error::new(EACCES)); } if (flags & O_ACCMODE == O_WRONLY || flags & O_ACCMODE == O_RDWR) - && !node.1.permission(uid, gid, Node::MODE_WRITE) + && !node.data().permission(uid, gid, Node::MODE_WRITE) { - // println!("file not writable {:o}", node.1.mode); + // println!("file not writable {:o}", node.data().mode); return Err(Error::new(EACCES)); } if flags & O_TRUNC == O_TRUNC { - if !node.1.permission(uid, gid, Node::MODE_WRITE) { - // println!("file not writable {:o}", node.1.mode); + if !node.data().permission(uid, gid, Node::MODE_WRITE) { + // println!("file not writable {:o}", node.data().mode); return Err(Error::new(EACCES)); } - fs.node_set_len(node.0, 0)?; + let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + self.fs.tx(|tx| { + tx.truncate_node(node_ptr, 0, mtime.as_secs(), mtime.subsec_nanos()) + })?; } - Box::new(FileResource::new(path.to_string(), node.0, flags, uid)) + Box::new(FileResource::new(path.to_string(), node_ptr, flags, uid)) } } None => { @@ -305,8 +325,8 @@ impl Scheme for FileScheme { } } if !last_part.is_empty() { - if let Some(parent) = nodes.last() { - if !parent.1.permission(uid, gid, Node::MODE_WRITE) { + if let Some((parent, _parent_name)) = nodes.last() { + if !parent.data().permission(uid, gid, Node::MODE_WRITE) { // println!("dir not writable {:o}", parent.1.mode); return Err(Error::new(EACCES)); } @@ -320,22 +340,28 @@ impl Scheme for FileScheme { Node::MODE_FILE }; - let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - let mut node = fs.create_node( - mode_type | (flags as u16 & Node::MODE_PERM), - &last_part, - parent.0, - ctime.as_secs(), - ctime.subsec_nanos(), - )?; - node.1.uid = uid; - node.1.gid = gid; - fs.write_at(node.0, &node.1)?; + let node_ptr = self.fs.tx(|tx| { + let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let mut node = tx.create_node( + parent.ptr(), + &last_part, + mode_type | (flags as u16 & Node::MODE_PERM), + ctime.as_secs(), + ctime.subsec_nanos(), + )?; + let node_ptr = node.ptr(); + if node.data().uid() != uid || node.data().gid() != gid { + node.data_mut().set_uid(uid); + node.data_mut().set_gid(gid); + tx.sync_tree(node)?; + } + Ok(node_ptr) + })?; if dir { - Box::new(DirResource::new(path.to_string(), node.0, None, uid)) + Box::new(DirResource::new(path.to_string(), node_ptr, None, uid)) } else { - Box::new(FileResource::new(path.to_string(), node.0, flags, uid)) + Box::new(FileResource::new(path.to_string(), node_ptr, flags, uid)) } } else { return Err(Error::new(EPERM)); @@ -350,196 +376,194 @@ impl Scheme for FileScheme { }; let id = self.next_id.fetch_add(1, Ordering::SeqCst); - self.files.borrow_mut().insert(id, resource); + self.files.insert(id, resource); Ok(id) } - fn chmod(&self, url: &str, mode: u16, uid: u32, gid: u32) -> Result { + fn chmod(&mut self, url: &str, mode: u16, uid: u32, gid: u32) -> Result { let path = url.trim_matches('/'); // println!("Chmod '{}'", path); - let mut fs = self.fs.borrow_mut(); + let scheme_name = &self.name; + self.fs.tx(|tx| { + let mut nodes = Vec::new(); + if let Some((mut node, _node_name)) = + Self::path_nodes(scheme_name, tx, path, uid, gid, &mut nodes)? + { + if node.data().uid() == uid || uid == 0 { + let old_mode = node.data().mode(); + let new_mode = (old_mode & !MODE_PERM) | (mode & MODE_PERM); + if old_mode != new_mode { + node.data_mut().set_mode(new_mode); + tx.sync_tree(node)?; + } - let mut nodes = Vec::new(); - if let Some(mut node) = self.path_nodes(&mut fs, path, uid, gid, &mut nodes)? { - if node.1.uid == uid || uid == 0 { - node.1.mode = (node.1.mode & !MODE_PERM) | (mode & MODE_PERM); - fs.write_at(node.0, &node.1)?; - Ok(0) + Ok(0) + } else { + Err(Error::new(EPERM)) + } } else { - Err(Error::new(EPERM)) + Err(Error::new(ENOENT)) } - } else { - Err(Error::new(ENOENT)) - } + }) } - fn rmdir(&self, url: &str, uid: u32, gid: u32) -> Result { + fn rmdir(&mut self, url: &str, uid: u32, gid: u32) -> Result { let path = url.trim_matches('/'); // println!("Rmdir '{}'", path); - let mut fs = self.fs.borrow_mut(); - - let mut nodes = Vec::new(); - if let Some(child) = self.path_nodes(&mut fs, path, uid, gid, &mut nodes)? { - if let Some(parent) = nodes.last() { - if !parent.1.permission(uid, gid, Node::MODE_WRITE) { - // println!("dir not writable {:o}", parent.1.mode); - return Err(Error::new(EACCES)); - } - - if child.1.is_dir() { - if !child.1.permission(uid, gid, Node::MODE_WRITE) { + let scheme_name = &self.name; + self.fs.tx(|tx| { + let mut nodes = Vec::new(); + if let Some((child, child_name)) = + Self::path_nodes(scheme_name, tx, path, uid, gid, &mut nodes)? + { + if let Some((parent, _parent_name)) = nodes.last() { + if !parent.data().permission(uid, gid, Node::MODE_WRITE) { // println!("dir not writable {:o}", parent.1.mode); return Err(Error::new(EACCES)); } - if let Ok(child_name) = child.1.name() { - fs.remove_node(Node::MODE_DIR, child_name, parent.0) + if child.data().is_dir() { + if !child.data().permission(uid, gid, Node::MODE_WRITE) { + // println!("dir not writable {:o}", parent.1.mode); + return Err(Error::new(EACCES)); + } + + tx.remove_node(parent.ptr(), &child_name, Node::MODE_DIR) .and(Ok(0)) } else { - Err(Error::new(ENOENT)) + Err(Error::new(ENOTDIR)) } } else { - Err(Error::new(ENOTDIR)) + Err(Error::new(EPERM)) } } else { - Err(Error::new(EPERM)) + Err(Error::new(ENOENT)) } - } else { - Err(Error::new(ENOENT)) - } + }) } - fn unlink(&self, url: &str, uid: u32, gid: u32) -> Result { + fn unlink(&mut self, url: &str, uid: u32, gid: u32) -> Result { let path = url.trim_matches('/'); // println!("Unlink '{}'", path); - let mut fs = self.fs.borrow_mut(); - - let mut nodes = Vec::new(); - if let Some(child) = self.path_nodes(&mut fs, path, uid, gid, &mut nodes)? { - if let Some(parent) = nodes.last() { - if !parent.1.permission(uid, gid, Node::MODE_WRITE) { - // println!("dir not writable {:o}", parent.1.mode); - return Err(Error::new(EACCES)); - } - - if !child.1.is_dir() { - if child.1.uid != uid { - // println!("file not owned by current user {}", parent.1.uid); + let scheme_name = &self.name; + self.fs.tx(|tx| { + let mut nodes = Vec::new(); + if let Some((child, child_name)) = + Self::path_nodes(scheme_name, tx, path, uid, gid, &mut nodes)? + { + if let Some((parent, _parent_name)) = nodes.last() { + if !parent.data().permission(uid, gid, Node::MODE_WRITE) { + // println!("dir not writable {:o}", parent.1.mode); return Err(Error::new(EACCES)); } - if let Ok(child_name) = child.1.name() { - if child.1.is_symlink() { - fs.remove_node(Node::MODE_SYMLINK, child_name, parent.0) + if !child.data().is_dir() { + if child.data().uid() != uid { + // println!("file not owned by current user {}", parent.1.uid); + return Err(Error::new(EACCES)); + } + + if child.data().is_symlink() { + tx.remove_node(parent.ptr(), &child_name, Node::MODE_SYMLINK) .and(Ok(0)) } else { - fs.remove_node(Node::MODE_FILE, child_name, parent.0) + tx.remove_node(parent.ptr(), &child_name, Node::MODE_FILE) .and(Ok(0)) } } else { - Err(Error::new(ENOENT)) + Err(Error::new(EISDIR)) } } else { - Err(Error::new(EISDIR)) + Err(Error::new(EPERM)) } } else { - Err(Error::new(EPERM)) + Err(Error::new(ENOENT)) } - } else { - Err(Error::new(ENOENT)) - } + }) } /* Resource operations */ #[allow(unused_variables)] - fn dup(&self, old_id: usize, buf: &[u8]) -> Result { + fn dup(&mut self, old_id: usize, buf: &[u8]) -> Result { // println!("Dup {}", old_id); if !buf.is_empty() { return Err(Error::new(EINVAL)); } - let mut files = self.files.borrow_mut(); - let resource = if let Some(old_resource) = files.get(&old_id) { + let resource = if let Some(old_resource) = self.files.get(&old_id) { old_resource.dup()? } else { return Err(Error::new(EBADF)); }; let id = self.next_id.fetch_add(1, Ordering::SeqCst); - files.insert(id, resource); + self.files.insert(id, resource); Ok(id) } #[allow(unused_variables)] - fn read(&self, id: usize, buf: &mut [u8]) -> Result { + fn read(&mut self, id: usize, buf: &mut [u8]) -> Result { // println!("Read {}, {:X} {}", id, buf.as_ptr() as usize, buf.len()); - let mut files = self.files.borrow_mut(); - if let Some(file) = files.get_mut(&id) { - file.read(buf, &mut self.fs.borrow_mut()) + if let Some(file) = self.files.get_mut(&id) { + self.fs.tx(|tx| file.read(buf, tx)) } else { Err(Error::new(EBADF)) } } - fn write(&self, id: usize, buf: &[u8]) -> Result { + fn write(&mut self, id: usize, buf: &[u8]) -> Result { // println!("Write {}, {:X} {}", id, buf.as_ptr() as usize, buf.len()); - let mut files = self.files.borrow_mut(); - if let Some(file) = files.get_mut(&id) { - file.write(buf, &mut self.fs.borrow_mut()) + if let Some(file) = self.files.get_mut(&id) { + self.fs.tx(|tx| file.write(buf, tx)) } else { Err(Error::new(EBADF)) } } - fn seek(&self, id: usize, pos: isize, whence: usize) -> Result { + fn seek(&mut self, id: usize, pos: isize, whence: usize) -> Result { // println!("Seek {}, {} {}", id, pos, whence); - let mut files = self.files.borrow_mut(); - if let Some(file) = files.get_mut(&id) { - file.seek(pos, whence, &mut self.fs.borrow_mut()) + if let Some(file) = self.files.get_mut(&id) { + self.fs.tx(|tx| file.seek(pos, whence, tx)) } else { Err(Error::new(EBADF)) } } - fn fchmod(&self, id: usize, mode: u16) -> Result { - let mut files = self.files.borrow_mut(); - if let Some(file) = files.get_mut(&id) { - file.fchmod(mode, &mut self.fs.borrow_mut()) + fn fchmod(&mut self, id: usize, mode: u16) -> Result { + if let Some(file) = self.files.get_mut(&id) { + self.fs.tx(|tx| file.fchmod(mode, tx)) } else { Err(Error::new(EBADF)) } } - fn fchown(&self, id: usize, uid: u32, gid: u32) -> Result { - let mut files = self.files.borrow_mut(); - if let Some(file) = files.get_mut(&id) { - file.fchown(uid, gid, &mut self.fs.borrow_mut()) + fn fchown(&mut self, id: usize, uid: u32, gid: u32) -> Result { + if let Some(file) = self.files.get_mut(&id) { + self.fs.tx(|tx| file.fchown(uid, gid, tx)) } else { Err(Error::new(EBADF)) } } - fn fcntl(&self, id: usize, cmd: usize, arg: usize) -> Result { - let mut files = self.files.borrow_mut(); - if let Some(file) = files.get_mut(&id) { + fn fcntl(&mut self, id: usize, cmd: usize, arg: usize) -> Result { + if let Some(file) = self.files.get_mut(&id) { file.fcntl(cmd, arg) } else { Err(Error::new(EBADF)) } } - fn fevent(&self, id: usize, _flags: EventFlags) -> Result { - let files = self.files.borrow_mut(); - if let Some(file) = files.get(&id) { + fn fevent(&mut self, id: usize, _flags: EventFlags) -> Result { + if let Some(_file) = self.files.get(&id) { // EPERM is returned for files that are always readable or writable Err(Error::new(EPERM)) } else { @@ -547,10 +571,9 @@ impl Scheme for FileScheme { } } - fn fpath(&self, id: usize, buf: &mut [u8]) -> Result { + fn fpath(&mut self, id: usize, buf: &mut [u8]) -> Result { // println!("Fpath {}, {:X} {}", id, buf.as_ptr() as usize, buf.len()); - let files = self.files.borrow_mut(); - if let Some(file) = files.get(&id) { + if let Some(file) = self.files.get(&id) { let name = self.name.as_bytes(); let mut i = 0; @@ -567,13 +590,23 @@ impl Scheme for FileScheme { i += 1; } - file.path(&mut buf[i..]).map(|count| i + count) + let path = file.path().as_bytes(); + let mut j = 0; + while i < buf.len() && j < path.len() { + buf[i] = path[j]; + i += 1; + j += 1; + } + + Ok(i) } else { Err(Error::new(EBADF)) } } - fn frename(&self, id: usize, url: &str, uid: u32, gid: u32) -> Result { + fn frename(&mut self, id: usize, url: &str, uid: u32, gid: u32) -> Result { + unimplemented!(); + /*TODO: FRENAME let path = url.trim_matches('/'); // println!("Frename {}, {} from {}, {}", id, path, uid, gid); @@ -613,19 +646,19 @@ impl Scheme for FileScheme { } if let Some(ref node) = node_opt { - if !node.1.owner(uid) { + if !node.data().owner(uid) { // println!("new dir not owned by caller {}", uid); return Err(Error::new(EACCES)); } - if node.1.is_dir() { + if node.data().is_dir() { if !orig.1.is_dir() { // println!("orig is file, new is dir"); return Err(Error::new(EACCES)); } let mut children = Vec::new(); - fs.child_nodes(&mut children, node.0)?; + fs.child_nodes(node.ptr(), &mut children)?; if !children.is_empty() { // println!("new dir not empty"); @@ -670,29 +703,23 @@ impl Scheme for FileScheme { } else { Err(Error::new(EBADF)) } + */ } - fn fstat(&self, id: usize, stat: &mut Stat) -> Result { + fn fstat(&mut self, id: usize, stat: &mut Stat) -> Result { // println!("Fstat {}, {:X}", id, stat as *mut Stat as usize); - let files = self.files.borrow_mut(); - if let Some(file) = files.get(&id) { - file.stat(stat, &mut self.fs.borrow_mut()) + if let Some(file) = self.files.get(&id) { + self.fs.tx(|tx| file.stat(stat, tx)) } else { Err(Error::new(EBADF)) } } - fn fstatvfs(&self, id: usize, stat: &mut StatVfs) -> Result { - let files = self.files.borrow_mut(); - if let Some(_file) = files.get(&id) { - let mut fs = self.fs.borrow_mut(); - - let free = fs.header.1.free; - let free_size = fs.node_len(free)?; - + fn fstatvfs(&mut self, id: usize, stat: &mut StatVfs) -> Result { + if let Some(_file) = self.files.get(&id) { stat.f_bsize = BLOCK_SIZE as u32; - stat.f_blocks = fs.header.1.size / (stat.f_bsize as u64); - stat.f_bfree = free_size / (stat.f_bsize as u64); + stat.f_blocks = self.fs.header.size() / (stat.f_bsize as u64); + stat.f_bfree = self.fs.allocator().free(); stat.f_bavail = stat.f_bfree; Ok(0) @@ -701,53 +728,48 @@ impl Scheme for FileScheme { } } - fn fsync(&self, id: usize) -> Result { + fn fsync(&mut self, id: usize) -> Result { // println!("Fsync {}", id); - let mut files = self.files.borrow_mut(); - if let Some(file) = files.get_mut(&id) { - file.sync(&mut self.fs.borrow_mut()) + if let Some(file) = self.files.get_mut(&id) { + self.fs.tx(|tx| file.sync(tx)) } else { Err(Error::new(EBADF)) } } - fn ftruncate(&self, id: usize, len: usize) -> Result { + fn ftruncate(&mut self, id: usize, len: usize) -> Result { // println!("Ftruncate {}, {}", id, len); - let mut files = self.files.borrow_mut(); - if let Some(file) = files.get_mut(&id) { - file.truncate(len, &mut self.fs.borrow_mut()) + if let Some(file) = self.files.get_mut(&id) { + self.fs.tx(|tx| file.truncate(len, tx)) } else { Err(Error::new(EBADF)) } } - fn futimens(&self, id: usize, times: &[TimeSpec]) -> Result { + fn futimens(&mut self, id: usize, times: &[TimeSpec]) -> Result { // println!("Futimens {}, {}", id, times.len()); - let mut files = self.files.borrow_mut(); - if let Some(file) = files.get_mut(&id) { - file.utimens(times, &mut self.fs.borrow_mut()) + if let Some(file) = self.files.get_mut(&id) { + self.fs.tx(|tx| file.utimens(times, tx)) } else { Err(Error::new(EBADF)) } } - fn fmap(&self, id: usize, map: &Map) -> Result { + fn fmap(&mut self, id: usize, map: &Map) -> Result { // println!("Fmap {}, {:?}", id, map); - let mut files = self.files.borrow_mut(); - if let Some(file) = files.get_mut(&id) { - let address = file.fmap(map, &mut self.fs.borrow_mut())?; - self.fmap.borrow_mut().insert(address, id); + if let Some(file) = self.files.get_mut(&id) { + let address = self.fs.tx(|tx| file.fmap(map, tx))?; + self.fmap.insert(address, id); Ok(address) } else { Err(Error::new(EBADF)) } } - fn funmap_old(&self, address: usize) -> Result { - if let Some(id) = self.fmap.borrow_mut().remove(&address) { - let mut files = self.files.borrow_mut(); - if let Some(file) = files.get_mut(&id) { - file.funmap(address, &mut self.fs.borrow_mut()) + fn funmap_old(&mut self, address: usize) -> Result { + if let Some(id) = self.fmap.remove(&address) { + if let Some(file) = self.files.get_mut(&id) { + self.fs.tx(|tx| file.funmap(address, tx)) } else { Err(Error::new(EINVAL)) } @@ -756,16 +778,23 @@ impl Scheme for FileScheme { } } - //TODO: implement - fn funmap(&self, address: usize, length: usize) -> Result { + //TODO: implement (length is ignored!) + fn funmap(&mut self, address: usize, length: usize) -> Result { println!("redoxfs: funmap 0x{:X}, {}", address, length); - Err(Error::new(ENOSYS)) + if let Some(id) = self.fmap.remove(&address) { + if let Some(file) = self.files.get_mut(&id) { + self.fs.tx(|tx| file.funmap(address, tx)) + } else { + Err(Error::new(EINVAL)) + } + } else { + Err(Error::new(EINVAL)) + } } - fn close(&self, id: usize) -> Result { + fn close(&mut self, id: usize) -> Result { // println!("Close {}", id); - let mut files = self.files.borrow_mut(); - if files.remove(&id).is_some() { + if self.files.remove(&id).is_some() { Ok(0) } else { Err(Error::new(EBADF)) diff --git a/src/node.rs b/src/node.rs index 11e9025..20d22aa 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,26 +1,133 @@ -use alloc::vec::Vec; -use core::{fmt, mem, ops, slice, str}; +use core::{fmt, mem, ops, slice}; +use simple_endian::*; -use super::Extent; -use syscall; -use BLOCK_SIZE; +use crate::{BlockList, BlockPtr, BlockRaw}; + +pub enum NodeLevel { + L0(usize), + L1(usize, usize), + L2(usize, usize, usize), + L3(usize, usize, usize, usize), + L4(usize, usize, usize, usize, usize), +} + +impl NodeLevel { + // Warning: this uses constant block offsets, make sure to sync with Node + pub fn new(mut block_offset: u64) -> Option { + // 1 << 8 = 256, this is the number of entries in a BlockList + const SHIFT: u64 = 8; + const NUM: u64 = 1 << SHIFT; + const MASK: u64 = NUM - 1; + + const L0: u64 = 128; + if block_offset < L0 { + return Some(Self::L0((block_offset & MASK) as usize)); + } else { + block_offset -= L0; + } + + const L1: u64 = 64 * NUM; + if block_offset < L1 { + return Some(Self::L1( + ((block_offset >> SHIFT) & MASK) as usize, + (block_offset & MASK) as usize, + )); + } else { + block_offset -= L1; + } + + const L2: u64 = 32 * NUM * NUM; + if block_offset < L2 { + return Some(Self::L2( + ((block_offset >> (2 * SHIFT)) & MASK) as usize, + ((block_offset >> SHIFT) & MASK) as usize, + (block_offset & MASK) as usize, + )); + } else { + block_offset -= L2; + } + + const L3: u64 = 16 * NUM * NUM * NUM; + if block_offset < L3 { + return Some(Self::L3( + ((block_offset >> (3 * SHIFT)) & MASK) as usize, + ((block_offset >> (2 * SHIFT)) & MASK) as usize, + ((block_offset >> SHIFT) & MASK) as usize, + (block_offset & MASK) as usize, + )); + } else { + block_offset -= L3; + } + + const L4: u64 = 12 * NUM * NUM * NUM * NUM; + if block_offset < L4 { + Some(Self::L4( + ((block_offset >> (4 * SHIFT)) & MASK) as usize, + ((block_offset >> (3 * SHIFT)) & MASK) as usize, + ((block_offset >> (2 * SHIFT)) & MASK) as usize, + ((block_offset >> SHIFT) & MASK) as usize, + (block_offset & MASK) as usize, + )) + } else { + None + } + } +} + +type BlockListL1 = BlockList; +type BlockListL2 = BlockList; +type BlockListL3 = BlockList; +type BlockListL4 = BlockList; /// A file/folder node #[repr(packed)] pub struct Node { - pub mode: u16, - pub uid: u32, - pub gid: u32, - pub ctime: u64, - pub ctime_nsec: u32, - pub mtime: u64, - pub mtime_nsec: u32, - pub atime: u64, - pub atime_nsec: u32, - pub name: [u8; 226], - pub parent: u64, - pub next: u64, - pub extents: [Extent; (BLOCK_SIZE as usize - 288) / 16], + pub mode: u16le, + pub uid: u32le, + pub gid: u32le, + pub links: u32le, + pub size: u64le, + pub ctime: u64le, + pub ctime_nsec: u32le, + pub mtime: u64le, + pub mtime_nsec: u32le, + pub atime: u64le, + pub atime_nsec: u32le, + pub padding: [u8; 6], + // 128 * BLOCK_SIZE (512 KiB, 4 KiB each) + pub level0: [BlockPtr; 128], + // 64 * 256 * BLOCK_SIZE (64 MiB, 1 MiB each) + pub level1: [BlockPtr; 64], + // 32 * 256 * 256 * BLOCK_SIZE (8 GiB, 256 MiB each) + pub level2: [BlockPtr; 32], + // 16 * 256 * 256 * 256 * BLOCK_SIZE (1 TiB, 64 GiB each) + pub level3: [BlockPtr; 16], + // 12 * 256 * 256 * 256 * 256 * BLOCK_SIZE (192 TiB, 16 TiB each) + pub level4: [BlockPtr; 12], +} + +impl Default for Node { + fn default() -> Self { + Self { + mode: 0.into(), + uid: 0.into(), + gid: 0.into(), + links: 0.into(), + size: 0.into(), + ctime: 0.into(), + ctime_nsec: 0.into(), + mtime: 0.into(), + mtime_nsec: 0.into(), + atime: 0.into(), + atime_nsec: 0.into(), + padding: [0; 6], + level0: [BlockPtr::default(); 128], + level1: [BlockPtr::default(); 64], + level2: [BlockPtr::default(); 32], + level3: [BlockPtr::default(); 16], + level4: [BlockPtr::default(); 12], + } + } } impl Node { @@ -34,112 +141,113 @@ impl Node { pub const MODE_WRITE: u16 = 0o2; pub const MODE_READ: u16 = 0o4; - pub fn default() -> Node { - Node { - mode: 0, - uid: 0, - gid: 0, - ctime: 0, - ctime_nsec: 0, - mtime: 0, - mtime_nsec: 0, - atime: 0, - atime_nsec: 0, - name: [0; 226], - parent: 0, - next: 0, - extents: [Extent::default(); (BLOCK_SIZE as usize - 288) / 16], + pub fn new(mode: u16, uid: u32, gid: u32, ctime: u64, ctime_nsec: u32) -> Self { + Self { + mode: mode.into(), + uid: uid.into(), + gid: gid.into(), + links: 0.into(), + ctime: ctime.into(), + ctime_nsec: ctime_nsec.into(), + mtime: ctime.into(), + mtime_nsec: ctime_nsec.into(), + atime: ctime.into(), + atime_nsec: ctime_nsec.into(), + ..Default::default() } } - pub fn new( - mode: u16, - name: &str, - parent: u64, - ctime: u64, - ctime_nsec: u32, - ) -> syscall::Result { - let mut bytes = [0; 226]; - if name.len() > bytes.len() { - return Err(syscall::Error::new(syscall::ENAMETOOLONG)); - } - for (b, c) in bytes.iter_mut().zip(name.bytes()) { - *b = c; - } + pub fn mode(&self) -> u16 { + { self.mode }.to_native() + } - Ok(Node { - mode: mode, - uid: 0, - gid: 0, - ctime: ctime, - ctime_nsec: ctime_nsec, - mtime: ctime, - mtime_nsec: ctime_nsec, - atime: ctime, - atime_nsec: ctime_nsec, - name: bytes, - parent: parent, - next: 0, - extents: [Extent::default(); (BLOCK_SIZE as usize - 288) / 16], - }) - } - - pub fn name(&self) -> Result<&str, str::Utf8Error> { - let mut len = 0; - - for &b in self.name.iter() { - if b == 0 { - break; - } - len += 1; - } + pub fn uid(&self) -> u32 { + { self.uid }.to_native() + } - str::from_utf8(&self.name[..len]) + pub fn gid(&self) -> u32 { + { self.gid }.to_native() } - pub fn set_name(&mut self, name: &str) -> syscall::Result<()> { - let mut bytes = [0; 226]; - if name.len() > bytes.len() { - return Err(syscall::Error::new(syscall::ENAMETOOLONG)); - } - for (b, c) in bytes.iter_mut().zip(name.bytes()) { - *b = c; - } + pub fn links(&self) -> u32 { + { self.links }.to_native() + } - self.name = bytes; + pub fn size(&self) -> u64 { + { self.size }.to_native() + } - Ok(()) + pub fn ctime(&self) -> (u64, u32) { + ({ self.ctime }.to_native(), { self.ctime_nsec }.to_native()) + } + + pub fn mtime(&self) -> (u64, u32) { + ({ self.mtime }.to_native(), { self.mtime_nsec }.to_native()) + } + + pub fn atime(&self) -> (u64, u32) { + ({ self.atime }.to_native(), { self.atime_nsec }.to_native()) + } + + pub fn set_mode(&mut self, mode: u16) { + self.mode = mode.into(); + } + + pub fn set_uid(&mut self, uid: u32) { + self.uid = uid.into(); + } + + pub fn set_gid(&mut self, gid: u32) { + self.gid = gid.into(); + } + + pub fn set_links(&mut self, links: u32) { + self.links = links.into(); + } + + pub fn set_size(&mut self, size: u64) { + self.size = size.into(); + } + + pub fn set_mtime(&mut self, mtime: u64, mtime_nsec: u32) { + self.mtime = mtime.into(); + self.mtime_nsec = mtime_nsec.into(); + } + + pub fn set_atime(&mut self, atime: u64, atime_nsec: u32) { + self.atime = atime.into(); + self.atime_nsec = atime_nsec.into(); } pub fn is_dir(&self) -> bool { - self.mode & Node::MODE_TYPE == Node::MODE_DIR + self.mode() & Self::MODE_TYPE == Self::MODE_DIR } pub fn is_file(&self) -> bool { - self.mode & Node::MODE_TYPE == Node::MODE_FILE + self.mode() & Self::MODE_TYPE == Self::MODE_FILE } pub fn is_symlink(&self) -> bool { - self.mode & Node::MODE_TYPE == Node::MODE_SYMLINK + self.mode() & Self::MODE_TYPE == Self::MODE_SYMLINK } /// Tests if UID is the owner of that file, only true when uid=0 or when the UID stored in metadata is equal to the UID you supply pub fn owner(&self, uid: u32) -> bool { - uid == 0 || self.uid == uid + uid == 0 || self.uid() == uid } /// Tests if the current user has enough permissions to view the file, op is the operation, /// like read and write, these modes are MODE_EXEC, MODE_READ, and MODE_WRITE pub fn permission(&self, uid: u32, gid: u32, op: u16) -> bool { - let mut perm = self.mode & 0o7; - if self.uid == uid { + let mut perm = self.mode() & 0o7; + if self.uid() == uid { // If self.mode is 101100110, >> 6 would be 000000101 // 0o7 is octal for 111, or, when expanded to 9 digits is 000000111 - perm |= (self.mode >> 6) & 0o7; + perm |= (self.mode() >> 6) & 0o7; // Since we erased the GID and OTHER bits when >>6'ing, |= will keep those bits in place. } - if self.gid == gid || gid == 0 { - perm |= (self.mode >> 3) & 0o7; + if self.gid() == gid || gid == 0 { + perm |= (self.mode() >> 3) & 0o7; } if uid == 0 { //set the `other` bits to 111 @@ -147,12 +255,6 @@ impl Node { } perm & op == op } - - pub fn size(&self) -> u64 { - self.extents - .iter() - .fold(0, |size, extent| size + extent.length) - } } impl fmt::Debug for Node { @@ -160,28 +262,27 @@ impl fmt::Debug for Node { let mode = self.mode; let uid = self.uid; let gid = self.gid; + let links = self.links; + let size = self.size; let ctime = self.ctime; let ctime_nsec = self.ctime_nsec; let mtime = self.mtime; let mtime_nsec = self.mtime_nsec; - let name = self.name(); - let next = self.next; - let extents: Vec<&Extent> = self - .extents - .iter() - .filter(|extent| -> bool { extent.length > 0 }) - .collect(); + let atime = self.atime; + let atime_nsec = self.atime_nsec; f.debug_struct("Node") .field("mode", &mode) .field("uid", &uid) .field("gid", &gid) + .field("links", &links) + .field("size", &size) .field("ctime", &ctime) .field("ctime_nsec", &ctime_nsec) .field("mtime", &mtime) .field("mtime_nsec", &mtime_nsec) - .field("name", &name) - .field("next", &next) - .field("extents", &extents) + .field("atime", &atime) + .field("atime_nsec", &atime_nsec) + //TODO: level0/1/2/3 .finish() } } @@ -207,5 +308,5 @@ impl ops::DerefMut for Node { #[test] fn node_size_test() { - assert_eq!(mem::size_of::(), BLOCK_SIZE as usize); + assert_eq!(mem::size_of::(), crate::BLOCK_SIZE as usize); } diff --git a/src/tests.rs b/src/tests.rs index 617d7bf..eff1536 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -14,7 +14,7 @@ where let mount_path = "image"; let res = { - let disk = DiskSparse::create(dbg!(disk_path)).unwrap(); + let disk = DiskSparse::create(dbg!(disk_path), 1024 * 1024 * 1024).unwrap(); if cfg!(not(target_os = "redox")) { if !Path::new(mount_path).exists() { @@ -23,8 +23,7 @@ where } let ctime = dbg!(time::SystemTime::now().duration_since(time::UNIX_EPOCH)).unwrap(); - let fs = - FileSystem::create_reserved(disk, &[], ctime.as_secs(), ctime.subsec_nanos()).unwrap(); + let fs = FileSystem::create(disk, None, ctime.as_secs(), ctime.subsec_nanos()).unwrap(); let callback_mutex = sync::Arc::new(sync::Mutex::new(callback)); let join_handle = crate::mount(fs, dbg!(mount_path), move |real_path| { @@ -40,6 +39,10 @@ where if cfg!(target_os = "redox") { dbg!(fs::remove_file(dbg!(format!(":{}", mount_path)))).unwrap(); } else { + if !dbg!(Command::new("sync").status()).unwrap().success() { + panic!("sync failed"); + } + let status_res = if cfg!(target_os = "linux") { Command::new("fusermount") .arg("-u") @@ -82,7 +85,6 @@ fn simple() { #[cfg(target_os = "redox")] #[test] fn mmap() { - use std::os::unix::ffi::OsStrExt; use syscall; //TODO diff --git a/src/transaction.rs b/src/transaction.rs new file mode 100644 index 0000000..6090906 --- /dev/null +++ b/src/transaction.rs @@ -0,0 +1,1015 @@ +use alloc::{ + collections::{BTreeMap, VecDeque}, + vec::Vec, +}; +use core::{ + cmp::min, + mem, + ops::{Deref, DerefMut}, +}; +use syscall::error::{ + Error, Result, EEXIST, EINVAL, EIO, EISDIR, ENOENT, ENOSPC, ENOTDIR, ENOTEMPTY, ERANGE, +}; + +use crate::{ + AllocEntry, AllocList, Allocator, BlockData, BlockPtr, BlockRaw, DirEntry, DirList, Disk, + FileSystem, Header, Node, NodeLevel, TreeData, TreePtr, ALLOC_LIST_ENTRIES, BLOCK_SIZE, + HEADER_RING, +}; + +pub struct Transaction<'a, D: Disk> { + fs: &'a mut FileSystem, + //TODO: make private + pub header: Header, + //TODO: make private + pub header_changed: bool, + allocator: Allocator, + allocator_log: VecDeque, + deallocate: Vec, + write_cache: BTreeMap, +} + +impl<'a, D: Disk> Transaction<'a, D> { + pub(crate) fn new(fs: &'a mut FileSystem) -> Self { + let header = fs.header.clone(); + let allocator = fs.allocator.clone(); + Self { + fs, + header, + header_changed: false, + allocator, + allocator_log: VecDeque::new(), + deallocate: Vec::new(), + write_cache: BTreeMap::new(), + } + } + + pub fn commit(mut self, squash: bool) -> Result<()> { + self.sync(squash)?; + self.fs.header = self.header; + self.fs.allocator = self.allocator; + Ok(()) + } + + // Unsafe because order must be done carefully and changes must be flushed to disk + unsafe fn allocate(&mut self) -> Result { + match self.allocator.allocate() { + Some(addr) => { + self.allocator_log.push_back(AllocEntry::new(addr, -1)); + Ok(addr) + } + None => Err(Error::new(ENOSPC)), + } + } + + // Unsafe because order must be done carefully and changes must be flushed to disk + unsafe fn deallocate(&mut self, addr: u64) { + //TODO: should we use some sort of not-null abstraction? + assert!(addr != 0); + + // Remove from write_cache if it is there, since it no longer needs to be written + self.write_cache.remove(&addr); + + // Search and remove the last matching entry in allocator_log + let mut found = false; + for i in (0..self.allocator_log.len()).rev() { + let entry = self.allocator_log[i]; + if entry.addr() == addr && entry.count() == -1 { + found = true; + self.allocator_log.remove(i); + break; + } + } + + if found { + // Deallocate immediately since it is an allocation that was not needed + self.allocator.deallocate(addr); + } else { + // Deallocate later when syncing filesystem, to avoid re-use + self.deallocate.push(addr); + } + } + + fn deallocate_block(&mut self, ptr: BlockPtr) { + if !ptr.is_null() { + unsafe { + self.deallocate(ptr.addr()); + } + } + } + + fn sync_allocator(&mut self, squash: bool) -> Result { + let mut prev_ptr = BlockPtr::default(); + if squash { + // Clear and rebuild alloc log + self.allocator_log.clear(); + let levels = self.allocator.levels(); + for level in (0..levels.len()).rev() { + let count = (1 << level) as i64; + 'addrs: for &addr in levels[level].iter() { + for entry in self.allocator_log.iter_mut() { + if addr + count as u64 == entry.addr() { + // New entry is at start of existing entry + *entry = AllocEntry::new(addr, count + entry.count()); + continue 'addrs; + } else if entry.addr() + entry.count() as u64 == addr { + // New entry is at end of existing entry + *entry = AllocEntry::new(entry.addr(), entry.count() + count); + continue 'addrs; + } + } + + self.allocator_log.push_back(AllocEntry::new(addr, count)); + } + } + + // Prepare to deallocate old alloc blocks + let mut alloc_ptr = self.header.alloc; + while !alloc_ptr.is_null() { + let alloc = self.read_block(alloc_ptr)?; + self.deallocate_block(alloc_ptr); + alloc_ptr = alloc.data().prev; + } + } else { + // Return if there are no log changes + if self.allocator_log.is_empty() && self.deallocate.is_empty() { + return Ok(false); + } + + // Push old alloc block to front of allocator log + //TODO: just skip this if it is already full? + let alloc = self.read_block(self.header.alloc)?; + for i in (0..alloc.data().entries.len()).rev() { + let entry = alloc.data().entries[i]; + if !entry.is_null() { + self.allocator_log.push_front(entry); + } + } + + // Prepare to deallocate old alloc block + unsafe { + self.deallocate(alloc.addr()); + } + + // Link to previous alloc block + prev_ptr = alloc.data().prev; + } + + // Allocate required blocks, including CoW of current alloc tail + let mut new_blocks = Vec::new(); + while new_blocks.len() * ALLOC_LIST_ENTRIES + <= self.allocator_log.len() + self.deallocate.len() + { + new_blocks.push(unsafe { self.allocate()? }); + } + + // De-allocate old blocks (after allocation to prevent re-use) + //TODO: optimize allocator log in memory + while let Some(addr) = self.deallocate.pop() { + self.allocator.deallocate(addr); + self.allocator_log.push_back(AllocEntry::new(addr, 1)); + } + + for new_block in new_blocks { + let mut alloc = BlockData::new(new_block, AllocList::default()); + alloc.data_mut().prev = prev_ptr; + for entry in alloc.data_mut().entries.iter_mut() { + if let Some(log_entry) = self.allocator_log.pop_front() { + *entry = log_entry; + } else { + break; + } + } + prev_ptr = unsafe { self.write_block(alloc)? }; + } + + self.header.alloc = prev_ptr; + self.header_changed = true; + + Ok(true) + } + + //TODO: change this function, provide another way to squash, only write header in commit + pub fn sync(&mut self, squash: bool) -> Result { + // Make sure alloc is synced + self.sync_allocator(squash)?; + + // Write all items in write cache + for (addr, raw) in self.write_cache.iter_mut() { + assert!(self.header_changed); + self.fs.encrypt(raw); + let count = unsafe { self.fs.disk.write_at(self.fs.block + addr, &raw)? }; + if count != mem::size_of::() { + // Read wrong number of bytes + log::error!("SYNC WRITE_CACHE: WRONG NUMBER OF BYTES"); + return Err(Error::new(EIO)); + } + } + self.write_cache.clear(); + + if !self.header_changed { + return Ok(false); + } + + // Update header to next generation + let gen = self.header.update(self.fs.aes_opt.as_ref()); + let gen_block = gen % HEADER_RING; + + // Write header + let count = unsafe { + self.fs + .disk + .write_at(self.fs.block + gen_block, &self.header)? + }; + if count != mem::size_of_val(&self.header) { + // Read wrong number of bytes + log::error!("SYNC: WRONG NUMBER OF BYTES"); + return Err(Error::new(EIO)); + } + + self.header_changed = false; + Ok(true) + } + + pub fn read_block>( + &mut self, + ptr: BlockPtr, + ) -> Result> { + if ptr.is_null() { + // Pointer is invalid (should this return None?) + log::error!("READ_BLOCK: POINTER IS NULL"); + return Err(Error::new(ENOENT)); + } + + let mut data = T::default(); + if let Some(raw) = self.write_cache.get(&ptr.addr()) { + data.copy_from_slice(raw); + } else { + let count = unsafe { + self.fs + .disk + .read_at(self.fs.block + ptr.addr(), &mut data)? + }; + if count != mem::size_of::() { + // Read wrong number of bytes + log::error!("READ_BLOCK: WRONG NUMBER OF BYTES"); + return Err(Error::new(EIO)); + } + self.fs.decrypt(&mut data); + } + + let block = BlockData::new(ptr.addr(), data); + let block_ptr = block.create_ptr(); + if block_ptr.hash() != ptr.hash() { + // Incorrect hash + log::error!( + "READ_BLOCK: INCORRECT HASH {} != {} for block {}", + block_ptr.hash(), + ptr.hash(), + ptr.addr() + ); + return Err(Error::new(EIO)); + } + Ok(block) + } + + /// Read block data or, if pointer is null, return default block data + /// + /// # Safety + /// Unsafe because it creates strange BlockData types that must be swapped before use + unsafe fn read_block_or_default>( + &mut self, + ptr: BlockPtr, + ) -> Result> { + if ptr.is_null() { + Ok(BlockData::new(0, T::default())) + } else { + self.read_block(ptr) + } + } + + /// Write block data to a new address, returning new address + pub fn sync_block>( + &mut self, + mut block: BlockData, + ) -> Result> { + // Swap block to new address + let old_addr = block.swap_addr(unsafe { self.allocate()? }); + // Deallocate old address (will only take effect after sync_allocator, which helps to + // prevent re-use before a new header is written + if old_addr != 0 { + unsafe { + self.deallocate(old_addr); + } + } + // Write new block + unsafe { self.write_block(block) } + } + + /// Write block data, returning a calculated block pointer + /// + /// # Safety + /// Unsafe to encourage CoW semantics + pub(crate) unsafe fn write_block>( + &mut self, + block: BlockData, + ) -> Result> { + if block.addr() == 0 { + // Pointer is invalid + log::error!("WRITE_BLOCK: POINTER IS NULL"); + return Err(Error::new(ENOENT)); + } + + //TODO: transmute? + let mut raw = BlockRaw::default(); + raw.copy_from_slice(block.data()); + self.write_cache.insert(block.addr(), raw); + + Ok(block.create_ptr()) + } + + pub fn read_tree>( + &mut self, + ptr: TreePtr, + ) -> Result> { + if ptr.is_null() { + // ID is invalid (should this return None?) + log::error!("READ_TREE: ID IS NULL"); + return Err(Error::new(ENOENT)); + } + + let (i3, i2, i1, i0) = ptr.indexes(); + let l3 = self.read_block(self.header.tree)?; + let l2 = self.read_block(l3.data().ptrs[i3])?; + let l1 = self.read_block(l2.data().ptrs[i2])?; + let l0 = self.read_block(l1.data().ptrs[i1])?; + let raw = self.read_block(l0.data().ptrs[i0])?; + + //TODO: transmute instead of copy? + let mut data = T::default(); + data.copy_from_slice(raw.data()); + + Ok(TreeData::new(ptr.id(), data)) + } + + //TODO: improve performance, reduce writes + pub fn insert_tree>( + &mut self, + block_ptr: BlockPtr, + ) -> Result> { + // Remember that if there is a free block at any level it will always sync when it + // allocates at the lowest level, so we can save a write by not writing each level as it + // is allocated. + unsafe { + let mut l3 = self.read_block(self.header.tree)?; + for i3 in 0..l3.data().ptrs.len() { + let mut l2 = self.read_block_or_default(l3.data().ptrs[i3])?; + for i2 in 0..l2.data().ptrs.len() { + let mut l1 = self.read_block_or_default(l2.data().ptrs[i2])?; + for i1 in 0..l1.data().ptrs.len() { + let mut l0 = self.read_block_or_default(l1.data().ptrs[i1])?; + for i0 in 0..l0.data().ptrs.len() { + let pn = l0.data().ptrs[i0]; + + // Skip if already in use + if !pn.is_null() { + continue; + } + + let tree_ptr = TreePtr::from_indexes((i3, i2, i1, i0)); + + // Skip if this is a reserved node (null) + if tree_ptr.is_null() { + continue; + } + + // Write updates to newly allocated blocks + l0.data_mut().ptrs[i0] = block_ptr.cast(); + l1.data_mut().ptrs[i1] = self.sync_block(l0)?; + l2.data_mut().ptrs[i2] = self.sync_block(l1)?; + l3.data_mut().ptrs[i3] = self.sync_block(l2)?; + self.header.tree = self.sync_block(l3)?; + self.header_changed = true; + + return Ok(tree_ptr); + } + } + } + } + } + + Err(Error::new(ENOSPC)) + } + + pub fn sync_trees>(&mut self, nodes: &[TreeData]) -> Result<()> { + for node in nodes.iter().rev() { + let ptr = node.ptr(); + if ptr.is_null() { + // ID is invalid + log::error!("SYNC_TREE: ID IS NULL"); + return Err(Error::new(ENOENT)); + } + } + + for node in nodes.iter().rev() { + let (i3, i2, i1, i0) = node.ptr().indexes(); + let mut l3 = self.read_block(self.header.tree)?; + let mut l2 = self.read_block(l3.data().ptrs[i3])?; + let mut l1 = self.read_block(l2.data().ptrs[i2])?; + let mut l0 = self.read_block(l1.data().ptrs[i1])?; + let mut raw = self.read_block(l0.data().ptrs[i0])?; + + // Return if data is equal + if raw.data().deref() == node.data().deref() { + continue; + } + + //TODO: transmute instead of copy? + raw.data_mut().copy_from_slice(node.data()); + + // Write updates to newly allocated blocks + l0.data_mut().ptrs[i0] = self.sync_block(raw)?; + l1.data_mut().ptrs[i1] = self.sync_block(l0)?; + l2.data_mut().ptrs[i2] = self.sync_block(l1)?; + l3.data_mut().ptrs[i3] = self.sync_block(l2)?; + self.header.tree = self.sync_block(l3)?; + self.header_changed = true; + } + + Ok(()) + } + + pub fn sync_tree>(&mut self, node: TreeData) -> Result<()> { + self.sync_trees(&[node]) + } + + //TODO: use more efficient methods for reading directories + pub fn child_nodes( + &mut self, + parent_ptr: TreePtr, + children: &mut Vec, + ) -> Result<()> { + let parent = self.read_tree(parent_ptr)?; + for block_offset in 0..(parent.data().size() / BLOCK_SIZE) { + let block_ptr = self.node_block_ptr(&parent, block_offset)?; + let dir_ptr: BlockPtr = unsafe { block_ptr.cast() }; + let dir = self.read_block(dir_ptr)?; + for entry in dir.data().entries { + let node_ptr = entry.node_ptr(); + + // Skip empty entries + if node_ptr.is_null() { + continue; + } + + children.push(entry); + } + } + + Ok(()) + } + + //TODO: improve performance (h-tree?) + pub fn find_node(&mut self, parent_ptr: TreePtr, name: &str) -> Result> { + let parent = self.read_tree(parent_ptr)?; + for block_offset in 0..(parent.data().size() / BLOCK_SIZE) { + let block_ptr = self.node_block_ptr(&parent, block_offset)?; + let dir_ptr: BlockPtr = unsafe { block_ptr.cast() }; + let dir = self.read_block(dir_ptr)?; + for entry in dir.data().entries { + let node_ptr = entry.node_ptr(); + + // Skip empty entries + if node_ptr.is_null() { + continue; + } + + // Return node pointer if name matches + if let Some(entry_name) = entry.name() { + if entry_name == name { + //TODO: Do not require read of node + return self.read_tree(node_ptr); + } + } + } + } + + Err(Error::new(ENOENT)) + } + + //TODO: improve performance (h-tree?) + pub fn create_node( + &mut self, + parent_ptr: TreePtr, + name: &str, + mode: u16, + ctime: u64, + ctime_nsec: u32, + ) -> Result> { + if name.contains(':') { + Err(Error::new(EINVAL)) + } else if self.find_node(parent_ptr, name).is_ok() { + Err(Error::new(EEXIST)) + } else { + unsafe { + let parent = self.read_tree(parent_ptr)?; + let node_block_data = BlockData::new( + self.allocate()?, + Node::new( + mode, + parent.data().uid(), + parent.data().gid(), + ctime, + ctime_nsec, + ), + ); + let node_block_ptr = self.write_block(node_block_data)?; + let node_ptr = self.insert_tree(node_block_ptr)?; + + self.link_node(parent_ptr, name, node_ptr)?; + + //TODO: do not re-read node + self.read_tree(node_ptr) + } + } + } + + pub fn link_node( + &mut self, + parent_ptr: TreePtr, + name: &str, + node_ptr: TreePtr, + ) -> Result<()> { + let mut parent = self.read_tree(parent_ptr)?; + + let mut node = self.read_tree(node_ptr)?; + let links = node.data().links(); + node.data_mut().set_links(links + 1); + + let entry = DirEntry::new(node_ptr, name).ok_or(Error::new(EINVAL))?; + + let block_end = parent.data().size() / BLOCK_SIZE; + for block_offset in 0..block_end { + let mut dir_block_ptr = self.node_block_ptr(&parent, block_offset)?; + let mut dir_ptr: BlockPtr = unsafe { dir_block_ptr.cast() }; + let mut dir = self.read_block(dir_ptr)?; + let mut dir_changed = false; + for old_entry in dir.data_mut().entries.iter_mut() { + // Skip filled entries + if !old_entry.node_ptr().is_null() { + continue; + } + + *old_entry = entry; + dir_changed = true; + break; + } + if dir_changed { + dir_ptr = self.sync_block(dir)?; + dir_block_ptr = unsafe { dir_ptr.cast() }; + + self.sync_node_block_ptr(&mut parent, block_offset, dir_block_ptr)?; + self.sync_trees(&[parent, node])?; + + return Ok(()); + } + } + + // Append a new dirlist, with first entry set to new entry + let mut dir = BlockData::new(unsafe { self.allocate()? }, DirList::default()); + dir.data_mut().entries[0] = entry; + let dir_ptr = unsafe { self.write_block(dir)? }; + let dir_block_ptr: BlockPtr = unsafe { dir_ptr.cast() }; + + self.sync_node_block_ptr(&mut parent, block_end, dir_block_ptr)?; + parent.data_mut().set_size((block_end + 1) * BLOCK_SIZE); + self.sync_trees(&[parent, node])?; + + Ok(()) + } + + pub fn remove_node(&mut self, parent_ptr: TreePtr, name: &str, mode: u16) -> Result<()> { + let mut parent = self.read_tree(parent_ptr)?; + let blocks = parent.data().size() / BLOCK_SIZE; + for block_offset in 0..blocks { + let mut dir_block_ptr = self.node_block_ptr(&parent, block_offset)?; + let mut dir_ptr: BlockPtr = unsafe { dir_block_ptr.cast() }; + let mut dir = self.read_block(dir_ptr)?; + let mut node_opt = None; + for entry in dir.data_mut().entries.iter_mut() { + let node_ptr = entry.node_ptr(); + + // Skip empty entries + if node_ptr.is_null() { + continue; + } + + // Check if name matches + if let Some(entry_name) = entry.name() { + if entry_name == name { + // Read node and test type against requested type + let node = self.read_tree(node_ptr)?; + if node.data().mode() & Node::MODE_TYPE == mode { + if node.data().is_dir() && node.data().size() > 0 { + // Tried to remove directory that still has entries + return Err(Error::new(ENOTEMPTY)); + } + + // Save node and clear entry + node_opt = Some(node); + *entry = DirEntry::default(); + break; + } else if node.data().is_dir() { + // Found directory instead of requested type + return Err(Error::new(EISDIR)); + } else { + // Did not find directory when requested + return Err(Error::new(ENOTDIR)); + } + } + } + } + + if let Some(mut node) = node_opt { + let links = node.data().links(); + if links > 1 { + node.data_mut().set_links(links - 1); + } else { + node.data_mut().set_links(0); + self.truncate_node_inner(&mut node, 0)?; + } + + if block_offset == blocks - 1 && dir.data().is_empty() { + // Remove empty parent block, if it is at the end + self.remove_node_block_ptr(&mut parent, block_offset)?; + parent.data_mut().set_size(block_offset * BLOCK_SIZE); + } else { + // Save new parent block + dir_ptr = self.sync_block(dir)?; + dir_block_ptr = unsafe { dir_ptr.cast() }; + self.sync_node_block_ptr(&mut parent, block_offset, dir_block_ptr)?; + } + + // Sync both parent and node at the same time + self.sync_trees(&[parent, node])?; + + return Ok(()); + } + } + + Err(Error::new(ENOENT)) + } + + fn node_block_ptr( + &mut self, + node: &TreeData, + block_offset: u64, + ) -> Result> { + match NodeLevel::new(block_offset).ok_or(Error::new(ERANGE))? { + NodeLevel::L0(i0) => Ok(node.data().level0[i0]), + NodeLevel::L1(i1, i0) => { + let l0 = self.read_block(node.data().level1[i1])?; + Ok(l0.data().ptrs[i0]) + } + NodeLevel::L2(i2, i1, i0) => { + let l1 = self.read_block(node.data().level2[i2])?; + let l0 = self.read_block(l1.data().ptrs[i1])?; + Ok(l0.data().ptrs[i0]) + } + NodeLevel::L3(i3, i2, i1, i0) => { + let l2 = self.read_block(node.data().level3[i3])?; + let l1 = self.read_block(l2.data().ptrs[i2])?; + let l0 = self.read_block(l1.data().ptrs[i1])?; + Ok(l0.data().ptrs[i0]) + } + NodeLevel::L4(i4, i3, i2, i1, i0) => { + let l3 = self.read_block(node.data().level4[i4])?; + let l2 = self.read_block(l3.data().ptrs[i3])?; + let l1 = self.read_block(l2.data().ptrs[i2])?; + let l0 = self.read_block(l1.data().ptrs[i1])?; + Ok(l0.data().ptrs[i0]) + } + } + } + + fn remove_node_block_ptr( + &mut self, + node: &mut TreeData, + block_offset: u64, + ) -> Result<()> { + match NodeLevel::new(block_offset).ok_or(Error::new(ERANGE))? { + NodeLevel::L0(i0) => { + self.deallocate_block(node.data_mut().level0[i0].clear()); + } + NodeLevel::L1(i1, i0) => { + let mut l0 = self.read_block(node.data().level1[i1])?; + self.deallocate_block(l0.data_mut().ptrs[i0].clear()); + if l0.data().is_empty() { + self.deallocate_block(node.data_mut().level1[i1].clear()); + } else { + node.data_mut().level1[i1] = self.sync_block(l0)?; + } + } + NodeLevel::L2(i2, i1, i0) => { + let mut l1 = self.read_block(node.data().level2[i2])?; + let mut l0 = self.read_block(l1.data().ptrs[i1])?; + self.deallocate_block(l0.data_mut().ptrs[i0].clear()); + if l0.data().is_empty() { + self.deallocate_block(l1.data_mut().ptrs[i1].clear()); + } else { + l1.data_mut().ptrs[i1] = self.sync_block(l0)?; + } + if l1.data().is_empty() { + self.deallocate_block(node.data_mut().level2[i2].clear()); + } else { + node.data_mut().level2[i2] = self.sync_block(l1)?; + } + } + NodeLevel::L3(i3, i2, i1, i0) => { + let mut l2 = self.read_block(node.data().level3[i3])?; + let mut l1 = self.read_block(l2.data().ptrs[i2])?; + let mut l0 = self.read_block(l1.data().ptrs[i1])?; + self.deallocate_block(l0.data_mut().ptrs[i0].clear()); + if l0.data().is_empty() { + self.deallocate_block(l1.data_mut().ptrs[i1].clear()); + } else { + l1.data_mut().ptrs[i1] = self.sync_block(l0)?; + } + if l1.data().is_empty() { + self.deallocate_block(l2.data_mut().ptrs[i2].clear()); + } else { + l2.data_mut().ptrs[i2] = self.sync_block(l1)?; + } + if l2.data().is_empty() { + self.deallocate_block(node.data_mut().level3[i3].clear()); + } else { + node.data_mut().level3[i3] = self.sync_block(l2)?; + } + } + NodeLevel::L4(i4, i3, i2, i1, i0) => { + let mut l3 = self.read_block(node.data().level4[i4])?; + let mut l2 = self.read_block(l3.data().ptrs[i3])?; + let mut l1 = self.read_block(l2.data().ptrs[i2])?; + let mut l0 = self.read_block(l1.data().ptrs[i1])?; + self.deallocate_block(l0.data_mut().ptrs[i0].clear()); + if l0.data().is_empty() { + self.deallocate_block(l1.data_mut().ptrs[i1].clear()); + } else { + l1.data_mut().ptrs[i1] = self.sync_block(l0)?; + } + if l1.data().is_empty() { + self.deallocate_block(l2.data_mut().ptrs[i2].clear()); + } else { + l2.data_mut().ptrs[i2] = self.sync_block(l1)?; + } + if l2.data().is_empty() { + self.deallocate_block(l3.data_mut().ptrs[i3].clear()); + } else { + l3.data_mut().ptrs[i3] = self.sync_block(l2)?; + } + if l3.data().is_empty() { + self.deallocate_block(node.data_mut().level4[i4].clear()); + } else { + node.data_mut().level4[i4] = self.sync_block(l3)?; + } + } + } + + Ok(()) + } + + fn sync_node_block_ptr( + &mut self, + node: &mut TreeData, + block_offset: u64, + ptr: BlockPtr, + ) -> Result<()> { + unsafe { + match NodeLevel::new(block_offset).ok_or(Error::new(ERANGE))? { + NodeLevel::L0(i0) => { + node.data_mut().level0[i0] = ptr; + } + NodeLevel::L1(i1, i0) => { + let mut l0 = self.read_block_or_default(node.data().level1[i1])?; + + l0.data_mut().ptrs[i0] = ptr; + node.data_mut().level1[i1] = self.sync_block(l0)?; + } + NodeLevel::L2(i2, i1, i0) => { + let mut l1 = self.read_block_or_default(node.data().level2[i2])?; + let mut l0 = self.read_block_or_default(l1.data().ptrs[i1])?; + + l0.data_mut().ptrs[i0] = ptr; + l1.data_mut().ptrs[i1] = self.sync_block(l0)?; + node.data_mut().level2[i2] = self.sync_block(l1)?; + } + NodeLevel::L3(i3, i2, i1, i0) => { + let mut l2 = self.read_block_or_default(node.data().level3[i3])?; + let mut l1 = self.read_block_or_default(l2.data().ptrs[i2])?; + let mut l0 = self.read_block_or_default(l1.data().ptrs[i1])?; + + l0.data_mut().ptrs[i0] = ptr; + l1.data_mut().ptrs[i1] = self.sync_block(l0)?; + l2.data_mut().ptrs[i2] = self.sync_block(l1)?; + node.data_mut().level3[i3] = self.sync_block(l2)?; + } + NodeLevel::L4(i4, i3, i2, i1, i0) => { + let mut l3 = self.read_block_or_default(node.data().level4[i4])?; + let mut l2 = self.read_block_or_default(l3.data().ptrs[i3])?; + let mut l1 = self.read_block_or_default(l2.data().ptrs[i2])?; + let mut l0 = self.read_block_or_default(l1.data().ptrs[i1])?; + + l0.data_mut().ptrs[i0] = ptr; + l1.data_mut().ptrs[i1] = self.sync_block(l0)?; + l2.data_mut().ptrs[i2] = self.sync_block(l1)?; + l3.data_mut().ptrs[i3] = self.sync_block(l2)?; + node.data_mut().level4[i4] = self.sync_block(l3)?; + } + } + } + + Ok(()) + } + + pub fn read_node_inner( + &mut self, + node: &TreeData, + mut offset: u64, + buf: &mut [u8], + ) -> Result { + let node_size = node.data().size(); + let mut i = 0; + while i < buf.len() && offset < node_size { + let block_ptr = self.node_block_ptr(&node, offset / BLOCK_SIZE)?; + let block = self.read_block(block_ptr)?; + + let j = (offset % BLOCK_SIZE) as usize; + let len = min( + buf.len() - i, + min(BLOCK_SIZE - j as u64, node_size - offset) as usize, + ); + buf[i..i + len].copy_from_slice(&block.data()[j..j + len]); + + i += len; + offset += len as u64; + } + Ok(i) + } + + pub fn read_node( + &mut self, + node_ptr: TreePtr, + offset: u64, + buf: &mut [u8], + atime: u64, + atime_nsec: u32, + ) -> Result { + let mut node = self.read_tree(node_ptr)?; + let mut node_changed = false; + + let i = self.read_node_inner(&node, offset, buf)?; + if i > 0 { + let node_atime = node.data().atime(); + if atime > node_atime.0 || (atime == node_atime.0 && atime_nsec > node_atime.1) { + let is_old = atime - node_atime.0 > 3600; // Last read was more than a day ago + if is_old { + node.data_mut().set_atime(atime, atime_nsec); + node_changed = true; + } + } + } + + if node_changed { + self.sync_tree(node)?; + } + + Ok(i) + } + + pub fn truncate_node_inner(&mut self, node: &mut TreeData, size: u64) -> Result { + let old_size = node.data().size(); + + // Size already matches, return + if old_size == size { + return Ok(false); + } + + if old_size < size { + // If size is smaller, write zeroes until the size matches + let zeroes = [0; BLOCK_SIZE as usize]; + + let mut offset = old_size; + while offset < size { + let start = offset % BLOCK_SIZE; + let end = if offset / BLOCK_SIZE == size / BLOCK_SIZE { + size % BLOCK_SIZE + } else { + BLOCK_SIZE + }; + self.write_node_inner(node, &mut offset, &zeroes[start as usize..end as usize])?; + } + assert_eq!(offset, size); + } else { + // Deallocate blocks + for block in ((size + BLOCK_SIZE - 1) / BLOCK_SIZE..old_size / BLOCK_SIZE).rev() { + self.remove_node_block_ptr(node, block)?; + } + } + + // Update size + node.data_mut().set_size(size); + + Ok(true) + } + + pub fn truncate_node( + &mut self, + node_ptr: TreePtr, + size: u64, + mtime: u64, + mtime_nsec: u32, + ) -> Result<()> { + let mut node = self.read_tree(node_ptr)?; + if self.truncate_node_inner(&mut node, size)? { + let node_mtime = node.data().mtime(); + if mtime > node_mtime.0 || (mtime == node_mtime.0 && mtime_nsec > node_mtime.1) { + node.data_mut().set_mtime(mtime, mtime_nsec); + } + + self.sync_tree(node)?; + } + + Ok(()) + } + + pub fn write_node_inner( + &mut self, + node: &mut TreeData, + offset: &mut u64, + buf: &[u8], + ) -> Result { + let mut node_changed = false; + + let node_blocks = (node.data().size() + BLOCK_SIZE - 1) / BLOCK_SIZE; + + let mut i = 0; + while i < buf.len() { + let mut block_ptr = if node_blocks > (*offset / BLOCK_SIZE) { + self.node_block_ptr(node, *offset / BLOCK_SIZE)? + } else { + BlockPtr::default() + }; + let mut block = unsafe { self.read_block_or_default(block_ptr)? }; + + let j = (*offset % BLOCK_SIZE) as usize; + let len = min(buf.len() - i, BLOCK_SIZE as usize - j); + if block_ptr.is_null() || buf[i..i + len] != block.data()[j..j + len] { + unsafe { + let old_addr = block.swap_addr(self.allocate()?); + + block.data_mut()[j..j + len].copy_from_slice(&buf[i..i + len]); + block_ptr = self.write_block(block)?; + + if old_addr != 0 { + self.deallocate(old_addr); + } + } + + self.sync_node_block_ptr(node, *offset / BLOCK_SIZE, block_ptr)?; + node_changed = true; + } + + i += len; + *offset += len as u64; + } + + if node.data().size() < *offset { + node.data_mut().set_size(*offset); + node_changed = true; + } + + Ok(node_changed) + } + + pub fn write_node( + &mut self, + node_ptr: TreePtr, + mut offset: u64, + buf: &[u8], + mtime: u64, + mtime_nsec: u32, + ) -> Result { + let mut node = self.read_tree(node_ptr)?; + + if self.write_node_inner(&mut node, &mut offset, buf)? { + let node_mtime = node.data().mtime(); + if mtime > node_mtime.0 || (mtime == node_mtime.0 && mtime_nsec > node_mtime.1) { + node.data_mut().set_mtime(mtime, mtime_nsec); + } + + self.sync_tree(node)?; + } + + Ok(buf.len()) + } +} diff --git a/src/tree.rs b/src/tree.rs new file mode 100644 index 0000000..8148c93 --- /dev/null +++ b/src/tree.rs @@ -0,0 +1,161 @@ +use core::{marker::PhantomData, mem, ops, slice}; +use simple_endian::*; + +use crate::{BlockPtr, BlockRaw}; + +// 1 << 8 = 256, this is the number of entries in a TreeList +const TREE_LIST_SHIFT: u32 = 8; + +// Tree with 4 levels +pub type Tree = TreeList>>>; + +#[derive(Clone, Copy, Debug, Default)] +pub struct TreeData { + id: u32, + data: T, +} + +impl TreeData { + pub fn new(id: u32, data: T) -> Self { + Self { id, data } + } + + pub fn id(&self) -> u32 { + self.id + } + + pub fn data(&self) -> &T { + &self.data + } + + pub fn data_mut(&mut self) -> &mut T { + &mut self.data + } + + pub fn into_data(self) -> T { + self.data + } + + pub fn ptr(&self) -> TreePtr { + TreePtr { + id: self.id.into(), + phantom: PhantomData, + } + } +} + +#[repr(packed)] +pub struct TreeList { + pub ptrs: [BlockPtr; 1 << TREE_LIST_SHIFT], +} + +impl Default for TreeList { + fn default() -> Self { + Self { + ptrs: [BlockPtr::default(); 1 << TREE_LIST_SHIFT], + } + } +} + +impl ops::Deref for TreeList { + type Target = [u8]; + fn deref(&self) -> &[u8] { + unsafe { + slice::from_raw_parts( + self as *const TreeList as *const u8, + mem::size_of::>(), + ) as &[u8] + } + } +} + +impl ops::DerefMut for TreeList { + fn deref_mut(&mut self) -> &mut [u8] { + unsafe { + slice::from_raw_parts_mut( + self as *mut TreeList as *mut u8, + mem::size_of::>(), + ) as &mut [u8] + } + } +} + +#[repr(packed)] +pub struct TreePtr { + id: u32le, + phantom: PhantomData, +} + +impl TreePtr { + pub fn root() -> Self { + Self::new(1) + } + + pub fn new(id: u32) -> Self { + Self { + id: id.into(), + phantom: PhantomData, + } + } + + pub fn from_indexes(indexes: (usize, usize, usize, usize)) -> Self { + const SHIFT: u32 = TREE_LIST_SHIFT; + let id = ((indexes.0 << (3 * SHIFT)) as u32) + | ((indexes.1 << (2 * SHIFT)) as u32) + | ((indexes.2 << SHIFT) as u32) + | (indexes.3 as u32); + Self { + id: id.into(), + phantom: PhantomData, + } + } + + pub fn id(&self) -> u32 { + { self.id }.to_native() + } + + pub fn is_null(&self) -> bool { + self.id() == 0 + } + + pub fn indexes(&self) -> (usize, usize, usize, usize) { + const SHIFT: u32 = TREE_LIST_SHIFT; + const NUM: u32 = 1 << SHIFT; + const MASK: u32 = NUM - 1; + let id = self.id(); + ( + ((id >> (3 * SHIFT)) & MASK) as usize, + ((id >> (2 * SHIFT)) & MASK) as usize, + ((id >> SHIFT) & MASK) as usize, + (id & MASK) as usize, + ) + } +} + +impl Clone for TreePtr { + fn clone(&self) -> Self { + Self { + id: self.id, + phantom: PhantomData, + } + } +} + +impl Copy for TreePtr {} + +impl Default for TreePtr { + fn default() -> Self { + Self { + id: 0.into(), + phantom: PhantomData, + } + } +} + +#[test] +fn tree_list_size_test() { + assert_eq!( + mem::size_of::>(), + crate::BLOCK_SIZE as usize + ); +} diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..e6ddec9 --- /dev/null +++ b/test.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +CARGO_ARGS=(--release) +TARGET=target/release +export RUST_BACKTRACE=full +export RUST_LOG=info + +function cleanup { + sync + fusermount -u image || true +} + +trap 'cleanup' ERR + +set -eEx + +cleanup + +redoxer test -- --lib -- --nocapture +cargo test --lib --no-default-features -- --nocapture +cargo test --lib -- --nocapture +cargo build "${CARGO_ARGS[@]}" + +rm -f image.bin +fallocate -l 1G image.bin +time "${TARGET}/redoxfs-mkfs" image.bin + +mkdir -p image +"${TARGET}/redoxfs" image.bin image + +df -h image +ls -lah image + +mkdir image/test +time cp -r src image/test/src +dd if=/dev/urandom of=image/test/random bs=1M count=256 +dd if=image/test/random of=/dev/null bs=1M count=256 +dd if=/dev/zero of=image/test/zero bs=1M count=256 +dd if=image/test/zero of=/dev/null bs=1M count=256 +ls -lah image/test + +df -h image + +rm image/test/random +rm image/test/zero +rm -rf image/test/src +rmdir image/test + +df -h image +ls -lah image + +cleanup + +"${TARGET}/redoxfs" image.bin image + +df -h image +ls -lah image + +cleanup -- GitLab