Merge branch 'Improve-file-creation-performance' into 'v7'

Improve performance

See merge request redox-os/redoxfs!87
This commit is contained in:
Jeremy Soller
2025-07-01 19:11:00 -06:00
17 changed files with 3061 additions and 379 deletions
+1
View File
@@ -1,3 +1,4 @@
target
image.bin
image
image*
+239 -140
View File
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "aes"
@@ -23,6 +23,56 @@ 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"
@@ -40,37 +90,20 @@ dependencies = [
[[package]]
name = "argon2"
version = "0.3.4"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25df3c03f1040d0069fcd3907e24e36d59f9b6fa07ba49be0eb25a794f036ba7"
checksum = "db4ce4441f99dbd377ca8a8f57b698c44d0d6e712d8329b5040da5a64aa1ce73"
dependencies = [
"base64ct",
"blake2",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.5.0"
@@ -133,6 +166,12 @@ dependencies = [
"generic-array",
]
[[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"
@@ -152,6 +191,15 @@ dependencies = [
"typenum",
]
[[package]]
name = "deranged"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
dependencies = [
"powerfmt",
]
[[package]]
name = "derive_arbitrary"
version = "1.3.2"
@@ -181,16 +229,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad847bb2094f110bbdd6fa564894ca4556fd978958e93985420d680d3cb6d14"
[[package]]
name = "env_logger"
version = "0.9.3"
name = "env_filter"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[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]]
@@ -200,7 +258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@@ -211,9 +269,9 @@ checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
[[package]]
name = "fuser"
version = "0.12.0"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5910691a0ececcc6eba8bb14029025c2d123e96a53db1533f6a4602861a5aaf7"
checksum = "2e697f6f62c20b6fad1ba0f84ae909f25971cf16e735273524e3977c94604cf8"
dependencies = [
"libc",
"log",
@@ -221,7 +279,6 @@ dependencies = [
"page_size",
"pkg-config",
"smallvec",
"users",
"zerocopy",
]
@@ -243,23 +300,38 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasi",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "jiff"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ad87c89110f55e4cd4dc2893a9790820206729eaf221555f742d540b0724a0"
dependencies = [
"libc",
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
"serde",
]
[[package]]
name = "humantime"
version = "2.1.0"
name = "jiff-static"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
checksum = "d076d5b64a7e2fe6f0743f02c43ca4a6725c0f904203bfe276a5b3e793103605"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "jobserver"
@@ -287,26 +359,15 @@ dependencies = [
"once_cell",
]
[[package]]
name = "libredox"
version = "0.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607"
dependencies = [
"bitflags 2.5.0",
"libc",
"redox_syscall 0.4.1",
]
[[package]]
name = "libredox"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags 2.5.0",
"bitflags",
"libc",
"redox_syscall 0.5.2",
"redox_syscall",
]
[[package]]
@@ -333,23 +394,29 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags 2.5.0",
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "numtoa"
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[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.19.0"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "opaque-debug"
@@ -359,9 +426,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "page_size"
version = "0.4.2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd"
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
dependencies = [
"libc",
"winapi",
@@ -374,19 +441,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]]
name = "proc-macro2"
version = "1.0.85"
name = "portable-atomic"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
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 = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[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.36"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
@@ -404,21 +492,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717"
[[package]]
name = "redox_syscall"
version = "0.4.1"
name = "redox-scheme"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
checksum = "6143c4d307e1c99ac14f60b5b07b2dccaf9d17137f7cee4e4e29977dd8014a1b"
dependencies = [
"bitflags 1.3.2",
"libredox",
"redox_syscall",
]
[[package]]
name = "redox_syscall"
version = "0.5.2"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd"
checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
dependencies = [
"bitflags 2.5.0",
"bitflags",
]
[[package]]
@@ -429,7 +518,7 @@ checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb"
[[package]]
name = "redoxfs"
version = "0.6.4"
version = "0.6.10"
dependencies = [
"aes",
"argon2",
@@ -439,11 +528,12 @@ dependencies = [
"fuser",
"getrandom",
"libc",
"libredox 0.1.3",
"libredox",
"log",
"range-tree",
"redox-path",
"redox_syscall 0.5.2",
"redox-scheme",
"redox_syscall",
"seahash",
"termion",
"time",
@@ -498,11 +588,11 @@ version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [
"bitflags 2.5.0",
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@@ -511,6 +601,26 @@ 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"
@@ -525,9 +635,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "syn"
version = "2.0.66"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
@@ -543,41 +653,40 @@ dependencies = [
"cfg-if",
"fastrand",
"rustix",
"windows-sys",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
"windows-sys 0.52.0",
]
[[package]]
name = "termion"
version = "2.0.3"
version = "4.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4648c7def6f2043b2568617b9f9b75eae88ca185dbc1f1fda30e95a85d49d7d"
checksum = "3669a69de26799d6321a5aa713f55f7e2cd37bd47be044b50f2acafc42c122bb"
dependencies = [
"libc",
"libredox 0.0.2",
"libredox",
"numtoa",
"redox_termios",
]
[[package]]
name = "time"
version = "0.1.45"
version = "0.3.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
"deranged",
"num-conv",
"powerfmt",
"serde",
"time-core",
]
[[package]]
name = "time-core"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
[[package]]
name = "typenum"
version = "1.17.0"
@@ -591,14 +700,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "users"
version = "0.11.0"
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
dependencies = [
"libc",
"log",
]
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
@@ -615,12 +720,6 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@@ -643,15 +742,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
dependencies = [
"windows-sys",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
@@ -668,10 +758,19 @@ dependencies = [
]
[[package]]
name = "windows-targets"
version = "0.52.5"
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
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",
@@ -685,57 +784,57 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "zerocopy"
version = "0.6.6"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854e949ac82d619ee9a14c66a1b674ac730422372ccb759ce0c39cabcf2bf8e6"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
@@ -743,9 +842,9 @@ dependencies = [
[[package]]
name = "zerocopy-derive"
version = "0.6.6"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
+1 -1
View File
@@ -14,7 +14,7 @@ cargo-fuzz = true
[dependencies]
anyhow = "1.0.86"
arbitrary = { version = "1.3.2", features = ["derive"] }
fuser = { version = "0.12.0" }
fuser = { version = "0.14" }
libfuzzer-sys = "0.4"
nix = { version = "0.29.0", features = ["fs"] }
tempfile = "3.10.1"
+1 -1
View File
@@ -315,7 +315,7 @@ fuzz_target!(|test_case: TestCase| -> Corpus {
for mount_seq in test_case.mount_sequences.iter() {
#[cfg(feature = "log")]
eprintln!("mount fs");
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();
+24
View File
@@ -231,6 +231,19 @@ impl<T> BlockPtr<T> {
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
@@ -316,3 +329,14 @@ fn block_list_size_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());
}
+216 -24
View File
@@ -1,7 +1,6 @@
use alloc::{boxed::Box, vec};
use core::{mem, ops, slice, str};
use crate::{BlockLevel, BlockTrait, Node, TreePtr, DIR_ENTRY_MAX_LENGTH, RECORD_LEVEL};
use crate::{BlockLevel, BlockTrait, Node, TreePtr, BLOCK_SIZE, DIR_ENTRY_MAX_LENGTH};
#[repr(C, packed)]
#[derive(Clone, Copy)]
@@ -26,7 +25,7 @@ impl DirEntry {
self.node_ptr
}
pub fn name(&self) -> Option<&str> {
fn name_len(&self) -> usize {
let mut len = 0;
while len < self.name.len() {
if self.name[len] == 0 {
@@ -34,9 +33,63 @@ impl DirEntry {
}
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 {
@@ -48,17 +101,19 @@ impl Default for DirEntry {
}
}
//TODO: this is a box to prevent stack overflows
pub struct DirList {
pub entries: Box<[DirEntry]>,
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 <= RECORD_LEVEL {
let entries = level.bytes() as usize / mem::size_of::<DirEntry>();
if level.0 == 0 {
Some(Self {
entries: vec![DirEntry::default(); entries].into_boxed_slice(),
count: 0,
entry_bytes_len: 0,
entry_bytes: [0; BLOCK_SIZE as usize - 4],
})
} else {
None
@@ -68,7 +123,96 @@ unsafe impl BlockTrait for DirList {
impl DirList {
pub fn is_empty(&self) -> bool {
self.entries.iter().all(|entry| entry.node_ptr().is_null())
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
}
}
@@ -77,8 +221,8 @@ impl ops::Deref for DirList {
fn deref(&self) -> &[u8] {
unsafe {
slice::from_raw_parts(
self.entries.as_ptr() as *const u8,
self.entries.len() * mem::size_of::<DirEntry>(),
self as *const DirList as *const u8,
mem::size_of::<DirList>(),
) as &[u8]
}
}
@@ -87,22 +231,70 @@ impl ops::Deref for DirList {
impl ops::DerefMut for DirList {
fn deref_mut(&mut self) -> &mut [u8] {
unsafe {
slice::from_raw_parts_mut(
self.entries.as_mut_ptr() as *mut u8,
self.entries.len() * mem::size_of::<DirEntry>(),
) as &mut [u8]
slice::from_raw_parts_mut(self as *mut DirList as *mut u8, mem::size_of::<DirList>())
as &mut [u8]
}
}
}
#[test]
fn dir_list_size_test() {
use core::ops::Deref;
for level_i in 0..RECORD_LEVEL {
let level = BlockLevel(level_i);
assert_eq!(
DirList::empty(level).unwrap().deref().len(),
level.bytes() as usize
);
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}"));
}
}
}
+3 -3
View File
@@ -47,7 +47,7 @@ impl<T: Disk> Disk for DiskCache<T> {
let mut read = 0;
let mut failed = false;
for i in 0..(buffer.len() + BLOCK_SIZE as usize - 1) / (BLOCK_SIZE as usize) {
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;
@@ -66,7 +66,7 @@ impl<T: Disk> Disk for DiskCache<T> {
self.inner.read_at(block, buffer)?;
read = 0;
for i in 0..(buffer.len() + BLOCK_SIZE as usize - 1) / (BLOCK_SIZE as usize) {
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;
@@ -89,7 +89,7 @@ impl<T: Disk> Disk for DiskCache<T> {
self.inner.write_at(block, buffer)?;
let mut written = 0;
for i in 0..(buffer.len() + BLOCK_SIZE as usize - 1) / (BLOCK_SIZE as usize) {
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;
+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)
}
}
+4
View File
@@ -7,6 +7,8 @@ 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")]
@@ -16,6 +18,8 @@ mod file;
#[cfg(feature = "std")]
mod io;
#[cfg(feature = "std")]
mod memory;
#[cfg(feature = "std")]
mod sparse;
/// A disk
-1
View File
@@ -1,7 +1,6 @@
use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::Path;
use std::u64;
use syscall::error::{Error, Result, EIO};
use crate::disk::Disk;
+1 -1
View File
@@ -118,7 +118,7 @@ impl<D: Disk> FileSystem<D> {
ctime_nsec: u32,
) -> Result<Self> {
let size = disk.size()?;
let block_offset = (reserved.len() as u64 + BLOCK_SIZE - 1) / BLOCK_SIZE;
let block_offset = (reserved.len() as u64).div_ceil(BLOCK_SIZE);
if size < (block_offset + HEADER_RING + 4) * BLOCK_SIZE {
return Err(Error::new(syscall::error::ENOSPC));
+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);
}
}
+2 -1
View File
@@ -16,7 +16,7 @@ pub const BLOCK_SIZE: u64 = 4096;
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 = 6;
pub const VERSION: u64 = 7;
pub const DIR_ENTRY_MAX_LENGTH: usize = 252;
pub static IS_UMT: AtomicUsize = AtomicUsize::new(0);
@@ -49,6 +49,7 @@ mod dir;
mod disk;
mod filesystem;
mod header;
mod htree;
mod key;
#[cfg(all(feature = "std", not(fuzzing)))]
mod mount;
+2 -2
View File
@@ -77,7 +77,7 @@ fn node_attr(node: &TreeData<Node>) -> FileAttr {
ino: node.id() as u64,
size: node.data().size(),
// Blocks is in 512 byte blocks, not in our block size
blocks: (node.data().size() + BLOCK_SIZE - 1) / BLOCK_SIZE * (BLOCK_SIZE / 512),
blocks: node.data().size().div_ceil(BLOCK_SIZE) * (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),
@@ -99,7 +99,7 @@ fn node_attr(node: &TreeData<Node>) -> FileAttr {
}
}
impl<'f, D: Disk> Filesystem for Fuse<'f, D> {
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
+1189 -22
View File
File diff suppressed because it is too large Load Diff
+540 -175
View File
@@ -13,6 +13,7 @@ use syscall::error::{
};
use crate::{
htree::{self, HTreeHash, HTreeNode, HTreePtr},
AllocEntry, AllocList, Allocator, BlockAddr, BlockData, BlockLevel, BlockPtr, BlockTrait,
DirEntry, DirList, Disk, FileSystem, Header, Node, NodeLevel, RecordRaw, TreeData, TreePtr,
ALLOC_GC_THRESHOLD, ALLOC_LIST_ENTRIES, DIR_ENTRY_MAX_LENGTH, HEADER_RING,
@@ -59,7 +60,7 @@ impl<'a, D: Disk> Transaction<'a, D> {
/// Allocate a new block of size `level`, returning its address.
/// - returns `Err(ENOSPC)` if a block of this size could not be alloated.
/// - unsafe because order must be done carefully and changes must be flushed to disk
unsafe fn allocate(&mut self, level: BlockLevel) -> Result<BlockAddr> {
pub(crate) unsafe fn allocate(&mut self, level: BlockLevel) -> Result<BlockAddr> {
match self.allocator.allocate(level) {
Some(addr) => {
self.allocator_log.push_back(AllocEntry::allocate(addr));
@@ -71,7 +72,7 @@ impl<'a, D: Disk> Transaction<'a, D> {
/// Deallocate the given block.
/// - unsafe because order must be done carefully and changes must be flushed to disk
unsafe fn deallocate(&mut self, addr: BlockAddr) {
pub(crate) unsafe fn deallocate(&mut self, addr: BlockAddr) {
//TODO: should we use some sort of not-null abstraction?
assert!(!addr.is_null());
@@ -456,39 +457,53 @@ impl<'a, D: Disk> Transaction<'a, D> {
&mut self,
block_ptr: BlockPtr<T>,
) -> Result<TreePtr<T>> {
// TODO: improve performance, reduce writes
// Remember that if there is a free block at any level it will always sync when it
// allocates at the lowest level, so we can save a write by not writing each level as it
// is allocated.
unsafe {
let mut l3 = self.read_block(self.header.tree)?;
for i3 in 0..l3.data().ptrs.len() {
if l3.data().branch_is_full(i3) {
continue;
}
let mut l2 = self.read_block_or_empty(l3.data().ptrs[i3])?;
for i2 in 0..l2.data().ptrs.len() {
if l2.data().branch_is_full(i2) {
continue;
}
let mut l1 = self.read_block_or_empty(l2.data().ptrs[i2])?;
for i1 in 0..l1.data().ptrs.len() {
if l1.data().branch_is_full(i1) {
continue;
}
let mut l0 = self.read_block_or_empty(l1.data().ptrs[i1])?;
for i0 in 0..l0.data().ptrs.len() {
let pn = l0.data().ptrs[i0];
// Skip if already in use
if !pn.is_null() {
if l0.data().branch_is_full(i0) {
continue;
}
let pn = l0.data().ptrs[i0];
assert!(pn.is_null());
let tree_ptr = TreePtr::from_indexes((i3, i2, i1, i0));
// Skip if this is a reserved node (null)
if tree_ptr.is_null() {
l0.data_mut().set_branch_full(i0, true);
continue;
}
// TODO: do we need to write all of these?
// Write updates to newly allocated blocks
l0.data_mut().set_branch_full(i0, true);
l0.data_mut().ptrs[i0] = block_ptr.cast();
l1.data_mut()
.set_branch_full(i1, l0.data().tree_list_is_full());
l1.data_mut().ptrs[i1] = self.sync_block(l0)?;
l2.data_mut()
.set_branch_full(i2, l1.data().tree_list_is_full());
l2.data_mut().ptrs[i2] = self.sync_block(l1)?;
l3.data_mut()
.set_branch_full(i3, l2.data().tree_list_is_full());
l3.data_mut().ptrs[i3] = self.sync_block(l2)?;
self.header.tree = self.sync_block(l3)?;
self.header_changed = true;
@@ -522,13 +537,45 @@ impl<'a, D: Disk> Transaction<'a, D> {
let mut l1 = self.read_block(l2.data().ptrs[i2])?;
let mut l0 = self.read_block(l1.data().ptrs[i1])?;
// Clear the value in the tree, but do not deallocate the block, as that should already
// have been done at the node level.
// Clear the value in the tree, but do not deallocate the node block, as that should already
// have been done at the node level. The inner tree nodes can be deallocated if they are empty.
l0.data_mut().set_branch_full(i0, false);
l0.data_mut().ptrs[i0] = BlockPtr::default();
l1.data_mut().ptrs[i1] = self.sync_block(l0)?;
l2.data_mut().ptrs[i2] = self.sync_block(l1)?;
l3.data_mut().ptrs[i3] = self.sync_block(l2)?;
self.header.tree = self.sync_block(l3)?;
let l0_ptr = if l0.data().tree_list_is_empty() {
unsafe { self.deallocate(l0.addr()) };
BlockPtr::default()
} else {
self.sync_block(l0)?
};
l1.data_mut().set_branch_full(i1, false);
l1.data_mut().ptrs[i1] = l0_ptr;
let l1_ptr = if l1.data().tree_list_is_empty() {
unsafe { self.deallocate(l1.addr()) };
BlockPtr::default()
} else {
self.sync_block(l1)?
};
l2.data_mut().set_branch_full(i2, false);
l2.data_mut().ptrs[i2] = l1_ptr;
let l2_ptr = if l2.data().tree_list_is_empty() {
unsafe { self.deallocate(l2.addr()) };
BlockPtr::default()
} else {
self.sync_block(l2)?
};
l3.data_mut().set_branch_full(i3, false);
l3.data_mut().ptrs[i3] = l2_ptr;
let l3_ptr = if l3.data().tree_list_is_empty() {
unsafe { self.deallocate(l3.addr()) };
BlockPtr::default()
} else {
self.sync_block(l3)?
};
self.header.tree = l3_ptr;
self.header_changed = true;
Ok(())
}
@@ -580,7 +627,6 @@ impl<'a, D: Disk> Transaction<'a, D> {
// MARK: node operations
//
// TODO: use more efficient methods for reading directories
/// Write all children of `parent_ptr` to `children`.
/// `parent_ptr` must point to a directory node.
pub fn child_nodes(
@@ -589,60 +635,130 @@ impl<'a, D: Disk> Transaction<'a, D> {
children: &mut Vec<DirEntry>,
) -> Result<()> {
let parent = self.read_tree(parent_ptr)?;
let record_level = parent.data().record_level();
for record_offset in 0..(parent.data().size() / record_level.bytes()) {
let block_ptr = self.node_record_ptr(&parent, record_offset)?;
// TODO: is this safe? what if child_nodes is called on
// a node that isn't a directory?
let dir_ptr: BlockPtr<DirList> = unsafe { block_ptr.cast() };
let dir = self.read_block(dir_ptr)?;
for entry in dir.data().entries.iter() {
let node_ptr = entry.node_ptr();
if parent.data().level0[0].is_marker() {
let htree_levels = parent.data().level0[0].addr().level().0;
let htree_root = if htree_levels == 0 {
// Create a fake root to satisfy the recursive child_nodes_inner function signature
let mut fake_htree_node =
BlockData::<HTreeNode<RecordRaw>>::empty(BlockAddr::default()).unwrap();
let dir_ptr = parent.data().level0[1];
let htree_ptr = HTreePtr::new(HTreeHash::MAX, dir_ptr);
fake_htree_node.data_mut().ptrs[0] = htree_ptr;
fake_htree_node
} else {
let htree_record_ptr = parent.data().level0[1];
let htree_ptr: BlockPtr<HTreeNode<RecordRaw>> = unsafe { htree_record_ptr.cast() };
self.read_block(htree_ptr)?
};
self.child_nodes_inner(htree_root.data(), children, htree_levels.max(1))?;
}
Ok(())
}
// Skip empty entries
if node_ptr.is_null() {
continue;
fn child_nodes_inner(
&mut self,
htree_node: &HTreeNode<RecordRaw>,
children: &mut Vec<DirEntry>,
htree_levels: usize,
) -> Result<()> {
assert!(htree_levels > 0);
if htree_levels == 1 {
for entry in htree_node.ptrs.iter().filter(|entry| !entry.is_null()) {
let dir_ptr: BlockPtr<DirList> = unsafe { entry.ptr.cast() };
let dir = self.read_block(dir_ptr)?;
for entry in dir.data().entries() {
children.push(entry);
}
children.push(*entry);
}
} else {
for entry in htree_node.ptrs.iter().filter(|entry| !entry.is_null()) {
let htree_ptr: BlockPtr<HTreeNode<RecordRaw>> = unsafe { entry.ptr.cast() };
let htree_node = self.read_block(htree_ptr)?;
self.child_nodes_inner(htree_node.data(), children, htree_levels - 1)?;
}
}
Ok(())
}
//TODO: improve performance (h-tree?)
/// Find a node that is a child of the `parent_ptr` and is named `name`.
/// Returns ENOENT if this node is not found.
pub fn find_node(&mut self, parent_ptr: TreePtr<Node>, name: &str) -> Result<TreeData<Node>> {
let parent = self.read_tree(parent_ptr)?;
let record_level = parent.data().record_level();
for block_offset in 0..(parent.data().size() / record_level.bytes()) {
let block_ptr = self.node_record_ptr(&parent, block_offset)?;
let dir_ptr: BlockPtr<DirList> = unsafe { block_ptr.cast() };
let dir = self.read_block(dir_ptr)?;
for entry in dir.data().entries.iter() {
let node_ptr = entry.node_ptr();
if !parent.data().level0[0].is_marker() {
return Err(Error::new(ENOENT));
}
// Skip empty entries
if node_ptr.is_null() {
continue;
}
let htree_levels = parent.data().level0[0].addr().level().0;
// Return node pointer if name matches
if let Some(entry_name) = entry.name() {
if entry_name == name {
//TODO: Do not require read of node
return self.read_tree(node_ptr);
}
let root_htree_node = if htree_levels == 0 {
// Create a fake root to satisfy the recursive inner_find_node function signature
let mut fake_htree_node =
BlockData::<HTreeNode<RecordRaw>>::empty(BlockAddr::default()).unwrap();
let dir_ptr = parent.data().level0[1];
let htree_ptr = HTreePtr::new(HTreeHash::MAX, dir_ptr);
fake_htree_node.data_mut().ptrs[0] = htree_ptr;
fake_htree_node
} else {
let root_htree_ptr: BlockPtr<HTreeNode<RecordRaw>> =
unsafe { parent.data().level0[1].cast() };
self.read_block(root_htree_ptr)?
};
let result = self.find_node_inner(
root_htree_node.data(),
name,
HTreeHash::from_name(name),
htree_levels.max(1),
)?;
result
.map(|(tree_node, _address)| tree_node)
.ok_or(Error::new(ENOENT))
}
fn find_node_inner(
&mut self,
parent_htree_node: &HTreeNode<RecordRaw>,
name: &str,
name_hash: HTreeHash,
htree_levels: usize,
) -> Result<Option<(TreeData<Node>, BlockAddr)>> {
assert!(htree_levels > 0);
if htree_levels == 1 {
// If we are at the leaf level, search for the name
for (_, htree_ptr) in parent_htree_node.find_ptrs_for_read(name_hash) {
let dir_ptr: BlockPtr<DirList> = unsafe { htree_ptr.ptr.cast() };
let dir = self.read_block(dir_ptr)?;
if let Some(entry) = dir.data().find_entry(name) {
let node_ptr = entry.node_ptr();
return Ok(Some(self.read_tree_and_addr(node_ptr)?));
}
}
#[cfg(feature = "log")]
log::trace!("FIND_NODE: Node not found in leaf level 1");
return Ok(None);
}
// Otherwise, search the next level of the H-tree
for (_, entry) in parent_htree_node.find_ptrs_for_read(name_hash) {
let htree_ptr: BlockPtr<HTreeNode<RecordRaw>> = unsafe { entry.ptr.cast() };
let htree_node = self.read_block(htree_ptr)?;
let result =
self.find_node_inner(htree_node.data(), name, name_hash, htree_levels - 1)?;
if let Some(node) = result {
return Ok(Some(node));
}
}
Err(Error::new(ENOENT))
#[cfg(feature = "log")]
log::trace!(
"FIND_NODE: Node not found in higher level: {}",
htree_levels
);
Ok(None)
}
// TODO: improve performance (h-tree?)
/// Create a new node in the tree with the given parameters.
pub fn create_node(
&mut self,
@@ -682,8 +798,6 @@ impl<'a, D: Disk> Transaction<'a, D> {
name: &str,
node_ptr: TreePtr<Node>,
) -> Result<()> {
self.check_name(&parent_ptr, name)?;
let mut parent = self.read_tree(parent_ptr)?;
let mut node = self.read_tree(node_ptr)?;
@@ -691,153 +805,403 @@ impl<'a, D: Disk> Transaction<'a, D> {
let links = node.data().links();
node.data_mut().set_links(links + 1);
let entry = DirEntry::new(node_ptr, name);
let dir_entry = DirEntry::new(node_ptr, name);
let dir_entry_htree_hash = HTreeHash::from_name(name);
let record_byte_size = parent.data().record_level().bytes();
let record_level = parent.data().record_level();
let record_end = parent.data().size() / record_level.bytes();
for record_offset in 0..record_end {
let mut dir_record_ptr = self.node_record_ptr(&parent, record_offset)?;
let mut dir_ptr: BlockPtr<DirList> = unsafe { dir_record_ptr.cast() };
let mut dir = self.read_block(dir_ptr)?;
// If this is a brand new directory, create the first DirList block
if !parent.data().level0[0].is_marker() {
let marker: BlockPtr<RecordRaw> = BlockPtr::marker(0);
assert!(marker.is_marker());
for old_entry in dir.data_mut().entries.iter_mut() {
if !old_entry.node_ptr().is_null() {
continue;
}
parent.data_mut().level0[0] = BlockPtr::marker(0);
assert!(parent.data().level0[0].is_marker());
// Write our new entry into the first
// free slot in this directory
*old_entry = entry;
// Create the first DirList block
let dir = BlockData::<DirList>::empty(BlockAddr::default()).unwrap();
let dir_ptr = self.sync_block(dir)?;
// Write updated blocks
dir_ptr = self.sync_block(dir)?;
dir_record_ptr = unsafe { dir_ptr.cast() };
self.sync_node_record_ptr(&mut parent, record_offset, dir_record_ptr)?;
self.sync_trees(&[parent, node])?;
// Add the DirList directly to the parent directory
parent.data_mut().level0[1] = unsafe { dir_ptr.cast() };
let size = parent.data().size() + record_byte_size;
parent.data_mut().set_size(size);
}
return Ok(());
let mut htree_levels = parent.data().level0[0].addr().level().0;
let mut htree_root = if htree_levels == 0 {
// If we have no H-tree root, create a fake one to satisfy the recurisve inner_link_node function
let mut fake_htree_node =
BlockData::<HTreeNode<RecordRaw>>::empty(BlockAddr::default()).unwrap();
let dir_ptr = parent.data().level0[1];
let htree_ptr = HTreePtr::new(HTreeHash::MAX, dir_ptr);
fake_htree_node.data_mut().ptrs[0] = htree_ptr;
fake_htree_node
} else {
// Otherwise get the real H-tree root
let htree_root_ptr: BlockPtr<HTreeNode<RecordRaw>> =
unsafe { parent.data().level0[1].cast() };
self.read_block(htree_root_ptr)?
};
let new_sibling = self.link_node_inner(
&mut parent,
htree_root.data_mut(),
dir_entry,
dir_entry_htree_hash,
htree_levels.max(1),
)?;
// If we used a fake root, and we grew beyond a single DirList block, we need to create a real root
if htree_levels == 0 && !htree_root.data().ptrs[1].is_null() {
htree_levels = 1;
parent.data_mut().level0[0] = BlockPtr::marker(1);
let size = parent.data().size() + record_byte_size;
parent.data_mut().set_size(size);
}
// If the H-tree root was split, create a new root to hold the old root as a sibling along with the new sibling
if let Some((sibling_htree_hash, unallocated_sibling)) = new_sibling {
assert!(htree_levels > 0);
// Prep the new sibling H-tree block to be added to the new root
let mut sibling =
BlockData::<HTreeNode<RecordRaw>>::empty(BlockAddr::default()).unwrap();
let _ = mem::replace(sibling.data_mut(), unallocated_sibling);
let sibling_block_ptr = self.sync_block(sibling)?;
let sibling_htree_ptr = HTreePtr::new(sibling_htree_hash, sibling_block_ptr);
let sibling_record_ptr: HTreePtr<RecordRaw> = unsafe { sibling_htree_ptr.cast() };
// Prep the existing H-tree root to become a sibling
let root_htree_hash = htree_root
.data()
.find_max_htree_hash()
.ok_or(Error::new(EIO))?;
let root_block_ptr = self.sync_block(htree_root)?;
let root_htree_ptr = HTreePtr::new(root_htree_hash, root_block_ptr);
let root_record_ptr: HTreePtr<RecordRaw> = unsafe { root_htree_ptr.cast() };
// Create the new root H-tree block
let mut new_root =
BlockData::<HTreeNode<RecordRaw>>::empty(BlockAddr::default()).unwrap();
new_root.data_mut().ptrs[0] = sibling_record_ptr;
let unexpected_sibling = htree::add_inner_node(new_root.data_mut(), root_record_ptr)?;
assert!(unexpected_sibling.is_none());
let new_root_ptr = self.sync_block(new_root)?;
// Add the parent node pointer, increase the level, and increase one block size per allocated block
parent.data_mut().level0[0] = BlockPtr::marker(htree_levels as u8 + 1);
parent.data_mut().level0[1] = unsafe { new_root_ptr.cast() };
let size = parent.data().size() + 2 * record_byte_size;
parent.data_mut().set_size(size);
} else if htree_levels > 0 {
// Update the parent node with the new root pointer
let root_block_ptr = self.sync_block(htree_root)?;
parent.data_mut().level0[1] = unsafe { root_block_ptr.cast() };
} else {
// Update the parent with the DirList block, ignoring the fake htree_root
parent.data_mut().level0[1] = htree_root.data().ptrs[0].ptr;
}
self.sync_trees(&[parent, node])?;
Ok(())
}
fn link_node_inner(
&mut self,
parent_dir_node: &mut TreeData<Node>,
parent_htree_node: &mut HTreeNode<RecordRaw>,
dir_entry: DirEntry,
dir_entry_htree_hash: HTreeHash,
htree_levels: usize,
) -> Result<Option<(HTreeHash, HTreeNode<RecordRaw>)>> {
let record_byte_size = parent_dir_node.data().record_level().bytes();
// Find the entry to update
let mut htree_ptr = parent_htree_node.ptrs[0];
let mut htree_ptr_idx = 0;
for (idx, entry) in parent_htree_node.ptrs.iter().enumerate() {
if entry.is_null() {
break;
}
htree_ptr = *entry;
htree_ptr_idx = idx;
if htree_ptr.htree_hash >= dir_entry_htree_hash {
break;
}
}
// We couldn't find a free direntry slot, this directory is full.
// We now need to add a new dirlist block to the parent node,
// with `entry` as its first member.
// The recursion terminates by processing the last inner node
assert!(htree_levels > 0);
if htree_levels == 1 {
// Add the entry to the DirList block
let dir_ptr: BlockPtr<DirList> = unsafe { htree_ptr.ptr.cast() };
let mut dir = self.read_block(dir_ptr)?;
let unallocated_sibling =
htree::add_dir_entry(dir.data_mut(), &mut htree_ptr.htree_hash, dir_entry)?;
let dir_record_ptr = unsafe { self.sync_block(dir)?.cast() };
parent_htree_node.ptrs[htree_ptr_idx] =
HTreePtr::new(htree_ptr.htree_hash, dir_record_ptr);
let mut dir =
BlockData::<DirList>::empty(unsafe { self.allocate(BlockLevel::default())? }).unwrap();
dir.data_mut().entries[0] = entry;
let dir_ptr = unsafe { self.write_block(dir)? };
let dir_record_ptr = unsafe { dir_ptr.cast() };
if let Some((new_hash, new_unallocated_dir)) = unallocated_sibling {
// The DirList block was split, so we need to add it to the h-tree
let mut dir = BlockData::<DirList>::empty(BlockAddr::default()).unwrap();
let _ = mem::replace(dir.data_mut(), new_unallocated_dir);
let dir_ptr = self.sync_block(dir)?;
let dir_htree_ptr = HTreePtr::new(new_hash, dir_ptr);
let dir_record_ptr: HTreePtr<RecordRaw> = unsafe { dir_htree_ptr.cast() };
let size = parent_dir_node.data().size() + record_byte_size;
parent_dir_node.data_mut().set_size(size);
self.sync_node_record_ptr(&mut parent, record_end, dir_record_ptr)?;
parent
// We mutate the parent, but let the caller write the parent to disk
return htree::add_inner_node(parent_htree_node, dir_record_ptr);
}
return Ok(None);
}
// Recursively insert the entry into the next H-tree level
let htree_block_ptr: BlockPtr<HTreeNode<RecordRaw>> = unsafe { htree_ptr.ptr.cast() };
let mut htree_block = self.read_block(htree_block_ptr)?;
let unallocated_sibling = self.link_node_inner(
parent_dir_node,
htree_block.data_mut(),
dir_entry,
dir_entry_htree_hash,
htree_levels - 1,
)?;
// Write the muteated H-tree block back to disk and update the parent node's pointer
let htree_hash = htree_block.data().find_max_htree_hash().unwrap();
let htree_block_ptr = self.sync_block(htree_block)?;
let htree_record_ptr: BlockPtr<RecordRaw> = unsafe { htree_block_ptr.cast() };
parent_htree_node.ptrs[htree_ptr_idx] = HTreePtr::new(htree_hash, htree_record_ptr);
// If the inner insert function returns a new H-tree sibling block, write it and add it to the parent H-tree node
if let Some((new_hash, new_unallocated_sibling)) = unallocated_sibling {
let mut sibling =
BlockData::<HTreeNode<RecordRaw>>::empty(BlockAddr::default()).unwrap();
let _ = mem::replace(sibling.data_mut(), new_unallocated_sibling);
let sibling_ptr = self.sync_block(sibling)?;
let sibling_htree_ptr = HTreePtr::new(new_hash, sibling_ptr);
let sibling_record_ptr: HTreePtr<RecordRaw> = unsafe { sibling_htree_ptr.cast() };
let size = parent_dir_node.data().size() + record_byte_size;
parent_dir_node.data_mut().set_size(size);
// We mutate the parent, but let the caller write the parent to disk
return htree::add_inner_node(parent_htree_node, sibling_record_ptr);
}
Ok(None)
}
pub fn remove_node(&mut self, parent_ptr: TreePtr<Node>, name: &str, mode: u16) -> Result<()> {
#[cfg(feature = "log")]
log::debug!(
"REMOVE_NODE: name: {}, mode: {:x}, parent_ptr: {:?}",
name,
mode,
parent_ptr.indexes()
);
let mut parent = self.read_tree(parent_ptr)?;
if !parent.data().level0[0].is_marker() {
#[cfg(feature = "log")]
log::error!("REMOVE_NODE: Parent has no htree marker set (not a directory or empty)");
return Err(Error::new(ENOENT));
}
let htree_levels = parent.data().level0[0].addr().level().0;
let name_hash = HTreeHash::from_name(name);
let mut htree_root = if htree_levels == 0 {
// If we have no H-tree root, create a fake one to satisfy the recurisve inner_link_node function
let mut fake_htree_node =
BlockData::<HTreeNode<RecordRaw>>::empty(BlockAddr::default()).unwrap();
let dir_ptr = parent.data().level0[1];
let htree_ptr = HTreePtr::new(HTreeHash::MAX, dir_ptr);
fake_htree_node.data_mut().ptrs[0] = htree_ptr;
fake_htree_node
} else {
// Otherwise get the real H-tree root
let htree_root_record_ptr = parent.data().level0[1];
let htree_root_ptr: BlockPtr<HTreeNode<RecordRaw>> =
unsafe { htree_root_record_ptr.cast() };
self.read_block(htree_root_ptr)?
};
// Read node and test type against requested type
// TODO: Do this check as part of the removal tree processing, and get rid of this extra find
let (mut node, node_addr) = self
.find_node_inner(htree_root.data(), name, name_hash, htree_levels.max(1))?
.ok_or(Error::new(ENOENT))?;
if mode & Node::MODE_TYPE == Node::MODE_DIR {
if !node.data().is_dir() {
// Found a file instead of a directory
return Err(Error::new(ENOTDIR));
} else if node.data().size() > 0 && node.data().links() == 1 {
// Tried to remove directory that still has entries
return Err(Error::new(ENOTEMPTY));
}
// The directory will be removed.
} else {
if node.data().is_dir() {
// Found a directory instead of file
return Err(Error::new(EISDIR));
}
// The non-directory entry will be removed.
}
let links = node.data().links();
let remove_node = if links > 1 {
node.data_mut().set_links(links - 1);
false
} else {
node.data_mut().set_links(0);
self.truncate_node_inner(&mut node, 0)?;
true
};
// Recursively remove the node from the H-tree, removing empty H-tree nodes
self.remove_node_inner(
&mut parent,
htree_root.data_mut(),
name,
name_hash,
htree_levels.max(1),
)?;
htree_root
.data_mut()
.set_size((record_end + 1) * record_level.bytes());
self.sync_trees(&[parent, node])?;
.ptrs
.sort_by(|a, b| a.htree_hash.cmp(&b.htree_hash));
if htree_root.data().ptrs[0].is_null() {
// Dealocate the htree_root only if it was a real root node in the H-tree
if htree_levels > 0 {
unsafe {
self.deallocate(htree_root.addr());
}
let record_byte_size = parent.data().record_level().bytes();
let size = parent.data().size() - record_byte_size;
parent.data_mut().set_size(size);
}
parent.data_mut().level0[0] = BlockPtr::default();
parent.data_mut().level0[1] = BlockPtr::default();
} else if htree_levels > 0 {
// Update the real htree_root and update the ptr in the parent
let htree_root_block_ptr = self.sync_block(htree_root)?;
parent.data_mut().level0[1] = unsafe { htree_root_block_ptr.cast() };
} else {
// The htree_root is fake, so update the parent with the ptr to the one and only directory list
let dir_list_block_ptr = htree_root.data().ptrs[0].ptr;
parent.data_mut().level0[1] = unsafe { dir_list_block_ptr.cast() };
}
if remove_node {
self.sync_tree(parent)?;
self.remove_tree(node.ptr())?;
unsafe {
self.deallocate(node_addr);
}
} else {
// Sync both parent and node at the same time
self.sync_trees(&[parent, node])?;
}
Ok(())
}
pub fn remove_node(&mut self, parent_ptr: TreePtr<Node>, name: &str, mode: u16) -> Result<()> {
let mut parent = self.read_tree(parent_ptr)?;
let record_level = parent.data().record_level();
let records = parent.data().size() / record_level.bytes();
for record_offset in 0..records {
let mut dir_record_ptr = self.node_record_ptr(&parent, record_offset)?;
let mut dir_ptr: BlockPtr<DirList> = unsafe { dir_record_ptr.cast() };
let mut dir = self.read_block(dir_ptr)?;
let mut node_opt = None;
for entry in dir.data_mut().entries.iter_mut() {
let node_ptr = entry.node_ptr();
fn remove_node_inner(
&mut self,
parent_dir_node: &mut TreeData<Node>,
parent_htree_node: &mut HTreeNode<RecordRaw>,
dir_entry_name: &str,
dir_entry_htree_hash: HTreeHash,
htree_levels: usize,
) -> Result<()> {
let record_byte_size = parent_dir_node.data().record_level().bytes();
// Skip empty entries
if node_ptr.is_null() {
// Process every node that could hold the entry
assert!(htree_levels > 0);
let relevant_entry_indexes: Vec<usize> = parent_htree_node
.find_ptrs_for_read(dir_entry_htree_hash)
.map(|x| x.0)
.collect();
for entry_idx in relevant_entry_indexes {
let entry_ptr = parent_htree_node.ptrs[entry_idx];
if htree_levels == 1 {
let dir_ptr: BlockPtr<DirList> = unsafe { entry_ptr.ptr.cast() };
let mut dir_list = self.read_block(dir_ptr)?;
// If we don't find the entry to remove, continue to the next relevant node
if !dir_list.data_mut().remove_entry(dir_entry_name) {
continue;
}
// Check if name matches
if let Some(entry_name) = entry.name() {
if entry_name == name {
// Read node and test type against requested type
let (node, addr) = self.read_tree_and_addr(node_ptr)?;
if node.data().mode() & Node::MODE_TYPE == mode {
if node.data().is_dir()
&& node.data().size() > 0
&& node.data().links() == 1
{
// Tried to remove directory that still has entries
return Err(Error::new(ENOTEMPTY));
}
// Save node and clear entry
node_opt = Some((entry.node_ptr(), node, addr));
*entry = DirEntry::default();
break;
} else if node.data().is_dir() {
// Found directory instead of requested type
return Err(Error::new(EISDIR));
} else {
// Did not find directory when requested
return Err(Error::new(ENOTDIR));
}
}
}
}
if let Some((node_tree_ptr, mut node, addr)) = node_opt {
let links = node.data().links();
let remove_node = if links > 1 {
node.data_mut().set_links(links - 1);
false
// Determine if the htree_hash needs to be updated
let new_htree_hash = if dir_entry_htree_hash == HTreeHash::from_name(dir_entry_name)
{
HTreeHash::find_max(dir_list.data())
} else {
node.data_mut().set_links(0);
self.truncate_node_inner(&mut node, 0)?;
true
Some(dir_entry_htree_hash)
};
if record_offset == records - 1 && dir.data().is_empty() {
let mut remove_record = record_offset;
loop {
// Remove empty parent record, if it is at the end
self.remove_node_record_ptr(&mut parent, remove_record)?;
parent
.data_mut()
.set_size(remove_record * record_level.bytes());
// Keep going for any other empty records
if remove_record > 0 {
remove_record -= 1;
dir_record_ptr = self.node_record_ptr(&parent, remove_record)?;
dir_ptr = unsafe { dir_record_ptr.cast() };
dir = self.read_block(dir_ptr)?;
if dir.data().is_empty() {
continue;
}
}
break;
}
if let Some(new_tree_hash) = new_htree_hash {
// The entry_ptr needs to be updated in the parent_htree_node
let dir_block_ptr = self.sync_block(dir_list)?;
let dir_record_ptr: BlockPtr<RecordRaw> = unsafe { dir_block_ptr.cast() };
parent_htree_node.ptrs[entry_idx] =
HTreePtr::new(new_tree_hash, dir_record_ptr);
} else {
// Save new parent record
dir_ptr = self.sync_block(dir)?;
dir_record_ptr = unsafe { dir_ptr.cast() };
self.sync_node_record_ptr(&mut parent, record_offset, dir_record_ptr)?;
// The entry needs to be removed from the parent_htree_noce
parent_htree_node.ptrs[entry_idx] = HTreePtr::default();
unsafe { self.deallocate(dir_list.addr()) };
let size = parent_dir_node.data().size() - record_byte_size;
parent_dir_node.data_mut().set_size(size);
}
return Ok(());
} else {
let htree_ptr: BlockPtr<HTreeNode<RecordRaw>> = unsafe { entry_ptr.ptr.cast() };
let mut htree_node = self.read_block(htree_ptr)?;
let result = self.remove_node_inner(
parent_dir_node,
htree_node.data_mut(),
dir_entry_name,
dir_entry_htree_hash,
htree_levels - 1,
);
// If the removal attempt resulted in ENOENT, iterate to look at the next relevant node
if result.is_err() && result.err().unwrap().errno == ENOENT {
continue;
}
if remove_node {
self.sync_tree(parent)?;
self.remove_tree(node_tree_ptr)?;
unsafe {
self.deallocate(addr);
}
} else {
// Sync both parent and node at the same time
self.sync_trees(&[parent, node])?;
}
// In case it is some other err
result?;
// Sort entries, moving them to the start of the ptrs array in H-tree hash order
htree_node
.data_mut()
.ptrs
.sort_by(|a, b| a.htree_hash.cmp(&b.htree_hash));
if let Some(new_htree_hash) = htree_node.data().find_max_htree_hash() {
// The entry_ptr needs to be updated in the parent_htree_node
let htree_block_ptr = self.sync_block(htree_node)?;
let htree_record_ptr: BlockPtr<RecordRaw> = unsafe { htree_block_ptr.cast() };
parent_htree_node.ptrs[entry_idx] =
HTreePtr::new(new_htree_hash, htree_record_ptr);
} else {
// The htree_node is now empty, so remove it
parent_htree_node.ptrs[entry_idx] = HTreePtr::default();
unsafe { self.deallocate(htree_node.addr()) };
let size = parent_dir_node.data().size() - record_byte_size;
parent_dir_node.data_mut().set_size(size);
}
return Ok(());
}
}
Err(Error::new(ENOENT))
}
@@ -867,6 +1231,7 @@ impl<'a, D: Disk> Transaction<'a, D> {
}
// Link original file to new name
self.check_name(&new_parent_ptr, new_name)?;
self.link_node(new_parent_ptr, new_name, orig.ptr())?;
// Remove original file
@@ -888,7 +1253,8 @@ impl<'a, D: Disk> Transaction<'a, D> {
return Err(Error::new(EINVAL));
}
if self.find_node(parent_ptr.clone(), name).is_ok() {
// TODO: Can this be removed if link_node satisfies this check itself?
if self.find_node(*parent_ptr, name).is_ok() {
return Err(Error::new(EEXIST));
}
@@ -1175,9 +1541,8 @@ impl<'a, D: Disk> Transaction<'a, D> {
assert_eq!(offset, size);
} else {
// Deallocate records
for record in ((size + record_level.bytes() - 1) / record_level.bytes()
..old_size / record_level.bytes())
.rev()
for record in
(size.div_ceil(record_level.bytes())..old_size / record_level.bytes()).rev()
{
self.remove_node_record_ptr(node, record)?;
}
@@ -1222,7 +1587,7 @@ impl<'a, D: Disk> Transaction<'a, D> {
let mut node_changed = false;
let record_level = node.data().record_level();
let node_records = (node.data().size() + record_level.bytes() - 1) / record_level.bytes();
let node_records = node.data().size().div_ceil(record_level.bytes());
let mut i = 0;
while i < buf.len() {
+105 -8
View File
@@ -5,7 +5,7 @@ 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;
const TREE_LIST_ENTRIES: usize = (1 << TREE_LIST_SHIFT) - 2;
/// A tree with 4 levels
pub type Tree = TreeList<TreeList<TreeList<TreeList<BlockRaw>>>>;
@@ -54,6 +54,41 @@ impl<T> TreeData<T> {
#[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> {
@@ -61,6 +96,7 @@ unsafe impl<T> BlockTrait for TreeList<T> {
if level.0 == 0 {
Some(Self {
ptrs: [BlockPtr::default(); TREE_LIST_ENTRIES],
full_flags: [0; 2],
})
} else {
None
@@ -153,7 +189,19 @@ impl<T> TreePtr<T> {
let i1 = ((id >> SHIFT) & MASK) as usize;
let i0 = (id & MASK) as usize;
return (i3, i2, i1, i0);
(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,
}
}
}
@@ -174,10 +222,59 @@ impl<T> Default for TreePtr<T> {
}
}
#[test]
fn tree_list_size_test() {
assert_eq!(
mem::size_of::<TreeList<BlockRaw>>(),
crate::BLOCK_SIZE as usize
);
#[cfg(test)]
mod tests {
use crate::{BlockAddr, BlockData};
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, BlockLevel::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());
}
}