├── .gitignore ├── .gitlab-ci.yml ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── fuzz ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── fuzz_targets │ └── fuse_fuzz_target.rs ├── src ├── allocator.rs ├── archive.rs ├── bin │ ├── ar.rs │ ├── mkfs.rs │ └── mount.rs ├── block.rs ├── dir.rs ├── disk │ ├── cache.rs │ ├── file.rs │ ├── io.rs │ ├── mod.rs │ └── sparse.rs ├── filesystem.rs ├── header.rs ├── key.rs ├── lib.rs ├── mount │ ├── fuse.rs │ ├── mod.rs │ └── redox │ │ ├── mod.rs │ │ ├── resource.rs │ │ └── scheme.rs ├── node.rs ├── record.rs ├── tests.rs ├── transaction.rs ├── tree.rs └── unmount.rs └── test.sh /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | image.bin 3 | image 4 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: "redoxos/redoxer" 2 | 3 | stages: 4 | - build 5 | - test 6 | 7 | cache: 8 | paths: 9 | - target/ 10 | 11 | build:linux: 12 | stage: build 13 | script: cargo +nightly build --verbose 14 | 15 | build:redox: 16 | stage: build 17 | script: redoxer build --verbose 18 | 19 | test:linux: 20 | stage: test 21 | dependencies: 22 | - build:linux 23 | script: cargo +nightly test --verbose 24 | 25 | test:redox: 26 | stage: test 27 | dependencies: 28 | - build:redox 29 | script: redoxer test --verbose 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: rust 3 | rust: 4 | - nightly 5 | os: 6 | - linux 7 | - osx 8 | dist: trusty 9 | before_install: 10 | - if [ "$TRAVIS_OS_NAME" = "linux" ]; then 11 | sudo apt-get install -qq pkg-config fuse libfuse-dev; 12 | sudo modprobe fuse; 13 | sudo chmod 666 /dev/fuse; 14 | sudo chown root:$USER /etc/fuse.conf; 15 | fi 16 | - if [ "$TRAVIS_OS_NAME" = "osx" ]; then 17 | brew update; 18 | brew install Caskroom/cask/osxfuse; 19 | fi 20 | notifications: 21 | email: false 22 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aes" 7 | version = "0.7.5" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" 10 | dependencies = [ 11 | "cfg-if", 12 | "cipher", 13 | "cpufeatures", 14 | "opaque-debug", 15 | ] 16 | 17 | [[package]] 18 | name = "aho-corasick" 19 | version = "1.1.3" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 22 | dependencies = [ 23 | "memchr", 24 | ] 25 | 26 | [[package]] 27 | name = "anstream" 28 | version = "0.6.18" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 31 | dependencies = [ 32 | "anstyle", 33 | "anstyle-parse", 34 | "anstyle-query", 35 | "anstyle-wincon", 36 | "colorchoice", 37 | "is_terminal_polyfill", 38 | "utf8parse", 39 | ] 40 | 41 | [[package]] 42 | name = "anstyle" 43 | version = "1.0.10" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 46 | 47 | [[package]] 48 | name = "anstyle-parse" 49 | version = "0.2.6" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 52 | dependencies = [ 53 | "utf8parse", 54 | ] 55 | 56 | [[package]] 57 | name = "anstyle-query" 58 | version = "1.1.2" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 61 | dependencies = [ 62 | "windows-sys", 63 | ] 64 | 65 | [[package]] 66 | name = "anstyle-wincon" 67 | version = "3.0.7" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 70 | dependencies = [ 71 | "anstyle", 72 | "once_cell", 73 | "windows-sys", 74 | ] 75 | 76 | [[package]] 77 | name = "argon2" 78 | version = "0.4.1" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "db4ce4441f99dbd377ca8a8f57b698c44d0d6e712d8329b5040da5a64aa1ce73" 81 | dependencies = [ 82 | "base64ct", 83 | "blake2", 84 | ] 85 | 86 | [[package]] 87 | name = "base64ct" 88 | version = "1.7.3" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" 91 | 92 | [[package]] 93 | name = "bitflags" 94 | version = "2.9.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 97 | 98 | [[package]] 99 | name = "blake2" 100 | version = "0.10.6" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" 103 | dependencies = [ 104 | "digest", 105 | ] 106 | 107 | [[package]] 108 | name = "block-buffer" 109 | version = "0.10.4" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 112 | dependencies = [ 113 | "generic-array", 114 | ] 115 | 116 | [[package]] 117 | name = "byteorder" 118 | version = "1.5.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 121 | 122 | [[package]] 123 | name = "cfg-if" 124 | version = "1.0.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 127 | 128 | [[package]] 129 | name = "cipher" 130 | version = "0.3.0" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" 133 | dependencies = [ 134 | "generic-array", 135 | ] 136 | 137 | [[package]] 138 | name = "colorchoice" 139 | version = "1.0.3" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 142 | 143 | [[package]] 144 | name = "cpufeatures" 145 | version = "0.2.17" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 148 | dependencies = [ 149 | "libc", 150 | ] 151 | 152 | [[package]] 153 | name = "crypto-common" 154 | version = "0.1.6" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 157 | dependencies = [ 158 | "generic-array", 159 | "typenum", 160 | ] 161 | 162 | [[package]] 163 | name = "deranged" 164 | version = "0.4.0" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" 167 | dependencies = [ 168 | "powerfmt", 169 | ] 170 | 171 | [[package]] 172 | name = "digest" 173 | version = "0.10.7" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 176 | dependencies = [ 177 | "block-buffer", 178 | "crypto-common", 179 | "subtle", 180 | ] 181 | 182 | [[package]] 183 | name = "endian-num" 184 | version = "0.1.2" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "f8f59926911ef34d1efb9ea1ee8ca78385df62ce700ccf2bcb149011bd226888" 187 | 188 | [[package]] 189 | name = "env_filter" 190 | version = "0.1.3" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" 193 | dependencies = [ 194 | "log", 195 | "regex", 196 | ] 197 | 198 | [[package]] 199 | name = "env_logger" 200 | version = "0.11.8" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" 203 | dependencies = [ 204 | "anstream", 205 | "anstyle", 206 | "env_filter", 207 | "jiff", 208 | "log", 209 | ] 210 | 211 | [[package]] 212 | name = "fuser" 213 | version = "0.14.0" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "2e697f6f62c20b6fad1ba0f84ae909f25971cf16e735273524e3977c94604cf8" 216 | dependencies = [ 217 | "libc", 218 | "log", 219 | "memchr", 220 | "page_size", 221 | "pkg-config", 222 | "smallvec", 223 | "zerocopy", 224 | ] 225 | 226 | [[package]] 227 | name = "generic-array" 228 | version = "0.14.7" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 231 | dependencies = [ 232 | "typenum", 233 | "version_check", 234 | ] 235 | 236 | [[package]] 237 | name = "getrandom" 238 | version = "0.2.16" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 241 | dependencies = [ 242 | "cfg-if", 243 | "libc", 244 | "wasi 0.11.0+wasi-snapshot-preview1", 245 | ] 246 | 247 | [[package]] 248 | name = "getrandom" 249 | version = "0.3.2" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" 252 | dependencies = [ 253 | "cfg-if", 254 | "libc", 255 | "r-efi", 256 | "wasi 0.14.2+wasi-0.2.4", 257 | ] 258 | 259 | [[package]] 260 | name = "is_terminal_polyfill" 261 | version = "1.70.1" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 264 | 265 | [[package]] 266 | name = "jiff" 267 | version = "0.2.12" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "d07d8d955d798e7a4d6f9c58cd1f1916e790b42b092758a9ef6e16fef9f1b3fd" 270 | dependencies = [ 271 | "jiff-static", 272 | "log", 273 | "portable-atomic", 274 | "portable-atomic-util", 275 | "serde", 276 | ] 277 | 278 | [[package]] 279 | name = "jiff-static" 280 | version = "0.2.12" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "f244cfe006d98d26f859c7abd1318d85327e1882dc9cef80f62daeeb0adcf300" 283 | dependencies = [ 284 | "proc-macro2", 285 | "quote", 286 | "syn", 287 | ] 288 | 289 | [[package]] 290 | name = "libc" 291 | version = "0.2.172" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 294 | 295 | [[package]] 296 | name = "libredox" 297 | version = "0.1.3" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 300 | dependencies = [ 301 | "bitflags", 302 | "libc", 303 | "redox_syscall", 304 | ] 305 | 306 | [[package]] 307 | name = "log" 308 | version = "0.4.27" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 311 | 312 | [[package]] 313 | name = "memchr" 314 | version = "2.7.4" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 317 | 318 | [[package]] 319 | name = "num-conv" 320 | version = "0.1.0" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 323 | 324 | [[package]] 325 | name = "numtoa" 326 | version = "0.2.4" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f" 329 | 330 | [[package]] 331 | name = "once_cell" 332 | version = "1.21.3" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 335 | 336 | [[package]] 337 | name = "opaque-debug" 338 | version = "0.3.1" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" 341 | 342 | [[package]] 343 | name = "page_size" 344 | version = "0.6.0" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" 347 | dependencies = [ 348 | "libc", 349 | "winapi", 350 | ] 351 | 352 | [[package]] 353 | name = "pkg-config" 354 | version = "0.3.32" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 357 | 358 | [[package]] 359 | name = "portable-atomic" 360 | version = "1.11.0" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" 363 | 364 | [[package]] 365 | name = "portable-atomic-util" 366 | version = "0.2.4" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" 369 | dependencies = [ 370 | "portable-atomic", 371 | ] 372 | 373 | [[package]] 374 | name = "powerfmt" 375 | version = "0.2.0" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 378 | 379 | [[package]] 380 | name = "proc-macro2" 381 | version = "1.0.95" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 384 | dependencies = [ 385 | "unicode-ident", 386 | ] 387 | 388 | [[package]] 389 | name = "quote" 390 | version = "1.0.40" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 393 | dependencies = [ 394 | "proc-macro2", 395 | ] 396 | 397 | [[package]] 398 | name = "r-efi" 399 | version = "5.2.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 402 | 403 | [[package]] 404 | name = "range-tree" 405 | version = "0.1.0" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "384c2842d4e069d5ccacf5fe1dca4ef8d07a5444329715f0fc3c61813502d4d1" 408 | 409 | [[package]] 410 | name = "redox-path" 411 | version = "0.3.1" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717" 414 | 415 | [[package]] 416 | name = "redox-scheme" 417 | version = "0.2.4" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "6143c4d307e1c99ac14f60b5b07b2dccaf9d17137f7cee4e4e29977dd8014a1b" 420 | dependencies = [ 421 | "libredox", 422 | "redox_syscall", 423 | ] 424 | 425 | [[package]] 426 | name = "redox_syscall" 427 | version = "0.5.12" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" 430 | dependencies = [ 431 | "bitflags", 432 | ] 433 | 434 | [[package]] 435 | name = "redox_termios" 436 | version = "0.1.3" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" 439 | 440 | [[package]] 441 | name = "redoxfs" 442 | version = "0.6.11" 443 | dependencies = [ 444 | "aes", 445 | "argon2", 446 | "base64ct", 447 | "endian-num", 448 | "env_logger", 449 | "fuser", 450 | "getrandom 0.2.16", 451 | "libc", 452 | "libredox", 453 | "log", 454 | "range-tree", 455 | "redox-path", 456 | "redox-scheme", 457 | "redox_syscall", 458 | "seahash", 459 | "termion", 460 | "time", 461 | "uuid", 462 | ] 463 | 464 | [[package]] 465 | name = "regex" 466 | version = "1.11.1" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 469 | dependencies = [ 470 | "aho-corasick", 471 | "memchr", 472 | "regex-automata", 473 | "regex-syntax", 474 | ] 475 | 476 | [[package]] 477 | name = "regex-automata" 478 | version = "0.4.9" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 481 | dependencies = [ 482 | "aho-corasick", 483 | "memchr", 484 | "regex-syntax", 485 | ] 486 | 487 | [[package]] 488 | name = "regex-syntax" 489 | version = "0.8.5" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 492 | 493 | [[package]] 494 | name = "seahash" 495 | version = "4.1.0" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" 498 | 499 | [[package]] 500 | name = "serde" 501 | version = "1.0.219" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 504 | dependencies = [ 505 | "serde_derive", 506 | ] 507 | 508 | [[package]] 509 | name = "serde_derive" 510 | version = "1.0.219" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 513 | dependencies = [ 514 | "proc-macro2", 515 | "quote", 516 | "syn", 517 | ] 518 | 519 | [[package]] 520 | name = "smallvec" 521 | version = "1.15.0" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 524 | 525 | [[package]] 526 | name = "subtle" 527 | version = "2.6.1" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 530 | 531 | [[package]] 532 | name = "syn" 533 | version = "2.0.101" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 536 | dependencies = [ 537 | "proc-macro2", 538 | "quote", 539 | "unicode-ident", 540 | ] 541 | 542 | [[package]] 543 | name = "termion" 544 | version = "4.0.5" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "3669a69de26799d6321a5aa713f55f7e2cd37bd47be044b50f2acafc42c122bb" 547 | dependencies = [ 548 | "libc", 549 | "libredox", 550 | "numtoa", 551 | "redox_termios", 552 | ] 553 | 554 | [[package]] 555 | name = "time" 556 | version = "0.3.41" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" 559 | dependencies = [ 560 | "deranged", 561 | "num-conv", 562 | "powerfmt", 563 | "serde", 564 | "time-core", 565 | ] 566 | 567 | [[package]] 568 | name = "time-core" 569 | version = "0.1.4" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" 572 | 573 | [[package]] 574 | name = "typenum" 575 | version = "1.18.0" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 578 | 579 | [[package]] 580 | name = "unicode-ident" 581 | version = "1.0.18" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 584 | 585 | [[package]] 586 | name = "utf8parse" 587 | version = "0.2.2" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 590 | 591 | [[package]] 592 | name = "uuid" 593 | version = "1.16.0" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" 596 | dependencies = [ 597 | "getrandom 0.3.2", 598 | ] 599 | 600 | [[package]] 601 | name = "version_check" 602 | version = "0.9.5" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 605 | 606 | [[package]] 607 | name = "wasi" 608 | version = "0.11.0+wasi-snapshot-preview1" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 611 | 612 | [[package]] 613 | name = "wasi" 614 | version = "0.14.2+wasi-0.2.4" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 617 | dependencies = [ 618 | "wit-bindgen-rt", 619 | ] 620 | 621 | [[package]] 622 | name = "winapi" 623 | version = "0.3.9" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 626 | dependencies = [ 627 | "winapi-i686-pc-windows-gnu", 628 | "winapi-x86_64-pc-windows-gnu", 629 | ] 630 | 631 | [[package]] 632 | name = "winapi-i686-pc-windows-gnu" 633 | version = "0.4.0" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 636 | 637 | [[package]] 638 | name = "winapi-x86_64-pc-windows-gnu" 639 | version = "0.4.0" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 642 | 643 | [[package]] 644 | name = "windows-sys" 645 | version = "0.59.0" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 648 | dependencies = [ 649 | "windows-targets", 650 | ] 651 | 652 | [[package]] 653 | name = "windows-targets" 654 | version = "0.52.6" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 657 | dependencies = [ 658 | "windows_aarch64_gnullvm", 659 | "windows_aarch64_msvc", 660 | "windows_i686_gnu", 661 | "windows_i686_gnullvm", 662 | "windows_i686_msvc", 663 | "windows_x86_64_gnu", 664 | "windows_x86_64_gnullvm", 665 | "windows_x86_64_msvc", 666 | ] 667 | 668 | [[package]] 669 | name = "windows_aarch64_gnullvm" 670 | version = "0.52.6" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 673 | 674 | [[package]] 675 | name = "windows_aarch64_msvc" 676 | version = "0.52.6" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 679 | 680 | [[package]] 681 | name = "windows_i686_gnu" 682 | version = "0.52.6" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 685 | 686 | [[package]] 687 | name = "windows_i686_gnullvm" 688 | version = "0.52.6" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 691 | 692 | [[package]] 693 | name = "windows_i686_msvc" 694 | version = "0.52.6" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 697 | 698 | [[package]] 699 | name = "windows_x86_64_gnu" 700 | version = "0.52.6" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 703 | 704 | [[package]] 705 | name = "windows_x86_64_gnullvm" 706 | version = "0.52.6" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 709 | 710 | [[package]] 711 | name = "windows_x86_64_msvc" 712 | version = "0.52.6" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 715 | 716 | [[package]] 717 | name = "wit-bindgen-rt" 718 | version = "0.39.0" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 721 | dependencies = [ 722 | "bitflags", 723 | ] 724 | 725 | [[package]] 726 | name = "zerocopy" 727 | version = "0.7.35" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 730 | dependencies = [ 731 | "byteorder", 732 | "zerocopy-derive", 733 | ] 734 | 735 | [[package]] 736 | name = "zerocopy-derive" 737 | version = "0.7.35" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 740 | dependencies = [ 741 | "proc-macro2", 742 | "quote", 743 | "syn", 744 | ] 745 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "redoxfs" 3 | description = "The Redox Filesystem" 4 | repository = "https://gitlab.redox-os.org/redox-os/redoxfs" 5 | version = "0.6.11" 6 | license-file = "LICENSE" 7 | readme = "README.md" 8 | authors = ["Jeremy Soller "] 9 | edition = "2021" 10 | 11 | [lib] 12 | name = "redoxfs" 13 | path = "src/lib.rs" 14 | 15 | [[bin]] 16 | name = "redoxfs" 17 | path = "src/bin/mount.rs" 18 | doc = false 19 | required-features = ["std"] 20 | 21 | [[bin]] 22 | name = "redoxfs-ar" 23 | path = "src/bin/ar.rs" 24 | doc = false 25 | required-features = ["std"] 26 | 27 | [[bin]] 28 | name = "redoxfs-mkfs" 29 | path = "src/bin/mkfs.rs" 30 | doc = false 31 | required-features = ["std"] 32 | 33 | [dependencies] 34 | aes = { version = "=0.7.5", default-features = false } 35 | argon2 = { version = "0.4", default-features = false, features = ["alloc"] } 36 | base64ct = { version = "1", default-features = false } 37 | env_logger = { version = "0.11", optional = true } 38 | endian-num = "0.1" 39 | getrandom = { version = "0.2.5", optional = true } 40 | libc = "0.2" 41 | log = { version = "0.4.14", default-features = false, optional = true} 42 | redox_syscall = { version = "0.5.12" } 43 | range-tree = { version = "0.1", optional = true } 44 | seahash = { version = "4.1.0", default-features = false } 45 | termion = { version = "4", optional = true } 46 | uuid = { version = "1.4", default-features = false } 47 | redox-path = "0.3.0" 48 | libredox = { version = "0.1.3", optional = true } 49 | redox-scheme = { version = "0.6.2", optional = true } 50 | 51 | [features] 52 | default = ["std", "log"] 53 | force-soft = [ 54 | "aes/force-soft" 55 | ] 56 | std = [ 57 | "env_logger", 58 | "fuser", 59 | "getrandom", 60 | "libc", 61 | "libredox", 62 | "range-tree", 63 | "termion", 64 | "time", 65 | "uuid/v4", 66 | "redox_syscall/std", 67 | "redox-scheme" 68 | ] 69 | 70 | [target.'cfg(not(target_os = "redox"))'.dependencies] 71 | fuser = { version = "0.14", optional = true } 72 | libc = { version = "0.2", optional = true } 73 | time = { version = "0.3", optional = true } 74 | 75 | [lints.rust] 76 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jeremy Soller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | UNAME := $(shell uname) 2 | 3 | ifeq ($(UNAME),Darwin) 4 | FUMOUNT=umount 5 | else ifeq ($(UNAME),FreeBSD) 6 | FUMOUNT=sudo umount 7 | else 8 | # Detect which version of the fusermount binary is available. 9 | ifneq (, $(shell which fusermount3)) 10 | FUMOUNT=fusermount3 -u 11 | else 12 | FUMOUNT=fusermount -u 13 | endif 14 | endif 15 | 16 | image.bin: 17 | cargo build --release --bin redoxfs-mkfs 18 | dd if=/dev/zero of=image.bin bs=1048576 count=1024 19 | target/release/redoxfs-mkfs image.bin 20 | 21 | mount: image.bin FORCE 22 | mkdir -p image 23 | cargo build --release --bin redoxfs 24 | target/release/redoxfs image.bin image 25 | 26 | unmount: FORCE 27 | sync 28 | -${FUMOUNT} image 29 | rm -rf image 30 | 31 | clean: FORCE 32 | sync 33 | -${FUMOUNT} image 34 | rm -rf image image.bin 35 | cargo clean 36 | 37 | FORCE: 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RedoxFS 2 | 3 | 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. 4 | 5 | (It's a replacement for [TFS](https://gitlab.redox-os.org/redox-os/tfs)) 6 | 7 | Current features: 8 | 9 | - Compatible with Redox and Linux (FUSE) 10 | - Copy-on-write 11 | - Data/metadata checksums 12 | - Transparent encryption 13 | - Standard Unix file attributes 14 | - File/directory size limit up to 193TiB (212TB) 15 | - File/directory quantity limit up to 4 billion per 193TiB (2^32 - 1 = 4294967295) 16 | - MIT licensed 17 | - Disk encryption fully supported by the Redox bootloader, letting it load the kernel off an encrypted partition. 18 | 19 | Being MIT licensed, RedoxFS can be bundled on GPL-licensed operating systems (Linux, for example). 20 | 21 | ### How to mount a partition 22 | 23 | - Install RedoxFS 24 | 25 | ```sh 26 | cargo install redoxfs 27 | ``` 28 | 29 | You can also build RedoxFS from this repository. 30 | 31 | - Configure your storage device to allow rootless usage 32 | 33 | If you are on Linux you need root permission to acess block devices (storage), but it's recommended to run RedoxFS as rootless. 34 | 35 | To do that you need to configure your storage device permission to your user with the following command: 36 | 37 | ```sh 38 | sudo setfacl -m u:your-username:rw /path/to/disk 39 | ``` 40 | 41 | - Mount your RedoxFS partition 42 | 43 | ```sh 44 | redoxfs /path/to/disk /path/to/mount 45 | ``` 46 | 47 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) 48 | [![crates.io](http://meritbadge.herokuapp.com/redoxfs)](https://crates.io/crates/redoxfs) 49 | [![docs.rs](https://docs.rs/redoxfs/badge.svg)](https://docs.rs/redoxfs) 50 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | coverage 5 | -------------------------------------------------------------------------------- /fuzz/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aes" 7 | version = "0.7.5" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" 10 | dependencies = [ 11 | "cfg-if", 12 | "cipher", 13 | "cpufeatures", 14 | "opaque-debug", 15 | ] 16 | 17 | [[package]] 18 | name = "aho-corasick" 19 | version = "1.1.3" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 22 | dependencies = [ 23 | "memchr", 24 | ] 25 | 26 | [[package]] 27 | name = "anyhow" 28 | version = "1.0.86" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 31 | 32 | [[package]] 33 | name = "arbitrary" 34 | version = "1.3.2" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" 37 | dependencies = [ 38 | "derive_arbitrary", 39 | ] 40 | 41 | [[package]] 42 | name = "argon2" 43 | version = "0.3.4" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "25df3c03f1040d0069fcd3907e24e36d59f9b6fa07ba49be0eb25a794f036ba7" 46 | dependencies = [ 47 | "base64ct", 48 | "blake2", 49 | ] 50 | 51 | [[package]] 52 | name = "atty" 53 | version = "0.2.14" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 56 | dependencies = [ 57 | "hermit-abi", 58 | "libc", 59 | "winapi", 60 | ] 61 | 62 | [[package]] 63 | name = "base64ct" 64 | version = "1.6.0" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" 67 | 68 | [[package]] 69 | name = "bitflags" 70 | version = "1.3.2" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 73 | 74 | [[package]] 75 | name = "bitflags" 76 | version = "2.5.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 79 | 80 | [[package]] 81 | name = "blake2" 82 | version = "0.10.6" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" 85 | dependencies = [ 86 | "digest", 87 | ] 88 | 89 | [[package]] 90 | name = "block-buffer" 91 | version = "0.10.4" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 94 | dependencies = [ 95 | "generic-array", 96 | ] 97 | 98 | [[package]] 99 | name = "byteorder" 100 | version = "1.5.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 103 | 104 | [[package]] 105 | name = "cc" 106 | version = "1.0.99" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" 109 | dependencies = [ 110 | "jobserver", 111 | "libc", 112 | "once_cell", 113 | ] 114 | 115 | [[package]] 116 | name = "cfg-if" 117 | version = "1.0.0" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 120 | 121 | [[package]] 122 | name = "cfg_aliases" 123 | version = "0.2.1" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 126 | 127 | [[package]] 128 | name = "cipher" 129 | version = "0.3.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" 132 | dependencies = [ 133 | "generic-array", 134 | ] 135 | 136 | [[package]] 137 | name = "cpufeatures" 138 | version = "0.2.12" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" 141 | dependencies = [ 142 | "libc", 143 | ] 144 | 145 | [[package]] 146 | name = "crypto-common" 147 | version = "0.1.6" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 150 | dependencies = [ 151 | "generic-array", 152 | "typenum", 153 | ] 154 | 155 | [[package]] 156 | name = "derive_arbitrary" 157 | version = "1.3.2" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" 160 | dependencies = [ 161 | "proc-macro2", 162 | "quote", 163 | "syn", 164 | ] 165 | 166 | [[package]] 167 | name = "digest" 168 | version = "0.10.7" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 171 | dependencies = [ 172 | "block-buffer", 173 | "crypto-common", 174 | "subtle", 175 | ] 176 | 177 | [[package]] 178 | name = "endian-num" 179 | version = "0.1.1" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "1ad847bb2094f110bbdd6fa564894ca4556fd978958e93985420d680d3cb6d14" 182 | 183 | [[package]] 184 | name = "env_logger" 185 | version = "0.9.3" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" 188 | dependencies = [ 189 | "atty", 190 | "humantime", 191 | "log", 192 | "regex", 193 | "termcolor", 194 | ] 195 | 196 | [[package]] 197 | name = "errno" 198 | version = "0.3.9" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 201 | dependencies = [ 202 | "libc", 203 | "windows-sys", 204 | ] 205 | 206 | [[package]] 207 | name = "fastrand" 208 | version = "2.1.0" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" 211 | 212 | [[package]] 213 | name = "fuser" 214 | version = "0.12.0" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "5910691a0ececcc6eba8bb14029025c2d123e96a53db1533f6a4602861a5aaf7" 217 | dependencies = [ 218 | "libc", 219 | "log", 220 | "memchr", 221 | "page_size", 222 | "pkg-config", 223 | "smallvec", 224 | "users", 225 | "zerocopy", 226 | ] 227 | 228 | [[package]] 229 | name = "generic-array" 230 | version = "0.14.7" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 233 | dependencies = [ 234 | "typenum", 235 | "version_check", 236 | ] 237 | 238 | [[package]] 239 | name = "getrandom" 240 | version = "0.2.15" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 243 | dependencies = [ 244 | "cfg-if", 245 | "libc", 246 | "wasi 0.11.0+wasi-snapshot-preview1", 247 | ] 248 | 249 | [[package]] 250 | name = "hermit-abi" 251 | version = "0.1.19" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 254 | dependencies = [ 255 | "libc", 256 | ] 257 | 258 | [[package]] 259 | name = "humantime" 260 | version = "2.1.0" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 263 | 264 | [[package]] 265 | name = "jobserver" 266 | version = "0.1.31" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" 269 | dependencies = [ 270 | "libc", 271 | ] 272 | 273 | [[package]] 274 | name = "libc" 275 | version = "0.2.155" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 278 | 279 | [[package]] 280 | name = "libfuzzer-sys" 281 | version = "0.4.7" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" 284 | dependencies = [ 285 | "arbitrary", 286 | "cc", 287 | "once_cell", 288 | ] 289 | 290 | [[package]] 291 | name = "libredox" 292 | version = "0.0.2" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" 295 | dependencies = [ 296 | "bitflags 2.5.0", 297 | "libc", 298 | "redox_syscall 0.4.1", 299 | ] 300 | 301 | [[package]] 302 | name = "libredox" 303 | version = "0.1.3" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 306 | dependencies = [ 307 | "bitflags 2.5.0", 308 | "libc", 309 | "redox_syscall 0.5.2", 310 | ] 311 | 312 | [[package]] 313 | name = "linux-raw-sys" 314 | version = "0.4.14" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 317 | 318 | [[package]] 319 | name = "log" 320 | version = "0.4.21" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 323 | 324 | [[package]] 325 | name = "memchr" 326 | version = "2.7.4" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 329 | 330 | [[package]] 331 | name = "nix" 332 | version = "0.29.0" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 335 | dependencies = [ 336 | "bitflags 2.5.0", 337 | "cfg-if", 338 | "cfg_aliases", 339 | "libc", 340 | ] 341 | 342 | [[package]] 343 | name = "numtoa" 344 | version = "0.1.0" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" 347 | 348 | [[package]] 349 | name = "once_cell" 350 | version = "1.19.0" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 353 | 354 | [[package]] 355 | name = "opaque-debug" 356 | version = "0.3.1" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" 359 | 360 | [[package]] 361 | name = "page_size" 362 | version = "0.4.2" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" 365 | dependencies = [ 366 | "libc", 367 | "winapi", 368 | ] 369 | 370 | [[package]] 371 | name = "pkg-config" 372 | version = "0.3.30" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 375 | 376 | [[package]] 377 | name = "proc-macro2" 378 | version = "1.0.85" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" 381 | dependencies = [ 382 | "unicode-ident", 383 | ] 384 | 385 | [[package]] 386 | name = "quote" 387 | version = "1.0.36" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 390 | dependencies = [ 391 | "proc-macro2", 392 | ] 393 | 394 | [[package]] 395 | name = "range-tree" 396 | version = "0.1.0" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "384c2842d4e069d5ccacf5fe1dca4ef8d07a5444329715f0fc3c61813502d4d1" 399 | 400 | [[package]] 401 | name = "redox-path" 402 | version = "0.3.1" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717" 405 | 406 | [[package]] 407 | name = "redox_syscall" 408 | version = "0.4.1" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 411 | dependencies = [ 412 | "bitflags 1.3.2", 413 | ] 414 | 415 | [[package]] 416 | name = "redox_syscall" 417 | version = "0.5.2" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" 420 | dependencies = [ 421 | "bitflags 2.5.0", 422 | ] 423 | 424 | [[package]] 425 | name = "redox_termios" 426 | version = "0.1.3" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" 429 | 430 | [[package]] 431 | name = "redoxfs" 432 | version = "0.6.4" 433 | dependencies = [ 434 | "aes", 435 | "argon2", 436 | "base64ct", 437 | "endian-num", 438 | "env_logger", 439 | "fuser", 440 | "getrandom", 441 | "libc", 442 | "libredox 0.1.3", 443 | "log", 444 | "range-tree", 445 | "redox-path", 446 | "redox_syscall 0.5.2", 447 | "seahash", 448 | "termion", 449 | "time", 450 | "uuid", 451 | ] 452 | 453 | [[package]] 454 | name = "redoxfs-fuzz" 455 | version = "0.0.0" 456 | dependencies = [ 457 | "anyhow", 458 | "arbitrary", 459 | "fuser", 460 | "libfuzzer-sys", 461 | "nix", 462 | "redoxfs", 463 | "tempfile", 464 | ] 465 | 466 | [[package]] 467 | name = "regex" 468 | version = "1.10.5" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" 471 | dependencies = [ 472 | "aho-corasick", 473 | "memchr", 474 | "regex-automata", 475 | "regex-syntax", 476 | ] 477 | 478 | [[package]] 479 | name = "regex-automata" 480 | version = "0.4.7" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 483 | dependencies = [ 484 | "aho-corasick", 485 | "memchr", 486 | "regex-syntax", 487 | ] 488 | 489 | [[package]] 490 | name = "regex-syntax" 491 | version = "0.8.4" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 494 | 495 | [[package]] 496 | name = "rustix" 497 | version = "0.38.34" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" 500 | dependencies = [ 501 | "bitflags 2.5.0", 502 | "errno", 503 | "libc", 504 | "linux-raw-sys", 505 | "windows-sys", 506 | ] 507 | 508 | [[package]] 509 | name = "seahash" 510 | version = "4.1.0" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" 513 | 514 | [[package]] 515 | name = "smallvec" 516 | version = "1.13.2" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 519 | 520 | [[package]] 521 | name = "subtle" 522 | version = "2.5.0" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 525 | 526 | [[package]] 527 | name = "syn" 528 | version = "2.0.66" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" 531 | dependencies = [ 532 | "proc-macro2", 533 | "quote", 534 | "unicode-ident", 535 | ] 536 | 537 | [[package]] 538 | name = "tempfile" 539 | version = "3.10.1" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" 542 | dependencies = [ 543 | "cfg-if", 544 | "fastrand", 545 | "rustix", 546 | "windows-sys", 547 | ] 548 | 549 | [[package]] 550 | name = "termcolor" 551 | version = "1.4.1" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 554 | dependencies = [ 555 | "winapi-util", 556 | ] 557 | 558 | [[package]] 559 | name = "termion" 560 | version = "2.0.3" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "c4648c7def6f2043b2568617b9f9b75eae88ca185dbc1f1fda30e95a85d49d7d" 563 | dependencies = [ 564 | "libc", 565 | "libredox 0.0.2", 566 | "numtoa", 567 | "redox_termios", 568 | ] 569 | 570 | [[package]] 571 | name = "time" 572 | version = "0.1.45" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" 575 | dependencies = [ 576 | "libc", 577 | "wasi 0.10.0+wasi-snapshot-preview1", 578 | "winapi", 579 | ] 580 | 581 | [[package]] 582 | name = "typenum" 583 | version = "1.17.0" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 586 | 587 | [[package]] 588 | name = "unicode-ident" 589 | version = "1.0.12" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 592 | 593 | [[package]] 594 | name = "users" 595 | version = "0.11.0" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" 598 | dependencies = [ 599 | "libc", 600 | "log", 601 | ] 602 | 603 | [[package]] 604 | name = "uuid" 605 | version = "1.8.0" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" 608 | dependencies = [ 609 | "getrandom", 610 | ] 611 | 612 | [[package]] 613 | name = "version_check" 614 | version = "0.9.4" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 617 | 618 | [[package]] 619 | name = "wasi" 620 | version = "0.10.0+wasi-snapshot-preview1" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 623 | 624 | [[package]] 625 | name = "wasi" 626 | version = "0.11.0+wasi-snapshot-preview1" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 629 | 630 | [[package]] 631 | name = "winapi" 632 | version = "0.3.9" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 635 | dependencies = [ 636 | "winapi-i686-pc-windows-gnu", 637 | "winapi-x86_64-pc-windows-gnu", 638 | ] 639 | 640 | [[package]] 641 | name = "winapi-i686-pc-windows-gnu" 642 | version = "0.4.0" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 645 | 646 | [[package]] 647 | name = "winapi-util" 648 | version = "0.1.8" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" 651 | dependencies = [ 652 | "windows-sys", 653 | ] 654 | 655 | [[package]] 656 | name = "winapi-x86_64-pc-windows-gnu" 657 | version = "0.4.0" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 660 | 661 | [[package]] 662 | name = "windows-sys" 663 | version = "0.52.0" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 666 | dependencies = [ 667 | "windows-targets", 668 | ] 669 | 670 | [[package]] 671 | name = "windows-targets" 672 | version = "0.52.5" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 675 | dependencies = [ 676 | "windows_aarch64_gnullvm", 677 | "windows_aarch64_msvc", 678 | "windows_i686_gnu", 679 | "windows_i686_gnullvm", 680 | "windows_i686_msvc", 681 | "windows_x86_64_gnu", 682 | "windows_x86_64_gnullvm", 683 | "windows_x86_64_msvc", 684 | ] 685 | 686 | [[package]] 687 | name = "windows_aarch64_gnullvm" 688 | version = "0.52.5" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 691 | 692 | [[package]] 693 | name = "windows_aarch64_msvc" 694 | version = "0.52.5" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 697 | 698 | [[package]] 699 | name = "windows_i686_gnu" 700 | version = "0.52.5" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 703 | 704 | [[package]] 705 | name = "windows_i686_gnullvm" 706 | version = "0.52.5" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 709 | 710 | [[package]] 711 | name = "windows_i686_msvc" 712 | version = "0.52.5" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 715 | 716 | [[package]] 717 | name = "windows_x86_64_gnu" 718 | version = "0.52.5" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 721 | 722 | [[package]] 723 | name = "windows_x86_64_gnullvm" 724 | version = "0.52.5" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 727 | 728 | [[package]] 729 | name = "windows_x86_64_msvc" 730 | version = "0.52.5" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 733 | 734 | [[package]] 735 | name = "zerocopy" 736 | version = "0.6.6" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "854e949ac82d619ee9a14c66a1b674ac730422372ccb759ce0c39cabcf2bf8e6" 739 | dependencies = [ 740 | "byteorder", 741 | "zerocopy-derive", 742 | ] 743 | 744 | [[package]] 745 | name = "zerocopy-derive" 746 | version = "0.6.6" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91" 749 | dependencies = [ 750 | "proc-macro2", 751 | "quote", 752 | "syn", 753 | ] 754 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "redoxfs-fuzz" 3 | version = "0.0.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [features] 8 | default = [] 9 | log = [] 10 | 11 | [package.metadata] 12 | cargo-fuzz = true 13 | 14 | [dependencies] 15 | anyhow = "1.0.86" 16 | arbitrary = { version = "1.3.2", features = ["derive"] } 17 | fuser = { version = "0.12.0" } 18 | libfuzzer-sys = "0.4" 19 | nix = { version = "0.29.0", features = ["fs"] } 20 | tempfile = "3.10.1" 21 | 22 | [dependencies.redoxfs] 23 | path = ".." 24 | 25 | [[bin]] 26 | name = "fuse_fuzz_target" 27 | path = "fuzz_targets/fuse_fuzz_target.rs" 28 | test = false 29 | doc = false 30 | bench = false 31 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/fuse_fuzz_target.rs: -------------------------------------------------------------------------------- 1 | //! Fuzzer that exercises random file system operations against a FUSE-mounted redoxfs. 2 | 3 | #![no_main] 4 | 5 | use anyhow::{ensure, Result}; 6 | use fuser; 7 | use libfuzzer_sys::{arbitrary::Arbitrary, fuzz_target, Corpus}; 8 | use nix::sys::statvfs::statvfs; 9 | use std::{ 10 | fs::{self, File, FileTimes, OpenOptions}, 11 | io::{Read, Seek, SeekFrom, Write}, 12 | os::unix::fs::{self as unix_fs, PermissionsExt}, 13 | path::{Path, PathBuf}, 14 | thread, 15 | time::{Duration, SystemTime, UNIX_EPOCH}, 16 | }; 17 | use tempfile; 18 | 19 | use redoxfs::{mount::fuse::Fuse, DiskSparse, FileSystem}; 20 | 21 | /// Maximum size for files and buffers. Chosen arbitrarily with fuzzing performance in mind. 22 | const MAX_SIZE: u64 = 10_000_000; 23 | /// Limit on the number of remounts in a single test case. Chosen arbitrarily with fuzzing 24 | /// performance in mind: remounts are costly. 25 | const MAX_MOUNT_SEQUENCES: usize = 3; 26 | 27 | /// An operation to be performed by the fuzzer. 28 | #[derive(Arbitrary, Clone, Debug)] 29 | enum Operation { 30 | Chown { 31 | path: PathBuf, 32 | uid: Option, 33 | gid: Option, 34 | }, 35 | CreateDir { 36 | path: PathBuf, 37 | }, 38 | HardLink { 39 | original: PathBuf, 40 | link: PathBuf, 41 | }, 42 | Metadata { 43 | path: PathBuf, 44 | }, 45 | Read { 46 | path: PathBuf, 47 | }, 48 | ReadDir { 49 | path: PathBuf, 50 | }, 51 | ReadLink { 52 | path: PathBuf, 53 | }, 54 | RemoveDir { 55 | path: PathBuf, 56 | }, 57 | RemoveFile { 58 | path: PathBuf, 59 | }, 60 | Rename { 61 | from: PathBuf, 62 | to: PathBuf, 63 | }, 64 | SeekRead { 65 | path: PathBuf, 66 | seek_pos: u64, 67 | buf_size: usize, 68 | }, 69 | SeekWrite { 70 | path: PathBuf, 71 | seek_pos: u64, 72 | buf_size: usize, 73 | }, 74 | SetLen { 75 | path: PathBuf, 76 | size: u64, 77 | }, 78 | SetPermissions { 79 | path: PathBuf, 80 | readonly: Option, 81 | mode: Option, 82 | }, 83 | SetTimes { 84 | path: PathBuf, 85 | accessed_since_epoch: Option, 86 | modified_since_epoch: Option, 87 | }, 88 | Statvfs {}, 89 | SymLink { 90 | original: PathBuf, 91 | link: PathBuf, 92 | }, 93 | Write { 94 | path: PathBuf, 95 | buf_size: usize, 96 | }, 97 | } 98 | 99 | /// Parameters for mounting the file system and operations to be performed afterwards. 100 | #[derive(Arbitrary, Clone, Debug)] 101 | struct MountSequence { 102 | squash: bool, 103 | operations: Vec, 104 | } 105 | 106 | /// The whole input to a single fuzzer invocation. 107 | #[derive(Arbitrary, Clone, Debug)] 108 | struct TestCase { 109 | disk_size: u64, 110 | reserved_size: u64, 111 | mount_sequences: Vec, 112 | } 113 | 114 | /// Creates the disk for backing the Redoxfs. 115 | fn create_disk(temp_path: &Path, disk_size: u64) -> DiskSparse { 116 | let disk_path = temp_path.join("disk.img"); 117 | DiskSparse::create(disk_path, disk_size).unwrap() 118 | } 119 | 120 | /// Creates an empty Redoxfs. 121 | fn create_redoxfs(disk: DiskSparse, reserved_size: u64) -> bool { 122 | let password = None; 123 | let reserved = vec![0; reserved_size as usize]; 124 | let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); 125 | FileSystem::create_reserved( 126 | disk, 127 | password, 128 | &reserved, 129 | ctime.as_secs(), 130 | ctime.subsec_nanos(), 131 | ) 132 | .is_ok() 133 | } 134 | 135 | /// Mounts an existing Redoxfs, runs the callback and performs the unmount. 136 | fn with_redoxfs_mount(temp_path: &Path, disk: DiskSparse, squash: bool, callback: F) 137 | where 138 | F: FnOnce(&Path) + Send + 'static, 139 | { 140 | let password = None; 141 | let block = None; 142 | let mut fs = FileSystem::open(disk, password, block, squash).unwrap(); 143 | 144 | let mount_path = temp_path.join("mount"); 145 | fs::create_dir_all(&mount_path).unwrap(); 146 | let mut session = fuser::Session::new(Fuse { fs: &mut fs }, &mount_path, &[]).unwrap(); 147 | let mut unmounter = session.unmount_callable(); 148 | 149 | let join_handle = thread::spawn(move || { 150 | callback(&mount_path); 151 | unmounter.unmount().unwrap(); 152 | }); 153 | 154 | session.run().unwrap(); 155 | join_handle.join().unwrap(); 156 | } 157 | 158 | fn get_path_within_fs(fs_path: &Path, path_to_add: &Path) -> Result { 159 | ensure!(path_to_add.is_relative()); 160 | ensure!(path_to_add 161 | .components() 162 | .all(|c| c != std::path::Component::ParentDir)); 163 | Ok(fs_path.join(path_to_add)) 164 | } 165 | 166 | fn do_operation(fs_path: &Path, op: &Operation) -> Result<()> { 167 | match op { 168 | Operation::Chown { path, uid, gid } => { 169 | let path = get_path_within_fs(fs_path, path)?; 170 | unix_fs::chown(path, *uid, *gid)?; 171 | } 172 | Operation::CreateDir { path } => { 173 | let path = get_path_within_fs(fs_path, path)?; 174 | fs::create_dir(path)?; 175 | } 176 | Operation::HardLink { original, link } => { 177 | let original = get_path_within_fs(fs_path, original)?; 178 | let link = get_path_within_fs(fs_path, link)?; 179 | fs::hard_link(original, link)?; 180 | } 181 | Operation::Metadata { path } => { 182 | let path = get_path_within_fs(fs_path, path)?; 183 | fs::metadata(path)?; 184 | } 185 | Operation::Read { path } => { 186 | let path = get_path_within_fs(fs_path, path)?; 187 | fs::read(path)?; 188 | } 189 | Operation::ReadDir { path } => { 190 | let path = get_path_within_fs(fs_path, path)?; 191 | let _ = fs::read_dir(path)?.count(); 192 | } 193 | Operation::ReadLink { path } => { 194 | let path = get_path_within_fs(fs_path, path)?; 195 | fs::read_link(path)?; 196 | } 197 | Operation::RemoveDir { path } => { 198 | let path = get_path_within_fs(fs_path, path)?; 199 | fs::remove_dir(path)?; 200 | } 201 | Operation::RemoveFile { path } => { 202 | let path = get_path_within_fs(fs_path, path)?; 203 | fs::remove_file(path)?; 204 | } 205 | Operation::Rename { from, to } => { 206 | let from = get_path_within_fs(fs_path, from)?; 207 | let to = get_path_within_fs(fs_path, to)?; 208 | fs::rename(from, to)?; 209 | } 210 | Operation::SeekRead { 211 | path, 212 | seek_pos, 213 | buf_size, 214 | } => { 215 | ensure!(*buf_size as u64 <= MAX_SIZE); 216 | let path = get_path_within_fs(fs_path, path)?; 217 | let mut file = File::open(path)?; 218 | file.seek(SeekFrom::Start(*seek_pos))?; 219 | let mut buf = vec![0; *buf_size]; 220 | file.read(&mut buf)?; 221 | } 222 | Operation::SeekWrite { 223 | path, 224 | seek_pos, 225 | buf_size, 226 | } => { 227 | ensure!(*seek_pos <= MAX_SIZE); 228 | ensure!(*buf_size as u64 <= MAX_SIZE); 229 | let path = get_path_within_fs(fs_path, path)?; 230 | let mut file = OpenOptions::new().write(true).open(path)?; 231 | file.seek(SeekFrom::Start(*seek_pos))?; 232 | let buf = vec![0; *buf_size]; 233 | file.write(&buf)?; 234 | } 235 | Operation::SetLen { path, size } => { 236 | let path = get_path_within_fs(fs_path, path)?; 237 | let file = OpenOptions::new().write(true).open(path)?; 238 | file.set_len(*size)?; 239 | } 240 | Operation::SetPermissions { 241 | path, 242 | readonly, 243 | mode, 244 | } => { 245 | let path = get_path_within_fs(fs_path, path)?; 246 | let metadata = fs::metadata(&path)?; 247 | let mut perms = metadata.permissions(); 248 | if let Some(readonly) = readonly { 249 | perms.set_readonly(*readonly); 250 | } 251 | if let Some(mode) = mode { 252 | perms.set_mode(*mode); 253 | } 254 | fs::set_permissions(path, perms)?; 255 | } 256 | Operation::SetTimes { 257 | path, 258 | accessed_since_epoch, 259 | modified_since_epoch, 260 | } => { 261 | let path = get_path_within_fs(fs_path, path)?; 262 | let file = File::options().write(true).open(path)?; 263 | let mut times = FileTimes::new(); 264 | if let Some(accessed_since_epoch) = accessed_since_epoch { 265 | if let Some(accessed) = UNIX_EPOCH.checked_add(*accessed_since_epoch) { 266 | times = times.set_accessed(accessed); 267 | } 268 | } 269 | if let Some(modified_since_epoch) = modified_since_epoch { 270 | if let Some(modified) = UNIX_EPOCH.checked_add(*modified_since_epoch) { 271 | times = times.set_modified(modified); 272 | } 273 | } 274 | file.set_times(times)?; 275 | } 276 | Operation::Statvfs {} => { 277 | statvfs(fs_path)?; 278 | } 279 | Operation::SymLink { original, link } => { 280 | let original = get_path_within_fs(fs_path, original)?; 281 | let link = get_path_within_fs(fs_path, link)?; 282 | unix_fs::symlink(original, link)?; 283 | } 284 | Operation::Write { path, buf_size } => { 285 | ensure!(*buf_size as u64 <= MAX_SIZE); 286 | let path = get_path_within_fs(fs_path, path)?; 287 | let buf = vec![0; *buf_size]; 288 | fs::write(path, &buf)?; 289 | } 290 | } 291 | Ok(()) 292 | } 293 | 294 | fuzz_target!(|test_case: TestCase| -> Corpus { 295 | if test_case.disk_size > MAX_SIZE 296 | || test_case.reserved_size > MAX_SIZE 297 | || test_case.mount_sequences.len() > MAX_MOUNT_SEQUENCES 298 | { 299 | return Corpus::Reject; 300 | } 301 | 302 | let temp_dir = tempfile::Builder::new() 303 | .prefix("fuse_fuzz_target") 304 | .tempdir() 305 | .unwrap(); 306 | 307 | #[cfg(feature = "log")] 308 | eprintln!("create fs"); 309 | let disk = create_disk(temp_dir.path(), test_case.disk_size); 310 | if !create_redoxfs(disk, test_case.reserved_size) { 311 | // File system creation failed (e.g., due to insufficient space) so we bail out, still 312 | // exercising this code path is useful. 313 | return Corpus::Keep; 314 | } 315 | 316 | for mount_seq in test_case.mount_sequences.iter() { 317 | #[cfg(feature = "log")] 318 | eprintln!("mount fs"); 319 | 320 | let disk = create_disk(temp_dir.path(), test_case.disk_size); 321 | let operations = mount_seq.operations.clone(); 322 | with_redoxfs_mount(temp_dir.path(), disk, mount_seq.squash, move |fs_path| { 323 | for operation in operations.iter() { 324 | #[cfg(feature = "log")] 325 | eprintln!("do operation {operation:?}"); 326 | 327 | let _result = do_operation(fs_path, operation); 328 | 329 | #[cfg(feature = "log")] 330 | eprintln!("operation result {:?}", _result.err()); 331 | } 332 | }); 333 | 334 | #[cfg(feature = "log")] 335 | eprintln!("unmounted fs"); 336 | } 337 | Corpus::Keep 338 | }); 339 | -------------------------------------------------------------------------------- /src/allocator.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use core::{fmt, mem, ops, slice}; 3 | use endian_num::Le; 4 | 5 | use crate::{BlockAddr, BlockLevel, BlockPtr, BlockTrait, BLOCK_SIZE}; 6 | 7 | pub const ALLOC_LIST_ENTRIES: usize = 8 | (BLOCK_SIZE as usize - mem::size_of::>()) / mem::size_of::(); 9 | 10 | /// The RedoxFS block allocator. This struct manages all "data" blocks in RedoxFS 11 | /// (i.e, all blocks that aren't reserved or part of the header chain). 12 | /// 13 | /// [`Allocator`] can allocate blocks of many "levels"---that is, it can 14 | /// allocate multiple consecutive [`BLOCK_SIZE`] blocks in one operation. 15 | /// 16 | /// This reduces the amount of memory that the [`Allocator`] uses: 17 | /// Instead of storing the index of each free [`BLOCK_SIZE`] block, 18 | /// the `levels` array can keep track of higher-level blocks, splitting 19 | /// them when a smaller block is requested. 20 | /// 21 | /// Higher-level blocks also allow us to more efficiently allocate memory 22 | /// for large files. 23 | #[derive(Clone, Default)] 24 | pub struct Allocator { 25 | /// This array keeps track of all free blocks of each level, 26 | /// and is initialized using the AllocList chain when we open the filesystem. 27 | /// 28 | /// Every element of the outer array represents a block level: 29 | /// - item 0: free level 0 blocks (with size [`BLOCK_SIZE`]) 30 | /// - item 1: free level 1 blocks (with size 2*[`BLOCK_SIZE`]) 31 | /// - item 2: free level 2 blocks (with size 4*[`BLOCK_SIZE`]) 32 | /// ...and so on. 33 | /// 34 | /// Each inner array contains a list of free block indices, 35 | levels: Vec>, 36 | } 37 | 38 | impl Allocator { 39 | pub fn levels(&self) -> &Vec> { 40 | &self.levels 41 | } 42 | 43 | /// Count the number of free [`BLOCK_SIZE`] available to this [`Allocator`]. 44 | pub fn free(&self) -> u64 { 45 | let mut free = 0; 46 | for level in 0..self.levels.len() { 47 | let level_size = 1 << level; 48 | free += self.levels[level].len() as u64 * level_size; 49 | } 50 | free 51 | } 52 | 53 | /// Find a free block of the given level, mark it as "used", and return its address. 54 | /// Returns [`None`] if there are no free blocks with this level. 55 | pub fn allocate(&mut self, block_level: BlockLevel) -> Option { 56 | // First, find the lowest level with a free block 57 | let mut index_opt = None; 58 | let mut level = block_level.0; 59 | // Start searching at the level we want. Smaller levels are too small! 60 | while level < self.levels.len() { 61 | if !self.levels[level].is_empty() { 62 | index_opt = self.levels[level].pop(); 63 | break; 64 | } 65 | level += 1; 66 | } 67 | 68 | // If a free block was found, split it until we find a usable block of the right level. 69 | // The left side of the split block is kept free, and the right side is allocated. 70 | let index = index_opt?; 71 | while level > block_level.0 { 72 | level -= 1; 73 | let level_size = 1 << level; 74 | self.levels[level].push(index + level_size); 75 | } 76 | 77 | Some(unsafe { BlockAddr::new(index, block_level) }) 78 | } 79 | 80 | /// Try to allocate the exact block specified, making all necessary splits. 81 | /// Returns [`None`] if this some (or all) of this block is already allocated. 82 | /// 83 | /// Note that [`BlockAddr`] encodes the blocks location _and_ level. 84 | pub fn allocate_exact(&mut self, exact_addr: BlockAddr) -> Option { 85 | // This function only supports level 0 right now 86 | assert_eq!(exact_addr.level().0, 0); 87 | let exact_index = exact_addr.index(); 88 | 89 | let mut index_opt = None; 90 | 91 | // Go from the highest to the lowest level 92 | for level in (0..self.levels.len()).rev() { 93 | let level_size = 1 << level; 94 | 95 | // Split higher block if found 96 | if let Some(index) = index_opt.take() { 97 | self.levels[level].push(index); 98 | self.levels[level].push(index + level_size); 99 | } 100 | 101 | // Look for matching block and remove it 102 | for i in 0..self.levels[level].len() { 103 | let start = self.levels[level][i]; 104 | if start <= exact_index { 105 | let end = start + level_size; 106 | if end > exact_index { 107 | self.levels[level].remove(i); 108 | index_opt = Some(start); 109 | break; 110 | } 111 | } 112 | } 113 | } 114 | 115 | Some(unsafe { BlockAddr::new(index_opt?, exact_addr.level()) }) 116 | } 117 | 118 | /// Deallocate the given block, marking it "free" so that it can be re-used later. 119 | pub fn deallocate(&mut self, addr: BlockAddr) { 120 | // When we deallocate, we check if block we're deallocating has a free sibling. 121 | // If it does, we join the two to create one free block in the next (higher) level. 122 | // 123 | // We repeat this until we no longer have a sibling to join. 124 | let mut index = addr.index(); 125 | let mut level = addr.level().0; 126 | loop { 127 | while level >= self.levels.len() { 128 | self.levels.push(Vec::new()); 129 | } 130 | 131 | let level_size = 1 << level; 132 | let next_size = level_size << 1; 133 | 134 | let mut found = false; 135 | let mut i = 0; 136 | // look at all free blocks in the current level... 137 | while i < self.levels[level].len() { 138 | // index of the second block we're looking at 139 | let level_index = self.levels[level][i]; 140 | 141 | // - the block we just freed aligns with the next largest block, and 142 | // - the second block we're looking at is the right sibling of this block 143 | if index % next_size == 0 && index + level_size == level_index { 144 | // "alloc" the next highest block, repeat deallocation process. 145 | self.levels[level].remove(i); 146 | found = true; 147 | break; 148 | // - the index of this block doesn't align with the next largest block, and 149 | // - the block we're looking at is the left neighbor of this block 150 | } else if level_index % next_size == 0 && level_index + level_size == index { 151 | // "alloc" the next highest block, repeat deallocation process. 152 | self.levels[level].remove(i); 153 | index = level_index; // index moves to left block 154 | found = true; 155 | break; 156 | } 157 | i += 1; 158 | } 159 | 160 | // We couldn't find a higher block, 161 | // deallocate this one and finish 162 | if !found { 163 | self.levels[level].push(index); 164 | return; 165 | } 166 | 167 | // repeat deallocation process on the 168 | // higher-level block we just created. 169 | level += 1; 170 | } 171 | } 172 | } 173 | 174 | #[repr(C, packed)] 175 | #[derive(Clone, Copy, Default, Debug)] 176 | pub struct AllocEntry { 177 | /// The index of the first block this [`AllocEntry`] refers to 178 | index: Le, 179 | 180 | /// The number of blocks after (and including) `index` that are are free or used. 181 | /// If negative, they are used; if positive, they are free. 182 | count: Le, 183 | } 184 | 185 | impl AllocEntry { 186 | pub fn new(index: u64, count: i64) -> Self { 187 | Self { 188 | index: index.into(), 189 | count: count.into(), 190 | } 191 | } 192 | 193 | pub fn allocate(addr: BlockAddr) -> Self { 194 | Self::new(addr.index(), -addr.level().blocks()) 195 | } 196 | 197 | pub fn deallocate(addr: BlockAddr) -> Self { 198 | Self::new(addr.index(), addr.level().blocks()) 199 | } 200 | 201 | pub fn index(&self) -> u64 { 202 | self.index.to_ne() 203 | } 204 | 205 | pub fn count(&self) -> i64 { 206 | self.count.to_ne() 207 | } 208 | 209 | pub fn is_null(&self) -> bool { 210 | self.count() == 0 211 | } 212 | } 213 | 214 | /// A node in the allocation chain. 215 | #[repr(C, packed)] 216 | pub struct AllocList { 217 | /// A pointer to the previous AllocList. 218 | /// If this is the null pointer, this is the first element of the chain. 219 | pub prev: BlockPtr, 220 | 221 | /// Allocation entries. 222 | pub entries: [AllocEntry; ALLOC_LIST_ENTRIES], 223 | } 224 | 225 | unsafe impl BlockTrait for AllocList { 226 | fn empty(level: BlockLevel) -> Option { 227 | if level.0 == 0 { 228 | Some(Self { 229 | prev: BlockPtr::default(), 230 | entries: [AllocEntry::default(); ALLOC_LIST_ENTRIES], 231 | }) 232 | } else { 233 | None 234 | } 235 | } 236 | } 237 | 238 | impl fmt::Debug for AllocList { 239 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 240 | let prev = self.prev; 241 | let entries: Vec<&AllocEntry> = self 242 | .entries 243 | .iter() 244 | .filter(|entry| entry.count() > 0) 245 | .collect(); 246 | f.debug_struct("AllocList") 247 | .field("prev", &prev) 248 | .field("entries", &entries) 249 | .finish() 250 | } 251 | } 252 | 253 | impl ops::Deref for AllocList { 254 | type Target = [u8]; 255 | fn deref(&self) -> &[u8] { 256 | unsafe { 257 | slice::from_raw_parts( 258 | self as *const AllocList as *const u8, 259 | mem::size_of::(), 260 | ) as &[u8] 261 | } 262 | } 263 | } 264 | 265 | impl ops::DerefMut for AllocList { 266 | fn deref_mut(&mut self) -> &mut [u8] { 267 | unsafe { 268 | slice::from_raw_parts_mut( 269 | self as *mut AllocList as *mut u8, 270 | mem::size_of::(), 271 | ) as &mut [u8] 272 | } 273 | } 274 | } 275 | 276 | #[test] 277 | fn alloc_node_size_test() { 278 | assert_eq!(mem::size_of::(), crate::BLOCK_SIZE as usize); 279 | } 280 | 281 | #[test] 282 | fn allocator_test() { 283 | let mut alloc = Allocator::default(); 284 | 285 | assert_eq!(alloc.allocate(BlockLevel::default()), None); 286 | 287 | alloc.deallocate(unsafe { BlockAddr::new(1, BlockLevel::default()) }); 288 | assert_eq!( 289 | alloc.allocate(BlockLevel::default()), 290 | Some(unsafe { BlockAddr::new(1, BlockLevel::default()) }) 291 | ); 292 | assert_eq!(alloc.allocate(BlockLevel::default()), None); 293 | 294 | for addr in 1023..2048 { 295 | alloc.deallocate(unsafe { BlockAddr::new(addr, BlockLevel::default()) }); 296 | } 297 | 298 | assert_eq!(alloc.levels.len(), 11); 299 | for level in 0..alloc.levels.len() { 300 | if level == 0 { 301 | assert_eq!(alloc.levels[level], [1023]); 302 | } else if level == 10 { 303 | assert_eq!(alloc.levels[level], [1024]); 304 | } else { 305 | assert_eq!(alloc.levels[level], [0u64; 0]); 306 | } 307 | } 308 | 309 | for addr in 1023..2048 { 310 | assert_eq!( 311 | alloc.allocate(BlockLevel::default()), 312 | Some(unsafe { BlockAddr::new(addr, BlockLevel::default()) }) 313 | ); 314 | } 315 | assert_eq!(alloc.allocate(BlockLevel::default()), None); 316 | 317 | assert_eq!(alloc.levels.len(), 11); 318 | for level in 0..alloc.levels.len() { 319 | assert_eq!(alloc.levels[level], [0u64; 0]); 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /src/archive.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::io; 3 | use std::os::unix::ffi::OsStrExt; 4 | use std::os::unix::fs::MetadataExt; 5 | use std::path::Path; 6 | 7 | use crate::{Disk, FileSystem, Node, Transaction, TreePtr, BLOCK_SIZE}; 8 | 9 | fn syscall_err(err: syscall::Error) -> io::Error { 10 | io::Error::from_raw_os_error(err.errno) 11 | } 12 | 13 | pub fn archive_at>( 14 | tx: &mut Transaction, 15 | parent_path: P, 16 | parent_ptr: TreePtr, 17 | ) -> io::Result<()> { 18 | for entry_res in fs::read_dir(parent_path)? { 19 | let entry = entry_res?; 20 | 21 | let metadata = entry.metadata()?; 22 | let file_type = metadata.file_type(); 23 | 24 | let name = entry.file_name().into_string().map_err(|_| { 25 | io::Error::new(io::ErrorKind::InvalidData, "filename is not valid UTF-8") 26 | })?; 27 | 28 | let mode_type = if file_type.is_dir() { 29 | Node::MODE_DIR 30 | } else if file_type.is_file() { 31 | Node::MODE_FILE 32 | } else if file_type.is_symlink() { 33 | Node::MODE_SYMLINK 34 | } else { 35 | return Err(io::Error::new( 36 | io::ErrorKind::Other, 37 | format!("Does not support parsing {:?}", file_type), 38 | )); 39 | }; 40 | 41 | let node_ptr; 42 | { 43 | let mode = mode_type | (metadata.mode() as u16 & Node::MODE_PERM); 44 | let mut node = tx 45 | .create_node( 46 | parent_ptr, 47 | &name, 48 | mode, 49 | metadata.ctime() as u64, 50 | metadata.ctime_nsec() as u32, 51 | ) 52 | .map_err(syscall_err)?; 53 | 54 | node_ptr = node.ptr(); 55 | 56 | if node.data().uid() != metadata.uid() || node.data().gid() != metadata.gid() { 57 | node.data_mut().set_uid(metadata.uid()); 58 | node.data_mut().set_gid(metadata.gid()); 59 | tx.sync_tree(node).map_err(syscall_err)?; 60 | } 61 | } 62 | 63 | let path = entry.path(); 64 | if file_type.is_dir() { 65 | archive_at(tx, path, node_ptr)?; 66 | } else if file_type.is_file() { 67 | let data = fs::read(path)?; 68 | let count = tx 69 | .write_node( 70 | node_ptr, 71 | 0, 72 | &data, 73 | metadata.mtime() as u64, 74 | metadata.mtime_nsec() as u32, 75 | ) 76 | .map_err(syscall_err)?; 77 | if count != data.len() { 78 | panic!("file write count {} != {}", count, data.len()); 79 | } 80 | } else if file_type.is_symlink() { 81 | let destination = fs::read_link(path)?; 82 | let data = destination.as_os_str().as_bytes(); 83 | let count = tx 84 | .write_node( 85 | node_ptr, 86 | 0, 87 | data, 88 | metadata.mtime() as u64, 89 | metadata.mtime_nsec() as u32, 90 | ) 91 | .map_err(syscall_err)?; 92 | if count != data.len() { 93 | panic!("symlink write count {} != {}", count, data.len()); 94 | } 95 | } else { 96 | return Err(io::Error::new( 97 | io::ErrorKind::Other, 98 | format!("Does not support creating {:?}", file_type), 99 | )); 100 | } 101 | } 102 | 103 | Ok(()) 104 | } 105 | 106 | pub fn archive>(fs: &mut FileSystem, parent_path: P) -> io::Result { 107 | let end_block = fs 108 | .tx(|tx| { 109 | // Archive_at root node 110 | archive_at(tx, parent_path, TreePtr::root()) 111 | .map_err(|err| syscall::Error::new(err.raw_os_error().unwrap()))?; 112 | 113 | // Squash alloc log 114 | tx.sync(true)?; 115 | 116 | let end_block = tx.header.size() / BLOCK_SIZE; 117 | /* TODO: Cut off any free blocks at the end of the filesystem 118 | let mut end_changed = true; 119 | while end_changed { 120 | end_changed = false; 121 | 122 | let allocator = fs.allocator(); 123 | let levels = allocator.levels(); 124 | for level in 0..levels.len() { 125 | let level_size = 1 << level; 126 | for &block in levels[level].iter() { 127 | if block < end_block && block + level_size >= end_block { 128 | end_block = block; 129 | end_changed = true; 130 | } 131 | } 132 | } 133 | } 134 | */ 135 | 136 | // Update header 137 | tx.header.size = (end_block * BLOCK_SIZE).into(); 138 | tx.header_changed = true; 139 | tx.sync(false)?; 140 | 141 | Ok(end_block) 142 | }) 143 | .map_err(syscall_err)?; 144 | 145 | Ok((fs.block + end_block) * BLOCK_SIZE) 146 | } 147 | -------------------------------------------------------------------------------- /src/bin/ar.rs: -------------------------------------------------------------------------------- 1 | extern crate redoxfs; 2 | extern crate syscall; 3 | extern crate uuid; 4 | 5 | use std::io::Read; 6 | use std::time::{SystemTime, UNIX_EPOCH}; 7 | use std::{env, fs, process}; 8 | 9 | use redoxfs::{archive, DiskFile, FileSystem}; 10 | use uuid::Uuid; 11 | 12 | fn main() { 13 | env_logger::init(); 14 | 15 | let mut args = env::args().skip(1); 16 | 17 | let disk_path = if let Some(path) = args.next() { 18 | path 19 | } else { 20 | println!("redoxfs-ar: no disk image provided"); 21 | println!("redoxfs-ar DISK FOLDER [BOOTLOADER]"); 22 | process::exit(1); 23 | }; 24 | 25 | let folder_path = if let Some(path) = args.next() { 26 | path 27 | } else { 28 | println!("redoxfs-ar: no folder provided"); 29 | println!("redoxfs-ar DISK FOLDER [BOOTLOADER]"); 30 | process::exit(1); 31 | }; 32 | 33 | let bootloader_path_opt = args.next(); 34 | 35 | let disk = match DiskFile::open(&disk_path) { 36 | Ok(disk) => disk, 37 | Err(err) => { 38 | println!("redoxfs-ar: failed to open image {}: {}", disk_path, err); 39 | process::exit(1); 40 | } 41 | }; 42 | 43 | let mut bootloader = vec![]; 44 | if let Some(bootloader_path) = bootloader_path_opt { 45 | match fs::File::open(&bootloader_path) { 46 | Ok(mut file) => match file.read_to_end(&mut bootloader) { 47 | Ok(_) => (), 48 | Err(err) => { 49 | println!( 50 | "redoxfs-ar: failed to read bootloader {}: {}", 51 | bootloader_path, err 52 | ); 53 | process::exit(1); 54 | } 55 | }, 56 | Err(err) => { 57 | println!( 58 | "redoxfs-ar: failed to open bootloader {}: {}", 59 | bootloader_path, err 60 | ); 61 | process::exit(1); 62 | } 63 | } 64 | }; 65 | 66 | let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); 67 | match FileSystem::create_reserved( 68 | disk, 69 | None, 70 | &bootloader, 71 | ctime.as_secs(), 72 | ctime.subsec_nanos(), 73 | ) { 74 | Ok(mut fs) => { 75 | let size = match archive(&mut fs, &folder_path) { 76 | Ok(ok) => ok, 77 | Err(err) => { 78 | println!("redoxfs-ar: failed to archive {}: {}", folder_path, err); 79 | process::exit(1); 80 | } 81 | }; 82 | 83 | if let Err(err) = fs.disk.file.set_len(size) { 84 | println!( 85 | "redoxfs-ar: failed to truncate {} to {}: {}", 86 | disk_path, size, err 87 | ); 88 | process::exit(1); 89 | } 90 | 91 | let uuid = Uuid::from_bytes(fs.header.uuid()); 92 | println!( 93 | "redoxfs-ar: created filesystem on {}, reserved {} blocks, size {} MB, uuid {}", 94 | disk_path, 95 | fs.block, 96 | fs.header.size() / 1000 / 1000, 97 | uuid.hyphenated() 98 | ); 99 | } 100 | Err(err) => { 101 | println!( 102 | "redoxfs-ar: failed to create filesystem on {}: {}", 103 | disk_path, err 104 | ); 105 | process::exit(1); 106 | } 107 | }; 108 | } 109 | -------------------------------------------------------------------------------- /src/bin/mkfs.rs: -------------------------------------------------------------------------------- 1 | extern crate redoxfs; 2 | extern crate uuid; 3 | 4 | use std::io::Read; 5 | use std::{env, fs, io, process, time}; 6 | 7 | use redoxfs::{DiskFile, FileSystem}; 8 | use termion::input::TermRead; 9 | use uuid::Uuid; 10 | 11 | fn usage() -> ! { 12 | eprintln!("redoxfs-mkfs [--encrypt] DISK [BOOTLOADER]"); 13 | process::exit(1); 14 | } 15 | 16 | fn main() { 17 | env_logger::init(); 18 | 19 | let mut encrypt = false; 20 | let mut disk_path_opt = None; 21 | let mut bootloader_path_opt = None; 22 | for arg in env::args().skip(1) { 23 | if arg == "--encrypt" { 24 | encrypt = true; 25 | } else if disk_path_opt.is_none() { 26 | disk_path_opt = Some(arg); 27 | } else if bootloader_path_opt.is_none() { 28 | bootloader_path_opt = Some(arg); 29 | } else { 30 | eprintln!("redoxfs-mkfs: too many arguments provided"); 31 | usage(); 32 | } 33 | } 34 | 35 | let disk_path = if let Some(path) = disk_path_opt { 36 | path 37 | } else { 38 | eprintln!("redoxfs-mkfs: no disk image provided"); 39 | usage(); 40 | }; 41 | 42 | let disk = match DiskFile::open(&disk_path) { 43 | Ok(disk) => disk, 44 | Err(err) => { 45 | eprintln!("redoxfs-mkfs: failed to open image {}: {}", disk_path, err); 46 | process::exit(1); 47 | } 48 | }; 49 | 50 | let mut bootloader = vec![]; 51 | if let Some(bootloader_path) = bootloader_path_opt { 52 | match fs::File::open(&bootloader_path) { 53 | Ok(mut file) => match file.read_to_end(&mut bootloader) { 54 | Ok(_) => (), 55 | Err(err) => { 56 | eprintln!( 57 | "redoxfs-mkfs: failed to read bootloader {}: {}", 58 | bootloader_path, err 59 | ); 60 | process::exit(1); 61 | } 62 | }, 63 | Err(err) => { 64 | eprintln!( 65 | "redoxfs-mkfs: failed to open bootloader {}: {}", 66 | bootloader_path, err 67 | ); 68 | process::exit(1); 69 | } 70 | } 71 | }; 72 | 73 | let password_opt = if encrypt { 74 | eprint!("redoxfs-mkfs: password: "); 75 | 76 | let password = io::stdin() 77 | .read_passwd(&mut io::stderr()) 78 | .unwrap() 79 | .unwrap_or_default(); 80 | 81 | eprintln!(); 82 | 83 | if password.is_empty() { 84 | eprintln!("redoxfs-mkfs: empty password, giving up"); 85 | process::exit(1); 86 | } 87 | 88 | Some(password) 89 | } else { 90 | None 91 | }; 92 | 93 | let ctime = time::SystemTime::now() 94 | .duration_since(time::UNIX_EPOCH) 95 | .unwrap(); 96 | match FileSystem::create_reserved( 97 | disk, 98 | password_opt.as_ref().map(|x| x.as_bytes()), 99 | &bootloader, 100 | ctime.as_secs(), 101 | ctime.subsec_nanos(), 102 | ) { 103 | Ok(filesystem) => { 104 | let uuid = Uuid::from_bytes(filesystem.header.uuid()); 105 | eprintln!( 106 | "redoxfs-mkfs: created filesystem on {}, reserved {} blocks, size {} MB, uuid {}", 107 | disk_path, 108 | filesystem.block, 109 | filesystem.header.size() / 1000 / 1000, 110 | uuid.hyphenated() 111 | ); 112 | } 113 | Err(err) => { 114 | eprintln!( 115 | "redoxfs-mkfs: failed to create filesystem on {}: {}", 116 | disk_path, err 117 | ); 118 | process::exit(1); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/bin/mount.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | extern crate redoxfs; 3 | #[cfg(target_os = "redox")] 4 | extern crate syscall; 5 | extern crate uuid; 6 | 7 | use std::env; 8 | use std::fs::File; 9 | use std::io::{self, Read, Write}; 10 | use std::os::unix::io::{FromRawFd, RawFd}; 11 | use std::process; 12 | 13 | #[cfg(target_os = "redox")] 14 | use std::{mem::MaybeUninit, ptr::addr_of_mut, sync::atomic::Ordering}; 15 | 16 | use redoxfs::{mount, DiskCache, DiskFile, FileSystem}; 17 | use termion::input::TermRead; 18 | use uuid::Uuid; 19 | 20 | #[cfg(target_os = "redox")] 21 | extern "C" fn unmount_handler(_s: usize) { 22 | redoxfs::IS_UMT.store(1, Ordering::SeqCst); 23 | } 24 | 25 | #[cfg(target_os = "redox")] 26 | //set up a signal handler on redox, this implements unmounting. I have no idea what sa_flags is 27 | //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 28 | //send it a sigkill, it shuts off the filesystem 29 | fn setsig() { 30 | // TODO: High-level wrapper like the nix crate? 31 | unsafe { 32 | let mut action = MaybeUninit::::uninit(); 33 | 34 | assert_eq!( 35 | libc::sigemptyset(addr_of_mut!((*action.as_mut_ptr()).sa_mask)), 36 | 0 37 | ); 38 | addr_of_mut!((*action.as_mut_ptr()).sa_flags).write(0); 39 | addr_of_mut!((*action.as_mut_ptr()).sa_sigaction).write(unmount_handler as usize); 40 | 41 | assert_eq!( 42 | libc::sigaction(libc::SIGTERM, action.as_ptr(), core::ptr::null_mut()), 43 | 0 44 | ); 45 | } 46 | } 47 | 48 | #[cfg(not(target_os = "redox"))] 49 | // on linux, this is implemented properly, so no need for this unscrupulous nonsense! 50 | fn setsig() {} 51 | 52 | fn fork() -> isize { 53 | unsafe { libc::fork() as isize } 54 | } 55 | 56 | fn pipe(pipes: &mut [i32; 2]) -> isize { 57 | unsafe { libc::pipe(pipes.as_mut_ptr()) as isize } 58 | } 59 | 60 | #[cfg(not(target_os = "redox"))] 61 | fn capability_mode() {} 62 | 63 | #[cfg(not(target_os = "redox"))] 64 | fn bootloader_password() -> Option> { 65 | None 66 | } 67 | 68 | #[cfg(target_os = "redox")] 69 | fn capability_mode() { 70 | libredox::call::setrens(0, 0).expect("redoxfs: failed to enter null namespace"); 71 | } 72 | 73 | #[cfg(target_os = "redox")] 74 | fn bootloader_password() -> Option> { 75 | use libredox::call::MmapArgs; 76 | 77 | let addr_env = env::var_os("REDOXFS_PASSWORD_ADDR")?; 78 | let size_env = env::var_os("REDOXFS_PASSWORD_SIZE")?; 79 | 80 | let addr = usize::from_str_radix( 81 | addr_env.to_str().expect("REDOXFS_PASSWORD_ADDR not valid"), 82 | 16, 83 | ) 84 | .expect("failed to parse REDOXFS_PASSWORD_ADDR"); 85 | 86 | let size = usize::from_str_radix( 87 | size_env.to_str().expect("REDOXFS_PASSWORD_SIZE not valid"), 88 | 16, 89 | ) 90 | .expect("failed to parse REDOXFS_PASSWORD_SIZE"); 91 | 92 | let mut password = Vec::with_capacity(size); 93 | unsafe { 94 | let aligned_size = size.next_multiple_of(syscall::PAGE_SIZE); 95 | 96 | let fd = libredox::Fd::open("memory:physical", libredox::flag::O_CLOEXEC, 0) 97 | .expect("failed to open physical memory file"); 98 | 99 | let password_map = libredox::call::mmap(MmapArgs { 100 | addr: core::ptr::null_mut(), 101 | length: aligned_size, 102 | prot: libredox::flag::PROT_READ, 103 | flags: libredox::flag::MAP_SHARED, 104 | fd: fd.raw(), 105 | offset: addr as u64, 106 | }) 107 | .expect("failed to map REDOXFS_PASSWORD") 108 | .cast::(); 109 | 110 | for i in 0..size { 111 | password.push(password_map.add(i).read()); 112 | } 113 | 114 | let _ = libredox::call::munmap(password_map.cast(), aligned_size); 115 | } 116 | Some(password) 117 | } 118 | 119 | fn print_err_exit(err: impl AsRef) -> ! { 120 | eprintln!("{}", err.as_ref()); 121 | usage(); 122 | process::exit(1) 123 | } 124 | 125 | fn print_usage_exit() -> ! { 126 | usage(); 127 | process::exit(1) 128 | } 129 | 130 | fn usage() { 131 | println!("redoxfs --no-daemon [-d] [--uuid] [disk or uuid] [mountpoint] [block in hex]"); 132 | } 133 | 134 | enum DiskId { 135 | Path(String), 136 | Uuid(Uuid), 137 | } 138 | 139 | fn filesystem_by_path( 140 | path: &str, 141 | block_opt: Option, 142 | ) -> Option<(String, FileSystem>)> { 143 | println!("redoxfs: opening {}", path); 144 | let attempts = 10; 145 | for attempt in 0..=attempts { 146 | let password_opt = if attempt > 0 { 147 | eprint!("redoxfs: password: "); 148 | 149 | let password = io::stdin() 150 | .read_passwd(&mut io::stderr()) 151 | .unwrap() 152 | .unwrap_or_default(); 153 | 154 | eprintln!(); 155 | 156 | if password.is_empty() { 157 | eprintln!("redoxfs: empty password, giving up"); 158 | 159 | // Password is empty, exit loop 160 | break; 161 | } 162 | 163 | Some(password.into_bytes()) 164 | } else { 165 | bootloader_password() 166 | }; 167 | 168 | match DiskFile::open(path).map(DiskCache::new) { 169 | Ok(disk) => { 170 | match redoxfs::FileSystem::open(disk, password_opt.as_deref(), block_opt, true) { 171 | Ok(filesystem) => { 172 | println!( 173 | "redoxfs: opened filesystem on {} with uuid {}", 174 | path, 175 | Uuid::from_bytes(filesystem.header.uuid()).hyphenated() 176 | ); 177 | 178 | return Some((path.to_string(), filesystem)); 179 | } 180 | Err(err) => match err.errno { 181 | syscall::ENOKEY => { 182 | if password_opt.is_some() { 183 | println!("redoxfs: incorrect password ({}/{})", attempt, attempts); 184 | } 185 | } 186 | _ => { 187 | println!("redoxfs: failed to open filesystem {}: {}", path, err); 188 | break; 189 | } 190 | }, 191 | } 192 | } 193 | Err(err) => { 194 | println!("redoxfs: failed to open image {}: {}", path, err); 195 | break; 196 | } 197 | } 198 | } 199 | None 200 | } 201 | 202 | #[cfg(not(target_os = "redox"))] 203 | fn filesystem_by_uuid( 204 | _uuid: &Uuid, 205 | _block_opt: Option, 206 | ) -> Option<(String, FileSystem>)> { 207 | None 208 | } 209 | 210 | #[cfg(target_os = "redox")] 211 | fn filesystem_by_uuid( 212 | uuid: &Uuid, 213 | block_opt: Option, 214 | ) -> Option<(String, FileSystem>)> { 215 | use std::fs; 216 | 217 | use redox_path::RedoxPath; 218 | 219 | match fs::read_dir("/scheme") { 220 | Ok(entries) => { 221 | for entry_res in entries { 222 | if let Ok(entry) = entry_res { 223 | if let Some(disk) = entry.path().to_str() { 224 | if RedoxPath::from_absolute(disk) 225 | .unwrap_or(RedoxPath::from_absolute("/")?) 226 | .is_scheme_category("disk") 227 | { 228 | println!("redoxfs: found scheme {}", disk); 229 | match fs::read_dir(disk) { 230 | Ok(entries) => { 231 | for entry_res in entries { 232 | if let Ok(entry) = entry_res { 233 | if let Ok(path) = 234 | entry.path().into_os_string().into_string() 235 | { 236 | println!("redoxfs: found path {}", path); 237 | if let Some((path, filesystem)) = 238 | filesystem_by_path(&path, block_opt) 239 | { 240 | if &filesystem.header.uuid() == uuid.as_bytes() 241 | { 242 | println!( 243 | "redoxfs: filesystem on {} matches uuid {}", 244 | path, 245 | uuid.hyphenated() 246 | ); 247 | return Some((path, filesystem)); 248 | } else { 249 | println!( 250 | "redoxfs: filesystem on {} does not match uuid {}", 251 | path, 252 | uuid.hyphenated() 253 | ); 254 | } 255 | } 256 | } 257 | } 258 | } 259 | } 260 | Err(err) => { 261 | println!("redoxfs: failed to list '{}': {}", disk, err); 262 | } 263 | } 264 | } 265 | } 266 | } 267 | } 268 | } 269 | Err(err) => { 270 | println!("redoxfs: failed to list schemes: {}", err); 271 | } 272 | } 273 | 274 | None 275 | } 276 | 277 | fn daemon( 278 | disk_id: &DiskId, 279 | mountpoint: &str, 280 | block_opt: Option, 281 | mut write: Option, 282 | ) -> ! { 283 | setsig(); 284 | 285 | let filesystem_opt = match *disk_id { 286 | DiskId::Path(ref path) => filesystem_by_path(path, block_opt), 287 | DiskId::Uuid(ref uuid) => filesystem_by_uuid(uuid, block_opt), 288 | }; 289 | 290 | if let Some((path, filesystem)) = filesystem_opt { 291 | match mount(filesystem, mountpoint, |mounted_path| { 292 | capability_mode(); 293 | 294 | println!( 295 | "redoxfs: mounted filesystem on {} to {}", 296 | path, 297 | mounted_path.display() 298 | ); 299 | 300 | if let Some(ref mut write) = write { 301 | let _ = write.write(&[0]); 302 | } 303 | }) { 304 | Ok(()) => { 305 | process::exit(0); 306 | } 307 | Err(err) => { 308 | println!( 309 | "redoxfs: failed to mount {} to {}: {}", 310 | path, mountpoint, err 311 | ); 312 | } 313 | } 314 | } 315 | 316 | match *disk_id { 317 | DiskId::Path(ref path) => { 318 | println!("redoxfs: not able to mount path {}", path); 319 | } 320 | DiskId::Uuid(ref uuid) => { 321 | println!("redoxfs: not able to mount uuid {}", uuid.hyphenated()); 322 | } 323 | } 324 | 325 | if let Some(ref mut write) = write { 326 | let _ = write.write(&[1]); 327 | } 328 | 329 | process::exit(1); 330 | } 331 | 332 | fn main() { 333 | env_logger::init(); 334 | 335 | let mut args = env::args().skip(1); 336 | 337 | let mut daemonise = true; 338 | let mut disk_id: Option = None; 339 | let mut mountpoint: Option = None; 340 | let mut block_opt: Option = None; 341 | 342 | while let Some(arg) = args.next() { 343 | match arg.as_str() { 344 | "--no-daemon" | "-d" => daemonise = false, 345 | 346 | "--uuid" if disk_id.is_none() => { 347 | disk_id = Some(DiskId::Uuid( 348 | match args.next().as_deref().map(Uuid::parse_str) { 349 | Some(Ok(uuid)) => uuid, 350 | Some(Err(err)) => { 351 | print_err_exit(format!("redoxfs: invalid uuid '{}': {}", arg, err)) 352 | } 353 | None => print_err_exit("redoxfs: no uuid provided"), 354 | }, 355 | )); 356 | } 357 | 358 | disk if disk_id.is_none() => disk_id = Some(DiskId::Path(disk.to_owned())), 359 | 360 | mnt if disk_id.is_some() && mountpoint.is_none() => mountpoint = Some(mnt.to_owned()), 361 | 362 | opts if mountpoint.is_some() => match u64::from_str_radix(opts, 16) { 363 | Ok(block) => block_opt = Some(block), 364 | Err(err) => print_err_exit(format!("redoxfs: invalid block '{}': {}", opts, err)), 365 | }, 366 | 367 | _ => print_usage_exit(), 368 | } 369 | } 370 | 371 | let Some(disk_id) = disk_id else { 372 | print_err_exit("redoxfs: no disk provided"); 373 | }; 374 | 375 | let Some(mountpoint) = mountpoint else { 376 | print_err_exit("redoxfs: no mountpoint provided"); 377 | }; 378 | 379 | if daemonise { 380 | let mut pipes = [0; 2]; 381 | if pipe(&mut pipes) == 0 { 382 | let mut read = unsafe { File::from_raw_fd(pipes[0] as RawFd) }; 383 | let write = unsafe { File::from_raw_fd(pipes[1] as RawFd) }; 384 | 385 | let pid = fork(); 386 | if pid == 0 { 387 | drop(read); 388 | 389 | daemon(&disk_id, &mountpoint, block_opt, Some(write)); 390 | } else if pid > 0 { 391 | drop(write); 392 | 393 | let mut res = [0]; 394 | read.read_exact(&mut res).unwrap(); 395 | 396 | process::exit(res[0] as i32); 397 | } else { 398 | panic!("redoxfs: failed to fork"); 399 | } 400 | } else { 401 | panic!("redoxfs: failed to create pipe"); 402 | } 403 | } else { 404 | println!("redoxfs: running in foreground"); 405 | daemon(&disk_id, &mountpoint, block_opt, None); 406 | } 407 | } 408 | -------------------------------------------------------------------------------- /src/block.rs: -------------------------------------------------------------------------------- 1 | use core::{fmt, marker::PhantomData, mem, ops, slice}; 2 | use endian_num::Le; 3 | 4 | use crate::BLOCK_SIZE; 5 | 6 | const BLOCK_LIST_ENTRIES: usize = BLOCK_SIZE as usize / mem::size_of::>(); 7 | 8 | /// An address of a data block. 9 | /// 10 | /// This encodes a block's position _and_ [`BlockLevel`]: 11 | /// the first four bits of this `u64` encode the block's level, 12 | /// the rest encode its index. 13 | #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] 14 | pub struct BlockAddr(u64); 15 | 16 | impl BlockAddr { 17 | // Unsafe because this can create invalid blocks 18 | pub(crate) unsafe fn new(index: u64, level: BlockLevel) -> Self { 19 | // Level must only use the lowest four bits 20 | if level.0 > 0xF { 21 | panic!("block level used more than four bits"); 22 | } 23 | 24 | // Index must not use the highest four bits 25 | let inner = index 26 | .checked_shl(4) 27 | .expect("block index used highest four bits") 28 | | (level.0 as u64); 29 | Self(inner) 30 | } 31 | 32 | pub fn null(level: BlockLevel) -> Self { 33 | unsafe { Self::new(0, level) } 34 | } 35 | 36 | pub fn index(&self) -> u64 { 37 | // The first four bits store the level 38 | self.0 >> 4 39 | } 40 | 41 | pub fn level(&self) -> BlockLevel { 42 | // The first four bits store the level 43 | BlockLevel((self.0 & 0xF) as usize) 44 | } 45 | 46 | pub fn is_null(&self) -> bool { 47 | self.index() == 0 48 | } 49 | } 50 | 51 | /// The size of a block. 52 | /// 53 | /// Level 0 blocks are blocks of [`BLOCK_SIZE`] bytes. 54 | /// A level 1 block consists of two consecutive level 0 blocks. 55 | /// A level n block consists of two consecutive level n-1 blocks. 56 | /// 57 | /// See [`crate::Allocator`] docs for more details. 58 | #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] 59 | pub struct BlockLevel(pub(crate) usize); 60 | 61 | impl BlockLevel { 62 | /// Returns the smallest block level that can contain 63 | /// the given number of bytes. 64 | pub(crate) fn for_bytes(bytes: u64) -> Self { 65 | if bytes == 0 { 66 | return BlockLevel(0); 67 | } 68 | let level = bytes 69 | .div_ceil(BLOCK_SIZE) 70 | .next_power_of_two() 71 | .trailing_zeros() as usize; 72 | BlockLevel(level) 73 | } 74 | 75 | /// The number of [`BLOCK_SIZE`] blocks (i.e, level 0 blocks) 76 | /// in a block of this level 77 | pub fn blocks(self) -> i64 { 78 | 1 << self.0 79 | } 80 | 81 | /// The number of bytes in a block of this level 82 | pub fn bytes(self) -> u64 { 83 | BLOCK_SIZE << self.0 84 | } 85 | } 86 | 87 | pub unsafe trait BlockTrait { 88 | /// Create an empty block of this type. 89 | fn empty(level: BlockLevel) -> Option 90 | where 91 | Self: Sized; 92 | } 93 | 94 | /// A [`BlockAddr`] and the data it points to. 95 | #[derive(Clone, Copy, Debug, Default)] 96 | pub struct BlockData { 97 | addr: BlockAddr, 98 | data: T, 99 | } 100 | 101 | impl BlockData { 102 | pub fn new(addr: BlockAddr, data: T) -> Self { 103 | Self { addr, data } 104 | } 105 | 106 | pub fn addr(&self) -> BlockAddr { 107 | self.addr 108 | } 109 | 110 | pub fn data(&self) -> &T { 111 | &self.data 112 | } 113 | 114 | pub fn data_mut(&mut self) -> &mut T { 115 | &mut self.data 116 | } 117 | 118 | pub(crate) unsafe fn into_parts(self) -> (BlockAddr, T) { 119 | (self.addr, self.data) 120 | } 121 | 122 | /// Set the address of this [`BlockData`] to `addr`, returning this 123 | /// block's old address. This method does not update block data. 124 | /// 125 | /// `addr` must point to a block with the same level as this block. 126 | #[must_use = "don't forget to de-allocate old block address"] 127 | pub fn swap_addr(&mut self, addr: BlockAddr) -> BlockAddr { 128 | // Address levels must match 129 | assert_eq!(self.addr.level(), addr.level()); 130 | let old = self.addr; 131 | self.addr = addr; 132 | old 133 | } 134 | } 135 | 136 | impl BlockData { 137 | pub fn empty(addr: BlockAddr) -> Option { 138 | let empty = T::empty(addr.level())?; 139 | Some(Self::new(addr, empty)) 140 | } 141 | } 142 | 143 | impl> BlockData { 144 | pub fn create_ptr(&self) -> BlockPtr { 145 | BlockPtr { 146 | addr: self.addr.0.into(), 147 | hash: seahash::hash(self.data.deref()).into(), 148 | phantom: PhantomData, 149 | } 150 | } 151 | } 152 | 153 | #[repr(C, packed)] 154 | pub struct BlockList { 155 | pub ptrs: [BlockPtr; BLOCK_LIST_ENTRIES], 156 | } 157 | 158 | unsafe impl BlockTrait for BlockList { 159 | fn empty(level: BlockLevel) -> Option { 160 | if level.0 == 0 { 161 | Some(Self { 162 | ptrs: [BlockPtr::default(); BLOCK_LIST_ENTRIES], 163 | }) 164 | } else { 165 | None 166 | } 167 | } 168 | } 169 | 170 | impl BlockList { 171 | pub fn is_empty(&self) -> bool { 172 | self.ptrs.iter().all(|ptr| ptr.is_null()) 173 | } 174 | } 175 | 176 | impl ops::Deref for BlockList { 177 | type Target = [u8]; 178 | fn deref(&self) -> &[u8] { 179 | unsafe { 180 | slice::from_raw_parts( 181 | self as *const BlockList as *const u8, 182 | mem::size_of::>(), 183 | ) as &[u8] 184 | } 185 | } 186 | } 187 | 188 | impl ops::DerefMut for BlockList { 189 | fn deref_mut(&mut self) -> &mut [u8] { 190 | unsafe { 191 | slice::from_raw_parts_mut( 192 | self as *mut BlockList as *mut u8, 193 | mem::size_of::>(), 194 | ) as &mut [u8] 195 | } 196 | } 197 | } 198 | 199 | /// An address of a data block, along with a checksum of its data. 200 | /// 201 | /// This encodes a block's position _and_ [`BlockLevel`]. 202 | /// the first four bits of `addr` encode the block's level, 203 | /// the rest encode its index. 204 | /// 205 | /// Also see [`BlockAddr`]. 206 | #[repr(C, packed)] 207 | pub struct BlockPtr { 208 | addr: Le, 209 | hash: Le, 210 | phantom: PhantomData, 211 | } 212 | 213 | impl BlockPtr { 214 | pub fn null(level: BlockLevel) -> Self { 215 | Self { 216 | addr: BlockAddr::null(level).0.into(), 217 | hash: 0.into(), 218 | phantom: PhantomData, 219 | } 220 | } 221 | 222 | pub fn addr(&self) -> BlockAddr { 223 | BlockAddr(self.addr.to_ne()) 224 | } 225 | 226 | pub fn hash(&self) -> u64 { 227 | self.hash.to_ne() 228 | } 229 | 230 | pub fn is_null(&self) -> bool { 231 | self.addr().is_null() 232 | } 233 | 234 | /// Cast BlockPtr to another type 235 | /// 236 | /// # Safety 237 | /// Unsafe because it can be used to transmute types 238 | pub unsafe fn cast(self) -> BlockPtr { 239 | BlockPtr { 240 | addr: self.addr, 241 | hash: self.hash, 242 | phantom: PhantomData, 243 | } 244 | } 245 | 246 | #[must_use = "the returned pointer should usually be deallocated"] 247 | pub fn clear(&mut self) -> BlockPtr { 248 | let mut ptr = Self::default(); 249 | mem::swap(self, &mut ptr); 250 | ptr 251 | } 252 | } 253 | 254 | impl Clone for BlockPtr { 255 | fn clone(&self) -> Self { 256 | *self 257 | } 258 | } 259 | 260 | impl Copy for BlockPtr {} 261 | 262 | impl Default for BlockPtr { 263 | fn default() -> Self { 264 | Self { 265 | addr: 0.into(), 266 | hash: 0.into(), 267 | phantom: PhantomData, 268 | } 269 | } 270 | } 271 | 272 | impl fmt::Debug for BlockPtr { 273 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 274 | let addr = self.addr(); 275 | let hash = self.hash(); 276 | f.debug_struct("BlockPtr") 277 | .field("addr", &addr) 278 | .field("hash", &hash) 279 | .finish() 280 | } 281 | } 282 | 283 | #[repr(C, packed)] 284 | #[derive(Clone)] 285 | pub struct BlockRaw([u8; BLOCK_SIZE as usize]); 286 | 287 | unsafe impl BlockTrait for BlockRaw { 288 | fn empty(level: BlockLevel) -> Option { 289 | if level.0 == 0 { 290 | Some(Self([0; BLOCK_SIZE as usize])) 291 | } else { 292 | None 293 | } 294 | } 295 | } 296 | 297 | impl ops::Deref for BlockRaw { 298 | type Target = [u8]; 299 | fn deref(&self) -> &[u8] { 300 | &self.0 301 | } 302 | } 303 | 304 | impl ops::DerefMut for BlockRaw { 305 | fn deref_mut(&mut self) -> &mut [u8] { 306 | &mut self.0 307 | } 308 | } 309 | 310 | #[test] 311 | fn block_list_size_test() { 312 | assert_eq!(mem::size_of::>(), BLOCK_SIZE as usize); 313 | } 314 | 315 | #[test] 316 | fn block_raw_size_test() { 317 | assert_eq!(mem::size_of::(), BLOCK_SIZE as usize); 318 | } 319 | -------------------------------------------------------------------------------- /src/dir.rs: -------------------------------------------------------------------------------- 1 | use alloc::{boxed::Box, vec}; 2 | use core::{mem, ops, slice, str}; 3 | 4 | use crate::{BlockLevel, BlockTrait, Node, TreePtr, DIR_ENTRY_MAX_LENGTH, RECORD_LEVEL}; 5 | 6 | #[repr(C, packed)] 7 | #[derive(Clone, Copy)] 8 | pub struct DirEntry { 9 | node_ptr: TreePtr, 10 | name: [u8; DIR_ENTRY_MAX_LENGTH], 11 | } 12 | 13 | impl DirEntry { 14 | pub fn new(node_ptr: TreePtr, name: &str) -> DirEntry { 15 | let mut entry = DirEntry { 16 | node_ptr, 17 | ..Default::default() 18 | }; 19 | 20 | entry.name[..name.len()].copy_from_slice(name.as_bytes()); 21 | 22 | entry 23 | } 24 | 25 | pub fn node_ptr(&self) -> TreePtr { 26 | self.node_ptr 27 | } 28 | 29 | pub fn name(&self) -> Option<&str> { 30 | let mut len = 0; 31 | while len < self.name.len() { 32 | if self.name[len] == 0 { 33 | break; 34 | } 35 | len += 1; 36 | } 37 | //TODO: report utf8 error? 38 | str::from_utf8(&self.name[..len]).ok() 39 | } 40 | } 41 | 42 | impl Default for DirEntry { 43 | fn default() -> Self { 44 | Self { 45 | node_ptr: TreePtr::default(), 46 | name: [0; DIR_ENTRY_MAX_LENGTH], 47 | } 48 | } 49 | } 50 | 51 | //TODO: this is a box to prevent stack overflows 52 | pub struct DirList { 53 | pub entries: Box<[DirEntry]>, 54 | } 55 | 56 | unsafe impl BlockTrait for DirList { 57 | fn empty(level: BlockLevel) -> Option { 58 | if level.0 <= RECORD_LEVEL { 59 | let entries = level.bytes() as usize / mem::size_of::(); 60 | Some(Self { 61 | entries: vec![DirEntry::default(); entries].into_boxed_slice(), 62 | }) 63 | } else { 64 | None 65 | } 66 | } 67 | } 68 | 69 | impl DirList { 70 | pub fn is_empty(&self) -> bool { 71 | self.entries.iter().all(|entry| entry.node_ptr().is_null()) 72 | } 73 | } 74 | 75 | impl ops::Deref for DirList { 76 | type Target = [u8]; 77 | fn deref(&self) -> &[u8] { 78 | unsafe { 79 | slice::from_raw_parts( 80 | self.entries.as_ptr() as *const u8, 81 | self.entries.len() * mem::size_of::(), 82 | ) as &[u8] 83 | } 84 | } 85 | } 86 | 87 | impl ops::DerefMut for DirList { 88 | fn deref_mut(&mut self) -> &mut [u8] { 89 | unsafe { 90 | slice::from_raw_parts_mut( 91 | self.entries.as_mut_ptr() as *mut u8, 92 | self.entries.len() * mem::size_of::(), 93 | ) as &mut [u8] 94 | } 95 | } 96 | } 97 | 98 | #[test] 99 | fn dir_list_size_test() { 100 | use core::ops::Deref; 101 | for level_i in 0..RECORD_LEVEL { 102 | let level = BlockLevel(level_i); 103 | assert_eq!( 104 | DirList::empty(level).unwrap().deref().len(), 105 | level.bytes() as usize 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/disk/cache.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, VecDeque}; 2 | use std::{cmp, ptr}; 3 | use syscall::error::Result; 4 | 5 | use crate::disk::Disk; 6 | use crate::BLOCK_SIZE; 7 | 8 | fn copy_memory(src: &[u8], dest: &mut [u8]) -> usize { 9 | let len = cmp::min(src.len(), dest.len()); 10 | unsafe { ptr::copy(src.as_ptr(), dest.as_mut_ptr(), len) }; 11 | len 12 | } 13 | 14 | pub struct DiskCache { 15 | inner: T, 16 | cache: HashMap, 17 | order: VecDeque, 18 | size: usize, 19 | } 20 | 21 | impl DiskCache { 22 | pub fn new(inner: T) -> Self { 23 | // 16 MB cache 24 | let size = 16 * 1024 * 1024 / BLOCK_SIZE as usize; 25 | DiskCache { 26 | inner, 27 | cache: HashMap::with_capacity(size), 28 | order: VecDeque::with_capacity(size), 29 | size, 30 | } 31 | } 32 | 33 | fn insert(&mut self, i: u64, data: [u8; BLOCK_SIZE as usize]) { 34 | while self.order.len() >= self.size { 35 | let removed = self.order.pop_front().unwrap(); 36 | self.cache.remove(&removed); 37 | } 38 | 39 | self.cache.insert(i, data); 40 | self.order.push_back(i); 41 | } 42 | } 43 | 44 | impl Disk for DiskCache { 45 | unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result { 46 | // println!("Cache read at {}", block); 47 | 48 | let mut read = 0; 49 | let mut failed = false; 50 | for i in 0..(buffer.len() + BLOCK_SIZE as usize - 1) / (BLOCK_SIZE as usize) { 51 | let block_i = block + i as u64; 52 | 53 | let buffer_i = i * BLOCK_SIZE as usize; 54 | let buffer_j = cmp::min(buffer_i + BLOCK_SIZE as usize, buffer.len()); 55 | let buffer_slice = &mut buffer[buffer_i..buffer_j]; 56 | 57 | if let Some(cache_buf) = self.cache.get_mut(&block_i) { 58 | read += copy_memory(cache_buf, buffer_slice); 59 | } else { 60 | failed = true; 61 | break; 62 | } 63 | } 64 | 65 | if failed { 66 | self.inner.read_at(block, buffer)?; 67 | 68 | read = 0; 69 | for i in 0..(buffer.len() + BLOCK_SIZE as usize - 1) / (BLOCK_SIZE as usize) { 70 | let block_i = block + i as u64; 71 | 72 | let buffer_i = i * BLOCK_SIZE as usize; 73 | let buffer_j = cmp::min(buffer_i + BLOCK_SIZE as usize, buffer.len()); 74 | let buffer_slice = &buffer[buffer_i..buffer_j]; 75 | 76 | let mut cache_buf = [0; BLOCK_SIZE as usize]; 77 | read += copy_memory(buffer_slice, &mut cache_buf); 78 | self.insert(block_i, cache_buf); 79 | } 80 | } 81 | 82 | Ok(read) 83 | } 84 | 85 | unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result { 86 | //TODO: Write only blocks that have changed 87 | // println!("Cache write at {}", block); 88 | 89 | self.inner.write_at(block, buffer)?; 90 | 91 | let mut written = 0; 92 | for i in 0..(buffer.len() + BLOCK_SIZE as usize - 1) / (BLOCK_SIZE as usize) { 93 | let block_i = block + i as u64; 94 | 95 | let buffer_i = i * BLOCK_SIZE as usize; 96 | let buffer_j = cmp::min(buffer_i + BLOCK_SIZE as usize, buffer.len()); 97 | let buffer_slice = &buffer[buffer_i..buffer_j]; 98 | 99 | let mut cache_buf = [0; BLOCK_SIZE as usize]; 100 | written += copy_memory(buffer_slice, &mut cache_buf); 101 | self.insert(block_i, cache_buf); 102 | } 103 | 104 | Ok(written) 105 | } 106 | 107 | fn size(&mut self) -> Result { 108 | self.inner.size() 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/disk/file.rs: -------------------------------------------------------------------------------- 1 | use std::fs::{File, OpenOptions}; 2 | use std::io::{Seek, SeekFrom}; 3 | use std::os::unix::fs::FileExt; 4 | use std::path::Path; 5 | 6 | use syscall::error::{Error, Result, EIO}; 7 | 8 | use crate::disk::Disk; 9 | use crate::BLOCK_SIZE; 10 | 11 | pub struct DiskFile { 12 | pub file: File, 13 | } 14 | 15 | trait ResultExt { 16 | type T; 17 | fn or_eio(self) -> Result; 18 | } 19 | impl ResultExt for Result { 20 | type T = T; 21 | fn or_eio(self) -> Result { 22 | match self { 23 | Ok(t) => Ok(t), 24 | Err(err) => { 25 | eprintln!("RedoxFS: IO ERROR: {err}"); 26 | Err(Error::new(EIO)) 27 | } 28 | } 29 | } 30 | } 31 | impl ResultExt for std::io::Result { 32 | type T = T; 33 | fn or_eio(self) -> Result { 34 | match self { 35 | Ok(t) => Ok(t), 36 | Err(err) => { 37 | eprintln!("RedoxFS: IO ERROR: {err}"); 38 | Err(Error::new(EIO)) 39 | } 40 | } 41 | } 42 | } 43 | 44 | impl DiskFile { 45 | pub fn open(path: impl AsRef) -> Result { 46 | let file = OpenOptions::new() 47 | .read(true) 48 | .write(true) 49 | .open(path) 50 | .or_eio()?; 51 | Ok(DiskFile { file }) 52 | } 53 | 54 | pub fn create(path: impl AsRef, size: u64) -> Result { 55 | let file = OpenOptions::new() 56 | .read(true) 57 | .write(true) 58 | .create(true) 59 | .open(path) 60 | .or_eio()?; 61 | file.set_len(size).or_eio()?; 62 | Ok(DiskFile { file }) 63 | } 64 | } 65 | 66 | impl Disk for DiskFile { 67 | unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result { 68 | self.file.read_at(buffer, block * BLOCK_SIZE).or_eio() 69 | } 70 | 71 | unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result { 72 | self.file.write_at(buffer, block * BLOCK_SIZE).or_eio() 73 | } 74 | 75 | fn size(&mut self) -> Result { 76 | self.file.seek(SeekFrom::End(0)).or_eio() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/disk/io.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Seek, SeekFrom, Write}; 2 | use syscall::error::{Error, Result, EIO}; 3 | 4 | use crate::disk::Disk; 5 | use crate::BLOCK_SIZE; 6 | 7 | macro_rules! try_disk { 8 | ($expr:expr) => { 9 | match $expr { 10 | Ok(val) => val, 11 | Err(err) => { 12 | eprintln!("Disk I/O Error: {}", err); 13 | return Err(Error::new(EIO)); 14 | } 15 | } 16 | }; 17 | } 18 | 19 | pub struct DiskIo(pub T); 20 | 21 | impl Disk for DiskIo { 22 | unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result { 23 | try_disk!(self.0.seek(SeekFrom::Start(block * BLOCK_SIZE))); 24 | let count = try_disk!(self.0.read(buffer)); 25 | Ok(count) 26 | } 27 | 28 | unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result { 29 | try_disk!(self.0.seek(SeekFrom::Start(block * BLOCK_SIZE))); 30 | let count = try_disk!(self.0.write(buffer)); 31 | Ok(count) 32 | } 33 | 34 | fn size(&mut self) -> Result { 35 | let size = try_disk!(self.0.seek(SeekFrom::End(0))); 36 | Ok(size) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/disk/mod.rs: -------------------------------------------------------------------------------- 1 | use syscall::error::Result; 2 | 3 | #[cfg(feature = "std")] 4 | pub use self::cache::DiskCache; 5 | #[cfg(feature = "std")] 6 | pub use self::file::DiskFile; 7 | #[cfg(feature = "std")] 8 | pub use self::io::DiskIo; 9 | #[cfg(feature = "std")] 10 | pub use self::sparse::DiskSparse; 11 | 12 | #[cfg(feature = "std")] 13 | mod cache; 14 | #[cfg(feature = "std")] 15 | mod file; 16 | #[cfg(feature = "std")] 17 | mod io; 18 | #[cfg(feature = "std")] 19 | mod sparse; 20 | 21 | /// A disk 22 | pub trait Disk { 23 | /// Read blocks from disk 24 | /// 25 | /// # Safety 26 | /// Unsafe to discourage use, use filesystem wrappers instead 27 | unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result; 28 | 29 | /// Write blocks from disk 30 | /// 31 | /// # Safety 32 | /// Unsafe to discourage use, use filesystem wrappers instead 33 | unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result; 34 | 35 | /// Get size of disk in bytes 36 | fn size(&mut self) -> Result; 37 | } 38 | -------------------------------------------------------------------------------- /src/disk/sparse.rs: -------------------------------------------------------------------------------- 1 | use std::fs::{File, OpenOptions}; 2 | use std::io::{Read, Seek, SeekFrom, Write}; 3 | use std::path::Path; 4 | use std::u64; 5 | use syscall::error::{Error, Result, EIO}; 6 | 7 | use crate::disk::Disk; 8 | use crate::BLOCK_SIZE; 9 | 10 | macro_rules! try_disk { 11 | ($expr:expr) => { 12 | match $expr { 13 | Ok(val) => val, 14 | Err(err) => { 15 | eprintln!("Disk I/O Error: {}", err); 16 | return Err(Error::new(EIO)); 17 | } 18 | } 19 | }; 20 | } 21 | 22 | pub struct DiskSparse { 23 | pub file: File, 24 | pub max_size: u64, 25 | } 26 | 27 | impl DiskSparse { 28 | pub fn create>(path: P, max_size: u64) -> Result { 29 | let file = try_disk!(OpenOptions::new() 30 | .read(true) 31 | .write(true) 32 | .create(true) 33 | .open(path)); 34 | Ok(DiskSparse { file, max_size }) 35 | } 36 | } 37 | 38 | impl Disk for DiskSparse { 39 | unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result { 40 | try_disk!(self.file.seek(SeekFrom::Start(block * BLOCK_SIZE))); 41 | let count = try_disk!(self.file.read(buffer)); 42 | Ok(count) 43 | } 44 | 45 | unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result { 46 | try_disk!(self.file.seek(SeekFrom::Start(block * BLOCK_SIZE))); 47 | let count = try_disk!(self.file.write(buffer)); 48 | Ok(count) 49 | } 50 | 51 | fn size(&mut self) -> Result { 52 | Ok(self.max_size) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/filesystem.rs: -------------------------------------------------------------------------------- 1 | use aes::{Aes128, BlockDecrypt, BlockEncrypt}; 2 | use alloc::{collections::VecDeque, vec::Vec}; 3 | use syscall::error::{Error, Result, EKEYREJECTED, ENOENT, ENOKEY}; 4 | 5 | #[cfg(feature = "std")] 6 | use crate::{AllocEntry, AllocList, BlockData, BlockTrait, Key, KeySlot, Node, Salt, TreeList}; 7 | use crate::{Allocator, BlockAddr, BlockLevel, Disk, Header, Transaction, BLOCK_SIZE, HEADER_RING}; 8 | 9 | /// A file system 10 | pub struct FileSystem { 11 | //TODO: make private 12 | pub disk: D, 13 | //TODO: make private 14 | pub block: u64, 15 | //TODO: make private 16 | pub header: Header, 17 | pub(crate) allocator: Allocator, 18 | pub(crate) aes_opt: Option, 19 | aes_blocks: Vec, 20 | } 21 | 22 | impl FileSystem { 23 | /// Open a file system on a disk 24 | pub fn open( 25 | mut disk: D, 26 | password_opt: Option<&[u8]>, 27 | block_opt: Option, 28 | squash: bool, 29 | ) -> Result { 30 | for ring_block in block_opt.map_or(0..65536, |x| x..x + 1) { 31 | let mut header = Header::default(); 32 | unsafe { disk.read_at(ring_block, &mut header)? }; 33 | 34 | // Skip invalid headers 35 | if !header.valid() { 36 | continue; 37 | } 38 | 39 | let block = ring_block - (header.generation() % HEADER_RING); 40 | for i in 0..HEADER_RING { 41 | let mut other_header = Header::default(); 42 | unsafe { disk.read_at(block + i, &mut other_header)? }; 43 | 44 | // Skip invalid headers 45 | if !other_header.valid() { 46 | continue; 47 | } 48 | 49 | // If this is a newer header, use it 50 | if other_header.generation() > header.generation() { 51 | header = other_header; 52 | } 53 | } 54 | 55 | let aes_opt = match password_opt { 56 | Some(password) => { 57 | if !header.encrypted() { 58 | // Header not encrypted but password provided 59 | return Err(Error::new(EKEYREJECTED)); 60 | } 61 | match header.aes(password) { 62 | Some(aes) => Some(aes), 63 | None => { 64 | // Header encrypted with a different password 65 | return Err(Error::new(ENOKEY)); 66 | } 67 | } 68 | } 69 | None => { 70 | if header.encrypted() { 71 | // Header encrypted but no password provided 72 | return Err(Error::new(ENOKEY)); 73 | } 74 | None 75 | } 76 | }; 77 | 78 | let mut fs = FileSystem { 79 | disk, 80 | block, 81 | header, 82 | allocator: Allocator::default(), 83 | aes_opt, 84 | aes_blocks: Vec::with_capacity(BLOCK_SIZE as usize / aes::BLOCK_SIZE), 85 | }; 86 | 87 | unsafe { fs.reset_allocator()? }; 88 | 89 | // Squash allocations and sync 90 | Transaction::new(&mut fs).commit(squash)?; 91 | 92 | return Ok(fs); 93 | } 94 | 95 | Err(Error::new(ENOENT)) 96 | } 97 | 98 | /// Create a file system on a disk 99 | #[cfg(feature = "std")] 100 | pub fn create( 101 | disk: D, 102 | password_opt: Option<&[u8]>, 103 | ctime: u64, 104 | ctime_nsec: u32, 105 | ) -> Result { 106 | Self::create_reserved(disk, password_opt, &[], ctime, ctime_nsec) 107 | } 108 | 109 | /// Create a file system on a disk, with reserved data at the beginning 110 | /// Reserved data will be zero padded up to the nearest block 111 | /// We need to pass ctime and ctime_nsec in order to initialize the unix timestamps 112 | #[cfg(feature = "std")] 113 | pub fn create_reserved( 114 | mut disk: D, 115 | password_opt: Option<&[u8]>, 116 | reserved: &[u8], 117 | ctime: u64, 118 | ctime_nsec: u32, 119 | ) -> Result { 120 | let size = disk.size()?; 121 | let block_offset = (reserved.len() as u64 + BLOCK_SIZE - 1) / BLOCK_SIZE; 122 | 123 | if size < (block_offset + HEADER_RING + 4) * BLOCK_SIZE { 124 | return Err(Error::new(syscall::error::ENOSPC)); 125 | } 126 | 127 | // Fill reserved data, pad with zeroes 128 | for block in 0..block_offset as usize { 129 | let mut data = [0; BLOCK_SIZE as usize]; 130 | 131 | let mut i = 0; 132 | while i < data.len() && block * BLOCK_SIZE as usize + i < reserved.len() { 133 | data[i] = reserved[block * BLOCK_SIZE as usize + i]; 134 | i += 1; 135 | } 136 | 137 | unsafe { 138 | disk.write_at(block as u64, &data)?; 139 | } 140 | } 141 | 142 | let mut header = Header::new(size); 143 | 144 | let aes_opt = match password_opt { 145 | Some(password) => { 146 | //TODO: handle errors 147 | header.key_slots[0] = 148 | KeySlot::new(password, Salt::new().unwrap(), Key::new().unwrap()).unwrap(); 149 | Some(header.key_slots[0].key(password).unwrap().into_aes()) 150 | } 151 | None => None, 152 | }; 153 | 154 | let mut fs = FileSystem { 155 | disk, 156 | block: block_offset, 157 | header, 158 | allocator: Allocator::default(), 159 | aes_opt, 160 | aes_blocks: Vec::with_capacity(BLOCK_SIZE as usize / aes::BLOCK_SIZE), 161 | }; 162 | 163 | // Write header generation zero 164 | let count = unsafe { fs.disk.write_at(fs.block, &fs.header)? }; 165 | if count != core::mem::size_of_val(&fs.header) { 166 | // Wrote wrong number of bytes 167 | #[cfg(feature = "log")] 168 | log::error!("CREATE: WRONG NUMBER OF BYTES"); 169 | return Err(Error::new(syscall::error::EIO)); 170 | } 171 | 172 | // Set tree and alloc pointers and write header generation one 173 | fs.tx(|tx| unsafe { 174 | let tree = BlockData::new( 175 | BlockAddr::new(HEADER_RING + 1, BlockLevel::default()), 176 | TreeList::empty(BlockLevel::default()).unwrap(), 177 | ); 178 | 179 | let mut alloc = BlockData::new( 180 | BlockAddr::new(HEADER_RING + 2, BlockLevel::default()), 181 | AllocList::empty(BlockLevel::default()).unwrap(), 182 | ); 183 | 184 | let alloc_free = size / BLOCK_SIZE - (block_offset + HEADER_RING + 4); 185 | alloc.data_mut().entries[0] = AllocEntry::new(HEADER_RING + 4, alloc_free as i64); 186 | 187 | tx.header.tree = tx.write_block(tree)?; 188 | tx.header.alloc = tx.write_block(alloc)?; 189 | tx.header_changed = true; 190 | 191 | Ok(()) 192 | })?; 193 | 194 | unsafe { 195 | fs.reset_allocator()?; 196 | } 197 | 198 | fs.tx(|tx| unsafe { 199 | let mut root = BlockData::new( 200 | BlockAddr::new(HEADER_RING + 3, BlockLevel::default()), 201 | Node::new(Node::MODE_DIR | 0o755, 0, 0, ctime, ctime_nsec), 202 | ); 203 | root.data_mut().set_links(1); 204 | let root_ptr = tx.write_block(root)?; 205 | assert_eq!(tx.insert_tree(root_ptr)?.id(), 1); 206 | Ok(()) 207 | })?; 208 | 209 | // Make sure everything is synced and squash allocations 210 | Transaction::new(&mut fs).commit(true)?; 211 | 212 | Ok(fs) 213 | } 214 | 215 | /// start a filesystem transaction, required for making any changes 216 | pub fn tx) -> Result, T>(&mut self, f: F) -> Result { 217 | let mut tx = Transaction::new(self); 218 | let t = f(&mut tx)?; 219 | tx.commit(false)?; 220 | Ok(t) 221 | } 222 | 223 | pub fn allocator(&self) -> &Allocator { 224 | &self.allocator 225 | } 226 | 227 | /// Reset allocator to state stored on disk 228 | /// 229 | /// # Safety 230 | /// Unsafe, it must only be called when opening the filesystem 231 | unsafe fn reset_allocator(&mut self) -> Result<()> { 232 | self.allocator = Allocator::default(); 233 | 234 | // To avoid having to update all prior alloc blocks, there is only a previous pointer 235 | // This means we need to roll back all allocations. Currently we do this by reading the 236 | // alloc log into a buffer to reverse it. 237 | let mut allocs = VecDeque::new(); 238 | self.tx(|tx| { 239 | let mut alloc_ptr = tx.header.alloc; 240 | while !alloc_ptr.is_null() { 241 | let alloc = tx.read_block(alloc_ptr)?; 242 | alloc_ptr = alloc.data().prev; 243 | allocs.push_front(alloc); 244 | } 245 | Ok(()) 246 | })?; 247 | 248 | for alloc in allocs { 249 | for entry in alloc.data().entries.iter() { 250 | let index = entry.index(); 251 | let count = entry.count(); 252 | if count < 0 { 253 | for i in 0..-count { 254 | //TODO: replace assert with error? 255 | let addr = BlockAddr::new(index + i as u64, BlockLevel::default()); 256 | assert_eq!(self.allocator.allocate_exact(addr), Some(addr)); 257 | } 258 | } else { 259 | for i in 0..count { 260 | let addr = BlockAddr::new(index + i as u64, BlockLevel::default()); 261 | self.allocator.deallocate(addr); 262 | } 263 | } 264 | } 265 | } 266 | 267 | Ok(()) 268 | } 269 | 270 | pub(crate) fn decrypt(&mut self, data: &mut [u8]) -> bool { 271 | let aes = if let Some(ref aes) = self.aes_opt { 272 | aes 273 | } else { 274 | // Do nothing if encryption is disabled 275 | return false; 276 | }; 277 | 278 | assert_eq!(data.len() % aes::BLOCK_SIZE, 0); 279 | 280 | self.aes_blocks.clear(); 281 | for i in 0..data.len() / aes::BLOCK_SIZE { 282 | self.aes_blocks.push(aes::Block::clone_from_slice( 283 | &data[i * aes::BLOCK_SIZE..(i + 1) * aes::BLOCK_SIZE], 284 | )); 285 | } 286 | 287 | aes.decrypt_blocks(&mut self.aes_blocks); 288 | 289 | for i in 0..data.len() / aes::BLOCK_SIZE { 290 | data[i * aes::BLOCK_SIZE..(i + 1) * aes::BLOCK_SIZE] 291 | .copy_from_slice(&self.aes_blocks[i]); 292 | } 293 | self.aes_blocks.clear(); 294 | 295 | true 296 | } 297 | 298 | pub(crate) fn encrypt(&mut self, data: &mut [u8]) -> bool { 299 | let aes = if let Some(ref aes) = self.aes_opt { 300 | aes 301 | } else { 302 | // Do nothing if encryption is disabled 303 | return false; 304 | }; 305 | 306 | assert_eq!(data.len() % aes::BLOCK_SIZE, 0); 307 | 308 | self.aes_blocks.clear(); 309 | for i in 0..data.len() / aes::BLOCK_SIZE { 310 | self.aes_blocks.push(aes::Block::clone_from_slice( 311 | &data[i * aes::BLOCK_SIZE..(i + 1) * aes::BLOCK_SIZE], 312 | )); 313 | } 314 | 315 | aes.encrypt_blocks(&mut self.aes_blocks); 316 | 317 | for i in 0..data.len() / aes::BLOCK_SIZE { 318 | data[i * aes::BLOCK_SIZE..(i + 1) * aes::BLOCK_SIZE] 319 | .copy_from_slice(&self.aes_blocks[i]); 320 | } 321 | self.aes_blocks.clear(); 322 | 323 | true 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /src/header.rs: -------------------------------------------------------------------------------- 1 | use core::ops::{Deref, DerefMut}; 2 | use core::{fmt, mem, slice}; 3 | use endian_num::Le; 4 | 5 | use aes::{Aes128, BlockDecrypt, BlockEncrypt}; 6 | 7 | use crate::{AllocList, BlockPtr, KeySlot, Tree, BLOCK_SIZE, SIGNATURE, VERSION}; 8 | 9 | pub const HEADER_RING: u64 = 256; 10 | 11 | /// The header of the filesystem 12 | #[derive(Clone, Copy)] 13 | #[repr(C, packed)] 14 | pub struct Header { 15 | /// Signature, should be SIGNATURE 16 | pub signature: [u8; 8], 17 | /// Version, should be VERSION 18 | pub version: Le, 19 | /// Disk ID, a 128-bit unique identifier 20 | pub uuid: [u8; 16], 21 | /// Disk size, in number of BLOCK_SIZE sectors 22 | pub size: Le, 23 | /// Generation of header 24 | pub generation: Le, 25 | /// Block of first tree node 26 | pub tree: BlockPtr, 27 | /// Block of last alloc node 28 | pub alloc: BlockPtr, 29 | /// Key slots 30 | pub key_slots: [KeySlot; 64], 31 | /// Padding 32 | pub padding: [u8; BLOCK_SIZE as usize - 2152], 33 | /// encrypted hash of header data without hash, set to hash and padded if disk is not encrypted 34 | pub encrypted_hash: [u8; 16], 35 | /// hash of header data without hash 36 | pub hash: Le, 37 | } 38 | 39 | impl Header { 40 | #[cfg(feature = "std")] 41 | pub fn new(size: u64) -> Header { 42 | let uuid = uuid::Uuid::new_v4(); 43 | let mut header = Header { 44 | signature: *SIGNATURE, 45 | version: VERSION.into(), 46 | uuid: *uuid.as_bytes(), 47 | size: size.into(), 48 | ..Default::default() 49 | }; 50 | header.update_hash(None); 51 | header 52 | } 53 | 54 | pub fn valid(&self) -> bool { 55 | if &self.signature != SIGNATURE { 56 | // Signature does not match 57 | return false; 58 | } 59 | 60 | if self.version.to_ne() != VERSION { 61 | // Version does not match 62 | return false; 63 | } 64 | 65 | if self.hash.to_ne() != self.create_hash() { 66 | // Hash does not match 67 | return false; 68 | } 69 | 70 | // All tests passed, header is valid 71 | true 72 | } 73 | 74 | pub fn uuid(&self) -> [u8; 16] { 75 | self.uuid 76 | } 77 | 78 | pub fn size(&self) -> u64 { 79 | self.size.to_ne() 80 | } 81 | 82 | pub fn generation(&self) -> u64 { 83 | self.generation.to_ne() 84 | } 85 | 86 | fn create_hash(&self) -> u64 { 87 | // Calculate part of header to hash (everything before the hashes) 88 | let end = mem::size_of_val(self) 89 | - mem::size_of_val(&{ self.hash }) 90 | - mem::size_of_val(&{ self.encrypted_hash }); 91 | seahash::hash(&self[..end]) 92 | } 93 | 94 | fn create_encrypted_hash(&self, aes_opt: Option<&Aes128>) -> [u8; 16] { 95 | let mut encrypted_hash = [0; 16]; 96 | for (i, b) in self.hash.to_le_bytes().iter().enumerate() { 97 | encrypted_hash[i] = *b; 98 | } 99 | if let Some(aes) = aes_opt { 100 | let mut block = aes::Block::from(encrypted_hash); 101 | aes.encrypt_block(&mut block); 102 | encrypted_hash = block.into(); 103 | } 104 | encrypted_hash 105 | } 106 | 107 | pub fn encrypted(&self) -> bool { 108 | (self.encrypted_hash) != self.create_encrypted_hash(None) 109 | } 110 | 111 | pub fn aes(&self, password: &[u8]) -> Option { 112 | let hash = self.create_encrypted_hash(None); 113 | for slot in self.key_slots.iter() { 114 | //TODO: handle errors 115 | let aes = slot.key(password).unwrap().into_aes(); 116 | let mut block = aes::Block::from(self.encrypted_hash); 117 | aes.decrypt_block(&mut block); 118 | if block == aes::Block::from(hash) { 119 | return Some(aes); 120 | } 121 | } 122 | None 123 | } 124 | fn update_hash(&mut self, aes_opt: Option<&Aes128>) { 125 | self.hash = self.create_hash().into(); 126 | // Make sure to do this second, it relies on the hash being up to date 127 | self.encrypted_hash = self.create_encrypted_hash(aes_opt); 128 | } 129 | 130 | pub fn update(&mut self, aes_opt: Option<&Aes128>) -> u64 { 131 | let mut generation = self.generation(); 132 | generation += 1; 133 | self.generation = generation.into(); 134 | self.update_hash(aes_opt); 135 | generation 136 | } 137 | } 138 | 139 | impl Default for Header { 140 | fn default() -> Self { 141 | Self { 142 | signature: [0; 8], 143 | version: 0.into(), 144 | uuid: [0; 16], 145 | size: 0.into(), 146 | generation: 0.into(), 147 | tree: BlockPtr::::default(), 148 | alloc: BlockPtr::::default(), 149 | key_slots: [KeySlot::default(); 64], 150 | padding: [0; BLOCK_SIZE as usize - 2152], 151 | encrypted_hash: [0; 16], 152 | hash: 0.into(), 153 | } 154 | } 155 | } 156 | 157 | impl fmt::Debug for Header { 158 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 159 | let signature = self.signature; 160 | let version = self.version; 161 | let uuid = self.uuid; 162 | let size = self.size; 163 | let generation = self.generation; 164 | let tree = self.tree; 165 | let alloc = self.alloc; 166 | let hash = self.hash; 167 | f.debug_struct("Header") 168 | .field("signature", &signature) 169 | .field("version", &version) 170 | .field("uuid", &uuid) 171 | .field("size", &size) 172 | .field("generation", &generation) 173 | .field("tree", &tree) 174 | .field("alloc", &alloc) 175 | .field("hash", &hash) 176 | .finish() 177 | } 178 | } 179 | 180 | impl Deref for Header { 181 | type Target = [u8]; 182 | fn deref(&self) -> &[u8] { 183 | unsafe { 184 | slice::from_raw_parts(self as *const Header as *const u8, mem::size_of::
()) 185 | as &[u8] 186 | } 187 | } 188 | } 189 | 190 | impl DerefMut for Header { 191 | fn deref_mut(&mut self) -> &mut [u8] { 192 | unsafe { 193 | slice::from_raw_parts_mut(self as *mut Header as *mut u8, mem::size_of::
()) 194 | as &mut [u8] 195 | } 196 | } 197 | } 198 | 199 | #[test] 200 | fn header_not_valid_test() { 201 | assert_eq!(Header::default().valid(), false); 202 | } 203 | 204 | #[test] 205 | fn header_size_test() { 206 | assert_eq!(mem::size_of::
(), BLOCK_SIZE as usize); 207 | } 208 | 209 | #[test] 210 | fn header_hash_test() { 211 | let mut header = Header::default(); 212 | assert_eq!(header.create_hash(), 0xe81ffcb86026ff96); 213 | header.update_hash(None); 214 | assert_eq!(header.hash.to_ne(), 0xe81ffcb86026ff96); 215 | assert_eq!( 216 | header.encrypted_hash, 217 | [0x96, 0xff, 0x26, 0x60, 0xb8, 0xfc, 0x1f, 0xe8, 0, 0, 0, 0, 0, 0, 0, 0] 218 | ); 219 | } 220 | 221 | #[cfg(feature = "std")] 222 | #[test] 223 | fn header_valid_test() { 224 | assert_eq!(Header::new(0).valid(), true); 225 | } 226 | -------------------------------------------------------------------------------- /src/key.rs: -------------------------------------------------------------------------------- 1 | use aes::{Aes128, BlockDecrypt, BlockEncrypt, NewBlockCipher}; 2 | 3 | // The raw key, keep secret! 4 | #[repr(transparent)] 5 | pub struct Key([u8; 16]); 6 | 7 | impl Key { 8 | /// Generate a random key 9 | #[cfg(feature = "std")] 10 | pub fn new() -> Result { 11 | let mut bytes = [0; 16]; 12 | getrandom::getrandom(&mut bytes)?; 13 | Ok(Self(bytes)) 14 | } 15 | 16 | pub fn into_aes(self) -> Aes128 { 17 | Aes128::new(&aes::Block::from(self.0)) 18 | } 19 | } 20 | 21 | /// The encrypted key, encrypted with AES using the salt and password 22 | #[derive(Clone, Copy, Default)] 23 | #[repr(transparent)] 24 | pub struct EncryptedKey([u8; 16]); 25 | 26 | /// Salt used to prevent rainbow table attacks on the encryption password 27 | #[derive(Clone, Copy, Default)] 28 | #[repr(transparent)] 29 | pub struct Salt([u8; 16]); 30 | 31 | impl Salt { 32 | /// Generate a random salt 33 | #[cfg(feature = "std")] 34 | pub fn new() -> Result { 35 | let mut bytes = [0; 16]; 36 | getrandom::getrandom(&mut bytes)?; 37 | Ok(Self(bytes)) 38 | } 39 | } 40 | 41 | /// The key slot, containing the salt and encrypted key that are used with one password 42 | #[derive(Clone, Copy, Default)] 43 | #[repr(C, packed)] 44 | pub struct KeySlot { 45 | salt: Salt, 46 | encrypted_key: EncryptedKey, 47 | } 48 | 49 | impl KeySlot { 50 | /// Get the password AES key (generated from the password and salt, encrypts the real key) 51 | pub fn password_aes(password: &[u8], salt: &Salt) -> Result { 52 | let mut key = Key([0; 16]); 53 | 54 | let mut params_builder = argon2::ParamsBuilder::new(); 55 | params_builder.output_len(key.0.len())?; 56 | 57 | let argon2 = argon2::Argon2::new( 58 | argon2::Algorithm::Argon2id, 59 | argon2::Version::V0x13, 60 | params_builder.params()?, 61 | ); 62 | 63 | argon2.hash_password_into(password, &salt.0, &mut key.0)?; 64 | 65 | Ok(key.into_aes()) 66 | } 67 | 68 | /// Create a new key slot from a password, salt, and encryption key 69 | pub fn new(password: &[u8], salt: Salt, key: Key) -> Result { 70 | let password_aes = Self::password_aes(password, &salt)?; 71 | 72 | // Encrypt the real AES key 73 | let mut block = aes::Block::from(key.0); 74 | password_aes.encrypt_block(&mut block); 75 | 76 | Ok(Self { 77 | salt, 78 | encrypted_key: EncryptedKey(block.into()), 79 | }) 80 | } 81 | 82 | /// Get the encryption key from this key slot 83 | pub fn key(&self, password: &[u8]) -> Result { 84 | let password_aes = Self::password_aes(password, &self.salt)?; 85 | 86 | // Decrypt the real AES key 87 | let mut block = aes::Block::from(self.encrypted_key.0); 88 | password_aes.decrypt_block(&mut block); 89 | 90 | Ok(Key(block.into())) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![crate_name = "redoxfs"] 2 | #![crate_type = "lib"] 3 | #![cfg_attr(not(feature = "std"), no_std)] 4 | // Used often in generating redox_syscall errors 5 | #![allow(clippy::or_fun_call)] 6 | #![allow(unexpected_cfgs)] 7 | 8 | extern crate alloc; 9 | 10 | use core::sync::atomic::AtomicUsize; 11 | 12 | // The alloc log grows by 1 block about every 21 generations 13 | pub const ALLOC_GC_THRESHOLD: u64 = 1024; 14 | pub const BLOCK_SIZE: u64 = 4096; 15 | // A record is 4KiB << 5 = 128KiB 16 | pub const RECORD_LEVEL: usize = 5; 17 | pub const RECORD_SIZE: u64 = BLOCK_SIZE << RECORD_LEVEL; 18 | pub const SIGNATURE: &[u8; 8] = b"RedoxFS\0"; 19 | pub const VERSION: u64 = 6; 20 | pub const DIR_ENTRY_MAX_LENGTH: usize = 252; 21 | 22 | pub static IS_UMT: AtomicUsize = AtomicUsize::new(0); 23 | 24 | pub use self::allocator::{AllocEntry, AllocList, Allocator, ALLOC_LIST_ENTRIES}; 25 | #[cfg(feature = "std")] 26 | pub use self::archive::{archive, archive_at}; 27 | pub use self::block::{ 28 | BlockAddr, BlockData, BlockLevel, BlockList, BlockPtr, BlockRaw, BlockTrait, 29 | }; 30 | pub use self::dir::{DirEntry, DirList}; 31 | pub use self::disk::*; 32 | pub use self::filesystem::FileSystem; 33 | pub use self::header::{Header, HEADER_RING}; 34 | pub use self::key::{Key, KeySlot, Salt}; 35 | #[cfg(feature = "std")] 36 | pub use self::mount::mount; 37 | pub use self::node::{Node, NodeLevel}; 38 | pub use self::record::RecordRaw; 39 | pub use self::transaction::Transaction; 40 | pub use self::tree::{Tree, TreeData, TreeList, TreePtr}; 41 | #[cfg(feature = "std")] 42 | pub use self::unmount::unmount_path; 43 | 44 | mod allocator; 45 | #[cfg(feature = "std")] 46 | mod archive; 47 | mod block; 48 | mod dir; 49 | mod disk; 50 | mod filesystem; 51 | mod header; 52 | mod key; 53 | #[cfg(all(feature = "std", not(fuzzing)))] 54 | mod mount; 55 | #[cfg(all(feature = "std", fuzzing))] 56 | pub mod mount; 57 | mod node; 58 | mod record; 59 | mod transaction; 60 | mod tree; 61 | #[cfg(feature = "std")] 62 | mod unmount; 63 | 64 | #[cfg(all(feature = "std", test))] 65 | mod tests; 66 | -------------------------------------------------------------------------------- /src/mount/fuse.rs: -------------------------------------------------------------------------------- 1 | extern crate fuser; 2 | 3 | use std::cmp; 4 | use std::ffi::OsStr; 5 | use std::io; 6 | use std::os::unix::ffi::OsStrExt; 7 | use std::path::Path; 8 | use std::time::{SystemTime, UNIX_EPOCH}; 9 | 10 | use self::fuser::MountOption; 11 | use self::fuser::TimeOrNow; 12 | use crate::mount::fuse::TimeOrNow::Now; 13 | use crate::mount::fuse::TimeOrNow::SpecificTime; 14 | 15 | use crate::{filesystem, Disk, Node, Transaction, TreeData, TreePtr, BLOCK_SIZE}; 16 | 17 | use self::fuser::{ 18 | FileAttr, FileType, Filesystem, ReplyAttr, ReplyCreate, ReplyData, ReplyDirectory, ReplyEmpty, 19 | ReplyEntry, ReplyStatfs, ReplyWrite, Request, Session, 20 | }; 21 | use std::time::Duration; 22 | 23 | const TTL: Duration = Duration::new(1, 0); // 1 second 24 | 25 | const NULL_TIME: Duration = Duration::new(0, 0); 26 | 27 | pub fn mount( 28 | mut filesystem: filesystem::FileSystem, 29 | mountpoint: P, 30 | callback: F, 31 | ) -> io::Result 32 | where 33 | D: Disk, 34 | P: AsRef, 35 | F: FnOnce(&Path) -> T, 36 | { 37 | let mountpoint = mountpoint.as_ref(); 38 | 39 | // One of the uses of this redoxfs fuse wrapper is to populate a filesystem 40 | // while building the Redox OS kernel. This means that we need to write on 41 | // a filesystem that belongs to `root`, which in turn means that we need to 42 | // be `root`, thus that we need to allow `root` to have access. 43 | let defer_permissions = [MountOption::CUSTOM("defer_permissions".to_owned())]; 44 | 45 | let res = { 46 | let mut session = Session::new( 47 | Fuse { 48 | fs: &mut filesystem, 49 | }, 50 | mountpoint, 51 | if cfg!(target_os = "macos") { 52 | &defer_permissions 53 | } else { 54 | &[] 55 | }, 56 | )?; 57 | 58 | let res = callback(mountpoint); 59 | 60 | session.run()?; 61 | 62 | res 63 | }; 64 | 65 | // Squash allocations and sync on unmount 66 | let _ = Transaction::new(&mut filesystem).commit(true); 67 | 68 | Ok(res) 69 | } 70 | 71 | pub struct Fuse<'f, D: Disk> { 72 | pub fs: &'f mut filesystem::FileSystem, 73 | } 74 | 75 | fn node_attr(node: &TreeData) -> FileAttr { 76 | FileAttr { 77 | ino: node.id() as u64, 78 | size: node.data().size(), 79 | // Blocks is in 512 byte blocks, not in our block size 80 | blocks: (node.data().size() + BLOCK_SIZE - 1) / BLOCK_SIZE * (BLOCK_SIZE / 512), 81 | blksize: 512, 82 | atime: SystemTime::UNIX_EPOCH + Duration::new(node.data().atime().0, node.data().atime().1), 83 | mtime: SystemTime::UNIX_EPOCH + Duration::new(node.data().mtime().0, node.data().mtime().1), 84 | ctime: SystemTime::UNIX_EPOCH + Duration::new(node.data().ctime().0, node.data().ctime().1), 85 | crtime: UNIX_EPOCH + NULL_TIME, 86 | kind: if node.data().is_dir() { 87 | FileType::Directory 88 | } else if node.data().is_symlink() { 89 | FileType::Symlink 90 | } else { 91 | FileType::RegularFile 92 | }, 93 | perm: node.data().mode() & Node::MODE_PERM, 94 | nlink: node.data().links(), 95 | uid: node.data().uid(), 96 | gid: node.data().gid(), 97 | rdev: 0, 98 | flags: 0, 99 | } 100 | } 101 | 102 | impl<'f, D: Disk> Filesystem for Fuse<'f, D> { 103 | fn lookup(&mut self, _req: &Request, parent_id: u64, name: &OsStr, reply: ReplyEntry) { 104 | let parent_ptr = TreePtr::new(parent_id as u32); 105 | match self 106 | .fs 107 | .tx(|tx| tx.find_node(parent_ptr, name.to_str().unwrap())) 108 | { 109 | Ok(node) => { 110 | reply.entry(&TTL, &node_attr(&node), 0); 111 | } 112 | Err(err) => { 113 | reply.error(err.errno); 114 | } 115 | } 116 | } 117 | 118 | fn getattr(&mut self, _req: &Request, node_id: u64, reply: ReplyAttr) { 119 | let node_ptr = TreePtr::::new(node_id as u32); 120 | match self.fs.tx(|tx| tx.read_tree(node_ptr)) { 121 | Ok(node) => { 122 | reply.attr(&TTL, &node_attr(&node)); 123 | } 124 | Err(err) => { 125 | reply.error(err.errno); 126 | } 127 | } 128 | } 129 | 130 | fn setattr( 131 | &mut self, 132 | _req: &Request, 133 | node_id: u64, 134 | mode: Option, 135 | uid: Option, 136 | gid: Option, 137 | size: Option, 138 | atime: Option, 139 | mtime: Option, 140 | _ctime: Option, 141 | _fh: Option, 142 | _crtime: Option, 143 | _chgtime: Option, 144 | _bkuptime: Option, 145 | _flags: Option, 146 | reply: ReplyAttr, 147 | ) { 148 | let node_ptr = TreePtr::::new(node_id as u32); 149 | 150 | let mut node = match self.fs.tx(|tx| tx.read_tree(node_ptr)) { 151 | Ok(ok) => ok, 152 | Err(err) => { 153 | reply.error(err.errno); 154 | return; 155 | } 156 | }; 157 | let mut node_changed = false; 158 | 159 | if let Some(mode) = mode { 160 | if node.data().mode() & Node::MODE_PERM != mode as u16 & Node::MODE_PERM { 161 | let new_mode = 162 | (node.data().mode() & Node::MODE_TYPE) | (mode as u16 & Node::MODE_PERM); 163 | node.data_mut().set_mode(new_mode); 164 | node_changed = true; 165 | } 166 | } 167 | 168 | if let Some(uid) = uid { 169 | if node.data().uid() != uid { 170 | node.data_mut().set_uid(uid); 171 | node_changed = true; 172 | } 173 | } 174 | 175 | if let Some(gid) = gid { 176 | if node.data().gid() != gid { 177 | node.data_mut().set_gid(gid); 178 | node_changed = true; 179 | } 180 | } 181 | 182 | if let Some(atime) = atime { 183 | let atime_c = match atime { 184 | SpecificTime(st) => st.duration_since(UNIX_EPOCH).unwrap(), 185 | Now => SystemTime::now().duration_since(UNIX_EPOCH).unwrap(), 186 | }; 187 | node.data_mut() 188 | .set_atime(atime_c.as_secs(), atime_c.subsec_nanos()); 189 | node_changed = true; 190 | } 191 | 192 | if let Some(mtime) = mtime { 193 | let mtime_c = match mtime { 194 | SpecificTime(st) => st.duration_since(UNIX_EPOCH).unwrap(), 195 | Now => SystemTime::now().duration_since(UNIX_EPOCH).unwrap(), 196 | }; 197 | node.data_mut() 198 | .set_mtime(mtime_c.as_secs(), mtime_c.subsec_nanos()); 199 | node_changed = true; 200 | } 201 | 202 | if let Some(size) = size { 203 | match self.fs.tx(|tx| tx.truncate_node_inner(&mut node, size)) { 204 | Ok(ok) => { 205 | if ok { 206 | node_changed = true; 207 | } 208 | } 209 | Err(err) => { 210 | reply.error(err.errno); 211 | return; 212 | } 213 | } 214 | } 215 | 216 | let attr = node_attr(&node); 217 | 218 | if node_changed { 219 | if let Err(err) = self.fs.tx(|tx| tx.sync_tree(node)) { 220 | reply.error(err.errno); 221 | return; 222 | } 223 | } 224 | 225 | reply.attr(&TTL, &attr); 226 | } 227 | 228 | fn read( 229 | &mut self, 230 | _req: &Request, 231 | node_id: u64, 232 | _fh: u64, 233 | offset: i64, 234 | size: u32, 235 | _flags: i32, 236 | _lock_owner: Option, 237 | reply: ReplyData, 238 | ) { 239 | let node_ptr = TreePtr::::new(node_id as u32); 240 | 241 | let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); 242 | let mut data = vec![0; size as usize]; 243 | match self.fs.tx(|tx| { 244 | tx.read_node( 245 | node_ptr, 246 | cmp::max(0, offset) as u64, 247 | &mut data, 248 | atime.as_secs(), 249 | atime.subsec_nanos(), 250 | ) 251 | }) { 252 | Ok(count) => { 253 | reply.data(&data[..count]); 254 | } 255 | Err(err) => { 256 | reply.error(err.errno); 257 | } 258 | } 259 | } 260 | 261 | fn write( 262 | &mut self, 263 | _req: &Request, 264 | node_id: u64, 265 | _fh: u64, 266 | offset: i64, 267 | data: &[u8], 268 | _write_flags: u32, 269 | _flags: i32, 270 | _lock_owner: Option, 271 | reply: ReplyWrite, 272 | ) { 273 | let node_ptr = TreePtr::::new(node_id as u32); 274 | 275 | let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); 276 | match self.fs.tx(|tx| { 277 | tx.write_node( 278 | node_ptr, 279 | cmp::max(0, offset) as u64, 280 | data, 281 | mtime.as_secs(), 282 | mtime.subsec_nanos(), 283 | ) 284 | }) { 285 | Ok(count) => { 286 | reply.written(count as u32); 287 | } 288 | Err(err) => { 289 | reply.error(err.errno); 290 | } 291 | } 292 | } 293 | 294 | fn flush(&mut self, _req: &Request, _ino: u64, _fh: u64, _lock_owner: u64, reply: ReplyEmpty) { 295 | reply.ok(); 296 | } 297 | 298 | fn fsync(&mut self, _req: &Request, _ino: u64, _fh: u64, _datasync: bool, reply: ReplyEmpty) { 299 | reply.ok(); 300 | } 301 | 302 | fn readdir( 303 | &mut self, 304 | _req: &Request, 305 | parent_id: u64, 306 | _fh: u64, 307 | offset: i64, 308 | mut reply: ReplyDirectory, 309 | ) { 310 | let parent_ptr = TreePtr::new(parent_id as u32); 311 | let mut children = Vec::new(); 312 | match self.fs.tx(|tx| tx.child_nodes(parent_ptr, &mut children)) { 313 | Ok(()) => { 314 | let mut i; 315 | let skip; 316 | if offset == 0 { 317 | skip = 0; 318 | i = 0; 319 | let _full = reply.add(parent_id, i, FileType::Directory, "."); 320 | 321 | i += 1; 322 | let _full = reply.add( 323 | //TODO: get parent? 324 | parent_id, 325 | i, 326 | FileType::Directory, 327 | "..", 328 | ); 329 | i += 1; 330 | } else { 331 | i = offset + 1; 332 | skip = offset as usize - 1; 333 | } 334 | 335 | for child in children.iter().skip(skip) { 336 | //TODO: make it possible to get file type from directory entry 337 | let node = match self.fs.tx(|tx| tx.read_tree(child.node_ptr())) { 338 | Ok(ok) => ok, 339 | Err(err) => { 340 | reply.error(err.errno); 341 | return; 342 | } 343 | }; 344 | 345 | let full = reply.add( 346 | child.node_ptr().id() as u64, 347 | i, 348 | if node.data().is_dir() { 349 | FileType::Directory 350 | } else { 351 | FileType::RegularFile 352 | }, 353 | child.name().unwrap(), 354 | ); 355 | 356 | if full { 357 | break; 358 | } 359 | 360 | i += 1; 361 | } 362 | reply.ok(); 363 | } 364 | Err(err) => { 365 | reply.error(err.errno); 366 | } 367 | } 368 | } 369 | 370 | fn create( 371 | &mut self, 372 | _req: &Request, 373 | parent_id: u64, 374 | name: &OsStr, 375 | mode: u32, 376 | _umask: u32, 377 | _flags: i32, 378 | reply: ReplyCreate, 379 | ) { 380 | let parent_ptr = TreePtr::::new(parent_id as u32); 381 | let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); 382 | match self.fs.tx(|tx| { 383 | tx.create_node( 384 | parent_ptr, 385 | name.to_str().unwrap(), 386 | Node::MODE_FILE | (mode as u16 & Node::MODE_PERM), 387 | ctime.as_secs(), 388 | ctime.subsec_nanos(), 389 | ) 390 | }) { 391 | Ok(node) => { 392 | // println!("Create {:?}:{:o}:{:o}", node.1.name(), node.1.mode, mode); 393 | reply.created(&TTL, &node_attr(&node), 0, 0, 0); 394 | } 395 | Err(error) => { 396 | reply.error(error.errno); 397 | } 398 | } 399 | } 400 | 401 | fn mkdir( 402 | &mut self, 403 | _req: &Request, 404 | parent_id: u64, 405 | name: &OsStr, 406 | mode: u32, 407 | _umask: u32, 408 | reply: ReplyEntry, 409 | ) { 410 | let parent_ptr = TreePtr::::new(parent_id as u32); 411 | let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); 412 | match self.fs.tx(|tx| { 413 | tx.create_node( 414 | parent_ptr, 415 | name.to_str().unwrap(), 416 | Node::MODE_DIR | (mode as u16 & Node::MODE_PERM), 417 | ctime.as_secs(), 418 | ctime.subsec_nanos(), 419 | ) 420 | }) { 421 | Ok(node) => { 422 | // println!("Mkdir {:?}:{:o}:{:o}", node.1.name(), node.1.mode, mode); 423 | reply.entry(&TTL, &node_attr(&node), 0); 424 | } 425 | Err(error) => { 426 | reply.error(error.errno); 427 | } 428 | } 429 | } 430 | 431 | fn rmdir(&mut self, _req: &Request, parent_id: u64, name: &OsStr, reply: ReplyEmpty) { 432 | let parent_ptr = TreePtr::::new(parent_id as u32); 433 | match self 434 | .fs 435 | .tx(|tx| tx.remove_node(parent_ptr, name.to_str().unwrap(), Node::MODE_DIR)) 436 | { 437 | Ok(()) => { 438 | reply.ok(); 439 | } 440 | Err(err) => { 441 | reply.error(err.errno); 442 | } 443 | } 444 | } 445 | 446 | fn unlink(&mut self, _req: &Request, parent_id: u64, name: &OsStr, reply: ReplyEmpty) { 447 | let parent_ptr = TreePtr::::new(parent_id as u32); 448 | match self 449 | .fs 450 | .tx(|tx| tx.remove_node(parent_ptr, name.to_str().unwrap(), Node::MODE_FILE)) 451 | { 452 | Ok(()) => { 453 | reply.ok(); 454 | } 455 | Err(err) => { 456 | reply.error(err.errno); 457 | } 458 | } 459 | } 460 | 461 | fn statfs(&mut self, _req: &Request, _ino: u64, reply: ReplyStatfs) { 462 | let bsize = BLOCK_SIZE; 463 | let blocks = self.fs.header.size() / bsize; 464 | let bfree = self.fs.allocator().free(); 465 | reply.statfs(blocks, bfree, bfree, 0, 0, bsize as u32, 256, 0); 466 | } 467 | 468 | fn symlink( 469 | &mut self, 470 | _req: &Request, 471 | parent_id: u64, 472 | name: &OsStr, 473 | link: &Path, 474 | reply: ReplyEntry, 475 | ) { 476 | let parent_ptr = TreePtr::::new(parent_id as u32); 477 | let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); 478 | match self.fs.tx(|tx| { 479 | let node = tx.create_node( 480 | parent_ptr, 481 | name.to_str().unwrap(), 482 | Node::MODE_SYMLINK | 0o777, 483 | ctime.as_secs(), 484 | ctime.subsec_nanos(), 485 | )?; 486 | 487 | let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); 488 | tx.write_node( 489 | node.ptr(), 490 | 0, 491 | link.as_os_str().as_bytes(), 492 | mtime.as_secs(), 493 | mtime.subsec_nanos(), 494 | )?; 495 | 496 | Ok(node) 497 | }) { 498 | Ok(node) => { 499 | reply.entry(&TTL, &node_attr(&node), 0); 500 | } 501 | Err(error) => { 502 | reply.error(error.errno); 503 | } 504 | } 505 | } 506 | 507 | fn readlink(&mut self, _req: &Request, node_id: u64, reply: ReplyData) { 508 | let node_ptr = TreePtr::::new(node_id as u32); 509 | let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); 510 | let mut data = vec![0; 4096]; 511 | match self.fs.tx(|tx| { 512 | tx.read_node( 513 | node_ptr, 514 | 0, 515 | &mut data, 516 | atime.as_secs(), 517 | atime.subsec_nanos(), 518 | ) 519 | }) { 520 | Ok(count) => { 521 | reply.data(&data[..count]); 522 | } 523 | Err(err) => { 524 | reply.error(err.errno); 525 | } 526 | } 527 | } 528 | 529 | fn rename( 530 | &mut self, 531 | _req: &Request, 532 | orig_parent: u64, 533 | orig_name: &OsStr, 534 | new_parent: u64, 535 | new_name: &OsStr, 536 | _flags: u32, 537 | reply: ReplyEmpty, 538 | ) { 539 | let orig_parent_ptr = TreePtr::::new(orig_parent as u32); 540 | let orig_name = orig_name.to_str().expect("name is not utf-8"); 541 | let new_parent_ptr = TreePtr::::new(new_parent as u32); 542 | let new_name = new_name.to_str().expect("name is not utf-8"); 543 | 544 | // TODO: improve performance 545 | match self 546 | .fs 547 | .tx(|tx| tx.rename_node(orig_parent_ptr, orig_name, new_parent_ptr, new_name)) 548 | { 549 | Ok(()) => reply.ok(), 550 | Err(err) => reply.error(err.errno), 551 | } 552 | } 553 | } 554 | -------------------------------------------------------------------------------- /src/mount/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(not(target_os = "redox"), not(fuzzing)))] 2 | mod fuse; 3 | #[cfg(all(not(target_os = "redox"), fuzzing))] 4 | pub mod fuse; 5 | 6 | #[cfg(not(target_os = "redox"))] 7 | pub use self::fuse::mount; 8 | 9 | #[cfg(target_os = "redox")] 10 | mod redox; 11 | 12 | #[cfg(target_os = "redox")] 13 | pub use self::redox::mount; 14 | -------------------------------------------------------------------------------- /src/mount/redox/mod.rs: -------------------------------------------------------------------------------- 1 | use redox_scheme::{RequestKind, SignalBehavior, Socket}; 2 | use std::io; 3 | use std::path::Path; 4 | use std::sync::atomic::Ordering; 5 | 6 | use crate::{Disk, FileSystem, Transaction, IS_UMT}; 7 | 8 | use self::scheme::FileScheme; 9 | 10 | pub mod resource; 11 | pub mod scheme; 12 | 13 | pub fn mount(filesystem: FileSystem, mountpoint: P, mut callback: F) -> io::Result 14 | where 15 | D: Disk, 16 | P: AsRef, 17 | F: FnOnce(&Path) -> T, 18 | { 19 | let mountpoint = mountpoint.as_ref(); 20 | let socket = Socket::create(&format!("{}", mountpoint.display()))?; 21 | 22 | let mounted_path = format!("/scheme/{}", mountpoint.display()); 23 | let res = callback(Path::new(&mounted_path)); 24 | 25 | let mut scheme = FileScheme::new(format!("{}", mountpoint.display()), filesystem); 26 | while IS_UMT.load(Ordering::SeqCst) == 0 { 27 | let req = match socket.next_request(SignalBehavior::Restart)? { 28 | None => break, 29 | Some(req) => { 30 | if let RequestKind::Call(r) = req.kind() { 31 | r 32 | } else { 33 | // TODO: Redoxfs does not yet support asynchronous file IO. It might still make 34 | // sense to implement cancellation for huge buffers, e.g. dd bs=1G 35 | continue; 36 | } 37 | } 38 | }; 39 | let response = req.handle_sync(&mut scheme); 40 | 41 | if !socket.write_response(response, SignalBehavior::Restart)? { 42 | break; 43 | } 44 | } 45 | 46 | // Squash allocations and sync on unmount 47 | let _ = Transaction::new(&mut scheme.fs).commit(true); 48 | 49 | Ok(res) 50 | } 51 | -------------------------------------------------------------------------------- /src/mount/redox/resource.rs: -------------------------------------------------------------------------------- 1 | use std::slice; 2 | use std::time::{SystemTime, UNIX_EPOCH}; 3 | 4 | use alloc::collections::BTreeMap; 5 | use libredox::call::MmapArgs; 6 | use range_tree::RangeTree; 7 | 8 | use syscall::data::{Stat, TimeSpec}; 9 | use syscall::dirent::{DirEntry, DirentBuf, DirentKind}; 10 | use syscall::error::{Error, Result, EBADF, EINVAL, EISDIR, ENOTDIR, EPERM}; 11 | use syscall::flag::{ 12 | MapFlags, F_GETFL, F_SETFL, MODE_PERM, O_ACCMODE, O_APPEND, O_RDONLY, O_RDWR, O_WRONLY, 13 | PROT_READ, PROT_WRITE, 14 | }; 15 | use syscall::{EBADFD, PAGE_SIZE}; 16 | 17 | use crate::{Disk, Node, Transaction, TreePtr}; 18 | 19 | pub type Fmaps = BTreeMap; 20 | 21 | pub trait Resource { 22 | fn parent_ptr_opt(&self) -> Option>; 23 | 24 | fn node_ptr(&self) -> TreePtr; 25 | 26 | fn uid(&self) -> u32; 27 | 28 | fn set_path(&mut self, path: &str); 29 | 30 | fn read(&mut self, buf: &mut [u8], offset: u64, tx: &mut Transaction) -> Result; 31 | 32 | fn write(&mut self, buf: &[u8], offset: u64, tx: &mut Transaction) -> Result; 33 | 34 | fn fsize(&mut self, tx: &mut Transaction) -> Result; 35 | 36 | fn fmap( 37 | &mut self, 38 | fmaps: &mut Fmaps, 39 | flags: MapFlags, 40 | size: usize, 41 | offset: u64, 42 | tx: &mut Transaction, 43 | ) -> Result; 44 | 45 | fn funmap( 46 | &mut self, 47 | fmaps: &mut Fmaps, 48 | offset: u64, 49 | size: usize, 50 | tx: &mut Transaction, 51 | ) -> Result<()>; 52 | 53 | fn fchmod(&mut self, mode: u16, tx: &mut Transaction) -> Result<()> { 54 | let mut node = tx.read_tree(self.node_ptr())?; 55 | 56 | if node.data().uid() == self.uid() || self.uid() == 0 { 57 | let old_mode = node.data().mode(); 58 | let new_mode = (old_mode & !MODE_PERM) | (mode & MODE_PERM); 59 | if old_mode != new_mode { 60 | node.data_mut().set_mode(new_mode); 61 | tx.sync_tree(node)?; 62 | } 63 | 64 | Ok(()) 65 | } else { 66 | Err(Error::new(EPERM)) 67 | } 68 | } 69 | 70 | fn fchown(&mut self, uid: u32, gid: u32, tx: &mut Transaction) -> Result<()> { 71 | let mut node = tx.read_tree(self.node_ptr())?; 72 | 73 | let old_uid = node.data().uid(); 74 | if old_uid == self.uid() || self.uid() == 0 { 75 | let mut node_changed = false; 76 | 77 | if uid as i32 != -1 { 78 | if uid != old_uid { 79 | node.data_mut().set_uid(uid); 80 | node_changed = true; 81 | } 82 | } 83 | 84 | if gid as i32 != -1 { 85 | let old_gid = node.data().gid(); 86 | if gid != old_gid { 87 | node.data_mut().set_gid(gid); 88 | node_changed = true; 89 | } 90 | } 91 | 92 | if node_changed { 93 | tx.sync_tree(node)?; 94 | } 95 | 96 | Ok(()) 97 | } else { 98 | Err(Error::new(EPERM)) 99 | } 100 | } 101 | 102 | fn fcntl(&mut self, cmd: usize, arg: usize) -> Result; 103 | 104 | fn path(&self) -> &str; 105 | 106 | fn stat(&self, stat: &mut Stat, tx: &mut Transaction) -> Result<()> { 107 | let node = tx.read_tree(self.node_ptr())?; 108 | 109 | let ctime = node.data().ctime(); 110 | let mtime = node.data().mtime(); 111 | let atime = node.data().atime(); 112 | *stat = Stat { 113 | st_dev: 0, // TODO 114 | st_ino: node.id() as u64, 115 | st_mode: node.data().mode(), 116 | st_nlink: node.data().links(), 117 | st_uid: node.data().uid(), 118 | st_gid: node.data().gid(), 119 | st_size: node.data().size(), 120 | st_mtime: mtime.0, 121 | st_mtime_nsec: mtime.1, 122 | st_atime: atime.0, 123 | st_atime_nsec: atime.1, 124 | st_ctime: ctime.0, 125 | st_ctime_nsec: ctime.1, 126 | ..Default::default() 127 | }; 128 | 129 | Ok(()) 130 | } 131 | 132 | fn sync(&mut self, fmaps: &mut Fmaps, tx: &mut Transaction) -> Result<()>; 133 | 134 | fn truncate(&mut self, len: u64, tx: &mut Transaction) -> Result<()>; 135 | 136 | fn utimens(&mut self, times: &[TimeSpec], tx: &mut Transaction) -> Result<()>; 137 | 138 | fn getdents<'buf>( 139 | &mut self, 140 | buf: DirentBuf<&'buf mut [u8]>, 141 | opaque_offset: u64, 142 | tx: &mut Transaction, 143 | ) -> Result>; 144 | } 145 | 146 | pub struct Entry { 147 | pub node_ptr: TreePtr, 148 | pub name: String, 149 | } 150 | 151 | pub struct DirResource { 152 | path: String, 153 | parent_ptr_opt: Option>, 154 | node_ptr: TreePtr, 155 | data: Option>, 156 | uid: u32, 157 | } 158 | 159 | impl DirResource { 160 | pub fn new( 161 | path: String, 162 | parent_ptr_opt: Option>, 163 | node_ptr: TreePtr, 164 | data: Option>, 165 | uid: u32, 166 | ) -> DirResource { 167 | DirResource { 168 | path, 169 | parent_ptr_opt, 170 | node_ptr, 171 | data, 172 | uid, 173 | } 174 | } 175 | } 176 | 177 | impl Resource for DirResource { 178 | fn parent_ptr_opt(&self) -> Option> { 179 | self.parent_ptr_opt 180 | } 181 | 182 | fn node_ptr(&self) -> TreePtr { 183 | self.node_ptr 184 | } 185 | 186 | fn uid(&self) -> u32 { 187 | self.uid 188 | } 189 | 190 | fn set_path(&mut self, path: &str) { 191 | self.path = path.to_string(); 192 | } 193 | 194 | fn read(&mut self, _buf: &mut [u8], _offset: u64, _tx: &mut Transaction) -> Result { 195 | Err(Error::new(EISDIR)) 196 | } 197 | 198 | fn write(&mut self, _buf: &[u8], _offset: u64, _tx: &mut Transaction) -> Result { 199 | Err(Error::new(EBADF)) 200 | } 201 | 202 | fn fsize(&mut self, _tx: &mut Transaction) -> Result { 203 | Ok(self.data.as_ref().ok_or(Error::new(EBADF))?.len() as u64) 204 | } 205 | 206 | fn fmap( 207 | &mut self, 208 | _fmaps: &mut Fmaps, 209 | _flags: MapFlags, 210 | _size: usize, 211 | _offset: u64, 212 | _tx: &mut Transaction, 213 | ) -> Result { 214 | Err(Error::new(EBADF)) 215 | } 216 | fn funmap( 217 | &mut self, 218 | _fmaps: &mut Fmaps, 219 | _offset: u64, 220 | _size: usize, 221 | _tx: &mut Transaction, 222 | ) -> Result<()> { 223 | Err(Error::new(EBADF)) 224 | } 225 | 226 | fn fcntl(&mut self, _cmd: usize, _arg: usize) -> Result { 227 | Err(Error::new(EBADF)) 228 | } 229 | 230 | fn path(&self) -> &str { 231 | &self.path 232 | } 233 | 234 | fn sync(&mut self, _fmaps: &mut Fmaps, _tx: &mut Transaction) -> Result<()> { 235 | Err(Error::new(EBADF)) 236 | } 237 | 238 | fn truncate(&mut self, _len: u64, _tx: &mut Transaction) -> Result<()> { 239 | Err(Error::new(EBADF)) 240 | } 241 | 242 | fn utimens(&mut self, _times: &[TimeSpec], _tx: &mut Transaction) -> Result<()> { 243 | Err(Error::new(EBADF)) 244 | } 245 | 246 | fn getdents<'buf>( 247 | &mut self, 248 | mut buf: DirentBuf<&'buf mut [u8]>, 249 | opaque_offset: u64, 250 | tx: &mut Transaction, 251 | ) -> Result> { 252 | match &self.data { 253 | Some(data) => { 254 | for (idx, entry) in data.iter().enumerate().skip(opaque_offset as usize) { 255 | let child = tx.read_tree(entry.node_ptr)?; 256 | buf.entry(DirEntry { 257 | inode: child.id() as u64, 258 | next_opaque_id: idx as u64 + 1, 259 | name: &entry.name, 260 | kind: match child.data().mode() & Node::MODE_TYPE { 261 | Node::MODE_DIR => DirentKind::Directory, 262 | Node::MODE_FILE => DirentKind::Regular, 263 | Node::MODE_SYMLINK => DirentKind::Symlink, 264 | //TODO: more types? 265 | _ => DirentKind::Unspecified, 266 | }, 267 | })?; 268 | } 269 | Ok(buf) 270 | } 271 | None => Err(Error::new(EBADF)), 272 | } 273 | } 274 | } 275 | 276 | #[derive(Debug)] 277 | pub struct Fmap { 278 | rc: usize, 279 | flags: MapFlags, 280 | last_page_tail: u16, 281 | } 282 | 283 | impl Fmap { 284 | pub unsafe fn new( 285 | node_ptr: TreePtr, 286 | flags: MapFlags, 287 | unaligned_size: usize, 288 | offset: u64, 289 | base: *mut u8, 290 | tx: &mut Transaction, 291 | ) -> Result { 292 | // Memory provided to fmap must be page aligned and sized 293 | let aligned_size = unaligned_size.next_multiple_of(syscall::PAGE_SIZE); 294 | 295 | let address = base.add(offset as usize); 296 | //println!("ADDR {:p} {:p}", base, address); 297 | 298 | // Read buffer from disk 299 | let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); 300 | 301 | let buf = slice::from_raw_parts_mut(address, unaligned_size); 302 | 303 | let count = match tx.read_node(node_ptr, offset, buf, atime.as_secs(), atime.subsec_nanos()) 304 | { 305 | Ok(ok) => ok, 306 | Err(err) => { 307 | let _ = libredox::call::munmap(address.cast(), aligned_size); 308 | return Err(err); 309 | } 310 | }; 311 | 312 | // Make sure remaining data is zeroed 313 | buf[count..].fill(0_u8); 314 | 315 | Ok(Self { 316 | rc: 1, 317 | flags, 318 | last_page_tail: (unaligned_size % PAGE_SIZE) as u16, 319 | }) 320 | } 321 | 322 | pub unsafe fn sync( 323 | &mut self, 324 | node_ptr: TreePtr, 325 | base: *mut u8, 326 | offset: u64, 327 | size: usize, 328 | tx: &mut Transaction, 329 | ) -> Result<()> { 330 | if self.flags & PROT_WRITE == PROT_WRITE { 331 | let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); 332 | tx.write_node( 333 | node_ptr, 334 | offset, 335 | unsafe { core::slice::from_raw_parts(base.add(offset as usize), size) }, 336 | mtime.as_secs(), 337 | mtime.subsec_nanos(), 338 | )?; 339 | } 340 | Ok(()) 341 | } 342 | } 343 | 344 | pub struct FileResource { 345 | path: String, 346 | parent_ptr_opt: Option>, 347 | node_ptr: TreePtr, 348 | flags: usize, 349 | uid: u32, 350 | } 351 | #[derive(Debug)] 352 | pub struct FileMmapInfo { 353 | base: *mut u8, 354 | size: usize, 355 | ranges: RangeTree, 356 | pub open_fds: usize, 357 | } 358 | impl Default for FileMmapInfo { 359 | fn default() -> Self { 360 | Self { 361 | base: core::ptr::null_mut(), 362 | size: 0, 363 | ranges: RangeTree::new(), 364 | open_fds: 0, 365 | } 366 | } 367 | } 368 | 369 | impl FileResource { 370 | pub fn new( 371 | path: String, 372 | parent_ptr_opt: Option>, 373 | node_ptr: TreePtr, 374 | flags: usize, 375 | uid: u32, 376 | ) -> FileResource { 377 | FileResource { 378 | path, 379 | parent_ptr_opt, 380 | node_ptr, 381 | flags, 382 | uid, 383 | } 384 | } 385 | } 386 | 387 | impl Resource for FileResource { 388 | fn parent_ptr_opt(&self) -> Option> { 389 | self.parent_ptr_opt 390 | } 391 | 392 | fn node_ptr(&self) -> TreePtr { 393 | self.node_ptr 394 | } 395 | 396 | fn uid(&self) -> u32 { 397 | self.uid 398 | } 399 | 400 | fn set_path(&mut self, path: &str) { 401 | self.path = path.to_string(); 402 | } 403 | 404 | fn read(&mut self, buf: &mut [u8], offset: u64, tx: &mut Transaction) -> Result { 405 | if self.flags & O_ACCMODE != O_RDWR && self.flags & O_ACCMODE != O_RDONLY { 406 | return Err(Error::new(EBADF)); 407 | } 408 | let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); 409 | tx.read_node( 410 | self.node_ptr, 411 | offset, 412 | buf, 413 | atime.as_secs(), 414 | atime.subsec_nanos(), 415 | ) 416 | } 417 | 418 | fn write(&mut self, buf: &[u8], offset: u64, tx: &mut Transaction) -> Result { 419 | if self.flags & O_ACCMODE != O_RDWR && self.flags & O_ACCMODE != O_WRONLY { 420 | return Err(Error::new(EBADF)); 421 | } 422 | let effective_offset = if self.flags & O_APPEND == O_APPEND { 423 | let node = tx.read_tree(self.node_ptr)?; 424 | node.data().size() 425 | } else { 426 | offset 427 | }; 428 | let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); 429 | tx.write_node( 430 | self.node_ptr, 431 | effective_offset, 432 | buf, 433 | mtime.as_secs(), 434 | mtime.subsec_nanos(), 435 | ) 436 | } 437 | 438 | fn fsize(&mut self, tx: &mut Transaction) -> Result { 439 | let node = tx.read_tree(self.node_ptr)?; 440 | Ok(node.data().size()) 441 | } 442 | 443 | fn fmap( 444 | &mut self, 445 | fmaps: &mut Fmaps, 446 | flags: MapFlags, 447 | unaligned_size: usize, 448 | offset: u64, 449 | tx: &mut Transaction, 450 | ) -> Result { 451 | //dbg!(&self.fmaps); 452 | let accmode = self.flags & O_ACCMODE; 453 | if flags.contains(PROT_READ) && !(accmode == O_RDWR || accmode == O_RDONLY) { 454 | return Err(Error::new(EBADF)); 455 | } 456 | if flags.contains(PROT_WRITE) && !(accmode == O_RDWR || accmode == O_WRONLY) { 457 | return Err(Error::new(EBADF)); 458 | } 459 | 460 | let aligned_size = unaligned_size.next_multiple_of(PAGE_SIZE); 461 | 462 | // TODO: PROT_EXEC? It is however unenforcable without restricting anonymous mmap, since a 463 | // program can always map anonymous RW-, read from a file, then remap as R-E. But it might 464 | // be usable as a hint, prohibiting direct executable mmaps at least. 465 | 466 | // TODO: Pass entry directory to Resource trait functions, since the node_ptr can be 467 | // obtained by the caller. 468 | let fmap_info = fmaps 469 | .get_mut(&self.node_ptr.id()) 470 | .ok_or(Error::new(EBADFD))?; 471 | 472 | let new_size = (offset as usize + aligned_size).next_multiple_of(PAGE_SIZE); 473 | if new_size > fmap_info.size { 474 | fmap_info.base = if fmap_info.base.is_null() { 475 | unsafe { 476 | libredox::call::mmap(MmapArgs { 477 | length: new_size, 478 | // PRIVATE/SHARED doesn't matter once the pages are passed in the fmap 479 | // handler. 480 | prot: libredox::flag::PROT_READ | libredox::flag::PROT_WRITE, 481 | flags: libredox::flag::MAP_PRIVATE, 482 | 483 | offset: 0, 484 | fd: !0, 485 | addr: core::ptr::null_mut(), 486 | })? as *mut u8 487 | } 488 | } else { 489 | unsafe { 490 | syscall::syscall5( 491 | syscall::SYS_MREMAP, 492 | fmap_info.base as usize, 493 | fmap_info.size, 494 | 0, 495 | new_size, 496 | syscall::MremapFlags::empty().bits() | (PROT_READ | PROT_WRITE).bits(), 497 | )? as *mut u8 498 | } 499 | }; 500 | fmap_info.size = new_size; 501 | } 502 | 503 | let affected_fmaps = fmap_info 504 | .ranges 505 | .remove_and_unused(offset..offset + aligned_size as u64); 506 | 507 | for (range, v_opt) in affected_fmaps { 508 | //dbg!(&range); 509 | if let Some(mut fmap) = v_opt { 510 | fmap.rc += 1; 511 | fmap.flags |= flags; 512 | 513 | fmap_info 514 | .ranges 515 | .insert(range.start, range.end - range.start, fmap); 516 | } else { 517 | let map = unsafe { 518 | Fmap::new( 519 | self.node_ptr, 520 | flags, 521 | unaligned_size, 522 | offset, 523 | fmap_info.base, 524 | tx, 525 | )? 526 | }; 527 | fmap_info.ranges.insert(offset, aligned_size as u64, map); 528 | } 529 | } 530 | //dbg!(&self.fmaps); 531 | 532 | Ok(fmap_info.base as usize + offset as usize) 533 | } 534 | 535 | fn funmap( 536 | &mut self, 537 | fmaps: &mut Fmaps, 538 | offset: u64, 539 | size: usize, 540 | tx: &mut Transaction, 541 | ) -> Result<()> { 542 | let fmap_info = fmaps 543 | .get_mut(&self.node_ptr.id()) 544 | .ok_or(Error::new(EBADFD))?; 545 | 546 | //dbg!(&self.fmaps); 547 | //dbg!(self.fmaps.conflicts(offset..offset + size as u64).collect::>()); 548 | #[allow(unused_mut)] 549 | let mut affected_fmaps = fmap_info.ranges.remove(offset..offset + size as u64); 550 | 551 | for (range, mut fmap) in affected_fmaps { 552 | fmap.rc = fmap.rc.checked_sub(1).unwrap(); 553 | 554 | //log::info!("SYNCING {}..{}", range.start, range.end); 555 | unsafe { 556 | fmap.sync( 557 | self.node_ptr, 558 | fmap_info.base, 559 | range.start, 560 | (range.end - range.start) as usize, 561 | tx, 562 | )?; 563 | } 564 | 565 | if fmap.rc > 0 { 566 | fmap_info 567 | .ranges 568 | .insert(range.start, range.end - range.start, fmap); 569 | } 570 | } 571 | //dbg!(&self.fmaps); 572 | 573 | Ok(()) 574 | } 575 | 576 | fn fcntl(&mut self, cmd: usize, arg: usize) -> Result { 577 | match cmd { 578 | F_GETFL => Ok(self.flags), 579 | F_SETFL => { 580 | self.flags = (self.flags & O_ACCMODE) | (arg & !O_ACCMODE); 581 | Ok(0) 582 | } 583 | _ => Err(Error::new(EINVAL)), 584 | } 585 | } 586 | 587 | fn path(&self) -> &str { 588 | &self.path 589 | } 590 | 591 | fn sync(&mut self, fmaps: &mut Fmaps, tx: &mut Transaction) -> Result<()> { 592 | if let Some(fmap_info) = fmaps.get_mut(&self.node_ptr.id()) { 593 | for (range, fmap) in fmap_info.ranges.iter_mut() { 594 | unsafe { 595 | fmap.sync( 596 | self.node_ptr, 597 | fmap_info.base, 598 | range.start, 599 | (range.end - range.start) as usize, 600 | tx, 601 | )?; 602 | } 603 | } 604 | } 605 | 606 | Ok(()) 607 | } 608 | 609 | fn truncate(&mut self, len: u64, tx: &mut Transaction) -> Result<()> { 610 | if self.flags & O_ACCMODE == O_RDWR || self.flags & O_ACCMODE == O_WRONLY { 611 | let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); 612 | tx.truncate_node(self.node_ptr, len, mtime.as_secs(), mtime.subsec_nanos())?; 613 | Ok(()) 614 | } else { 615 | Err(Error::new(EBADF)) 616 | } 617 | } 618 | 619 | fn utimens(&mut self, times: &[TimeSpec], tx: &mut Transaction) -> Result<()> { 620 | let mut node = tx.read_tree(self.node_ptr)?; 621 | 622 | if node.data().uid() == self.uid || self.uid == 0 { 623 | if let &[atime, mtime] = times { 624 | let mut node_changed = false; 625 | 626 | let old_mtime = node.data().mtime(); 627 | let new_mtime = (mtime.tv_sec as u64, mtime.tv_nsec as u32); 628 | if old_mtime != new_mtime { 629 | node.data_mut().set_mtime(new_mtime.0, new_mtime.1); 630 | node_changed = true; 631 | } 632 | 633 | let old_atime = node.data().atime(); 634 | let new_atime = (atime.tv_sec as u64, atime.tv_nsec as u32); 635 | if old_atime != new_atime { 636 | node.data_mut().set_atime(new_atime.0, new_atime.1); 637 | node_changed = true; 638 | } 639 | 640 | if node_changed { 641 | tx.sync_tree(node)?; 642 | } 643 | } 644 | Ok(()) 645 | } else { 646 | Err(Error::new(EPERM)) 647 | } 648 | } 649 | 650 | fn getdents<'buf>( 651 | &mut self, 652 | _buf: DirentBuf<&'buf mut [u8]>, 653 | _opaque_offset: u64, 654 | _tx: &mut Transaction, 655 | ) -> Result> { 656 | Err(Error::new(ENOTDIR)) 657 | } 658 | } 659 | 660 | impl Drop for FileResource { 661 | fn drop(&mut self) { 662 | /* 663 | if !self.fmaps.is_empty() { 664 | eprintln!( 665 | "redoxfs: file {} still has {} fmaps!", 666 | self.path, 667 | self.fmaps.len() 668 | ); 669 | } 670 | */ 671 | } 672 | } 673 | 674 | impl range_tree::Value for Fmap { 675 | type K = u64; 676 | 677 | fn try_merge_forward(self, other: &Self) -> core::result::Result { 678 | if self.rc == other.rc && self.flags == other.flags && self.last_page_tail == 0 { 679 | Ok(self) 680 | } else { 681 | Err(self) 682 | } 683 | } 684 | fn try_merge_backwards(self, other: &Self) -> core::result::Result { 685 | if self.rc == other.rc && self.flags == other.flags && other.last_page_tail == 0 { 686 | Ok(self) 687 | } else { 688 | Err(self) 689 | } 690 | } 691 | #[allow(unused_variables)] 692 | fn split( 693 | self, 694 | prev_range: Option>, 695 | range: core::ops::Range, 696 | next_range: Option>, 697 | ) -> (Option, Self, Option) { 698 | ( 699 | prev_range.map(|_range| Fmap { 700 | rc: self.rc, 701 | flags: self.flags, 702 | last_page_tail: 0, 703 | }), 704 | Fmap { 705 | rc: self.rc, 706 | flags: self.flags, 707 | last_page_tail: if next_range.is_none() { 708 | self.last_page_tail 709 | } else { 710 | 0 711 | }, 712 | }, 713 | next_range.map(|_range| Fmap { 714 | rc: self.rc, 715 | flags: self.flags, 716 | last_page_tail: self.last_page_tail, 717 | }), 718 | ) 719 | } 720 | } 721 | -------------------------------------------------------------------------------- /src/node.rs: -------------------------------------------------------------------------------- 1 | use core::{fmt, mem, ops, slice}; 2 | use endian_num::Le; 3 | 4 | use crate::{BlockLevel, BlockList, BlockPtr, BlockTrait, RecordRaw, BLOCK_SIZE, RECORD_LEVEL}; 5 | 6 | /// An index into a [`Node`]'s block table. 7 | pub enum NodeLevel { 8 | L0(usize), 9 | L1(usize, usize), 10 | L2(usize, usize, usize), 11 | L3(usize, usize, usize, usize), 12 | L4(usize, usize, usize, usize, usize), 13 | } 14 | 15 | impl NodeLevel { 16 | // Warning: this uses constant record offsets, make sure to sync with Node 17 | 18 | /// Return the [`NodeLevel`] of the record with the given index. 19 | /// - the first 128 are level 0, 20 | /// - the next 64*256 are level 1, 21 | /// - ...and so on. 22 | pub fn new(mut record_offset: u64) -> Option { 23 | // 1 << 8 = 256, this is the number of entries in a BlockList 24 | const SHIFT: u64 = 8; 25 | const NUM: u64 = 1 << SHIFT; 26 | const MASK: u64 = NUM - 1; 27 | 28 | const L0: u64 = 128; 29 | if record_offset < L0 { 30 | return Some(Self::L0((record_offset & MASK) as usize)); 31 | } else { 32 | record_offset -= L0; 33 | } 34 | 35 | const L1: u64 = 64 * NUM; 36 | if record_offset < L1 { 37 | return Some(Self::L1( 38 | ((record_offset >> SHIFT) & MASK) as usize, 39 | (record_offset & MASK) as usize, 40 | )); 41 | } else { 42 | record_offset -= L1; 43 | } 44 | 45 | const L2: u64 = 32 * NUM * NUM; 46 | if record_offset < L2 { 47 | return Some(Self::L2( 48 | ((record_offset >> (2 * SHIFT)) & MASK) as usize, 49 | ((record_offset >> SHIFT) & MASK) as usize, 50 | (record_offset & MASK) as usize, 51 | )); 52 | } else { 53 | record_offset -= L2; 54 | } 55 | 56 | const L3: u64 = 16 * NUM * NUM * NUM; 57 | if record_offset < L3 { 58 | return Some(Self::L3( 59 | ((record_offset >> (3 * SHIFT)) & MASK) as usize, 60 | ((record_offset >> (2 * SHIFT)) & MASK) as usize, 61 | ((record_offset >> SHIFT) & MASK) as usize, 62 | (record_offset & MASK) as usize, 63 | )); 64 | } else { 65 | record_offset -= L3; 66 | } 67 | 68 | const L4: u64 = 12 * NUM * NUM * NUM * NUM; 69 | if record_offset < L4 { 70 | Some(Self::L4( 71 | ((record_offset >> (4 * SHIFT)) & MASK) as usize, 72 | ((record_offset >> (3 * SHIFT)) & MASK) as usize, 73 | ((record_offset >> (2 * SHIFT)) & MASK) as usize, 74 | ((record_offset >> SHIFT) & MASK) as usize, 75 | (record_offset & MASK) as usize, 76 | )) 77 | } else { 78 | None 79 | } 80 | } 81 | } 82 | 83 | type BlockListL1 = BlockList; 84 | type BlockListL2 = BlockList; 85 | type BlockListL3 = BlockList; 86 | type BlockListL4 = BlockList; 87 | 88 | /// A file/folder node 89 | #[repr(C, packed)] 90 | pub struct Node { 91 | /// This node's type & permissions. 92 | /// - first four bits are permissions 93 | /// - next four bits are permissions for the file's user 94 | /// - next four bits are permissions for the file's group 95 | /// - last four bits are permissions for everyone else 96 | pub mode: Le, 97 | 98 | /// The uid that owns this file 99 | pub uid: Le, 100 | 101 | /// The gid that owns this file 102 | pub gid: Le, 103 | 104 | /// The number of links to this file 105 | /// (directory entries, symlinks, etc) 106 | pub links: Le, 107 | 108 | /// The length of this file, in bytes 109 | pub size: Le, 110 | 111 | pub ctime: Le, 112 | pub ctime_nsec: Le, 113 | pub mtime: Le, 114 | pub mtime_nsec: Le, 115 | pub atime: Le, 116 | pub atime_nsec: Le, 117 | 118 | pub record_level: Le, 119 | 120 | pub padding: [u8; BLOCK_SIZE as usize - 4094], 121 | 122 | /// The first 128 blocks of this file. 123 | /// 124 | /// Total size: 128 * RECORD_SIZE (16 MiB, 128 KiB each) 125 | pub level0: [BlockPtr; 128], 126 | 127 | /// The next 64 * 256 blocks of this file, 128 | /// stored behind 64 level one tables. 129 | /// 130 | /// Total size: 64 * 256 * RECORD_SIZE (2 GiB, 32 MiB each) 131 | pub level1: [BlockPtr; 64], 132 | 133 | /// The next 32 * 256 * 256 blocks of this file, 134 | /// stored behind 32 level two tables. 135 | /// Each level two table points to 256 level one tables. 136 | /// 137 | /// Total size: 32 * 256 * 256 * RECORD_SIZE (256 GiB, 8 GiB each) 138 | pub level2: [BlockPtr; 32], 139 | 140 | /// The next 16 * 256 * 256 * 256 blocks of this file, 141 | /// stored behind 16 level three tables. 142 | /// 143 | /// Total size: 16 * 256 * 256 * 256 * RECORD_SIZE (32 TiB, 2 TiB each) 144 | pub level3: [BlockPtr; 16], 145 | 146 | /// The next 12 * 256 * 256 * 256 * 256 blocks of this file, 147 | /// stored behind 12 level four tables. 148 | /// 149 | /// Total size: 12 * 256 * 256 * 256 * 256 * RECORD_SIZE (6 PiB, 512 TiB each) 150 | pub level4: [BlockPtr; 12], 151 | } 152 | 153 | unsafe impl BlockTrait for Node { 154 | fn empty(level: BlockLevel) -> Option { 155 | if level.0 == 0 { 156 | Some(Self::default()) 157 | } else { 158 | None 159 | } 160 | } 161 | } 162 | 163 | impl Default for Node { 164 | fn default() -> Self { 165 | Self { 166 | mode: 0.into(), 167 | uid: 0.into(), 168 | gid: 0.into(), 169 | links: 0.into(), 170 | size: 0.into(), 171 | ctime: 0.into(), 172 | ctime_nsec: 0.into(), 173 | mtime: 0.into(), 174 | mtime_nsec: 0.into(), 175 | atime: 0.into(), 176 | atime_nsec: 0.into(), 177 | record_level: 0.into(), 178 | padding: [0; BLOCK_SIZE as usize - 4094], 179 | level0: [BlockPtr::default(); 128], 180 | level1: [BlockPtr::default(); 64], 181 | level2: [BlockPtr::default(); 32], 182 | level3: [BlockPtr::default(); 16], 183 | level4: [BlockPtr::default(); 12], 184 | } 185 | } 186 | } 187 | 188 | impl Node { 189 | pub const MODE_TYPE: u16 = 0xF000; 190 | pub const MODE_FILE: u16 = 0x8000; 191 | pub const MODE_DIR: u16 = 0x4000; 192 | pub const MODE_SYMLINK: u16 = 0xA000; 193 | 194 | /// Mask for node permission bits 195 | pub const MODE_PERM: u16 = 0x0FFF; 196 | pub const MODE_EXEC: u16 = 0o1; 197 | pub const MODE_WRITE: u16 = 0o2; 198 | pub const MODE_READ: u16 = 0o4; 199 | 200 | /// Create a new, empty node with the given metadata 201 | pub fn new(mode: u16, uid: u32, gid: u32, ctime: u64, ctime_nsec: u32) -> Self { 202 | Self { 203 | mode: mode.into(), 204 | uid: uid.into(), 205 | gid: gid.into(), 206 | links: 0.into(), 207 | ctime: ctime.into(), 208 | ctime_nsec: ctime_nsec.into(), 209 | mtime: ctime.into(), 210 | mtime_nsec: ctime_nsec.into(), 211 | atime: ctime.into(), 212 | atime_nsec: ctime_nsec.into(), 213 | record_level: if mode & Self::MODE_TYPE == Self::MODE_FILE { 214 | // Files take on record level 215 | RECORD_LEVEL as u32 216 | } else { 217 | // Folders do not 218 | 0 219 | } 220 | .into(), 221 | ..Default::default() 222 | } 223 | } 224 | 225 | /// This node's type & permissions. 226 | /// - first four bits are permissions 227 | /// - next four bits are permissions for the file's user 228 | /// - next four bits are permissions for the file's group 229 | /// - last four bits are permissions for everyone else 230 | pub fn mode(&self) -> u16 { 231 | self.mode.to_ne() 232 | } 233 | 234 | /// The uid that owns this file 235 | pub fn uid(&self) -> u32 { 236 | self.uid.to_ne() 237 | } 238 | 239 | /// The gid that owns this file 240 | pub fn gid(&self) -> u32 { 241 | self.gid.to_ne() 242 | } 243 | 244 | /// The number of links to this file 245 | /// (directory entries, symlinks, etc) 246 | pub fn links(&self) -> u32 { 247 | self.links.to_ne() 248 | } 249 | 250 | /// The length of this file, in bytes. 251 | pub fn size(&self) -> u64 { 252 | self.size.to_ne() 253 | } 254 | 255 | pub fn ctime(&self) -> (u64, u32) { 256 | (self.ctime.to_ne(), self.ctime_nsec.to_ne()) 257 | } 258 | 259 | pub fn mtime(&self) -> (u64, u32) { 260 | (self.mtime.to_ne(), self.mtime_nsec.to_ne()) 261 | } 262 | 263 | pub fn atime(&self) -> (u64, u32) { 264 | (self.atime.to_ne(), self.atime_nsec.to_ne()) 265 | } 266 | 267 | pub fn record_level(&self) -> BlockLevel { 268 | BlockLevel(self.record_level.to_ne() as usize) 269 | } 270 | 271 | pub fn set_mode(&mut self, mode: u16) { 272 | self.mode = mode.into(); 273 | } 274 | 275 | pub fn set_uid(&mut self, uid: u32) { 276 | self.uid = uid.into(); 277 | } 278 | 279 | pub fn set_gid(&mut self, gid: u32) { 280 | self.gid = gid.into(); 281 | } 282 | 283 | pub fn set_links(&mut self, links: u32) { 284 | self.links = links.into(); 285 | } 286 | 287 | pub fn set_size(&mut self, size: u64) { 288 | self.size = size.into(); 289 | } 290 | 291 | pub fn set_mtime(&mut self, mtime: u64, mtime_nsec: u32) { 292 | self.mtime = mtime.into(); 293 | self.mtime_nsec = mtime_nsec.into(); 294 | } 295 | 296 | pub fn set_atime(&mut self, atime: u64, atime_nsec: u32) { 297 | self.atime = atime.into(); 298 | self.atime_nsec = atime_nsec.into(); 299 | } 300 | 301 | pub fn is_dir(&self) -> bool { 302 | self.mode() & Self::MODE_TYPE == Self::MODE_DIR 303 | } 304 | 305 | pub fn is_file(&self) -> bool { 306 | self.mode() & Self::MODE_TYPE == Self::MODE_FILE 307 | } 308 | 309 | pub fn is_symlink(&self) -> bool { 310 | self.mode() & Self::MODE_TYPE == Self::MODE_SYMLINK 311 | } 312 | 313 | /// 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 314 | pub fn owner(&self, uid: u32) -> bool { 315 | uid == 0 || self.uid() == uid 316 | } 317 | 318 | /// Tests if the current user has enough permissions to view the file, op is the operation, 319 | /// like read and write, these modes are MODE_EXEC, MODE_READ, and MODE_WRITE 320 | pub fn permission(&self, uid: u32, gid: u32, op: u16) -> bool { 321 | let mut perm = self.mode() & 0o7; 322 | if self.uid() == uid { 323 | // If self.mode is 101100110, >> 6 would be 000000101 324 | // 0o7 is octal for 111, or, when expanded to 9 digits is 000000111 325 | perm |= (self.mode() >> 6) & 0o7; 326 | // Since we erased the GID and OTHER bits when >>6'ing, |= will keep those bits in place. 327 | } 328 | if self.gid() == gid || gid == 0 { 329 | perm |= (self.mode() >> 3) & 0o7; 330 | } 331 | if uid == 0 { 332 | //set the `other` bits to 111 333 | perm |= 0o7; 334 | } 335 | perm & op == op 336 | } 337 | } 338 | 339 | impl fmt::Debug for Node { 340 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 341 | let mode = self.mode; 342 | let uid = self.uid; 343 | let gid = self.gid; 344 | let links = self.links; 345 | let size = self.size; 346 | let ctime = self.ctime; 347 | let ctime_nsec = self.ctime_nsec; 348 | let mtime = self.mtime; 349 | let mtime_nsec = self.mtime_nsec; 350 | let atime = self.atime; 351 | let atime_nsec = self.atime_nsec; 352 | f.debug_struct("Node") 353 | .field("mode", &mode) 354 | .field("uid", &uid) 355 | .field("gid", &gid) 356 | .field("links", &links) 357 | .field("size", &size) 358 | .field("ctime", &ctime) 359 | .field("ctime_nsec", &ctime_nsec) 360 | .field("mtime", &mtime) 361 | .field("mtime_nsec", &mtime_nsec) 362 | .field("atime", &atime) 363 | .field("atime_nsec", &atime_nsec) 364 | //TODO: level0/1/2/3 365 | .finish() 366 | } 367 | } 368 | 369 | impl ops::Deref for Node { 370 | type Target = [u8]; 371 | fn deref(&self) -> &[u8] { 372 | unsafe { 373 | slice::from_raw_parts(self as *const Node as *const u8, mem::size_of::()) 374 | as &[u8] 375 | } 376 | } 377 | } 378 | 379 | impl ops::DerefMut for Node { 380 | fn deref_mut(&mut self) -> &mut [u8] { 381 | unsafe { 382 | slice::from_raw_parts_mut(self as *mut Node as *mut u8, mem::size_of::()) 383 | as &mut [u8] 384 | } 385 | } 386 | } 387 | 388 | #[test] 389 | fn node_size_test() { 390 | assert_eq!(mem::size_of::(), crate::BLOCK_SIZE as usize); 391 | } 392 | 393 | #[cfg(kani)] 394 | #[kani::proof] 395 | fn check_node_level() { 396 | let offset = kani::any(); 397 | NodeLevel::new(offset); 398 | } 399 | 400 | #[cfg(kani)] 401 | #[kani::proof] 402 | fn check_node_perms() { 403 | let mode = 0o750; 404 | 405 | let uid = kani::any(); 406 | let gid = kani::any(); 407 | 408 | let ctime = kani::any(); 409 | let ctime_nsec = kani::any(); 410 | 411 | let node = Node::new(mode, uid, gid, ctime, ctime_nsec); 412 | 413 | let root_uid = 0; 414 | let root_gid = 0; 415 | 416 | let other_uid = kani::any(); 417 | kani::assume(other_uid != uid); 418 | kani::assume(other_uid != root_uid); 419 | let other_gid = kani::any(); 420 | kani::assume(other_gid != gid); 421 | kani::assume(other_gid != root_gid); 422 | 423 | assert!(node.owner(uid)); 424 | assert!(node.permission(uid, gid, 0o7)); 425 | assert!(node.permission(uid, gid, 0o5)); 426 | assert!(node.permission(uid, other_gid, 0o7)); 427 | assert!(node.permission(uid, other_gid, 0o5)); 428 | assert!(!node.permission(other_uid, gid, 0o7)); 429 | assert!(node.permission(other_uid, gid, 0o5)); 430 | 431 | assert!(node.owner(root_uid)); 432 | assert!(node.permission(root_uid, root_gid, 0o7)); 433 | assert!(node.permission(root_uid, root_gid, 0o5)); 434 | assert!(node.permission(root_uid, other_gid, 0o7)); 435 | assert!(node.permission(root_uid, other_gid, 0o5)); 436 | assert!(!node.permission(other_uid, root_gid, 0o7)); 437 | assert!(node.permission(other_uid, root_gid, 0o5)); 438 | 439 | assert!(!node.owner(other_uid)); 440 | assert!(!node.permission(other_uid, other_gid, 0o7)); 441 | assert!(!node.permission(other_uid, other_gid, 0o5)); 442 | } 443 | -------------------------------------------------------------------------------- /src/record.rs: -------------------------------------------------------------------------------- 1 | use alloc::{boxed::Box, vec}; 2 | use core::ops; 3 | 4 | use crate::{BlockLevel, BlockTrait, RECORD_LEVEL}; 5 | 6 | //TODO: this is a box to prevent stack overflows 7 | #[derive(Clone)] 8 | pub struct RecordRaw(Box<[u8]>); 9 | 10 | unsafe impl BlockTrait for RecordRaw { 11 | fn empty(level: BlockLevel) -> Option { 12 | if level.0 <= RECORD_LEVEL { 13 | Some(Self(vec![0; level.bytes() as usize].into_boxed_slice())) 14 | } else { 15 | None 16 | } 17 | } 18 | } 19 | 20 | impl ops::Deref for RecordRaw { 21 | type Target = [u8]; 22 | fn deref(&self) -> &[u8] { 23 | &self.0 24 | } 25 | } 26 | 27 | impl ops::DerefMut for RecordRaw { 28 | fn deref_mut(&mut self) -> &mut [u8] { 29 | &mut self.0 30 | } 31 | } 32 | 33 | #[test] 34 | fn record_raw_size_test() { 35 | for level_i in 0..RECORD_LEVEL { 36 | let level = BlockLevel(level_i); 37 | assert_eq!( 38 | RecordRaw::empty(level).unwrap().len(), 39 | level.bytes() as usize 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::{unmount_path, DiskSparse, FileSystem, Node, TreePtr, ALLOC_GC_THRESHOLD}; 2 | use std::path::Path; 3 | use std::process::Command; 4 | use std::sync::atomic::AtomicUsize; 5 | use std::sync::atomic::Ordering::Relaxed; 6 | use std::{fs, thread, time}; 7 | 8 | static IMAGE_SEQ: AtomicUsize = AtomicUsize::new(0); 9 | 10 | fn with_redoxfs(callback: F) -> T 11 | where 12 | T: Send + Sync + 'static, 13 | F: FnOnce(FileSystem) -> T + Send + Sync + 'static, 14 | { 15 | let disk_path = format!("image{}.bin", IMAGE_SEQ.fetch_add(1, Relaxed)); 16 | 17 | let res = { 18 | let disk = DiskSparse::create(dbg!(&disk_path), 1024 * 1024 * 1024).unwrap(); 19 | 20 | let ctime = dbg!(time::SystemTime::now().duration_since(time::UNIX_EPOCH)).unwrap(); 21 | let fs = FileSystem::create(disk, None, ctime.as_secs(), ctime.subsec_nanos()).unwrap(); 22 | 23 | callback(fs) 24 | }; 25 | 26 | dbg!(fs::remove_file(dbg!(disk_path))).unwrap(); 27 | 28 | res 29 | } 30 | 31 | fn with_mounted(callback: F) -> T 32 | where 33 | T: Send + Sync + 'static, 34 | F: FnOnce(&Path) -> T + Send + Sync + 'static, 35 | { 36 | let mount_path_o = format!("image{}", IMAGE_SEQ.fetch_add(1, Relaxed)); 37 | let mount_path = mount_path_o.clone(); 38 | 39 | let res = with_redoxfs(move |fs| { 40 | if cfg!(not(target_os = "redox")) { 41 | if !Path::new(&mount_path).exists() { 42 | dbg!(fs::create_dir(dbg!(&mount_path))).unwrap(); 43 | } 44 | } 45 | let join_handle = crate::mount(fs, dbg!(mount_path), move |real_path| { 46 | let real_path = real_path.to_owned(); 47 | thread::spawn(move || { 48 | let res = callback(&real_path); 49 | let real_path = real_path.to_str().unwrap(); 50 | 51 | if cfg!(target_os = "redox") { 52 | dbg!(fs::remove_file(dbg!(format!(":{}", real_path)))).unwrap(); 53 | } else { 54 | if !dbg!(Command::new("sync").status()).unwrap().success() { 55 | panic!("sync failed"); 56 | } 57 | 58 | if !unmount_path(real_path).is_ok() { 59 | panic!("umount failed"); 60 | } 61 | } 62 | 63 | res 64 | }) 65 | }) 66 | .unwrap(); 67 | 68 | join_handle.join().unwrap() 69 | }); 70 | 71 | if cfg!(not(target_os = "redox")) { 72 | dbg!(fs::remove_dir(dbg!(mount_path_o))).unwrap(); 73 | } 74 | 75 | res 76 | } 77 | 78 | #[test] 79 | fn simple() { 80 | with_mounted(|path| { 81 | dbg!(fs::create_dir(&path.join("test"))).unwrap(); 82 | }) 83 | } 84 | 85 | #[cfg(target_os = "redox")] 86 | #[test] 87 | fn mmap() { 88 | use syscall; 89 | 90 | //TODO 91 | with_mounted(|path| { 92 | use std::slice; 93 | 94 | let path = dbg!(path.join("test")); 95 | 96 | let mmap_inner = |write: bool| { 97 | let fd = dbg!(libredox::call::open( 98 | path.to_str().unwrap(), 99 | libredox::flag::O_CREAT | libredox::flag::O_RDWR | libredox::flag::O_CLOEXEC, 100 | 0, 101 | )) 102 | .unwrap(); 103 | 104 | let map = unsafe { 105 | slice::from_raw_parts_mut( 106 | dbg!(libredox::call::mmap(libredox::call::MmapArgs { 107 | fd, 108 | offset: 0, 109 | length: 128, 110 | prot: libredox::flag::PROT_READ | libredox::flag::PROT_WRITE, 111 | flags: libredox::flag::MAP_SHARED, 112 | addr: core::ptr::null_mut(), 113 | })) 114 | .unwrap() as *mut u8, 115 | 128, 116 | ) 117 | }; 118 | 119 | // Maps should be available after closing 120 | assert_eq!(dbg!(libredox::call::close(fd)), Ok(())); 121 | 122 | for i in 0..128 { 123 | if write { 124 | map[i as usize] = i; 125 | } 126 | assert_eq!(map[i as usize], i); 127 | } 128 | 129 | //TODO: add msync 130 | unsafe { 131 | assert_eq!( 132 | dbg!(libredox::call::munmap(map.as_mut_ptr().cast(), map.len())), 133 | Ok(()) 134 | ); 135 | } 136 | }; 137 | 138 | mmap_inner(true); 139 | mmap_inner(false); 140 | }) 141 | } 142 | 143 | #[test] 144 | fn create_remove_should_not_increase_size() { 145 | with_redoxfs(|mut fs| { 146 | let initially_free = fs.allocator().free(); 147 | 148 | let tree_ptr = TreePtr::::root(); 149 | let name = "test"; 150 | let _ = fs 151 | .tx(|tx| { 152 | tx.create_node(tree_ptr, name, Node::MODE_FILE | 0644, 1, 0)?; 153 | tx.remove_node(tree_ptr, name, Node::MODE_FILE) 154 | }) 155 | .unwrap(); 156 | 157 | assert_eq!(fs.allocator().free(), initially_free); 158 | }); 159 | } 160 | 161 | #[test] 162 | fn many_create_remove_should_not_increase_size() { 163 | with_redoxfs(|mut fs| { 164 | let initially_free = fs.allocator().free(); 165 | let tree_ptr = TreePtr::::root(); 166 | let name = "test"; 167 | 168 | // Iterate over 255 times to prove deleted files don't retain space within the node tree 169 | // Iterate to an ALLOC_GC_THRESHOLD boundary to ensure the allocator GC reclaims space 170 | let start = fs.header.generation.to_ne(); 171 | let end = start + ALLOC_GC_THRESHOLD; 172 | let end = end - (end % ALLOC_GC_THRESHOLD) + 1 + ALLOC_GC_THRESHOLD; 173 | for i in start..end { 174 | let _ = fs 175 | .tx(|tx| { 176 | tx.create_node( 177 | tree_ptr, 178 | &format!("{}{}", name, i), 179 | Node::MODE_FILE | 0644, 180 | 1, 181 | 0, 182 | )?; 183 | tx.remove_node(tree_ptr, &format!("{}{}", name, i), Node::MODE_FILE) 184 | }) 185 | .unwrap(); 186 | } 187 | 188 | // Any value greater than 0 indicates a storage leak 189 | let diff = initially_free - fs.allocator().free(); 190 | assert_eq!(diff, 0); 191 | }); 192 | } 193 | -------------------------------------------------------------------------------- /src/tree.rs: -------------------------------------------------------------------------------- 1 | use core::{marker::PhantomData, mem, ops, slice}; 2 | use endian_num::Le; 3 | 4 | use crate::{BlockLevel, BlockPtr, BlockRaw, BlockTrait}; 5 | 6 | // 1 << 8 = 256, this is the number of entries in a TreeList 7 | const TREE_LIST_SHIFT: u32 = 8; 8 | const TREE_LIST_ENTRIES: usize = 1 << TREE_LIST_SHIFT; 9 | 10 | /// A tree with 4 levels 11 | pub type Tree = TreeList>>>; 12 | 13 | /// A [`TreePtr`] and the contents of the block it references. 14 | #[derive(Clone, Copy, Debug, Default)] 15 | pub struct TreeData { 16 | /// The value of the [`TreePtr`] 17 | id: u32, 18 | 19 | // The data 20 | data: T, 21 | } 22 | 23 | impl TreeData { 24 | pub fn new(id: u32, data: T) -> Self { 25 | Self { id, data } 26 | } 27 | 28 | pub fn id(&self) -> u32 { 29 | self.id 30 | } 31 | 32 | pub fn data(&self) -> &T { 33 | &self.data 34 | } 35 | 36 | pub fn data_mut(&mut self) -> &mut T { 37 | &mut self.data 38 | } 39 | 40 | pub fn into_data(self) -> T { 41 | self.data 42 | } 43 | 44 | pub fn ptr(&self) -> TreePtr { 45 | TreePtr { 46 | id: self.id.into(), 47 | phantom: PhantomData, 48 | } 49 | } 50 | } 51 | 52 | /// A list of pointers to blocks of type `T`. 53 | /// This is one level of a [`Tree`], defined above. 54 | #[repr(C, packed)] 55 | pub struct TreeList { 56 | pub ptrs: [BlockPtr; TREE_LIST_ENTRIES], 57 | } 58 | 59 | unsafe impl BlockTrait for TreeList { 60 | fn empty(level: BlockLevel) -> Option { 61 | if level.0 == 0 { 62 | Some(Self { 63 | ptrs: [BlockPtr::default(); TREE_LIST_ENTRIES], 64 | }) 65 | } else { 66 | None 67 | } 68 | } 69 | } 70 | 71 | impl ops::Deref for TreeList { 72 | type Target = [u8]; 73 | fn deref(&self) -> &[u8] { 74 | unsafe { 75 | slice::from_raw_parts( 76 | self as *const TreeList as *const u8, 77 | mem::size_of::>(), 78 | ) as &[u8] 79 | } 80 | } 81 | } 82 | 83 | impl ops::DerefMut for TreeList { 84 | fn deref_mut(&mut self) -> &mut [u8] { 85 | unsafe { 86 | slice::from_raw_parts_mut( 87 | self as *mut TreeList as *mut u8, 88 | mem::size_of::>(), 89 | ) as &mut [u8] 90 | } 91 | } 92 | } 93 | 94 | /// A pointer to an entry in a [`Tree`]. 95 | #[repr(C, packed)] 96 | pub struct TreePtr { 97 | id: Le, 98 | phantom: PhantomData, 99 | } 100 | 101 | impl TreePtr { 102 | /// Get a [`TreePtr`] to the filesystem root 103 | /// directory's node. 104 | pub fn root() -> Self { 105 | Self::new(1) 106 | } 107 | 108 | pub fn new(id: u32) -> Self { 109 | Self { 110 | id: id.into(), 111 | phantom: PhantomData, 112 | } 113 | } 114 | 115 | /// Create a [`TreePtr`] from [`Tree`] indices, 116 | /// Where `indexes` is `(i3, i2, i1, i0)`. 117 | /// - `i3` is the index into the level 3 table, 118 | /// - `i2` is the index into the level 2 table at `i3` 119 | /// - ...and so on. 120 | pub fn from_indexes(indexes: (usize, usize, usize, usize)) -> Self { 121 | const SHIFT: u32 = TREE_LIST_SHIFT; 122 | let id = ((indexes.0 << (3 * SHIFT)) as u32) 123 | | ((indexes.1 << (2 * SHIFT)) as u32) 124 | | ((indexes.2 << SHIFT) as u32) 125 | | (indexes.3 as u32); 126 | Self { 127 | id: id.into(), 128 | phantom: PhantomData, 129 | } 130 | } 131 | 132 | pub fn id(&self) -> u32 { 133 | self.id.to_ne() 134 | } 135 | 136 | pub fn is_null(&self) -> bool { 137 | self.id() == 0 138 | } 139 | 140 | /// Get this indices of this [`TreePtr`] in a [`Tree`]. 141 | /// Returns `(i3, i2, i1, i0)`: 142 | /// - `i3` is the index into the level 3 table, 143 | /// - `i2` is the index into the level 2 table at `i3` 144 | /// - ...and so on. 145 | pub fn indexes(&self) -> (usize, usize, usize, usize) { 146 | const SHIFT: u32 = TREE_LIST_SHIFT; 147 | const NUM: u32 = 1 << SHIFT; 148 | const MASK: u32 = NUM - 1; 149 | let id = self.id(); 150 | 151 | let i3 = ((id >> (3 * SHIFT)) & MASK) as usize; 152 | let i2 = ((id >> (2 * SHIFT)) & MASK) as usize; 153 | let i1 = ((id >> SHIFT) & MASK) as usize; 154 | let i0 = (id & MASK) as usize; 155 | 156 | return (i3, i2, i1, i0); 157 | } 158 | } 159 | 160 | impl Clone for TreePtr { 161 | fn clone(&self) -> Self { 162 | *self 163 | } 164 | } 165 | 166 | impl Copy for TreePtr {} 167 | 168 | impl Default for TreePtr { 169 | fn default() -> Self { 170 | Self { 171 | id: 0.into(), 172 | phantom: PhantomData, 173 | } 174 | } 175 | } 176 | 177 | #[test] 178 | fn tree_list_size_test() { 179 | assert_eq!( 180 | mem::size_of::>(), 181 | crate::BLOCK_SIZE as usize 182 | ); 183 | } 184 | -------------------------------------------------------------------------------- /src/unmount.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs, 3 | io::{self}, 4 | process::{Command, ExitStatus}, 5 | }; 6 | 7 | fn unmount_linux_path(mount_path: &str) -> io::Result { 8 | // Different distributions can have various fusermount binaries. Try 9 | // them all. 10 | let commands = ["fusermount", "fusermount3"]; 11 | 12 | for command in commands { 13 | let status = Command::new(command).arg("-u").arg(mount_path).status(); 14 | if status.is_ok() { 15 | return status; 16 | } 17 | if let Err(ref e) = status { 18 | if e.kind() == io::ErrorKind::NotFound { 19 | continue; 20 | } 21 | } 22 | } 23 | 24 | // Unmounting failed since no suitable command was found 25 | Err(std::io::Error::new( 26 | io::ErrorKind::NotFound, 27 | format!( 28 | "Unable to locate any fusermount binaries. Tried {:?}. Is fuse installed?", 29 | commands 30 | ), 31 | )) 32 | } 33 | 34 | pub fn unmount_path(mount_path: &str) -> Result<(), io::Error> { 35 | if cfg!(target_os = "redox") { 36 | fs::remove_file(format!(":{}", mount_path))? 37 | } else { 38 | let status_res = if cfg!(target_os = "linux") { 39 | unmount_linux_path(mount_path) 40 | } else { 41 | Command::new("umount").arg(mount_path).status() 42 | }; 43 | 44 | let status = status_res?; 45 | if !status.success() { 46 | return Err(io::Error::new( 47 | io::ErrorKind::Other, 48 | "redoxfs umount failed", 49 | )); 50 | } 51 | } 52 | 53 | Ok(()) 54 | } 55 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CARGO_ARGS=(--release) 4 | TARGET=target/release 5 | export RUST_BACKTRACE=full 6 | export RUST_LOG=info 7 | 8 | function cleanup { 9 | sync 10 | fusermount -u image || true 11 | fusermount3 -u image || true 12 | } 13 | 14 | trap 'cleanup' ERR 15 | 16 | set -eEx 17 | 18 | cleanup 19 | 20 | redoxer test -- --lib -- --nocapture 21 | cargo test --lib --no-default-features -- --nocapture 22 | cargo test --lib -- --nocapture 23 | cargo build "${CARGO_ARGS[@]}" 24 | 25 | rm -f image.bin 26 | fallocate -l 1G image.bin 27 | time "${TARGET}/redoxfs-mkfs" image.bin 28 | 29 | mkdir -p image 30 | "${TARGET}/redoxfs" image.bin image 31 | 32 | df -h image 33 | ls -lah image 34 | 35 | mkdir image/test 36 | time cp -r src image/test/src 37 | 38 | dd if=/dev/urandom of=image/test/random bs=1M count=256 39 | dd if=image/test/random of=/dev/null bs=1M count=256 40 | 41 | time truncate --size=256M image/test/sparse 42 | dd if=image/test/sparse of=/dev/null bs=1M count=256 43 | 44 | dd if=/dev/zero of=image/test/zero bs=1M count=256 45 | dd if=image/test/zero of=/dev/null bs=1M count=256 46 | 47 | ls -lah image/test 48 | 49 | df -h image 50 | 51 | rm image/test/random 52 | rm image/test/sparse 53 | rm image/test/zero 54 | rm -rf image/test/src 55 | rmdir image/test 56 | 57 | df -h image 58 | ls -lah image 59 | 60 | cleanup 61 | 62 | "${TARGET}/redoxfs" image.bin image 63 | 64 | df -h image 65 | ls -lah image 66 | 67 | cleanup 68 | --------------------------------------------------------------------------------