1 Commits

Author SHA1 Message Date
Red Bear OS ab50acfcc7 Red Bear OS redoxfs baseline from 0.1.0 pre-patched archive 2026-06-27 09:21:43 +03:00
62 changed files with 13835 additions and 2535 deletions
+4 -1
View File
@@ -1 +1,4 @@
/target/
target
image.bin
image
image*
+30
View File
@@ -0,0 +1,30 @@
image: "redoxos/redoxer"
stages:
- build
- test
cache:
paths:
- target/
build:linux:
stage: build
script: cargo +nightly build --verbose
build:redox:
stage: build
script: redoxer build --verbose
test:linux:
stage: test
dependencies:
- build:linux
script: cargo +nightly test --verbose
test:redox:
stage: test
dependencies:
- build:redox
# only run integration test as without KVM unit tests is super slow
script: redoxer test --verbose -- --test '*' -- --nocapture
+16 -1
View File
@@ -1,6 +1,21 @@
sudo: required
language: rust
rust:
- nightly
sudo: false
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
+693 -341
View File
File diff suppressed because it is too large Load Diff
+79 -59
View File
@@ -1,76 +1,96 @@
[package]
name = "userutils"
version = "0.1.0"
edition = "2024"
name = "redoxfs"
description = "The Redox Filesystem"
repository = "https://gitlab.redox-os.org/redox-os/redoxfs"
version = "0.9.0"
license-file = "LICENSE"
readme = "README.md"
authors = ["Jeremy Soller <jackpot51@gmail.com>"]
edition = "2021"
[lib]
name = "redoxfs"
path = "src/lib.rs"
[[bin]]
name = "id"
path = "src/bin/id.rs"
name = "redoxfs"
path = "src/bin/mount.rs"
doc = false
required-features = ["std"]
[[bin]]
name = "getty"
path = "src/bin/getty.rs"
name = "redoxfs-ar"
path = "src/bin/ar.rs"
doc = false
required-features = ["std"]
[[bin]]
name = "groupadd"
path = "src/bin/groupadd.rs"
name = "redoxfs-clone"
path = "src/bin/clone.rs"
doc = false
required-features = ["std"]
[[bin]]
name = "groupdel"
path = "src/bin/groupdel.rs"
name = "redoxfs-mkfs"
path = "src/bin/mkfs.rs"
doc = false
required-features = ["std"]
[[bin]]
name = "groupmod"
path = "src/bin/groupmod.rs"
[[bin]]
name = "login"
path = "src/bin/login.rs"
[[bin]]
name = "passwd"
path = "src/bin/passwd.rs"
[[bin]]
name = "su"
path = "src/bin/su.rs"
[[bin]]
name = "sudo"
path = "src/bin/sudo.rs"
[[bin]]
name = "useradd"
path = "src/bin/useradd.rs"
[[bin]]
name = "userdel"
path = "src/bin/userdel.rs"
[[bin]]
name = "usermod"
path = "src/bin/usermod.rs"
name = "redoxfs-resize"
path = "src/bin/resize.rs"
doc = false
required-features = ["std"]
[dependencies]
clap = "2.33.0"
extra = { git = "https://gitlab.redox-os.org/redox-os/libextra.git" }
orbclient = "0.3.47"
plain = "0.2.3"
redox_liner = "0.5.2"
libredox = { version = "0.1.12", features = ["mkns"] }
redox_termios = "0.1.3"
redox_event = "0.4.3"
redox-scheme = "0.9.0"
redox_syscall = "0.7.0"
redox_users = "0.4.6"
termion = "4"
aes = { version = "0.8", default-features = false }
argon2 = { version = "0.4", default-features = false, features = ["alloc"] }
base64ct = { version = "1", default-features = false }
bitflags = "2"
endian-num = "0.1"
env_logger = { version = "0.11", optional = true }
getrandom = { version = "0.2.5", optional = true }
humansize = { version = "2", optional = true }
libc = "0.2"
serde = { version = "1.0.203", features = ["derive"] }
toml = "0.8.11"
ioslice = "0.6"
log = { version = "0.4.14", default-features = false, optional = true }
lz4_flex = { version = "0.11", default-features = false, features = ["checked-decode"] }
parse-size = { version = "1", optional = true }
range-tree = { version = "0.1", optional = true }
redox_syscall = "0.7.3"
seahash = { version = "4.1.0", default-features = false }
termion = { version = "4", optional = true }
uuid = { version = "1.4", default-features = false }
xts-mode = { version = "0.5", default-features = false }
[features]
default = ["std", "log", "fuse"]
fuse = [
"fuser",
"std",
]
std = [
"env_logger",
"getrandom",
"humansize",
"libredox",
"parse-size",
"range-tree",
"termion",
"uuid/v4",
"redox_syscall/std",
"redox-scheme",
]
[target.'cfg(not(target_os = "redox"))'.dependencies]
fuser = { version = "0.16", optional = true }
[target.'cfg(target_os = "redox")'.dependencies]
redox-rt = { git = "https://gitlab.redox-os.org/redox-os/relibc", default-features = false }
libredox = { version = "0.1.13", optional = true }
redox-path = "0.3.0"
redox-scheme = { version = "0.11.0", optional = true }
[profile.release]
lto = true
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }
[dev-dependencies]
assert_cmd = "2.0.17"
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2016 The Redox developers
Copyright (c) 2016 Jeremy Soller
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+37
View File
@@ -0,0 +1,37 @@
UNAME := $(shell uname)
ifeq ($(UNAME),Darwin)
FUMOUNT=umount
else ifeq ($(UNAME),FreeBSD)
FUMOUNT=sudo umount
else
# Detect which version of the fusermount binary is available.
ifneq (, $(shell which fusermount3))
FUMOUNT=fusermount3 -u
else
FUMOUNT=fusermount -u
endif
endif
image.bin:
cargo build --release --bin redoxfs-mkfs
dd if=/dev/zero of=image.bin bs=1048576 count=1024
target/release/redoxfs-mkfs image.bin
mount: image.bin FORCE
mkdir -p image
cargo build --release --bin redoxfs
target/release/redoxfs image.bin image
unmount: FORCE
sync
-${FUMOUNT} image
rm -rf image
clean: FORCE
sync
-${FUMOUNT} image
rm -rf image image.bin
cargo clean
FORCE:
+48 -19
View File
@@ -1,24 +1,53 @@
# Redox OS user and group utilities.
# RedoxFS
The `userutils` crate contains the utilities for dealing with users and groups in Redox OS.
They are heavily influenced by UNIX and are, when needed, tailored to specific Redox use cases.
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.
These implementations strive to be as simple as possible drawing particular
inspiration by BSD systems. They are indeed small, by choice.
(It's a replacement for [TFS](https://gitlab.redox-os.org/redox-os/tfs))
[![Travis Build Status](https://travis-ci.org/redox-os/userutils.svg?branch=master)](https://travis-ci.org/redox-os/userutils)
Current features:
**Currently included:**
- 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.
- `getty`: Used by `init(8)` to open and initialize the TTY line, read a login name and invoke `login(1)`.
- `id`: Displays user identity.
- `login`: Allows users to login into the system
- `passwd`: Allows users to modify their passwords.
- `su`: Allows users to substitute identity.
- `sudo`: Enables users to execute a command as another user.
- `useradd`: Add a user
- `usermod`: Modify user information
- `userdel`: Delete a user
- `groupadd`: Add a user group
- `groupmod`: Modify group information
- `groupdel`: Remove a user group
Being MIT licensed, RedoxFS can be bundled on GPL-licensed operating systems (Linux, for example).
### Install RedoxFS
```sh
cargo install redoxfs
```
You can also build RedoxFS from this repository.
### Configure your storage device to allow rootless usage
If you are on Linux you need root permission to acess block devices (storage), but it's recommended to run RedoxFS as rootless.
To do that you need to configure your storage device permission to your user with the following command:
```sh
sudo setfacl -m u:your-username:rw /path/to/disk
```
### Create, mount and customize your RedoxFS partition
See [the instructions in the book](https://doc.redox-os.org/book/redoxfs.html) for RedoxFS tooling usage.
Currently RedoxFS tooling are:
- `redoxfs` mount a RedoxFS disk
- `redoxfs-ar` write files to a RedoxFS disk
- `redoxfs-clone` clone a RedoxFS disk
- `redoxfs-mkfs` create an empty RedoxFS disk
- `redoxfs-resize` resize a RedoxFS disk
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
[![crates.io](http://meritbadge.herokuapp.com/redoxfs)](https://crates.io/crates/redoxfs)
[![docs.rs](https://docs.rs/redoxfs/badge.svg)](https://docs.rs/redoxfs)
+4
View File
@@ -0,0 +1,4 @@
target
corpus
artifacts
coverage
+858
View File
@@ -0,0 +1,858 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aes"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
dependencies = [
"anstyle",
"once_cell",
"windows-sys 0.59.0",
]
[[package]]
name = "anyhow"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "arbitrary"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
dependencies = [
"derive_arbitrary",
]
[[package]]
name = "argon2"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db4ce4441f99dbd377ca8a8f57b698c44d0d6e712d8329b5040da5a64aa1ce73"
dependencies = [
"base64ct",
"blake2",
]
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bitflags"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695"
dependencies = [
"jobserver",
"libc",
"once_cell",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "colorchoice"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "cpufeatures"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "derive_arbitrary"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]]
name = "endian-num"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad847bb2094f110bbdd6fa564894ca4556fd978958e93985420d680d3cb6d14"
[[package]]
name = "env_filter"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"jiff",
"log",
]
[[package]]
name = "errno"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "fastrand"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
[[package]]
name = "fuser"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bb29a3ae32279fe3e79a958fe01899f5fb23eadccee919cf88e145b54ed9367"
dependencies = [
"libc",
"log",
"memchr",
"nix",
"page_size",
"smallvec",
"zerocopy",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "humansize"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
dependencies = [
"libm",
]
[[package]]
name = "inout"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
dependencies = [
"generic-array",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "jiff"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ad87c89110f55e4cd4dc2893a9790820206729eaf221555f742d540b0724a0"
dependencies = [
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
"serde",
]
[[package]]
name = "jiff-static"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d076d5b64a7e2fe6f0743f02c43ca4a6725c0f904203bfe276a5b3e793103605"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "jobserver"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e"
dependencies = [
"libc",
]
[[package]]
name = "libc"
version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "libfuzzer-sys"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7"
dependencies = [
"arbitrary",
"cc",
"once_cell",
]
[[package]]
name = "libm"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
name = "libredox"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
dependencies = [
"bitflags",
"libc",
"redox_syscall 0.7.0",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "lz4_flex"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "nix"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "numtoa"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f"
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "page_size"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "parse-size"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "487f2ccd1e17ce8c1bfab3a65c89525af41cfad4c8659021a1e9a2aacd73b89b"
[[package]]
name = "portable-atomic"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
[[package]]
name = "portable-atomic-util"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
"portable-atomic",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "range-tree"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384c2842d4e069d5ccacf5fe1dca4ef8d07a5444329715f0fc3c61813502d4d1"
[[package]]
name = "redox-path"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717"
[[package]]
name = "redox-scheme"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cea2668a5932f878a4298a1d7f8950249bbbb77120fb263da252c589152f5ea"
dependencies = [
"libredox",
"redox_syscall 0.6.0",
]
[[package]]
name = "redox_syscall"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_syscall"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_termios"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb"
[[package]]
name = "redoxfs"
version = "0.8.4"
dependencies = [
"aes",
"argon2",
"base64ct",
"bitflags",
"endian-num",
"env_logger",
"fuser",
"getrandom",
"humansize",
"libc",
"libredox",
"log",
"lz4_flex",
"parse-size",
"range-tree",
"redox-path",
"redox-scheme",
"redox_syscall 0.6.0",
"seahash",
"termion",
"uuid",
"xts-mode",
]
[[package]]
name = "redoxfs-fuzz"
version = "0.0.0"
dependencies = [
"anyhow",
"arbitrary",
"fuser",
"libfuzzer-sys",
"nix",
"redoxfs",
"tempfile",
]
[[package]]
name = "regex"
version = "1.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "rustix"
version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "seahash"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "subtle"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "syn"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
dependencies = [
"cfg-if",
"fastrand",
"rustix",
"windows-sys 0.52.0",
]
[[package]]
name = "termion"
version = "4.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3669a69de26799d6321a5aa713f55f7e2cd37bd47be044b50f2acafc42c122bb"
dependencies = [
"libc",
"libredox",
"numtoa",
"redox_termios",
]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
dependencies = [
"getrandom",
]
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "xts-mode"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cbddb7545ca0b9ffa7bdc653e8743303e1712687a6918ced25f2cdbed42520"
dependencies = [
"byteorder",
"cipher",
]
[[package]]
name = "zerocopy"
version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
+30
View File
@@ -0,0 +1,30 @@
[package]
name = "redoxfs-fuzz"
version = "0.0.0"
publish = false
edition = "2021"
[features]
default = []
log = []
[package.metadata]
cargo-fuzz = true
[dependencies]
anyhow = "1.0.86"
arbitrary = { version = "1.3.2", features = ["derive"] }
fuser = { version = "0.16" }
libfuzzer-sys = "0.4"
nix = { version = "0.29.0", features = ["fs"] }
tempfile = "3.10.1"
[dependencies.redoxfs]
path = ".."
[[bin]]
name = "fuse_fuzz_target"
path = "fuzz_targets/fuse_fuzz_target.rs"
test = false
doc = false
bench = false
+338
View File
@@ -0,0 +1,338 @@
//! Fuzzer that exercises random file system operations against a FUSE-mounted redoxfs.
#![no_main]
use anyhow::{ensure, Result};
use fuser;
use libfuzzer_sys::{arbitrary::Arbitrary, fuzz_target, Corpus};
use nix::sys::statvfs::statvfs;
use std::{
fs::{self, File, FileTimes, OpenOptions},
io::{Read, Seek, SeekFrom, Write},
os::unix::fs::{self as unix_fs, PermissionsExt},
path::{Path, PathBuf},
thread,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use tempfile;
use redoxfs::{mount::fuse::Fuse, DiskSparse, FileSystem};
/// Maximum size for files and buffers. Chosen arbitrarily with fuzzing performance in mind.
const MAX_SIZE: u64 = 10_000_000;
/// Limit on the number of remounts in a single test case. Chosen arbitrarily with fuzzing
/// performance in mind: remounts are costly.
const MAX_MOUNT_SEQUENCES: usize = 3;
/// An operation to be performed by the fuzzer.
#[derive(Arbitrary, Clone, Debug)]
enum Operation {
Chown {
path: PathBuf,
uid: Option<u32>,
gid: Option<u32>,
},
CreateDir {
path: PathBuf,
},
HardLink {
original: PathBuf,
link: PathBuf,
},
Metadata {
path: PathBuf,
},
Read {
path: PathBuf,
},
ReadDir {
path: PathBuf,
},
ReadLink {
path: PathBuf,
},
RemoveDir {
path: PathBuf,
},
RemoveFile {
path: PathBuf,
},
Rename {
from: PathBuf,
to: PathBuf,
},
SeekRead {
path: PathBuf,
seek_pos: u64,
buf_size: usize,
},
SeekWrite {
path: PathBuf,
seek_pos: u64,
buf_size: usize,
},
SetLen {
path: PathBuf,
size: u64,
},
SetPermissions {
path: PathBuf,
readonly: Option<bool>,
mode: Option<u32>,
},
SetTimes {
path: PathBuf,
accessed_since_epoch: Option<Duration>,
modified_since_epoch: Option<Duration>,
},
Statvfs {},
SymLink {
original: PathBuf,
link: PathBuf,
},
Write {
path: PathBuf,
buf_size: usize,
},
}
/// Parameters for mounting the file system and operations to be performed afterwards.
#[derive(Arbitrary, Clone, Debug)]
struct MountSequence {
cleanup: bool,
operations: Vec<Operation>,
}
/// The whole input to a single fuzzer invocation.
#[derive(Arbitrary, Clone, Debug)]
struct TestCase {
disk_size: u64,
reserved_size: u64,
mount_sequences: Vec<MountSequence>,
}
/// Creates the disk for backing the Redoxfs.
fn create_disk(temp_path: &Path, disk_size: u64) -> DiskSparse {
let disk_path = temp_path.join("disk.img");
DiskSparse::create(disk_path, disk_size).unwrap()
}
/// Creates an empty Redoxfs.
fn create_redoxfs(disk: DiskSparse, reserved_size: u64) -> bool {
let password = None;
let reserved = vec![0; reserved_size as usize];
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
FileSystem::create_reserved(
disk,
password,
&reserved,
ctime.as_secs(),
ctime.subsec_nanos(),
)
.is_ok()
}
/// Mounts an existing Redoxfs, runs the callback and performs the unmount.
fn with_redoxfs_mount<F>(temp_path: &Path, disk: DiskSparse, cleanup: bool, callback: F)
where
F: FnOnce(&Path) + Send + 'static,
{
let password = None;
let block = None;
let mut fs = FileSystem::open(disk, password, block, cleanup).unwrap();
let mount_path = temp_path.join("mount");
fs::create_dir_all(&mount_path).unwrap();
let mut session = fuser::Session::new(Fuse { fs: &mut fs }, &mount_path, &[]).unwrap();
let mut unmounter = session.unmount_callable();
let join_handle = thread::spawn(move || {
callback(&mount_path);
unmounter.unmount().unwrap();
});
session.run().unwrap();
join_handle.join().unwrap();
}
fn get_path_within_fs(fs_path: &Path, path_to_add: &Path) -> Result<PathBuf> {
ensure!(path_to_add.is_relative());
ensure!(path_to_add
.components()
.all(|c| c != std::path::Component::ParentDir));
Ok(fs_path.join(path_to_add))
}
fn do_operation(fs_path: &Path, op: &Operation) -> Result<()> {
match op {
Operation::Chown { path, uid, gid } => {
let path = get_path_within_fs(fs_path, path)?;
unix_fs::chown(path, *uid, *gid)?;
}
Operation::CreateDir { path } => {
let path = get_path_within_fs(fs_path, path)?;
fs::create_dir(path)?;
}
Operation::HardLink { original, link } => {
let original = get_path_within_fs(fs_path, original)?;
let link = get_path_within_fs(fs_path, link)?;
fs::hard_link(original, link)?;
}
Operation::Metadata { path } => {
let path = get_path_within_fs(fs_path, path)?;
fs::metadata(path)?;
}
Operation::Read { path } => {
let path = get_path_within_fs(fs_path, path)?;
fs::read(path)?;
}
Operation::ReadDir { path } => {
let path = get_path_within_fs(fs_path, path)?;
let _ = fs::read_dir(path)?.count();
}
Operation::ReadLink { path } => {
let path = get_path_within_fs(fs_path, path)?;
fs::read_link(path)?;
}
Operation::RemoveDir { path } => {
let path = get_path_within_fs(fs_path, path)?;
fs::remove_dir(path)?;
}
Operation::RemoveFile { path } => {
let path = get_path_within_fs(fs_path, path)?;
fs::remove_file(path)?;
}
Operation::Rename { from, to } => {
let from = get_path_within_fs(fs_path, from)?;
let to = get_path_within_fs(fs_path, to)?;
fs::rename(from, to)?;
}
Operation::SeekRead {
path,
seek_pos,
buf_size,
} => {
ensure!(*buf_size as u64 <= MAX_SIZE);
let path = get_path_within_fs(fs_path, path)?;
let mut file = File::open(path)?;
file.seek(SeekFrom::Start(*seek_pos))?;
let mut buf = vec![0; *buf_size];
file.read(&mut buf)?;
}
Operation::SeekWrite {
path,
seek_pos,
buf_size,
} => {
ensure!(*seek_pos <= MAX_SIZE);
ensure!(*buf_size as u64 <= MAX_SIZE);
let path = get_path_within_fs(fs_path, path)?;
let mut file = OpenOptions::new().write(true).open(path)?;
file.seek(SeekFrom::Start(*seek_pos))?;
let buf = vec![0; *buf_size];
file.write(&buf)?;
}
Operation::SetLen { path, size } => {
let path = get_path_within_fs(fs_path, path)?;
let file = OpenOptions::new().write(true).open(path)?;
file.set_len(*size)?;
}
Operation::SetPermissions {
path,
readonly,
mode,
} => {
let path = get_path_within_fs(fs_path, path)?;
let metadata = fs::metadata(&path)?;
let mut perms = metadata.permissions();
if let Some(readonly) = readonly {
perms.set_readonly(*readonly);
}
if let Some(mode) = mode {
perms.set_mode(*mode);
}
fs::set_permissions(path, perms)?;
}
Operation::SetTimes {
path,
accessed_since_epoch,
modified_since_epoch,
} => {
let path = get_path_within_fs(fs_path, path)?;
let file = File::options().write(true).open(path)?;
let mut times = FileTimes::new();
if let Some(accessed_since_epoch) = accessed_since_epoch {
if let Some(accessed) = UNIX_EPOCH.checked_add(*accessed_since_epoch) {
times = times.set_accessed(accessed);
}
}
if let Some(modified_since_epoch) = modified_since_epoch {
if let Some(modified) = UNIX_EPOCH.checked_add(*modified_since_epoch) {
times = times.set_modified(modified);
}
}
file.set_times(times)?;
}
Operation::Statvfs {} => {
statvfs(fs_path)?;
}
Operation::SymLink { original, link } => {
let original = get_path_within_fs(fs_path, original)?;
let link = get_path_within_fs(fs_path, link)?;
unix_fs::symlink(original, link)?;
}
Operation::Write { path, buf_size } => {
ensure!(*buf_size as u64 <= MAX_SIZE);
let path = get_path_within_fs(fs_path, path)?;
let buf = vec![0; *buf_size];
fs::write(path, &buf)?;
}
}
Ok(())
}
fuzz_target!(|test_case: TestCase| -> Corpus {
if test_case.disk_size > MAX_SIZE
|| test_case.reserved_size > MAX_SIZE
|| test_case.mount_sequences.len() > MAX_MOUNT_SEQUENCES
{
return Corpus::Reject;
}
let temp_dir = tempfile::Builder::new()
.prefix("fuse_fuzz_target")
.tempdir()
.unwrap();
#[cfg(feature = "log")]
eprintln!("create fs");
let disk = create_disk(temp_dir.path(), test_case.disk_size);
if !create_redoxfs(disk, test_case.reserved_size) {
// File system creation failed (e.g., due to insufficient space) so we bail out, still
// exercising this code path is useful.
return Corpus::Keep;
}
for mount_seq in test_case.mount_sequences.iter() {
#[cfg(feature = "log")]
eprintln!("mount fs: path {:?}, size{}", temp_dir.path(), test_case.disk_size);
let disk = create_disk(temp_dir.path(), test_case.disk_size);
let operations = mount_seq.operations.clone();
with_redoxfs_mount(temp_dir.path(), disk, mount_seq.cleanup, move |fs_path| {
for operation in operations.iter() {
#[cfg(feature = "log")]
eprintln!("do operation {operation:?}");
let _result = do_operation(fs_path, operation);
#[cfg(feature = "log")]
eprintln!("operation result {:?}", _result.err());
}
});
#[cfg(feature = "log")]
eprintln!("unmounted fs");
}
Corpus::Keep
});
-6
View File
@@ -1,6 +0,0 @@
########## Red Bear OS #########
# Login with the following: #
# `user` #
# `root`:`password` #
################################
-6
View File
@@ -1,6 +0,0 @@
########## Redox OS ##########
# Login with the following: #
# `user` #
# `root`:`password` #
##############################
-2
View File
@@ -1,2 +0,0 @@
Welcome to Red Bear OS!
+394
View File
@@ -0,0 +1,394 @@
use alloc::{collections::BTreeSet, vec::Vec};
use core::{fmt, mem, ops, slice};
use endian_num::Le;
use crate::{BlockAddr, BlockLevel, BlockMeta, BlockPtr, BlockTrait, Node, TreePtr, BLOCK_SIZE};
pub const ALLOC_LIST_ENTRIES: usize =
(BLOCK_SIZE as usize - mem::size_of::<BlockPtr<AllocList>>()) / mem::size_of::<AllocEntry>();
pub const RELEASE_LIST_ENTRIES: usize = (BLOCK_SIZE as usize
- mem::size_of::<BlockPtr<ReleaseList>>())
/ mem::size_of::<TreePtr<Node>>();
/// The RedoxFS block allocator. This struct manages all "data" blocks in RedoxFS
/// (i.e, all blocks that aren't reserved or part of the header chain).
///
/// [`Allocator`] can allocate blocks of many "levels"---that is, it can
/// allocate multiple consecutive [`BLOCK_SIZE`] blocks in one operation.
///
/// This reduces the amount of memory that the [`Allocator`] uses:
/// Instead of storing the index of each free [`BLOCK_SIZE`] block,
/// the `levels` array can keep track of higher-level blocks, splitting
/// them when a smaller block is requested.
///
/// Higher-level blocks also allow us to more efficiently allocate memory
/// for large files.
#[derive(Clone, Default)]
pub struct Allocator {
/// This array keeps track of all free blocks of each level,
/// and is initialized using the AllocList chain when we open the filesystem.
///
/// Every element of the outer array represents a block level:
/// - item 0: free level 0 blocks (with size [`BLOCK_SIZE`])
/// - item 1: free level 1 blocks (with size 2*[`BLOCK_SIZE`])
/// - item 2: free level 2 blocks (with size 4*[`BLOCK_SIZE`])
/// ...and so on.
///
/// Each inner array contains a list of free block indices,
levels: Vec<BTreeSet<u64>>,
}
impl Allocator {
pub fn levels(&self) -> &Vec<BTreeSet<u64>> {
&self.levels
}
/// Count the number of free [`BLOCK_SIZE`] available to this [`Allocator`].
pub fn free(&self) -> u64 {
let mut free = 0;
for level in 0..self.levels.len() {
let level_size = 1 << level;
free += self.levels[level].len() as u64 * level_size;
}
free
}
/// Find a free block of the given level, mark it as "used", and return its address.
/// Returns [`None`] if there are no free blocks with this level.
pub fn allocate(&mut self, meta: BlockMeta) -> Option<BlockAddr> {
// First, find the lowest level with a free block
let mut free_opt = None;
{
let mut level = meta.level.0;
// Start searching at the level we want. Smaller levels are too small!
while level < self.levels.len() {
if let Some(&index) = self.levels[level].first() {
// Find the index closest to the start of the filesystem
free_opt = match free_opt {
Some((free_level, free_index)) if free_index <= index => {
Some((free_level, free_index))
}
_ => Some((level, index)),
};
}
level += 1;
}
}
// If a free block was found, split it until we find a usable block of the right level.
// The left side of the split block is kept free, and the right side is allocated.
let (mut level, index) = free_opt?;
self.levels[level].remove(&index);
while level > meta.level.0 {
level -= 1;
let level_size = 1 << level;
self.levels[level].insert(index + level_size);
}
Some(unsafe { BlockAddr::new(index, meta) })
}
/// Try to allocate the exact block specified, making all necessary splits.
/// Returns [`None`] if this some (or all) of this block is already allocated.
///
/// Note that [`BlockAddr`] encodes the blocks location _and_ level.
pub fn allocate_exact(&mut self, exact_addr: BlockAddr) -> Option<BlockAddr> {
// This function only supports level 0 right now
assert_eq!(exact_addr.level().0, 0);
let exact_index = exact_addr.index();
let mut index_opt = None;
// Go from the highest to the lowest level
for level in (0..self.levels.len()).rev() {
let level_size = 1 << level;
// Split higher block if found
if let Some(index) = index_opt.take() {
self.levels[level].insert(index);
self.levels[level].insert(index + level_size);
}
// Look for matching block and remove it
for &start in self.levels[level].iter() {
if start <= exact_index {
let end = start + level_size;
if end > exact_index {
self.levels[level].remove(&start);
index_opt = Some(start);
break;
}
}
}
}
Some(unsafe { BlockAddr::new(index_opt?, exact_addr.meta()) })
}
/// Deallocate the given block, marking it "free" so that it can be re-used later.
pub fn deallocate(&mut self, addr: BlockAddr) {
// When we deallocate, we check if block we're deallocating has a free sibling.
// If it does, we join the two to create one free block in the next (higher) level.
//
// We repeat this until we no longer have a sibling to join.
let mut index = addr.index();
let mut level = addr.level().0;
loop {
while level >= self.levels.len() {
self.levels.push(BTreeSet::new());
}
let level_size = 1 << level;
let next_size = level_size << 1;
let mut found = false;
// look at all free blocks in the current level...
for &level_index in self.levels[level].iter() {
// - the block we just freed aligns with the next largest block, and
// - the second block we're looking at is the right sibling of this block
if index % next_size == 0 && index + level_size == level_index {
// "alloc" the next highest block, repeat deallocation process.
self.levels[level].remove(&level_index);
found = true;
break;
// - the index of this block doesn't align with the next largest block, and
// - the block we're looking at is the left neighbor of this block
} else if level_index % next_size == 0 && level_index + level_size == index {
// "alloc" the next highest block, repeat deallocation process.
self.levels[level].remove(&level_index);
index = level_index; // index moves to left block
found = true;
break;
}
}
// We couldn't find a higher block,
// deallocate this one and finish
if !found {
self.levels[level].insert(index);
return;
}
// repeat deallocation process on the
// higher-level block we just created.
level += 1;
}
}
}
#[repr(C, packed)]
#[derive(Clone, Copy, Default, Debug)]
pub struct AllocEntry {
/// The index of the first block this [`AllocEntry`] refers to
index: Le<u64>,
/// The number of blocks after (and including) `index` that are are free or used.
/// If negative, they are used; if positive, they are free.
count: Le<i64>,
}
impl AllocEntry {
pub fn new(index: u64, count: i64) -> Self {
Self {
index: index.into(),
count: count.into(),
}
}
pub fn allocate(addr: BlockAddr) -> Self {
Self::new(addr.index(), -addr.level().blocks::<i64>())
}
pub fn deallocate(addr: BlockAddr) -> Self {
Self::new(addr.index(), addr.level().blocks::<i64>())
}
pub fn index(&self) -> u64 {
self.index.to_ne()
}
pub fn count(&self) -> i64 {
self.count.to_ne()
}
pub fn is_null(&self) -> bool {
self.count() == 0
}
}
/// A node in the allocation chain.
#[repr(C, packed)]
pub struct AllocList {
/// A pointer to the previous AllocList.
/// If this is the null pointer, this is the first element of the chain.
pub prev: BlockPtr<AllocList>,
/// Allocation entries.
pub entries: [AllocEntry; ALLOC_LIST_ENTRIES],
}
unsafe impl BlockTrait for AllocList {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self {
prev: BlockPtr::default(),
entries: [AllocEntry::default(); ALLOC_LIST_ENTRIES],
})
} else {
None
}
}
}
impl fmt::Debug for AllocList {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let prev = self.prev;
let entries: Vec<&AllocEntry> = self
.entries
.iter()
.filter(|entry| entry.count() > 0)
.collect();
f.debug_struct("AllocList")
.field("prev", &prev)
.field("entries", &entries)
.finish()
}
}
impl ops::Deref for AllocList {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const AllocList as *const u8,
mem::size_of::<AllocList>(),
) as &[u8]
}
}
}
impl ops::DerefMut for AllocList {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut AllocList as *mut u8,
mem::size_of::<AllocList>(),
) as &mut [u8]
}
}
}
/// A list of nodes pending release.
#[repr(C, packed)]
pub struct ReleaseList {
/// A pointer to the previous ReleaseList.
/// If this is the null pointer, this is the first element of the chain.
pub prev: BlockPtr<ReleaseList>,
/// Allocation entries.
pub entries: [TreePtr<Node>; RELEASE_LIST_ENTRIES],
}
unsafe impl BlockTrait for ReleaseList {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self {
prev: BlockPtr::default(),
entries: [TreePtr::<Node>::default(); RELEASE_LIST_ENTRIES],
})
} else {
None
}
}
}
impl fmt::Debug for ReleaseList {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let prev = self.prev;
let entries: Vec<_> = self
.entries
.iter()
.filter(|entry| !entry.is_null())
.map(|entry| entry.id())
.collect();
f.debug_struct("ReleaseList")
.field("prev", &prev)
.field("entries", &entries)
.finish()
}
}
impl ops::Deref for ReleaseList {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const ReleaseList as *const u8,
mem::size_of::<ReleaseList>(),
) as &[u8]
}
}
}
impl ops::DerefMut for ReleaseList {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut ReleaseList as *mut u8,
mem::size_of::<ReleaseList>(),
) as &mut [u8]
}
}
}
#[test]
fn alloc_node_size_test() {
assert_eq!(mem::size_of::<AllocList>(), crate::BLOCK_SIZE as usize);
}
#[test]
fn release_node_size_test() {
assert_eq!(mem::size_of::<ReleaseList>(), crate::BLOCK_SIZE as usize);
}
#[test]
fn allocator_test() {
let mut alloc = Allocator::default();
assert_eq!(alloc.allocate(BlockMeta::default()), None);
alloc.deallocate(unsafe { BlockAddr::new(1, BlockMeta::default()) });
assert_eq!(
alloc.allocate(BlockMeta::default()),
Some(unsafe { BlockAddr::new(1, BlockMeta::default()) })
);
assert_eq!(alloc.allocate(BlockMeta::default()), None);
for addr in 1023..2048 {
alloc.deallocate(unsafe { BlockAddr::new(addr, BlockMeta::default()) });
}
assert_eq!(alloc.levels.len(), 11);
for level in 0..alloc.levels.len() {
if level == 0 {
assert_eq!(alloc.levels[level], [1023].into());
} else if level == 10 {
assert_eq!(alloc.levels[level], [1024].into());
} else {
assert_eq!(alloc.levels[level], [0u64; 0].into());
}
}
for addr in 1023..2048 {
assert_eq!(
alloc.allocate(BlockMeta::default()),
Some(unsafe { BlockAddr::new(addr, BlockMeta::default()) })
);
}
assert_eq!(alloc.allocate(BlockMeta::default()), None);
assert_eq!(alloc.levels.len(), 11);
for level in 0..alloc.levels.len() {
assert_eq!(alloc.levels[level], [0u64; 0].into());
}
}
+146
View File
@@ -0,0 +1,146 @@
use std::fs;
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::MetadataExt;
use std::path::Path;
use crate::{Disk, FileSystem, Node, Transaction, TreePtr, BLOCK_SIZE};
fn syscall_err(err: syscall::Error) -> io::Error {
io::Error::from_raw_os_error(err.errno)
}
pub fn archive_at<D: Disk, P: AsRef<Path>>(
tx: &mut Transaction<D>,
parent_path: P,
parent_ptr: TreePtr<Node>,
) -> io::Result<()> {
for entry_res in fs::read_dir(parent_path)? {
let entry = entry_res?;
let metadata = entry.metadata()?;
let file_type = metadata.file_type();
let name = entry.file_name().into_string().map_err(|_| {
io::Error::new(io::ErrorKind::InvalidData, "filename is not valid UTF-8")
})?;
let mode_type = if file_type.is_dir() {
Node::MODE_DIR
} else if file_type.is_file() {
Node::MODE_FILE
} else if file_type.is_symlink() {
Node::MODE_SYMLINK
} else {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("Does not support parsing {:?}", file_type),
));
};
let node_ptr;
{
let mode = mode_type | (metadata.mode() as u16 & Node::MODE_PERM);
let mut node = tx
.create_node(
parent_ptr,
&name,
mode,
metadata.ctime() as u64,
metadata.ctime_nsec() as u32,
)
.map_err(syscall_err)?;
node_ptr = node.ptr();
if node.data().uid() != metadata.uid() || node.data().gid() != metadata.gid() {
node.data_mut().set_uid(metadata.uid());
node.data_mut().set_gid(metadata.gid());
tx.sync_tree(node).map_err(syscall_err)?;
}
}
let path = entry.path();
if file_type.is_dir() {
archive_at(tx, path, node_ptr)?;
} else if file_type.is_file() {
let data = fs::read(path)?;
let count = tx
.write_node(
node_ptr,
0,
&data,
metadata.mtime() as u64,
metadata.mtime_nsec() as u32,
)
.map_err(syscall_err)?;
if count != data.len() {
panic!("file write count {} != {}", count, data.len());
}
} else if file_type.is_symlink() {
let destination = fs::read_link(path)?;
let data = destination.as_os_str().as_bytes();
let count = tx
.write_node(
node_ptr,
0,
data,
metadata.mtime() as u64,
metadata.mtime_nsec() as u32,
)
.map_err(syscall_err)?;
if count != data.len() {
panic!("symlink write count {} != {}", count, data.len());
}
} else {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("Does not support creating {:?}", file_type),
));
}
}
Ok(())
}
pub fn archive<D: Disk, P: AsRef<Path>>(fs: &mut FileSystem<D>, parent_path: P) -> io::Result<u64> {
let end_block = fs
.tx(|tx| {
// Archive_at root node
archive_at(tx, parent_path, TreePtr::root())
.map_err(|err| syscall::Error::new(err.raw_os_error().unwrap()))?;
// Squash alloc log
tx.sync(true)?;
let end_block = tx.header.size() / BLOCK_SIZE;
/* TODO: Cut off any free blocks at the end of the filesystem
let mut end_changed = true;
while end_changed {
end_changed = false;
let allocator = fs.allocator();
let levels = allocator.levels();
for level in 0..levels.len() {
let level_size = 1 << level;
for &block in levels[level].iter() {
if block < end_block && block + level_size >= end_block {
end_block = block;
end_changed = true;
}
}
}
}
*/
// Update header
tx.header.size = (end_block * BLOCK_SIZE).into();
tx.header_changed = true;
tx.sync(false)?;
Ok(end_block)
})
.map_err(syscall_err)?;
Ok((fs.block + end_block) * BLOCK_SIZE)
}
+108
View File
@@ -0,0 +1,108 @@
extern crate redoxfs;
extern crate syscall;
extern crate uuid;
use std::io::Read;
use std::time::{SystemTime, UNIX_EPOCH};
use std::{env, fs, process};
use redoxfs::{archive, DiskFile, FileSystem};
use uuid::Uuid;
fn main() {
env_logger::init();
let mut args = env::args().skip(1);
let disk_path = if let Some(path) = args.next() {
path
} else {
println!("redoxfs-ar: no disk image provided");
println!("redoxfs-ar DISK FOLDER [BOOTLOADER]");
process::exit(1);
};
let folder_path = if let Some(path) = args.next() {
path
} else {
println!("redoxfs-ar: no folder provided");
println!("redoxfs-ar DISK FOLDER [BOOTLOADER]");
process::exit(1);
};
let bootloader_path_opt = args.next();
let disk = match DiskFile::open(&disk_path) {
Ok(disk) => disk,
Err(err) => {
println!("redoxfs-ar: failed to open image {}: {}", disk_path, err);
process::exit(1);
}
};
let mut bootloader = vec![];
if let Some(bootloader_path) = bootloader_path_opt {
match fs::File::open(&bootloader_path) {
Ok(mut file) => match file.read_to_end(&mut bootloader) {
Ok(_) => (),
Err(err) => {
println!(
"redoxfs-ar: failed to read bootloader {}: {}",
bootloader_path, err
);
process::exit(1);
}
},
Err(err) => {
println!(
"redoxfs-ar: failed to open bootloader {}: {}",
bootloader_path, err
);
process::exit(1);
}
}
};
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
match FileSystem::create_reserved(
disk,
None,
&bootloader,
ctime.as_secs(),
ctime.subsec_nanos(),
) {
Ok(mut fs) => {
let size = match archive(&mut fs, &folder_path) {
Ok(ok) => ok,
Err(err) => {
println!("redoxfs-ar: failed to archive {}: {}", folder_path, err);
process::exit(1);
}
};
if let Err(err) = fs.disk.file.set_len(size) {
println!(
"redoxfs-ar: failed to truncate {} to {}: {}",
disk_path, size, err
);
process::exit(1);
}
let uuid = Uuid::from_bytes(fs.header.uuid());
println!(
"redoxfs-ar: created filesystem on {}, reserved {} blocks, size {} MB, uuid {}",
disk_path,
fs.block,
fs.header.size() / 1000 / 1000,
uuid.hyphenated()
);
}
Err(err) => {
println!(
"redoxfs-ar: failed to create filesystem on {}: {}",
disk_path, err
);
process::exit(1);
}
};
}
+153
View File
@@ -0,0 +1,153 @@
extern crate redoxfs;
extern crate syscall;
extern crate uuid;
use std::io::Read;
use std::time::{SystemTime, UNIX_EPOCH};
use std::{env, fs, process};
use redoxfs::{clone, DiskFile, FileSystem};
use uuid::Uuid;
fn main() {
env_logger::init();
let mut args = env::args().skip(1);
let disk_path_old = if let Some(path) = args.next() {
path
} else {
println!("redoxfs-clone: no old disk image provided");
println!("redoxfs-clone NEW-DISK OLD-DISK [BOOTLOADER]");
process::exit(1);
};
let disk_path = if let Some(path) = args.next() {
path
} else {
println!("redoxfs-clone: no new disk image provided");
println!("redoxfs-clone NEW-DISK OLD-DISK [BOOTLOADER]");
process::exit(1);
};
let bootloader_path_opt = args.next();
// Open old disk in readonly mode
let disk_old = match fs::OpenOptions::new()
.read(true)
.write(false)
.open(&disk_path_old)
.map(DiskFile::from)
{
Ok(disk) => disk,
Err(err) => {
println!(
"redoxfs-clone: failed to open old disk image {}: {}",
disk_path_old, err
);
process::exit(1);
}
};
let mut fs_old = match FileSystem::open(disk_old, None, None, false) {
Ok(fs) => fs,
Err(err) => {
println!(
"redoxfs-clone: failed to open filesystem on {}: {}",
disk_path_old, err
);
process::exit(1);
}
};
let disk = match DiskFile::open(&disk_path) {
Ok(disk) => disk,
Err(err) => {
println!(
"redoxfs-clone: failed to open new disk image {}: {}",
disk_path, err
);
process::exit(1);
}
};
let mut bootloader = vec![];
if let Some(bootloader_path) = bootloader_path_opt {
match fs::File::open(&bootloader_path) {
Ok(mut file) => match file.read_to_end(&mut bootloader) {
Ok(_) => (),
Err(err) => {
println!(
"redoxfs-clone: failed to read bootloader {}: {}",
bootloader_path, err
);
process::exit(1);
}
},
Err(err) => {
println!(
"redoxfs-clone: failed to open bootloader {}: {}",
bootloader_path, err
);
process::exit(1);
}
}
};
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
let mut fs = match FileSystem::create_reserved(
disk,
None,
&bootloader,
ctime.as_secs(),
ctime.subsec_nanos(),
) {
Ok(fs) => fs,
Err(err) => {
println!(
"redoxfs-clone: failed to create filesystem on {}: {}",
disk_path, err
);
process::exit(1);
}
};
let size_old = fs_old.header.size();
let free_old = fs_old.allocator().free() * redoxfs::BLOCK_SIZE;
let used_old = size_old - free_old;
let mut last_percent = 0;
let clone_res = clone(&mut fs_old, &mut fs, move |used| {
let percent = (used * 100) / used_old;
if percent != last_percent {
eprint!(
"\r{}%: {} MB/{} MB",
percent,
used / 1000 / 1000,
used_old / 1000 / 1000
);
last_percent = percent;
}
});
eprintln!();
match clone_res {
Ok(()) => (),
Err(err) => {
println!(
"redoxfs-clone: failed to clone {} to {}: {}",
disk_path_old, disk_path, err
);
process::exit(1);
}
}
let uuid = Uuid::from_bytes(fs.header.uuid());
let size = fs.header.size();
let free = fs.allocator().free() * redoxfs::BLOCK_SIZE;
let used = size - free;
println!("redoxfs-clone: created filesystem on {}", disk_path,);
println!("\treserved: {} blocks", fs.block);
println!("\tuuid: {}", uuid.hyphenated());
println!("\tsize: {} MB", size / 1000 / 1000);
println!("\tused: {} MB", used / 1000 / 1000);
println!("\tfree: {} MB", free / 1000 / 1000);
}
-285
View File
@@ -1,285 +0,0 @@
#[macro_use]
extern crate clap;
use std::error::Error;
use std::fs::File;
use std::io::{self, ErrorKind, Read, Stderr, Write};
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::process::{Child, Command, Stdio};
use std::str;
use std::time::{Duration, Instant};
use event::{EventFlags, RawEventQueue};
use extra::io::fail;
use libredox::call as redox;
use libredox::errno::EAGAIN;
use libredox::flag;
const _MAN_PAGE: &'static str = /* @MANSTART{getty} */
r#"
NAME
getty - set terminal mode
SYNOPSIS
getty [-J | --noclear | -C | --contain ] tty
getty [ -h | --help ]
DESCRIPTION
The getty utility is called by init(8) to open and initialize the tty line,
read a login name, and invoke login(1).
OPTIONS
-h, --help
Display this help and exit.
-J, --noclear
Do not clear the screen before forking login(1).
-C, --contain
Run contain_login instead of login
AUTHOR
Written by Jeremy Soller.
"#; /* @MANEND */
const DEFAULT_COLS: u16 = 80;
const DEFAULT_LINES: u16 = 30;
pub fn handle(
event_queue: &mut RawEventQueue,
tty_fd: RawFd,
master_fd: RawFd,
process: &mut Child,
) {
// tty_fd => Display
// master_fd => PTY
let handle_event = |event_id: usize| {
if event_id as RawFd == tty_fd {
let mut packet = [0; 4096];
loop {
let count = match redox::read(tty_fd as usize, &mut packet) {
Ok(0) => return,
Ok(count) => count,
Err(ref err) if err.errno() == EAGAIN => break,
Err(_) => panic!("getty: failed to read from TTY"),
};
redox::write(master_fd as usize, &packet[..count])
.expect("getty: failed to write master PTY");
}
} else if event_id as RawFd == master_fd {
let mut packet = [0; 4096];
loop {
let count = match redox::read(master_fd as usize, &mut packet) {
Ok(0) => return,
Ok(count) => count,
Err(ref err) if err.errno() == EAGAIN => break,
Err(_) => panic!("getty: failed to read from master TTY"),
};
redox::write(tty_fd as usize, &packet[1..count])
.expect("getty: failed to write to TTY");
if packet[0] & 1 == 1 {
let _ = redox::fsync(tty_fd as usize);
}
}
}
};
handle_event(tty_fd as usize);
handle_event(master_fd as usize);
'events: loop {
let sys_event = event_queue
.next()
.expect("getty: event queue stopped")
.expect("getty: failed to read event file");
handle_event(sys_event.fd);
match process.try_wait() {
Ok(status) => match status {
Some(_code) => break 'events,
None => (),
},
Err(err) => match err.kind() {
ErrorKind::WouldBlock => (),
_ => panic!("getty: failed to wait on child: {:?}", err),
},
}
}
let _ = process.kill();
process.wait().expect("getty: failed to wait on login");
}
pub fn getpty(columns: u16, lines: u16) -> (RawFd, String) {
let master = redox::open(
"/scheme/pty",
flag::O_CLOEXEC | flag::O_RDWR | flag::O_CREAT | flag::O_NONBLOCK,
0,
)
.expect("getty: failed to create PTY");
if let Ok(winsize_fd) = redox::dup(master, b"winsize") {
let _ = redox::write(
winsize_fd,
&redox_termios::Winsize {
ws_row: lines,
ws_col: columns,
},
);
let _ = redox::close(winsize_fd);
}
let mut buf: [u8; 4096] = [0; 4096];
let count = redox::fpath(master, &mut buf).unwrap();
(master as RawFd, unsafe {
String::from_utf8_unchecked(Vec::from(&buf[..count]))
})
}
// termion cursor_pos prone to error and does not work on nonblocking files
fn tty_cursor_pos(tty: &mut File) -> Result<(u16, u16), Box<dyn Error>> {
write!(tty, "\x1B[6n")?;
tty.flush()?;
let timeout = Duration::from_millis(500);
let instant = Instant::now();
let mut data = String::new();
while instant.elapsed() < timeout {
let mut bytes = [0];
match tty.read(&mut bytes) {
Ok(count) => if count == 1 {
let c = bytes[0] as char;
if c == 'R' {
break;
}
data.push(c);
},
Err(err) => if err.kind() != ErrorKind::WouldBlock {
return Err(err.into());
}
}
}
if data.is_empty() {
return Err("cursor position timed out".into());
}
let beg = data.rfind('[').ok_or("failed to find [")?;
let coords: String = data.chars().skip(beg + 1).collect();
let mut nums = coords.split(';');
let row = nums.next().ok_or("failed to find row")?.parse::<u16>()?;
let col = nums.next().ok_or("failed to find col")?.parse::<u16>()?;
Ok((col, row))
}
fn tty_columns_lines(tty: &mut File) -> Result<(u16, u16), Box<dyn Error>> {
write!(tty, "{}", termion::cursor::Save)?;
tty.flush()?;
write!(tty, "{}", termion::cursor::Goto(999, 999))?;
tty.flush()?;
let res = tty_cursor_pos(tty);
write!(tty, "{}", termion::cursor::Restore)?;
tty.flush()?;
res
}
fn daemon(tty: &mut File, clear: bool, contain: bool, stderr: &mut Stderr) {
let (columns, lines) = tty_columns_lines(tty).unwrap_or((DEFAULT_COLS, DEFAULT_LINES));
let tty_fd = tty.as_raw_fd();
let (master_fd, pty) = getpty(columns, lines);
let mut event_queue = event::RawEventQueue::new().expect("getty: failed to open event queue");
event_queue
.subscribe(tty_fd as usize, 0, EventFlags::READ)
.expect("getty: failed to fevent TTY");
event_queue
.subscribe(master_fd as usize, 0, EventFlags::READ)
.expect("getty: failed to fevent master PTY");
loop {
if clear {
let _ = redox::write(tty_fd as usize, b"\x1Bc");
}
let _ = redox::fsync(tty_fd as usize);
let slave_stdin = redox::open(&pty, flag::O_CLOEXEC | flag::O_RDONLY, 0)
.expect("getty: failed to open slave stdin");
let slave_stdout = redox::open(&pty, flag::O_CLOEXEC | flag::O_WRONLY, 0)
.expect("getty: failed to open slave stdout");
let slave_stderr = redox::open(&pty, flag::O_CLOEXEC | flag::O_WRONLY, 0)
.expect("getty: failed to open slave stderr");
let mut command = if contain {
Command::new("contain_login")
} else {
Command::new("login")
};
unsafe {
command
.stdin(Stdio::from_raw_fd(slave_stdin as RawFd))
.stdout(Stdio::from_raw_fd(slave_stdout as RawFd))
.stderr(Stdio::from_raw_fd(slave_stderr as RawFd))
.env("TERM", "xterm-256color")
.env("TTY", &pty);
}
match command.spawn() {
Ok(mut process) => {
handle(&mut event_queue, tty_fd, master_fd, &mut process);
}
Err(err) => fail(&format!("getty: failed to execute login: {}", err), stderr),
}
}
}
pub fn main() {
let mut stderr = io::stderr();
let args = clap_app!(getty =>
(author: "Jeremy Soller")
(about: "Set terminal mode")
(@arg TTY: +required "")
(@arg NO_CLEAR: -J --("no-clear") "Do not clear the screen before forking")
(@arg CONTAIN: -C --("contain") "Run contain_login instead of login")
)
.get_matches();
let clear = !args.is_present("NO_CLEAR");
let contain = args.is_present("CONTAIN");
let vt = args.value_of("TTY").unwrap();
let buf: String;
let vt_path = if vt.parse::<usize>().is_ok() {
buf = format!("/scheme/fbcon/{vt}");
&*buf
} else {
vt
};
let mut tty = match redox::open(
&vt_path,
flag::O_CLOEXEC | flag::O_RDWR | flag::O_NONBLOCK,
0,
) {
Ok(fd) => unsafe { File::from_raw_fd(fd as RawFd) },
Err(err) => fail(
&format!("getty: failed to open TTY {}: {}", vt_path, err),
&mut stderr,
),
};
daemon(&mut tty, clear, contain, &mut stderr);
}
-82
View File
@@ -1,82 +0,0 @@
#[macro_use]
extern crate clap;
use extra::option::OptionalExt;
use std::process::exit;
use redox_users::{All, AllGroups, Config, Error, GroupBuilder};
const _MAN_PAGE: &'static str = /* @MANSTART{groupadd} */
r#"
NAME
groupadd - add a user group
SYNOPSIS
groupadd [ -f | --force ] GROUP
groupadd [ -h | --help ]
DESCRIPTION
The groupadd utility adds a new user group using values
passed on the command line and system defaults.
OPTIONS
-f, --force
Simply forces the exit status of the program to 0
even if the group already exists. A message is still
printed to stdout.
-g, --gid GID
The group id to use. This value must not be used and must
be non-negative. The default is to pick the smallest available
group id (between values defined in redox_users).
-h, --help
Display this help and exit.
AUTHOR
Written by Wesley Hershberger.
"#; /* @MANEND */
fn main() {
let args = clap_app!(groupadd =>
(author: "Wesley Hershberger")
(about: "Add groups based on the system's redox_users backend")
(@arg GROUP: +required "Add group GROUP")
(@arg FORCE: -f --force "Force the status of the program to be 0 even if the group exists")
(@arg GID: -g --gid +takes_value "Group id. Positive integer and must not be in use")
)
.get_matches();
let mut sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
let groupname = args.value_of("GROUP").unwrap();
let gid = match args.value_of("GID") {
Some(gid) => {
let id = gid.parse::<usize>().unwrap_or_exit(1);
if let Some(_group) = sys_groups.get_by_id(id) {
eprintln!("groupadd: group already exists");
exit(1);
}
id
}
None => sys_groups.get_unique_id().unwrap_or_else(|| {
eprintln!("groupadd: no available gid");
exit(1);
}),
};
let group = GroupBuilder::new(groupname).gid(gid);
match sys_groups.add_group(group) {
Ok(_) => (),
Err(Error::GroupAlreadyExists) if args.is_present("FORCE") => {
exit(0);
}
Err(err) => {
eprintln!("groupadd: {}", err);
exit(1);
}
}
sys_groups.save().unwrap_or_exit(1);
}
-47
View File
@@ -1,47 +0,0 @@
#[macro_use]
extern crate clap;
use extra::option::OptionalExt;
use redox_users::{All, AllGroups, Config};
const _MAN_PAGE: &'static str = /* @MANSTART{groupdel} */
r#"
NAME
groupdel - modify system files to delete groups
SYNOPSYS
groupdel [ options ] GROUP
groupdel [ -h | --help ]
DESCRIPTION
groupdel removes groups from whatever backend is employed by
the system's redox_users.
Note that you should not remove a primary user group before
removing the user. It is also generally wise not to remove
groups that still own files on the system.
OPTIONS
-h, --help
Print this help page and exit.
AUTHORS
Wesley Hershberger.
"#; /* @MANEND */
fn main() {
let matches = clap_app!(groupdel =>
(author: "Wesley Hershberger")
(about: "Removes a group from the system using redox_users")
(@arg GROUP: +required "Removes group GROUP")
)
.get_matches();
let group = matches.value_of("GROUP").unwrap();
let mut sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
sys_groups.remove_by_name(group.to_string());
sys_groups.save().unwrap_or_exit(1);
}
-81
View File
@@ -1,81 +0,0 @@
#[macro_use]
extern crate clap;
use std::process::exit;
use extra::option::OptionalExt;
use redox_users::{All, AllGroups, AllUsers, Config};
const _MAN_PAGE: &'static str = /* @MANSTART{groupmod} */
r#"
NAME
groupmod - modify group information
SYNOPSYS
groupmod [ options ] GROUP
groupmod [ -h | --help ]
DESCRIPTION
groupmod modifies a user group GROUP in the system's
redox_users backend.
OPTIONS
-h, --help
Print this help page and exit.
-g, --gid GID
Change GROUP's group id. GID must be a non-negative
decimal integer.
Files with GROUP's old gid will not be updated.
User's who use the old gid as their primary gid will
be updated.
-n, --name NAME
The name of the group will be set to NAME
AUTHORS
Wesley Hershberger.
"#; /* @MANEND */
fn main() {
let args = clap_app!(groupmod =>
(author: "Wesley Hershberger")
(about: "Modify users according to the system's redox_users backend")
(@arg GROUP: +required "Modify GROUP")
(@arg GID: -g --gid +takes_value "Change GROUP's group id. See man page for details")
(@arg NAME: -n --name +takes_value "Change GROUP's name")
)
.get_matches();
let groupname = args.value_of("GROUP").unwrap();
let mut sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
{
let group = sys_groups.get_mut_by_name(groupname).unwrap_or_else(|| {
eprintln!("groupmod: group not found: {}", groupname);
exit(1);
});
if let Some(gid) = args.value_of("GID") {
let gid = gid.parse::<usize>().unwrap_or_exit(1);
// Update users
let mut sys_users =
AllUsers::authenticator(Config::default().writeable(true)).unwrap_or_exit(1);
for user in sys_users.iter_mut() {
if user.gid == group.gid {
user.gid = gid;
}
}
sys_users.save().unwrap_or_exit(1);
group.gid = gid;
}
if let Some(name) = args.value_of("NAME") {
group.group = name.to_string();
}
}
sys_groups.save().unwrap_or_exit(1);
}
-163
View File
@@ -1,163 +0,0 @@
#[macro_use]
extern crate clap;
use std::env::args;
use std::process::exit;
use extra::option::OptionalExt;
use redox_users::{All, AllGroups, AllUsers, Config, get_egid, get_euid, get_gid, get_uid};
const _MAN_PAGE: &'static str = /* @MANSTART{id} */
r#"
NAME
id - display user identity
SYNOPSIS
id
id -g [-nr]
id -u [-nr]
id [ -h | --help ]
DESCRIPTION
The id utility displays the user and group names and numeric IDs, of
the calling process, to the standard output.
OPTIONS
-G, --groups
Display the different group IDs (effective and real) as white-space
separated numbers, in no particular order.
-g, --group
Display the effective group ID as a number.
-n, --name
Display the name of the user or group ID for the -g and -u options
instead of the number.
-u, --user
Display the effective user ID as a number.
-a
Ignored for compatibility with other id implementations.
-r, --real
Display the real ID for the -g and -u options instead of the effective ID.
-h, --help
Display help and exit.
AUTHOR
Written by Jose Narvaez.
"#; /* @MANEND */
pub fn main() {
let app = clap_app!(id =>
(author: "Jose Narvaez")
(about: "Get user and group information about the current user")
(@arg IGNORE: -a "Ignored for compatibility with other impls of id")
(@arg GROUPS: -G --groups conflicts_with[selector modifier] "Display current user's real and effective group id's")
(@group selector =>
(@arg GROUP: -g --group "Display current user's effective group id")
(@arg USER: -u --user "Display the effective userid")
)
(@group modifier =>
(@arg NAME: -n --name requires[selector] "Display names of groups/users instead of ids (use with -g or -u)")
(@arg REAL: -r --real requires[selector] "Display real id's instead of effective ids (use with -g and -u)")
)
);
let args = match &*args().nth(0).unwrap_or(String::new()) {
"whoami" => app.get_matches_from(["id", "-un"].iter()),
_ => app.get_matches(),
};
// Display the different group IDs (effective and real)
// as white-space separated numbers, in no particular order.
if args.is_present("GROUPS") {
let egid = get_egid().unwrap_or_exit(1);
let gid = get_gid().unwrap_or_exit(1);
println!("{} {}", egid, gid);
exit(0);
}
// Display effective/real process user ID UNIX user name
if args.is_present("USER") && args.is_present("NAME") {
// Did they pass -r? If so, we show the real
let uid = if args.is_present("REAL") {
get_uid()
} else {
get_euid()
}
.unwrap_or_exit(1);
let users = AllUsers::basic(Config::default()).unwrap_or_exit(1);
let user = users.get_by_id(uid).unwrap_or_exit(1);
println!("{}", user.user);
exit(0);
}
// Display real user ID
if args.is_present("USER") && args.is_present("REAL") {
let uid = get_uid().unwrap_or_exit(1);
println!("{}", uid);
exit(0);
}
// Display effective user ID
if args.is_present("USER") {
let euid = get_euid().unwrap_or_exit(1);
println!("{}", euid);
exit(0);
}
// Display effective/real process group ID UNIX group name
if args.is_present("GROUP") && args.is_present("NAME") {
// Did they pass -r? If so we show the real one
let gid = if args.is_present("REAL") {
get_gid()
} else {
get_egid()
}
.unwrap_or_exit(1);
let groups = AllGroups::new(Config::default()).unwrap_or_exit(1);
let group = groups.get_by_id(gid).unwrap_or_exit(1);
println!("{}", group.group);
exit(0);
}
// Display the real group ID
if args.is_present("GROUP") && args.is_present("REAL") {
let gid = get_gid().unwrap_or_exit(1);
println!("{}", gid);
exit(0);
}
// Display effective group ID
if args.is_present("GROUP") {
let egid = get_egid().unwrap_or_exit(1);
println!("{}", egid);
exit(0);
}
// We get everything we can and show
let euid = get_euid().unwrap_or_exit(1);
let egid = get_egid().unwrap_or_exit(1);
let users = AllUsers::basic(Config::default()).unwrap_or_exit(1);
let groups = AllGroups::new(Config::default()).unwrap_or_exit(1);
let user = users.get_by_id(euid).unwrap_or_exit(1);
let group = groups.get_by_id(egid).unwrap_or_exit(1);
println!("uid={}({}) gid={}({})", euid, user.user, egid, group.group);
exit(0);
}
-204
View File
@@ -1,204 +0,0 @@
#[macro_use]
extern crate clap;
use libredox::error::Result;
use std::fs::File;
use std::io::{self, Write};
use std::str;
use extra::option::OptionalExt;
use redox_users::{All, AllUsers, Config, User};
use termion::input::TermRead;
use userutils::spawn_shell;
const _MAN_PAGE: &'static str = /* @MANSTART{login} */
r#"
NAME
login - log into the computer
SYNOPSIS
login
DESCRIPTION
The login utility logs users (and pseudo-users) into the computer system.
OPTIONS
-h --help
Display help info and exit.
AUTHOR
Written by Jeremy Soller, Jose Narvaez.
"#; /* @MANEND */
const ISSUE_FILE: &'static str = "/etc/issue";
const MOTD_FILE: &'static str = "/etc/motd";
// TODO: Move to redox_users once the definition solidifies.
const DEFAULT_SCHEMES: [&'static str; 26] = [
// Kernel schemes
"debug",
"event",
"memory",
"pipe",
"serio",
"irq",
"time",
"sys",
// Base schemes
"rand",
"null",
"zero",
"log",
// Network schemes
"ip",
"icmp",
"tcp",
"udp",
// IPC schemes
"shm",
"chan",
"uds_stream",
"uds_dgram",
// File schemes
"file",
// Display schemes
"display.vesa",
"display*",
// Other schemes
"pty",
"sudo",
"audio",
];
pub fn apply_login_schemes(
user: &User<redox_users::auth::Full>,
default_schemes: &[&str],
) -> Result<libredox::Fd> {
let schemes = match load_config_schemes(user) {
Some(s) => s,
_ => default_schemes.iter().map(|s| s.to_string()).collect(),
};
let mut names: Vec<ioslice::IoSlice> = Vec::with_capacity(schemes.len());
for scheme in schemes.iter() {
names.push(ioslice::IoSlice::new(scheme.as_bytes()));
}
let ns_fd = libredox::call::mkns(&names)?;
let before_ns_fd = libredox::Fd::new(libredox::call::setns(ns_fd)?);
Ok(before_ns_fd)
}
fn load_config_schemes(user: &User<redox_users::auth::Full>) -> Option<Vec<String>> {
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fs;
const LOGIN_SCHEMES_FILE: &'static str = "/etc/login_schemes.toml";
#[derive(Debug, Clone, Serialize, Deserialize)]
struct UserSchemeConfig {
pub schemes: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct LoginConfig {
#[serde(rename = "user_schemes")]
pub user_schemes: BTreeMap<String, UserSchemeConfig>,
}
let config_str = fs::read_to_string(LOGIN_SCHEMES_FILE).ok()?;
let config: LoginConfig = toml::from_str(&config_str).ok()?;
config
.user_schemes
.get(&user.user)
.map(|cfg| cfg.schemes.clone())
}
pub fn main() {
let mut stdout = io::stdout();
let mut stderr = io::stderr();
let _args = clap_app!(login =>
(author: "Jeremy Soller, Jose Narvaez")
(about: "Login as a user")
)
.get_matches();
if let Ok(mut issue) = File::open(ISSUE_FILE) {
io::copy(&mut issue, &mut stdout).r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
}
loop {
let user = liner::Context::new()
.read_line(
liner::Prompt::from("\x1B[1mRed Bear login:\x1B[0m "),
None,
&mut liner::BasicCompleter::new(Vec::<String>::new()),
)
.r#try(&mut stderr);
if !user.is_empty() {
let stdin = io::stdin();
let mut stdin = stdin.lock();
let sys_users = AllUsers::authenticator(Config::default()).unwrap_or_exit(1);
match sys_users.get_by_name(user) {
None => {
stdout.write(b"\nLogin incorrect\n").r#try(&mut stderr);
stdout.write(b"\n").r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
continue;
}
Some(user) => {
if user.is_passwd_blank() {
if let Ok(mut motd) = File::open(MOTD_FILE) {
io::copy(&mut motd, &mut stdout).r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
}
let before_ns_fd =
apply_login_schemes(user, &DEFAULT_SCHEMES).unwrap_or_exit(1);
let _ = syscall::fcntl(
before_ns_fd.raw(),
syscall::F_SETFD,
syscall::O_CLOEXEC,
);
spawn_shell(user).unwrap_or_exit(1);
let _ = syscall::fcntl(before_ns_fd.raw(), syscall::F_SETFD, 0);
let _ = libredox::call::close(
libredox::call::setns(before_ns_fd.into_raw()).unwrap_or_exit(1),
);
break;
}
stdout
.write_all(b"\x1B[1mpassword:\x1B[0m ")
.r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
if let Some(password) = stdin.read_passwd(&mut stdout).r#try(&mut stderr) {
stdout.write(b"\n").r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
if user.verify_passwd(&password) {
if let Ok(mut motd) = File::open(MOTD_FILE) {
io::copy(&mut motd, &mut stdout).r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
}
spawn_shell(user).unwrap_or_exit(1);
break;
}
}
}
}
} else {
stdout.write(b"\n").r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
}
}
}
+121
View File
@@ -0,0 +1,121 @@
extern crate redoxfs;
extern crate uuid;
use std::io::Read;
use std::{env, fs, io, process, time};
use redoxfs::{DiskFile, FileSystem};
use termion::input::TermRead;
use uuid::Uuid;
fn usage() -> ! {
eprintln!("redoxfs-mkfs [--encrypt] DISK [BOOTLOADER]");
process::exit(1);
}
fn main() {
env_logger::init();
let mut encrypt = false;
let mut disk_path_opt = None;
let mut bootloader_path_opt = None;
for arg in env::args().skip(1) {
if arg == "--encrypt" {
encrypt = true;
} else if disk_path_opt.is_none() {
disk_path_opt = Some(arg);
} else if bootloader_path_opt.is_none() {
bootloader_path_opt = Some(arg);
} else {
eprintln!("redoxfs-mkfs: too many arguments provided");
usage();
}
}
let disk_path = if let Some(path) = disk_path_opt {
path
} else {
eprintln!("redoxfs-mkfs: no disk image provided");
usage();
};
let disk = match DiskFile::open(&disk_path) {
Ok(disk) => disk,
Err(err) => {
eprintln!("redoxfs-mkfs: failed to open image {}: {}", disk_path, err);
process::exit(1);
}
};
let mut bootloader = vec![];
if let Some(bootloader_path) = bootloader_path_opt {
match fs::File::open(&bootloader_path) {
Ok(mut file) => match file.read_to_end(&mut bootloader) {
Ok(_) => (),
Err(err) => {
eprintln!(
"redoxfs-mkfs: failed to read bootloader {}: {}",
bootloader_path, err
);
process::exit(1);
}
},
Err(err) => {
eprintln!(
"redoxfs-mkfs: failed to open bootloader {}: {}",
bootloader_path, err
);
process::exit(1);
}
}
};
let password_opt = if encrypt {
eprint!("redoxfs-mkfs: password: ");
let password = io::stdin()
.read_passwd(&mut io::stderr())
.unwrap()
.unwrap_or_default();
eprintln!();
if password.is_empty() {
eprintln!("redoxfs-mkfs: empty password, giving up");
process::exit(1);
}
Some(password)
} else {
None
};
let ctime = time::SystemTime::now()
.duration_since(time::UNIX_EPOCH)
.unwrap();
match FileSystem::create_reserved(
disk,
password_opt.as_ref().map(|x| x.as_bytes()),
&bootloader,
ctime.as_secs(),
ctime.subsec_nanos(),
) {
Ok(filesystem) => {
let uuid = Uuid::from_bytes(filesystem.header.uuid());
eprintln!(
"redoxfs-mkfs: created filesystem on {}, reserved {} blocks, size {} MB, uuid {}",
disk_path,
filesystem.block,
filesystem.header.size() / 1000 / 1000,
uuid.hyphenated()
);
}
Err(err) => {
eprintln!(
"redoxfs-mkfs: failed to create filesystem on {}: {}",
disk_path, err
);
process::exit(1);
}
}
}
+409
View File
@@ -0,0 +1,409 @@
extern crate libc;
extern crate redoxfs;
#[cfg(target_os = "redox")]
extern crate syscall;
extern crate uuid;
use std::env;
use std::fs::File;
use std::io::{self, Read, Write};
use std::os::unix::io::{FromRawFd, RawFd};
use std::process;
#[cfg(target_os = "redox")]
use std::{mem::MaybeUninit, ptr::addr_of_mut, sync::atomic::Ordering};
use redoxfs::{mount, DiskCache, DiskFile, FileSystem};
use termion::input::TermRead;
use uuid::Uuid;
#[cfg(target_os = "redox")]
extern "C" fn unmount_handler(_s: usize) {
redoxfs::IS_UMT.store(1, Ordering::SeqCst);
}
#[cfg(target_os = "redox")]
//set up a signal handler on redox, this implements unmounting. I have no idea what sa_flags is
//for, so I put 2. I don't think 0,0 is a valid sa_mask. I don't know what i'm doing here. When u
//send it a sigkill, it shuts off the filesystem
fn setsig() {
// TODO: High-level wrapper like the nix crate?
unsafe {
let mut action = MaybeUninit::<libc::sigaction>::uninit();
assert_eq!(
libc::sigemptyset(addr_of_mut!((*action.as_mut_ptr()).sa_mask)),
0
);
addr_of_mut!((*action.as_mut_ptr()).sa_flags).write(0);
addr_of_mut!((*action.as_mut_ptr()).sa_sigaction).write(unmount_handler as usize);
assert_eq!(
libc::sigaction(libc::SIGTERM, action.as_ptr(), core::ptr::null_mut()),
0
);
}
}
#[cfg(not(target_os = "redox"))]
// on linux, this is implemented properly, so no need for this unscrupulous nonsense!
fn setsig() {}
fn fork() -> isize {
unsafe { libc::fork() as isize }
}
fn pipe(pipes: &mut [i32; 2]) -> isize {
unsafe { libc::pipe(pipes.as_mut_ptr()) as isize }
}
#[cfg(not(target_os = "redox"))]
fn capability_mode() {}
#[cfg(not(target_os = "redox"))]
fn bootloader_password() -> Option<Vec<u8>> {
None
}
#[cfg(target_os = "redox")]
fn capability_mode() {
libredox::call::setrens(0, 0).expect("redoxfs: failed to enter null namespace");
}
#[cfg(target_os = "redox")]
fn bootloader_password() -> Option<Vec<u8>> {
use libredox::call::MmapArgs;
let addr_env = env::var_os("REDOXFS_PASSWORD_ADDR")?;
let size_env = env::var_os("REDOXFS_PASSWORD_SIZE")?;
let addr = usize::from_str_radix(
addr_env.to_str().expect("REDOXFS_PASSWORD_ADDR not valid"),
16,
)
.expect("failed to parse REDOXFS_PASSWORD_ADDR");
let size = usize::from_str_radix(
size_env.to_str().expect("REDOXFS_PASSWORD_SIZE not valid"),
16,
)
.expect("failed to parse REDOXFS_PASSWORD_SIZE");
let mut password = Vec::with_capacity(size);
unsafe {
let aligned_size = size.next_multiple_of(syscall::PAGE_SIZE);
let fd = libredox::Fd::open("memory:physical", libredox::flag::O_CLOEXEC, 0)
.expect("failed to open physical memory file");
let password_map = libredox::call::mmap(MmapArgs {
addr: core::ptr::null_mut(),
length: aligned_size,
prot: libredox::flag::PROT_READ,
flags: libredox::flag::MAP_SHARED,
fd: fd.raw(),
offset: addr as u64,
})
.expect("failed to map REDOXFS_PASSWORD")
.cast::<u8>();
for i in 0..size {
password.push(password_map.add(i).read());
}
let _ = libredox::call::munmap(password_map.cast(), aligned_size);
}
Some(password)
}
fn print_err_exit(err: impl AsRef<str>) -> ! {
eprintln!("redoxfs: {}", err.as_ref());
usage();
process::exit(1)
}
fn print_usage_exit() -> ! {
usage();
process::exit(1)
}
fn usage() {
eprintln!("redoxfs [--no-daemon|-d] [--uuid] [disk or uuid] [mountpoint] [block in hex]");
}
enum DiskId {
Path(String),
Uuid(Uuid),
}
fn filesystem_by_path(
path: &str,
block_opt: Option<u64>,
log_errors: bool,
) -> Option<(String, FileSystem<DiskCache<DiskFile>>)> {
log::debug!("opening {}", path);
let attempts = 10;
for attempt in 0..=attempts {
let password_opt = if attempt > 0 {
eprint!("redoxfs: password: ");
let password = io::stdin()
.read_passwd(&mut io::stderr())
.unwrap()
.unwrap_or_default();
eprintln!();
if password.is_empty() {
eprintln!("redoxfs: empty password, giving up");
// Password is empty, exit loop
break;
}
Some(password.into_bytes())
} else {
bootloader_password()
};
match DiskFile::open(path).map(DiskCache::new) {
Ok(disk) => {
match redoxfs::FileSystem::open(disk, password_opt.as_deref(), block_opt, true) {
Ok(filesystem) => {
log::debug!(
"opened filesystem on {} with uuid {}",
path,
Uuid::from_bytes(filesystem.header.uuid()).hyphenated()
);
return Some((path.to_string(), filesystem));
}
Err(err) => match err.errno {
syscall::ENOKEY => {
if password_opt.is_some() {
eprintln!("redoxfs: incorrect password ({}/{})", attempt, attempts);
}
}
_ => {
if log_errors {
log::error!("failed to open filesystem {}: {}", path, err);
}
break;
}
},
}
}
Err(err) => {
if log_errors {
log::error!("failed to open image {}: {}", path, err);
}
break;
}
}
}
None
}
#[cfg(not(target_os = "redox"))]
fn filesystem_by_uuid(
_uuid: &Uuid,
_block_opt: Option<u64>,
) -> Option<(String, FileSystem<DiskCache<DiskFile>>)> {
None
}
#[cfg(target_os = "redox")]
fn filesystem_by_uuid(
uuid: &Uuid,
block_opt: Option<u64>,
) -> Option<(String, FileSystem<DiskCache<DiskFile>>)> {
use std::fs;
use redox_path::RedoxPath;
match fs::read_dir("/scheme") {
Ok(entries) => {
for entry_res in entries {
if let Ok(entry) = entry_res {
if let Some(disk) = entry.path().to_str() {
if RedoxPath::from_absolute(disk)
.unwrap_or(RedoxPath::from_absolute("/")?)
.is_scheme_category("disk")
{
log::debug!("found scheme {}", disk);
match fs::read_dir(disk) {
Ok(entries) => {
for entry_res in entries {
if let Ok(entry) = entry_res {
if let Ok(path) =
entry.path().into_os_string().into_string()
{
log::debug!("found path {}", path);
if let Some((path, filesystem)) =
filesystem_by_path(&path, block_opt, false)
{
if &filesystem.header.uuid() == uuid.as_bytes()
{
log::debug!(
"filesystem on {} matches uuid {}",
path,
uuid.hyphenated()
);
return Some((path, filesystem));
} else {
log::debug!(
"filesystem on {} does not match uuid {}",
path,
uuid.hyphenated()
);
}
}
}
}
}
}
Err(err) => {
log::debug!("failed to list '{}': {}", disk, err);
}
}
}
}
}
}
}
Err(err) => {
log::error!("failed to list schemes: {}", err);
}
}
None
}
fn daemon(
disk_id: &DiskId,
mountpoint: &str,
block_opt: Option<u64>,
mut write: Option<File>,
) -> ! {
setsig();
let filesystem_opt = match *disk_id {
DiskId::Path(ref path) => filesystem_by_path(path, block_opt, true),
DiskId::Uuid(ref uuid) => filesystem_by_uuid(uuid, block_opt),
};
if let Some((path, filesystem)) = filesystem_opt {
match mount(filesystem, mountpoint, |mounted_path| {
capability_mode();
log::info!(
"mounted filesystem on {} to {}",
path,
mounted_path.display()
);
if let Some(ref mut write) = write {
let _ = write.write(&[0]);
}
}) {
Ok(()) => {
process::exit(0);
}
Err(err) => {
log::error!("failed to mount {} to {}: {}", path, mountpoint, err);
}
}
}
match *disk_id {
DiskId::Path(ref path) => {
log::error!("not able to mount path {}", path);
}
DiskId::Uuid(ref uuid) => {
log::error!("not able to mount uuid {}", uuid.hyphenated());
}
}
if let Some(ref mut write) = write {
let _ = write.write(&[1]);
}
process::exit(1);
}
fn main() {
env_logger::init();
let mut args = env::args().skip(1);
let mut daemonise = true;
let mut disk_id: Option<DiskId> = None;
let mut mountpoint: Option<String> = None;
let mut block_opt: Option<u64> = None;
while let Some(arg) = args.next() {
match arg.as_str() {
"--no-daemon" | "-d" => daemonise = false,
"--uuid" if disk_id.is_none() => {
disk_id = Some(DiskId::Uuid(
match args.next().as_deref().map(Uuid::parse_str) {
Some(Ok(uuid)) => uuid,
Some(Err(err)) => {
print_err_exit(format!("invalid uuid '{}': {}", arg, err))
}
None => print_err_exit("no uuid provided"),
},
));
}
disk if disk_id.is_none() => disk_id = Some(DiskId::Path(disk.to_owned())),
mnt if disk_id.is_some() && mountpoint.is_none() => mountpoint = Some(mnt.to_owned()),
opts if mountpoint.is_some() => match u64::from_str_radix(opts, 16) {
Ok(block) => block_opt = Some(block),
Err(err) => print_err_exit(format!("invalid block '{}': {}", opts, err)),
},
_ => print_usage_exit(),
}
}
let Some(disk_id) = disk_id else {
print_err_exit("no disk provided");
};
let Some(mountpoint) = mountpoint else {
print_err_exit("no mountpoint provided");
};
if daemonise {
let mut pipes = [0; 2];
if pipe(&mut pipes) == 0 {
let mut read = unsafe { File::from_raw_fd(pipes[0] as RawFd) };
let write = unsafe { File::from_raw_fd(pipes[1] as RawFd) };
let pid = fork();
if pid == 0 {
drop(read);
daemon(&disk_id, &mountpoint, block_opt, Some(write));
} else if pid > 0 {
drop(write);
let mut res = [0];
read.read_exact(&mut res).unwrap();
process::exit(res[0] as i32);
} else {
panic!("redoxfs: failed to fork");
}
} else {
panic!("redoxfs: failed to create pipe");
}
} else {
log::info!("running in foreground");
daemon(&disk_id, &mountpoint, block_opt, None);
}
}
-197
View File
@@ -1,197 +0,0 @@
#[macro_use]
extern crate clap;
use std::io;
use std::io::Write;
use std::process::exit;
use extra::option::OptionalExt;
use libredox::flag::O_CLOEXEC;
use libredox::errno::EPERM;
use redox_users::{All, AllUsers, Config, get_uid};
use termion::input::TermRead;
const _MAN_PAGE: &'static str = /* @MANSTART{passwd} */
r#"
NAME
passwd - modify a user's password
SYNOPSIS
passwd [ LOGIN ]
passwd [ -h | --help ]
DESCRIPTION
The passwd utility changes the user's local password. If the user is not
the super-user, passwd first prompts for the current password and will
not continue unless the correct password is entered.
OPTIONS
-h, --help
Display this help and exit.
-l, --lock
Lock the password of the named account. This changes the stored password
hash so that it matches no encrypted value ("!")
Users with locked passwords are not allowed to change their password.
AUTHOR
Written by Jeremy Soller, Jose Narvaez.
"#; /* @MANEND */
fn main() {
let mut stdin = io::stdin().lock();
let mut stdout = io::stdout().lock();
let mut stderr = io::stderr();
let args = clap_app!(passwd =>
(author: "Jeremy Soller, Jose Narvaez")
(about: "Set user passwords")
(@arg LOGIN: "Apply to login. Sets password for current user if not supplied")
(@arg LOCK: -l --lock "Lock the password for an account (no login)")
)
.get_matches();
if args.is_present("LOCK") {
if get_uid().unwrap_or_exit(1) != 0 {
eprintln!("passwd: only root is allowed to lock accounts");
exit(1);
}
let mut users =
AllUsers::authenticator(Config::default().writeable(true)).unwrap_or_exit(1);
let Some(login) = args.value_of("LOGIN") else {
eprintln!("passwd: no account specified to lock");
exit(1);
};
let user = users.get_mut_by_name(login).unwrap_or_else(|| {
eprintln!("passwd: user does not exist: {}", login);
exit(1);
});
user.unset_passwd();
users.save().unwrap_or_exit(1);
return;
}
let uid = get_uid().unwrap_or_exit(1);
if uid == 0 {
let mut users =
AllUsers::authenticator(Config::default().writeable(true)).unwrap_or_exit(1);
let user = find_user(&args, &mut users);
let msg = format!("changing password for '{}' \n", user.user);
stdout.write_all(&msg.as_bytes()).r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
let new_password = ask_new_password(stdin, stdout, stderr);
user.set_passwd(&new_password).unwrap_or_exit(1);
users.save().unwrap_or_exit(1);
return;
}
let mut users = AllUsers::basic(Config::default()).unwrap_or_exit(1);
let user = find_user(&args, &mut users);
if user.uid != uid {
eprintln!(
"passwd: you do not have permission to set the password of '{}'",
user.user
);
exit(1);
}
let msg = format!("changing password for '{}' \n", user.user);
stdout.write_all(&msg.as_bytes()).r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
drop(users); // Unlock /etc/passwd
stdout.write_all(b"current password: ").r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
let file = libredox::call::open("/scheme/sudo/passwd", O_CLOEXEC, 0).unwrap();
if let Some(password) = stdin.read_passwd(&mut stdout).r#try(&mut stderr) {
stdout.write(b"\n").r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
match libredox::call::write(file, password.as_bytes()) {
Ok(_) => {}
Err(err) if err.errno() == EPERM => {
eprintln!("passwd: incorrect current password");
exit(1);
}
Err(err) => panic!("{err}"),
}
} else {
eprintln!("passwd: incorrect current password");
exit(1);
}
let new_password = ask_new_password(stdin, stdout, stderr);
match libredox::call::write(file, new_password.as_bytes()) {
Ok(_) => {}
Err(err) if err.errno() == EPERM => {
eprintln!("passwd: invalid new password");
exit(1);
}
Err(err) => panic!("{err}"),
}
}
fn find_user<'a, T: Default>(
args: &clap::ArgMatches<'_>,
users: &'a mut AllUsers<T>,
) -> &'a mut redox_users::User<T> {
let uid = get_uid().unwrap_or_exit(1);
match args.value_of("LOGIN") {
Some(login) => users.get_mut_by_name(login).unwrap_or_else(|| {
eprintln!("passwd: user does not exist: {}", login);
exit(1);
}),
None => users.get_mut_by_id(uid).unwrap_or_else(|| {
eprintln!("passwd: you do not exist");
exit(1);
}),
}
}
fn ask_new_password(
mut stdin: io::StdinLock<'_>,
mut stdout: io::StdoutLock<'_>,
mut stderr: io::Stderr,
) -> String {
stdout.write_all(b"new password: ").r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
let Some(new_password) = stdin.read_passwd(&mut stdout).r#try(&mut stderr) else {
eprintln!("passwd: no new password provided");
exit(1);
};
stdout.write(b"\nconfirm password: ").r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
let Some(confirm_password) = stdin.read_passwd(&mut stdout).r#try(&mut stderr) else {
eprintln!("\npasswd: no confirm password provided");
exit(1);
};
stdout.write(b"\n").r#try(&mut stderr);
stdout.flush().r#try(&mut stderr);
if new_password != confirm_password {
eprintln!("passwd: new password does not match confirm password");
exit(1);
}
new_password
}
+206
View File
@@ -0,0 +1,206 @@
use std::{env, process};
use humansize::{format_size, BINARY, DECIMAL};
use redoxfs::{BlockAddr, BlockMeta, Disk, DiskFile, FileSystem};
use uuid::Uuid;
fn resize<D: Disk>(fs: &mut FileSystem<D>, size_arg: String) -> Result<(), String> {
let disk_size = fs
.disk
.size()
.map_err(|err| format!("failed to read disk size: {}", err))?;
// Find contiguous free region
//TODO: better error management
let mut last_free = None;
let mut last_end = 0;
fs.tx(|tx| {
let mut alloc_ptr = tx.header.alloc;
while !alloc_ptr.is_null() {
let alloc = tx.read_block(alloc_ptr)?;
alloc_ptr = alloc.data().prev;
for entry in alloc.data().entries.iter() {
let count = entry.count();
if count <= 0 {
continue;
}
let end = entry.index() + count as u64;
if end > last_end {
last_free = Some(*entry);
last_end = end;
}
}
}
Ok(())
})
.map_err(|err| format!("failed to read alloc log: {}", err))?;
let old_size = fs.header.size();
let min_size = if let Some(entry) = last_free {
entry.index() * redoxfs::BLOCK_SIZE
} else {
old_size
};
let max_size = disk_size - (fs.block * redoxfs::BLOCK_SIZE);
let new_size = match size_arg.to_lowercase().as_str() {
"min" | "minimum" => min_size,
"" | "max" | "maximum" => max_size,
_ => match parse_size::parse_size(&size_arg) {
Ok(new_size) => {
if new_size < min_size {
return Err(format!(
"requested size {} is smaller than {} by {}",
new_size,
min_size,
min_size - new_size
));
}
if new_size > max_size {
return Err(format!(
"requested size {} is larger than {} by {}",
new_size,
max_size,
new_size - max_size
));
}
new_size
}
Err(err) => {
return Err(format!(
"failed to parse size argument {:?}: {}",
size_arg, err
));
}
},
};
println!(
"minimum size: {} ({})",
format_size(min_size, DECIMAL),
format_size(min_size, BINARY)
);
println!(
"maximum size: {} ({})",
format_size(max_size, DECIMAL),
format_size(max_size, BINARY)
);
println!(
"new size: {} ({})",
format_size(new_size, DECIMAL),
format_size(new_size, BINARY)
);
let old_blocks = old_size / redoxfs::BLOCK_SIZE;
let new_blocks = new_size / redoxfs::BLOCK_SIZE;
let (start, end, shrink) = if new_size == old_size {
println!("already requested size");
return Ok(());
} else if new_size < old_size {
println!("shrinking by {}", old_size - new_size);
(new_blocks, old_blocks, true)
} else {
println!("growing by {}", new_size - old_size);
(old_blocks, new_blocks, false)
};
// Allocate or deallocate blocks as needed
unsafe {
let allocator = fs.allocator_mut();
for index in start..end {
if shrink {
//TODO: replace assert with error?
let addr = BlockAddr::new(index as u64, BlockMeta::default());
assert_eq!(allocator.allocate_exact(addr), Some(addr));
} else {
let addr = BlockAddr::new(index as u64, BlockMeta::default());
allocator.deallocate(addr);
}
}
}
fs.tx(|tx| {
// Update header
tx.header.size = new_size.into();
tx.header_changed = true;
// Sync with squash
tx.sync(true)?;
Ok(())
})
.map_err(|err| format!("transaction failed: {}", err))
}
fn main() {
env_logger::init();
let mut args = env::args().skip(1);
let disk_path = if let Some(path) = args.next() {
path
} else {
eprintln!("redoxfs-resize: no new disk image provided");
eprintln!("redoxfs-resize NEW-DISK [SIZE]");
process::exit(1);
};
let size_arg = args.next().unwrap_or_default();
let disk = match DiskFile::open(&disk_path) {
Ok(disk) => disk,
Err(err) => {
eprintln!(
"redoxfs-resize: failed to open disk image {}: {}",
disk_path, err
);
process::exit(1);
}
};
let mut fs = match FileSystem::open(disk, None, None, true) {
Ok(fs) => fs,
Err(err) => {
eprintln!(
"redoxfs-resize: failed to open filesystem on {}: {}",
disk_path, err
);
process::exit(1);
}
};
match resize(&mut fs, size_arg) {
Ok(()) => {}
Err(err) => {
eprintln!(
"redoxfs-resize: failed to resize filesystem on {}: {}",
disk_path, err
);
process::exit(1);
}
}
let uuid = Uuid::from_bytes(fs.header.uuid());
let size = fs.header.size();
let free = fs.allocator().free() * redoxfs::BLOCK_SIZE;
let used = size - free;
println!("redoxfs-resize: resized filesystem on {}", disk_path);
println!("\tuuid: {}", uuid.hyphenated());
println!(
"\tsize: {} ({})",
format_size(size, DECIMAL),
format_size(size, BINARY)
);
println!(
"\tused: {} ({})",
format_size(used, DECIMAL),
format_size(used, BINARY)
);
println!(
"\tfree: {} ({})",
format_size(free, DECIMAL),
format_size(free, BINARY)
);
}
-84
View File
@@ -1,84 +0,0 @@
#[macro_use]
extern crate clap;
use std::io::{self, Write};
use std::process::exit;
use std::str;
use extra::option::OptionalExt;
use libredox::flag::O_CLOEXEC;
use redox_users::{All, AllUsers, Config, get_uid};
use syscall::EPERM;
use termion::input::TermRead;
use userutils::spawn_shell;
const _MAN_PAGE: &'static str = /* @MANSTART{su} */
r#"
NAME
su - substitute user identity
SYNOPSIS
su [ user ]
su [ -h | --help ]
DESCRIPTION
The su utility requests appropriate user credentials via PAM and switches to
that user ID (the default user is the superuser). A shell is then executed.
OPTIONS
-h, --help
Display this help and exit.
AUTHOR
Written by Jeremy Soller, Jose Narvaez.
"#; /* @MANEND */
pub fn main() {
let stdin = io::stdin();
let mut stdin = stdin.lock();
let stdout = io::stdout();
let mut stdout = stdout.lock();
let mut stderr = io::stderr();
let args = clap_app!(su =>
(author: "Jeremy Soller, Jose Narvaez")
(about: "substitue user identity")
(@arg LOGIN: "Login as LOGIN. Default is \'root\'")
)
.get_matches();
let target_user = args.value_of("LOGIN").unwrap_or("root");
let uid = get_uid().unwrap_or_exit(1);
let users = AllUsers::basic(Config::default()).unwrap_or_exit(1);
let user = users.get_by_name(&target_user).unwrap_or_exit(1);
// If the user executing su is root, then they can do anything without a password.
// Same if the user we're being asked to login as doesn't have a password.
if uid == 0 {
writeln!(stdout).unwrap_or_exit(1);
exit(spawn_shell(user).unwrap_or_exit(1));
} else {
let file = libredox::call::open("/scheme/sudo/su", O_CLOEXEC, 0).unwrap();
write!(stdout, "password: ").unwrap_or_exit(1);
stdout.flush().unwrap_or_exit(1);
// Read the password, reading an empty string if CTRL-d is specified
let password = stdin
.read_passwd(&mut stdout)
.r#try(&mut stderr)
.unwrap_or(String::new());
match libredox::call::write(file, password.as_bytes()) {
Ok(_) => exit(spawn_shell(user).unwrap_or_exit(1)),
Err(err) if err.errno() == EPERM => {
writeln!(stderr, "su: authentication failed").unwrap_or_exit(1);
exit(1);
}
Err(err) => panic!("{err}"),
}
}
}
-401
View File
@@ -1,401 +0,0 @@
use std::collections::HashMap;
use std::env;
use std::io::{self, Write};
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::os::unix::process::CommandExt;
use std::process::{Command, exit};
use extra::option::OptionalExt;
use libredox::flag::O_CLOEXEC;
use redox_rt::protocol::ProcCall;
use redox_rt::sys::proc_call;
use redox_scheme::scheme::{SchemeSync, register_sync_scheme};
use redox_scheme::{
CallerCtx, OpenResult, RequestKind, Response, SendFdRequest, SignalBehavior, Socket,
};
use redox_users::{All, AllGroups, AllUsers, Config, get_uid};
use syscall::error::*;
use syscall::flag::*;
use syscall::schemev2::NewFdFlags;
use termion::input::TermRead;
const MAX_ATTEMPTS: u16 = 3;
const _MAN_PAGE: &'static str = /* @MANSTART{sudo} */
r#"
NAME
sudo - execute a command as another user
SYNOPSIS
sudo command
sudo [ -h | --help ]
DESCRIPTION
The sudo utility allows a permitted user to execute a command as the
superuser or another user, as specified by the security policy.
EXIT STATUS
Upon successful execution of a command, the exit status from sudo will
be the exit status of the program that was executed. In case of error
the exit status will be >0.
AUTHOR
Written by Jeremy Soller, Jose Narvaez, bjorn3.
"#; /* @MANEND */
fn main() {
if env::args().nth(1).as_deref() == Some("--daemon") {
daemon_main();
}
let mut args = env::args().skip(1);
let cmd = args.next().unwrap_or_else(|| {
eprintln!("sudo: no command provided");
exit(1);
});
let users = AllUsers::basic(Config::default()).unwrap_or_exit(1);
let uid = get_uid().unwrap_or_exit(1);
let user = users.get_by_id(uid).unwrap_or_exit(1);
if uid == 0 {
// We are root already. No need to elevate privileges
run_command_as_root(&cmd, &args.collect());
}
let file = libredox::call::open("/scheme/sudo", O_CLOEXEC, 0).unwrap();
let mut attempts = 0;
loop {
print!("[sudo] password for {}: ", user.user);
let _ = io::stdout().flush();
match io::stdin().read_passwd(&mut io::stdout()).unwrap() {
Some(password) => {
println!();
match libredox::call::write(file, password.as_bytes()) {
Ok(_) => break,
Err(err) if err.errno() == EPERM => {
attempts += 1;
eprintln!(
"sudo: incorrect password or not in sudo group ({}/{})",
attempts, MAX_ATTEMPTS,
);
if attempts >= MAX_ATTEMPTS {
exit(1);
}
}
Err(err) => panic!("{err}"),
}
}
None => {
println!();
exit(1);
}
}
}
// FIXME move to libredox
unsafe extern "C" {
safe fn redox_cur_procfd_v0() -> usize;
}
// Elevate privileges of our own process with help from the sudo daemon
syscall::sendfd(
file,
syscall::dup(redox_cur_procfd_v0(), &[]).unwrap(),
0,
0,
)
.unwrap();
// FIXME perhaps keep the original namespace available in a subdirectory of the namespace we switch to?
let ns = syscall::openat(file, "ns", 0, syscall::O_CLOEXEC).unwrap();
libredox::call::setns(ns).unwrap();
run_command_as_root(&cmd, &args.collect());
}
enum Policy {
Deny,
Authenticate,
}
fn policy_for_user(uid: u32) -> Policy {
let users = AllUsers::authenticator(Config::default()).unwrap_or_exit(1);
let groups = AllGroups::new(Config::default()).unwrap_or_exit(1);
let user = users.get_by_id(uid as usize).unwrap_or_exit(1);
let sudo_group = groups.get_by_name("sudo").unwrap_or_exit(1);
if !sudo_group.users.iter().any(|name| name == &user.user) {
return Policy::Deny;
}
Policy::Authenticate
}
fn run_command_as_root(cmd: &str, args: &Vec<String>) -> ! {
let mut command = Command::new(&cmd);
for arg in args {
command.arg(&arg);
}
command.uid(0);
command.gid(0);
command.env("USER", "root");
command.env("UID", "0");
command.env("GROUPS", "0");
let err = command.exec();
eprintln!("sudo: failed to execute {}: {}", cmd, err);
exit(1);
}
struct Scheme {
next_fd: usize,
handles: HashMap<usize, Handle>,
}
enum Handle {
AwaitingPassword { uid: u32 },
AwaitingRootPassword,
AwaitingContextFd,
AwaitingNamespaceFetch { ns: libredox::Fd },
AwaitingPasswordForPasswd { uid: u32 },
AwaitingNewPassword { uid: u32 },
Placeholder,
SchemeRoot,
}
impl SchemeSync for Scheme {
fn scheme_root(&mut self) -> Result<usize> {
let fd = self.next_fd;
self.next_fd = self.next_fd.checked_add(1).ok_or(Error::new(EMFILE))?;
self.handles.insert(fd, Handle::SchemeRoot);
Ok(fd)
}
fn openat(
&mut self,
dirfd: usize,
path: &str,
_flags: usize,
_fcntl_flags: u32,
ctx: &CallerCtx,
) -> Result<OpenResult> {
let handle = match self.handles.get_mut(&dirfd).ok_or(Error::new(EBADF))? {
Handle::SchemeRoot => match path {
"" => Handle::AwaitingPassword { uid: ctx.uid },
"su" => Handle::AwaitingRootPassword,
"passwd" => Handle::AwaitingPasswordForPasswd { uid: ctx.uid },
_ => return Err(Error::new(ENOENT)),
},
Handle::AwaitingNamespaceFetch { .. } => {
if path != "ns" {
return Err(Error::new(ENOENT));
}
let ns = match self.handles.insert(dirfd, Handle::Placeholder).unwrap() {
Handle::AwaitingNamespaceFetch { ns } => ns,
_ => unreachable!(),
};
return Ok(OpenResult::OtherScheme { fd: ns.into_raw() });
}
_ => return Err(Error::new(EINVAL)),
};
let fd = self.next_fd;
self.next_fd = self.next_fd.checked_add(1).ok_or(Error::new(EMFILE))?;
self.handles.insert(fd, handle);
Ok(OpenResult::ThisScheme {
number: fd,
flags: NewFdFlags::empty(),
})
}
fn write(
&mut self,
id: usize,
buf: &[u8],
_off: u64,
_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?;
let validate_utf8 = |buf| std::str::from_utf8(buf).map_err(|_| Error::new(EINVAL));
match std::mem::replace(handle, Handle::Placeholder) {
Handle::AwaitingPassword { uid } => {
let users = AllUsers::authenticator(Config::default()).unwrap_or_exit(1);
let user = users.get_by_id(uid as usize).unwrap_or_exit(1);
match policy_for_user(uid) {
Policy::Deny => {
*handle = Handle::AwaitingPassword { uid };
return Err(Error::new(EPERM));
}
Policy::Authenticate => {
let password = validate_utf8(buf)?;
if user.verify_passwd(&password) {
*handle = Handle::AwaitingContextFd
} else {
*handle = Handle::AwaitingPassword { uid };
return Err(Error::new(EPERM));
}
}
}
}
Handle::AwaitingRootPassword => {
let users = AllUsers::authenticator(Config::default()).unwrap_or_exit(1);
let user = users.get_by_id(0).unwrap_or_exit(1);
let password = validate_utf8(buf)?;
if user.verify_passwd(&password) {
*handle = Handle::AwaitingContextFd
} else {
*handle = Handle::AwaitingRootPassword;
return Err(Error::new(EPERM));
}
}
Handle::AwaitingContextFd => {
*handle = Handle::AwaitingContextFd;
return Err(Error::new(EINVAL));
}
Handle::AwaitingPasswordForPasswd { uid } => {
let users =
AllUsers::authenticator(Config::default()).map_err(|_| Error::new(ENOLCK))?;
let user = users.get_by_id(uid as usize).ok_or(Error::new(EEXIST))?;
let password = validate_utf8(buf)?;
if user.verify_passwd(&password) {
*handle = Handle::AwaitingNewPassword { uid }
} else {
*handle = Handle::AwaitingPasswordForPasswd { uid };
return Err(Error::new(EPERM));
}
}
Handle::AwaitingNewPassword { uid } => {
let mut users = AllUsers::authenticator(Config::default().writeable(true))
.map_err(|_| Error::new(ENOLCK))?;
let user = users
.get_mut_by_id(uid as usize)
.ok_or(Error::new(EEXIST))?;
let new_password = validate_utf8(buf)?;
if user.set_passwd(&new_password).is_ok() {
users.save().map_err(|_| Error::new(ENOLCK))?;
*handle = Handle::Placeholder
} else {
*handle = Handle::AwaitingNewPassword { uid };
return Err(Error::new(EPERM));
}
}
Handle::AwaitingNamespaceFetch { .. } => {
eprintln!("sudo: found namespace fetch handle with ID {id}");
return Err(Error::new(EBADFD));
}
Handle::Placeholder => {
eprintln!("sudo: found placeholder handle with ID {id}");
return Err(Error::new(EBADFD));
}
Handle::SchemeRoot => {
eprintln!("sudo: found Scheme root handle with ID {id}");
return Err(Error::new(EBADFD));
}
}
Ok(buf.len())
}
}
impl Scheme {
fn on_close(&mut self, id: usize) {
self.handles.remove(&id);
}
fn on_sendfd(&mut self, socket: &Socket, req: &SendFdRequest) -> Result<usize> {
let handle = self.handles.get_mut(&req.id()).ok_or(Error::new(EBADF))?;
match std::mem::replace(handle, Handle::Placeholder) {
Handle::AwaitingContextFd => {
let mut proc_fd = usize::MAX;
req.obtain_fd(
socket,
FobtainFdFlags::empty(),
std::slice::from_mut(&mut proc_fd),
)?;
let proc_fd = unsafe { OwnedFd::from_raw_fd(proc_fd as RawFd) };
let [ruid, euid, suid] = [0, 0, 0];
let [rgid, egid, sgid] = [0, 0, 0];
let mut payload = [0; size_of::<u32>() * 6];
plain::slice_from_mut_bytes(&mut payload)
.unwrap()
.copy_from_slice(&[ruid, euid, suid, rgid, egid, sgid]);
if let Err(err) = proc_call(
proc_fd.as_raw_fd() as usize,
&mut payload,
CallFlags::empty(),
&[ProcCall::SetResugid as u64],
) {
eprintln!("failed to setresugid: {err}");
}
*handle = Handle::AwaitingNamespaceFetch {
ns: libredox::Fd::new(
syscall::dup(libredox::call::getns().unwrap(), b"").unwrap(),
),
};
}
old => {
*handle = old;
return Err(Error::new(EBADF));
}
}
Ok(0)
}
}
fn daemon_main() -> ! {
// TODO: Linux kernel audit-like logging?
let socket = Socket::create().expect("failed to open scheme socket");
let mut scheme = Scheme {
next_fd: 1,
handles: HashMap::new(),
};
register_sync_scheme(&socket, "sudo", &mut scheme)
.expect("failed to register sudo scheme to namespace");
loop {
let Some(req) = socket
.next_request(SignalBehavior::Restart)
.expect("failed to get request")
else {
break;
};
let response = match req.kind() {
RequestKind::Call(call) => call.handle_sync(&mut scheme),
RequestKind::SendFd(req) => Response::new(scheme.on_sendfd(&socket, &req), req),
RequestKind::OnClose { id } => {
scheme.on_close(id);
continue;
}
_ => continue,
};
socket
.write_response(response, SignalBehavior::Restart)
.expect("sudo: scheme write failed");
}
std::process::exit(0)
}
-205
View File
@@ -1,205 +0,0 @@
#[macro_use]
extern crate clap;
use std::process::exit;
use extra::option::OptionalExt;
use redox_users::{All, AllGroups, AllUsers, Config, GroupBuilder, UserBuilder};
use userutils::create_user_dir;
const _MAN_PAGE: &'static str = /* @MANSTART{useradd} */
r#"
NAME
useradd - add a new user
SYNOPSYS
useradd [ options ] LOGIN
useradd [ -h | --help ]
DESCRIPTION
The useradd utility creates a new user based on
system defaults and values passed on the command line.
Useradd creates a new group for the user by default and
can also be instructed to create the user's home directory.
Note that useradd creates a new user with the password
unset (no login). This is better documented with the
redox_users crate.
OPTIONS
-h, --help
Display this help and exit.
-c, --comment
Any text string, usually used as the user's full name.
Historically known as the GECOS field
-d, --home-dir HOME_DIR
The new user will be created with HOME_DIR as their home
directory. The default value is LOGIN prepended with "/home/".
This flag DOES NOT create the home directory. See --create-home
-g, --gid GID
The group id to use when creating the default login group. This value
must not be in use and must be non-negative. The default is to pick the
smallest available group id between values defined in redox_users.
-m, --create-home
Creates the user's home directory if it does not already exist.
This option is not enabled by default. This option must be specified
for a home directory to be created. If not set, the user's home dir is
set to "/"
-N, --no-user-group
Do not attempt to create the user's user group. Instead, the groupid
is set to 99 ("nobody"). -N and -g are mutually exclusive.
-s, --shell SHELL
The path to the user's default login shell. If not specified, the
default shell is set as "/bin/ion"
-u, --uid UID
The user id to use. This value must not be in use and must be
non-negative. The default is to pick the smallest available
user id between the defaults defined in redox_users
AUTHORS
Written by Wesley Hershberger.
"#; /* @MANEND */
const DEFAULT_SHELL: &'static str = "/bin/ion";
const DEFAULT_HOME: &'static str = "/home";
const DEFAULT_NO_GROUP: &'static str = "nobody";
fn main() {
let args = clap_app!(useradd =>
(author: "Wesley Hershberger")
(about: "Add users based on the system's redox_users backend")
(@arg LOGIN:
+required
"Add user LOGIN")
(@arg COMMENT:
-c --comment
+takes_value
"Set user description (GECOS field)")
(@arg HOME_DIR:
-d --("home-dir")
+takes_value
"Set LOGIN's home dir to HOME_DIR (does not create directory)")
(@arg CREATE_HOME:
-m --("create-home")
"Create the user's home directory")
(@arg SHELL:
-s --shell
+takes_value
"Set user's default login shell")
(@arg GID:
-g --gid
+takes_value
"Set LOGIN's primary group id. Positive integer and must not be in use.")
(@arg NO_USER_GROUP:
-N --("no-user-group")
conflicts_with[GID]
"Do not create primary user group (set gid to 99, \"nobody\")")
(@arg UID:
-u --uid
+takes_value
"Set LOGIN's user id. Positive ineger and must not be in use.")
)
.get_matches();
// unwrap is safe because of "+required". clap-rs is cool...
let login = args.value_of("LOGIN").unwrap();
let mut sys_users =
AllUsers::authenticator(Config::default().writeable(true)).unwrap_or_exit(1);
let mut sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
let uid = match args.value_of("UID") {
Some(uid) => {
let id = uid.parse::<usize>().unwrap_or_exit(1);
if let Some(_user) = sys_users.get_by_id(id) {
eprintln!("useradd: userid already in use: {}", id);
exit(1);
}
id
}
None => sys_users.get_unique_id().unwrap_or_else(|| {
eprintln!("useradd: no available uid");
exit(1);
}),
};
let gid = if args.is_present("NO_USER_GROUP") {
let nobody = sys_groups
.get_mut_by_name(DEFAULT_NO_GROUP)
.unwrap_or_else(|| {
eprintln!("useradd: group not found: {}", DEFAULT_NO_GROUP);
exit(1)
});
nobody.users.push(login.to_string());
99
} else {
let id = match args.value_of("GID") {
Some(id) => {
let id = id.parse::<usize>().unwrap_or_exit(1);
if let Some(_group) = sys_groups.get_by_id(id) {
eprintln!("useradd: group already exists with gid: {}", id);
exit(1);
}
id
}
None => sys_groups.get_unique_id().unwrap_or_else(|| {
eprintln!("useradd: no available gid");
exit(1);
}),
};
sys_groups
.add_group(GroupBuilder::new(login).gid(id).user(login))
.unwrap_or_else(|err| {
eprintln!("useradd: {}: {}", err, login);
exit(1);
});
id
};
let gecos = args.value_of("COMMENT").unwrap_or(login);
//Ugly way to satisfy the borrow checker...
let mut sys_homes = String::from(DEFAULT_HOME);
let userhome = args.value_of("HOME_DIR").unwrap_or_else(|| {
if args.is_present("CREATE_HOME") {
sys_homes.push_str("/");
sys_homes.push_str(&login);
sys_homes.as_str()
} else {
"/"
}
});
let shell = args.value_of("SHELL").unwrap_or(DEFAULT_SHELL);
let user = UserBuilder::new(login)
.uid(uid)
.gid(gid)
.name(gecos)
.home(userhome)
.shell(shell);
sys_users.add_user(user).unwrap_or_else(|err| {
eprintln!("useradd: {}: {}", err, login);
exit(1);
});
// Make sure to try and create the user/groups before we create
// their home, that way we get a permissions error that makes
// more sense
sys_groups.save().unwrap_or_exit(1);
sys_users.save().unwrap_or_exit(1);
if args.is_present("CREATE_HOME") {
//Shouldn't ever error...
let user = sys_users.get_by_id(uid).unwrap_or_exit(1);
create_user_dir(user, userhome).unwrap_or_exit(1);
}
}
-69
View File
@@ -1,69 +0,0 @@
#[macro_use]
extern crate clap;
use std::fs::remove_dir;
use std::process::exit;
use extra::option::OptionalExt;
use redox_users::{All, AllGroups, AllUsers, Config};
use userutils::AllGroupsExt;
const _MAN_PAGE: &'static str = /* @MANSTART{userdel} */
r#"
NAME
userdel - modify system files to delete users
SYNOPSYS
userdel [ options ] LOGIN
userdel [ -h | --help ]
DESCRIPTION
userdel removes users from whatever backend is employed by
the system's redox_users. The utility removes the user from
all groups of which they are a member.
It can also be used to manage removal of home directories.
OPTIONS
-h, --help
Print this help page and exit.
-r, --remove
The user's home directory and all files inside will be
removed.
AUTHORS
Wesley Hershberger.
"#; /* @MANEND */
fn main() {
let args = clap_app!(userdel =>
(author: "Wesley Hershberger")
(about: "Removes system users using redox_users")
(@arg LOGIN: +required "Remove user LOGIN")
(@arg REMOVE: -r --remove "Remove the user's home and all files and directories inside")
)
.get_matches();
let login = args.value_of("LOGIN").unwrap();
let mut sys_users =
AllUsers::authenticator(Config::default().writeable(true)).unwrap_or_exit(1);
let mut sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
{
sys_groups.remove_user_from_all_groups(login);
if args.is_present("REMOVE") {
let user = sys_users.get_by_name(login).unwrap_or_else(|| {
eprintln!("userdel: user does not exist: {}", login);
exit(1);
});
remove_dir(&user.home).unwrap_or_exit(1);
}
}
sys_users.remove_by_name(login.to_string());
sys_groups.save().unwrap_or_exit(1);
sys_users.save().unwrap_or_exit(1);
}
-196
View File
@@ -1,196 +0,0 @@
#[macro_use]
extern crate clap;
use std::fs::{remove_dir, rename};
use std::process::exit;
use extra::option::OptionalExt;
use redox_users::{All, AllGroups, AllUsers, Config};
use userutils::{AllGroupsExt, create_user_dir};
const _MAN_PAGE: &'static str = /* @MANSTART{usermod} */
r#"
NAME
usermod - modify user information
SYNOPSYS
usermod [ options ] LOGIN
usermod [ -h | --help ]
DESCRIPTION
The usermod utility can be used to modify user information.
This utility uses the redox_users API, so the backend is whatever
backend in use on the system for that API at the time.
See passwd for setting user passwords.
OPTIONS
-h, --help
Display this help and exit.
-c, --comment COMMENT
The comment field (or GECOS, historically) for the user. This
is typically the full name of the user, although sometimes it
includes an e-mail.
-d, --home-dir HOME_DIR
Sets the home directory to HOME_DIR and creates the directory.
See -m for move
-m, --move-home
Moves the the user's old home directory into the home directory
specified by --home-dir. Has no effect if passed without --home-dir
-G, --append-groups GROUP[,GROUP, ...]
Add this user to GROUP groups. This does not remove the user from
any group of which they are already a member.
-S, --set-groups GROUP[,GROUP, ...]
Remove the user from all groups of which they are a part and add
them to GROUP groups.
-g, --gid GID
Set the user's primary group id. If the group does not exist,
a warning is issued and no changes are applied.
-l, --login NEW_LOGIN
Set the new login name for the user. Must not be in use.
-s, --shell SHELL
Set the user's login shell as SHELL. This must be a full path.
-u, --uid UID
Set the user's user id. If another user's userid is the same as
UID, a warning is issued and no changes are applied. Note that
changing the value of the user's userid may have unexpected consequences.
AUTHORS
Written by Wesley Hershberger.
"#; /* @MANEND */
fn main() {
let args = clap_app!(usermod =>
(author: "Wesley Hershberger")
(about: "Modify users according to the system's redox_users backend")
(@arg LOGIN:
+required
"Apply modifications to LOGIN")
(@arg COMMENT:
-c --comment
+takes_value
"Set LOGIN's description (GECOS field)")
(@arg HOME_DIR:
-d --("home-dir")
+takes_value
"Create and set LOGIN's home directory")
(@arg MOVE_HOME:
-m --("move-home")
requires[HOME_DIR]
"Move LOGIN's old home to HOME_DIR (see --home-dir) instead of creating it. Requires -d")
(@arg APPEND_GROUPS:
-G --("append-groups")
+takes_value conflicts_with[SET_GROUPS]
"Add user to groups specified (comma separated list, see man page)")
(@arg SET_GROUPS:
-S --("set-groups")
+takes_value conflicts_with[APPEND_GROUPS]
"Set LOGIN's groups as specified (truncates existing, see man page)")
(@arg GID:
-g --gid
+takes_value
"Set LOGIN's primary group id. Group must exist")
(@arg NEW_LOGIN:
-l --login
+takes_value
"Set LOGIN's name to NEW_LOGIN")
(@arg SHELL:
-s --shell
+takes_value
"Set LOGIN's default login shell")
(@arg UID:
-u --uid
+takes_value
"Set LOGIN's user id. See man page for details")
).get_matches();
let login = args.value_of("LOGIN").unwrap();
//TODO: Does not always need shadowfile access
let mut sys_users =
AllUsers::authenticator(Config::default().writeable(true)).unwrap_or_exit(1);
let mut sys_groups;
if let Some(new_groups) = args.value_of("SET_GROUPS") {
sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
sys_groups.remove_user_from_all_groups(login);
sys_groups
.add_user_to_groups(login, new_groups.split(',').collect())
.unwrap_or_exit(1);
sys_groups.save().unwrap_or_exit(1);
}
if let Some(new_groups) = args.value_of("APPEND_GROUPS") {
sys_groups = AllGroups::new(Config::default().writeable(true)).unwrap_or_exit(1);
sys_groups
.add_user_to_groups(login, new_groups.split(',').collect())
.unwrap_or_exit(1);
sys_groups.save().unwrap_or_exit(1);
}
let uid = args.value_of("UID").map(|uid| {
let uid = uid.parse::<usize>().unwrap_or_exit(1);
if let Some(_user) = sys_users.get_by_id(uid) {
eprintln!("usermod: userid already in use: {}", uid);
exit(1);
}
uid
});
{
let user = sys_users.get_mut_by_name(&login).unwrap_or_else(|| {
eprintln!("usermod: user \"{}\" not found", login);
exit(1);
});
if let Some(gecos) = args.value_of("COMMENT") {
user.name = gecos.to_string();
}
if let Some(new_login) = args.value_of("NEW_LOGIN") {
user.user = new_login.to_string();
}
if let Some(shell) = args.value_of("SHELL") {
user.shell = shell.to_string();
}
if let Some(home) = args.value_of("HOME_DIR") {
if args.is_present("MOVE_HOME") {
rename(&user.home, &home).unwrap_or_exit(1);
} else {
create_user_dir(user, &home).unwrap_or_exit(1);
remove_dir(&user.home).unwrap_or_exit(1);
}
user.home = home.to_string();
}
if let Some(uid) = uid {
user.uid = uid;
}
if let Some(gid) = args.value_of("GID") {
sys_groups = AllGroups::new(Config::default()).unwrap_or_exit(1);
let gid = gid.parse::<usize>().unwrap_or_exit(1);
if let Some(_group) = sys_groups.get_by_id(gid) {
user.gid = gid;
} else {
eprintln!("usermod: no group found for id: {}", gid);
exit(1);
}
}
}
sys_users.save().unwrap_or_exit(1);
}
+393
View File
@@ -0,0 +1,393 @@
use core::{fmt, marker::PhantomData, mem, ops, slice};
use endian_num::Le;
use crate::BLOCK_SIZE;
const BLOCK_LIST_ENTRIES: usize = BLOCK_SIZE as usize / mem::size_of::<BlockPtr<BlockRaw>>();
/// An address of a data block.
///
/// This encodes a block's position _and_ [`BlockLevel`]:
/// the first four bits of this `u64` encode the block's level,
/// the next four bits indicates decompression level,
/// the rest encode its index.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct BlockAddr(u64);
impl BlockAddr {
const INDEX_SHIFT: u64 = 8;
const DECOMP_LEVEL_MASK: u64 = 0xF0;
const DECOMP_LEVEL_SHIFT: u64 = 4;
const LEVEL_MASK: u64 = 0xF;
// Unsafe because this can create invalid blocks
pub unsafe fn new(index: u64, meta: BlockMeta) -> Self {
// Level must fit within LEVEL_MASK
if meta.level.0 > Self::LEVEL_MASK as usize {
panic!("block level too large");
}
// Decomp level must fit within DECOMP_LEVEL_MASK
let decomp_level = meta.decomp_level.unwrap_or_default();
if (decomp_level.0 << Self::DECOMP_LEVEL_SHIFT) > Self::DECOMP_LEVEL_MASK as usize {
panic!("decompressed block level too large");
}
// Index must not use the metadata bits
let inner = index
.checked_shl(Self::INDEX_SHIFT as u32)
.expect("block index too large")
| ((decomp_level.0 as u64) << Self::DECOMP_LEVEL_SHIFT)
| (meta.level.0 as u64);
Self(inner)
}
pub fn null(meta: BlockMeta) -> Self {
unsafe { Self::new(0, meta) }
}
pub fn index(&self) -> u64 {
// The first four bits store the level
self.0 >> Self::INDEX_SHIFT
}
pub fn level(&self) -> BlockLevel {
// The first four bits store the level
BlockLevel((self.0 & Self::LEVEL_MASK) as usize)
}
pub fn decomp_level(&self) -> Option<BlockLevel> {
let value = (self.0 & Self::DECOMP_LEVEL_MASK) >> Self::DECOMP_LEVEL_SHIFT;
if value != 0 {
Some(BlockLevel(value as usize))
} else {
None
}
}
pub fn meta(&self) -> BlockMeta {
BlockMeta {
level: self.level(),
decomp_level: self.decomp_level(),
}
}
pub fn is_null(&self) -> bool {
self.index() == 0
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct BlockMeta {
pub(crate) level: BlockLevel,
pub(crate) decomp_level: Option<BlockLevel>,
}
impl BlockMeta {
pub fn new(level: BlockLevel) -> Self {
Self {
level,
decomp_level: None,
}
}
pub fn new_compressed(level: BlockLevel, decomp_level: BlockLevel) -> Self {
Self {
level,
decomp_level: Some(decomp_level),
}
}
}
/// The size of a block.
///
/// Level 0 blocks are blocks of [`BLOCK_SIZE`] bytes.
/// A level 1 block consists of two consecutive level 0 blocks.
/// A level n block consists of two consecutive level n-1 blocks.
///
/// See [`crate::Allocator`] docs for more details.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct BlockLevel(pub(crate) usize);
impl BlockLevel {
/// Returns the smallest block level that can contain
/// the given number of bytes.
pub(crate) fn for_bytes(bytes: u64) -> Self {
if bytes == 0 {
return BlockLevel(0);
}
let level = bytes
.div_ceil(BLOCK_SIZE)
.next_power_of_two()
.trailing_zeros() as usize;
BlockLevel(level)
}
/// The number of [`BLOCK_SIZE`] blocks (i.e, level 0 blocks)
/// in a block of this level
pub fn blocks<T: From<u32>>(self) -> T {
T::from(1u32 << self.0)
}
/// The number of bytes in a block of this level
pub fn bytes(self) -> u64 {
BLOCK_SIZE << self.0
}
}
pub unsafe trait BlockTrait {
/// Create an empty block of this type.
fn empty(level: BlockLevel) -> Option<Self>
where
Self: Sized;
}
/// A [`BlockAddr`] and the data it points to.
#[derive(Clone, Copy, Debug, Default)]
pub struct BlockData<T> {
addr: BlockAddr,
data: T,
}
impl<T> BlockData<T> {
pub fn new(addr: BlockAddr, data: T) -> Self {
Self { addr, data }
}
pub fn addr(&self) -> BlockAddr {
self.addr
}
pub fn data(&self) -> &T {
&self.data
}
pub fn data_mut(&mut self) -> &mut T {
&mut self.data
}
pub(crate) unsafe fn into_parts(self) -> (BlockAddr, T) {
(self.addr, self.data)
}
/// Set the address of this [`BlockData`] to `addr`, returning this
/// block's old address. This method does not update block data.
///
/// `addr` must point to a block with the same level as this block.
#[must_use = "don't forget to de-allocate old block address"]
pub fn swap_addr(&mut self, addr: BlockAddr) -> BlockAddr {
// Address levels must match
assert_eq!(self.addr.level(), addr.level());
let old = self.addr;
self.addr = addr;
old
}
}
impl<T: BlockTrait> BlockData<T> {
pub fn empty(addr: BlockAddr) -> Option<Self> {
let empty = T::empty(addr.level())?;
Some(Self::new(addr, empty))
}
}
impl<T: ops::Deref<Target = [u8]>> BlockData<T> {
pub fn create_ptr(&self) -> BlockPtr<T> {
BlockPtr {
addr: self.addr.0.into(),
hash: seahash::hash(self.data.deref()).into(),
phantom: PhantomData,
}
}
}
#[repr(C, packed)]
pub struct BlockList<T> {
pub ptrs: [BlockPtr<T>; BLOCK_LIST_ENTRIES],
}
unsafe impl<T> BlockTrait for BlockList<T> {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self {
ptrs: [BlockPtr::default(); BLOCK_LIST_ENTRIES],
})
} else {
None
}
}
}
impl<T> BlockList<T> {
pub fn is_empty(&self) -> bool {
self.ptrs.iter().all(|ptr| ptr.is_null())
}
}
impl<T> ops::Deref for BlockList<T> {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const BlockList<T> as *const u8,
mem::size_of::<BlockList<T>>(),
) as &[u8]
}
}
}
impl<T> ops::DerefMut for BlockList<T> {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut BlockList<T> as *mut u8,
mem::size_of::<BlockList<T>>(),
) as &mut [u8]
}
}
}
/// An address of a data block, along with a checksum of its data.
///
/// This encodes a block's position _and_ [`BlockLevel`].
/// the first four bits of `addr` encode the block's level,
/// the rest encode its index.
///
/// Also see [`BlockAddr`].
#[repr(C, packed)]
pub struct BlockPtr<T> {
addr: Le<u64>,
hash: Le<u64>,
phantom: PhantomData<T>,
}
impl<T> BlockPtr<T> {
pub fn null(meta: BlockMeta) -> Self {
Self {
addr: BlockAddr::null(meta).0.into(),
hash: 0.into(),
phantom: PhantomData,
}
}
pub fn addr(&self) -> BlockAddr {
BlockAddr(self.addr.to_ne())
}
pub fn hash(&self) -> u64 {
self.hash.to_ne()
}
pub fn is_null(&self) -> bool {
self.addr().is_null()
}
pub fn marker(level: u8) -> Self {
assert!(level <= 0xF);
Self {
addr: (0xFFFF_FFFF_FFFF_FFF0 | (level as u64)).into(),
hash: u64::MAX.into(),
phantom: PhantomData,
}
}
pub fn is_marker(&self) -> bool {
(self.addr.to_ne() | 0xF) == u64::MAX && self.hash.to_ne() == u64::MAX
}
/// Cast BlockPtr to another type
///
/// # Safety
/// Unsafe because it can be used to transmute types
pub unsafe fn cast<U>(self) -> BlockPtr<U> {
BlockPtr {
addr: self.addr,
hash: self.hash,
phantom: PhantomData,
}
}
#[must_use = "the returned pointer should usually be deallocated"]
pub fn clear(&mut self) -> BlockPtr<T> {
let mut ptr = Self::default();
mem::swap(self, &mut ptr);
ptr
}
}
impl<T> Clone for BlockPtr<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for BlockPtr<T> {}
impl<T> Default for BlockPtr<T> {
fn default() -> Self {
Self {
addr: 0.into(),
hash: 0.into(),
phantom: PhantomData,
}
}
}
impl<T> fmt::Debug for BlockPtr<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let addr = self.addr();
let hash = self.hash();
f.debug_struct("BlockPtr")
.field("addr", &addr)
.field("hash", &hash)
.finish()
}
}
#[repr(C, packed)]
#[derive(Clone)]
pub struct BlockRaw([u8; BLOCK_SIZE as usize]);
unsafe impl BlockTrait for BlockRaw {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self([0; BLOCK_SIZE as usize]))
} else {
None
}
}
}
impl ops::Deref for BlockRaw {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.0
}
}
impl ops::DerefMut for BlockRaw {
fn deref_mut(&mut self) -> &mut [u8] {
&mut self.0
}
}
#[test]
fn block_list_size_test() {
assert_eq!(mem::size_of::<BlockList<BlockRaw>>(), BLOCK_SIZE as usize);
}
#[test]
fn block_raw_size_test() {
assert_eq!(mem::size_of::<BlockRaw>(), BLOCK_SIZE as usize);
}
#[test]
fn block_ptr_marker_test() {
let ptr = BlockPtr::<BlockRaw>::marker(0);
assert_eq!(ptr.addr().level().0, 0);
assert!(ptr.is_marker());
let ptr = BlockPtr::<BlockRaw>::marker(2);
assert_eq!(ptr.addr().level().0, 2);
assert!(ptr.is_marker());
}
+90
View File
@@ -0,0 +1,90 @@
use crate::{Disk, FileSystem, Node, Transaction, TreePtr, BLOCK_SIZE};
fn tx_progress<D: Disk, F: FnMut(u64)>(tx: &mut Transaction<D>, progress: &mut F) {
let size = tx.header.size();
let free = tx.allocator.free() * BLOCK_SIZE;
progress(size - free);
}
//TODO: handle hard links
fn clone_at<D: Disk, E: Disk, F: FnMut(u64)>(
tx_old: &mut Transaction<D>,
parent_ptr_old: TreePtr<Node>,
tx: &mut Transaction<E>,
parent_ptr: TreePtr<Node>,
buf: &mut [u8],
progress: &mut F,
) -> syscall::Result<()> {
let mut entries = Vec::new();
tx_old.child_nodes(parent_ptr_old, &mut entries)?;
for entry in entries {
//TODO: return error instead?
let Some(name) = entry.name() else {
continue;
};
let node_ptr_old = entry.node_ptr();
let node_old = tx_old.read_tree(node_ptr_old)?;
//TODO: this slows down the clone, but Redox has issues without this (Linux is fine)
if tx.write_cache.len() > 64 {
tx.sync(false)?;
}
let node_ptr = {
let mode = node_old.data().mode();
let (ctime, ctime_nsec) = node_old.data().ctime();
let (mtime, mtime_nsec) = node_old.data().mtime();
let mut node = tx.create_node(parent_ptr, &name, mode, ctime, ctime_nsec)?;
node.data_mut().set_uid(node_old.data().uid());
node.data_mut().set_gid(node_old.data().gid());
node.data_mut().set_mtime(mtime, mtime_nsec);
if !node_old.data().is_dir() {
let mut offset = 0;
loop {
let count = tx_old.read_node_inner(&node_old, offset, buf)?;
if count == 0 {
break;
}
tx.write_node_inner(&mut node, &mut offset, &buf[..count])?;
}
}
let node_ptr = node.ptr();
tx.sync_tree(node)?;
node_ptr
};
tx_progress(tx, progress);
if node_old.data().is_dir() {
clone_at(tx_old, node_ptr_old, tx, node_ptr, buf, progress)?;
}
}
Ok(())
}
pub fn clone<D: Disk, E: Disk, F: FnMut(u64)>(
fs_old: &mut FileSystem<D>,
fs: &mut FileSystem<E>,
mut progress: F,
) -> syscall::Result<()> {
fs_old.tx(|tx_old| {
let mut tx = Transaction::new(fs);
// Clone at root node
let mut buf = vec![0; 4 * 1024 * 1024];
clone_at(
tx_old,
TreePtr::root(),
&mut tx,
TreePtr::root(),
&mut buf,
&mut progress,
)?;
// Commit and squash alloc log
tx.commit(true)
})
}
+300
View File
@@ -0,0 +1,300 @@
use core::{mem, ops, slice, str};
use crate::{BlockLevel, BlockTrait, Node, TreePtr, BLOCK_SIZE, DIR_ENTRY_MAX_LENGTH};
#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct DirEntry {
node_ptr: TreePtr<Node>,
name: [u8; DIR_ENTRY_MAX_LENGTH],
}
impl DirEntry {
pub fn new(node_ptr: TreePtr<Node>, name: &str) -> DirEntry {
let mut entry = DirEntry {
node_ptr,
..Default::default()
};
entry.name[..name.len()].copy_from_slice(name.as_bytes());
entry
}
pub fn node_ptr(&self) -> TreePtr<Node> {
self.node_ptr
}
fn name_len(&self) -> usize {
let mut len = 0;
while len < self.name.len() {
if self.name[len] == 0 {
break;
}
len += 1;
}
len
}
pub fn name(&self) -> Option<&str> {
let len = self.name_len();
//TODO: report utf8 error?
str::from_utf8(&self.name[..len]).ok()
}
// 4 bytes TreePtr
// 1 byte name_len
const SERIALIZED_PREFIX_SIZE: usize = mem::size_of::<TreePtr<Node>>() + 1;
pub fn serialized_size(&self) -> usize {
DirEntry::SERIALIZED_PREFIX_SIZE + self.name_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Option<usize> {
let required = self.serialized_size();
if buf.len() < required {
return None;
}
buf[0..4].copy_from_slice(&self.node_ptr().to_bytes());
buf[4] = self.name_len() as u8;
buf[5..5 + self.name_len()].copy_from_slice(&self.name[..self.name_len()]);
Some(required)
}
fn deserialize_from(buf: &[u8]) -> Result<(Self, usize), &'static str> {
if buf.len() <= DirEntry::SERIALIZED_PREFIX_SIZE {
return Err("Buffer too small");
}
let node_ptr: TreePtr<Node> =
TreePtr::from_bytes(buf[0..4].try_into().expect("Slice must be 4 bytes long"));
let name_len = buf[4] as usize;
if name_len < 1 || name_len > DIR_ENTRY_MAX_LENGTH {
return Err("Invalid name length");
}
if buf.len() < DirEntry::SERIALIZED_PREFIX_SIZE + name_len {
return Err("Buffer too small");
}
let mut name = [0u8; DIR_ENTRY_MAX_LENGTH];
name[..name_len].copy_from_slice(
&buf[DirEntry::SERIALIZED_PREFIX_SIZE..DirEntry::SERIALIZED_PREFIX_SIZE + name_len],
);
Ok((
DirEntry { node_ptr, name },
DirEntry::SERIALIZED_PREFIX_SIZE + name_len,
))
}
}
impl Default for DirEntry {
fn default() -> Self {
Self {
node_ptr: TreePtr::default(),
name: [0; DIR_ENTRY_MAX_LENGTH],
}
}
}
pub struct DirList {
count: u16,
entry_bytes_len: u16,
entry_bytes: [u8; BLOCK_SIZE as usize - 4],
}
unsafe impl BlockTrait for DirList {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self {
count: 0,
entry_bytes_len: 0,
entry_bytes: [0; BLOCK_SIZE as usize - 4],
})
} else {
None
}
}
}
impl DirList {
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn entries(&self) -> DirEntryIterator<'_> {
DirEntryIterator {
dir_list: self,
emit_count: 0,
position: 0,
}
}
fn entry_position_for_name(&self, name: &str) -> Option<usize> {
let name_len = name.len();
let mut position = 0;
let mut entry_id = 0;
while entry_id < self.count {
let entry_name_len = self.entry_bytes[position + 4] as usize;
if entry_name_len == name_len {
let start = DirEntry::SERIALIZED_PREFIX_SIZE + position;
let entry_name = &self.entry_bytes[start..start + entry_name_len];
if entry_name == name.as_bytes() {
return Some(position);
}
}
position += DirEntry::SERIALIZED_PREFIX_SIZE + entry_name_len;
entry_id += 1;
}
None
}
pub fn find_entry(&self, name: &str) -> Option<DirEntry> {
if let Some(position) = self.entry_position_for_name(name) {
let (entry, _) = DirEntry::deserialize_from(&self.entry_bytes[position..]).unwrap();
return Some(entry);
}
None
}
pub fn remove_entry(&mut self, name: &str) -> bool {
if let Some(position) = self.entry_position_for_name(name) {
let entry_size =
DirEntry::SERIALIZED_PREFIX_SIZE + self.entry_bytes[position + 4] as usize;
let remaining_size = self.entry_bytes_len as usize - position - entry_size;
if remaining_size > 0 {
self.entry_bytes.copy_within(
position + entry_size..self.entry_bytes_len as usize,
position,
);
}
self.entry_bytes_len -= entry_size as u16;
self.count -= 1;
return true;
}
false
}
pub fn for_each_entry<F>(&self, mut f: F)
where
F: FnMut(&[u8; 4], &[u8]),
{
let mut position = 0;
let mut entry_id = 0;
while entry_id < self.count {
let node_ptr_bytes = &self.entry_bytes[position..position + 4];
//let node_ptr = TreePtr::<Node>::from_bytes(node_ptr_bytes.try_into().unwrap());
let entry_name_len = self.entry_bytes[position + 4] as usize;
let start = DirEntry::SERIALIZED_PREFIX_SIZE + position;
let entry_name = &self.entry_bytes[start..start + entry_name_len];
f(node_ptr_bytes.try_into().unwrap(), entry_name);
position += DirEntry::SERIALIZED_PREFIX_SIZE + entry_name_len;
entry_id += 1;
}
}
pub fn append(&mut self, entry: &DirEntry) -> bool {
let entry_bytes_len = self.entry_bytes_len as usize;
if let Some(size) = entry.serialize_into(&mut self.entry_bytes[entry_bytes_len..]) {
self.count += 1;
self.entry_bytes_len += size as u16;
return true;
}
false
}
pub fn entry_count(&self) -> usize {
self.count as usize
}
}
impl ops::Deref for DirList {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const DirList as *const u8,
mem::size_of::<DirList>(),
) as &[u8]
}
}
}
impl ops::DerefMut for DirList {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(self as *mut DirList as *mut u8, mem::size_of::<DirList>())
as &mut [u8]
}
}
}
pub struct DirEntryIterator<'a> {
dir_list: &'a DirList,
emit_count: usize,
position: usize,
}
impl Iterator for DirEntryIterator<'_> {
type Item = DirEntry;
fn next(&mut self) -> Option<Self::Item> {
if self.emit_count < self.dir_list.entry_count() {
let position = self.position;
let (entry, bytes_read) =
DirEntry::deserialize_from(&self.dir_list.entry_bytes[position..]).unwrap();
self.emit_count += 1;
self.position += bytes_read;
Some(entry)
} else {
None
}
}
}
#[cfg(test)]
mod test {
use super::*;
use alloc::format;
#[test]
fn dir_list_size_test() {
use core::ops::Deref;
assert_eq!(
DirList::empty(BlockLevel(0)).unwrap().deref().len(),
BLOCK_SIZE as usize
);
}
#[test]
fn test_append() {
let mut dir_list = DirList::empty(BlockLevel(0)).unwrap();
let dirent = DirEntry::new(TreePtr::new(123), "test000");
assert!(dir_list.append(&dirent));
assert_eq!(dir_list.entry_count(), 1);
assert_eq!(dir_list.entry_bytes_len as usize, dirent.serialized_size());
let max_entries = dir_list.entry_bytes.len() / dirent.serialized_size();
for i in 1..max_entries {
let dirent = DirEntry::new(TreePtr::new(123), format!("test{i:03}").as_str());
assert!(dir_list.append(&dirent), "Failed on iteration {i}");
}
let dirent = DirEntry::new(TreePtr::new(123), format!("test{max_entries}").as_str());
assert!(!dir_list.append(&dirent));
for (i, entry) in dir_list.entries().enumerate() {
assert_eq!(entry.name().unwrap(), format!("test{i:03}"));
}
}
}
+110
View File
@@ -0,0 +1,110 @@
use std::collections::{HashMap, VecDeque};
use std::{cmp, ptr};
use syscall::error::Result;
use crate::disk::Disk;
use crate::BLOCK_SIZE;
fn copy_memory(src: &[u8], dest: &mut [u8]) -> usize {
let len = cmp::min(src.len(), dest.len());
unsafe { ptr::copy(src.as_ptr(), dest.as_mut_ptr(), len) };
len
}
pub struct DiskCache<T> {
inner: T,
cache: HashMap<u64, [u8; BLOCK_SIZE as usize]>,
order: VecDeque<u64>,
size: usize,
}
impl<T: Disk> DiskCache<T> {
pub fn new(inner: T) -> Self {
// 16 MB cache
let size = 16 * 1024 * 1024 / BLOCK_SIZE as usize;
DiskCache {
inner,
cache: HashMap::with_capacity(size),
order: VecDeque::with_capacity(size),
size,
}
}
fn insert(&mut self, i: u64, data: [u8; BLOCK_SIZE as usize]) {
while self.order.len() >= self.size {
let removed = self.order.pop_front().unwrap();
self.cache.remove(&removed);
}
self.cache.insert(i, data);
self.order.push_back(i);
}
}
impl<T: Disk> Disk for DiskCache<T> {
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
// println!("Cache read at {}", block);
let mut read = 0;
let mut failed = false;
for i in 0..buffer.len().div_ceil(BLOCK_SIZE as usize) {
let block_i = block + i as u64;
let buffer_i = i * BLOCK_SIZE as usize;
let buffer_j = cmp::min(buffer_i + BLOCK_SIZE as usize, buffer.len());
let buffer_slice = &mut buffer[buffer_i..buffer_j];
if let Some(cache_buf) = self.cache.get_mut(&block_i) {
read += copy_memory(cache_buf, buffer_slice);
} else {
failed = true;
break;
}
}
if failed {
self.inner.read_at(block, buffer)?;
read = 0;
for i in 0..buffer.len().div_ceil(BLOCK_SIZE as usize) {
let block_i = block + i as u64;
let buffer_i = i * BLOCK_SIZE as usize;
let buffer_j = cmp::min(buffer_i + BLOCK_SIZE as usize, buffer.len());
let buffer_slice = &buffer[buffer_i..buffer_j];
let mut cache_buf = [0; BLOCK_SIZE as usize];
read += copy_memory(buffer_slice, &mut cache_buf);
self.insert(block_i, cache_buf);
}
}
Ok(read)
}
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
//TODO: Write only blocks that have changed
// println!("Cache write at {}", block);
self.inner.write_at(block, buffer)?;
let mut written = 0;
for i in 0..buffer.len().div_ceil(BLOCK_SIZE as usize) {
let block_i = block + i as u64;
let buffer_i = i * BLOCK_SIZE as usize;
let buffer_j = cmp::min(buffer_i + BLOCK_SIZE as usize, buffer.len());
let buffer_slice = &buffer[buffer_i..buffer_j];
let mut cache_buf = [0; BLOCK_SIZE as usize];
written += copy_memory(buffer_slice, &mut cache_buf);
self.insert(block_i, cache_buf);
}
Ok(written)
}
fn size(&mut self) -> Result<u64> {
self.inner.size()
}
}
+84
View File
@@ -0,0 +1,84 @@
use std::fs::{File, OpenOptions};
use std::io::{Seek, SeekFrom};
use std::os::unix::fs::FileExt;
use std::path::Path;
use syscall::error::{Error, Result, EIO};
use crate::disk::Disk;
use crate::BLOCK_SIZE;
pub struct DiskFile {
pub file: File,
}
trait ResultExt {
type T;
fn or_eio(self) -> Result<Self::T>;
}
impl<T> ResultExt for Result<T> {
type T = T;
fn or_eio(self) -> Result<Self::T> {
match self {
Ok(t) => Ok(t),
Err(err) => {
eprintln!("RedoxFS: IO ERROR: {err}");
Err(Error::new(EIO))
}
}
}
}
impl<T> ResultExt for std::io::Result<T> {
type T = T;
fn or_eio(self) -> Result<Self::T> {
match self {
Ok(t) => Ok(t),
Err(err) => {
eprintln!("RedoxFS: IO ERROR: {err}");
Err(Error::new(EIO))
}
}
}
}
impl DiskFile {
pub fn open(path: impl AsRef<Path>) -> Result<DiskFile> {
let file = OpenOptions::new()
.read(true)
.write(true)
.open(path)
.or_eio()?;
Ok(DiskFile { file })
}
pub fn create(path: impl AsRef<Path>, size: u64) -> Result<DiskFile> {
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path)
.or_eio()?;
file.set_len(size).or_eio()?;
Ok(DiskFile { file })
}
}
impl Disk for DiskFile {
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
self.file.read_at(buffer, block * BLOCK_SIZE).or_eio()
}
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
self.file.write_at(buffer, block * BLOCK_SIZE).or_eio()
}
fn size(&mut self) -> Result<u64> {
self.file.seek(SeekFrom::End(0)).or_eio()
}
}
impl From<File> for DiskFile {
fn from(file: File) -> Self {
Self { file }
}
}
+38
View File
@@ -0,0 +1,38 @@
use std::io::{Read, Seek, SeekFrom, Write};
use syscall::error::{Error, Result, EIO};
use crate::disk::Disk;
use crate::BLOCK_SIZE;
macro_rules! try_disk {
($expr:expr) => {
match $expr {
Ok(val) => val,
Err(err) => {
eprintln!("Disk I/O Error: {}", err);
return Err(Error::new(EIO));
}
}
};
}
pub struct DiskIo<T>(pub T);
impl<T: Read + Write + Seek> Disk for DiskIo<T> {
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
try_disk!(self.0.seek(SeekFrom::Start(block * BLOCK_SIZE)));
let count = try_disk!(self.0.read(buffer));
Ok(count)
}
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
try_disk!(self.0.seek(SeekFrom::Start(block * BLOCK_SIZE)));
let count = try_disk!(self.0.write(buffer));
Ok(count)
}
fn size(&mut self) -> Result<u64> {
let size = try_disk!(self.0.seek(SeekFrom::End(0)));
Ok(size)
}
}
+42
View File
@@ -0,0 +1,42 @@
use syscall::error::{Error, Result, EIO};
use crate::disk::Disk;
use crate::BLOCK_SIZE;
pub struct DiskMemory {
data: Vec<u8>,
}
impl DiskMemory {
pub fn new(size: u64) -> DiskMemory {
DiskMemory {
data: vec![0; size as usize],
}
}
}
impl Disk for DiskMemory {
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
let offset = (block * BLOCK_SIZE) as usize;
let end = offset + buffer.len();
if end > self.data.len() {
return Err(Error::new(EIO));
}
buffer.copy_from_slice(&self.data[offset..end]);
Ok(buffer.len())
}
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
let offset = (block * BLOCK_SIZE) as usize;
let end = offset + buffer.len();
if end > self.data.len() {
return Err(Error::new(EIO));
}
self.data[offset..end].copy_from_slice(buffer);
Ok(buffer.len())
}
fn size(&mut self) -> Result<u64> {
Ok(self.data.len() as u64)
}
}
+41
View File
@@ -0,0 +1,41 @@
use syscall::error::Result;
#[cfg(feature = "std")]
pub use self::cache::DiskCache;
#[cfg(feature = "std")]
pub use self::file::DiskFile;
#[cfg(feature = "std")]
pub use self::io::DiskIo;
#[cfg(feature = "std")]
pub use self::memory::DiskMemory;
#[cfg(feature = "std")]
pub use self::sparse::DiskSparse;
#[cfg(feature = "std")]
mod cache;
#[cfg(feature = "std")]
mod file;
#[cfg(feature = "std")]
mod io;
#[cfg(feature = "std")]
mod memory;
#[cfg(feature = "std")]
mod sparse;
/// A disk
pub trait Disk {
/// Read blocks from disk
///
/// # Safety
/// Unsafe to discourage use, use filesystem wrappers instead
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize>;
/// Write blocks from disk
///
/// # Safety
/// Unsafe to discourage use, use filesystem wrappers instead
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize>;
/// Get size of disk in bytes
fn size(&mut self) -> Result<u64>;
}
+53
View File
@@ -0,0 +1,53 @@
use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::Path;
use syscall::error::{Error, Result, EIO};
use crate::disk::Disk;
use crate::BLOCK_SIZE;
macro_rules! try_disk {
($expr:expr) => {
match $expr {
Ok(val) => val,
Err(err) => {
eprintln!("Disk I/O Error: {}", err);
return Err(Error::new(EIO));
}
}
};
}
pub struct DiskSparse {
pub file: File,
pub max_size: u64,
}
impl DiskSparse {
pub fn create<P: AsRef<Path>>(path: P, max_size: u64) -> Result<DiskSparse> {
let file = try_disk!(OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path));
Ok(DiskSparse { file, max_size })
}
}
impl Disk for DiskSparse {
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
try_disk!(self.file.seek(SeekFrom::Start(block * BLOCK_SIZE)));
let count = try_disk!(self.file.read(buffer));
Ok(count)
}
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
try_disk!(self.file.seek(SeekFrom::Start(block * BLOCK_SIZE)));
let count = try_disk!(self.file.write(buffer));
Ok(count)
}
fn size(&mut self) -> Result<u64> {
Ok(self.max_size)
}
}
+331
View File
@@ -0,0 +1,331 @@
use aes::Aes128;
use alloc::{
boxed::Box,
collections::{BTreeMap, VecDeque},
vec,
};
use syscall::error::{Error, Result, EKEYREJECTED, ENOENT, ENOKEY};
use xts_mode::{get_tweak_default, Xts128};
#[cfg(feature = "std")]
use crate::{AllocEntry, AllocList, BlockData, BlockTrait, Key, KeySlot, Node, Salt, TreeList};
use crate::{
Allocator, BlockAddr, BlockLevel, BlockMeta, Disk, Header, Transaction, BLOCK_SIZE,
HEADER_RING, RECORD_SIZE,
};
fn compress_cache() -> Box<[u8]> {
vec![0; lz4_flex::block::get_maximum_output_size(RECORD_SIZE as usize)].into_boxed_slice()
}
/// A file system
pub struct FileSystem<D: Disk> {
//TODO: make private
pub disk: D,
//TODO: make private
pub block: u64,
//TODO: make private
pub header: Header,
pub(crate) allocator: Allocator,
pub(crate) cipher_opt: Option<Xts128<Aes128>>,
pub(crate) compress_cache: Box<[u8]>,
pub node_usages: BTreeMap<u32, u64>,
}
impl<D: Disk> FileSystem<D> {
/// Open a file system on a disk
pub fn open(
mut disk: D,
password_opt: Option<&[u8]>,
block_opt: Option<u64>,
cleanup: bool,
) -> Result<Self> {
for ring_block in block_opt.map_or(0..65536, |x| x..x + 1) {
let mut header = Header::default();
unsafe { disk.read_at(ring_block, &mut header)? };
// Skip invalid headers
if !header.valid() {
continue;
}
let block = ring_block - (header.generation() % HEADER_RING);
for i in 0..HEADER_RING {
let mut other_header = Header::default();
unsafe { disk.read_at(block + i, &mut other_header)? };
// Skip invalid headers
if !other_header.valid() {
continue;
}
// If this is a newer header, use it
if other_header.generation() > header.generation() {
header = other_header;
}
}
let cipher_opt = match password_opt {
Some(password) => {
if !header.encrypted() {
// Header not encrypted but password provided
return Err(Error::new(EKEYREJECTED));
}
match header.cipher(password) {
Some(cipher) => Some(cipher),
None => {
// Header encrypted with a different password
return Err(Error::new(ENOKEY));
}
}
}
None => {
if header.encrypted() {
// Header encrypted but no password provided
return Err(Error::new(ENOKEY));
}
None
}
};
let mut fs = FileSystem {
disk,
block,
header,
allocator: Allocator::default(),
cipher_opt,
compress_cache: compress_cache(),
node_usages: BTreeMap::new(),
};
unsafe { fs.reset_allocator()? };
if cleanup {
fs.cleanup()?
}
return Ok(fs);
}
Err(Error::new(ENOENT))
}
/// Create a file system on a disk
#[cfg(feature = "std")]
pub fn create(
disk: D,
password_opt: Option<&[u8]>,
ctime: u64,
ctime_nsec: u32,
) -> Result<Self> {
Self::create_reserved(disk, password_opt, &[], ctime, ctime_nsec)
}
/// Create a file system on a disk, with reserved data at the beginning
/// Reserved data will be zero padded up to the nearest block
/// We need to pass ctime and ctime_nsec in order to initialize the unix timestamps
#[cfg(feature = "std")]
pub fn create_reserved(
mut disk: D,
password_opt: Option<&[u8]>,
reserved: &[u8],
ctime: u64,
ctime_nsec: u32,
) -> Result<Self> {
let disk_size = disk.size()?;
let disk_blocks = disk_size / BLOCK_SIZE;
let block_offset = (reserved.len() as u64).div_ceil(BLOCK_SIZE);
if disk_blocks < (block_offset + HEADER_RING + 4) {
return Err(Error::new(syscall::error::ENOSPC));
}
let fs_blocks = disk_blocks - block_offset;
// Fill reserved data, pad with zeroes
for block in 0..block_offset as usize {
let mut data = [0; BLOCK_SIZE as usize];
let mut i = 0;
while i < data.len() && block * BLOCK_SIZE as usize + i < reserved.len() {
data[i] = reserved[block * BLOCK_SIZE as usize + i];
i += 1;
}
unsafe {
disk.write_at(block as u64, &data)?;
}
}
let mut header = Header::new(fs_blocks * BLOCK_SIZE);
let cipher_opt = match password_opt {
Some(password) => {
//TODO: handle errors
header.key_slots[0] = KeySlot::new(
password,
Salt::new().unwrap(),
(Key::new().unwrap(), Key::new().unwrap()),
)
.unwrap();
Some(header.key_slots[0].cipher(password).unwrap())
}
None => None,
};
let mut fs = FileSystem {
disk,
block: block_offset,
header,
allocator: Allocator::default(),
cipher_opt,
compress_cache: compress_cache(),
node_usages: BTreeMap::new(),
};
// Write header generation zero
let count = unsafe { fs.disk.write_at(fs.block, &fs.header)? };
if count != core::mem::size_of_val(&fs.header) {
// Wrote wrong number of bytes
#[cfg(feature = "log")]
log::error!("CREATE: WRONG NUMBER OF BYTES");
return Err(Error::new(syscall::error::EIO));
}
// Set tree and alloc pointers and write header generation one
fs.tx(|tx| unsafe {
let tree = BlockData::new(
BlockAddr::new(HEADER_RING + 1, BlockMeta::default()),
TreeList::empty(BlockLevel::default()).unwrap(),
);
let mut alloc = BlockData::new(
BlockAddr::new(HEADER_RING + 2, BlockMeta::default()),
AllocList::empty(BlockLevel::default()).unwrap(),
);
let alloc_free = fs_blocks - (HEADER_RING + 4);
alloc.data_mut().entries[0] = AllocEntry::new(HEADER_RING + 4, alloc_free as i64);
tx.header.tree = tx.write_block(tree)?;
tx.header.alloc = tx.write_block(alloc)?;
tx.header_changed = true;
Ok(())
})?;
unsafe {
fs.reset_allocator()?;
}
fs.tx(|tx| unsafe {
let mut root = BlockData::new(
BlockAddr::new(HEADER_RING + 3, BlockMeta::default()),
Node::new(Node::MODE_DIR | 0o755, 0, 0, ctime, ctime_nsec),
);
root.data_mut().set_links(1);
let root_ptr = tx.write_block(root)?;
assert_eq!(tx.insert_tree(root_ptr)?.id(), 1);
Ok(())
})?;
fs.cleanup()?;
Ok(fs)
}
/// Release unused nodes and squash allocation log, happens on mount (with cleanup) and unmount
pub fn cleanup(&mut self) -> Result<()> {
let mut tx = Transaction::new(self);
tx.release_unused_nodes()?;
tx.commit(true)
}
/// start a filesystem transaction, required for making any changes
pub fn tx<F: FnOnce(&mut Transaction<D>) -> Result<T>, T>(&mut self, f: F) -> Result<T> {
let mut tx = Transaction::new(self);
let t = f(&mut tx)?;
tx.commit(false)?;
Ok(t)
}
pub fn allocator(&self) -> &Allocator {
&self.allocator
}
/// Unsafe as it can corrupt the filesystem
pub unsafe fn allocator_mut(&mut self) -> &mut Allocator {
&mut self.allocator
}
/// Reset allocator to state stored on disk
///
/// # Safety
/// Unsafe, it must only be called when opening the filesystem
unsafe fn reset_allocator(&mut self) -> Result<()> {
self.allocator = Allocator::default();
// To avoid having to update all prior alloc blocks, there is only a previous pointer
// This means we need to roll back all allocations. Currently we do this by reading the
// alloc log into a buffer to reverse it.
let mut allocs = VecDeque::new();
self.tx(|tx| {
let mut alloc_ptr = tx.header.alloc;
while !alloc_ptr.is_null() {
let alloc = tx.read_block(alloc_ptr)?;
alloc_ptr = alloc.data().prev;
allocs.push_front(alloc);
}
Ok(())
})?;
for alloc in allocs {
for entry in alloc.data().entries.iter() {
let index = entry.index();
let count = entry.count();
if count < 0 {
for i in 0..-count {
//TODO: replace assert with error?
let addr = BlockAddr::new(index + i as u64, BlockMeta::default());
assert_eq!(self.allocator.allocate_exact(addr), Some(addr));
}
} else {
for i in 0..count {
let addr = BlockAddr::new(index + i as u64, BlockMeta::default());
self.allocator.deallocate(addr);
}
}
}
}
Ok(())
}
pub(crate) fn decrypt(&mut self, data: &mut [u8], addr: BlockAddr) -> bool {
if let Some(ref cipher) = self.cipher_opt {
cipher.decrypt_area(
data,
BLOCK_SIZE as usize,
addr.index().into(),
get_tweak_default,
);
true
} else {
// Do nothing if encryption is disabled
false
}
}
pub(crate) fn encrypt(&mut self, data: &mut [u8], addr: BlockAddr) -> bool {
if let Some(ref cipher) = self.cipher_opt {
cipher.encrypt_area(
data,
BLOCK_SIZE as usize,
addr.index().into(),
get_tweak_default,
);
true
} else {
// Do nothing if encryption is disabled
false
}
}
}
+242
View File
@@ -0,0 +1,242 @@
use core::ops::{Deref, DerefMut};
use core::{fmt, mem, slice};
use endian_num::Le;
use aes::Aes128;
use xts_mode::{get_tweak_default, Xts128};
use crate::{AllocList, BlockPtr, KeySlot, ReleaseList, Tree, BLOCK_SIZE, SIGNATURE, VERSION};
pub const HEADER_RING: u64 = 256;
/// The header of the filesystem
#[derive(Clone, Copy)]
#[repr(C, packed)]
pub struct Header {
/// Signature, should be SIGNATURE
pub signature: [u8; 8],
/// Version, should be VERSION
pub version: Le<u64>,
/// Disk ID, a 128-bit unique identifier
pub uuid: [u8; 16],
/// Disk size, in number of BLOCK_SIZE sectors
pub size: Le<u64>,
/// Generation of header
pub generation: Le<u64>,
/// Block of first tree node
pub tree: BlockPtr<Tree>,
/// Block of last alloc node
pub alloc: BlockPtr<AllocList>,
/// Key slots
pub key_slots: [KeySlot; 64],
/// Nodes pending release, may be null
pub release: BlockPtr<ReleaseList>,
/// Padding
pub padding: [u8; BLOCK_SIZE as usize - 3192],
/// encrypted hash of header data without hash, set to hash and padded if disk is not encrypted
pub encrypted_hash: [u8; 16],
/// hash of header data without hash
pub hash: Le<u64>,
}
impl Header {
#[cfg(feature = "std")]
pub fn new(size: u64) -> Header {
let uuid = uuid::Uuid::new_v4();
let mut header = Header {
signature: *SIGNATURE,
version: VERSION.into(),
uuid: *uuid.as_bytes(),
size: size.into(),
..Default::default()
};
header.update_hash(None);
header
}
pub fn valid(&self) -> bool {
if &self.signature != SIGNATURE {
// Signature does not match
return false;
}
if self.version.to_ne() != VERSION {
// Version does not match
return false;
}
if self.hash.to_ne() != self.create_hash() {
// Hash does not match
return false;
}
// All tests passed, header is valid
true
}
pub fn uuid(&self) -> [u8; 16] {
self.uuid
}
pub fn size(&self) -> u64 {
self.size.to_ne()
}
pub fn generation(&self) -> u64 {
self.generation.to_ne()
}
fn create_hash(&self) -> u64 {
// Calculate part of header to hash (everything before the hashes)
let end = mem::size_of_val(self)
- mem::size_of_val(&{ self.hash })
- mem::size_of_val(&{ self.encrypted_hash });
seahash::hash(&self[..end])
}
fn create_encrypted_hash(&self, cipher_opt: Option<&Xts128<Aes128>>) -> [u8; 16] {
let mut encrypted_hash = [0; 16];
for (i, b) in self.hash.to_le_bytes().iter().enumerate() {
encrypted_hash[i] = *b;
}
if let Some(cipher) = cipher_opt {
let mut block = aes::Block::from(encrypted_hash);
cipher.encrypt_area(
&mut block,
BLOCK_SIZE as usize,
self.generation().into(),
get_tweak_default,
);
encrypted_hash = block.into();
}
encrypted_hash
}
pub fn encrypted(&self) -> bool {
(self.encrypted_hash) != self.create_encrypted_hash(None)
}
pub fn cipher(&self, password: &[u8]) -> Option<Xts128<Aes128>> {
let hash = self.create_encrypted_hash(None);
for slot in self.key_slots.iter() {
//TODO: handle errors
let cipher = slot.cipher(password).unwrap();
let mut block = aes::Block::from(self.encrypted_hash);
cipher.decrypt_area(
&mut block,
BLOCK_SIZE as usize,
self.generation().into(),
get_tweak_default,
);
if block == aes::Block::from(hash) {
return Some(cipher);
}
}
None
}
fn update_hash(&mut self, cipher_opt: Option<&Xts128<Aes128>>) {
self.hash = self.create_hash().into();
// Make sure to do this second, it relies on the hash being up to date
self.encrypted_hash = self.create_encrypted_hash(cipher_opt);
}
pub fn update(&mut self, cipher_opt: Option<&Xts128<Aes128>>) -> u64 {
let mut generation = self.generation();
generation += 1;
self.generation = generation.into();
self.update_hash(cipher_opt);
generation
}
}
impl Default for Header {
fn default() -> Self {
Self {
signature: [0; 8],
version: 0.into(),
uuid: [0; 16],
size: 0.into(),
generation: 0.into(),
tree: BlockPtr::<Tree>::default(),
alloc: BlockPtr::<AllocList>::default(),
key_slots: [KeySlot::default(); 64],
release: BlockPtr::<ReleaseList>::default(),
padding: [0; BLOCK_SIZE as usize - 3192],
encrypted_hash: [0; 16],
hash: 0.into(),
}
}
}
impl fmt::Debug for Header {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let signature = self.signature;
let version = self.version;
let uuid = self.uuid;
let size = self.size;
let generation = self.generation;
let tree = self.tree;
let alloc = self.alloc;
let release = self.release;
let hash = self.hash;
f.debug_struct("Header")
.field("signature", &signature)
.field("version", &version)
.field("uuid", &uuid)
.field("size", &size)
.field("generation", &generation)
.field("tree", &tree)
.field("alloc", &alloc)
.field("release", &release)
.field("hash", &hash)
.finish()
}
}
impl Deref for Header {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(self as *const Header as *const u8, mem::size_of::<Header>())
as &[u8]
}
}
}
impl DerefMut for Header {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(self as *mut Header as *mut u8, mem::size_of::<Header>())
as &mut [u8]
}
}
}
#[test]
fn header_not_valid_test() {
assert_eq!(Header::default().valid(), false);
}
#[test]
fn header_size_test() {
assert_eq!(mem::size_of::<Header>(), BLOCK_SIZE as usize);
}
#[test]
fn header_hash_test() {
let mut header = Header::default();
assert_eq!(header.create_hash(), 0xe81ffcb86026ff96);
header.update_hash(None);
assert_eq!(header.hash.to_ne(), 0xe81ffcb86026ff96);
assert_eq!(
header.encrypted_hash,
[0x96, 0xff, 0x26, 0x60, 0xb8, 0xfc, 0x1f, 0xe8, 0, 0, 0, 0, 0, 0, 0, 0]
);
}
#[cfg(feature = "std")]
#[test]
fn header_valid_test() {
assert_eq!(Header::new(0).valid(), true);
}
+691
View File
@@ -0,0 +1,691 @@
use alloc::string::String;
use alloc::vec::Vec;
use core::{fmt, mem, ops, slice};
use endian_num::Le;
use syscall::error::{Error, Result, EEXIST, EIO};
use crate::{
BlockLevel, BlockPtr, BlockRaw, BlockTrait, DirEntry, DirList, BLOCK_SIZE, RECORD_LEVEL,
};
pub const HTREE_IDX_ENTRIES: usize = BLOCK_SIZE as usize / mem::size_of::<HTreePtr<BlockRaw>>();
const HTREE_IDX_PADDING: usize =
BLOCK_SIZE as usize - mem::size_of::<[HTreePtr<BlockRaw>; HTREE_IDX_ENTRIES]>();
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(C, packed)]
pub struct HTreeHash(Le<u32>);
impl HTreeHash {
// Create a MAX constant populated iwth the maximum value of Le<u32> minus 1
pub const MAX: HTreeHash = HTreeHash(Le(u32::MAX - 1));
#[cfg(not(test))]
pub fn from_name(name: &str) -> Self {
let hash = seahash::hash(name.as_bytes()) as u32;
// Don't allow the default hash value to be calculated for a real hash
if hash == u32::MAX {
return Self::MAX;
}
Self(hash.into())
}
#[cfg(test)]
pub fn from_name(name: &str) -> Self {
// Allow overriding the hashing function to something easily controled for testing.
let hash = if let Some(pos) = name.rfind("__") {
let number_str = &name[pos + 2..];
number_str.parse::<u32>().unwrap()
} else {
seahash::hash(name.as_bytes()) as u32
};
// Don't allow the default hash value to be calculated for a real hash
if hash == u32::MAX {
return Self::MAX;
}
Self(hash.into())
}
/// Returns the maximum of two `HTreeHash` values, ignoring the default hash value.
pub fn max_ignoring_default(&self, other: Self) -> Self {
let default = HTreeHash::default();
if *self == default {
return other;
}
if other == default {
return *self;
}
if *self > other {
*self
} else {
other
}
}
pub fn find_max(dir_list: &DirList) -> Option<HTreeHash> {
let mut max_hash = HTreeHash::default();
dir_list.for_each_entry(|_ptr_bytes, name_bytes| {
let name = String::from_utf8_lossy(name_bytes);
let hash = HTreeHash::from_name(name.as_ref());
max_hash = max_hash.max_ignoring_default(hash);
});
if max_hash == HTreeHash::default() {
None
} else {
Some(max_hash)
}
}
}
impl Default for HTreeHash {
/// The default hash value is the maximum possible value to push it to the end of the list when sorting.
fn default() -> Self {
Self(u32::MAX.into())
}
}
#[repr(C, packed)]
pub struct HTreePtr<T> {
pub htree_hash: HTreeHash,
pub ptr: BlockPtr<T>,
}
impl<T> HTreePtr<T> {
pub fn new(htree_hash: HTreeHash, ptr: BlockPtr<T>) -> Self {
Self { htree_hash, ptr }
}
/// Cast HTreePtr to another type
///
/// # Safety
/// Unsafe because it can be used to transmute types
pub unsafe fn cast<U>(self) -> HTreePtr<U> {
HTreePtr {
htree_hash: self.htree_hash,
ptr: self.ptr.cast(),
}
}
}
impl<T> HTreePtr<T> {
pub fn is_null(&self) -> bool {
self.ptr.is_null()
}
}
impl<T> Clone for HTreePtr<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for HTreePtr<T> {}
impl<T> Default for HTreePtr<T> {
fn default() -> Self {
Self {
htree_hash: HTreeHash::default(),
ptr: BlockPtr::default(),
}
}
}
impl<T> fmt::Debug for HTreePtr<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let htree_hash = self.htree_hash;
let ptr = self.ptr;
f.debug_struct("BlockPtr")
.field("htree_hash", &htree_hash)
.field("ptr", &ptr)
.finish()
}
}
#[repr(C, packed)]
pub struct HTreeNode<T> {
pub ptrs: [HTreePtr<T>; HTREE_IDX_ENTRIES],
padding: [u8; HTREE_IDX_PADDING],
}
impl<T> HTreeNode<T> {
pub fn find_max_htree_hash(&self) -> Option<HTreeHash> {
let mut hash = HTreeHash::default();
for entry in self.ptrs.iter() {
hash = hash.max_ignoring_default(entry.htree_hash);
}
if hash != HTreeHash::default() {
Some(hash)
} else {
None
}
}
pub fn find_ptrs_for_read(
&self,
htree_hash: HTreeHash,
) -> impl Iterator<Item = (usize, &HTreePtr<T>)> {
let mut last_hash = HTreeHash(0.into());
self.ptrs
.iter()
.enumerate()
.filter(move |(_idx, entry)| entry.htree_hash >= htree_hash)
.take_while(move |(_idx, entry)| {
let should_take = !entry.is_null() && last_hash <= htree_hash;
last_hash = entry.htree_hash;
should_take
})
}
}
unsafe impl<T> BlockTrait for HTreeNode<T> {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 <= RECORD_LEVEL {
Some(Self {
ptrs: [HTreePtr::default(); HTREE_IDX_ENTRIES],
padding: [0; HTREE_IDX_PADDING],
})
} else {
None
}
}
}
impl<T> ops::Deref for HTreeNode<T> {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const HTreeNode<T> as *const u8,
mem::size_of::<HTreeNode<T>>(),
) as &[u8]
}
}
}
impl<T> ops::DerefMut for HTreeNode<T> {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut HTreeNode<T> as *mut u8,
mem::size_of::<HTreeNode<T>>(),
) as &mut [u8]
}
}
}
pub fn add_inner_node<T>(
parent: &mut HTreeNode<T>,
new_ptr: HTreePtr<T>,
) -> Result<Option<(HTreeHash, HTreeNode<T>)>> {
// Update the input htree parameters in place
for ptr in parent.ptrs.iter_mut() {
if ptr.is_null() {
*ptr = new_ptr;
parent.ptrs.sort_by(|a, b| a.htree_hash.cmp(&b.htree_hash));
return Ok(None);
}
}
// The parent is full. We need to split it into two by half, ordered by the htree hash.
let mut all_ptrs = Vec::with_capacity(parent.ptrs.len() + 1);
for ptr in parent.ptrs.iter() {
all_ptrs.push(*ptr);
}
all_ptrs.push(new_ptr);
all_ptrs.sort_by(|a, b| a.htree_hash.cmp(&b.htree_hash));
let half_idx = all_ptrs.len() / 2;
// Find if there are duplicate name hashes on the boundary of where we want to split
let half_name_hash = all_ptrs[half_idx].htree_hash;
let mut first_idx = half_idx;
let mut last_idx = half_idx;
for (i, ptr) in all_ptrs.iter().enumerate() {
if ptr.htree_hash == half_name_hash {
if i < first_idx {
first_idx = i;
}
if i > last_idx {
last_idx = i;
}
}
}
// Split the entries_with_name_hash list at the index that minimizes the number of entries in each list while keeping the duplicate name hashes together
let split = if (half_idx - first_idx) < (last_idx - half_idx) {
first_idx
} else {
last_idx
};
let (ptrs1, ptrs2) = all_ptrs.split_at(split);
// Update the existing parent with the first half of the entries
let mut htree_idx1 = HTreeNode::empty(BlockLevel::default()).ok_or(Error::new(EIO))?;
htree_idx1.ptrs[..ptrs1.len()].copy_from_slice(ptrs1);
let _ = mem::replace(parent, htree_idx1);
// Return the second half as a new sibling parent
let mut htree_idx2 = HTreeNode::empty(BlockLevel::default()).ok_or(Error::new(EIO))?;
htree_idx2.ptrs[..ptrs2.len()].copy_from_slice(ptrs2);
let htree_hash2 = ptrs2[ptrs2.len() - 1].htree_hash;
Ok(Some((htree_hash2, htree_idx2)))
}
pub fn add_dir_entry(
dir_list: &mut DirList,
htree_hash: &mut HTreeHash,
dirent: DirEntry,
) -> Result<Option<(HTreeHash, DirList)>> {
if let Some(name) = dirent.name() {
if dir_list.find_entry(name).is_some() {
return Err(Error::new(EEXIST));
}
}
// Update the input htree parameters in place
let name = dirent.name().ok_or(Error::new(EIO))?;
if dir_list.append(&dirent) {
*htree_hash = HTreeHash::from_name(name).max_ignoring_default(*htree_hash);
return Ok(None);
}
// The dir_list is full. We need to split it into two dir_lists by half, ordered by the name hash.
let mut entries_with_name_hash = Vec::with_capacity(dir_list.entry_count() + 1);
for entry in dir_list.entries() {
entries_with_name_hash.push((
HTreeHash::from_name(entry.name().ok_or(Error::new(EIO))?),
entry,
));
}
entries_with_name_hash.push((HTreeHash::from_name(dirent.name().unwrap()), dirent));
entries_with_name_hash.sort_by(|a, b| a.0.cmp(&b.0));
let half = entries_with_name_hash.len() / 2;
let half_name_hash = entries_with_name_hash[half].0;
// Find if there are duplicate name hashes on the boundary of where we want to split
let mut first_idx = half;
let mut last_idx = half;
for (i, (name_hash, _)) in entries_with_name_hash.iter().enumerate() {
if *name_hash == half_name_hash {
if i < first_idx {
first_idx = i;
}
if i > last_idx {
last_idx = i;
}
}
}
last_idx += 1;
// Split the entries_with_name_hash list at the index that minimizes the number of entries in each list while keeping the duplicate name hashes together
let split = if (half - first_idx) < (last_idx - half) {
first_idx
} else {
last_idx
};
let split = split.max(1);
let sorted_entries = entries_with_name_hash
.iter()
.map(|(_, entry)| *entry)
.collect::<Vec<DirEntry>>();
let (entries1, entries2) = sorted_entries.split_at(split);
// Update the existing dir_list with the first half of the entries
let mut new_dir_list = DirList::empty(BlockLevel::default()).ok_or(Error::new(EIO))?;
for entry in entries1.iter() {
new_dir_list.append(entry);
}
let _ = mem::replace(dir_list, new_dir_list);
*htree_hash = entries_with_name_hash[entries1.len() - 1].0;
// Return the second half of the entries as a new dir_list
let mut new_dir_list = DirList::empty(BlockLevel::default()).ok_or(Error::new(EIO))?;
for entry in entries2.iter() {
new_dir_list.append(entry);
}
let new_name_hash = entries_with_name_hash[entries_with_name_hash.len() - 1].0;
Ok(Some((new_name_hash, new_dir_list)))
}
//
// MARK: Unit Tests
//
#[cfg(test)]
mod tests {
use super::*;
use crate::alloc::string::ToString;
use crate::TreePtr;
use alloc::format;
use alloc::string::String;
#[test]
fn htree_ptr_size_test() {
assert_eq!(mem::size_of::<HTreePtr<BlockRaw>>(), 20);
}
#[test]
fn htree_node_size_test() {
assert_eq!(mem::size_of::<HTreeNode<BlockRaw>>(), BLOCK_SIZE as usize);
}
#[test]
fn htree_hash_max_test() {
assert_eq!(HTreeHash::MAX, HTreeHash((u32::MAX - 1).into()));
}
#[test]
fn htree_hash_max_ignoring_default_test() {
let default = HTreeHash::default();
let hash1 = HTreeHash(0.into());
let hash2 = HTreeHash(1.into());
assert_eq!(hash1.max_ignoring_default(default), hash1);
assert_eq!(default.max_ignoring_default(hash1), hash1);
assert_eq!(hash1.max_ignoring_default(hash2), hash2);
}
#[test]
fn htree_node_find_max_htree_hash() {
// In practice, the HTreeHash values should always be in sorted order
let mut htree_node: HTreeNode<String> = HTreeNode::empty(BlockLevel::default()).unwrap();
htree_node.ptrs[0] = HTreePtr::new(HTreeHash(0.into()), BlockPtr::marker(0));
htree_node.ptrs[1] = HTreePtr::new(HTreeHash(1.into()), BlockPtr::marker(0));
htree_node.ptrs[2] = HTreePtr::new(HTreeHash(2.into()), BlockPtr::marker(0));
assert_eq!(
htree_node.find_max_htree_hash().unwrap(),
HTreeHash(2.into())
);
htree_node.ptrs[2] = HTreePtr::default();
assert_eq!(
htree_node.find_max_htree_hash().unwrap(),
HTreeHash(1.into())
);
htree_node.ptrs[1] = HTreePtr::default();
assert_eq!(
htree_node.find_max_htree_hash().unwrap(),
HTreeHash(0.into())
);
htree_node.ptrs[0] = HTreePtr::default();
assert!(htree_node.find_max_htree_hash().is_none());
// For thoroughness, test with HTreeHash out of order
htree_node.ptrs[2] = HTreePtr::new(HTreeHash(4.into()), BlockPtr::marker(0));
htree_node.ptrs[4] = HTreePtr::new(HTreeHash(6.into()), BlockPtr::marker(0));
htree_node.ptrs[6] = HTreePtr::new(HTreeHash(2.into()), BlockPtr::marker(0));
assert_eq!(
htree_node.find_max_htree_hash().unwrap(),
HTreeHash(6.into())
);
}
#[test]
fn htree_node_find_for_read() {
let mut htree_node: HTreeNode<String> = HTreeNode::empty(BlockLevel::default()).unwrap();
htree_node.ptrs[0] = HTreePtr::new(HTreeHash(0.into()), BlockPtr::marker(0));
htree_node.ptrs[1] = HTreePtr::new(HTreeHash(1.into()), BlockPtr::marker(0));
htree_node.ptrs[2] = HTreePtr::new(HTreeHash(2.into()), BlockPtr::marker(0));
htree_node.ptrs[3] = HTreePtr::new(HTreeHash(2.into()), BlockPtr::marker(0));
htree_node.ptrs[4] = HTreePtr::new(HTreeHash(3.into()), BlockPtr::marker(0));
htree_node.ptrs[5] = HTreePtr::new(HTreeHash(3.into()), BlockPtr::marker(0));
htree_node.ptrs[6] = HTreePtr::new(HTreeHash(5.into()), BlockPtr::marker(0));
htree_node.ptrs[7] = HTreePtr::new(HTreeHash(6.into()), BlockPtr::marker(0));
// Confirm that a hash that does not exist, but is less than an existing hash results in a single entry
let mut iter = htree_node.find_ptrs_for_read(HTreeHash(4.into()));
let mut val = iter.next().unwrap();
assert_eq!(val.0, 6);
assert_eq!(val.1.htree_hash, HTreeHash(5.into()));
assert!(iter.next().is_none());
// Confirm that a hash that equals an existing hash results in the match and one following entry
let mut iter = htree_node.find_ptrs_for_read(HTreeHash(1.into()));
val = iter.next().unwrap();
assert_eq!(val.0, 1);
assert_eq!(val.1.htree_hash, HTreeHash(1.into()));
val = iter.next().unwrap();
assert_eq!(val.0, 2);
assert_eq!(val.1.htree_hash, HTreeHash(2.into()));
assert!(iter.next().is_none());
// Confirm that multiple exact hash matches are all returned plus the next entry
let mut iter = htree_node.find_ptrs_for_read(HTreeHash(2.into()));
val = iter.next().unwrap();
assert_eq!(val.0, 2);
assert_eq!(val.1.htree_hash, HTreeHash(2.into()));
val = iter.next().unwrap();
assert_eq!(val.0, 3);
assert_eq!(val.1.htree_hash, HTreeHash(2.into()));
val = iter.next().unwrap();
assert_eq!(val.0, 4);
assert_eq!(val.1.htree_hash, HTreeHash(3.into()));
assert!(iter.next().is_none());
// Confirm that if the last entry matches and the next entry is null, only the match is returned
let mut iter = htree_node.find_ptrs_for_read(HTreeHash(6.into()));
val = iter.next().unwrap();
assert_eq!(val.0, 7);
assert_eq!(val.1.htree_hash, HTreeHash(6.into()));
assert!(iter.next().is_none());
// Confirm that if a hash that is larger than any existing entries, then no entries are returned
let mut iter = htree_node.find_ptrs_for_read(HTreeHash(7.into()));
assert!(iter.next().is_none());
}
#[test]
fn add_dir_entry_exists_test() {
let mut dir_list = DirList::empty(BlockLevel::default()).unwrap();
let mut htree_hash = HTreeHash::default();
let dirent = DirEntry::new(TreePtr::new(123), "test");
let new_sibling = add_dir_entry(&mut dir_list, &mut htree_hash, dirent).unwrap();
assert!(new_sibling.is_none());
assert_eq!(htree_hash, HTreeHash::from_name("test"));
assert_eq!(dir_list.entries().next().unwrap().name(), Some("test"));
// Add the same entry again, and it should fail with an appropriate IO error
let dirent = DirEntry::new(TreePtr::new(123), "test");
let error_expected = add_dir_entry(&mut dir_list, &mut htree_hash, dirent);
assert!(error_expected.is_err());
assert_eq!(error_expected.err().unwrap().errno, EEXIST);
}
#[test]
fn add_dir_entry_many_test() {
let mut dir_list = DirList::empty(BlockLevel::default()).unwrap();
let mut htree_hash = HTreeHash::default();
let total_count = 16;
// Fill up the dir_list
for i in 0..total_count {
let v: usize = i % 10;
let dirent = DirEntry::new(TreePtr::new(123), format!("test{v}_{i:0244}").as_str());
let new_sibling = add_dir_entry(&mut dir_list, &mut htree_hash, dirent).unwrap();
assert!(new_sibling.is_none());
}
// The maximum htree_hash should be retained
let max_tree_hash =
dir_list
.entries()
.enumerate()
.fold(HTreeHash::default(), |max, (i, _)| {
let v = i % 10;
let hash = HTreeHash::from_name(format!("test{v}_{i:0244}").as_str());
max.max_ignoring_default(hash)
});
assert_eq!(htree_hash, max_tree_hash);
// Confirm all the entries exist. Note they happen to be in insert order
for (i, entry) in dir_list.entries().enumerate() {
let v = i % 10;
assert_eq!(entry.name(), Some(format!("test{v}_{i:0244}").as_str()));
}
// Test a split by adding one more entry
let dirent = DirEntry::new(TreePtr::new(123), "test_split");
let new_sibling = add_dir_entry(&mut dir_list, &mut htree_hash, dirent).unwrap();
let (new_sibling_htree_hash, new_sibling_dir_list) =
new_sibling.expect("new_sibling should be created");
// assert!(new_sibling_dir_list.entries.len() );
assert!(new_sibling_htree_hash > htree_hash);
// The htree_hash should be less than the minimum htree_hash in new_sibling_dir_list
let new_sibling_min_htree_hash = new_sibling_dir_list
.entries()
.filter(|entry| !entry.node_ptr().is_null())
.fold(HTreeHash::default(), |min, entry| {
let hash = HTreeHash::from_name(entry.name().unwrap());
min.min(hash)
});
assert!(htree_hash < new_sibling_min_htree_hash);
// Confirm all the entries exist across both dir_lists
let mut expected_names: Vec<String> = (0..total_count)
.map(|i| {
let v = i % 10;
format!("test{v}_{i:0244}")
})
.collect();
expected_names.push("test_split".to_string());
expected_names.sort();
let mut dir_list_entry_count = 0;
for entry in dir_list.entries() {
dir_list_entry_count += 1;
let name = entry.name().unwrap().to_string();
let _ = expected_names.remove(expected_names.binary_search(&name).unwrap());
}
let mut new_sibling_entry_count = 0;
for entry in new_sibling_dir_list.entries() {
new_sibling_entry_count += 1;
let name = entry.name().unwrap().to_string();
let _ = expected_names.remove(expected_names.binary_search(&name).unwrap());
}
assert!(expected_names.is_empty());
// Confirm that the split is in half
assert!((dir_list_entry_count as i32 - new_sibling_entry_count).abs() <= 1);
}
#[test]
fn add_inner_node_simple_test() {
let mut htree_node: HTreeNode<_> = HTreeNode::empty(BlockLevel::default()).unwrap();
let htree_ptr: HTreePtr<_> = HTreePtr::<BlockRaw> {
htree_hash: HTreeHash::from_name("test"),
ptr: BlockPtr::marker(0),
};
let new_sibling = add_inner_node(&mut htree_node, htree_ptr).unwrap();
assert!(new_sibling.is_none());
assert_eq!(htree_node.ptrs[0].htree_hash, HTreeHash::from_name("test"));
}
#[test]
fn add_inner_node_multiple_test() {
let mut htree_node: HTreeNode<_> = HTreeNode::empty(BlockLevel::default()).unwrap();
for i in 0..HTREE_IDX_ENTRIES {
let htree_ptr: HTreePtr<_> = HTreePtr::<BlockRaw> {
htree_hash: HTreeHash(((100_000 + (i % 10) * 1000 + i) as u32).into()),
ptr: BlockPtr::marker(0),
};
let new_sibling = add_inner_node(&mut htree_node, htree_ptr).unwrap();
assert!(new_sibling.is_none());
// Confirm that the htree_ptrs are in sorted order at the start of the ptrs list
let mut prev_hash = HTreeHash::default();
let mut count = 0;
for ptr in htree_node.ptrs.iter() {
if ptr.is_null() {
continue;
}
assert!(
ptr.htree_hash.max_ignoring_default(prev_hash) == ptr.htree_hash,
"index {i}: {:?} > {:?}",
ptr.htree_hash,
prev_hash
);
prev_hash = ptr.htree_hash;
count += 1;
}
assert_eq!(count, i + 1);
}
// Confirm all expected hashes are present
let mut expected_hashes: Vec<u32> = (0..HTREE_IDX_ENTRIES)
.map(|i| (100_000 + (i % 10) * 1000 + i) as u32)
.collect();
expected_hashes.sort();
for ptr in htree_node.ptrs.iter() {
if ptr.is_null() {
break;
}
let idx = expected_hashes
.binary_search(&ptr.htree_hash.0.into())
.unwrap();
expected_hashes.remove(idx);
}
assert!(expected_hashes.is_empty());
// Force a split by adding one more entry
let htree_ptr: HTreePtr<_> = HTreePtr::<BlockRaw> {
htree_hash: HTreeHash(130_000.into()),
ptr: BlockPtr::marker(0),
};
let mut expected_hashes: Vec<u32> = (0..HTREE_IDX_ENTRIES)
.map(|i| (100_000 + (i % 10) * 1000 + i) as u32)
.collect();
expected_hashes.push(130_000);
expected_hashes.sort();
let new_sibling = add_inner_node(&mut htree_node, htree_ptr).unwrap();
let new_sibling = new_sibling.expect("new_sibling should be created");
// Confirm all the entries exist across both htree_nodes
let mut htree_node_entry_count = 0;
for ptr in htree_node.ptrs.iter() {
if ptr.ptr.is_null() {
break;
}
htree_node_entry_count += 1;
let idx = expected_hashes
.binary_search(&ptr.htree_hash.0.into())
.unwrap();
expected_hashes.remove(idx);
}
let mut new_sibling_entry_count = 0;
for ptr in new_sibling.1.ptrs.iter() {
if ptr.ptr.is_null() {
break;
}
new_sibling_entry_count += 1;
let idx = expected_hashes
.binary_search(&ptr.htree_hash.0.into())
.unwrap();
expected_hashes.remove(idx);
}
assert!(
expected_hashes.is_empty(),
"expected_hashes should be empty, but had length {}: {:?}",
expected_hashes.len(),
expected_hashes
);
// Confirm that the split is in half
assert!((htree_node_entry_count as i32 - new_sibling_entry_count).abs() <= 1);
}
}
+104
View File
@@ -0,0 +1,104 @@
use aes::{
cipher::{BlockDecrypt, BlockEncrypt, KeyInit},
Aes128,
};
use xts_mode::Xts128;
// The raw key, keep secret!
#[repr(transparent)]
pub struct Key([u8; 16]);
impl Key {
/// Generate a random key
#[cfg(feature = "std")]
pub fn new() -> Result<Self, getrandom::Error> {
let mut bytes = [0; 16];
getrandom::getrandom(&mut bytes)?;
Ok(Self(bytes))
}
pub fn encrypt(&self, password_aes: &Aes128) -> EncryptedKey {
let mut block = aes::Block::from(self.0);
password_aes.encrypt_block(&mut block);
EncryptedKey(block.into())
}
pub fn into_aes(self) -> Aes128 {
Aes128::new(&aes::Block::from(self.0))
}
}
/// The encrypted key, encrypted with AES using the salt and password
#[derive(Clone, Copy, Default)]
#[repr(transparent)]
pub struct EncryptedKey([u8; 16]);
impl EncryptedKey {
pub fn decrypt(&self, password_aes: &Aes128) -> Key {
let mut block = aes::Block::from(self.0);
password_aes.decrypt_block(&mut block);
Key(block.into())
}
}
/// Salt used to prevent rainbow table attacks on the encryption password
#[derive(Clone, Copy, Default)]
#[repr(transparent)]
pub struct Salt([u8; 16]);
impl Salt {
/// Generate a random salt
#[cfg(feature = "std")]
pub fn new() -> Result<Self, getrandom::Error> {
let mut bytes = [0; 16];
getrandom::getrandom(&mut bytes)?;
Ok(Self(bytes))
}
}
/// The key slot, containing the salt and encrypted key that are used with one password
#[derive(Clone, Copy, Default)]
#[repr(C, packed)]
pub struct KeySlot {
salt: Salt,
// Two keys for AES XTS 128
encrypted_keys: (EncryptedKey, EncryptedKey),
}
impl KeySlot {
/// Get the password AES key (generated from the password and salt, encrypts the real key)
pub fn password_aes(password: &[u8], salt: &Salt) -> Result<Aes128, argon2::Error> {
let mut key = Key([0; 16]);
let mut params_builder = argon2::ParamsBuilder::new();
params_builder.output_len(key.0.len())?;
let argon2 = argon2::Argon2::new(
argon2::Algorithm::Argon2id,
argon2::Version::V0x13,
params_builder.params()?,
);
argon2.hash_password_into(password, &salt.0, &mut key.0)?;
Ok(key.into_aes())
}
/// Create a new key slot from a password, salt, and encryption key
pub fn new(password: &[u8], salt: Salt, keys: (Key, Key)) -> Result<Self, argon2::Error> {
let password_aes = Self::password_aes(password, &salt)?;
Ok(Self {
salt,
encrypted_keys: (keys.0.encrypt(&password_aes), keys.1.encrypt(&password_aes)),
})
}
/// Get the encryption cipher from this key slot
pub fn cipher(&self, password: &[u8]) -> Result<Xts128<Aes128>, argon2::Error> {
let password_aes = Self::password_aes(password, &self.salt)?;
Ok(Xts128::new(
self.encrypted_keys.0.decrypt(&password_aes).into_aes(),
self.encrypted_keys.1.decrypt(&password_aes).into_aes(),
))
}
}
+63 -85
View File
@@ -1,92 +1,70 @@
//! Redox OS user and group utilities.
//!
//! The `userutils` crate contains the utilities for dealing with users and groups in Redox OS.
//! They are heavily influenced by UNIX and are, when needed, tailored to specific Redox use cases.
//!
//! These implementations strive to be as simple as possible drawing particular
//! inspiration by BSD systems. They are indeed small, by choice.
//!
//! The included utilities are:
//!
//! - `getty`: Used by `init(8)` to open and initialize the TTY line, read a login name and invoke `login(1)`.
//! - `id`: Displays user identity.
//! - `login`: Allows users to into the system.
//! - `passwd`: Allows users to modify their passwords.
//! - `su`: Allows users to substitute identity.
//! - `sudo`: Enables users to execute a command as another user.
//! - `whoami`: Display effective user ID.
#![crate_name = "redoxfs"]
#![crate_type = "lib"]
#![cfg_attr(not(feature = "std"), no_std)]
// Used often in generating redox_syscall errors
#![allow(clippy::or_fun_call)]
#![allow(unexpected_cfgs)]
use std::io::Result as IoResult;
extern crate alloc;
use libredox::call::{fchown, open};
use libredox::error::Result as SysResult;
use libredox::flag::{O_CLOEXEC, O_CREAT, O_DIRECTORY};
use redox_users::{All, AllGroups, Error, Result, User, auth};
use core::sync::atomic::AtomicUsize;
const DEFAULT_MODE: u16 = 0o700;
// The alloc log grows by 1 block about every 21 generations
pub const ALLOC_GC_THRESHOLD: u64 = 1024;
pub const BLOCK_SIZE: u64 = 4096;
// A record is 4KiB << 5 = 128KiB
pub const RECORD_LEVEL: usize = 5;
pub const RECORD_SIZE: u64 = BLOCK_SIZE << RECORD_LEVEL;
pub const SIGNATURE: &[u8; 8] = b"RedoxFS\0";
pub const VERSION: u64 = 8;
pub const DIR_ENTRY_MAX_LENGTH: usize = 252;
// Not the prettiest thing in the world, but some functionality here makes
// some of the utils much less gross
pub trait AllGroupsExt {
fn add_user_to_groups(&mut self, login: &str, groups: Vec<&str>) -> Result<()>;
fn remove_user_from_all_groups(&mut self, login: &str);
}
pub static IS_UMT: AtomicUsize = AtomicUsize::new(0);
impl AllGroupsExt for AllGroups {
// new_groups is a comma separated list of groupnames
fn add_user_to_groups(&mut self, login: &str, new_groups: Vec<&str>) -> Result<()> {
for groupname in new_groups {
let group = match self.get_mut_by_name(groupname) {
Some(group) => group,
None => return Err(Error::UserNotFound),
};
group.users.push(login.to_string());
}
Ok(())
}
pub use self::allocator::{AllocEntry, AllocList, Allocator, ReleaseList, ALLOC_LIST_ENTRIES};
#[cfg(feature = "std")]
pub use self::archive::{archive, archive_at};
pub use self::block::{
BlockAddr, BlockData, BlockLevel, BlockList, BlockMeta, BlockPtr, BlockRaw, BlockTrait,
};
#[cfg(feature = "std")]
pub use self::clone::clone;
pub use self::dir::{DirEntry, DirList};
pub use self::disk::*;
pub use self::filesystem::FileSystem;
pub use self::header::{Header, HEADER_RING};
pub use self::key::{Key, KeySlot, Salt};
#[cfg(feature = "std")]
pub use self::mount::mount;
pub use self::node::{Node, NodeFlags, NodeLevel, NodeLevelData};
pub use self::record::RecordRaw;
pub use self::transaction::Transaction;
pub use self::tree::{Tree, TreeData, TreeList, TreePtr};
#[cfg(feature = "std")]
pub use self::unmount::unmount_path;
/// Remove a user from all groups of which they are a member
fn remove_user_from_all_groups(&mut self, login: &str) {
for group in self.iter_mut() {
let op_pos = group.users.iter().position(|username| username == login);
if let Some(indx) = op_pos {
group.users.remove(indx);
}
}
}
}
mod allocator;
#[cfg(feature = "std")]
mod archive;
mod block;
#[cfg(feature = "std")]
mod clone;
mod dir;
mod disk;
mod filesystem;
mod header;
mod htree;
mod key;
#[cfg(all(feature = "std", not(fuzzing)))]
mod mount;
#[cfg(all(feature = "std", fuzzing))]
pub mod mount;
mod node;
mod record;
mod transaction;
mod tree;
#[cfg(feature = "std")]
mod unmount;
/// Spawns a shell for the given `User`.
///
/// This function wraps the shell_cmd function of the User struct
/// from redox_users and manages the child process. It is a blocking
/// operation.
///
/// # Examples
///
/// ```
/// use redox_users::AllUsers;
///
/// let sys_users = AllUsers::new().unwrap();
/// let user = sys_users.get_by_name("goyox86");
/// spawn_shell(user).unwrap();
/// ```
pub fn spawn_shell<T: Default>(user: &User<T>) -> IoResult<i32> {
let mut command = user.shell_cmd();
let mut child = command.spawn()?;
match child.wait()?.code() {
Some(code) => Ok(code),
None => Ok(1),
}
}
/// Creates a directory with 700 user:user permissions
pub fn create_user_dir<T>(user: &User<auth::Full>, dir: T) -> SysResult<()>
where
T: AsRef<str> + std::convert::AsRef<[u8]>,
{
let fd = open(dir, O_CREAT | O_DIRECTORY | O_CLOEXEC, DEFAULT_MODE)?;
fchown(fd, user.uid as u32, user.gid as u32)?;
Ok(())
}
#[cfg(all(feature = "std", test))]
mod tests;
+580
View File
@@ -0,0 +1,580 @@
extern crate fuser;
use std::cmp;
use std::ffi::OsStr;
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
use self::fuser::MountOption;
use self::fuser::TimeOrNow;
use crate::mount::fuse::TimeOrNow::Now;
use crate::mount::fuse::TimeOrNow::SpecificTime;
use crate::{filesystem, Disk, Node, TreeData, TreePtr, BLOCK_SIZE};
use self::fuser::{
FileAttr, FileType, Filesystem, ReplyAttr, ReplyCreate, ReplyData, ReplyDirectory, ReplyEmpty,
ReplyEntry, ReplyOpen, ReplyStatfs, ReplyWrite, Request, Session,
};
use std::time::Duration;
const TTL: Duration = Duration::new(1, 0); // 1 second
const NULL_TIME: Duration = Duration::new(0, 0);
pub fn mount<D, P, T, F>(
mut filesystem: filesystem::FileSystem<D>,
mountpoint: P,
callback: F,
) -> io::Result<T>
where
D: Disk,
P: AsRef<Path>,
F: FnOnce(&Path) -> T,
{
let mountpoint = mountpoint.as_ref();
// One of the uses of this redoxfs fuse wrapper is to populate a filesystem
// while building the Redox OS kernel. This means that we need to write on
// a filesystem that belongs to `root`, which in turn means that we need to
// be `root`, thus that we need to allow `root` to have access.
let defer_permissions = [MountOption::CUSTOM("defer_permissions".to_owned())];
let res = {
let mut session = Session::new(
Fuse {
fs: &mut filesystem,
},
mountpoint,
if cfg!(target_os = "macos") {
&defer_permissions
} else {
&[]
},
)?;
let res = callback(mountpoint);
session.run()?;
res
};
// Cleanup on unmount
filesystem.cleanup()?;
Ok(res)
}
pub struct Fuse<'f, D: Disk> {
pub fs: &'f mut filesystem::FileSystem<D>,
}
fn node_attr(node: &TreeData<Node>) -> FileAttr {
FileAttr {
ino: node.id() as u64,
size: node.data().size(),
// Blocks is in 512 byte blocks, not in our block size
blocks: node.data().blocks() * (BLOCK_SIZE / 512),
blksize: 512,
atime: SystemTime::UNIX_EPOCH + Duration::new(node.data().atime().0, node.data().atime().1),
mtime: SystemTime::UNIX_EPOCH + Duration::new(node.data().mtime().0, node.data().mtime().1),
ctime: SystemTime::UNIX_EPOCH + Duration::new(node.data().ctime().0, node.data().ctime().1),
crtime: UNIX_EPOCH + NULL_TIME,
kind: if node.data().is_dir() {
FileType::Directory
} else if node.data().is_symlink() {
FileType::Symlink
} else {
FileType::RegularFile
},
perm: node.data().mode() & Node::MODE_PERM,
nlink: node.data().links(),
uid: node.data().uid(),
gid: node.data().gid(),
rdev: 0,
flags: 0,
}
}
impl<D: Disk> Filesystem for Fuse<'_, D> {
fn lookup(&mut self, _req: &Request, parent_id: u64, name: &OsStr, reply: ReplyEntry) {
let parent_ptr = TreePtr::new(parent_id as u32);
match self
.fs
.tx(|tx| tx.find_node(parent_ptr, name.to_str().unwrap()))
{
Ok(node) => {
reply.entry(&TTL, &node_attr(&node), 0);
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn getattr(&mut self, _req: &Request, node_id: u64, _fh: Option<u64>, reply: ReplyAttr) {
let node_ptr = TreePtr::<Node>::new(node_id as u32);
match self.fs.tx(|tx| tx.read_tree(node_ptr)) {
Ok(node) => {
reply.attr(&TTL, &node_attr(&node));
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn setattr(
&mut self,
_req: &Request,
node_id: u64,
mode: Option<u32>,
uid: Option<u32>,
gid: Option<u32>,
size: Option<u64>,
atime: Option<TimeOrNow>,
mtime: Option<TimeOrNow>,
_ctime: Option<SystemTime>,
_fh: Option<u64>,
_crtime: Option<SystemTime>,
_chgtime: Option<SystemTime>,
_bkuptime: Option<SystemTime>,
_flags: Option<u32>,
reply: ReplyAttr,
) {
let node_ptr = TreePtr::<Node>::new(node_id as u32);
let mut node = match self.fs.tx(|tx| tx.read_tree(node_ptr)) {
Ok(ok) => ok,
Err(err) => {
reply.error(err.errno);
return;
}
};
let mut node_changed = false;
if let Some(mode) = mode {
if node.data().mode() & Node::MODE_PERM != mode as u16 & Node::MODE_PERM {
let new_mode =
(node.data().mode() & Node::MODE_TYPE) | (mode as u16 & Node::MODE_PERM);
node.data_mut().set_mode(new_mode);
node_changed = true;
}
}
if let Some(uid) = uid {
if node.data().uid() != uid {
node.data_mut().set_uid(uid);
node_changed = true;
}
}
if let Some(gid) = gid {
if node.data().gid() != gid {
node.data_mut().set_gid(gid);
node_changed = true;
}
}
if let Some(atime) = atime {
let atime_c = match atime {
SpecificTime(st) => st.duration_since(UNIX_EPOCH).unwrap(),
Now => SystemTime::now().duration_since(UNIX_EPOCH).unwrap(),
};
node.data_mut()
.set_atime(atime_c.as_secs(), atime_c.subsec_nanos());
node_changed = true;
}
if let Some(mtime) = mtime {
let mtime_c = match mtime {
SpecificTime(st) => st.duration_since(UNIX_EPOCH).unwrap(),
Now => SystemTime::now().duration_since(UNIX_EPOCH).unwrap(),
};
node.data_mut()
.set_mtime(mtime_c.as_secs(), mtime_c.subsec_nanos());
node_changed = true;
}
if let Some(size) = size {
match self.fs.tx(|tx| tx.truncate_node_inner(&mut node, size)) {
Ok(ok) => {
if ok {
node_changed = true;
}
}
Err(err) => {
reply.error(err.errno);
return;
}
}
}
let attr = node_attr(&node);
if node_changed {
if let Err(err) = self.fs.tx(|tx| tx.sync_tree(node)) {
reply.error(err.errno);
return;
}
}
reply.attr(&TTL, &attr);
}
fn open(&mut self, _req: &Request<'_>, node_id: u64, _flags: i32, reply: ReplyOpen) {
let node_ptr = TreePtr::<Node>::new(node_id as u32);
match self.fs.tx(|tx| tx.on_open_node(node_ptr)) {
Ok(()) => reply.opened(0, 0),
Err(err) => reply.error(err.errno),
}
}
fn read(
&mut self,
_req: &Request,
node_id: u64,
_fh: u64,
offset: i64,
size: u32,
_flags: i32,
_lock_owner: Option<u64>,
reply: ReplyData,
) {
let node_ptr = TreePtr::<Node>::new(node_id as u32);
let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
let mut data = vec![0; size as usize];
match self.fs.tx(|tx| {
tx.read_node(
node_ptr,
cmp::max(0, offset) as u64,
&mut data,
atime.as_secs(),
atime.subsec_nanos(),
)
}) {
Ok(count) => {
reply.data(&data[..count]);
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn write(
&mut self,
_req: &Request,
node_id: u64,
_fh: u64,
offset: i64,
data: &[u8],
_write_flags: u32,
_flags: i32,
_lock_owner: Option<u64>,
reply: ReplyWrite,
) {
let node_ptr = TreePtr::<Node>::new(node_id as u32);
let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
match self.fs.tx(|tx| {
tx.write_node(
node_ptr,
cmp::max(0, offset) as u64,
data,
mtime.as_secs(),
mtime.subsec_nanos(),
)
}) {
Ok(count) => {
reply.written(count as u32);
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn flush(&mut self, _req: &Request, _ino: u64, _fh: u64, _lock_owner: u64, reply: ReplyEmpty) {
reply.ok();
}
fn release(
&mut self,
_req: &Request,
node_id: u64,
_fh: u64,
_flags: i32,
_lock_owner: Option<u64>,
_flush: bool,
reply: ReplyEmpty,
) {
let node_ptr = TreePtr::new(node_id as u32);
match self.fs.tx(|tx| tx.on_close_node(node_ptr)) {
Ok(()) => reply.ok(),
Err(err) => reply.error(err.errno),
}
}
fn fsync(&mut self, _req: &Request, _ino: u64, _fh: u64, _datasync: bool, reply: ReplyEmpty) {
reply.ok();
}
fn readdir(
&mut self,
_req: &Request,
parent_id: u64,
_fh: u64,
offset: i64,
mut reply: ReplyDirectory,
) {
let parent_ptr = TreePtr::new(parent_id as u32);
let mut children = Vec::new();
match self.fs.tx(|tx| tx.child_nodes(parent_ptr, &mut children)) {
Ok(()) => {
let mut i;
let skip;
if offset == 0 {
skip = 0;
i = 0;
let _full = reply.add(parent_id, i, FileType::Directory, ".");
i += 1;
let _full = reply.add(
//TODO: get parent?
parent_id,
i,
FileType::Directory,
"..",
);
i += 1;
} else {
i = offset + 1;
skip = offset as usize - 1;
}
for child in children.iter().skip(skip) {
//TODO: make it possible to get file type from directory entry
let node = match self.fs.tx(|tx| tx.read_tree(child.node_ptr())) {
Ok(ok) => ok,
Err(err) => {
reply.error(err.errno);
return;
}
};
let full = reply.add(
child.node_ptr().id() as u64,
i,
if node.data().is_dir() {
FileType::Directory
} else {
FileType::RegularFile
},
child.name().unwrap(),
);
if full {
break;
}
i += 1;
}
reply.ok();
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn create(
&mut self,
_req: &Request,
parent_id: u64,
name: &OsStr,
mode: u32,
_umask: u32,
_flags: i32,
reply: ReplyCreate,
) {
let parent_ptr = TreePtr::<Node>::new(parent_id as u32);
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
match self.fs.tx(|tx| {
let node = tx.create_node(
parent_ptr,
name.to_str().unwrap(),
Node::MODE_FILE | (mode as u16 & Node::MODE_PERM),
ctime.as_secs(),
ctime.subsec_nanos(),
)?;
tx.on_open_node(node.ptr())?;
Ok(node)
}) {
Ok(node) => {
// println!("Create {:?}:{:o}:{:o}", node.1.name(), node.1.mode, mode);
reply.created(&TTL, &node_attr(&node), 0, 0, 0);
}
Err(error) => {
reply.error(error.errno);
}
}
}
fn mkdir(
&mut self,
_req: &Request,
parent_id: u64,
name: &OsStr,
mode: u32,
_umask: u32,
reply: ReplyEntry,
) {
let parent_ptr = TreePtr::<Node>::new(parent_id as u32);
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
match self.fs.tx(|tx| {
tx.create_node(
parent_ptr,
name.to_str().unwrap(),
Node::MODE_DIR | (mode as u16 & Node::MODE_PERM),
ctime.as_secs(),
ctime.subsec_nanos(),
)
}) {
Ok(node) => {
// println!("Mkdir {:?}:{:o}:{:o}", node.1.name(), node.1.mode, mode);
reply.entry(&TTL, &node_attr(&node), 0);
}
Err(error) => {
reply.error(error.errno);
}
}
}
fn rmdir(&mut self, _req: &Request, parent_id: u64, name: &OsStr, reply: ReplyEmpty) {
let parent_ptr = TreePtr::<Node>::new(parent_id as u32);
match self
.fs
.tx(|tx| tx.remove_node(parent_ptr, name.to_str().unwrap(), Node::MODE_DIR))
{
Ok(_) => {
reply.ok();
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn unlink(&mut self, _req: &Request, parent_id: u64, name: &OsStr, reply: ReplyEmpty) {
let parent_ptr = TreePtr::<Node>::new(parent_id as u32);
match self
.fs
.tx(|tx| tx.remove_node(parent_ptr, name.to_str().unwrap(), Node::MODE_FILE))
{
Ok(_) => {
reply.ok();
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn statfs(&mut self, _req: &Request, _ino: u64, reply: ReplyStatfs) {
let bsize = BLOCK_SIZE;
let blocks = self.fs.header.size() / bsize;
let bfree = self.fs.allocator().free();
reply.statfs(blocks, bfree, bfree, 0, 0, bsize as u32, 256, 0);
}
fn symlink(
&mut self,
_req: &Request,
parent_id: u64,
name: &OsStr,
link: &Path,
reply: ReplyEntry,
) {
let parent_ptr = TreePtr::<Node>::new(parent_id as u32);
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
match self.fs.tx(|tx| {
let node = tx.create_node(
parent_ptr,
name.to_str().unwrap(),
Node::MODE_SYMLINK | 0o777,
ctime.as_secs(),
ctime.subsec_nanos(),
)?;
let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
tx.write_node(
node.ptr(),
0,
link.as_os_str().as_bytes(),
mtime.as_secs(),
mtime.subsec_nanos(),
)?;
Ok(node)
}) {
Ok(node) => {
reply.entry(&TTL, &node_attr(&node), 0);
}
Err(error) => {
reply.error(error.errno);
}
}
}
fn readlink(&mut self, _req: &Request, node_id: u64, reply: ReplyData) {
let node_ptr = TreePtr::<Node>::new(node_id as u32);
let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
let mut data = vec![0; 4096];
match self.fs.tx(|tx| {
tx.read_node(
node_ptr,
0,
&mut data,
atime.as_secs(),
atime.subsec_nanos(),
)
}) {
Ok(count) => {
reply.data(&data[..count]);
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn rename(
&mut self,
_req: &Request,
orig_parent: u64,
orig_name: &OsStr,
new_parent: u64,
new_name: &OsStr,
_flags: u32,
reply: ReplyEmpty,
) {
let orig_parent_ptr = TreePtr::<Node>::new(orig_parent as u32);
let orig_name = orig_name.to_str().expect("name is not utf-8");
let new_parent_ptr = TreePtr::<Node>::new(new_parent as u32);
let new_name = new_name.to_str().expect("name is not utf-8");
// TODO: improve performance
match self
.fs
.tx(|tx| tx.rename_node(orig_parent_ptr, orig_name, new_parent_ptr, new_name))
{
Ok(()) => reply.ok(),
Err(err) => reply.error(err.errno),
}
}
}
+23
View File
@@ -0,0 +1,23 @@
#[cfg(all(not(target_os = "redox"), not(fuzzing), feature = "fuse"))]
mod fuse;
#[cfg(all(not(target_os = "redox"), fuzzing, feature = "fuse"))]
pub mod fuse;
#[cfg(all(not(target_os = "redox"), feature = "fuse"))]
pub use self::fuse::mount;
#[cfg(all(not(target_os = "redox"), not(fuzzing), not(feature = "fuse")))]
mod stub;
#[cfg(all(not(target_os = "redox"), fuzzing, not(feature = "fuse")))]
pub mod stub;
#[cfg(all(not(target_os = "redox"), not(feature = "fuse")))]
pub use self::stub::mount;
#[cfg(target_os = "redox")]
mod redox;
#[cfg(target_os = "redox")]
pub use self::redox::mount;
+89
View File
@@ -0,0 +1,89 @@
use redox_scheme::{
scheme::{SchemeState, SchemeSync},
RequestKind, Response, SignalBehavior, Socket,
};
use std::io;
use std::path::Path;
use std::sync::atomic::Ordering;
use crate::{Disk, FileSystem, IS_UMT};
use self::scheme::FileScheme;
pub mod resource;
pub mod scheme;
//FIXME: mut callback is not mut
#[allow(unused_mut)]
pub fn mount<D, P, T, F>(filesystem: FileSystem<D>, mountpoint: P, mut callback: F) -> io::Result<T>
where
D: Disk,
P: AsRef<Path>,
F: FnOnce(&Path) -> T,
{
let mountpoint = mountpoint.as_ref();
let socket = Socket::create()?;
let scheme_name = format!("{}", mountpoint.display());
let mounted_path = format!("/scheme/{}", mountpoint.display());
let mut state = SchemeState::new();
let mut scheme = FileScheme::new(scheme_name, mounted_path.clone(), filesystem, &socket)?;
redox_scheme::scheme::register_sync_scheme(
&socket,
&format!("{}", mountpoint.display()),
&mut scheme,
)?;
let res = callback(Path::new(&mounted_path));
while IS_UMT.load(Ordering::SeqCst) == 0 {
let req = match socket.next_request(SignalBehavior::Restart)? {
None => break,
Some(req) => {
match req.kind() {
RequestKind::Call(r) => r,
RequestKind::SendFd(sendfd_request) => {
let result = scheme.on_sendfd(&sendfd_request);
let response = Response::new(result, sendfd_request);
if !socket.write_response(response, SignalBehavior::Restart)? {
break;
}
continue;
}
RequestKind::OnClose { id } => {
scheme.on_close(id);
state.on_close(id);
continue;
}
RequestKind::OnDetach { id, pid } => {
let Ok(inode) = scheme.inode(id) else {
log::warn!("RequestKind::OnDetach with invalid `id`");
continue;
};
state.on_detach(id, inode, pid);
continue;
}
_ => {
// TODO: Redoxfs does not yet support asynchronous file IO. It might still make
// sense to implement cancellation for huge buffers, e.g. dd bs=1G
continue;
}
}
}
};
let response = req.handle_sync(&mut scheme, &mut state);
if !socket.write_response(response, SignalBehavior::Restart)? {
break;
}
}
// Cleanup on unmount
scheme.fs.cleanup()?;
Ok(res)
}
+794
View File
@@ -0,0 +1,794 @@
use std::slice;
use std::time::{SystemTime, UNIX_EPOCH};
use alloc::collections::BTreeMap;
use libredox::call::MmapArgs;
use range_tree::RangeTree;
use syscall::data::{Stat, TimeSpec};
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
use syscall::error::{Error, Result, EBADF, EINVAL, EISDIR, ENOTDIR, EPERM};
use syscall::flag::{
MapFlags, F_GETFL, F_SETFL, MODE_PERM, O_ACCMODE, O_APPEND, O_RDONLY, O_RDWR, O_WRONLY,
PROT_READ, PROT_WRITE,
};
use syscall::{EBADFD, ENOENT, PAGE_SIZE};
use crate::{Disk, Node, Transaction, TreePtr, BLOCK_SIZE};
pub type Fmaps = BTreeMap<u32, FileMmapInfo>;
pub trait Resource<D: Disk> {
fn parent_ptr_opt(&self) -> Option<TreePtr<Node>>;
fn node_ptr(&self) -> TreePtr<Node>;
fn uid(&self) -> u32;
fn set_path(&mut self, path: &str);
fn read(&mut self, buf: &mut [u8], offset: u64, tx: &mut Transaction<D>) -> Result<usize>;
fn write(&mut self, buf: &[u8], offset: u64, tx: &mut Transaction<D>) -> Result<usize>;
fn fsize(&mut self, tx: &mut Transaction<D>) -> Result<u64>;
fn fmap(
&mut self,
fmaps: &mut Fmaps,
flags: MapFlags,
size: usize,
offset: u64,
tx: &mut Transaction<D>,
) -> Result<usize>;
fn funmap(
&mut self,
fmaps: &mut Fmaps,
offset: u64,
size: usize,
tx: &mut Transaction<D>,
) -> Result<()>;
fn fchmod(&mut self, mode: u16, tx: &mut Transaction<D>) -> Result<()> {
let mut node = tx.read_tree(self.node_ptr())?;
if node.data().uid() == self.uid() || self.uid() == 0 {
let old_mode = node.data().mode();
let new_mode = (old_mode & !MODE_PERM) | (mode & MODE_PERM);
if old_mode != new_mode {
node.data_mut().set_mode(new_mode);
tx.sync_tree(node)?;
}
Ok(())
} else {
Err(Error::new(EPERM))
}
}
fn fchown(&mut self, uid: u32, gid: u32, tx: &mut Transaction<D>) -> Result<()> {
let mut node = tx.read_tree(self.node_ptr())?;
let old_uid = node.data().uid();
if old_uid == self.uid() || self.uid() == 0 {
let mut node_changed = false;
if uid as i32 != -1 {
if uid != old_uid {
node.data_mut().set_uid(uid);
node_changed = true;
}
}
if gid as i32 != -1 {
let old_gid = node.data().gid();
if gid != old_gid {
node.data_mut().set_gid(gid);
node_changed = true;
}
}
if node_changed {
tx.sync_tree(node)?;
}
Ok(())
} else {
Err(Error::new(EPERM))
}
}
fn fcntl(&mut self, cmd: usize, arg: usize) -> Result<usize>;
fn path(&self) -> &str;
fn stat(&self, stat: &mut Stat, tx: &mut Transaction<D>) -> Result<()> {
let node = tx.read_tree(self.node_ptr())?;
let ctime = node.data().ctime();
let mtime = node.data().mtime();
let atime = node.data().atime();
*stat = Stat {
st_dev: 0, // TODO
st_ino: node.id() as u64,
st_mode: node.data().mode(),
st_nlink: node.data().links(),
st_uid: node.data().uid(),
st_gid: node.data().gid(),
st_size: node.data().size(),
st_blksize: 512,
// Blocks is in 512 byte blocks, not in our block size
st_blocks: node.data().blocks() * (BLOCK_SIZE / 512),
st_mtime: mtime.0,
st_mtime_nsec: mtime.1,
st_atime: atime.0,
st_atime_nsec: atime.1,
st_ctime: ctime.0,
st_ctime_nsec: ctime.1,
};
Ok(())
}
fn sync(&mut self, fmaps: &mut Fmaps, tx: &mut Transaction<D>) -> Result<()>;
fn truncate(&mut self, len: u64, tx: &mut Transaction<D>) -> Result<()>;
fn utimens(&mut self, times: &[TimeSpec], tx: &mut Transaction<D>) -> Result<()>;
fn getdents<'buf>(
&mut self,
buf: DirentBuf<&'buf mut [u8]>,
opaque_offset: u64,
tx: &mut Transaction<D>,
) -> Result<DirentBuf<&'buf mut [u8]>>;
}
pub struct Entry {
pub node_ptr: TreePtr<Node>,
pub name: String,
}
pub struct DirResource {
path: String,
parent_ptr_opt: Option<TreePtr<Node>>,
node_ptr: TreePtr<Node>,
data: Option<Vec<Entry>>,
uid: u32,
}
impl DirResource {
pub fn new(
path: String,
parent_ptr_opt: Option<TreePtr<Node>>,
node_ptr: TreePtr<Node>,
data: Option<Vec<Entry>>,
uid: u32,
) -> DirResource {
DirResource {
path,
parent_ptr_opt,
node_ptr,
data,
uid,
}
}
}
impl<D: Disk> Resource<D> for DirResource {
fn parent_ptr_opt(&self) -> Option<TreePtr<Node>> {
self.parent_ptr_opt
}
fn node_ptr(&self) -> TreePtr<Node> {
self.node_ptr
}
fn uid(&self) -> u32 {
self.uid
}
fn set_path(&mut self, path: &str) {
self.path = path.to_string();
}
fn read(&mut self, _buf: &mut [u8], _offset: u64, _tx: &mut Transaction<D>) -> Result<usize> {
Err(Error::new(EISDIR))
}
fn write(&mut self, _buf: &[u8], _offset: u64, _tx: &mut Transaction<D>) -> Result<usize> {
Err(Error::new(EBADF))
}
fn fsize(&mut self, _tx: &mut Transaction<D>) -> Result<u64> {
Ok(self.data.as_ref().ok_or(Error::new(EBADF))?.len() as u64)
}
fn fmap(
&mut self,
_fmaps: &mut Fmaps,
_flags: MapFlags,
_size: usize,
_offset: u64,
_tx: &mut Transaction<D>,
) -> Result<usize> {
Err(Error::new(EBADF))
}
fn funmap(
&mut self,
_fmaps: &mut Fmaps,
_offset: u64,
_size: usize,
_tx: &mut Transaction<D>,
) -> Result<()> {
Err(Error::new(EBADF))
}
fn fcntl(&mut self, _cmd: usize, _arg: usize) -> Result<usize> {
Err(Error::new(EBADF))
}
fn path(&self) -> &str {
&self.path
}
fn sync(&mut self, _fmaps: &mut Fmaps, _tx: &mut Transaction<D>) -> Result<()> {
Err(Error::new(EBADF))
}
fn truncate(&mut self, _len: u64, _tx: &mut Transaction<D>) -> Result<()> {
Err(Error::new(EBADF))
}
fn utimens(&mut self, times: &[TimeSpec], tx: &mut Transaction<D>) -> Result<()> {
let mut node = tx.read_tree(self.node_ptr)?;
if node.data().uid() == self.uid || self.uid == 0 {
if let &[atime, mtime] = times {
let mut node_changed = false;
let old_mtime = node.data().mtime();
let new_mtime = (mtime.tv_sec as u64, mtime.tv_nsec as u32);
if old_mtime != new_mtime {
node.data_mut().set_mtime(new_mtime.0, new_mtime.1);
node_changed = true;
}
let old_atime = node.data().atime();
let new_atime = (atime.tv_sec as u64, atime.tv_nsec as u32);
if old_atime != new_atime {
node.data_mut().set_atime(new_atime.0, new_atime.1);
node_changed = true;
}
if node_changed {
tx.sync_tree(node)?;
}
}
Ok(())
} else {
Err(Error::new(EPERM))
}
}
fn getdents<'buf>(
&mut self,
mut buf: DirentBuf<&'buf mut [u8]>,
opaque_offset: u64,
tx: &mut Transaction<D>,
) -> Result<DirentBuf<&'buf mut [u8]>> {
match &self.data {
Some(data) => {
let opaque_offset = opaque_offset as usize;
for (idx, entry) in data.iter().enumerate().skip(opaque_offset) {
let child = match tx.read_tree(entry.node_ptr) {
Ok(r) => r,
Err(Error { errno: ENOENT }) => continue,
Err(err) => return Err(err),
};
let result = buf.entry(DirEntry {
inode: child.id() as u64,
next_opaque_id: idx as u64 + 1,
name: &entry.name,
kind: match child.data().mode() & Node::MODE_TYPE {
Node::MODE_DIR => DirentKind::Directory,
Node::MODE_FILE => DirentKind::Regular,
Node::MODE_SYMLINK => DirentKind::Symlink,
//TODO: more types?
_ => DirentKind::Unspecified,
},
});
if let Err(err) = result {
if err.errno == EINVAL && idx > opaque_offset {
// POSIX allows partial result of getdents
break;
} else {
return Err(err);
}
}
}
Ok(buf)
}
None => Err(Error::new(EBADF)),
}
}
}
#[derive(Debug)]
pub struct Fmap {
rc: usize,
flags: MapFlags,
last_page_tail: u16,
}
impl Fmap {
pub unsafe fn new<D: Disk>(
node_ptr: TreePtr<Node>,
flags: MapFlags,
unaligned_size: usize,
offset: u64,
base: *mut u8,
tx: &mut Transaction<D>,
) -> Result<Self> {
// Memory provided to fmap must be page aligned and sized
let aligned_size = unaligned_size.next_multiple_of(syscall::PAGE_SIZE);
let address = base.add(offset as usize);
//println!("ADDR {:p} {:p}", base, address);
// Read buffer from disk
let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
let buf = slice::from_raw_parts_mut(address, unaligned_size);
let count = match tx.read_node(node_ptr, offset, buf, atime.as_secs(), atime.subsec_nanos())
{
Ok(ok) => ok,
Err(err) => {
let _ = libredox::call::munmap(address.cast(), aligned_size);
return Err(err);
}
};
// Make sure remaining data is zeroed
buf[count..].fill(0_u8);
Ok(Self {
rc: 1,
flags,
last_page_tail: (unaligned_size % PAGE_SIZE) as u16,
})
}
pub unsafe fn sync<D: Disk>(
&mut self,
node_ptr: TreePtr<Node>,
base: *mut u8,
offset: u64,
size: usize,
tx: &mut Transaction<D>,
) -> Result<()> {
if self.flags & PROT_WRITE == PROT_WRITE {
let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
tx.write_node(
node_ptr,
offset,
unsafe { core::slice::from_raw_parts(base.add(offset as usize), size) },
mtime.as_secs(),
mtime.subsec_nanos(),
)?;
}
Ok(())
}
}
pub struct FileResource {
path: String,
parent_ptr_opt: Option<TreePtr<Node>>,
node_ptr: TreePtr<Node>,
flags: usize,
uid: u32,
}
#[derive(Debug)]
pub struct FileMmapInfo {
base: *mut u8,
size: usize,
pub ranges: RangeTree<Fmap>,
pub open_fds: usize,
}
impl FileMmapInfo {
pub fn new() -> Self {
Self {
base: core::ptr::null_mut(),
size: 0,
ranges: RangeTree::new(),
open_fds: 0,
}
}
pub fn in_use(&self) -> bool {
self.open_fds > 0 || !self.ranges.is_empty()
}
}
impl Drop for FileMmapInfo {
fn drop(&mut self) {
if self.in_use() {
log::error!("FileMmapInfo dropped while in use");
}
}
}
impl FileResource {
pub fn new(
path: String,
parent_ptr_opt: Option<TreePtr<Node>>,
node_ptr: TreePtr<Node>,
flags: usize,
uid: u32,
) -> FileResource {
FileResource {
path,
parent_ptr_opt,
node_ptr,
flags,
uid,
}
}
}
impl<D: Disk> Resource<D> for FileResource {
fn parent_ptr_opt(&self) -> Option<TreePtr<Node>> {
self.parent_ptr_opt
}
fn node_ptr(&self) -> TreePtr<Node> {
self.node_ptr
}
fn uid(&self) -> u32 {
self.uid
}
fn set_path(&mut self, path: &str) {
self.path = path.to_string();
}
fn read(&mut self, buf: &mut [u8], offset: u64, tx: &mut Transaction<D>) -> Result<usize> {
if self.flags & O_ACCMODE != O_RDWR && self.flags & O_ACCMODE != O_RDONLY {
return Err(Error::new(EBADF));
}
let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
tx.read_node(
self.node_ptr,
offset,
buf,
atime.as_secs(),
atime.subsec_nanos(),
)
}
fn write(&mut self, buf: &[u8], offset: u64, tx: &mut Transaction<D>) -> Result<usize> {
if self.flags & O_ACCMODE != O_RDWR && self.flags & O_ACCMODE != O_WRONLY {
return Err(Error::new(EBADF));
}
let effective_offset = if self.flags & O_APPEND == O_APPEND {
let node = tx.read_tree(self.node_ptr)?;
node.data().size()
} else {
offset
};
let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
tx.write_node(
self.node_ptr,
effective_offset,
buf,
mtime.as_secs(),
mtime.subsec_nanos(),
)
}
fn fsize(&mut self, tx: &mut Transaction<D>) -> Result<u64> {
let node = tx.read_tree(self.node_ptr)?;
Ok(node.data().size())
}
fn fmap(
&mut self,
fmaps: &mut Fmaps,
flags: MapFlags,
unaligned_size: usize,
offset: u64,
tx: &mut Transaction<D>,
) -> Result<usize> {
//dbg!(&self.fmaps);
let accmode = self.flags & O_ACCMODE;
if flags.contains(PROT_READ) && !(accmode == O_RDWR || accmode == O_RDONLY) {
return Err(Error::new(EBADF));
}
if flags.contains(PROT_WRITE) && !(accmode == O_RDWR || accmode == O_WRONLY) {
return Err(Error::new(EBADF));
}
let aligned_size = unaligned_size.next_multiple_of(PAGE_SIZE);
// TODO: PROT_EXEC? It is however unenforcable without restricting anonymous mmap, since a
// program can always map anonymous RW-, read from a file, then remap as R-E. But it might
// be usable as a hint, prohibiting direct executable mmaps at least.
// TODO: Pass entry directory to Resource trait functions, since the node_ptr can be
// obtained by the caller.
let fmap_info = fmaps
.get_mut(&self.node_ptr.id())
.ok_or(Error::new(EBADFD))?;
if !fmap_info.in_use() {
// Notify filesystem of open
tx.on_open_node(self.node_ptr)?;
}
let new_size = (offset as usize + aligned_size).next_multiple_of(PAGE_SIZE);
if new_size > fmap_info.size {
fmap_info.base = if fmap_info.base.is_null() {
unsafe {
libredox::call::mmap(MmapArgs {
length: new_size,
// PRIVATE/SHARED doesn't matter once the pages are passed in the fmap
// handler.
prot: libredox::flag::PROT_READ | libredox::flag::PROT_WRITE,
flags: libredox::flag::MAP_PRIVATE,
offset: 0,
fd: !0,
addr: core::ptr::null_mut(),
})? as *mut u8
}
} else {
unsafe {
syscall::syscall5(
syscall::SYS_MREMAP,
fmap_info.base as usize,
fmap_info.size,
0,
new_size,
syscall::MremapFlags::empty().bits() | (PROT_READ | PROT_WRITE).bits(),
)? as *mut u8
}
};
fmap_info.size = new_size;
}
let affected_fmaps = fmap_info
.ranges
.remove_and_unused(offset..offset + aligned_size as u64);
for (range, v_opt) in affected_fmaps {
//dbg!(&range);
if let Some(mut fmap) = v_opt {
fmap.rc += 1;
fmap.flags |= flags;
//FIXME: Use result?
let _ = fmap_info
.ranges
.insert(range.start, range.end - range.start, fmap);
} else {
let map = unsafe {
Fmap::new(
self.node_ptr,
flags,
unaligned_size,
offset,
fmap_info.base,
tx,
)?
};
//FIXME: Use result?
let _ = fmap_info.ranges.insert(offset, aligned_size as u64, map);
}
}
//dbg!(&self.fmaps);
Ok(fmap_info.base as usize + offset as usize)
}
fn funmap(
&mut self,
fmaps: &mut Fmaps,
offset: u64,
size: usize,
tx: &mut Transaction<D>,
) -> Result<()> {
let fmap_info = fmaps
.get_mut(&self.node_ptr.id())
.ok_or(Error::new(EBADFD))?;
//dbg!(&self.fmaps);
//dbg!(self.fmaps.conflicts(offset..offset + size as u64).collect::<Vec<_>>());
#[allow(unused_mut)]
let mut affected_fmaps = fmap_info.ranges.remove(offset..offset + size as u64);
for (range, mut fmap) in affected_fmaps {
fmap.rc = fmap.rc.checked_sub(1).unwrap();
//log::info!("SYNCING {}..{}", range.start, range.end);
unsafe {
fmap.sync(
self.node_ptr,
fmap_info.base,
range.start,
(range.end - range.start) as usize,
tx,
)?;
}
if fmap.rc > 0 {
//FIXME: Use result?
let _ = fmap_info
.ranges
.insert(range.start, range.end - range.start, fmap);
}
}
//dbg!(&self.fmaps);
// Allow release of node if not in use anymore
if !fmap_info.in_use() {
// Notify filesystem of close
tx.on_close_node(self.node_ptr)?;
/*TODO: leaks memory, but why?
// Remove from fmaps list
fmaps.remove(&self.node_ptr.id());
*/
}
Ok(())
}
fn fcntl(&mut self, cmd: usize, arg: usize) -> Result<usize> {
match cmd {
F_GETFL => Ok(self.flags),
F_SETFL => {
self.flags = (self.flags & O_ACCMODE) | (arg & !O_ACCMODE);
Ok(0)
}
_ => Err(Error::new(EINVAL)),
}
}
fn path(&self) -> &str {
&self.path
}
fn sync(&mut self, fmaps: &mut Fmaps, tx: &mut Transaction<D>) -> Result<()> {
if let Some(fmap_info) = fmaps.get_mut(&self.node_ptr.id()) {
for (range, fmap) in fmap_info.ranges.iter_mut() {
unsafe {
fmap.sync(
self.node_ptr,
fmap_info.base,
range.start,
(range.end - range.start) as usize,
tx,
)?;
}
}
}
Ok(())
}
fn truncate(&mut self, len: u64, tx: &mut Transaction<D>) -> Result<()> {
if self.flags & O_ACCMODE == O_RDWR || self.flags & O_ACCMODE == O_WRONLY {
let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
tx.truncate_node(self.node_ptr, len, mtime.as_secs(), mtime.subsec_nanos())?;
Ok(())
} else {
Err(Error::new(EBADF))
}
}
fn utimens(&mut self, times: &[TimeSpec], tx: &mut Transaction<D>) -> Result<()> {
let mut node = tx.read_tree(self.node_ptr)?;
if node.data().uid() == self.uid || self.uid == 0 {
if let &[atime, mtime] = times {
let mut node_changed = false;
let old_mtime = node.data().mtime();
let new_mtime = (mtime.tv_sec as u64, mtime.tv_nsec as u32);
if old_mtime != new_mtime {
node.data_mut().set_mtime(new_mtime.0, new_mtime.1);
node_changed = true;
}
let old_atime = node.data().atime();
let new_atime = (atime.tv_sec as u64, atime.tv_nsec as u32);
if old_atime != new_atime {
node.data_mut().set_atime(new_atime.0, new_atime.1);
node_changed = true;
}
if node_changed {
tx.sync_tree(node)?;
}
}
Ok(())
} else {
Err(Error::new(EPERM))
}
}
fn getdents<'buf>(
&mut self,
_buf: DirentBuf<&'buf mut [u8]>,
_opaque_offset: u64,
_tx: &mut Transaction<D>,
) -> Result<DirentBuf<&'buf mut [u8]>> {
Err(Error::new(ENOTDIR))
}
}
impl Drop for FileResource {
fn drop(&mut self) {
/*
if !self.fmaps.is_empty() {
eprintln!(
"redoxfs: file {} still has {} fmaps!",
self.path,
self.fmaps.len()
);
}
*/
}
}
impl range_tree::Value for Fmap {
type K = u64;
fn try_merge_forward(self, other: &Self) -> core::result::Result<Self, Self> {
if self.rc == other.rc && self.flags == other.flags && self.last_page_tail == 0 {
Ok(self)
} else {
Err(self)
}
}
fn try_merge_backwards(self, other: &Self) -> core::result::Result<Self, Self> {
if self.rc == other.rc && self.flags == other.flags && other.last_page_tail == 0 {
Ok(self)
} else {
Err(self)
}
}
#[allow(unused_variables)]
fn split(
self,
prev_range: Option<core::ops::Range<Self::K>>,
range: core::ops::Range<Self::K>,
next_range: Option<core::ops::Range<Self::K>>,
) -> (Option<Self>, Self, Option<Self>) {
(
prev_range.map(|_range| Fmap {
rc: self.rc,
flags: self.flags,
last_page_tail: 0,
}),
Fmap {
rc: self.rc,
flags: self.flags,
last_page_tail: if next_range.is_none() {
self.last_page_tail
} else {
0
},
},
next_range.map(|_range| Fmap {
rc: self.rc,
flags: self.flags,
last_page_tail: self.last_page_tail,
}),
)
}
}
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
use std::{io, path::Path};
use crate::{filesystem, Disk};
pub fn mount<D, P, T, F>(
mut _filesystem: filesystem::FileSystem<D>,
_mountpoint: P,
_callback: F,
) -> io::Result<T>
where
D: Disk,
P: AsRef<Path>,
F: FnOnce(&Path) -> T,
{
Err(io::Error::new(
io::ErrorKind::Unsupported,
"FUSE mount feature is disabled",
))
}
+584
View File
@@ -0,0 +1,584 @@
use core::{fmt, mem, ops, slice};
use endian_num::Le;
use crate::{BlockLevel, BlockList, BlockPtr, BlockTrait, RecordRaw, BLOCK_SIZE, RECORD_LEVEL};
bitflags::bitflags! {
pub struct NodeFlags: u32 {
const INLINE_DATA = 0x1;
}
}
/// An index into a [`Node`]'s block table.
pub enum NodeLevel {
L0(usize),
L1(usize, usize),
L2(usize, usize, usize),
L3(usize, usize, usize, usize),
L4(usize, usize, usize, usize, usize),
}
impl NodeLevel {
// Warning: this uses constant record offsets, make sure to sync with Node
/// Return the [`NodeLevel`] of the record with the given index.
/// - the first 128 are level 0,
/// - the next 64*256 are level 1,
/// - ...and so on.
pub fn new(mut record_offset: u64) -> Option<Self> {
// 1 << 8 = 256, this is the number of entries in a BlockList
const SHIFT: u64 = 8;
const NUM: u64 = 1 << SHIFT;
const MASK: u64 = NUM - 1;
const L0: u64 = 128;
if record_offset < L0 {
return Some(Self::L0((record_offset & MASK) as usize));
} else {
record_offset -= L0;
}
const L1: u64 = 64 * NUM;
if record_offset < L1 {
return Some(Self::L1(
((record_offset >> SHIFT) & MASK) as usize,
(record_offset & MASK) as usize,
));
} else {
record_offset -= L1;
}
const L2: u64 = 32 * NUM * NUM;
if record_offset < L2 {
return Some(Self::L2(
((record_offset >> (2 * SHIFT)) & MASK) as usize,
((record_offset >> SHIFT) & MASK) as usize,
(record_offset & MASK) as usize,
));
} else {
record_offset -= L2;
}
const L3: u64 = 16 * NUM * NUM * NUM;
if record_offset < L3 {
return Some(Self::L3(
((record_offset >> (3 * SHIFT)) & MASK) as usize,
((record_offset >> (2 * SHIFT)) & MASK) as usize,
((record_offset >> SHIFT) & MASK) as usize,
(record_offset & MASK) as usize,
));
} else {
record_offset -= L3;
}
const L4: u64 = 12 * NUM * NUM * NUM * NUM;
if record_offset < L4 {
Some(Self::L4(
((record_offset >> (4 * SHIFT)) & MASK) as usize,
((record_offset >> (3 * SHIFT)) & MASK) as usize,
((record_offset >> (2 * SHIFT)) & MASK) as usize,
((record_offset >> SHIFT) & MASK) as usize,
(record_offset & MASK) as usize,
))
} else {
None
}
}
}
type BlockListL1 = BlockList<RecordRaw>;
type BlockListL2 = BlockList<BlockListL1>;
type BlockListL3 = BlockList<BlockListL2>;
type BlockListL4 = BlockList<BlockListL3>;
#[repr(C, packed)]
pub struct NodeLevelData {
/// The first 128 blocks of this file.
///
/// Total size: 128 * RECORD_SIZE (16 MiB, 128 KiB each)
pub level0: [BlockPtr<RecordRaw>; 128],
/// The next 64 * 256 blocks of this file,
/// stored behind 64 level one tables.
///
/// Total size: 64 * 256 * RECORD_SIZE (2 GiB, 32 MiB each)
pub level1: [BlockPtr<BlockListL1>; 64],
/// The next 32 * 256 * 256 blocks of this file,
/// stored behind 32 level two tables.
/// Each level two table points to 256 level one tables.
///
/// Total size: 32 * 256 * 256 * RECORD_SIZE (256 GiB, 8 GiB each)
pub level2: [BlockPtr<BlockListL2>; 32],
/// The next 16 * 256 * 256 * 256 blocks of this file,
/// stored behind 16 level three tables.
///
/// Total size: 16 * 256 * 256 * 256 * RECORD_SIZE (32 TiB, 2 TiB each)
pub level3: [BlockPtr<BlockListL3>; 16],
/// The next 8 * 256 * 256 * 256 * 256 blocks of this file,
/// stored behind 8 level four tables.
///
/// Total size: 8 * 256 * 256 * 256 * 256 * RECORD_SIZE (4 PiB, 512 TiB each)
pub level4: [BlockPtr<BlockListL4>; 8],
}
impl Default for NodeLevelData {
fn default() -> Self {
Self {
level0: [BlockPtr::default(); 128],
level1: [BlockPtr::default(); 64],
level2: [BlockPtr::default(); 32],
level3: [BlockPtr::default(); 16],
level4: [BlockPtr::default(); 8],
}
}
}
/// A file/folder node
#[repr(C, packed)]
pub struct Node {
/// This node's type & permissions.
/// - four most significant bits are the node's type
/// - next four bits are permissions for the node's user
/// - next four bits are permissions for the node's group
/// - four least significant bits are permissions for everyone else
pub mode: Le<u16>,
/// The uid that owns this file
pub uid: Le<u32>,
/// The gid that owns this file
pub gid: Le<u32>,
/// The number of hard links to this file
pub links: Le<u32>,
/// The length of this file, in bytes
pub size: Le<u64>,
/// The disk usage of this file, in blocks
pub blocks: Le<u64>,
/// Creation time
pub ctime: Le<u64>,
pub ctime_nsec: Le<u32>,
/// Modification time
pub mtime: Le<u64>,
pub mtime_nsec: Le<u32>,
/// Access time
pub atime: Le<u64>,
pub atime_nsec: Le<u32>,
/// Record level
pub record_level: Le<u32>,
/// Flags
pub flags: Le<u32>,
/// Padding
pub padding: [u8; BLOCK_SIZE as usize - 4042],
/// Level data, should not be used directly so inline data can be supported
pub(crate) level_data: NodeLevelData,
}
unsafe impl BlockTrait for Node {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self::default())
} else {
None
}
}
}
impl Default for Node {
fn default() -> Self {
Self {
mode: 0.into(),
uid: 0.into(),
gid: 0.into(),
links: 0.into(),
size: 0.into(),
// This node counts as a block
blocks: 1.into(),
ctime: 0.into(),
ctime_nsec: 0.into(),
mtime: 0.into(),
mtime_nsec: 0.into(),
atime: 0.into(),
atime_nsec: 0.into(),
record_level: 0.into(),
flags: 0.into(),
padding: [0; BLOCK_SIZE as usize - 4042],
level_data: NodeLevelData::default(),
}
}
}
impl Node {
pub const MODE_TYPE: u16 = 0xF000;
pub const MODE_FILE: u16 = 0x8000;
pub const MODE_DIR: u16 = 0x4000;
pub const MODE_SYMLINK: u16 = 0xA000;
pub const MODE_SOCK: u16 = 0xC000;
/// Mask for node permission bits
pub const MODE_PERM: u16 = 0x0FFF;
pub const MODE_EXEC: u16 = 0o1;
pub const MODE_WRITE: u16 = 0o2;
pub const MODE_READ: u16 = 0o4;
/// Create a new, empty node with the given metadata
pub fn new(mode: u16, uid: u32, gid: u32, ctime: u64, ctime_nsec: u32) -> Self {
Self {
mode: mode.into(),
uid: uid.into(),
gid: gid.into(),
links: 0.into(),
ctime: ctime.into(),
ctime_nsec: ctime_nsec.into(),
mtime: ctime.into(),
mtime_nsec: ctime_nsec.into(),
atime: ctime.into(),
atime_nsec: ctime_nsec.into(),
record_level: if mode & Self::MODE_TYPE == Self::MODE_FILE {
// Files take on record level
RECORD_LEVEL as u32
} else {
// Folders do not
0
}
.into(),
flags: if mode & Self::MODE_TYPE == Self::MODE_DIR {
// Directories must not use inline data (until h-tree supports it)
NodeFlags::empty()
} else {
NodeFlags::INLINE_DATA
}
.bits()
.into(),
..Default::default()
}
}
/// This node's type & permissions.
/// - four most significant bits are the node's type
/// - next four bits are permissions for the node's user
/// - next four bits are permissions for the node's group
/// - four least significant bits are permissions for everyone else
pub fn mode(&self) -> u16 {
self.mode.to_ne()
}
/// The uid that owns this file
pub fn uid(&self) -> u32 {
self.uid.to_ne()
}
/// The gid that owns this file
pub fn gid(&self) -> u32 {
self.gid.to_ne()
}
/// The number of links to this file
/// (directory entries, symlinks, etc)
pub fn links(&self) -> u32 {
self.links.to_ne()
}
/// The length of this file, in bytes.
pub fn size(&self) -> u64 {
self.size.to_ne()
}
/// The disk usage of this file, in blocks.
pub fn blocks(&self) -> u64 {
self.blocks.to_ne()
}
pub fn ctime(&self) -> (u64, u32) {
(self.ctime.to_ne(), self.ctime_nsec.to_ne())
}
pub fn mtime(&self) -> (u64, u32) {
(self.mtime.to_ne(), self.mtime_nsec.to_ne())
}
pub fn atime(&self) -> (u64, u32) {
(self.atime.to_ne(), self.atime_nsec.to_ne())
}
pub fn record_level(&self) -> BlockLevel {
BlockLevel(self.record_level.to_ne() as usize)
}
pub fn flags(&self) -> NodeFlags {
NodeFlags::from_bits_retain(self.flags.to_ne())
}
pub fn set_mode(&mut self, mode: u16) {
self.mode = mode.into();
}
pub fn set_uid(&mut self, uid: u32) {
self.uid = uid.into();
}
pub fn set_gid(&mut self, gid: u32) {
self.gid = gid.into();
}
pub fn set_links(&mut self, links: u32) {
self.links = links.into();
}
pub fn set_size(&mut self, size: u64) {
self.size = size.into();
}
pub fn set_blocks(&mut self, blocks: u64) {
self.blocks = blocks.into();
}
pub fn set_mtime(&mut self, mtime: u64, mtime_nsec: u32) {
self.mtime = mtime.into();
self.mtime_nsec = mtime_nsec.into();
}
pub fn set_atime(&mut self, atime: u64, atime_nsec: u32) {
self.atime = atime.into();
self.atime_nsec = atime_nsec.into();
}
pub fn set_flags(&mut self, flags: NodeFlags) {
self.flags = flags.bits().into();
}
pub fn has_inline_data(&self) -> bool {
self.flags().contains(NodeFlags::INLINE_DATA)
}
pub fn inline_data(&self) -> Option<&[u8]> {
if self.has_inline_data() {
Some(unsafe {
slice::from_raw_parts(
&self.level_data as *const NodeLevelData as *const u8,
mem::size_of::<NodeLevelData>(),
)
})
} else {
None
}
}
pub fn inline_data_mut(&mut self) -> Option<&mut [u8]> {
if self.has_inline_data() {
Some(unsafe {
slice::from_raw_parts_mut(
&mut self.level_data as *mut NodeLevelData as *mut u8,
mem::size_of::<NodeLevelData>(),
)
})
} else {
None
}
}
pub fn level_data(&self) -> Option<&NodeLevelData> {
if !self.has_inline_data() {
Some(&self.level_data)
} else {
None
}
}
pub fn level_data_mut(&mut self) -> Option<&mut NodeLevelData> {
if !self.has_inline_data() {
Some(&mut self.level_data)
} else {
None
}
}
pub fn is_dir(&self) -> bool {
self.mode() & Self::MODE_TYPE == Self::MODE_DIR
}
pub fn is_file(&self) -> bool {
self.mode() & Self::MODE_TYPE == Self::MODE_FILE
}
pub fn is_symlink(&self) -> bool {
self.mode() & Self::MODE_TYPE == Self::MODE_SYMLINK
}
pub fn is_sock(&self) -> bool {
self.mode() & Self::MODE_SOCK == Self::MODE_SOCK
}
/// Tests if UID is the owner of that file, only true when uid=0 or when the UID stored in metadata is equal to the UID you supply
pub fn owner(&self, uid: u32) -> bool {
uid == 0 || self.uid() == uid
}
/// Tests if the current user has enough permissions to view the file, op is the operation,
/// like read and write, these modes are MODE_EXEC, MODE_READ, and MODE_WRITE
pub fn permission(&self, uid: u32, gid: u32, op: u16) -> bool {
let mut perm = self.mode() & 0o7;
if self.uid() == uid {
// If self.mode is 101100110, >> 6 would be 000000101
// 0o7 is octal for 111, or, when expanded to 9 digits is 000000111
perm |= (self.mode() >> 6) & 0o7;
// Since we erased the GID and OTHER bits when >>6'ing, |= will keep those bits in place.
}
if self.gid() == gid || gid == 0 {
perm |= (self.mode() >> 3) & 0o7;
}
if uid == 0 {
//set the `other` bits to 111
perm |= 0o7;
}
perm & op == op
}
}
impl fmt::Debug for Node {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mode = self.mode;
let uid = self.uid;
let gid = self.gid;
let links = self.links;
let size = self.size;
let blocks = self.blocks;
let ctime = self.ctime;
let ctime_nsec = self.ctime_nsec;
let mtime = self.mtime;
let mtime_nsec = self.mtime_nsec;
let atime = self.atime;
let atime_nsec = self.atime_nsec;
f.debug_struct("Node")
.field("mode", &mode)
.field("uid", &uid)
.field("gid", &gid)
.field("links", &links)
.field("size", &size)
.field("blocks", &blocks)
.field("ctime", &ctime)
.field("ctime_nsec", &ctime_nsec)
.field("mtime", &mtime)
.field("mtime_nsec", &mtime_nsec)
.field("atime", &atime)
.field("atime_nsec", &atime_nsec)
//TODO: level0/1/2/3
.finish()
}
}
impl ops::Deref for Node {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(self as *const Node as *const u8, mem::size_of::<Node>())
as &[u8]
}
}
}
impl ops::DerefMut for Node {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(self as *mut Node as *mut u8, mem::size_of::<Node>())
as &mut [u8]
}
}
}
#[test]
fn node_size_test() {
assert_eq!(mem::size_of::<Node>(), crate::BLOCK_SIZE as usize);
}
#[test]
fn node_inline_data_test() {
let mut node = Node::default();
assert!(!node.has_inline_data());
assert!(node.inline_data().is_none());
assert!(node.inline_data_mut().is_none());
assert!(node.level_data().is_some());
assert!(node.level_data_mut().is_some());
node.set_flags(NodeFlags::INLINE_DATA);
assert!(node.has_inline_data());
assert!(node.level_data().is_none());
assert!(node.level_data_mut().is_none());
let node_addr = &node as *const Node as usize;
let meta_size = 128;
{
let inline_data = node.inline_data().unwrap();
let inline_data_addr = inline_data.as_ptr() as usize;
assert_eq!(node_addr + meta_size, inline_data_addr);
assert_eq!(inline_data.len(), (crate::BLOCK_SIZE as usize) - meta_size);
}
{
let inline_data = node.inline_data_mut().unwrap();
let inline_data_addr = inline_data.as_ptr() as usize;
assert_eq!(node_addr + meta_size, inline_data_addr);
assert_eq!(inline_data.len(), (crate::BLOCK_SIZE as usize) - meta_size);
}
}
#[cfg(kani)]
#[kani::proof]
fn check_node_level() {
let offset = kani::any();
NodeLevel::new(offset);
}
#[cfg(kani)]
#[kani::proof]
fn check_node_perms() {
let mode = 0o750;
let uid = kani::any();
let gid = kani::any();
let ctime = kani::any();
let ctime_nsec = kani::any();
let node = Node::new(mode, uid, gid, ctime, ctime_nsec);
let root_uid = 0;
let root_gid = 0;
let other_uid = kani::any();
kani::assume(other_uid != uid);
kani::assume(other_uid != root_uid);
let other_gid = kani::any();
kani::assume(other_gid != gid);
kani::assume(other_gid != root_gid);
assert!(node.owner(uid));
assert!(node.permission(uid, gid, 0o7));
assert!(node.permission(uid, gid, 0o5));
assert!(node.permission(uid, other_gid, 0o7));
assert!(node.permission(uid, other_gid, 0o5));
assert!(!node.permission(other_uid, gid, 0o7));
assert!(node.permission(other_uid, gid, 0o5));
assert!(node.owner(root_uid));
assert!(node.permission(root_uid, root_gid, 0o7));
assert!(node.permission(root_uid, root_gid, 0o5));
assert!(node.permission(root_uid, other_gid, 0o7));
assert!(node.permission(root_uid, other_gid, 0o5));
assert!(!node.permission(other_uid, root_gid, 0o7));
assert!(node.permission(other_uid, root_gid, 0o5));
assert!(!node.owner(other_uid));
assert!(!node.permission(other_uid, other_gid, 0o7));
assert!(!node.permission(other_uid, other_gid, 0o5));
}
+42
View File
@@ -0,0 +1,42 @@
use alloc::{boxed::Box, vec};
use core::ops;
use crate::{BlockLevel, BlockTrait, RECORD_LEVEL};
//TODO: this is a box to prevent stack overflows
#[derive(Clone)]
pub struct RecordRaw(pub(crate) Box<[u8]>);
unsafe impl BlockTrait for RecordRaw {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 <= RECORD_LEVEL {
Some(Self(vec![0; level.bytes() as usize].into_boxed_slice()))
} else {
None
}
}
}
impl ops::Deref for RecordRaw {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.0
}
}
impl ops::DerefMut for RecordRaw {
fn deref_mut(&mut self) -> &mut [u8] {
&mut self.0
}
}
#[test]
fn record_raw_size_test() {
for level_i in 0..RECORD_LEVEL {
let level = BlockLevel(level_i);
assert_eq!(
RecordRaw::empty(level).unwrap().len(),
level.bytes() as usize
);
}
}
+1104
View File
File diff suppressed because it is too large Load Diff
+2053
View File
File diff suppressed because it is too large Load Diff
+280
View File
@@ -0,0 +1,280 @@
use core::{marker::PhantomData, mem, ops, slice};
use endian_num::Le;
use crate::{BlockLevel, BlockPtr, BlockRaw, BlockTrait};
// 1 << 8 = 256, this is the number of entries in a TreeList
const TREE_LIST_SHIFT: u32 = 8;
const TREE_LIST_ENTRIES: usize = (1 << TREE_LIST_SHIFT) - 2;
/// A tree with 4 levels
pub type Tree = TreeList<TreeList<TreeList<TreeList<BlockRaw>>>>;
/// A [`TreePtr`] and the contents of the block it references.
#[derive(Clone, Copy, Debug, Default)]
pub struct TreeData<T> {
/// The value of the [`TreePtr`]
id: u32,
// The data
data: T,
}
impl<T> TreeData<T> {
pub fn new(id: u32, data: T) -> Self {
Self { id, data }
}
pub fn id(&self) -> u32 {
self.id
}
pub fn data(&self) -> &T {
&self.data
}
pub fn data_mut(&mut self) -> &mut T {
&mut self.data
}
pub fn into_data(self) -> T {
self.data
}
pub fn ptr(&self) -> TreePtr<T> {
TreePtr {
id: self.id.into(),
phantom: PhantomData,
}
}
}
/// A list of pointers to blocks of type `T`.
/// This is one level of a [`Tree`], defined above.
#[repr(C, packed)]
pub struct TreeList<T> {
pub ptrs: [BlockPtr<T>; TREE_LIST_ENTRIES],
pub full_flags: [u128; 2],
}
impl<T> TreeList<T> {
pub fn tree_list_is_full(&self) -> bool {
self.full_flags[1] == u128::MAX & !(3 << 126) && self.full_flags[0] == u128::MAX
}
pub fn tree_list_is_empty(&self) -> bool {
for ptr in self.ptrs.iter() {
if !ptr.is_null() {
return false;
}
}
true
}
pub fn branch_is_full(&self, index: usize) -> bool {
assert!(index < TREE_LIST_ENTRIES);
let shift = index % 128;
let full_flags_index = index / 128;
self.full_flags[full_flags_index] & (1 << shift) != 0
}
pub fn set_branch_full(&mut self, index: usize, full: bool) {
assert!(index < TREE_LIST_ENTRIES);
let shift = index % 128;
let full_flags_index = index / 128;
if full {
self.full_flags[full_flags_index] |= 1 << shift;
} else {
self.full_flags[full_flags_index] &= !(1 << shift);
}
}
}
unsafe impl<T> BlockTrait for TreeList<T> {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self {
ptrs: [BlockPtr::default(); TREE_LIST_ENTRIES],
full_flags: [0; 2],
})
} else {
None
}
}
}
impl<T> ops::Deref for TreeList<T> {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const TreeList<T> as *const u8,
mem::size_of::<TreeList<T>>(),
) as &[u8]
}
}
}
impl<T> ops::DerefMut for TreeList<T> {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut TreeList<T> as *mut u8,
mem::size_of::<TreeList<T>>(),
) as &mut [u8]
}
}
}
/// A pointer to an entry in a [`Tree`].
#[repr(C, packed)]
pub struct TreePtr<T> {
id: Le<u32>,
phantom: PhantomData<T>,
}
impl<T> TreePtr<T> {
/// Get a [`TreePtr`] to the filesystem root
/// directory's node.
pub fn root() -> Self {
Self::new(1)
}
pub fn new(id: u32) -> Self {
Self {
id: id.into(),
phantom: PhantomData,
}
}
/// Create a [`TreePtr`] from [`Tree`] indices,
/// Where `indexes` is `(i3, i2, i1, i0)`.
/// - `i3` is the index into the level 3 table,
/// - `i2` is the index into the level 2 table at `i3`
/// - ...and so on.
pub fn from_indexes(indexes: (usize, usize, usize, usize)) -> Self {
const SHIFT: u32 = TREE_LIST_SHIFT;
let id = ((indexes.0 << (3 * SHIFT)) as u32)
| ((indexes.1 << (2 * SHIFT)) as u32)
| ((indexes.2 << SHIFT) as u32)
| (indexes.3 as u32);
Self {
id: id.into(),
phantom: PhantomData,
}
}
pub fn id(&self) -> u32 {
self.id.to_ne()
}
pub fn is_null(&self) -> bool {
self.id() == 0
}
/// Get this indices of this [`TreePtr`] in a [`Tree`].
/// Returns `(i3, i2, i1, i0)`:
/// - `i3` is the index into the level 3 table,
/// - `i2` is the index into the level 2 table at `i3`
/// - ...and so on.
pub fn indexes(&self) -> (usize, usize, usize, usize) {
const SHIFT: u32 = TREE_LIST_SHIFT;
const NUM: u32 = 1 << SHIFT;
const MASK: u32 = NUM - 1;
let id = self.id();
let i3 = ((id >> (3 * SHIFT)) & MASK) as usize;
let i2 = ((id >> (2 * SHIFT)) & MASK) as usize;
let i1 = ((id >> SHIFT) & MASK) as usize;
let i0 = (id & MASK) as usize;
(i3, i2, i1, i0)
}
pub fn to_bytes(&self) -> [u8; 4] {
self.id.to_le_bytes()
}
pub fn from_bytes(bytes: [u8; 4]) -> Self {
let val = u32::from_le_bytes(bytes);
Self {
id: Le(val),
phantom: PhantomData,
}
}
}
impl<T> Clone for TreePtr<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for TreePtr<T> {}
impl<T> Default for TreePtr<T> {
fn default() -> Self {
Self {
id: 0.into(),
phantom: PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use crate::{BlockAddr, BlockData, BlockMeta};
use super::*;
#[test]
fn tree_list_size_test() {
assert_eq!(
mem::size_of::<TreeList<BlockRaw>>(),
crate::BLOCK_SIZE as usize
);
}
#[test]
fn tree_list_is_full_test() {
let mut tree_list = TreeList::<BlockRaw>::empty(BlockLevel::default()).unwrap();
assert!(!tree_list.tree_list_is_full());
for i in 0..TREE_LIST_ENTRIES {
assert!(!tree_list.branch_is_full(i));
tree_list.set_branch_full(i, true);
assert!(tree_list.branch_is_full(i));
}
assert!(tree_list.tree_list_is_full());
for i in 0..TREE_LIST_ENTRIES {
assert!(tree_list.branch_is_full(i));
tree_list.set_branch_full(i, false);
assert!(!tree_list.branch_is_full(i));
}
}
fn mock_block(addr: u64) -> BlockPtr<BlockRaw> {
let block_addr = unsafe { BlockAddr::new(addr, BlockMeta::default()) };
BlockData::empty(block_addr).unwrap().create_ptr()
}
#[test]
fn tree_list_is_empty() {
let mut tree_list = TreeList::<BlockRaw>::empty(BlockLevel::default()).unwrap();
assert!(tree_list.tree_list_is_empty());
tree_list.ptrs[3] = mock_block(123);
assert!(!tree_list.tree_list_is_empty());
}
#[test]
fn tree_ptr_to_and_from_bytes() {
let ptr: TreePtr<BlockRaw> = TreePtr::new(123456);
let bytes = ptr.to_bytes();
let ptr2: TreePtr<BlockRaw> = TreePtr::from_bytes(bytes);
assert_eq!(ptr.id(), ptr2.id());
}
}
+51
View File
@@ -0,0 +1,51 @@
use std::{
fs,
io::{self},
process::{Command, ExitStatus},
};
fn unmount_linux_path(mount_path: &str) -> io::Result<ExitStatus> {
// Different distributions can have various fusermount binaries. Try
// them all.
let commands = ["fusermount", "fusermount3"];
for command in commands {
let status = Command::new(command).arg("-u").arg(mount_path).status();
if status.is_ok() {
return status;
}
if let Err(ref e) = status {
if e.kind() == io::ErrorKind::NotFound {
continue;
}
}
}
// Unmounting failed since no suitable command was found
Err(std::io::Error::new(
io::ErrorKind::NotFound,
format!(
"Unable to locate any fusermount binaries. Tried {:?}. Is fuse installed?",
commands
),
))
}
pub fn unmount_path(mount_path: &str) -> Result<(), io::Error> {
if cfg!(target_os = "redox") {
fs::remove_dir(format!("/scheme/{}", mount_path))?
} else {
let status_res = if cfg!(target_os = "linux") {
unmount_linux_path(mount_path)
} else {
Command::new("umount").arg(mount_path).status()
};
let status = status_res?;
if !status.success() {
return Err(io::Error::other("redoxfs umount failed"));
}
}
Ok(())
}
Executable
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env bash
CARGO_ARGS=(--release)
TARGET=target/release
export RUST_BACKTRACE=full
export RUST_LOG=info
function cleanup {
sync
fusermount -u image || true
fusermount3 -u image || true
}
trap 'cleanup' ERR
set -eEx
cleanup
redoxer test -- --lib -- --nocapture
cargo test --lib --no-default-features -- --nocapture
cargo test --lib -- --nocapture
cargo build "${CARGO_ARGS[@]}"
rm -f image.bin
fallocate -l 1G image.bin
time "${TARGET}/redoxfs-mkfs" image.bin
mkdir -p image
"${TARGET}/redoxfs" image.bin image
df -h image
ls -lah image
mkdir image/test
time cp -r src image/test/src
dd if=/dev/urandom of=image/test/random bs=1M count=256
dd if=image/test/random of=/dev/null bs=1M count=256
time truncate --size=256M image/test/sparse
dd if=image/test/sparse of=/dev/null bs=1M count=256
dd if=/dev/zero of=image/test/zero bs=1M count=256
dd if=image/test/zero of=/dev/null bs=1M count=256
ls -lah image/test
df -h image
rm image/test/random
rm image/test/sparse
rm image/test/zero
rm -rf image/test/src
rmdir image/test
df -h image
ls -lah image
cleanup
"${TARGET}/redoxfs" image.bin image
df -h image
ls -lah image
cleanup
+639
View File
@@ -0,0 +1,639 @@
use core::panic::AssertUnwindSafe;
use redoxfs::{unmount_path, DirEntry, DiskMemory, DiskSparse, FileSystem, Node, TreePtr};
use std::io::{Read, Seek, SeekFrom, Write};
use std::panic::catch_unwind;
use std::path::Path;
use std::process::Command;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering::Relaxed;
use std::thread::sleep;
use std::time::Duration;
use std::{env, fs, time};
static IMAGE_SEQ: AtomicUsize = AtomicUsize::new(0);
fn with_redoxfs<T, F>(callback: F) -> T
where
T: Send + Sync + 'static,
F: FnOnce(&str) -> T + Send + Sync + 'static,
{
let disk_path = format!("image{}.bin", IMAGE_SEQ.fetch_add(1, Relaxed));
{
let disk = DiskSparse::create(dbg!(&disk_path), 1024 * 1024 * 1024).unwrap();
let ctime = dbg!(time::SystemTime::now().duration_since(time::UNIX_EPOCH)).unwrap();
FileSystem::create(disk, None, ctime.as_secs(), ctime.subsec_nanos()).unwrap();
}
let res = callback(&disk_path);
dbg!(fs::remove_file(dbg!(disk_path))).unwrap();
res
}
fn with_mounted<T, F>(callback: F) -> T
where
T: Send + Sync + 'static,
F: FnOnce(&Path) -> T + Send + Sync + 'static,
{
let mount_path_o = format!("image{}", IMAGE_SEQ.fetch_add(1, Relaxed));
let mount_path = mount_path_o.clone();
let res = with_redoxfs(move |fs| {
// At redox, we mount on /scheme/ path, no need an empty dir
if cfg!(not(target_os = "redox")) {
if !Path::new(&mount_path).exists() {
dbg!(fs::create_dir(dbg!(&mount_path))).unwrap();
}
} else {
//FIXME: cargo_bin is broken when cross compiling. This is redoxer specific workaround
env::set_var(
"CARGO_BIN_EXE_redoxfs",
"/root/target/x86_64-unknown-redox/debug/redoxfs",
);
}
let mut mount_cmd = Command::new(assert_cmd::cargo_bin!("redoxfs"));
mount_cmd.arg("-d").arg(dbg!(&fs)).arg(dbg!(&mount_path));
let mut child = mount_cmd.spawn().expect("mount failed to run");
let real_path = if cfg!(target_os = "redox") {
let real_path = dbg!(Path::new("/scheme").join(&mount_path));
let mut tries = 0;
loop {
if real_path.exists() {
break;
}
tries += 1;
if tries == 10 {
panic!("Fail to wait for mount")
}
println!("{tries}");
sleep(Duration::from_millis(500));
}
real_path
} else {
sleep(Duration::from_millis(200));
let r = Path::new(".").join(&mount_path);
r
};
let res = catch_unwind(AssertUnwindSafe(|| callback(&real_path)));
sleep(Duration::from_millis(200));
child.kill().expect("Can't kill");
let _ = child.wait();
if cfg!(target_os = "redox") {
unmount_path(&mount_path).unwrap();
} else {
if !dbg!(Command::new("sync").status()).unwrap().success() {
panic!("sync failed");
}
if unmount_path(&mount_path).is_err() {
// There seems to be a race condition where the device can be busy when trying to unmount.
// So, we pause for a moment and retry. There will still be an error output to the logs
// for the first failed attempt.
sleep(Duration::from_millis(200));
if unmount_path(&mount_path).is_err() {
panic!("umount failed");
}
}
}
res.expect("Test failed")
});
if cfg!(not(target_os = "redox")) {
dbg!(fs::remove_dir(dbg!(mount_path_o))).unwrap();
}
res
}
#[test]
fn simple() {
with_mounted(|path| {
dbg!(fs::create_dir(path.join("test"))).unwrap();
})
}
#[test]
fn create_and_remove_file() {
with_mounted(|path| {
let file_name = "test_file.txt";
let file_path = path.join(file_name);
// Create the file
fs::write(&file_path, "Hello, world!").unwrap();
assert!(fs::exists(&file_path).unwrap());
// Read the file
let contents = fs::read_to_string(&file_path).unwrap();
assert_eq!(contents, "Hello, world!");
// Remove the file
fs::remove_file(&file_path).unwrap();
assert!(!fs::exists(&file_path).unwrap());
});
}
#[test]
fn create_and_remove_directory() {
with_mounted(|path| {
let dir_name = "test_dir";
let dir_path = path.join(dir_name);
// Create the directory
fs::create_dir(&dir_path)
.unwrap_or_else(|_| panic!("cannot create dir {}", &dir_path.display()));
assert!(fs::exists(&dir_path).unwrap());
// Check that the directory is empty
let entries: Vec<_> = fs::read_dir(&dir_path)
.unwrap()
.map(|e| e.unwrap().file_name())
.collect();
assert!(entries.is_empty());
// Add a file to the directory
let file_name = "test_file.txt";
let file_path = dir_path.join(file_name);
fs::write(&file_path, "Hello, world!").unwrap();
// Check that the dir cannot be removed when not empty
let error = fs::remove_dir(&dir_path);
assert!(error.is_err());
assert_eq!(
error.unwrap_err().kind(),
std::io::ErrorKind::DirectoryNotEmpty
);
// Remove the file
fs::remove_file(&file_path).unwrap();
// Remove the directory
fs::remove_dir(&dir_path).unwrap();
assert!(!fs::exists(&dir_path).unwrap());
});
}
#[test]
fn create_and_remove_symlink() {
with_mounted(|path| {
let real_file = "real_file.txt";
let real_path = path.join(real_file);
let symlink_file = "symlink_to_real_file.txt";
let symlink_path = path.join(symlink_file);
// Create the real file
fs::write(&real_path, "Hello, world!").unwrap();
// Create the symmlink according to the platform
#[cfg(unix)]
std::os::unix::fs::symlink(real_file, &symlink_path).unwrap();
#[cfg(windows)]
std::os::windows::fs::symlink_file(&real_file, &symlink_path).unwrap();
// Check that the symlink exists and points to the correct target
let exists = fs::exists(&symlink_path);
assert!(
exists.is_ok() && exists.unwrap(),
"Symlink should exist but was: {:?}",
fs::exists(&symlink_path)
);
let symlink_metadata = fs::symlink_metadata(&symlink_path).unwrap();
assert!(symlink_metadata.file_type().is_symlink());
let target = fs::read_link(&symlink_path).unwrap();
assert_eq!(target.to_str().unwrap(), real_file);
assert_eq!(fs::read(&symlink_path).unwrap(), b"Hello, world!");
// Confirm the symlink cannot be removed as a directory
let error = fs::remove_dir(&symlink_path);
assert!(error.is_err());
assert_eq!(error.unwrap_err().kind(), std::io::ErrorKind::NotADirectory);
// Remove the symlink
fs::remove_file(&symlink_path).unwrap();
assert!(!fs::exists(&symlink_path).unwrap());
});
}
#[cfg(target_os = "redox")]
#[test]
fn mmap() {
//TODO
with_mounted(|path| {
use std::slice;
let path = dbg!(path.join("test"));
let mmap_inner = |write: bool| {
let fd = dbg!(libredox::call::open(
path.to_str().unwrap(),
libredox::flag::O_CREAT | libredox::flag::O_RDWR | libredox::flag::O_CLOEXEC,
0,
))
.unwrap();
let map = unsafe {
slice::from_raw_parts_mut(
dbg!(libredox::call::mmap(libredox::call::MmapArgs {
fd,
offset: 0,
length: 128,
prot: libredox::flag::PROT_READ | libredox::flag::PROT_WRITE,
flags: libredox::flag::MAP_SHARED,
addr: core::ptr::null_mut(),
}))
.unwrap() as *mut u8,
128,
)
};
// Maps should be available after closing
assert_eq!(dbg!(libredox::call::close(fd)), Ok(()));
for i in 0..128 {
if write {
map[i as usize] = i;
}
assert_eq!(map[i as usize], i);
}
//TODO: add msync
unsafe {
assert_eq!(
dbg!(libredox::call::munmap(map.as_mut_ptr().cast(), map.len())),
Ok(())
);
}
};
mmap_inner(true);
mmap_inner(false);
})
}
// TODO: When increasing the total_count to 8000, the Allocator's deallocate() function surfaces as "slow" according to flamegraph. This
// appears to be the result of bulk deleting in this test, but I would bet that any filesystem that has lived for a long time would
// start to see degraded performance due to this.
#[test]
fn many_create_write_list_find_read_delete() {
let disk = DiskMemory::new(1024 * 1024 * 1024);
let ctime = time::SystemTime::now()
.duration_since(time::UNIX_EPOCH)
.unwrap();
let mut fs = FileSystem::create(disk, None, ctime.as_secs(), ctime.subsec_nanos()).unwrap();
let tree_ptr = TreePtr::<Node>::root();
let total_count = 3000;
// Create a bunch of files
for i in 0..total_count {
let result = fs.tx(|tx| {
tx.create_node(
tree_ptr,
&format!("file{i:05}"),
Node::MODE_FILE | 0o644,
1,
0,
)
});
if result.is_err() {
println!("Failure on create iteration {i}");
}
let file_node = result.unwrap();
let result = fs.tx(|tx| {
tx.write_node(
file_node.ptr(),
0,
format!("Hello World! #{i}").as_bytes(),
ctime.as_secs(),
ctime.subsec_nanos(),
)
});
if result.is_err() {
println!("Failure on write iteration {i}");
}
assert!(result.unwrap() > 0)
}
// Confirm that they can be listed
{
let mut children = Vec::<DirEntry>::with_capacity(total_count);
fs.tx(|tx| tx.child_nodes(tree_ptr, &mut children)).unwrap();
assert_eq!(
children.len(),
total_count,
"The list of children should match the number of files created."
);
let mut children: Vec<String> = children
.iter()
.map(|entry| entry.name().unwrap_or_default().to_string())
.collect();
children.sort();
for i in 0..total_count {
let expected = format!("file{i:05}");
let idx = children.binary_search(&expected);
assert!(idx.is_ok(), "Children did not contain '{}'", expected);
}
}
// Find and read the files
for i in 0..total_count {
let result = fs.tx(|tx| tx.find_node(tree_ptr, &format!("file{i:05}")));
if result.is_err() {
println!("Failure on find node iteration {i}");
}
let file_node = result.unwrap();
let offset = 0;
let mut buf = [0_u8; 32];
let result = fs.tx(|tx| {
tx.read_node(
file_node.ptr(),
offset,
&mut buf,
ctime.as_secs(),
ctime.subsec_nanos(),
)
});
if result.is_err() {
println!("Failure on read iteration {i}");
}
let size = result.unwrap();
let body = std::str::from_utf8(&buf[..size]).unwrap();
assert_eq!(body, format!("Hello World! #{i}"));
}
// Delete all the files
for i in 0..total_count {
let file_name = format!("file{i:05}");
if let Err(e) = fs.tx(|tx| tx.remove_node(tree_ptr, &file_name, Node::MODE_FILE)) {
println!("Failure on delete iteration {i}");
panic!("{e}");
}
let result = fs.tx(|tx| tx.find_node(tree_ptr, &file_name));
if result.is_ok() || result.unwrap_err().errno != syscall::error::ENOENT {
println!("Failure on delete verification iteration {i}");
panic!("Deletion appears to have failed");
}
}
}
#[test]
fn many_write_read_delete_mounted() {
with_mounted(|path| {
let total_count = 500;
for i in 0..total_count {
fs::write(
path.join(format!("file{}", i)),
format!("Hello, number {i}!"),
)
.unwrap();
}
// Confirm each of the created files can be found and read
for i in 0..total_count {
let contents = fs::read_to_string(path.join(format!("file{}", i))).unwrap();
assert_eq!(contents, format!("Hello, number {i}!"));
}
// Remove all the files
for i in 0..total_count {
let file_path = path.join(format!("file{i}"));
assert!(fs::exists(&file_path).unwrap());
fs::remove_file(&file_path).unwrap();
assert!(!fs::exists(&file_path).unwrap());
}
});
}
#[test]
fn rename_no_replace() {
let disk = DiskMemory::new(1024 * 1024 * 1024);
let mut fs = FileSystem::create(disk, None, 0, 0)
.expect("Creating in memory file system should succeed");
let root = TreePtr::root();
let dir = fs
.tx(|tx| tx.create_node(root, "dir", Node::MODE_DIR, 0, 0))
.expect("Creating a directory should succeed");
let source_file = fs
.tx(|tx| tx.create_node(root, "source", Node::MODE_FILE, 0, 0))
.expect("Creating source file to copy should succeed");
let no_clobber_file = fs
.tx(|tx| tx.create_node(root, "no_clobber", Node::MODE_FILE, 0, 0))
.expect("Creating second file to not clobber should succeed");
// Rename /source to /target
fs.tx(|tx| tx.rename_node_no_replace(root, "source", root, "target"))
.expect("Renaming existing 'source' to non-existing 'target' should succeed");
let target_file = fs
.tx(|tx| tx.find_node(root, "target"))
.expect("'target' should exist because we just renamed 'source' to 'target'");
assert_eq!(
source_file.id(),
target_file.id(),
"source and target are most definitely the same file"
);
// Don't rename /target to /no_clobber
let err = fs
.tx(|tx| tx.rename_node_no_replace(root, "target", root, "no_clobber"))
.expect_err("Renaming 'target' to existing 'no_clobber' should fail");
assert_eq!(
syscall::EEXIST,
err.errno,
"Renaming to existing file should fail with EEXIST"
);
assert_ne!(
no_clobber_file.id(),
target_file.id(),
"'target' and 'no_clobber' should be distinct files"
);
// Don't rename /target to /dir
let err = fs
.tx(|tx| tx.rename_node_no_replace(root, "target", root, "dir"))
.expect_err("Renaming 'target' to existing directory 'dir' should fail");
assert_eq!(
syscall::EEXIST,
err.errno,
"Renaming to existing file should fail with EEXIST"
);
assert_ne!(
dir.id(),
target_file.id(),
"'target' and 'dir' should be distinct nodes"
);
// Don't rename /dir to /target
let err = fs
.tx(|tx| tx.rename_node_no_replace(root, "dir", root, "target"))
.expect_err("Renaming 'dir' to existing file 'target' should fail");
assert_eq!(
syscall::EEXIST,
err.errno,
"Renaming to existing file should fail with EEXIST"
);
assert_ne!(
target_file.id(),
dir.id(),
"'dir' and 'target' should be distinct nodes"
);
// Don't rename /target to /target
let err = fs
.tx(|tx| tx.rename_node_no_replace(root, "target", root, "target"))
.expect_err("Renaming 'target' to itself should fail");
assert_eq!(
syscall::EEXIST,
err.errno,
"Renaming file to itself should fail with EEXIST"
);
// Rename /target to /dir/target
fs.tx(|tx| tx.rename_node_no_replace(root, "target", dir.ptr(), "target"))
.expect("Renaming /target to /dir/target should succeed");
let moved_target = fs
.tx(|tx| tx.find_node(dir.ptr(), "target"))
.expect("'target' should have moved to /dir/target");
assert_eq!(target_file.id(), moved_target.id());
// Rename /dir to /newdir
fs.tx(|tx| tx.rename_node_no_replace(root, "dir", root, "newdir"))
.expect("Renaming 'dir' to 'newdir' should succeed");
}
#[test]
fn rename_works() {
let disk = DiskMemory::new(1024 * 1024 * 1024);
let mut fs = FileSystem::create(disk, None, 0, 0)
.expect("Creating in memory file system should succeed");
let root = TreePtr::root();
let dir = fs
.tx(|tx| tx.create_node(root, "dir", Node::MODE_DIR, 0, 0))
.expect("Creating a directory should succeed");
let source_file = fs
.tx(|tx| tx.create_node(root, "source", Node::MODE_FILE, 0, 0))
.expect("Creating source file should succeed");
let target_file_orig = fs
.tx(|tx| tx.create_node(root, "target", Node::MODE_FILE, 0, 0))
.expect("Creating target file should succeed");
// Rename /source to /source2
fs.tx(|tx| tx.rename_node(root, "source", root, "source2"))
.expect("Renaming existing 'source' to non-existing 'source2' should succeed");
let source2_file = fs
.tx(|tx| tx.find_node(root, "source2"))
.expect("'source2' should exist because we just renamed 'source' to 'source2'");
assert_eq!(source_file.id(), source2_file.id());
let err = fs
.tx(|tx| tx.find_node(root, "source"))
.expect_err("'source' should not exist because it was moved");
assert_eq!(syscall::ENOENT, err.errno);
// Rename /source2 to /target
fs.tx(|tx| tx.rename_node(root, "source2", root, "target"))
.expect("Renaming existing 'source2' to existing 'target' should succeed");
let target_file_mv = fs
.tx(|tx| tx.find_node(root, "target"))
.expect("'target' should exist because the rename succeeded");
assert_ne!(
target_file_orig.id(),
target_file_mv.id(),
"Move failed because 'target' is still the same"
);
assert_eq!(
source2_file.id(),
target_file_mv.id(),
"Move failed because 'source2' != 'target'"
);
// Don't rename /target to /dir
// XXX: A similar test fails on Linux using rename(). Not sure if the discrepancy matters.
// let err = fs
// .tx(|tx| tx.rename_node(root, "target", root, "dir"))
// .expect_err("Renaming 'target' to existing directory 'dir' should fail");
// assert_eq!(
// syscall::EEXIST,
// err.errno,
// "Renaming to existing file should fail with EEXIST"
// );
// assert_ne!(
// dir.id(),
// target_file_mv.id(),
// "'target' and 'dir' should be distinct nodes"
// );
// Don't rename /dir to /target
// XXX: A similar test fails on Linux using rename().
// let err = fs
// .tx(|tx| tx.rename_node(root, "dir", root, "target"))
// .expect_err("Renaming 'dir' to existing file 'target' should fail");
// assert_eq!(
// syscall::EEXIST,
// err.errno,
// "Renaming to existing file should fail with EEXIST"
// );
// assert_ne!(
// target_file_mv.id(),
// dir.id(),
// "'dir' and 'target' should be distinct nodes"
// );
// Rename /target to /target
fs.tx(|tx| tx.rename_node(root, "target", root, "target"))
.expect("Renaming 'target' to itself should succeed");
let target_self_mv = fs
.tx(|tx| tx.find_node(root, "target"))
.expect("'target' should exist because rename succeeded");
assert_eq!(
target_file_mv.id(),
target_self_mv.id(),
"'target' shouldn't have changed during a move to self"
);
// Rename /target to /dir/target
fs.tx(|tx| tx.rename_node(root, "target", dir.ptr(), "target"))
.expect("Renaming /target to /dir/target should succeed");
let moved_target = fs
.tx(|tx| tx.find_node(dir.ptr(), "target"))
.expect("'target' should have moved to /dir/target");
assert_eq!(target_file_mv.id(), moved_target.id());
// Rename /dir to /newdir
fs.tx(|tx| tx.rename_node(root, "dir", root, "newdir"))
.expect("Renaming 'dir' to 'newdir' should succeed");
}
#[test]
fn temporary_file() {
with_mounted(|path| {
let file_path = path.join("temp");
let mut file = fs::File::create_new(&file_path).expect("failed to create temp file");
fs::remove_file(&file_path).expect("failed to unlink temp file");
let write_data = "Test\n";
file.write_all(write_data.as_bytes())
.expect("failed to write temp file");
let mut read_data = String::new();
file.seek(SeekFrom::Start(0))
.expect("failed to seek temp file");
file.read_to_string(&mut read_data)
.expect("failed to read temp file");
assert_eq!(read_data, write_data);
});
}