Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ab50acfcc7 |
+3
-1
@@ -1,2 +1,4 @@
|
|||||||
Cargo.lock
|
|
||||||
target
|
target
|
||||||
|
image.bin
|
||||||
|
image
|
||||||
|
image*
|
||||||
|
|||||||
+19
-6
@@ -2,16 +2,29 @@ image: "redoxos/redoxer"
|
|||||||
|
|
||||||
stages:
|
stages:
|
||||||
- build
|
- build
|
||||||
|
- test
|
||||||
|
|
||||||
workflow:
|
cache:
|
||||||
rules:
|
paths:
|
||||||
- if: '$CI_COMMIT_BRANCH == "master" && $CI_PROJECT_NAMESPACE == "redox-os"'
|
- target/
|
||||||
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"'
|
|
||||||
|
|
||||||
build:linux:
|
build:linux:
|
||||||
stage: build
|
stage: build
|
||||||
script: cargo +nightly build
|
script: cargo +nightly build --verbose
|
||||||
|
|
||||||
build:redox:
|
build:redox:
|
||||||
stage: build
|
stage: build
|
||||||
script: redoxer 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
@@ -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
File diff suppressed because it is too large
Load Diff
+87
-16
@@ -1,25 +1,96 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "redox_syscall"
|
name = "redoxfs"
|
||||||
version = "0.8.1"
|
description = "The Redox Filesystem"
|
||||||
description = "A Rust library to access raw Redox system calls"
|
repository = "https://gitlab.redox-os.org/redox-os/redoxfs"
|
||||||
license = "MIT"
|
version = "0.9.0"
|
||||||
|
license-file = "LICENSE"
|
||||||
|
readme = "README.md"
|
||||||
authors = ["Jeremy Soller <jackpot51@gmail.com>"]
|
authors = ["Jeremy Soller <jackpot51@gmail.com>"]
|
||||||
repository = "https://gitlab.redox-os.org/redox-os/syscall"
|
|
||||||
documentation = "https://docs.rs/redox_syscall"
|
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "syscall"
|
name = "redoxfs"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[features]
|
[[bin]]
|
||||||
default = ["userspace"]
|
name = "redoxfs"
|
||||||
rustc-dep-of-std = ["core", "bitflags/rustc-dep-of-std"]
|
path = "src/bin/mount.rs"
|
||||||
userspace = []
|
doc = false
|
||||||
std = []
|
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]
|
[dependencies]
|
||||||
bitflags = "2.4"
|
aes = { version = "0.8", default-features = false }
|
||||||
core = { version = "1.0.0", optional = true, package = "rustc-std-workspace-core" }
|
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 }
|
||||||
|
|
||||||
[target.'cfg(loom)'.dev-dependencies]
|
[features]
|
||||||
loom = "0.7"
|
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"
|
||||||
|
|||||||
@@ -1,22 +1,21 @@
|
|||||||
Copyright (c) 2017 Redox OS Developers
|
The MIT License (MIT)
|
||||||
|
|
||||||
MIT License
|
Copyright (c) 2016 Jeremy Soller
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
a copy of this software and associated documentation files (the
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
"Software"), to deal in the Software without restriction, including
|
in the Software without restriction, including without limitation the rights
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
furnished to do so, subject to the following conditions:
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
The above copyright notice and this permission notice shall be included in all
|
||||||
included in all copies or substantial portions of the Software.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
SOFTWARE.
|
||||||
|
|||||||
@@ -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:
|
||||||
@@ -1,7 +1,53 @@
|
|||||||
# syscall
|
# RedoxFS
|
||||||
|
|
||||||
This crate contains the system call numbers and Rust wrappers for the inline Assembly code of system calls.
|
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
|
||||||
|
|
||||||
[](./LICENSE)
|
[](./LICENSE)
|
||||||
[](https://crates.io/crates/redox_syscall)
|
[](https://crates.io/crates/redoxfs)
|
||||||
[](https://docs.rs/redox_syscall)
|
[](https://docs.rs/redoxfs)
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
target
|
||||||
|
corpus
|
||||||
|
artifacts
|
||||||
|
coverage
|
||||||
Generated
+858
@@ -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",
|
||||||
|
]
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
});
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
use core::{
|
|
||||||
mem,
|
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
slice,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::error::{Error, Result};
|
|
||||||
|
|
||||||
pub const PAGE_SIZE: usize = 4096;
|
|
||||||
/// Size of the metadata region used to transfer information from the kernel to the bootstrapper.
|
|
||||||
pub const KERNEL_METADATA_SIZE: usize = 4 * PAGE_SIZE;
|
|
||||||
|
|
||||||
#[cfg(feature = "userspace")]
|
|
||||||
macro_rules! syscall {
|
|
||||||
($($name:ident($a:ident, $($b:ident, $($c:ident, $($d:ident, $($e:ident, $($f:ident, $($g:ident, )?)?)?)?)?)?);)+) => {
|
|
||||||
$(
|
|
||||||
pub unsafe fn $name($a: usize, $($b: usize, $($c: usize, $($d: usize, $($e: usize, $($f: usize, $($g: usize)?)?)?)?)?)?) -> Result<usize> {
|
|
||||||
let ret: usize;
|
|
||||||
|
|
||||||
core::arch::asm!(
|
|
||||||
"svc 0",
|
|
||||||
in("x8") $a,
|
|
||||||
$(
|
|
||||||
in("x0") $b,
|
|
||||||
$(
|
|
||||||
in("x1") $c,
|
|
||||||
$(
|
|
||||||
in("x2") $d,
|
|
||||||
$(
|
|
||||||
in("x3") $e,
|
|
||||||
$(
|
|
||||||
in("x4") $f,
|
|
||||||
$(
|
|
||||||
in("x5") $g,
|
|
||||||
)?
|
|
||||||
)?
|
|
||||||
)?
|
|
||||||
)?
|
|
||||||
)?
|
|
||||||
)?
|
|
||||||
lateout("x0") ret,
|
|
||||||
options(nostack),
|
|
||||||
);
|
|
||||||
|
|
||||||
Error::demux(ret)
|
|
||||||
}
|
|
||||||
)+
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "userspace")]
|
|
||||||
syscall! {
|
|
||||||
syscall0(a,);
|
|
||||||
syscall1(a, b,);
|
|
||||||
syscall2(a, b, c,);
|
|
||||||
syscall3(a, b, c, d,);
|
|
||||||
syscall4(a, b, c, d, e,);
|
|
||||||
syscall5(a, b, c, d, e, f,);
|
|
||||||
syscall6(a, b, c, d, e, f, g,);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct IntRegisters {
|
|
||||||
pub x30: usize,
|
|
||||||
pub x29: usize,
|
|
||||||
pub x28: usize,
|
|
||||||
pub x27: usize,
|
|
||||||
pub x26: usize,
|
|
||||||
pub x25: usize,
|
|
||||||
pub x24: usize,
|
|
||||||
pub x23: usize,
|
|
||||||
pub x22: usize,
|
|
||||||
pub x21: usize,
|
|
||||||
pub x20: usize,
|
|
||||||
pub x19: usize,
|
|
||||||
pub x18: usize,
|
|
||||||
pub x17: usize,
|
|
||||||
pub x16: usize,
|
|
||||||
pub x15: usize,
|
|
||||||
pub x14: usize,
|
|
||||||
pub x13: usize,
|
|
||||||
pub x12: usize,
|
|
||||||
pub x11: usize,
|
|
||||||
pub x10: usize,
|
|
||||||
pub x9: usize,
|
|
||||||
pub x8: usize,
|
|
||||||
pub x7: usize,
|
|
||||||
pub x6: usize,
|
|
||||||
pub x5: usize,
|
|
||||||
pub x4: usize,
|
|
||||||
pub x3: usize,
|
|
||||||
pub x2: usize,
|
|
||||||
pub x1: usize,
|
|
||||||
pub x0: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for IntRegisters {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const IntRegisters as *const u8,
|
|
||||||
mem::size_of::<IntRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for IntRegisters {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut IntRegisters as *mut u8,
|
|
||||||
mem::size_of::<IntRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct FloatRegisters {
|
|
||||||
pub fp_simd_regs: [u128; 32],
|
|
||||||
pub fpsr: u32,
|
|
||||||
pub fpcr: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for FloatRegisters {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const FloatRegisters as *const u8,
|
|
||||||
mem::size_of::<FloatRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for FloatRegisters {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut FloatRegisters as *mut u8,
|
|
||||||
mem::size_of::<FloatRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct EnvRegisters {
|
|
||||||
pub tpidr_el0: usize,
|
|
||||||
pub tpidrro_el0: usize,
|
|
||||||
}
|
|
||||||
impl Deref for EnvRegisters {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const EnvRegisters as *const u8,
|
|
||||||
mem::size_of::<EnvRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for EnvRegisters {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut EnvRegisters as *mut u8,
|
|
||||||
mem::size_of::<EnvRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct Exception {
|
|
||||||
pub kind: usize,
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
impl Deref for Exception {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const Exception as *const u8,
|
|
||||||
mem::size_of::<Exception>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for Exception {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut Exception as *mut u8,
|
|
||||||
mem::size_of::<Exception>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
use super::error::{Error, Result};
|
|
||||||
use core::arch::asm;
|
|
||||||
use core::{
|
|
||||||
mem,
|
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
slice,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const PAGE_SIZE: usize = 4096;
|
|
||||||
/// Size of the metadata region used to transfer information from the kernel to the bootstrapper.
|
|
||||||
pub const KERNEL_METADATA_SIZE: usize = 4 * PAGE_SIZE;
|
|
||||||
|
|
||||||
#[cfg(feature = "userspace")]
|
|
||||||
macro_rules! syscall {
|
|
||||||
($($name:ident($a:ident, $($b:ident, $($c:ident, $($d:ident, $($e:ident, $($f:ident, $($g:ident, )?)?)?)?)?)?);)+) => {
|
|
||||||
$(
|
|
||||||
pub unsafe fn $name($a: usize, $($b: usize, $($c: usize, $($d: usize, $($e: usize, $($f: usize, $($g: usize)?)?)?)?)?)?) -> Result<usize> {
|
|
||||||
let ret: usize;
|
|
||||||
|
|
||||||
asm!(
|
|
||||||
"ecall",
|
|
||||||
in("a7") $a,
|
|
||||||
$(
|
|
||||||
in("a0") $b,
|
|
||||||
$(
|
|
||||||
in("a1") $c,
|
|
||||||
$(
|
|
||||||
in("a2") $d,
|
|
||||||
$(
|
|
||||||
in("a3") $e,
|
|
||||||
$(
|
|
||||||
in("a4") $f,
|
|
||||||
$(
|
|
||||||
in("a5") $g,
|
|
||||||
)?
|
|
||||||
)?
|
|
||||||
)?
|
|
||||||
)?
|
|
||||||
)?
|
|
||||||
)?
|
|
||||||
lateout("a0") ret,
|
|
||||||
options(nostack),
|
|
||||||
);
|
|
||||||
|
|
||||||
Error::demux(ret)
|
|
||||||
}
|
|
||||||
)+
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "userspace")]
|
|
||||||
syscall! {
|
|
||||||
syscall0(a,);
|
|
||||||
syscall1(a, b,);
|
|
||||||
syscall2(a, b, c,);
|
|
||||||
syscall3(a, b, c, d,);
|
|
||||||
syscall4(a, b, c, d, e,);
|
|
||||||
syscall5(a, b, c, d, e, f,);
|
|
||||||
syscall6(a, b, c, d, e, f, g,);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct IntRegisters {
|
|
||||||
pub pc: usize,
|
|
||||||
pub x31: usize,
|
|
||||||
pub x30: usize,
|
|
||||||
pub x29: usize,
|
|
||||||
pub x28: usize,
|
|
||||||
pub x27: usize,
|
|
||||||
pub x26: usize,
|
|
||||||
pub x25: usize,
|
|
||||||
pub x24: usize,
|
|
||||||
pub x23: usize,
|
|
||||||
pub x22: usize,
|
|
||||||
pub x21: usize,
|
|
||||||
pub x20: usize,
|
|
||||||
pub x19: usize,
|
|
||||||
pub x18: usize,
|
|
||||||
pub x17: usize,
|
|
||||||
pub x16: usize,
|
|
||||||
pub x15: usize,
|
|
||||||
pub x14: usize,
|
|
||||||
pub x13: usize,
|
|
||||||
pub x12: usize,
|
|
||||||
pub x11: usize,
|
|
||||||
pub x10: usize,
|
|
||||||
pub x9: usize,
|
|
||||||
pub x8: usize,
|
|
||||||
pub x7: usize,
|
|
||||||
pub x6: usize,
|
|
||||||
pub x5: usize,
|
|
||||||
// x4(tp) is in env
|
|
||||||
// x3(gp) is a platform scratch register
|
|
||||||
pub x2: usize,
|
|
||||||
pub x1: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for IntRegisters {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const IntRegisters as *const u8,
|
|
||||||
mem::size_of::<IntRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for IntRegisters {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut IntRegisters as *mut u8,
|
|
||||||
mem::size_of::<IntRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct FloatRegisters {
|
|
||||||
pub fregs: [u64; 32],
|
|
||||||
pub fcsr: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for FloatRegisters {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const FloatRegisters as *const u8,
|
|
||||||
mem::size_of::<FloatRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for FloatRegisters {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut FloatRegisters as *mut u8,
|
|
||||||
mem::size_of::<FloatRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
#[repr(packed)]
|
|
||||||
pub struct EnvRegisters {
|
|
||||||
pub tp: usize,
|
|
||||||
}
|
|
||||||
impl Deref for EnvRegisters {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const EnvRegisters as *const u8,
|
|
||||||
mem::size_of::<EnvRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for EnvRegisters {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut EnvRegisters as *mut u8,
|
|
||||||
mem::size_of::<EnvRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct Exception {
|
|
||||||
pub kind: usize,
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
impl Deref for Exception {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const Exception as *const u8,
|
|
||||||
mem::size_of::<Exception>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for Exception {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut Exception as *mut u8,
|
|
||||||
mem::size_of::<Exception>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-288
@@ -1,288 +0,0 @@
|
|||||||
use core::{
|
|
||||||
arch::asm,
|
|
||||||
mem,
|
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
slice,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::error::{Error, Result};
|
|
||||||
|
|
||||||
pub const PAGE_SIZE: usize = 4096;
|
|
||||||
/// Size of the metadata region used to transfer information from the kernel to the bootstrapper.
|
|
||||||
pub const KERNEL_METADATA_SIZE: usize = 4 * PAGE_SIZE;
|
|
||||||
|
|
||||||
#[cfg(feature = "userspace")]
|
|
||||||
macro_rules! syscall {
|
|
||||||
($($name:ident($a:ident, $($b:ident, $($c:ident, $($d:ident, $($e:ident, $($f:ident, )?)?)?)?)?);)+) => {
|
|
||||||
$(
|
|
||||||
pub unsafe fn $name(mut $a: usize, $($b: usize, $($c: usize, $($d: usize, $($e: usize, $($f: usize)?)?)?)?)?) -> Result<usize> {
|
|
||||||
asm!(
|
|
||||||
"int 0x80",
|
|
||||||
inout("eax") $a,
|
|
||||||
$(
|
|
||||||
in("ebx") $b,
|
|
||||||
$(
|
|
||||||
in("ecx") $c,
|
|
||||||
$(
|
|
||||||
in("edx") $d,
|
|
||||||
$(
|
|
||||||
in("esi") $e,
|
|
||||||
$(
|
|
||||||
in("edi") $f,
|
|
||||||
)?
|
|
||||||
)?
|
|
||||||
)?
|
|
||||||
)?
|
|
||||||
)?
|
|
||||||
options(nostack),
|
|
||||||
);
|
|
||||||
|
|
||||||
Error::demux($a)
|
|
||||||
}
|
|
||||||
)+
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "userspace")]
|
|
||||||
syscall! {
|
|
||||||
syscall0(a,);
|
|
||||||
syscall1(a, b,);
|
|
||||||
syscall2(a, b, c,);
|
|
||||||
syscall3(a, b, c, d,);
|
|
||||||
// Must be done custom because LLVM reserves ESI
|
|
||||||
//syscall4(a, b, c, d, e,);
|
|
||||||
//syscall5(a, b, c, d, e, f,);
|
|
||||||
//syscall6(a, b, c, d, e, f, g,);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "userspace")]
|
|
||||||
pub unsafe fn syscall4(mut a: usize, b: usize, c: usize, d: usize, e: usize) -> Result<usize> {
|
|
||||||
asm!(
|
|
||||||
"xchg esi, {e}
|
|
||||||
int 0x80
|
|
||||||
xchg esi, {e}",
|
|
||||||
e = in(reg) e,
|
|
||||||
inout("eax") a,
|
|
||||||
in("ebx") b,
|
|
||||||
in("ecx") c,
|
|
||||||
in("edx") d,
|
|
||||||
options(nostack),
|
|
||||||
);
|
|
||||||
|
|
||||||
Error::demux(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "userspace")]
|
|
||||||
pub unsafe fn syscall5(
|
|
||||||
mut a: usize,
|
|
||||||
b: usize,
|
|
||||||
c: usize,
|
|
||||||
d: usize,
|
|
||||||
e: usize,
|
|
||||||
f: usize,
|
|
||||||
) -> Result<usize> {
|
|
||||||
asm!(
|
|
||||||
"xchg esi, {e}
|
|
||||||
int 0x80
|
|
||||||
xchg esi, {e}",
|
|
||||||
e = in(reg) e,
|
|
||||||
inout("eax") a,
|
|
||||||
in("ebx") b,
|
|
||||||
in("ecx") c,
|
|
||||||
in("edx") d,
|
|
||||||
in("edi") f,
|
|
||||||
options(nostack),
|
|
||||||
);
|
|
||||||
|
|
||||||
Error::demux(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "userspace")]
|
|
||||||
pub unsafe fn syscall6(
|
|
||||||
mut a: usize,
|
|
||||||
b: usize,
|
|
||||||
c: usize,
|
|
||||||
d: usize,
|
|
||||||
e: usize,
|
|
||||||
f: usize,
|
|
||||||
g: usize,
|
|
||||||
) -> Result<usize> {
|
|
||||||
#[repr(C)]
|
|
||||||
struct PackedArgs {
|
|
||||||
arg4: usize,
|
|
||||||
arg6: usize,
|
|
||||||
nr: usize,
|
|
||||||
}
|
|
||||||
let args = PackedArgs {
|
|
||||||
arg4: e,
|
|
||||||
arg6: g,
|
|
||||||
nr: a,
|
|
||||||
};
|
|
||||||
let args_ptr = &args as *const PackedArgs;
|
|
||||||
asm!(
|
|
||||||
"push ebp",
|
|
||||||
"push esi",
|
|
||||||
"mov esi, [eax + 0]", // arg4 -> esi
|
|
||||||
"mov ebp, [eax + 4]", // arg6 -> ebp
|
|
||||||
"mov eax, [eax + 8]", // nr -> eax
|
|
||||||
"int 0x80",
|
|
||||||
"pop esi",
|
|
||||||
"pop ebp",
|
|
||||||
inout("eax") args_ptr => a,
|
|
||||||
in("ebx") b,
|
|
||||||
in("ecx") c,
|
|
||||||
in("edx") d,
|
|
||||||
in("edi") f,
|
|
||||||
options(nostack),
|
|
||||||
);
|
|
||||||
|
|
||||||
Error::demux(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct IntRegisters {
|
|
||||||
// TODO: Some of these don't get set by Redox yet. Should they?
|
|
||||||
pub ebp: usize,
|
|
||||||
pub esi: usize,
|
|
||||||
pub edi: usize,
|
|
||||||
pub ebx: usize,
|
|
||||||
pub eax: usize,
|
|
||||||
pub ecx: usize,
|
|
||||||
pub edx: usize,
|
|
||||||
// pub orig_rax: usize,
|
|
||||||
pub eip: usize,
|
|
||||||
pub cs: usize,
|
|
||||||
pub eflags: usize,
|
|
||||||
pub esp: usize,
|
|
||||||
pub ss: usize,
|
|
||||||
// pub fs_base: usize,
|
|
||||||
// pub gs_base: usize,
|
|
||||||
// pub ds: usize,
|
|
||||||
// pub es: usize,
|
|
||||||
pub fs: usize,
|
|
||||||
// pub gs: usize
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for IntRegisters {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const IntRegisters as *const u8,
|
|
||||||
mem::size_of::<IntRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for IntRegisters {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut IntRegisters as *mut u8,
|
|
||||||
mem::size_of::<IntRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct FloatRegisters {
|
|
||||||
pub fcw: u16,
|
|
||||||
pub fsw: u16,
|
|
||||||
pub ftw: u8,
|
|
||||||
pub _reserved: u8,
|
|
||||||
pub fop: u16,
|
|
||||||
pub fip: u64,
|
|
||||||
pub fdp: u64,
|
|
||||||
pub mxcsr: u32,
|
|
||||||
pub mxcsr_mask: u32,
|
|
||||||
pub st_space: [u128; 8],
|
|
||||||
pub xmm_space: [u128; 16],
|
|
||||||
// TODO: YMM/ZMM
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for FloatRegisters {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const FloatRegisters as *const u8,
|
|
||||||
mem::size_of::<FloatRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for FloatRegisters {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut FloatRegisters as *mut u8,
|
|
||||||
mem::size_of::<FloatRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct EnvRegisters {
|
|
||||||
pub fsbase: u32,
|
|
||||||
pub gsbase: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for EnvRegisters {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const EnvRegisters as *const u8,
|
|
||||||
mem::size_of::<EnvRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for EnvRegisters {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut EnvRegisters as *mut u8,
|
|
||||||
mem::size_of::<EnvRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct Exception {
|
|
||||||
pub kind: usize,
|
|
||||||
pub code: usize,
|
|
||||||
pub address: usize,
|
|
||||||
}
|
|
||||||
impl Deref for Exception {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const Exception as *const u8,
|
|
||||||
mem::size_of::<Exception>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for Exception {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut Exception as *mut u8,
|
|
||||||
mem::size_of::<Exception>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
use core::{
|
|
||||||
mem,
|
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
slice,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const PAGE_SIZE: usize = 4096;
|
|
||||||
/// Size of the metadata region used to transfer information from the kernel to the bootstrapper.
|
|
||||||
pub const KERNEL_METADATA_SIZE: usize = 4 * PAGE_SIZE;
|
|
||||||
|
|
||||||
#[cfg(feature = "userspace")]
|
|
||||||
macro_rules! syscall {
|
|
||||||
($($name:ident($a:ident, $($b:ident, $($c:ident, $($d:ident, $($e:ident, $($f:ident, $($g:ident, )?)?)?)?)?)?);)+) => {
|
|
||||||
$(
|
|
||||||
pub unsafe fn $name(mut $a: usize, $($b: usize, $($c: usize, $($d: usize, $($e: usize, $($f: usize, $($g: usize)?)?)?)?)?)?) -> crate::error::Result<usize> {
|
|
||||||
core::arch::asm!(
|
|
||||||
"syscall",
|
|
||||||
inout("rax") $a,
|
|
||||||
$(
|
|
||||||
in("rdi") $b,
|
|
||||||
$(
|
|
||||||
in("rsi") $c,
|
|
||||||
$(
|
|
||||||
in("rdx") $d,
|
|
||||||
$(
|
|
||||||
in("r10") $e,
|
|
||||||
$(
|
|
||||||
in("r8") $f,
|
|
||||||
$(
|
|
||||||
in("r9") $g,
|
|
||||||
)?
|
|
||||||
)?
|
|
||||||
)?
|
|
||||||
)?
|
|
||||||
)?
|
|
||||||
)?
|
|
||||||
out("rcx") _,
|
|
||||||
out("r11") _,
|
|
||||||
options(nostack),
|
|
||||||
);
|
|
||||||
|
|
||||||
crate::error::Error::demux($a)
|
|
||||||
}
|
|
||||||
)+
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "userspace")]
|
|
||||||
syscall! {
|
|
||||||
syscall0(a,);
|
|
||||||
syscall1(a, b,);
|
|
||||||
syscall2(a, b, c,);
|
|
||||||
syscall3(a, b, c, d,);
|
|
||||||
syscall4(a, b, c, d, e,);
|
|
||||||
syscall5(a, b, c, d, e, f,);
|
|
||||||
syscall6(a, b, c, d, e, f, g,);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct IntRegisters {
|
|
||||||
pub r15: usize,
|
|
||||||
pub r14: usize,
|
|
||||||
pub r13: usize,
|
|
||||||
pub r12: usize,
|
|
||||||
pub rbp: usize,
|
|
||||||
pub rbx: usize,
|
|
||||||
pub r11: usize,
|
|
||||||
pub r10: usize,
|
|
||||||
pub r9: usize,
|
|
||||||
pub r8: usize,
|
|
||||||
pub rax: usize,
|
|
||||||
pub rcx: usize,
|
|
||||||
pub rdx: usize,
|
|
||||||
pub rsi: usize,
|
|
||||||
pub rdi: usize,
|
|
||||||
pub rip: usize,
|
|
||||||
pub cs: usize,
|
|
||||||
pub rflags: usize,
|
|
||||||
pub rsp: usize,
|
|
||||||
pub ss: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for IntRegisters {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe { slice::from_raw_parts(self as *const Self as *const u8, mem::size_of::<Self>()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for IntRegisters {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe { slice::from_raw_parts_mut(self as *mut Self as *mut u8, mem::size_of::<Self>()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct FloatRegisters {
|
|
||||||
pub fcw: u16,
|
|
||||||
pub fsw: u16,
|
|
||||||
pub ftw: u8,
|
|
||||||
pub _reserved: u8,
|
|
||||||
pub fop: u16,
|
|
||||||
pub fip: u64,
|
|
||||||
pub fdp: u64,
|
|
||||||
pub mxcsr: u32,
|
|
||||||
pub mxcsr_mask: u32,
|
|
||||||
pub st_space: [u128; 8],
|
|
||||||
pub xmm_space: [u128; 16],
|
|
||||||
// TODO: YMM/ZMM
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for FloatRegisters {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const FloatRegisters as *const u8,
|
|
||||||
mem::size_of::<FloatRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for FloatRegisters {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut FloatRegisters as *mut u8,
|
|
||||||
mem::size_of::<FloatRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct EnvRegisters {
|
|
||||||
pub fsbase: u64,
|
|
||||||
pub gsbase: u64,
|
|
||||||
// TODO: PKRU?
|
|
||||||
}
|
|
||||||
impl Deref for EnvRegisters {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const EnvRegisters as *const u8,
|
|
||||||
mem::size_of::<EnvRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for EnvRegisters {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut EnvRegisters as *mut u8,
|
|
||||||
mem::size_of::<EnvRegisters>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct Exception {
|
|
||||||
pub kind: usize,
|
|
||||||
pub code: usize,
|
|
||||||
pub address: usize,
|
|
||||||
}
|
|
||||||
impl Deref for Exception {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const Exception as *const u8,
|
|
||||||
mem::size_of::<Exception>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for Exception {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut Exception as *mut u8,
|
|
||||||
mem::size_of::<Exception>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+146
@@ -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
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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());
|
||||||
|
}
|
||||||
-410
@@ -1,410 +0,0 @@
|
|||||||
use super::{
|
|
||||||
arch::*,
|
|
||||||
data::{Map, Stat, StatVfs, StdFsCallMeta, TimeSpec},
|
|
||||||
error::Result,
|
|
||||||
flag::*,
|
|
||||||
number::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
use core::mem;
|
|
||||||
|
|
||||||
/// Close a file
|
|
||||||
pub fn close(fd: usize) -> Result<usize> {
|
|
||||||
unsafe { syscall1(SYS_CLOSE, fd) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the current system time
|
|
||||||
pub fn clock_gettime(clock: usize, tp: &mut TimeSpec) -> Result<usize> {
|
|
||||||
unsafe { syscall2(SYS_CLOCK_GETTIME, clock, tp as *mut TimeSpec as usize) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copy and transform a file descriptor
|
|
||||||
pub fn dup(fd: usize, buf: &[u8]) -> Result<usize> {
|
|
||||||
unsafe { syscall3(SYS_DUP, fd, buf.as_ptr() as usize, buf.len()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Copy and transform a file descriptor
|
|
||||||
pub fn dup2(fd: usize, newfd: usize, buf: &[u8]) -> Result<usize> {
|
|
||||||
unsafe { syscall4(SYS_DUP2, fd, newfd, buf.as_ptr() as usize, buf.len()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change file permissions
|
|
||||||
pub fn fchmod(fd: usize, mode: u16) -> Result<usize> {
|
|
||||||
unsafe { syscall2(SYS_FCHMOD, fd, mode as usize) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change file ownership
|
|
||||||
pub fn fchown(fd: usize, uid: u32, gid: u32) -> Result<usize> {
|
|
||||||
unsafe { syscall3(SYS_FCHOWN, fd, uid as usize, gid as usize) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change file descriptor flags
|
|
||||||
pub fn fcntl(fd: usize, cmd: usize, arg: usize) -> Result<usize> {
|
|
||||||
unsafe { syscall3(SYS_FCNTL, fd, cmd, arg) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Map a file into memory, but with the ability to set the address to map into, either as a hint
|
|
||||||
/// or as a requirement of the map.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
/// `EACCES` - the file descriptor was not open for reading
|
|
||||||
/// `EBADF` - if the file descriptor was invalid
|
|
||||||
/// `ENODEV` - mmapping was not supported
|
|
||||||
/// `EINVAL` - invalid combination of flags
|
|
||||||
/// `EEXIST` - if [`MapFlags::MAP_FIXED`] was set, and the address specified was already in use.
|
|
||||||
///
|
|
||||||
pub unsafe fn fmap(fd: usize, map: &Map) -> Result<usize> {
|
|
||||||
syscall3(
|
|
||||||
SYS_FMAP,
|
|
||||||
fd,
|
|
||||||
map as *const Map as usize,
|
|
||||||
mem::size_of::<Map>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unmap whole (or partial) continous memory-mapped files
|
|
||||||
pub unsafe fn funmap(addr: usize, len: usize) -> Result<usize> {
|
|
||||||
syscall2(SYS_FUNMAP, addr, len)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve the canonical path of a file
|
|
||||||
pub fn fpath(fd: usize, buf: &mut [u8]) -> Result<usize> {
|
|
||||||
unsafe { syscall3(SYS_FPATH, fd, buf.as_mut_ptr() as usize, buf.len()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a link to a file
|
|
||||||
pub fn flink<T: AsRef<str>>(fd: usize, path: T) -> Result<usize> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
unsafe { syscall3(SYS_FLINK, fd, path.as_ptr() as usize, path.len()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rename a file
|
|
||||||
pub fn frename<T: AsRef<str>>(fd: usize, path: T) -> Result<usize> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
unsafe { syscall3(SYS_FRENAME, fd, path.as_ptr() as usize, path.len()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get metadata about a file
|
|
||||||
pub fn fstat(fd: usize, stat: &mut Stat) -> Result<usize> {
|
|
||||||
unsafe {
|
|
||||||
syscall3(
|
|
||||||
SYS_FSTAT,
|
|
||||||
fd,
|
|
||||||
stat as *mut Stat as usize,
|
|
||||||
mem::size_of::<Stat>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get metadata about a filesystem
|
|
||||||
pub fn fstatvfs(fd: usize, stat: &mut StatVfs) -> Result<usize> {
|
|
||||||
unsafe {
|
|
||||||
syscall3(
|
|
||||||
SYS_FSTATVFS,
|
|
||||||
fd,
|
|
||||||
stat as *mut StatVfs as usize,
|
|
||||||
mem::size_of::<StatVfs>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sync a file descriptor to its underlying medium
|
|
||||||
pub fn fsync(fd: usize) -> Result<usize> {
|
|
||||||
unsafe { syscall1(SYS_FSYNC, fd) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Truncate or extend a file to a specified length
|
|
||||||
pub fn ftruncate(fd: usize, len: usize) -> Result<usize> {
|
|
||||||
unsafe { syscall2(SYS_FTRUNCATE, fd, len) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change modify and/or access times
|
|
||||||
pub fn futimens(fd: usize, times: &[TimeSpec]) -> Result<usize> {
|
|
||||||
unsafe {
|
|
||||||
syscall3(
|
|
||||||
SYS_FUTIMENS,
|
|
||||||
fd,
|
|
||||||
times.as_ptr() as usize,
|
|
||||||
mem::size_of_val(times),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fast userspace mutex
|
|
||||||
pub unsafe fn futex(
|
|
||||||
addr: *mut i32,
|
|
||||||
op: usize,
|
|
||||||
val: i32,
|
|
||||||
val2: usize,
|
|
||||||
addr2: *mut i32,
|
|
||||||
) -> Result<usize> {
|
|
||||||
syscall5(
|
|
||||||
SYS_FUTEX,
|
|
||||||
addr as usize,
|
|
||||||
op,
|
|
||||||
(val as isize) as usize,
|
|
||||||
val2,
|
|
||||||
addr2 as usize,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Seek to `offset` bytes in a file descriptor
|
|
||||||
pub fn lseek(fd: usize, offset: isize, whence: usize) -> Result<usize> {
|
|
||||||
unsafe { syscall3(SYS_LSEEK, fd, offset as usize, whence) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make a new scheme namespace
|
|
||||||
pub fn mkns(schemes: &[[usize; 2]]) -> Result<usize> {
|
|
||||||
unsafe { syscall2(SYS_MKNS, schemes.as_ptr() as usize, schemes.len()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change mapping flags
|
|
||||||
pub unsafe fn mprotect(addr: usize, size: usize, flags: MapFlags) -> Result<usize> {
|
|
||||||
syscall3(SYS_MPROTECT, addr, size, flags.bits())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sleep for the time specified in `req`
|
|
||||||
pub fn nanosleep(req: &TimeSpec, rem: &mut TimeSpec) -> Result<usize> {
|
|
||||||
unsafe {
|
|
||||||
syscall2(
|
|
||||||
SYS_NANOSLEEP,
|
|
||||||
req as *const TimeSpec as usize,
|
|
||||||
rem as *mut TimeSpec as usize,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Open a file at a specific path
|
|
||||||
pub fn openat<T: AsRef<str>>(
|
|
||||||
fd: usize,
|
|
||||||
path: T,
|
|
||||||
flags: usize,
|
|
||||||
fcntl_flags: usize,
|
|
||||||
) -> Result<usize> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
unsafe {
|
|
||||||
syscall5(
|
|
||||||
SYS_OPENAT,
|
|
||||||
fd,
|
|
||||||
path.as_ptr() as usize,
|
|
||||||
path.len(),
|
|
||||||
flags,
|
|
||||||
fcntl_flags,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Open a file at a specific path with filter
|
|
||||||
pub fn openat_with_filter<T: AsRef<str>>(
|
|
||||||
fd: usize,
|
|
||||||
path: T,
|
|
||||||
flags: usize,
|
|
||||||
fcntl_flags: usize,
|
|
||||||
euid: u32,
|
|
||||||
egid: u32,
|
|
||||||
) -> Result<usize> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
unsafe {
|
|
||||||
syscall6(
|
|
||||||
SYS_OPENAT_WITH_FILTER,
|
|
||||||
fd,
|
|
||||||
path.as_ptr() as usize,
|
|
||||||
path.len(),
|
|
||||||
flags | fcntl_flags,
|
|
||||||
// NOTE: Short-term solution to allow namespace management.
|
|
||||||
// In the long term, we need to figure out how we should best handle
|
|
||||||
// Unix permissions using capabilities.
|
|
||||||
euid as usize,
|
|
||||||
egid as usize,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove a file at at specific path
|
|
||||||
pub fn unlinkat<T: AsRef<str>>(fd: usize, path: T, flags: usize) -> Result<usize> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
unsafe { syscall4(SYS_UNLINKAT, fd, path.as_ptr() as usize, path.len(), flags) }
|
|
||||||
}
|
|
||||||
/// Remove a file at at specific path with filter
|
|
||||||
pub fn unlinkat_with_filter<T: AsRef<str>>(
|
|
||||||
fd: usize,
|
|
||||||
path: T,
|
|
||||||
flags: usize,
|
|
||||||
euid: u32,
|
|
||||||
egid: u32,
|
|
||||||
) -> Result<usize> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
unsafe {
|
|
||||||
syscall6(
|
|
||||||
SYS_UNLINKAT_WITH_FILTER,
|
|
||||||
fd,
|
|
||||||
path.as_ptr() as usize,
|
|
||||||
path.len(),
|
|
||||||
flags,
|
|
||||||
// NOTE: Short-term solution to allow namespace management.
|
|
||||||
// In the long term, we need to figure out how we should best handle
|
|
||||||
// Unix permissions using capabilities.
|
|
||||||
euid as usize,
|
|
||||||
egid as usize,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read from a file descriptor into a buffer
|
|
||||||
pub fn read(fd: usize, buf: &mut [u8]) -> Result<usize> {
|
|
||||||
unsafe { syscall3(SYS_READ, fd, buf.as_mut_ptr() as usize, buf.len()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write a buffer to a file descriptor
|
|
||||||
///
|
|
||||||
/// The kernel will attempt to write the bytes in `buf` to the file descriptor `fd`, returning
|
|
||||||
/// either an `Err`, explained below, or `Ok(count)` where `count` is the number of bytes which
|
|
||||||
/// were written.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// * `EAGAIN` - the file descriptor was opened with `O_NONBLOCK` and writing would block
|
|
||||||
/// * `EBADF` - the file descriptor is not valid or is not open for writing
|
|
||||||
/// * `EFAULT` - `buf` does not point to the process's addressible memory
|
|
||||||
/// * `EIO` - an I/O error occurred
|
|
||||||
/// * `ENOSPC` - the device containing the file descriptor has no room for data
|
|
||||||
/// * `EPIPE` - the file descriptor refers to a pipe or socket whose reading end is closed
|
|
||||||
pub fn write(fd: usize, buf: &[u8]) -> Result<usize> {
|
|
||||||
unsafe { syscall3(SYS_WRITE, fd, buf.as_ptr() as usize, buf.len()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Yield the process's time slice to the kernel
|
|
||||||
///
|
|
||||||
/// This function will return Ok(0) on success
|
|
||||||
pub fn sched_yield() -> Result<usize> {
|
|
||||||
unsafe { syscall0(SYS_YIELD) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send a file descriptor `fd`, handled by the scheme providing `receiver_socket`. `flags` is
|
|
||||||
/// currently unused (must be zero), and `arg` is included in the scheme call.
|
|
||||||
///
|
|
||||||
/// The scheme can return an arbitrary value.
|
|
||||||
pub fn sendfd(receiver_socket: usize, fd: usize, flags: usize, arg: u64) -> Result<usize> {
|
|
||||||
#[cfg(target_pointer_width = "32")]
|
|
||||||
unsafe {
|
|
||||||
syscall5(
|
|
||||||
SYS_SENDFD,
|
|
||||||
receiver_socket,
|
|
||||||
fd,
|
|
||||||
flags,
|
|
||||||
arg as u32 as usize,
|
|
||||||
(arg >> 32) as u32 as usize,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "64")]
|
|
||||||
unsafe {
|
|
||||||
syscall4(SYS_SENDFD, receiver_socket, fd, flags, arg as usize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Call {
|
|
||||||
unsafe fn raw_call(
|
|
||||||
&self,
|
|
||||||
payload_ptr: *const u8,
|
|
||||||
len: usize,
|
|
||||||
flags: CallFlags,
|
|
||||||
metadata: &[u64],
|
|
||||||
) -> Result<usize>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Call for usize {
|
|
||||||
unsafe fn raw_call(
|
|
||||||
&self,
|
|
||||||
payload_ptr: *const u8,
|
|
||||||
len: usize,
|
|
||||||
flags: CallFlags,
|
|
||||||
metadata: &[u64],
|
|
||||||
) -> Result<usize> {
|
|
||||||
unsafe {
|
|
||||||
syscall5(
|
|
||||||
SYS_CALL,
|
|
||||||
*self,
|
|
||||||
payload_ptr as usize,
|
|
||||||
len,
|
|
||||||
metadata.len() | flags.bits(),
|
|
||||||
metadata.as_ptr() as usize,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Call for &[usize] {
|
|
||||||
unsafe fn raw_call(
|
|
||||||
&self,
|
|
||||||
payload_ptr: *const u8,
|
|
||||||
len: usize,
|
|
||||||
flags: CallFlags,
|
|
||||||
metadata: &[u64],
|
|
||||||
) -> Result<usize> {
|
|
||||||
let combined_flags = flags | CallFlags::MULTIPLE_FDS;
|
|
||||||
unsafe {
|
|
||||||
syscall6(
|
|
||||||
SYS_CALL,
|
|
||||||
self.as_ptr() as usize,
|
|
||||||
payload_ptr as usize,
|
|
||||||
len,
|
|
||||||
metadata.len() | combined_flags.bits(),
|
|
||||||
metadata.as_ptr() as usize,
|
|
||||||
self.len() * mem::size_of::<usize>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// SYS_CALL interface, read-only variant
|
|
||||||
pub fn call_ro<T: Call>(
|
|
||||||
fd: T,
|
|
||||||
payload: &mut [u8],
|
|
||||||
flags: CallFlags,
|
|
||||||
metadata: &[u64],
|
|
||||||
) -> Result<usize> {
|
|
||||||
unsafe {
|
|
||||||
fd.raw_call(
|
|
||||||
payload.as_mut_ptr(),
|
|
||||||
payload.len(),
|
|
||||||
flags | CallFlags::READ,
|
|
||||||
metadata,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// SYS_CALL interface, write-only variant
|
|
||||||
pub fn call_wo<T: Call>(
|
|
||||||
fd: T,
|
|
||||||
payload: &[u8],
|
|
||||||
flags: CallFlags,
|
|
||||||
metadata: &[u64],
|
|
||||||
) -> Result<usize> {
|
|
||||||
unsafe {
|
|
||||||
fd.raw_call(
|
|
||||||
payload.as_ptr(),
|
|
||||||
payload.len(),
|
|
||||||
flags | CallFlags::WRITE,
|
|
||||||
metadata,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// SYS_CALL interface, read-write variant
|
|
||||||
pub fn call_rw<T: Call>(
|
|
||||||
fd: T,
|
|
||||||
payload: &mut [u8],
|
|
||||||
flags: CallFlags,
|
|
||||||
metadata: &[u64],
|
|
||||||
) -> Result<usize> {
|
|
||||||
unsafe {
|
|
||||||
fd.raw_call(
|
|
||||||
payload.as_mut_ptr(),
|
|
||||||
payload.len(),
|
|
||||||
flags | CallFlags::READ | CallFlags::WRITE,
|
|
||||||
metadata,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn std_fs_call<T: Call>(fd: T, payload: &mut [u8], metadata: &StdFsCallMeta) -> Result<usize> {
|
|
||||||
call_rw(fd, payload, CallFlags::STD_FS, metadata)
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
-501
@@ -1,501 +0,0 @@
|
|||||||
use core::{
|
|
||||||
mem,
|
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
slice,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::flag::{EventFlags, MapFlags, PtraceFlags, StdFsCallKind};
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct Event {
|
|
||||||
pub id: usize,
|
|
||||||
pub flags: EventFlags,
|
|
||||||
pub data: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Event {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe { slice::from_raw_parts(self as *const Event as *const u8, mem::size_of::<Event>()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for Event {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe { slice::from_raw_parts_mut(self as *mut Event as *mut u8, mem::size_of::<Event>()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct ITimerSpec {
|
|
||||||
pub it_interval: TimeSpec,
|
|
||||||
pub it_value: TimeSpec,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for ITimerSpec {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const ITimerSpec as *const u8,
|
|
||||||
mem::size_of::<ITimerSpec>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for ITimerSpec {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut ITimerSpec as *mut u8,
|
|
||||||
mem::size_of::<ITimerSpec>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct OldMap {
|
|
||||||
pub offset: usize,
|
|
||||||
pub size: usize,
|
|
||||||
pub flags: MapFlags,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for OldMap {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(self as *const OldMap as *const u8, mem::size_of::<OldMap>())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for OldMap {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(self as *mut OldMap as *mut u8, mem::size_of::<OldMap>())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct Map {
|
|
||||||
/// The offset inside the file that is being mapped.
|
|
||||||
pub offset: usize,
|
|
||||||
|
|
||||||
/// The size of the memory map.
|
|
||||||
pub size: usize,
|
|
||||||
|
|
||||||
/// Contains both prot and map flags.
|
|
||||||
pub flags: MapFlags,
|
|
||||||
|
|
||||||
/// Functions as a hint to where in the virtual address space of the running process, to place
|
|
||||||
/// the memory map. If [`MapFlags::MAP_FIXED`] is set, then this address must be the address to
|
|
||||||
/// map to.
|
|
||||||
pub address: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Map {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe { slice::from_raw_parts(self as *const Map as *const u8, mem::size_of::<Map>()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for Map {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe { slice::from_raw_parts_mut(self as *mut Map as *mut u8, mem::size_of::<Map>()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct Stat {
|
|
||||||
pub st_dev: u64,
|
|
||||||
pub st_ino: u64,
|
|
||||||
pub st_mode: u16,
|
|
||||||
pub st_nlink: u32,
|
|
||||||
pub st_uid: u32,
|
|
||||||
pub st_gid: u32,
|
|
||||||
pub st_size: u64,
|
|
||||||
pub st_blksize: u32,
|
|
||||||
pub st_blocks: u64,
|
|
||||||
pub st_mtime: u64,
|
|
||||||
pub st_mtime_nsec: u32,
|
|
||||||
pub st_atime: u64,
|
|
||||||
pub st_atime_nsec: u32,
|
|
||||||
pub st_ctime: u64,
|
|
||||||
pub st_ctime_nsec: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Stat {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe { slice::from_raw_parts(self as *const Stat as *const u8, mem::size_of::<Stat>()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for Stat {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe { slice::from_raw_parts_mut(self as *mut Stat as *mut u8, mem::size_of::<Stat>()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct StatVfs {
|
|
||||||
pub f_bsize: u32,
|
|
||||||
pub f_blocks: u64,
|
|
||||||
pub f_bfree: u64,
|
|
||||||
pub f_bavail: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for StatVfs {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const StatVfs as *const u8,
|
|
||||||
mem::size_of::<StatVfs>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for StatVfs {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(self as *mut StatVfs as *mut u8, mem::size_of::<StatVfs>())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct StdFsCallMeta {
|
|
||||||
pub kind: u8, // enum StdFsCallKind
|
|
||||||
_rsvd: [u8; 7],
|
|
||||||
pub arg1: u64,
|
|
||||||
pub arg2: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdFsCallMeta {
|
|
||||||
pub fn new(kind: StdFsCallKind, arg1: u64, arg2: u64) -> Self {
|
|
||||||
Self {
|
|
||||||
kind: kind as u8,
|
|
||||||
_rsvd: [0; 7],
|
|
||||||
arg1,
|
|
||||||
arg2,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for StdFsCallMeta {
|
|
||||||
type Target = [u64];
|
|
||||||
fn deref(&self) -> &[u64] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const StdFsCallMeta as *const u64,
|
|
||||||
mem::size_of::<StdFsCallMeta>() / mem::size_of::<u64>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for StdFsCallMeta {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u64] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut StdFsCallMeta as *mut u64,
|
|
||||||
mem::size_of::<StdFsCallMeta>() / mem::size_of::<u64>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct TimeSpec {
|
|
||||||
pub tv_sec: i64,
|
|
||||||
pub tv_nsec: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
const NANOS_PER_SEC: u128 = 1_000_000_000;
|
|
||||||
|
|
||||||
impl TimeSpec {
|
|
||||||
pub fn from_nanos(nanos: u128) -> Self {
|
|
||||||
Self {
|
|
||||||
tv_sec: i64::try_from(nanos / NANOS_PER_SEC).unwrap_or(i64::MAX),
|
|
||||||
tv_nsec: (nanos % NANOS_PER_SEC) as i32, // guaranteed to never overflow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn to_nanos(&self) -> u128 {
|
|
||||||
self.tv_sec as u128 * NANOS_PER_SEC + self.tv_nsec as u128
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for TimeSpec {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const TimeSpec as *const u8,
|
|
||||||
mem::size_of::<TimeSpec>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for TimeSpec {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(self as *mut TimeSpec as *mut u8, mem::size_of::<TimeSpec>())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct PtraceEvent {
|
|
||||||
pub cause: PtraceFlags,
|
|
||||||
pub a: usize,
|
|
||||||
pub b: usize,
|
|
||||||
pub c: usize,
|
|
||||||
pub d: usize,
|
|
||||||
pub e: usize,
|
|
||||||
pub f: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for PtraceEvent {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const PtraceEvent as *const u8,
|
|
||||||
mem::size_of::<PtraceEvent>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for PtraceEvent {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut PtraceEvent as *mut u8,
|
|
||||||
mem::size_of::<PtraceEvent>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! ptrace_event {
|
|
||||||
($cause:expr $(, $a:expr $(, $b:expr $(, $c:expr)?)?)?) => {
|
|
||||||
$crate::data::PtraceEvent {
|
|
||||||
cause: $cause,
|
|
||||||
$(a: $a,
|
|
||||||
$(b: $b,
|
|
||||||
$(c: $c,)?
|
|
||||||
)?
|
|
||||||
)?
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitflags::bitflags! {
|
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy, Default)]
|
|
||||||
pub struct GrantFlags: usize {
|
|
||||||
const GRANT_READ = 0x0000_0001;
|
|
||||||
const GRANT_WRITE = 0x0000_0002;
|
|
||||||
const GRANT_EXEC = 0x0000_0004;
|
|
||||||
|
|
||||||
const GRANT_SHARED = 0x0000_0008;
|
|
||||||
const GRANT_LAZY = 0x0000_0010;
|
|
||||||
const GRANT_SCHEME = 0x0000_0020;
|
|
||||||
const GRANT_PHYS = 0x0000_0040;
|
|
||||||
const GRANT_PINNED = 0x0000_0080;
|
|
||||||
const GRANT_PHYS_CONTIGUOUS = 0x0000_0100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GrantFlags {
|
|
||||||
#[deprecated = "use the safe `from_bits_retain` method instead"]
|
|
||||||
pub unsafe fn from_bits_unchecked(bits: usize) -> Self {
|
|
||||||
Self::from_bits_retain(bits)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct GrantDesc {
|
|
||||||
pub base: usize,
|
|
||||||
pub size: usize,
|
|
||||||
pub flags: GrantFlags,
|
|
||||||
pub offset: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for GrantDesc {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
self as *const GrantDesc as *const u8,
|
|
||||||
mem::size_of::<GrantDesc>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for GrantDesc {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut GrantDesc as *mut u8,
|
|
||||||
mem::size_of::<GrantDesc>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct SetSighandlerData {
|
|
||||||
pub user_handler: usize,
|
|
||||||
pub excp_handler: usize,
|
|
||||||
pub thread_control_addr: usize,
|
|
||||||
pub proc_control_addr: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for SetSighandlerData {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe { slice::from_raw_parts(self as *const Self as *const u8, mem::size_of::<Self>()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for SetSighandlerData {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe { slice::from_raw_parts_mut(self as *mut Self as *mut u8, mem::size_of::<Self>()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub use crate::sigabi::*;
|
|
||||||
|
|
||||||
/// UNSTABLE
|
|
||||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct ProcSchemeAttrs {
|
|
||||||
pub pid: u32,
|
|
||||||
pub euid: u32,
|
|
||||||
pub egid: u32,
|
|
||||||
pub prio: u32,
|
|
||||||
pub debug_name: [u8; 32],
|
|
||||||
}
|
|
||||||
impl Deref for ProcSchemeAttrs {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe { slice::from_raw_parts(self as *const Self as *const u8, mem::size_of::<Self>()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl DerefMut for ProcSchemeAttrs {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut ProcSchemeAttrs as *mut u8,
|
|
||||||
mem::size_of::<ProcSchemeAttrs>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct CtxtStsBuf {
|
|
||||||
pub status: usize,
|
|
||||||
pub excp: crate::Exception,
|
|
||||||
}
|
|
||||||
impl Deref for CtxtStsBuf {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe { slice::from_raw_parts(self as *const Self as *const u8, mem::size_of::<Self>()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl DerefMut for CtxtStsBuf {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts_mut(
|
|
||||||
self as *mut CtxtStsBuf as *mut u8,
|
|
||||||
mem::size_of::<CtxtStsBuf>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct NewFdParams {
|
|
||||||
pub offset: u64,
|
|
||||||
pub number: usize,
|
|
||||||
pub flags: usize,
|
|
||||||
pub internal_flags: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u8)]
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub enum GlobalSchemes {
|
|
||||||
Debug = 1,
|
|
||||||
Event = 2,
|
|
||||||
Memory = 3,
|
|
||||||
Pipe = 4,
|
|
||||||
Serio = 5,
|
|
||||||
Irq = 6,
|
|
||||||
Time = 7,
|
|
||||||
Sys = 8,
|
|
||||||
Proc = 9,
|
|
||||||
Acpi = 10,
|
|
||||||
Dtb = 11,
|
|
||||||
}
|
|
||||||
impl GlobalSchemes {
|
|
||||||
pub fn try_from_raw(raw: u8) -> Option<Self> {
|
|
||||||
match raw {
|
|
||||||
1 => Some(Self::Debug),
|
|
||||||
2 => Some(Self::Event),
|
|
||||||
3 => Some(Self::Memory),
|
|
||||||
4 => Some(Self::Pipe),
|
|
||||||
5 => Some(Self::Serio),
|
|
||||||
6 => Some(Self::Irq),
|
|
||||||
7 => Some(Self::Time),
|
|
||||||
8 => Some(Self::Sys),
|
|
||||||
9 => Some(Self::Proc),
|
|
||||||
10 => Some(Self::Acpi),
|
|
||||||
11 => Some(Self::Dtb),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn as_str(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Self::Debug => "debug",
|
|
||||||
Self::Event => "event",
|
|
||||||
Self::Memory => "memory",
|
|
||||||
Self::Pipe => "pipe",
|
|
||||||
Self::Serio => "serio",
|
|
||||||
Self::Irq => "irq",
|
|
||||||
Self::Time => "time",
|
|
||||||
Self::Sys => "sys",
|
|
||||||
Self::Proc => "kernel.proc",
|
|
||||||
Self::Acpi => "kernel.acpi",
|
|
||||||
Self::Dtb => "kernel.dtb",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
|
||||||
pub struct KernelSchemeInfo {
|
|
||||||
pub scheme_id: u8,
|
|
||||||
pub fd: usize,
|
|
||||||
}
|
|
||||||
+300
@@ -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}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-231
@@ -1,231 +0,0 @@
|
|||||||
use core::{
|
|
||||||
mem::size_of,
|
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
slice,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
error::{Error, Result, EINVAL},
|
|
||||||
ENAMETOOLONG,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
#[repr(packed)]
|
|
||||||
pub struct DirentHeader {
|
|
||||||
pub inode: u64,
|
|
||||||
/// A filesystem-specific opaque value used to uniquely identify directory entries. This value,
|
|
||||||
/// in the last returned entry from a SYS_GETDENTS invocation, shall be passed to the next
|
|
||||||
/// call.
|
|
||||||
pub next_opaque_id: u64,
|
|
||||||
// This struct intentionally does not include a "next" offset field, unlike Linux, to easily
|
|
||||||
// guarantee the iterator will be reasonably deterministic, even if the scheme is adversarial.
|
|
||||||
pub record_len: u16,
|
|
||||||
/// A `DirentKind`.
|
|
||||||
///
|
|
||||||
/// May not be directly available (Unspecified), and if so needs to be looked using fstat.
|
|
||||||
pub kind: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for DirentHeader {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe { slice::from_raw_parts(self as *const Self as *const u8, size_of::<Self>()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for DirentHeader {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe { slice::from_raw_parts_mut(self as *mut Self as *mut u8, size_of::<Self>()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: Must match relibc/include/bits/dirent.h
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum DirentKind {
|
|
||||||
#[default]
|
|
||||||
Unspecified = 0,
|
|
||||||
|
|
||||||
CharDev = 2,
|
|
||||||
Directory = 4,
|
|
||||||
BlockDev = 6,
|
|
||||||
Regular = 8,
|
|
||||||
Symlink = 10,
|
|
||||||
Socket = 12,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DirentKind {
|
|
||||||
// TODO: derive(FromPrimitive)
|
|
||||||
pub fn try_from_raw(raw: u8) -> Option<Self> {
|
|
||||||
Some(match raw {
|
|
||||||
0 => Self::Unspecified,
|
|
||||||
|
|
||||||
2 => Self::CharDev,
|
|
||||||
4 => Self::Directory,
|
|
||||||
6 => Self::BlockDev,
|
|
||||||
8 => Self::Regular,
|
|
||||||
10 => Self::Symlink,
|
|
||||||
12 => Self::Socket,
|
|
||||||
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DirentIter<'a>(&'a [u8]);
|
|
||||||
|
|
||||||
impl<'a> DirentIter<'a> {
|
|
||||||
pub const fn new(buffer: &'a [u8]) -> Self {
|
|
||||||
Self(buffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Invalid;
|
|
||||||
|
|
||||||
impl<'a> Iterator for DirentIter<'a> {
|
|
||||||
type Item = Result<(&'a DirentHeader, &'a [u8]), Invalid>;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
if self.0.len() < size_of::<DirentHeader>() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let header = unsafe { &*(self.0.as_ptr().cast::<DirentHeader>()) };
|
|
||||||
if self.0.len() < usize::from(header.record_len) {
|
|
||||||
return Some(Err(Invalid));
|
|
||||||
}
|
|
||||||
let (this, remaining) = self.0.split_at(usize::from(header.record_len));
|
|
||||||
self.0 = remaining;
|
|
||||||
|
|
||||||
let name_and_nul = &this[size_of::<DirentHeader>()..];
|
|
||||||
let name = &name_and_nul[..name_and_nul.len() - 1];
|
|
||||||
|
|
||||||
Some(Ok((header, name)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DirentBuf<B> {
|
|
||||||
buffer: B,
|
|
||||||
|
|
||||||
// Exists in order to allow future extensions to the DirentHeader struct.
|
|
||||||
|
|
||||||
// TODO: Might add an upper bound to protect against cache miss DoS. The kernel currently
|
|
||||||
// forbids any other value than size_of::<DirentHeader>().
|
|
||||||
header_size: u16,
|
|
||||||
|
|
||||||
written: usize,
|
|
||||||
}
|
|
||||||
/// Abstraction between &mut [u8] and the kernel's UserSliceWo.
|
|
||||||
pub trait Buffer<'a>: Sized + 'a {
|
|
||||||
fn empty() -> Self;
|
|
||||||
fn length(&self) -> usize;
|
|
||||||
|
|
||||||
/// Split all of `self` into two disjoint contiguous subbuffers of lengths `index` and `length
|
|
||||||
/// - index` respectively.
|
|
||||||
///
|
|
||||||
/// Returns None if and only if `index > length`.
|
|
||||||
fn split_at(self, index: usize) -> Option<[Self; 2]>;
|
|
||||||
|
|
||||||
/// Copy from `src`, lengths must match exactly.
|
|
||||||
///
|
|
||||||
/// Allowed to overwrite subsequent buffer space, for performance reasons. Can be changed in
|
|
||||||
/// the future if too restrictive.
|
|
||||||
fn copy_from_slice_exact(self, src: &[u8]) -> Result<()>;
|
|
||||||
|
|
||||||
/// Write zeroes to this part of the buffer.
|
|
||||||
///
|
|
||||||
/// Allowed to overwrite subsequent buffer space, for performance reasons. Can be changed in
|
|
||||||
/// the future if too restrictive.
|
|
||||||
fn zero_out(self) -> Result<()>;
|
|
||||||
}
|
|
||||||
impl<'a> Buffer<'a> for &'a mut [u8] {
|
|
||||||
fn empty() -> Self {
|
|
||||||
&mut []
|
|
||||||
}
|
|
||||||
fn length(&self) -> usize {
|
|
||||||
self.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn split_at(self, index: usize) -> Option<[Self; 2]> {
|
|
||||||
self.split_at_mut_checked(index).map(|(a, b)| [a, b])
|
|
||||||
}
|
|
||||||
fn copy_from_slice_exact(self, src: &[u8]) -> Result<()> {
|
|
||||||
self.copy_from_slice(src);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn zero_out(self) -> Result<()> {
|
|
||||||
self.fill(0);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DirEntry<'name> {
|
|
||||||
pub inode: u64,
|
|
||||||
pub next_opaque_id: u64,
|
|
||||||
pub name: &'name str,
|
|
||||||
pub kind: DirentKind,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, B: Buffer<'a>> DirentBuf<B> {
|
|
||||||
pub fn new(buffer: B, header_size: u16) -> Option<Self> {
|
|
||||||
if usize::from(header_size) < size_of::<DirentHeader>() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Self {
|
|
||||||
buffer,
|
|
||||||
header_size,
|
|
||||||
written: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn entry(&mut self, entry: DirEntry<'_>) -> Result<()> {
|
|
||||||
let name16 = u16::try_from(entry.name.len()).map_err(|_| Error::new(EINVAL))?;
|
|
||||||
let record_align = align_of::<*const DirentHeader>();
|
|
||||||
let record_len = self
|
|
||||||
.header_size
|
|
||||||
.checked_add(name16)
|
|
||||||
// XXX: NUL byte. Unfortunately this is probably the only performant way to be
|
|
||||||
// compatible with C.
|
|
||||||
.and_then(|l| l.checked_add(1))
|
|
||||||
// Align length so next header is aligned
|
|
||||||
.and_then(|l| l.checked_next_multiple_of(record_align as u16))
|
|
||||||
.ok_or(Error::new(ENAMETOOLONG))?;
|
|
||||||
|
|
||||||
let [this, remaining] = core::mem::replace(&mut self.buffer, B::empty())
|
|
||||||
.split_at(usize::from(record_len))
|
|
||||||
.ok_or(Error::new(EINVAL))?;
|
|
||||||
|
|
||||||
let [this_header_variable, this_name_and_nul] = this
|
|
||||||
.split_at(usize::from(self.header_size))
|
|
||||||
.expect("already know header_size + ... >= header_size");
|
|
||||||
|
|
||||||
let [this_name, this_name_nul] = this_name_and_nul
|
|
||||||
.split_at(usize::from(name16))
|
|
||||||
.expect("already know name.len() <= name.len() + 1");
|
|
||||||
|
|
||||||
// Every write here is currently sequential, allowing the buffer trait to do optimizations
|
|
||||||
// where subbuffer writes are out-of-bounds (but inside the total buffer).
|
|
||||||
|
|
||||||
let [this_header, this_header_extra] = this_header_variable
|
|
||||||
.split_at(size_of::<DirentHeader>())
|
|
||||||
.expect("already checked header_size <= size_of Header");
|
|
||||||
|
|
||||||
this_header.copy_from_slice_exact(&DirentHeader {
|
|
||||||
record_len,
|
|
||||||
next_opaque_id: entry.next_opaque_id,
|
|
||||||
inode: entry.inode,
|
|
||||||
kind: entry.kind as u8,
|
|
||||||
})?;
|
|
||||||
this_header_extra.zero_out()?;
|
|
||||||
this_name.copy_from_slice_exact(entry.name.as_bytes())?;
|
|
||||||
this_name_nul.zero_out()?;
|
|
||||||
|
|
||||||
self.written += usize::from(record_len);
|
|
||||||
self.buffer = remaining;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn finalize(self) -> usize {
|
|
||||||
self.written
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>;
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
-327
@@ -1,327 +0,0 @@
|
|||||||
use core::{fmt, result};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
|
||||||
pub struct Error {
|
|
||||||
pub errno: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T, E = Error> = result::Result<T, E>;
|
|
||||||
|
|
||||||
impl Error {
|
|
||||||
pub fn new(errno: i32) -> Error {
|
|
||||||
Error { errno }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mux(result: Result<usize>) -> usize {
|
|
||||||
match result {
|
|
||||||
Ok(value) => value,
|
|
||||||
Err(error) => -error.errno as usize,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn demux(value: usize) -> Result<usize> {
|
|
||||||
let errno = -(value as i32);
|
|
||||||
if errno >= 1 && errno < STR_ERROR.len() as i32 {
|
|
||||||
Err(Error::new(errno))
|
|
||||||
} else {
|
|
||||||
Ok(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text(&self) -> &'static str {
|
|
||||||
STR_ERROR
|
|
||||||
.get(self.errno as usize)
|
|
||||||
.map(|&x| x)
|
|
||||||
.unwrap_or("Unknown Error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
|
|
||||||
f.write_str(self.text())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
|
|
||||||
f.write_str(self.text())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
impl std::error::Error for Error {}
|
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
impl From<Error> for std::io::Error {
|
|
||||||
fn from(value: Error) -> Self {
|
|
||||||
std::io::Error::from_raw_os_error(value.errno)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const EPERM: i32 = 1; /* Operation not permitted */
|
|
||||||
pub const ENOENT: i32 = 2; /* No such file or directory */
|
|
||||||
pub const ESRCH: i32 = 3; /* No such process */
|
|
||||||
pub const EINTR: i32 = 4; /* Interrupted system call */
|
|
||||||
pub const EIO: i32 = 5; /* I/O error */
|
|
||||||
pub const ENXIO: i32 = 6; /* No such device or address */
|
|
||||||
pub const E2BIG: i32 = 7; /* Argument list too long */
|
|
||||||
pub const ENOEXEC: i32 = 8; /* Exec format error */
|
|
||||||
pub const EBADF: i32 = 9; /* Bad file number */
|
|
||||||
pub const ECHILD: i32 = 10; /* No child processes */
|
|
||||||
pub const EAGAIN: i32 = 11; /* Try again */
|
|
||||||
pub const ENOMEM: i32 = 12; /* Out of memory */
|
|
||||||
pub const EACCES: i32 = 13; /* Permission denied */
|
|
||||||
pub const EFAULT: i32 = 14; /* Bad address */
|
|
||||||
pub const ENOTBLK: i32 = 15; /* Block device required */
|
|
||||||
pub const EBUSY: i32 = 16; /* Device or resource busy */
|
|
||||||
pub const EEXIST: i32 = 17; /* File exists */
|
|
||||||
pub const EXDEV: i32 = 18; /* Cross-device link */
|
|
||||||
pub const ENODEV: i32 = 19; /* No such device */
|
|
||||||
pub const ENOTDIR: i32 = 20; /* Not a directory */
|
|
||||||
pub const EISDIR: i32 = 21; /* Is a directory */
|
|
||||||
pub const EINVAL: i32 = 22; /* Invalid argument */
|
|
||||||
pub const ENFILE: i32 = 23; /* File table overflow */
|
|
||||||
pub const EMFILE: i32 = 24; /* Too many open files */
|
|
||||||
pub const ENOTTY: i32 = 25; /* Not a typewriter */
|
|
||||||
pub const ETXTBSY: i32 = 26; /* Text file busy */
|
|
||||||
pub const EFBIG: i32 = 27; /* File too large */
|
|
||||||
pub const ENOSPC: i32 = 28; /* No space left on device */
|
|
||||||
pub const ESPIPE: i32 = 29; /* Illegal seek */
|
|
||||||
pub const EROFS: i32 = 30; /* Read-only file system */
|
|
||||||
pub const EMLINK: i32 = 31; /* Too many links */
|
|
||||||
pub const EPIPE: i32 = 32; /* Broken pipe */
|
|
||||||
pub const EDOM: i32 = 33; /* Math argument out of domain of func */
|
|
||||||
pub const ERANGE: i32 = 34; /* Math result not representable */
|
|
||||||
pub const EDEADLK: i32 = 35; /* Resource deadlock would occur */
|
|
||||||
pub const ENAMETOOLONG: i32 = 36; /* File name too long */
|
|
||||||
pub const ENOLCK: i32 = 37; /* No record locks available */
|
|
||||||
pub const ENOSYS: i32 = 38; /* Function not implemented */
|
|
||||||
pub const ENOTEMPTY: i32 = 39; /* Directory not empty */
|
|
||||||
pub const ELOOP: i32 = 40; /* Too many symbolic links encountered */
|
|
||||||
pub const EWOULDBLOCK: i32 = 41; /* Operation would block */
|
|
||||||
pub const ENOMSG: i32 = 42; /* No message of desired type */
|
|
||||||
pub const EIDRM: i32 = 43; /* Identifier removed */
|
|
||||||
pub const ECHRNG: i32 = 44; /* Channel number out of range */
|
|
||||||
pub const EL2NSYNC: i32 = 45; /* Level 2 not synchronized */
|
|
||||||
pub const EL3HLT: i32 = 46; /* Level 3 halted */
|
|
||||||
pub const EL3RST: i32 = 47; /* Level 3 reset */
|
|
||||||
pub const ELNRNG: i32 = 48; /* Link number out of range */
|
|
||||||
pub const EUNATCH: i32 = 49; /* Protocol driver not attached */
|
|
||||||
pub const ENOCSI: i32 = 50; /* No CSI structure available */
|
|
||||||
pub const EL2HLT: i32 = 51; /* Level 2 halted */
|
|
||||||
pub const EBADE: i32 = 52; /* Invalid exchange */
|
|
||||||
pub const EBADR: i32 = 53; /* Invalid request descriptor */
|
|
||||||
pub const EXFULL: i32 = 54; /* Exchange full */
|
|
||||||
pub const ENOANO: i32 = 55; /* No anode */
|
|
||||||
pub const EBADRQC: i32 = 56; /* Invalid request code */
|
|
||||||
pub const EBADSLT: i32 = 57; /* Invalid slot */
|
|
||||||
pub const EDEADLOCK: i32 = 58; /* Resource deadlock would occur */
|
|
||||||
pub const EBFONT: i32 = 59; /* Bad font file format */
|
|
||||||
pub const ENOSTR: i32 = 60; /* Device not a stream */
|
|
||||||
pub const ENODATA: i32 = 61; /* No data available */
|
|
||||||
pub const ETIME: i32 = 62; /* Timer expired */
|
|
||||||
pub const ENOSR: i32 = 63; /* Out of streams resources */
|
|
||||||
pub const ENONET: i32 = 64; /* Machine is not on the network */
|
|
||||||
pub const ENOPKG: i32 = 65; /* Package not installed */
|
|
||||||
pub const EREMOTE: i32 = 66; /* Object is remote */
|
|
||||||
pub const ENOLINK: i32 = 67; /* Link has been severed */
|
|
||||||
pub const EADV: i32 = 68; /* Advertise error */
|
|
||||||
pub const ESRMNT: i32 = 69; /* Srmount error */
|
|
||||||
pub const ECOMM: i32 = 70; /* Communication error on send */
|
|
||||||
pub const EPROTO: i32 = 71; /* Protocol error */
|
|
||||||
pub const EMULTIHOP: i32 = 72; /* Multihop attempted */
|
|
||||||
pub const EDOTDOT: i32 = 73; /* RFS specific error */
|
|
||||||
pub const EBADMSG: i32 = 74; /* Not a data message */
|
|
||||||
pub const EOVERFLOW: i32 = 75; /* Value too large for defined data type */
|
|
||||||
pub const ENOTUNIQ: i32 = 76; /* Name not unique on network */
|
|
||||||
pub const EBADFD: i32 = 77; /* File descriptor in bad state */
|
|
||||||
pub const EREMCHG: i32 = 78; /* Remote address changed */
|
|
||||||
pub const ELIBACC: i32 = 79; /* Can not access a needed shared library */
|
|
||||||
pub const ELIBBAD: i32 = 80; /* Accessing a corrupted shared library */
|
|
||||||
pub const ELIBSCN: i32 = 81; /* .lib section in a.out corrupted */
|
|
||||||
pub const ELIBMAX: i32 = 82; /* Attempting to link in too many shared libraries */
|
|
||||||
pub const ELIBEXEC: i32 = 83; /* Cannot exec a shared library directly */
|
|
||||||
pub const EILSEQ: i32 = 84; /* Illegal byte sequence */
|
|
||||||
pub const ERESTART: i32 = 85; /* Interrupted system call should be restarted */
|
|
||||||
pub const ESTRPIPE: i32 = 86; /* Streams pipe error */
|
|
||||||
pub const EUSERS: i32 = 87; /* Too many users */
|
|
||||||
pub const ENOTSOCK: i32 = 88; /* Socket operation on non-socket */
|
|
||||||
pub const EDESTADDRREQ: i32 = 89; /* Destination address required */
|
|
||||||
pub const EMSGSIZE: i32 = 90; /* Message too long */
|
|
||||||
pub const EPROTOTYPE: i32 = 91; /* Protocol wrong type for socket */
|
|
||||||
pub const ENOPROTOOPT: i32 = 92; /* Protocol not available */
|
|
||||||
pub const EPROTONOSUPPORT: i32 = 93; /* Protocol not supported */
|
|
||||||
pub const ESOCKTNOSUPPORT: i32 = 94; /* Socket type not supported */
|
|
||||||
pub const EOPNOTSUPP: i32 = 95; /* Operation not supported on transport endpoint */
|
|
||||||
pub const EPFNOSUPPORT: i32 = 96; /* Protocol family not supported */
|
|
||||||
pub const EAFNOSUPPORT: i32 = 97; /* Address family not supported by protocol */
|
|
||||||
pub const EADDRINUSE: i32 = 98; /* Address already in use */
|
|
||||||
pub const EADDRNOTAVAIL: i32 = 99; /* Cannot assign requested address */
|
|
||||||
pub const ENETDOWN: i32 = 100; /* Network is down */
|
|
||||||
pub const ENETUNREACH: i32 = 101; /* Network is unreachable */
|
|
||||||
pub const ENETRESET: i32 = 102; /* Network dropped connection because of reset */
|
|
||||||
pub const ECONNABORTED: i32 = 103; /* Software caused connection abort */
|
|
||||||
pub const ECONNRESET: i32 = 104; /* Connection reset by peer */
|
|
||||||
pub const ENOBUFS: i32 = 105; /* No buffer space available */
|
|
||||||
pub const EISCONN: i32 = 106; /* Transport endpoint is already connected */
|
|
||||||
pub const ENOTCONN: i32 = 107; /* Transport endpoint is not connected */
|
|
||||||
pub const ESHUTDOWN: i32 = 108; /* Cannot send after transport endpoint shutdown */
|
|
||||||
pub const ETOOMANYREFS: i32 = 109; /* Too many references: cannot splice */
|
|
||||||
pub const ETIMEDOUT: i32 = 110; /* Connection timed out */
|
|
||||||
pub const ECONNREFUSED: i32 = 111; /* Connection refused */
|
|
||||||
pub const EHOSTDOWN: i32 = 112; /* Host is down */
|
|
||||||
pub const EHOSTUNREACH: i32 = 113; /* No route to host */
|
|
||||||
pub const EALREADY: i32 = 114; /* Operation already in progress */
|
|
||||||
pub const EINPROGRESS: i32 = 115; /* Operation now in progress */
|
|
||||||
pub const ESTALE: i32 = 116; /* Stale NFS file handle */
|
|
||||||
pub const EUCLEAN: i32 = 117; /* Structure needs cleaning */
|
|
||||||
pub const ENOTNAM: i32 = 118; /* Not a XENIX named type file */
|
|
||||||
pub const ENAVAIL: i32 = 119; /* No XENIX semaphores available */
|
|
||||||
pub const EISNAM: i32 = 120; /* Is a named type file */
|
|
||||||
pub const EREMOTEIO: i32 = 121; /* Remote I/O error */
|
|
||||||
pub const EDQUOT: i32 = 122; /* Quota exceeded */
|
|
||||||
pub const ENOMEDIUM: i32 = 123; /* No medium found */
|
|
||||||
pub const EMEDIUMTYPE: i32 = 124; /* Wrong medium type */
|
|
||||||
pub const ECANCELED: i32 = 125; /* Operation Canceled */
|
|
||||||
pub const ENOKEY: i32 = 126; /* Required key not available */
|
|
||||||
pub const EKEYEXPIRED: i32 = 127; /* Key has expired */
|
|
||||||
pub const EKEYREVOKED: i32 = 128; /* Key has been revoked */
|
|
||||||
pub const EKEYREJECTED: i32 = 129; /* Key was rejected by service */
|
|
||||||
pub const EOWNERDEAD: i32 = 130; /* Owner died */
|
|
||||||
pub const ENOTRECOVERABLE: i32 = 131; /* State not recoverable */
|
|
||||||
pub const ERSVD: i32 = 132; /* Reserved (formerly "scheme-kernel message code") */
|
|
||||||
|
|
||||||
pub static STR_ERROR: [&'static str; 133] = [
|
|
||||||
"Success",
|
|
||||||
"Operation not permitted",
|
|
||||||
"No such file or directory",
|
|
||||||
"No such process",
|
|
||||||
"Interrupted system call",
|
|
||||||
"I/O error",
|
|
||||||
"No such device or address",
|
|
||||||
"Argument list too long",
|
|
||||||
"Exec format error",
|
|
||||||
"Bad file number",
|
|
||||||
"No child processes",
|
|
||||||
"Try again",
|
|
||||||
"Out of memory",
|
|
||||||
"Permission denied",
|
|
||||||
"Bad address",
|
|
||||||
"Block device required",
|
|
||||||
"Device or resource busy",
|
|
||||||
"File exists",
|
|
||||||
"Cross-device link",
|
|
||||||
"No such device",
|
|
||||||
"Not a directory",
|
|
||||||
"Is a directory",
|
|
||||||
"Invalid argument",
|
|
||||||
"File table overflow",
|
|
||||||
"Too many open files",
|
|
||||||
"Not a typewriter",
|
|
||||||
"Text file busy",
|
|
||||||
"File too large",
|
|
||||||
"No space left on device",
|
|
||||||
"Illegal seek",
|
|
||||||
"Read-only file system",
|
|
||||||
"Too many links",
|
|
||||||
"Broken pipe",
|
|
||||||
"Math argument out of domain of func",
|
|
||||||
"Math result not representable",
|
|
||||||
"Resource deadlock would occur",
|
|
||||||
"File name too long",
|
|
||||||
"No record locks available",
|
|
||||||
"Function not implemented",
|
|
||||||
"Directory not empty",
|
|
||||||
"Too many symbolic links encountered",
|
|
||||||
"Operation would block",
|
|
||||||
"No message of desired type",
|
|
||||||
"Identifier removed",
|
|
||||||
"Channel number out of range",
|
|
||||||
"Level 2 not synchronized",
|
|
||||||
"Level 3 halted",
|
|
||||||
"Level 3 reset",
|
|
||||||
"Link number out of range",
|
|
||||||
"Protocol driver not attached",
|
|
||||||
"No CSI structure available",
|
|
||||||
"Level 2 halted",
|
|
||||||
"Invalid exchange",
|
|
||||||
"Invalid request descriptor",
|
|
||||||
"Exchange full",
|
|
||||||
"No anode",
|
|
||||||
"Invalid request code",
|
|
||||||
"Invalid slot",
|
|
||||||
"Resource deadlock would occur",
|
|
||||||
"Bad font file format",
|
|
||||||
"Device not a stream",
|
|
||||||
"No data available",
|
|
||||||
"Timer expired",
|
|
||||||
"Out of streams resources",
|
|
||||||
"Machine is not on the network",
|
|
||||||
"Package not installed",
|
|
||||||
"Object is remote",
|
|
||||||
"Link has been severed",
|
|
||||||
"Advertise error",
|
|
||||||
"Srmount error",
|
|
||||||
"Communication error on send",
|
|
||||||
"Protocol error",
|
|
||||||
"Multihop attempted",
|
|
||||||
"RFS specific error",
|
|
||||||
"Not a data message",
|
|
||||||
"Value too large for defined data type",
|
|
||||||
"Name not unique on network",
|
|
||||||
"File descriptor in bad state",
|
|
||||||
"Remote address changed",
|
|
||||||
"Can not access a needed shared library",
|
|
||||||
"Accessing a corrupted shared library",
|
|
||||||
".lib section in a.out corrupted",
|
|
||||||
"Attempting to link in too many shared libraries",
|
|
||||||
"Cannot exec a shared library directly",
|
|
||||||
"Illegal byte sequence",
|
|
||||||
"Interrupted system call should be restarted",
|
|
||||||
"Streams pipe error",
|
|
||||||
"Too many users",
|
|
||||||
"Socket operation on non-socket",
|
|
||||||
"Destination address required",
|
|
||||||
"Message too long",
|
|
||||||
"Protocol wrong type for socket",
|
|
||||||
"Protocol not available",
|
|
||||||
"Protocol not supported",
|
|
||||||
"Socket type not supported",
|
|
||||||
"Operation not supported on transport endpoint",
|
|
||||||
"Protocol family not supported",
|
|
||||||
"Address family not supported by protocol",
|
|
||||||
"Address already in use",
|
|
||||||
"Cannot assign requested address",
|
|
||||||
"Network is down",
|
|
||||||
"Network is unreachable",
|
|
||||||
"Network dropped connection because of reset",
|
|
||||||
"Software caused connection abort",
|
|
||||||
"Connection reset by peer",
|
|
||||||
"No buffer space available",
|
|
||||||
"Transport endpoint is already connected",
|
|
||||||
"Transport endpoint is not connected",
|
|
||||||
"Cannot send after transport endpoint shutdown",
|
|
||||||
"Too many references: cannot splice",
|
|
||||||
"Connection timed out",
|
|
||||||
"Connection refused",
|
|
||||||
"Host is down",
|
|
||||||
"No route to host",
|
|
||||||
"Operation already in progress",
|
|
||||||
"Operation now in progress",
|
|
||||||
"Stale NFS file handle",
|
|
||||||
"Structure needs cleaning",
|
|
||||||
"Not a XENIX named type file",
|
|
||||||
"No XENIX semaphores available",
|
|
||||||
"Is a named type file",
|
|
||||||
"Remote I/O error",
|
|
||||||
"Quota exceeded",
|
|
||||||
"No medium found",
|
|
||||||
"Wrong medium type",
|
|
||||||
"Operation Canceled",
|
|
||||||
"Required key not available",
|
|
||||||
"Key has expired",
|
|
||||||
"Key has been revoked",
|
|
||||||
"Key was rejected by service",
|
|
||||||
"Owner died",
|
|
||||||
"State not recoverable",
|
|
||||||
"Reserved (formerly scheme-kernel message code)",
|
|
||||||
];
|
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-567
@@ -1,567 +0,0 @@
|
|||||||
use bitflags::bitflags as inner_bitflags;
|
|
||||||
use core::{mem, ops::Deref, slice};
|
|
||||||
|
|
||||||
macro_rules! bitflags {
|
|
||||||
(
|
|
||||||
$(#[$outer:meta])*
|
|
||||||
pub struct $BitFlags:ident: $T:ty {
|
|
||||||
$(
|
|
||||||
$(#[$inner:ident $($args:tt)*])*
|
|
||||||
const $Flag:ident = $value:expr;
|
|
||||||
)+
|
|
||||||
}
|
|
||||||
) => {
|
|
||||||
// First, use the inner bitflags
|
|
||||||
inner_bitflags! {
|
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy, Default)]
|
|
||||||
$(#[$outer])*
|
|
||||||
pub struct $BitFlags: $T {
|
|
||||||
$(
|
|
||||||
$(#[$inner $($args)*])*
|
|
||||||
const $Flag = $value;
|
|
||||||
)+
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl $BitFlags {
|
|
||||||
#[deprecated = "use the safe `from_bits_retain` method instead"]
|
|
||||||
pub unsafe fn from_bits_unchecked(bits: $T) -> Self {
|
|
||||||
Self::from_bits_retain(bits)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Secondly, re-export all inner constants
|
|
||||||
// (`pub use self::Struct::*` doesn't work)
|
|
||||||
$(
|
|
||||||
$(#[$inner $($args)*])*
|
|
||||||
pub const $Flag: $BitFlags = $BitFlags::$Flag;
|
|
||||||
)+
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const CLOCK_REALTIME: usize = 1;
|
|
||||||
pub const CLOCK_MONOTONIC: usize = 4;
|
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
pub struct EventFlags: usize {
|
|
||||||
const EVENT_NONE = 0;
|
|
||||||
const EVENT_READ = 1;
|
|
||||||
const EVENT_WRITE = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const F_DUPFD: usize = 0;
|
|
||||||
pub const F_GETFD: usize = 1;
|
|
||||||
pub const F_SETFD: usize = 2;
|
|
||||||
pub const F_GETFL: usize = 3;
|
|
||||||
pub const F_SETFL: usize = 4;
|
|
||||||
pub const F_DUPFD_CLOEXEC: usize = 1030;
|
|
||||||
|
|
||||||
pub const FUTEX_WAIT: usize = 0;
|
|
||||||
pub const FUTEX_WAKE: usize = 1;
|
|
||||||
pub const FUTEX_REQUEUE: usize = 2;
|
|
||||||
pub const FUTEX_WAIT64: usize = 3;
|
|
||||||
|
|
||||||
// TODO: Split SendFdFlags into caller flags and flags that the scheme receives?
|
|
||||||
bitflags::bitflags! {
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub struct SendFdFlags: usize {
|
|
||||||
/// If set, the kernel will enforce that the file descriptors are exclusively owned.
|
|
||||||
///
|
|
||||||
/// That is, there will no longer exist any other reference to those FDs when removed from
|
|
||||||
/// the file table (sendfd always removes the FDs from the file table, but without this
|
|
||||||
/// flag, it can be retained by SYS_DUPing them first).
|
|
||||||
const EXCLUSIVE = 1;
|
|
||||||
|
|
||||||
/// If set, the file descriptors will be cloned and *not* removed from the sender's file table.
|
|
||||||
/// By default, `SYS_SENDFD` moves the file descriptors, removing them from the sender.
|
|
||||||
const CLONE = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bitflags::bitflags! {
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub struct FobtainFdFlags: usize {
|
|
||||||
/// If set, the SYS_CALL payload specifies the destination file descriptor slots, otherwise the lowest
|
|
||||||
/// available slots will be selected, and placed in the usize pointed to by SYS_CALL
|
|
||||||
/// payload.
|
|
||||||
const MANUAL_FD = 1;
|
|
||||||
|
|
||||||
/// If set, the file descriptors received are guaranteed to be exclusively owned (by the file
|
|
||||||
/// table the obtainer is running in).
|
|
||||||
const EXCLUSIVE = 2;
|
|
||||||
|
|
||||||
/// If set, the file descriptors received will be placed into the *upper* file table.
|
|
||||||
const UPPER_TBL = 4;
|
|
||||||
|
|
||||||
/// If set, the received file descriptors are marked as close-on-exec.
|
|
||||||
const CLOEXEC = 8;
|
|
||||||
|
|
||||||
// No, cloexec won't be stored in the kernel in the future, when the stable ABI is moved to
|
|
||||||
// relibc, so no flag for that!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bitflags::bitflags! {
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub struct RecvFdFlags: usize {
|
|
||||||
/// If set, the SYS_CALL payload specifies the destination file descriptor slots, otherwise the lowest
|
|
||||||
/// available slots will be selected, and placed in the usize pointed to by SYS_CALL
|
|
||||||
/// payload.
|
|
||||||
const MANUAL_FD = 1;
|
|
||||||
|
|
||||||
/// If set, the file descriptors received will be placed into the *upper* file table.
|
|
||||||
const UPPER_TBL = 2;
|
|
||||||
|
|
||||||
/// If set, the received file descriptors are marked as close-on-exec.
|
|
||||||
const CLOEXEC = 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bitflags::bitflags! {
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub struct FmoveFdFlags: usize {
|
|
||||||
/// If set, the kernel will enforce that the file descriptors are exclusively owned.
|
|
||||||
///
|
|
||||||
/// That is, there will no longer exist any other reference to those FDs when removed from
|
|
||||||
/// the file table (SYS_CALL always removes the FDs from the file table, but without this
|
|
||||||
/// flag, it can be retained by SYS_DUPing them first).
|
|
||||||
const EXCLUSIVE = 1;
|
|
||||||
|
|
||||||
/// If set, the file descriptors will be cloned and *not* removed from the sender's file table.
|
|
||||||
/// By default, sendfd moves the file descriptors, removing them from the sender.
|
|
||||||
const CLONE = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
pub struct MapFlags: usize {
|
|
||||||
// TODO: Downgrade PROT_NONE to global constant? (bitflags specifically states zero flags
|
|
||||||
// can cause buggy behavior).
|
|
||||||
const PROT_NONE = 0x0000_0000;
|
|
||||||
|
|
||||||
const PROT_EXEC = 0x0001_0000;
|
|
||||||
const PROT_WRITE = 0x0002_0000;
|
|
||||||
const PROT_READ = 0x0004_0000;
|
|
||||||
|
|
||||||
const MAP_SHARED = 0x0001;
|
|
||||||
const MAP_PRIVATE = 0x0002;
|
|
||||||
|
|
||||||
const MAP_FIXED = 0x0004;
|
|
||||||
const MAP_FIXED_NOREPLACE = 0x000C;
|
|
||||||
|
|
||||||
/// For *userspace-backed mmaps*, return from the mmap call before all pages have been
|
|
||||||
/// provided by the scheme. This requires the scheme to be trusted, as the current context
|
|
||||||
/// can block indefinitely, if the scheme does not respond to the page fault handler's
|
|
||||||
/// request, as it tries to map the page by requesting it from the scheme.
|
|
||||||
///
|
|
||||||
/// In some cases however, such as the program loader, the data needs to be trusted as much
|
|
||||||
/// with or without MAP_LAZY, and if so, mapping lazily will not cause insecureness by
|
|
||||||
/// itself.
|
|
||||||
///
|
|
||||||
/// For kernel-backed mmaps, this flag has no effect at all. It is unspecified whether
|
|
||||||
/// kernel mmaps are lazy or not.
|
|
||||||
const MAP_LAZY = 0x0010;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bitflags! {
|
|
||||||
pub struct MunmapFlags: usize {
|
|
||||||
/// Indicates whether the funmap call must implicitly do an msync, for the changes to
|
|
||||||
/// become visible later.
|
|
||||||
///
|
|
||||||
/// This flag will currently be set if and only if MAP_SHARED | PROT_WRITE are set.
|
|
||||||
const NEEDS_SYNC = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const MODE_TYPE: u16 = 0xF000;
|
|
||||||
pub const MODE_DIR: u16 = 0x4000;
|
|
||||||
pub const MODE_FILE: u16 = 0x8000;
|
|
||||||
pub const MODE_SYMLINK: u16 = 0xA000;
|
|
||||||
pub const MODE_FIFO: u16 = 0x1000;
|
|
||||||
pub const MODE_CHR: u16 = 0x2000;
|
|
||||||
pub const MODE_SOCK: u16 = 0xC000;
|
|
||||||
|
|
||||||
pub const MODE_PERM: u16 = 0x0FFF;
|
|
||||||
pub const MODE_SETUID: u16 = 0o4000;
|
|
||||||
pub const MODE_SETGID: u16 = 0o2000;
|
|
||||||
|
|
||||||
pub const O_RDONLY: usize = 0x0001_0000;
|
|
||||||
pub const O_WRONLY: usize = 0x0002_0000;
|
|
||||||
pub const O_RDWR: usize = 0x0003_0000;
|
|
||||||
pub const O_NONBLOCK: usize = 0x0004_0000;
|
|
||||||
pub const O_APPEND: usize = 0x0008_0000;
|
|
||||||
pub const O_SHLOCK: usize = 0x0010_0000;
|
|
||||||
pub const O_EXLOCK: usize = 0x0020_0000;
|
|
||||||
pub const O_ASYNC: usize = 0x0040_0000;
|
|
||||||
pub const O_FSYNC: usize = 0x0080_0000;
|
|
||||||
pub const O_CLOEXEC: usize = 0x0100_0000;
|
|
||||||
pub const O_CREAT: usize = 0x0200_0000;
|
|
||||||
pub const O_TRUNC: usize = 0x0400_0000;
|
|
||||||
pub const O_EXCL: usize = 0x0800_0000;
|
|
||||||
pub const O_DIRECTORY: usize = 0x1000_0000;
|
|
||||||
pub const O_STAT: usize = 0x2000_0000;
|
|
||||||
pub const O_SYMLINK: usize = 0x4000_0000;
|
|
||||||
pub const O_NOFOLLOW: usize = 0x8000_0000;
|
|
||||||
pub const O_ACCMODE: usize = O_RDONLY | O_WRONLY | O_RDWR;
|
|
||||||
pub const O_FCNTL_MASK: usize = O_NONBLOCK | O_APPEND | O_ASYNC | O_FSYNC;
|
|
||||||
|
|
||||||
/// Remove directory instead of unlinking file.
|
|
||||||
pub const AT_REMOVEDIR: usize = 0x200;
|
|
||||||
|
|
||||||
// The top 48 bits of PTRACE_* are reserved, for now
|
|
||||||
|
|
||||||
// NOT ABI STABLE!
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
|
||||||
#[repr(usize)]
|
|
||||||
pub enum ContextStatus {
|
|
||||||
Runnable,
|
|
||||||
Blocked,
|
|
||||||
NotYetStarted,
|
|
||||||
Dead,
|
|
||||||
ForceKilled,
|
|
||||||
Stopped,
|
|
||||||
UnhandledExcp,
|
|
||||||
#[default]
|
|
||||||
Other, // reserved
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
#[repr(usize)]
|
|
||||||
pub enum ContextVerb {
|
|
||||||
Stop = 1,
|
|
||||||
Unstop = 2,
|
|
||||||
Interrupt = 3,
|
|
||||||
ForceKill = usize::MAX,
|
|
||||||
}
|
|
||||||
impl ContextVerb {
|
|
||||||
pub fn try_from_raw(raw: usize) -> Option<Self> {
|
|
||||||
Some(match raw {
|
|
||||||
1 => Self::Stop,
|
|
||||||
2 => Self::Unstop,
|
|
||||||
3 => Self::Interrupt,
|
|
||||||
usize::MAX => Self::ForceKill,
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOT ABI STABLE!
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum AddrSpaceVerb {
|
|
||||||
MmapMin = 255,
|
|
||||||
}
|
|
||||||
impl AddrSpaceVerb {
|
|
||||||
pub fn try_from_raw(verb: u8) -> Option<Self> {
|
|
||||||
Some(match verb {
|
|
||||||
255 => Self::MmapMin,
|
|
||||||
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOT ABI STABLE!
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum ProcSchemeVerb {
|
|
||||||
RegsInt = 250,
|
|
||||||
RegsFloat = 251,
|
|
||||||
RegsEnv = 252,
|
|
||||||
SchedAffinity = 253,
|
|
||||||
Start = 254,
|
|
||||||
Iopl = 255,
|
|
||||||
}
|
|
||||||
impl ProcSchemeVerb {
|
|
||||||
pub fn try_from_raw(verb: u8) -> Option<Self> {
|
|
||||||
Some(match verb {
|
|
||||||
250 => Self::RegsInt,
|
|
||||||
251 => Self::RegsFloat,
|
|
||||||
252 => Self::RegsEnv,
|
|
||||||
253 => Self::SchedAffinity,
|
|
||||||
254 => Self::Start,
|
|
||||||
255 => Self::Iopl,
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
pub enum FileTableVerb {
|
|
||||||
Close = 1,
|
|
||||||
Dup2 = 2,
|
|
||||||
CloseCloExec = 3,
|
|
||||||
}
|
|
||||||
impl FileTableVerb {
|
|
||||||
pub fn try_from_raw(value: u8) -> Option<Self> {
|
|
||||||
Some(match value {
|
|
||||||
1 => Self::Close,
|
|
||||||
2 => Self::Dup2,
|
|
||||||
3 => Self::CloseCloExec,
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOT ABI-STABLE!
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
#[repr(u64)]
|
|
||||||
pub enum AcpiVerb {
|
|
||||||
// copies the rsdt/xsdt to the payload buffer (the number of bytes that fit), and returns the
|
|
||||||
// rsdt/xsdt length regardless
|
|
||||||
ReadRxsdt = 1,
|
|
||||||
// no payload, just returns 0 or 1
|
|
||||||
CheckShutdown = 2,
|
|
||||||
/// Red Bear OS extension (Phase I): acpid requests the kernel
|
|
||||||
/// enter s2idle (Modern Standby / S0ix). The kernel sets
|
|
||||||
/// `S2IDLE_REQUESTED`; the idle path calls `mwait_loop()`. Read
|
|
||||||
/// payload (1 byte) returns the *previous* value of the flag.
|
|
||||||
/// Write payload is opaque (ignored by current kernel).
|
|
||||||
/// Mirrors Linux 7.1 `s2idle_enter()` in
|
|
||||||
/// `kernel/power/suspend.c:91`. Hardware-agnostic — works on
|
|
||||||
/// any platform with Modern Standby firmware (Dell, HP, Lenovo,
|
|
||||||
/// LG Gram, etc.), not just LG Gram.
|
|
||||||
EnterS2Idle = 3,
|
|
||||||
/// Red Bear OS extension (Phase I): acpid signals s2idle
|
|
||||||
/// exit. Kernel clears `S2IDLE_REQUESTED`. Read payload (1
|
|
||||||
/// byte) always returns 0. Mirrors Linux 7.1 `s2idle_wake()` in
|
|
||||||
/// `kernel/power/suspend.c:133`. Hardware-agnostic.
|
|
||||||
ExitS2Idle = 4,
|
|
||||||
/// Red Bear OS extension (Phase II.X.W): acpid writes the
|
|
||||||
/// 64-bit kernel S3 resume trampoline address to
|
|
||||||
/// FACS.xfirmware_waking_vector. The kernel's
|
|
||||||
/// `arch/x86_shared/s3_resume.rs` trampoline is at this
|
|
||||||
/// address; the platform firmware jumps to it on S3 wake.
|
|
||||||
/// Write payload: 8-byte little-endian u64 (the trampoline
|
|
||||||
/// address). Read payload: 0 (no error condition; this verb
|
|
||||||
/// is one-way). Mirrors Linux 7.1's
|
|
||||||
/// `acpi_set_firmware_waking_vector` in ACPICA.
|
|
||||||
/// Hardware-agnostic: works on any x86_64 system with
|
|
||||||
/// standard ACPI S3 support (Dell, HP, Lenovo, LG Gram 14).
|
|
||||||
SetS3WakingVector = 5,
|
|
||||||
/// Red Bear OS extension (Phase II.X.W): acpid requests
|
|
||||||
/// the kernel to enter S3. The kernel's kstop handler
|
|
||||||
/// dispatches on the "s3" string arg, dispatches on the
|
|
||||||
/// SLP_TYP byte, and does the PM1 register write. The
|
|
||||||
/// acpid has already done the AML prep (`_TTS(3)`, `_PTS(3)`,
|
|
||||||
/// `_SST(3)`) and written the trampoline address to FACS
|
|
||||||
/// via `SetS3WakingVector`. No payload needed.
|
|
||||||
/// Mirrors Linux 7.1's `enter_sleep_state` /
|
|
||||||
/// `acpi_hw_legacy_sleep` for S3.
|
|
||||||
EnterS3 = 6,
|
|
||||||
}
|
|
||||||
impl AcpiVerb {
|
|
||||||
pub const fn try_from_raw(value: u64) -> Option<Self> {
|
|
||||||
Some(match value {
|
|
||||||
1 => Self::ReadRxsdt,
|
|
||||||
2 => Self::CheckShutdown,
|
|
||||||
3 => Self::EnterS2Idle,
|
|
||||||
4 => Self::ExitS2Idle,
|
|
||||||
5 => Self::SetS3WakingVector,
|
|
||||||
6 => Self::EnterS3,
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
#[repr(usize)]
|
|
||||||
pub enum SchemeSocketCall {
|
|
||||||
ObtainFd = 0,
|
|
||||||
MoveFd = 1,
|
|
||||||
}
|
|
||||||
impl SchemeSocketCall {
|
|
||||||
pub fn try_from_raw(raw: usize) -> Option<Self> {
|
|
||||||
Some(match raw {
|
|
||||||
0 => Self::ObtainFd,
|
|
||||||
1 => Self::MoveFd,
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
#[repr(usize)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum FsCall {
|
|
||||||
Connect = 0,
|
|
||||||
}
|
|
||||||
impl FsCall {
|
|
||||||
pub fn try_from_raw(raw: usize) -> Option<Self> {
|
|
||||||
Some(match raw {
|
|
||||||
0 => Self::Connect,
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
pub struct PtraceFlags: u64 {
|
|
||||||
/// Stop before a syscall is handled. Send PTRACE_FLAG_IGNORE to not
|
|
||||||
/// handle the syscall.
|
|
||||||
const PTRACE_STOP_PRE_SYSCALL = 0x0000_0000_0000_0001;
|
|
||||||
/// Stop after a syscall is handled.
|
|
||||||
const PTRACE_STOP_POST_SYSCALL = 0x0000_0000_0000_0002;
|
|
||||||
/// Stop after exactly one instruction. TODO: This may not handle
|
|
||||||
/// fexec/signal boundaries. Should it?
|
|
||||||
const PTRACE_STOP_SINGLESTEP = 0x0000_0000_0000_0004;
|
|
||||||
/// Stop before a signal is handled. Send PTRACE_FLAG_IGNORE to not
|
|
||||||
/// handle signal.
|
|
||||||
const PTRACE_STOP_SIGNAL = 0x0000_0000_0000_0008;
|
|
||||||
/// Stop on a software breakpoint, such as the int3 instruction for
|
|
||||||
/// x86_64.
|
|
||||||
const PTRACE_STOP_BREAKPOINT = 0x0000_0000_0000_0010;
|
|
||||||
/// Stop just before exiting for good.
|
|
||||||
const PTRACE_STOP_EXIT = 0x0000_0000_0000_0020;
|
|
||||||
|
|
||||||
const PTRACE_STOP_MASK = 0x0000_0000_0000_00FF;
|
|
||||||
|
|
||||||
|
|
||||||
/// Sent when a child is cloned, giving you the opportunity to trace it.
|
|
||||||
/// If you don't catch this, the child is started as normal.
|
|
||||||
const PTRACE_EVENT_CLONE = 0x0000_0000_0000_0100;
|
|
||||||
|
|
||||||
/// Sent when current-addrspace is changed, allowing the tracer to reopen the memory file.
|
|
||||||
const PTRACE_EVENT_ADDRSPACE_SWITCH = 0x0000_0000_0000_0200;
|
|
||||||
|
|
||||||
const PTRACE_EVENT_MASK = 0x0000_0000_0000_0F00;
|
|
||||||
|
|
||||||
/// Special meaning, depending on the event. Usually, when fired before
|
|
||||||
/// an action, it will skip performing that action.
|
|
||||||
const PTRACE_FLAG_IGNORE = 0x0000_0000_0000_1000;
|
|
||||||
|
|
||||||
const PTRACE_FLAG_MASK = 0x0000_0000_0000_F000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Deref for PtraceFlags {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
// Same as to_ne_bytes but in-place
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(&self.bits() as *const _ as *const u8, mem::size_of::<u64>())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const SEEK_SET: usize = 0;
|
|
||||||
pub const SEEK_CUR: usize = 1;
|
|
||||||
pub const SEEK_END: usize = 2;
|
|
||||||
|
|
||||||
pub const SIGCHLD: usize = 17;
|
|
||||||
pub const SIGTSTP: usize = 20;
|
|
||||||
pub const SIGTTIN: usize = 21;
|
|
||||||
pub const SIGTTOU: usize = 22;
|
|
||||||
|
|
||||||
pub const ADDRSPACE_OP_MMAP: usize = 0;
|
|
||||||
pub const ADDRSPACE_OP_MUNMAP: usize = 1;
|
|
||||||
pub const ADDRSPACE_OP_MPROTECT: usize = 2;
|
|
||||||
pub const ADDRSPACE_OP_TRANSFER: usize = 3;
|
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
pub struct MremapFlags: usize {
|
|
||||||
const FIXED = 1;
|
|
||||||
const FIXED_REPLACE = 3;
|
|
||||||
/// Alias's memory region at `old_address` to `new_address` such that both regions share
|
|
||||||
/// the same frames.
|
|
||||||
const KEEP_OLD = 1 << 2;
|
|
||||||
// TODO: MAYMOVE, DONTUNMAP
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bitflags! {
|
|
||||||
pub struct RwFlags: u32 {
|
|
||||||
const NONBLOCK = 1;
|
|
||||||
const APPEND = 2;
|
|
||||||
// TODO: sync/dsync
|
|
||||||
// TODO: O_DIRECT?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bitflags! {
|
|
||||||
pub struct SigcontrolFlags: usize {
|
|
||||||
/// Prevents the kernel from jumping the context to the signal trampoline, but otherwise
|
|
||||||
/// has absolutely no effect on which signals are blocked etc. Meant to be used for
|
|
||||||
/// short-lived critical sections inside libc.
|
|
||||||
const INHIBIT_DELIVERY = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bitflags! {
|
|
||||||
pub struct CallFlags: usize {
|
|
||||||
// reserved
|
|
||||||
const RSVD0 = 1 << 0;
|
|
||||||
const RSVD1 = 1 << 1;
|
|
||||||
const RSVD2 = 1 << 2;
|
|
||||||
const RSVD3 = 1 << 3;
|
|
||||||
const RSVD4 = 1 << 4;
|
|
||||||
const RSVD5 = 1 << 5;
|
|
||||||
const RSVD6 = 1 << 6;
|
|
||||||
const RSVD7 = 1 << 7;
|
|
||||||
|
|
||||||
/// Remove the fd from the caller's file table before sending the message.
|
|
||||||
const CONSUME = 1 << 8;
|
|
||||||
|
|
||||||
const WRITE = 1 << 9;
|
|
||||||
const READ = 1 << 10;
|
|
||||||
|
|
||||||
/// Indicates the request is a bulk fd passing request.
|
|
||||||
const FD = 1 << 11;
|
|
||||||
/// Flags for the fd passing request.
|
|
||||||
const FD_EXCLUSIVE = 1 << 12;
|
|
||||||
const FD_CLONE = 1 << 13;
|
|
||||||
const FD_UPPER = 1 << 14;
|
|
||||||
const FD_CLOEXEC = 1 << 15;
|
|
||||||
|
|
||||||
/// Call is a standard fs call, with metadata defined in `StdFsCallMeta`
|
|
||||||
const STD_FS = 1 << 16;
|
|
||||||
|
|
||||||
/// Call is taking multiple fds as an argument
|
|
||||||
const MULTIPLE_FDS = 1 << 17;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u8)]
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub enum StdFsCallKind {
|
|
||||||
// TODO: remove old syscalls
|
|
||||||
Fchmod = 1,
|
|
||||||
Fchown = 2,
|
|
||||||
Getdents = 3,
|
|
||||||
Fstat = 4,
|
|
||||||
Fstatvfs = 5,
|
|
||||||
Fsync = 6,
|
|
||||||
Ftruncate = 7,
|
|
||||||
Futimens = 8,
|
|
||||||
// 9 reserved in fscall RFC
|
|
||||||
// Unlinkat = 10,
|
|
||||||
Relpathat = 11,
|
|
||||||
Lock = 12,
|
|
||||||
Unlock = 13,
|
|
||||||
GetLock = 14,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdFsCallKind {
|
|
||||||
pub fn try_from_raw(raw: u8) -> Option<Self> {
|
|
||||||
use StdFsCallKind::*;
|
|
||||||
|
|
||||||
// TODO: Use a library where this match can be automated.
|
|
||||||
Some(match raw {
|
|
||||||
1 => Fchmod,
|
|
||||||
2 => Fchown,
|
|
||||||
3 => Getdents,
|
|
||||||
4 => Fstat,
|
|
||||||
5 => Fstatvfs,
|
|
||||||
6 => Fsync,
|
|
||||||
7 => Ftruncate,
|
|
||||||
8 => Futimens,
|
|
||||||
// 9 reserved in fscall RFC
|
|
||||||
// 10 => Unlinkat,
|
|
||||||
11 => Relpathat,
|
|
||||||
12 => Lock,
|
|
||||||
13 => Unlock,
|
|
||||||
14 => GetLock,
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The tag for the fd number in the upper file descriptor table.
|
|
||||||
pub const UPPER_FDTBL_TAG: usize = 1 << (usize::BITS - 2);
|
|
||||||
|
|
||||||
/// The identifier for registering event timeout
|
|
||||||
pub const EVENT_TIMEOUT_ID: usize = usize::MAX - 2;
|
|
||||||
+242
@@ -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
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
use core::{
|
|
||||||
cmp::PartialEq,
|
|
||||||
ops::{BitAnd, BitOr, Not},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub trait Io {
|
|
||||||
type Value: Copy
|
|
||||||
+ PartialEq
|
|
||||||
+ BitAnd<Output = Self::Value>
|
|
||||||
+ BitOr<Output = Self::Value>
|
|
||||||
+ Not<Output = Self::Value>;
|
|
||||||
|
|
||||||
fn read(&self) -> Self::Value;
|
|
||||||
fn write(&mut self, value: Self::Value);
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn readf(&self, flags: Self::Value) -> bool {
|
|
||||||
(self.read() & flags) as Self::Value == flags
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn writef(&mut self, flags: Self::Value, value: bool) {
|
|
||||||
let tmp: Self::Value = match value {
|
|
||||||
true => self.read() | flags,
|
|
||||||
false => self.read() & !flags,
|
|
||||||
};
|
|
||||||
self.write(tmp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ReadOnly<I> {
|
|
||||||
inner: I,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I> ReadOnly<I> {
|
|
||||||
pub const fn new(inner: I) -> ReadOnly<I> {
|
|
||||||
ReadOnly { inner: inner }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I: Io> ReadOnly<I> {
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn read(&self) -> I::Value {
|
|
||||||
self.inner.read()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn readf(&self, flags: I::Value) -> bool {
|
|
||||||
self.inner.readf(flags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct WriteOnly<I> {
|
|
||||||
inner: I,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I> WriteOnly<I> {
|
|
||||||
pub const fn new(inner: I) -> WriteOnly<I> {
|
|
||||||
WriteOnly { inner: inner }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I: Io> WriteOnly<I> {
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn write(&mut self, value: I::Value) {
|
|
||||||
self.inner.write(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn writef(&mut self, flags: I::Value, value: bool) {
|
|
||||||
self.inner.writef(flags, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-165
@@ -1,165 +0,0 @@
|
|||||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
|
||||||
use core::ops::{BitAnd, BitOr, Not};
|
|
||||||
use core::{mem::MaybeUninit, ptr};
|
|
||||||
|
|
||||||
use super::io::Io;
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct Mmio<T> {
|
|
||||||
value: MaybeUninit<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Mmio<T> {
|
|
||||||
pub unsafe fn zeroed() -> Self {
|
|
||||||
Self {
|
|
||||||
value: MaybeUninit::zeroed(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub unsafe fn uninit() -> Self {
|
|
||||||
Self {
|
|
||||||
value: MaybeUninit::uninit(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub const fn from(value: T) -> Self {
|
|
||||||
Self {
|
|
||||||
value: MaybeUninit::new(value),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic implementation (WARNING: requires aligned pointers!)
|
|
||||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
|
||||||
impl<T> Io for Mmio<T>
|
|
||||||
where
|
|
||||||
T: Copy + PartialEq + BitAnd<Output = T> + BitOr<Output = T> + Not<Output = T>,
|
|
||||||
{
|
|
||||||
type Value = T;
|
|
||||||
|
|
||||||
fn read(&self) -> T {
|
|
||||||
unsafe { ptr::read_volatile(ptr::addr_of!(self.value).cast::<T>()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&mut self, value: T) {
|
|
||||||
unsafe { ptr::write_volatile(ptr::addr_of_mut!(self.value).cast::<T>(), value) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// x86 u8 implementation
|
|
||||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
||||||
impl Io for Mmio<u8> {
|
|
||||||
type Value = u8;
|
|
||||||
|
|
||||||
fn read(&self) -> Self::Value {
|
|
||||||
unsafe {
|
|
||||||
let value: Self::Value;
|
|
||||||
let ptr: *const Self::Value = ptr::addr_of!(self.value).cast::<Self::Value>();
|
|
||||||
core::arch::asm!(
|
|
||||||
"mov {}, [{}]",
|
|
||||||
out(reg_byte) value,
|
|
||||||
in(reg) ptr
|
|
||||||
);
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&mut self, value: Self::Value) {
|
|
||||||
unsafe {
|
|
||||||
let ptr: *mut Self::Value = ptr::addr_of_mut!(self.value).cast::<Self::Value>();
|
|
||||||
core::arch::asm!(
|
|
||||||
"mov [{}], {}",
|
|
||||||
in(reg) ptr,
|
|
||||||
in(reg_byte) value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// x86 u16 implementation
|
|
||||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
||||||
impl Io for Mmio<u16> {
|
|
||||||
type Value = u16;
|
|
||||||
|
|
||||||
fn read(&self) -> Self::Value {
|
|
||||||
unsafe {
|
|
||||||
let value: Self::Value;
|
|
||||||
let ptr: *const Self::Value = ptr::addr_of!(self.value).cast::<Self::Value>();
|
|
||||||
core::arch::asm!(
|
|
||||||
"mov {:x}, [{}]",
|
|
||||||
out(reg) value,
|
|
||||||
in(reg) ptr
|
|
||||||
);
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&mut self, value: Self::Value) {
|
|
||||||
unsafe {
|
|
||||||
let ptr: *mut Self::Value = ptr::addr_of_mut!(self.value).cast::<Self::Value>();
|
|
||||||
core::arch::asm!(
|
|
||||||
"mov [{}], {:x}",
|
|
||||||
in(reg) ptr,
|
|
||||||
in(reg) value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// x86 u32 implementation
|
|
||||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
||||||
impl Io for Mmio<u32> {
|
|
||||||
type Value = u32;
|
|
||||||
|
|
||||||
fn read(&self) -> Self::Value {
|
|
||||||
unsafe {
|
|
||||||
let value: Self::Value;
|
|
||||||
let ptr: *const Self::Value = ptr::addr_of!(self.value).cast::<Self::Value>();
|
|
||||||
core::arch::asm!(
|
|
||||||
"mov {:e}, [{}]",
|
|
||||||
out(reg) value,
|
|
||||||
in(reg) ptr
|
|
||||||
);
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&mut self, value: Self::Value) {
|
|
||||||
unsafe {
|
|
||||||
let ptr: *mut Self::Value = ptr::addr_of_mut!(self.value).cast::<Self::Value>();
|
|
||||||
core::arch::asm!(
|
|
||||||
"mov [{}], {:e}",
|
|
||||||
in(reg) ptr,
|
|
||||||
in(reg) value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// x86 u64 implementation (x86_64 only)
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
impl Io for Mmio<u64> {
|
|
||||||
type Value = u64;
|
|
||||||
|
|
||||||
fn read(&self) -> Self::Value {
|
|
||||||
unsafe {
|
|
||||||
let value: Self::Value;
|
|
||||||
let ptr: *const Self::Value = ptr::addr_of!(self.value).cast::<Self::Value>();
|
|
||||||
core::arch::asm!(
|
|
||||||
"mov {:r}, [{}]",
|
|
||||||
out(reg) value,
|
|
||||||
in(reg) ptr
|
|
||||||
);
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&mut self, value: Self::Value) {
|
|
||||||
unsafe {
|
|
||||||
let ptr: *mut Self::Value = ptr::addr_of_mut!(self.value).cast::<Self::Value>();
|
|
||||||
core::arch::asm!(
|
|
||||||
"mov [{}], {:r}",
|
|
||||||
in(reg) ptr,
|
|
||||||
in(reg) value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
//! I/O functions
|
|
||||||
|
|
||||||
pub use self::{io::*, mmio::*};
|
|
||||||
|
|
||||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
||||||
pub use self::pio::*;
|
|
||||||
|
|
||||||
mod io;
|
|
||||||
mod mmio;
|
|
||||||
|
|
||||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
||||||
mod pio;
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
use core::{arch::asm, marker::PhantomData};
|
|
||||||
|
|
||||||
use super::io::Io;
|
|
||||||
|
|
||||||
/// Generic PIO
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct Pio<T> {
|
|
||||||
port: u16,
|
|
||||||
value: PhantomData<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Pio<T> {
|
|
||||||
/// Create a PIO from a given port
|
|
||||||
pub const fn new(port: u16) -> Self {
|
|
||||||
Pio::<T> {
|
|
||||||
port,
|
|
||||||
value: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read/Write for byte PIO
|
|
||||||
impl Io for Pio<u8> {
|
|
||||||
type Value = u8;
|
|
||||||
|
|
||||||
/// Read
|
|
||||||
#[inline(always)]
|
|
||||||
fn read(&self) -> u8 {
|
|
||||||
let value: u8;
|
|
||||||
unsafe {
|
|
||||||
asm!("in al, dx", in("dx") self.port, out("al") value, options(nostack, nomem, preserves_flags));
|
|
||||||
}
|
|
||||||
value
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write
|
|
||||||
#[inline(always)]
|
|
||||||
fn write(&mut self, value: u8) {
|
|
||||||
unsafe {
|
|
||||||
asm!("out dx, al", in("dx") self.port, in("al") value, options(nostack, nomem, preserves_flags));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read/Write for word PIO
|
|
||||||
impl Io for Pio<u16> {
|
|
||||||
type Value = u16;
|
|
||||||
|
|
||||||
/// Read
|
|
||||||
#[inline(always)]
|
|
||||||
fn read(&self) -> u16 {
|
|
||||||
let value: u16;
|
|
||||||
unsafe {
|
|
||||||
asm!("in ax, dx", in("dx") self.port, out("ax") value, options(nostack, nomem, preserves_flags));
|
|
||||||
}
|
|
||||||
value
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write
|
|
||||||
#[inline(always)]
|
|
||||||
fn write(&mut self, value: u16) {
|
|
||||||
unsafe {
|
|
||||||
asm!("out dx, ax", in("dx") self.port, in("ax") value, options(nostack, nomem, preserves_flags));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read/Write for doubleword PIO
|
|
||||||
impl Io for Pio<u32> {
|
|
||||||
type Value = u32;
|
|
||||||
|
|
||||||
/// Read
|
|
||||||
#[inline(always)]
|
|
||||||
fn read(&self) -> u32 {
|
|
||||||
let value: u32;
|
|
||||||
unsafe {
|
|
||||||
asm!("in eax, dx", in("dx") self.port, out("eax") value, options(nostack, nomem, preserves_flags));
|
|
||||||
}
|
|
||||||
value
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write
|
|
||||||
#[inline(always)]
|
|
||||||
fn write(&mut self, value: u32) {
|
|
||||||
unsafe {
|
|
||||||
asm!("out dx, eax", in("dx") self.port, in("eax") value, options(nostack, nomem, preserves_flags));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+104
@@ -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(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
+63
-46
@@ -1,53 +1,70 @@
|
|||||||
#![cfg_attr(not(any(feature = "std", test)), no_std)]
|
#![crate_name = "redoxfs"]
|
||||||
#![allow(unexpected_cfgs)] // why does this even exist?
|
#![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)]
|
||||||
|
|
||||||
#[cfg(test)]
|
extern crate alloc;
|
||||||
extern crate core;
|
|
||||||
|
|
||||||
pub use self::{arch::*, data::*, error::*, flag::*, io::*, number::*};
|
use core::sync::atomic::AtomicUsize;
|
||||||
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
// The alloc log grows by 1 block about every 21 generations
|
||||||
#[path = "arch/aarch64.rs"]
|
pub const ALLOC_GC_THRESHOLD: u64 = 1024;
|
||||||
mod arch;
|
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;
|
||||||
|
|
||||||
#[cfg(target_arch = "riscv64")]
|
pub static IS_UMT: AtomicUsize = AtomicUsize::new(0);
|
||||||
#[path = "arch/riscv64.rs"]
|
|
||||||
mod arch;
|
|
||||||
|
|
||||||
#[cfg(target_arch = "x86")]
|
pub use self::allocator::{AllocEntry, AllocList, Allocator, ReleaseList, ALLOC_LIST_ENTRIES};
|
||||||
#[path = "arch/x86.rs"]
|
#[cfg(feature = "std")]
|
||||||
mod arch;
|
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;
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
mod allocator;
|
||||||
#[path = "arch/x86_64.rs"]
|
#[cfg(feature = "std")]
|
||||||
mod arch;
|
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;
|
||||||
|
|
||||||
/// Function definitions
|
#[cfg(all(feature = "std", test))]
|
||||||
#[cfg(feature = "userspace")]
|
mod tests;
|
||||||
pub mod call;
|
|
||||||
|
|
||||||
#[cfg(feature = "userspace")]
|
|
||||||
pub use call::*;
|
|
||||||
|
|
||||||
/// Complex structures that are used for some system calls
|
|
||||||
pub mod data;
|
|
||||||
|
|
||||||
pub mod dirent;
|
|
||||||
|
|
||||||
/// All errors that can be generated by a system call
|
|
||||||
pub mod error;
|
|
||||||
|
|
||||||
/// Flags used as an argument to many system calls
|
|
||||||
pub mod flag;
|
|
||||||
|
|
||||||
/// Functions for low level hardware control
|
|
||||||
pub mod io;
|
|
||||||
|
|
||||||
/// Call numbers used by each system call
|
|
||||||
pub mod number;
|
|
||||||
|
|
||||||
/// ABI for shared memory based signals
|
|
||||||
pub mod sigabi;
|
|
||||||
|
|
||||||
/// V2 scheme format
|
|
||||||
pub mod schemev2;
|
|
||||||
|
|||||||
@@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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
@@ -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
@@ -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));
|
||||||
|
}
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
pub const SYS_CLASS: usize = 0xF000_0000;
|
|
||||||
pub const SYS_CLASS_PATH: usize = 0x1000_0000;
|
|
||||||
pub const SYS_CLASS_FILE: usize = 0x2000_0000;
|
|
||||||
|
|
||||||
pub const SYS_ARG: usize = 0x0F00_0000;
|
|
||||||
pub const SYS_ARG_SLICE: usize = 0x0100_0000;
|
|
||||||
pub const SYS_ARG_MSLICE: usize = 0x0200_0000;
|
|
||||||
pub const SYS_ARG_PATH: usize = 0x0300_0000;
|
|
||||||
|
|
||||||
pub const SYS_RET: usize = 0x00F0_0000;
|
|
||||||
pub const SYS_RET_FILE: usize = 0x0010_0000;
|
|
||||||
|
|
||||||
pub const SYS_OPENAT: usize = SYS_CLASS_PATH | SYS_RET_FILE | 7;
|
|
||||||
pub const SYS_OPENAT_WITH_FILTER: usize = SYS_CLASS_PATH | SYS_RET_FILE | 985;
|
|
||||||
pub const SYS_UNLINKAT: usize = SYS_CLASS_PATH | 263;
|
|
||||||
pub const SYS_UNLINKAT_WITH_FILTER: usize = SYS_CLASS_PATH | 986;
|
|
||||||
|
|
||||||
pub const SYS_CLOSE: usize = SYS_CLASS_FILE | 6;
|
|
||||||
pub const SYS_DUP: usize = SYS_CLASS_FILE | SYS_RET_FILE | 41;
|
|
||||||
pub const SYS_DUP2: usize = SYS_CLASS_FILE | SYS_RET_FILE | 63;
|
|
||||||
pub const SYS_READ: usize = SYS_CLASS_FILE | SYS_ARG_MSLICE | 3;
|
|
||||||
pub const SYS_READ2: usize = SYS_CLASS_FILE | SYS_ARG_MSLICE | 35;
|
|
||||||
pub const SYS_WRITE: usize = SYS_CLASS_FILE | SYS_ARG_SLICE | 4;
|
|
||||||
pub const SYS_WRITE2: usize = SYS_CLASS_FILE | SYS_ARG_SLICE | 45;
|
|
||||||
pub const SYS_LSEEK: usize = SYS_CLASS_FILE | 19;
|
|
||||||
pub const SYS_FCHMOD: usize = SYS_CLASS_FILE | 94;
|
|
||||||
pub const SYS_FCHOWN: usize = SYS_CLASS_FILE | 207;
|
|
||||||
pub const SYS_FCNTL: usize = SYS_CLASS_FILE | 55;
|
|
||||||
pub const SYS_FEVENT: usize = SYS_CLASS_FILE | 927;
|
|
||||||
|
|
||||||
// SYS_CALL, fd, inout buf ptr, inout buf len, flags, metadata buf ptr, metadata buf len
|
|
||||||
// TODO: new number for SYS_CALL where flags are sent as 6th argument (using syscall6)
|
|
||||||
pub const SYS_CALL: usize = SYS_CLASS_FILE | SYS_ARG_SLICE | SYS_ARG_MSLICE | 0xCA11;
|
|
||||||
|
|
||||||
pub const SYS_SENDFD: usize = SYS_CLASS_FILE | 34;
|
|
||||||
pub const SYS_GETDENTS: usize = SYS_CLASS_FILE | 43;
|
|
||||||
|
|
||||||
// TODO: Rename FMAP/FUNMAP to MMAP/MUNMAP
|
|
||||||
pub const SYS_FMAP: usize = SYS_CLASS_FILE | SYS_ARG_SLICE | 900;
|
|
||||||
// TODO: SYS_FUNMAP should be SYS_CLASS_FILE
|
|
||||||
pub const SYS_FUNMAP: usize = SYS_CLASS_FILE | 92;
|
|
||||||
pub const SYS_MREMAP: usize = 155;
|
|
||||||
|
|
||||||
pub const SYS_FLINK: usize = SYS_CLASS_FILE | SYS_ARG_PATH | 9;
|
|
||||||
pub const SYS_FPATH: usize = SYS_CLASS_FILE | SYS_ARG_MSLICE | 928;
|
|
||||||
pub const SYS_FRENAME: usize = SYS_CLASS_FILE | SYS_ARG_PATH | 38;
|
|
||||||
pub const SYS_FSTAT: usize = SYS_CLASS_FILE | SYS_ARG_MSLICE | 28;
|
|
||||||
pub const SYS_FSTATVFS: usize = SYS_CLASS_FILE | SYS_ARG_MSLICE | 100;
|
|
||||||
pub const SYS_FSYNC: usize = SYS_CLASS_FILE | 118;
|
|
||||||
pub const SYS_FTRUNCATE: usize = SYS_CLASS_FILE | 93;
|
|
||||||
pub const SYS_FUTIMENS: usize = SYS_CLASS_FILE | SYS_ARG_SLICE | 320;
|
|
||||||
|
|
||||||
pub const SYS_CLOCK_GETTIME: usize = 265;
|
|
||||||
pub const SYS_FUTEX: usize = 240;
|
|
||||||
pub const SYS_MPROTECT: usize = 125;
|
|
||||||
pub const SYS_MKNS: usize = 984;
|
|
||||||
pub const SYS_NANOSLEEP: usize = 162;
|
|
||||||
pub const SYS_YIELD: usize = 158;
|
|
||||||
@@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
-213
@@ -1,213 +0,0 @@
|
|||||||
use core::{
|
|
||||||
mem,
|
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
slice,
|
|
||||||
};
|
|
||||||
|
|
||||||
use bitflags::bitflags;
|
|
||||||
|
|
||||||
pub struct CallerCtx {
|
|
||||||
pub pid: usize,
|
|
||||||
pub uid: u32,
|
|
||||||
pub gid: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum OpenResult {
|
|
||||||
ThisScheme { number: usize },
|
|
||||||
OtherScheme { fd: usize },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
pub struct Sqe {
|
|
||||||
pub opcode: u8,
|
|
||||||
pub sqe_flags: SqeFlags,
|
|
||||||
pub _rsvd: u16, // TODO: priority
|
|
||||||
pub tag: u32,
|
|
||||||
pub args: [u64; 6],
|
|
||||||
pub caller: u64,
|
|
||||||
}
|
|
||||||
impl Deref for Sqe {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe { slice::from_raw_parts(self as *const Sqe as *const u8, mem::size_of::<Sqe>()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for Sqe {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe { slice::from_raw_parts_mut(self as *mut Sqe as *mut u8, mem::size_of::<Sqe>()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
pub struct SqeFlags: u8 {
|
|
||||||
// If zero, the message is bidirectional, and the scheme is expected to pass the Ksmsg's
|
|
||||||
// tag field to the Skmsg. Some opcodes require this flag to be set.
|
|
||||||
const ONEWAY = 1;
|
|
||||||
|
|
||||||
// If this flag is set, index 0 of Sqe's args stores the IDs buffer address,
|
|
||||||
// and index 1 stores the IDs buffer length.
|
|
||||||
const MULTIPLE_IDS = 1 << 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
pub struct Cqe {
|
|
||||||
pub flags: u8, // bits 2:0 are CqeOpcode
|
|
||||||
pub extra_raw: [u8; 3],
|
|
||||||
pub tag: u32,
|
|
||||||
pub result: u64,
|
|
||||||
}
|
|
||||||
impl Deref for Cqe {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &[u8] {
|
|
||||||
unsafe { slice::from_raw_parts(self as *const Cqe as *const u8, mem::size_of::<Cqe>()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for Cqe {
|
|
||||||
fn deref_mut(&mut self) -> &mut [u8] {
|
|
||||||
unsafe { slice::from_raw_parts_mut(self as *mut Cqe as *mut u8, mem::size_of::<Cqe>()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
|
||||||
pub struct NewFdFlags: u8 {
|
|
||||||
const POSITIONED = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cqe {
|
|
||||||
pub fn extra(&self) -> u32 {
|
|
||||||
u32::from_ne_bytes([self.extra_raw[0], self.extra_raw[1], self.extra_raw[2], 0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u8)]
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
||||||
pub enum CqeOpcode {
|
|
||||||
RespondRegular,
|
|
||||||
RespondWithFd,
|
|
||||||
SendFevent, // no tag
|
|
||||||
ObtainFd,
|
|
||||||
RespondWithMultipleFds,
|
|
||||||
/// [`SchemeAsync::on_close`] and [`SchemeSync::on_close`] are only called when the last file
|
|
||||||
/// descriptor referring to the file description is closed. To implement traditional POSIX
|
|
||||||
/// advisory file locking, [`CqeOpcode::RespondAndNotifyOnDetach`] is used to notify the scheme
|
|
||||||
/// by sending a [`RequestKind::OnDetach`] request the next time the file description is
|
|
||||||
/// "detached" from a file descriptor. Not done by default to avoid unnecessary IPC.
|
|
||||||
RespondAndNotifyOnDetach,
|
|
||||||
// TODO: ProvideMmap
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CqeOpcode {
|
|
||||||
pub fn try_from_raw(raw: u8) -> Option<Self> {
|
|
||||||
// TODO: Use a library where this match can be automated.
|
|
||||||
Some(match raw {
|
|
||||||
0 => Self::RespondRegular,
|
|
||||||
1 => Self::RespondWithFd,
|
|
||||||
2 => Self::SendFevent,
|
|
||||||
3 => Self::ObtainFd,
|
|
||||||
4 => Self::RespondWithMultipleFds,
|
|
||||||
5 => Self::RespondAndNotifyOnDetach,
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// SqeOpcode
|
|
||||||
#[repr(u8)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum Opcode {
|
|
||||||
Close = 3, // fd
|
|
||||||
Dup = 4, // old fd, buf_ptr, buf_len
|
|
||||||
Read = 5, // fd, buf_ptr, buf_len, TODO offset, TODO flags, _
|
|
||||||
Write = 6, // fd, buf_ptr, buf_len, TODO offset, TODO flags)
|
|
||||||
Fsize = 7, // fd
|
|
||||||
Fchmod = 8, // fd, new mode
|
|
||||||
Fchown = 9, // fd, new uid, new gid
|
|
||||||
Fcntl = 10, // fd, cmd, arg
|
|
||||||
Fevent = 11, // fd, requested mask
|
|
||||||
Sendfd = 12,
|
|
||||||
Fpath = 13, // fd, buf_ptr, buf_len
|
|
||||||
Frename = 14,
|
|
||||||
Fstat = 15, // fd, buf_ptr, buf_len
|
|
||||||
Fstatvfs = 16, // fd, buf_ptr, buf_len
|
|
||||||
Fsync = 17, // fd
|
|
||||||
Ftruncate = 18, // fd, new len
|
|
||||||
Futimens = 19, // fd, times_buf, times_len
|
|
||||||
|
|
||||||
MmapPrep = 20,
|
|
||||||
RequestMmap = 21,
|
|
||||||
Mremap = 22,
|
|
||||||
Munmap = 23,
|
|
||||||
Msync = 24, // TODO
|
|
||||||
|
|
||||||
Cancel = 25, // @tag
|
|
||||||
|
|
||||||
Getdents = 26,
|
|
||||||
CloseMsg = 27,
|
|
||||||
Call = 28,
|
|
||||||
|
|
||||||
OpenAt = 29, // fd, buf_ptr, buf_len, flags
|
|
||||||
Flink = 30,
|
|
||||||
Recvfd = 31,
|
|
||||||
|
|
||||||
UnlinkAt = 32, // fd, path_ptr, path_len (utf8), flags
|
|
||||||
StdFsCall = 33,
|
|
||||||
|
|
||||||
Detach = 34,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Opcode {
|
|
||||||
pub fn try_from_raw(raw: u8) -> Option<Self> {
|
|
||||||
use Opcode::*;
|
|
||||||
|
|
||||||
// TODO: Use a library where this match can be automated.
|
|
||||||
Some(match raw {
|
|
||||||
3 => Close,
|
|
||||||
4 => Dup,
|
|
||||||
5 => Read,
|
|
||||||
6 => Write,
|
|
||||||
7 => Fsize,
|
|
||||||
8 => Fchmod,
|
|
||||||
9 => Fchown,
|
|
||||||
10 => Fcntl,
|
|
||||||
11 => Fevent,
|
|
||||||
12 => Sendfd,
|
|
||||||
13 => Fpath,
|
|
||||||
14 => Frename,
|
|
||||||
15 => Fstat,
|
|
||||||
16 => Fstatvfs,
|
|
||||||
17 => Fsync,
|
|
||||||
18 => Ftruncate,
|
|
||||||
19 => Futimens,
|
|
||||||
|
|
||||||
20 => MmapPrep,
|
|
||||||
21 => RequestMmap,
|
|
||||||
22 => Mremap,
|
|
||||||
23 => Munmap,
|
|
||||||
24 => Msync,
|
|
||||||
|
|
||||||
25 => Cancel,
|
|
||||||
26 => Getdents,
|
|
||||||
27 => CloseMsg,
|
|
||||||
28 => Call,
|
|
||||||
|
|
||||||
29 => OpenAt,
|
|
||||||
30 => Flink,
|
|
||||||
31 => Recvfd,
|
|
||||||
|
|
||||||
32 => UnlinkAt,
|
|
||||||
33 => StdFsCall,
|
|
||||||
34 => Detach,
|
|
||||||
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-340
@@ -1,340 +0,0 @@
|
|||||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
|
||||||
|
|
||||||
/// Signal runtime struct for the entire process
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[repr(C, align(4096))]
|
|
||||||
pub struct SigProcControl {
|
|
||||||
pub pending: AtomicU64,
|
|
||||||
pub actions: [RawAction; 64],
|
|
||||||
pub sender_infos: [AtomicU64; 32],
|
|
||||||
//pub queue: [RealtimeSig; 32], TODO
|
|
||||||
// qhead, qtail TODO
|
|
||||||
}
|
|
||||||
/*#[derive(Debug)]
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct RealtimeSig {
|
|
||||||
pub arg: NonatomicUsize,
|
|
||||||
}*/
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
#[repr(C, align(16))]
|
|
||||||
pub struct RawAction {
|
|
||||||
/// Only two MSBs are interesting for the kernel. If bit 63 is set, signal is ignored. If bit
|
|
||||||
/// 62 is set and the signal is SIGTSTP/SIGTTIN/SIGTTOU, it's equivalent to the action of
|
|
||||||
/// Stop.
|
|
||||||
pub first: AtomicU64,
|
|
||||||
/// Completely ignored by the kernel, but exists so userspace can (when 16-byte atomics exist)
|
|
||||||
/// atomically set both the handler, sigaction flags, and sigaction mask.
|
|
||||||
pub user_data: AtomicU64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Signal runtime struct for a thread
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct Sigcontrol {
|
|
||||||
// composed of [lo "pending" | lo "unmasked", hi "pending" | hi "unmasked"]
|
|
||||||
pub word: [AtomicU64; 2],
|
|
||||||
|
|
||||||
// lo = sender pid, hi = sender ruid
|
|
||||||
pub sender_infos: [AtomicU64; 32],
|
|
||||||
|
|
||||||
pub control_flags: SigatomicUsize,
|
|
||||||
|
|
||||||
pub saved_ip: NonatomicUsize, // rip/eip/pc
|
|
||||||
pub saved_archdep_reg: NonatomicUsize, // rflags(x64)/eflags(x86)/x0(aarch64)/t0(riscv64)
|
|
||||||
}
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub struct SenderInfo {
|
|
||||||
pub pid: u32,
|
|
||||||
pub ruid: u32,
|
|
||||||
}
|
|
||||||
impl SenderInfo {
|
|
||||||
#[inline]
|
|
||||||
pub fn raw(self) -> u64 {
|
|
||||||
u64::from(self.pid) | (u64::from(self.ruid) << 32)
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub const fn from_raw(raw: u64) -> Self {
|
|
||||||
Self {
|
|
||||||
pid: raw as u32,
|
|
||||||
ruid: (raw >> 32) as u32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sigcontrol {
|
|
||||||
pub fn currently_pending_unblocked(&self, proc: &SigProcControl) -> u64 {
|
|
||||||
let proc_pending = proc.pending.load(Ordering::Relaxed);
|
|
||||||
let [w0, w1] = core::array::from_fn(|i| {
|
|
||||||
let w = self.word[i].load(Ordering::Relaxed);
|
|
||||||
((w | (proc_pending >> (i * 32))) & 0xffff_ffff) & (w >> 32)
|
|
||||||
});
|
|
||||||
//core::sync::atomic::fence(Ordering::Acquire);
|
|
||||||
w0 | (w1 << 32)
|
|
||||||
}
|
|
||||||
pub fn set_allowset(&self, new_allowset: u64) -> u64 {
|
|
||||||
//core::sync::atomic::fence(Ordering::Release);
|
|
||||||
let [w0, w1] = self.word.each_ref().map(|w| w.load(Ordering::Relaxed));
|
|
||||||
let old_a0 = w0 & 0xffff_ffff_0000_0000;
|
|
||||||
let old_a1 = w1 & 0xffff_ffff_0000_0000;
|
|
||||||
let new_a0 = (new_allowset & 0xffff_ffff) << 32;
|
|
||||||
let new_a1 = new_allowset & 0xffff_ffff_0000_0000;
|
|
||||||
|
|
||||||
let prev_w0 = self.word[0].fetch_add(new_a0.wrapping_sub(old_a0), Ordering::Relaxed);
|
|
||||||
let prev_w1 = self.word[0].fetch_add(new_a1.wrapping_sub(old_a1), Ordering::Relaxed);
|
|
||||||
//core::sync::atomic::fence(Ordering::Acquire);
|
|
||||||
let up0 = prev_w0 & (prev_w0 >> 32);
|
|
||||||
let up1 = prev_w1 & (prev_w1 >> 32);
|
|
||||||
|
|
||||||
up0 | (up1 << 32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct SigatomicUsize(AtomicUsize);
|
|
||||||
|
|
||||||
impl SigatomicUsize {
|
|
||||||
#[inline]
|
|
||||||
pub fn load(&self, ordering: Ordering) -> usize {
|
|
||||||
let value = self.0.load(Ordering::Relaxed);
|
|
||||||
if ordering != Ordering::Relaxed {
|
|
||||||
core::sync::atomic::compiler_fence(ordering);
|
|
||||||
}
|
|
||||||
value
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn store(&self, value: usize, ordering: Ordering) {
|
|
||||||
if ordering != Ordering::Relaxed {
|
|
||||||
core::sync::atomic::compiler_fence(ordering);
|
|
||||||
}
|
|
||||||
self.0.store(value, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct NonatomicUsize(AtomicUsize);
|
|
||||||
|
|
||||||
impl NonatomicUsize {
|
|
||||||
#[inline]
|
|
||||||
pub const fn new(a: usize) -> Self {
|
|
||||||
Self(AtomicUsize::new(a))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get(&self) -> usize {
|
|
||||||
self.0.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn set(&self, value: usize) {
|
|
||||||
self.0.store(value, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sig_bit(sig: usize) -> u64 {
|
|
||||||
1 << (sig - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Move to redox_rt?
|
|
||||||
impl SigProcControl {
|
|
||||||
/// Checks if `sig` should be ignored based on the current action flags.
|
|
||||||
///
|
|
||||||
/// * `sig` - The signal to check (e.g. `SIGCHLD`).
|
|
||||||
///
|
|
||||||
/// * `stop_or_continue` - Whether the signal is generated because a child
|
|
||||||
/// process stopped (`SIGSTOP`, `SIGTSTP`) or continued (`SIGCONT`). If
|
|
||||||
/// `true` and `sig` is `SIGCHLD`, the signal shall not be delivered if the
|
|
||||||
/// `SA_NOCLDSTOP` flag is set for `SIGCHLD`.
|
|
||||||
pub fn signal_will_ign(&self, sig: usize, stop_or_continue: bool) -> bool {
|
|
||||||
let flags = self.actions[sig - 1].first.load(Ordering::Relaxed);
|
|
||||||
let will_ign = flags & (1 << 63) != 0;
|
|
||||||
let sig_specific = flags & (1 << 62) != 0; // SA_NOCLDSTOP if sig == SIGCHLD
|
|
||||||
|
|
||||||
will_ign || (sig == SIGCHLD && stop_or_continue && sig_specific)
|
|
||||||
}
|
|
||||||
// TODO: Move to redox_rt?
|
|
||||||
pub fn signal_will_stop(&self, sig: usize) -> bool {
|
|
||||||
use crate::flag::*;
|
|
||||||
matches!(sig, SIGTSTP | SIGTTIN | SIGTTOU)
|
|
||||||
&& self.actions[sig - 1].first.load(Ordering::Relaxed) & (1 << 62) != 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "x86"))]
|
|
||||||
pub use core::sync::atomic::AtomicU64;
|
|
||||||
|
|
||||||
use crate::SIGCHLD;
|
|
||||||
|
|
||||||
#[cfg(target_arch = "x86")]
|
|
||||||
pub use self::atomic::AtomicU64;
|
|
||||||
|
|
||||||
#[cfg(target_arch = "x86")]
|
|
||||||
mod atomic {
|
|
||||||
use core::{cell::UnsafeCell, sync::atomic::Ordering};
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct AtomicU64(UnsafeCell<u64>);
|
|
||||||
|
|
||||||
unsafe impl Send for AtomicU64 {}
|
|
||||||
unsafe impl Sync for AtomicU64 {}
|
|
||||||
|
|
||||||
impl AtomicU64 {
|
|
||||||
pub const fn new(inner: u64) -> Self {
|
|
||||||
Self(UnsafeCell::new(inner))
|
|
||||||
}
|
|
||||||
pub fn compare_exchange(
|
|
||||||
&self,
|
|
||||||
old: u64,
|
|
||||||
new: u64,
|
|
||||||
_success: Ordering,
|
|
||||||
_failure: Ordering,
|
|
||||||
) -> Result<u64, u64> {
|
|
||||||
let old_hi = (old >> 32) as u32;
|
|
||||||
let old_lo = old as u32;
|
|
||||||
let new_hi = (new >> 32) as u32;
|
|
||||||
let new_lo = new as u32;
|
|
||||||
let mut out_hi;
|
|
||||||
let mut out_lo;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
core::arch::asm!("lock cmpxchg8b [{}]", in(reg) self.0.get(), inout("edx") old_hi => out_hi, inout("eax") old_lo => out_lo, in("ecx") new_hi, in("ebx") new_lo);
|
|
||||||
}
|
|
||||||
|
|
||||||
if old_hi == out_hi && old_lo == out_lo {
|
|
||||||
Ok(old)
|
|
||||||
} else {
|
|
||||||
Err(u64::from(out_lo) | (u64::from(out_hi) << 32))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn load(&self, ordering: Ordering) -> u64 {
|
|
||||||
match self.compare_exchange(0, 0, ordering, ordering) {
|
|
||||||
Ok(new) => new,
|
|
||||||
Err(new) => new,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn store(&self, new: u64, ordering: Ordering) {
|
|
||||||
let mut old = 0;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match self.compare_exchange(old, new, ordering, Ordering::Relaxed) {
|
|
||||||
Ok(_) => break,
|
|
||||||
Err(new) => {
|
|
||||||
old = new;
|
|
||||||
core::hint::spin_loop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn fetch_update(
|
|
||||||
&self,
|
|
||||||
set_order: Ordering,
|
|
||||||
fetch_order: Ordering,
|
|
||||||
mut f: impl FnMut(u64) -> Option<u64>,
|
|
||||||
) -> Result<u64, u64> {
|
|
||||||
let mut old = self.load(fetch_order);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let new = f(old).ok_or(old)?;
|
|
||||||
match self.compare_exchange(old, new, set_order, Ordering::Relaxed) {
|
|
||||||
Ok(_) => return Ok(new),
|
|
||||||
Err(changed) => {
|
|
||||||
old = changed;
|
|
||||||
core::hint::spin_loop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn fetch_or(&self, bits: u64, order: Ordering) -> u64 {
|
|
||||||
self.fetch_update(order, Ordering::Relaxed, |b| Some(b | bits))
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
pub fn fetch_and(&self, bits: u64, order: Ordering) -> u64 {
|
|
||||||
self.fetch_update(order, Ordering::Relaxed, |b| Some(b & bits))
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
pub fn fetch_add(&self, term: u64, order: Ordering) -> u64 {
|
|
||||||
self.fetch_update(order, Ordering::Relaxed, |b| Some(b.wrapping_add(term)))
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::sync::{
|
|
||||||
atomic::{AtomicU64, Ordering},
|
|
||||||
Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(not(loom))]
|
|
||||||
use std::{sync::Mutex, thread};
|
|
||||||
#[cfg(not(loom))]
|
|
||||||
fn model(f: impl FnOnce()) {
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(loom)]
|
|
||||||
use loom::{model, sync::Mutex, thread};
|
|
||||||
|
|
||||||
use crate::{RawAction, SigProcControl, Sigcontrol};
|
|
||||||
|
|
||||||
struct FakeThread {
|
|
||||||
ctl: Sigcontrol,
|
|
||||||
pctl: SigProcControl,
|
|
||||||
ctxt: Mutex<()>,
|
|
||||||
}
|
|
||||||
impl Default for FakeThread {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
ctl: Sigcontrol::default(),
|
|
||||||
pctl: SigProcControl {
|
|
||||||
pending: AtomicU64::new(0),
|
|
||||||
actions: core::array::from_fn(|_| RawAction::default()),
|
|
||||||
sender_infos: Default::default(),
|
|
||||||
},
|
|
||||||
ctxt: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn singlethread_mask() {
|
|
||||||
model(|| {
|
|
||||||
let fake_thread = Arc::new(FakeThread::default());
|
|
||||||
|
|
||||||
let thread = {
|
|
||||||
let fake_thread = Arc::clone(&fake_thread);
|
|
||||||
|
|
||||||
thread::spawn(move || {
|
|
||||||
fake_thread.ctl.set_allowset(!0);
|
|
||||||
{
|
|
||||||
let _g = fake_thread.ctxt.lock();
|
|
||||||
if fake_thread
|
|
||||||
.ctl
|
|
||||||
.currently_pending_unblocked(&fake_thread.pctl)
|
|
||||||
== 0
|
|
||||||
{
|
|
||||||
drop(_g);
|
|
||||||
thread::park();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
for sig in 1..=64 {
|
|
||||||
let _g = fake_thread.ctxt.lock();
|
|
||||||
|
|
||||||
let idx = sig - 1;
|
|
||||||
let bit = 1 << (idx % 32);
|
|
||||||
|
|
||||||
fake_thread.ctl.word[idx / 32].fetch_or(bit, Ordering::Relaxed);
|
|
||||||
let w = fake_thread.ctl.word[idx / 32].load(Ordering::Relaxed);
|
|
||||||
|
|
||||||
if w & (w >> 32) != 0 {
|
|
||||||
thread.thread().unpark();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
thread.join().unwrap();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1104
File diff suppressed because it is too large
Load Diff
+2053
File diff suppressed because it is too large
Load Diff
+280
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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(())
|
||||||
|
}
|
||||||
@@ -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
@@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user