514 Commits

Author SHA1 Message Date
Red Bear OS 735f9706cd 0.3.0: refresh Cargo.lock for +rb0.3.0 local forks 2026-07-06 20:41:37 +03:00
vasilito 1859eb5d0b 0.3.0: bump version suffix to +rb0.3.0 2026-07-06 19:47:38 +03:00
Red Bear OS 0fac378c94 redoxfs: import Vec from alloc for no_std builds (bootloader dependency) 2026-07-06 15:19:39 +03:00
vasilito 7387e45ffb redbear: apply readonly-live-media patch, add author attribution, use local redox-scheme path 2026-07-06 07:56:16 +03:00
Red Bear OS e6be9134d8 fix: use local path deps for redox_syscall and libredox 2026-07-05 23:58:07 +03:00
Red Bear OS f29cbc3dfe version: use +rb0.2.5 build metadata instead of -rb0.2.5 pre-release
Semver pre-release suffix (-rb0.2.5) breaks Cargo's [patch.crates-io]
matching for transitive deps. Build metadata (+rb0.2.5) is semver-
compatible: ^0.9.0 matches 0.9.0+rb0.2.5, patch redirection works,
and the Red Bear suffix is still visible in the version string.
2026-07-05 09:16:12 +03:00
Jeremy Soller 473b4baeb0 0.9.1 2026-07-01 08:32:44 -06:00
Jeremy Soller 00ba438c95 Merge branch 'update-redox-crates' into 'master'
chore: Update redox crates versions

See merge request redox-os/redoxfs!121
2026-07-01 08:31:44 -06:00
Ibuki Omatsu 6c1e2aa446 chore: Update redox crates versions 2026-07-01 08:31:44 -06:00
Jeremy Soller 3acc7a240f Merge branch 'no-box' into 'master'
Remove Box from implementation

See merge request redox-os/redoxfs!120
2026-06-24 13:48:49 -06:00
Wildan M 47c9d242f5 Remove Box from Vec conversion 2026-06-23 14:09:19 +07:00
Wildan M 6767faba13 Remove Box in scheme handle 2026-06-23 14:01:31 +07:00
Wildan M 44bbd27e1b Fix warning 2026-06-23 13:58:50 +07:00
Jeremy Soller af493b9f4e Merge branch 'inode-cache' into 'master'
Implement Inode cache from fmap

Closes #56

See merge request redox-os/redoxfs!118
2026-05-13 07:44:12 -06:00
Wildan M d03893a253 Add munmap to solve leak 2026-05-13 11:23:45 +07:00
Wildan M 3ed2bee9bb Fix CI run 2026-05-13 07:08:07 +07:00
Wildan M 1d1176de40 Add fmap version for invalidation 2026-05-13 06:48:39 +07:00
Wildan M 5b515c0c9b Implement fmap for read cache 2026-05-13 06:34:39 +07:00
Jeremy Soller f26c86b176 Update dependencies 2026-05-04 12:31:39 -06:00
Jeremy Soller b59677627c Merge branch 'retry-readdir' into 'master'
Skip missing getdents item

See merge request redox-os/redoxfs!117
2026-03-30 14:31:28 -06:00
Wildan M 3d6f9cec6e Skip missing getdents item 2026-03-30 21:04:03 +07:00
Jeremy Soller 0955762196 Merge branch 'fix_unmount' into 'master'
Partially fix unmount on redox

See merge request redox-os/redoxfs!116
2026-03-11 13:40:09 -06:00
bjorn3 7f469db981 Partially fix unmount on redox
This is not enough to cause redoxfs to quit, but does remove it from
the namespace.
2026-03-11 20:06:07 +01:00
Jeremy Soller 7dad335ba7 Merge branch 'fix-fchdir' into 'master'
fix: Fix fpath implementation for root directory

See merge request redox-os/redoxfs!115
2026-03-06 18:01:31 -07:00
Ibuki.O eb50a95c9b fix: Fix fpath implementation for root directory 2026-03-07 09:55:31 +09:00
Jeremy Soller c4b51b9ca8 Merge branch 'stdfscall' into 'master'
feat: Add std_fs_call support

See merge request redox-os/redoxfs!113
2026-02-28 08:05:09 -07:00
Ibuki Omatsu 04dbcbcd91 feat: Add std_fs_call support 2026-02-28 08:05:08 -07:00
Jeremy Soller e75d311776 Merge branch 'touch-dir' into 'master'
Implement utimens for directory

See merge request redox-os/redoxfs!114
2026-02-27 06:18:55 -07:00
Wildan M 64a8ff46e1 Implements utimens for directory 2026-02-27 14:27:34 +07:00
Jeremy Soller d00745b402 Merge branch 'master' into 'master'
feat(redox): flocking

See merge request redox-os/redoxfs!112
2026-02-25 08:24:56 -07:00
Anhad Singh c51642c8b6 chore: bump redox_syscall and redox_scheme
Signed-off-by: Anhad Singh <andypython@protonmail.com>
2026-02-26 02:14:10 +11:00
Anhad Singh 61d52cf6a7 misc(mount/redox): update to new on_detach API
Signed-off-by: Anhad Singh <andypython@protonmail.com>
2026-02-25 01:49:23 +11:00
Anhad Singh 2360a346ac feat(redox): flocking
Signed-off-by: Anhad Singh <andypython@protonmail.com>
2026-02-24 01:02:26 +11:00
Jeremy Soller 1e0119b3cb Merge branch 'canonical-relative-path' into 'master'
feat: Resolve parent paths

See merge request redox-os/redoxfs!111
2026-02-21 07:47:59 -07:00
Ibuki Omatsu ceeaf513b6 feat: Resolve parent paths 2026-02-21 07:47:59 -07:00
Anhad Singh f4a256ebcd misc: cargo fmt
Signed-off-by: Anhad Singh <andypython@protonmail.com>
2026-02-11 10:40:15 +11:00
Jeremy Soller d47cd7d3f6 Merge branch 'unlinkat' into 'master'
Introduce SchemeRoot and integrate unlink and rmdir into unlinkat.

See merge request redox-os/redoxfs!104
2026-01-20 20:55:47 -07:00
Ibuki Omatsu 32d067d293 Introduce SchemeRoot and integrate unlink and rmdir into unlinkat. 2026-01-20 20:55:47 -07:00
Jeremy Soller 4726ebdd9d Update Cargo.lock 2026-01-03 09:45:02 -07:00
Jeremy Soller 42db72daf2 0.8.6: workaround for memory leaks 2026-01-03 09:44:23 -07:00
Jeremy Soller 57277f2041 0.8.5 2026-01-02 19:31:28 -07:00
Jeremy Soller 9d4ae07987 Merge branch 'delete-after-close' into 'master'
delete after close instead of unlink

See merge request redox-os/redoxfs!102
2026-01-02 19:30:11 -07:00
Jeremy Soller 7bd6189ca9 Fix no_std 2026-01-02 19:25:26 -07:00
Jeremy Soller 5778818bf6 Fix typo in redox mount code 2026-01-02 13:26:09 -07:00
Jeremy Soller 0e01427cd8 Implement release list and add open node tracking to filesystem 2026-01-02 13:23:29 -07:00
Jeremy Soller 57e05acef2 Leak nodes still in use, preparation for release list 2025-12-25 15:52:27 -07:00
Jeremy Soller a44303b4f6 WIP: delete after close instead of unlink 2025-12-25 15:51:53 -07:00
Jeremy Soller 2e1bca19ac Merge branch 'test-lints' into 'master'
Fix test lints and add rename test

See merge request redox-os/redoxfs!109
2025-12-20 21:33:15 -07:00
Josh Megnauth 2b8ea938ea Fix test lints and add rename test
This fixes lints for tests/tests.rs and adds a test for rename. The new
rename test is based on the rename_no_replace tests. There are two
discrepancies between Linux and Redox but I'm not sure if they matter. I
added a note to those tests.

There are two fixed lints of note.
* assert_cmd deprecated Command::cargo_bin in favor of a compile time
  checked macro.
* The mode literals were wrong: 0666 instead of 0o666. 0o is the prefix
  for octal literals.
2025-12-20 19:17:36 -05:00
Jeremy Soller a2f3b55b68 Merge branch 'replace-unlink-rmdir-with-unlinkat' into 'master'
refactor: Replace unlink and rmdir with unlinkat.

See merge request redox-os/redoxfs!107
2025-12-17 18:45:39 -07:00
Ibuki Omatsu a59e7fca9b refactor: Replace unlink and rmdir with unlinkat. 2025-12-17 18:45:39 -07:00
Jeremy Soller c0777c08ac Merge branch 'bump-ver' into 'master'
Bump version to fix installation

See merge request redox-os/redoxfs!108
2025-12-16 09:48:06 -07:00
Wildan M 52b230fc67 Bump version to fix installation 2025-12-16 20:53:14 +07:00
Jeremy Soller b8f2c63bdd Merge branch 'book-on-readme' into 'master'
Mention book for usage on README

See merge request redox-os/redoxfs!106
2025-12-14 21:02:33 -07:00
Jeremy Soller dc77e817e2 Merge branch 'impl-rename-noreplace' into 'master'
Implement RENAME_NO_REPLACE

See merge request redox-os/redoxfs!105
2025-12-14 19:12:36 -07:00
Josh Megnauth 66f4766c6b Implement RENAME_NO_REPLACE
RENAME_NO_REPLACE is a neat flag for renameat2 which disallows renaming
a source onto an existing target.

I originally implemented this flag in relibc, but that code is still
subject to TOCTOU because the sequence of checking if the target exists
and the actual rename isn't atomic.

The code for the flag isn't used anywhere yet, like frename, but it's
unit tested and works for now.
2025-12-14 21:03:56 -05:00
Wildan M 644f747648 Mention book for usage on README 2025-12-15 05:21:05 +07:00
Jeremy Soller 5c94db6f1a Merge branch 'decouple-fuse' into 'master'
Split FUSE feature and move redox dependencies

See merge request redox-os/redoxfs!103
2025-11-17 06:53:54 -07:00
Wildan M 50683d5a7a Split fuse feature 2025-11-17 17:21:59 +07:00
Jeremy Soller 54df280b02 Use new scheme format in fpath 2025-11-14 14:29:19 -07:00
Jeremy Soller a7f61b12ad 0.8.2 2025-11-10 21:01:48 -07:00
Jeremy Soller 91c5ffb986 When upgrading from inline data, only take bytes that fit 2025-11-10 20:59:08 -07:00
Jeremy Soller 116d482d2b Reduce logging 2025-11-01 20:25:43 -06:00
Jeremy Soller b6700b94dc 0.8.1 2025-10-31 11:21:04 -06:00
Jeremy Soller fefd943c3f Add resizing support 2025-10-31 11:16:13 -06:00
Jeremy Soller ab92a083e8 Ensure header size matches allocated blocks 2025-10-31 07:41:29 -06:00
Jeremy Soller 5abce443a5 Ensure allocation is done with the lowest block indexes first 2025-10-30 21:25:37 -06:00
Jeremy Soller 84288a2f5d 0.8.0 2025-09-26 15:45:33 -06:00
Jeremy Soller 2dee3bba4c Merge branch 'v8' into 'master'
v8

See merge request redox-os/redoxfs!95
2025-09-26 15:44:29 -06:00
Jeremy Soller 5af2abde0a Fix reading sparse data when using inline data 2025-09-26 14:36:22 -06:00
Jeremy Soller 437d06ab8c Support inline data in files 2025-09-26 14:17:03 -06:00
Jeremy Soller 5c54b189f9 Prepare for inline node data support 2025-09-26 13:03:07 -06:00
Jeremy Soller 9484534c76 Merge remote-tracking branch 'origin/master' into v8 2025-09-26 12:03:22 -06:00
Jeremy Soller eea5c96137 Implement clone threshold to work around Redox problems 2025-09-26 11:25:22 -06:00
Jeremy Soller 5e044e88c0 Fix cursor when printing clone progress 2025-09-26 10:43:34 -06:00
Jeremy Soller d7391bc6a8 Throttle clone progress messages 2025-09-26 10:18:54 -06:00
Jeremy Soller c796cc7413 Add units for clone progress 2025-09-26 10:10:22 -06:00
Jeremy Soller f37afb04bb Add progress callback to clone function 2025-09-26 10:08:54 -06:00
Jeremy Soller 74582a0917 Squash allocation log in clone 2025-09-26 09:29:02 -06:00
Jeremy Soller 5622165c92 Support for fast cloning of RedoxFS partition 2025-09-26 09:24:21 -06:00
Jeremy Soller 8a46a6df39 Implement From<File> for DiskFile 2025-09-26 09:23:41 -06:00
Jeremy Soller ae034662e6 Fix documentation 2025-09-26 09:23:29 -06:00
Jeremy Soller 0a1fd60d76 Merge branch 'fix-ci' into 'master'
Move tests with mount as integration tests

See merge request redox-os/redoxfs!99
2025-09-25 12:15:47 -06:00
Wildan M fc4c2470a1 Fix test 2025-09-26 01:02:07 +07:00
Wildan Mubarok 508feda5eb Only run integration tests 2025-09-25 14:52:19 +00:00
Wildan M 8591d96f67 Detect redoxer hangs 2025-09-25 21:30:43 +07:00
Wildan M 2d61aceff9 Move tests with mount as integration tests 2025-09-25 20:58:49 +07:00
Jeremy Soller 25cc366a63 Merge branch 'partial_getdents' into 'master'
Allow partial result of getdents

See merge request redox-os/redoxfs!98
2025-09-23 10:57:23 -06:00
Wildan M 4af64e157a Allow partial result of getdents 2025-09-23 23:34:26 +07:00
Jeremy Soller 98286ce752 Fix compilation with no_std 2025-08-03 20:02:09 -06:00
Jeremy Soller 5828e12e85 Provide context for all allocations and deallocations 2025-08-01 21:32:16 -06:00
Jeremy Soller 0a14166c2f Fix issue where compressed data could be returned 2025-08-01 14:07:26 -06:00
Jeremy Soller d03aa518fe Fix compression with 128 KiB records, use compress cache 2025-08-01 13:23:56 -06:00
Jeremy Soller b40fecf185 Fix comment in node.rs 2025-08-01 12:55:49 -06:00
Jeremy Soller ca7ea3c0c8 node: debug blocks 2025-08-01 12:07:51 -06:00
Jeremy Soller a1c94311db Store number of blocks actually used 2025-08-01 11:51:37 -06:00
Jeremy Soller f30b363ce1 Fix build with no_std 2025-08-01 09:55:21 -06:00
Jeremy Soller 26dc32d710 v8: transparent lz4 compression 2025-07-31 18:48:17 -06:00
Jeremy Soller 0cb6f3cad5 Fix compilation on Linux 2025-07-24 13:45:57 -06:00
Jeremy Soller 1506ed50fc Merge branch 'bind-connect-redoxfs' into 'master'
feat: Add UDS bind and connect integration

See merge request redox-os/redoxfs!94
2025-07-21 18:32:18 -06:00
Ibuki Omatsu 63217aa69a feat: Add UDS bind and connect integration 2025-07-21 18:32:18 -06:00
Jeremy Soller dc25344b68 Update aes and xts-mode 2025-07-02 12:48:39 -06:00
Jeremy Soller bd6efe0c83 Update crate version to 0.7 2025-07-02 12:27:14 -06:00
Jeremy Soller aecd042afc Use AES-XTS for encryption 2025-07-02 10:20:17 -06:00
Jeremy Soller 291ce5edc0 Merge branch 'Improve-file-creation-performance' into 'v7'
Improve performance

See merge request redox-os/redoxfs!87
2025-07-01 19:11:00 -06:00
James Matlik b959c34287 Improve performance 2025-07-01 19:11:00 -06:00
Jeremy Soller 02685f5c70 Merge branch 'openat' into 'master'
Implement openat syscall

See merge request redox-os/redoxfs!92
2025-07-01 09:42:16 -06:00
Darley Barreto d0d5761b2d Implement openat syscall 2025-07-01 09:42:16 -06:00
Jeremy Soller 066e5f6e8a Merge branch 'update-syscall' into 'master'
Update redox_syscall

See merge request redox-os/redoxfs!91
2025-06-12 12:23:48 -06:00
Jeremy Soller 9e2e370c0b Merge branch 'improve-node-mode-docstrings' into 'master'
Improve node mode docstrings

See merge request redox-os/redoxfs!90
2025-06-12 10:46:40 -06:00
Ron Williams 165d674d98 Update redox_syscall 2025-06-12 09:46:19 -07:00
Yousef Moazzam 3d06dd39ba Refer to "node" rather than "file" in node mode docstrings 2025-05-24 21:56:30 +01:00
Yousef Moazzam 13642ab159 Clarify bit position using most/least significant bit terminology 2025-05-24 21:55:39 +01:00
Yousef Moazzam 204abb9c5d Add node type bits info to node mode docstrings 2025-05-24 21:22:54 +01:00
Jeremy Soller c403c1eb54 Update redox-scheme, implement flink and getdents 2025-05-03 13:27:33 -06:00
Jeremy Soller 940b027514 0.6.11 2025-04-20 18:52:21 -06:00
Jeremy Soller d17b938693 Merge branch 'Fix-reset_allocator-regression-incorrectly-returning-ENOSPC' into 'master'
Fix reset_allocator regression that incorrectly returns ENOSPC

See merge request redox-os/redoxfs!89
2025-04-20 21:54:10 +00:00
James Matlik fd0bb82b50 Fix reset_allocator regression that incorrectly returns ENOSPC 2025-04-20 17:43:08 -04:00
Jeremy Soller 3170e8cd9c Merge branch 'misc_improvements' into 'master'
Couple of misc improvements

See merge request redox-os/redoxfs!88
2025-04-01 20:01:31 +00:00
bjorn3 7e3915deb6 Use Iterator::all in two places 2025-04-01 21:53:28 +02:00
bjorn3 22f759aa4f Rustc now allows many derives for packed structs 2025-04-01 21:51:10 +02:00
bjorn3 dc507f7e2a Remove unreachable dup impl 2025-04-01 21:43:51 +02:00
bjorn3 5ee3138d3d Fix cargo test
Rustc complained about being unable to infer the type for []
2025-04-01 21:42:54 +02:00
Jeremy Soller 6bccac38c3 0.6.10: fix mount path on Redox 2025-03-25 15:45:15 -06:00
Jeremy Soller 32aeff0cae Merge branch 'Add-alloc-log-gc' into 'master'
Add alloc log GC

See merge request redox-os/redoxfs!86
2025-03-01 17:17:46 +00:00
James Matlik 1aa3142f21 Add alloc log GC 2025-03-01 17:17:46 +00:00
Jeremy Soller ec5f32ec38 0.6.9 2025-02-28 18:48:18 -07:00
Jeremy Soller 84cf28b5d0 Merge branch 'Fix-leaking-storage-on-file-delete' into 'master'
Fix leaking tree node on file delete

See merge request redox-os/redoxfs!85
2025-03-01 01:25:18 +00:00
James Matlik 9531b20bf8 Fix leaking tree node on file delete 2025-03-01 01:25:18 +00:00
Jeremy Soller 6abf533fdf Merge branch 'mark/fs-cleanup' into 'master'
Documentation & minor cleanup

See merge request redox-os/redoxfs!84
2024-12-24 14:26:19 +00:00
rm-dr a0c734da01 Documentation & minor cleanup 2024-12-24 14:26:19 +00:00
Jeremy Soller 90fbcab52e Merge branch 'document-mount' into 'master'
Document how to mount a RedoxFS partition

See merge request redox-os/redoxfs!83
2024-12-15 14:29:10 +00:00
Ribbon 356ec3ffb1 Document how to mount a RedoxFS partition 2024-12-15 14:29:10 +00:00
Jeremy Soller 9a0da927f9 Fix argument parsing 2024-11-04 21:24:41 -07:00
Jeremy Soller c2790a7168 Merge branch 'nodaemon' into 'master'
Added a no daemon option.

See merge request redox-os/redoxfs!82
2024-11-04 21:38:17 +00:00
JCake 947ea54411 No Daemon option 2024-11-02 21:30:58 +01:00
Jeremy Soller ee04ed2f8c 0.6.8 2024-10-29 07:52:42 -06:00
Jeremy Soller 8f6e47c1da Merge branch 'node_leak' into 'master'
Don't leak node block on node remove.

See merge request redox-os/redoxfs!81
2024-10-29 12:30:31 +00:00
Andrey Turkin c22fb790b8 Fix parallel test runs 2024-10-29 12:22:15 +03:00
Andrey Turkin 1d55c5aa6a Don't leak node block on node remove.
Refs #50
2024-10-29 10:17:47 +03:00
Jeremy Soller 59dbfee126 Merge branch 'fuse_fix' into 'master'
Fix a fuse bug

See merge request redox-os/redoxfs!80
2024-10-27 18:09:10 +00:00
Andrey Turkin 469db981a4 Fix a fuse bug
Create reply contains flags but those are not O_* passed in by request.
Those are FOPEN_* fuse-specific flags describing new file's mode (caching,
passthrough etc).
2024-10-27 20:45:25 +03:00
Jeremy Soller d8950b52c2 Merge branch 'change_blocklevel_calc' into 'master'
Changed for_bytes method for BlockLevel.

See merge request redox-os/redoxfs!79
2024-10-26 01:25:11 +00:00
James Francis 56e66993af Changed for_bytes method for BlockLevel. 2024-10-26 01:25:11 +00:00
Jeremy Soller ab371cff49 Update redox-scheme 2024-10-09 10:12:48 -06:00
Jeremy Soller 97ffcd4ad3 0.6.7 2024-10-06 11:07:32 -06:00
Jeremy Soller b9b740f3b0 Merge branch 'riscv' into 'master'
Bump dependencies

See merge request redox-os/redoxfs!78
2024-10-06 16:12:25 +00:00
Andrey Turkin 08361c6595 Bump dependencies 2024-10-06 18:58:27 +03:00
Jeremy Soller 3cf7bf58ab 0.6.6 2024-09-19 20:47:29 -06:00
4lDO2 707162611e Future-proof repr(packed) => repr(C, packed). 2024-09-08 21:06:03 +02:00
Jeremy Soller 5c8f22b069 Merge branch 'master' into 'master'
Clean up indentation using let-else and format.

See merge request redox-os/redoxfs!76
2024-09-05 13:53:15 +00:00
syn 43ec8d1417 Clean up indentation using let-else and format. 2024-09-02 00:46:48 +03:00
Jeremy Soller 2ac60da18d Merge branch 'fix' into 'master'
Fixed no_std regression in 0.6.5

See merge request redox-os/redoxfs!75
2024-07-15 14:49:34 +00:00
Andrey Turkin 3baf131419 Fixed no_std regression in 0.6.5
And bumped some dependencies and fixed some warnings
2024-06-29 20:52:24 +03:00
Jeremy Soller 554f3dc205 Merge branch 'fuzzer--fuse' into 'master'
Add redoxfs fuzzer for FUSE interface

See merge request redox-os/redoxfs!74
2024-06-27 16:05:42 +00:00
emaxx-g 6a6bd97910 Add redoxfs fuzzer for FUSE interface 2024-06-27 16:05:42 +00:00
Jeremy Soller 6c052ba241 0.6.5 2024-06-24 11:16:25 -06:00
Jeremy Soller af3032de26 Merge branch 'schemev2plus' into 'master'
Use positioned IO interfaces both for the scheme itself and disk IO

See merge request redox-os/redoxfs!73
2024-06-14 12:46:51 +00:00
4lDO2 4f2b5e65ee Update dependencies. 2024-06-14 14:32:47 +02:00
4lDO2 5174b2bba6 Use FileExt::{read,write}_at for disk IO. 2024-06-13 17:20:34 +02:00
4lDO2 708cc1c5ae Use SYS_READ2 and SYS_WRITE2 for disk IO. 2024-06-13 16:30:13 +02:00
4lDO2 8a31d1b6a6 Use positioned IO scheme interface. 2024-06-12 17:37:06 +02:00
4lDO2 3800bf1510 Switch to v2 packet interface. 2024-06-11 16:10:34 +02:00
Jeremy Soller 136ff8c8dc Update dependencies 2024-05-27 09:40:10 -06:00
Jeremy Soller faec36773b 0.6.4 2024-05-27 09:35:10 -06:00
Jeremy Soller 247ef8bf62 Merge branch 'endian-num' into 'master'
Migrate from `redox_simple_endian` to `endian-num`

See merge request redox-os/redoxfs!72
2024-05-27 15:34:24 +00:00
Martin Kröning 8cc2e31299 Migrate from redox_simple_endian to endian-num
Signed-off-by: Martin Kröning <martin.kroening@eonerc.rwth-aachen.de>
2024-05-27 14:13:51 +02:00
Jeremy Soller dcba5d8018 0.6.3: Fix CoW of records dropping data 2024-04-01 13:30:31 -06:00
Jeremy Soller 7dc8323d24 0.6.2: Make libredox only required for std feature 2024-03-23 09:15:06 -06:00
Jeremy Soller e8cea1af66 0.6.1: fix read_records block use after free 2024-03-23 08:52:54 -06:00
Jeremy Soller bfa2f33798 Merge branch 'libredox' into 'master'
Switch almost fully to libredox.

See merge request redox-os/redoxfs!71
2024-03-18 20:37:13 +00:00
4lDO2 15f3c909c7 Switch almost fully to libredox. 2024-03-18 17:28:43 +01:00
Jeremy Soller 1713850c0e Merge branch 'cancellation' into 'master'
Handle but don't impl scheme cancellation.

See merge request redox-os/redoxfs!70
2024-03-16 13:06:10 +00:00
4lDO2 efb29941f5 Handle but don't impl scheme cancellation. 2024-03-16 09:30:14 +01:00
Jeremy Soller 14a07dc268 Merge branch 'master' into 'master'
Master - Name check & Duplicate code removed & Makefile modification

See merge request redox-os/redoxfs!68
2024-03-07 14:40:28 +00:00
George Malandrakis e4ca510bc0 Master - Name check & Duplicate code removed & Makefile modification 2024-03-07 14:40:27 +00:00
Jeremy Soller 7c6e95d9f0 Merge branch 'path-improvements' into 'master'
use new path formatting and conversion code

See merge request redox-os/redoxfs!69
2024-03-01 14:59:34 +00:00
Ron Williams 5156ebd179 use new path formatting and conversion code 2024-03-01 06:24:13 -08:00
Jeremy Soller e7cf569a1f Merge branch 'v6' into 'master'
V6

See merge request redox-os/redoxfs!67
2024-02-15 23:03:38 +00:00
Jeremy Soller c07e46b7ba V6 2024-02-15 23:03:38 +00:00
Jeremy Soller 667ffcc01a Fix test compilation 2024-02-14 12:59:16 -07:00
Jeremy Soller 07e8093ee0 Allow allocating in different sizes 2024-02-14 12:53:06 -07:00
Jeremy Soller ab0bb9aec7 Fix more clippy lints 2024-02-14 12:45:19 -07:00
Jeremy Soller 644a2d2081 Fix clippy lints 2024-02-14 12:38:02 -07:00
Jeremy Soller 4e0ab54567 Make everything scale with block size and format 2024-02-14 10:15:26 -07:00
Jeremy Soller 5f00068c26 Merge branch 'redox-path' into 'master'
Use new path format

See merge request redox-os/redoxfs!66
2024-02-09 15:06:51 +00:00
Ron Williams c1601fe16c Use new path format 2024-02-09 15:06:51 +00:00
Jeremy Soller e3f0fe6ef9 Remove mmap prints 2024-02-08 17:34:20 -07:00
Jeremy Soller b3dd3aa5f0 Merge branch 'symlink-fix' into 'master'
fix symbolic links plus general cleanup

See merge request redox-os/redoxfs!65
2024-01-03 16:39:58 +00:00
Ron Williams 944b175a6e fix symbolic links plus general cleanup 2024-01-03 16:39:58 +00:00
Jeremy Soller 86c12ebd7c 0.5.13: Remove failure dependency and fix no_std 2023-12-12 13:24:59 -07:00
Jeremy Soller 2bec291700 0.5.12: Make range-tree optional 2023-12-12 13:16:31 -07:00
Jeremy Soller 9fad87efbe 0.5.11 2023-12-12 13:07:57 -07:00
Jeremy Soller dcf40072f4 Ensure that dealloc of allocator log blocks are queued, not immediate 2023-12-12 13:07:10 -07:00
Jeremy Soller 9a8437f468 0.5.10: Squash allocations and sync on unmount 2023-12-12 11:10:04 -07:00
Jeremy Soller 97bc669a76 0.5.9: Remove use of stable feature int_roundings 2023-12-11 10:26:27 -07:00
Jeremy Soller 50b4ebfcd5 Merge branch 'fix-statement' into 'master'
Fix GPL statement and quote FUSE for Linux

See merge request redox-os/redoxfs!64
2023-12-07 15:40:25 +00:00
Ribbon 3f1bff26e3 Fix GPL statement and quote FUSE for Linux 2023-12-07 15:40:25 +00:00
Jeremy Soller 6ce9d1fdac Merge branch 'remove_physmap' into 'master'
Replace physmap with fmap.

See merge request redox-os/redoxfs!63
2023-12-07 15:37:06 +00:00
4lDO2 2c2920572c Replace physmap with fmap. 2023-11-18 13:24:27 +01:00
Jeremy Soller ae715277f3 Cache current fmap size 2023-11-15 12:20:15 -07:00
Jeremy Soller 4048f541a0 Merge branch 'improve-readme' into 'master'
Improve/cleanup the README

See merge request redox-os/redoxfs!62
2023-09-30 17:38:07 +00:00
Ribbon b9960350c8 Improve/cleanup the README 2023-09-30 15:44:24 +00:00
Jeremy Soller 985d6e815c 0.5.8: update uuid to 1.4 2023-09-07 16:19:42 -06:00
Jeremy Soller 54341680ec Merge branch 'better_fmap' into 'master'
Offset-based mmap

See merge request redox-os/redoxfs!61
2023-08-07 15:37:15 +00:00
4lDO2 6234d76e8d Fix mremap usage. 2023-08-03 12:38:19 +02:00
4lDO2 7f8a2e467e Update dependencies. 2023-08-02 15:28:35 +02:00
4lDO2 ae1f15a449 Offset-based file mmap. 2023-07-26 16:06:53 +02:00
4lDO2 5db0da52e2 WIP: Use improved mmap interface. 2023-07-20 16:58:58 +02:00
Jeremy Soller e7e0e3ce8d Merge branch 'sighandling' into 'master'
Prefer libc over redox_syscall in mount.

See merge request redox-os/redoxfs!60
2023-06-21 19:27:43 +00:00
4lDO2 58fff36b4e Prefer libc over redox_syscall in mount. 2023-06-18 19:41:08 +02:00
Jeremy Soller 211e12b842 Merge branch 'master' into 'master'
Fix unnecessary mutable variable warning

See merge request redox-os/redoxfs!59
2023-05-08 12:37:33 +00:00
Will Angenent 13948965fa Fix unnecessary mutable variable warning 2023-05-08 13:11:16 +01:00
Jeremy Soller ad432d527d 0.5.7 - enable compilation with stable 2023-04-15 07:34:34 -06:00
Jeremy Soller 2db6998889 Merge commit '588bd3203b70219bf27bedf0703199ec2ea5f505' 2023-04-15 07:32:11 -06:00
Jeremy Soller eb8dc00fa8 Add cfg(kani) to proofs 2023-04-06 14:48:54 -06:00
Jeremy Soller 1a56983249 Formally verify node owner and permission functions 2023-04-05 20:58:51 -06:00
Jeremy Soller 588bd3203b 0.5.6 2023-03-27 08:45:41 -06:00
Jeremy Soller c9f9e0be7d Merge branch 'use-fusermount3-where-possible' into 'master'
Fix image unmounting & tooling to work under gentoo and MacOS

See merge request redox-os/redoxfs!58
2023-03-25 13:55:49 +00:00
Will Angenent 20fff1a0ee Adapted Makefile to work with gentoo and MacOS 2023-03-24 19:19:33 +00:00
Will Angenent a7c22caba5 Added unmount_path method that tries several fusermount binaries
This fixes unmounting with gentoo. This function will be use by the installer to
unmount the image after the build.
2023-03-24 18:54:08 +00:00
Jeremy Soller ace1726e29 0.5.5 2023-03-20 10:23:14 -06:00
Jeremy Soller 9a4638e922 Merge branch 'migrate-from-fuse-to-fuser' into 'master'
Migrate from fuse to fuser

See merge request redox-os/redoxfs!57
2023-03-20 01:15:13 +00:00
Will Angenent 62e59e1006 Migrate from fuse to fuser 2023-03-18 08:53:54 +00:00
Jeremy Soller cdccd5d786 0.5.4 2023-03-08 12:48:42 -07:00
Jeremy Soller 06d43016c1 Ensure header generation zero is written on filesystem create 2023-03-08 12:48:32 -07:00
Jeremy Soller 794e732c80 Merge branch 'fix_root_unlink' into 'master'
Fix bug where root could not remove other users' files

Closes redox#1366

See merge request redox-os/redoxfs!56
2023-03-01 16:28:58 +00:00
4lDO2 73c3d8bb79 Allow root to remove other users' files. 2023-03-01 17:24:37 +01:00
Jeremy Soller 467c9cfe9a Update libc crate 2023-02-11 14:44:07 -07:00
Jeremy Soller 8a1aa3663a Allow removal of temporary directory hardlinks
These will only exist in the middle of rename
2022-12-01 07:32:19 -07:00
Jeremy Soller f601b2a8ce 0.5.3 - make log optional 2022-11-07 18:12:03 -07:00
Jeremy Soller 63bde95462 Update lock file and fix import 2022-11-04 14:05:32 -06:00
Jeremy Soller 82f011363d 0.5.2 2022-11-04 14:04:16 -06:00
Jeremy Soller 898f9d8b2e redoxfs-ar: require disk to exist 2022-11-04 14:03:58 -06:00
Jeremy Soller 9fc80c8bf6 Update cargo.lock 2022-09-05 08:58:06 -06:00
Jeremy Soller 7de07e802e 0.5.1 - add DiskIo type 2022-09-04 20:27:16 -06:00
Jeremy Soller 790ea59269 Increase disk cache to 16 MiB again, it is pre-allocated now 2022-08-22 09:22:09 -06:00
Jeremy Soller 04b4a2e4ad Reduce disk cache size to 8MiB 2022-08-22 09:18:23 -06:00
4lDO2 63468fc414 Replace syscall::clone() with libc::fork() 2022-07-27 16:34:43 +02:00
Jeremy Soller 0ea2fd49dd Update Cargo.lock 2022-07-26 16:14:53 -06:00
Jeremy Soller 0e0ae520c5 Reduce disk cache size to 16 MiB 2022-04-13 09:03:03 -06:00
Jeremy Soller 9215e32254 Implement frename 2022-04-07 19:57:09 -06:00
Jeremy Soller 419bd0f0a6 Fix for some kinds of symbolic links 2022-04-06 18:17:23 -06:00
4lDO2 ebe9579d66 Use crates.io version of syscall, again. 2022-03-27 12:09:50 +02:00
4lDO2 d83e567e97 Update syscall 2022-03-25 15:29:16 +01:00
Jeremy Soller f9f4f7c5f0 Use redox_simple_endian fork 2022-03-11 09:09:11 -07:00
Jeremy Soller 2e46c28097 Merge branch '0.5.0' into 'master'
RedoxFS 0.5.0

See merge request redox-os/redoxfs!54
2022-03-11 16:02:22 +00:00
Jeremy Soller 731b97262b RedoxFS 0.5.0
Significant re-design to use copy-on-write and hashes for all data
2022-03-11 09:01:47 -07:00
Jeremy Soller c98e63af87 Improve speed by not enumerating all disks when opening by UUID 2022-02-11 10:07:40 -07:00
Jeremy Soller cfe5841646 0.4.4 - no_std support 2022-02-05 20:28:11 -07:00
Jeremy Soller 9083dd07c9 Fix warnings and remove dead code 2021-12-26 19:47:44 -07:00
4lDO2 25be3a82bd Update syscall. 2021-06-17 14:38:08 +02:00
Jeremy Soller 6d9a53c956 Update dependencies 2021-04-28 20:54:39 -06:00
Jeremy Soller c6b997f98b Update dependencies 2021-01-19 11:12:59 -07:00
Jeremy Soller 5d4ed0831a 0.4.3 - update redox_syscall 2020-12-16 10:43:30 -07:00
Jeremy Soller f1b88c38c0 Update syscall 2020-08-02 15:34:01 -06:00
Jeremy Soller 0068a6d498 Update dependencies 2020-05-25 07:42:45 -06:00
Jeremy Soller 5ff37f1f77 Update lock file 2020-03-13 20:06:34 -06:00
Jeremy Soller c990fa9a45 0.4.1 - Support rename in fuse 2020-03-13 20:06:01 -06:00
Jeremy Soller 80e3a34645 Merge branch 'format' into 'master'
Format the codebase

See merge request redox-os/redoxfs!49
2020-01-20 22:06:34 +00:00
Xavier L'Heureux 58f2d28ff5 Format the codebase 2020-01-20 15:00:26 -05:00
Jeremy Soller a6af6a9289 Merge branch 'add-atime' into 'master'
Add tracking of access times

See merge request redox-os/redoxfs!48
2020-01-20 16:56:51 +00:00
AdminXVII d2256b3eb3 Add tracking of access times
Add the atime field, which tracks the last access time. Only reads
update the atime.

**BREAKING CHANGE**: One less extent available per block, 4 more bytes
for node names.
2020-01-20 16:56:51 +00:00
Jeremy Soller 3191d7d182 Add argument to locate redoxfs by block 2019-10-28 20:27:53 -06:00
Jeremy Soller bede93cd85 0.3.7 2019-09-08 20:29:55 -06:00
Jeremy Soller e869b3b158 Merge branch 'update_path' into 'master'
Update path after frename

See merge request redox-os/redoxfs!46
2019-08-11 16:06:44 +00:00
Deepak Sirone 924adef1dc Update path after frename 2019-08-11 15:30:09 +05:30
Jeremy Soller 602a63acf6 Return EXDEV to tell kernel to resolve cross-scheme links 2019-07-23 21:05:54 -06:00
Jeremy Soller 43533e8dc7 Merge branch 'jD91mZM2/redoxfs-master' into HEAD 2019-07-20 19:41:48 -06:00
Jeremy Soller bf4d0161be Merge branch 'tests' into 'master'
Tests

See merge request redox-os/redoxfs!45
2019-07-20 17:08:33 +00:00
Jeremy Soller 4578a22297 Tests 2019-07-20 17:08:33 +00:00
Jeremy Soller 5e5656f110 Make mount points not require an extra reference 2019-07-18 21:36:16 -06:00
Jeremy Soller 86b0d612d8 Fix yaml again 2019-07-06 11:39:29 -06:00
Jeremy Soller 61c38afb52 Fix test:redox dependencies 2019-07-06 11:39:08 -06:00
Jeremy Soller 8bbdc558bb Fix yaml 2019-07-06 11:38:35 -06:00
Jeremy Soller 2ddf1dab3f Update CI configuration to use redoxer 2019-07-06 11:37:19 -06:00
Jeremy Soller 8e38015b2f Set libc to 0.2.54 2019-07-06 11:27:48 -06:00
Jeremy Soller 3872f74e9a 0.3.6 - Accept AsRef<Path> for disk path arguments 2019-07-06 08:21:23 -06:00
Jeremy Soller 39e8b8fa7f 0.3.5 - make archive_at public 2019-07-06 08:07:54 -06:00
Jeremy Soller b26195f49a 0.3.4 - expose archiving functionality 2019-07-04 15:46:12 -06:00
Jeremy Soller a70a01bd4a 0.3.3 2019-05-11 12:27:52 -06:00
Jeremy Soller a226d19096 Implement symlinks and fix truncation 2019-05-11 12:26:01 -06:00
Jeremy Soller 7b661e31e3 Remove unused ctime parameter 2019-05-11 11:51:46 -06:00
Jeremy Soller 966105e566 Copy uid, gid, ctime, and mtime 2019-05-11 11:50:19 -06:00
Jeremy Soller 237c156503 Read permissions from original file 2019-05-11 11:46:49 -06:00
Jeremy Soller 946efa2fb5 Add sparse disk file to not require image to be sized initially. 2019-05-08 21:07:21 -06:00
Jeremy Soller 0754019950 Truncate disk image 2019-05-08 20:55:37 -06:00
Jeremy Soller 842744c384 Merge branch 'master' of https://gitlab.redox-os.org/redox-os/redoxfs 2019-05-08 20:35:15 -06:00
Jeremy Soller 2e32481d4c Add redoxfs-ar program to create a redoxfs image from a folder 2019-05-08 20:34:20 -06:00
Jeremy Soller ec072bb718 Return correct error for fevent on files 2019-04-28 13:32:43 -06:00
Jeremy Soller 015d352e33 Fixes for Redox when part of unix target family 2019-04-23 13:55:46 -06:00
Jeremy Soller 219bf26966 Update for new rust 2019-04-07 10:49:56 -06:00
Jeremy Soller ff17fed199 Reimplement fmap/funmap using improvements to kernel 2019-04-06 20:11:51 -06:00
Jeremy Soller cb2bcf9f59 Fix redoxfs fmap using incorrect offset 2019-04-02 20:18:40 -06:00
Jeremy Soller a352d7c79c Merge branch 'add-getuuid' into 'master'
redoxfs: Add '--get-uuid' support

See merge request redox-os/redoxfs!44
2019-03-15 19:21:32 +00:00
Robin Randhawa 37aab3e566 redoxfs: Add '--get-uuid' support
With this, `redoxfs --get-uuid <disk>` prints out the UUID of the
provided redoxfs disk image.

This is needed especially for aarch64 kernels where the UUID of the boot
disk needs to be passed to the kernel by the bootloader. With this
extension to the mount command, the kernel build glue can query the
prepared redoxfs image for it's UUID.
2019-03-15 15:48:25 +00:00
Jeremy Soller b4df131aff Fix disk usage calculation 2019-02-24 20:46:21 -07:00
Jeremy Soller b32a421b97 Correct access control 2018-12-30 09:58:58 -07:00
Jeremy Soller 69d6cc05de Update fmap support 2018-12-30 09:47:33 -07:00
Jeremy Soller 3ae32d9901 Reduce diff - remove dirty flag as it could be added in a separate change 2018-12-08 08:47:28 -07:00
Jeremy Soller d53fc6afff Handle sigterm instead of sigkill 2018-12-08 08:18:21 -07:00
Jeremy Soller 87ecce9e44 Cleanup and use unmount flag inside of Redox scheme processing function 2018-12-08 08:10:23 -07:00
Jeremy Soller 3e1fc9dae5 Merge branch 'master' into liamnprg/redoxfs-master 2018-12-08 07:40:28 -07:00
Liam Naddell f96d330993 wip implementation of unmounting and dirty bits for redoxfs 2018-12-06 11:09:41 -05:00
Jeremy Soller 9c6e0e6789 Update Cargo.lock 2018-10-14 19:56:46 -06:00
jD91mZM2 79cef7c034 Adopt linux' O_APPEND behavior
According to `man open`:

> The file is opened in append mode. Before each write(2), the file offset
is positioned at the end of the file, as if with lseek(2). The modi‐
fication of the file offset and the write operation are performed as a
single atomic step.
2018-10-07 15:22:13 +02:00
Jeremy Soller 6f5ca24838 Merge branch 'master' into 'master'
A little bit more documentation

See merge request redox-os/redoxfs!38
2018-08-01 23:06:00 +00:00
Liam Naddell 6126bfeee3 docs 2018-08-01 18:47:18 -04:00
Jeremy Soller 3ef8557865 Merge branch 'master' into 'master'
Fmaps: Fix bug with syncing

See merge request redox-os/redoxfs!36
2018-07-03 13:23:34 +00:00
Jeremy Soller 3906a90a7b Merge branch 'mggmuggins/ci' into 'master'
Gitlab CI

See merge request redox-os/redoxfs!37
2018-07-02 18:32:30 +00:00
SamwiseFilmore f0450a4564 Gitlab CI 2018-07-02 18:32:30 +00:00
jD91mZM2 53a3f6d7e9 Fmaps: Fix bug with syncing 2018-06-18 20:21:44 +02:00
Jeremy Soller e9a7181e9f Merge branch 'master' into 'master'
fmap support

See merge request redox-os/redoxfs!35
2018-06-18 10:00:21 +00:00
jD91mZM2 8c57fd95e7 Fix bugs & increase page size 2018-06-18 10:33:08 +02:00
jD91mZM2 a1c69a8e82 WIP: fmap support 2018-06-17 18:40:09 +02:00
Jeremy Soller a345132e0f Update links to gitlab 2018-06-12 12:30:45 -06:00
Jeremy Soller 72b0492624 Prevent colons from being used in filenames 2018-05-24 08:43:05 -06:00
Jeremy Soller 78c5e1c6d2 Enter null namespace for redoxfs 2018-01-20 19:37:06 -07:00
Jeremy Soller 40e2a3e221 Implement frename fixing #37 and fix #11 by checking names 2017-12-29 14:08:36 -07:00
Jeremy Soller 2e6e272c72 Add stub for frename 2017-12-27 21:09:05 -07:00
Jeremy Soller f206dab653 Fix error with borrowing packed fields 2017-12-06 20:12:02 -07:00
Jeremy Soller eac37c7dc5 Add fchmod, fchown 2017-11-29 21:05:17 -07:00
Jeremy Soller 38b24ad321 0.3.2 2017-11-19 21:22:44 -07:00
Jeremy Soller 53a1e6d49f Move cache to HashMap 2017-11-19 21:00:07 -07:00
Jeremy Soller 16d6f6b93a Simplify disk cache 2017-11-19 20:51:12 -07:00
Jeremy Soller 2dd4c920c7 0.3.1 - fix issue with new FUSE 2017-11-19 16:22:35 -07:00
Jeremy Soller 1701198b11 0.3.0 2017-11-19 16:18:27 -07:00
Jeremy Soller 47c9acc417 Version 3 - bump block size to 4096 2017-11-19 16:18:06 -07:00
Jeremy Soller 513bfaf805 Set block size programatically 2017-11-19 16:15:01 -07:00
Jeremy Soller a0ec667025 Update lock format 2017-11-19 16:04:16 -07:00
Jeremy Soller ab690ce519 Update Cargo.lock 2017-10-11 20:54:45 -06:00
Jeremy Soller d4ea343a39 Fix disk scheme path 2017-09-30 20:34:34 -06:00
Jeremy Soller e74975d2f9 Add UUID matching 2017-09-30 20:24:59 -06:00
Jeremy Soller db22937361 Implement UUID 2017-09-30 15:07:46 -06:00
Jeremy Soller 6d62d22262 Fix offset calculations 2017-09-27 21:15:27 -06:00
Jeremy Soller cbdfba7b94 Allow a bootloader to be written during redoxfs-mkfs 2017-09-27 20:27:38 -06:00
Jeremy Soller 90dc177d75 Add docs.rs badge 2017-09-26 19:57:06 -06:00
Jeremy Soller ade6234f12 Update spin 2017-09-26 19:56:14 -06:00
Jeremy Soller dc0b0c5c2d Fix compilation on Redox 2017-09-26 19:55:10 -06:00
Jeremy Soller dfd2dd8ae1 Cleanup fuse backend, send signal at correct time 2017-09-26 19:36:27 -06:00
Jeremy Soller 260bc1e1e8 Fix cargo doc 2017-09-26 19:17:49 -06:00
Jeremy Soller 541a58181a Update FUSE 2017-09-25 21:50:07 -06:00
Jeremy Soller 070aeb54ef Cleanup to allow use of most functions when linking the library, including mounting and creating filesystems 2017-09-25 19:11:02 -06:00
Jeremy Soller b272cc2d19 Use nightly friendly spin 2017-09-18 22:01:58 -06:00
Jeremy Soller 9c907a22dd Merge pull request #34 from ids1024/import
Remove unused import causing lint
2017-09-11 12:50:22 -06:00
Ian Douglas Scott a9da8e7a57 Remove unused import causing lint 2017-09-11 10:22:27 -07:00
Jeremy Soller 8f41e5a777 Update Cargo.lock 2017-08-19 14:46:42 -06:00
Jeremy Soller 213b4aea5f Merge pull request #33 from ids1024/utimens
futimens: do not require O_RWONLY/O_RDWR
2017-08-14 17:59:00 -06:00
Ian Douglas Scott dc5bd70728 futimens: do not require O_RWONLY/O_RDWR 2017-08-14 16:35:03 -07:00
Jeremy Soller fc0af50b97 Merge pull request #32 from ids1024/directoryperm
Make it not an error to open a directory without O_DIRECTORY or O_STAT
2017-08-12 13:29:09 -06:00
Ian Douglas Scott f4a3e1dde0 Make read() on directory not opened as O_DIRECTORY return EISDIR 2017-08-12 12:25:36 -07:00
Ian Douglas Scott f474a9fd77 Actually, O_RDONLY should work with a directory 2017-08-12 11:32:39 -07:00
Ian Douglas Scott 93f7ec7a55 Make it not an error to open a directory without O_DIRECTORY or O_STAT
POSIX says this should work, and some software seems to rely on that.
2017-08-11 18:32:49 -07:00
Jeremy Soller 801cbd9072 Merge pull request #31 from ids1024/mut
Remove unneeded uses of 'mut'
2017-08-11 16:21:43 -06:00
Ian Douglas Scott 4f3b097a4a Remove unneeded uses of 'mut'
This seems to be a warning with the latest Rust, and an error due to #![deny(warnings)]
2017-08-11 14:55:19 -07:00
Jeremy Soller a830383220 Update Cargo.lock 2017-08-02 19:52:30 -06:00
Jeremy Soller e270ce117f Merge pull request #30 from ids1024/free
Avoid corrupting free node
2017-08-02 07:27:10 -06:00
Ian Douglas Scott 0996d844c4 Avoid corrupting free node 2017-08-01 20:27:43 -07:00
Jeremy Soller 1e9cec6092 Typo 2017-08-01 18:26:58 -06:00
Jeremy Soller e95be4caf3 Dealocate when node size changes 2017-08-01 18:23:45 -06:00
Jeremy Soller 3b78720167 Update Cargo.lock 2017-07-29 08:25:06 -06:00
Jeremy Soller b04efe081b Merge pull request #29 from ids1024/dirlink
Directory symlinks
2017-07-26 19:05:59 -06:00
Ian Douglas Scott 03f59a419b Make directory symlinks work correctly 2017-07-26 16:17:49 -07:00
Ian Douglas Scott ca1a1d615a Move symlink resolving to another function 2017-07-26 16:17:48 -07:00
Jeremy Soller c6c9399305 Merge branch 'master' of https://github.com/redox-os/redoxfs 2017-07-26 07:46:49 -06:00
Jeremy Soller 862e9b0a37 Add Cargo.lock 2017-07-26 07:46:45 -06:00
Jeremy Soller 154b456d99 Merge pull request #28 from ids1024/mtimefuse
fuse: allow setting mtime to earlier time
2017-07-25 17:32:05 -06:00
Ian Douglas Scott 5a1aaadd05 fuse: allow setting mtime to earlier time 2017-07-25 15:17:15 -07:00
Jeremy Soller 8737f19459 Merge pull request #27 from ids1024/readdir
fuse: Fix readdir when directory does not fit in buffer
2017-07-25 12:17:46 -06:00
Ian Douglas Scott 92d38e32c1 fuse: Fix readdir when directory does not fit in buffer 2017-07-25 08:59:35 -07:00
Jeremy Soller e635648ed7 Use EINVAL instead of ENOENT 2017-07-22 13:14:31 -06:00
Jeremy Soller f40f6921e8 Return error if dup buf is not empty 2017-07-22 13:08:08 -06:00
Jeremy Soller 3342778968 Merge pull request #26 from ids1024/unlinkuid
Require same uid as owner to unlink, not write permission
2017-07-16 18:52:21 -06:00
Ian Douglas Scott ad4826eb70 Require same uid as owner to unlink, not write permission 2017-07-16 15:43:59 -07:00
Jeremy Soller 7fc1f16a67 Merge pull request #25 from ids1024/o_creat
Do not fail due to file permissions on creation
2017-07-15 08:08:43 -06:00
Ian Douglas Scott cae1402f5a Do not fail due to file permissions on creation
Matches Posix.
2017-07-15 07:01:21 -07:00
Jeremy Soller bc2e8c2923 Merge pull request #24 from ids1024/fcntl
Make fcntl not clobber access mode
2017-07-12 14:41:33 -06:00
Ian Douglas Scott 8c03929973 Make fcntl not clobber access mode 2017-07-12 13:39:26 -07:00
Jeremy Soller 6965d67efd Remove unused import 2017-07-09 18:51:11 -06:00
Jeremy Soller 1549d1af8b Use second argument as mtime 2017-07-09 18:07:00 -06:00
Jeremy Soller 8d66d9ce54 Add futimens 2017-07-09 15:54:03 -06:00
Jeremy Soller 1f97d8220c Add times to stat 2017-07-09 10:25:26 -06:00
Jeremy Soller 20bce53b7a Fix build on Redox 2017-07-09 10:21:20 -06:00
Jeremy Soller 9471a2e410 WIP: Add mtime/ctime to Redox 2017-07-09 10:15:41 -06:00
Jeremy Soller dcce0d9e6e Add associated consts 2017-07-08 19:24:34 -06:00
Jeremy Soller 4820210cca Merge pull request #22 from ids1024/O_NOFOLLOW
Follow symlinks on O_STAT, unless O_NOFOLLOW is passed
2017-07-04 08:17:11 -06:00
Ian Douglas Scott 0d12413297 Follow symlinks on O_STAT, unless O_NOFOLLOW is passed 2017-07-03 19:23:23 -07:00
Jeremy Soller 2c770ca997 Merge pull request #21 from ids1024/unlink
Support unlink() on symlink
2017-07-03 05:25:47 -06:00
Ian Douglas Scott 3e0ce05e23 Support unlink() on symlink 2017-07-02 19:16:14 -07:00
Jeremy Soller 0032159492 Merge pull request #20 from ids1024/fuse
Symlinks in fuse
2017-06-30 12:36:19 -06:00
Ian Douglas Scott f582c60f34 Symlinks in fuse 2017-06-30 11:24:53 -07:00
Jeremy Soller 97652cc6f8 Merge pull request #19 from ids1024/symlink
Return EINVAL for O_SYMLINK on non-symlink
2017-06-29 16:52:12 -06:00
Ian Douglas Scott dffa85f874 Return EINVAL for O_SYMLINK on non-symlink 2017-06-29 14:55:49 -07:00
Jeremy Soller b972f08f50 Merge pull request #18 from ids1024/symlink
[WIP] Symlinks
2017-06-29 15:46:52 -06:00
Ian Douglas Scott 0d76e1441b Relative symlink 2017-06-29 14:08:53 -07:00
Ian Douglas Scott 7a4a41a34e Work in progress symlink implementation 2017-06-26 09:14:12 -07:00
Jeremy Soller 7470cc7846 Merge pull request #17 from ids1024/seek
Allow seek beyond end of file.
2017-06-02 20:23:04 -06:00
Ian Douglas Scott 86353cdff5 Allow seek beyond end of file.
This is enough to fix as. I don't know if it correctly fills the space
with zeros when writing beyond the current EOF.
2017-06-02 19:00:54 -07:00
Jeremy Soller 34f9b3f6df Merge pull request #16 from ids1024/try
Use ? instead of try!
2017-05-29 22:07:21 +02:00
Ian Douglas Scott 7d2663e6a2 Also initialize Stat to zero in implementation of Resource
I didn't notice there were two copies of this code
2017-05-28 23:26:57 -07:00
Ian Douglas Scott 1f90f21147 Use ? instead of try! 2017-05-28 23:06:45 -07:00
Jeremy Soller 05e3802844 Merge pull request #15 from ids1024/stat
Initialize fields of Stat struct to 0
2017-05-28 18:50:11 +02:00
Ian Douglas Scott 31cfde4eaf Use Default::default() 2017-05-28 09:17:36 -07:00
Ian Douglas Scott 138525cf7d Initialize fields of Stat struct to 0
Prevents potential undefined behavior based on how the call is made. Of
course, eventually these fields should actually be set.
2017-05-28 09:10:52 -07:00
Jeremy Soller 6c1d98cea8 Merge pull request #14 from bungcip/travis_macos_error
fix travis macos build
2017-04-26 17:15:15 -06:00
Gigih Aji Ibrahim 6642d36079 trying to fix travis macos build fail. don't know if this work or not. I don't have mac to test it :) 2017-04-27 05:32:20 +07:00
Jeremy Soller f0c38fe609 Perform cloexec in kernel 2017-04-16 12:52:07 -06:00
Jeremy Soller f418d30dc9 Add O_APPEND functionality 2017-04-09 22:09:29 -06:00
Jeremy Soller d0dfef2f0b Fix bug in seek 2017-04-09 22:00:26 -06:00
Jeremy Soller 3db0c2a4b1 Fix handling of missing files 2017-03-15 19:42:11 -06:00
Jeremy Soller aff13c0d66 Merge pull request #8 from xTibor/formatting_and_docs_fixes
Small formatting and documentation fixes
2017-02-13 19:47:33 -07:00
xTibor 5dc6c48623 Small formatting and documentation fixes 2017-02-03 00:04:32 +01:00
Jeremy Soller fa6c19f79b Add inode number to redoxfs 2017-01-30 20:44:09 -07:00
Jeremy Soller 665120f063 Better error messages 2017-01-13 10:44:11 -07:00
Jeremy Soller 3f0bda4e14 Use crates.io for dependencies 2017-01-13 10:28:44 -07:00
Jeremy Soller 5ab8c050d8 Fix for build on OS X 2016-12-13 18:58:59 -07:00
Jeremy Soller d0c698a031 Merge pull request #7 from redox-os/merge_fuse_scheme
Merge fuse and scheme, add mkfs utility, remove old utility
2016-12-13 18:05:24 -07:00
Jeremy Soller 9c2864e130 Merge fuse and scheme, add mkfs utility, remove old utility 2016-12-13 18:05:03 -07:00
Jeremy Soller f1073d0c48 Add leading / to path 2016-11-29 18:13:56 -07:00
Jeremy Soller b526a29e35 Enable caching again 2016-11-28 14:31:00 -07:00
Jeremy Soller 25fe6d3826 Implement O_DIRECTORY 2016-11-25 18:24:15 -07:00
Jeremy Soller dcaf5c76be Add statvfs 2016-11-18 08:42:48 -07:00
Jeremy Soller 31605167ce Add chmod 2016-11-15 17:07:37 -07:00
Jeremy Soller 8a22d96669 Minimal O_CLOEXEC implementation 2016-11-15 16:11:43 -07:00
Jeremy Soller 8449c5b6ad Switch to using fork 2016-11-10 20:01:30 -07:00
Jeremy Soller d058e56d6e Fix access modes 2016-11-08 10:52:27 -07:00
Jeremy Soller 81c39551d7 Update .travis.yml 2016-11-06 18:37:52 -07:00
Jeremy Soller fd4d674422 Merge pull request #4 from genodeftest/patch-3
Travis: fix config (on Linux)
2016-11-05 09:55:17 -06:00
Christian Stadelmann a313852428 Travis: fix config (on Linux)
With this change, travis will work on Linux at least.
2016-11-05 16:46:34 +01:00
Jeremy Soller b25b268756 Fix syscall crate name 2016-11-02 19:46:34 -06:00
Jeremy Soller a0b381765b Use defer permissions instead 2016-11-01 15:33:49 -06:00
Jeremy Soller 3a58139e57 Only add allow_root on OS X 2016-11-01 15:08:08 -06:00
Jeremy Soller 14cbb4f4f8 Merge pull request #3 from Yoric/master
Let root manipulate a fuse-mounted redoxfs vfs.
2016-11-01 14:29:30 -06:00
David Teller 0353bfc00c Let root manipulate a fuse-mounted redoxfs vfs.
This (along with a few Makefile changes) solves my issues building on macOS.
2016-11-01 21:21:20 +01:00
Jeremy Soller a5b6dcc0c0 Disable cache to improve performance 2016-10-31 21:56:35 -06:00
Jeremy Soller 4ecb62da40 Build redoxfs using syscall git 2016-10-31 13:27:35 -06:00
Jeremy Soller 5c0fc80db2 Merge pull request #2 from redox-os/new_kernel
New kernel
2016-10-26 13:21:48 -06:00
Jeremy Soller ec00f58d73 Add buf to dup 2016-10-25 12:16:28 -06:00
Jeremy Soller 3dcaad55fe Implement unix permissions 2016-10-05 18:00:46 -06:00
Jeremy Soller c06edb232b Lookup uid, gid, and mode from block for stat 2016-10-05 14:07:31 -06:00
Jeremy Soller 33f218aadb WIP: Permissions support 2016-10-05 12:13:45 -06:00
Jeremy Soller 1488d1ef56 64-bit stat size 2016-09-28 20:41:45 -06:00
Jeremy Soller 36b3cf04a9 Add name to fpath 2016-09-28 10:27:32 -06:00
Jeremy Soller 9c16bca2a5 Fix warning 2016-09-28 08:58:51 -06:00
Jeremy Soller 9bd5d69776 Compatibility with new kernel 2016-09-27 21:55:57 -06:00
Jeremy Soller 11f3485173 Port to new kernel 2016-09-27 21:19:28 -06:00
Jeremy Soller b7608c1530 Merge pull request #1 from redox-os/new_abi
Switch to new ABI
2016-08-10 19:18:32 -06:00
Jeremy Soller 35ed814e68 WIP: Switch to new ABI 2016-08-08 21:31:12 -06:00
Jeremy Soller fe0a5de2d4 Decrease size to increase install speed 2016-07-31 12:56:56 -06:00
Jeremy Soller ac8cee662e Increase header search range to 4 MB 2016-07-31 06:53:44 -06:00
Jeremy Soller dc575b790f Use Duration sleep 2016-07-30 18:01:16 -06:00
Jeremy Soller fb2eb845ba Increase size of image 2016-07-28 16:55:36 -06:00
Jeremy Soller e8d92bee0f Remove print 2016-06-21 11:14:42 -06:00
Jeremy Soller dd4a90119f Fixes for node_ensure_len 2016-06-21 11:13:59 -06:00
Jeremy Soller 55e6b13555 Alignment of write 2016-06-21 11:01:45 -06:00
Jeremy Soller d9bdd6e3dd Comment out fstat 2016-06-10 20:20:47 -06:00
Jeremy Soller ac07b358ad Support O_TRUNC in open 2016-06-09 20:24:10 -06:00
Jeremy Soller 896812c68a Sync size when truncate occurs 2016-06-09 20:23:30 -06:00
Jeremy Soller 4316326792 Remove debug for 'Creating cache' 2016-06-08 21:08:21 -06:00
Jeremy Soller b6d49e2fa1 Reenable dup, disable debugging 2016-06-07 17:30:33 -06:00
Jeremy Soller 195271779d old_id vs id 2016-06-06 18:24:29 -06:00
Jeremy Soller 7e80cba121 Add dup 2016-06-06 17:29:57 -06:00
Jeremy Soller 32c61e93e0 C stat support 2016-06-03 23:23:40 -06:00
Jeremy Soller d17b55089a More efficient reads 2016-05-16 09:49:05 -06:00
Jeremy Soller cf728be7ae Remove debugging on filesystem open 2016-05-09 20:15:01 -06:00
Jeremy Soller 3841521174 Deny warnings 2016-04-29 17:38:56 -06:00
Jeremy Soller 0bb53636e4 Faster read from disk if cache miss 2016-04-19 20:14:31 -06:00
Jeremy Soller dc5326a13a FUSE truncation 2016-04-16 15:57:11 -06:00
Jeremy Soller 7d48fcf382 WIP: Add truncate 2016-04-16 13:33:21 -06:00
Jeremy Soller fb55c69fe5 Add stat 2016-04-15 08:35:33 -06:00
Jeremy Soller e97f126f35 Fixes for dirs and files so they do not have to read all contents at once 2016-04-14 21:06:12 -06:00
Jeremy Soller f509c4b12c Caching! 2016-04-14 17:29:08 -06:00
Jeremy Soller e355f755ee Comment out prints 2016-04-13 15:34:06 -06:00
Jeremy Soller 94c36fa34d Fixes to fuse, allow filesystem offset 2016-04-13 15:23:52 -06:00
Jeremy Soller d13d926d7b Fixes for fuse 2016-04-12 20:56:38 -06:00
Jeremy Soller 8d806568b9 Turn off per-packet messages 2016-04-12 20:51:04 -06:00
Jeremy Soller bef665af8d Add debugging to scheme, add padding to header 2016-04-12 14:07:49 -06:00
Jeremy Soller 9cf1d60253 Add df support 2016-03-09 14:43:44 -07:00
Jeremy Soller dcc67e4cf4 Fixes for redox scheme 2016-03-09 14:12:17 -07:00
Jeremy Soller 77848132ea Fuse fixes, file read/write implemented 2016-03-09 14:10:56 -07:00
Jeremy Soller 73528fa872 Add read file support 2016-03-09 12:48:16 -07:00
Jeremy Soller a1cae73566 FUSE support for RedoxFS (WIP) 2016-03-09 12:12:46 -07:00
Jeremy Soller b1805779a8 Write can realloc 2016-03-03 17:18:41 -07:00
Jeremy Soller ff0106b212 Support for less-than-sector block allocations 2016-03-03 15:01:36 -07:00
Jeremy Soller 51e037150c Merge branch 'master' of https://github.com/redox-os/redoxfs 2016-03-02 11:42:12 -07:00
Jeremy Soller 856db3e21d WIP: Collection 2016-03-02 11:41:59 -07:00
Jeremy Soller 1a58014da9 Add more attributes 2016-03-01 21:08:21 -07:00
Jeremy Soller 6d5be41c13 Add truncate 2016-03-01 20:09:47 -07:00
Jeremy Soller 2d578215e3 rmdir, mkdir 2016-03-01 19:26:40 -07:00
Jeremy Soller 25174cc4ce WIP: Functions for accessing file data 2016-02-29 21:32:37 -07:00
Jeremy Soller 7412bdd6b7 WIP: Read/write 2016-02-29 20:02:54 -07:00
Jeremy Soller d2a073788a Add unlink and creat 2016-02-27 10:26:53 -07:00
Jeremy Soller 702bef4418 Path handling 2016-02-26 20:22:41 -07:00
Jeremy Soller 799a847ffa WIP: Scheme 2016-02-26 19:30:49 -07:00
Jeremy Soller 670d874d07 Use unix errors 2016-02-26 18:51:29 -07:00
Jeremy Soller 7f8776fafb Do not allow rmdir when node has children 2016-02-26 15:14:16 -07:00
Jeremy Soller 664801337d Add to free node list, fix insertion bug 2016-02-26 14:59:00 -07:00
Jeremy Soller ecfd43fdbd Fix remove truncation 2016-02-26 14:24:03 -07:00
Jeremy Soller 7a4d78149a WIP: Remove 2016-02-26 14:18:29 -07:00
Jeremy Soller 1eff7c7e37 WIP: Simple way to fill file with data 2016-02-26 10:53:05 -07:00
Jeremy Soller 39707bcf60 Add signature as constant 2016-02-25 16:33:12 -07:00
Jeremy Soller 1f08b76531 Add cd, Use invalid version until stable 2016-02-25 16:31:28 -07:00
Jeremy Soller e0fceebffd Allow a parent node to be given to the primitive functions 2016-02-25 14:25:14 -07:00
Jeremy Soller 4a444a3423 Add find function 2016-02-25 12:29:55 -07:00
Jackpot51 6450660c50 Create DESIGN.md 2016-02-25 12:29:08 -07:00
Jeremy Soller 556c842ec7 Add exnode 2016-02-24 21:59:57 -07:00
Jeremy Soller d51e891f22 Add to root node on mkdir, touch 2016-02-24 21:23:46 -07:00
Jeremy Soller 41e716219d Very basic allocation 2016-02-24 18:47:47 -07:00
Jeremy Soller bae24a54b0 Improve debugging and image creation 2016-02-24 18:23:25 -07:00
Jeremy Soller 8ff4863bbb Cleanup, add node and header commands 2016-02-24 17:30:37 -07:00
Jeremy Soller d116ce57da Add simple shell 2016-02-24 16:04:30 -07:00
Jeremy Soller c231ddb219 Remove cat todo 2016-02-24 15:06:35 -07:00
Jeremy Soller 49925a39d0 Error type, subdir argument 2016-02-24 14:59:14 -07:00
Jeremy Soller bc2d90f531 Allow read of filesystem names 2016-02-24 14:02:37 -07:00
63 changed files with 14037 additions and 4001 deletions
+3 -1
View File
@@ -1,2 +1,4 @@
Cargo.lock
target target
image.bin
image
image*
+19 -5
View File
@@ -1,17 +1,31 @@
image: "redoxos/redoxer" image: "redoxos/redoxer"
stages:
- build
workflow: workflow:
rules: rules:
- if: '$CI_COMMIT_BRANCH == "master" && $CI_PROJECT_NAMESPACE == "redox-os"' - if: '$CI_COMMIT_BRANCH == "master" && $CI_PROJECT_NAMESPACE == "redox-os"'
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"' - if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"'
stages:
- build
- test
build:linux: build:linux:
stage: build stage: build
script: cargo +nightly build script: cargo +nightly build --verbose
build:redox: build:redox:
stage: build stage: build
script: redoxer build script: redoxer build --verbose
test:linux:
stage: test
dependencies:
- build:linux
script: cargo +nightly test --verbose
test:redox:
stage: test
dependencies:
- build:redox
# only run integration test as without KVM unit tests is super slow
script: redoxer test --verbose -- --test '*' -- --nocapture
+21
View File
@@ -0,0 +1,21 @@
sudo: required
language: rust
rust:
- nightly
os:
- linux
- osx
dist: trusty
before_install:
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then
sudo apt-get install -qq pkg-config fuse libfuse-dev;
sudo modprobe fuse;
sudo chmod 666 /dev/fuse;
sudo chown root:$USER /etc/fuse.conf;
fi
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then
brew update;
brew install Caskroom/cask/osxfuse;
fi
notifications:
email: false
Generated
+808
View File
@@ -0,0 +1,808 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aes"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
[[package]]
name = "anstyle-parse"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "argon2"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db4ce4441f99dbd377ca8a8f57b698c44d0d6e712d8329b5040da5a64aa1ce73"
dependencies = [
"base64ct",
"blake2",
]
[[package]]
name = "assert_cmd"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6"
dependencies = [
"anstyle",
"bstr",
"libc",
"predicates",
"predicates-core",
"predicates-tree",
"wait-timeout",
]
[[package]]
name = "base64ct"
version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bstr"
version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79"
dependencies = [
"memchr",
"regex-automata",
"serde_core",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "colorchoice"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "defmt"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
dependencies = [
"bitflags 1.3.2",
"defmt-macros",
]
[[package]]
name = "defmt-macros"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
dependencies = [
"defmt-parser",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "defmt-parser"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
dependencies = [
"thiserror",
]
[[package]]
name = "difflib"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]]
name = "endian-num"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8f59926911ef34d1efb9ea1ee8ca78385df62ce700ccf2bcb149011bd226888"
[[package]]
name = "env_filter"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"jiff",
"log",
]
[[package]]
name = "fuser"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bb29a3ae32279fe3e79a958fe01899f5fb23eadccee919cf88e145b54ed9367"
dependencies = [
"libc",
"log",
"memchr",
"nix",
"page_size",
"smallvec",
"zerocopy",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "getrandom"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
"libc",
"r-efi",
]
[[package]]
name = "humansize"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
dependencies = [
"libm",
]
[[package]]
name = "inout"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
dependencies = [
"generic-array",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "jiff"
version = "0.2.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634"
dependencies = [
"defmt",
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
"serde_core",
]
[[package]]
name = "jiff-static"
version = "0.2.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e165e897f662d428f3cd3828a919dbe067c2d42bb1031eede74ef9d27ecdedd2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "libm"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
name = "libredox"
version = "0.1.18+rb0.3.0"
dependencies = [
"bitflags 2.13.0",
"libc",
"plain",
"redox_syscall",
]
[[package]]
name = "log"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
name = "lz4_flex"
version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a"
[[package]]
name = "memchr"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]]
name = "nix"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags 2.13.0",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "numtoa"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "page_size"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "parse-size"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "487f2ccd1e17ce8c1bfab3a65c89525af41cfad4c8659021a1e9a2aacd73b89b"
[[package]]
name = "plain"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
[[package]]
name = "portable-atomic"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]]
name = "portable-atomic-util"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
dependencies = [
"portable-atomic",
]
[[package]]
name = "predicates"
version = "3.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe"
dependencies = [
"anstyle",
"difflib",
"predicates-core",
]
[[package]]
name = "predicates-core"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144"
[[package]]
name = "predicates-tree"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2"
dependencies = [
"predicates-core",
"termtree",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "range-tree"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384c2842d4e069d5ccacf5fe1dca4ef8d07a5444329715f0fc3c61813502d4d1"
[[package]]
name = "redox-path"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717"
[[package]]
name = "redox-scheme"
version = "0.11.2+rb0.3.0"
dependencies = [
"libredox",
"redox_syscall",
]
[[package]]
name = "redox_syscall"
version = "0.9.0+rb0.3.0"
dependencies = [
"bitflags 2.13.0",
]
[[package]]
name = "redoxfs"
version = "0.9.1+rb0.3.0"
dependencies = [
"aes",
"argon2",
"assert_cmd",
"base64ct",
"bitflags 2.13.0",
"endian-num",
"env_logger",
"fuser",
"getrandom 0.2.17",
"humansize",
"libc",
"libredox",
"log",
"lz4_flex",
"parse-size",
"range-tree",
"redox-path",
"redox-scheme",
"redox_syscall",
"seahash",
"termion",
"uuid",
"xts-mode",
]
[[package]]
name = "regex"
version = "1.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "seahash"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "smallvec"
version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termion"
version = "4.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f44138a9ae08f0f502f24104d82517ef4da7330c35acd638f1f29d3cd5475ecb"
dependencies = [
"libc",
"numtoa",
]
[[package]]
name = "termtree"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "typenum"
version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53"
dependencies = [
"getrandom 0.4.3",
]
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wait-timeout"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
dependencies = [
"libc",
]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "xts-mode"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cbddb7545ca0b9ffa7bdc653e8743303e1712687a6918ced25f2cdbed42520"
dependencies = [
"byteorder",
"cipher",
]
[[package]]
name = "zerocopy"
version = "0.8.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75726053136156d419e285b9b7eddaaea9e3fea6ce32eed44a89901f0bd98de1"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4714fd92cf900833d49538023a9b3915155210801d1c1169eba513b2addefd71"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
+92 -16
View File
@@ -1,25 +1,101 @@
[package] [package]
name = "redox_syscall" name = "redoxfs"
version = "0.9.0+rb0.3.0" description = "The Redox Filesystem"
description = "A Rust library to access raw Redox system calls" repository = "https://gitlab.redox-os.org/redox-os/redoxfs"
license = "MIT" version = "0.9.1+rb0.3.0"
license-file = "LICENSE"
readme = "README.md"
authors = ["Jeremy Soller <jackpot51@gmail.com>", "vasilito <adminpupkin@gmail.com>"] authors = ["Jeremy Soller <jackpot51@gmail.com>", "vasilito <adminpupkin@gmail.com>"]
repository = "https://gitlab.redox-os.org/redox-os/syscall"
documentation = "https://docs.rs/redox_syscall"
edition = "2021" edition = "2021"
[lib] [lib]
name = "syscall" name = "redoxfs"
path = "src/lib.rs"
[features] [[bin]]
default = ["userspace"] name = "redoxfs"
rustc-dep-of-std = ["core", "bitflags/rustc-dep-of-std"] path = "src/bin/mount.rs"
userspace = [] doc = false
std = [] required-features = ["std"]
[[bin]]
name = "redoxfs-ar"
path = "src/bin/ar.rs"
doc = false
required-features = ["std"]
[[bin]]
name = "redoxfs-clone"
path = "src/bin/clone.rs"
doc = false
required-features = ["std"]
[[bin]]
name = "redoxfs-mkfs"
path = "src/bin/mkfs.rs"
doc = false
required-features = ["std"]
[[bin]]
name = "redoxfs-resize"
path = "src/bin/resize.rs"
doc = false
required-features = ["std"]
[dependencies] [dependencies]
bitflags = "2.4" aes = { version = "0.8", default-features = false }
core = { version = "1.0.0", optional = true, package = "rustc-std-workspace-core" } argon2 = { version = "0.4", default-features = false, features = ["alloc"] }
base64ct = { version = "1", default-features = false }
bitflags = "2"
endian-num = "0.1"
env_logger = { version = "0.11", optional = true }
getrandom = { version = "0.2.5", optional = true }
humansize = { version = "2", optional = true }
libc = "0.2"
log = { version = "0.4.14", default-features = false, optional = true }
lz4_flex = { version = "0.11", default-features = false, features = ["checked-decode"] }
parse-size = { version = "1", optional = true }
range-tree = { version = "0.1", optional = true }
redox_syscall = { path = "../syscall" }
seahash = { version = "4.1.0", default-features = false }
termion = { version = "4", optional = true }
uuid = { version = "1.4", default-features = false }
xts-mode = { version = "0.5", default-features = false }
[target.'cfg(loom)'.dev-dependencies] [features]
loom = "0.7" default = ["std", "log", "fuse"]
fuse = [
"fuser",
"std",
]
std = [
"env_logger",
"getrandom",
"humansize",
"libredox",
"parse-size",
"range-tree",
"termion",
"uuid/v4",
"redox_syscall/std",
"redox-scheme",
]
[target.'cfg(not(target_os = "redox"))'.dependencies]
fuser = { version = "0.16", optional = true }
[target.'cfg(target_os = "redox")'.dependencies]
libredox = { path = "../libredox", optional = true }
redox-path = "0.3.0"
redox-scheme = { path = "../redox-scheme", optional = true }
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }
[dev-dependencies]
assert_cmd = "2.0.17"
[patch.crates-io]
redox_syscall = { path = "../syscall" }
libredox = { path = "../libredox" }
redox-scheme = { path = "../redox-scheme" }
+17 -18
View File
@@ -1,22 +1,21 @@
Copyright (c) 2017 Redox OS Developers The MIT License (MIT)
MIT License Copyright (c) 2016 Jeremy Soller
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining a copy
a copy of this software and associated documentation files (the of this software and associated documentation files (the "Software"), to deal
"Software"), to deal in the Software without restriction, including in the Software without restriction, including without limitation the rights
without limitation the rights to use, copy, modify, merge, publish, to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
distribute, sublicense, and/or sell copies of the Software, and to copies of the Software, and to permit persons to whom the Software is
permit persons to whom the Software is furnished to do so, subject to furnished to do so, subject to the following conditions:
the following conditions:
The above copyright notice and this permission notice shall be The above copyright notice and this permission notice shall be included in all
included in all copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. SOFTWARE.
+37
View File
@@ -0,0 +1,37 @@
UNAME := $(shell uname)
ifeq ($(UNAME),Darwin)
FUMOUNT=umount
else ifeq ($(UNAME),FreeBSD)
FUMOUNT=sudo umount
else
# Detect which version of the fusermount binary is available.
ifneq (, $(shell which fusermount3))
FUMOUNT=fusermount3 -u
else
FUMOUNT=fusermount -u
endif
endif
image.bin:
cargo build --release --bin redoxfs-mkfs
dd if=/dev/zero of=image.bin bs=1048576 count=1024
target/release/redoxfs-mkfs image.bin
mount: image.bin FORCE
mkdir -p image
cargo build --release --bin redoxfs
target/release/redoxfs image.bin image
unmount: FORCE
sync
-${FUMOUNT} image
rm -rf image
clean: FORCE
sync
-${FUMOUNT} image
rm -rf image image.bin
cargo clean
FORCE:
+50 -4
View File
@@ -1,7 +1,53 @@
# syscall # RedoxFS
This crate contains the system call numbers and Rust wrappers for the inline Assembly code of system calls. This is the default filesystem of Redox OS inspired by [ZFS](https://docs.freebsd.org/en/books/handbook/zfs/) and adapted to a microkernel architecture.
(It's a replacement for [TFS](https://gitlab.redox-os.org/redox-os/tfs))
Current features:
- Compatible with Redox and Linux (FUSE)
- Copy-on-write
- Data/metadata checksums
- Transparent encryption
- Standard Unix file attributes
- File/directory size limit up to 193TiB (212TB)
- File/directory quantity limit up to 4 billion per 193TiB (2^32 - 1 = 4294967295)
- MIT licensed
- Disk encryption fully supported by the Redox bootloader, letting it load the kernel off an encrypted partition.
Being MIT licensed, RedoxFS can be bundled on GPL-licensed operating systems (Linux, for example).
### Install RedoxFS
```sh
cargo install redoxfs
```
You can also build RedoxFS from this repository.
### Configure your storage device to allow rootless usage
If you are on Linux you need root permission to acess block devices (storage), but it's recommended to run RedoxFS as rootless.
To do that you need to configure your storage device permission to your user with the following command:
```sh
sudo setfacl -m u:your-username:rw /path/to/disk
```
### Create, mount and customize your RedoxFS partition
See [the instructions in the book](https://doc.redox-os.org/book/redoxfs.html) for RedoxFS tooling usage.
Currently RedoxFS tooling are:
- `redoxfs` mount a RedoxFS disk
- `redoxfs-ar` write files to a RedoxFS disk
- `redoxfs-clone` clone a RedoxFS disk
- `redoxfs-mkfs` create an empty RedoxFS disk
- `redoxfs-resize` resize a RedoxFS disk
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
[![crates.io](http://meritbadge.herokuapp.com/redox_syscall)](https://crates.io/crates/redox_syscall) [![crates.io](http://meritbadge.herokuapp.com/redoxfs)](https://crates.io/crates/redoxfs)
[![docs.rs](https://docs.rs/redox_syscall/badge.svg)](https://docs.rs/redox_syscall) [![docs.rs](https://docs.rs/redoxfs/badge.svg)](https://docs.rs/redoxfs)
+4
View File
@@ -0,0 +1,4 @@
target
corpus
artifacts
coverage
+858
View File
@@ -0,0 +1,858 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aes"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
dependencies = [
"anstyle",
"once_cell",
"windows-sys 0.59.0",
]
[[package]]
name = "anyhow"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
name = "arbitrary"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
dependencies = [
"derive_arbitrary",
]
[[package]]
name = "argon2"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db4ce4441f99dbd377ca8a8f57b698c44d0d6e712d8329b5040da5a64aa1ce73"
dependencies = [
"base64ct",
"blake2",
]
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bitflags"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]]
name = "blake2"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
dependencies = [
"digest",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695"
dependencies = [
"jobserver",
"libc",
"once_cell",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "colorchoice"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "cpufeatures"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "derive_arbitrary"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]]
name = "endian-num"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad847bb2094f110bbdd6fa564894ca4556fd978958e93985420d680d3cb6d14"
[[package]]
name = "env_filter"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"jiff",
"log",
]
[[package]]
name = "errno"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "fastrand"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
[[package]]
name = "fuser"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bb29a3ae32279fe3e79a958fe01899f5fb23eadccee919cf88e145b54ed9367"
dependencies = [
"libc",
"log",
"memchr",
"nix",
"page_size",
"smallvec",
"zerocopy",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "humansize"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
dependencies = [
"libm",
]
[[package]]
name = "inout"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
dependencies = [
"generic-array",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "jiff"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ad87c89110f55e4cd4dc2893a9790820206729eaf221555f742d540b0724a0"
dependencies = [
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
"serde",
]
[[package]]
name = "jiff-static"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d076d5b64a7e2fe6f0743f02c43ca4a6725c0f904203bfe276a5b3e793103605"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "jobserver"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e"
dependencies = [
"libc",
]
[[package]]
name = "libc"
version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "libfuzzer-sys"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7"
dependencies = [
"arbitrary",
"cc",
"once_cell",
]
[[package]]
name = "libm"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
name = "libredox"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
dependencies = [
"bitflags",
"libc",
"redox_syscall 0.7.0",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "lz4_flex"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "nix"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "numtoa"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f"
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "page_size"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "parse-size"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "487f2ccd1e17ce8c1bfab3a65c89525af41cfad4c8659021a1e9a2aacd73b89b"
[[package]]
name = "portable-atomic"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
[[package]]
name = "portable-atomic-util"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
"portable-atomic",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "range-tree"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384c2842d4e069d5ccacf5fe1dca4ef8d07a5444329715f0fc3c61813502d4d1"
[[package]]
name = "redox-path"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717"
[[package]]
name = "redox-scheme"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cea2668a5932f878a4298a1d7f8950249bbbb77120fb263da252c589152f5ea"
dependencies = [
"libredox",
"redox_syscall 0.6.0",
]
[[package]]
name = "redox_syscall"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_syscall"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_termios"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb"
[[package]]
name = "redoxfs"
version = "0.8.4"
dependencies = [
"aes",
"argon2",
"base64ct",
"bitflags",
"endian-num",
"env_logger",
"fuser",
"getrandom",
"humansize",
"libc",
"libredox",
"log",
"lz4_flex",
"parse-size",
"range-tree",
"redox-path",
"redox-scheme",
"redox_syscall 0.6.0",
"seahash",
"termion",
"uuid",
"xts-mode",
]
[[package]]
name = "redoxfs-fuzz"
version = "0.0.0"
dependencies = [
"anyhow",
"arbitrary",
"fuser",
"libfuzzer-sys",
"nix",
"redoxfs",
"tempfile",
]
[[package]]
name = "regex"
version = "1.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "rustix"
version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "seahash"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "subtle"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "syn"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
dependencies = [
"cfg-if",
"fastrand",
"rustix",
"windows-sys 0.52.0",
]
[[package]]
name = "termion"
version = "4.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3669a69de26799d6321a5aa713f55f7e2cd37bd47be044b50f2acafc42c122bb"
dependencies = [
"libc",
"libredox",
"numtoa",
"redox_termios",
]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
dependencies = [
"getrandom",
]
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "xts-mode"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cbddb7545ca0b9ffa7bdc653e8743303e1712687a6918ced25f2cdbed42520"
dependencies = [
"byteorder",
"cipher",
]
[[package]]
name = "zerocopy"
version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
+30
View File
@@ -0,0 +1,30 @@
[package]
name = "redoxfs-fuzz"
version = "0.0.0"
publish = false
edition = "2021"
[features]
default = []
log = []
[package.metadata]
cargo-fuzz = true
[dependencies]
anyhow = "1.0.86"
arbitrary = { version = "1.3.2", features = ["derive"] }
fuser = { version = "0.16" }
libfuzzer-sys = "0.4"
nix = { version = "0.29.0", features = ["fs"] }
tempfile = "3.10.1"
[dependencies.redoxfs]
path = ".."
[[bin]]
name = "fuse_fuzz_target"
path = "fuzz_targets/fuse_fuzz_target.rs"
test = false
doc = false
bench = false
+338
View File
@@ -0,0 +1,338 @@
//! Fuzzer that exercises random file system operations against a FUSE-mounted redoxfs.
#![no_main]
use anyhow::{ensure, Result};
use fuser;
use libfuzzer_sys::{arbitrary::Arbitrary, fuzz_target, Corpus};
use nix::sys::statvfs::statvfs;
use std::{
fs::{self, File, FileTimes, OpenOptions},
io::{Read, Seek, SeekFrom, Write},
os::unix::fs::{self as unix_fs, PermissionsExt},
path::{Path, PathBuf},
thread,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use tempfile;
use redoxfs::{mount::fuse::Fuse, DiskSparse, FileSystem};
/// Maximum size for files and buffers. Chosen arbitrarily with fuzzing performance in mind.
const MAX_SIZE: u64 = 10_000_000;
/// Limit on the number of remounts in a single test case. Chosen arbitrarily with fuzzing
/// performance in mind: remounts are costly.
const MAX_MOUNT_SEQUENCES: usize = 3;
/// An operation to be performed by the fuzzer.
#[derive(Arbitrary, Clone, Debug)]
enum Operation {
Chown {
path: PathBuf,
uid: Option<u32>,
gid: Option<u32>,
},
CreateDir {
path: PathBuf,
},
HardLink {
original: PathBuf,
link: PathBuf,
},
Metadata {
path: PathBuf,
},
Read {
path: PathBuf,
},
ReadDir {
path: PathBuf,
},
ReadLink {
path: PathBuf,
},
RemoveDir {
path: PathBuf,
},
RemoveFile {
path: PathBuf,
},
Rename {
from: PathBuf,
to: PathBuf,
},
SeekRead {
path: PathBuf,
seek_pos: u64,
buf_size: usize,
},
SeekWrite {
path: PathBuf,
seek_pos: u64,
buf_size: usize,
},
SetLen {
path: PathBuf,
size: u64,
},
SetPermissions {
path: PathBuf,
readonly: Option<bool>,
mode: Option<u32>,
},
SetTimes {
path: PathBuf,
accessed_since_epoch: Option<Duration>,
modified_since_epoch: Option<Duration>,
},
Statvfs {},
SymLink {
original: PathBuf,
link: PathBuf,
},
Write {
path: PathBuf,
buf_size: usize,
},
}
/// Parameters for mounting the file system and operations to be performed afterwards.
#[derive(Arbitrary, Clone, Debug)]
struct MountSequence {
cleanup: bool,
operations: Vec<Operation>,
}
/// The whole input to a single fuzzer invocation.
#[derive(Arbitrary, Clone, Debug)]
struct TestCase {
disk_size: u64,
reserved_size: u64,
mount_sequences: Vec<MountSequence>,
}
/// Creates the disk for backing the Redoxfs.
fn create_disk(temp_path: &Path, disk_size: u64) -> DiskSparse {
let disk_path = temp_path.join("disk.img");
DiskSparse::create(disk_path, disk_size).unwrap()
}
/// Creates an empty Redoxfs.
fn create_redoxfs(disk: DiskSparse, reserved_size: u64) -> bool {
let password = None;
let reserved = vec![0; reserved_size as usize];
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
FileSystem::create_reserved(
disk,
password,
&reserved,
ctime.as_secs(),
ctime.subsec_nanos(),
)
.is_ok()
}
/// Mounts an existing Redoxfs, runs the callback and performs the unmount.
fn with_redoxfs_mount<F>(temp_path: &Path, disk: DiskSparse, cleanup: bool, callback: F)
where
F: FnOnce(&Path) + Send + 'static,
{
let password = None;
let block = None;
let mut fs = FileSystem::open(disk, password, block, cleanup).unwrap();
let mount_path = temp_path.join("mount");
fs::create_dir_all(&mount_path).unwrap();
let mut session = fuser::Session::new(Fuse { fs: &mut fs }, &mount_path, &[]).unwrap();
let mut unmounter = session.unmount_callable();
let join_handle = thread::spawn(move || {
callback(&mount_path);
unmounter.unmount().unwrap();
});
session.run().unwrap();
join_handle.join().unwrap();
}
fn get_path_within_fs(fs_path: &Path, path_to_add: &Path) -> Result<PathBuf> {
ensure!(path_to_add.is_relative());
ensure!(path_to_add
.components()
.all(|c| c != std::path::Component::ParentDir));
Ok(fs_path.join(path_to_add))
}
fn do_operation(fs_path: &Path, op: &Operation) -> Result<()> {
match op {
Operation::Chown { path, uid, gid } => {
let path = get_path_within_fs(fs_path, path)?;
unix_fs::chown(path, *uid, *gid)?;
}
Operation::CreateDir { path } => {
let path = get_path_within_fs(fs_path, path)?;
fs::create_dir(path)?;
}
Operation::HardLink { original, link } => {
let original = get_path_within_fs(fs_path, original)?;
let link = get_path_within_fs(fs_path, link)?;
fs::hard_link(original, link)?;
}
Operation::Metadata { path } => {
let path = get_path_within_fs(fs_path, path)?;
fs::metadata(path)?;
}
Operation::Read { path } => {
let path = get_path_within_fs(fs_path, path)?;
fs::read(path)?;
}
Operation::ReadDir { path } => {
let path = get_path_within_fs(fs_path, path)?;
let _ = fs::read_dir(path)?.count();
}
Operation::ReadLink { path } => {
let path = get_path_within_fs(fs_path, path)?;
fs::read_link(path)?;
}
Operation::RemoveDir { path } => {
let path = get_path_within_fs(fs_path, path)?;
fs::remove_dir(path)?;
}
Operation::RemoveFile { path } => {
let path = get_path_within_fs(fs_path, path)?;
fs::remove_file(path)?;
}
Operation::Rename { from, to } => {
let from = get_path_within_fs(fs_path, from)?;
let to = get_path_within_fs(fs_path, to)?;
fs::rename(from, to)?;
}
Operation::SeekRead {
path,
seek_pos,
buf_size,
} => {
ensure!(*buf_size as u64 <= MAX_SIZE);
let path = get_path_within_fs(fs_path, path)?;
let mut file = File::open(path)?;
file.seek(SeekFrom::Start(*seek_pos))?;
let mut buf = vec![0; *buf_size];
file.read(&mut buf)?;
}
Operation::SeekWrite {
path,
seek_pos,
buf_size,
} => {
ensure!(*seek_pos <= MAX_SIZE);
ensure!(*buf_size as u64 <= MAX_SIZE);
let path = get_path_within_fs(fs_path, path)?;
let mut file = OpenOptions::new().write(true).open(path)?;
file.seek(SeekFrom::Start(*seek_pos))?;
let buf = vec![0; *buf_size];
file.write(&buf)?;
}
Operation::SetLen { path, size } => {
let path = get_path_within_fs(fs_path, path)?;
let file = OpenOptions::new().write(true).open(path)?;
file.set_len(*size)?;
}
Operation::SetPermissions {
path,
readonly,
mode,
} => {
let path = get_path_within_fs(fs_path, path)?;
let metadata = fs::metadata(&path)?;
let mut perms = metadata.permissions();
if let Some(readonly) = readonly {
perms.set_readonly(*readonly);
}
if let Some(mode) = mode {
perms.set_mode(*mode);
}
fs::set_permissions(path, perms)?;
}
Operation::SetTimes {
path,
accessed_since_epoch,
modified_since_epoch,
} => {
let path = get_path_within_fs(fs_path, path)?;
let file = File::options().write(true).open(path)?;
let mut times = FileTimes::new();
if let Some(accessed_since_epoch) = accessed_since_epoch {
if let Some(accessed) = UNIX_EPOCH.checked_add(*accessed_since_epoch) {
times = times.set_accessed(accessed);
}
}
if let Some(modified_since_epoch) = modified_since_epoch {
if let Some(modified) = UNIX_EPOCH.checked_add(*modified_since_epoch) {
times = times.set_modified(modified);
}
}
file.set_times(times)?;
}
Operation::Statvfs {} => {
statvfs(fs_path)?;
}
Operation::SymLink { original, link } => {
let original = get_path_within_fs(fs_path, original)?;
let link = get_path_within_fs(fs_path, link)?;
unix_fs::symlink(original, link)?;
}
Operation::Write { path, buf_size } => {
ensure!(*buf_size as u64 <= MAX_SIZE);
let path = get_path_within_fs(fs_path, path)?;
let buf = vec![0; *buf_size];
fs::write(path, &buf)?;
}
}
Ok(())
}
fuzz_target!(|test_case: TestCase| -> Corpus {
if test_case.disk_size > MAX_SIZE
|| test_case.reserved_size > MAX_SIZE
|| test_case.mount_sequences.len() > MAX_MOUNT_SEQUENCES
{
return Corpus::Reject;
}
let temp_dir = tempfile::Builder::new()
.prefix("fuse_fuzz_target")
.tempdir()
.unwrap();
#[cfg(feature = "log")]
eprintln!("create fs");
let disk = create_disk(temp_dir.path(), test_case.disk_size);
if !create_redoxfs(disk, test_case.reserved_size) {
// File system creation failed (e.g., due to insufficient space) so we bail out, still
// exercising this code path is useful.
return Corpus::Keep;
}
for mount_seq in test_case.mount_sequences.iter() {
#[cfg(feature = "log")]
eprintln!("mount fs: path {:?}, size{}", temp_dir.path(), test_case.disk_size);
let disk = create_disk(temp_dir.path(), test_case.disk_size);
let operations = mount_seq.operations.clone();
with_redoxfs_mount(temp_dir.path(), disk, mount_seq.cleanup, move |fs_path| {
for operation in operations.iter() {
#[cfg(feature = "log")]
eprintln!("do operation {operation:?}");
let _result = do_operation(fs_path, operation);
#[cfg(feature = "log")]
eprintln!("operation result {:?}", _result.err());
}
});
#[cfg(feature = "log")]
eprintln!("unmounted fs");
}
Corpus::Keep
});
+394
View File
@@ -0,0 +1,394 @@
use alloc::{collections::BTreeSet, vec::Vec};
use core::{fmt, mem, ops, slice};
use endian_num::Le;
use crate::{BlockAddr, BlockLevel, BlockMeta, BlockPtr, BlockTrait, Node, TreePtr, BLOCK_SIZE};
pub const ALLOC_LIST_ENTRIES: usize =
(BLOCK_SIZE as usize - mem::size_of::<BlockPtr<AllocList>>()) / mem::size_of::<AllocEntry>();
pub const RELEASE_LIST_ENTRIES: usize = (BLOCK_SIZE as usize
- mem::size_of::<BlockPtr<ReleaseList>>())
/ mem::size_of::<TreePtr<Node>>();
/// The RedoxFS block allocator. This struct manages all "data" blocks in RedoxFS
/// (i.e, all blocks that aren't reserved or part of the header chain).
///
/// [`Allocator`] can allocate blocks of many "levels"---that is, it can
/// allocate multiple consecutive [`BLOCK_SIZE`] blocks in one operation.
///
/// This reduces the amount of memory that the [`Allocator`] uses:
/// Instead of storing the index of each free [`BLOCK_SIZE`] block,
/// the `levels` array can keep track of higher-level blocks, splitting
/// them when a smaller block is requested.
///
/// Higher-level blocks also allow us to more efficiently allocate memory
/// for large files.
#[derive(Clone, Default)]
pub struct Allocator {
/// This array keeps track of all free blocks of each level,
/// and is initialized using the AllocList chain when we open the filesystem.
///
/// Every element of the outer array represents a block level:
/// - item 0: free level 0 blocks (with size [`BLOCK_SIZE`])
/// - item 1: free level 1 blocks (with size 2*[`BLOCK_SIZE`])
/// - item 2: free level 2 blocks (with size 4*[`BLOCK_SIZE`])
/// ...and so on.
///
/// Each inner array contains a list of free block indices,
levels: Vec<BTreeSet<u64>>,
}
impl Allocator {
pub fn levels(&self) -> &Vec<BTreeSet<u64>> {
&self.levels
}
/// Count the number of free [`BLOCK_SIZE`] available to this [`Allocator`].
pub fn free(&self) -> u64 {
let mut free = 0;
for level in 0..self.levels.len() {
let level_size = 1 << level;
free += self.levels[level].len() as u64 * level_size;
}
free
}
/// Find a free block of the given level, mark it as "used", and return its address.
/// Returns [`None`] if there are no free blocks with this level.
pub fn allocate(&mut self, meta: BlockMeta) -> Option<BlockAddr> {
// First, find the lowest level with a free block
let mut free_opt = None;
{
let mut level = meta.level.0;
// Start searching at the level we want. Smaller levels are too small!
while level < self.levels.len() {
if let Some(&index) = self.levels[level].first() {
// Find the index closest to the start of the filesystem
free_opt = match free_opt {
Some((free_level, free_index)) if free_index <= index => {
Some((free_level, free_index))
}
_ => Some((level, index)),
};
}
level += 1;
}
}
// If a free block was found, split it until we find a usable block of the right level.
// The left side of the split block is kept free, and the right side is allocated.
let (mut level, index) = free_opt?;
self.levels[level].remove(&index);
while level > meta.level.0 {
level -= 1;
let level_size = 1 << level;
self.levels[level].insert(index + level_size);
}
Some(unsafe { BlockAddr::new(index, meta) })
}
/// Try to allocate the exact block specified, making all necessary splits.
/// Returns [`None`] if this some (or all) of this block is already allocated.
///
/// Note that [`BlockAddr`] encodes the blocks location _and_ level.
pub fn allocate_exact(&mut self, exact_addr: BlockAddr) -> Option<BlockAddr> {
// This function only supports level 0 right now
assert_eq!(exact_addr.level().0, 0);
let exact_index = exact_addr.index();
let mut index_opt = None;
// Go from the highest to the lowest level
for level in (0..self.levels.len()).rev() {
let level_size = 1 << level;
// Split higher block if found
if let Some(index) = index_opt.take() {
self.levels[level].insert(index);
self.levels[level].insert(index + level_size);
}
// Look for matching block and remove it
for &start in self.levels[level].iter() {
if start <= exact_index {
let end = start + level_size;
if end > exact_index {
self.levels[level].remove(&start);
index_opt = Some(start);
break;
}
}
}
}
Some(unsafe { BlockAddr::new(index_opt?, exact_addr.meta()) })
}
/// Deallocate the given block, marking it "free" so that it can be re-used later.
pub fn deallocate(&mut self, addr: BlockAddr) {
// When we deallocate, we check if block we're deallocating has a free sibling.
// If it does, we join the two to create one free block in the next (higher) level.
//
// We repeat this until we no longer have a sibling to join.
let mut index = addr.index();
let mut level = addr.level().0;
loop {
while level >= self.levels.len() {
self.levels.push(BTreeSet::new());
}
let level_size = 1 << level;
let next_size = level_size << 1;
let mut found = false;
// look at all free blocks in the current level...
for &level_index in self.levels[level].iter() {
// - the block we just freed aligns with the next largest block, and
// - the second block we're looking at is the right sibling of this block
if index % next_size == 0 && index + level_size == level_index {
// "alloc" the next highest block, repeat deallocation process.
self.levels[level].remove(&level_index);
found = true;
break;
// - the index of this block doesn't align with the next largest block, and
// - the block we're looking at is the left neighbor of this block
} else if level_index % next_size == 0 && level_index + level_size == index {
// "alloc" the next highest block, repeat deallocation process.
self.levels[level].remove(&level_index);
index = level_index; // index moves to left block
found = true;
break;
}
}
// We couldn't find a higher block,
// deallocate this one and finish
if !found {
self.levels[level].insert(index);
return;
}
// repeat deallocation process on the
// higher-level block we just created.
level += 1;
}
}
}
#[repr(C, packed)]
#[derive(Clone, Copy, Default, Debug)]
pub struct AllocEntry {
/// The index of the first block this [`AllocEntry`] refers to
index: Le<u64>,
/// The number of blocks after (and including) `index` that are are free or used.
/// If negative, they are used; if positive, they are free.
count: Le<i64>,
}
impl AllocEntry {
pub fn new(index: u64, count: i64) -> Self {
Self {
index: index.into(),
count: count.into(),
}
}
pub fn allocate(addr: BlockAddr) -> Self {
Self::new(addr.index(), -addr.level().blocks::<i64>())
}
pub fn deallocate(addr: BlockAddr) -> Self {
Self::new(addr.index(), addr.level().blocks::<i64>())
}
pub fn index(&self) -> u64 {
self.index.to_ne()
}
pub fn count(&self) -> i64 {
self.count.to_ne()
}
pub fn is_null(&self) -> bool {
self.count() == 0
}
}
/// A node in the allocation chain.
#[repr(C, packed)]
pub struct AllocList {
/// A pointer to the previous AllocList.
/// If this is the null pointer, this is the first element of the chain.
pub prev: BlockPtr<AllocList>,
/// Allocation entries.
pub entries: [AllocEntry; ALLOC_LIST_ENTRIES],
}
unsafe impl BlockTrait for AllocList {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self {
prev: BlockPtr::default(),
entries: [AllocEntry::default(); ALLOC_LIST_ENTRIES],
})
} else {
None
}
}
}
impl fmt::Debug for AllocList {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let prev = self.prev;
let entries: Vec<&AllocEntry> = self
.entries
.iter()
.filter(|entry| entry.count() > 0)
.collect();
f.debug_struct("AllocList")
.field("prev", &prev)
.field("entries", &entries)
.finish()
}
}
impl ops::Deref for AllocList {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const AllocList as *const u8,
mem::size_of::<AllocList>(),
) as &[u8]
}
}
}
impl ops::DerefMut for AllocList {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut AllocList as *mut u8,
mem::size_of::<AllocList>(),
) as &mut [u8]
}
}
}
/// A list of nodes pending release.
#[repr(C, packed)]
pub struct ReleaseList {
/// A pointer to the previous ReleaseList.
/// If this is the null pointer, this is the first element of the chain.
pub prev: BlockPtr<ReleaseList>,
/// Allocation entries.
pub entries: [TreePtr<Node>; RELEASE_LIST_ENTRIES],
}
unsafe impl BlockTrait for ReleaseList {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self {
prev: BlockPtr::default(),
entries: [TreePtr::<Node>::default(); RELEASE_LIST_ENTRIES],
})
} else {
None
}
}
}
impl fmt::Debug for ReleaseList {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let prev = self.prev;
let entries: Vec<_> = self
.entries
.iter()
.filter(|entry| !entry.is_null())
.map(|entry| entry.id())
.collect();
f.debug_struct("ReleaseList")
.field("prev", &prev)
.field("entries", &entries)
.finish()
}
}
impl ops::Deref for ReleaseList {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const ReleaseList as *const u8,
mem::size_of::<ReleaseList>(),
) as &[u8]
}
}
}
impl ops::DerefMut for ReleaseList {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut ReleaseList as *mut u8,
mem::size_of::<ReleaseList>(),
) as &mut [u8]
}
}
}
#[test]
fn alloc_node_size_test() {
assert_eq!(mem::size_of::<AllocList>(), crate::BLOCK_SIZE as usize);
}
#[test]
fn release_node_size_test() {
assert_eq!(mem::size_of::<ReleaseList>(), crate::BLOCK_SIZE as usize);
}
#[test]
fn allocator_test() {
let mut alloc = Allocator::default();
assert_eq!(alloc.allocate(BlockMeta::default()), None);
alloc.deallocate(unsafe { BlockAddr::new(1, BlockMeta::default()) });
assert_eq!(
alloc.allocate(BlockMeta::default()),
Some(unsafe { BlockAddr::new(1, BlockMeta::default()) })
);
assert_eq!(alloc.allocate(BlockMeta::default()), None);
for addr in 1023..2048 {
alloc.deallocate(unsafe { BlockAddr::new(addr, BlockMeta::default()) });
}
assert_eq!(alloc.levels.len(), 11);
for level in 0..alloc.levels.len() {
if level == 0 {
assert_eq!(alloc.levels[level], [1023].into());
} else if level == 10 {
assert_eq!(alloc.levels[level], [1024].into());
} else {
assert_eq!(alloc.levels[level], [0u64; 0].into());
}
}
for addr in 1023..2048 {
assert_eq!(
alloc.allocate(BlockMeta::default()),
Some(unsafe { BlockAddr::new(addr, BlockMeta::default()) })
);
}
assert_eq!(alloc.allocate(BlockMeta::default()), None);
assert_eq!(alloc.levels.len(), 11);
for level in 0..alloc.levels.len() {
assert_eq!(alloc.levels[level], [0u64; 0].into());
}
}
-207
View File
@@ -1,207 +0,0 @@
use core::{
mem,
ops::{Deref, DerefMut},
slice,
};
use super::error::{Error, Result};
pub const PAGE_SIZE: usize = 4096;
/// Size of the metadata region used to transfer information from the kernel to the bootstrapper.
pub const KERNEL_METADATA_SIZE: usize = 4 * PAGE_SIZE;
#[cfg(feature = "userspace")]
macro_rules! syscall {
($($name:ident($a:ident, $($b:ident, $($c:ident, $($d:ident, $($e:ident, $($f:ident, $($g:ident, )?)?)?)?)?)?);)+) => {
$(
pub unsafe fn $name($a: usize, $($b: usize, $($c: usize, $($d: usize, $($e: usize, $($f: usize, $($g: usize)?)?)?)?)?)?) -> Result<usize> {
let ret: usize;
core::arch::asm!(
"svc 0",
in("x8") $a,
$(
in("x0") $b,
$(
in("x1") $c,
$(
in("x2") $d,
$(
in("x3") $e,
$(
in("x4") $f,
$(
in("x5") $g,
)?
)?
)?
)?
)?
)?
lateout("x0") ret,
options(nostack),
);
Error::demux(ret)
}
)+
};
}
#[cfg(feature = "userspace")]
syscall! {
syscall0(a,);
syscall1(a, b,);
syscall2(a, b, c,);
syscall3(a, b, c, d,);
syscall4(a, b, c, d, e,);
syscall5(a, b, c, d, e, f,);
syscall6(a, b, c, d, e, f, g,);
}
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
pub struct IntRegisters {
pub x30: usize,
pub x29: usize,
pub x28: usize,
pub x27: usize,
pub x26: usize,
pub x25: usize,
pub x24: usize,
pub x23: usize,
pub x22: usize,
pub x21: usize,
pub x20: usize,
pub x19: usize,
pub x18: usize,
pub x17: usize,
pub x16: usize,
pub x15: usize,
pub x14: usize,
pub x13: usize,
pub x12: usize,
pub x11: usize,
pub x10: usize,
pub x9: usize,
pub x8: usize,
pub x7: usize,
pub x6: usize,
pub x5: usize,
pub x4: usize,
pub x3: usize,
pub x2: usize,
pub x1: usize,
pub x0: usize,
}
impl Deref for IntRegisters {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const IntRegisters as *const u8,
mem::size_of::<IntRegisters>(),
)
}
}
}
impl DerefMut for IntRegisters {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut IntRegisters as *mut u8,
mem::size_of::<IntRegisters>(),
)
}
}
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(C, packed)]
pub struct FloatRegisters {
pub fp_simd_regs: [u128; 32],
pub fpsr: u32,
pub fpcr: u32,
}
impl Deref for FloatRegisters {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const FloatRegisters as *const u8,
mem::size_of::<FloatRegisters>(),
)
}
}
}
impl DerefMut for FloatRegisters {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut FloatRegisters as *mut u8,
mem::size_of::<FloatRegisters>(),
)
}
}
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(C, packed)]
pub struct EnvRegisters {
pub tpidr_el0: usize,
pub tpidrro_el0: usize,
}
impl Deref for EnvRegisters {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const EnvRegisters as *const u8,
mem::size_of::<EnvRegisters>(),
)
}
}
}
impl DerefMut for EnvRegisters {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut EnvRegisters as *mut u8,
mem::size_of::<EnvRegisters>(),
)
}
}
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(C, packed)]
pub struct Exception {
pub kind: usize,
// TODO
}
impl Deref for Exception {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const Exception as *const u8,
mem::size_of::<Exception>(),
)
}
}
}
impl DerefMut for Exception {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut Exception as *mut u8,
mem::size_of::<Exception>(),
)
}
}
}
-206
View File
@@ -1,206 +0,0 @@
use super::error::{Error, Result};
use core::arch::asm;
use core::{
mem,
ops::{Deref, DerefMut},
slice,
};
pub const PAGE_SIZE: usize = 4096;
/// Size of the metadata region used to transfer information from the kernel to the bootstrapper.
pub const KERNEL_METADATA_SIZE: usize = 4 * PAGE_SIZE;
#[cfg(feature = "userspace")]
macro_rules! syscall {
($($name:ident($a:ident, $($b:ident, $($c:ident, $($d:ident, $($e:ident, $($f:ident, $($g:ident, )?)?)?)?)?)?);)+) => {
$(
pub unsafe fn $name($a: usize, $($b: usize, $($c: usize, $($d: usize, $($e: usize, $($f: usize, $($g: usize)?)?)?)?)?)?) -> Result<usize> {
let ret: usize;
asm!(
"ecall",
in("a7") $a,
$(
in("a0") $b,
$(
in("a1") $c,
$(
in("a2") $d,
$(
in("a3") $e,
$(
in("a4") $f,
$(
in("a5") $g,
)?
)?
)?
)?
)?
)?
lateout("a0") ret,
options(nostack),
);
Error::demux(ret)
}
)+
};
}
#[cfg(feature = "userspace")]
syscall! {
syscall0(a,);
syscall1(a, b,);
syscall2(a, b, c,);
syscall3(a, b, c, d,);
syscall4(a, b, c, d, e,);
syscall5(a, b, c, d, e, f,);
syscall6(a, b, c, d, e, f, g,);
}
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
pub struct IntRegisters {
pub pc: usize,
pub x31: usize,
pub x30: usize,
pub x29: usize,
pub x28: usize,
pub x27: usize,
pub x26: usize,
pub x25: usize,
pub x24: usize,
pub x23: usize,
pub x22: usize,
pub x21: usize,
pub x20: usize,
pub x19: usize,
pub x18: usize,
pub x17: usize,
pub x16: usize,
pub x15: usize,
pub x14: usize,
pub x13: usize,
pub x12: usize,
pub x11: usize,
pub x10: usize,
pub x9: usize,
pub x8: usize,
pub x7: usize,
pub x6: usize,
pub x5: usize,
// x4(tp) is in env
// x3(gp) is a platform scratch register
pub x2: usize,
pub x1: usize,
}
impl Deref for IntRegisters {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const IntRegisters as *const u8,
mem::size_of::<IntRegisters>(),
)
}
}
}
impl DerefMut for IntRegisters {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut IntRegisters as *mut u8,
mem::size_of::<IntRegisters>(),
)
}
}
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(C, packed)]
pub struct FloatRegisters {
pub fregs: [u64; 32],
pub fcsr: u32,
}
impl Deref for FloatRegisters {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const FloatRegisters as *const u8,
mem::size_of::<FloatRegisters>(),
)
}
}
}
impl DerefMut for FloatRegisters {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut FloatRegisters as *mut u8,
mem::size_of::<FloatRegisters>(),
)
}
}
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(packed)]
pub struct EnvRegisters {
pub tp: usize,
}
impl Deref for EnvRegisters {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const EnvRegisters as *const u8,
mem::size_of::<EnvRegisters>(),
)
}
}
}
impl DerefMut for EnvRegisters {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut EnvRegisters as *mut u8,
mem::size_of::<EnvRegisters>(),
)
}
}
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(C, packed)]
pub struct Exception {
pub kind: usize,
// TODO
}
impl Deref for Exception {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const Exception as *const u8,
mem::size_of::<Exception>(),
)
}
}
}
impl DerefMut for Exception {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut Exception as *mut u8,
mem::size_of::<Exception>(),
)
}
}
}
-288
View File
@@ -1,288 +0,0 @@
use core::{
arch::asm,
mem,
ops::{Deref, DerefMut},
slice,
};
use super::error::{Error, Result};
pub const PAGE_SIZE: usize = 4096;
/// Size of the metadata region used to transfer information from the kernel to the bootstrapper.
pub const KERNEL_METADATA_SIZE: usize = 4 * PAGE_SIZE;
#[cfg(feature = "userspace")]
macro_rules! syscall {
($($name:ident($a:ident, $($b:ident, $($c:ident, $($d:ident, $($e:ident, $($f:ident, )?)?)?)?)?);)+) => {
$(
pub unsafe fn $name(mut $a: usize, $($b: usize, $($c: usize, $($d: usize, $($e: usize, $($f: usize)?)?)?)?)?) -> Result<usize> {
asm!(
"int 0x80",
inout("eax") $a,
$(
in("ebx") $b,
$(
in("ecx") $c,
$(
in("edx") $d,
$(
in("esi") $e,
$(
in("edi") $f,
)?
)?
)?
)?
)?
options(nostack),
);
Error::demux($a)
}
)+
};
}
#[cfg(feature = "userspace")]
syscall! {
syscall0(a,);
syscall1(a, b,);
syscall2(a, b, c,);
syscall3(a, b, c, d,);
// Must be done custom because LLVM reserves ESI
//syscall4(a, b, c, d, e,);
//syscall5(a, b, c, d, e, f,);
//syscall6(a, b, c, d, e, f, g,);
}
#[cfg(feature = "userspace")]
pub unsafe fn syscall4(mut a: usize, b: usize, c: usize, d: usize, e: usize) -> Result<usize> {
asm!(
"xchg esi, {e}
int 0x80
xchg esi, {e}",
e = in(reg) e,
inout("eax") a,
in("ebx") b,
in("ecx") c,
in("edx") d,
options(nostack),
);
Error::demux(a)
}
#[cfg(feature = "userspace")]
pub unsafe fn syscall5(
mut a: usize,
b: usize,
c: usize,
d: usize,
e: usize,
f: usize,
) -> Result<usize> {
asm!(
"xchg esi, {e}
int 0x80
xchg esi, {e}",
e = in(reg) e,
inout("eax") a,
in("ebx") b,
in("ecx") c,
in("edx") d,
in("edi") f,
options(nostack),
);
Error::demux(a)
}
#[cfg(feature = "userspace")]
pub unsafe fn syscall6(
mut a: usize,
b: usize,
c: usize,
d: usize,
e: usize,
f: usize,
g: usize,
) -> Result<usize> {
#[repr(C)]
struct PackedArgs {
arg4: usize,
arg6: usize,
nr: usize,
}
let args = PackedArgs {
arg4: e,
arg6: g,
nr: a,
};
let args_ptr = &args as *const PackedArgs;
asm!(
"push ebp",
"push esi",
"mov esi, [eax + 0]", // arg4 -> esi
"mov ebp, [eax + 4]", // arg6 -> ebp
"mov eax, [eax + 8]", // nr -> eax
"int 0x80",
"pop esi",
"pop ebp",
inout("eax") args_ptr => a,
in("ebx") b,
in("ecx") c,
in("edx") d,
in("edi") f,
options(nostack),
);
Error::demux(a)
}
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
pub struct IntRegisters {
// TODO: Some of these don't get set by Redox yet. Should they?
pub ebp: usize,
pub esi: usize,
pub edi: usize,
pub ebx: usize,
pub eax: usize,
pub ecx: usize,
pub edx: usize,
// pub orig_rax: usize,
pub eip: usize,
pub cs: usize,
pub eflags: usize,
pub esp: usize,
pub ss: usize,
// pub fs_base: usize,
// pub gs_base: usize,
// pub ds: usize,
// pub es: usize,
pub fs: usize,
// pub gs: usize
}
impl Deref for IntRegisters {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const IntRegisters as *const u8,
mem::size_of::<IntRegisters>(),
)
}
}
}
impl DerefMut for IntRegisters {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut IntRegisters as *mut u8,
mem::size_of::<IntRegisters>(),
)
}
}
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(C, packed)]
pub struct FloatRegisters {
pub fcw: u16,
pub fsw: u16,
pub ftw: u8,
pub _reserved: u8,
pub fop: u16,
pub fip: u64,
pub fdp: u64,
pub mxcsr: u32,
pub mxcsr_mask: u32,
pub st_space: [u128; 8],
pub xmm_space: [u128; 16],
// TODO: YMM/ZMM
}
impl Deref for FloatRegisters {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const FloatRegisters as *const u8,
mem::size_of::<FloatRegisters>(),
)
}
}
}
impl DerefMut for FloatRegisters {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut FloatRegisters as *mut u8,
mem::size_of::<FloatRegisters>(),
)
}
}
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(C, packed)]
pub struct EnvRegisters {
pub fsbase: u32,
pub gsbase: u32,
}
impl Deref for EnvRegisters {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const EnvRegisters as *const u8,
mem::size_of::<EnvRegisters>(),
)
}
}
}
impl DerefMut for EnvRegisters {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut EnvRegisters as *mut u8,
mem::size_of::<EnvRegisters>(),
)
}
}
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(C, packed)]
pub struct Exception {
pub kind: usize,
pub code: usize,
pub address: usize,
}
impl Deref for Exception {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const Exception as *const u8,
mem::size_of::<Exception>(),
)
}
}
}
impl DerefMut for Exception {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut Exception as *mut u8,
mem::size_of::<Exception>(),
)
}
}
}
-194
View File
@@ -1,194 +0,0 @@
use core::{
mem,
ops::{Deref, DerefMut},
slice,
};
pub const PAGE_SIZE: usize = 4096;
/// Size of the metadata region used to transfer information from the kernel to the bootstrapper.
pub const KERNEL_METADATA_SIZE: usize = 4 * PAGE_SIZE;
#[cfg(feature = "userspace")]
macro_rules! syscall {
($($name:ident($a:ident, $($b:ident, $($c:ident, $($d:ident, $($e:ident, $($f:ident, $($g:ident, )?)?)?)?)?)?);)+) => {
$(
pub unsafe fn $name(mut $a: usize, $($b: usize, $($c: usize, $($d: usize, $($e: usize, $($f: usize, $($g: usize)?)?)?)?)?)?) -> crate::error::Result<usize> {
core::arch::asm!(
"syscall",
inout("rax") $a,
$(
in("rdi") $b,
$(
in("rsi") $c,
$(
in("rdx") $d,
$(
in("r10") $e,
$(
in("r8") $f,
$(
in("r9") $g,
)?
)?
)?
)?
)?
)?
out("rcx") _,
out("r11") _,
options(nostack),
);
crate::error::Error::demux($a)
}
)+
};
}
#[cfg(feature = "userspace")]
syscall! {
syscall0(a,);
syscall1(a, b,);
syscall2(a, b, c,);
syscall3(a, b, c, d,);
syscall4(a, b, c, d, e,);
syscall5(a, b, c, d, e, f,);
syscall6(a, b, c, d, e, f, g,);
}
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
pub struct IntRegisters {
pub r15: usize,
pub r14: usize,
pub r13: usize,
pub r12: usize,
pub rbp: usize,
pub rbx: usize,
pub r11: usize,
pub r10: usize,
pub r9: usize,
pub r8: usize,
pub rax: usize,
pub rcx: usize,
pub rdx: usize,
pub rsi: usize,
pub rdi: usize,
pub rip: usize,
pub cs: usize,
pub rflags: usize,
pub rsp: usize,
pub ss: usize,
}
impl Deref for IntRegisters {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self as *const Self as *const u8, mem::size_of::<Self>()) }
}
}
impl DerefMut for IntRegisters {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self as *mut Self as *mut u8, mem::size_of::<Self>()) }
}
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(C, packed)]
pub struct FloatRegisters {
pub fcw: u16,
pub fsw: u16,
pub ftw: u8,
pub _reserved: u8,
pub fop: u16,
pub fip: u64,
pub fdp: u64,
pub mxcsr: u32,
pub mxcsr_mask: u32,
pub st_space: [u128; 8],
pub xmm_space: [u128; 16],
// TODO: YMM/ZMM
}
impl Deref for FloatRegisters {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const FloatRegisters as *const u8,
mem::size_of::<FloatRegisters>(),
)
}
}
}
impl DerefMut for FloatRegisters {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut FloatRegisters as *mut u8,
mem::size_of::<FloatRegisters>(),
)
}
}
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(C, packed)]
pub struct EnvRegisters {
pub fsbase: u64,
pub gsbase: u64,
// TODO: PKRU?
}
impl Deref for EnvRegisters {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const EnvRegisters as *const u8,
mem::size_of::<EnvRegisters>(),
)
}
}
}
impl DerefMut for EnvRegisters {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut EnvRegisters as *mut u8,
mem::size_of::<EnvRegisters>(),
)
}
}
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(C, packed)]
pub struct Exception {
pub kind: usize,
pub code: usize,
pub address: usize,
}
impl Deref for Exception {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const Exception as *const u8,
mem::size_of::<Exception>(),
)
}
}
}
impl DerefMut for Exception {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut Exception as *mut u8,
mem::size_of::<Exception>(),
)
}
}
}
+146
View File
@@ -0,0 +1,146 @@
use std::fs;
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::MetadataExt;
use std::path::Path;
use crate::{Disk, FileSystem, Node, Transaction, TreePtr, BLOCK_SIZE};
fn syscall_err(err: syscall::Error) -> io::Error {
io::Error::from_raw_os_error(err.errno)
}
pub fn archive_at<D: Disk, P: AsRef<Path>>(
tx: &mut Transaction<D>,
parent_path: P,
parent_ptr: TreePtr<Node>,
) -> io::Result<()> {
for entry_res in fs::read_dir(parent_path)? {
let entry = entry_res?;
let metadata = entry.metadata()?;
let file_type = metadata.file_type();
let name = entry.file_name().into_string().map_err(|_| {
io::Error::new(io::ErrorKind::InvalidData, "filename is not valid UTF-8")
})?;
let mode_type = if file_type.is_dir() {
Node::MODE_DIR
} else if file_type.is_file() {
Node::MODE_FILE
} else if file_type.is_symlink() {
Node::MODE_SYMLINK
} else {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("Does not support parsing {:?}", file_type),
));
};
let node_ptr;
{
let mode = mode_type | (metadata.mode() as u16 & Node::MODE_PERM);
let mut node = tx
.create_node(
parent_ptr,
&name,
mode,
metadata.ctime() as u64,
metadata.ctime_nsec() as u32,
)
.map_err(syscall_err)?;
node_ptr = node.ptr();
if node.data().uid() != metadata.uid() || node.data().gid() != metadata.gid() {
node.data_mut().set_uid(metadata.uid());
node.data_mut().set_gid(metadata.gid());
tx.sync_tree(node).map_err(syscall_err)?;
}
}
let path = entry.path();
if file_type.is_dir() {
archive_at(tx, path, node_ptr)?;
} else if file_type.is_file() {
let data = fs::read(path)?;
let count = tx
.write_node(
node_ptr,
0,
&data,
metadata.mtime() as u64,
metadata.mtime_nsec() as u32,
)
.map_err(syscall_err)?;
if count != data.len() {
panic!("file write count {} != {}", count, data.len());
}
} else if file_type.is_symlink() {
let destination = fs::read_link(path)?;
let data = destination.as_os_str().as_bytes();
let count = tx
.write_node(
node_ptr,
0,
data,
metadata.mtime() as u64,
metadata.mtime_nsec() as u32,
)
.map_err(syscall_err)?;
if count != data.len() {
panic!("symlink write count {} != {}", count, data.len());
}
} else {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("Does not support creating {:?}", file_type),
));
}
}
Ok(())
}
pub fn archive<D: Disk, P: AsRef<Path>>(fs: &mut FileSystem<D>, parent_path: P) -> io::Result<u64> {
let end_block = fs
.tx(|tx| {
// Archive_at root node
archive_at(tx, parent_path, TreePtr::root())
.map_err(|err| syscall::Error::new(err.raw_os_error().unwrap()))?;
// Squash alloc log
tx.sync(true)?;
let end_block = tx.header.size() / BLOCK_SIZE;
/* TODO: Cut off any free blocks at the end of the filesystem
let mut end_changed = true;
while end_changed {
end_changed = false;
let allocator = fs.allocator();
let levels = allocator.levels();
for level in 0..levels.len() {
let level_size = 1 << level;
for &block in levels[level].iter() {
if block < end_block && block + level_size >= end_block {
end_block = block;
end_changed = true;
}
}
}
}
*/
// Update header
tx.header.size = (end_block * BLOCK_SIZE).into();
tx.header_changed = true;
tx.sync(false)?;
Ok(end_block)
})
.map_err(syscall_err)?;
Ok((fs.block + end_block) * BLOCK_SIZE)
}
+108
View File
@@ -0,0 +1,108 @@
extern crate redoxfs;
extern crate syscall;
extern crate uuid;
use std::io::Read;
use std::time::{SystemTime, UNIX_EPOCH};
use std::{env, fs, process};
use redoxfs::{archive, DiskFile, FileSystem};
use uuid::Uuid;
fn main() {
env_logger::init();
let mut args = env::args().skip(1);
let disk_path = if let Some(path) = args.next() {
path
} else {
println!("redoxfs-ar: no disk image provided");
println!("redoxfs-ar DISK FOLDER [BOOTLOADER]");
process::exit(1);
};
let folder_path = if let Some(path) = args.next() {
path
} else {
println!("redoxfs-ar: no folder provided");
println!("redoxfs-ar DISK FOLDER [BOOTLOADER]");
process::exit(1);
};
let bootloader_path_opt = args.next();
let disk = match DiskFile::open(&disk_path) {
Ok(disk) => disk,
Err(err) => {
println!("redoxfs-ar: failed to open image {}: {}", disk_path, err);
process::exit(1);
}
};
let mut bootloader = vec![];
if let Some(bootloader_path) = bootloader_path_opt {
match fs::File::open(&bootloader_path) {
Ok(mut file) => match file.read_to_end(&mut bootloader) {
Ok(_) => (),
Err(err) => {
println!(
"redoxfs-ar: failed to read bootloader {}: {}",
bootloader_path, err
);
process::exit(1);
}
},
Err(err) => {
println!(
"redoxfs-ar: failed to open bootloader {}: {}",
bootloader_path, err
);
process::exit(1);
}
}
};
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
match FileSystem::create_reserved(
disk,
None,
&bootloader,
ctime.as_secs(),
ctime.subsec_nanos(),
) {
Ok(mut fs) => {
let size = match archive(&mut fs, &folder_path) {
Ok(ok) => ok,
Err(err) => {
println!("redoxfs-ar: failed to archive {}: {}", folder_path, err);
process::exit(1);
}
};
if let Err(err) = fs.disk.file.set_len(size) {
println!(
"redoxfs-ar: failed to truncate {} to {}: {}",
disk_path, size, err
);
process::exit(1);
}
let uuid = Uuid::from_bytes(fs.header.uuid());
println!(
"redoxfs-ar: created filesystem on {}, reserved {} blocks, size {} MB, uuid {}",
disk_path,
fs.block,
fs.header.size() / 1000 / 1000,
uuid.hyphenated()
);
}
Err(err) => {
println!(
"redoxfs-ar: failed to create filesystem on {}: {}",
disk_path, err
);
process::exit(1);
}
};
}
+153
View File
@@ -0,0 +1,153 @@
extern crate redoxfs;
extern crate syscall;
extern crate uuid;
use std::io::Read;
use std::time::{SystemTime, UNIX_EPOCH};
use std::{env, fs, process};
use redoxfs::{clone, DiskFile, FileSystem};
use uuid::Uuid;
fn main() {
env_logger::init();
let mut args = env::args().skip(1);
let disk_path_old = if let Some(path) = args.next() {
path
} else {
println!("redoxfs-clone: no old disk image provided");
println!("redoxfs-clone NEW-DISK OLD-DISK [BOOTLOADER]");
process::exit(1);
};
let disk_path = if let Some(path) = args.next() {
path
} else {
println!("redoxfs-clone: no new disk image provided");
println!("redoxfs-clone NEW-DISK OLD-DISK [BOOTLOADER]");
process::exit(1);
};
let bootloader_path_opt = args.next();
// Open old disk in readonly mode
let disk_old = match fs::OpenOptions::new()
.read(true)
.write(false)
.open(&disk_path_old)
.map(DiskFile::from)
{
Ok(disk) => disk,
Err(err) => {
println!(
"redoxfs-clone: failed to open old disk image {}: {}",
disk_path_old, err
);
process::exit(1);
}
};
let mut fs_old = match FileSystem::open(disk_old, None, None, false) {
Ok(fs) => fs,
Err(err) => {
println!(
"redoxfs-clone: failed to open filesystem on {}: {}",
disk_path_old, err
);
process::exit(1);
}
};
let disk = match DiskFile::open(&disk_path) {
Ok(disk) => disk,
Err(err) => {
println!(
"redoxfs-clone: failed to open new disk image {}: {}",
disk_path, err
);
process::exit(1);
}
};
let mut bootloader = vec![];
if let Some(bootloader_path) = bootloader_path_opt {
match fs::File::open(&bootloader_path) {
Ok(mut file) => match file.read_to_end(&mut bootloader) {
Ok(_) => (),
Err(err) => {
println!(
"redoxfs-clone: failed to read bootloader {}: {}",
bootloader_path, err
);
process::exit(1);
}
},
Err(err) => {
println!(
"redoxfs-clone: failed to open bootloader {}: {}",
bootloader_path, err
);
process::exit(1);
}
}
};
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
let mut fs = match FileSystem::create_reserved(
disk,
None,
&bootloader,
ctime.as_secs(),
ctime.subsec_nanos(),
) {
Ok(fs) => fs,
Err(err) => {
println!(
"redoxfs-clone: failed to create filesystem on {}: {}",
disk_path, err
);
process::exit(1);
}
};
let size_old = fs_old.header.size();
let free_old = fs_old.allocator().free() * redoxfs::BLOCK_SIZE;
let used_old = size_old - free_old;
let mut last_percent = 0;
let clone_res = clone(&mut fs_old, &mut fs, move |used| {
let percent = (used * 100) / used_old;
if percent != last_percent {
eprint!(
"\r{}%: {} MB/{} MB",
percent,
used / 1000 / 1000,
used_old / 1000 / 1000
);
last_percent = percent;
}
});
eprintln!();
match clone_res {
Ok(()) => (),
Err(err) => {
println!(
"redoxfs-clone: failed to clone {} to {}: {}",
disk_path_old, disk_path, err
);
process::exit(1);
}
}
let uuid = Uuid::from_bytes(fs.header.uuid());
let size = fs.header.size();
let free = fs.allocator().free() * redoxfs::BLOCK_SIZE;
let used = size - free;
println!("redoxfs-clone: created filesystem on {}", disk_path,);
println!("\treserved: {} blocks", fs.block);
println!("\tuuid: {}", uuid.hyphenated());
println!("\tsize: {} MB", size / 1000 / 1000);
println!("\tused: {} MB", used / 1000 / 1000);
println!("\tfree: {} MB", free / 1000 / 1000);
}
+121
View File
@@ -0,0 +1,121 @@
extern crate redoxfs;
extern crate uuid;
use std::io::Read;
use std::{env, fs, io, process, time};
use redoxfs::{DiskFile, FileSystem};
use termion::input::TermRead;
use uuid::Uuid;
fn usage() -> ! {
eprintln!("redoxfs-mkfs [--encrypt] DISK [BOOTLOADER]");
process::exit(1);
}
fn main() {
env_logger::init();
let mut encrypt = false;
let mut disk_path_opt = None;
let mut bootloader_path_opt = None;
for arg in env::args().skip(1) {
if arg == "--encrypt" {
encrypt = true;
} else if disk_path_opt.is_none() {
disk_path_opt = Some(arg);
} else if bootloader_path_opt.is_none() {
bootloader_path_opt = Some(arg);
} else {
eprintln!("redoxfs-mkfs: too many arguments provided");
usage();
}
}
let disk_path = if let Some(path) = disk_path_opt {
path
} else {
eprintln!("redoxfs-mkfs: no disk image provided");
usage();
};
let disk = match DiskFile::open(&disk_path) {
Ok(disk) => disk,
Err(err) => {
eprintln!("redoxfs-mkfs: failed to open image {}: {}", disk_path, err);
process::exit(1);
}
};
let mut bootloader = vec![];
if let Some(bootloader_path) = bootloader_path_opt {
match fs::File::open(&bootloader_path) {
Ok(mut file) => match file.read_to_end(&mut bootloader) {
Ok(_) => (),
Err(err) => {
eprintln!(
"redoxfs-mkfs: failed to read bootloader {}: {}",
bootloader_path, err
);
process::exit(1);
}
},
Err(err) => {
eprintln!(
"redoxfs-mkfs: failed to open bootloader {}: {}",
bootloader_path, err
);
process::exit(1);
}
}
};
let password_opt = if encrypt {
eprint!("redoxfs-mkfs: password: ");
let password = io::stdin()
.read_passwd(&mut io::stderr())
.unwrap()
.unwrap_or_default();
eprintln!();
if password.is_empty() {
eprintln!("redoxfs-mkfs: empty password, giving up");
process::exit(1);
}
Some(password)
} else {
None
};
let ctime = time::SystemTime::now()
.duration_since(time::UNIX_EPOCH)
.unwrap();
match FileSystem::create_reserved(
disk,
password_opt.as_ref().map(|x| x.as_bytes()),
&bootloader,
ctime.as_secs(),
ctime.subsec_nanos(),
) {
Ok(filesystem) => {
let uuid = Uuid::from_bytes(filesystem.header.uuid());
eprintln!(
"redoxfs-mkfs: created filesystem on {}, reserved {} blocks, size {} MB, uuid {}",
disk_path,
filesystem.block,
filesystem.header.size() / 1000 / 1000,
uuid.hyphenated()
);
}
Err(err) => {
eprintln!(
"redoxfs-mkfs: failed to create filesystem on {}: {}",
disk_path, err
);
process::exit(1);
}
}
}
+410
View File
@@ -0,0 +1,410 @@
extern crate libc;
extern crate redoxfs;
#[cfg(target_os = "redox")]
extern crate syscall;
extern crate uuid;
use std::env;
use std::fs::File;
use std::io::{self, Read, Write};
use std::os::unix::io::{FromRawFd, RawFd};
use std::process;
#[cfg(target_os = "redox")]
use std::{mem::MaybeUninit, ptr::addr_of_mut, sync::atomic::Ordering};
use redoxfs::{mount, DiskCache, DiskFile, FileSystem};
use termion::input::TermRead;
use uuid::Uuid;
#[cfg(target_os = "redox")]
extern "C" fn unmount_handler(_s: usize) {
redoxfs::IS_UMT.store(1, Ordering::SeqCst);
}
#[cfg(target_os = "redox")]
//set up a signal handler on redox, this implements unmounting. I have no idea what sa_flags is
//for, so I put 2. I don't think 0,0 is a valid sa_mask. I don't know what i'm doing here. When u
//send it a sigkill, it shuts off the filesystem
fn setsig() {
// TODO: High-level wrapper like the nix crate?
unsafe {
let mut action = MaybeUninit::<libc::sigaction>::uninit();
assert_eq!(
libc::sigemptyset(addr_of_mut!((*action.as_mut_ptr()).sa_mask)),
0
);
addr_of_mut!((*action.as_mut_ptr()).sa_flags).write(0);
addr_of_mut!((*action.as_mut_ptr()).sa_sigaction)
.write(unmount_handler as *const () as usize);
assert_eq!(
libc::sigaction(libc::SIGTERM, action.as_ptr(), core::ptr::null_mut()),
0
);
}
}
#[cfg(not(target_os = "redox"))]
// on linux, this is implemented properly, so no need for this unscrupulous nonsense!
fn setsig() {}
fn fork() -> isize {
unsafe { libc::fork() as isize }
}
fn pipe(pipes: &mut [i32; 2]) -> isize {
unsafe { libc::pipe(pipes.as_mut_ptr()) as isize }
}
#[cfg(not(target_os = "redox"))]
fn capability_mode() {}
#[cfg(not(target_os = "redox"))]
fn bootloader_password() -> Option<Vec<u8>> {
None
}
#[cfg(target_os = "redox")]
fn capability_mode() {
libredox::call::setrens(0, 0).expect("redoxfs: failed to enter null namespace");
}
#[cfg(target_os = "redox")]
fn bootloader_password() -> Option<Vec<u8>> {
use libredox::call::MmapArgs;
let addr_env = env::var_os("REDOXFS_PASSWORD_ADDR")?;
let size_env = env::var_os("REDOXFS_PASSWORD_SIZE")?;
let addr = usize::from_str_radix(
addr_env.to_str().expect("REDOXFS_PASSWORD_ADDR not valid"),
16,
)
.expect("failed to parse REDOXFS_PASSWORD_ADDR");
let size = usize::from_str_radix(
size_env.to_str().expect("REDOXFS_PASSWORD_SIZE not valid"),
16,
)
.expect("failed to parse REDOXFS_PASSWORD_SIZE");
let mut password = Vec::with_capacity(size);
unsafe {
let aligned_size = size.next_multiple_of(syscall::PAGE_SIZE);
let fd = libredox::Fd::open("memory:physical", libredox::flag::O_CLOEXEC, 0)
.expect("failed to open physical memory file");
let password_map = libredox::call::mmap(MmapArgs {
addr: core::ptr::null_mut(),
length: aligned_size,
prot: libredox::flag::PROT_READ,
flags: libredox::flag::MAP_SHARED,
fd: fd.raw(),
offset: addr as u64,
})
.expect("failed to map REDOXFS_PASSWORD")
.cast::<u8>();
for i in 0..size {
password.push(password_map.add(i).read());
}
let _ = libredox::call::munmap(password_map.cast(), aligned_size);
}
Some(password)
}
fn print_err_exit(err: impl AsRef<str>) -> ! {
eprintln!("redoxfs: {}", err.as_ref());
usage();
process::exit(1)
}
fn print_usage_exit() -> ! {
usage();
process::exit(1)
}
fn usage() {
eprintln!("redoxfs [--no-daemon|-d] [--uuid] [disk or uuid] [mountpoint] [block in hex]");
}
enum DiskId {
Path(String),
Uuid(Uuid),
}
fn filesystem_by_path(
path: &str,
block_opt: Option<u64>,
log_errors: bool,
) -> Option<(String, FileSystem<DiskCache<DiskFile>>)> {
log::debug!("opening {}", path);
let attempts = 10;
for attempt in 0..=attempts {
let password_opt = if attempt > 0 {
eprint!("redoxfs: password: ");
let password = io::stdin()
.read_passwd(&mut io::stderr())
.unwrap()
.unwrap_or_default();
eprintln!();
if password.is_empty() {
eprintln!("redoxfs: empty password, giving up");
// Password is empty, exit loop
break;
}
Some(password.into_bytes())
} else {
bootloader_password()
};
match DiskFile::open(path).map(DiskCache::new) {
Ok(disk) => {
match redoxfs::FileSystem::open(disk, password_opt.as_deref(), block_opt, true) {
Ok(filesystem) => {
log::debug!(
"opened filesystem on {} with uuid {}",
path,
Uuid::from_bytes(filesystem.header.uuid()).hyphenated()
);
return Some((path.to_string(), filesystem));
}
Err(err) => match err.errno {
syscall::ENOKEY => {
if password_opt.is_some() {
eprintln!("redoxfs: incorrect password ({}/{})", attempt, attempts);
}
}
_ => {
if log_errors {
log::error!("failed to open filesystem {}: {}", path, err);
}
break;
}
},
}
}
Err(err) => {
if log_errors {
log::error!("failed to open image {}: {}", path, err);
}
break;
}
}
}
None
}
#[cfg(not(target_os = "redox"))]
fn filesystem_by_uuid(
_uuid: &Uuid,
_block_opt: Option<u64>,
) -> Option<(String, FileSystem<DiskCache<DiskFile>>)> {
None
}
#[cfg(target_os = "redox")]
fn filesystem_by_uuid(
uuid: &Uuid,
block_opt: Option<u64>,
) -> Option<(String, FileSystem<DiskCache<DiskFile>>)> {
use std::fs;
use redox_path::RedoxPath;
match fs::read_dir("/scheme") {
Ok(entries) => {
for entry_res in entries {
if let Ok(entry) = entry_res {
if let Some(disk) = entry.path().to_str() {
if RedoxPath::from_absolute(disk)
.unwrap_or(RedoxPath::from_absolute("/")?)
.is_scheme_category("disk")
{
log::debug!("found scheme {}", disk);
match fs::read_dir(disk) {
Ok(entries) => {
for entry_res in entries {
if let Ok(entry) = entry_res {
if let Ok(path) =
entry.path().into_os_string().into_string()
{
log::debug!("found path {}", path);
if let Some((path, filesystem)) =
filesystem_by_path(&path, block_opt, false)
{
if &filesystem.header.uuid() == uuid.as_bytes()
{
log::debug!(
"filesystem on {} matches uuid {}",
path,
uuid.hyphenated()
);
return Some((path, filesystem));
} else {
log::debug!(
"filesystem on {} does not match uuid {}",
path,
uuid.hyphenated()
);
}
}
}
}
}
}
Err(err) => {
log::debug!("failed to list '{}': {}", disk, err);
}
}
}
}
}
}
}
Err(err) => {
log::error!("failed to list schemes: {}", err);
}
}
None
}
fn daemon(
disk_id: &DiskId,
mountpoint: &str,
block_opt: Option<u64>,
mut write: Option<File>,
) -> ! {
setsig();
let filesystem_opt = match *disk_id {
DiskId::Path(ref path) => filesystem_by_path(path, block_opt, true),
DiskId::Uuid(ref uuid) => filesystem_by_uuid(uuid, block_opt),
};
if let Some((path, filesystem)) = filesystem_opt {
match mount(filesystem, mountpoint, |mounted_path| {
capability_mode();
log::info!(
"mounted filesystem on {} to {}",
path,
mounted_path.display()
);
if let Some(ref mut write) = write {
let _ = write.write(&[0]);
}
}) {
Ok(()) => {
process::exit(0);
}
Err(err) => {
log::error!("failed to mount {} to {}: {}", path, mountpoint, err);
}
}
}
match *disk_id {
DiskId::Path(ref path) => {
log::error!("not able to mount path {}", path);
}
DiskId::Uuid(ref uuid) => {
log::error!("not able to mount uuid {}", uuid.hyphenated());
}
}
if let Some(ref mut write) = write {
let _ = write.write(&[1]);
}
process::exit(1);
}
fn main() {
env_logger::init();
let mut args = env::args().skip(1);
let mut daemonise = true;
let mut disk_id: Option<DiskId> = None;
let mut mountpoint: Option<String> = None;
let mut block_opt: Option<u64> = None;
while let Some(arg) = args.next() {
match arg.as_str() {
"--no-daemon" | "-d" => daemonise = false,
"--uuid" if disk_id.is_none() => {
disk_id = Some(DiskId::Uuid(
match args.next().as_deref().map(Uuid::parse_str) {
Some(Ok(uuid)) => uuid,
Some(Err(err)) => {
print_err_exit(format!("invalid uuid '{}': {}", arg, err))
}
None => print_err_exit("no uuid provided"),
},
));
}
disk if disk_id.is_none() => disk_id = Some(DiskId::Path(disk.to_owned())),
mnt if disk_id.is_some() && mountpoint.is_none() => mountpoint = Some(mnt.to_owned()),
opts if mountpoint.is_some() => match u64::from_str_radix(opts, 16) {
Ok(block) => block_opt = Some(block),
Err(err) => print_err_exit(format!("invalid block '{}': {}", opts, err)),
},
_ => print_usage_exit(),
}
}
let Some(disk_id) = disk_id else {
print_err_exit("no disk provided");
};
let Some(mountpoint) = mountpoint else {
print_err_exit("no mountpoint provided");
};
if daemonise {
let mut pipes = [0; 2];
if pipe(&mut pipes) == 0 {
let mut read = unsafe { File::from_raw_fd(pipes[0] as RawFd) };
let write = unsafe { File::from_raw_fd(pipes[1] as RawFd) };
let pid = fork();
if pid == 0 {
drop(read);
daemon(&disk_id, &mountpoint, block_opt, Some(write));
} else if pid > 0 {
drop(write);
let mut res = [0];
read.read_exact(&mut res).unwrap();
process::exit(res[0] as i32);
} else {
panic!("redoxfs: failed to fork");
}
} else {
panic!("redoxfs: failed to create pipe");
}
} else {
log::info!("running in foreground");
daemon(&disk_id, &mountpoint, block_opt, None);
}
}
+206
View File
@@ -0,0 +1,206 @@
use std::{env, process};
use humansize::{format_size, BINARY, DECIMAL};
use redoxfs::{BlockAddr, BlockMeta, Disk, DiskFile, FileSystem};
use uuid::Uuid;
fn resize<D: Disk>(fs: &mut FileSystem<D>, size_arg: String) -> Result<(), String> {
let disk_size = fs
.disk
.size()
.map_err(|err| format!("failed to read disk size: {}", err))?;
// Find contiguous free region
//TODO: better error management
let mut last_free = None;
let mut last_end = 0;
fs.tx(|tx| {
let mut alloc_ptr = tx.header.alloc;
while !alloc_ptr.is_null() {
let alloc = tx.read_block(alloc_ptr)?;
alloc_ptr = alloc.data().prev;
for entry in alloc.data().entries.iter() {
let count = entry.count();
if count <= 0 {
continue;
}
let end = entry.index() + count as u64;
if end > last_end {
last_free = Some(*entry);
last_end = end;
}
}
}
Ok(())
})
.map_err(|err| format!("failed to read alloc log: {}", err))?;
let old_size = fs.header.size();
let min_size = if let Some(entry) = last_free {
entry.index() * redoxfs::BLOCK_SIZE
} else {
old_size
};
let max_size = disk_size - (fs.block * redoxfs::BLOCK_SIZE);
let new_size = match size_arg.to_lowercase().as_str() {
"min" | "minimum" => min_size,
"" | "max" | "maximum" => max_size,
_ => match parse_size::parse_size(&size_arg) {
Ok(new_size) => {
if new_size < min_size {
return Err(format!(
"requested size {} is smaller than {} by {}",
new_size,
min_size,
min_size - new_size
));
}
if new_size > max_size {
return Err(format!(
"requested size {} is larger than {} by {}",
new_size,
max_size,
new_size - max_size
));
}
new_size
}
Err(err) => {
return Err(format!(
"failed to parse size argument {:?}: {}",
size_arg, err
));
}
},
};
println!(
"minimum size: {} ({})",
format_size(min_size, DECIMAL),
format_size(min_size, BINARY)
);
println!(
"maximum size: {} ({})",
format_size(max_size, DECIMAL),
format_size(max_size, BINARY)
);
println!(
"new size: {} ({})",
format_size(new_size, DECIMAL),
format_size(new_size, BINARY)
);
let old_blocks = old_size / redoxfs::BLOCK_SIZE;
let new_blocks = new_size / redoxfs::BLOCK_SIZE;
let (start, end, shrink) = if new_size == old_size {
println!("already requested size");
return Ok(());
} else if new_size < old_size {
println!("shrinking by {}", old_size - new_size);
(new_blocks, old_blocks, true)
} else {
println!("growing by {}", new_size - old_size);
(old_blocks, new_blocks, false)
};
// Allocate or deallocate blocks as needed
unsafe {
let allocator = fs.allocator_mut();
for index in start..end {
if shrink {
//TODO: replace assert with error?
let addr = BlockAddr::new(index as u64, BlockMeta::default());
assert_eq!(allocator.allocate_exact(addr), Some(addr));
} else {
let addr = BlockAddr::new(index as u64, BlockMeta::default());
allocator.deallocate(addr);
}
}
}
fs.tx(|tx| {
// Update header
tx.header.size = new_size.into();
tx.header_changed = true;
// Sync with squash
tx.sync(true)?;
Ok(())
})
.map_err(|err| format!("transaction failed: {}", err))
}
fn main() {
env_logger::init();
let mut args = env::args().skip(1);
let disk_path = if let Some(path) = args.next() {
path
} else {
eprintln!("redoxfs-resize: no new disk image provided");
eprintln!("redoxfs-resize NEW-DISK [SIZE]");
process::exit(1);
};
let size_arg = args.next().unwrap_or_default();
let disk = match DiskFile::open(&disk_path) {
Ok(disk) => disk,
Err(err) => {
eprintln!(
"redoxfs-resize: failed to open disk image {}: {}",
disk_path, err
);
process::exit(1);
}
};
let mut fs = match FileSystem::open(disk, None, None, true) {
Ok(fs) => fs,
Err(err) => {
eprintln!(
"redoxfs-resize: failed to open filesystem on {}: {}",
disk_path, err
);
process::exit(1);
}
};
match resize(&mut fs, size_arg) {
Ok(()) => {}
Err(err) => {
eprintln!(
"redoxfs-resize: failed to resize filesystem on {}: {}",
disk_path, err
);
process::exit(1);
}
}
let uuid = Uuid::from_bytes(fs.header.uuid());
let size = fs.header.size();
let free = fs.allocator().free() * redoxfs::BLOCK_SIZE;
let used = size - free;
println!("redoxfs-resize: resized filesystem on {}", disk_path);
println!("\tuuid: {}", uuid.hyphenated());
println!(
"\tsize: {} ({})",
format_size(size, DECIMAL),
format_size(size, BINARY)
);
println!(
"\tused: {} ({})",
format_size(used, DECIMAL),
format_size(used, BINARY)
);
println!(
"\tfree: {} ({})",
format_size(free, DECIMAL),
format_size(free, BINARY)
);
}
+393
View File
@@ -0,0 +1,393 @@
use core::{fmt, marker::PhantomData, mem, ops, slice};
use endian_num::Le;
use crate::BLOCK_SIZE;
const BLOCK_LIST_ENTRIES: usize = BLOCK_SIZE as usize / mem::size_of::<BlockPtr<BlockRaw>>();
/// An address of a data block.
///
/// This encodes a block's position _and_ [`BlockLevel`]:
/// the first four bits of this `u64` encode the block's level,
/// the next four bits indicates decompression level,
/// the rest encode its index.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct BlockAddr(u64);
impl BlockAddr {
const INDEX_SHIFT: u64 = 8;
const DECOMP_LEVEL_MASK: u64 = 0xF0;
const DECOMP_LEVEL_SHIFT: u64 = 4;
const LEVEL_MASK: u64 = 0xF;
// Unsafe because this can create invalid blocks
pub unsafe fn new(index: u64, meta: BlockMeta) -> Self {
// Level must fit within LEVEL_MASK
if meta.level.0 > Self::LEVEL_MASK as usize {
panic!("block level too large");
}
// Decomp level must fit within DECOMP_LEVEL_MASK
let decomp_level = meta.decomp_level.unwrap_or_default();
if (decomp_level.0 << Self::DECOMP_LEVEL_SHIFT) > Self::DECOMP_LEVEL_MASK as usize {
panic!("decompressed block level too large");
}
// Index must not use the metadata bits
let inner = index
.checked_shl(Self::INDEX_SHIFT as u32)
.expect("block index too large")
| ((decomp_level.0 as u64) << Self::DECOMP_LEVEL_SHIFT)
| (meta.level.0 as u64);
Self(inner)
}
pub fn null(meta: BlockMeta) -> Self {
unsafe { Self::new(0, meta) }
}
pub fn index(&self) -> u64 {
// The first four bits store the level
self.0 >> Self::INDEX_SHIFT
}
pub fn level(&self) -> BlockLevel {
// The first four bits store the level
BlockLevel((self.0 & Self::LEVEL_MASK) as usize)
}
pub fn decomp_level(&self) -> Option<BlockLevel> {
let value = (self.0 & Self::DECOMP_LEVEL_MASK) >> Self::DECOMP_LEVEL_SHIFT;
if value != 0 {
Some(BlockLevel(value as usize))
} else {
None
}
}
pub fn meta(&self) -> BlockMeta {
BlockMeta {
level: self.level(),
decomp_level: self.decomp_level(),
}
}
pub fn is_null(&self) -> bool {
self.index() == 0
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct BlockMeta {
pub(crate) level: BlockLevel,
pub(crate) decomp_level: Option<BlockLevel>,
}
impl BlockMeta {
pub fn new(level: BlockLevel) -> Self {
Self {
level,
decomp_level: None,
}
}
pub fn new_compressed(level: BlockLevel, decomp_level: BlockLevel) -> Self {
Self {
level,
decomp_level: Some(decomp_level),
}
}
}
/// The size of a block.
///
/// Level 0 blocks are blocks of [`BLOCK_SIZE`] bytes.
/// A level 1 block consists of two consecutive level 0 blocks.
/// A level n block consists of two consecutive level n-1 blocks.
///
/// See [`crate::Allocator`] docs for more details.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct BlockLevel(pub(crate) usize);
impl BlockLevel {
/// Returns the smallest block level that can contain
/// the given number of bytes.
pub(crate) fn for_bytes(bytes: u64) -> Self {
if bytes == 0 {
return BlockLevel(0);
}
let level = bytes
.div_ceil(BLOCK_SIZE)
.next_power_of_two()
.trailing_zeros() as usize;
BlockLevel(level)
}
/// The number of [`BLOCK_SIZE`] blocks (i.e, level 0 blocks)
/// in a block of this level
pub fn blocks<T: From<u32>>(self) -> T {
T::from(1u32 << self.0)
}
/// The number of bytes in a block of this level
pub fn bytes(self) -> u64 {
BLOCK_SIZE << self.0
}
}
pub unsafe trait BlockTrait {
/// Create an empty block of this type.
fn empty(level: BlockLevel) -> Option<Self>
where
Self: Sized;
}
/// A [`BlockAddr`] and the data it points to.
#[derive(Clone, Copy, Debug, Default)]
pub struct BlockData<T> {
addr: BlockAddr,
data: T,
}
impl<T> BlockData<T> {
pub fn new(addr: BlockAddr, data: T) -> Self {
Self { addr, data }
}
pub fn addr(&self) -> BlockAddr {
self.addr
}
pub fn data(&self) -> &T {
&self.data
}
pub fn data_mut(&mut self) -> &mut T {
&mut self.data
}
pub(crate) unsafe fn into_parts(self) -> (BlockAddr, T) {
(self.addr, self.data)
}
/// Set the address of this [`BlockData`] to `addr`, returning this
/// block's old address. This method does not update block data.
///
/// `addr` must point to a block with the same level as this block.
#[must_use = "don't forget to de-allocate old block address"]
pub fn swap_addr(&mut self, addr: BlockAddr) -> BlockAddr {
// Address levels must match
assert_eq!(self.addr.level(), addr.level());
let old = self.addr;
self.addr = addr;
old
}
}
impl<T: BlockTrait> BlockData<T> {
pub fn empty(addr: BlockAddr) -> Option<Self> {
let empty = T::empty(addr.level())?;
Some(Self::new(addr, empty))
}
}
impl<T: ops::Deref<Target = [u8]>> BlockData<T> {
pub fn create_ptr(&self) -> BlockPtr<T> {
BlockPtr {
addr: self.addr.0.into(),
hash: seahash::hash(self.data.deref()).into(),
phantom: PhantomData,
}
}
}
#[repr(C, packed)]
pub struct BlockList<T> {
pub ptrs: [BlockPtr<T>; BLOCK_LIST_ENTRIES],
}
unsafe impl<T> BlockTrait for BlockList<T> {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self {
ptrs: [BlockPtr::default(); BLOCK_LIST_ENTRIES],
})
} else {
None
}
}
}
impl<T> BlockList<T> {
pub fn is_empty(&self) -> bool {
self.ptrs.iter().all(|ptr| ptr.is_null())
}
}
impl<T> ops::Deref for BlockList<T> {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const BlockList<T> as *const u8,
mem::size_of::<BlockList<T>>(),
) as &[u8]
}
}
}
impl<T> ops::DerefMut for BlockList<T> {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut BlockList<T> as *mut u8,
mem::size_of::<BlockList<T>>(),
) as &mut [u8]
}
}
}
/// An address of a data block, along with a checksum of its data.
///
/// This encodes a block's position _and_ [`BlockLevel`].
/// the first four bits of `addr` encode the block's level,
/// the rest encode its index.
///
/// Also see [`BlockAddr`].
#[repr(C, packed)]
pub struct BlockPtr<T> {
addr: Le<u64>,
hash: Le<u64>,
phantom: PhantomData<T>,
}
impl<T> BlockPtr<T> {
pub fn null(meta: BlockMeta) -> Self {
Self {
addr: BlockAddr::null(meta).0.into(),
hash: 0.into(),
phantom: PhantomData,
}
}
pub fn addr(&self) -> BlockAddr {
BlockAddr(self.addr.to_ne())
}
pub fn hash(&self) -> u64 {
self.hash.to_ne()
}
pub fn is_null(&self) -> bool {
self.addr().is_null()
}
pub fn marker(level: u8) -> Self {
assert!(level <= 0xF);
Self {
addr: (0xFFFF_FFFF_FFFF_FFF0 | (level as u64)).into(),
hash: u64::MAX.into(),
phantom: PhantomData,
}
}
pub fn is_marker(&self) -> bool {
(self.addr.to_ne() | 0xF) == u64::MAX && self.hash.to_ne() == u64::MAX
}
/// Cast BlockPtr to another type
///
/// # Safety
/// Unsafe because it can be used to transmute types
pub unsafe fn cast<U>(self) -> BlockPtr<U> {
BlockPtr {
addr: self.addr,
hash: self.hash,
phantom: PhantomData,
}
}
#[must_use = "the returned pointer should usually be deallocated"]
pub fn clear(&mut self) -> BlockPtr<T> {
let mut ptr = Self::default();
mem::swap(self, &mut ptr);
ptr
}
}
impl<T> Clone for BlockPtr<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for BlockPtr<T> {}
impl<T> Default for BlockPtr<T> {
fn default() -> Self {
Self {
addr: 0.into(),
hash: 0.into(),
phantom: PhantomData,
}
}
}
impl<T> fmt::Debug for BlockPtr<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let addr = self.addr();
let hash = self.hash();
f.debug_struct("BlockPtr")
.field("addr", &addr)
.field("hash", &hash)
.finish()
}
}
#[repr(C, packed)]
#[derive(Clone)]
pub struct BlockRaw([u8; BLOCK_SIZE as usize]);
unsafe impl BlockTrait for BlockRaw {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self([0; BLOCK_SIZE as usize]))
} else {
None
}
}
}
impl ops::Deref for BlockRaw {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.0
}
}
impl ops::DerefMut for BlockRaw {
fn deref_mut(&mut self) -> &mut [u8] {
&mut self.0
}
}
#[test]
fn block_list_size_test() {
assert_eq!(mem::size_of::<BlockList<BlockRaw>>(), BLOCK_SIZE as usize);
}
#[test]
fn block_raw_size_test() {
assert_eq!(mem::size_of::<BlockRaw>(), BLOCK_SIZE as usize);
}
#[test]
fn block_ptr_marker_test() {
let ptr = BlockPtr::<BlockRaw>::marker(0);
assert_eq!(ptr.addr().level().0, 0);
assert!(ptr.is_marker());
let ptr = BlockPtr::<BlockRaw>::marker(2);
assert_eq!(ptr.addr().level().0, 2);
assert!(ptr.is_marker());
}
-425
View File
@@ -1,425 +0,0 @@
use super::{
arch::*,
data::{Map, Stat, StatVfs, StdFsCallMeta, TimeSpec},
error::Result,
flag::*,
number::*,
};
use core::mem;
/// Close a file descriptor
pub fn close(fd: usize) -> Result<usize> {
unsafe { syscall1(SYS_CLOSE, fd) }
}
/// Get the current system time
pub fn clock_gettime(clock: usize, tp: &mut TimeSpec) -> Result<usize> {
unsafe { syscall2(SYS_CLOCK_GETTIME, clock, tp as *mut TimeSpec as usize) }
}
/// Copy and transform a file descriptor into specified fd number
pub fn dup_into(fd: usize, out: usize, buf: &[u8]) -> Result<usize> {
unsafe { syscall4(SYS_DUP_INTO, fd, buf.as_ptr() as usize, buf.len(), out) }
}
/// Copy and transform a file descriptor, letting the kernel allocate the new fd
pub fn dup(fd: usize, buf: &[u8]) -> Result<usize> {
unsafe { syscall3(SYS_DUP, fd, buf.as_ptr() as usize, buf.len()) }
}
/// Copy and transform a file descriptor
pub fn dup2(fd: usize, newfd: usize, buf: &[u8]) -> Result<usize> {
unsafe { syscall4(SYS_DUP2, fd, newfd, buf.as_ptr() as usize, buf.len()) }
}
/// Change file permissions
pub fn fchmod(fd: usize, mode: u16) -> Result<usize> {
unsafe { syscall2(SYS_FCHMOD, fd, mode as usize) }
}
/// Change file ownership
pub fn fchown(fd: usize, uid: u32, gid: u32) -> Result<usize> {
unsafe { syscall3(SYS_FCHOWN, fd, uid as usize, gid as usize) }
}
/// Change file descriptor flags
pub fn fcntl(fd: usize, cmd: usize, arg: usize) -> Result<usize> {
unsafe { syscall3(SYS_FCNTL, fd, cmd, arg) }
}
/// Map a file into memory, but with the ability to set the address to map into, either as a hint
/// or as a requirement of the map.
///
/// # Errors
/// `EACCES` - the file descriptor was not open for reading
/// `EBADF` - if the file descriptor was invalid
/// `ENODEV` - mmapping was not supported
/// `EINVAL` - invalid combination of flags
/// `EEXIST` - if [`MapFlags::MAP_FIXED`] was set, and the address specified was already in use.
///
pub unsafe fn fmap(fd: usize, map: &Map) -> Result<usize> {
syscall3(
SYS_FMAP,
fd,
map as *const Map as usize,
mem::size_of::<Map>(),
)
}
/// Unmap whole (or partial) continous memory-mapped files
pub unsafe fn funmap(addr: usize, len: usize) -> Result<usize> {
syscall2(SYS_FUNMAP, addr, len)
}
/// Retrieve the canonical path of a file
pub fn fpath(fd: usize, buf: &mut [u8]) -> Result<usize> {
unsafe { syscall3(SYS_FPATH, fd, buf.as_mut_ptr() as usize, buf.len()) }
}
/// Create a link to a file
pub fn flink<T: AsRef<str>>(fd: usize, path: T) -> Result<usize> {
let path = path.as_ref();
unsafe { syscall3(SYS_FLINK, fd, path.as_ptr() as usize, path.len()) }
}
/// Rename a file
pub fn frename<T: AsRef<str>>(fd: usize, path: T) -> Result<usize> {
let path = path.as_ref();
unsafe { syscall3(SYS_FRENAME, fd, path.as_ptr() as usize, path.len()) }
}
/// Get metadata about a file
pub fn fstat(fd: usize, stat: &mut Stat) -> Result<usize> {
unsafe {
syscall3(
SYS_FSTAT,
fd,
stat as *mut Stat as usize,
mem::size_of::<Stat>(),
)
}
}
/// Get metadata about a filesystem
pub fn fstatvfs(fd: usize, stat: &mut StatVfs) -> Result<usize> {
unsafe {
syscall3(
SYS_FSTATVFS,
fd,
stat as *mut StatVfs as usize,
mem::size_of::<StatVfs>(),
)
}
}
/// Sync a file descriptor to its underlying medium
pub fn fsync(fd: usize) -> Result<usize> {
unsafe { syscall1(SYS_FSYNC, fd) }
}
/// Truncate or extend a file to a specified length
pub fn ftruncate(fd: usize, len: usize) -> Result<usize> {
unsafe { syscall2(SYS_FTRUNCATE, fd, len) }
}
// Change modify and/or access times
pub fn futimens(fd: usize, times: &[TimeSpec]) -> Result<usize> {
unsafe {
syscall3(
SYS_FUTIMENS,
fd,
times.as_ptr() as usize,
mem::size_of_val(times),
)
}
}
/// Fast userspace mutex
pub unsafe fn futex(
addr: *mut i32,
op: usize,
val: i32,
val2: usize,
addr2: *mut i32,
) -> Result<usize> {
syscall5(
SYS_FUTEX,
addr as usize,
op,
(val as isize) as usize,
val2,
addr2 as usize,
)
}
/// Seek to `offset` bytes in a file descriptor
pub fn lseek(fd: usize, offset: isize, whence: usize) -> Result<usize> {
unsafe { syscall3(SYS_LSEEK, fd, offset as usize, whence) }
}
/// Make a new scheme namespace
pub fn mkns(schemes: &[[usize; 2]]) -> Result<usize> {
unsafe { syscall2(SYS_MKNS, schemes.as_ptr() as usize, schemes.len()) }
}
/// Change mapping flags
pub unsafe fn mprotect(addr: usize, size: usize, flags: MapFlags) -> Result<usize> {
syscall3(SYS_MPROTECT, addr, size, flags.bits())
}
/// Sleep for the time specified in `req`
pub fn nanosleep(req: &TimeSpec, rem: &mut TimeSpec) -> Result<usize> {
unsafe {
syscall2(
SYS_NANOSLEEP,
req as *const TimeSpec as usize,
rem as *mut TimeSpec as usize,
)
}
}
/// Open a file at a specific path into specified fd number
pub fn openat_into<T: AsRef<str>>(
fd: usize,
out: usize,
path: T,
flags: usize,
fcntl_flags: usize,
) -> Result<usize> {
let path = path.as_ref();
unsafe {
syscall6(
SYS_OPENAT_INTO,
fd,
path.as_ptr() as usize,
path.len(),
flags,
fcntl_flags,
out,
)
}
}
/// Open a file at a specific path, letting the kernel allocate the new fd
pub fn openat<T: AsRef<str>>(fd: usize, path: T, flags: usize, fcntl_flags: usize) -> Result<usize> {
let path = path.as_ref();
unsafe {
syscall5(
SYS_OPENAT,
fd,
path.as_ptr() as usize,
path.len(),
flags,
fcntl_flags,
)
}
}
/// Open a file at a specific path with uid/gid filtering.
/// Red Bear: legacy wrapper preserved for bootstrap namespace capability checks.
pub fn openat_with_filter<T: AsRef<str>>(
fd: usize,
path: T,
flags: usize,
fcntl_flags: usize,
uid: u32,
gid: u32,
) -> Result<usize> {
let path = path.as_ref();
unsafe {
syscall6(
SYS_OPENAT_WITH_FILTER,
fd,
path.as_ptr() as usize,
path.len(),
flags | fcntl_flags,
uid as usize,
gid as usize,
)
}
}
/// Send a file descriptor to another process via a socket
pub fn sendfd(receiver_socket: usize, fd: usize, flags: usize, arg: u64) -> Result<usize> {
#[cfg(target_pointer_width = "32")]
unsafe {
syscall5(
SYS_SENDFD,
receiver_socket,
fd,
flags,
arg as u32 as usize,
(arg >> 32) as usize,
)
}
#[cfg(target_pointer_width = "64")]
unsafe {
syscall4(SYS_SENDFD, receiver_socket, fd, flags, arg as usize)
}
}
/// Remove a file at at specific path
pub fn unlinkat<T: AsRef<str>>(fd: usize, path: T, flags: usize) -> Result<usize> {
let path = path.as_ref();
unsafe { syscall4(SYS_UNLINKAT, fd, path.as_ptr() as usize, path.len(), flags) }
}
/// Remove a file at a specific path with uid/gid filtering.
/// Red Bear: legacy wrapper preserved for bootstrap namespace capability checks.
pub fn unlinkat_with_filter<T: AsRef<str>>(
fd: usize,
path: T,
flags: usize,
uid: u32,
gid: u32,
) -> Result<usize> {
let path = path.as_ref();
unsafe {
syscall6(
SYS_UNLINKAT_WITH_FILTER,
fd,
path.as_ptr() as usize,
path.len(),
flags,
uid as usize,
gid as usize,
)
}
}
/// Read from a file descriptor into a buffer
pub fn read(fd: usize, buf: &mut [u8]) -> Result<usize> {
unsafe { syscall3(SYS_READ, fd, buf.as_mut_ptr() as usize, buf.len()) }
}
/// Write a buffer to a file descriptor
///
/// The kernel will attempt to write the bytes in `buf` to the file descriptor `fd`, returning
/// either an `Err`, explained below, or `Ok(count)` where `count` is the number of bytes which
/// were written.
///
/// # Errors
///
/// * `EAGAIN` - the file descriptor was opened with `O_NONBLOCK` and writing would block
/// * `EBADF` - the file descriptor is not valid or is not open for writing
/// * `EFAULT` - `buf` does not point to the process's addressible memory
/// * `EIO` - an I/O error occurred
/// * `ENOSPC` - the device containing the file descriptor has no room for data
/// * `EPIPE` - the file descriptor refers to a pipe or socket whose reading end is closed
pub fn write(fd: usize, buf: &[u8]) -> Result<usize> {
unsafe { syscall3(SYS_WRITE, fd, buf.as_ptr() as usize, buf.len()) }
}
/// Yield the process's time slice to the kernel
///
/// This function will return Ok(0) on success
pub fn sched_yield() -> Result<usize> {
unsafe { syscall0(SYS_YIELD) }
}
pub trait Call {
unsafe fn raw_call(
&self,
payload_ptr: *const u8,
len: usize,
flags: CallFlags,
metadata: &[u64],
) -> Result<usize>;
}
impl Call for usize {
unsafe fn raw_call(
&self,
payload_ptr: *const u8,
len: usize,
flags: CallFlags,
metadata: &[u64],
) -> Result<usize> {
unsafe {
syscall5(
SYS_CALL,
*self,
payload_ptr as usize,
len,
metadata.len() | flags.bits(),
metadata.as_ptr() as usize,
)
}
}
}
impl Call for &[usize] {
unsafe fn raw_call(
&self,
payload_ptr: *const u8,
len: usize,
flags: CallFlags,
metadata: &[u64],
) -> Result<usize> {
let combined_flags = flags | CallFlags::MULTIPLE_FDS;
unsafe {
syscall6(
SYS_CALL,
self.as_ptr() as usize,
payload_ptr as usize,
len,
metadata.len() | combined_flags.bits(),
metadata.as_ptr() as usize,
self.len() * mem::size_of::<usize>(),
)
}
}
}
/// SYS_CALL interface, read-only variant
pub fn call_ro<T: Call>(
fd: T,
payload: &mut [u8],
flags: CallFlags,
metadata: &[u64],
) -> Result<usize> {
unsafe {
fd.raw_call(
payload.as_mut_ptr(),
payload.len(),
flags | CallFlags::READ,
metadata,
)
}
}
/// SYS_CALL interface, write-only variant
pub fn call_wo<T: Call>(
fd: T,
payload: &[u8],
flags: CallFlags,
metadata: &[u64],
) -> Result<usize> {
unsafe {
fd.raw_call(
payload.as_ptr(),
payload.len(),
flags | CallFlags::WRITE,
metadata,
)
}
}
/// SYS_CALL interface, read-write variant
pub fn call_rw<T: Call>(
fd: T,
payload: &mut [u8],
flags: CallFlags,
metadata: &[u64],
) -> Result<usize> {
unsafe {
fd.raw_call(
payload.as_mut_ptr(),
payload.len(),
flags | CallFlags::READ | CallFlags::WRITE,
metadata,
)
}
}
pub fn std_fs_call<T: Call>(fd: T, payload: &mut [u8], metadata: &StdFsCallMeta) -> Result<usize> {
call_rw(fd, payload, CallFlags::STD_FS, metadata)
}
+90
View File
@@ -0,0 +1,90 @@
use crate::{Disk, FileSystem, Node, Transaction, TreePtr, BLOCK_SIZE};
fn tx_progress<D: Disk, F: FnMut(u64)>(tx: &mut Transaction<D>, progress: &mut F) {
let size = tx.header.size();
let free = tx.allocator.free() * BLOCK_SIZE;
progress(size - free);
}
//TODO: handle hard links
fn clone_at<D: Disk, E: Disk, F: FnMut(u64)>(
tx_old: &mut Transaction<D>,
parent_ptr_old: TreePtr<Node>,
tx: &mut Transaction<E>,
parent_ptr: TreePtr<Node>,
buf: &mut [u8],
progress: &mut F,
) -> syscall::Result<()> {
let mut entries = Vec::new();
tx_old.child_nodes(parent_ptr_old, &mut entries)?;
for entry in entries {
//TODO: return error instead?
let Some(name) = entry.name() else {
continue;
};
let node_ptr_old = entry.node_ptr();
let node_old = tx_old.read_tree(node_ptr_old)?;
//TODO: this slows down the clone, but Redox has issues without this (Linux is fine)
if tx.write_cache.len() > 64 {
tx.sync(false)?;
}
let node_ptr = {
let mode = node_old.data().mode();
let (ctime, ctime_nsec) = node_old.data().ctime();
let (mtime, mtime_nsec) = node_old.data().mtime();
let mut node = tx.create_node(parent_ptr, &name, mode, ctime, ctime_nsec)?;
node.data_mut().set_uid(node_old.data().uid());
node.data_mut().set_gid(node_old.data().gid());
node.data_mut().set_mtime(mtime, mtime_nsec);
if !node_old.data().is_dir() {
let mut offset = 0;
loop {
let count = tx_old.read_node_inner(&node_old, offset, buf)?;
if count == 0 {
break;
}
tx.write_node_inner(&mut node, &mut offset, &buf[..count])?;
}
}
let node_ptr = node.ptr();
tx.sync_tree(node)?;
node_ptr
};
tx_progress(tx, progress);
if node_old.data().is_dir() {
clone_at(tx_old, node_ptr_old, tx, node_ptr, buf, progress)?;
}
}
Ok(())
}
pub fn clone<D: Disk, E: Disk, F: FnMut(u64)>(
fs_old: &mut FileSystem<D>,
fs: &mut FileSystem<E>,
mut progress: F,
) -> syscall::Result<()> {
fs_old.tx(|tx_old| {
let mut tx = Transaction::new(fs);
// Clone at root node
let mut buf = vec![0; 4 * 1024 * 1024];
clone_at(
tx_old,
TreePtr::root(),
&mut tx,
TreePtr::root(),
&mut buf,
&mut progress,
)?;
// Commit and squash alloc log
tx.commit(true)
})
}
-501
View File
@@ -1,501 +0,0 @@
use core::{
mem,
ops::{Deref, DerefMut},
slice,
};
use crate::flag::{EventFlags, MapFlags, PtraceFlags, StdFsCallKind};
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
pub struct Event {
pub id: usize,
pub flags: EventFlags,
pub data: usize,
}
impl Deref for Event {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self as *const Event as *const u8, mem::size_of::<Event>()) }
}
}
impl DerefMut for Event {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self as *mut Event as *mut u8, mem::size_of::<Event>()) }
}
}
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
pub struct ITimerSpec {
pub it_interval: TimeSpec,
pub it_value: TimeSpec,
}
impl Deref for ITimerSpec {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const ITimerSpec as *const u8,
mem::size_of::<ITimerSpec>(),
)
}
}
}
impl DerefMut for ITimerSpec {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut ITimerSpec as *mut u8,
mem::size_of::<ITimerSpec>(),
)
}
}
}
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
pub struct OldMap {
pub offset: usize,
pub size: usize,
pub flags: MapFlags,
}
impl Deref for OldMap {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(self as *const OldMap as *const u8, mem::size_of::<OldMap>())
}
}
}
impl DerefMut for OldMap {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(self as *mut OldMap as *mut u8, mem::size_of::<OldMap>())
}
}
}
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
pub struct Map {
/// The offset inside the file that is being mapped.
pub offset: usize,
/// The size of the memory map.
pub size: usize,
/// Contains both prot and map flags.
pub flags: MapFlags,
/// Functions as a hint to where in the virtual address space of the running process, to place
/// the memory map. If [`MapFlags::MAP_FIXED`] is set, then this address must be the address to
/// map to.
pub address: usize,
}
impl Deref for Map {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self as *const Map as *const u8, mem::size_of::<Map>()) }
}
}
impl DerefMut for Map {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self as *mut Map as *mut u8, mem::size_of::<Map>()) }
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq)]
#[repr(C)]
pub struct Stat {
pub st_dev: u64,
pub st_ino: u64,
pub st_mode: u16,
pub st_nlink: u32,
pub st_uid: u32,
pub st_gid: u32,
pub st_size: u64,
pub st_blksize: u32,
pub st_blocks: u64,
pub st_mtime: u64,
pub st_mtime_nsec: u32,
pub st_atime: u64,
pub st_atime_nsec: u32,
pub st_ctime: u64,
pub st_ctime_nsec: u32,
}
impl Deref for Stat {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self as *const Stat as *const u8, mem::size_of::<Stat>()) }
}
}
impl DerefMut for Stat {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self as *mut Stat as *mut u8, mem::size_of::<Stat>()) }
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq)]
#[repr(C)]
pub struct StatVfs {
pub f_bsize: u32,
pub f_blocks: u64,
pub f_bfree: u64,
pub f_bavail: u64,
}
impl Deref for StatVfs {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const StatVfs as *const u8,
mem::size_of::<StatVfs>(),
)
}
}
}
impl DerefMut for StatVfs {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(self as *mut StatVfs as *mut u8, mem::size_of::<StatVfs>())
}
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq)]
#[repr(C, packed)]
pub struct StdFsCallMeta {
pub kind: u8, // enum StdFsCallKind
_rsvd: [u8; 7],
pub arg1: u64,
pub arg2: u64,
}
impl StdFsCallMeta {
pub fn new(kind: StdFsCallKind, arg1: u64, arg2: u64) -> Self {
Self {
kind: kind as u8,
_rsvd: [0; 7],
arg1,
arg2,
}
}
}
impl Deref for StdFsCallMeta {
type Target = [u64];
fn deref(&self) -> &[u64] {
unsafe {
slice::from_raw_parts(
self as *const StdFsCallMeta as *const u64,
mem::size_of::<StdFsCallMeta>() / mem::size_of::<u64>(),
)
}
}
}
impl DerefMut for StdFsCallMeta {
fn deref_mut(&mut self) -> &mut [u64] {
unsafe {
slice::from_raw_parts_mut(
self as *mut StdFsCallMeta as *mut u64,
mem::size_of::<StdFsCallMeta>() / mem::size_of::<u64>(),
)
}
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq)]
#[repr(C)]
pub struct TimeSpec {
pub tv_sec: i64,
pub tv_nsec: i32,
}
const NANOS_PER_SEC: u128 = 1_000_000_000;
impl TimeSpec {
pub fn from_nanos(nanos: u128) -> Self {
Self {
tv_sec: i64::try_from(nanos / NANOS_PER_SEC).unwrap_or(i64::MAX),
tv_nsec: (nanos % NANOS_PER_SEC) as i32, // guaranteed to never overflow
}
}
pub fn to_nanos(&self) -> u128 {
self.tv_sec as u128 * NANOS_PER_SEC + self.tv_nsec as u128
}
}
impl Deref for TimeSpec {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const TimeSpec as *const u8,
mem::size_of::<TimeSpec>(),
)
}
}
}
impl DerefMut for TimeSpec {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(self as *mut TimeSpec as *mut u8, mem::size_of::<TimeSpec>())
}
}
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(C)]
pub struct PtraceEvent {
pub cause: PtraceFlags,
pub a: usize,
pub b: usize,
pub c: usize,
pub d: usize,
pub e: usize,
pub f: usize,
}
impl Deref for PtraceEvent {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const PtraceEvent as *const u8,
mem::size_of::<PtraceEvent>(),
)
}
}
}
impl DerefMut for PtraceEvent {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut PtraceEvent as *mut u8,
mem::size_of::<PtraceEvent>(),
)
}
}
}
#[macro_export]
macro_rules! ptrace_event {
($cause:expr $(, $a:expr $(, $b:expr $(, $c:expr)?)?)?) => {
$crate::data::PtraceEvent {
cause: $cause,
$(a: $a,
$(b: $b,
$(c: $c,)?
)?
)?
..Default::default()
}
}
}
bitflags::bitflags! {
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy, Default)]
pub struct GrantFlags: usize {
const GRANT_READ = 0x0000_0001;
const GRANT_WRITE = 0x0000_0002;
const GRANT_EXEC = 0x0000_0004;
const GRANT_SHARED = 0x0000_0008;
const GRANT_LAZY = 0x0000_0010;
const GRANT_SCHEME = 0x0000_0020;
const GRANT_PHYS = 0x0000_0040;
const GRANT_PINNED = 0x0000_0080;
const GRANT_PHYS_CONTIGUOUS = 0x0000_0100;
}
}
impl GrantFlags {
#[deprecated = "use the safe `from_bits_retain` method instead"]
pub unsafe fn from_bits_unchecked(bits: usize) -> Self {
Self::from_bits_retain(bits)
}
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(C)]
pub struct GrantDesc {
pub base: usize,
pub size: usize,
pub flags: GrantFlags,
pub offset: u64,
}
impl Deref for GrantDesc {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const GrantDesc as *const u8,
mem::size_of::<GrantDesc>(),
)
}
}
}
impl DerefMut for GrantDesc {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut GrantDesc as *mut u8,
mem::size_of::<GrantDesc>(),
)
}
}
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(C)]
pub struct SetSighandlerData {
pub user_handler: usize,
pub excp_handler: usize,
pub thread_control_addr: usize,
pub proc_control_addr: usize,
}
impl Deref for SetSighandlerData {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self as *const Self as *const u8, mem::size_of::<Self>()) }
}
}
impl DerefMut for SetSighandlerData {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self as *mut Self as *mut u8, mem::size_of::<Self>()) }
}
}
pub use crate::sigabi::*;
/// UNSTABLE
#[derive(Copy, Clone, Debug, Default, PartialEq)]
#[repr(C)]
pub struct ProcSchemeAttrs {
pub pid: u32,
pub euid: u32,
pub egid: u32,
pub prio: u32,
pub debug_name: [u8; 32],
}
impl Deref for ProcSchemeAttrs {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self as *const Self as *const u8, mem::size_of::<Self>()) }
}
}
impl DerefMut for ProcSchemeAttrs {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut ProcSchemeAttrs as *mut u8,
mem::size_of::<ProcSchemeAttrs>(),
)
}
}
}
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
pub struct CtxtStsBuf {
pub status: usize,
pub excp: crate::Exception,
}
impl Deref for CtxtStsBuf {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self as *const Self as *const u8, mem::size_of::<Self>()) }
}
}
impl DerefMut for CtxtStsBuf {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut CtxtStsBuf as *mut u8,
mem::size_of::<CtxtStsBuf>(),
)
}
}
}
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
pub struct NewFdParams {
pub offset: u64,
pub number: usize,
pub flags: usize,
pub internal_flags: u8,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum GlobalSchemes {
Debug = 1,
Event = 2,
Memory = 3,
Pipe = 4,
Serio = 5,
Irq = 6,
Time = 7,
Sys = 8,
Proc = 9,
Acpi = 10,
Dtb = 11,
}
impl GlobalSchemes {
pub fn try_from_raw(raw: u8) -> Option<Self> {
match raw {
1 => Some(Self::Debug),
2 => Some(Self::Event),
3 => Some(Self::Memory),
4 => Some(Self::Pipe),
5 => Some(Self::Serio),
6 => Some(Self::Irq),
7 => Some(Self::Time),
8 => Some(Self::Sys),
9 => Some(Self::Proc),
10 => Some(Self::Acpi),
11 => Some(Self::Dtb),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Debug => "debug",
Self::Event => "event",
Self::Memory => "memory",
Self::Pipe => "pipe",
Self::Serio => "serio",
Self::Irq => "irq",
Self::Time => "time",
Self::Sys => "sys",
Self::Proc => "kernel.proc",
Self::Acpi => "kernel.acpi",
Self::Dtb => "kernel.dtb",
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub struct KernelSchemeInfo {
pub scheme_id: u8,
pub fd: usize,
}
+300
View File
@@ -0,0 +1,300 @@
use core::{mem, ops, slice, str};
use crate::{BlockLevel, BlockTrait, Node, TreePtr, BLOCK_SIZE, DIR_ENTRY_MAX_LENGTH};
#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct DirEntry {
node_ptr: TreePtr<Node>,
name: [u8; DIR_ENTRY_MAX_LENGTH],
}
impl DirEntry {
pub fn new(node_ptr: TreePtr<Node>, name: &str) -> DirEntry {
let mut entry = DirEntry {
node_ptr,
..Default::default()
};
entry.name[..name.len()].copy_from_slice(name.as_bytes());
entry
}
pub fn node_ptr(&self) -> TreePtr<Node> {
self.node_ptr
}
fn name_len(&self) -> usize {
let mut len = 0;
while len < self.name.len() {
if self.name[len] == 0 {
break;
}
len += 1;
}
len
}
pub fn name(&self) -> Option<&str> {
let len = self.name_len();
//TODO: report utf8 error?
str::from_utf8(&self.name[..len]).ok()
}
// 4 bytes TreePtr
// 1 byte name_len
const SERIALIZED_PREFIX_SIZE: usize = mem::size_of::<TreePtr<Node>>() + 1;
pub fn serialized_size(&self) -> usize {
DirEntry::SERIALIZED_PREFIX_SIZE + self.name_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Option<usize> {
let required = self.serialized_size();
if buf.len() < required {
return None;
}
buf[0..4].copy_from_slice(&self.node_ptr().to_bytes());
buf[4] = self.name_len() as u8;
buf[5..5 + self.name_len()].copy_from_slice(&self.name[..self.name_len()]);
Some(required)
}
fn deserialize_from(buf: &[u8]) -> Result<(Self, usize), &'static str> {
if buf.len() <= DirEntry::SERIALIZED_PREFIX_SIZE {
return Err("Buffer too small");
}
let node_ptr: TreePtr<Node> =
TreePtr::from_bytes(buf[0..4].try_into().expect("Slice must be 4 bytes long"));
let name_len = buf[4] as usize;
if name_len < 1 || name_len > DIR_ENTRY_MAX_LENGTH {
return Err("Invalid name length");
}
if buf.len() < DirEntry::SERIALIZED_PREFIX_SIZE + name_len {
return Err("Buffer too small");
}
let mut name = [0u8; DIR_ENTRY_MAX_LENGTH];
name[..name_len].copy_from_slice(
&buf[DirEntry::SERIALIZED_PREFIX_SIZE..DirEntry::SERIALIZED_PREFIX_SIZE + name_len],
);
Ok((
DirEntry { node_ptr, name },
DirEntry::SERIALIZED_PREFIX_SIZE + name_len,
))
}
}
impl Default for DirEntry {
fn default() -> Self {
Self {
node_ptr: TreePtr::default(),
name: [0; DIR_ENTRY_MAX_LENGTH],
}
}
}
pub struct DirList {
count: u16,
entry_bytes_len: u16,
entry_bytes: [u8; BLOCK_SIZE as usize - 4],
}
unsafe impl BlockTrait for DirList {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self {
count: 0,
entry_bytes_len: 0,
entry_bytes: [0; BLOCK_SIZE as usize - 4],
})
} else {
None
}
}
}
impl DirList {
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn entries(&self) -> DirEntryIterator<'_> {
DirEntryIterator {
dir_list: self,
emit_count: 0,
position: 0,
}
}
fn entry_position_for_name(&self, name: &str) -> Option<usize> {
let name_len = name.len();
let mut position = 0;
let mut entry_id = 0;
while entry_id < self.count {
let entry_name_len = self.entry_bytes[position + 4] as usize;
if entry_name_len == name_len {
let start = DirEntry::SERIALIZED_PREFIX_SIZE + position;
let entry_name = &self.entry_bytes[start..start + entry_name_len];
if entry_name == name.as_bytes() {
return Some(position);
}
}
position += DirEntry::SERIALIZED_PREFIX_SIZE + entry_name_len;
entry_id += 1;
}
None
}
pub fn find_entry(&self, name: &str) -> Option<DirEntry> {
if let Some(position) = self.entry_position_for_name(name) {
let (entry, _) = DirEntry::deserialize_from(&self.entry_bytes[position..]).unwrap();
return Some(entry);
}
None
}
pub fn remove_entry(&mut self, name: &str) -> bool {
if let Some(position) = self.entry_position_for_name(name) {
let entry_size =
DirEntry::SERIALIZED_PREFIX_SIZE + self.entry_bytes[position + 4] as usize;
let remaining_size = self.entry_bytes_len as usize - position - entry_size;
if remaining_size > 0 {
self.entry_bytes.copy_within(
position + entry_size..self.entry_bytes_len as usize,
position,
);
}
self.entry_bytes_len -= entry_size as u16;
self.count -= 1;
return true;
}
false
}
pub fn for_each_entry<F>(&self, mut f: F)
where
F: FnMut(&[u8; 4], &[u8]),
{
let mut position = 0;
let mut entry_id = 0;
while entry_id < self.count {
let node_ptr_bytes = &self.entry_bytes[position..position + 4];
//let node_ptr = TreePtr::<Node>::from_bytes(node_ptr_bytes.try_into().unwrap());
let entry_name_len = self.entry_bytes[position + 4] as usize;
let start = DirEntry::SERIALIZED_PREFIX_SIZE + position;
let entry_name = &self.entry_bytes[start..start + entry_name_len];
f(node_ptr_bytes.try_into().unwrap(), entry_name);
position += DirEntry::SERIALIZED_PREFIX_SIZE + entry_name_len;
entry_id += 1;
}
}
pub fn append(&mut self, entry: &DirEntry) -> bool {
let entry_bytes_len = self.entry_bytes_len as usize;
if let Some(size) = entry.serialize_into(&mut self.entry_bytes[entry_bytes_len..]) {
self.count += 1;
self.entry_bytes_len += size as u16;
return true;
}
false
}
pub fn entry_count(&self) -> usize {
self.count as usize
}
}
impl ops::Deref for DirList {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const DirList as *const u8,
mem::size_of::<DirList>(),
) as &[u8]
}
}
}
impl ops::DerefMut for DirList {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(self as *mut DirList as *mut u8, mem::size_of::<DirList>())
as &mut [u8]
}
}
}
pub struct DirEntryIterator<'a> {
dir_list: &'a DirList,
emit_count: usize,
position: usize,
}
impl Iterator for DirEntryIterator<'_> {
type Item = DirEntry;
fn next(&mut self) -> Option<Self::Item> {
if self.emit_count < self.dir_list.entry_count() {
let position = self.position;
let (entry, bytes_read) =
DirEntry::deserialize_from(&self.dir_list.entry_bytes[position..]).unwrap();
self.emit_count += 1;
self.position += bytes_read;
Some(entry)
} else {
None
}
}
}
#[cfg(test)]
mod test {
use super::*;
use alloc::format;
#[test]
fn dir_list_size_test() {
use core::ops::Deref;
assert_eq!(
DirList::empty(BlockLevel(0)).unwrap().deref().len(),
BLOCK_SIZE as usize
);
}
#[test]
fn test_append() {
let mut dir_list = DirList::empty(BlockLevel(0)).unwrap();
let dirent = DirEntry::new(TreePtr::new(123), "test000");
assert!(dir_list.append(&dirent));
assert_eq!(dir_list.entry_count(), 1);
assert_eq!(dir_list.entry_bytes_len as usize, dirent.serialized_size());
let max_entries = dir_list.entry_bytes.len() / dirent.serialized_size();
for i in 1..max_entries {
let dirent = DirEntry::new(TreePtr::new(123), format!("test{i:03}").as_str());
assert!(dir_list.append(&dirent), "Failed on iteration {i}");
}
let dirent = DirEntry::new(TreePtr::new(123), format!("test{max_entries}").as_str());
assert!(!dir_list.append(&dirent));
for (i, entry) in dir_list.entries().enumerate() {
assert_eq!(entry.name().unwrap(), format!("test{i:03}"));
}
}
}
-231
View File
@@ -1,231 +0,0 @@
use core::{
mem::size_of,
ops::{Deref, DerefMut},
slice,
};
use crate::{
error::{Error, Result, EINVAL},
ENAMETOOLONG,
};
#[derive(Clone, Copy, Debug, Default)]
#[repr(packed)]
pub struct DirentHeader {
pub inode: u64,
/// A filesystem-specific opaque value used to uniquely identify directory entries. This value,
/// in the last returned entry from a SYS_GETDENTS invocation, shall be passed to the next
/// call.
pub next_opaque_id: u64,
// This struct intentionally does not include a "next" offset field, unlike Linux, to easily
// guarantee the iterator will be reasonably deterministic, even if the scheme is adversarial.
pub record_len: u16,
/// A `DirentKind`.
///
/// May not be directly available (Unspecified), and if so needs to be looked using fstat.
pub kind: u8,
}
impl Deref for DirentHeader {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self as *const Self as *const u8, size_of::<Self>()) }
}
}
impl DerefMut for DirentHeader {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self as *mut Self as *mut u8, size_of::<Self>()) }
}
}
// Note: Must match relibc/include/bits/dirent.h
#[derive(Clone, Copy, Debug, Default)]
#[repr(u8)]
pub enum DirentKind {
#[default]
Unspecified = 0,
CharDev = 2,
Directory = 4,
BlockDev = 6,
Regular = 8,
Symlink = 10,
Socket = 12,
}
impl DirentKind {
// TODO: derive(FromPrimitive)
pub fn try_from_raw(raw: u8) -> Option<Self> {
Some(match raw {
0 => Self::Unspecified,
2 => Self::CharDev,
4 => Self::Directory,
6 => Self::BlockDev,
8 => Self::Regular,
10 => Self::Symlink,
12 => Self::Socket,
_ => return None,
})
}
}
pub struct DirentIter<'a>(&'a [u8]);
impl<'a> DirentIter<'a> {
pub const fn new(buffer: &'a [u8]) -> Self {
Self(buffer)
}
}
#[derive(Debug)]
pub struct Invalid;
impl<'a> Iterator for DirentIter<'a> {
type Item = Result<(&'a DirentHeader, &'a [u8]), Invalid>;
fn next(&mut self) -> Option<Self::Item> {
if self.0.len() < size_of::<DirentHeader>() {
return None;
}
let header = unsafe { &*(self.0.as_ptr().cast::<DirentHeader>()) };
if self.0.len() < usize::from(header.record_len) {
return Some(Err(Invalid));
}
let (this, remaining) = self.0.split_at(usize::from(header.record_len));
self.0 = remaining;
let name_and_nul = &this[size_of::<DirentHeader>()..];
let name = &name_and_nul[..name_and_nul.len() - 1];
Some(Ok((header, name)))
}
}
#[derive(Debug)]
pub struct DirentBuf<B> {
buffer: B,
// Exists in order to allow future extensions to the DirentHeader struct.
// TODO: Might add an upper bound to protect against cache miss DoS. The kernel currently
// forbids any other value than size_of::<DirentHeader>().
header_size: u16,
written: usize,
}
/// Abstraction between &mut [u8] and the kernel's UserSliceWo.
pub trait Buffer<'a>: Sized + 'a {
fn empty() -> Self;
fn length(&self) -> usize;
/// Split all of `self` into two disjoint contiguous subbuffers of lengths `index` and `length
/// - index` respectively.
///
/// Returns None if and only if `index > length`.
fn split_at(self, index: usize) -> Option<[Self; 2]>;
/// Copy from `src`, lengths must match exactly.
///
/// Allowed to overwrite subsequent buffer space, for performance reasons. Can be changed in
/// the future if too restrictive.
fn copy_from_slice_exact(self, src: &[u8]) -> Result<()>;
/// Write zeroes to this part of the buffer.
///
/// Allowed to overwrite subsequent buffer space, for performance reasons. Can be changed in
/// the future if too restrictive.
fn zero_out(self) -> Result<()>;
}
impl<'a> Buffer<'a> for &'a mut [u8] {
fn empty() -> Self {
&mut []
}
fn length(&self) -> usize {
self.len()
}
fn split_at(self, index: usize) -> Option<[Self; 2]> {
self.split_at_mut_checked(index).map(|(a, b)| [a, b])
}
fn copy_from_slice_exact(self, src: &[u8]) -> Result<()> {
self.copy_from_slice(src);
Ok(())
}
fn zero_out(self) -> Result<()> {
self.fill(0);
Ok(())
}
}
pub struct DirEntry<'name> {
pub inode: u64,
pub next_opaque_id: u64,
pub name: &'name str,
pub kind: DirentKind,
}
impl<'a, B: Buffer<'a>> DirentBuf<B> {
pub fn new(buffer: B, header_size: u16) -> Option<Self> {
if usize::from(header_size) < size_of::<DirentHeader>() {
return None;
}
Some(Self {
buffer,
header_size,
written: 0,
})
}
pub fn entry(&mut self, entry: DirEntry<'_>) -> Result<()> {
let name16 = u16::try_from(entry.name.len()).map_err(|_| Error::new(EINVAL))?;
let record_align = align_of::<*const DirentHeader>();
let record_len = self
.header_size
.checked_add(name16)
// XXX: NUL byte. Unfortunately this is probably the only performant way to be
// compatible with C.
.and_then(|l| l.checked_add(1))
// Align length so next header is aligned
.and_then(|l| l.checked_next_multiple_of(record_align as u16))
.ok_or(Error::new(ENAMETOOLONG))?;
let [this, remaining] = core::mem::replace(&mut self.buffer, B::empty())
.split_at(usize::from(record_len))
.ok_or(Error::new(EINVAL))?;
let [this_header_variable, this_name_and_nul] = this
.split_at(usize::from(self.header_size))
.expect("already know header_size + ... >= header_size");
let [this_name, this_name_nul] = this_name_and_nul
.split_at(usize::from(name16))
.expect("already know name.len() <= name.len() + 1");
// Every write here is currently sequential, allowing the buffer trait to do optimizations
// where subbuffer writes are out-of-bounds (but inside the total buffer).
let [this_header, this_header_extra] = this_header_variable
.split_at(size_of::<DirentHeader>())
.expect("already checked header_size <= size_of Header");
this_header.copy_from_slice_exact(&DirentHeader {
record_len,
next_opaque_id: entry.next_opaque_id,
inode: entry.inode,
kind: entry.kind as u8,
})?;
this_header_extra.zero_out()?;
this_name.copy_from_slice_exact(entry.name.as_bytes())?;
this_name_nul.zero_out()?;
self.written += usize::from(record_len);
self.buffer = remaining;
Ok(())
}
pub fn finalize(self) -> usize {
self.written
}
}
+110
View File
@@ -0,0 +1,110 @@
use std::collections::{HashMap, VecDeque};
use std::{cmp, ptr};
use syscall::error::Result;
use crate::disk::Disk;
use crate::BLOCK_SIZE;
fn copy_memory(src: &[u8], dest: &mut [u8]) -> usize {
let len = cmp::min(src.len(), dest.len());
unsafe { ptr::copy(src.as_ptr(), dest.as_mut_ptr(), len) };
len
}
pub struct DiskCache<T> {
inner: T,
cache: HashMap<u64, [u8; BLOCK_SIZE as usize]>,
order: VecDeque<u64>,
size: usize,
}
impl<T: Disk> DiskCache<T> {
pub fn new(inner: T) -> Self {
// 16 MB cache
let size = 16 * 1024 * 1024 / BLOCK_SIZE as usize;
DiskCache {
inner,
cache: HashMap::with_capacity(size),
order: VecDeque::with_capacity(size),
size,
}
}
fn insert(&mut self, i: u64, data: [u8; BLOCK_SIZE as usize]) {
while self.order.len() >= self.size {
let removed = self.order.pop_front().unwrap();
self.cache.remove(&removed);
}
self.cache.insert(i, data);
self.order.push_back(i);
}
}
impl<T: Disk> Disk for DiskCache<T> {
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
// println!("Cache read at {}", block);
let mut read = 0;
let mut failed = false;
for i in 0..buffer.len().div_ceil(BLOCK_SIZE as usize) {
let block_i = block + i as u64;
let buffer_i = i * BLOCK_SIZE as usize;
let buffer_j = cmp::min(buffer_i + BLOCK_SIZE as usize, buffer.len());
let buffer_slice = &mut buffer[buffer_i..buffer_j];
if let Some(cache_buf) = self.cache.get_mut(&block_i) {
read += copy_memory(cache_buf, buffer_slice);
} else {
failed = true;
break;
}
}
if failed {
self.inner.read_at(block, buffer)?;
read = 0;
for i in 0..buffer.len().div_ceil(BLOCK_SIZE as usize) {
let block_i = block + i as u64;
let buffer_i = i * BLOCK_SIZE as usize;
let buffer_j = cmp::min(buffer_i + BLOCK_SIZE as usize, buffer.len());
let buffer_slice = &buffer[buffer_i..buffer_j];
let mut cache_buf = [0; BLOCK_SIZE as usize];
read += copy_memory(buffer_slice, &mut cache_buf);
self.insert(block_i, cache_buf);
}
}
Ok(read)
}
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
//TODO: Write only blocks that have changed
// println!("Cache write at {}", block);
self.inner.write_at(block, buffer)?;
let mut written = 0;
for i in 0..buffer.len().div_ceil(BLOCK_SIZE as usize) {
let block_i = block + i as u64;
let buffer_i = i * BLOCK_SIZE as usize;
let buffer_j = cmp::min(buffer_i + BLOCK_SIZE as usize, buffer.len());
let buffer_slice = &buffer[buffer_i..buffer_j];
let mut cache_buf = [0; BLOCK_SIZE as usize];
written += copy_memory(buffer_slice, &mut cache_buf);
self.insert(block_i, cache_buf);
}
Ok(written)
}
fn size(&mut self) -> Result<u64> {
self.inner.size()
}
}
+86
View File
@@ -0,0 +1,86 @@
use std::fs::{File, OpenOptions};
use std::io::{Seek, SeekFrom};
use std::os::unix::fs::FileExt;
use std::path::Path;
use syscall::error::{Error, Result, EIO};
use crate::disk::Disk;
use crate::BLOCK_SIZE;
pub struct DiskFile {
pub file: File,
}
trait ResultExt {
type T;
fn or_eio(self) -> Result<Self::T>;
}
impl<T> ResultExt for Result<T> {
type T = T;
fn or_eio(self) -> Result<Self::T> {
match self {
Ok(t) => Ok(t),
Err(err) => {
eprintln!("RedoxFS: IO ERROR: {err}");
Err(Error::new(EIO))
}
}
}
}
impl<T> ResultExt for std::io::Result<T> {
type T = T;
fn or_eio(self) -> Result<Self::T> {
match self {
Ok(t) => Ok(t),
Err(err) => {
eprintln!("RedoxFS: IO ERROR: {err}");
Err(Error::new(EIO))
}
}
}
}
impl DiskFile {
pub fn open(path: impl AsRef<Path>) -> Result<DiskFile> {
let path = path.as_ref();
let file = OpenOptions::new()
.read(true)
.write(true)
.open(path)
.or_else(|_| OpenOptions::new().read(true).open(path))
.or_eio()?;
Ok(DiskFile { file })
}
pub fn create(path: impl AsRef<Path>, size: u64) -> Result<DiskFile> {
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path)
.or_eio()?;
file.set_len(size).or_eio()?;
Ok(DiskFile { file })
}
}
impl Disk for DiskFile {
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
self.file.read_at(buffer, block * BLOCK_SIZE).or_eio()
}
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
self.file.write_at(buffer, block * BLOCK_SIZE).or_eio()
}
fn size(&mut self) -> Result<u64> {
self.file.seek(SeekFrom::End(0)).or_eio()
}
}
impl From<File> for DiskFile {
fn from(file: File) -> Self {
Self { file }
}
}
+38
View File
@@ -0,0 +1,38 @@
use std::io::{Read, Seek, SeekFrom, Write};
use syscall::error::{Error, Result, EIO};
use crate::disk::Disk;
use crate::BLOCK_SIZE;
macro_rules! try_disk {
($expr:expr) => {
match $expr {
Ok(val) => val,
Err(err) => {
eprintln!("Disk I/O Error: {}", err);
return Err(Error::new(EIO));
}
}
};
}
pub struct DiskIo<T>(pub T);
impl<T: Read + Write + Seek> Disk for DiskIo<T> {
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
try_disk!(self.0.seek(SeekFrom::Start(block * BLOCK_SIZE)));
let count = try_disk!(self.0.read(buffer));
Ok(count)
}
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
try_disk!(self.0.seek(SeekFrom::Start(block * BLOCK_SIZE)));
let count = try_disk!(self.0.write(buffer));
Ok(count)
}
fn size(&mut self) -> Result<u64> {
let size = try_disk!(self.0.seek(SeekFrom::End(0)));
Ok(size)
}
}
+42
View File
@@ -0,0 +1,42 @@
use syscall::error::{Error, Result, EIO};
use crate::disk::Disk;
use crate::BLOCK_SIZE;
pub struct DiskMemory {
data: Vec<u8>,
}
impl DiskMemory {
pub fn new(size: u64) -> DiskMemory {
DiskMemory {
data: vec![0; size as usize],
}
}
}
impl Disk for DiskMemory {
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
let offset = (block * BLOCK_SIZE) as usize;
let end = offset + buffer.len();
if end > self.data.len() {
return Err(Error::new(EIO));
}
buffer.copy_from_slice(&self.data[offset..end]);
Ok(buffer.len())
}
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
let offset = (block * BLOCK_SIZE) as usize;
let end = offset + buffer.len();
if end > self.data.len() {
return Err(Error::new(EIO));
}
self.data[offset..end].copy_from_slice(buffer);
Ok(buffer.len())
}
fn size(&mut self) -> Result<u64> {
Ok(self.data.len() as u64)
}
}
+41
View File
@@ -0,0 +1,41 @@
use syscall::error::Result;
#[cfg(feature = "std")]
pub use self::cache::DiskCache;
#[cfg(feature = "std")]
pub use self::file::DiskFile;
#[cfg(feature = "std")]
pub use self::io::DiskIo;
#[cfg(feature = "std")]
pub use self::memory::DiskMemory;
#[cfg(feature = "std")]
pub use self::sparse::DiskSparse;
#[cfg(feature = "std")]
mod cache;
#[cfg(feature = "std")]
mod file;
#[cfg(feature = "std")]
mod io;
#[cfg(feature = "std")]
mod memory;
#[cfg(feature = "std")]
mod sparse;
/// A disk
pub trait Disk {
/// Read blocks from disk
///
/// # Safety
/// Unsafe to discourage use, use filesystem wrappers instead
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize>;
/// Write blocks from disk
///
/// # Safety
/// Unsafe to discourage use, use filesystem wrappers instead
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize>;
/// Get size of disk in bytes
fn size(&mut self) -> Result<u64>;
}
+53
View File
@@ -0,0 +1,53 @@
use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::Path;
use syscall::error::{Error, Result, EIO};
use crate::disk::Disk;
use crate::BLOCK_SIZE;
macro_rules! try_disk {
($expr:expr) => {
match $expr {
Ok(val) => val,
Err(err) => {
eprintln!("Disk I/O Error: {}", err);
return Err(Error::new(EIO));
}
}
};
}
pub struct DiskSparse {
pub file: File,
pub max_size: u64,
}
impl DiskSparse {
pub fn create<P: AsRef<Path>>(path: P, max_size: u64) -> Result<DiskSparse> {
let file = try_disk!(OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path));
Ok(DiskSparse { file, max_size })
}
}
impl Disk for DiskSparse {
unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> {
try_disk!(self.file.seek(SeekFrom::Start(block * BLOCK_SIZE)));
let count = try_disk!(self.file.read(buffer));
Ok(count)
}
unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result<usize> {
try_disk!(self.file.seek(SeekFrom::Start(block * BLOCK_SIZE)));
let count = try_disk!(self.file.write(buffer));
Ok(count)
}
fn size(&mut self) -> Result<u64> {
Ok(self.max_size)
}
}
-327
View File
@@ -1,327 +0,0 @@
use core::{fmt, result};
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct Error {
pub errno: i32,
}
pub type Result<T, E = Error> = result::Result<T, E>;
impl Error {
pub fn new(errno: i32) -> Error {
Error { errno }
}
pub fn mux(result: Result<usize>) -> usize {
match result {
Ok(value) => value,
Err(error) => -error.errno as usize,
}
}
pub fn demux(value: usize) -> Result<usize> {
let errno = -(value as i32);
if errno >= 1 && errno < STR_ERROR.len() as i32 {
Err(Error::new(errno))
} else {
Ok(value)
}
}
pub fn text(&self) -> &'static str {
STR_ERROR
.get(self.errno as usize)
.map(|&x| x)
.unwrap_or("Unknown Error")
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
f.write_str(self.text())
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
f.write_str(self.text())
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
#[cfg(feature = "std")]
impl From<Error> for std::io::Error {
fn from(value: Error) -> Self {
std::io::Error::from_raw_os_error(value.errno)
}
}
pub const EPERM: i32 = 1; /* Operation not permitted */
pub const ENOENT: i32 = 2; /* No such file or directory */
pub const ESRCH: i32 = 3; /* No such process */
pub const EINTR: i32 = 4; /* Interrupted system call */
pub const EIO: i32 = 5; /* I/O error */
pub const ENXIO: i32 = 6; /* No such device or address */
pub const E2BIG: i32 = 7; /* Argument list too long */
pub const ENOEXEC: i32 = 8; /* Exec format error */
pub const EBADF: i32 = 9; /* Bad file number */
pub const ECHILD: i32 = 10; /* No child processes */
pub const EAGAIN: i32 = 11; /* Try again */
pub const ENOMEM: i32 = 12; /* Out of memory */
pub const EACCES: i32 = 13; /* Permission denied */
pub const EFAULT: i32 = 14; /* Bad address */
pub const ENOTBLK: i32 = 15; /* Block device required */
pub const EBUSY: i32 = 16; /* Device or resource busy */
pub const EEXIST: i32 = 17; /* File exists */
pub const EXDEV: i32 = 18; /* Cross-device link */
pub const ENODEV: i32 = 19; /* No such device */
pub const ENOTDIR: i32 = 20; /* Not a directory */
pub const EISDIR: i32 = 21; /* Is a directory */
pub const EINVAL: i32 = 22; /* Invalid argument */
pub const ENFILE: i32 = 23; /* File table overflow */
pub const EMFILE: i32 = 24; /* Too many open files */
pub const ENOTTY: i32 = 25; /* Not a typewriter */
pub const ETXTBSY: i32 = 26; /* Text file busy */
pub const EFBIG: i32 = 27; /* File too large */
pub const ENOSPC: i32 = 28; /* No space left on device */
pub const ESPIPE: i32 = 29; /* Illegal seek */
pub const EROFS: i32 = 30; /* Read-only file system */
pub const EMLINK: i32 = 31; /* Too many links */
pub const EPIPE: i32 = 32; /* Broken pipe */
pub const EDOM: i32 = 33; /* Math argument out of domain of func */
pub const ERANGE: i32 = 34; /* Math result not representable */
pub const EDEADLK: i32 = 35; /* Resource deadlock would occur */
pub const ENAMETOOLONG: i32 = 36; /* File name too long */
pub const ENOLCK: i32 = 37; /* No record locks available */
pub const ENOSYS: i32 = 38; /* Function not implemented */
pub const ENOTEMPTY: i32 = 39; /* Directory not empty */
pub const ELOOP: i32 = 40; /* Too many symbolic links encountered */
pub const EWOULDBLOCK: i32 = 41; /* Operation would block */
pub const ENOMSG: i32 = 42; /* No message of desired type */
pub const EIDRM: i32 = 43; /* Identifier removed */
pub const ECHRNG: i32 = 44; /* Channel number out of range */
pub const EL2NSYNC: i32 = 45; /* Level 2 not synchronized */
pub const EL3HLT: i32 = 46; /* Level 3 halted */
pub const EL3RST: i32 = 47; /* Level 3 reset */
pub const ELNRNG: i32 = 48; /* Link number out of range */
pub const EUNATCH: i32 = 49; /* Protocol driver not attached */
pub const ENOCSI: i32 = 50; /* No CSI structure available */
pub const EL2HLT: i32 = 51; /* Level 2 halted */
pub const EBADE: i32 = 52; /* Invalid exchange */
pub const EBADR: i32 = 53; /* Invalid request descriptor */
pub const EXFULL: i32 = 54; /* Exchange full */
pub const ENOANO: i32 = 55; /* No anode */
pub const EBADRQC: i32 = 56; /* Invalid request code */
pub const EBADSLT: i32 = 57; /* Invalid slot */
pub const EDEADLOCK: i32 = 58; /* Resource deadlock would occur */
pub const EBFONT: i32 = 59; /* Bad font file format */
pub const ENOSTR: i32 = 60; /* Device not a stream */
pub const ENODATA: i32 = 61; /* No data available */
pub const ETIME: i32 = 62; /* Timer expired */
pub const ENOSR: i32 = 63; /* Out of streams resources */
pub const ENONET: i32 = 64; /* Machine is not on the network */
pub const ENOPKG: i32 = 65; /* Package not installed */
pub const EREMOTE: i32 = 66; /* Object is remote */
pub const ENOLINK: i32 = 67; /* Link has been severed */
pub const EADV: i32 = 68; /* Advertise error */
pub const ESRMNT: i32 = 69; /* Srmount error */
pub const ECOMM: i32 = 70; /* Communication error on send */
pub const EPROTO: i32 = 71; /* Protocol error */
pub const EMULTIHOP: i32 = 72; /* Multihop attempted */
pub const EDOTDOT: i32 = 73; /* RFS specific error */
pub const EBADMSG: i32 = 74; /* Not a data message */
pub const EOVERFLOW: i32 = 75; /* Value too large for defined data type */
pub const ENOTUNIQ: i32 = 76; /* Name not unique on network */
pub const EBADFD: i32 = 77; /* File descriptor in bad state */
pub const EREMCHG: i32 = 78; /* Remote address changed */
pub const ELIBACC: i32 = 79; /* Can not access a needed shared library */
pub const ELIBBAD: i32 = 80; /* Accessing a corrupted shared library */
pub const ELIBSCN: i32 = 81; /* .lib section in a.out corrupted */
pub const ELIBMAX: i32 = 82; /* Attempting to link in too many shared libraries */
pub const ELIBEXEC: i32 = 83; /* Cannot exec a shared library directly */
pub const EILSEQ: i32 = 84; /* Illegal byte sequence */
pub const ERESTART: i32 = 85; /* Interrupted system call should be restarted */
pub const ESTRPIPE: i32 = 86; /* Streams pipe error */
pub const EUSERS: i32 = 87; /* Too many users */
pub const ENOTSOCK: i32 = 88; /* Socket operation on non-socket */
pub const EDESTADDRREQ: i32 = 89; /* Destination address required */
pub const EMSGSIZE: i32 = 90; /* Message too long */
pub const EPROTOTYPE: i32 = 91; /* Protocol wrong type for socket */
pub const ENOPROTOOPT: i32 = 92; /* Protocol not available */
pub const EPROTONOSUPPORT: i32 = 93; /* Protocol not supported */
pub const ESOCKTNOSUPPORT: i32 = 94; /* Socket type not supported */
pub const EOPNOTSUPP: i32 = 95; /* Operation not supported on transport endpoint */
pub const EPFNOSUPPORT: i32 = 96; /* Protocol family not supported */
pub const EAFNOSUPPORT: i32 = 97; /* Address family not supported by protocol */
pub const EADDRINUSE: i32 = 98; /* Address already in use */
pub const EADDRNOTAVAIL: i32 = 99; /* Cannot assign requested address */
pub const ENETDOWN: i32 = 100; /* Network is down */
pub const ENETUNREACH: i32 = 101; /* Network is unreachable */
pub const ENETRESET: i32 = 102; /* Network dropped connection because of reset */
pub const ECONNABORTED: i32 = 103; /* Software caused connection abort */
pub const ECONNRESET: i32 = 104; /* Connection reset by peer */
pub const ENOBUFS: i32 = 105; /* No buffer space available */
pub const EISCONN: i32 = 106; /* Transport endpoint is already connected */
pub const ENOTCONN: i32 = 107; /* Transport endpoint is not connected */
pub const ESHUTDOWN: i32 = 108; /* Cannot send after transport endpoint shutdown */
pub const ETOOMANYREFS: i32 = 109; /* Too many references: cannot splice */
pub const ETIMEDOUT: i32 = 110; /* Connection timed out */
pub const ECONNREFUSED: i32 = 111; /* Connection refused */
pub const EHOSTDOWN: i32 = 112; /* Host is down */
pub const EHOSTUNREACH: i32 = 113; /* No route to host */
pub const EALREADY: i32 = 114; /* Operation already in progress */
pub const EINPROGRESS: i32 = 115; /* Operation now in progress */
pub const ESTALE: i32 = 116; /* Stale NFS file handle */
pub const EUCLEAN: i32 = 117; /* Structure needs cleaning */
pub const ENOTNAM: i32 = 118; /* Not a XENIX named type file */
pub const ENAVAIL: i32 = 119; /* No XENIX semaphores available */
pub const EISNAM: i32 = 120; /* Is a named type file */
pub const EREMOTEIO: i32 = 121; /* Remote I/O error */
pub const EDQUOT: i32 = 122; /* Quota exceeded */
pub const ENOMEDIUM: i32 = 123; /* No medium found */
pub const EMEDIUMTYPE: i32 = 124; /* Wrong medium type */
pub const ECANCELED: i32 = 125; /* Operation Canceled */
pub const ENOKEY: i32 = 126; /* Required key not available */
pub const EKEYEXPIRED: i32 = 127; /* Key has expired */
pub const EKEYREVOKED: i32 = 128; /* Key has been revoked */
pub const EKEYREJECTED: i32 = 129; /* Key was rejected by service */
pub const EOWNERDEAD: i32 = 130; /* Owner died */
pub const ENOTRECOVERABLE: i32 = 131; /* State not recoverable */
pub const ERSVD: i32 = 132; /* Reserved (formerly "scheme-kernel message code") */
pub static STR_ERROR: [&'static str; 133] = [
"Success",
"Operation not permitted",
"No such file or directory",
"No such process",
"Interrupted system call",
"I/O error",
"No such device or address",
"Argument list too long",
"Exec format error",
"Bad file number",
"No child processes",
"Try again",
"Out of memory",
"Permission denied",
"Bad address",
"Block device required",
"Device or resource busy",
"File exists",
"Cross-device link",
"No such device",
"Not a directory",
"Is a directory",
"Invalid argument",
"File table overflow",
"Too many open files",
"Not a typewriter",
"Text file busy",
"File too large",
"No space left on device",
"Illegal seek",
"Read-only file system",
"Too many links",
"Broken pipe",
"Math argument out of domain of func",
"Math result not representable",
"Resource deadlock would occur",
"File name too long",
"No record locks available",
"Function not implemented",
"Directory not empty",
"Too many symbolic links encountered",
"Operation would block",
"No message of desired type",
"Identifier removed",
"Channel number out of range",
"Level 2 not synchronized",
"Level 3 halted",
"Level 3 reset",
"Link number out of range",
"Protocol driver not attached",
"No CSI structure available",
"Level 2 halted",
"Invalid exchange",
"Invalid request descriptor",
"Exchange full",
"No anode",
"Invalid request code",
"Invalid slot",
"Resource deadlock would occur",
"Bad font file format",
"Device not a stream",
"No data available",
"Timer expired",
"Out of streams resources",
"Machine is not on the network",
"Package not installed",
"Object is remote",
"Link has been severed",
"Advertise error",
"Srmount error",
"Communication error on send",
"Protocol error",
"Multihop attempted",
"RFS specific error",
"Not a data message",
"Value too large for defined data type",
"Name not unique on network",
"File descriptor in bad state",
"Remote address changed",
"Can not access a needed shared library",
"Accessing a corrupted shared library",
".lib section in a.out corrupted",
"Attempting to link in too many shared libraries",
"Cannot exec a shared library directly",
"Illegal byte sequence",
"Interrupted system call should be restarted",
"Streams pipe error",
"Too many users",
"Socket operation on non-socket",
"Destination address required",
"Message too long",
"Protocol wrong type for socket",
"Protocol not available",
"Protocol not supported",
"Socket type not supported",
"Operation not supported on transport endpoint",
"Protocol family not supported",
"Address family not supported by protocol",
"Address already in use",
"Cannot assign requested address",
"Network is down",
"Network is unreachable",
"Network dropped connection because of reset",
"Software caused connection abort",
"Connection reset by peer",
"No buffer space available",
"Transport endpoint is already connected",
"Transport endpoint is not connected",
"Cannot send after transport endpoint shutdown",
"Too many references: cannot splice",
"Connection timed out",
"Connection refused",
"Host is down",
"No route to host",
"Operation already in progress",
"Operation now in progress",
"Stale NFS file handle",
"Structure needs cleaning",
"Not a XENIX named type file",
"No XENIX semaphores available",
"Is a named type file",
"Remote I/O error",
"Quota exceeded",
"No medium found",
"Wrong medium type",
"Operation Canceled",
"Required key not available",
"Key has expired",
"Key has been revoked",
"Key was rejected by service",
"Owner died",
"State not recoverable",
"Reserved (formerly scheme-kernel message code)",
];
+331
View File
@@ -0,0 +1,331 @@
use aes::Aes128;
use alloc::{
collections::{BTreeMap, VecDeque},
vec,
vec::Vec,
};
use syscall::error::{Error, Result, EKEYREJECTED, ENOENT, ENOKEY};
use xts_mode::{get_tweak_default, Xts128};
#[cfg(feature = "std")]
use crate::{AllocEntry, AllocList, BlockData, BlockTrait, Key, KeySlot, Node, Salt, TreeList};
use crate::{
Allocator, BlockAddr, BlockLevel, BlockMeta, Disk, Header, Transaction, BLOCK_SIZE,
HEADER_RING, RECORD_SIZE,
};
fn compress_cache() -> Vec<u8> {
vec![0; lz4_flex::block::get_maximum_output_size(RECORD_SIZE as usize)]
}
/// A file system
pub struct FileSystem<D: Disk> {
//TODO: make private
pub disk: D,
//TODO: make private
pub block: u64,
//TODO: make private
pub header: Header,
pub(crate) allocator: Allocator,
pub(crate) cipher_opt: Option<Xts128<Aes128>>,
pub(crate) compress_cache: Vec<u8>,
pub node_usages: BTreeMap<u32, u64>,
}
impl<D: Disk> FileSystem<D> {
/// Open a file system on a disk
pub fn open(
mut disk: D,
password_opt: Option<&[u8]>,
block_opt: Option<u64>,
cleanup: bool,
) -> Result<Self> {
for ring_block in block_opt.map_or(0..65536, |x| x..x + 1) {
let mut header = Header::default();
unsafe { disk.read_at(ring_block, &mut header)? };
// Skip invalid headers
if !header.valid() {
continue;
}
let block = ring_block - (header.generation() % HEADER_RING);
for i in 0..HEADER_RING {
let mut other_header = Header::default();
unsafe { disk.read_at(block + i, &mut other_header)? };
// Skip invalid headers
if !other_header.valid() {
continue;
}
// If this is a newer header, use it
if other_header.generation() > header.generation() {
header = other_header;
}
}
let cipher_opt = match password_opt {
Some(password) => {
if !header.encrypted() {
// Header not encrypted but password provided
return Err(Error::new(EKEYREJECTED));
}
match header.cipher(password) {
Some(cipher) => Some(cipher),
None => {
// Header encrypted with a different password
return Err(Error::new(ENOKEY));
}
}
}
None => {
if header.encrypted() {
// Header encrypted but no password provided
return Err(Error::new(ENOKEY));
}
None
}
};
let mut fs = FileSystem {
disk,
block,
header,
allocator: Allocator::default(),
cipher_opt,
compress_cache: compress_cache(),
node_usages: BTreeMap::new(),
};
unsafe { fs.reset_allocator()? };
if cleanup {
fs.cleanup()?
}
return Ok(fs);
}
Err(Error::new(ENOENT))
}
/// Create a file system on a disk
#[cfg(feature = "std")]
pub fn create(
disk: D,
password_opt: Option<&[u8]>,
ctime: u64,
ctime_nsec: u32,
) -> Result<Self> {
Self::create_reserved(disk, password_opt, &[], ctime, ctime_nsec)
}
/// Create a file system on a disk, with reserved data at the beginning
/// Reserved data will be zero padded up to the nearest block
/// We need to pass ctime and ctime_nsec in order to initialize the unix timestamps
#[cfg(feature = "std")]
pub fn create_reserved(
mut disk: D,
password_opt: Option<&[u8]>,
reserved: &[u8],
ctime: u64,
ctime_nsec: u32,
) -> Result<Self> {
let disk_size = disk.size()?;
let disk_blocks = disk_size / BLOCK_SIZE;
let block_offset = (reserved.len() as u64).div_ceil(BLOCK_SIZE);
if disk_blocks < (block_offset + HEADER_RING + 4) {
return Err(Error::new(syscall::error::ENOSPC));
}
let fs_blocks = disk_blocks - block_offset;
// Fill reserved data, pad with zeroes
for block in 0..block_offset as usize {
let mut data = [0; BLOCK_SIZE as usize];
let mut i = 0;
while i < data.len() && block * BLOCK_SIZE as usize + i < reserved.len() {
data[i] = reserved[block * BLOCK_SIZE as usize + i];
i += 1;
}
unsafe {
disk.write_at(block as u64, &data)?;
}
}
let mut header = Header::new(fs_blocks * BLOCK_SIZE);
let cipher_opt = match password_opt {
Some(password) => {
//TODO: handle errors
header.key_slots[0] = KeySlot::new(
password,
Salt::new().unwrap(),
(Key::new().unwrap(), Key::new().unwrap()),
)
.unwrap();
Some(header.key_slots[0].cipher(password).unwrap())
}
None => None,
};
let mut fs = FileSystem {
disk,
block: block_offset,
header,
allocator: Allocator::default(),
cipher_opt,
compress_cache: compress_cache(),
node_usages: BTreeMap::new(),
};
// Write header generation zero
let count = unsafe { fs.disk.write_at(fs.block, &fs.header)? };
if count != core::mem::size_of_val(&fs.header) {
// Wrote wrong number of bytes
#[cfg(feature = "log")]
log::error!("CREATE: WRONG NUMBER OF BYTES");
return Err(Error::new(syscall::error::EIO));
}
// Set tree and alloc pointers and write header generation one
fs.tx(|tx| unsafe {
let tree = BlockData::new(
BlockAddr::new(HEADER_RING + 1, BlockMeta::default()),
TreeList::empty(BlockLevel::default()).unwrap(),
);
let mut alloc = BlockData::new(
BlockAddr::new(HEADER_RING + 2, BlockMeta::default()),
AllocList::empty(BlockLevel::default()).unwrap(),
);
let alloc_free = fs_blocks - (HEADER_RING + 4);
alloc.data_mut().entries[0] = AllocEntry::new(HEADER_RING + 4, alloc_free as i64);
tx.header.tree = tx.write_block(tree)?;
tx.header.alloc = tx.write_block(alloc)?;
tx.header_changed = true;
Ok(())
})?;
unsafe {
fs.reset_allocator()?;
}
fs.tx(|tx| unsafe {
let mut root = BlockData::new(
BlockAddr::new(HEADER_RING + 3, BlockMeta::default()),
Node::new(Node::MODE_DIR | 0o755, 0, 0, ctime, ctime_nsec),
);
root.data_mut().set_links(1);
let root_ptr = tx.write_block(root)?;
assert_eq!(tx.insert_tree(root_ptr)?.id(), 1);
Ok(())
})?;
fs.cleanup()?;
Ok(fs)
}
/// Release unused nodes and squash allocation log, happens on mount (with cleanup) and unmount
pub fn cleanup(&mut self) -> Result<()> {
let mut tx = Transaction::new(self);
tx.release_unused_nodes()?;
tx.commit(true)
}
/// start a filesystem transaction, required for making any changes
pub fn tx<F: FnOnce(&mut Transaction<D>) -> Result<T>, T>(&mut self, f: F) -> Result<T> {
let mut tx = Transaction::new(self);
let t = f(&mut tx)?;
tx.commit(false)?;
Ok(t)
}
pub fn allocator(&self) -> &Allocator {
&self.allocator
}
/// Unsafe as it can corrupt the filesystem
pub unsafe fn allocator_mut(&mut self) -> &mut Allocator {
&mut self.allocator
}
/// Reset allocator to state stored on disk
///
/// # Safety
/// Unsafe, it must only be called when opening the filesystem
unsafe fn reset_allocator(&mut self) -> Result<()> {
self.allocator = Allocator::default();
// To avoid having to update all prior alloc blocks, there is only a previous pointer
// This means we need to roll back all allocations. Currently we do this by reading the
// alloc log into a buffer to reverse it.
let mut allocs = VecDeque::new();
self.tx(|tx| {
let mut alloc_ptr = tx.header.alloc;
while !alloc_ptr.is_null() {
let alloc = tx.read_block(alloc_ptr)?;
alloc_ptr = alloc.data().prev;
allocs.push_front(alloc);
}
Ok(())
})?;
for alloc in allocs {
for entry in alloc.data().entries.iter() {
let index = entry.index();
let count = entry.count();
if count < 0 {
for i in 0..-count {
//TODO: replace assert with error?
let addr = BlockAddr::new(index + i as u64, BlockMeta::default());
assert_eq!(self.allocator.allocate_exact(addr), Some(addr));
}
} else {
for i in 0..count {
let addr = BlockAddr::new(index + i as u64, BlockMeta::default());
self.allocator.deallocate(addr);
}
}
}
}
Ok(())
}
pub(crate) fn decrypt(&mut self, data: &mut [u8], addr: BlockAddr) -> bool {
if let Some(ref cipher) = self.cipher_opt {
cipher.decrypt_area(
data,
BLOCK_SIZE as usize,
addr.index().into(),
get_tweak_default,
);
true
} else {
// Do nothing if encryption is disabled
false
}
}
pub(crate) fn encrypt(&mut self, data: &mut [u8], addr: BlockAddr) -> bool {
if let Some(ref cipher) = self.cipher_opt {
cipher.encrypt_area(
data,
BLOCK_SIZE as usize,
addr.index().into(),
get_tweak_default,
);
true
} else {
// Do nothing if encryption is disabled
false
}
}
}
-573
View File
@@ -1,573 +0,0 @@
use bitflags::bitflags as inner_bitflags;
use core::{mem, ops::Deref, slice};
macro_rules! bitflags {
(
$(#[$outer:meta])*
pub struct $BitFlags:ident: $T:ty {
$(
$(#[$inner:ident $($args:tt)*])*
const $Flag:ident = $value:expr;
)+
}
) => {
// First, use the inner bitflags
inner_bitflags! {
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy, Default)]
$(#[$outer])*
pub struct $BitFlags: $T {
$(
$(#[$inner $($args)*])*
const $Flag = $value;
)+
}
}
impl $BitFlags {
#[deprecated = "use the safe `from_bits_retain` method instead"]
pub unsafe fn from_bits_unchecked(bits: $T) -> Self {
Self::from_bits_retain(bits)
}
}
// Secondly, re-export all inner constants
// (`pub use self::Struct::*` doesn't work)
$(
$(#[$inner $($args)*])*
pub const $Flag: $BitFlags = $BitFlags::$Flag;
)+
}
}
pub const CLOCK_REALTIME: usize = 1;
pub const CLOCK_MONOTONIC: usize = 4;
bitflags! {
pub struct EventFlags: usize {
const EVENT_NONE = 0;
const EVENT_READ = 1;
const EVENT_WRITE = 2;
}
}
pub const F_DUPFD: usize = 0;
pub const F_GETFD: usize = 1;
pub const F_SETFD: usize = 2;
pub const F_GETFL: usize = 3;
pub const F_SETFL: usize = 4;
/// Duplicate file descriptor with close-on-exec set (Red Bear extension; libredox ABI).
pub const F_DUPFD_CLOEXEC: usize = 5;
pub const FUTEX_WAIT: usize = 0;
pub const FUTEX_WAKE: usize = 1;
pub const FUTEX_REQUEUE: usize = 2;
pub const FUTEX_WAIT64: usize = 3;
// TODO: Split SendFdFlags into caller flags and flags that the scheme receives?
bitflags::bitflags! {
#[derive(Clone, Copy, Debug)]
pub struct SendFdFlags: usize {
/// If set, the kernel will enforce that the file descriptors are exclusively owned.
///
/// That is, there will no longer exist any other reference to those FDs when removed from
/// the file table (sendfd always removes the FDs from the file table, but without this
/// flag, it can be retained by SYS_DUPing them first).
const EXCLUSIVE = 1;
/// If set, the file descriptors will be cloned and *not* removed from the sender's file table.
/// By default, `SYS_SENDFD` moves the file descriptors, removing them from the sender.
const CLONE = 2;
}
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug)]
pub struct FobtainFdFlags: usize {
/// If set, the SYS_CALL payload specifies the destination file descriptor slots, otherwise the lowest
/// available slots will be selected, and placed in the usize pointed to by SYS_CALL
/// payload.
const MANUAL_FD = 1;
/// If set, the file descriptors received are guaranteed to be exclusively owned (by the file
/// table the obtainer is running in).
const EXCLUSIVE = 2;
/// If set, the file descriptors received will be placed into the *upper* file table.
const UPPER_TBL = 4;
/// If set, the received file descriptors are marked as close-on-exec.
const CLOEXEC = 8;
// No, cloexec won't be stored in the kernel in the future, when the stable ABI is moved to
// relibc, so no flag for that!
}
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug)]
pub struct RecvFdFlags: usize {
/// If set, the SYS_CALL payload specifies the destination file descriptor slots, otherwise the lowest
/// available slots will be selected, and placed in the usize pointed to by SYS_CALL
/// payload.
const MANUAL_FD = 1;
/// If set, the file descriptors received will be placed into the *upper* file table.
const UPPER_TBL = 2;
/// If set, the received file descriptors are marked as close-on-exec.
const CLOEXEC = 4;
}
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug)]
pub struct FmoveFdFlags: usize {
/// If set, the kernel will enforce that the file descriptors are exclusively owned.
///
/// That is, there will no longer exist any other reference to those FDs when removed from
/// the file table (SYS_CALL always removes the FDs from the file table, but without this
/// flag, it can be retained by SYS_DUPing them first).
const EXCLUSIVE = 1;
/// If set, the file descriptors will be cloned and *not* removed from the sender's file table.
/// By default, sendfd moves the file descriptors, removing them from the sender.
const CLONE = 2;
}
}
bitflags! {
pub struct MapFlags: usize {
// TODO: Downgrade PROT_NONE to global constant? (bitflags specifically states zero flags
// can cause buggy behavior).
const PROT_NONE = 0x0000_0000;
const PROT_EXEC = 0x0001_0000;
const PROT_WRITE = 0x0002_0000;
const PROT_READ = 0x0004_0000;
const MAP_SHARED = 0x0001;
const MAP_PRIVATE = 0x0002;
const MAP_FIXED = 0x0004;
const MAP_FIXED_NOREPLACE = 0x000C;
/// For *userspace-backed mmaps*, return from the mmap call before all pages have been
/// provided by the scheme. This requires the scheme to be trusted, as the current context
/// can block indefinitely, if the scheme does not respond to the page fault handler's
/// request, as it tries to map the page by requesting it from the scheme.
///
/// In some cases however, such as the program loader, the data needs to be trusted as much
/// with or without MAP_LAZY, and if so, mapping lazily will not cause insecureness by
/// itself.
///
/// For kernel-backed mmaps, this flag has no effect at all. It is unspecified whether
/// kernel mmaps are lazy or not.
const MAP_LAZY = 0x0010;
}
}
bitflags! {
pub struct MunmapFlags: usize {
/// Indicates whether the funmap call must implicitly do an msync, for the changes to
/// become visible later.
///
/// This flag will currently be set if and only if MAP_SHARED | PROT_WRITE are set.
const NEEDS_SYNC = 1;
}
}
pub const MODE_TYPE: u16 = 0xF000;
pub const MODE_DIR: u16 = 0x4000;
pub const MODE_FILE: u16 = 0x8000;
pub const MODE_SYMLINK: u16 = 0xA000;
pub const MODE_FIFO: u16 = 0x1000;
pub const MODE_CHR: u16 = 0x2000;
pub const MODE_SOCK: u16 = 0xC000;
pub const MODE_PERM: u16 = 0x0FFF;
pub const MODE_SETUID: u16 = 0o4000;
pub const MODE_SETGID: u16 = 0o2000;
pub const O_RDONLY: usize = 0x0001_0000;
pub const O_WRONLY: usize = 0x0002_0000;
pub const O_RDWR: usize = 0x0003_0000;
pub const O_NONBLOCK: usize = 0x0004_0000;
pub const O_APPEND: usize = 0x0008_0000;
pub const O_SHLOCK: usize = 0x0010_0000;
pub const O_EXLOCK: usize = 0x0020_0000;
pub const O_ASYNC: usize = 0x0040_0000;
pub const O_FSYNC: usize = 0x0080_0000;
pub const O_CREAT: usize = 0x0200_0000;
pub const O_TRUNC: usize = 0x0400_0000;
pub const O_EXCL: usize = 0x0800_0000;
pub const O_DIRECTORY: usize = 0x1000_0000;
pub const O_STAT: usize = 0x2000_0000;
pub const O_SYMLINK: usize = 0x4000_0000;
pub const O_NOFOLLOW: usize = 0x8000_0000;
pub const O_ACCMODE: usize = O_RDONLY | O_WRONLY | O_RDWR;
pub const O_FCNTL_MASK: usize = O_NONBLOCK | O_APPEND | O_ASYNC | O_FSYNC;
/// Atomically set close-on-exec when opening a file (Red Bear extension; libredox ABI).
pub const O_CLOEXEC: usize = 0x0100_0000;
/// Remove directory instead of unlinking file.
pub const AT_REMOVEDIR: usize = 0x200;
// The top 48 bits of PTRACE_* are reserved, for now
// NOT ABI STABLE!
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[repr(usize)]
pub enum ContextStatus {
Runnable,
Blocked,
NotYetStarted,
Dead,
ForceKilled,
Stopped,
UnhandledExcp,
#[default]
Other, // reserved
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(usize)]
pub enum ContextVerb {
Stop = 1,
Unstop = 2,
Interrupt = 3,
ForceKill = usize::MAX,
}
impl ContextVerb {
pub fn try_from_raw(raw: usize) -> Option<Self> {
Some(match raw {
1 => Self::Stop,
2 => Self::Unstop,
3 => Self::Interrupt,
usize::MAX => Self::ForceKill,
_ => return None,
})
}
}
// NOT ABI STABLE!
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum AddrSpaceVerb {
MmapMin = 255,
}
impl AddrSpaceVerb {
pub fn try_from_raw(verb: u8) -> Option<Self> {
Some(match verb {
255 => Self::MmapMin,
_ => return None,
})
}
}
// NOT ABI STABLE!
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum ProcSchemeVerb {
RegsInt = 250,
RegsFloat = 251,
RegsEnv = 252,
SchedAffinity = 253,
Start = 254,
Iopl = 255,
}
impl ProcSchemeVerb {
pub fn try_from_raw(verb: u8) -> Option<Self> {
Some(match verb {
250 => Self::RegsInt,
251 => Self::RegsFloat,
252 => Self::RegsEnv,
253 => Self::SchedAffinity,
254 => Self::Start,
255 => Self::Iopl,
_ => return None,
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FileTableVerb {
Close = 1,
Dup2 = 2,
Reserved1 = 3,
Resize = 4,
}
impl FileTableVerb {
pub fn try_from_raw(value: u8) -> Option<Self> {
Some(match value {
1 => Self::Close,
2 => Self::Dup2,
3 => Self::Reserved1,
4 => Self::Resize,
_ => return None,
})
}
}
// NOT ABI-STABLE!
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u64)]
pub enum AcpiVerb {
// copies the rsdt/xsdt to the payload buffer (the number of bytes that fit), and returns the
// rsdt/xsdt length regardless
ReadRxsdt = 1,
// no payload, just returns 0 or 1
CheckShutdown = 2,
/// Red Bear OS extension (Phase I): acpid requests the kernel
/// enter s2idle (Modern Standby / S0ix). The kernel sets
/// `S2IDLE_REQUESTED`; the idle path calls `mwait_loop()`. Read
/// payload (1 byte) returns the *previous* value of the flag.
/// Write payload is opaque (ignored by current kernel).
/// Mirrors Linux 7.1 `s2idle_enter()` in
/// `kernel/power/suspend.c:91`. Hardware-agnostic — works on
/// any platform with Modern Standby firmware (Dell, HP, Lenovo,
/// LG Gram, etc.), not just LG Gram.
EnterS2Idle = 3,
/// Red Bear OS extension (Phase I): acpid signals s2idle
/// exit. Kernel clears `S2IDLE_REQUESTED`. Read payload (1
/// byte) always returns 0. Mirrors Linux 7.1 `s2idle_wake()` in
/// `kernel/power/suspend.c:133`. Hardware-agnostic.
ExitS2Idle = 4,
/// Red Bear OS extension (Phase II.X.W): acpid writes the
/// 64-bit kernel S3 resume trampoline address to
/// FACS.xfirmware_waking_vector. The kernel's
/// `arch/x86_shared/s3_resume.rs` trampoline is at this
/// address; the platform firmware jumps to it on S3 wake.
/// Write payload: 8-byte little-endian u64 (the trampoline
/// address). Read payload: 0 (no error condition; this verb
/// is one-way). Mirrors Linux 7.1's
/// `acpi_set_firmware_waking_vector` in ACPICA.
/// Hardware-agnostic: works on any x86_64 system with
/// standard ACPI S3 support (Dell, HP, Lenovo, LG Gram 14).
SetS3WakingVector = 5,
/// Red Bear OS extension (Phase II.X.W): acpid requests
/// the kernel to enter S3. The kernel's kstop handler
/// dispatches on the "s3" string arg, dispatches on the
/// SLP_TYP byte, and does the PM1 register write. The
/// acpid has already done the AML prep (`_TTS(3)`, `_PTS(3)`,
/// `_SST(3)`) and written the trampoline address to FACS
/// via `SetS3WakingVector`. No payload needed.
/// Mirrors Linux 7.1's `enter_sleep_state` /
/// `acpi_hw_legacy_sleep` for S3.
EnterS3 = 6,
}
impl AcpiVerb {
pub const fn try_from_raw(value: u64) -> Option<Self> {
Some(match value {
1 => Self::ReadRxsdt,
2 => Self::CheckShutdown,
3 => Self::EnterS2Idle,
4 => Self::ExitS2Idle,
5 => Self::SetS3WakingVector,
6 => Self::EnterS3,
_ => return None,
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(usize)]
pub enum SchemeSocketCall {
ObtainFd = 0,
MoveFd = 1,
}
impl SchemeSocketCall {
pub fn try_from_raw(raw: usize) -> Option<Self> {
Some(match raw {
0 => Self::ObtainFd,
1 => Self::MoveFd,
_ => return None,
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(usize)]
#[non_exhaustive]
pub enum FsCall {
Connect = 0,
}
impl FsCall {
pub fn try_from_raw(raw: usize) -> Option<Self> {
Some(match raw {
0 => Self::Connect,
_ => return None,
})
}
}
bitflags! {
pub struct PtraceFlags: u64 {
/// Stop before a syscall is handled. Send PTRACE_FLAG_IGNORE to not
/// handle the syscall.
const PTRACE_STOP_PRE_SYSCALL = 0x0000_0000_0000_0001;
/// Stop after a syscall is handled.
const PTRACE_STOP_POST_SYSCALL = 0x0000_0000_0000_0002;
/// Stop after exactly one instruction. TODO: This may not handle
/// fexec/signal boundaries. Should it?
const PTRACE_STOP_SINGLESTEP = 0x0000_0000_0000_0004;
/// Stop before a signal is handled. Send PTRACE_FLAG_IGNORE to not
/// handle signal.
const PTRACE_STOP_SIGNAL = 0x0000_0000_0000_0008;
/// Stop on a software breakpoint, such as the int3 instruction for
/// x86_64.
const PTRACE_STOP_BREAKPOINT = 0x0000_0000_0000_0010;
/// Stop just before exiting for good.
const PTRACE_STOP_EXIT = 0x0000_0000_0000_0020;
const PTRACE_STOP_MASK = 0x0000_0000_0000_00FF;
/// Sent when a child is cloned, giving you the opportunity to trace it.
/// If you don't catch this, the child is started as normal.
const PTRACE_EVENT_CLONE = 0x0000_0000_0000_0100;
/// Sent when current-addrspace is changed, allowing the tracer to reopen the memory file.
const PTRACE_EVENT_ADDRSPACE_SWITCH = 0x0000_0000_0000_0200;
const PTRACE_EVENT_MASK = 0x0000_0000_0000_0F00;
/// Special meaning, depending on the event. Usually, when fired before
/// an action, it will skip performing that action.
const PTRACE_FLAG_IGNORE = 0x0000_0000_0000_1000;
const PTRACE_FLAG_MASK = 0x0000_0000_0000_F000;
}
}
impl Deref for PtraceFlags {
type Target = [u8];
fn deref(&self) -> &Self::Target {
// Same as to_ne_bytes but in-place
unsafe {
slice::from_raw_parts(&self.bits() as *const _ as *const u8, mem::size_of::<u64>())
}
}
}
pub const SEEK_SET: usize = 0;
pub const SEEK_CUR: usize = 1;
pub const SEEK_END: usize = 2;
pub const SIGCHLD: usize = 17;
pub const SIGTSTP: usize = 20;
pub const SIGTTIN: usize = 21;
pub const SIGTTOU: usize = 22;
pub const ADDRSPACE_OP_MMAP: usize = 0;
pub const ADDRSPACE_OP_MUNMAP: usize = 1;
pub const ADDRSPACE_OP_MPROTECT: usize = 2;
pub const ADDRSPACE_OP_TRANSFER: usize = 3;
bitflags! {
pub struct MremapFlags: usize {
const FIXED = 1;
const FIXED_REPLACE = 3;
/// Alias's memory region at `old_address` to `new_address` such that both regions share
/// the same frames.
const KEEP_OLD = 1 << 2;
// TODO: MAYMOVE, DONTUNMAP
}
}
bitflags! {
pub struct RwFlags: u32 {
const NONBLOCK = 1;
const APPEND = 2;
// TODO: sync/dsync
// TODO: O_DIRECT?
}
}
bitflags! {
pub struct SigcontrolFlags: usize {
/// Prevents the kernel from jumping the context to the signal trampoline, but otherwise
/// has absolutely no effect on which signals are blocked etc. Meant to be used for
/// short-lived critical sections inside libc.
const INHIBIT_DELIVERY = 1;
}
}
bitflags! {
pub struct CallFlags: usize {
// reserved
const RSVD0 = 1 << 0;
const RSVD1 = 1 << 1;
const RSVD2 = 1 << 2;
const RSVD3 = 1 << 3;
const RSVD4 = 1 << 4;
const RSVD5 = 1 << 5;
const RSVD6 = 1 << 6;
const RSVD7 = 1 << 7;
/// Remove the fd from the caller's file table before sending the message.
const CONSUME = 1 << 8;
const WRITE = 1 << 9;
const READ = 1 << 10;
/// Indicates the request is a bulk fd passing request.
const FD = 1 << 11;
/// Flags for the fd passing request.
const FD_EXCLUSIVE = 1 << 12;
const FD_CLONE = 1 << 13;
const FD_UPPER = 1 << 14;
const FD_CLOEXEC = 1 << 15;
/// Call is a standard fs call, with metadata defined in `StdFsCallMeta`
const STD_FS = 1 << 16;
/// Call is taking multiple fds as an argument
const MULTIPLE_FDS = 1 << 17;
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum StdFsCallKind {
// TODO: remove old syscalls
Fchmod = 1,
Fchown = 2,
Getdents = 3,
Fstat = 4,
Fstatvfs = 5,
Fsync = 6,
Ftruncate = 7,
Futimens = 8,
// 9 reserved in fscall RFC
// Unlinkat = 10,
Relpathat = 11,
Lock = 12,
Unlock = 13,
GetLock = 14,
}
impl StdFsCallKind {
pub fn try_from_raw(raw: u8) -> Option<Self> {
use StdFsCallKind::*;
// TODO: Use a library where this match can be automated.
Some(match raw {
1 => Fchmod,
2 => Fchown,
3 => Getdents,
4 => Fstat,
5 => Fstatvfs,
6 => Fsync,
7 => Ftruncate,
8 => Futimens,
// 9 reserved in fscall RFC
// 10 => Unlinkat,
11 => Relpathat,
12 => Lock,
13 => Unlock,
14 => GetLock,
_ => return None,
})
}
}
/// The tag for the fd number in the upper file descriptor table.
pub const UPPER_FDTBL_TAG: usize = 1 << (usize::BITS - 2);
/// The identifier for registering event timeout
pub const EVENT_TIMEOUT_ID: usize = usize::MAX - 2;
+242
View File
@@ -0,0 +1,242 @@
use core::ops::{Deref, DerefMut};
use core::{fmt, mem, slice};
use endian_num::Le;
use aes::Aes128;
use xts_mode::{get_tweak_default, Xts128};
use crate::{AllocList, BlockPtr, KeySlot, ReleaseList, Tree, BLOCK_SIZE, SIGNATURE, VERSION};
pub const HEADER_RING: u64 = 256;
/// The header of the filesystem
#[derive(Clone, Copy)]
#[repr(C, packed)]
pub struct Header {
/// Signature, should be SIGNATURE
pub signature: [u8; 8],
/// Version, should be VERSION
pub version: Le<u64>,
/// Disk ID, a 128-bit unique identifier
pub uuid: [u8; 16],
/// Disk size, in number of BLOCK_SIZE sectors
pub size: Le<u64>,
/// Generation of header
pub generation: Le<u64>,
/// Block of first tree node
pub tree: BlockPtr<Tree>,
/// Block of last alloc node
pub alloc: BlockPtr<AllocList>,
/// Key slots
pub key_slots: [KeySlot; 64],
/// Nodes pending release, may be null
pub release: BlockPtr<ReleaseList>,
/// Padding
pub padding: [u8; BLOCK_SIZE as usize - 3192],
/// encrypted hash of header data without hash, set to hash and padded if disk is not encrypted
pub encrypted_hash: [u8; 16],
/// hash of header data without hash
pub hash: Le<u64>,
}
impl Header {
#[cfg(feature = "std")]
pub fn new(size: u64) -> Header {
let uuid = uuid::Uuid::new_v4();
let mut header = Header {
signature: *SIGNATURE,
version: VERSION.into(),
uuid: *uuid.as_bytes(),
size: size.into(),
..Default::default()
};
header.update_hash(None);
header
}
pub fn valid(&self) -> bool {
if &self.signature != SIGNATURE {
// Signature does not match
return false;
}
if self.version.to_ne() != VERSION {
// Version does not match
return false;
}
if self.hash.to_ne() != self.create_hash() {
// Hash does not match
return false;
}
// All tests passed, header is valid
true
}
pub fn uuid(&self) -> [u8; 16] {
self.uuid
}
pub fn size(&self) -> u64 {
self.size.to_ne()
}
pub fn generation(&self) -> u64 {
self.generation.to_ne()
}
fn create_hash(&self) -> u64 {
// Calculate part of header to hash (everything before the hashes)
let end = mem::size_of_val(self)
- mem::size_of_val(&{ self.hash })
- mem::size_of_val(&{ self.encrypted_hash });
seahash::hash(&self[..end])
}
fn create_encrypted_hash(&self, cipher_opt: Option<&Xts128<Aes128>>) -> [u8; 16] {
let mut encrypted_hash = [0; 16];
for (i, b) in self.hash.to_le_bytes().iter().enumerate() {
encrypted_hash[i] = *b;
}
if let Some(cipher) = cipher_opt {
let mut block = aes::Block::from(encrypted_hash);
cipher.encrypt_area(
&mut block,
BLOCK_SIZE as usize,
self.generation().into(),
get_tweak_default,
);
encrypted_hash = block.into();
}
encrypted_hash
}
pub fn encrypted(&self) -> bool {
(self.encrypted_hash) != self.create_encrypted_hash(None)
}
pub fn cipher(&self, password: &[u8]) -> Option<Xts128<Aes128>> {
let hash = self.create_encrypted_hash(None);
for slot in self.key_slots.iter() {
//TODO: handle errors
let cipher = slot.cipher(password).unwrap();
let mut block = aes::Block::from(self.encrypted_hash);
cipher.decrypt_area(
&mut block,
BLOCK_SIZE as usize,
self.generation().into(),
get_tweak_default,
);
if block == aes::Block::from(hash) {
return Some(cipher);
}
}
None
}
fn update_hash(&mut self, cipher_opt: Option<&Xts128<Aes128>>) {
self.hash = self.create_hash().into();
// Make sure to do this second, it relies on the hash being up to date
self.encrypted_hash = self.create_encrypted_hash(cipher_opt);
}
pub fn update(&mut self, cipher_opt: Option<&Xts128<Aes128>>) -> u64 {
let mut generation = self.generation();
generation += 1;
self.generation = generation.into();
self.update_hash(cipher_opt);
generation
}
}
impl Default for Header {
fn default() -> Self {
Self {
signature: [0; 8],
version: 0.into(),
uuid: [0; 16],
size: 0.into(),
generation: 0.into(),
tree: BlockPtr::<Tree>::default(),
alloc: BlockPtr::<AllocList>::default(),
key_slots: [KeySlot::default(); 64],
release: BlockPtr::<ReleaseList>::default(),
padding: [0; BLOCK_SIZE as usize - 3192],
encrypted_hash: [0; 16],
hash: 0.into(),
}
}
}
impl fmt::Debug for Header {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let signature = self.signature;
let version = self.version;
let uuid = self.uuid;
let size = self.size;
let generation = self.generation;
let tree = self.tree;
let alloc = self.alloc;
let release = self.release;
let hash = self.hash;
f.debug_struct("Header")
.field("signature", &signature)
.field("version", &version)
.field("uuid", &uuid)
.field("size", &size)
.field("generation", &generation)
.field("tree", &tree)
.field("alloc", &alloc)
.field("release", &release)
.field("hash", &hash)
.finish()
}
}
impl Deref for Header {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(self as *const Header as *const u8, mem::size_of::<Header>())
as &[u8]
}
}
}
impl DerefMut for Header {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(self as *mut Header as *mut u8, mem::size_of::<Header>())
as &mut [u8]
}
}
}
#[test]
fn header_not_valid_test() {
assert_eq!(Header::default().valid(), false);
}
#[test]
fn header_size_test() {
assert_eq!(mem::size_of::<Header>(), BLOCK_SIZE as usize);
}
#[test]
fn header_hash_test() {
let mut header = Header::default();
assert_eq!(header.create_hash(), 0xe81ffcb86026ff96);
header.update_hash(None);
assert_eq!(header.hash.to_ne(), 0xe81ffcb86026ff96);
assert_eq!(
header.encrypted_hash,
[0x96, 0xff, 0x26, 0x60, 0xb8, 0xfc, 0x1f, 0xe8, 0, 0, 0, 0, 0, 0, 0, 0]
);
}
#[cfg(feature = "std")]
#[test]
fn header_valid_test() {
assert_eq!(Header::new(0).valid(), true);
}
+691
View File
@@ -0,0 +1,691 @@
use alloc::string::String;
use alloc::vec::Vec;
use core::{fmt, mem, ops, slice};
use endian_num::Le;
use syscall::error::{Error, Result, EEXIST, EIO};
use crate::{
BlockLevel, BlockPtr, BlockRaw, BlockTrait, DirEntry, DirList, BLOCK_SIZE, RECORD_LEVEL,
};
pub const HTREE_IDX_ENTRIES: usize = BLOCK_SIZE as usize / mem::size_of::<HTreePtr<BlockRaw>>();
const HTREE_IDX_PADDING: usize =
BLOCK_SIZE as usize - mem::size_of::<[HTreePtr<BlockRaw>; HTREE_IDX_ENTRIES]>();
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(C, packed)]
pub struct HTreeHash(Le<u32>);
impl HTreeHash {
// Create a MAX constant populated iwth the maximum value of Le<u32> minus 1
pub const MAX: HTreeHash = HTreeHash(Le(u32::MAX - 1));
#[cfg(not(test))]
pub fn from_name(name: &str) -> Self {
let hash = seahash::hash(name.as_bytes()) as u32;
// Don't allow the default hash value to be calculated for a real hash
if hash == u32::MAX {
return Self::MAX;
}
Self(hash.into())
}
#[cfg(test)]
pub fn from_name(name: &str) -> Self {
// Allow overriding the hashing function to something easily controled for testing.
let hash = if let Some(pos) = name.rfind("__") {
let number_str = &name[pos + 2..];
number_str.parse::<u32>().unwrap()
} else {
seahash::hash(name.as_bytes()) as u32
};
// Don't allow the default hash value to be calculated for a real hash
if hash == u32::MAX {
return Self::MAX;
}
Self(hash.into())
}
/// Returns the maximum of two `HTreeHash` values, ignoring the default hash value.
pub fn max_ignoring_default(&self, other: Self) -> Self {
let default = HTreeHash::default();
if *self == default {
return other;
}
if other == default {
return *self;
}
if *self > other {
*self
} else {
other
}
}
pub fn find_max(dir_list: &DirList) -> Option<HTreeHash> {
let mut max_hash = HTreeHash::default();
dir_list.for_each_entry(|_ptr_bytes, name_bytes| {
let name = String::from_utf8_lossy(name_bytes);
let hash = HTreeHash::from_name(name.as_ref());
max_hash = max_hash.max_ignoring_default(hash);
});
if max_hash == HTreeHash::default() {
None
} else {
Some(max_hash)
}
}
}
impl Default for HTreeHash {
/// The default hash value is the maximum possible value to push it to the end of the list when sorting.
fn default() -> Self {
Self(u32::MAX.into())
}
}
#[repr(C, packed)]
pub struct HTreePtr<T> {
pub htree_hash: HTreeHash,
pub ptr: BlockPtr<T>,
}
impl<T> HTreePtr<T> {
pub fn new(htree_hash: HTreeHash, ptr: BlockPtr<T>) -> Self {
Self { htree_hash, ptr }
}
/// Cast HTreePtr to another type
///
/// # Safety
/// Unsafe because it can be used to transmute types
pub unsafe fn cast<U>(self) -> HTreePtr<U> {
HTreePtr {
htree_hash: self.htree_hash,
ptr: self.ptr.cast(),
}
}
}
impl<T> HTreePtr<T> {
pub fn is_null(&self) -> bool {
self.ptr.is_null()
}
}
impl<T> Clone for HTreePtr<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for HTreePtr<T> {}
impl<T> Default for HTreePtr<T> {
fn default() -> Self {
Self {
htree_hash: HTreeHash::default(),
ptr: BlockPtr::default(),
}
}
}
impl<T> fmt::Debug for HTreePtr<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let htree_hash = self.htree_hash;
let ptr = self.ptr;
f.debug_struct("BlockPtr")
.field("htree_hash", &htree_hash)
.field("ptr", &ptr)
.finish()
}
}
#[repr(C, packed)]
pub struct HTreeNode<T> {
pub ptrs: [HTreePtr<T>; HTREE_IDX_ENTRIES],
padding: [u8; HTREE_IDX_PADDING],
}
impl<T> HTreeNode<T> {
pub fn find_max_htree_hash(&self) -> Option<HTreeHash> {
let mut hash = HTreeHash::default();
for entry in self.ptrs.iter() {
hash = hash.max_ignoring_default(entry.htree_hash);
}
if hash != HTreeHash::default() {
Some(hash)
} else {
None
}
}
pub fn find_ptrs_for_read(
&self,
htree_hash: HTreeHash,
) -> impl Iterator<Item = (usize, &HTreePtr<T>)> {
let mut last_hash = HTreeHash(0.into());
self.ptrs
.iter()
.enumerate()
.filter(move |(_idx, entry)| entry.htree_hash >= htree_hash)
.take_while(move |(_idx, entry)| {
let should_take = !entry.is_null() && last_hash <= htree_hash;
last_hash = entry.htree_hash;
should_take
})
}
}
unsafe impl<T> BlockTrait for HTreeNode<T> {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 <= RECORD_LEVEL {
Some(Self {
ptrs: [HTreePtr::default(); HTREE_IDX_ENTRIES],
padding: [0; HTREE_IDX_PADDING],
})
} else {
None
}
}
}
impl<T> ops::Deref for HTreeNode<T> {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const HTreeNode<T> as *const u8,
mem::size_of::<HTreeNode<T>>(),
) as &[u8]
}
}
}
impl<T> ops::DerefMut for HTreeNode<T> {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut HTreeNode<T> as *mut u8,
mem::size_of::<HTreeNode<T>>(),
) as &mut [u8]
}
}
}
pub fn add_inner_node<T>(
parent: &mut HTreeNode<T>,
new_ptr: HTreePtr<T>,
) -> Result<Option<(HTreeHash, HTreeNode<T>)>> {
// Update the input htree parameters in place
for ptr in parent.ptrs.iter_mut() {
if ptr.is_null() {
*ptr = new_ptr;
parent.ptrs.sort_by(|a, b| a.htree_hash.cmp(&b.htree_hash));
return Ok(None);
}
}
// The parent is full. We need to split it into two by half, ordered by the htree hash.
let mut all_ptrs = Vec::with_capacity(parent.ptrs.len() + 1);
for ptr in parent.ptrs.iter() {
all_ptrs.push(*ptr);
}
all_ptrs.push(new_ptr);
all_ptrs.sort_by(|a, b| a.htree_hash.cmp(&b.htree_hash));
let half_idx = all_ptrs.len() / 2;
// Find if there are duplicate name hashes on the boundary of where we want to split
let half_name_hash = all_ptrs[half_idx].htree_hash;
let mut first_idx = half_idx;
let mut last_idx = half_idx;
for (i, ptr) in all_ptrs.iter().enumerate() {
if ptr.htree_hash == half_name_hash {
if i < first_idx {
first_idx = i;
}
if i > last_idx {
last_idx = i;
}
}
}
// Split the entries_with_name_hash list at the index that minimizes the number of entries in each list while keeping the duplicate name hashes together
let split = if (half_idx - first_idx) < (last_idx - half_idx) {
first_idx
} else {
last_idx
};
let (ptrs1, ptrs2) = all_ptrs.split_at(split);
// Update the existing parent with the first half of the entries
let mut htree_idx1 = HTreeNode::empty(BlockLevel::default()).ok_or(Error::new(EIO))?;
htree_idx1.ptrs[..ptrs1.len()].copy_from_slice(ptrs1);
let _ = mem::replace(parent, htree_idx1);
// Return the second half as a new sibling parent
let mut htree_idx2 = HTreeNode::empty(BlockLevel::default()).ok_or(Error::new(EIO))?;
htree_idx2.ptrs[..ptrs2.len()].copy_from_slice(ptrs2);
let htree_hash2 = ptrs2[ptrs2.len() - 1].htree_hash;
Ok(Some((htree_hash2, htree_idx2)))
}
pub fn add_dir_entry(
dir_list: &mut DirList,
htree_hash: &mut HTreeHash,
dirent: DirEntry,
) -> Result<Option<(HTreeHash, DirList)>> {
if let Some(name) = dirent.name() {
if dir_list.find_entry(name).is_some() {
return Err(Error::new(EEXIST));
}
}
// Update the input htree parameters in place
let name = dirent.name().ok_or(Error::new(EIO))?;
if dir_list.append(&dirent) {
*htree_hash = HTreeHash::from_name(name).max_ignoring_default(*htree_hash);
return Ok(None);
}
// The dir_list is full. We need to split it into two dir_lists by half, ordered by the name hash.
let mut entries_with_name_hash = Vec::with_capacity(dir_list.entry_count() + 1);
for entry in dir_list.entries() {
entries_with_name_hash.push((
HTreeHash::from_name(entry.name().ok_or(Error::new(EIO))?),
entry,
));
}
entries_with_name_hash.push((HTreeHash::from_name(dirent.name().unwrap()), dirent));
entries_with_name_hash.sort_by(|a, b| a.0.cmp(&b.0));
let half = entries_with_name_hash.len() / 2;
let half_name_hash = entries_with_name_hash[half].0;
// Find if there are duplicate name hashes on the boundary of where we want to split
let mut first_idx = half;
let mut last_idx = half;
for (i, (name_hash, _)) in entries_with_name_hash.iter().enumerate() {
if *name_hash == half_name_hash {
if i < first_idx {
first_idx = i;
}
if i > last_idx {
last_idx = i;
}
}
}
last_idx += 1;
// Split the entries_with_name_hash list at the index that minimizes the number of entries in each list while keeping the duplicate name hashes together
let split = if (half - first_idx) < (last_idx - half) {
first_idx
} else {
last_idx
};
let split = split.max(1);
let sorted_entries = entries_with_name_hash
.iter()
.map(|(_, entry)| *entry)
.collect::<Vec<DirEntry>>();
let (entries1, entries2) = sorted_entries.split_at(split);
// Update the existing dir_list with the first half of the entries
let mut new_dir_list = DirList::empty(BlockLevel::default()).ok_or(Error::new(EIO))?;
for entry in entries1.iter() {
new_dir_list.append(entry);
}
let _ = mem::replace(dir_list, new_dir_list);
*htree_hash = entries_with_name_hash[entries1.len() - 1].0;
// Return the second half of the entries as a new dir_list
let mut new_dir_list = DirList::empty(BlockLevel::default()).ok_or(Error::new(EIO))?;
for entry in entries2.iter() {
new_dir_list.append(entry);
}
let new_name_hash = entries_with_name_hash[entries_with_name_hash.len() - 1].0;
Ok(Some((new_name_hash, new_dir_list)))
}
//
// MARK: Unit Tests
//
#[cfg(test)]
mod tests {
use super::*;
use crate::alloc::string::ToString;
use crate::TreePtr;
use alloc::format;
use alloc::string::String;
#[test]
fn htree_ptr_size_test() {
assert_eq!(mem::size_of::<HTreePtr<BlockRaw>>(), 20);
}
#[test]
fn htree_node_size_test() {
assert_eq!(mem::size_of::<HTreeNode<BlockRaw>>(), BLOCK_SIZE as usize);
}
#[test]
fn htree_hash_max_test() {
assert_eq!(HTreeHash::MAX, HTreeHash((u32::MAX - 1).into()));
}
#[test]
fn htree_hash_max_ignoring_default_test() {
let default = HTreeHash::default();
let hash1 = HTreeHash(0.into());
let hash2 = HTreeHash(1.into());
assert_eq!(hash1.max_ignoring_default(default), hash1);
assert_eq!(default.max_ignoring_default(hash1), hash1);
assert_eq!(hash1.max_ignoring_default(hash2), hash2);
}
#[test]
fn htree_node_find_max_htree_hash() {
// In practice, the HTreeHash values should always be in sorted order
let mut htree_node: HTreeNode<String> = HTreeNode::empty(BlockLevel::default()).unwrap();
htree_node.ptrs[0] = HTreePtr::new(HTreeHash(0.into()), BlockPtr::marker(0));
htree_node.ptrs[1] = HTreePtr::new(HTreeHash(1.into()), BlockPtr::marker(0));
htree_node.ptrs[2] = HTreePtr::new(HTreeHash(2.into()), BlockPtr::marker(0));
assert_eq!(
htree_node.find_max_htree_hash().unwrap(),
HTreeHash(2.into())
);
htree_node.ptrs[2] = HTreePtr::default();
assert_eq!(
htree_node.find_max_htree_hash().unwrap(),
HTreeHash(1.into())
);
htree_node.ptrs[1] = HTreePtr::default();
assert_eq!(
htree_node.find_max_htree_hash().unwrap(),
HTreeHash(0.into())
);
htree_node.ptrs[0] = HTreePtr::default();
assert!(htree_node.find_max_htree_hash().is_none());
// For thoroughness, test with HTreeHash out of order
htree_node.ptrs[2] = HTreePtr::new(HTreeHash(4.into()), BlockPtr::marker(0));
htree_node.ptrs[4] = HTreePtr::new(HTreeHash(6.into()), BlockPtr::marker(0));
htree_node.ptrs[6] = HTreePtr::new(HTreeHash(2.into()), BlockPtr::marker(0));
assert_eq!(
htree_node.find_max_htree_hash().unwrap(),
HTreeHash(6.into())
);
}
#[test]
fn htree_node_find_for_read() {
let mut htree_node: HTreeNode<String> = HTreeNode::empty(BlockLevel::default()).unwrap();
htree_node.ptrs[0] = HTreePtr::new(HTreeHash(0.into()), BlockPtr::marker(0));
htree_node.ptrs[1] = HTreePtr::new(HTreeHash(1.into()), BlockPtr::marker(0));
htree_node.ptrs[2] = HTreePtr::new(HTreeHash(2.into()), BlockPtr::marker(0));
htree_node.ptrs[3] = HTreePtr::new(HTreeHash(2.into()), BlockPtr::marker(0));
htree_node.ptrs[4] = HTreePtr::new(HTreeHash(3.into()), BlockPtr::marker(0));
htree_node.ptrs[5] = HTreePtr::new(HTreeHash(3.into()), BlockPtr::marker(0));
htree_node.ptrs[6] = HTreePtr::new(HTreeHash(5.into()), BlockPtr::marker(0));
htree_node.ptrs[7] = HTreePtr::new(HTreeHash(6.into()), BlockPtr::marker(0));
// Confirm that a hash that does not exist, but is less than an existing hash results in a single entry
let mut iter = htree_node.find_ptrs_for_read(HTreeHash(4.into()));
let mut val = iter.next().unwrap();
assert_eq!(val.0, 6);
assert_eq!(val.1.htree_hash, HTreeHash(5.into()));
assert!(iter.next().is_none());
// Confirm that a hash that equals an existing hash results in the match and one following entry
let mut iter = htree_node.find_ptrs_for_read(HTreeHash(1.into()));
val = iter.next().unwrap();
assert_eq!(val.0, 1);
assert_eq!(val.1.htree_hash, HTreeHash(1.into()));
val = iter.next().unwrap();
assert_eq!(val.0, 2);
assert_eq!(val.1.htree_hash, HTreeHash(2.into()));
assert!(iter.next().is_none());
// Confirm that multiple exact hash matches are all returned plus the next entry
let mut iter = htree_node.find_ptrs_for_read(HTreeHash(2.into()));
val = iter.next().unwrap();
assert_eq!(val.0, 2);
assert_eq!(val.1.htree_hash, HTreeHash(2.into()));
val = iter.next().unwrap();
assert_eq!(val.0, 3);
assert_eq!(val.1.htree_hash, HTreeHash(2.into()));
val = iter.next().unwrap();
assert_eq!(val.0, 4);
assert_eq!(val.1.htree_hash, HTreeHash(3.into()));
assert!(iter.next().is_none());
// Confirm that if the last entry matches and the next entry is null, only the match is returned
let mut iter = htree_node.find_ptrs_for_read(HTreeHash(6.into()));
val = iter.next().unwrap();
assert_eq!(val.0, 7);
assert_eq!(val.1.htree_hash, HTreeHash(6.into()));
assert!(iter.next().is_none());
// Confirm that if a hash that is larger than any existing entries, then no entries are returned
let mut iter = htree_node.find_ptrs_for_read(HTreeHash(7.into()));
assert!(iter.next().is_none());
}
#[test]
fn add_dir_entry_exists_test() {
let mut dir_list = DirList::empty(BlockLevel::default()).unwrap();
let mut htree_hash = HTreeHash::default();
let dirent = DirEntry::new(TreePtr::new(123), "test");
let new_sibling = add_dir_entry(&mut dir_list, &mut htree_hash, dirent).unwrap();
assert!(new_sibling.is_none());
assert_eq!(htree_hash, HTreeHash::from_name("test"));
assert_eq!(dir_list.entries().next().unwrap().name(), Some("test"));
// Add the same entry again, and it should fail with an appropriate IO error
let dirent = DirEntry::new(TreePtr::new(123), "test");
let error_expected = add_dir_entry(&mut dir_list, &mut htree_hash, dirent);
assert!(error_expected.is_err());
assert_eq!(error_expected.err().unwrap().errno, EEXIST);
}
#[test]
fn add_dir_entry_many_test() {
let mut dir_list = DirList::empty(BlockLevel::default()).unwrap();
let mut htree_hash = HTreeHash::default();
let total_count = 16;
// Fill up the dir_list
for i in 0..total_count {
let v: usize = i % 10;
let dirent = DirEntry::new(TreePtr::new(123), format!("test{v}_{i:0244}").as_str());
let new_sibling = add_dir_entry(&mut dir_list, &mut htree_hash, dirent).unwrap();
assert!(new_sibling.is_none());
}
// The maximum htree_hash should be retained
let max_tree_hash =
dir_list
.entries()
.enumerate()
.fold(HTreeHash::default(), |max, (i, _)| {
let v = i % 10;
let hash = HTreeHash::from_name(format!("test{v}_{i:0244}").as_str());
max.max_ignoring_default(hash)
});
assert_eq!(htree_hash, max_tree_hash);
// Confirm all the entries exist. Note they happen to be in insert order
for (i, entry) in dir_list.entries().enumerate() {
let v = i % 10;
assert_eq!(entry.name(), Some(format!("test{v}_{i:0244}").as_str()));
}
// Test a split by adding one more entry
let dirent = DirEntry::new(TreePtr::new(123), "test_split");
let new_sibling = add_dir_entry(&mut dir_list, &mut htree_hash, dirent).unwrap();
let (new_sibling_htree_hash, new_sibling_dir_list) =
new_sibling.expect("new_sibling should be created");
// assert!(new_sibling_dir_list.entries.len() );
assert!(new_sibling_htree_hash > htree_hash);
// The htree_hash should be less than the minimum htree_hash in new_sibling_dir_list
let new_sibling_min_htree_hash = new_sibling_dir_list
.entries()
.filter(|entry| !entry.node_ptr().is_null())
.fold(HTreeHash::default(), |min, entry| {
let hash = HTreeHash::from_name(entry.name().unwrap());
min.min(hash)
});
assert!(htree_hash < new_sibling_min_htree_hash);
// Confirm all the entries exist across both dir_lists
let mut expected_names: Vec<String> = (0..total_count)
.map(|i| {
let v = i % 10;
format!("test{v}_{i:0244}")
})
.collect();
expected_names.push("test_split".to_string());
expected_names.sort();
let mut dir_list_entry_count = 0;
for entry in dir_list.entries() {
dir_list_entry_count += 1;
let name = entry.name().unwrap().to_string();
let _ = expected_names.remove(expected_names.binary_search(&name).unwrap());
}
let mut new_sibling_entry_count = 0;
for entry in new_sibling_dir_list.entries() {
new_sibling_entry_count += 1;
let name = entry.name().unwrap().to_string();
let _ = expected_names.remove(expected_names.binary_search(&name).unwrap());
}
assert!(expected_names.is_empty());
// Confirm that the split is in half
assert!((dir_list_entry_count as i32 - new_sibling_entry_count).abs() <= 1);
}
#[test]
fn add_inner_node_simple_test() {
let mut htree_node: HTreeNode<_> = HTreeNode::empty(BlockLevel::default()).unwrap();
let htree_ptr: HTreePtr<_> = HTreePtr::<BlockRaw> {
htree_hash: HTreeHash::from_name("test"),
ptr: BlockPtr::marker(0),
};
let new_sibling = add_inner_node(&mut htree_node, htree_ptr).unwrap();
assert!(new_sibling.is_none());
assert_eq!(htree_node.ptrs[0].htree_hash, HTreeHash::from_name("test"));
}
#[test]
fn add_inner_node_multiple_test() {
let mut htree_node: HTreeNode<_> = HTreeNode::empty(BlockLevel::default()).unwrap();
for i in 0..HTREE_IDX_ENTRIES {
let htree_ptr: HTreePtr<_> = HTreePtr::<BlockRaw> {
htree_hash: HTreeHash(((100_000 + (i % 10) * 1000 + i) as u32).into()),
ptr: BlockPtr::marker(0),
};
let new_sibling = add_inner_node(&mut htree_node, htree_ptr).unwrap();
assert!(new_sibling.is_none());
// Confirm that the htree_ptrs are in sorted order at the start of the ptrs list
let mut prev_hash = HTreeHash::default();
let mut count = 0;
for ptr in htree_node.ptrs.iter() {
if ptr.is_null() {
continue;
}
assert!(
ptr.htree_hash.max_ignoring_default(prev_hash) == ptr.htree_hash,
"index {i}: {:?} > {:?}",
ptr.htree_hash,
prev_hash
);
prev_hash = ptr.htree_hash;
count += 1;
}
assert_eq!(count, i + 1);
}
// Confirm all expected hashes are present
let mut expected_hashes: Vec<u32> = (0..HTREE_IDX_ENTRIES)
.map(|i| (100_000 + (i % 10) * 1000 + i) as u32)
.collect();
expected_hashes.sort();
for ptr in htree_node.ptrs.iter() {
if ptr.is_null() {
break;
}
let idx = expected_hashes
.binary_search(&ptr.htree_hash.0.into())
.unwrap();
expected_hashes.remove(idx);
}
assert!(expected_hashes.is_empty());
// Force a split by adding one more entry
let htree_ptr: HTreePtr<_> = HTreePtr::<BlockRaw> {
htree_hash: HTreeHash(130_000.into()),
ptr: BlockPtr::marker(0),
};
let mut expected_hashes: Vec<u32> = (0..HTREE_IDX_ENTRIES)
.map(|i| (100_000 + (i % 10) * 1000 + i) as u32)
.collect();
expected_hashes.push(130_000);
expected_hashes.sort();
let new_sibling = add_inner_node(&mut htree_node, htree_ptr).unwrap();
let new_sibling = new_sibling.expect("new_sibling should be created");
// Confirm all the entries exist across both htree_nodes
let mut htree_node_entry_count = 0;
for ptr in htree_node.ptrs.iter() {
if ptr.ptr.is_null() {
break;
}
htree_node_entry_count += 1;
let idx = expected_hashes
.binary_search(&ptr.htree_hash.0.into())
.unwrap();
expected_hashes.remove(idx);
}
let mut new_sibling_entry_count = 0;
for ptr in new_sibling.1.ptrs.iter() {
if ptr.ptr.is_null() {
break;
}
new_sibling_entry_count += 1;
let idx = expected_hashes
.binary_search(&ptr.htree_hash.0.into())
.unwrap();
expected_hashes.remove(idx);
}
assert!(
expected_hashes.is_empty(),
"expected_hashes should be empty, but had length {}: {:?}",
expected_hashes.len(),
expected_hashes
);
// Confirm that the split is in half
assert!((htree_node_entry_count as i32 - new_sibling_entry_count).abs() <= 1);
}
}
-73
View File
@@ -1,73 +0,0 @@
use core::{
cmp::PartialEq,
ops::{BitAnd, BitOr, Not},
};
pub trait Io {
type Value: Copy
+ PartialEq
+ BitAnd<Output = Self::Value>
+ BitOr<Output = Self::Value>
+ Not<Output = Self::Value>;
fn read(&self) -> Self::Value;
fn write(&mut self, value: Self::Value);
#[inline(always)]
fn readf(&self, flags: Self::Value) -> bool {
(self.read() & flags) as Self::Value == flags
}
#[inline(always)]
fn writef(&mut self, flags: Self::Value, value: bool) {
let tmp: Self::Value = match value {
true => self.read() | flags,
false => self.read() & !flags,
};
self.write(tmp);
}
}
pub struct ReadOnly<I> {
inner: I,
}
impl<I> ReadOnly<I> {
pub const fn new(inner: I) -> ReadOnly<I> {
ReadOnly { inner: inner }
}
}
impl<I: Io> ReadOnly<I> {
#[inline(always)]
pub fn read(&self) -> I::Value {
self.inner.read()
}
#[inline(always)]
pub fn readf(&self, flags: I::Value) -> bool {
self.inner.readf(flags)
}
}
pub struct WriteOnly<I> {
inner: I,
}
impl<I> WriteOnly<I> {
pub const fn new(inner: I) -> WriteOnly<I> {
WriteOnly { inner: inner }
}
}
impl<I: Io> WriteOnly<I> {
#[inline(always)]
pub fn write(&mut self, value: I::Value) {
self.inner.write(value)
}
#[inline(always)]
pub fn writef(&mut self, flags: I::Value, value: bool) {
self.inner.writef(flags, value)
}
}
-165
View File
@@ -1,165 +0,0 @@
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
use core::ops::{BitAnd, BitOr, Not};
use core::{mem::MaybeUninit, ptr};
use super::io::Io;
#[repr(transparent)]
pub struct Mmio<T> {
value: MaybeUninit<T>,
}
impl<T> Mmio<T> {
pub unsafe fn zeroed() -> Self {
Self {
value: MaybeUninit::zeroed(),
}
}
pub unsafe fn uninit() -> Self {
Self {
value: MaybeUninit::uninit(),
}
}
pub const fn from(value: T) -> Self {
Self {
value: MaybeUninit::new(value),
}
}
}
// Generic implementation (WARNING: requires aligned pointers!)
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
impl<T> Io for Mmio<T>
where
T: Copy + PartialEq + BitAnd<Output = T> + BitOr<Output = T> + Not<Output = T>,
{
type Value = T;
fn read(&self) -> T {
unsafe { ptr::read_volatile(ptr::addr_of!(self.value).cast::<T>()) }
}
fn write(&mut self, value: T) {
unsafe { ptr::write_volatile(ptr::addr_of_mut!(self.value).cast::<T>(), value) };
}
}
// x86 u8 implementation
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
impl Io for Mmio<u8> {
type Value = u8;
fn read(&self) -> Self::Value {
unsafe {
let value: Self::Value;
let ptr: *const Self::Value = ptr::addr_of!(self.value).cast::<Self::Value>();
core::arch::asm!(
"mov {}, [{}]",
out(reg_byte) value,
in(reg) ptr
);
value
}
}
fn write(&mut self, value: Self::Value) {
unsafe {
let ptr: *mut Self::Value = ptr::addr_of_mut!(self.value).cast::<Self::Value>();
core::arch::asm!(
"mov [{}], {}",
in(reg) ptr,
in(reg_byte) value,
);
}
}
}
// x86 u16 implementation
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
impl Io for Mmio<u16> {
type Value = u16;
fn read(&self) -> Self::Value {
unsafe {
let value: Self::Value;
let ptr: *const Self::Value = ptr::addr_of!(self.value).cast::<Self::Value>();
core::arch::asm!(
"mov {:x}, [{}]",
out(reg) value,
in(reg) ptr
);
value
}
}
fn write(&mut self, value: Self::Value) {
unsafe {
let ptr: *mut Self::Value = ptr::addr_of_mut!(self.value).cast::<Self::Value>();
core::arch::asm!(
"mov [{}], {:x}",
in(reg) ptr,
in(reg) value,
);
}
}
}
// x86 u32 implementation
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
impl Io for Mmio<u32> {
type Value = u32;
fn read(&self) -> Self::Value {
unsafe {
let value: Self::Value;
let ptr: *const Self::Value = ptr::addr_of!(self.value).cast::<Self::Value>();
core::arch::asm!(
"mov {:e}, [{}]",
out(reg) value,
in(reg) ptr
);
value
}
}
fn write(&mut self, value: Self::Value) {
unsafe {
let ptr: *mut Self::Value = ptr::addr_of_mut!(self.value).cast::<Self::Value>();
core::arch::asm!(
"mov [{}], {:e}",
in(reg) ptr,
in(reg) value,
);
}
}
}
// x86 u64 implementation (x86_64 only)
#[cfg(target_arch = "x86_64")]
impl Io for Mmio<u64> {
type Value = u64;
fn read(&self) -> Self::Value {
unsafe {
let value: Self::Value;
let ptr: *const Self::Value = ptr::addr_of!(self.value).cast::<Self::Value>();
core::arch::asm!(
"mov {:r}, [{}]",
out(reg) value,
in(reg) ptr
);
value
}
}
fn write(&mut self, value: Self::Value) {
unsafe {
let ptr: *mut Self::Value = ptr::addr_of_mut!(self.value).cast::<Self::Value>();
core::arch::asm!(
"mov [{}], {:r}",
in(reg) ptr,
in(reg) value,
);
}
}
}
-12
View File
@@ -1,12 +0,0 @@
//! I/O functions
pub use self::{io::*, mmio::*};
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub use self::pio::*;
mod io;
mod mmio;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod pio;
-89
View File
@@ -1,89 +0,0 @@
use core::{arch::asm, marker::PhantomData};
use super::io::Io;
/// Generic PIO
#[derive(Copy, Clone)]
pub struct Pio<T> {
port: u16,
value: PhantomData<T>,
}
impl<T> Pio<T> {
/// Create a PIO from a given port
pub const fn new(port: u16) -> Self {
Pio::<T> {
port,
value: PhantomData,
}
}
}
/// Read/Write for byte PIO
impl Io for Pio<u8> {
type Value = u8;
/// Read
#[inline(always)]
fn read(&self) -> u8 {
let value: u8;
unsafe {
asm!("in al, dx", in("dx") self.port, out("al") value, options(nostack, nomem, preserves_flags));
}
value
}
/// Write
#[inline(always)]
fn write(&mut self, value: u8) {
unsafe {
asm!("out dx, al", in("dx") self.port, in("al") value, options(nostack, nomem, preserves_flags));
}
}
}
/// Read/Write for word PIO
impl Io for Pio<u16> {
type Value = u16;
/// Read
#[inline(always)]
fn read(&self) -> u16 {
let value: u16;
unsafe {
asm!("in ax, dx", in("dx") self.port, out("ax") value, options(nostack, nomem, preserves_flags));
}
value
}
/// Write
#[inline(always)]
fn write(&mut self, value: u16) {
unsafe {
asm!("out dx, ax", in("dx") self.port, in("ax") value, options(nostack, nomem, preserves_flags));
}
}
}
/// Read/Write for doubleword PIO
impl Io for Pio<u32> {
type Value = u32;
/// Read
#[inline(always)]
fn read(&self) -> u32 {
let value: u32;
unsafe {
asm!("in eax, dx", in("dx") self.port, out("eax") value, options(nostack, nomem, preserves_flags));
}
value
}
/// Write
#[inline(always)]
fn write(&mut self, value: u32) {
unsafe {
asm!("out dx, eax", in("dx") self.port, in("eax") value, options(nostack, nomem, preserves_flags));
}
}
}
+104
View File
@@ -0,0 +1,104 @@
use aes::{
cipher::{BlockDecrypt, BlockEncrypt, KeyInit},
Aes128,
};
use xts_mode::Xts128;
// The raw key, keep secret!
#[repr(transparent)]
pub struct Key([u8; 16]);
impl Key {
/// Generate a random key
#[cfg(feature = "std")]
pub fn new() -> Result<Self, getrandom::Error> {
let mut bytes = [0; 16];
getrandom::getrandom(&mut bytes)?;
Ok(Self(bytes))
}
pub fn encrypt(&self, password_aes: &Aes128) -> EncryptedKey {
let mut block = aes::Block::from(self.0);
password_aes.encrypt_block(&mut block);
EncryptedKey(block.into())
}
pub fn into_aes(self) -> Aes128 {
Aes128::new(&aes::Block::from(self.0))
}
}
/// The encrypted key, encrypted with AES using the salt and password
#[derive(Clone, Copy, Default)]
#[repr(transparent)]
pub struct EncryptedKey([u8; 16]);
impl EncryptedKey {
pub fn decrypt(&self, password_aes: &Aes128) -> Key {
let mut block = aes::Block::from(self.0);
password_aes.decrypt_block(&mut block);
Key(block.into())
}
}
/// Salt used to prevent rainbow table attacks on the encryption password
#[derive(Clone, Copy, Default)]
#[repr(transparent)]
pub struct Salt([u8; 16]);
impl Salt {
/// Generate a random salt
#[cfg(feature = "std")]
pub fn new() -> Result<Self, getrandom::Error> {
let mut bytes = [0; 16];
getrandom::getrandom(&mut bytes)?;
Ok(Self(bytes))
}
}
/// The key slot, containing the salt and encrypted key that are used with one password
#[derive(Clone, Copy, Default)]
#[repr(C, packed)]
pub struct KeySlot {
salt: Salt,
// Two keys for AES XTS 128
encrypted_keys: (EncryptedKey, EncryptedKey),
}
impl KeySlot {
/// Get the password AES key (generated from the password and salt, encrypts the real key)
pub fn password_aes(password: &[u8], salt: &Salt) -> Result<Aes128, argon2::Error> {
let mut key = Key([0; 16]);
let mut params_builder = argon2::ParamsBuilder::new();
params_builder.output_len(key.0.len())?;
let argon2 = argon2::Argon2::new(
argon2::Algorithm::Argon2id,
argon2::Version::V0x13,
params_builder.params()?,
);
argon2.hash_password_into(password, &salt.0, &mut key.0)?;
Ok(key.into_aes())
}
/// Create a new key slot from a password, salt, and encryption key
pub fn new(password: &[u8], salt: Salt, keys: (Key, Key)) -> Result<Self, argon2::Error> {
let password_aes = Self::password_aes(password, &salt)?;
Ok(Self {
salt,
encrypted_keys: (keys.0.encrypt(&password_aes), keys.1.encrypt(&password_aes)),
})
}
/// Get the encryption cipher from this key slot
pub fn cipher(&self, password: &[u8]) -> Result<Xts128<Aes128>, argon2::Error> {
let password_aes = Self::password_aes(password, &self.salt)?;
Ok(Xts128::new(
self.encrypted_keys.0.decrypt(&password_aes).into_aes(),
self.encrypted_keys.1.decrypt(&password_aes).into_aes(),
))
}
}
+63 -46
View File
@@ -1,53 +1,70 @@
#![cfg_attr(not(any(feature = "std", test)), no_std)] #![crate_name = "redoxfs"]
#![allow(unexpected_cfgs)] // why does this even exist? #![crate_type = "lib"]
#![cfg_attr(not(feature = "std"), no_std)]
// Used often in generating redox_syscall errors
#![allow(clippy::or_fun_call)]
#![allow(unexpected_cfgs)]
#[cfg(test)] extern crate alloc;
extern crate core;
pub use self::{arch::*, data::*, error::*, flag::*, io::*, number::*}; use core::sync::atomic::AtomicUsize;
#[cfg(target_arch = "aarch64")] // The alloc log grows by 1 block about every 21 generations
#[path = "arch/aarch64.rs"] pub const ALLOC_GC_THRESHOLD: u64 = 1024;
mod arch; pub const BLOCK_SIZE: u64 = 4096;
// A record is 4KiB << 5 = 128KiB
pub const RECORD_LEVEL: usize = 5;
pub const RECORD_SIZE: u64 = BLOCK_SIZE << RECORD_LEVEL;
pub const SIGNATURE: &[u8; 8] = b"RedoxFS\0";
pub const VERSION: u64 = 8;
pub const DIR_ENTRY_MAX_LENGTH: usize = 252;
#[cfg(target_arch = "riscv64")] pub static IS_UMT: AtomicUsize = AtomicUsize::new(0);
#[path = "arch/riscv64.rs"]
mod arch;
#[cfg(target_arch = "x86")] pub use self::allocator::{AllocEntry, AllocList, Allocator, ReleaseList, ALLOC_LIST_ENTRIES};
#[path = "arch/x86.rs"] #[cfg(feature = "std")]
mod arch; pub use self::archive::{archive, archive_at};
pub use self::block::{
BlockAddr, BlockData, BlockLevel, BlockList, BlockMeta, BlockPtr, BlockRaw, BlockTrait,
};
#[cfg(feature = "std")]
pub use self::clone::clone;
pub use self::dir::{DirEntry, DirList};
pub use self::disk::*;
pub use self::filesystem::FileSystem;
pub use self::header::{Header, HEADER_RING};
pub use self::key::{Key, KeySlot, Salt};
#[cfg(feature = "std")]
pub use self::mount::mount;
pub use self::node::{Node, NodeFlags, NodeLevel, NodeLevelData};
pub use self::record::RecordRaw;
pub use self::transaction::Transaction;
pub use self::tree::{Tree, TreeData, TreeList, TreePtr};
#[cfg(feature = "std")]
pub use self::unmount::unmount_path;
#[cfg(target_arch = "x86_64")] mod allocator;
#[path = "arch/x86_64.rs"] #[cfg(feature = "std")]
mod arch; mod archive;
mod block;
#[cfg(feature = "std")]
mod clone;
mod dir;
mod disk;
mod filesystem;
mod header;
mod htree;
mod key;
#[cfg(all(feature = "std", not(fuzzing)))]
mod mount;
#[cfg(all(feature = "std", fuzzing))]
pub mod mount;
mod node;
mod record;
mod transaction;
mod tree;
#[cfg(feature = "std")]
mod unmount;
/// Function definitions #[cfg(all(feature = "std", test))]
#[cfg(feature = "userspace")] mod tests;
pub mod call;
#[cfg(feature = "userspace")]
pub use call::*;
/// Complex structures that are used for some system calls
pub mod data;
pub mod dirent;
/// All errors that can be generated by a system call
pub mod error;
/// Flags used as an argument to many system calls
pub mod flag;
/// Functions for low level hardware control
pub mod io;
/// Call numbers used by each system call
pub mod number;
/// ABI for shared memory based signals
pub mod sigabi;
/// V2 scheme format
pub mod schemev2;
+580
View File
@@ -0,0 +1,580 @@
extern crate fuser;
use std::cmp;
use std::ffi::OsStr;
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
use self::fuser::MountOption;
use self::fuser::TimeOrNow;
use crate::mount::fuse::TimeOrNow::Now;
use crate::mount::fuse::TimeOrNow::SpecificTime;
use crate::{filesystem, Disk, Node, TreeData, TreePtr, BLOCK_SIZE};
use self::fuser::{
FileAttr, FileType, Filesystem, ReplyAttr, ReplyCreate, ReplyData, ReplyDirectory, ReplyEmpty,
ReplyEntry, ReplyOpen, ReplyStatfs, ReplyWrite, Request, Session,
};
use std::time::Duration;
const TTL: Duration = Duration::new(1, 0); // 1 second
const NULL_TIME: Duration = Duration::new(0, 0);
pub fn mount<D, P, T, F>(
mut filesystem: filesystem::FileSystem<D>,
mountpoint: P,
callback: F,
) -> io::Result<T>
where
D: Disk,
P: AsRef<Path>,
F: FnOnce(&Path) -> T,
{
let mountpoint = mountpoint.as_ref();
// One of the uses of this redoxfs fuse wrapper is to populate a filesystem
// while building the Redox OS kernel. This means that we need to write on
// a filesystem that belongs to `root`, which in turn means that we need to
// be `root`, thus that we need to allow `root` to have access.
let defer_permissions = [MountOption::CUSTOM("defer_permissions".to_owned())];
let res = {
let mut session = Session::new(
Fuse {
fs: &mut filesystem,
},
mountpoint,
if cfg!(target_os = "macos") {
&defer_permissions
} else {
&[]
},
)?;
let res = callback(mountpoint);
session.run()?;
res
};
// Cleanup on unmount
filesystem.cleanup()?;
Ok(res)
}
pub struct Fuse<'f, D: Disk> {
pub fs: &'f mut filesystem::FileSystem<D>,
}
fn node_attr(node: &TreeData<Node>) -> FileAttr {
FileAttr {
ino: node.id() as u64,
size: node.data().size(),
// Blocks is in 512 byte blocks, not in our block size
blocks: node.data().blocks() * (BLOCK_SIZE / 512),
blksize: 512,
atime: SystemTime::UNIX_EPOCH + Duration::new(node.data().atime().0, node.data().atime().1),
mtime: SystemTime::UNIX_EPOCH + Duration::new(node.data().mtime().0, node.data().mtime().1),
ctime: SystemTime::UNIX_EPOCH + Duration::new(node.data().ctime().0, node.data().ctime().1),
crtime: UNIX_EPOCH + NULL_TIME,
kind: if node.data().is_dir() {
FileType::Directory
} else if node.data().is_symlink() {
FileType::Symlink
} else {
FileType::RegularFile
},
perm: node.data().mode() & Node::MODE_PERM,
nlink: node.data().links(),
uid: node.data().uid(),
gid: node.data().gid(),
rdev: 0,
flags: 0,
}
}
impl<D: Disk> Filesystem for Fuse<'_, D> {
fn lookup(&mut self, _req: &Request, parent_id: u64, name: &OsStr, reply: ReplyEntry) {
let parent_ptr = TreePtr::new(parent_id as u32);
match self
.fs
.tx(|tx| tx.find_node(parent_ptr, name.to_str().unwrap()))
{
Ok(node) => {
reply.entry(&TTL, &node_attr(&node), 0);
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn getattr(&mut self, _req: &Request, node_id: u64, _fh: Option<u64>, reply: ReplyAttr) {
let node_ptr = TreePtr::<Node>::new(node_id as u32);
match self.fs.tx(|tx| tx.read_tree(node_ptr)) {
Ok(node) => {
reply.attr(&TTL, &node_attr(&node));
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn setattr(
&mut self,
_req: &Request,
node_id: u64,
mode: Option<u32>,
uid: Option<u32>,
gid: Option<u32>,
size: Option<u64>,
atime: Option<TimeOrNow>,
mtime: Option<TimeOrNow>,
_ctime: Option<SystemTime>,
_fh: Option<u64>,
_crtime: Option<SystemTime>,
_chgtime: Option<SystemTime>,
_bkuptime: Option<SystemTime>,
_flags: Option<u32>,
reply: ReplyAttr,
) {
let node_ptr = TreePtr::<Node>::new(node_id as u32);
let mut node = match self.fs.tx(|tx| tx.read_tree(node_ptr)) {
Ok(ok) => ok,
Err(err) => {
reply.error(err.errno);
return;
}
};
let mut node_changed = false;
if let Some(mode) = mode {
if node.data().mode() & Node::MODE_PERM != mode as u16 & Node::MODE_PERM {
let new_mode =
(node.data().mode() & Node::MODE_TYPE) | (mode as u16 & Node::MODE_PERM);
node.data_mut().set_mode(new_mode);
node_changed = true;
}
}
if let Some(uid) = uid {
if node.data().uid() != uid {
node.data_mut().set_uid(uid);
node_changed = true;
}
}
if let Some(gid) = gid {
if node.data().gid() != gid {
node.data_mut().set_gid(gid);
node_changed = true;
}
}
if let Some(atime) = atime {
let atime_c = match atime {
SpecificTime(st) => st.duration_since(UNIX_EPOCH).unwrap(),
Now => SystemTime::now().duration_since(UNIX_EPOCH).unwrap(),
};
node.data_mut()
.set_atime(atime_c.as_secs(), atime_c.subsec_nanos());
node_changed = true;
}
if let Some(mtime) = mtime {
let mtime_c = match mtime {
SpecificTime(st) => st.duration_since(UNIX_EPOCH).unwrap(),
Now => SystemTime::now().duration_since(UNIX_EPOCH).unwrap(),
};
node.data_mut()
.set_mtime(mtime_c.as_secs(), mtime_c.subsec_nanos());
node_changed = true;
}
if let Some(size) = size {
match self.fs.tx(|tx| tx.truncate_node_inner(&mut node, size)) {
Ok(ok) => {
if ok {
node_changed = true;
}
}
Err(err) => {
reply.error(err.errno);
return;
}
}
}
let attr = node_attr(&node);
if node_changed {
if let Err(err) = self.fs.tx(|tx| tx.sync_tree(node)) {
reply.error(err.errno);
return;
}
}
reply.attr(&TTL, &attr);
}
fn open(&mut self, _req: &Request<'_>, node_id: u64, _flags: i32, reply: ReplyOpen) {
let node_ptr = TreePtr::<Node>::new(node_id as u32);
match self.fs.tx(|tx| tx.on_open_node(node_ptr)) {
Ok(()) => reply.opened(0, 0),
Err(err) => reply.error(err.errno),
}
}
fn read(
&mut self,
_req: &Request,
node_id: u64,
_fh: u64,
offset: i64,
size: u32,
_flags: i32,
_lock_owner: Option<u64>,
reply: ReplyData,
) {
let node_ptr = TreePtr::<Node>::new(node_id as u32);
let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
let mut data = vec![0; size as usize];
match self.fs.tx(|tx| {
tx.read_node(
node_ptr,
cmp::max(0, offset) as u64,
&mut data,
atime.as_secs(),
atime.subsec_nanos(),
)
}) {
Ok(count) => {
reply.data(&data[..count]);
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn write(
&mut self,
_req: &Request,
node_id: u64,
_fh: u64,
offset: i64,
data: &[u8],
_write_flags: u32,
_flags: i32,
_lock_owner: Option<u64>,
reply: ReplyWrite,
) {
let node_ptr = TreePtr::<Node>::new(node_id as u32);
let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
match self.fs.tx(|tx| {
tx.write_node(
node_ptr,
cmp::max(0, offset) as u64,
data,
mtime.as_secs(),
mtime.subsec_nanos(),
)
}) {
Ok(count) => {
reply.written(count as u32);
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn flush(&mut self, _req: &Request, _ino: u64, _fh: u64, _lock_owner: u64, reply: ReplyEmpty) {
reply.ok();
}
fn release(
&mut self,
_req: &Request,
node_id: u64,
_fh: u64,
_flags: i32,
_lock_owner: Option<u64>,
_flush: bool,
reply: ReplyEmpty,
) {
let node_ptr = TreePtr::new(node_id as u32);
match self.fs.tx(|tx| tx.on_close_node(node_ptr)) {
Ok(()) => reply.ok(),
Err(err) => reply.error(err.errno),
}
}
fn fsync(&mut self, _req: &Request, _ino: u64, _fh: u64, _datasync: bool, reply: ReplyEmpty) {
reply.ok();
}
fn readdir(
&mut self,
_req: &Request,
parent_id: u64,
_fh: u64,
offset: i64,
mut reply: ReplyDirectory,
) {
let parent_ptr = TreePtr::new(parent_id as u32);
let mut children = Vec::new();
match self.fs.tx(|tx| tx.child_nodes(parent_ptr, &mut children)) {
Ok(()) => {
let mut i;
let skip;
if offset == 0 {
skip = 0;
i = 0;
let _full = reply.add(parent_id, i, FileType::Directory, ".");
i += 1;
let _full = reply.add(
//TODO: get parent?
parent_id,
i,
FileType::Directory,
"..",
);
i += 1;
} else {
i = offset + 1;
skip = offset as usize - 1;
}
for child in children.iter().skip(skip) {
//TODO: make it possible to get file type from directory entry
let node = match self.fs.tx(|tx| tx.read_tree(child.node_ptr())) {
Ok(ok) => ok,
Err(err) => {
reply.error(err.errno);
return;
}
};
let full = reply.add(
child.node_ptr().id() as u64,
i,
if node.data().is_dir() {
FileType::Directory
} else {
FileType::RegularFile
},
child.name().unwrap(),
);
if full {
break;
}
i += 1;
}
reply.ok();
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn create(
&mut self,
_req: &Request,
parent_id: u64,
name: &OsStr,
mode: u32,
_umask: u32,
_flags: i32,
reply: ReplyCreate,
) {
let parent_ptr = TreePtr::<Node>::new(parent_id as u32);
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
match self.fs.tx(|tx| {
let node = tx.create_node(
parent_ptr,
name.to_str().unwrap(),
Node::MODE_FILE | (mode as u16 & Node::MODE_PERM),
ctime.as_secs(),
ctime.subsec_nanos(),
)?;
tx.on_open_node(node.ptr())?;
Ok(node)
}) {
Ok(node) => {
// println!("Create {:?}:{:o}:{:o}", node.1.name(), node.1.mode, mode);
reply.created(&TTL, &node_attr(&node), 0, 0, 0);
}
Err(error) => {
reply.error(error.errno);
}
}
}
fn mkdir(
&mut self,
_req: &Request,
parent_id: u64,
name: &OsStr,
mode: u32,
_umask: u32,
reply: ReplyEntry,
) {
let parent_ptr = TreePtr::<Node>::new(parent_id as u32);
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
match self.fs.tx(|tx| {
tx.create_node(
parent_ptr,
name.to_str().unwrap(),
Node::MODE_DIR | (mode as u16 & Node::MODE_PERM),
ctime.as_secs(),
ctime.subsec_nanos(),
)
}) {
Ok(node) => {
// println!("Mkdir {:?}:{:o}:{:o}", node.1.name(), node.1.mode, mode);
reply.entry(&TTL, &node_attr(&node), 0);
}
Err(error) => {
reply.error(error.errno);
}
}
}
fn rmdir(&mut self, _req: &Request, parent_id: u64, name: &OsStr, reply: ReplyEmpty) {
let parent_ptr = TreePtr::<Node>::new(parent_id as u32);
match self
.fs
.tx(|tx| tx.remove_node(parent_ptr, name.to_str().unwrap(), Node::MODE_DIR))
{
Ok(_) => {
reply.ok();
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn unlink(&mut self, _req: &Request, parent_id: u64, name: &OsStr, reply: ReplyEmpty) {
let parent_ptr = TreePtr::<Node>::new(parent_id as u32);
match self
.fs
.tx(|tx| tx.remove_node(parent_ptr, name.to_str().unwrap(), Node::MODE_FILE))
{
Ok(_) => {
reply.ok();
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn statfs(&mut self, _req: &Request, _ino: u64, reply: ReplyStatfs) {
let bsize = BLOCK_SIZE;
let blocks = self.fs.header.size() / bsize;
let bfree = self.fs.allocator().free();
reply.statfs(blocks, bfree, bfree, 0, 0, bsize as u32, 256, 0);
}
fn symlink(
&mut self,
_req: &Request,
parent_id: u64,
name: &OsStr,
link: &Path,
reply: ReplyEntry,
) {
let parent_ptr = TreePtr::<Node>::new(parent_id as u32);
let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
match self.fs.tx(|tx| {
let node = tx.create_node(
parent_ptr,
name.to_str().unwrap(),
Node::MODE_SYMLINK | 0o777,
ctime.as_secs(),
ctime.subsec_nanos(),
)?;
let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
tx.write_node(
node.ptr(),
0,
link.as_os_str().as_bytes(),
mtime.as_secs(),
mtime.subsec_nanos(),
)?;
Ok(node)
}) {
Ok(node) => {
reply.entry(&TTL, &node_attr(&node), 0);
}
Err(error) => {
reply.error(error.errno);
}
}
}
fn readlink(&mut self, _req: &Request, node_id: u64, reply: ReplyData) {
let node_ptr = TreePtr::<Node>::new(node_id as u32);
let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
let mut data = vec![0; 4096];
match self.fs.tx(|tx| {
tx.read_node(
node_ptr,
0,
&mut data,
atime.as_secs(),
atime.subsec_nanos(),
)
}) {
Ok(count) => {
reply.data(&data[..count]);
}
Err(err) => {
reply.error(err.errno);
}
}
}
fn rename(
&mut self,
_req: &Request,
orig_parent: u64,
orig_name: &OsStr,
new_parent: u64,
new_name: &OsStr,
_flags: u32,
reply: ReplyEmpty,
) {
let orig_parent_ptr = TreePtr::<Node>::new(orig_parent as u32);
let orig_name = orig_name.to_str().expect("name is not utf-8");
let new_parent_ptr = TreePtr::<Node>::new(new_parent as u32);
let new_name = new_name.to_str().expect("name is not utf-8");
// TODO: improve performance
match self
.fs
.tx(|tx| tx.rename_node(orig_parent_ptr, orig_name, new_parent_ptr, new_name))
{
Ok(()) => reply.ok(),
Err(err) => reply.error(err.errno),
}
}
}
+23
View File
@@ -0,0 +1,23 @@
#[cfg(all(not(target_os = "redox"), not(fuzzing), feature = "fuse"))]
mod fuse;
#[cfg(all(not(target_os = "redox"), fuzzing, feature = "fuse"))]
pub mod fuse;
#[cfg(all(not(target_os = "redox"), feature = "fuse"))]
pub use self::fuse::mount;
#[cfg(all(not(target_os = "redox"), not(fuzzing), not(feature = "fuse")))]
mod stub;
#[cfg(all(not(target_os = "redox"), fuzzing, not(feature = "fuse")))]
pub mod stub;
#[cfg(all(not(target_os = "redox"), not(feature = "fuse")))]
pub use self::stub::mount;
#[cfg(target_os = "redox")]
mod redox;
#[cfg(target_os = "redox")]
pub use self::redox::mount;
+89
View File
@@ -0,0 +1,89 @@
use redox_scheme::{
scheme::{SchemeState, SchemeSync},
RequestKind, Response, SignalBehavior, Socket,
};
use std::io;
use std::path::Path;
use std::sync::atomic::Ordering;
use crate::{Disk, FileSystem, IS_UMT};
use self::scheme::FileScheme;
pub mod resource;
pub mod scheme;
//FIXME: mut callback is not mut
#[allow(unused_mut)]
pub fn mount<D, P, T, F>(filesystem: FileSystem<D>, mountpoint: P, mut callback: F) -> io::Result<T>
where
D: Disk,
P: AsRef<Path>,
F: FnOnce(&Path) -> T,
{
let mountpoint = mountpoint.as_ref();
let socket = Socket::create()?;
let scheme_name = format!("{}", mountpoint.display());
let mounted_path = format!("/scheme/{}", mountpoint.display());
let mut state = SchemeState::new();
let mut scheme = FileScheme::new(scheme_name, mounted_path.clone(), filesystem, &socket)?;
redox_scheme::scheme::register_sync_scheme(
&socket,
&format!("{}", mountpoint.display()),
&mut scheme,
)?;
let res = callback(Path::new(&mounted_path));
while IS_UMT.load(Ordering::SeqCst) == 0 {
let req = match socket.next_request(SignalBehavior::Restart)? {
None => break,
Some(req) => {
match req.kind() {
RequestKind::Call(r) => r,
RequestKind::SendFd(sendfd_request) => {
let result = scheme.on_sendfd(&sendfd_request);
let response = Response::new(result, sendfd_request);
if !socket.write_response(response, SignalBehavior::Restart)? {
break;
}
continue;
}
RequestKind::OnClose { id } => {
scheme.on_close(id);
state.on_close(id);
continue;
}
RequestKind::OnDetach { id, pid } => {
let Ok(inode) = scheme.inode(id) else {
log::warn!("RequestKind::OnDetach with invalid `id`");
continue;
};
state.on_detach(id, inode, pid);
continue;
}
_ => {
// TODO: Redoxfs does not yet support asynchronous file IO. It might still make
// sense to implement cancellation for huge buffers, e.g. dd bs=1G
continue;
}
}
}
};
let response = req.handle_sync(&mut scheme, &mut state);
if !socket.write_response(response, SignalBehavior::Restart)? {
break;
}
}
// Cleanup on unmount
scheme.fs.cleanup()?;
Ok(res)
}
+881
View File
@@ -0,0 +1,881 @@
use std::slice;
use std::time::{SystemTime, UNIX_EPOCH};
use alloc::collections::BTreeMap;
use libredox::call::MmapArgs;
use range_tree::RangeTree;
use syscall::data::{Stat, TimeSpec};
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
use syscall::error::{Error, Result, EBADF, EINVAL, EISDIR, ENOTDIR, EPERM};
use syscall::flag::{
MapFlags, F_GETFL, F_SETFL, MODE_PERM, O_ACCMODE, O_APPEND, O_RDONLY, O_RDWR, O_WRONLY,
PROT_READ, PROT_WRITE,
};
use syscall::{EBADFD, ENOENT, PAGE_SIZE};
use crate::{Disk, Node, Transaction, TreePtr, BLOCK_SIZE};
pub type Fmaps = BTreeMap<u32, FileMmapInfo>;
pub trait Resource<D: Disk> {
fn parent_ptr_opt(&self) -> Option<TreePtr<Node>>;
fn node_ptr(&self) -> TreePtr<Node>;
fn uid(&self) -> u32;
fn set_path(&mut self, path: &str);
fn read(
&mut self,
fmaps: &mut Fmaps,
buf: &mut [u8],
offset: u64,
tx: &mut Transaction<D>,
) -> Result<usize>;
fn write(
&mut self,
fmaps: &mut Fmaps,
buf: &[u8],
offset: u64,
tx: &mut Transaction<D>,
) -> Result<usize>;
fn fsize(&mut self, tx: &mut Transaction<D>) -> Result<u64>;
fn fmap(
&mut self,
fmaps: &mut Fmaps,
flags: MapFlags,
size: usize,
offset: u64,
tx: &mut Transaction<D>,
) -> Result<usize>;
fn funmap(
&mut self,
fmaps: &mut Fmaps,
offset: u64,
size: usize,
tx: &mut Transaction<D>,
) -> Result<()>;
fn fchmod(&mut self, mode: u16, tx: &mut Transaction<D>) -> Result<()> {
let mut node = tx.read_tree(self.node_ptr())?;
if node.data().uid() == self.uid() || self.uid() == 0 {
let old_mode = node.data().mode();
let new_mode = (old_mode & !MODE_PERM) | (mode & MODE_PERM);
if old_mode != new_mode {
node.data_mut().set_mode(new_mode);
tx.sync_tree(node)?;
}
Ok(())
} else {
Err(Error::new(EPERM))
}
}
fn fchown(&mut self, uid: u32, gid: u32, tx: &mut Transaction<D>) -> Result<()> {
let mut node = tx.read_tree(self.node_ptr())?;
let old_uid = node.data().uid();
if old_uid == self.uid() || self.uid() == 0 {
let mut node_changed = false;
if uid as i32 != -1 {
if uid != old_uid {
node.data_mut().set_uid(uid);
node_changed = true;
}
}
if gid as i32 != -1 {
let old_gid = node.data().gid();
if gid != old_gid {
node.data_mut().set_gid(gid);
node_changed = true;
}
}
if node_changed {
tx.sync_tree(node)?;
}
Ok(())
} else {
Err(Error::new(EPERM))
}
}
fn fcntl(&mut self, cmd: usize, arg: usize) -> Result<usize>;
fn path(&self) -> &str;
fn stat(&self, stat: &mut Stat, tx: &mut Transaction<D>) -> Result<()> {
let node = tx.read_tree(self.node_ptr())?;
let ctime = node.data().ctime();
let mtime = node.data().mtime();
let atime = node.data().atime();
*stat = Stat {
st_dev: 0, // TODO
st_ino: node.id() as u64,
st_mode: node.data().mode(),
st_nlink: node.data().links(),
st_uid: node.data().uid(),
st_gid: node.data().gid(),
st_size: node.data().size(),
st_blksize: 512,
// Blocks is in 512 byte blocks, not in our block size
st_blocks: node.data().blocks() * (BLOCK_SIZE / 512),
st_mtime: mtime.0,
st_mtime_nsec: mtime.1,
st_atime: atime.0,
st_atime_nsec: atime.1,
st_ctime: ctime.0,
st_ctime_nsec: ctime.1,
};
Ok(())
}
fn sync(&mut self, fmaps: &mut Fmaps, tx: &mut Transaction<D>) -> Result<()>;
fn truncate(&mut self, len: u64, tx: &mut Transaction<D>) -> Result<()>;
fn utimens(&mut self, times: &[TimeSpec], tx: &mut Transaction<D>) -> Result<()>;
fn getdents<'buf>(
&mut self,
buf: DirentBuf<&'buf mut [u8]>,
opaque_offset: u64,
tx: &mut Transaction<D>,
) -> Result<DirentBuf<&'buf mut [u8]>>;
}
pub struct Entry {
pub node_ptr: TreePtr<Node>,
pub name: String,
}
pub struct DirResource {
path: String,
parent_ptr_opt: Option<TreePtr<Node>>,
node_ptr: TreePtr<Node>,
data: Option<Vec<Entry>>,
uid: u32,
}
impl DirResource {
pub fn new(
path: String,
parent_ptr_opt: Option<TreePtr<Node>>,
node_ptr: TreePtr<Node>,
data: Option<Vec<Entry>>,
uid: u32,
) -> DirResource {
DirResource {
path,
parent_ptr_opt,
node_ptr,
data,
uid,
}
}
}
impl<D: Disk> Resource<D> for DirResource {
fn parent_ptr_opt(&self) -> Option<TreePtr<Node>> {
self.parent_ptr_opt
}
fn node_ptr(&self) -> TreePtr<Node> {
self.node_ptr
}
fn uid(&self) -> u32 {
self.uid
}
fn set_path(&mut self, path: &str) {
self.path = path.to_string();
}
fn read(
&mut self,
_fmaps: &mut Fmaps,
_buf: &mut [u8],
_offset: u64,
_tx: &mut Transaction<D>,
) -> Result<usize> {
Err(Error::new(EISDIR))
}
fn write(
&mut self,
_fmaps: &mut Fmaps,
_buf: &[u8],
_offset: u64,
_tx: &mut Transaction<D>,
) -> Result<usize> {
Err(Error::new(EBADF))
}
fn fsize(&mut self, _tx: &mut Transaction<D>) -> Result<u64> {
Ok(self.data.as_ref().ok_or(Error::new(EBADF))?.len() as u64)
}
fn fmap(
&mut self,
_fmaps: &mut Fmaps,
_flags: MapFlags,
_size: usize,
_offset: u64,
_tx: &mut Transaction<D>,
) -> Result<usize> {
Err(Error::new(EBADF))
}
fn funmap(
&mut self,
_fmaps: &mut Fmaps,
_offset: u64,
_size: usize,
_tx: &mut Transaction<D>,
) -> Result<()> {
Err(Error::new(EBADF))
}
fn fcntl(&mut self, _cmd: usize, _arg: usize) -> Result<usize> {
Err(Error::new(EBADF))
}
fn path(&self) -> &str {
&self.path
}
fn sync(&mut self, _fmaps: &mut Fmaps, _tx: &mut Transaction<D>) -> Result<()> {
Err(Error::new(EBADF))
}
fn truncate(&mut self, _len: u64, _tx: &mut Transaction<D>) -> Result<()> {
Err(Error::new(EBADF))
}
fn utimens(&mut self, times: &[TimeSpec], tx: &mut Transaction<D>) -> Result<()> {
let mut node = tx.read_tree(self.node_ptr)?;
if node.data().uid() == self.uid || self.uid == 0 {
if let &[atime, mtime] = times {
let mut node_changed = false;
let old_mtime = node.data().mtime();
let new_mtime = (mtime.tv_sec as u64, mtime.tv_nsec as u32);
if old_mtime != new_mtime {
node.data_mut().set_mtime(new_mtime.0, new_mtime.1);
node_changed = true;
}
let old_atime = node.data().atime();
let new_atime = (atime.tv_sec as u64, atime.tv_nsec as u32);
if old_atime != new_atime {
node.data_mut().set_atime(new_atime.0, new_atime.1);
node_changed = true;
}
if node_changed {
tx.sync_tree(node)?;
}
}
Ok(())
} else {
Err(Error::new(EPERM))
}
}
fn getdents<'buf>(
&mut self,
mut buf: DirentBuf<&'buf mut [u8]>,
opaque_offset: u64,
tx: &mut Transaction<D>,
) -> Result<DirentBuf<&'buf mut [u8]>> {
match &self.data {
Some(data) => {
let opaque_offset = opaque_offset as usize;
for (idx, entry) in data.iter().enumerate().skip(opaque_offset) {
let child = match tx.read_tree(entry.node_ptr) {
Ok(r) => r,
Err(Error { errno: ENOENT }) => continue,
Err(err) => return Err(err),
};
let result = buf.entry(DirEntry {
inode: child.id() as u64,
next_opaque_id: idx as u64 + 1,
name: &entry.name,
kind: match child.data().mode() & Node::MODE_TYPE {
Node::MODE_DIR => DirentKind::Directory,
Node::MODE_FILE => DirentKind::Regular,
Node::MODE_SYMLINK => DirentKind::Symlink,
//TODO: more types?
_ => DirentKind::Unspecified,
},
});
if let Err(err) = result {
if err.errno == EINVAL && idx > opaque_offset {
// POSIX allows partial result of getdents
break;
} else {
return Err(err);
}
}
}
Ok(buf)
}
None => Err(Error::new(EBADF)),
}
}
}
#[derive(Debug)]
pub struct Fmap {
rc: usize,
flags: MapFlags,
last_page_tail: u16,
version: u64,
}
impl Fmap {
pub unsafe fn new<D: Disk>(
node_ptr: TreePtr<Node>,
flags: MapFlags,
unaligned_size: usize,
offset: u64,
base: *mut u8,
tx: &mut Transaction<D>,
) -> Result<Self> {
// Memory provided to fmap must be page aligned and sized
let aligned_size = unaligned_size.next_multiple_of(syscall::PAGE_SIZE);
let address = base.add(offset as usize);
//println!("ADDR {:p} {:p}", base, address);
// Read buffer from disk
let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
let buf = slice::from_raw_parts_mut(address, unaligned_size);
let count = match tx.read_node(node_ptr, offset, buf, atime.as_secs(), atime.subsec_nanos())
{
Ok(ok) => ok,
Err(err) => {
let _ = libredox::call::munmap(address.cast(), aligned_size);
return Err(err);
}
};
// Make sure remaining data is zeroed
buf[count..].fill(0_u8);
Ok(Self {
rc: 1,
flags,
last_page_tail: (unaligned_size % PAGE_SIZE) as u16,
version: 0,
})
}
pub unsafe fn sync<D: Disk>(
&mut self,
node_ptr: TreePtr<Node>,
base: *mut u8,
offset: u64,
size: usize,
tx: &mut Transaction<D>,
) -> Result<()> {
if self.flags & PROT_WRITE == PROT_WRITE {
let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
tx.write_node(
node_ptr,
offset,
unsafe { core::slice::from_raw_parts(base.add(offset as usize), size) },
mtime.as_secs(),
mtime.subsec_nanos(),
)?;
}
Ok(())
}
}
pub struct FileResource {
path: String,
parent_ptr_opt: Option<TreePtr<Node>>,
node_ptr: TreePtr<Node>,
flags: usize,
uid: u32,
}
#[derive(Debug)]
pub struct FileMmapInfo {
base: *mut u8,
size: usize,
pub ranges: RangeTree<Fmap>,
pub open_fds: usize,
pub version: u64,
}
impl FileMmapInfo {
pub fn new() -> Self {
Self {
base: core::ptr::null_mut(),
size: 0,
ranges: RangeTree::new(),
open_fds: 0,
version: 0,
}
}
pub fn in_use(&self) -> bool {
self.open_fds > 0 || self.ranges.iter().any(|(_, fmap)| fmap.rc > 0)
}
pub fn stale(&self) -> bool {
// TODO: should this be any?
// TODO: stale by duration/memory pressure
self.ranges
.iter()
.all(|(_, fmap)| fmap.version != self.version)
}
}
impl Drop for FileMmapInfo {
fn drop(&mut self) {
if self.in_use() {
log::error!("FileMmapInfo dropped while in use");
}
}
}
impl FileResource {
pub fn new(
path: String,
parent_ptr_opt: Option<TreePtr<Node>>,
node_ptr: TreePtr<Node>,
flags: usize,
uid: u32,
) -> FileResource {
FileResource {
path,
parent_ptr_opt,
node_ptr,
flags,
uid,
}
}
}
impl<D: Disk> Resource<D> for FileResource {
fn parent_ptr_opt(&self) -> Option<TreePtr<Node>> {
self.parent_ptr_opt
}
fn node_ptr(&self) -> TreePtr<Node> {
self.node_ptr
}
fn uid(&self) -> u32 {
self.uid
}
fn set_path(&mut self, path: &str) {
self.path = path.to_string();
}
fn read(
&mut self,
fmaps: &mut Fmaps,
buf: &mut [u8],
offset: u64,
tx: &mut Transaction<D>,
) -> Result<usize> {
if self.flags & O_ACCMODE != O_RDWR && self.flags & O_ACCMODE != O_RDONLY {
return Err(Error::new(EBADF));
}
if let Some(fmap_info) = fmaps.get_mut(&self.node_ptr.id()) {
if !fmap_info.base.is_null() {
let requested_end = offset + buf.len() as u64;
let mut next_offset = offset;
let mut cacheable = true;
for (range, fmap) in fmap_info.ranges.conflicts(offset..requested_end) {
if range.start > next_offset || fmap.version != fmap_info.version {
// there's a hole
cacheable = false;
break;
}
next_offset = next_offset.max(range.end);
}
if cacheable && next_offset >= requested_end {
unsafe {
core::ptr::copy_nonoverlapping(
fmap_info.base.add(offset as usize),
buf.as_mut_ptr(),
buf.len(),
);
}
return Ok(buf.len());
}
}
}
let atime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
tx.read_node(
self.node_ptr,
offset,
buf,
atime.as_secs(),
atime.subsec_nanos(),
)
}
fn write(
&mut self,
fmaps: &mut Fmaps,
buf: &[u8],
offset: u64,
tx: &mut Transaction<D>,
) -> Result<usize> {
if self.flags & O_ACCMODE != O_RDWR && self.flags & O_ACCMODE != O_WRONLY {
return Err(Error::new(EBADF));
}
let effective_offset = if self.flags & O_APPEND == O_APPEND {
let node = tx.read_tree(self.node_ptr)?;
node.data().size()
} else {
offset
};
if let Some(fmap_info) = fmaps.get_mut(&self.node_ptr.id()) {
fmap_info.version += 1;
}
let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
tx.write_node(
self.node_ptr,
effective_offset,
buf,
mtime.as_secs(),
mtime.subsec_nanos(),
)
}
fn fsize(&mut self, tx: &mut Transaction<D>) -> Result<u64> {
let node = tx.read_tree(self.node_ptr)?;
Ok(node.data().size())
}
fn fmap(
&mut self,
fmaps: &mut Fmaps,
flags: MapFlags,
unaligned_size: usize,
offset: u64,
tx: &mut Transaction<D>,
) -> Result<usize> {
//dbg!(&self.fmaps);
let accmode = self.flags & O_ACCMODE;
if flags.contains(PROT_READ) && !(accmode == O_RDWR || accmode == O_RDONLY) {
return Err(Error::new(EBADF));
}
if flags.contains(PROT_WRITE) && !(accmode == O_RDWR || accmode == O_WRONLY) {
return Err(Error::new(EBADF));
}
let aligned_size = unaligned_size.next_multiple_of(PAGE_SIZE);
// TODO: PROT_EXEC? It is however unenforcable without restricting anonymous mmap, since a
// program can always map anonymous RW-, read from a file, then remap as R-E. But it might
// be usable as a hint, prohibiting direct executable mmaps at least.
// TODO: Pass entry directory to Resource trait functions, since the node_ptr can be
// obtained by the caller.
let fmap_info = fmaps
.get_mut(&self.node_ptr.id())
.ok_or(Error::new(EBADFD))?;
if !fmap_info.in_use() {
// Notify filesystem of open
tx.on_open_node(self.node_ptr)?;
}
let new_size = (offset as usize + aligned_size).next_multiple_of(PAGE_SIZE);
if new_size > fmap_info.size {
fmap_info.base = if fmap_info.base.is_null() {
unsafe {
libredox::call::mmap(MmapArgs {
length: new_size,
// PRIVATE/SHARED doesn't matter once the pages are passed in the fmap
// handler.
prot: libredox::flag::PROT_READ | libredox::flag::PROT_WRITE,
flags: libredox::flag::MAP_PRIVATE,
offset: 0,
fd: !0,
addr: core::ptr::null_mut(),
})? as *mut u8
}
} else {
unsafe {
syscall::syscall5(
syscall::SYS_MREMAP,
fmap_info.base as usize,
fmap_info.size,
0,
new_size,
syscall::MremapFlags::empty().bits() | (PROT_READ | PROT_WRITE).bits(),
)? as *mut u8
}
};
fmap_info.size = new_size;
}
let affected_fmaps = fmap_info
.ranges
.remove_and_unused(offset..offset + aligned_size as u64);
for (range, v_opt) in affected_fmaps {
//dbg!(&range);
let range_size = range.end - range.start;
if let Some(mut fmap) = v_opt {
fmap.rc += 1;
fmap.flags |= flags;
fmap.version = fmap_info.version;
//FIXME: Use result?
let _ = fmap_info.ranges.insert(range.start, range_size, fmap);
} else {
let map = unsafe {
Fmap::new(
self.node_ptr,
flags,
range_size as usize,
range.start,
fmap_info.base,
tx,
)?
};
//FIXME: Use result?
//TODO: Save aligned size for unmap
let _ = fmap_info.ranges.insert(range.start, range_size, map);
}
}
//dbg!(&self.fmaps);
Ok(fmap_info.base as usize + offset as usize)
}
fn funmap(
&mut self,
fmaps: &mut Fmaps,
offset: u64,
size: usize,
tx: &mut Transaction<D>,
) -> Result<()> {
let fmap_info = fmaps
.get_mut(&self.node_ptr.id())
.ok_or(Error::new(EBADFD))?;
//dbg!(&self.fmaps);
//dbg!(self.fmaps.conflicts(offset..offset + size as u64).collect::<Vec<_>>());
#[allow(unused_mut)]
let mut affected_fmaps = fmap_info.ranges.remove(offset..offset + size as u64);
for (range, mut fmap) in affected_fmaps {
fmap.rc = fmap.rc.checked_sub(1).unwrap();
//log::info!("SYNCING {}..{}", range.start, range.end);
unsafe {
fmap.sync(
self.node_ptr,
fmap_info.base,
range.start,
(range.end - range.start) as usize,
tx,
)?;
}
//FIXME: Use result?
let _ = fmap_info
.ranges
.insert(range.start, range.end - range.start, fmap);
}
//dbg!(&self.fmaps);
// Allow release of node if not in use anymore
if !fmap_info.in_use() {
// Notify filesystem of close
tx.on_close_node(self.node_ptr)?;
// if this fmap version is outdated it's no use
if fmap_info.stale() {
let fmap = fmaps
.remove(&self.node_ptr.id())
.expect("fmap_info must exist");
if let Err(e) = unsafe { libredox::call::munmap(fmap.base as *mut _, fmap.size) } {
log::error!("Munmap error {e}");
}
}
}
Ok(())
}
fn fcntl(&mut self, cmd: usize, arg: usize) -> Result<usize> {
match cmd {
F_GETFL => Ok(self.flags),
F_SETFL => {
self.flags = (self.flags & O_ACCMODE) | (arg & !O_ACCMODE);
Ok(0)
}
_ => Err(Error::new(EINVAL)),
}
}
fn path(&self) -> &str {
&self.path
}
fn sync(&mut self, fmaps: &mut Fmaps, tx: &mut Transaction<D>) -> Result<()> {
if let Some(fmap_info) = fmaps.get_mut(&self.node_ptr.id()) {
for (range, fmap) in fmap_info.ranges.iter_mut() {
unsafe {
fmap.sync(
self.node_ptr,
fmap_info.base,
range.start,
(range.end - range.start) as usize,
tx,
)?;
}
}
}
Ok(())
}
fn truncate(&mut self, len: u64, tx: &mut Transaction<D>) -> Result<()> {
if self.flags & O_ACCMODE == O_RDWR || self.flags & O_ACCMODE == O_WRONLY {
let mtime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
tx.truncate_node(self.node_ptr, len, mtime.as_secs(), mtime.subsec_nanos())?;
Ok(())
} else {
Err(Error::new(EBADF))
}
}
fn utimens(&mut self, times: &[TimeSpec], tx: &mut Transaction<D>) -> Result<()> {
let mut node = tx.read_tree(self.node_ptr)?;
if node.data().uid() == self.uid || self.uid == 0 {
if let &[atime, mtime] = times {
let mut node_changed = false;
let old_mtime = node.data().mtime();
let new_mtime = (mtime.tv_sec as u64, mtime.tv_nsec as u32);
if old_mtime != new_mtime {
node.data_mut().set_mtime(new_mtime.0, new_mtime.1);
node_changed = true;
}
let old_atime = node.data().atime();
let new_atime = (atime.tv_sec as u64, atime.tv_nsec as u32);
if old_atime != new_atime {
node.data_mut().set_atime(new_atime.0, new_atime.1);
node_changed = true;
}
if node_changed {
tx.sync_tree(node)?;
}
}
Ok(())
} else {
Err(Error::new(EPERM))
}
}
fn getdents<'buf>(
&mut self,
_buf: DirentBuf<&'buf mut [u8]>,
_opaque_offset: u64,
_tx: &mut Transaction<D>,
) -> Result<DirentBuf<&'buf mut [u8]>> {
Err(Error::new(ENOTDIR))
}
}
impl Drop for FileResource {
fn drop(&mut self) {
/*
if !self.fmaps.is_empty() {
eprintln!(
"redoxfs: file {} still has {} fmaps!",
self.path,
self.fmaps.len()
);
}
*/
}
}
impl range_tree::Value for Fmap {
type K = u64;
fn try_merge_forward(self, other: &Self) -> core::result::Result<Self, Self> {
if self.rc == other.rc && self.flags == other.flags && self.last_page_tail == 0 {
Ok(self)
} else {
Err(self)
}
}
fn try_merge_backwards(self, other: &Self) -> core::result::Result<Self, Self> {
if self.rc == other.rc && self.flags == other.flags && other.last_page_tail == 0 {
Ok(self)
} else {
Err(self)
}
}
#[allow(unused_variables)]
fn split(
self,
prev_range: Option<core::ops::Range<Self::K>>,
range: core::ops::Range<Self::K>,
next_range: Option<core::ops::Range<Self::K>>,
) -> (Option<Self>, Self, Option<Self>) {
(
prev_range.map(|_range| Fmap {
rc: self.rc,
flags: self.flags,
last_page_tail: 0,
version: self.version,
}),
Fmap {
rc: self.rc,
flags: self.flags,
last_page_tail: if next_range.is_none() {
self.last_page_tail
} else {
0
},
version: self.version,
},
next_range.map(|_range| Fmap {
rc: self.rc,
flags: self.flags,
last_page_tail: self.last_page_tail,
version: self.version,
}),
)
}
}
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
use std::{io, path::Path};
use crate::{filesystem, Disk};
pub fn mount<D, P, T, F>(
mut _filesystem: filesystem::FileSystem<D>,
_mountpoint: P,
_callback: F,
) -> io::Result<T>
where
D: Disk,
P: AsRef<Path>,
F: FnOnce(&Path) -> T,
{
Err(io::Error::new(
io::ErrorKind::Unsupported,
"FUSE mount feature is disabled",
))
}
+584
View File
@@ -0,0 +1,584 @@
use core::{fmt, mem, ops, slice};
use endian_num::Le;
use crate::{BlockLevel, BlockList, BlockPtr, BlockTrait, RecordRaw, BLOCK_SIZE, RECORD_LEVEL};
bitflags::bitflags! {
pub struct NodeFlags: u32 {
const INLINE_DATA = 0x1;
}
}
/// An index into a [`Node`]'s block table.
pub enum NodeLevel {
L0(usize),
L1(usize, usize),
L2(usize, usize, usize),
L3(usize, usize, usize, usize),
L4(usize, usize, usize, usize, usize),
}
impl NodeLevel {
// Warning: this uses constant record offsets, make sure to sync with Node
/// Return the [`NodeLevel`] of the record with the given index.
/// - the first 128 are level 0,
/// - the next 64*256 are level 1,
/// - ...and so on.
pub fn new(mut record_offset: u64) -> Option<Self> {
// 1 << 8 = 256, this is the number of entries in a BlockList
const SHIFT: u64 = 8;
const NUM: u64 = 1 << SHIFT;
const MASK: u64 = NUM - 1;
const L0: u64 = 128;
if record_offset < L0 {
return Some(Self::L0((record_offset & MASK) as usize));
} else {
record_offset -= L0;
}
const L1: u64 = 64 * NUM;
if record_offset < L1 {
return Some(Self::L1(
((record_offset >> SHIFT) & MASK) as usize,
(record_offset & MASK) as usize,
));
} else {
record_offset -= L1;
}
const L2: u64 = 32 * NUM * NUM;
if record_offset < L2 {
return Some(Self::L2(
((record_offset >> (2 * SHIFT)) & MASK) as usize,
((record_offset >> SHIFT) & MASK) as usize,
(record_offset & MASK) as usize,
));
} else {
record_offset -= L2;
}
const L3: u64 = 16 * NUM * NUM * NUM;
if record_offset < L3 {
return Some(Self::L3(
((record_offset >> (3 * SHIFT)) & MASK) as usize,
((record_offset >> (2 * SHIFT)) & MASK) as usize,
((record_offset >> SHIFT) & MASK) as usize,
(record_offset & MASK) as usize,
));
} else {
record_offset -= L3;
}
const L4: u64 = 12 * NUM * NUM * NUM * NUM;
if record_offset < L4 {
Some(Self::L4(
((record_offset >> (4 * SHIFT)) & MASK) as usize,
((record_offset >> (3 * SHIFT)) & MASK) as usize,
((record_offset >> (2 * SHIFT)) & MASK) as usize,
((record_offset >> SHIFT) & MASK) as usize,
(record_offset & MASK) as usize,
))
} else {
None
}
}
}
type BlockListL1 = BlockList<RecordRaw>;
type BlockListL2 = BlockList<BlockListL1>;
type BlockListL3 = BlockList<BlockListL2>;
type BlockListL4 = BlockList<BlockListL3>;
#[repr(C, packed)]
pub struct NodeLevelData {
/// The first 128 blocks of this file.
///
/// Total size: 128 * RECORD_SIZE (16 MiB, 128 KiB each)
pub level0: [BlockPtr<RecordRaw>; 128],
/// The next 64 * 256 blocks of this file,
/// stored behind 64 level one tables.
///
/// Total size: 64 * 256 * RECORD_SIZE (2 GiB, 32 MiB each)
pub level1: [BlockPtr<BlockListL1>; 64],
/// The next 32 * 256 * 256 blocks of this file,
/// stored behind 32 level two tables.
/// Each level two table points to 256 level one tables.
///
/// Total size: 32 * 256 * 256 * RECORD_SIZE (256 GiB, 8 GiB each)
pub level2: [BlockPtr<BlockListL2>; 32],
/// The next 16 * 256 * 256 * 256 blocks of this file,
/// stored behind 16 level three tables.
///
/// Total size: 16 * 256 * 256 * 256 * RECORD_SIZE (32 TiB, 2 TiB each)
pub level3: [BlockPtr<BlockListL3>; 16],
/// The next 8 * 256 * 256 * 256 * 256 blocks of this file,
/// stored behind 8 level four tables.
///
/// Total size: 8 * 256 * 256 * 256 * 256 * RECORD_SIZE (4 PiB, 512 TiB each)
pub level4: [BlockPtr<BlockListL4>; 8],
}
impl Default for NodeLevelData {
fn default() -> Self {
Self {
level0: [BlockPtr::default(); 128],
level1: [BlockPtr::default(); 64],
level2: [BlockPtr::default(); 32],
level3: [BlockPtr::default(); 16],
level4: [BlockPtr::default(); 8],
}
}
}
/// A file/folder node
#[repr(C, packed)]
pub struct Node {
/// This node's type & permissions.
/// - four most significant bits are the node's type
/// - next four bits are permissions for the node's user
/// - next four bits are permissions for the node's group
/// - four least significant bits are permissions for everyone else
pub mode: Le<u16>,
/// The uid that owns this file
pub uid: Le<u32>,
/// The gid that owns this file
pub gid: Le<u32>,
/// The number of hard links to this file
pub links: Le<u32>,
/// The length of this file, in bytes
pub size: Le<u64>,
/// The disk usage of this file, in blocks
pub blocks: Le<u64>,
/// Creation time
pub ctime: Le<u64>,
pub ctime_nsec: Le<u32>,
/// Modification time
pub mtime: Le<u64>,
pub mtime_nsec: Le<u32>,
/// Access time
pub atime: Le<u64>,
pub atime_nsec: Le<u32>,
/// Record level
pub record_level: Le<u32>,
/// Flags
pub flags: Le<u32>,
/// Padding
pub padding: [u8; BLOCK_SIZE as usize - 4042],
/// Level data, should not be used directly so inline data can be supported
pub(crate) level_data: NodeLevelData,
}
unsafe impl BlockTrait for Node {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self::default())
} else {
None
}
}
}
impl Default for Node {
fn default() -> Self {
Self {
mode: 0.into(),
uid: 0.into(),
gid: 0.into(),
links: 0.into(),
size: 0.into(),
// This node counts as a block
blocks: 1.into(),
ctime: 0.into(),
ctime_nsec: 0.into(),
mtime: 0.into(),
mtime_nsec: 0.into(),
atime: 0.into(),
atime_nsec: 0.into(),
record_level: 0.into(),
flags: 0.into(),
padding: [0; BLOCK_SIZE as usize - 4042],
level_data: NodeLevelData::default(),
}
}
}
impl Node {
pub const MODE_TYPE: u16 = 0xF000;
pub const MODE_FILE: u16 = 0x8000;
pub const MODE_DIR: u16 = 0x4000;
pub const MODE_SYMLINK: u16 = 0xA000;
pub const MODE_SOCK: u16 = 0xC000;
/// Mask for node permission bits
pub const MODE_PERM: u16 = 0x0FFF;
pub const MODE_EXEC: u16 = 0o1;
pub const MODE_WRITE: u16 = 0o2;
pub const MODE_READ: u16 = 0o4;
/// Create a new, empty node with the given metadata
pub fn new(mode: u16, uid: u32, gid: u32, ctime: u64, ctime_nsec: u32) -> Self {
Self {
mode: mode.into(),
uid: uid.into(),
gid: gid.into(),
links: 0.into(),
ctime: ctime.into(),
ctime_nsec: ctime_nsec.into(),
mtime: ctime.into(),
mtime_nsec: ctime_nsec.into(),
atime: ctime.into(),
atime_nsec: ctime_nsec.into(),
record_level: if mode & Self::MODE_TYPE == Self::MODE_FILE {
// Files take on record level
RECORD_LEVEL as u32
} else {
// Folders do not
0
}
.into(),
flags: if mode & Self::MODE_TYPE == Self::MODE_DIR {
// Directories must not use inline data (until h-tree supports it)
NodeFlags::empty()
} else {
NodeFlags::INLINE_DATA
}
.bits()
.into(),
..Default::default()
}
}
/// This node's type & permissions.
/// - four most significant bits are the node's type
/// - next four bits are permissions for the node's user
/// - next four bits are permissions for the node's group
/// - four least significant bits are permissions for everyone else
pub fn mode(&self) -> u16 {
self.mode.to_ne()
}
/// The uid that owns this file
pub fn uid(&self) -> u32 {
self.uid.to_ne()
}
/// The gid that owns this file
pub fn gid(&self) -> u32 {
self.gid.to_ne()
}
/// The number of links to this file
/// (directory entries, symlinks, etc)
pub fn links(&self) -> u32 {
self.links.to_ne()
}
/// The length of this file, in bytes.
pub fn size(&self) -> u64 {
self.size.to_ne()
}
/// The disk usage of this file, in blocks.
pub fn blocks(&self) -> u64 {
self.blocks.to_ne()
}
pub fn ctime(&self) -> (u64, u32) {
(self.ctime.to_ne(), self.ctime_nsec.to_ne())
}
pub fn mtime(&self) -> (u64, u32) {
(self.mtime.to_ne(), self.mtime_nsec.to_ne())
}
pub fn atime(&self) -> (u64, u32) {
(self.atime.to_ne(), self.atime_nsec.to_ne())
}
pub fn record_level(&self) -> BlockLevel {
BlockLevel(self.record_level.to_ne() as usize)
}
pub fn flags(&self) -> NodeFlags {
NodeFlags::from_bits_retain(self.flags.to_ne())
}
pub fn set_mode(&mut self, mode: u16) {
self.mode = mode.into();
}
pub fn set_uid(&mut self, uid: u32) {
self.uid = uid.into();
}
pub fn set_gid(&mut self, gid: u32) {
self.gid = gid.into();
}
pub fn set_links(&mut self, links: u32) {
self.links = links.into();
}
pub fn set_size(&mut self, size: u64) {
self.size = size.into();
}
pub fn set_blocks(&mut self, blocks: u64) {
self.blocks = blocks.into();
}
pub fn set_mtime(&mut self, mtime: u64, mtime_nsec: u32) {
self.mtime = mtime.into();
self.mtime_nsec = mtime_nsec.into();
}
pub fn set_atime(&mut self, atime: u64, atime_nsec: u32) {
self.atime = atime.into();
self.atime_nsec = atime_nsec.into();
}
pub fn set_flags(&mut self, flags: NodeFlags) {
self.flags = flags.bits().into();
}
pub fn has_inline_data(&self) -> bool {
self.flags().contains(NodeFlags::INLINE_DATA)
}
pub fn inline_data(&self) -> Option<&[u8]> {
if self.has_inline_data() {
Some(unsafe {
slice::from_raw_parts(
&self.level_data as *const NodeLevelData as *const u8,
mem::size_of::<NodeLevelData>(),
)
})
} else {
None
}
}
pub fn inline_data_mut(&mut self) -> Option<&mut [u8]> {
if self.has_inline_data() {
Some(unsafe {
slice::from_raw_parts_mut(
&mut self.level_data as *mut NodeLevelData as *mut u8,
mem::size_of::<NodeLevelData>(),
)
})
} else {
None
}
}
pub fn level_data(&self) -> Option<&NodeLevelData> {
if !self.has_inline_data() {
Some(&self.level_data)
} else {
None
}
}
pub fn level_data_mut(&mut self) -> Option<&mut NodeLevelData> {
if !self.has_inline_data() {
Some(&mut self.level_data)
} else {
None
}
}
pub fn is_dir(&self) -> bool {
self.mode() & Self::MODE_TYPE == Self::MODE_DIR
}
pub fn is_file(&self) -> bool {
self.mode() & Self::MODE_TYPE == Self::MODE_FILE
}
pub fn is_symlink(&self) -> bool {
self.mode() & Self::MODE_TYPE == Self::MODE_SYMLINK
}
pub fn is_sock(&self) -> bool {
self.mode() & Self::MODE_SOCK == Self::MODE_SOCK
}
/// Tests if UID is the owner of that file, only true when uid=0 or when the UID stored in metadata is equal to the UID you supply
pub fn owner(&self, uid: u32) -> bool {
uid == 0 || self.uid() == uid
}
/// Tests if the current user has enough permissions to view the file, op is the operation,
/// like read and write, these modes are MODE_EXEC, MODE_READ, and MODE_WRITE
pub fn permission(&self, uid: u32, gid: u32, op: u16) -> bool {
let mut perm = self.mode() & 0o7;
if self.uid() == uid {
// If self.mode is 101100110, >> 6 would be 000000101
// 0o7 is octal for 111, or, when expanded to 9 digits is 000000111
perm |= (self.mode() >> 6) & 0o7;
// Since we erased the GID and OTHER bits when >>6'ing, |= will keep those bits in place.
}
if self.gid() == gid || gid == 0 {
perm |= (self.mode() >> 3) & 0o7;
}
if uid == 0 {
//set the `other` bits to 111
perm |= 0o7;
}
perm & op == op
}
}
impl fmt::Debug for Node {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mode = self.mode;
let uid = self.uid;
let gid = self.gid;
let links = self.links;
let size = self.size;
let blocks = self.blocks;
let ctime = self.ctime;
let ctime_nsec = self.ctime_nsec;
let mtime = self.mtime;
let mtime_nsec = self.mtime_nsec;
let atime = self.atime;
let atime_nsec = self.atime_nsec;
f.debug_struct("Node")
.field("mode", &mode)
.field("uid", &uid)
.field("gid", &gid)
.field("links", &links)
.field("size", &size)
.field("blocks", &blocks)
.field("ctime", &ctime)
.field("ctime_nsec", &ctime_nsec)
.field("mtime", &mtime)
.field("mtime_nsec", &mtime_nsec)
.field("atime", &atime)
.field("atime_nsec", &atime_nsec)
//TODO: level0/1/2/3
.finish()
}
}
impl ops::Deref for Node {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(self as *const Node as *const u8, mem::size_of::<Node>())
as &[u8]
}
}
}
impl ops::DerefMut for Node {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(self as *mut Node as *mut u8, mem::size_of::<Node>())
as &mut [u8]
}
}
}
#[test]
fn node_size_test() {
assert_eq!(mem::size_of::<Node>(), crate::BLOCK_SIZE as usize);
}
#[test]
fn node_inline_data_test() {
let mut node = Node::default();
assert!(!node.has_inline_data());
assert!(node.inline_data().is_none());
assert!(node.inline_data_mut().is_none());
assert!(node.level_data().is_some());
assert!(node.level_data_mut().is_some());
node.set_flags(NodeFlags::INLINE_DATA);
assert!(node.has_inline_data());
assert!(node.level_data().is_none());
assert!(node.level_data_mut().is_none());
let node_addr = &node as *const Node as usize;
let meta_size = 128;
{
let inline_data = node.inline_data().unwrap();
let inline_data_addr = inline_data.as_ptr() as usize;
assert_eq!(node_addr + meta_size, inline_data_addr);
assert_eq!(inline_data.len(), (crate::BLOCK_SIZE as usize) - meta_size);
}
{
let inline_data = node.inline_data_mut().unwrap();
let inline_data_addr = inline_data.as_ptr() as usize;
assert_eq!(node_addr + meta_size, inline_data_addr);
assert_eq!(inline_data.len(), (crate::BLOCK_SIZE as usize) - meta_size);
}
}
#[cfg(kani)]
#[kani::proof]
fn check_node_level() {
let offset = kani::any();
NodeLevel::new(offset);
}
#[cfg(kani)]
#[kani::proof]
fn check_node_perms() {
let mode = 0o750;
let uid = kani::any();
let gid = kani::any();
let ctime = kani::any();
let ctime_nsec = kani::any();
let node = Node::new(mode, uid, gid, ctime, ctime_nsec);
let root_uid = 0;
let root_gid = 0;
let other_uid = kani::any();
kani::assume(other_uid != uid);
kani::assume(other_uid != root_uid);
let other_gid = kani::any();
kani::assume(other_gid != gid);
kani::assume(other_gid != root_gid);
assert!(node.owner(uid));
assert!(node.permission(uid, gid, 0o7));
assert!(node.permission(uid, gid, 0o5));
assert!(node.permission(uid, other_gid, 0o7));
assert!(node.permission(uid, other_gid, 0o5));
assert!(!node.permission(other_uid, gid, 0o7));
assert!(node.permission(other_uid, gid, 0o5));
assert!(node.owner(root_uid));
assert!(node.permission(root_uid, root_gid, 0o7));
assert!(node.permission(root_uid, root_gid, 0o5));
assert!(node.permission(root_uid, other_gid, 0o7));
assert!(node.permission(root_uid, other_gid, 0o5));
assert!(!node.permission(other_uid, root_gid, 0o7));
assert!(node.permission(other_uid, root_gid, 0o5));
assert!(!node.owner(other_uid));
assert!(!node.permission(other_uid, other_gid, 0o7));
assert!(!node.permission(other_uid, other_gid, 0o5));
}
-67
View File
@@ -1,67 +0,0 @@
pub const SYS_CLASS: usize = 0xF000_0000;
pub const SYS_CLASS_PATH: usize = 0x1000_0000;
pub const SYS_CLASS_FILE: usize = 0x2000_0000;
pub const SYS_ARG: usize = 0x0F00_0000;
pub const SYS_ARG_SLICE: usize = 0x0100_0000;
pub const SYS_ARG_MSLICE: usize = 0x0200_0000;
pub const SYS_ARG_PATH: usize = 0x0300_0000;
pub const SYS_RET: usize = 0x00F0_0000;
pub const SYS_RET_FILE: usize = 0x0010_0000;
/// Upstream 0.9.0 `openat_into` (caller supplies target fd number).
pub const SYS_OPENAT_INTO: usize = SYS_CLASS_PATH | SYS_RET_FILE | 987;
/// Legacy Red Bear `openat` (kernel allocates a new fd).
pub const SYS_OPENAT: usize = SYS_CLASS_PATH | SYS_RET_FILE | 7;
/// Red Bear: preserve legacy SYS_OPENAT_WITH_FILTER (upstream 0.9.0 removed it; retained for kernel/userspace ABI).
pub const SYS_OPENAT_WITH_FILTER: usize = SYS_CLASS_PATH | SYS_RET_FILE | 985;
pub const SYS_UNLINKAT: usize = SYS_CLASS_PATH | 263;
/// Red Bear: preserve legacy SYS_UNLINKAT_WITH_FILTER (upstream 0.9.0 removed it; retained for kernel/userspace ABI).
pub const SYS_UNLINKAT_WITH_FILTER: usize = SYS_CLASS_PATH | 986;
pub const SYS_CLOSE: usize = SYS_CLASS_FILE | 6;
/// Upstream 0.9.0 `dup_into` (caller supplies target fd number).
pub const SYS_DUP_INTO: usize = SYS_CLASS_FILE | SYS_RET_FILE | 988;
/// Legacy Red Bear `dup` (kernel allocates a new fd).
pub const SYS_DUP: usize = SYS_CLASS_FILE | SYS_RET_FILE | 41;
pub const SYS_DUP2: usize = SYS_CLASS_FILE | SYS_RET_FILE | 63;
/// Red Bear: preserve legacy SYS_SENDFD (upstream 0.9.0 removed it; retained for kernel/userspace ABI).
pub const SYS_SENDFD: usize = SYS_CLASS_FILE | 34;
pub const SYS_READ: usize = SYS_CLASS_FILE | SYS_ARG_MSLICE | 3;
pub const SYS_READ2: usize = SYS_CLASS_FILE | SYS_ARG_MSLICE | 35;
pub const SYS_WRITE: usize = SYS_CLASS_FILE | SYS_ARG_SLICE | 4;
pub const SYS_WRITE2: usize = SYS_CLASS_FILE | SYS_ARG_SLICE | 45;
pub const SYS_LSEEK: usize = SYS_CLASS_FILE | 19;
pub const SYS_FCHMOD: usize = SYS_CLASS_FILE | 94;
pub const SYS_FCHOWN: usize = SYS_CLASS_FILE | 207;
pub const SYS_FCNTL: usize = SYS_CLASS_FILE | 55;
pub const SYS_FEVENT: usize = SYS_CLASS_FILE | 927;
// SYS_CALL, fd, inout buf ptr, inout buf len, flags, metadata buf ptr, metadata buf len
// TODO: new number for SYS_CALL where flags are sent as 6th argument (using syscall6)
pub const SYS_CALL: usize = SYS_CLASS_FILE | SYS_ARG_SLICE | SYS_ARG_MSLICE | 0xCA11;
pub const SYS_GETDENTS: usize = SYS_CLASS_FILE | 43;
// TODO: Rename FMAP/FUNMAP to MMAP/MUNMAP
pub const SYS_FMAP: usize = SYS_CLASS_FILE | SYS_ARG_SLICE | 900;
// TODO: SYS_FUNMAP should be SYS_CLASS_FILE
pub const SYS_FUNMAP: usize = SYS_CLASS_FILE | 92;
pub const SYS_MREMAP: usize = 155;
pub const SYS_FLINK: usize = SYS_CLASS_FILE | SYS_ARG_PATH | 9;
pub const SYS_FPATH: usize = SYS_CLASS_FILE | SYS_ARG_MSLICE | 928;
pub const SYS_FRENAME: usize = SYS_CLASS_FILE | SYS_ARG_PATH | 38;
pub const SYS_FSTAT: usize = SYS_CLASS_FILE | SYS_ARG_MSLICE | 28;
pub const SYS_FSTATVFS: usize = SYS_CLASS_FILE | SYS_ARG_MSLICE | 100;
pub const SYS_FSYNC: usize = SYS_CLASS_FILE | 118;
pub const SYS_FTRUNCATE: usize = SYS_CLASS_FILE | 93;
pub const SYS_FUTIMENS: usize = SYS_CLASS_FILE | SYS_ARG_SLICE | 320;
pub const SYS_CLOCK_GETTIME: usize = 265;
pub const SYS_FUTEX: usize = 240;
pub const SYS_MPROTECT: usize = 125;
pub const SYS_MKNS: usize = 984;
pub const SYS_NANOSLEEP: usize = 162;
pub const SYS_YIELD: usize = 158;
+41
View File
@@ -0,0 +1,41 @@
use alloc::{vec, vec::Vec};
use core::ops;
use crate::{BlockLevel, BlockTrait, RECORD_LEVEL};
#[derive(Clone)]
pub struct RecordRaw(pub(crate) Vec<u8>);
unsafe impl BlockTrait for RecordRaw {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 <= RECORD_LEVEL {
Some(Self(vec![0; level.bytes() as usize]))
} else {
None
}
}
}
impl ops::Deref for RecordRaw {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.0
}
}
impl ops::DerefMut for RecordRaw {
fn deref_mut(&mut self) -> &mut [u8] {
&mut self.0
}
}
#[test]
fn record_raw_size_test() {
for level_i in 0..RECORD_LEVEL {
let level = BlockLevel(level_i);
assert_eq!(
RecordRaw::empty(level).unwrap().len(),
level.bytes() as usize
);
}
}
-213
View File
@@ -1,213 +0,0 @@
use core::{
mem,
ops::{Deref, DerefMut},
slice,
};
use bitflags::bitflags;
pub struct CallerCtx {
pub pid: usize,
pub uid: u32,
pub gid: u32,
}
pub enum OpenResult {
ThisScheme { number: usize },
OtherScheme { fd: usize },
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct Sqe {
pub opcode: u8,
pub sqe_flags: SqeFlags,
pub _rsvd: u16, // TODO: priority
pub tag: u32,
pub args: [u64; 6],
pub caller: u64,
}
impl Deref for Sqe {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self as *const Sqe as *const u8, mem::size_of::<Sqe>()) }
}
}
impl DerefMut for Sqe {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self as *mut Sqe as *mut u8, mem::size_of::<Sqe>()) }
}
}
bitflags! {
#[derive(Clone, Copy, Debug, Default)]
pub struct SqeFlags: u8 {
// If zero, the message is bidirectional, and the scheme is expected to pass the Ksmsg's
// tag field to the Skmsg. Some opcodes require this flag to be set.
const ONEWAY = 1;
// If this flag is set, index 0 of Sqe's args stores the IDs buffer address,
// and index 1 stores the IDs buffer length.
const MULTIPLE_IDS = 1 << 1;
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct Cqe {
pub flags: u8, // bits 2:0 are CqeOpcode
pub extra_raw: [u8; 3],
pub tag: u32,
pub result: u64,
}
impl Deref for Cqe {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self as *const Cqe as *const u8, mem::size_of::<Cqe>()) }
}
}
impl DerefMut for Cqe {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self as *mut Cqe as *mut u8, mem::size_of::<Cqe>()) }
}
}
bitflags! {
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct NewFdFlags: u8 {
const POSITIONED = 1;
}
}
impl Cqe {
pub fn extra(&self) -> u32 {
u32::from_ne_bytes([self.extra_raw[0], self.extra_raw[1], self.extra_raw[2], 0])
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CqeOpcode {
RespondRegular,
RespondWithFd,
SendFevent, // no tag
ObtainFd,
RespondWithMultipleFds,
/// [`SchemeAsync::on_close`] and [`SchemeSync::on_close`] are only called when the last file
/// descriptor referring to the file description is closed. To implement traditional POSIX
/// advisory file locking, [`CqeOpcode::RespondAndNotifyOnDetach`] is used to notify the scheme
/// by sending a [`RequestKind::OnDetach`] request the next time the file description is
/// "detached" from a file descriptor. Not done by default to avoid unnecessary IPC.
RespondAndNotifyOnDetach,
// TODO: ProvideMmap
}
impl CqeOpcode {
pub fn try_from_raw(raw: u8) -> Option<Self> {
// TODO: Use a library where this match can be automated.
Some(match raw {
0 => Self::RespondRegular,
1 => Self::RespondWithFd,
2 => Self::SendFevent,
3 => Self::ObtainFd,
4 => Self::RespondWithMultipleFds,
5 => Self::RespondAndNotifyOnDetach,
_ => return None,
})
}
}
/// SqeOpcode
#[repr(u8)]
#[non_exhaustive]
#[derive(Clone, Copy, Debug)]
pub enum Opcode {
Close = 3, // fd
Dup = 4, // old fd, buf_ptr, buf_len
Read = 5, // fd, buf_ptr, buf_len, TODO offset, TODO flags, _
Write = 6, // fd, buf_ptr, buf_len, TODO offset, TODO flags)
Fsize = 7, // fd
Fchmod = 8, // fd, new mode
Fchown = 9, // fd, new uid, new gid
Fcntl = 10, // fd, cmd, arg
Fevent = 11, // fd, requested mask
Sendfd = 12,
Fpath = 13, // fd, buf_ptr, buf_len
Frename = 14,
Fstat = 15, // fd, buf_ptr, buf_len
Fstatvfs = 16, // fd, buf_ptr, buf_len
Fsync = 17, // fd
Ftruncate = 18, // fd, new len
Futimens = 19, // fd, times_buf, times_len
MmapPrep = 20,
RequestMmap = 21,
Mremap = 22,
Munmap = 23,
Msync = 24, // TODO
Cancel = 25, // @tag
Getdents = 26,
CloseMsg = 27,
Call = 28,
OpenAt = 29, // fd, buf_ptr, buf_len, flags
Flink = 30,
Recvfd = 31,
UnlinkAt = 32, // fd, path_ptr, path_len (utf8), flags
StdFsCall = 33,
Detach = 34,
}
impl Opcode {
pub fn try_from_raw(raw: u8) -> Option<Self> {
use Opcode::*;
// TODO: Use a library where this match can be automated.
Some(match raw {
3 => Close,
4 => Dup,
5 => Read,
6 => Write,
7 => Fsize,
8 => Fchmod,
9 => Fchown,
10 => Fcntl,
11 => Fevent,
12 => Sendfd,
13 => Fpath,
14 => Frename,
15 => Fstat,
16 => Fstatvfs,
17 => Fsync,
18 => Ftruncate,
19 => Futimens,
20 => MmapPrep,
21 => RequestMmap,
22 => Mremap,
23 => Munmap,
24 => Msync,
25 => Cancel,
26 => Getdents,
27 => CloseMsg,
28 => Call,
29 => OpenAt,
30 => Flink,
31 => Recvfd,
32 => UnlinkAt,
33 => StdFsCall,
34 => Detach,
_ => return None,
})
}
}
-340
View File
@@ -1,340 +0,0 @@
use core::sync::atomic::{AtomicUsize, Ordering};
/// Signal runtime struct for the entire process
#[derive(Debug)]
#[repr(C, align(4096))]
pub struct SigProcControl {
pub pending: AtomicU64,
pub actions: [RawAction; 64],
pub sender_infos: [AtomicU64; 32],
//pub queue: [RealtimeSig; 32], TODO
// qhead, qtail TODO
}
/*#[derive(Debug)]
#[repr(transparent)]
pub struct RealtimeSig {
pub arg: NonatomicUsize,
}*/
#[derive(Debug, Default)]
#[repr(C, align(16))]
pub struct RawAction {
/// Only two MSBs are interesting for the kernel. If bit 63 is set, signal is ignored. If bit
/// 62 is set and the signal is SIGTSTP/SIGTTIN/SIGTTOU, it's equivalent to the action of
/// Stop.
pub first: AtomicU64,
/// Completely ignored by the kernel, but exists so userspace can (when 16-byte atomics exist)
/// atomically set both the handler, sigaction flags, and sigaction mask.
pub user_data: AtomicU64,
}
/// Signal runtime struct for a thread
#[derive(Debug, Default)]
#[repr(C)]
pub struct Sigcontrol {
// composed of [lo "pending" | lo "unmasked", hi "pending" | hi "unmasked"]
pub word: [AtomicU64; 2],
// lo = sender pid, hi = sender ruid
pub sender_infos: [AtomicU64; 32],
pub control_flags: SigatomicUsize,
pub saved_ip: NonatomicUsize, // rip/eip/pc
pub saved_archdep_reg: NonatomicUsize, // rflags(x64)/eflags(x86)/x0(aarch64)/t0(riscv64)
}
#[derive(Clone, Copy, Debug)]
pub struct SenderInfo {
pub pid: u32,
pub ruid: u32,
}
impl SenderInfo {
#[inline]
pub fn raw(self) -> u64 {
u64::from(self.pid) | (u64::from(self.ruid) << 32)
}
#[inline]
pub const fn from_raw(raw: u64) -> Self {
Self {
pid: raw as u32,
ruid: (raw >> 32) as u32,
}
}
}
impl Sigcontrol {
pub fn currently_pending_unblocked(&self, proc: &SigProcControl) -> u64 {
let proc_pending = proc.pending.load(Ordering::Relaxed);
let [w0, w1] = core::array::from_fn(|i| {
let w = self.word[i].load(Ordering::Relaxed);
((w | (proc_pending >> (i * 32))) & 0xffff_ffff) & (w >> 32)
});
//core::sync::atomic::fence(Ordering::Acquire);
w0 | (w1 << 32)
}
pub fn set_allowset(&self, new_allowset: u64) -> u64 {
//core::sync::atomic::fence(Ordering::Release);
let [w0, w1] = self.word.each_ref().map(|w| w.load(Ordering::Relaxed));
let old_a0 = w0 & 0xffff_ffff_0000_0000;
let old_a1 = w1 & 0xffff_ffff_0000_0000;
let new_a0 = (new_allowset & 0xffff_ffff) << 32;
let new_a1 = new_allowset & 0xffff_ffff_0000_0000;
let prev_w0 = self.word[0].fetch_add(new_a0.wrapping_sub(old_a0), Ordering::Relaxed);
let prev_w1 = self.word[0].fetch_add(new_a1.wrapping_sub(old_a1), Ordering::Relaxed);
//core::sync::atomic::fence(Ordering::Acquire);
let up0 = prev_w0 & (prev_w0 >> 32);
let up1 = prev_w1 & (prev_w1 >> 32);
up0 | (up1 << 32)
}
}
#[derive(Debug, Default)]
#[repr(transparent)]
pub struct SigatomicUsize(AtomicUsize);
impl SigatomicUsize {
#[inline]
pub fn load(&self, ordering: Ordering) -> usize {
let value = self.0.load(Ordering::Relaxed);
if ordering != Ordering::Relaxed {
core::sync::atomic::compiler_fence(ordering);
}
value
}
#[inline]
pub fn store(&self, value: usize, ordering: Ordering) {
if ordering != Ordering::Relaxed {
core::sync::atomic::compiler_fence(ordering);
}
self.0.store(value, Ordering::Relaxed);
}
}
#[derive(Debug, Default)]
#[repr(transparent)]
pub struct NonatomicUsize(AtomicUsize);
impl NonatomicUsize {
#[inline]
pub const fn new(a: usize) -> Self {
Self(AtomicUsize::new(a))
}
#[inline]
pub fn get(&self) -> usize {
self.0.load(Ordering::Relaxed)
}
#[inline]
pub fn set(&self, value: usize) {
self.0.store(value, Ordering::Relaxed);
}
}
pub fn sig_bit(sig: usize) -> u64 {
1 << (sig - 1)
}
// TODO: Move to redox_rt?
impl SigProcControl {
/// Checks if `sig` should be ignored based on the current action flags.
///
/// * `sig` - The signal to check (e.g. `SIGCHLD`).
///
/// * `stop_or_continue` - Whether the signal is generated because a child
/// process stopped (`SIGSTOP`, `SIGTSTP`) or continued (`SIGCONT`). If
/// `true` and `sig` is `SIGCHLD`, the signal shall not be delivered if the
/// `SA_NOCLDSTOP` flag is set for `SIGCHLD`.
pub fn signal_will_ign(&self, sig: usize, stop_or_continue: bool) -> bool {
let flags = self.actions[sig - 1].first.load(Ordering::Relaxed);
let will_ign = flags & (1 << 63) != 0;
let sig_specific = flags & (1 << 62) != 0; // SA_NOCLDSTOP if sig == SIGCHLD
will_ign || (sig == SIGCHLD && stop_or_continue && sig_specific)
}
// TODO: Move to redox_rt?
pub fn signal_will_stop(&self, sig: usize) -> bool {
use crate::flag::*;
matches!(sig, SIGTSTP | SIGTTIN | SIGTTOU)
&& self.actions[sig - 1].first.load(Ordering::Relaxed) & (1 << 62) != 0
}
}
#[cfg(not(target_arch = "x86"))]
pub use core::sync::atomic::AtomicU64;
use crate::SIGCHLD;
#[cfg(target_arch = "x86")]
pub use self::atomic::AtomicU64;
#[cfg(target_arch = "x86")]
mod atomic {
use core::{cell::UnsafeCell, sync::atomic::Ordering};
#[derive(Debug, Default)]
pub struct AtomicU64(UnsafeCell<u64>);
unsafe impl Send for AtomicU64 {}
unsafe impl Sync for AtomicU64 {}
impl AtomicU64 {
pub const fn new(inner: u64) -> Self {
Self(UnsafeCell::new(inner))
}
pub fn compare_exchange(
&self,
old: u64,
new: u64,
_success: Ordering,
_failure: Ordering,
) -> Result<u64, u64> {
let old_hi = (old >> 32) as u32;
let old_lo = old as u32;
let new_hi = (new >> 32) as u32;
let new_lo = new as u32;
let mut out_hi;
let mut out_lo;
unsafe {
core::arch::asm!("lock cmpxchg8b [{}]", in(reg) self.0.get(), inout("edx") old_hi => out_hi, inout("eax") old_lo => out_lo, in("ecx") new_hi, in("ebx") new_lo);
}
if old_hi == out_hi && old_lo == out_lo {
Ok(old)
} else {
Err(u64::from(out_lo) | (u64::from(out_hi) << 32))
}
}
pub fn load(&self, ordering: Ordering) -> u64 {
match self.compare_exchange(0, 0, ordering, ordering) {
Ok(new) => new,
Err(new) => new,
}
}
pub fn store(&self, new: u64, ordering: Ordering) {
let mut old = 0;
loop {
match self.compare_exchange(old, new, ordering, Ordering::Relaxed) {
Ok(_) => break,
Err(new) => {
old = new;
core::hint::spin_loop();
}
}
}
}
pub fn fetch_update(
&self,
set_order: Ordering,
fetch_order: Ordering,
mut f: impl FnMut(u64) -> Option<u64>,
) -> Result<u64, u64> {
let mut old = self.load(fetch_order);
loop {
let new = f(old).ok_or(old)?;
match self.compare_exchange(old, new, set_order, Ordering::Relaxed) {
Ok(_) => return Ok(new),
Err(changed) => {
old = changed;
core::hint::spin_loop();
}
}
}
}
pub fn fetch_or(&self, bits: u64, order: Ordering) -> u64 {
self.fetch_update(order, Ordering::Relaxed, |b| Some(b | bits))
.unwrap()
}
pub fn fetch_and(&self, bits: u64, order: Ordering) -> u64 {
self.fetch_update(order, Ordering::Relaxed, |b| Some(b & bits))
.unwrap()
}
pub fn fetch_add(&self, term: u64, order: Ordering) -> u64 {
self.fetch_update(order, Ordering::Relaxed, |b| Some(b.wrapping_add(term)))
.unwrap()
}
}
}
#[cfg(test)]
mod tests {
use std::sync::{
atomic::{AtomicU64, Ordering},
Arc,
};
#[cfg(not(loom))]
use std::{sync::Mutex, thread};
#[cfg(not(loom))]
fn model(f: impl FnOnce()) {
f()
}
#[cfg(loom)]
use loom::{model, sync::Mutex, thread};
use crate::{RawAction, SigProcControl, Sigcontrol};
struct FakeThread {
ctl: Sigcontrol,
pctl: SigProcControl,
ctxt: Mutex<()>,
}
impl Default for FakeThread {
fn default() -> Self {
Self {
ctl: Sigcontrol::default(),
pctl: SigProcControl {
pending: AtomicU64::new(0),
actions: core::array::from_fn(|_| RawAction::default()),
sender_infos: Default::default(),
},
ctxt: Default::default(),
}
}
}
#[test]
fn singlethread_mask() {
model(|| {
let fake_thread = Arc::new(FakeThread::default());
let thread = {
let fake_thread = Arc::clone(&fake_thread);
thread::spawn(move || {
fake_thread.ctl.set_allowset(!0);
{
let _g = fake_thread.ctxt.lock();
if fake_thread
.ctl
.currently_pending_unblocked(&fake_thread.pctl)
== 0
{
drop(_g);
thread::park();
}
}
})
};
for sig in 1..=64 {
let _g = fake_thread.ctxt.lock();
let idx = sig - 1;
let bit = 1 << (idx % 32);
fake_thread.ctl.word[idx / 32].fetch_or(bit, Ordering::Relaxed);
let w = fake_thread.ctl.word[idx / 32].load(Ordering::Relaxed);
if w & (w >> 32) != 0 {
thread.thread().unpark();
}
}
thread.join().unwrap();
});
}
}
+1104
View File
File diff suppressed because it is too large Load Diff
+2049
View File
File diff suppressed because it is too large Load Diff
+280
View File
@@ -0,0 +1,280 @@
use core::{marker::PhantomData, mem, ops, slice};
use endian_num::Le;
use crate::{BlockLevel, BlockPtr, BlockRaw, BlockTrait};
// 1 << 8 = 256, this is the number of entries in a TreeList
const TREE_LIST_SHIFT: u32 = 8;
const TREE_LIST_ENTRIES: usize = (1 << TREE_LIST_SHIFT) - 2;
/// A tree with 4 levels
pub type Tree = TreeList<TreeList<TreeList<TreeList<BlockRaw>>>>;
/// A [`TreePtr`] and the contents of the block it references.
#[derive(Clone, Copy, Debug, Default)]
pub struct TreeData<T> {
/// The value of the [`TreePtr`]
id: u32,
// The data
data: T,
}
impl<T> TreeData<T> {
pub fn new(id: u32, data: T) -> Self {
Self { id, data }
}
pub fn id(&self) -> u32 {
self.id
}
pub fn data(&self) -> &T {
&self.data
}
pub fn data_mut(&mut self) -> &mut T {
&mut self.data
}
pub fn into_data(self) -> T {
self.data
}
pub fn ptr(&self) -> TreePtr<T> {
TreePtr {
id: self.id.into(),
phantom: PhantomData,
}
}
}
/// A list of pointers to blocks of type `T`.
/// This is one level of a [`Tree`], defined above.
#[repr(C, packed)]
pub struct TreeList<T> {
pub ptrs: [BlockPtr<T>; TREE_LIST_ENTRIES],
pub full_flags: [u128; 2],
}
impl<T> TreeList<T> {
pub fn tree_list_is_full(&self) -> bool {
self.full_flags[1] == u128::MAX & !(3 << 126) && self.full_flags[0] == u128::MAX
}
pub fn tree_list_is_empty(&self) -> bool {
for ptr in self.ptrs.iter() {
if !ptr.is_null() {
return false;
}
}
true
}
pub fn branch_is_full(&self, index: usize) -> bool {
assert!(index < TREE_LIST_ENTRIES);
let shift = index % 128;
let full_flags_index = index / 128;
self.full_flags[full_flags_index] & (1 << shift) != 0
}
pub fn set_branch_full(&mut self, index: usize, full: bool) {
assert!(index < TREE_LIST_ENTRIES);
let shift = index % 128;
let full_flags_index = index / 128;
if full {
self.full_flags[full_flags_index] |= 1 << shift;
} else {
self.full_flags[full_flags_index] &= !(1 << shift);
}
}
}
unsafe impl<T> BlockTrait for TreeList<T> {
fn empty(level: BlockLevel) -> Option<Self> {
if level.0 == 0 {
Some(Self {
ptrs: [BlockPtr::default(); TREE_LIST_ENTRIES],
full_flags: [0; 2],
})
} else {
None
}
}
}
impl<T> ops::Deref for TreeList<T> {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self as *const TreeList<T> as *const u8,
mem::size_of::<TreeList<T>>(),
) as &[u8]
}
}
}
impl<T> ops::DerefMut for TreeList<T> {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self as *mut TreeList<T> as *mut u8,
mem::size_of::<TreeList<T>>(),
) as &mut [u8]
}
}
}
/// A pointer to an entry in a [`Tree`].
#[repr(C, packed)]
pub struct TreePtr<T> {
id: Le<u32>,
phantom: PhantomData<T>,
}
impl<T> TreePtr<T> {
/// Get a [`TreePtr`] to the filesystem root
/// directory's node.
pub fn root() -> Self {
Self::new(1)
}
pub fn new(id: u32) -> Self {
Self {
id: id.into(),
phantom: PhantomData,
}
}
/// Create a [`TreePtr`] from [`Tree`] indices,
/// Where `indexes` is `(i3, i2, i1, i0)`.
/// - `i3` is the index into the level 3 table,
/// - `i2` is the index into the level 2 table at `i3`
/// - ...and so on.
pub fn from_indexes(indexes: (usize, usize, usize, usize)) -> Self {
const SHIFT: u32 = TREE_LIST_SHIFT;
let id = ((indexes.0 << (3 * SHIFT)) as u32)
| ((indexes.1 << (2 * SHIFT)) as u32)
| ((indexes.2 << SHIFT) as u32)
| (indexes.3 as u32);
Self {
id: id.into(),
phantom: PhantomData,
}
}
pub fn id(&self) -> u32 {
self.id.to_ne()
}
pub fn is_null(&self) -> bool {
self.id() == 0
}
/// Get this indices of this [`TreePtr`] in a [`Tree`].
/// Returns `(i3, i2, i1, i0)`:
/// - `i3` is the index into the level 3 table,
/// - `i2` is the index into the level 2 table at `i3`
/// - ...and so on.
pub fn indexes(&self) -> (usize, usize, usize, usize) {
const SHIFT: u32 = TREE_LIST_SHIFT;
const NUM: u32 = 1 << SHIFT;
const MASK: u32 = NUM - 1;
let id = self.id();
let i3 = ((id >> (3 * SHIFT)) & MASK) as usize;
let i2 = ((id >> (2 * SHIFT)) & MASK) as usize;
let i1 = ((id >> SHIFT) & MASK) as usize;
let i0 = (id & MASK) as usize;
(i3, i2, i1, i0)
}
pub fn to_bytes(&self) -> [u8; 4] {
self.id.to_le_bytes()
}
pub fn from_bytes(bytes: [u8; 4]) -> Self {
let val = u32::from_le_bytes(bytes);
Self {
id: Le(val),
phantom: PhantomData,
}
}
}
impl<T> Clone for TreePtr<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for TreePtr<T> {}
impl<T> Default for TreePtr<T> {
fn default() -> Self {
Self {
id: 0.into(),
phantom: PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use crate::{BlockAddr, BlockData, BlockMeta};
use super::*;
#[test]
fn tree_list_size_test() {
assert_eq!(
mem::size_of::<TreeList<BlockRaw>>(),
crate::BLOCK_SIZE as usize
);
}
#[test]
fn tree_list_is_full_test() {
let mut tree_list = TreeList::<BlockRaw>::empty(BlockLevel::default()).unwrap();
assert!(!tree_list.tree_list_is_full());
for i in 0..TREE_LIST_ENTRIES {
assert!(!tree_list.branch_is_full(i));
tree_list.set_branch_full(i, true);
assert!(tree_list.branch_is_full(i));
}
assert!(tree_list.tree_list_is_full());
for i in 0..TREE_LIST_ENTRIES {
assert!(tree_list.branch_is_full(i));
tree_list.set_branch_full(i, false);
assert!(!tree_list.branch_is_full(i));
}
}
fn mock_block(addr: u64) -> BlockPtr<BlockRaw> {
let block_addr = unsafe { BlockAddr::new(addr, BlockMeta::default()) };
BlockData::empty(block_addr).unwrap().create_ptr()
}
#[test]
fn tree_list_is_empty() {
let mut tree_list = TreeList::<BlockRaw>::empty(BlockLevel::default()).unwrap();
assert!(tree_list.tree_list_is_empty());
tree_list.ptrs[3] = mock_block(123);
assert!(!tree_list.tree_list_is_empty());
}
#[test]
fn tree_ptr_to_and_from_bytes() {
let ptr: TreePtr<BlockRaw> = TreePtr::new(123456);
let bytes = ptr.to_bytes();
let ptr2: TreePtr<BlockRaw> = TreePtr::from_bytes(bytes);
assert_eq!(ptr.id(), ptr2.id());
}
}
+51
View File
@@ -0,0 +1,51 @@
use std::{
fs,
io::{self},
process::{Command, ExitStatus},
};
fn unmount_linux_path(mount_path: &str) -> io::Result<ExitStatus> {
// Different distributions can have various fusermount binaries. Try
// them all.
let commands = ["fusermount", "fusermount3"];
for command in commands {
let status = Command::new(command).arg("-u").arg(mount_path).status();
if status.is_ok() {
return status;
}
if let Err(ref e) = status {
if e.kind() == io::ErrorKind::NotFound {
continue;
}
}
}
// Unmounting failed since no suitable command was found
Err(std::io::Error::new(
io::ErrorKind::NotFound,
format!(
"Unable to locate any fusermount binaries. Tried {:?}. Is fuse installed?",
commands
),
))
}
pub fn unmount_path(mount_path: &str) -> Result<(), io::Error> {
if cfg!(target_os = "redox") {
fs::remove_dir(format!("/scheme/{}", mount_path))?
} else {
let status_res = if cfg!(target_os = "linux") {
unmount_linux_path(mount_path)
} else {
Command::new("umount").arg(mount_path).status()
};
let status = status_res?;
if !status.success() {
return Err(io::Error::other("redoxfs umount failed"));
}
}
Ok(())
}
Executable
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env bash
CARGO_ARGS=(--release)
TARGET=target/release
export RUST_BACKTRACE=full
export RUST_LOG=info
function cleanup {
sync
fusermount -u image || true
fusermount3 -u image || true
}
trap 'cleanup' ERR
set -eEx
cleanup
redoxer test -- --lib -- --nocapture
cargo test --lib --no-default-features -- --nocapture
cargo test --lib -- --nocapture
cargo build "${CARGO_ARGS[@]}"
rm -f image.bin
fallocate -l 1G image.bin
time "${TARGET}/redoxfs-mkfs" image.bin
mkdir -p image
"${TARGET}/redoxfs" image.bin image
df -h image
ls -lah image
mkdir image/test
time cp -r src image/test/src
dd if=/dev/urandom of=image/test/random bs=1M count=256
dd if=image/test/random of=/dev/null bs=1M count=256
time truncate --size=256M image/test/sparse
dd if=image/test/sparse of=/dev/null bs=1M count=256
dd if=/dev/zero of=image/test/zero bs=1M count=256
dd if=image/test/zero of=/dev/null bs=1M count=256
ls -lah image/test
df -h image
rm image/test/random
rm image/test/sparse
rm image/test/zero
rm -rf image/test/src
rmdir image/test
df -h image
ls -lah image
cleanup
"${TARGET}/redoxfs" image.bin image
df -h image
ls -lah image
cleanup
+639
View File
@@ -0,0 +1,639 @@
use core::panic::AssertUnwindSafe;
use redoxfs::{unmount_path, DirEntry, DiskMemory, DiskSparse, FileSystem, Node, TreePtr};
use std::io::{Read, Seek, SeekFrom, Write};
use std::panic::catch_unwind;
use std::path::Path;
use std::process::Command;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering::Relaxed;
use std::thread::sleep;
use std::time::Duration;
use std::{env, fs, time};
static IMAGE_SEQ: AtomicUsize = AtomicUsize::new(0);
fn with_redoxfs<T, F>(callback: F) -> T
where
T: Send + Sync + 'static,
F: FnOnce(&str) -> T + Send + Sync + 'static,
{
let disk_path = format!("image{}.bin", IMAGE_SEQ.fetch_add(1, Relaxed));
{
let disk = DiskSparse::create(dbg!(&disk_path), 1024 * 1024 * 1024).unwrap();
let ctime = dbg!(time::SystemTime::now().duration_since(time::UNIX_EPOCH)).unwrap();
FileSystem::create(disk, None, ctime.as_secs(), ctime.subsec_nanos()).unwrap();
}
let res = callback(&disk_path);
dbg!(fs::remove_file(dbg!(disk_path))).unwrap();
res
}
fn with_mounted<T, F>(callback: F) -> T
where
T: Send + Sync + 'static,
F: FnOnce(&Path) -> T + Send + Sync + 'static,
{
let mount_path_o = format!("image{}", IMAGE_SEQ.fetch_add(1, Relaxed));
let mount_path = mount_path_o.clone();
let res = with_redoxfs(move |fs| {
// At redox, we mount on /scheme/ path, no need an empty dir
if cfg!(not(target_os = "redox")) {
if !Path::new(&mount_path).exists() {
dbg!(fs::create_dir(dbg!(&mount_path))).unwrap();
}
} else {
//FIXME: cargo_bin is broken when cross compiling. This is redoxer specific workaround
env::set_var(
"CARGO_BIN_EXE_redoxfs",
"/root/target/x86_64-unknown-redox/debug/redoxfs",
);
}
let mut mount_cmd = Command::new(assert_cmd::cargo_bin!("redoxfs"));
mount_cmd.arg("-d").arg(dbg!(&fs)).arg(dbg!(&mount_path));
let mut child = mount_cmd.spawn().expect("mount failed to run");
let real_path = if cfg!(target_os = "redox") {
let real_path = dbg!(Path::new("/scheme").join(&mount_path));
let mut tries = 0;
loop {
if real_path.exists() {
break;
}
tries += 1;
if tries == 10 {
panic!("Fail to wait for mount")
}
println!("{tries}");
sleep(Duration::from_millis(500));
}
real_path
} else {
sleep(Duration::from_millis(200));
let r = Path::new(".").join(&mount_path);
r
};
let res = catch_unwind(AssertUnwindSafe(|| callback(&real_path)));
sleep(Duration::from_millis(200));
child.kill().expect("Can't kill");
let _ = child.wait();
if cfg!(target_os = "redox") {
unmount_path(&mount_path).unwrap();
} else {
if !dbg!(Command::new("sync").status()).unwrap().success() {
panic!("sync failed");
}
if unmount_path(&mount_path).is_err() {
// There seems to be a race condition where the device can be busy when trying to unmount.
// So, we pause for a moment and retry. There will still be an error output to the logs
// for the first failed attempt.
sleep(Duration::from_millis(200));
if unmount_path(&mount_path).is_err() {
panic!("umount failed");
}
}
}
res.expect("Test failed")
});
if cfg!(not(target_os = "redox")) {
dbg!(fs::remove_dir(dbg!(mount_path_o))).unwrap();
}
res
}
#[test]
fn simple() {
with_mounted(|path| {
dbg!(fs::create_dir(path.join("test"))).unwrap();
})
}
#[test]
fn create_and_remove_file() {
with_mounted(|path| {
let file_name = "test_file.txt";
let file_path = path.join(file_name);
// Create the file
fs::write(&file_path, "Hello, world!").unwrap();
assert!(fs::exists(&file_path).unwrap());
// Read the file
let contents = fs::read_to_string(&file_path).unwrap();
assert_eq!(contents, "Hello, world!");
// Remove the file
fs::remove_file(&file_path).unwrap();
assert!(!fs::exists(&file_path).unwrap());
});
}
#[test]
fn create_and_remove_directory() {
with_mounted(|path| {
let dir_name = "test_dir";
let dir_path = path.join(dir_name);
// Create the directory
fs::create_dir(&dir_path)
.unwrap_or_else(|_| panic!("cannot create dir {}", &dir_path.display()));
assert!(fs::exists(&dir_path).unwrap());
// Check that the directory is empty
let entries: Vec<_> = fs::read_dir(&dir_path)
.unwrap()
.map(|e| e.unwrap().file_name())
.collect();
assert!(entries.is_empty());
// Add a file to the directory
let file_name = "test_file.txt";
let file_path = dir_path.join(file_name);
fs::write(&file_path, "Hello, world!").unwrap();
// Check that the dir cannot be removed when not empty
let error = fs::remove_dir(&dir_path);
assert!(error.is_err());
assert_eq!(
error.unwrap_err().kind(),
std::io::ErrorKind::DirectoryNotEmpty
);
// Remove the file
fs::remove_file(&file_path).unwrap();
// Remove the directory
fs::remove_dir(&dir_path).unwrap();
assert!(!fs::exists(&dir_path).unwrap());
});
}
#[test]
fn create_and_remove_symlink() {
with_mounted(|path| {
let real_file = "real_file.txt";
let real_path = path.join(real_file);
let symlink_file = "symlink_to_real_file.txt";
let symlink_path = path.join(symlink_file);
// Create the real file
fs::write(&real_path, "Hello, world!").unwrap();
// Create the symmlink according to the platform
#[cfg(unix)]
std::os::unix::fs::symlink(real_file, &symlink_path).unwrap();
#[cfg(windows)]
std::os::windows::fs::symlink_file(&real_file, &symlink_path).unwrap();
// Check that the symlink exists and points to the correct target
let exists = fs::exists(&symlink_path);
assert!(
exists.is_ok() && exists.unwrap(),
"Symlink should exist but was: {:?}",
fs::exists(&symlink_path)
);
let symlink_metadata = fs::symlink_metadata(&symlink_path).unwrap();
assert!(symlink_metadata.file_type().is_symlink());
let target = fs::read_link(&symlink_path).unwrap();
assert_eq!(target.to_str().unwrap(), real_file);
assert_eq!(fs::read(&symlink_path).unwrap(), b"Hello, world!");
// Confirm the symlink cannot be removed as a directory
let error = fs::remove_dir(&symlink_path);
assert!(error.is_err());
assert_eq!(error.unwrap_err().kind(), std::io::ErrorKind::NotADirectory);
// Remove the symlink
fs::remove_file(&symlink_path).unwrap();
assert!(!fs::exists(&symlink_path).unwrap());
});
}
#[cfg(target_os = "redox")]
#[test]
fn mmap() {
//TODO
with_mounted(|path| {
use std::slice;
let path = dbg!(path.join("test"));
let mmap_inner = |write: bool| {
let fd = dbg!(libredox::call::open(
path.to_str().unwrap(),
libredox::flag::O_CREAT | libredox::flag::O_RDWR | libredox::flag::O_CLOEXEC,
0,
))
.unwrap();
let map = unsafe {
slice::from_raw_parts_mut(
dbg!(libredox::call::mmap(libredox::call::MmapArgs {
fd,
offset: 0,
length: 128,
prot: libredox::flag::PROT_READ | libredox::flag::PROT_WRITE,
flags: libredox::flag::MAP_SHARED,
addr: core::ptr::null_mut(),
}))
.unwrap() as *mut u8,
128,
)
};
// Maps should be available after closing
assert_eq!(dbg!(libredox::call::close(fd)), Ok(()));
for i in 0..128 {
if write {
map[i as usize] = i;
}
assert_eq!(map[i as usize], i);
}
//TODO: add msync
unsafe {
assert_eq!(
dbg!(libredox::call::munmap(map.as_mut_ptr().cast(), map.len())),
Ok(())
);
}
};
mmap_inner(true);
mmap_inner(false);
})
}
// TODO: When increasing the total_count to 8000, the Allocator's deallocate() function surfaces as "slow" according to flamegraph. This
// appears to be the result of bulk deleting in this test, but I would bet that any filesystem that has lived for a long time would
// start to see degraded performance due to this.
#[test]
fn many_create_write_list_find_read_delete() {
let disk = DiskMemory::new(1024 * 1024 * 1024);
let ctime = time::SystemTime::now()
.duration_since(time::UNIX_EPOCH)
.unwrap();
let mut fs = FileSystem::create(disk, None, ctime.as_secs(), ctime.subsec_nanos()).unwrap();
let tree_ptr = TreePtr::<Node>::root();
let total_count = 3000;
// Create a bunch of files
for i in 0..total_count {
let result = fs.tx(|tx| {
tx.create_node(
tree_ptr,
&format!("file{i:05}"),
Node::MODE_FILE | 0o644,
1,
0,
)
});
if result.is_err() {
println!("Failure on create iteration {i}");
}
let file_node = result.unwrap();
let result = fs.tx(|tx| {
tx.write_node(
file_node.ptr(),
0,
format!("Hello World! #{i}").as_bytes(),
ctime.as_secs(),
ctime.subsec_nanos(),
)
});
if result.is_err() {
println!("Failure on write iteration {i}");
}
assert!(result.unwrap() > 0)
}
// Confirm that they can be listed
{
let mut children = Vec::<DirEntry>::with_capacity(total_count);
fs.tx(|tx| tx.child_nodes(tree_ptr, &mut children)).unwrap();
assert_eq!(
children.len(),
total_count,
"The list of children should match the number of files created."
);
let mut children: Vec<String> = children
.iter()
.map(|entry| entry.name().unwrap_or_default().to_string())
.collect();
children.sort();
for i in 0..total_count {
let expected = format!("file{i:05}");
let idx = children.binary_search(&expected);
assert!(idx.is_ok(), "Children did not contain '{}'", expected);
}
}
// Find and read the files
for i in 0..total_count {
let result = fs.tx(|tx| tx.find_node(tree_ptr, &format!("file{i:05}")));
if result.is_err() {
println!("Failure on find node iteration {i}");
}
let file_node = result.unwrap();
let offset = 0;
let mut buf = [0_u8; 32];
let result = fs.tx(|tx| {
tx.read_node(
file_node.ptr(),
offset,
&mut buf,
ctime.as_secs(),
ctime.subsec_nanos(),
)
});
if result.is_err() {
println!("Failure on read iteration {i}");
}
let size = result.unwrap();
let body = std::str::from_utf8(&buf[..size]).unwrap();
assert_eq!(body, format!("Hello World! #{i}"));
}
// Delete all the files
for i in 0..total_count {
let file_name = format!("file{i:05}");
if let Err(e) = fs.tx(|tx| tx.remove_node(tree_ptr, &file_name, Node::MODE_FILE)) {
println!("Failure on delete iteration {i}");
panic!("{e}");
}
let result = fs.tx(|tx| tx.find_node(tree_ptr, &file_name));
if result.is_ok() || result.unwrap_err().errno != syscall::error::ENOENT {
println!("Failure on delete verification iteration {i}");
panic!("Deletion appears to have failed");
}
}
}
#[test]
fn many_write_read_delete_mounted() {
with_mounted(|path| {
let total_count = 500;
for i in 0..total_count {
fs::write(
path.join(format!("file{}", i)),
format!("Hello, number {i}!"),
)
.unwrap();
}
// Confirm each of the created files can be found and read
for i in 0..total_count {
let contents = fs::read_to_string(path.join(format!("file{}", i))).unwrap();
assert_eq!(contents, format!("Hello, number {i}!"));
}
// Remove all the files
for i in 0..total_count {
let file_path = path.join(format!("file{i}"));
assert!(fs::exists(&file_path).unwrap());
fs::remove_file(&file_path).unwrap();
assert!(!fs::exists(&file_path).unwrap());
}
});
}
#[test]
fn rename_no_replace() {
let disk = DiskMemory::new(1024 * 1024 * 1024);
let mut fs = FileSystem::create(disk, None, 0, 0)
.expect("Creating in memory file system should succeed");
let root = TreePtr::root();
let dir = fs
.tx(|tx| tx.create_node(root, "dir", Node::MODE_DIR, 0, 0))
.expect("Creating a directory should succeed");
let source_file = fs
.tx(|tx| tx.create_node(root, "source", Node::MODE_FILE, 0, 0))
.expect("Creating source file to copy should succeed");
let no_clobber_file = fs
.tx(|tx| tx.create_node(root, "no_clobber", Node::MODE_FILE, 0, 0))
.expect("Creating second file to not clobber should succeed");
// Rename /source to /target
fs.tx(|tx| tx.rename_node_no_replace(root, "source", root, "target"))
.expect("Renaming existing 'source' to non-existing 'target' should succeed");
let target_file = fs
.tx(|tx| tx.find_node(root, "target"))
.expect("'target' should exist because we just renamed 'source' to 'target'");
assert_eq!(
source_file.id(),
target_file.id(),
"source and target are most definitely the same file"
);
// Don't rename /target to /no_clobber
let err = fs
.tx(|tx| tx.rename_node_no_replace(root, "target", root, "no_clobber"))
.expect_err("Renaming 'target' to existing 'no_clobber' should fail");
assert_eq!(
syscall::EEXIST,
err.errno,
"Renaming to existing file should fail with EEXIST"
);
assert_ne!(
no_clobber_file.id(),
target_file.id(),
"'target' and 'no_clobber' should be distinct files"
);
// Don't rename /target to /dir
let err = fs
.tx(|tx| tx.rename_node_no_replace(root, "target", root, "dir"))
.expect_err("Renaming 'target' to existing directory 'dir' should fail");
assert_eq!(
syscall::EEXIST,
err.errno,
"Renaming to existing file should fail with EEXIST"
);
assert_ne!(
dir.id(),
target_file.id(),
"'target' and 'dir' should be distinct nodes"
);
// Don't rename /dir to /target
let err = fs
.tx(|tx| tx.rename_node_no_replace(root, "dir", root, "target"))
.expect_err("Renaming 'dir' to existing file 'target' should fail");
assert_eq!(
syscall::EEXIST,
err.errno,
"Renaming to existing file should fail with EEXIST"
);
assert_ne!(
target_file.id(),
dir.id(),
"'dir' and 'target' should be distinct nodes"
);
// Don't rename /target to /target
let err = fs
.tx(|tx| tx.rename_node_no_replace(root, "target", root, "target"))
.expect_err("Renaming 'target' to itself should fail");
assert_eq!(
syscall::EEXIST,
err.errno,
"Renaming file to itself should fail with EEXIST"
);
// Rename /target to /dir/target
fs.tx(|tx| tx.rename_node_no_replace(root, "target", dir.ptr(), "target"))
.expect("Renaming /target to /dir/target should succeed");
let moved_target = fs
.tx(|tx| tx.find_node(dir.ptr(), "target"))
.expect("'target' should have moved to /dir/target");
assert_eq!(target_file.id(), moved_target.id());
// Rename /dir to /newdir
fs.tx(|tx| tx.rename_node_no_replace(root, "dir", root, "newdir"))
.expect("Renaming 'dir' to 'newdir' should succeed");
}
#[test]
fn rename_works() {
let disk = DiskMemory::new(1024 * 1024 * 1024);
let mut fs = FileSystem::create(disk, None, 0, 0)
.expect("Creating in memory file system should succeed");
let root = TreePtr::root();
let dir = fs
.tx(|tx| tx.create_node(root, "dir", Node::MODE_DIR, 0, 0))
.expect("Creating a directory should succeed");
let source_file = fs
.tx(|tx| tx.create_node(root, "source", Node::MODE_FILE, 0, 0))
.expect("Creating source file should succeed");
let target_file_orig = fs
.tx(|tx| tx.create_node(root, "target", Node::MODE_FILE, 0, 0))
.expect("Creating target file should succeed");
// Rename /source to /source2
fs.tx(|tx| tx.rename_node(root, "source", root, "source2"))
.expect("Renaming existing 'source' to non-existing 'source2' should succeed");
let source2_file = fs
.tx(|tx| tx.find_node(root, "source2"))
.expect("'source2' should exist because we just renamed 'source' to 'source2'");
assert_eq!(source_file.id(), source2_file.id());
let err = fs
.tx(|tx| tx.find_node(root, "source"))
.expect_err("'source' should not exist because it was moved");
assert_eq!(syscall::ENOENT, err.errno);
// Rename /source2 to /target
fs.tx(|tx| tx.rename_node(root, "source2", root, "target"))
.expect("Renaming existing 'source2' to existing 'target' should succeed");
let target_file_mv = fs
.tx(|tx| tx.find_node(root, "target"))
.expect("'target' should exist because the rename succeeded");
assert_ne!(
target_file_orig.id(),
target_file_mv.id(),
"Move failed because 'target' is still the same"
);
assert_eq!(
source2_file.id(),
target_file_mv.id(),
"Move failed because 'source2' != 'target'"
);
// Don't rename /target to /dir
// XXX: A similar test fails on Linux using rename(). Not sure if the discrepancy matters.
// let err = fs
// .tx(|tx| tx.rename_node(root, "target", root, "dir"))
// .expect_err("Renaming 'target' to existing directory 'dir' should fail");
// assert_eq!(
// syscall::EEXIST,
// err.errno,
// "Renaming to existing file should fail with EEXIST"
// );
// assert_ne!(
// dir.id(),
// target_file_mv.id(),
// "'target' and 'dir' should be distinct nodes"
// );
// Don't rename /dir to /target
// XXX: A similar test fails on Linux using rename().
// let err = fs
// .tx(|tx| tx.rename_node(root, "dir", root, "target"))
// .expect_err("Renaming 'dir' to existing file 'target' should fail");
// assert_eq!(
// syscall::EEXIST,
// err.errno,
// "Renaming to existing file should fail with EEXIST"
// );
// assert_ne!(
// target_file_mv.id(),
// dir.id(),
// "'dir' and 'target' should be distinct nodes"
// );
// Rename /target to /target
fs.tx(|tx| tx.rename_node(root, "target", root, "target"))
.expect("Renaming 'target' to itself should succeed");
let target_self_mv = fs
.tx(|tx| tx.find_node(root, "target"))
.expect("'target' should exist because rename succeeded");
assert_eq!(
target_file_mv.id(),
target_self_mv.id(),
"'target' shouldn't have changed during a move to self"
);
// Rename /target to /dir/target
fs.tx(|tx| tx.rename_node(root, "target", dir.ptr(), "target"))
.expect("Renaming /target to /dir/target should succeed");
let moved_target = fs
.tx(|tx| tx.find_node(dir.ptr(), "target"))
.expect("'target' should have moved to /dir/target");
assert_eq!(target_file_mv.id(), moved_target.id());
// Rename /dir to /newdir
fs.tx(|tx| tx.rename_node(root, "dir", root, "newdir"))
.expect("Renaming 'dir' to 'newdir' should succeed");
}
#[test]
fn temporary_file() {
with_mounted(|path| {
let file_path = path.join("temp");
let mut file = fs::File::create_new(&file_path).expect("failed to create temp file");
fs::remove_file(&file_path).expect("failed to unlink temp file");
let write_data = "Test\n";
file.write_all(write_data.as_bytes())
.expect("failed to write temp file");
let mut read_data = String::new();
file.seek(SeekFrom::Start(0))
.expect("failed to seek temp file");
file.read_to_string(&mut read_data)
.expect("failed to read temp file");
assert_eq!(read_data, write_data);
});
}