RedoxFS 0.5.0
Significant re-design to use copy-on-write and hashes for all data
This commit is contained in:
committed by
Jeremy Soller
parent
c98e63af87
commit
731b97262b
Generated
+281
-15
@@ -3,10 +3,76 @@
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
name = "aes"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
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.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 = "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"
|
||||
@@ -34,10 +152,46 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.97"
|
||||
name = "generic-array"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
|
||||
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.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
@@ -58,10 +212,28 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.19"
|
||||
name = "memchr"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
|
||||
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.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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 = "redoxfs"
|
||||
version = "0.4.4"
|
||||
name = "redox_termios"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
|
||||
dependencies = [
|
||||
"fuse",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redoxfs"
|
||||
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"
|
||||
|
||||
+33
-5
@@ -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 <jackpot51@gmail.com>"]
|
||||
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 }
|
||||
|
||||
@@ -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],
|
||||
}
|
||||
```
|
||||
@@ -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<Vec<u64>>,
|
||||
}
|
||||
|
||||
impl Allocator {
|
||||
pub fn levels(&self) -> &Vec<Vec<u64>> {
|
||||
&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<u64> {
|
||||
// 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<u64> {
|
||||
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<AllocList>,
|
||||
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::<AllocList>(),
|
||||
) 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::<AllocList>(),
|
||||
) as &mut [u8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alloc_node_size_test() {
|
||||
assert_eq!(mem::size_of::<AllocList>(), 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], []);
|
||||
}
|
||||
}
|
||||
+85
-45
@@ -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<D: Disk, P: AsRef<Path>>(
|
||||
fs: &mut FileSystem<D>,
|
||||
tx: &mut Transaction<D>,
|
||||
parent_path: P,
|
||||
parent_block: u64,
|
||||
parent_ptr: TreePtr<Node>,
|
||||
) -> io::Result<()> {
|
||||
for entry_res in fs::read_dir(parent_path)? {
|
||||
let entry = entry_res?;
|
||||
@@ -38,44 +38,60 @@ pub fn archive_at<D: Disk, P: AsRef<Path>>(
|
||||
));
|
||||
};
|
||||
|
||||
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<D: Disk, P: AsRef<Path>>(
|
||||
}
|
||||
|
||||
pub fn archive<D: Disk, P: AsRef<Path>>(fs: &mut FileSystem<D>, parent_path: P) -> io::Result<u64> {
|
||||
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()))?;
|
||||
|
||||
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)?;
|
||||
// Squash alloc log
|
||||
tx.sync(true)?;
|
||||
|
||||
fs.header.1.size = end_size;
|
||||
let header = fs.header;
|
||||
fs.write_at(header.0, &header.1).map_err(syscall_err)?;
|
||||
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;
|
||||
|
||||
Ok(header.0 * BLOCK_SIZE + end_size)
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Update header
|
||||
tx.header.size = (end_block * BLOCK_SIZE).into();
|
||||
tx.header_changed = true;
|
||||
tx.sync(false)?;
|
||||
|
||||
Ok(end_block)
|
||||
})
|
||||
.map_err(syscall_err)?;
|
||||
|
||||
Ok((fs.block + end_block) * BLOCK_SIZE)
|
||||
}
|
||||
|
||||
+12
-4
@@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
+62
-17
@@ -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 main() {
|
||||
let mut args = env::args().skip(1);
|
||||
fn usage() -> ! {
|
||||
eprintln!("redoxfs-mkfs [--encrypt] DISK [BOOTLOADER]");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
let disk_path = if let Some(path) = args.next() {
|
||||
fn main() {
|
||||
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) = 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
|
||||
);
|
||||
|
||||
+122
-63
@@ -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<Vec<u8>> {
|
||||
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<Vec<u8>> {
|
||||
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<u64>) -> Option<(String, FileSystem<DiskCache<DiskFile>>)> {
|
||||
fn filesystem_by_path(
|
||||
path: &str,
|
||||
block_opt: Option<u64>,
|
||||
) -> Option<(String, FileSystem<DiskCache<DiskFile>>)> {
|
||||
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: ");
|
||||
|
||||
return Some((path.to_string(), filesystem));
|
||||
let password = io::stdin()
|
||||
.read_passwd(&mut io::stderr())
|
||||
.unwrap()
|
||||
.unwrap_or(String::new());
|
||||
|
||||
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<u64>) -> Option<(String, FileSystem<DiskCache<DiskFile>>)> {
|
||||
fn filesystem_by_uuid(
|
||||
_uuid: &Uuid,
|
||||
_block_opt: Option<u64>,
|
||||
) -> Option<(String, FileSystem<DiskCache<DiskFile>>)> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn filesystem_by_uuid(uuid: &Uuid, block_opt: Option<u64>) -> Option<(String, FileSystem<DiskCache<DiskFile>>)> {
|
||||
fn filesystem_by_uuid(
|
||||
uuid: &Uuid,
|
||||
block_opt: Option<u64>,
|
||||
) -> Option<(String, FileSystem<DiskCache<DiskFile>>)> {
|
||||
use std::fs;
|
||||
|
||||
match fs::read_dir(":") {
|
||||
@@ -128,16 +213,21 @@ fn filesystem_by_uuid(uuid: &Uuid, block_opt: Option<u64>) -> 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<u64>, 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<u64>, 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 {
|
||||
|
||||
+207
@@ -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<T> {
|
||||
addr: u64,
|
||||
data: T,
|
||||
}
|
||||
|
||||
impl<T> BlockData<T> {
|
||||
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<T: ops::Deref<Target = [u8]>> BlockData<T> {
|
||||
pub fn create_ptr(&self) -> BlockPtr<T> {
|
||||
BlockPtr {
|
||||
addr: self.addr.into(),
|
||||
hash: seahash::hash(self.data.deref()).into(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
pub struct BlockList<T> {
|
||||
pub ptrs: [BlockPtr<T>; 256],
|
||||
}
|
||||
|
||||
impl<T> BlockList<T> {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
for ptr in self.ptrs.iter() {
|
||||
if !ptr.is_null() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for BlockList<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ptrs: [BlockPtr::default(); 256],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ops::Deref for BlockList<T> {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &[u8] {
|
||||
unsafe {
|
||||
slice::from_raw_parts(
|
||||
self as *const BlockList<T> as *const u8,
|
||||
mem::size_of::<BlockList<T>>(),
|
||||
) as &[u8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ops::DerefMut for BlockList<T> {
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
unsafe {
|
||||
slice::from_raw_parts_mut(
|
||||
self as *mut BlockList<T> as *mut u8,
|
||||
mem::size_of::<BlockList<T>>(),
|
||||
) as &mut [u8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
pub struct BlockPtr<T> {
|
||||
addr: u64le,
|
||||
hash: u64le,
|
||||
phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> BlockPtr<T> {
|
||||
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<U>(self) -> BlockPtr<U> {
|
||||
BlockPtr {
|
||||
addr: self.addr,
|
||||
hash: self.hash,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use = "the returned pointer should usually be deallocated"]
|
||||
pub fn clear(&mut self) -> BlockPtr<T> {
|
||||
let mut ptr = Self::default();
|
||||
mem::swap(self, &mut ptr);
|
||||
ptr
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for BlockPtr<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
addr: self.addr,
|
||||
hash: self.hash,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for BlockPtr<T> {}
|
||||
|
||||
impl<T> Default for BlockPtr<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
addr: 0.into(),
|
||||
hash: 0.into(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for BlockPtr<T> {
|
||||
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::<BlockList<BlockRaw>>(), BLOCK_SIZE as usize);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_raw_size_test() {
|
||||
assert_eq!(mem::size_of::<BlockRaw>(), BLOCK_SIZE as usize);
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
use core::{mem, ops, slice, str};
|
||||
|
||||
use crate::{Node, TreePtr};
|
||||
|
||||
#[repr(packed)]
|
||||
pub struct DirEntry {
|
||||
node_ptr: TreePtr<Node>,
|
||||
name: [u8; 252],
|
||||
}
|
||||
|
||||
impl DirEntry {
|
||||
pub fn new(node_ptr: TreePtr<Node>, name: &str) -> Option<DirEntry> {
|
||||
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<Node> {
|
||||
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::<DirList>(),
|
||||
) 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::<DirList>())
|
||||
as &mut [u8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dir_list_size_test() {
|
||||
assert_eq!(mem::size_of::<DirList>(), crate::BLOCK_SIZE as usize);
|
||||
}
|
||||
+5
-5
@@ -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<T> {
|
||||
impl<T: Disk> DiskCache<T> {
|
||||
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<T: Disk> DiskCache<T> {
|
||||
}
|
||||
|
||||
impl<T: Disk> Disk for DiskCache<T> {
|
||||
fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
|
||||
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
|
||||
// println!("Cache read at {}", block);
|
||||
|
||||
let mut read = 0;
|
||||
@@ -80,7 +80,7 @@ impl<T: Disk> Disk for DiskCache<T> {
|
||||
Ok(read)
|
||||
}
|
||||
|
||||
fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
|
||||
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
|
||||
//TODO: Write only blocks that have changed
|
||||
// println!("Cache write at {}", block);
|
||||
|
||||
|
||||
+6
-6
@@ -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<P: AsRef<Path>>(path: P) -> Result<DiskFile> {
|
||||
let file = try_disk!(OpenOptions::new().read(true).write(true).open(path));
|
||||
Ok(DiskFile { file: file })
|
||||
Ok(DiskFile { file })
|
||||
}
|
||||
|
||||
pub fn create<P: AsRef<Path>>(path: P, size: u64) -> Result<DiskFile> {
|
||||
@@ -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<usize> {
|
||||
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
|
||||
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<usize> {
|
||||
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
|
||||
try_disk!(self.file.seek(SeekFrom::Start(block * BLOCK_SIZE)));
|
||||
let count = try_disk!(self.file.write(buffer));
|
||||
Ok(count)
|
||||
|
||||
+13
-2
@@ -16,7 +16,18 @@ mod sparse;
|
||||
|
||||
/// A disk
|
||||
pub trait Disk {
|
||||
fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize>;
|
||||
fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize>;
|
||||
/// 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<usize>;
|
||||
|
||||
/// Write blocks from disk
|
||||
///
|
||||
/// # Safety
|
||||
/// Unsafe to discourage use, use filesystem wrappers instead
|
||||
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize>;
|
||||
|
||||
/// Get size of disk in bytes
|
||||
fn size(&mut self) -> Result<u64>;
|
||||
}
|
||||
|
||||
+8
-7
@@ -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<P: AsRef<Path>>(path: P) -> Result<DiskSparse> {
|
||||
pub fn create<P: AsRef<Path>>(path: P, max_size: u64) -> Result<DiskSparse> {
|
||||
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<usize> {
|
||||
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
|
||||
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<usize> {
|
||||
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
|
||||
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<u64> {
|
||||
Ok(u64::MAX)
|
||||
Ok(self.max_size)
|
||||
}
|
||||
}
|
||||
|
||||
+224
-574
@@ -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<D: Disk> {
|
||||
//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<Aes128>,
|
||||
aes_blocks: Vec<aes::Block>,
|
||||
}
|
||||
|
||||
impl<D: Disk> FileSystem<D> {
|
||||
/// Open a file system on a disk
|
||||
pub fn open(mut disk: D, block_opt: Option<u64>) -> Result<Self> {
|
||||
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)?;
|
||||
pub fn open(
|
||||
mut disk: D,
|
||||
password_opt: Option<&[u8]>,
|
||||
block_opt: Option<u64>,
|
||||
squash: bool,
|
||||
) -> Result<Self> {
|
||||
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)? };
|
||||
|
||||
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,
|
||||
});
|
||||
// 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<D: Disk> FileSystem<D> {
|
||||
|
||||
/// Create a file system on a disk
|
||||
#[cfg(feature = "std")]
|
||||
pub fn create(disk: D, ctime: u64, ctime_nsec: u32) -> Result<Self> {
|
||||
Self::create_reserved(disk, &[], ctime, ctime_nsec)
|
||||
pub fn create(
|
||||
disk: D,
|
||||
password_opt: Option<&[u8]>,
|
||||
ctime: u64,
|
||||
ctime_nsec: u32,
|
||||
) -> Result<Self> {
|
||||
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<D: Disk> FileSystem<D> {
|
||||
#[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<D: Disk> FileSystem<D> {
|
||||
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<D: Disk> FileSystem<D> {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
disk.write_at(block as u64, &data)?;
|
||||
unsafe {
|
||||
disk.write_at(block as u64, &data)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(FileSystem {
|
||||
disk: disk,
|
||||
let mut header = Header::new(size);
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
let mut fs = FileSystem {
|
||||
disk,
|
||||
block: block_offset,
|
||||
header: header,
|
||||
})
|
||||
} else {
|
||||
Err(Error::new(ENOSPC))
|
||||
}
|
||||
}
|
||||
header,
|
||||
allocator: Allocator::default(),
|
||||
aes_opt,
|
||||
aes_blocks: Vec::with_capacity(BLOCK_SIZE as usize / aes::BLOCK_SIZE),
|
||||
};
|
||||
|
||||
/// Read at a certain spot in the disk, returning data into buffer
|
||||
pub fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
|
||||
self.disk.read_at(self.block + block, buffer)
|
||||
}
|
||||
fs.tx(|tx| unsafe {
|
||||
let tree = BlockData::new(HEADER_RING + 1, TreeList::default());
|
||||
|
||||
pub fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
|
||||
self.disk.write_at(self.block + block, buffer)
|
||||
}
|
||||
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);
|
||||
|
||||
pub fn allocate(&mut self, length: u64) -> Result<u64> {
|
||||
//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))
|
||||
}
|
||||
}
|
||||
tx.header.tree = tx.write_block(tree)?;
|
||||
tx.header.alloc = tx.write_block(alloc)?;
|
||||
tx.header_changed = true;
|
||||
|
||||
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)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 matches = false;
|
||||
if let Ok(child_name) = child.1.name() {
|
||||
if child_name == name {
|
||||
matches = true;
|
||||
}
|
||||
}
|
||||
|
||||
if matches {
|
||||
return Ok(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_blocks(&mut self, block: u64, length: u64, parent_block: u64) -> Result<()> {
|
||||
if parent_block == 0 {
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
if left.length > 0 {
|
||||
*extent = left;
|
||||
|
||||
if right.length > 0 {
|
||||
replace_option = Some(right);
|
||||
}
|
||||
} else if right.length > 0 {
|
||||
*extent = right;
|
||||
} else {
|
||||
*extent = Extent::default();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if removed {
|
||||
self.write_at(parent.0, &parent.1)?;
|
||||
|
||||
if let Some(replace) = replace_option {
|
||||
self.insert_blocks(replace.block, replace.length, parent_block)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
self.remove_blocks(block, length, parent.1.next)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
// 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(())
|
||||
})?;
|
||||
|
||||
unsafe {
|
||||
fs.reset_allocator()?;
|
||||
}
|
||||
|
||||
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(())
|
||||
})?;
|
||||
|
||||
// Make sure everything is synced and squash allocations
|
||||
Transaction::new(&mut fs).commit(true)?;
|
||||
|
||||
Ok(fs)
|
||||
} else {
|
||||
Ok(())
|
||||
Err(Error::new(ENOSPC))
|
||||
}
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
}
|
||||
|
||||
if changed {
|
||||
self.write_at(node.0, &node.1)?;
|
||||
}
|
||||
|
||||
if node.1.next > 0 {
|
||||
self.node_set_len(node.1.next, length)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
/// Start a filesystem transaction, required for making any changes
|
||||
pub fn tx<F: FnOnce(&mut Transaction<D>) -> Result<T>, T>(&mut self, f: F) -> Result<T> {
|
||||
let mut tx = Transaction::new(self);
|
||||
let t = f(&mut tx)?;
|
||||
tx.commit(false)?;
|
||||
Ok(t)
|
||||
}
|
||||
|
||||
fn node_extents(
|
||||
&mut self,
|
||||
block: u64,
|
||||
mut offset: u64,
|
||||
mut len: usize,
|
||||
extents: &mut Vec<Extent>,
|
||||
) -> Result<()> {
|
||||
if block == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
pub fn allocator(&self) -> &Allocator {
|
||||
&self.allocator
|
||||
}
|
||||
|
||||
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;
|
||||
/// 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);
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
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)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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],
|
||||
));
|
||||
}
|
||||
|
||||
aes.decrypt_blocks(&mut self.aes_blocks);
|
||||
|
||||
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();
|
||||
|
||||
true
|
||||
} else {
|
||||
Ok(())
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_node(
|
||||
&mut self,
|
||||
block: u64,
|
||||
offset: u64,
|
||||
buf: &mut [u8],
|
||||
atime: u64,
|
||||
atime_nsec: u32,
|
||||
) -> Result<usize> {
|
||||
let block_offset = offset / BLOCK_SIZE;
|
||||
let mut byte_offset = (offset % BLOCK_SIZE) as usize;
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
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.encrypt_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)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(i)
|
||||
}
|
||||
|
||||
pub fn write_node(
|
||||
&mut self,
|
||||
block: u64,
|
||||
offset: u64,
|
||||
buf: &[u8],
|
||||
mtime: u64,
|
||||
mtime_nsec: u32,
|
||||
) -> Result<usize> {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 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)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(i)
|
||||
}
|
||||
|
||||
pub fn node_len(&mut self, block: u64) -> Result<u64> {
|
||||
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;
|
||||
}
|
||||
|
||||
if node.1.next > 0 {
|
||||
size += self.node_len(node.1.next)?;
|
||||
Ok(size)
|
||||
true
|
||||
} else {
|
||||
Ok(size)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+161
-34
@@ -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<Tree>,
|
||||
/// Block of last alloc node
|
||||
pub alloc: BlockPtr<AllocList>,
|
||||
/// 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<Aes128> {
|
||||
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::<Tree>::default(),
|
||||
alloc: BlockPtr::<AllocList>::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::<Header>(), 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);
|
||||
}
|
||||
|
||||
+92
@@ -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<Self, getrandom::Error> {
|
||||
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<Self, getrandom::Error> {
|
||||
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<Aes128, argon2::Error> {
|
||||
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<Self, argon2::Error> {
|
||||
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<Key, argon2::Error> {
|
||||
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()))
|
||||
}
|
||||
}
|
||||
+19
-14
@@ -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;
|
||||
|
||||
+200
-209
@@ -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<D: Disk> {
|
||||
pub fs: filesystem::FileSystem<D>,
|
||||
}
|
||||
|
||||
fn node_attr(node: &(u64, Node)) -> FileAttr {
|
||||
fn node_attr(node: &TreeData<Node>) -> 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<D: Disk> Filesystem for Fuse<D> {
|
||||
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<D: Disk> Filesystem for Fuse<D> {
|
||||
}
|
||||
}
|
||||
|
||||
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::<Node>::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<D: Disk> Filesystem for Fuse<D> {
|
||||
fn setattr(
|
||||
&mut self,
|
||||
_req: &Request,
|
||||
block: u64,
|
||||
node_id: u64,
|
||||
mode: Option<u32>,
|
||||
uid: Option<u32>,
|
||||
gid: Option<u32>,
|
||||
@@ -134,53 +139,57 @@ impl<D: Disk> Filesystem for Fuse<D> {
|
||||
_flags: Option<u32>,
|
||||
reply: ReplyAttr,
|
||||
) {
|
||||
let node_ptr = TreePtr::<Node>::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;
|
||||
}
|
||||
if node.data().gid() != gid {
|
||||
node.data_mut().set_gid(gid);
|
||||
node_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(atime) = atime {
|
||||
node.data_mut()
|
||||
.set_atime(atime.sec as u64, atime.nsec as u32);
|
||||
node_changed = true;
|
||||
}
|
||||
|
||||
if let Some(mtime) = mtime {
|
||||
node.data_mut()
|
||||
.set_mtime(mtime.sec as u64, mtime.nsec as u32);
|
||||
node_changed = true;
|
||||
}
|
||||
|
||||
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) => {
|
||||
@@ -190,67 +199,40 @@ impl<D: Disk> Filesystem for Fuse<D> {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(size) = size {
|
||||
if let Err(err) = self.fs.node_set_len(block, size) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match self.fs.node(block) {
|
||||
Ok(node) => {
|
||||
reply.attr(&TTL, &node_attr(&node));
|
||||
}
|
||||
Err(err) => {
|
||||
reply.error(err.errno as i32);
|
||||
}
|
||||
}
|
||||
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::<Node>::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<D: Disk> Filesystem for Fuse<D> {
|
||||
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::<Node>::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<D: Disk> Filesystem for Fuse<D> {
|
||||
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<D: Disk> Filesystem for Fuse<D> {
|
||||
}
|
||||
|
||||
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<D: Disk> Filesystem for Fuse<D> {
|
||||
fn create(
|
||||
&mut self,
|
||||
_req: &Request,
|
||||
parent_block: u64,
|
||||
parent_id: u64,
|
||||
name: &OsStr,
|
||||
mode: u32,
|
||||
flags: u32,
|
||||
reply: ReplyCreate,
|
||||
) {
|
||||
let parent_ptr = TreePtr::<Node>::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<D: Disk> Filesystem for Fuse<D> {
|
||||
fn mkdir(
|
||||
&mut self,
|
||||
_req: &Request,
|
||||
parent_block: u64,
|
||||
parent_id: u64,
|
||||
name: &OsStr,
|
||||
mode: u32,
|
||||
reply: ReplyEntry,
|
||||
) {
|
||||
let parent_ptr = TreePtr::<Node>::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<D: Disk> Filesystem for Fuse<D> {
|
||||
}
|
||||
}
|
||||
|
||||
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::<Node>::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<D: Disk> Filesystem for Fuse<D> {
|
||||
}
|
||||
}
|
||||
|
||||
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::<Node>::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<D: Disk> Filesystem for Fuse<D> {
|
||||
}
|
||||
|
||||
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::<Node>::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<D: Disk> Filesystem for Fuse<D> {
|
||||
}
|
||||
}
|
||||
|
||||
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::<Node>::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<D: Disk> Filesystem for Fuse<D> {
|
||||
&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<D>| -> 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::<Node>::new(orig_parent as u32);
|
||||
let orig_name = orig_name.to_str().expect("name is not utf-8");
|
||||
let new_parent_ptr = TreePtr::<Node>::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),
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
+203
-209
@@ -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<D: Disk> {
|
||||
fn block(&self) -> u64;
|
||||
fn dup(&self) -> Result<Box<Resource<D>>>;
|
||||
fn node_ptr(&self) -> TreePtr<Node>;
|
||||
|
||||
fn uid(&self) -> u32;
|
||||
|
||||
fn dup(&self) -> Result<Box<dyn Resource<D>>>;
|
||||
|
||||
fn set_path(&mut self, path: &str);
|
||||
fn read(&mut self, buf: &mut [u8], fs: &mut FileSystem<D>) -> Result<usize>;
|
||||
fn write(&mut self, buf: &[u8], fs: &mut FileSystem<D>) -> Result<usize>;
|
||||
fn seek(&mut self, offset: isize, whence: usize, fs: &mut FileSystem<D>) -> Result<isize>;
|
||||
fn fmap(&mut self, map: &Map, fs: &mut FileSystem<D>) -> Result<usize>;
|
||||
fn funmap(&mut self, address: usize, fs: &mut FileSystem<D>) -> Result<usize>;
|
||||
fn fchmod(&mut self, mode: u16, fs: &mut FileSystem<D>) -> Result<usize>;
|
||||
fn fchown(&mut self, uid: u32, gid: u32, fs: &mut FileSystem<D>) -> Result<usize>;
|
||||
|
||||
fn read(&mut self, buf: &mut [u8], tx: &mut Transaction<D>) -> Result<usize>;
|
||||
|
||||
fn write(&mut self, buf: &[u8], tx: &mut Transaction<D>) -> Result<usize>;
|
||||
|
||||
fn seek(&mut self, offset: isize, whence: usize, tx: &mut Transaction<D>) -> Result<isize>;
|
||||
|
||||
fn fmap(&mut self, map: &Map, tx: &mut Transaction<D>) -> Result<usize>;
|
||||
|
||||
fn funmap(&mut self, address: usize, tx: &mut Transaction<D>) -> Result<usize>;
|
||||
|
||||
fn fchmod(&mut self, mode: u16, tx: &mut Transaction<D>) -> Result<usize> {
|
||||
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<D>) -> Result<usize> {
|
||||
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<usize>;
|
||||
fn path(&self, buf: &mut [u8]) -> Result<usize>;
|
||||
fn stat(&self, _stat: &mut Stat, fs: &mut FileSystem<D>) -> Result<usize>;
|
||||
fn sync(&mut self, fs: &mut FileSystem<D>) -> Result<usize>;
|
||||
fn truncate(&mut self, len: usize, fs: &mut FileSystem<D>) -> Result<usize>;
|
||||
fn utimens(&mut self, times: &[TimeSpec], fs: &mut FileSystem<D>) -> Result<usize>;
|
||||
|
||||
fn path(&self) -> &str;
|
||||
|
||||
fn stat(&self, stat: &mut Stat, tx: &mut Transaction<D>) -> Result<usize> {
|
||||
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<D>) -> Result<usize>;
|
||||
|
||||
fn truncate(&mut self, len: usize, tx: &mut Transaction<D>) -> Result<usize>;
|
||||
|
||||
fn utimens(&mut self, times: &[TimeSpec], tx: &mut Transaction<D>) -> Result<usize>;
|
||||
}
|
||||
|
||||
pub struct DirResource {
|
||||
path: String,
|
||||
block: u64,
|
||||
node_ptr: TreePtr<Node>,
|
||||
data: Option<Vec<u8>>,
|
||||
seek: isize,
|
||||
uid: u32,
|
||||
}
|
||||
|
||||
impl DirResource {
|
||||
pub fn new(path: String, block: u64, data: Option<Vec<u8>>, uid: u32) -> DirResource {
|
||||
pub fn new(
|
||||
path: String,
|
||||
node_ptr: TreePtr<Node>,
|
||||
data: Option<Vec<u8>>,
|
||||
uid: u32,
|
||||
) -> DirResource {
|
||||
DirResource {
|
||||
path: path,
|
||||
block: block,
|
||||
data: data,
|
||||
path,
|
||||
node_ptr,
|
||||
data,
|
||||
seek: 0,
|
||||
uid: uid,
|
||||
uid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Disk> Resource<D> for DirResource {
|
||||
fn block(&self) -> u64 {
|
||||
self.block
|
||||
fn node_ptr(&self) -> TreePtr<Node> {
|
||||
self.node_ptr
|
||||
}
|
||||
|
||||
fn dup(&self) -> Result<Box<Resource<D>>> {
|
||||
fn uid(&self) -> u32 {
|
||||
self.uid
|
||||
}
|
||||
|
||||
fn dup(&self) -> Result<Box<dyn Resource<D>>> {
|
||||
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<D: Disk> Resource<D> for DirResource {
|
||||
self.path = path.to_string();
|
||||
}
|
||||
|
||||
fn read(&mut self, buf: &mut [u8], _fs: &mut FileSystem<D>) -> Result<usize> {
|
||||
fn read(&mut self, buf: &mut [u8], _tx: &mut Transaction<D>) -> Result<usize> {
|
||||
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<D: Disk> Resource<D> for DirResource {
|
||||
Ok(i)
|
||||
}
|
||||
|
||||
fn write(&mut self, _buf: &[u8], _fs: &mut FileSystem<D>) -> Result<usize> {
|
||||
fn write(&mut self, _buf: &[u8], _tx: &mut Transaction<D>) -> Result<usize> {
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
|
||||
fn seek(&mut self, offset: isize, whence: usize, _fs: &mut FileSystem<D>) -> Result<isize> {
|
||||
fn seek(&mut self, offset: isize, whence: usize, _tx: &mut Transaction<D>) -> Result<isize> {
|
||||
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<D: Disk> Resource<D> for DirResource {
|
||||
Ok(self.seek)
|
||||
}
|
||||
|
||||
fn fmap(&mut self, _map: &Map, _fs: &mut FileSystem<D>) -> Result<usize> {
|
||||
fn fmap(&mut self, _map: &Map, _tx: &mut Transaction<D>) -> Result<usize> {
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
fn funmap(&mut self, _address: usize, _fs: &mut FileSystem<D>) -> Result<usize> {
|
||||
fn funmap(&mut self, _address: usize, _tx: &mut Transaction<D>) -> Result<usize> {
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
|
||||
fn fchmod(&mut self, mode: u16, fs: &mut FileSystem<D>) -> Result<usize> {
|
||||
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<D>) -> Result<usize> {
|
||||
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<usize> {
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
|
||||
fn path(&self, buf: &mut [u8]) -> Result<usize> {
|
||||
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 path(&self) -> &str {
|
||||
&self.path
|
||||
}
|
||||
|
||||
fn stat(&self, stat: &mut Stat, fs: &mut FileSystem<D>) -> Result<usize> {
|
||||
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 sync(&mut self, _fs: &mut FileSystem<D>) -> Result<usize> {
|
||||
fn sync(&mut self, _tx: &mut Transaction<D>) -> Result<usize> {
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
|
||||
fn truncate(&mut self, _len: usize, _fs: &mut FileSystem<D>) -> Result<usize> {
|
||||
fn truncate(&mut self, _len: usize, _tx: &mut Transaction<D>) -> Result<usize> {
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
|
||||
fn utimens(&mut self, _times: &[TimeSpec], _fs: &mut FileSystem<D>) -> Result<usize> {
|
||||
fn utimens(&mut self, _times: &[TimeSpec], _tx: &mut Transaction<D>) -> Result<usize> {
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Fmap {
|
||||
block: u64,
|
||||
node_ptr: TreePtr<Node>,
|
||||
offset: usize,
|
||||
flags: MapFlags,
|
||||
data: &'static mut [u8],
|
||||
}
|
||||
|
||||
impl Fmap {
|
||||
pub unsafe fn new<D: Disk>(block: u64, map: &Map, fs: &mut FileSystem<D>) -> Result<Self> {
|
||||
pub unsafe fn new<D: Disk>(
|
||||
node_ptr: TreePtr<Node>,
|
||||
map: &Map,
|
||||
tx: &mut Transaction<D>,
|
||||
) -> Result<Self> {
|
||||
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<D: Disk>(&mut self, fs: &mut FileSystem<D>) -> Result<()> {
|
||||
pub fn sync<D: Disk>(&mut self, tx: &mut Transaction<D>) -> 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<Node>,
|
||||
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<Node>, flags: usize, uid: u32) -> FileResource {
|
||||
FileResource {
|
||||
path,
|
||||
block,
|
||||
node_ptr,
|
||||
flags,
|
||||
seek: 0,
|
||||
uid,
|
||||
@@ -293,14 +326,18 @@ impl FileResource {
|
||||
}
|
||||
|
||||
impl<D: Disk> Resource<D> for FileResource {
|
||||
fn block(&self) -> u64 {
|
||||
self.block
|
||||
fn node_ptr(&self) -> TreePtr<Node> {
|
||||
self.node_ptr
|
||||
}
|
||||
|
||||
fn dup(&self) -> Result<Box<Resource<D>>> {
|
||||
fn uid(&self) -> u32 {
|
||||
self.uid
|
||||
}
|
||||
|
||||
fn dup(&self) -> Result<Box<dyn Resource<D>>> {
|
||||
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<D: Disk> Resource<D> for FileResource {
|
||||
self.path = path.to_string();
|
||||
}
|
||||
|
||||
fn read(&mut self, buf: &mut [u8], fs: &mut FileSystem<D>) -> Result<usize> {
|
||||
fn read(&mut self, buf: &mut [u8], tx: &mut Transaction<D>) -> Result<usize> {
|
||||
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<D: Disk> Resource<D> for FileResource {
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, buf: &[u8], fs: &mut FileSystem<D>) -> Result<usize> {
|
||||
fn write(&mut self, buf: &[u8], tx: &mut Transaction<D>) -> Result<usize> {
|
||||
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<D: Disk> Resource<D> for FileResource {
|
||||
}
|
||||
}
|
||||
|
||||
fn seek(&mut self, offset: isize, whence: usize, fs: &mut FileSystem<D>) -> Result<isize> {
|
||||
let size = fs.node_len(self.block)? as isize;
|
||||
fn seek(&mut self, offset: isize, whence: usize, tx: &mut Transaction<D>) -> Result<isize> {
|
||||
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<D>) -> Result<usize> {
|
||||
fn fmap(&mut self, map: &Map, tx: &mut Transaction<D>) -> Result<usize> {
|
||||
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<D: Disk> Resource<D> 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<D>) -> Result<usize> {
|
||||
fn funmap(&mut self, address: usize, tx: &mut Transaction<D>) -> Result<usize> {
|
||||
if let Some(mut fmap) = self.fmaps.remove(&address) {
|
||||
fmap.sync(fs)?;
|
||||
fmap.sync(tx)?;
|
||||
|
||||
Ok(0)
|
||||
} else {
|
||||
@@ -386,40 +426,6 @@ impl<D: Disk> Resource<D> for FileResource {
|
||||
}
|
||||
}
|
||||
|
||||
fn fchmod(&mut self, mode: u16, fs: &mut FileSystem<D>) -> Result<usize> {
|
||||
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<D>) -> Result<usize> {
|
||||
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<usize> {
|
||||
match cmd {
|
||||
F_GETFL => Ok(self.flags),
|
||||
@@ -431,69 +437,57 @@ impl<D: Disk> Resource<D> for FileResource {
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self, buf: &mut [u8]) -> Result<usize> {
|
||||
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 path(&self) -> &str {
|
||||
&self.path
|
||||
}
|
||||
|
||||
fn stat(&self, stat: &mut Stat, fs: &mut FileSystem<D>) -> Result<usize> {
|
||||
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 sync(&mut self, fs: &mut FileSystem<D>) -> Result<usize> {
|
||||
fn sync(&mut self, tx: &mut Transaction<D>) -> Result<usize> {
|
||||
for fmap in self.fmaps.values_mut() {
|
||||
fmap.sync(fs)?;
|
||||
fmap.sync(tx)?;
|
||||
}
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn truncate(&mut self, len: usize, fs: &mut FileSystem<D>) -> Result<usize> {
|
||||
fn truncate(&mut self, len: usize, tx: &mut Transaction<D>) -> Result<usize> {
|
||||
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<D>) -> Result<usize> {
|
||||
let mut node = fs.node(self.block)?;
|
||||
fn utimens(&mut self, times: &[TimeSpec], tx: &mut Transaction<D>) -> Result<usize> {
|
||||
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;
|
||||
let mut node_changed = false;
|
||||
|
||||
fs.write_at(node.0, &node.1)?;
|
||||
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 {
|
||||
|
||||
+266
-237
@@ -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<D: Disk> {
|
||||
name: String,
|
||||
fs: RefCell<FileSystem<D>>,
|
||||
fs: FileSystem<D>,
|
||||
next_id: AtomicUsize,
|
||||
files: RefCell<BTreeMap<usize, Box<Resource<D>>>>,
|
||||
fmap: RefCell<BTreeMap<usize, usize>>,
|
||||
files: BTreeMap<usize, Box<dyn Resource<D>>>,
|
||||
fmap: BTreeMap<usize, usize>,
|
||||
}
|
||||
|
||||
impl<D: Disk> FileScheme<D> {
|
||||
pub fn new(name: String, fs: FileSystem<D>) -> FileScheme<D> {
|
||||
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<D>,
|
||||
scheme_name: &str,
|
||||
tx: &mut Transaction<D>,
|
||||
uid: u32,
|
||||
gid: u32,
|
||||
url: &[u8],
|
||||
node: (u64, Node),
|
||||
nodes: &mut Vec<(u64, Node)>,
|
||||
url: &str,
|
||||
node: TreeData<Node>,
|
||||
nodes: &mut Vec<(TreeData<Node>, String)>,
|
||||
) -> Result<Vec<u8>> {
|
||||
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<D: Disk> FileScheme<D> {
|
||||
}
|
||||
|
||||
fn path_nodes(
|
||||
&self,
|
||||
fs: &mut FileSystem<D>,
|
||||
scheme_name: &str,
|
||||
tx: &mut Transaction<D>,
|
||||
path: &str,
|
||||
uid: u32,
|
||||
gid: u32,
|
||||
nodes: &mut Vec<(u64, Node)>,
|
||||
) -> Result<Option<(u64, Node)>> {
|
||||
nodes: &mut Vec<(TreeData<Node>, String)>,
|
||||
) -> Result<Option<(TreeData<Node>, 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<u8> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Disk> Scheme for FileScheme<D> {
|
||||
fn open(&self, url: &str, flags: usize, uid: u32, gid: u32) -> Result<usize> {
|
||||
impl<D: Disk> SchemeMut for FileScheme<D> {
|
||||
fn open(&mut self, url: &str, flags: usize, uid: u32, gid: u32) -> Result<usize> {
|
||||
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<Resource<D>> = 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<dyn Resource<D>> = 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<D: Disk> Scheme for FileScheme<D> {
|
||||
}
|
||||
}
|
||||
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<D: Disk> Scheme for FileScheme<D> {
|
||||
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<D: Disk> Scheme for FileScheme<D> {
|
||||
};
|
||||
|
||||
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<usize> {
|
||||
fn chmod(&mut self, url: &str, mode: u16, uid: u32, gid: u32) -> Result<usize> {
|
||||
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<usize> {
|
||||
fn rmdir(&mut self, url: &str, uid: u32, gid: u32) -> Result<usize> {
|
||||
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<usize> {
|
||||
fn unlink(&mut self, url: &str, uid: u32, gid: u32) -> Result<usize> {
|
||||
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<usize> {
|
||||
fn dup(&mut self, old_id: usize, buf: &[u8]) -> Result<usize> {
|
||||
// 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<usize> {
|
||||
fn read(&mut self, id: usize, buf: &mut [u8]) -> Result<usize> {
|
||||
// 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<usize> {
|
||||
fn write(&mut self, id: usize, buf: &[u8]) -> Result<usize> {
|
||||
// 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<isize> {
|
||||
fn seek(&mut self, id: usize, pos: isize, whence: usize) -> Result<isize> {
|
||||
// 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<usize> {
|
||||
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<usize> {
|
||||
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<usize> {
|
||||
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<usize> {
|
||||
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<usize> {
|
||||
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<usize> {
|
||||
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<EventFlags> {
|
||||
let files = self.files.borrow_mut();
|
||||
if let Some(file) = files.get(&id) {
|
||||
fn fevent(&mut self, id: usize, _flags: EventFlags) -> Result<EventFlags> {
|
||||
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<D: Disk> Scheme for FileScheme<D> {
|
||||
}
|
||||
}
|
||||
|
||||
fn fpath(&self, id: usize, buf: &mut [u8]) -> Result<usize> {
|
||||
fn fpath(&mut self, id: usize, buf: &mut [u8]) -> Result<usize> {
|
||||
// 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<D: Disk> Scheme for FileScheme<D> {
|
||||
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<usize> {
|
||||
fn frename(&mut self, id: usize, url: &str, uid: u32, gid: u32) -> Result<usize> {
|
||||
unimplemented!();
|
||||
/*TODO: FRENAME
|
||||
let path = url.trim_matches('/');
|
||||
|
||||
// println!("Frename {}, {} from {}, {}", id, path, uid, gid);
|
||||
@@ -613,19 +646,19 @@ impl<D: Disk> Scheme for FileScheme<D> {
|
||||
}
|
||||
|
||||
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<D: Disk> Scheme for FileScheme<D> {
|
||||
} else {
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
fn fstat(&self, id: usize, stat: &mut Stat) -> Result<usize> {
|
||||
fn fstat(&mut self, id: usize, stat: &mut Stat) -> Result<usize> {
|
||||
// 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<usize> {
|
||||
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<usize> {
|
||||
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<D: Disk> Scheme for FileScheme<D> {
|
||||
}
|
||||
}
|
||||
|
||||
fn fsync(&self, id: usize) -> Result<usize> {
|
||||
fn fsync(&mut self, id: usize) -> Result<usize> {
|
||||
// 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<usize> {
|
||||
fn ftruncate(&mut self, id: usize, len: usize) -> Result<usize> {
|
||||
// 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<usize> {
|
||||
fn futimens(&mut self, id: usize, times: &[TimeSpec]) -> Result<usize> {
|
||||
// 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<usize> {
|
||||
fn fmap(&mut self, id: usize, map: &Map) -> Result<usize> {
|
||||
// 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<usize> {
|
||||
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<usize> {
|
||||
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<D: Disk> Scheme for FileScheme<D> {
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: implement
|
||||
fn funmap(&self, address: usize, length: usize) -> Result<usize> {
|
||||
//TODO: implement (length is ignored!)
|
||||
fn funmap(&mut self, address: usize, length: usize) -> Result<usize> {
|
||||
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<usize> {
|
||||
fn close(&mut self, id: usize) -> Result<usize> {
|
||||
// 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))
|
||||
|
||||
+211
-110
@@ -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<Self> {
|
||||
// 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<BlockRaw>;
|
||||
type BlockListL2 = BlockList<BlockListL1>;
|
||||
type BlockListL3 = BlockList<BlockListL2>;
|
||||
type BlockListL4 = BlockList<BlockListL3>;
|
||||
|
||||
/// 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<BlockRaw>; 128],
|
||||
// 64 * 256 * BLOCK_SIZE (64 MiB, 1 MiB each)
|
||||
pub level1: [BlockPtr<BlockListL1>; 64],
|
||||
// 32 * 256 * 256 * BLOCK_SIZE (8 GiB, 256 MiB each)
|
||||
pub level2: [BlockPtr<BlockListL2>; 32],
|
||||
// 16 * 256 * 256 * 256 * BLOCK_SIZE (1 TiB, 64 GiB each)
|
||||
pub level3: [BlockPtr<BlockListL3>; 16],
|
||||
// 12 * 256 * 256 * 256 * 256 * BLOCK_SIZE (192 TiB, 16 TiB each)
|
||||
pub level4: [BlockPtr<BlockListL4>; 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<Node> {
|
||||
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;
|
||||
}
|
||||
|
||||
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 mode(&self) -> u16 {
|
||||
{ self.mode }.to_native()
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Result<&str, str::Utf8Error> {
|
||||
let mut len = 0;
|
||||
|
||||
for &b in self.name.iter() {
|
||||
if b == 0 {
|
||||
break;
|
||||
}
|
||||
len += 1;
|
||||
}
|
||||
|
||||
str::from_utf8(&self.name[..len])
|
||||
pub fn uid(&self) -> u32 {
|
||||
{ self.uid }.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 gid(&self) -> u32 {
|
||||
{ self.gid }.to_native()
|
||||
}
|
||||
|
||||
self.name = bytes;
|
||||
pub fn links(&self) -> u32 {
|
||||
{ self.links }.to_native()
|
||||
}
|
||||
|
||||
Ok(())
|
||||
pub fn size(&self) -> u64 {
|
||||
{ self.size }.to_native()
|
||||
}
|
||||
|
||||
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::<Node>(), BLOCK_SIZE as usize);
|
||||
assert_eq!(mem::size_of::<Node>(), crate::BLOCK_SIZE as usize);
|
||||
}
|
||||
|
||||
+6
-4
@@ -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
|
||||
|
||||
+1015
File diff suppressed because it is too large
Load Diff
+161
@@ -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<TreeList<TreeList<TreeList<BlockRaw>>>>;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct TreeData<T> {
|
||||
id: u32,
|
||||
data: T,
|
||||
}
|
||||
|
||||
impl<T> TreeData<T> {
|
||||
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<T> {
|
||||
TreePtr {
|
||||
id: self.id.into(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
pub struct TreeList<T> {
|
||||
pub ptrs: [BlockPtr<T>; 1 << TREE_LIST_SHIFT],
|
||||
}
|
||||
|
||||
impl<T> Default for TreeList<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ptrs: [BlockPtr::default(); 1 << TREE_LIST_SHIFT],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ops::Deref for TreeList<T> {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &[u8] {
|
||||
unsafe {
|
||||
slice::from_raw_parts(
|
||||
self as *const TreeList<T> as *const u8,
|
||||
mem::size_of::<TreeList<T>>(),
|
||||
) as &[u8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ops::DerefMut for TreeList<T> {
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
unsafe {
|
||||
slice::from_raw_parts_mut(
|
||||
self as *mut TreeList<T> as *mut u8,
|
||||
mem::size_of::<TreeList<T>>(),
|
||||
) as &mut [u8]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
pub struct TreePtr<T> {
|
||||
id: u32le,
|
||||
phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> TreePtr<T> {
|
||||
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<T> Clone for TreePtr<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: self.id,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for TreePtr<T> {}
|
||||
|
||||
impl<T> Default for TreePtr<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: 0.into(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_list_size_test() {
|
||||
assert_eq!(
|
||||
mem::size_of::<TreeList<BlockRaw>>(),
|
||||
crate::BLOCK_SIZE as usize
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user