Red Bear OS redoxfs baseline from 0.1.0 pre-patched archive

This commit is contained in:
Red Bear OS
2026-06-27 09:21:43 +03:00
commit ab50acfcc7
47 changed files with 14213 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
target
image.bin
image
image*
+30
View File
@@ -0,0 +1,30 @@
image: "redoxos/redoxer"
stages:
- build
- test
cache:
paths:
- target/
build:linux:
stage: build
script: cargo +nightly build --verbose
build:redox:
stage: build
script: redoxer build --verbose
test:linux:
stage: test
dependencies:
- build:linux
script: cargo +nightly test --verbose
test:redox:
stage: test
dependencies:
- build:redox
# only run integration test as without KVM unit tests is super slow
script: redoxer test --verbose -- --test '*' -- --nocapture
+21
View File
@@ -0,0 +1,21 @@
sudo: required
language: rust
rust:
- nightly
os:
- linux
- osx
dist: trusty
before_install:
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then
sudo apt-get install -qq pkg-config fuse libfuse-dev;
sudo modprobe fuse;
sudo chmod 666 /dev/fuse;
sudo chown root:$USER /etc/fuse.conf;
fi
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then
brew update;
brew install Caskroom/cask/osxfuse;
fi
notifications:
email: false
Generated
+1017
View File
File diff suppressed because it is too large Load Diff
+96
View File
@@ -0,0 +1,96 @@
[package]
name = "redoxfs"
description = "The Redox Filesystem"
repository = "https://gitlab.redox-os.org/redox-os/redoxfs"
version = "0.9.0"
license-file = "LICENSE"
readme = "README.md"
authors = ["Jeremy Soller <jackpot51@gmail.com>"]
edition = "2021"
[lib]
name = "redoxfs"
path = "src/lib.rs"
[[bin]]
name = "redoxfs"
path = "src/bin/mount.rs"
doc = false
required-features = ["std"]
[[bin]]
name = "redoxfs-ar"
path = "src/bin/ar.rs"
doc = false
required-features = ["std"]
[[bin]]
name = "redoxfs-clone"
path = "src/bin/clone.rs"
doc = false
required-features = ["std"]
[[bin]]
name = "redoxfs-mkfs"
path = "src/bin/mkfs.rs"
doc = false
required-features = ["std"]
[[bin]]
name = "redoxfs-resize"
path = "src/bin/resize.rs"
doc = false
required-features = ["std"]
[dependencies]
aes = { version = "0.8", default-features = false }
argon2 = { version = "0.4", default-features = false, features = ["alloc"] }
base64ct = { version = "1", default-features = false }
bitflags = "2"
endian-num = "0.1"
env_logger = { version = "0.11", optional = true }
getrandom = { version = "0.2.5", optional = true }
humansize = { version = "2", optional = true }
libc = "0.2"
log = { version = "0.4.14", default-features = false, optional = true }
lz4_flex = { version = "0.11", default-features = false, features = ["checked-decode"] }
parse-size = { version = "1", optional = true }
range-tree = { version = "0.1", optional = true }
redox_syscall = "0.7.3"
seahash = { version = "4.1.0", default-features = false }
termion = { version = "4", optional = true }
uuid = { version = "1.4", default-features = false }
xts-mode = { version = "0.5", default-features = false }
[features]
default = ["std", "log", "fuse"]
fuse = [
"fuser",
"std",
]
std = [
"env_logger",
"getrandom",
"humansize",
"libredox",
"parse-size",
"range-tree",
"termion",
"uuid/v4",
"redox_syscall/std",
"redox-scheme",
]
[target.'cfg(not(target_os = "redox"))'.dependencies]
fuser = { version = "0.16", optional = true }
[target.'cfg(target_os = "redox")'.dependencies]
libredox = { version = "0.1.13", optional = true }
redox-path = "0.3.0"
redox-scheme = { version = "0.11.0", optional = true }
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }
[dev-dependencies]
assert_cmd = "2.0.17"
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Jeremy Soller
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+37
View File
@@ -0,0 +1,37 @@
UNAME := $(shell uname)
ifeq ($(UNAME),Darwin)
FUMOUNT=umount
else ifeq ($(UNAME),FreeBSD)
FUMOUNT=sudo umount
else
# Detect which version of the fusermount binary is available.
ifneq (, $(shell which fusermount3))
FUMOUNT=fusermount3 -u
else
FUMOUNT=fusermount -u
endif
endif
image.bin:
cargo build --release --bin redoxfs-mkfs
dd if=/dev/zero of=image.bin bs=1048576 count=1024
target/release/redoxfs-mkfs image.bin
mount: image.bin FORCE
mkdir -p image
cargo build --release --bin redoxfs
target/release/redoxfs image.bin image
unmount: FORCE
sync
-${FUMOUNT} image
rm -rf image
clean: FORCE
sync
-${FUMOUNT} image
rm -rf image image.bin
cargo clean
FORCE:
+53
View File
@@ -0,0 +1,53 @@
# RedoxFS
This is the default filesystem of Redox OS inspired by [ZFS](https://docs.freebsd.org/en/books/handbook/zfs/) and adapted to a microkernel architecture.
(It's a replacement for [TFS](https://gitlab.redox-os.org/redox-os/tfs))
Current features:
- Compatible with Redox and Linux (FUSE)
- Copy-on-write
- Data/metadata checksums
- Transparent encryption
- Standard Unix file attributes
- File/directory size limit up to 193TiB (212TB)
- File/directory quantity limit up to 4 billion per 193TiB (2^32 - 1 = 4294967295)
- MIT licensed
- Disk encryption fully supported by the Redox bootloader, letting it load the kernel off an encrypted partition.
Being MIT licensed, RedoxFS can be bundled on GPL-licensed operating systems (Linux, for example).
### Install RedoxFS
```sh
cargo install redoxfs
```
You can also build RedoxFS from this repository.
### Configure your storage device to allow rootless usage
If you are on Linux you need root permission to acess block devices (storage), but it's recommended to run RedoxFS as rootless.
To do that you need to configure your storage device permission to your user with the following command:
```sh
sudo setfacl -m u:your-username:rw /path/to/disk
```
### Create, mount and customize your RedoxFS partition
See [the instructions in the book](https://doc.redox-os.org/book/redoxfs.html) for RedoxFS tooling usage.
Currently RedoxFS tooling are:
- `redoxfs` mount a RedoxFS disk
- `redoxfs-ar` write files to a RedoxFS disk
- `redoxfs-clone` clone a RedoxFS disk
- `redoxfs-mkfs` create an empty RedoxFS disk
- `redoxfs-resize` resize a RedoxFS disk
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
[![crates.io](http://meritbadge.herokuapp.com/redoxfs)](https://crates.io/crates/redoxfs)
[![docs.rs](https://docs.rs/redoxfs/badge.svg)](https://docs.rs/redoxfs)
+4
View File
@@ -0,0 +1,4 @@
target
corpus
artifacts
coverage
+858
View File
@@ -0,0 +1,858 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aes"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
dependencies = [
"anstyle",
"once_cell",
"windows-sys 0.59.0",
]
[[package]]
name = "anyhow"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "arbitrary"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
dependencies = [
"derive_arbitrary",
]
[[package]]
name = "argon2"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db4ce4441f99dbd377ca8a8f57b698c44d0d6e712d8329b5040da5a64aa1ce73"
dependencies = [
"base64ct",
"blake2",
]
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bitflags"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695"
dependencies = [
"jobserver",
"libc",
"once_cell",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "colorchoice"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "cpufeatures"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "derive_arbitrary"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]]
name = "endian-num"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad847bb2094f110bbdd6fa564894ca4556fd978958e93985420d680d3cb6d14"
[[package]]
name = "env_filter"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"jiff",
"log",
]
[[package]]
name = "errno"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "fastrand"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
[[package]]
name = "fuser"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bb29a3ae32279fe3e79a958fe01899f5fb23eadccee919cf88e145b54ed9367"
dependencies = [
"libc",
"log",
"memchr",
"nix",
"page_size",
"smallvec",
"zerocopy",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "humansize"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
dependencies = [
"libm",
]
[[package]]
name = "inout"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
dependencies = [
"generic-array",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "jiff"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ad87c89110f55e4cd4dc2893a9790820206729eaf221555f742d540b0724a0"
dependencies = [
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
"serde",
]
[[package]]
name = "jiff-static"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d076d5b64a7e2fe6f0743f02c43ca4a6725c0f904203bfe276a5b3e793103605"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "jobserver"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e"
dependencies = [
"libc",
]
[[package]]
name = "libc"
version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "libfuzzer-sys"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7"
dependencies = [
"arbitrary",
"cc",
"once_cell",
]
[[package]]
name = "libm"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
name = "libredox"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
dependencies = [
"bitflags",
"libc",
"redox_syscall 0.7.0",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "lz4_flex"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "nix"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "numtoa"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f"
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "page_size"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "parse-size"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "487f2ccd1e17ce8c1bfab3a65c89525af41cfad4c8659021a1e9a2aacd73b89b"
[[package]]
name = "portable-atomic"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
[[package]]
name = "portable-atomic-util"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
"portable-atomic",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "range-tree"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384c2842d4e069d5ccacf5fe1dca4ef8d07a5444329715f0fc3c61813502d4d1"
[[package]]
name = "redox-path"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717"
[[package]]
name = "redox-scheme"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cea2668a5932f878a4298a1d7f8950249bbbb77120fb263da252c589152f5ea"
dependencies = [
"libredox",
"redox_syscall 0.6.0",
]
[[package]]
name = "redox_syscall"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_syscall"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_termios"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb"
[[package]]
name = "redoxfs"
version = "0.8.4"
dependencies = [
"aes",
"argon2",
"base64ct",
"bitflags",
"endian-num",
"env_logger",
"fuser",
"getrandom",
"humansize",
"libc",
"libredox",
"log",
"lz4_flex",
"parse-size",
"range-tree",
"redox-path",
"redox-scheme",
"redox_syscall 0.6.0",
"seahash",
"termion",
"uuid",
"xts-mode",
]
[[package]]
name = "redoxfs-fuzz"
version = "0.0.0"
dependencies = [
"anyhow",
"arbitrary",
"fuser",
"libfuzzer-sys",
"nix",
"redoxfs",
"tempfile",
]
[[package]]
name = "regex"
version = "1.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "rustix"
version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "seahash"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "subtle"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "syn"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
dependencies = [
"cfg-if",
"fastrand",
"rustix",
"windows-sys 0.52.0",
]
[[package]]
name = "termion"
version = "4.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3669a69de26799d6321a5aa713f55f7e2cd37bd47be044b50f2acafc42c122bb"
dependencies = [
"libc",
"libredox",
"numtoa",
"redox_termios",
]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
dependencies = [
"getrandom",
]
[[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.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "xts-mode"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cbddb7545ca0b9ffa7bdc653e8743303e1712687a6918ced25f2cdbed42520"
dependencies = [
"byteorder",
"cipher",
]
[[package]]
name = "zerocopy"
version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
+30
View File
@@ -0,0 +1,30 @@
[package]
name = "redoxfs-fuzz"
version = "0.0.0"
publish = false
edition = "2021"
[features]
default = []
log = []
[package.metadata]
cargo-fuzz = true
[dependencies]
anyhow = "1.0.86"
arbitrary = { version = "1.3.2", features = ["derive"] }
fuser = { version = "0.16" }
libfuzzer-sys = "0.4"
nix = { version = "0.29.0", features = ["fs"] }
tempfile = "3.10.1"
[dependencies.redoxfs]
path = ".."
[[bin]]
name = "fuse_fuzz_target"
path = "fuzz_targets/fuse_fuzz_target.rs"
test = false
doc = false
bench = false
+338
View File
@@ -0,0 +1,338 @@
//! Fuzzer that exercises random file system operations against a FUSE-mounted redoxfs.
#![no_main]
use anyhow::{ensure, Result};
use fuser;
use libfuzzer_sys::{arbitrary::Arbitrary, fuzz_target, Corpus};
use nix::sys::statvfs::statvfs;
use std::{
fs::{self, File, FileTimes, OpenOptions},
io::{Read, Seek, SeekFrom, Write},
os::unix::fs::{self as unix_fs, PermissionsExt},
path::{Path, PathBuf},
thread,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use tempfile;
use redoxfs::{mount::fuse::Fuse, DiskSparse, FileSystem};
/// Maximum size for files and buffers. Chosen arbitrarily with fuzzing performance in mind.
const MAX_SIZE: u64 = 10_000_000;
/// Limit on the number of remounts in a single test case. Chosen arbitrarily with fuzzing
/// performance in mind: remounts are costly.
const MAX_MOUNT_SEQUENCES: usize = 3;
/// An operation to be performed by the fuzzer.
#[derive(Arbitrary, Clone, Debug)]
enum Operation {
Chown {
path: PathBuf,
uid: Option<u32>,
gid: Option<u32>,
},
CreateDir {
path: PathBuf,
},
HardLink {
original: PathBuf,
link: PathBuf,
},
Metadata {
path: PathBuf,
},
Read {
path: PathBuf,
},
ReadDir {
path: PathBuf,
},
ReadLink {
path: PathBuf,
},
RemoveDir {
path: PathBuf,
},
RemoveFile {
path: PathBuf,
},
Rename {
from: PathBuf,
to: PathBuf,
},
SeekRead {
path: PathBuf,
seek_pos: u64,
buf_size: usize,
},
SeekWrite {
path: PathBuf,
seek_pos: u64,
buf_size: usize,
},
SetLen {
path: PathBuf,
size: u64,
},
SetPermissions {
path: PathBuf,
readonly: Option<bool>,
mode: Option<u32>,
},
SetTimes {
path: PathBuf,
accessed_since_epoch: Option<Duration>,
modified_since_epoch: Option<Duration>,
},
Statvfs {},
SymLink {
original: PathBuf,
link: PathBuf,
},
Write {
path: PathBuf,
buf_size: usize,
},
}
/// Parameters for mounting the file system and operations to be performed afterwards.
#[derive(Arbitrary, Clone, Debug)]
struct MountSequence {
cleanup: bool,
operations: Vec<Operation>,
}
/// The whole input to a single fuzzer invocation.
#[derive(Arbitrary, Clone, Debug)]
struct TestCase {
disk_size: u64,
reserved_size: u64,
mount_sequences: Vec<MountSequence>,
}
/// Creates the disk for backing the Redoxfs.
fn create_disk(temp_path: &Path, disk_size: u64) -> DiskSparse {
let disk_path = temp_path.join("disk.img");
DiskSparse::create(disk_path, disk_size).unwrap()
}
/// Creates an empty Redoxfs.
fn create_redoxfs(disk: DiskSparse, reserved_size: u64) -> bool {
let password = None;
let reserved = vec![0; reserved_size as usize];
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
FileSystem::create_reserved(
disk,
password,
&reserved,
ctime.as_secs(),
ctime.subsec_nanos(),
)
.is_ok()
}
/// Mounts an existing Redoxfs, runs the callback and performs the unmount.
fn with_redoxfs_mount<F>(temp_path: &Path, disk: DiskSparse, cleanup: bool, callback: F)
where
F: FnOnce(&Path) + Send + 'static,
{
let password = None;
let block = None;
let mut fs = FileSystem::open(disk, password, block, cleanup).unwrap();
let mount_path = temp_path.join("mount");
fs::create_dir_all(&mount_path).unwrap();
let mut session = fuser::Session::new(Fuse { fs: &mut fs }, &mount_path, &[]).unwrap();
let mut unmounter = session.unmount_callable();
let join_handle = thread::spawn(move || {
callback(&mount_path);
unmounter.unmount().unwrap();
});
session.run().unwrap();
join_handle.join().unwrap();
}
fn get_path_within_fs(fs_path: &Path, path_to_add: &Path) -> Result<PathBuf> {
ensure!(path_to_add.is_relative());
ensure!(path_to_add
.components()
.all(|c| c != std::path::Component::ParentDir));
Ok(fs_path.join(path_to_add))
}
fn do_operation(fs_path: &Path, op: &Operation) -> Result<()> {
match op {
Operation::Chown { path, uid, gid } => {
let path = get_path_within_fs(fs_path, path)?;
unix_fs::chown(path, *uid, *gid)?;
}
Operation::CreateDir { path } => {
let path = get_path_within_fs(fs_path, path)?;
fs::create_dir(path)?;
}
Operation::HardLink { original, link } => {
let original = get_path_within_fs(fs_path, original)?;
let link = get_path_within_fs(fs_path, link)?;
fs::hard_link(original, link)?;
}
Operation::Metadata { path } => {
let path = get_path_within_fs(fs_path, path)?;
fs::metadata(path)?;
}
Operation::Read { path } => {
let path = get_path_within_fs(fs_path, path)?;
fs::read(path)?;
}
Operation::ReadDir { path } => {
let path = get_path_within_fs(fs_path, path)?;
let _ = fs::read_dir(path)?.count();
}
Operation::ReadLink { path } => {
let path = get_path_within_fs(fs_path, path)?;
fs::read_link(path)?;
}
Operation::RemoveDir { path } => {
let path = get_path_within_fs(fs_path, path)?;
fs::remove_dir(path)?;
}
Operation::RemoveFile { path } => {
let path = get_path_within_fs(fs_path, path)?;
fs::remove_file(path)?;
}
Operation::Rename { from, to } => {
let from = get_path_within_fs(fs_path, from)?;
let to = get_path_within_fs(fs_path, to)?;
fs::rename(from, to)?;
}
Operation::SeekRead {
path,
seek_pos,
buf_size,
} => {
ensure!(*buf_size as u64 <= MAX_SIZE);
let path = get_path_within_fs(fs_path, path)?;
let mut file = File::open(path)?;
file.seek(SeekFrom::Start(*seek_pos))?;
let mut buf = vec![0; *buf_size];
file.read(&mut buf)?;
}
Operation::SeekWrite {
path,
seek_pos,
buf_size,
} => {
ensure!(*seek_pos <= MAX_SIZE);
ensure!(*buf_size as u64 <= MAX_SIZE);
let path = get_path_within_fs(fs_path, path)?;
let mut file = OpenOptions::new().write(true).open(path)?;
file.seek(SeekFrom::Start(*seek_pos))?;
let buf = vec![0; *buf_size];
file.write(&buf)?;
}
Operation::SetLen { path, size } => {
let path = get_path_within_fs(fs_path, path)?;
let file = OpenOptions::new().write(true).open(path)?;
file.set_len(*size)?;
}
Operation::SetPermissions {
path,
readonly,
mode,
} => {
let path = get_path_within_fs(fs_path, path)?;
let metadata = fs::metadata(&path)?;
let mut perms = metadata.permissions();
if let Some(readonly) = readonly {
perms.set_readonly(*readonly);
}
if let Some(mode) = mode {
perms.set_mode(*mode);
}
fs::set_permissions(path, perms)?;
}
Operation::SetTimes {
path,
accessed_since_epoch,
modified_since_epoch,
} => {
let path = get_path_within_fs(fs_path, path)?;
let file = File::options().write(true).open(path)?;
let mut times = FileTimes::new();
if let Some(accessed_since_epoch) = accessed_since_epoch {
if let Some(accessed) = UNIX_EPOCH.checked_add(*accessed_since_epoch) {
times = times.set_accessed(accessed);
}
}
if let Some(modified_since_epoch) = modified_since_epoch {
if let Some(modified) = UNIX_EPOCH.checked_add(*modified_since_epoch) {
times = times.set_modified(modified);
}
}
file.set_times(times)?;
}
Operation::Statvfs {} => {
statvfs(fs_path)?;
}
Operation::SymLink { original, link } => {
let original = get_path_within_fs(fs_path, original)?;
let link = get_path_within_fs(fs_path, link)?;
unix_fs::symlink(original, link)?;
}
Operation::Write { path, buf_size } => {
ensure!(*buf_size as u64 <= MAX_SIZE);
let path = get_path_within_fs(fs_path, path)?;
let buf = vec![0; *buf_size];
fs::write(path, &buf)?;
}
}
Ok(())
}
fuzz_target!(|test_case: TestCase| -> Corpus {
if test_case.disk_size > MAX_SIZE
|| test_case.reserved_size > MAX_SIZE
|| test_case.mount_sequences.len() > MAX_MOUNT_SEQUENCES
{
return Corpus::Reject;
}
let temp_dir = tempfile::Builder::new()
.prefix("fuse_fuzz_target")
.tempdir()
.unwrap();
#[cfg(feature = "log")]
eprintln!("create fs");
let disk = create_disk(temp_dir.path(), test_case.disk_size);
if !create_redoxfs(disk, test_case.reserved_size) {
// File system creation failed (e.g., due to insufficient space) so we bail out, still
// exercising this code path is useful.
return Corpus::Keep;
}
for mount_seq in test_case.mount_sequences.iter() {
#[cfg(feature = "log")]
eprintln!("mount fs: path {:?}, size{}", temp_dir.path(), test_case.disk_size);
let disk = create_disk(temp_dir.path(), test_case.disk_size);
let operations = mount_seq.operations.clone();
with_redoxfs_mount(temp_dir.path(), disk, mount_seq.cleanup, move |fs_path| {
for operation in operations.iter() {
#[cfg(feature = "log")]
eprintln!("do operation {operation:?}");
let _result = do_operation(fs_path, operation);
#[cfg(feature = "log")]
eprintln!("operation result {:?}", _result.err());
}
});
#[cfg(feature = "log")]
eprintln!("unmounted fs");
}
Corpus::Keep
});
+394
View File
@@ -0,0 +1,394 @@
use alloc::{collections::BTreeSet, vec::Vec};
use core::{fmt, mem, ops, slice};
use endian_num::Le;
use crate::{BlockAddr, BlockLevel, BlockMeta, BlockPtr, BlockTrait, Node, TreePtr, BLOCK_SIZE};
pub const ALLOC_LIST_ENTRIES: usize =
(BLOCK_SIZE as usize - mem::size_of::<BlockPtr<AllocList>>()) / mem::size_of::<AllocEntry>();
pub const RELEASE_LIST_ENTRIES: usize = (BLOCK_SIZE as usize
- mem::size_of::<BlockPtr<ReleaseList>>())
/ mem::size_of::<TreePtr<Node>>();
/// The RedoxFS block allocator. This struct manages all "data" blocks in RedoxFS
/// (i.e, all blocks that aren't reserved or part of the header chain).
///
/// [`Allocator`] can allocate blocks of many "levels"---that is, it can
/// allocate multiple consecutive [`BLOCK_SIZE`] blocks in one operation.
///
/// This reduces the amount of memory that the [`Allocator`] uses:
/// Instead of storing the index of each free [`BLOCK_SIZE`] block,
/// the `levels` array can keep track of higher-level blocks, splitting
/// them when a smaller block is requested.
///
/// Higher-level blocks also allow us to more efficiently allocate memory
/// for large files.
#[derive(Clone, Default)]
pub struct Allocator {
/// This array keeps track of all free blocks of each level,
/// and is initialized using the AllocList chain when we open the filesystem.
///
/// Every element of the outer array represents a block level:
/// - item 0: free level 0 blocks (with size [`BLOCK_SIZE`])
/// - item 1: free level 1 blocks (with size 2*[`BLOCK_SIZE`])
/// - item 2: free level 2 blocks (with size 4*[`BLOCK_SIZE`])
/// ...and so on.
///
/// Each inner array contains a list of free block indices,
levels: Vec<BTreeSet<u64>>,
}
impl Allocator {
pub fn levels(&self) -> &Vec<BTreeSet<u64>> {
&self.levels
}
/// Count the number of free [`BLOCK_SIZE`] available to this [`Allocator`].
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
}
/// Find a free block of the given level, mark it as "used", and return its address.
/// Returns [`None`] if there are no free blocks with this level.
pub fn allocate(&mut self, meta: BlockMeta) -> Option<BlockAddr> {
// First, find the lowest level with a free block
let mut free_opt = None;
{
let mut level = meta.level.0;
// Start searching at the level we want. Smaller levels are too small!
while level < self.levels.len() {
if let Some(&index) = self.levels[level].first() {
// Find the index closest to the start of the filesystem
free_opt = match free_opt {
Some((free_level, free_index)) if free_index <= index => {
Some((free_level, free_index))
}
_ => Some((level, index)),
};
}
level += 1;
}
}
// If a free block was found, split it until we find a usable block of the right level.
// The left side of the split block is kept free, and the right side is allocated.
let (mut level, index) = free_opt?;
self.levels[level].remove(&index);
while level > meta.level.0 {
level -= 1;
let level_size = 1 << level;
self.levels[level].insert(index + level_size);
}
Some(unsafe { BlockAddr::new(index, meta) })
}
/// Try to allocate the exact block specified, making all necessary splits.
/// Returns [`None`] if this some (or all) of this block is already allocated.
///
/// Note that [`BlockAddr`] encodes the blocks location _and_ level.
pub fn allocate_exact(&mut self, exact_addr: BlockAddr) -> Option<BlockAddr> {
// This function only supports level 0 right now
assert_eq!(exact_addr.level().0, 0);
let exact_index = exact_addr.index();
let mut index_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(index) = index_opt.take() {
self.levels[level].insert(index);
self.levels[level].insert(index + level_size);
}
// Look for matching block and remove it
for &start in self.levels[level].iter() {
if start <= exact_index {
let end = start + level_size;
if end > exact_index {
self.levels[level].remove(&start);
index_opt = Some(start);
break;
}
}
}
}
Some(unsafe { BlockAddr::new(index_opt?, exact_addr.meta()) })
}
/// Deallocate the given block, marking it "free" so that it can be re-used later.
pub fn deallocate(&mut self, addr: BlockAddr) {
// When we deallocate, we check if block we're deallocating has a free sibling.
// If it does, we join the two to create one free block in the next (higher) level.
//
// We repeat this until we no longer have a sibling to join.
let mut index = addr.index();
let mut level = addr.level().0;
loop {
while level >= self.levels.len() {
self.levels.push(BTreeSet::new());
}
let level_size = 1 << level;
let next_size = level_size << 1;
let mut found = false;
// look at all free blocks in the current level...
for &level_index in self.levels[level].iter() {
// - the block we just freed aligns with the next largest block, and
// - the second block we're looking at is the right sibling of this block
if index % next_size == 0 && index + level_size == level_index {
// "alloc" the next highest block, repeat deallocation process.
self.levels[level].remove(&level_index);
found = true;
break;
// - the index of this block doesn't align with the next largest block, and
// - the block we're looking at is the left neighbor of this block
} else if level_index % next_size == 0 && level_index + level_size == index {
// "alloc" the next highest block, repeat deallocation process.
self.levels[level].remove(&level_index);
index = level_index; // index moves to left block
found = true;
break;
}
}
// We couldn't find a higher block,
// deallocate this one and finish
if !found {
self.levels[level].insert(index);
return;
}
// repeat deallocation process on the
// higher-level block we just created.
level += 1;
}
}
}
#[repr(C, packed)]
#[derive(Clone, Copy, Default, Debug)]
pub struct AllocEntry {
/// The index of the first block this [`AllocEntry`] refers to
index: Le<u64>,
/// The number of blocks after (and including) `index` that are are free or used.
/// If negative, they are used; if positive, they are free.
count: Le<i64>,
}
impl AllocEntry {
pub fn new(index: u64, count: i64) -> Self {
Self {
index: index.into(),
count: count.into(),
}
}
pub fn allocate(addr: BlockAddr) -> Self {
Self::new(addr.index(), -addr.level().blocks::<i64>())
}
pub fn deallocate(addr: BlockAddr) -> Self {
Self::new(addr.index(), addr.level().blocks::<i64>())
}
pub fn index(&self) -> u64 {
self.index.to_ne()
}
pub fn count(&self) -> i64 {
self.count.to_ne()
}
pub fn is_null(&self) -> bool {
self.count() == 0
}
}
/// A node in the allocation chain.
#[repr(C, packed)]
pub struct AllocList {
/// A pointer to the previous AllocList.
/// If this is the null pointer, this is the first element of the chain.
pub prev: BlockPtr<AllocList>,
/// Allocation entries.
pub entries: [AllocEntry; ALLOC_LIST_ENTRIES],
}
unsafe impl BlockTrait for AllocList {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self {
prev: BlockPtr::default(),
entries: [AllocEntry::default(); ALLOC_LIST_ENTRIES],
})
} else {
None
}
}
}
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]
}
}
}
/// A list of nodes pending release.
#[repr(C, packed)]
pub struct ReleaseList {
/// A pointer to the previous ReleaseList.
/// If this is the null pointer, this is the first element of the chain.
pub prev: BlockPtr<ReleaseList>,
/// Allocation entries.
pub entries: [TreePtr<Node>; RELEASE_LIST_ENTRIES],
}
unsafe impl BlockTrait for ReleaseList {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self {
prev: BlockPtr::default(),
entries: [TreePtr::<Node>::default(); RELEASE_LIST_ENTRIES],
})
} else {
None
}
}
}
impl fmt::Debug for ReleaseList {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let prev = self.prev;
let entries: Vec<_> = self
.entries
.iter()
.filter(|entry| !entry.is_null())
.map(|entry| entry.id())
.collect();
f.debug_struct("ReleaseList")
.field("prev", &prev)
.field("entries", &entries)
.finish()
}
}
impl ops::Deref for ReleaseList {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const ReleaseList as *const u8,
mem::size_of::<ReleaseList>(),
) as &[u8]
}
}
}
impl ops::DerefMut for ReleaseList {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut ReleaseList as *mut u8,
mem::size_of::<ReleaseList>(),
) as &mut [u8]
}
}
}
#[test]
fn alloc_node_size_test() {
assert_eq!(mem::size_of::<AllocList>(), crate::BLOCK_SIZE as usize);
}
#[test]
fn release_node_size_test() {
assert_eq!(mem::size_of::<ReleaseList>(), crate::BLOCK_SIZE as usize);
}
#[test]
fn allocator_test() {
let mut alloc = Allocator::default();
assert_eq!(alloc.allocate(BlockMeta::default()), None);
alloc.deallocate(unsafe { BlockAddr::new(1, BlockMeta::default()) });
assert_eq!(
alloc.allocate(BlockMeta::default()),
Some(unsafe { BlockAddr::new(1, BlockMeta::default()) })
);
assert_eq!(alloc.allocate(BlockMeta::default()), None);
for addr in 1023..2048 {
alloc.deallocate(unsafe { BlockAddr::new(addr, BlockMeta::default()) });
}
assert_eq!(alloc.levels.len(), 11);
for level in 0..alloc.levels.len() {
if level == 0 {
assert_eq!(alloc.levels[level], [1023].into());
} else if level == 10 {
assert_eq!(alloc.levels[level], [1024].into());
} else {
assert_eq!(alloc.levels[level], [0u64; 0].into());
}
}
for addr in 1023..2048 {
assert_eq!(
alloc.allocate(BlockMeta::default()),
Some(unsafe { BlockAddr::new(addr, BlockMeta::default()) })
);
}
assert_eq!(alloc.allocate(BlockMeta::default()), None);
assert_eq!(alloc.levels.len(), 11);
for level in 0..alloc.levels.len() {
assert_eq!(alloc.levels[level], [0u64; 0].into());
}
}
+146
View File
@@ -0,0 +1,146 @@
use std::fs;
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::MetadataExt;
use std::path::Path;
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>>(
tx: &mut Transaction<D>,
parent_path: P,
parent_ptr: TreePtr<Node>,
) -> io::Result<()> {
for entry_res in fs::read_dir(parent_path)? {
let entry = entry_res?;
let metadata = entry.metadata()?;
let file_type = metadata.file_type();
let name = entry.file_name().into_string().map_err(|_| {
io::Error::new(io::ErrorKind::InvalidData, "filename is not valid UTF-8")
})?;
let mode_type = if file_type.is_dir() {
Node::MODE_DIR
} else if file_type.is_file() {
Node::MODE_FILE
} else if file_type.is_symlink() {
Node::MODE_SYMLINK
} else {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("Does not support parsing {:?}", file_type),
));
};
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(tx, path, node_ptr)?;
} else if file_type.is_file() {
let data = fs::read(path)?;
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();
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,
format!("Does not support creating {:?}", file_type),
));
}
}
Ok(())
}
pub fn archive<D: Disk, P: AsRef<Path>>(fs: &mut FileSystem<D>, parent_path: P) -> io::Result<u64> {
let end_block = fs
.tx(|tx| {
// Archive_at root node
archive_at(tx, parent_path, TreePtr::root())
.map_err(|err| syscall::Error::new(err.raw_os_error().unwrap()))?;
// Squash alloc log
tx.sync(true)?;
let end_block = tx.header.size() / BLOCK_SIZE;
/* TODO: Cut off any free blocks at the end of the filesystem
let mut end_changed = true;
while end_changed {
end_changed = false;
let allocator = fs.allocator();
let levels = allocator.levels();
for level in 0..levels.len() {
let level_size = 1 << level;
for &block in levels[level].iter() {
if block < end_block && block + level_size >= end_block {
end_block = block;
end_changed = true;
}
}
}
}
*/
// 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)
}
+108
View File
@@ -0,0 +1,108 @@
extern crate redoxfs;
extern crate syscall;
extern crate uuid;
use std::io::Read;
use std::time::{SystemTime, UNIX_EPOCH};
use std::{env, fs, process};
use redoxfs::{archive, DiskFile, 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() {
path
} else {
println!("redoxfs-ar: no disk image provided");
println!("redoxfs-ar DISK FOLDER [BOOTLOADER]");
process::exit(1);
};
let folder_path = if let Some(path) = args.next() {
path
} else {
println!("redoxfs-ar: no folder provided");
println!("redoxfs-ar DISK FOLDER [BOOTLOADER]");
process::exit(1);
};
let bootloader_path_opt = args.next();
let disk = match DiskFile::open(&disk_path) {
Ok(disk) => disk,
Err(err) => {
println!("redoxfs-ar: failed to open image {}: {}", disk_path, err);
process::exit(1);
}
};
let mut bootloader = vec![];
if let Some(bootloader_path) = bootloader_path_opt {
match fs::File::open(&bootloader_path) {
Ok(mut file) => match file.read_to_end(&mut bootloader) {
Ok(_) => (),
Err(err) => {
println!(
"redoxfs-ar: failed to read bootloader {}: {}",
bootloader_path, err
);
process::exit(1);
}
},
Err(err) => {
println!(
"redoxfs-ar: failed to open bootloader {}: {}",
bootloader_path, err
);
process::exit(1);
}
}
};
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
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,
Err(err) => {
println!("redoxfs-ar: failed to archive {}: {}", folder_path, err);
process::exit(1);
}
};
if let Err(err) = fs.disk.file.set_len(size) {
println!(
"redoxfs-ar: failed to truncate {} to {}: {}",
disk_path, size, err
);
process::exit(1);
}
let uuid = Uuid::from_bytes(fs.header.uuid());
println!(
"redoxfs-ar: created filesystem on {}, reserved {} blocks, size {} MB, uuid {}",
disk_path,
fs.block,
fs.header.size() / 1000 / 1000,
uuid.hyphenated()
);
}
Err(err) => {
println!(
"redoxfs-ar: failed to create filesystem on {}: {}",
disk_path, err
);
process::exit(1);
}
};
}
+153
View File
@@ -0,0 +1,153 @@
extern crate redoxfs;
extern crate syscall;
extern crate uuid;
use std::io::Read;
use std::time::{SystemTime, UNIX_EPOCH};
use std::{env, fs, process};
use redoxfs::{clone, DiskFile, FileSystem};
use uuid::Uuid;
fn main() {
env_logger::init();
let mut args = env::args().skip(1);
let disk_path_old = if let Some(path) = args.next() {
path
} else {
println!("redoxfs-clone: no old disk image provided");
println!("redoxfs-clone NEW-DISK OLD-DISK [BOOTLOADER]");
process::exit(1);
};
let disk_path = if let Some(path) = args.next() {
path
} else {
println!("redoxfs-clone: no new disk image provided");
println!("redoxfs-clone NEW-DISK OLD-DISK [BOOTLOADER]");
process::exit(1);
};
let bootloader_path_opt = args.next();
// Open old disk in readonly mode
let disk_old = match fs::OpenOptions::new()
.read(true)
.write(false)
.open(&disk_path_old)
.map(DiskFile::from)
{
Ok(disk) => disk,
Err(err) => {
println!(
"redoxfs-clone: failed to open old disk image {}: {}",
disk_path_old, err
);
process::exit(1);
}
};
let mut fs_old = match FileSystem::open(disk_old, None, None, false) {
Ok(fs) => fs,
Err(err) => {
println!(
"redoxfs-clone: failed to open filesystem on {}: {}",
disk_path_old, err
);
process::exit(1);
}
};
let disk = match DiskFile::open(&disk_path) {
Ok(disk) => disk,
Err(err) => {
println!(
"redoxfs-clone: failed to open new disk image {}: {}",
disk_path, err
);
process::exit(1);
}
};
let mut bootloader = vec![];
if let Some(bootloader_path) = bootloader_path_opt {
match fs::File::open(&bootloader_path) {
Ok(mut file) => match file.read_to_end(&mut bootloader) {
Ok(_) => (),
Err(err) => {
println!(
"redoxfs-clone: failed to read bootloader {}: {}",
bootloader_path, err
);
process::exit(1);
}
},
Err(err) => {
println!(
"redoxfs-clone: failed to open bootloader {}: {}",
bootloader_path, err
);
process::exit(1);
}
}
};
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
let mut fs = match FileSystem::create_reserved(
disk,
None,
&bootloader,
ctime.as_secs(),
ctime.subsec_nanos(),
) {
Ok(fs) => fs,
Err(err) => {
println!(
"redoxfs-clone: failed to create filesystem on {}: {}",
disk_path, err
);
process::exit(1);
}
};
let size_old = fs_old.header.size();
let free_old = fs_old.allocator().free() * redoxfs::BLOCK_SIZE;
let used_old = size_old - free_old;
let mut last_percent = 0;
let clone_res = clone(&mut fs_old, &mut fs, move |used| {
let percent = (used * 100) / used_old;
if percent != last_percent {
eprint!(
"\r{}%: {} MB/{} MB",
percent,
used / 1000 / 1000,
used_old / 1000 / 1000
);
last_percent = percent;
}
});
eprintln!();
match clone_res {
Ok(()) => (),
Err(err) => {
println!(
"redoxfs-clone: failed to clone {} to {}: {}",
disk_path_old, disk_path, err
);
process::exit(1);
}
}
let uuid = Uuid::from_bytes(fs.header.uuid());
let size = fs.header.size();
let free = fs.allocator().free() * redoxfs::BLOCK_SIZE;
let used = size - free;
println!("redoxfs-clone: created filesystem on {}", disk_path,);
println!("\treserved: {} blocks", fs.block);
println!("\tuuid: {}", uuid.hyphenated());
println!("\tsize: {} MB", size / 1000 / 1000);
println!("\tused: {} MB", used / 1000 / 1000);
println!("\tfree: {} MB", free / 1000 / 1000);
}
+121
View File
@@ -0,0 +1,121 @@
extern crate redoxfs;
extern crate uuid;
use std::io::Read;
use std::{env, fs, io, process, time};
use redoxfs::{DiskFile, FileSystem};
use termion::input::TermRead;
use uuid::Uuid;
fn usage() -> ! {
eprintln!("redoxfs-mkfs [--encrypt] DISK [BOOTLOADER]");
process::exit(1);
}
fn main() {
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 {
eprintln!("redoxfs-mkfs: no disk image provided");
usage();
};
let disk = match DiskFile::open(&disk_path) {
Ok(disk) => disk,
Err(err) => {
eprintln!("redoxfs-mkfs: failed to open image {}: {}", disk_path, err);
process::exit(1);
}
};
let mut bootloader = vec![];
if let Some(bootloader_path) = bootloader_path_opt {
match fs::File::open(&bootloader_path) {
Ok(mut file) => match file.read_to_end(&mut bootloader) {
Ok(_) => (),
Err(err) => {
eprintln!(
"redoxfs-mkfs: failed to read bootloader {}: {}",
bootloader_path, err
);
process::exit(1);
}
},
Err(err) => {
eprintln!(
"redoxfs-mkfs: failed to open bootloader {}: {}",
bootloader_path, err
);
process::exit(1);
}
}
};
let password_opt = if encrypt {
eprint!("redoxfs-mkfs: password: ");
let password = io::stdin()
.read_passwd(&mut io::stderr())
.unwrap()
.unwrap_or_default();
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,
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.uuid());
eprintln!(
"redoxfs-mkfs: created filesystem on {}, reserved {} blocks, size {} MB, uuid {}",
disk_path,
filesystem.block,
filesystem.header.size() / 1000 / 1000,
uuid.hyphenated()
);
}
Err(err) => {
eprintln!(
"redoxfs-mkfs: failed to create filesystem on {}: {}",
disk_path, err
);
process::exit(1);
}
}
}
+409
View File
@@ -0,0 +1,409 @@
extern crate libc;
extern crate redoxfs;
#[cfg(target_os = "redox")]
extern crate syscall;
extern crate uuid;
use std::env;
use std::fs::File;
use std::io::{self, Read, Write};
use std::os::unix::io::{FromRawFd, RawFd};
use std::process;
#[cfg(target_os = "redox")]
use std::{mem::MaybeUninit, ptr::addr_of_mut, sync::atomic::Ordering};
use redoxfs::{mount, DiskCache, DiskFile, FileSystem};
use termion::input::TermRead;
use uuid::Uuid;
#[cfg(target_os = "redox")]
extern "C" fn unmount_handler(_s: usize) {
redoxfs::IS_UMT.store(1, Ordering::SeqCst);
}
#[cfg(target_os = "redox")]
//set up a signal handler on redox, this implements unmounting. I have no idea what sa_flags is
//for, so I put 2. I don't think 0,0 is a valid sa_mask. I don't know what i'm doing here. When u
//send it a sigkill, it shuts off the filesystem
fn setsig() {
// TODO: High-level wrapper like the nix crate?
unsafe {
let mut action = MaybeUninit::<libc::sigaction>::uninit();
assert_eq!(
libc::sigemptyset(addr_of_mut!((*action.as_mut_ptr()).sa_mask)),
0
);
addr_of_mut!((*action.as_mut_ptr()).sa_flags).write(0);
addr_of_mut!((*action.as_mut_ptr()).sa_sigaction).write(unmount_handler as usize);
assert_eq!(
libc::sigaction(libc::SIGTERM, action.as_ptr(), core::ptr::null_mut()),
0
);
}
}
#[cfg(not(target_os = "redox"))]
// on linux, this is implemented properly, so no need for this unscrupulous nonsense!
fn setsig() {}
fn fork() -> isize {
unsafe { libc::fork() as isize }
}
fn pipe(pipes: &mut [i32; 2]) -> isize {
unsafe { libc::pipe(pipes.as_mut_ptr()) as isize }
}
#[cfg(not(target_os = "redox"))]
fn capability_mode() {}
#[cfg(not(target_os = "redox"))]
fn bootloader_password() -> Option<Vec<u8>> {
None
}
#[cfg(target_os = "redox")]
fn capability_mode() {
libredox::call::setrens(0, 0).expect("redoxfs: failed to enter null namespace");
}
#[cfg(target_os = "redox")]
fn bootloader_password() -> Option<Vec<u8>> {
use libredox::call::MmapArgs;
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 aligned_size = size.next_multiple_of(syscall::PAGE_SIZE);
let fd = libredox::Fd::open("memory:physical", libredox::flag::O_CLOEXEC, 0)
.expect("failed to open physical memory file");
let password_map = libredox::call::mmap(MmapArgs {
addr: core::ptr::null_mut(),
length: aligned_size,
prot: libredox::flag::PROT_READ,
flags: libredox::flag::MAP_SHARED,
fd: fd.raw(),
offset: addr as u64,
})
.expect("failed to map REDOXFS_PASSWORD")
.cast::<u8>();
for i in 0..size {
password.push(password_map.add(i).read());
}
let _ = libredox::call::munmap(password_map.cast(), aligned_size);
}
Some(password)
}
fn print_err_exit(err: impl AsRef<str>) -> ! {
eprintln!("redoxfs: {}", err.as_ref());
usage();
process::exit(1)
}
fn print_usage_exit() -> ! {
usage();
process::exit(1)
}
fn usage() {
eprintln!("redoxfs [--no-daemon|-d] [--uuid] [disk or uuid] [mountpoint] [block in hex]");
}
enum DiskId {
Path(String),
Uuid(Uuid),
}
fn filesystem_by_path(
path: &str,
block_opt: Option<u64>,
log_errors: bool,
) -> Option<(String, FileSystem<DiskCache<DiskFile>>)> {
log::debug!("opening {}", path);
let attempts = 10;
for attempt in 0..=attempts {
let password_opt = if attempt > 0 {
eprint!("redoxfs: password: ");
let password = io::stdin()
.read_passwd(&mut io::stderr())
.unwrap()
.unwrap_or_default();
eprintln!();
if password.is_empty() {
eprintln!("redoxfs: empty password, giving up");
// Password is empty, exit loop
break;
}
Some(password.into_bytes())
} else {
bootloader_password()
};
match DiskFile::open(path).map(DiskCache::new) {
Ok(disk) => {
match redoxfs::FileSystem::open(disk, password_opt.as_deref(), block_opt, true) {
Ok(filesystem) => {
log::debug!(
"opened filesystem on {} with uuid {}",
path,
Uuid::from_bytes(filesystem.header.uuid()).hyphenated()
);
return Some((path.to_string(), filesystem));
}
Err(err) => match err.errno {
syscall::ENOKEY => {
if password_opt.is_some() {
eprintln!("redoxfs: incorrect password ({}/{})", attempt, attempts);
}
}
_ => {
if log_errors {
log::error!("failed to open filesystem {}: {}", path, err);
}
break;
}
},
}
}
Err(err) => {
if log_errors {
log::error!("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>>)> {
None
}
#[cfg(target_os = "redox")]
fn filesystem_by_uuid(
uuid: &Uuid,
block_opt: Option<u64>,
) -> Option<(String, FileSystem<DiskCache<DiskFile>>)> {
use std::fs;
use redox_path::RedoxPath;
match fs::read_dir("/scheme") {
Ok(entries) => {
for entry_res in entries {
if let Ok(entry) = entry_res {
if let Some(disk) = entry.path().to_str() {
if RedoxPath::from_absolute(disk)
.unwrap_or(RedoxPath::from_absolute("/")?)
.is_scheme_category("disk")
{
log::debug!("found scheme {}", disk);
match fs::read_dir(disk) {
Ok(entries) => {
for entry_res in entries {
if let Ok(entry) = entry_res {
if let Ok(path) =
entry.path().into_os_string().into_string()
{
log::debug!("found path {}", path);
if let Some((path, filesystem)) =
filesystem_by_path(&path, block_opt, false)
{
if &filesystem.header.uuid() == uuid.as_bytes()
{
log::debug!(
"filesystem on {} matches uuid {}",
path,
uuid.hyphenated()
);
return Some((path, filesystem));
} else {
log::debug!(
"filesystem on {} does not match uuid {}",
path,
uuid.hyphenated()
);
}
}
}
}
}
}
Err(err) => {
log::debug!("failed to list '{}': {}", disk, err);
}
}
}
}
}
}
}
Err(err) => {
log::error!("failed to list schemes: {}", err);
}
}
None
}
fn daemon(
disk_id: &DiskId,
mountpoint: &str,
block_opt: Option<u64>,
mut write: Option<File>,
) -> ! {
setsig();
let filesystem_opt = match *disk_id {
DiskId::Path(ref path) => filesystem_by_path(path, block_opt, true),
DiskId::Uuid(ref uuid) => filesystem_by_uuid(uuid, block_opt),
};
if let Some((path, filesystem)) = filesystem_opt {
match mount(filesystem, mountpoint, |mounted_path| {
capability_mode();
log::info!(
"mounted filesystem on {} to {}",
path,
mounted_path.display()
);
if let Some(ref mut write) = write {
let _ = write.write(&[0]);
}
}) {
Ok(()) => {
process::exit(0);
}
Err(err) => {
log::error!("failed to mount {} to {}: {}", path, mountpoint, err);
}
}
}
match *disk_id {
DiskId::Path(ref path) => {
log::error!("not able to mount path {}", path);
}
DiskId::Uuid(ref uuid) => {
log::error!("not able to mount uuid {}", uuid.hyphenated());
}
}
if let Some(ref mut write) = write {
let _ = write.write(&[1]);
}
process::exit(1);
}
fn main() {
env_logger::init();
let mut args = env::args().skip(1);
let mut daemonise = true;
let mut disk_id: Option<DiskId> = None;
let mut mountpoint: Option<String> = None;
let mut block_opt: Option<u64> = None;
while let Some(arg) = args.next() {
match arg.as_str() {
"--no-daemon" | "-d" => daemonise = false,
"--uuid" if disk_id.is_none() => {
disk_id = Some(DiskId::Uuid(
match args.next().as_deref().map(Uuid::parse_str) {
Some(Ok(uuid)) => uuid,
Some(Err(err)) => {
print_err_exit(format!("invalid uuid '{}': {}", arg, err))
}
None => print_err_exit("no uuid provided"),
},
));
}
disk if disk_id.is_none() => disk_id = Some(DiskId::Path(disk.to_owned())),
mnt if disk_id.is_some() && mountpoint.is_none() => mountpoint = Some(mnt.to_owned()),
opts if mountpoint.is_some() => match u64::from_str_radix(opts, 16) {
Ok(block) => block_opt = Some(block),
Err(err) => print_err_exit(format!("invalid block '{}': {}", opts, err)),
},
_ => print_usage_exit(),
}
}
let Some(disk_id) = disk_id else {
print_err_exit("no disk provided");
};
let Some(mountpoint) = mountpoint else {
print_err_exit("no mountpoint provided");
};
if daemonise {
let mut pipes = [0; 2];
if pipe(&mut pipes) == 0 {
let mut read = unsafe { File::from_raw_fd(pipes[0] as RawFd) };
let write = unsafe { File::from_raw_fd(pipes[1] as RawFd) };
let pid = fork();
if pid == 0 {
drop(read);
daemon(&disk_id, &mountpoint, block_opt, Some(write));
} else if pid > 0 {
drop(write);
let mut res = [0];
read.read_exact(&mut res).unwrap();
process::exit(res[0] as i32);
} else {
panic!("redoxfs: failed to fork");
}
} else {
panic!("redoxfs: failed to create pipe");
}
} else {
log::info!("running in foreground");
daemon(&disk_id, &mountpoint, block_opt, None);
}
}
+206
View File
@@ -0,0 +1,206 @@
use std::{env, process};
use humansize::{format_size, BINARY, DECIMAL};
use redoxfs::{BlockAddr, BlockMeta, Disk, DiskFile, FileSystem};
use uuid::Uuid;
fn resize<D: Disk>(fs: &mut FileSystem<D>, size_arg: String) -> Result<(), String> {
let disk_size = fs
.disk
.size()
.map_err(|err| format!("failed to read disk size: {}", err))?;
// Find contiguous free region
//TODO: better error management
let mut last_free = None;
let mut last_end = 0;
fs.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;
for entry in alloc.data().entries.iter() {
let count = entry.count();
if count <= 0 {
continue;
}
let end = entry.index() + count as u64;
if end > last_end {
last_free = Some(*entry);
last_end = end;
}
}
}
Ok(())
})
.map_err(|err| format!("failed to read alloc log: {}", err))?;
let old_size = fs.header.size();
let min_size = if let Some(entry) = last_free {
entry.index() * redoxfs::BLOCK_SIZE
} else {
old_size
};
let max_size = disk_size - (fs.block * redoxfs::BLOCK_SIZE);
let new_size = match size_arg.to_lowercase().as_str() {
"min" | "minimum" => min_size,
"" | "max" | "maximum" => max_size,
_ => match parse_size::parse_size(&size_arg) {
Ok(new_size) => {
if new_size < min_size {
return Err(format!(
"requested size {} is smaller than {} by {}",
new_size,
min_size,
min_size - new_size
));
}
if new_size > max_size {
return Err(format!(
"requested size {} is larger than {} by {}",
new_size,
max_size,
new_size - max_size
));
}
new_size
}
Err(err) => {
return Err(format!(
"failed to parse size argument {:?}: {}",
size_arg, err
));
}
},
};
println!(
"minimum size: {} ({})",
format_size(min_size, DECIMAL),
format_size(min_size, BINARY)
);
println!(
"maximum size: {} ({})",
format_size(max_size, DECIMAL),
format_size(max_size, BINARY)
);
println!(
"new size: {} ({})",
format_size(new_size, DECIMAL),
format_size(new_size, BINARY)
);
let old_blocks = old_size / redoxfs::BLOCK_SIZE;
let new_blocks = new_size / redoxfs::BLOCK_SIZE;
let (start, end, shrink) = if new_size == old_size {
println!("already requested size");
return Ok(());
} else if new_size < old_size {
println!("shrinking by {}", old_size - new_size);
(new_blocks, old_blocks, true)
} else {
println!("growing by {}", new_size - old_size);
(old_blocks, new_blocks, false)
};
// Allocate or deallocate blocks as needed
unsafe {
let allocator = fs.allocator_mut();
for index in start..end {
if shrink {
//TODO: replace assert with error?
let addr = BlockAddr::new(index as u64, BlockMeta::default());
assert_eq!(allocator.allocate_exact(addr), Some(addr));
} else {
let addr = BlockAddr::new(index as u64, BlockMeta::default());
allocator.deallocate(addr);
}
}
}
fs.tx(|tx| {
// Update header
tx.header.size = new_size.into();
tx.header_changed = true;
// Sync with squash
tx.sync(true)?;
Ok(())
})
.map_err(|err| format!("transaction failed: {}", err))
}
fn main() {
env_logger::init();
let mut args = env::args().skip(1);
let disk_path = if let Some(path) = args.next() {
path
} else {
eprintln!("redoxfs-resize: no new disk image provided");
eprintln!("redoxfs-resize NEW-DISK [SIZE]");
process::exit(1);
};
let size_arg = args.next().unwrap_or_default();
let disk = match DiskFile::open(&disk_path) {
Ok(disk) => disk,
Err(err) => {
eprintln!(
"redoxfs-resize: failed to open disk image {}: {}",
disk_path, err
);
process::exit(1);
}
};
let mut fs = match FileSystem::open(disk, None, None, true) {
Ok(fs) => fs,
Err(err) => {
eprintln!(
"redoxfs-resize: failed to open filesystem on {}: {}",
disk_path, err
);
process::exit(1);
}
};
match resize(&mut fs, size_arg) {
Ok(()) => {}
Err(err) => {
eprintln!(
"redoxfs-resize: failed to resize filesystem on {}: {}",
disk_path, err
);
process::exit(1);
}
}
let uuid = Uuid::from_bytes(fs.header.uuid());
let size = fs.header.size();
let free = fs.allocator().free() * redoxfs::BLOCK_SIZE;
let used = size - free;
println!("redoxfs-resize: resized filesystem on {}", disk_path);
println!("\tuuid: {}", uuid.hyphenated());
println!(
"\tsize: {} ({})",
format_size(size, DECIMAL),
format_size(size, BINARY)
);
println!(
"\tused: {} ({})",
format_size(used, DECIMAL),
format_size(used, BINARY)
);
println!(
"\tfree: {} ({})",
format_size(free, DECIMAL),
format_size(free, BINARY)
);
}
+393
View File
@@ -0,0 +1,393 @@
use core::{fmt, marker::PhantomData, mem, ops, slice};
use endian_num::Le;
use crate::BLOCK_SIZE;
const BLOCK_LIST_ENTRIES: usize = BLOCK_SIZE as usize / mem::size_of::<BlockPtr<BlockRaw>>();
/// An address of a data block.
///
/// This encodes a block's position _and_ [`BlockLevel`]:
/// the first four bits of this `u64` encode the block's level,
/// the next four bits indicates decompression level,
/// the rest encode its index.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct BlockAddr(u64);
impl BlockAddr {
const INDEX_SHIFT: u64 = 8;
const DECOMP_LEVEL_MASK: u64 = 0xF0;
const DECOMP_LEVEL_SHIFT: u64 = 4;
const LEVEL_MASK: u64 = 0xF;
// Unsafe because this can create invalid blocks
pub unsafe fn new(index: u64, meta: BlockMeta) -> Self {
// Level must fit within LEVEL_MASK
if meta.level.0 > Self::LEVEL_MASK as usize {
panic!("block level too large");
}
// Decomp level must fit within DECOMP_LEVEL_MASK
let decomp_level = meta.decomp_level.unwrap_or_default();
if (decomp_level.0 << Self::DECOMP_LEVEL_SHIFT) > Self::DECOMP_LEVEL_MASK as usize {
panic!("decompressed block level too large");
}
// Index must not use the metadata bits
let inner = index
.checked_shl(Self::INDEX_SHIFT as u32)
.expect("block index too large")
| ((decomp_level.0 as u64) << Self::DECOMP_LEVEL_SHIFT)
| (meta.level.0 as u64);
Self(inner)
}
pub fn null(meta: BlockMeta) -> Self {
unsafe { Self::new(0, meta) }
}
pub fn index(&self) -> u64 {
// The first four bits store the level
self.0 >> Self::INDEX_SHIFT
}
pub fn level(&self) -> BlockLevel {
// The first four bits store the level
BlockLevel((self.0 & Self::LEVEL_MASK) as usize)
}
pub fn decomp_level(&self) -> Option<BlockLevel> {
let value = (self.0 & Self::DECOMP_LEVEL_MASK) >> Self::DECOMP_LEVEL_SHIFT;
if value != 0 {
Some(BlockLevel(value as usize))
} else {
None
}
}
pub fn meta(&self) -> BlockMeta {
BlockMeta {
level: self.level(),
decomp_level: self.decomp_level(),
}
}
pub fn is_null(&self) -> bool {
self.index() == 0
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct BlockMeta {
pub(crate) level: BlockLevel,
pub(crate) decomp_level: Option<BlockLevel>,
}
impl BlockMeta {
pub fn new(level: BlockLevel) -> Self {
Self {
level,
decomp_level: None,
}
}
pub fn new_compressed(level: BlockLevel, decomp_level: BlockLevel) -> Self {
Self {
level,
decomp_level: Some(decomp_level),
}
}
}
/// The size of a block.
///
/// Level 0 blocks are blocks of [`BLOCK_SIZE`] bytes.
/// A level 1 block consists of two consecutive level 0 blocks.
/// A level n block consists of two consecutive level n-1 blocks.
///
/// See [`crate::Allocator`] docs for more details.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct BlockLevel(pub(crate) usize);
impl BlockLevel {
/// Returns the smallest block level that can contain
/// the given number of bytes.
pub(crate) fn for_bytes(bytes: u64) -> Self {
if bytes == 0 {
return BlockLevel(0);
}
let level = bytes
.div_ceil(BLOCK_SIZE)
.next_power_of_two()
.trailing_zeros() as usize;
BlockLevel(level)
}
/// The number of [`BLOCK_SIZE`] blocks (i.e, level 0 blocks)
/// in a block of this level
pub fn blocks<T: From<u32>>(self) -> T {
T::from(1u32 << self.0)
}
/// The number of bytes in a block of this level
pub fn bytes(self) -> u64 {
BLOCK_SIZE << self.0
}
}
pub unsafe trait BlockTrait {
/// Create an empty block of this type.
fn empty(level: BlockLevel) -> Option<Self>
where
Self: Sized;
}
/// A [`BlockAddr`] and the data it points to.
#[derive(Clone, Copy, Debug, Default)]
pub struct BlockData<T> {
addr: BlockAddr,
data: T,
}
impl<T> BlockData<T> {
pub fn new(addr: BlockAddr, data: T) -> Self {
Self { addr, data }
}
pub fn addr(&self) -> BlockAddr {
self.addr
}
pub fn data(&self) -> &T {
&self.data
}
pub fn data_mut(&mut self) -> &mut T {
&mut self.data
}
pub(crate) unsafe fn into_parts(self) -> (BlockAddr, T) {
(self.addr, self.data)
}
/// Set the address of this [`BlockData`] to `addr`, returning this
/// block's old address. This method does not update block data.
///
/// `addr` must point to a block with the same level as this block.
#[must_use = "don't forget to de-allocate old block address"]
pub fn swap_addr(&mut self, addr: BlockAddr) -> BlockAddr {
// Address levels must match
assert_eq!(self.addr.level(), addr.level());
let old = self.addr;
self.addr = addr;
old
}
}
impl<T: BlockTrait> BlockData<T> {
pub fn empty(addr: BlockAddr) -> Option<Self> {
let empty = T::empty(addr.level())?;
Some(Self::new(addr, empty))
}
}
impl<T: ops::Deref<Target = [u8]>> BlockData<T> {
pub fn create_ptr(&self) -> BlockPtr<T> {
BlockPtr {
addr: self.addr.0.into(),
hash: seahash::hash(self.data.deref()).into(),
phantom: PhantomData,
}
}
}
#[repr(C, packed)]
pub struct BlockList<T> {
pub ptrs: [BlockPtr<T>; BLOCK_LIST_ENTRIES],
}
unsafe impl<T> BlockTrait for BlockList<T> {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self {
ptrs: [BlockPtr::default(); BLOCK_LIST_ENTRIES],
})
} else {
None
}
}
}
impl<T> BlockList<T> {
pub fn is_empty(&self) -> bool {
self.ptrs.iter().all(|ptr| ptr.is_null())
}
}
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]
}
}
}
/// An address of a data block, along with a checksum of its data.
///
/// This encodes a block's position _and_ [`BlockLevel`].
/// the first four bits of `addr` encode the block's level,
/// the rest encode its index.
///
/// Also see [`BlockAddr`].
#[repr(C, packed)]
pub struct BlockPtr<T> {
addr: Le<u64>,
hash: Le<u64>,
phantom: PhantomData<T>,
}
impl<T> BlockPtr<T> {
pub fn null(meta: BlockMeta) -> Self {
Self {
addr: BlockAddr::null(meta).0.into(),
hash: 0.into(),
phantom: PhantomData,
}
}
pub fn addr(&self) -> BlockAddr {
BlockAddr(self.addr.to_ne())
}
pub fn hash(&self) -> u64 {
self.hash.to_ne()
}
pub fn is_null(&self) -> bool {
self.addr().is_null()
}
pub fn marker(level: u8) -> Self {
assert!(level <= 0xF);
Self {
addr: (0xFFFF_FFFF_FFFF_FFF0 | (level as u64)).into(),
hash: u64::MAX.into(),
phantom: PhantomData,
}
}
pub fn is_marker(&self) -> bool {
(self.addr.to_ne() | 0xF) == u64::MAX && self.hash.to_ne() == u64::MAX
}
/// 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
}
}
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(C, packed)]
#[derive(Clone)]
pub struct BlockRaw([u8; BLOCK_SIZE as usize]);
unsafe impl BlockTrait for BlockRaw {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self([0; BLOCK_SIZE as usize]))
} else {
None
}
}
}
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);
}
#[test]
fn block_ptr_marker_test() {
let ptr = BlockPtr::<BlockRaw>::marker(0);
assert_eq!(ptr.addr().level().0, 0);
assert!(ptr.is_marker());
let ptr = BlockPtr::<BlockRaw>::marker(2);
assert_eq!(ptr.addr().level().0, 2);
assert!(ptr.is_marker());
}
+90
View File
@@ -0,0 +1,90 @@
use crate::{Disk, FileSystem, Node, Transaction, TreePtr, BLOCK_SIZE};
fn tx_progress<D: Disk, F: FnMut(u64)>(tx: &mut Transaction<D>, progress: &mut F) {
let size = tx.header.size();
let free = tx.allocator.free() * BLOCK_SIZE;
progress(size - free);
}
//TODO: handle hard links
fn clone_at<D: Disk, E: Disk, F: FnMut(u64)>(
tx_old: &mut Transaction<D>,
parent_ptr_old: TreePtr<Node>,
tx: &mut Transaction<E>,
parent_ptr: TreePtr<Node>,
buf: &mut [u8],
progress: &mut F,
) -> syscall::Result<()> {
let mut entries = Vec::new();
tx_old.child_nodes(parent_ptr_old, &mut entries)?;
for entry in entries {
//TODO: return error instead?
let Some(name) = entry.name() else {
continue;
};
let node_ptr_old = entry.node_ptr();
let node_old = tx_old.read_tree(node_ptr_old)?;
//TODO: this slows down the clone, but Redox has issues without this (Linux is fine)
if tx.write_cache.len() > 64 {
tx.sync(false)?;
}
let node_ptr = {
let mode = node_old.data().mode();
let (ctime, ctime_nsec) = node_old.data().ctime();
let (mtime, mtime_nsec) = node_old.data().mtime();
let mut node = tx.create_node(parent_ptr, &name, mode, ctime, ctime_nsec)?;
node.data_mut().set_uid(node_old.data().uid());
node.data_mut().set_gid(node_old.data().gid());
node.data_mut().set_mtime(mtime, mtime_nsec);
if !node_old.data().is_dir() {
let mut offset = 0;
loop {
let count = tx_old.read_node_inner(&node_old, offset, buf)?;
if count == 0 {
break;
}
tx.write_node_inner(&mut node, &mut offset, &buf[..count])?;
}
}
let node_ptr = node.ptr();
tx.sync_tree(node)?;
node_ptr
};
tx_progress(tx, progress);
if node_old.data().is_dir() {
clone_at(tx_old, node_ptr_old, tx, node_ptr, buf, progress)?;
}
}
Ok(())
}
pub fn clone<D: Disk, E: Disk, F: FnMut(u64)>(
fs_old: &mut FileSystem<D>,
fs: &mut FileSystem<E>,
mut progress: F,
) -> syscall::Result<()> {
fs_old.tx(|tx_old| {
let mut tx = Transaction::new(fs);
// Clone at root node
let mut buf = vec![0; 4 * 1024 * 1024];
clone_at(
tx_old,
TreePtr::root(),
&mut tx,
TreePtr::root(),
&mut buf,
&mut progress,
)?;
// Commit and squash alloc log
tx.commit(true)
})
}
+300
View File
@@ -0,0 +1,300 @@
use core::{mem, ops, slice, str};
use crate::{BlockLevel, BlockTrait, Node, TreePtr, BLOCK_SIZE, DIR_ENTRY_MAX_LENGTH};
#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct DirEntry {
node_ptr: TreePtr<Node>,
name: [u8; DIR_ENTRY_MAX_LENGTH],
}
impl DirEntry {
pub fn new(node_ptr: TreePtr<Node>, name: &str) -> DirEntry {
let mut entry = DirEntry {
node_ptr,
..Default::default()
};
entry.name[..name.len()].copy_from_slice(name.as_bytes());
entry
}
pub fn node_ptr(&self) -> TreePtr<Node> {
self.node_ptr
}
fn name_len(&self) -> usize {
let mut len = 0;
while len < self.name.len() {
if self.name[len] == 0 {
break;
}
len += 1;
}
len
}
pub fn name(&self) -> Option<&str> {
let len = self.name_len();
//TODO: report utf8 error?
str::from_utf8(&self.name[..len]).ok()
}
// 4 bytes TreePtr
// 1 byte name_len
const SERIALIZED_PREFIX_SIZE: usize = mem::size_of::<TreePtr<Node>>() + 1;
pub fn serialized_size(&self) -> usize {
DirEntry::SERIALIZED_PREFIX_SIZE + self.name_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Option<usize> {
let required = self.serialized_size();
if buf.len() < required {
return None;
}
buf[0..4].copy_from_slice(&self.node_ptr().to_bytes());
buf[4] = self.name_len() as u8;
buf[5..5 + self.name_len()].copy_from_slice(&self.name[..self.name_len()]);
Some(required)
}
fn deserialize_from(buf: &[u8]) -> Result<(Self, usize), &'static str> {
if buf.len() <= DirEntry::SERIALIZED_PREFIX_SIZE {
return Err("Buffer too small");
}
let node_ptr: TreePtr<Node> =
TreePtr::from_bytes(buf[0..4].try_into().expect("Slice must be 4 bytes long"));
let name_len = buf[4] as usize;
if name_len < 1 || name_len > DIR_ENTRY_MAX_LENGTH {
return Err("Invalid name length");
}
if buf.len() < DirEntry::SERIALIZED_PREFIX_SIZE + name_len {
return Err("Buffer too small");
}
let mut name = [0u8; DIR_ENTRY_MAX_LENGTH];
name[..name_len].copy_from_slice(
&buf[DirEntry::SERIALIZED_PREFIX_SIZE..DirEntry::SERIALIZED_PREFIX_SIZE + name_len],
);
Ok((
DirEntry { node_ptr, name },
DirEntry::SERIALIZED_PREFIX_SIZE + name_len,
))
}
}
impl Default for DirEntry {
fn default() -> Self {
Self {
node_ptr: TreePtr::default(),
name: [0; DIR_ENTRY_MAX_LENGTH],
}
}
}
pub struct DirList {
count: u16,
entry_bytes_len: u16,
entry_bytes: [u8; BLOCK_SIZE as usize - 4],
}
unsafe impl BlockTrait for DirList {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self {
count: 0,
entry_bytes_len: 0,
entry_bytes: [0; BLOCK_SIZE as usize - 4],
})
} else {
None
}
}
}
impl DirList {
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn entries(&self) -> DirEntryIterator<'_> {
DirEntryIterator {
dir_list: self,
emit_count: 0,
position: 0,
}
}
fn entry_position_for_name(&self, name: &str) -> Option<usize> {
let name_len = name.len();
let mut position = 0;
let mut entry_id = 0;
while entry_id < self.count {
let entry_name_len = self.entry_bytes[position + 4] as usize;
if entry_name_len == name_len {
let start = DirEntry::SERIALIZED_PREFIX_SIZE + position;
let entry_name = &self.entry_bytes[start..start + entry_name_len];
if entry_name == name.as_bytes() {
return Some(position);
}
}
position += DirEntry::SERIALIZED_PREFIX_SIZE + entry_name_len;
entry_id += 1;
}
None
}
pub fn find_entry(&self, name: &str) -> Option<DirEntry> {
if let Some(position) = self.entry_position_for_name(name) {
let (entry, _) = DirEntry::deserialize_from(&self.entry_bytes[position..]).unwrap();
return Some(entry);
}
None
}
pub fn remove_entry(&mut self, name: &str) -> bool {
if let Some(position) = self.entry_position_for_name(name) {
let entry_size =
DirEntry::SERIALIZED_PREFIX_SIZE + self.entry_bytes[position + 4] as usize;
let remaining_size = self.entry_bytes_len as usize - position - entry_size;
if remaining_size > 0 {
self.entry_bytes.copy_within(
position + entry_size..self.entry_bytes_len as usize,
position,
);
}
self.entry_bytes_len -= entry_size as u16;
self.count -= 1;
return true;
}
false
}
pub fn for_each_entry<F>(&self, mut f: F)
where
F: FnMut(&[u8; 4], &[u8]),
{
let mut position = 0;
let mut entry_id = 0;
while entry_id < self.count {
let node_ptr_bytes = &self.entry_bytes[position..position + 4];
//let node_ptr = TreePtr::<Node>::from_bytes(node_ptr_bytes.try_into().unwrap());
let entry_name_len = self.entry_bytes[position + 4] as usize;
let start = DirEntry::SERIALIZED_PREFIX_SIZE + position;
let entry_name = &self.entry_bytes[start..start + entry_name_len];
f(node_ptr_bytes.try_into().unwrap(), entry_name);
position += DirEntry::SERIALIZED_PREFIX_SIZE + entry_name_len;
entry_id += 1;
}
}
pub fn append(&mut self, entry: &DirEntry) -> bool {
let entry_bytes_len = self.entry_bytes_len as usize;
if let Some(size) = entry.serialize_into(&mut self.entry_bytes[entry_bytes_len..]) {
self.count += 1;
self.entry_bytes_len += size as u16;
return true;
}
false
}
pub fn entry_count(&self) -> usize {
self.count as usize
}
}
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]
}
}
}
pub struct DirEntryIterator<'a> {
dir_list: &'a DirList,
emit_count: usize,
position: usize,
}
impl Iterator for DirEntryIterator<'_> {
type Item = DirEntry;
fn next(&mut self) -> Option<Self::Item> {
if self.emit_count < self.dir_list.entry_count() {
let position = self.position;
let (entry, bytes_read) =
DirEntry::deserialize_from(&self.dir_list.entry_bytes[position..]).unwrap();
self.emit_count += 1;
self.position += bytes_read;
Some(entry)
} else {
None
}
}
}
#[cfg(test)]
mod test {
use super::*;
use alloc::format;
#[test]
fn dir_list_size_test() {
use core::ops::Deref;
assert_eq!(
DirList::empty(BlockLevel(0)).unwrap().deref().len(),
BLOCK_SIZE as usize
);
}
#[test]
fn test_append() {
let mut dir_list = DirList::empty(BlockLevel(0)).unwrap();
let dirent = DirEntry::new(TreePtr::new(123), "test000");
assert!(dir_list.append(&dirent));
assert_eq!(dir_list.entry_count(), 1);
assert_eq!(dir_list.entry_bytes_len as usize, dirent.serialized_size());
let max_entries = dir_list.entry_bytes.len() / dirent.serialized_size();
for i in 1..max_entries {
let dirent = DirEntry::new(TreePtr::new(123), format!("test{i:03}").as_str());
assert!(dir_list.append(&dirent), "Failed on iteration {i}");
}
let dirent = DirEntry::new(TreePtr::new(123), format!("test{max_entries}").as_str());
assert!(!dir_list.append(&dirent));
for (i, entry) in dir_list.entries().enumerate() {
assert_eq!(entry.name().unwrap(), format!("test{i:03}"));
}
}
}
+110
View File
@@ -0,0 +1,110 @@
use std::collections::{HashMap, VecDeque};
use std::{cmp, ptr};
use syscall::error::Result;
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());
unsafe { ptr::copy(src.as_ptr(), dest.as_mut_ptr(), len) };
len
}
pub struct DiskCache<T> {
inner: T,
cache: HashMap<u64, [u8; BLOCK_SIZE as usize]>,
order: VecDeque<u64>,
size: usize,
}
impl<T: Disk> DiskCache<T> {
pub fn new(inner: T) -> Self {
// 16 MB cache
let size = 16 * 1024 * 1024 / BLOCK_SIZE as usize;
DiskCache {
inner,
cache: HashMap::with_capacity(size),
order: VecDeque::with_capacity(size),
size,
}
}
fn insert(&mut self, i: u64, data: [u8; BLOCK_SIZE as usize]) {
while self.order.len() >= self.size {
let removed = self.order.pop_front().unwrap();
self.cache.remove(&removed);
}
self.cache.insert(i, data);
self.order.push_back(i);
}
}
impl<T: Disk> Disk for DiskCache<T> {
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
// println!("Cache read at {}", block);
let mut read = 0;
let mut failed = false;
for i in 0..buffer.len().div_ceil(BLOCK_SIZE as usize) {
let block_i = block + i as u64;
let buffer_i = i * BLOCK_SIZE as usize;
let buffer_j = cmp::min(buffer_i + BLOCK_SIZE as usize, buffer.len());
let buffer_slice = &mut buffer[buffer_i..buffer_j];
if let Some(cache_buf) = self.cache.get_mut(&block_i) {
read += copy_memory(cache_buf, buffer_slice);
} else {
failed = true;
break;
}
}
if failed {
self.inner.read_at(block, buffer)?;
read = 0;
for i in 0..buffer.len().div_ceil(BLOCK_SIZE as usize) {
let block_i = block + i as u64;
let buffer_i = i * BLOCK_SIZE as usize;
let buffer_j = cmp::min(buffer_i + BLOCK_SIZE as usize, buffer.len());
let buffer_slice = &buffer[buffer_i..buffer_j];
let mut cache_buf = [0; BLOCK_SIZE as usize];
read += copy_memory(buffer_slice, &mut cache_buf);
self.insert(block_i, cache_buf);
}
}
Ok(read)
}
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
//TODO: Write only blocks that have changed
// println!("Cache write at {}", block);
self.inner.write_at(block, buffer)?;
let mut written = 0;
for i in 0..buffer.len().div_ceil(BLOCK_SIZE as usize) {
let block_i = block + i as u64;
let buffer_i = i * BLOCK_SIZE as usize;
let buffer_j = cmp::min(buffer_i + BLOCK_SIZE as usize, buffer.len());
let buffer_slice = &buffer[buffer_i..buffer_j];
let mut cache_buf = [0; BLOCK_SIZE as usize];
written += copy_memory(buffer_slice, &mut cache_buf);
self.insert(block_i, cache_buf);
}
Ok(written)
}
fn size(&mut self) -> Result<u64> {
self.inner.size()
}
}
+84
View File
@@ -0,0 +1,84 @@
use std::fs::{File, OpenOptions};
use std::io::{Seek, SeekFrom};
use std::os::unix::fs::FileExt;
use std::path::Path;
use syscall::error::{Error, Result, EIO};
use crate::disk::Disk;
use crate::BLOCK_SIZE;
pub struct DiskFile {
pub file: File,
}
trait ResultExt {
type T;
fn or_eio(self) -> Result<Self::T>;
}
impl<T> ResultExt for Result<T> {
type T = T;
fn or_eio(self) -> Result<Self::T> {
match self {
Ok(t) => Ok(t),
Err(err) => {
eprintln!("RedoxFS: IO ERROR: {err}");
Err(Error::new(EIO))
}
}
}
}
impl<T> ResultExt for std::io::Result<T> {
type T = T;
fn or_eio(self) -> Result<Self::T> {
match self {
Ok(t) => Ok(t),
Err(err) => {
eprintln!("RedoxFS: IO ERROR: {err}");
Err(Error::new(EIO))
}
}
}
}
impl DiskFile {
pub fn open(path: impl AsRef<Path>) -> Result<DiskFile> {
let file = OpenOptions::new()
.read(true)
.write(true)
.open(path)
.or_eio()?;
Ok(DiskFile { file })
}
pub fn create(path: impl AsRef<Path>, size: u64) -> Result<DiskFile> {
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path)
.or_eio()?;
file.set_len(size).or_eio()?;
Ok(DiskFile { file })
}
}
impl Disk for DiskFile {
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
self.file.read_at(buffer, block * BLOCK_SIZE).or_eio()
}
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
self.file.write_at(buffer, block * BLOCK_SIZE).or_eio()
}
fn size(&mut self) -> Result<u64> {
self.file.seek(SeekFrom::End(0)).or_eio()
}
}
impl From<File> for DiskFile {
fn from(file: File) -> Self {
Self { file }
}
}
+38
View File
@@ -0,0 +1,38 @@
use std::io::{Read, Seek, SeekFrom, Write};
use syscall::error::{Error, Result, EIO};
use crate::disk::Disk;
use crate::BLOCK_SIZE;
macro_rules! try_disk {
($expr:expr) => {
match $expr {
Ok(val) => val,
Err(err) => {
eprintln!("Disk I/O Error: {}", err);
return Err(Error::new(EIO));
}
}
};
}
pub struct DiskIo<T>(pub T);
impl<T: Read + Write + Seek> Disk for DiskIo<T> {
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
try_disk!(self.0.seek(SeekFrom::Start(block * BLOCK_SIZE)));
let count = try_disk!(self.0.read(buffer));
Ok(count)
}
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
try_disk!(self.0.seek(SeekFrom::Start(block * BLOCK_SIZE)));
let count = try_disk!(self.0.write(buffer));
Ok(count)
}
fn size(&mut self) -> Result<u64> {
let size = try_disk!(self.0.seek(SeekFrom::End(0)));
Ok(size)
}
}
+42
View File
@@ -0,0 +1,42 @@
use syscall::error::{Error, Result, EIO};
use crate::disk::Disk;
use crate::BLOCK_SIZE;
pub struct DiskMemory {
data: Vec<u8>,
}
impl DiskMemory {
pub fn new(size: u64) -> DiskMemory {
DiskMemory {
data: vec![0; size as usize],
}
}
}
impl Disk for DiskMemory {
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
let offset = (block * BLOCK_SIZE) as usize;
let end = offset + buffer.len();
if end > self.data.len() {
return Err(Error::new(EIO));
}
buffer.copy_from_slice(&self.data[offset..end]);
Ok(buffer.len())
}
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
let offset = (block * BLOCK_SIZE) as usize;
let end = offset + buffer.len();
if end > self.data.len() {
return Err(Error::new(EIO));
}
self.data[offset..end].copy_from_slice(buffer);
Ok(buffer.len())
}
fn size(&mut self) -> Result<u64> {
Ok(self.data.len() as u64)
}
}
+41
View File
@@ -0,0 +1,41 @@
use syscall::error::Result;
#[cfg(feature = "std")]
pub use self::cache::DiskCache;
#[cfg(feature = "std")]
pub use self::file::DiskFile;
#[cfg(feature = "std")]
pub use self::io::DiskIo;
#[cfg(feature = "std")]
pub use self::memory::DiskMemory;
#[cfg(feature = "std")]
pub use self::sparse::DiskSparse;
#[cfg(feature = "std")]
mod cache;
#[cfg(feature = "std")]
mod file;
#[cfg(feature = "std")]
mod io;
#[cfg(feature = "std")]
mod memory;
#[cfg(feature = "std")]
mod sparse;
/// A disk
pub trait Disk {
/// 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>;
}
+53
View File
@@ -0,0 +1,53 @@
use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::Path;
use syscall::error::{Error, Result, EIO};
use crate::disk::Disk;
use crate::BLOCK_SIZE;
macro_rules! try_disk {
($expr:expr) => {
match $expr {
Ok(val) => val,
Err(err) => {
eprintln!("Disk I/O Error: {}", err);
return Err(Error::new(EIO));
}
}
};
}
pub struct DiskSparse {
pub file: File,
pub max_size: u64,
}
impl 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, max_size })
}
}
impl Disk for DiskSparse {
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)
}
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(self.max_size)
}
}
+331
View File
@@ -0,0 +1,331 @@
use aes::Aes128;
use alloc::{
boxed::Box,
collections::{BTreeMap, VecDeque},
vec,
};
use syscall::error::{Error, Result, EKEYREJECTED, ENOENT, ENOKEY};
use xts_mode::{get_tweak_default, Xts128};
#[cfg(feature = "std")]
use crate::{AllocEntry, AllocList, BlockData, BlockTrait, Key, KeySlot, Node, Salt, TreeList};
use crate::{
Allocator, BlockAddr, BlockLevel, BlockMeta, Disk, Header, Transaction, BLOCK_SIZE,
HEADER_RING, RECORD_SIZE,
};
fn compress_cache() -> Box<[u8]> {
vec![0; lz4_flex::block::get_maximum_output_size(RECORD_SIZE as usize)].into_boxed_slice()
}
/// A file system
pub struct FileSystem<D: Disk> {
//TODO: make private
pub disk: D,
//TODO: make private
pub block: u64,
//TODO: make private
pub header: Header,
pub(crate) allocator: Allocator,
pub(crate) cipher_opt: Option<Xts128<Aes128>>,
pub(crate) compress_cache: Box<[u8]>,
pub node_usages: BTreeMap<u32, u64>,
}
impl<D: Disk> FileSystem<D> {
/// Open a file system on a disk
pub fn open(
mut disk: D,
password_opt: Option<&[u8]>,
block_opt: Option<u64>,
cleanup: 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)? };
// 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 cipher_opt = match password_opt {
Some(password) => {
if !header.encrypted() {
// Header not encrypted but password provided
return Err(Error::new(EKEYREJECTED));
}
match header.cipher(password) {
Some(cipher) => Some(cipher),
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(),
cipher_opt,
compress_cache: compress_cache(),
node_usages: BTreeMap::new(),
};
unsafe { fs.reset_allocator()? };
if cleanup {
fs.cleanup()?
}
return Ok(fs);
}
Err(Error::new(ENOENT))
}
/// Create a file system on a disk
#[cfg(feature = "std")]
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
/// Reserved data will be zero padded up to the nearest block
/// We need to pass ctime and ctime_nsec in order to initialize the unix timestamps
#[cfg(feature = "std")]
pub fn create_reserved(
mut disk: D,
password_opt: Option<&[u8]>,
reserved: &[u8],
ctime: u64,
ctime_nsec: u32,
) -> Result<Self> {
let disk_size = disk.size()?;
let disk_blocks = disk_size / BLOCK_SIZE;
let block_offset = (reserved.len() as u64).div_ceil(BLOCK_SIZE);
if disk_blocks < (block_offset + HEADER_RING + 4) {
return Err(Error::new(syscall::error::ENOSPC));
}
let fs_blocks = disk_blocks - block_offset;
// Fill reserved data, pad with zeroes
for block in 0..block_offset as usize {
let mut data = [0; BLOCK_SIZE as usize];
let mut i = 0;
while i < data.len() && block * BLOCK_SIZE as usize + i < reserved.len() {
data[i] = reserved[block * BLOCK_SIZE as usize + i];
i += 1;
}
unsafe {
disk.write_at(block as u64, &data)?;
}
}
let mut header = Header::new(fs_blocks * BLOCK_SIZE);
let cipher_opt = match password_opt {
Some(password) => {
//TODO: handle errors
header.key_slots[0] = KeySlot::new(
password,
Salt::new().unwrap(),
(Key::new().unwrap(), Key::new().unwrap()),
)
.unwrap();
Some(header.key_slots[0].cipher(password).unwrap())
}
None => None,
};
let mut fs = FileSystem {
disk,
block: block_offset,
header,
allocator: Allocator::default(),
cipher_opt,
compress_cache: compress_cache(),
node_usages: BTreeMap::new(),
};
// Write header generation zero
let count = unsafe { fs.disk.write_at(fs.block, &fs.header)? };
if count != core::mem::size_of_val(&fs.header) {
// Wrote wrong number of bytes
#[cfg(feature = "log")]
log::error!("CREATE: WRONG NUMBER OF BYTES");
return Err(Error::new(syscall::error::EIO));
}
// Set tree and alloc pointers and write header generation one
fs.tx(|tx| unsafe {
let tree = BlockData::new(
BlockAddr::new(HEADER_RING + 1, BlockMeta::default()),
TreeList::empty(BlockLevel::default()).unwrap(),
);
let mut alloc = BlockData::new(
BlockAddr::new(HEADER_RING + 2, BlockMeta::default()),
AllocList::empty(BlockLevel::default()).unwrap(),
);
let alloc_free = fs_blocks - (HEADER_RING + 4);
alloc.data_mut().entries[0] = AllocEntry::new(HEADER_RING + 4, alloc_free as i64);
tx.header.tree = tx.write_block(tree)?;
tx.header.alloc = tx.write_block(alloc)?;
tx.header_changed = true;
Ok(())
})?;
unsafe {
fs.reset_allocator()?;
}
fs.tx(|tx| unsafe {
let mut root = BlockData::new(
BlockAddr::new(HEADER_RING + 3, BlockMeta::default()),
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(())
})?;
fs.cleanup()?;
Ok(fs)
}
/// Release unused nodes and squash allocation log, happens on mount (with cleanup) and unmount
pub fn cleanup(&mut self) -> Result<()> {
let mut tx = Transaction::new(self);
tx.release_unused_nodes()?;
tx.commit(true)
}
/// 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)
}
pub fn allocator(&self) -> &Allocator {
&self.allocator
}
/// Unsafe as it can corrupt the filesystem
pub unsafe fn allocator_mut(&mut self) -> &mut Allocator {
&mut self.allocator
}
/// Reset allocator to state stored on disk
///
/// # Safety
/// Unsafe, it must only be called when opening 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 index = entry.index();
let count = entry.count();
if count < 0 {
for i in 0..-count {
//TODO: replace assert with error?
let addr = BlockAddr::new(index + i as u64, BlockMeta::default());
assert_eq!(self.allocator.allocate_exact(addr), Some(addr));
}
} else {
for i in 0..count {
let addr = BlockAddr::new(index + i as u64, BlockMeta::default());
self.allocator.deallocate(addr);
}
}
}
}
Ok(())
}
pub(crate) fn decrypt(&mut self, data: &mut [u8], addr: BlockAddr) -> bool {
if let Some(ref cipher) = self.cipher_opt {
cipher.decrypt_area(
data,
BLOCK_SIZE as usize,
addr.index().into(),
get_tweak_default,
);
true
} else {
// Do nothing if encryption is disabled
false
}
}
pub(crate) fn encrypt(&mut self, data: &mut [u8], addr: BlockAddr) -> bool {
if let Some(ref cipher) = self.cipher_opt {
cipher.encrypt_area(
data,
BLOCK_SIZE as usize,
addr.index().into(),
get_tweak_default,
);
true
} else {
// Do nothing if encryption is disabled
false
}
}
}
+242
View File
@@ -0,0 +1,242 @@
use core::ops::{Deref, DerefMut};
use core::{fmt, mem, slice};
use endian_num::Le;
use aes::Aes128;
use xts_mode::{get_tweak_default, Xts128};
use crate::{AllocList, BlockPtr, KeySlot, ReleaseList, Tree, BLOCK_SIZE, SIGNATURE, VERSION};
pub const HEADER_RING: u64 = 256;
/// The header of the filesystem
#[derive(Clone, Copy)]
#[repr(C, packed)]
pub struct Header {
/// Signature, should be SIGNATURE
pub signature: [u8; 8],
/// Version, should be VERSION
pub version: Le<u64>,
/// Disk ID, a 128-bit unique identifier
pub uuid: [u8; 16],
/// Disk size, in number of BLOCK_SIZE sectors
pub size: Le<u64>,
/// Generation of header
pub generation: Le<u64>,
/// 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],
/// Nodes pending release, may be null
pub release: BlockPtr<ReleaseList>,
/// Padding
pub padding: [u8; BLOCK_SIZE as usize - 3192],
/// 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: Le<u64>,
}
impl Header {
#[cfg(feature = "std")]
pub fn new(size: u64) -> Header {
let uuid = uuid::Uuid::new_v4();
let mut header = Header {
signature: *SIGNATURE,
version: VERSION.into(),
uuid: *uuid.as_bytes(),
size: size.into(),
..Default::default()
};
header.update_hash(None);
header
}
pub fn valid(&self) -> bool {
if &self.signature != SIGNATURE {
// Signature does not match
return false;
}
if self.version.to_ne() != VERSION {
// Version does not match
return false;
}
if self.hash.to_ne() != 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_ne()
}
pub fn generation(&self) -> u64 {
self.generation.to_ne()
}
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, cipher_opt: Option<&Xts128<Aes128>>) -> [u8; 16] {
let mut encrypted_hash = [0; 16];
for (i, b) in self.hash.to_le_bytes().iter().enumerate() {
encrypted_hash[i] = *b;
}
if let Some(cipher) = cipher_opt {
let mut block = aes::Block::from(encrypted_hash);
cipher.encrypt_area(
&mut block,
BLOCK_SIZE as usize,
self.generation().into(),
get_tweak_default,
);
encrypted_hash = block.into();
}
encrypted_hash
}
pub fn encrypted(&self) -> bool {
(self.encrypted_hash) != self.create_encrypted_hash(None)
}
pub fn cipher(&self, password: &[u8]) -> Option<Xts128<Aes128>> {
let hash = self.create_encrypted_hash(None);
for slot in self.key_slots.iter() {
//TODO: handle errors
let cipher = slot.cipher(password).unwrap();
let mut block = aes::Block::from(self.encrypted_hash);
cipher.decrypt_area(
&mut block,
BLOCK_SIZE as usize,
self.generation().into(),
get_tweak_default,
);
if block == aes::Block::from(hash) {
return Some(cipher);
}
}
None
}
fn update_hash(&mut self, cipher_opt: Option<&Xts128<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(cipher_opt);
}
pub fn update(&mut self, cipher_opt: Option<&Xts128<Aes128>>) -> u64 {
let mut generation = self.generation();
generation += 1;
self.generation = generation.into();
self.update_hash(cipher_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],
release: BlockPtr::<ReleaseList>::default(),
padding: [0; BLOCK_SIZE as usize - 3192],
encrypted_hash: [0; 16],
hash: 0.into(),
}
}
}
impl fmt::Debug for Header {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let signature = self.signature;
let version = self.version;
let uuid = self.uuid;
let size = self.size;
let generation = self.generation;
let tree = self.tree;
let alloc = self.alloc;
let release = self.release;
let hash = self.hash;
f.debug_struct("Header")
.field("signature", &signature)
.field("version", &version)
.field("uuid", &uuid)
.field("size", &size)
.field("generation", &generation)
.field("tree", &tree)
.field("alloc", &alloc)
.field("release", &release)
.field("hash", &hash)
.finish()
}
}
impl Deref for Header {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(self as *const Header as *const u8, mem::size_of::<Header>())
as &[u8]
}
}
}
impl DerefMut for Header {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(self as *mut Header as *mut u8, mem::size_of::<Header>())
as &mut [u8]
}
}
}
#[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_ne(), 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);
}
+691
View File
@@ -0,0 +1,691 @@
use alloc::string::String;
use alloc::vec::Vec;
use core::{fmt, mem, ops, slice};
use endian_num::Le;
use syscall::error::{Error, Result, EEXIST, EIO};
use crate::{
BlockLevel, BlockPtr, BlockRaw, BlockTrait, DirEntry, DirList, BLOCK_SIZE, RECORD_LEVEL,
};
pub const HTREE_IDX_ENTRIES: usize = BLOCK_SIZE as usize / mem::size_of::<HTreePtr<BlockRaw>>();
const HTREE_IDX_PADDING: usize =
BLOCK_SIZE as usize - mem::size_of::<[HTreePtr<BlockRaw>; HTREE_IDX_ENTRIES]>();
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(C, packed)]
pub struct HTreeHash(Le<u32>);
impl HTreeHash {
// Create a MAX constant populated iwth the maximum value of Le<u32> minus 1
pub const MAX: HTreeHash = HTreeHash(Le(u32::MAX - 1));
#[cfg(not(test))]
pub fn from_name(name: &str) -> Self {
let hash = seahash::hash(name.as_bytes()) as u32;
// Don't allow the default hash value to be calculated for a real hash
if hash == u32::MAX {
return Self::MAX;
}
Self(hash.into())
}
#[cfg(test)]
pub fn from_name(name: &str) -> Self {
// Allow overriding the hashing function to something easily controled for testing.
let hash = if let Some(pos) = name.rfind("__") {
let number_str = &name[pos + 2..];
number_str.parse::<u32>().unwrap()
} else {
seahash::hash(name.as_bytes()) as u32
};
// Don't allow the default hash value to be calculated for a real hash
if hash == u32::MAX {
return Self::MAX;
}
Self(hash.into())
}
/// Returns the maximum of two `HTreeHash` values, ignoring the default hash value.
pub fn max_ignoring_default(&self, other: Self) -> Self {
let default = HTreeHash::default();
if *self == default {
return other;
}
if other == default {
return *self;
}
if *self > other {
*self
} else {
other
}
}
pub fn find_max(dir_list: &DirList) -> Option<HTreeHash> {
let mut max_hash = HTreeHash::default();
dir_list.for_each_entry(|_ptr_bytes, name_bytes| {
let name = String::from_utf8_lossy(name_bytes);
let hash = HTreeHash::from_name(name.as_ref());
max_hash = max_hash.max_ignoring_default(hash);
});
if max_hash == HTreeHash::default() {
None
} else {
Some(max_hash)
}
}
}
impl Default for HTreeHash {
/// The default hash value is the maximum possible value to push it to the end of the list when sorting.
fn default() -> Self {
Self(u32::MAX.into())
}
}
#[repr(C, packed)]
pub struct HTreePtr<T> {
pub htree_hash: HTreeHash,
pub ptr: BlockPtr<T>,
}
impl<T> HTreePtr<T> {
pub fn new(htree_hash: HTreeHash, ptr: BlockPtr<T>) -> Self {
Self { htree_hash, ptr }
}
/// Cast HTreePtr to another type
///
/// # Safety
/// Unsafe because it can be used to transmute types
pub unsafe fn cast<U>(self) -> HTreePtr<U> {
HTreePtr {
htree_hash: self.htree_hash,
ptr: self.ptr.cast(),
}
}
}
impl<T> HTreePtr<T> {
pub fn is_null(&self) -> bool {
self.ptr.is_null()
}
}
impl<T> Clone for HTreePtr<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for HTreePtr<T> {}
impl<T> Default for HTreePtr<T> {
fn default() -> Self {
Self {
htree_hash: HTreeHash::default(),
ptr: BlockPtr::default(),
}
}
}
impl<T> fmt::Debug for HTreePtr<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let htree_hash = self.htree_hash;
let ptr = self.ptr;
f.debug_struct("BlockPtr")
.field("htree_hash", &htree_hash)
.field("ptr", &ptr)
.finish()
}
}
#[repr(C, packed)]
pub struct HTreeNode<T> {
pub ptrs: [HTreePtr<T>; HTREE_IDX_ENTRIES],
padding: [u8; HTREE_IDX_PADDING],
}
impl<T> HTreeNode<T> {
pub fn find_max_htree_hash(&self) -> Option<HTreeHash> {
let mut hash = HTreeHash::default();
for entry in self.ptrs.iter() {
hash = hash.max_ignoring_default(entry.htree_hash);
}
if hash != HTreeHash::default() {
Some(hash)
} else {
None
}
}
pub fn find_ptrs_for_read(
&self,
htree_hash: HTreeHash,
) -> impl Iterator<Item = (usize, &HTreePtr<T>)> {
let mut last_hash = HTreeHash(0.into());
self.ptrs
.iter()
.enumerate()
.filter(move |(_idx, entry)| entry.htree_hash >= htree_hash)
.take_while(move |(_idx, entry)| {
let should_take = !entry.is_null() && last_hash <= htree_hash;
last_hash = entry.htree_hash;
should_take
})
}
}
unsafe impl<T> BlockTrait for HTreeNode<T> {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 <= RECORD_LEVEL {
Some(Self {
ptrs: [HTreePtr::default(); HTREE_IDX_ENTRIES],
padding: [0; HTREE_IDX_PADDING],
})
} else {
None
}
}
}
impl<T> ops::Deref for HTreeNode<T> {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const HTreeNode<T> as *const u8,
mem::size_of::<HTreeNode<T>>(),
) as &[u8]
}
}
}
impl<T> ops::DerefMut for HTreeNode<T> {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut HTreeNode<T> as *mut u8,
mem::size_of::<HTreeNode<T>>(),
) as &mut [u8]
}
}
}
pub fn add_inner_node<T>(
parent: &mut HTreeNode<T>,
new_ptr: HTreePtr<T>,
) -> Result<Option<(HTreeHash, HTreeNode<T>)>> {
// Update the input htree parameters in place
for ptr in parent.ptrs.iter_mut() {
if ptr.is_null() {
*ptr = new_ptr;
parent.ptrs.sort_by(|a, b| a.htree_hash.cmp(&b.htree_hash));
return Ok(None);
}
}
// The parent is full. We need to split it into two by half, ordered by the htree hash.
let mut all_ptrs = Vec::with_capacity(parent.ptrs.len() + 1);
for ptr in parent.ptrs.iter() {
all_ptrs.push(*ptr);
}
all_ptrs.push(new_ptr);
all_ptrs.sort_by(|a, b| a.htree_hash.cmp(&b.htree_hash));
let half_idx = all_ptrs.len() / 2;
// Find if there are duplicate name hashes on the boundary of where we want to split
let half_name_hash = all_ptrs[half_idx].htree_hash;
let mut first_idx = half_idx;
let mut last_idx = half_idx;
for (i, ptr) in all_ptrs.iter().enumerate() {
if ptr.htree_hash == half_name_hash {
if i < first_idx {
first_idx = i;
}
if i > last_idx {
last_idx = i;
}
}
}
// Split the entries_with_name_hash list at the index that minimizes the number of entries in each list while keeping the duplicate name hashes together
let split = if (half_idx - first_idx) < (last_idx - half_idx) {
first_idx
} else {
last_idx
};
let (ptrs1, ptrs2) = all_ptrs.split_at(split);
// Update the existing parent with the first half of the entries
let mut htree_idx1 = HTreeNode::empty(BlockLevel::default()).ok_or(Error::new(EIO))?;
htree_idx1.ptrs[..ptrs1.len()].copy_from_slice(ptrs1);
let _ = mem::replace(parent, htree_idx1);
// Return the second half as a new sibling parent
let mut htree_idx2 = HTreeNode::empty(BlockLevel::default()).ok_or(Error::new(EIO))?;
htree_idx2.ptrs[..ptrs2.len()].copy_from_slice(ptrs2);
let htree_hash2 = ptrs2[ptrs2.len() - 1].htree_hash;
Ok(Some((htree_hash2, htree_idx2)))
}
pub fn add_dir_entry(
dir_list: &mut DirList,
htree_hash: &mut HTreeHash,
dirent: DirEntry,
) -> Result<Option<(HTreeHash, DirList)>> {
if let Some(name) = dirent.name() {
if dir_list.find_entry(name).is_some() {
return Err(Error::new(EEXIST));
}
}
// Update the input htree parameters in place
let name = dirent.name().ok_or(Error::new(EIO))?;
if dir_list.append(&dirent) {
*htree_hash = HTreeHash::from_name(name).max_ignoring_default(*htree_hash);
return Ok(None);
}
// The dir_list is full. We need to split it into two dir_lists by half, ordered by the name hash.
let mut entries_with_name_hash = Vec::with_capacity(dir_list.entry_count() + 1);
for entry in dir_list.entries() {
entries_with_name_hash.push((
HTreeHash::from_name(entry.name().ok_or(Error::new(EIO))?),
entry,
));
}
entries_with_name_hash.push((HTreeHash::from_name(dirent.name().unwrap()), dirent));
entries_with_name_hash.sort_by(|a, b| a.0.cmp(&b.0));
let half = entries_with_name_hash.len() / 2;
let half_name_hash = entries_with_name_hash[half].0;
// Find if there are duplicate name hashes on the boundary of where we want to split
let mut first_idx = half;
let mut last_idx = half;
for (i, (name_hash, _)) in entries_with_name_hash.iter().enumerate() {
if *name_hash == half_name_hash {
if i < first_idx {
first_idx = i;
}
if i > last_idx {
last_idx = i;
}
}
}
last_idx += 1;
// Split the entries_with_name_hash list at the index that minimizes the number of entries in each list while keeping the duplicate name hashes together
let split = if (half - first_idx) < (last_idx - half) {
first_idx
} else {
last_idx
};
let split = split.max(1);
let sorted_entries = entries_with_name_hash
.iter()
.map(|(_, entry)| *entry)
.collect::<Vec<DirEntry>>();
let (entries1, entries2) = sorted_entries.split_at(split);
// Update the existing dir_list with the first half of the entries
let mut new_dir_list = DirList::empty(BlockLevel::default()).ok_or(Error::new(EIO))?;
for entry in entries1.iter() {
new_dir_list.append(entry);
}
let _ = mem::replace(dir_list, new_dir_list);
*htree_hash = entries_with_name_hash[entries1.len() - 1].0;
// Return the second half of the entries as a new dir_list
let mut new_dir_list = DirList::empty(BlockLevel::default()).ok_or(Error::new(EIO))?;
for entry in entries2.iter() {
new_dir_list.append(entry);
}
let new_name_hash = entries_with_name_hash[entries_with_name_hash.len() - 1].0;
Ok(Some((new_name_hash, new_dir_list)))
}
//
// MARK: Unit Tests
//
#[cfg(test)]
mod tests {
use super::*;
use crate::alloc::string::ToString;
use crate::TreePtr;
use alloc::format;
use alloc::string::String;
#[test]
fn htree_ptr_size_test() {
assert_eq!(mem::size_of::<HTreePtr<BlockRaw>>(), 20);
}
#[test]
fn htree_node_size_test() {
assert_eq!(mem::size_of::<HTreeNode<BlockRaw>>(), BLOCK_SIZE as usize);
}
#[test]
fn htree_hash_max_test() {
assert_eq!(HTreeHash::MAX, HTreeHash((u32::MAX - 1).into()));
}
#[test]
fn htree_hash_max_ignoring_default_test() {
let default = HTreeHash::default();
let hash1 = HTreeHash(0.into());
let hash2 = HTreeHash(1.into());
assert_eq!(hash1.max_ignoring_default(default), hash1);
assert_eq!(default.max_ignoring_default(hash1), hash1);
assert_eq!(hash1.max_ignoring_default(hash2), hash2);
}
#[test]
fn htree_node_find_max_htree_hash() {
// In practice, the HTreeHash values should always be in sorted order
let mut htree_node: HTreeNode<String> = HTreeNode::empty(BlockLevel::default()).unwrap();
htree_node.ptrs[0] = HTreePtr::new(HTreeHash(0.into()), BlockPtr::marker(0));
htree_node.ptrs[1] = HTreePtr::new(HTreeHash(1.into()), BlockPtr::marker(0));
htree_node.ptrs[2] = HTreePtr::new(HTreeHash(2.into()), BlockPtr::marker(0));
assert_eq!(
htree_node.find_max_htree_hash().unwrap(),
HTreeHash(2.into())
);
htree_node.ptrs[2] = HTreePtr::default();
assert_eq!(
htree_node.find_max_htree_hash().unwrap(),
HTreeHash(1.into())
);
htree_node.ptrs[1] = HTreePtr::default();
assert_eq!(
htree_node.find_max_htree_hash().unwrap(),
HTreeHash(0.into())
);
htree_node.ptrs[0] = HTreePtr::default();
assert!(htree_node.find_max_htree_hash().is_none());
// For thoroughness, test with HTreeHash out of order
htree_node.ptrs[2] = HTreePtr::new(HTreeHash(4.into()), BlockPtr::marker(0));
htree_node.ptrs[4] = HTreePtr::new(HTreeHash(6.into()), BlockPtr::marker(0));
htree_node.ptrs[6] = HTreePtr::new(HTreeHash(2.into()), BlockPtr::marker(0));
assert_eq!(
htree_node.find_max_htree_hash().unwrap(),
HTreeHash(6.into())
);
}
#[test]
fn htree_node_find_for_read() {
let mut htree_node: HTreeNode<String> = HTreeNode::empty(BlockLevel::default()).unwrap();
htree_node.ptrs[0] = HTreePtr::new(HTreeHash(0.into()), BlockPtr::marker(0));
htree_node.ptrs[1] = HTreePtr::new(HTreeHash(1.into()), BlockPtr::marker(0));
htree_node.ptrs[2] = HTreePtr::new(HTreeHash(2.into()), BlockPtr::marker(0));
htree_node.ptrs[3] = HTreePtr::new(HTreeHash(2.into()), BlockPtr::marker(0));
htree_node.ptrs[4] = HTreePtr::new(HTreeHash(3.into()), BlockPtr::marker(0));
htree_node.ptrs[5] = HTreePtr::new(HTreeHash(3.into()), BlockPtr::marker(0));
htree_node.ptrs[6] = HTreePtr::new(HTreeHash(5.into()), BlockPtr::marker(0));
htree_node.ptrs[7] = HTreePtr::new(HTreeHash(6.into()), BlockPtr::marker(0));
// Confirm that a hash that does not exist, but is less than an existing hash results in a single entry
let mut iter = htree_node.find_ptrs_for_read(HTreeHash(4.into()));
let mut val = iter.next().unwrap();
assert_eq!(val.0, 6);
assert_eq!(val.1.htree_hash, HTreeHash(5.into()));
assert!(iter.next().is_none());
// Confirm that a hash that equals an existing hash results in the match and one following entry
let mut iter = htree_node.find_ptrs_for_read(HTreeHash(1.into()));
val = iter.next().unwrap();
assert_eq!(val.0, 1);
assert_eq!(val.1.htree_hash, HTreeHash(1.into()));
val = iter.next().unwrap();
assert_eq!(val.0, 2);
assert_eq!(val.1.htree_hash, HTreeHash(2.into()));
assert!(iter.next().is_none());
// Confirm that multiple exact hash matches are all returned plus the next entry
let mut iter = htree_node.find_ptrs_for_read(HTreeHash(2.into()));
val = iter.next().unwrap();
assert_eq!(val.0, 2);
assert_eq!(val.1.htree_hash, HTreeHash(2.into()));
val = iter.next().unwrap();
assert_eq!(val.0, 3);
assert_eq!(val.1.htree_hash, HTreeHash(2.into()));
val = iter.next().unwrap();
assert_eq!(val.0, 4);
assert_eq!(val.1.htree_hash, HTreeHash(3.into()));
assert!(iter.next().is_none());
// Confirm that if the last entry matches and the next entry is null, only the match is returned
let mut iter = htree_node.find_ptrs_for_read(HTreeHash(6.into()));
val = iter.next().unwrap();
assert_eq!(val.0, 7);
assert_eq!(val.1.htree_hash, HTreeHash(6.into()));
assert!(iter.next().is_none());
// Confirm that if a hash that is larger than any existing entries, then no entries are returned
let mut iter = htree_node.find_ptrs_for_read(HTreeHash(7.into()));
assert!(iter.next().is_none());
}
#[test]
fn add_dir_entry_exists_test() {
let mut dir_list = DirList::empty(BlockLevel::default()).unwrap();
let mut htree_hash = HTreeHash::default();
let dirent = DirEntry::new(TreePtr::new(123), "test");
let new_sibling = add_dir_entry(&mut dir_list, &mut htree_hash, dirent).unwrap();
assert!(new_sibling.is_none());
assert_eq!(htree_hash, HTreeHash::from_name("test"));
assert_eq!(dir_list.entries().next().unwrap().name(), Some("test"));
// Add the same entry again, and it should fail with an appropriate IO error
let dirent = DirEntry::new(TreePtr::new(123), "test");
let error_expected = add_dir_entry(&mut dir_list, &mut htree_hash, dirent);
assert!(error_expected.is_err());
assert_eq!(error_expected.err().unwrap().errno, EEXIST);
}
#[test]
fn add_dir_entry_many_test() {
let mut dir_list = DirList::empty(BlockLevel::default()).unwrap();
let mut htree_hash = HTreeHash::default();
let total_count = 16;
// Fill up the dir_list
for i in 0..total_count {
let v: usize = i % 10;
let dirent = DirEntry::new(TreePtr::new(123), format!("test{v}_{i:0244}").as_str());
let new_sibling = add_dir_entry(&mut dir_list, &mut htree_hash, dirent).unwrap();
assert!(new_sibling.is_none());
}
// The maximum htree_hash should be retained
let max_tree_hash =
dir_list
.entries()
.enumerate()
.fold(HTreeHash::default(), |max, (i, _)| {
let v = i % 10;
let hash = HTreeHash::from_name(format!("test{v}_{i:0244}").as_str());
max.max_ignoring_default(hash)
});
assert_eq!(htree_hash, max_tree_hash);
// Confirm all the entries exist. Note they happen to be in insert order
for (i, entry) in dir_list.entries().enumerate() {
let v = i % 10;
assert_eq!(entry.name(), Some(format!("test{v}_{i:0244}").as_str()));
}
// Test a split by adding one more entry
let dirent = DirEntry::new(TreePtr::new(123), "test_split");
let new_sibling = add_dir_entry(&mut dir_list, &mut htree_hash, dirent).unwrap();
let (new_sibling_htree_hash, new_sibling_dir_list) =
new_sibling.expect("new_sibling should be created");
// assert!(new_sibling_dir_list.entries.len() );
assert!(new_sibling_htree_hash > htree_hash);
// The htree_hash should be less than the minimum htree_hash in new_sibling_dir_list
let new_sibling_min_htree_hash = new_sibling_dir_list
.entries()
.filter(|entry| !entry.node_ptr().is_null())
.fold(HTreeHash::default(), |min, entry| {
let hash = HTreeHash::from_name(entry.name().unwrap());
min.min(hash)
});
assert!(htree_hash < new_sibling_min_htree_hash);
// Confirm all the entries exist across both dir_lists
let mut expected_names: Vec<String> = (0..total_count)
.map(|i| {
let v = i % 10;
format!("test{v}_{i:0244}")
})
.collect();
expected_names.push("test_split".to_string());
expected_names.sort();
let mut dir_list_entry_count = 0;
for entry in dir_list.entries() {
dir_list_entry_count += 1;
let name = entry.name().unwrap().to_string();
let _ = expected_names.remove(expected_names.binary_search(&name).unwrap());
}
let mut new_sibling_entry_count = 0;
for entry in new_sibling_dir_list.entries() {
new_sibling_entry_count += 1;
let name = entry.name().unwrap().to_string();
let _ = expected_names.remove(expected_names.binary_search(&name).unwrap());
}
assert!(expected_names.is_empty());
// Confirm that the split is in half
assert!((dir_list_entry_count as i32 - new_sibling_entry_count).abs() <= 1);
}
#[test]
fn add_inner_node_simple_test() {
let mut htree_node: HTreeNode<_> = HTreeNode::empty(BlockLevel::default()).unwrap();
let htree_ptr: HTreePtr<_> = HTreePtr::<BlockRaw> {
htree_hash: HTreeHash::from_name("test"),
ptr: BlockPtr::marker(0),
};
let new_sibling = add_inner_node(&mut htree_node, htree_ptr).unwrap();
assert!(new_sibling.is_none());
assert_eq!(htree_node.ptrs[0].htree_hash, HTreeHash::from_name("test"));
}
#[test]
fn add_inner_node_multiple_test() {
let mut htree_node: HTreeNode<_> = HTreeNode::empty(BlockLevel::default()).unwrap();
for i in 0..HTREE_IDX_ENTRIES {
let htree_ptr: HTreePtr<_> = HTreePtr::<BlockRaw> {
htree_hash: HTreeHash(((100_000 + (i % 10) * 1000 + i) as u32).into()),
ptr: BlockPtr::marker(0),
};
let new_sibling = add_inner_node(&mut htree_node, htree_ptr).unwrap();
assert!(new_sibling.is_none());
// Confirm that the htree_ptrs are in sorted order at the start of the ptrs list
let mut prev_hash = HTreeHash::default();
let mut count = 0;
for ptr in htree_node.ptrs.iter() {
if ptr.is_null() {
continue;
}
assert!(
ptr.htree_hash.max_ignoring_default(prev_hash) == ptr.htree_hash,
"index {i}: {:?} > {:?}",
ptr.htree_hash,
prev_hash
);
prev_hash = ptr.htree_hash;
count += 1;
}
assert_eq!(count, i + 1);
}
// Confirm all expected hashes are present
let mut expected_hashes: Vec<u32> = (0..HTREE_IDX_ENTRIES)
.map(|i| (100_000 + (i % 10) * 1000 + i) as u32)
.collect();
expected_hashes.sort();
for ptr in htree_node.ptrs.iter() {
if ptr.is_null() {
break;
}
let idx = expected_hashes
.binary_search(&ptr.htree_hash.0.into())
.unwrap();
expected_hashes.remove(idx);
}
assert!(expected_hashes.is_empty());
// Force a split by adding one more entry
let htree_ptr: HTreePtr<_> = HTreePtr::<BlockRaw> {
htree_hash: HTreeHash(130_000.into()),
ptr: BlockPtr::marker(0),
};
let mut expected_hashes: Vec<u32> = (0..HTREE_IDX_ENTRIES)
.map(|i| (100_000 + (i % 10) * 1000 + i) as u32)
.collect();
expected_hashes.push(130_000);
expected_hashes.sort();
let new_sibling = add_inner_node(&mut htree_node, htree_ptr).unwrap();
let new_sibling = new_sibling.expect("new_sibling should be created");
// Confirm all the entries exist across both htree_nodes
let mut htree_node_entry_count = 0;
for ptr in htree_node.ptrs.iter() {
if ptr.ptr.is_null() {
break;
}
htree_node_entry_count += 1;
let idx = expected_hashes
.binary_search(&ptr.htree_hash.0.into())
.unwrap();
expected_hashes.remove(idx);
}
let mut new_sibling_entry_count = 0;
for ptr in new_sibling.1.ptrs.iter() {
if ptr.ptr.is_null() {
break;
}
new_sibling_entry_count += 1;
let idx = expected_hashes
.binary_search(&ptr.htree_hash.0.into())
.unwrap();
expected_hashes.remove(idx);
}
assert!(
expected_hashes.is_empty(),
"expected_hashes should be empty, but had length {}: {:?}",
expected_hashes.len(),
expected_hashes
);
// Confirm that the split is in half
assert!((htree_node_entry_count as i32 - new_sibling_entry_count).abs() <= 1);
}
}
+104
View File
@@ -0,0 +1,104 @@
use aes::{
cipher::{BlockDecrypt, BlockEncrypt, KeyInit},
Aes128,
};
use xts_mode::Xts128;
// 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 encrypt(&self, password_aes: &Aes128) -> EncryptedKey {
let mut block = aes::Block::from(self.0);
password_aes.encrypt_block(&mut block);
EncryptedKey(block.into())
}
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]);
impl EncryptedKey {
pub fn decrypt(&self, password_aes: &Aes128) -> Key {
let mut block = aes::Block::from(self.0);
password_aes.decrypt_block(&mut block);
Key(block.into())
}
}
/// 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(C, packed)]
pub struct KeySlot {
salt: Salt,
// Two keys for AES XTS 128
encrypted_keys: (EncryptedKey, 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, keys: (Key, Key)) -> Result<Self, argon2::Error> {
let password_aes = Self::password_aes(password, &salt)?;
Ok(Self {
salt,
encrypted_keys: (keys.0.encrypt(&password_aes), keys.1.encrypt(&password_aes)),
})
}
/// Get the encryption cipher from this key slot
pub fn cipher(&self, password: &[u8]) -> Result<Xts128<Aes128>, argon2::Error> {
let password_aes = Self::password_aes(password, &self.salt)?;
Ok(Xts128::new(
self.encrypted_keys.0.decrypt(&password_aes).into_aes(),
self.encrypted_keys.1.decrypt(&password_aes).into_aes(),
))
}
}
+70
View File
@@ -0,0 +1,70 @@
#![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)]
#![allow(unexpected_cfgs)]
extern crate alloc;
use core::sync::atomic::AtomicUsize;
// The alloc log grows by 1 block about every 21 generations
pub const ALLOC_GC_THRESHOLD: u64 = 1024;
pub const BLOCK_SIZE: u64 = 4096;
// A record is 4KiB << 5 = 128KiB
pub const RECORD_LEVEL: usize = 5;
pub const RECORD_SIZE: u64 = BLOCK_SIZE << RECORD_LEVEL;
pub const SIGNATURE: &[u8; 8] = b"RedoxFS\0";
pub const VERSION: u64 = 8;
pub const DIR_ENTRY_MAX_LENGTH: usize = 252;
pub static IS_UMT: AtomicUsize = AtomicUsize::new(0);
pub use self::allocator::{AllocEntry, AllocList, Allocator, ReleaseList, ALLOC_LIST_ENTRIES};
#[cfg(feature = "std")]
pub use self::archive::{archive, archive_at};
pub use self::block::{
BlockAddr, BlockData, BlockLevel, BlockList, BlockMeta, BlockPtr, BlockRaw, BlockTrait,
};
#[cfg(feature = "std")]
pub use self::clone::clone;
pub use self::dir::{DirEntry, DirList};
pub use self::disk::*;
pub use self::filesystem::FileSystem;
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, NodeFlags, NodeLevel, NodeLevelData};
pub use self::record::RecordRaw;
pub use self::transaction::Transaction;
pub use self::tree::{Tree, TreeData, TreeList, TreePtr};
#[cfg(feature = "std")]
pub use self::unmount::unmount_path;
mod allocator;
#[cfg(feature = "std")]
mod archive;
mod block;
#[cfg(feature = "std")]
mod clone;
mod dir;
mod disk;
mod filesystem;
mod header;
mod htree;
mod key;
#[cfg(all(feature = "std", not(fuzzing)))]
mod mount;
#[cfg(all(feature = "std", fuzzing))]
pub mod mount;
mod node;
mod record;
mod transaction;
mod tree;
#[cfg(feature = "std")]
mod unmount;
#[cfg(all(feature = "std", test))]
mod tests;
+580
View File
@@ -0,0 +1,580 @@
extern crate fuser;
use std::cmp;
use std::ffi::OsStr;
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
use self::fuser::MountOption;
use self::fuser::TimeOrNow;
use crate::mount::fuse::TimeOrNow::Now;
use crate::mount::fuse::TimeOrNow::SpecificTime;
use crate::{filesystem, Disk, Node, TreeData, TreePtr, BLOCK_SIZE};
use self::fuser::{
FileAttr, FileType, Filesystem, ReplyAttr, ReplyCreate, ReplyData, ReplyDirectory, ReplyEmpty,
ReplyEntry, ReplyOpen, ReplyStatfs, ReplyWrite, Request, Session,
};
use std::time::Duration;
const TTL: Duration = Duration::new(1, 0); // 1 second
const NULL_TIME: Duration = Duration::new(0, 0);
pub fn mount<D, P, T, F>(
mut filesystem: filesystem::FileSystem<D>,
mountpoint: P,
callback: F,
) -> io::Result<T>
where
D: Disk,
P: AsRef<Path>,
F: FnOnce(&Path) -> T,
{
let mountpoint = mountpoint.as_ref();
// One of the uses of this redoxfs fuse wrapper is to populate a filesystem
// while building the Redox OS kernel. This means that we need to write on
// a filesystem that belongs to `root`, which in turn means that we need to
// be `root`, thus that we need to allow `root` to have access.
let defer_permissions = [MountOption::CUSTOM("defer_permissions".to_owned())];
let res = {
let mut session = Session::new(
Fuse {
fs: &mut filesystem,
},
mountpoint,
if cfg!(target_os = "macos") {
&defer_permissions
} else {
&[]
},
)?;
let res = callback(mountpoint);
session.run()?;
res
};
// Cleanup on unmount
filesystem.cleanup()?;
Ok(res)
}
pub struct Fuse<'f, D: Disk> {
pub fs: &'f mut filesystem::FileSystem<D>,
}
fn node_attr(node: &TreeData<Node>) -> FileAttr {
FileAttr {
ino: node.id() as u64,
size: node.data().size(),
// Blocks is in 512 byte blocks, not in our block size
blocks: node.data().blocks() * (BLOCK_SIZE / 512),
blksize: 512,
atime: SystemTime::UNIX_EPOCH + Duration::new(node.data().atime().0, node.data().atime().1),
mtime: SystemTime::UNIX_EPOCH + Duration::new(node.data().mtime().0, node.data().mtime().1),
ctime: SystemTime::UNIX_EPOCH + Duration::new(node.data().ctime().0, node.data().ctime().1),
crtime: UNIX_EPOCH + NULL_TIME,
kind: if node.data().is_dir() {
FileType::Directory
} else if node.data().is_symlink() {
FileType::Symlink
} else {
FileType::RegularFile
},
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_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);
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn getattr(&mut self, _req: &Request, node_id: u64, _fh: Option<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));
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn setattr(
&mut self,
_req: &Request,
node_id: u64,
mode: Option<u32>,
uid: Option<u32>,
gid: Option<u32>,
size: Option<u64>,
atime: Option<TimeOrNow>,
mtime: Option<TimeOrNow>,
_ctime: Option<SystemTime>,
_fh: Option<u64>,
_crtime: Option<SystemTime>,
_chgtime: Option<SystemTime>,
_bkuptime: Option<SystemTime>,
_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);
return;
}
};
let mut node_changed = false;
if let Some(mode) = mode {
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 {
if node.data().uid() != uid {
node.data_mut().set_uid(uid);
node_changed = true;
}
}
if let Some(gid) = gid {
if node.data().gid() != gid {
node.data_mut().set_gid(gid);
node_changed = true;
}
}
if let Some(atime) = atime {
let atime_c = match atime {
SpecificTime(st) => st.duration_since(UNIX_EPOCH).unwrap(),
Now => SystemTime::now().duration_since(UNIX_EPOCH).unwrap(),
};
node.data_mut()
.set_atime(atime_c.as_secs(), atime_c.subsec_nanos());
node_changed = true;
}
if let Some(mtime) = mtime {
let mtime_c = match mtime {
SpecificTime(st) => st.duration_since(UNIX_EPOCH).unwrap(),
Now => SystemTime::now().duration_since(UNIX_EPOCH).unwrap(),
};
node.data_mut()
.set_mtime(mtime_c.as_secs(), mtime_c.subsec_nanos());
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) => {
reply.error(err.errno);
return;
}
}
}
let attr = node_attr(&node);
if node_changed {
if let Err(err) = self.fs.tx(|tx| tx.sync_tree(node)) {
reply.error(err.errno);
return;
}
}
reply.attr(&TTL, &attr);
}
fn open(&mut self, _req: &Request<'_>, node_id: u64, _flags: i32, reply: ReplyOpen) {
let node_ptr = TreePtr::<Node>::new(node_id as u32);
match self.fs.tx(|tx| tx.on_open_node(node_ptr)) {
Ok(()) => reply.opened(0, 0),
Err(err) => reply.error(err.errno),
}
}
fn read(
&mut self,
_req: &Request,
node_id: u64,
_fh: u64,
offset: i64,
size: u32,
_flags: i32,
_lock_owner: Option<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; size as usize];
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]);
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn write(
&mut self,
_req: &Request,
node_id: u64,
_fh: u64,
offset: i64,
data: &[u8],
_write_flags: u32,
_flags: i32,
_lock_owner: Option<u64>,
reply: ReplyWrite,
) {
let node_ptr = TreePtr::<Node>::new(node_id as u32);
let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
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);
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn flush(&mut self, _req: &Request, _ino: u64, _fh: u64, _lock_owner: u64, reply: ReplyEmpty) {
reply.ok();
}
fn release(
&mut self,
_req: &Request,
node_id: u64,
_fh: u64,
_flags: i32,
_lock_owner: Option<u64>,
_flush: bool,
reply: ReplyEmpty,
) {
let node_ptr = TreePtr::new(node_id as u32);
match self.fs.tx(|tx| tx.on_close_node(node_ptr)) {
Ok(()) => reply.ok(),
Err(err) => reply.error(err.errno),
}
}
fn fsync(&mut self, _req: &Request, _ino: u64, _fh: u64, _datasync: bool, reply: ReplyEmpty) {
reply.ok();
}
fn readdir(
&mut self,
_req: &Request,
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.tx(|tx| tx.child_nodes(parent_ptr, &mut children)) {
Ok(()) => {
let mut i;
let skip;
if offset == 0 {
skip = 0;
i = 0;
let _full = reply.add(parent_id, i, FileType::Directory, ".");
i += 1;
let _full = reply.add(
//TODO: get parent?
parent_id,
i,
FileType::Directory,
"..",
);
i += 1;
} else {
i = offset + 1;
skip = offset as usize - 1;
}
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);
return;
}
};
let full = reply.add(
child.node_ptr().id() as u64,
i,
if node.data().is_dir() {
FileType::Directory
} else {
FileType::RegularFile
},
child.name().unwrap(),
);
if full {
break;
}
i += 1;
}
reply.ok();
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn create(
&mut self,
_req: &Request,
parent_id: u64,
name: &OsStr,
mode: u32,
_umask: u32,
_flags: i32,
reply: ReplyCreate,
) {
let parent_ptr = TreePtr::<Node>::new(parent_id as u32);
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
match self.fs.tx(|tx| {
let node = tx.create_node(
parent_ptr,
name.to_str().unwrap(),
Node::MODE_FILE | (mode as u16 & Node::MODE_PERM),
ctime.as_secs(),
ctime.subsec_nanos(),
)?;
tx.on_open_node(node.ptr())?;
Ok(node)
}) {
Ok(node) => {
// println!("Create {:?}:{:o}:{:o}", node.1.name(), node.1.mode, mode);
reply.created(&TTL, &node_attr(&node), 0, 0, 0);
}
Err(error) => {
reply.error(error.errno);
}
}
}
fn mkdir(
&mut self,
_req: &Request,
parent_id: u64,
name: &OsStr,
mode: u32,
_umask: 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.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);
}
Err(error) => {
reply.error(error.errno);
}
}
}
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
.tx(|tx| tx.remove_node(parent_ptr, name.to_str().unwrap(), Node::MODE_DIR))
{
Ok(_) => {
reply.ok();
}
Err(err) => {
reply.error(err.errno);
}
}
}
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
.tx(|tx| tx.remove_node(parent_ptr, name.to_str().unwrap(), Node::MODE_FILE))
{
Ok(_) => {
reply.ok();
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn statfs(&mut self, _req: &Request, _ino: u64, reply: ReplyStatfs) {
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_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.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) => {
reply.entry(&TTL, &node_attr(&node), 0);
}
Err(error) => {
reply.error(error.errno);
}
}
}
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.tx(|tx| {
tx.read_node(
node_ptr,
0,
&mut data,
atime.as_secs(),
atime.subsec_nanos(),
)
}) {
Ok(count) => {
reply.data(&data[..count]);
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn rename(
&mut self,
_req: &Request,
orig_parent: u64,
orig_name: &OsStr,
new_parent: u64,
new_name: &OsStr,
_flags: u32,
reply: ReplyEmpty,
) {
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| tx.rename_node(orig_parent_ptr, orig_name, new_parent_ptr, new_name))
{
Ok(()) => reply.ok(),
Err(err) => reply.error(err.errno),
}
}
}
+23
View File
@@ -0,0 +1,23 @@
#[cfg(all(not(target_os = "redox"), not(fuzzing), feature = "fuse"))]
mod fuse;
#[cfg(all(not(target_os = "redox"), fuzzing, feature = "fuse"))]
pub mod fuse;
#[cfg(all(not(target_os = "redox"), feature = "fuse"))]
pub use self::fuse::mount;
#[cfg(all(not(target_os = "redox"), not(fuzzing), not(feature = "fuse")))]
mod stub;
#[cfg(all(not(target_os = "redox"), fuzzing, not(feature = "fuse")))]
pub mod stub;
#[cfg(all(not(target_os = "redox"), not(feature = "fuse")))]
pub use self::stub::mount;
#[cfg(target_os = "redox")]
mod redox;
#[cfg(target_os = "redox")]
pub use self::redox::mount;
+89
View File
@@ -0,0 +1,89 @@
use redox_scheme::{
scheme::{SchemeState, SchemeSync},
RequestKind, Response, SignalBehavior, Socket,
};
use std::io;
use std::path::Path;
use std::sync::atomic::Ordering;
use crate::{Disk, FileSystem, IS_UMT};
use self::scheme::FileScheme;
pub mod resource;
pub mod scheme;
//FIXME: mut callback is not mut
#[allow(unused_mut)]
pub fn mount<D, P, T, F>(filesystem: FileSystem<D>, mountpoint: P, mut callback: F) -> io::Result<T>
where
D: Disk,
P: AsRef<Path>,
F: FnOnce(&Path) -> T,
{
let mountpoint = mountpoint.as_ref();
let socket = Socket::create()?;
let scheme_name = format!("{}", mountpoint.display());
let mounted_path = format!("/scheme/{}", mountpoint.display());
let mut state = SchemeState::new();
let mut scheme = FileScheme::new(scheme_name, mounted_path.clone(), filesystem, &socket)?;
redox_scheme::scheme::register_sync_scheme(
&socket,
&format!("{}", mountpoint.display()),
&mut scheme,
)?;
let res = callback(Path::new(&mounted_path));
while IS_UMT.load(Ordering::SeqCst) == 0 {
let req = match socket.next_request(SignalBehavior::Restart)? {
None => break,
Some(req) => {
match req.kind() {
RequestKind::Call(r) => r,
RequestKind::SendFd(sendfd_request) => {
let result = scheme.on_sendfd(&sendfd_request);
let response = Response::new(result, sendfd_request);
if !socket.write_response(response, SignalBehavior::Restart)? {
break;
}
continue;
}
RequestKind::OnClose { id } => {
scheme.on_close(id);
state.on_close(id);
continue;
}
RequestKind::OnDetach { id, pid } => {
let Ok(inode) = scheme.inode(id) else {
log::warn!("RequestKind::OnDetach with invalid `id`");
continue;
};
state.on_detach(id, inode, pid);
continue;
}
_ => {
// TODO: Redoxfs does not yet support asynchronous file IO. It might still make
// sense to implement cancellation for huge buffers, e.g. dd bs=1G
continue;
}
}
}
};
let response = req.handle_sync(&mut scheme, &mut state);
if !socket.write_response(response, SignalBehavior::Restart)? {
break;
}
}
// Cleanup on unmount
scheme.fs.cleanup()?;
Ok(res)
}
+794
View File
@@ -0,0 +1,794 @@
use std::slice;
use std::time::{SystemTime, UNIX_EPOCH};
use alloc::collections::BTreeMap;
use libredox::call::MmapArgs;
use range_tree::RangeTree;
use syscall::data::{Stat, TimeSpec};
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
use syscall::error::{Error, Result, EBADF, EINVAL, EISDIR, ENOTDIR, EPERM};
use syscall::flag::{
MapFlags, F_GETFL, F_SETFL, MODE_PERM, O_ACCMODE, O_APPEND, O_RDONLY, O_RDWR, O_WRONLY,
PROT_READ, PROT_WRITE,
};
use syscall::{EBADFD, ENOENT, PAGE_SIZE};
use crate::{Disk, Node, Transaction, TreePtr, BLOCK_SIZE};
pub type Fmaps = BTreeMap<u32, FileMmapInfo>;
pub trait Resource<D: Disk> {
fn parent_ptr_opt(&self) -> Option<TreePtr<Node>>;
fn node_ptr(&self) -> TreePtr<Node>;
fn uid(&self) -> u32;
fn set_path(&mut self, path: &str);
fn read(&mut self, buf: &mut [u8], offset: u64, tx: &mut Transaction<D>) -> Result<usize>;
fn write(&mut self, buf: &[u8], offset: u64, tx: &mut Transaction<D>) -> Result<usize>;
fn fsize(&mut self, tx: &mut Transaction<D>) -> Result<u64>;
fn fmap(
&mut self,
fmaps: &mut Fmaps,
flags: MapFlags,
size: usize,
offset: u64,
tx: &mut Transaction<D>,
) -> Result<usize>;
fn funmap(
&mut self,
fmaps: &mut Fmaps,
offset: u64,
size: usize,
tx: &mut Transaction<D>,
) -> Result<()>;
fn fchmod(&mut self, mode: u16, tx: &mut Transaction<D>) -> Result<()> {
let mut node = tx.read_tree(self.node_ptr())?;
if node.data().uid() == self.uid() || self.uid() == 0 {
let old_mode = node.data().mode();
let new_mode = (old_mode & !MODE_PERM) | (mode & MODE_PERM);
if old_mode != new_mode {
node.data_mut().set_mode(new_mode);
tx.sync_tree(node)?;
}
Ok(())
} else {
Err(Error::new(EPERM))
}
}
fn fchown(&mut self, uid: u32, gid: u32, tx: &mut Transaction<D>) -> Result<()> {
let mut node = tx.read_tree(self.node_ptr())?;
let old_uid = node.data().uid();
if old_uid == self.uid() || self.uid() == 0 {
let mut node_changed = false;
if uid as i32 != -1 {
if uid != old_uid {
node.data_mut().set_uid(uid);
node_changed = true;
}
}
if gid as i32 != -1 {
let old_gid = node.data().gid();
if gid != old_gid {
node.data_mut().set_gid(gid);
node_changed = true;
}
}
if node_changed {
tx.sync_tree(node)?;
}
Ok(())
} else {
Err(Error::new(EPERM))
}
}
fn fcntl(&mut self, cmd: usize, arg: usize) -> Result<usize>;
fn path(&self) -> &str;
fn stat(&self, stat: &mut Stat, tx: &mut Transaction<D>) -> Result<()> {
let node = tx.read_tree(self.node_ptr())?;
let ctime = node.data().ctime();
let mtime = node.data().mtime();
let atime = node.data().atime();
*stat = Stat {
st_dev: 0, // TODO
st_ino: node.id() as u64,
st_mode: node.data().mode(),
st_nlink: node.data().links(),
st_uid: node.data().uid(),
st_gid: node.data().gid(),
st_size: node.data().size(),
st_blksize: 512,
// Blocks is in 512 byte blocks, not in our block size
st_blocks: node.data().blocks() * (BLOCK_SIZE / 512),
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,
};
Ok(())
}
fn sync(&mut self, fmaps: &mut Fmaps, tx: &mut Transaction<D>) -> Result<()>;
fn truncate(&mut self, len: u64, tx: &mut Transaction<D>) -> Result<()>;
fn utimens(&mut self, times: &[TimeSpec], tx: &mut Transaction<D>) -> Result<()>;
fn getdents<'buf>(
&mut self,
buf: DirentBuf<&'buf mut [u8]>,
opaque_offset: u64,
tx: &mut Transaction<D>,
) -> Result<DirentBuf<&'buf mut [u8]>>;
}
pub struct Entry {
pub node_ptr: TreePtr<Node>,
pub name: String,
}
pub struct DirResource {
path: String,
parent_ptr_opt: Option<TreePtr<Node>>,
node_ptr: TreePtr<Node>,
data: Option<Vec<Entry>>,
uid: u32,
}
impl DirResource {
pub fn new(
path: String,
parent_ptr_opt: Option<TreePtr<Node>>,
node_ptr: TreePtr<Node>,
data: Option<Vec<Entry>>,
uid: u32,
) -> DirResource {
DirResource {
path,
parent_ptr_opt,
node_ptr,
data,
uid,
}
}
}
impl<D: Disk> Resource<D> for DirResource {
fn parent_ptr_opt(&self) -> Option<TreePtr<Node>> {
self.parent_ptr_opt
}
fn node_ptr(&self) -> TreePtr<Node> {
self.node_ptr
}
fn uid(&self) -> u32 {
self.uid
}
fn set_path(&mut self, path: &str) {
self.path = path.to_string();
}
fn read(&mut self, _buf: &mut [u8], _offset: u64, _tx: &mut Transaction<D>) -> Result<usize> {
Err(Error::new(EISDIR))
}
fn write(&mut self, _buf: &[u8], _offset: u64, _tx: &mut Transaction<D>) -> Result<usize> {
Err(Error::new(EBADF))
}
fn fsize(&mut self, _tx: &mut Transaction<D>) -> Result<u64> {
Ok(self.data.as_ref().ok_or(Error::new(EBADF))?.len() as u64)
}
fn fmap(
&mut self,
_fmaps: &mut Fmaps,
_flags: MapFlags,
_size: usize,
_offset: u64,
_tx: &mut Transaction<D>,
) -> Result<usize> {
Err(Error::new(EBADF))
}
fn funmap(
&mut self,
_fmaps: &mut Fmaps,
_offset: u64,
_size: usize,
_tx: &mut Transaction<D>,
) -> Result<()> {
Err(Error::new(EBADF))
}
fn fcntl(&mut self, _cmd: usize, _arg: usize) -> Result<usize> {
Err(Error::new(EBADF))
}
fn path(&self) -> &str {
&self.path
}
fn sync(&mut self, _fmaps: &mut Fmaps, _tx: &mut Transaction<D>) -> Result<()> {
Err(Error::new(EBADF))
}
fn truncate(&mut self, _len: u64, _tx: &mut Transaction<D>) -> Result<()> {
Err(Error::new(EBADF))
}
fn utimens(&mut self, times: &[TimeSpec], tx: &mut Transaction<D>) -> Result<()> {
let mut node = tx.read_tree(self.node_ptr)?;
if node.data().uid() == self.uid || self.uid == 0 {
if let &[atime, mtime] = times {
let mut node_changed = false;
let old_mtime = node.data().mtime();
let new_mtime = (mtime.tv_sec as u64, mtime.tv_nsec as u32);
if old_mtime != new_mtime {
node.data_mut().set_mtime(new_mtime.0, new_mtime.1);
node_changed = true;
}
let old_atime = node.data().atime();
let new_atime = (atime.tv_sec as u64, atime.tv_nsec as u32);
if old_atime != new_atime {
node.data_mut().set_atime(new_atime.0, new_atime.1);
node_changed = true;
}
if node_changed {
tx.sync_tree(node)?;
}
}
Ok(())
} else {
Err(Error::new(EPERM))
}
}
fn getdents<'buf>(
&mut self,
mut buf: DirentBuf<&'buf mut [u8]>,
opaque_offset: u64,
tx: &mut Transaction<D>,
) -> Result<DirentBuf<&'buf mut [u8]>> {
match &self.data {
Some(data) => {
let opaque_offset = opaque_offset as usize;
for (idx, entry) in data.iter().enumerate().skip(opaque_offset) {
let child = match tx.read_tree(entry.node_ptr) {
Ok(r) => r,
Err(Error { errno: ENOENT }) => continue,
Err(err) => return Err(err),
};
let result = buf.entry(DirEntry {
inode: child.id() as u64,
next_opaque_id: idx as u64 + 1,
name: &entry.name,
kind: match child.data().mode() & Node::MODE_TYPE {
Node::MODE_DIR => DirentKind::Directory,
Node::MODE_FILE => DirentKind::Regular,
Node::MODE_SYMLINK => DirentKind::Symlink,
//TODO: more types?
_ => DirentKind::Unspecified,
},
});
if let Err(err) = result {
if err.errno == EINVAL && idx > opaque_offset {
// POSIX allows partial result of getdents
break;
} else {
return Err(err);
}
}
}
Ok(buf)
}
None => Err(Error::new(EBADF)),
}
}
}
#[derive(Debug)]
pub struct Fmap {
rc: usize,
flags: MapFlags,
last_page_tail: u16,
}
impl Fmap {
pub unsafe fn new<D: Disk>(
node_ptr: TreePtr<Node>,
flags: MapFlags,
unaligned_size: usize,
offset: u64,
base: *mut u8,
tx: &mut Transaction<D>,
) -> Result<Self> {
// Memory provided to fmap must be page aligned and sized
let aligned_size = unaligned_size.next_multiple_of(syscall::PAGE_SIZE);
let address = base.add(offset as usize);
//println!("ADDR {:p} {:p}", base, address);
// Read buffer from disk
let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
let buf = slice::from_raw_parts_mut(address, unaligned_size);
let count = match tx.read_node(node_ptr, offset, buf, atime.as_secs(), atime.subsec_nanos())
{
Ok(ok) => ok,
Err(err) => {
let _ = libredox::call::munmap(address.cast(), aligned_size);
return Err(err);
}
};
// Make sure remaining data is zeroed
buf[count..].fill(0_u8);
Ok(Self {
rc: 1,
flags,
last_page_tail: (unaligned_size % PAGE_SIZE) as u16,
})
}
pub unsafe fn sync<D: Disk>(
&mut self,
node_ptr: TreePtr<Node>,
base: *mut u8,
offset: u64,
size: usize,
tx: &mut Transaction<D>,
) -> Result<()> {
if self.flags & PROT_WRITE == PROT_WRITE {
let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
tx.write_node(
node_ptr,
offset,
unsafe { core::slice::from_raw_parts(base.add(offset as usize), size) },
mtime.as_secs(),
mtime.subsec_nanos(),
)?;
}
Ok(())
}
}
pub struct FileResource {
path: String,
parent_ptr_opt: Option<TreePtr<Node>>,
node_ptr: TreePtr<Node>,
flags: usize,
uid: u32,
}
#[derive(Debug)]
pub struct FileMmapInfo {
base: *mut u8,
size: usize,
pub ranges: RangeTree<Fmap>,
pub open_fds: usize,
}
impl FileMmapInfo {
pub fn new() -> Self {
Self {
base: core::ptr::null_mut(),
size: 0,
ranges: RangeTree::new(),
open_fds: 0,
}
}
pub fn in_use(&self) -> bool {
self.open_fds > 0 || !self.ranges.is_empty()
}
}
impl Drop for FileMmapInfo {
fn drop(&mut self) {
if self.in_use() {
log::error!("FileMmapInfo dropped while in use");
}
}
}
impl FileResource {
pub fn new(
path: String,
parent_ptr_opt: Option<TreePtr<Node>>,
node_ptr: TreePtr<Node>,
flags: usize,
uid: u32,
) -> FileResource {
FileResource {
path,
parent_ptr_opt,
node_ptr,
flags,
uid,
}
}
}
impl<D: Disk> Resource<D> for FileResource {
fn parent_ptr_opt(&self) -> Option<TreePtr<Node>> {
self.parent_ptr_opt
}
fn node_ptr(&self) -> TreePtr<Node> {
self.node_ptr
}
fn uid(&self) -> u32 {
self.uid
}
fn set_path(&mut self, path: &str) {
self.path = path.to_string();
}
fn read(&mut self, buf: &mut [u8], offset: u64, tx: &mut Transaction<D>) -> Result<usize> {
if self.flags & O_ACCMODE != O_RDWR && self.flags & O_ACCMODE != O_RDONLY {
return Err(Error::new(EBADF));
}
let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
tx.read_node(
self.node_ptr,
offset,
buf,
atime.as_secs(),
atime.subsec_nanos(),
)
}
fn write(&mut self, buf: &[u8], offset: u64, tx: &mut Transaction<D>) -> Result<usize> {
if self.flags & O_ACCMODE != O_RDWR && self.flags & O_ACCMODE != O_WRONLY {
return Err(Error::new(EBADF));
}
let effective_offset = if self.flags & O_APPEND == O_APPEND {
let node = tx.read_tree(self.node_ptr)?;
node.data().size()
} else {
offset
};
let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
tx.write_node(
self.node_ptr,
effective_offset,
buf,
mtime.as_secs(),
mtime.subsec_nanos(),
)
}
fn fsize(&mut self, tx: &mut Transaction<D>) -> Result<u64> {
let node = tx.read_tree(self.node_ptr)?;
Ok(node.data().size())
}
fn fmap(
&mut self,
fmaps: &mut Fmaps,
flags: MapFlags,
unaligned_size: usize,
offset: u64,
tx: &mut Transaction<D>,
) -> Result<usize> {
//dbg!(&self.fmaps);
let accmode = self.flags & O_ACCMODE;
if flags.contains(PROT_READ) && !(accmode == O_RDWR || accmode == O_RDONLY) {
return Err(Error::new(EBADF));
}
if flags.contains(PROT_WRITE) && !(accmode == O_RDWR || accmode == O_WRONLY) {
return Err(Error::new(EBADF));
}
let aligned_size = unaligned_size.next_multiple_of(PAGE_SIZE);
// TODO: PROT_EXEC? It is however unenforcable without restricting anonymous mmap, since a
// program can always map anonymous RW-, read from a file, then remap as R-E. But it might
// be usable as a hint, prohibiting direct executable mmaps at least.
// TODO: Pass entry directory to Resource trait functions, since the node_ptr can be
// obtained by the caller.
let fmap_info = fmaps
.get_mut(&self.node_ptr.id())
.ok_or(Error::new(EBADFD))?;
if !fmap_info.in_use() {
// Notify filesystem of open
tx.on_open_node(self.node_ptr)?;
}
let new_size = (offset as usize + aligned_size).next_multiple_of(PAGE_SIZE);
if new_size > fmap_info.size {
fmap_info.base = if fmap_info.base.is_null() {
unsafe {
libredox::call::mmap(MmapArgs {
length: new_size,
// PRIVATE/SHARED doesn't matter once the pages are passed in the fmap
// handler.
prot: libredox::flag::PROT_READ | libredox::flag::PROT_WRITE,
flags: libredox::flag::MAP_PRIVATE,
offset: 0,
fd: !0,
addr: core::ptr::null_mut(),
})? as *mut u8
}
} else {
unsafe {
syscall::syscall5(
syscall::SYS_MREMAP,
fmap_info.base as usize,
fmap_info.size,
0,
new_size,
syscall::MremapFlags::empty().bits() | (PROT_READ | PROT_WRITE).bits(),
)? as *mut u8
}
};
fmap_info.size = new_size;
}
let affected_fmaps = fmap_info
.ranges
.remove_and_unused(offset..offset + aligned_size as u64);
for (range, v_opt) in affected_fmaps {
//dbg!(&range);
if let Some(mut fmap) = v_opt {
fmap.rc += 1;
fmap.flags |= flags;
//FIXME: Use result?
let _ = fmap_info
.ranges
.insert(range.start, range.end - range.start, fmap);
} else {
let map = unsafe {
Fmap::new(
self.node_ptr,
flags,
unaligned_size,
offset,
fmap_info.base,
tx,
)?
};
//FIXME: Use result?
let _ = fmap_info.ranges.insert(offset, aligned_size as u64, map);
}
}
//dbg!(&self.fmaps);
Ok(fmap_info.base as usize + offset as usize)
}
fn funmap(
&mut self,
fmaps: &mut Fmaps,
offset: u64,
size: usize,
tx: &mut Transaction<D>,
) -> Result<()> {
let fmap_info = fmaps
.get_mut(&self.node_ptr.id())
.ok_or(Error::new(EBADFD))?;
//dbg!(&self.fmaps);
//dbg!(self.fmaps.conflicts(offset..offset + size as u64).collect::<Vec<_>>());
#[allow(unused_mut)]
let mut affected_fmaps = fmap_info.ranges.remove(offset..offset + size as u64);
for (range, mut fmap) in affected_fmaps {
fmap.rc = fmap.rc.checked_sub(1).unwrap();
//log::info!("SYNCING {}..{}", range.start, range.end);
unsafe {
fmap.sync(
self.node_ptr,
fmap_info.base,
range.start,
(range.end - range.start) as usize,
tx,
)?;
}
if fmap.rc > 0 {
//FIXME: Use result?
let _ = fmap_info
.ranges
.insert(range.start, range.end - range.start, fmap);
}
}
//dbg!(&self.fmaps);
// Allow release of node if not in use anymore
if !fmap_info.in_use() {
// Notify filesystem of close
tx.on_close_node(self.node_ptr)?;
/*TODO: leaks memory, but why?
// Remove from fmaps list
fmaps.remove(&self.node_ptr.id());
*/
}
Ok(())
}
fn fcntl(&mut self, cmd: usize, arg: usize) -> Result<usize> {
match cmd {
F_GETFL => Ok(self.flags),
F_SETFL => {
self.flags = (self.flags & O_ACCMODE) | (arg & !O_ACCMODE);
Ok(0)
}
_ => Err(Error::new(EINVAL)),
}
}
fn path(&self) -> &str {
&self.path
}
fn sync(&mut self, fmaps: &mut Fmaps, tx: &mut Transaction<D>) -> Result<()> {
if let Some(fmap_info) = fmaps.get_mut(&self.node_ptr.id()) {
for (range, fmap) in fmap_info.ranges.iter_mut() {
unsafe {
fmap.sync(
self.node_ptr,
fmap_info.base,
range.start,
(range.end - range.start) as usize,
tx,
)?;
}
}
}
Ok(())
}
fn truncate(&mut self, len: u64, tx: &mut Transaction<D>) -> Result<()> {
if self.flags & O_ACCMODE == O_RDWR || self.flags & O_ACCMODE == O_WRONLY {
let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
tx.truncate_node(self.node_ptr, len, mtime.as_secs(), mtime.subsec_nanos())?;
Ok(())
} else {
Err(Error::new(EBADF))
}
}
fn utimens(&mut self, times: &[TimeSpec], tx: &mut Transaction<D>) -> Result<()> {
let mut node = tx.read_tree(self.node_ptr)?;
if node.data().uid() == self.uid || self.uid == 0 {
if let &[atime, mtime] = times {
let mut node_changed = false;
let old_mtime = node.data().mtime();
let new_mtime = (mtime.tv_sec as u64, mtime.tv_nsec as u32);
if old_mtime != new_mtime {
node.data_mut().set_mtime(new_mtime.0, new_mtime.1);
node_changed = true;
}
let old_atime = node.data().atime();
let new_atime = (atime.tv_sec as u64, atime.tv_nsec as u32);
if old_atime != new_atime {
node.data_mut().set_atime(new_atime.0, new_atime.1);
node_changed = true;
}
if node_changed {
tx.sync_tree(node)?;
}
}
Ok(())
} else {
Err(Error::new(EPERM))
}
}
fn getdents<'buf>(
&mut self,
_buf: DirentBuf<&'buf mut [u8]>,
_opaque_offset: u64,
_tx: &mut Transaction<D>,
) -> Result<DirentBuf<&'buf mut [u8]>> {
Err(Error::new(ENOTDIR))
}
}
impl Drop for FileResource {
fn drop(&mut self) {
/*
if !self.fmaps.is_empty() {
eprintln!(
"redoxfs: file {} still has {} fmaps!",
self.path,
self.fmaps.len()
);
}
*/
}
}
impl range_tree::Value for Fmap {
type K = u64;
fn try_merge_forward(self, other: &Self) -> core::result::Result<Self, Self> {
if self.rc == other.rc && self.flags == other.flags && self.last_page_tail == 0 {
Ok(self)
} else {
Err(self)
}
}
fn try_merge_backwards(self, other: &Self) -> core::result::Result<Self, Self> {
if self.rc == other.rc && self.flags == other.flags && other.last_page_tail == 0 {
Ok(self)
} else {
Err(self)
}
}
#[allow(unused_variables)]
fn split(
self,
prev_range: Option<core::ops::Range<Self::K>>,
range: core::ops::Range<Self::K>,
next_range: Option<core::ops::Range<Self::K>>,
) -> (Option<Self>, Self, Option<Self>) {
(
prev_range.map(|_range| Fmap {
rc: self.rc,
flags: self.flags,
last_page_tail: 0,
}),
Fmap {
rc: self.rc,
flags: self.flags,
last_page_tail: if next_range.is_none() {
self.last_page_tail
} else {
0
},
},
next_range.map(|_range| Fmap {
rc: self.rc,
flags: self.flags,
last_page_tail: self.last_page_tail,
}),
)
}
}
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
use std::{io, path::Path};
use crate::{filesystem, Disk};
pub fn mount<D, P, T, F>(
mut _filesystem: filesystem::FileSystem<D>,
_mountpoint: P,
_callback: F,
) -> io::Result<T>
where
D: Disk,
P: AsRef<Path>,
F: FnOnce(&Path) -> T,
{
Err(io::Error::new(
io::ErrorKind::Unsupported,
"FUSE mount feature is disabled",
))
}
+584
View File
@@ -0,0 +1,584 @@
use core::{fmt, mem, ops, slice};
use endian_num::Le;
use crate::{BlockLevel, BlockList, BlockPtr, BlockTrait, RecordRaw, BLOCK_SIZE, RECORD_LEVEL};
bitflags::bitflags! {
pub struct NodeFlags: u32 {
const INLINE_DATA = 0x1;
}
}
/// An index into a [`Node`]'s block table.
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 record offsets, make sure to sync with Node
/// Return the [`NodeLevel`] of the record with the given index.
/// - the first 128 are level 0,
/// - the next 64*256 are level 1,
/// - ...and so on.
pub fn new(mut record_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 record_offset < L0 {
return Some(Self::L0((record_offset & MASK) as usize));
} else {
record_offset -= L0;
}
const L1: u64 = 64 * NUM;
if record_offset < L1 {
return Some(Self::L1(
((record_offset >> SHIFT) & MASK) as usize,
(record_offset & MASK) as usize,
));
} else {
record_offset -= L1;
}
const L2: u64 = 32 * NUM * NUM;
if record_offset < L2 {
return Some(Self::L2(
((record_offset >> (2 * SHIFT)) & MASK) as usize,
((record_offset >> SHIFT) & MASK) as usize,
(record_offset & MASK) as usize,
));
} else {
record_offset -= L2;
}
const L3: u64 = 16 * NUM * NUM * NUM;
if record_offset < L3 {
return Some(Self::L3(
((record_offset >> (3 * SHIFT)) & MASK) as usize,
((record_offset >> (2 * SHIFT)) & MASK) as usize,
((record_offset >> SHIFT) & MASK) as usize,
(record_offset & MASK) as usize,
));
} else {
record_offset -= L3;
}
const L4: u64 = 12 * NUM * NUM * NUM * NUM;
if record_offset < L4 {
Some(Self::L4(
((record_offset >> (4 * SHIFT)) & MASK) as usize,
((record_offset >> (3 * SHIFT)) & MASK) as usize,
((record_offset >> (2 * SHIFT)) & MASK) as usize,
((record_offset >> SHIFT) & MASK) as usize,
(record_offset & MASK) as usize,
))
} else {
None
}
}
}
type BlockListL1 = BlockList<RecordRaw>;
type BlockListL2 = BlockList<BlockListL1>;
type BlockListL3 = BlockList<BlockListL2>;
type BlockListL4 = BlockList<BlockListL3>;
#[repr(C, packed)]
pub struct NodeLevelData {
/// The first 128 blocks of this file.
///
/// Total size: 128 * RECORD_SIZE (16 MiB, 128 KiB each)
pub level0: [BlockPtr<RecordRaw>; 128],
/// The next 64 * 256 blocks of this file,
/// stored behind 64 level one tables.
///
/// Total size: 64 * 256 * RECORD_SIZE (2 GiB, 32 MiB each)
pub level1: [BlockPtr<BlockListL1>; 64],
/// The next 32 * 256 * 256 blocks of this file,
/// stored behind 32 level two tables.
/// Each level two table points to 256 level one tables.
///
/// Total size: 32 * 256 * 256 * RECORD_SIZE (256 GiB, 8 GiB each)
pub level2: [BlockPtr<BlockListL2>; 32],
/// The next 16 * 256 * 256 * 256 blocks of this file,
/// stored behind 16 level three tables.
///
/// Total size: 16 * 256 * 256 * 256 * RECORD_SIZE (32 TiB, 2 TiB each)
pub level3: [BlockPtr<BlockListL3>; 16],
/// The next 8 * 256 * 256 * 256 * 256 blocks of this file,
/// stored behind 8 level four tables.
///
/// Total size: 8 * 256 * 256 * 256 * 256 * RECORD_SIZE (4 PiB, 512 TiB each)
pub level4: [BlockPtr<BlockListL4>; 8],
}
impl Default for NodeLevelData {
fn default() -> Self {
Self {
level0: [BlockPtr::default(); 128],
level1: [BlockPtr::default(); 64],
level2: [BlockPtr::default(); 32],
level3: [BlockPtr::default(); 16],
level4: [BlockPtr::default(); 8],
}
}
}
/// A file/folder node
#[repr(C, packed)]
pub struct Node {
/// This node's type & permissions.
/// - four most significant bits are the node's type
/// - next four bits are permissions for the node's user
/// - next four bits are permissions for the node's group
/// - four least significant bits are permissions for everyone else
pub mode: Le<u16>,
/// The uid that owns this file
pub uid: Le<u32>,
/// The gid that owns this file
pub gid: Le<u32>,
/// The number of hard links to this file
pub links: Le<u32>,
/// The length of this file, in bytes
pub size: Le<u64>,
/// The disk usage of this file, in blocks
pub blocks: Le<u64>,
/// Creation time
pub ctime: Le<u64>,
pub ctime_nsec: Le<u32>,
/// Modification time
pub mtime: Le<u64>,
pub mtime_nsec: Le<u32>,
/// Access time
pub atime: Le<u64>,
pub atime_nsec: Le<u32>,
/// Record level
pub record_level: Le<u32>,
/// Flags
pub flags: Le<u32>,
/// Padding
pub padding: [u8; BLOCK_SIZE as usize - 4042],
/// Level data, should not be used directly so inline data can be supported
pub(crate) level_data: NodeLevelData,
}
unsafe impl BlockTrait for Node {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self::default())
} else {
None
}
}
}
impl Default for Node {
fn default() -> Self {
Self {
mode: 0.into(),
uid: 0.into(),
gid: 0.into(),
links: 0.into(),
size: 0.into(),
// This node counts as a block
blocks: 1.into(),
ctime: 0.into(),
ctime_nsec: 0.into(),
mtime: 0.into(),
mtime_nsec: 0.into(),
atime: 0.into(),
atime_nsec: 0.into(),
record_level: 0.into(),
flags: 0.into(),
padding: [0; BLOCK_SIZE as usize - 4042],
level_data: NodeLevelData::default(),
}
}
}
impl Node {
pub const MODE_TYPE: u16 = 0xF000;
pub const MODE_FILE: u16 = 0x8000;
pub const MODE_DIR: u16 = 0x4000;
pub const MODE_SYMLINK: u16 = 0xA000;
pub const MODE_SOCK: u16 = 0xC000;
/// Mask for node permission bits
pub const MODE_PERM: u16 = 0x0FFF;
pub const MODE_EXEC: u16 = 0o1;
pub const MODE_WRITE: u16 = 0o2;
pub const MODE_READ: u16 = 0o4;
/// Create a new, empty node with the given metadata
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(),
record_level: if mode & Self::MODE_TYPE == Self::MODE_FILE {
// Files take on record level
RECORD_LEVEL as u32
} else {
// Folders do not
0
}
.into(),
flags: if mode & Self::MODE_TYPE == Self::MODE_DIR {
// Directories must not use inline data (until h-tree supports it)
NodeFlags::empty()
} else {
NodeFlags::INLINE_DATA
}
.bits()
.into(),
..Default::default()
}
}
/// This node's type & permissions.
/// - four most significant bits are the node's type
/// - next four bits are permissions for the node's user
/// - next four bits are permissions for the node's group
/// - four least significant bits are permissions for everyone else
pub fn mode(&self) -> u16 {
self.mode.to_ne()
}
/// The uid that owns this file
pub fn uid(&self) -> u32 {
self.uid.to_ne()
}
/// The gid that owns this file
pub fn gid(&self) -> u32 {
self.gid.to_ne()
}
/// The number of links to this file
/// (directory entries, symlinks, etc)
pub fn links(&self) -> u32 {
self.links.to_ne()
}
/// The length of this file, in bytes.
pub fn size(&self) -> u64 {
self.size.to_ne()
}
/// The disk usage of this file, in blocks.
pub fn blocks(&self) -> u64 {
self.blocks.to_ne()
}
pub fn ctime(&self) -> (u64, u32) {
(self.ctime.to_ne(), self.ctime_nsec.to_ne())
}
pub fn mtime(&self) -> (u64, u32) {
(self.mtime.to_ne(), self.mtime_nsec.to_ne())
}
pub fn atime(&self) -> (u64, u32) {
(self.atime.to_ne(), self.atime_nsec.to_ne())
}
pub fn record_level(&self) -> BlockLevel {
BlockLevel(self.record_level.to_ne() as usize)
}
pub fn flags(&self) -> NodeFlags {
NodeFlags::from_bits_retain(self.flags.to_ne())
}
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_blocks(&mut self, blocks: u64) {
self.blocks = blocks.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 set_flags(&mut self, flags: NodeFlags) {
self.flags = flags.bits().into();
}
pub fn has_inline_data(&self) -> bool {
self.flags().contains(NodeFlags::INLINE_DATA)
}
pub fn inline_data(&self) -> Option<&[u8]> {
if self.has_inline_data() {
Some(unsafe {
slice::from_raw_parts(
&self.level_data as *const NodeLevelData as *const u8,
mem::size_of::<NodeLevelData>(),
)
})
} else {
None
}
}
pub fn inline_data_mut(&mut self) -> Option<&mut [u8]> {
if self.has_inline_data() {
Some(unsafe {
slice::from_raw_parts_mut(
&mut self.level_data as *mut NodeLevelData as *mut u8,
mem::size_of::<NodeLevelData>(),
)
})
} else {
None
}
}
pub fn level_data(&self) -> Option<&NodeLevelData> {
if !self.has_inline_data() {
Some(&self.level_data)
} else {
None
}
}
pub fn level_data_mut(&mut self) -> Option<&mut NodeLevelData> {
if !self.has_inline_data() {
Some(&mut self.level_data)
} else {
None
}
}
pub fn is_dir(&self) -> bool {
self.mode() & Self::MODE_TYPE == Self::MODE_DIR
}
pub fn is_file(&self) -> bool {
self.mode() & Self::MODE_TYPE == Self::MODE_FILE
}
pub fn is_symlink(&self) -> bool {
self.mode() & Self::MODE_TYPE == Self::MODE_SYMLINK
}
pub fn is_sock(&self) -> bool {
self.mode() & Self::MODE_SOCK == Self::MODE_SOCK
}
/// 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
}
/// 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 {
// 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;
// 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 uid == 0 {
//set the `other` bits to 111
perm |= 0o7;
}
perm & op == op
}
}
impl fmt::Debug for Node {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mode = self.mode;
let uid = self.uid;
let gid = self.gid;
let links = self.links;
let size = self.size;
let blocks = self.blocks;
let ctime = self.ctime;
let ctime_nsec = self.ctime_nsec;
let mtime = self.mtime;
let mtime_nsec = self.mtime_nsec;
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("blocks", &blocks)
.field("ctime", &ctime)
.field("ctime_nsec", &ctime_nsec)
.field("mtime", &mtime)
.field("mtime_nsec", &mtime_nsec)
.field("atime", &atime)
.field("atime_nsec", &atime_nsec)
//TODO: level0/1/2/3
.finish()
}
}
impl ops::Deref for Node {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(self as *const Node as *const u8, mem::size_of::<Node>())
as &[u8]
}
}
}
impl ops::DerefMut for Node {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(self as *mut Node as *mut u8, mem::size_of::<Node>())
as &mut [u8]
}
}
}
#[test]
fn node_size_test() {
assert_eq!(mem::size_of::<Node>(), crate::BLOCK_SIZE as usize);
}
#[test]
fn node_inline_data_test() {
let mut node = Node::default();
assert!(!node.has_inline_data());
assert!(node.inline_data().is_none());
assert!(node.inline_data_mut().is_none());
assert!(node.level_data().is_some());
assert!(node.level_data_mut().is_some());
node.set_flags(NodeFlags::INLINE_DATA);
assert!(node.has_inline_data());
assert!(node.level_data().is_none());
assert!(node.level_data_mut().is_none());
let node_addr = &node as *const Node as usize;
let meta_size = 128;
{
let inline_data = node.inline_data().unwrap();
let inline_data_addr = inline_data.as_ptr() as usize;
assert_eq!(node_addr + meta_size, inline_data_addr);
assert_eq!(inline_data.len(), (crate::BLOCK_SIZE as usize) - meta_size);
}
{
let inline_data = node.inline_data_mut().unwrap();
let inline_data_addr = inline_data.as_ptr() as usize;
assert_eq!(node_addr + meta_size, inline_data_addr);
assert_eq!(inline_data.len(), (crate::BLOCK_SIZE as usize) - meta_size);
}
}
#[cfg(kani)]
#[kani::proof]
fn check_node_level() {
let offset = kani::any();
NodeLevel::new(offset);
}
#[cfg(kani)]
#[kani::proof]
fn check_node_perms() {
let mode = 0o750;
let uid = kani::any();
let gid = kani::any();
let ctime = kani::any();
let ctime_nsec = kani::any();
let node = Node::new(mode, uid, gid, ctime, ctime_nsec);
let root_uid = 0;
let root_gid = 0;
let other_uid = kani::any();
kani::assume(other_uid != uid);
kani::assume(other_uid != root_uid);
let other_gid = kani::any();
kani::assume(other_gid != gid);
kani::assume(other_gid != root_gid);
assert!(node.owner(uid));
assert!(node.permission(uid, gid, 0o7));
assert!(node.permission(uid, gid, 0o5));
assert!(node.permission(uid, other_gid, 0o7));
assert!(node.permission(uid, other_gid, 0o5));
assert!(!node.permission(other_uid, gid, 0o7));
assert!(node.permission(other_uid, gid, 0o5));
assert!(node.owner(root_uid));
assert!(node.permission(root_uid, root_gid, 0o7));
assert!(node.permission(root_uid, root_gid, 0o5));
assert!(node.permission(root_uid, other_gid, 0o7));
assert!(node.permission(root_uid, other_gid, 0o5));
assert!(!node.permission(other_uid, root_gid, 0o7));
assert!(node.permission(other_uid, root_gid, 0o5));
assert!(!node.owner(other_uid));
assert!(!node.permission(other_uid, other_gid, 0o7));
assert!(!node.permission(other_uid, other_gid, 0o5));
}
+42
View File
@@ -0,0 +1,42 @@
use alloc::{boxed::Box, vec};
use core::ops;
use crate::{BlockLevel, BlockTrait, RECORD_LEVEL};
//TODO: this is a box to prevent stack overflows
#[derive(Clone)]
pub struct RecordRaw(pub(crate) Box<[u8]>);
unsafe impl BlockTrait for RecordRaw {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 <= RECORD_LEVEL {
Some(Self(vec![0; level.bytes() as usize].into_boxed_slice()))
} else {
None
}
}
}
impl ops::Deref for RecordRaw {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.0
}
}
impl ops::DerefMut for RecordRaw {
fn deref_mut(&mut self) -> &mut [u8] {
&mut self.0
}
}
#[test]
fn record_raw_size_test() {
for level_i in 0..RECORD_LEVEL {
let level = BlockLevel(level_i);
assert_eq!(
RecordRaw::empty(level).unwrap().len(),
level.bytes() as usize
);
}
}
+1104
View File
File diff suppressed because it is too large Load Diff
+2053
View File
File diff suppressed because it is too large Load Diff
+280
View File
@@ -0,0 +1,280 @@
use core::{marker::PhantomData, mem, ops, slice};
use endian_num::Le;
use crate::{BlockLevel, BlockPtr, BlockRaw, BlockTrait};
// 1 << 8 = 256, this is the number of entries in a TreeList
const TREE_LIST_SHIFT: u32 = 8;
const TREE_LIST_ENTRIES: usize = (1 << TREE_LIST_SHIFT) - 2;
/// A tree with 4 levels
pub type Tree = TreeList<TreeList<TreeList<TreeList<BlockRaw>>>>;
/// A [`TreePtr`] and the contents of the block it references.
#[derive(Clone, Copy, Debug, Default)]
pub struct TreeData<T> {
/// The value of the [`TreePtr`]
id: u32,
// The data
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,
}
}
}
/// A list of pointers to blocks of type `T`.
/// This is one level of a [`Tree`], defined above.
#[repr(C, packed)]
pub struct TreeList<T> {
pub ptrs: [BlockPtr<T>; TREE_LIST_ENTRIES],
pub full_flags: [u128; 2],
}
impl<T> TreeList<T> {
pub fn tree_list_is_full(&self) -> bool {
self.full_flags[1] == u128::MAX & !(3 << 126) && self.full_flags[0] == u128::MAX
}
pub fn tree_list_is_empty(&self) -> bool {
for ptr in self.ptrs.iter() {
if !ptr.is_null() {
return false;
}
}
true
}
pub fn branch_is_full(&self, index: usize) -> bool {
assert!(index < TREE_LIST_ENTRIES);
let shift = index % 128;
let full_flags_index = index / 128;
self.full_flags[full_flags_index] & (1 << shift) != 0
}
pub fn set_branch_full(&mut self, index: usize, full: bool) {
assert!(index < TREE_LIST_ENTRIES);
let shift = index % 128;
let full_flags_index = index / 128;
if full {
self.full_flags[full_flags_index] |= 1 << shift;
} else {
self.full_flags[full_flags_index] &= !(1 << shift);
}
}
}
unsafe impl<T> BlockTrait for TreeList<T> {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self {
ptrs: [BlockPtr::default(); TREE_LIST_ENTRIES],
full_flags: [0; 2],
})
} else {
None
}
}
}
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]
}
}
}
/// A pointer to an entry in a [`Tree`].
#[repr(C, packed)]
pub struct TreePtr<T> {
id: Le<u32>,
phantom: PhantomData<T>,
}
impl<T> TreePtr<T> {
/// Get a [`TreePtr`] to the filesystem root
/// directory's node.
pub fn root() -> Self {
Self::new(1)
}
pub fn new(id: u32) -> Self {
Self {
id: id.into(),
phantom: PhantomData,
}
}
/// Create a [`TreePtr`] from [`Tree`] indices,
/// Where `indexes` is `(i3, i2, i1, i0)`.
/// - `i3` is the index into the level 3 table,
/// - `i2` is the index into the level 2 table at `i3`
/// - ...and so on.
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_ne()
}
pub fn is_null(&self) -> bool {
self.id() == 0
}
/// Get this indices of this [`TreePtr`] in a [`Tree`].
/// Returns `(i3, i2, i1, i0)`:
/// - `i3` is the index into the level 3 table,
/// - `i2` is the index into the level 2 table at `i3`
/// - ...and so on.
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();
let i3 = ((id >> (3 * SHIFT)) & MASK) as usize;
let i2 = ((id >> (2 * SHIFT)) & MASK) as usize;
let i1 = ((id >> SHIFT) & MASK) as usize;
let i0 = (id & MASK) as usize;
(i3, i2, i1, i0)
}
pub fn to_bytes(&self) -> [u8; 4] {
self.id.to_le_bytes()
}
pub fn from_bytes(bytes: [u8; 4]) -> Self {
let val = u32::from_le_bytes(bytes);
Self {
id: Le(val),
phantom: PhantomData,
}
}
}
impl<T> Clone for TreePtr<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for TreePtr<T> {}
impl<T> Default for TreePtr<T> {
fn default() -> Self {
Self {
id: 0.into(),
phantom: PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use crate::{BlockAddr, BlockData, BlockMeta};
use super::*;
#[test]
fn tree_list_size_test() {
assert_eq!(
mem::size_of::<TreeList<BlockRaw>>(),
crate::BLOCK_SIZE as usize
);
}
#[test]
fn tree_list_is_full_test() {
let mut tree_list = TreeList::<BlockRaw>::empty(BlockLevel::default()).unwrap();
assert!(!tree_list.tree_list_is_full());
for i in 0..TREE_LIST_ENTRIES {
assert!(!tree_list.branch_is_full(i));
tree_list.set_branch_full(i, true);
assert!(tree_list.branch_is_full(i));
}
assert!(tree_list.tree_list_is_full());
for i in 0..TREE_LIST_ENTRIES {
assert!(tree_list.branch_is_full(i));
tree_list.set_branch_full(i, false);
assert!(!tree_list.branch_is_full(i));
}
}
fn mock_block(addr: u64) -> BlockPtr<BlockRaw> {
let block_addr = unsafe { BlockAddr::new(addr, BlockMeta::default()) };
BlockData::empty(block_addr).unwrap().create_ptr()
}
#[test]
fn tree_list_is_empty() {
let mut tree_list = TreeList::<BlockRaw>::empty(BlockLevel::default()).unwrap();
assert!(tree_list.tree_list_is_empty());
tree_list.ptrs[3] = mock_block(123);
assert!(!tree_list.tree_list_is_empty());
}
#[test]
fn tree_ptr_to_and_from_bytes() {
let ptr: TreePtr<BlockRaw> = TreePtr::new(123456);
let bytes = ptr.to_bytes();
let ptr2: TreePtr<BlockRaw> = TreePtr::from_bytes(bytes);
assert_eq!(ptr.id(), ptr2.id());
}
}
+51
View File
@@ -0,0 +1,51 @@
use std::{
fs,
io::{self},
process::{Command, ExitStatus},
};
fn unmount_linux_path(mount_path: &str) -> io::Result<ExitStatus> {
// Different distributions can have various fusermount binaries. Try
// them all.
let commands = ["fusermount", "fusermount3"];
for command in commands {
let status = Command::new(command).arg("-u").arg(mount_path).status();
if status.is_ok() {
return status;
}
if let Err(ref e) = status {
if e.kind() == io::ErrorKind::NotFound {
continue;
}
}
}
// Unmounting failed since no suitable command was found
Err(std::io::Error::new(
io::ErrorKind::NotFound,
format!(
"Unable to locate any fusermount binaries. Tried {:?}. Is fuse installed?",
commands
),
))
}
pub fn unmount_path(mount_path: &str) -> Result<(), io::Error> {
if cfg!(target_os = "redox") {
fs::remove_dir(format!("/scheme/{}", mount_path))?
} else {
let status_res = if cfg!(target_os = "linux") {
unmount_linux_path(mount_path)
} else {
Command::new("umount").arg(mount_path).status()
};
let status = status_res?;
if !status.success() {
return Err(io::Error::other("redoxfs umount failed"));
}
}
Ok(())
}
Executable
+67
View File
@@ -0,0 +1,67 @@
#!/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
fusermount3 -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
time truncate --size=256M image/test/sparse
dd if=image/test/sparse 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/sparse
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
+639
View File
@@ -0,0 +1,639 @@
use core::panic::AssertUnwindSafe;
use redoxfs::{unmount_path, DirEntry, DiskMemory, DiskSparse, FileSystem, Node, TreePtr};
use std::io::{Read, Seek, SeekFrom, Write};
use std::panic::catch_unwind;
use std::path::Path;
use std::process::Command;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering::Relaxed;
use std::thread::sleep;
use std::time::Duration;
use std::{env, fs, time};
static IMAGE_SEQ: AtomicUsize = AtomicUsize::new(0);
fn with_redoxfs<T, F>(callback: F) -> T
where
T: Send + Sync + 'static,
F: FnOnce(&str) -> T + Send + Sync + 'static,
{
let disk_path = format!("image{}.bin", IMAGE_SEQ.fetch_add(1, Relaxed));
{
let disk = DiskSparse::create(dbg!(&disk_path), 1024 * 1024 * 1024).unwrap();
let ctime = dbg!(time::SystemTime::now().duration_since(time::UNIX_EPOCH)).unwrap();
FileSystem::create(disk, None, ctime.as_secs(), ctime.subsec_nanos()).unwrap();
}
let res = callback(&disk_path);
dbg!(fs::remove_file(dbg!(disk_path))).unwrap();
res
}
fn with_mounted<T, F>(callback: F) -> T
where
T: Send + Sync + 'static,
F: FnOnce(&Path) -> T + Send + Sync + 'static,
{
let mount_path_o = format!("image{}", IMAGE_SEQ.fetch_add(1, Relaxed));
let mount_path = mount_path_o.clone();
let res = with_redoxfs(move |fs| {
// At redox, we mount on /scheme/ path, no need an empty dir
if cfg!(not(target_os = "redox")) {
if !Path::new(&mount_path).exists() {
dbg!(fs::create_dir(dbg!(&mount_path))).unwrap();
}
} else {
//FIXME: cargo_bin is broken when cross compiling. This is redoxer specific workaround
env::set_var(
"CARGO_BIN_EXE_redoxfs",
"/root/target/x86_64-unknown-redox/debug/redoxfs",
);
}
let mut mount_cmd = Command::new(assert_cmd::cargo_bin!("redoxfs"));
mount_cmd.arg("-d").arg(dbg!(&fs)).arg(dbg!(&mount_path));
let mut child = mount_cmd.spawn().expect("mount failed to run");
let real_path = if cfg!(target_os = "redox") {
let real_path = dbg!(Path::new("/scheme").join(&mount_path));
let mut tries = 0;
loop {
if real_path.exists() {
break;
}
tries += 1;
if tries == 10 {
panic!("Fail to wait for mount")
}
println!("{tries}");
sleep(Duration::from_millis(500));
}
real_path
} else {
sleep(Duration::from_millis(200));
let r = Path::new(".").join(&mount_path);
r
};
let res = catch_unwind(AssertUnwindSafe(|| callback(&real_path)));
sleep(Duration::from_millis(200));
child.kill().expect("Can't kill");
let _ = child.wait();
if cfg!(target_os = "redox") {
unmount_path(&mount_path).unwrap();
} else {
if !dbg!(Command::new("sync").status()).unwrap().success() {
panic!("sync failed");
}
if unmount_path(&mount_path).is_err() {
// There seems to be a race condition where the device can be busy when trying to unmount.
// So, we pause for a moment and retry. There will still be an error output to the logs
// for the first failed attempt.
sleep(Duration::from_millis(200));
if unmount_path(&mount_path).is_err() {
panic!("umount failed");
}
}
}
res.expect("Test failed")
});
if cfg!(not(target_os = "redox")) {
dbg!(fs::remove_dir(dbg!(mount_path_o))).unwrap();
}
res
}
#[test]
fn simple() {
with_mounted(|path| {
dbg!(fs::create_dir(path.join("test"))).unwrap();
})
}
#[test]
fn create_and_remove_file() {
with_mounted(|path| {
let file_name = "test_file.txt";
let file_path = path.join(file_name);
// Create the file
fs::write(&file_path, "Hello, world!").unwrap();
assert!(fs::exists(&file_path).unwrap());
// Read the file
let contents = fs::read_to_string(&file_path).unwrap();
assert_eq!(contents, "Hello, world!");
// Remove the file
fs::remove_file(&file_path).unwrap();
assert!(!fs::exists(&file_path).unwrap());
});
}
#[test]
fn create_and_remove_directory() {
with_mounted(|path| {
let dir_name = "test_dir";
let dir_path = path.join(dir_name);
// Create the directory
fs::create_dir(&dir_path)
.unwrap_or_else(|_| panic!("cannot create dir {}", &dir_path.display()));
assert!(fs::exists(&dir_path).unwrap());
// Check that the directory is empty
let entries: Vec<_> = fs::read_dir(&dir_path)
.unwrap()
.map(|e| e.unwrap().file_name())
.collect();
assert!(entries.is_empty());
// Add a file to the directory
let file_name = "test_file.txt";
let file_path = dir_path.join(file_name);
fs::write(&file_path, "Hello, world!").unwrap();
// Check that the dir cannot be removed when not empty
let error = fs::remove_dir(&dir_path);
assert!(error.is_err());
assert_eq!(
error.unwrap_err().kind(),
std::io::ErrorKind::DirectoryNotEmpty
);
// Remove the file
fs::remove_file(&file_path).unwrap();
// Remove the directory
fs::remove_dir(&dir_path).unwrap();
assert!(!fs::exists(&dir_path).unwrap());
});
}
#[test]
fn create_and_remove_symlink() {
with_mounted(|path| {
let real_file = "real_file.txt";
let real_path = path.join(real_file);
let symlink_file = "symlink_to_real_file.txt";
let symlink_path = path.join(symlink_file);
// Create the real file
fs::write(&real_path, "Hello, world!").unwrap();
// Create the symmlink according to the platform
#[cfg(unix)]
std::os::unix::fs::symlink(real_file, &symlink_path).unwrap();
#[cfg(windows)]
std::os::windows::fs::symlink_file(&real_file, &symlink_path).unwrap();
// Check that the symlink exists and points to the correct target
let exists = fs::exists(&symlink_path);
assert!(
exists.is_ok() && exists.unwrap(),
"Symlink should exist but was: {:?}",
fs::exists(&symlink_path)
);
let symlink_metadata = fs::symlink_metadata(&symlink_path).unwrap();
assert!(symlink_metadata.file_type().is_symlink());
let target = fs::read_link(&symlink_path).unwrap();
assert_eq!(target.to_str().unwrap(), real_file);
assert_eq!(fs::read(&symlink_path).unwrap(), b"Hello, world!");
// Confirm the symlink cannot be removed as a directory
let error = fs::remove_dir(&symlink_path);
assert!(error.is_err());
assert_eq!(error.unwrap_err().kind(), std::io::ErrorKind::NotADirectory);
// Remove the symlink
fs::remove_file(&symlink_path).unwrap();
assert!(!fs::exists(&symlink_path).unwrap());
});
}
#[cfg(target_os = "redox")]
#[test]
fn mmap() {
//TODO
with_mounted(|path| {
use std::slice;
let path = dbg!(path.join("test"));
let mmap_inner = |write: bool| {
let fd = dbg!(libredox::call::open(
path.to_str().unwrap(),
libredox::flag::O_CREAT | libredox::flag::O_RDWR | libredox::flag::O_CLOEXEC,
0,
))
.unwrap();
let map = unsafe {
slice::from_raw_parts_mut(
dbg!(libredox::call::mmap(libredox::call::MmapArgs {
fd,
offset: 0,
length: 128,
prot: libredox::flag::PROT_READ | libredox::flag::PROT_WRITE,
flags: libredox::flag::MAP_SHARED,
addr: core::ptr::null_mut(),
}))
.unwrap() as *mut u8,
128,
)
};
// Maps should be available after closing
assert_eq!(dbg!(libredox::call::close(fd)), Ok(()));
for i in 0..128 {
if write {
map[i as usize] = i;
}
assert_eq!(map[i as usize], i);
}
//TODO: add msync
unsafe {
assert_eq!(
dbg!(libredox::call::munmap(map.as_mut_ptr().cast(), map.len())),
Ok(())
);
}
};
mmap_inner(true);
mmap_inner(false);
})
}
// TODO: When increasing the total_count to 8000, the Allocator's deallocate() function surfaces as "slow" according to flamegraph. This
// appears to be the result of bulk deleting in this test, but I would bet that any filesystem that has lived for a long time would
// start to see degraded performance due to this.
#[test]
fn many_create_write_list_find_read_delete() {
let disk = DiskMemory::new(1024 * 1024 * 1024);
let ctime = time::SystemTime::now()
.duration_since(time::UNIX_EPOCH)
.unwrap();
let mut fs = FileSystem::create(disk, None, ctime.as_secs(), ctime.subsec_nanos()).unwrap();
let tree_ptr = TreePtr::<Node>::root();
let total_count = 3000;
// Create a bunch of files
for i in 0..total_count {
let result = fs.tx(|tx| {
tx.create_node(
tree_ptr,
&format!("file{i:05}"),
Node::MODE_FILE | 0o644,
1,
0,
)
});
if result.is_err() {
println!("Failure on create iteration {i}");
}
let file_node = result.unwrap();
let result = fs.tx(|tx| {
tx.write_node(
file_node.ptr(),
0,
format!("Hello World! #{i}").as_bytes(),
ctime.as_secs(),
ctime.subsec_nanos(),
)
});
if result.is_err() {
println!("Failure on write iteration {i}");
}
assert!(result.unwrap() > 0)
}
// Confirm that they can be listed
{
let mut children = Vec::<DirEntry>::with_capacity(total_count);
fs.tx(|tx| tx.child_nodes(tree_ptr, &mut children)).unwrap();
assert_eq!(
children.len(),
total_count,
"The list of children should match the number of files created."
);
let mut children: Vec<String> = children
.iter()
.map(|entry| entry.name().unwrap_or_default().to_string())
.collect();
children.sort();
for i in 0..total_count {
let expected = format!("file{i:05}");
let idx = children.binary_search(&expected);
assert!(idx.is_ok(), "Children did not contain '{}'", expected);
}
}
// Find and read the files
for i in 0..total_count {
let result = fs.tx(|tx| tx.find_node(tree_ptr, &format!("file{i:05}")));
if result.is_err() {
println!("Failure on find node iteration {i}");
}
let file_node = result.unwrap();
let offset = 0;
let mut buf = [0_u8; 32];
let result = fs.tx(|tx| {
tx.read_node(
file_node.ptr(),
offset,
&mut buf,
ctime.as_secs(),
ctime.subsec_nanos(),
)
});
if result.is_err() {
println!("Failure on read iteration {i}");
}
let size = result.unwrap();
let body = std::str::from_utf8(&buf[..size]).unwrap();
assert_eq!(body, format!("Hello World! #{i}"));
}
// Delete all the files
for i in 0..total_count {
let file_name = format!("file{i:05}");
if let Err(e) = fs.tx(|tx| tx.remove_node(tree_ptr, &file_name, Node::MODE_FILE)) {
println!("Failure on delete iteration {i}");
panic!("{e}");
}
let result = fs.tx(|tx| tx.find_node(tree_ptr, &file_name));
if result.is_ok() || result.unwrap_err().errno != syscall::error::ENOENT {
println!("Failure on delete verification iteration {i}");
panic!("Deletion appears to have failed");
}
}
}
#[test]
fn many_write_read_delete_mounted() {
with_mounted(|path| {
let total_count = 500;
for i in 0..total_count {
fs::write(
path.join(format!("file{}", i)),
format!("Hello, number {i}!"),
)
.unwrap();
}
// Confirm each of the created files can be found and read
for i in 0..total_count {
let contents = fs::read_to_string(path.join(format!("file{}", i))).unwrap();
assert_eq!(contents, format!("Hello, number {i}!"));
}
// Remove all the files
for i in 0..total_count {
let file_path = path.join(format!("file{i}"));
assert!(fs::exists(&file_path).unwrap());
fs::remove_file(&file_path).unwrap();
assert!(!fs::exists(&file_path).unwrap());
}
});
}
#[test]
fn rename_no_replace() {
let disk = DiskMemory::new(1024 * 1024 * 1024);
let mut fs = FileSystem::create(disk, None, 0, 0)
.expect("Creating in memory file system should succeed");
let root = TreePtr::root();
let dir = fs
.tx(|tx| tx.create_node(root, "dir", Node::MODE_DIR, 0, 0))
.expect("Creating a directory should succeed");
let source_file = fs
.tx(|tx| tx.create_node(root, "source", Node::MODE_FILE, 0, 0))
.expect("Creating source file to copy should succeed");
let no_clobber_file = fs
.tx(|tx| tx.create_node(root, "no_clobber", Node::MODE_FILE, 0, 0))
.expect("Creating second file to not clobber should succeed");
// Rename /source to /target
fs.tx(|tx| tx.rename_node_no_replace(root, "source", root, "target"))
.expect("Renaming existing 'source' to non-existing 'target' should succeed");
let target_file = fs
.tx(|tx| tx.find_node(root, "target"))
.expect("'target' should exist because we just renamed 'source' to 'target'");
assert_eq!(
source_file.id(),
target_file.id(),
"source and target are most definitely the same file"
);
// Don't rename /target to /no_clobber
let err = fs
.tx(|tx| tx.rename_node_no_replace(root, "target", root, "no_clobber"))
.expect_err("Renaming 'target' to existing 'no_clobber' should fail");
assert_eq!(
syscall::EEXIST,
err.errno,
"Renaming to existing file should fail with EEXIST"
);
assert_ne!(
no_clobber_file.id(),
target_file.id(),
"'target' and 'no_clobber' should be distinct files"
);
// Don't rename /target to /dir
let err = fs
.tx(|tx| tx.rename_node_no_replace(root, "target", root, "dir"))
.expect_err("Renaming 'target' to existing directory 'dir' should fail");
assert_eq!(
syscall::EEXIST,
err.errno,
"Renaming to existing file should fail with EEXIST"
);
assert_ne!(
dir.id(),
target_file.id(),
"'target' and 'dir' should be distinct nodes"
);
// Don't rename /dir to /target
let err = fs
.tx(|tx| tx.rename_node_no_replace(root, "dir", root, "target"))
.expect_err("Renaming 'dir' to existing file 'target' should fail");
assert_eq!(
syscall::EEXIST,
err.errno,
"Renaming to existing file should fail with EEXIST"
);
assert_ne!(
target_file.id(),
dir.id(),
"'dir' and 'target' should be distinct nodes"
);
// Don't rename /target to /target
let err = fs
.tx(|tx| tx.rename_node_no_replace(root, "target", root, "target"))
.expect_err("Renaming 'target' to itself should fail");
assert_eq!(
syscall::EEXIST,
err.errno,
"Renaming file to itself should fail with EEXIST"
);
// Rename /target to /dir/target
fs.tx(|tx| tx.rename_node_no_replace(root, "target", dir.ptr(), "target"))
.expect("Renaming /target to /dir/target should succeed");
let moved_target = fs
.tx(|tx| tx.find_node(dir.ptr(), "target"))
.expect("'target' should have moved to /dir/target");
assert_eq!(target_file.id(), moved_target.id());
// Rename /dir to /newdir
fs.tx(|tx| tx.rename_node_no_replace(root, "dir", root, "newdir"))
.expect("Renaming 'dir' to 'newdir' should succeed");
}
#[test]
fn rename_works() {
let disk = DiskMemory::new(1024 * 1024 * 1024);
let mut fs = FileSystem::create(disk, None, 0, 0)
.expect("Creating in memory file system should succeed");
let root = TreePtr::root();
let dir = fs
.tx(|tx| tx.create_node(root, "dir", Node::MODE_DIR, 0, 0))
.expect("Creating a directory should succeed");
let source_file = fs
.tx(|tx| tx.create_node(root, "source", Node::MODE_FILE, 0, 0))
.expect("Creating source file should succeed");
let target_file_orig = fs
.tx(|tx| tx.create_node(root, "target", Node::MODE_FILE, 0, 0))
.expect("Creating target file should succeed");
// Rename /source to /source2
fs.tx(|tx| tx.rename_node(root, "source", root, "source2"))
.expect("Renaming existing 'source' to non-existing 'source2' should succeed");
let source2_file = fs
.tx(|tx| tx.find_node(root, "source2"))
.expect("'source2' should exist because we just renamed 'source' to 'source2'");
assert_eq!(source_file.id(), source2_file.id());
let err = fs
.tx(|tx| tx.find_node(root, "source"))
.expect_err("'source' should not exist because it was moved");
assert_eq!(syscall::ENOENT, err.errno);
// Rename /source2 to /target
fs.tx(|tx| tx.rename_node(root, "source2", root, "target"))
.expect("Renaming existing 'source2' to existing 'target' should succeed");
let target_file_mv = fs
.tx(|tx| tx.find_node(root, "target"))
.expect("'target' should exist because the rename succeeded");
assert_ne!(
target_file_orig.id(),
target_file_mv.id(),
"Move failed because 'target' is still the same"
);
assert_eq!(
source2_file.id(),
target_file_mv.id(),
"Move failed because 'source2' != 'target'"
);
// Don't rename /target to /dir
// XXX: A similar test fails on Linux using rename(). Not sure if the discrepancy matters.
// let err = fs
// .tx(|tx| tx.rename_node(root, "target", root, "dir"))
// .expect_err("Renaming 'target' to existing directory 'dir' should fail");
// assert_eq!(
// syscall::EEXIST,
// err.errno,
// "Renaming to existing file should fail with EEXIST"
// );
// assert_ne!(
// dir.id(),
// target_file_mv.id(),
// "'target' and 'dir' should be distinct nodes"
// );
// Don't rename /dir to /target
// XXX: A similar test fails on Linux using rename().
// let err = fs
// .tx(|tx| tx.rename_node(root, "dir", root, "target"))
// .expect_err("Renaming 'dir' to existing file 'target' should fail");
// assert_eq!(
// syscall::EEXIST,
// err.errno,
// "Renaming to existing file should fail with EEXIST"
// );
// assert_ne!(
// target_file_mv.id(),
// dir.id(),
// "'dir' and 'target' should be distinct nodes"
// );
// Rename /target to /target
fs.tx(|tx| tx.rename_node(root, "target", root, "target"))
.expect("Renaming 'target' to itself should succeed");
let target_self_mv = fs
.tx(|tx| tx.find_node(root, "target"))
.expect("'target' should exist because rename succeeded");
assert_eq!(
target_file_mv.id(),
target_self_mv.id(),
"'target' shouldn't have changed during a move to self"
);
// Rename /target to /dir/target
fs.tx(|tx| tx.rename_node(root, "target", dir.ptr(), "target"))
.expect("Renaming /target to /dir/target should succeed");
let moved_target = fs
.tx(|tx| tx.find_node(dir.ptr(), "target"))
.expect("'target' should have moved to /dir/target");
assert_eq!(target_file_mv.id(), moved_target.id());
// Rename /dir to /newdir
fs.tx(|tx| tx.rename_node(root, "dir", root, "newdir"))
.expect("Renaming 'dir' to 'newdir' should succeed");
}
#[test]
fn temporary_file() {
with_mounted(|path| {
let file_path = path.join("temp");
let mut file = fs::File::create_new(&file_path).expect("failed to create temp file");
fs::remove_file(&file_path).expect("failed to unlink temp file");
let write_data = "Test\n";
file.write_all(write_data.as_bytes())
.expect("failed to write temp file");
let mut read_data = String::new();
file.seek(SeekFrom::Start(0))
.expect("failed to seek temp file");
file.read_to_string(&mut read_data)
.expect("failed to read temp file");
assert_eq!(read_data, write_data);
});
}