Compare commits
1 commit
main
...
short-name
| Author | SHA1 | Date | |
|---|---|---|---|
| 7921064ae2 |
21 changed files with 844 additions and 2695 deletions
393
Cargo.lock
generated
393
Cargo.lock
generated
|
|
@ -2,15 +2,6 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aho-corasick"
|
|
||||||
version = "1.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android-tzdata"
|
name = "android-tzdata"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
|
@ -26,56 +17,6 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstream"
|
|
||||||
version = "0.6.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
|
|
||||||
dependencies = [
|
|
||||||
"anstyle",
|
|
||||||
"anstyle-parse",
|
|
||||||
"anstyle-query",
|
|
||||||
"anstyle-wincon",
|
|
||||||
"colorchoice",
|
|
||||||
"is_terminal_polyfill",
|
|
||||||
"utf8parse",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle"
|
|
||||||
version = "1.0.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-parse"
|
|
||||||
version = "0.2.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
|
||||||
dependencies = [
|
|
||||||
"utf8parse",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-query"
|
|
||||||
version = "1.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-wincon"
|
|
||||||
version = "3.0.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
|
|
||||||
dependencies = [
|
|
||||||
"anstyle",
|
|
||||||
"once_cell_polyfill",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.98"
|
version = "1.0.98"
|
||||||
|
|
@ -100,21 +41,6 @@ version = "3.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "byteorder"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "castaway"
|
|
||||||
version = "0.2.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
|
|
||||||
dependencies = [
|
|
||||||
"rustversion",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.30"
|
version = "1.2.30"
|
||||||
|
|
@ -148,52 +74,12 @@ dependencies = [
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "colorchoice"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "compact_str"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a"
|
|
||||||
dependencies = [
|
|
||||||
"castaway",
|
|
||||||
"cfg-if",
|
|
||||||
"itoa",
|
|
||||||
"rustversion",
|
|
||||||
"ryu",
|
|
||||||
"static_assertions",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "compact_string"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5255b88d8ea09573f588088dea17fbea682b4442abea6761a15d1da2c3a76c5c"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
"thiserror 1.0.69",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ctrlc"
|
|
||||||
version = "3.4.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73"
|
|
||||||
dependencies = [
|
|
||||||
"nix 0.30.1",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "enum_dispatch"
|
name = "enum_dispatch"
|
||||||
version = "0.3.13"
|
version = "0.3.13"
|
||||||
|
|
@ -206,29 +92,6 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[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]]
|
[[package]]
|
||||||
name = "fat-bits"
|
name = "fat-bits"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -236,11 +99,9 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"chrono",
|
"chrono",
|
||||||
"compact_str",
|
|
||||||
"enum_dispatch",
|
"enum_dispatch",
|
||||||
"log",
|
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
"thiserror 2.0.12",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -256,27 +117,13 @@ name = "fat-fuse"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags",
|
|
||||||
"chrono",
|
"chrono",
|
||||||
"compact_string",
|
|
||||||
"fat-bits",
|
"fat-bits",
|
||||||
"fuser",
|
"fuser",
|
||||||
"fxhash",
|
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"rand",
|
"rand",
|
||||||
"thiserror 2.0.12",
|
"thiserror",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fat-mount"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"ctrlc",
|
|
||||||
"env_logger",
|
|
||||||
"fat-fuse",
|
|
||||||
"fuser",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -288,22 +135,13 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"memchr",
|
"memchr",
|
||||||
"nix 0.29.0",
|
"nix",
|
||||||
"page_size",
|
"page_size",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fxhash"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
|
@ -340,42 +178,6 @@ dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "is_terminal_polyfill"
|
|
||||||
version = "1.70.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itoa"
|
|
||||||
version = "1.0.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "jiff"
|
|
||||||
version = "0.2.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
|
|
||||||
dependencies = [
|
|
||||||
"jiff-static",
|
|
||||||
"log",
|
|
||||||
"portable-atomic",
|
|
||||||
"portable-atomic-util",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "jiff-static"
|
|
||||||
version = "0.2.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.77"
|
version = "0.3.77"
|
||||||
|
|
@ -416,18 +218,6 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nix"
|
|
||||||
version = "0.30.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"cfg-if",
|
|
||||||
"cfg_aliases",
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
|
|
@ -443,12 +233,6 @@ version = "1.21.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "once_cell_polyfill"
|
|
||||||
version = "1.70.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "page_size"
|
name = "page_size"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
|
@ -465,21 +249,6 @@ version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "portable-atomic"
|
|
||||||
version = "1.11.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
|
||||||
|
|
||||||
[[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]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.95"
|
version = "1.0.95"
|
||||||
|
|
@ -522,67 +291,12 @@ dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex"
|
|
||||||
version = "1.11.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick",
|
|
||||||
"memchr",
|
|
||||||
"regex-automata",
|
|
||||||
"regex-syntax",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-automata"
|
|
||||||
version = "0.4.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
|
||||||
dependencies = [
|
|
||||||
"aho-corasick",
|
|
||||||
"memchr",
|
|
||||||
"regex-syntax",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-syntax"
|
|
||||||
version = "0.8.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.21"
|
version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ryu"
|
|
||||||
version = "1.0.20"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
|
||||||
|
|
||||||
[[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]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
|
@ -612,33 +326,13 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror"
|
|
||||||
version = "1.0.69"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror-impl 1.0.69",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.12"
|
version = "2.0.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl 2.0.12",
|
"thiserror-impl",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror-impl"
|
|
||||||
version = "1.0.69"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -658,12 +352,6 @@ version = "1.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "utf8parse"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.14.2+wasi-0.2.4"
|
version = "0.14.2+wasi-0.2.4"
|
||||||
|
|
@ -812,79 +500,6 @@ dependencies = [
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[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]]
|
[[package]]
|
||||||
name = "wit-bindgen-rt"
|
name = "wit-bindgen-rt"
|
||||||
version = "0.39.0"
|
version = "0.39.0"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["fat-bits", "fat-dump", "fat-fuse", "fat-mount"]
|
members = ["fat-bits", "fat-dump", "fat-fuse"]
|
||||||
|
|
|
||||||
10
README.md
10
README.md
|
|
@ -1,9 +1 @@
|
||||||
# FUSE implementation for the FAT file system
|
specification: [https://academy.cba.mit.edu/classes/networking_communications/SD/FAT.pdf](https://academy.cba.mit.edu/classes/networking_communications/SD/FAT.pdf)
|
||||||
|
|
||||||
End-to-end implementation of a FAT driver, with the goal of being able to mount disk images with complete read-write support.
|
|
||||||
|
|
||||||
Uses (fuser)[https://docs.rs/fuser/latest/fuser/] as the underlying FUSE library.
|
|
||||||
|
|
||||||
## Specification
|
|
||||||
|
|
||||||
[https://academy.cba.mit.edu/classes/networking_communications/SD/FAT.pdf](https://academy.cba.mit.edu/classes/networking_communications/SD/FAT.pdf)
|
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,8 @@ anyhow = "1.0.98"
|
||||||
bitflags = "2.9.1"
|
bitflags = "2.9.1"
|
||||||
chrono = { version = "0.4.41", default-features = false, features = [
|
chrono = { version = "0.4.41", default-features = false, features = [
|
||||||
"alloc",
|
"alloc",
|
||||||
"clock",
|
|
||||||
"std",
|
"std",
|
||||||
] }
|
] }
|
||||||
compact_str = "0.9.0"
|
|
||||||
enum_dispatch = "0.3.13"
|
enum_dispatch = "0.3.13"
|
||||||
log = "0.4.27"
|
|
||||||
static_assertions = "1.1.0"
|
static_assertions = "1.1.0"
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
|
|
|
||||||
|
|
@ -236,7 +236,6 @@ impl Bpb {
|
||||||
if self.fat_type() == FatType::Fat32 {
|
if self.fat_type() == FatType::Fat32 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(self.fat_offset() + self.sector_to_offset(self.num_fats() as u32 * self.fat_size()))
|
Some(self.fat_offset() + self.sector_to_offset(self.num_fats() as u32 * self.fat_size()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
use chrono::{DateTime, Datelike, Local, NaiveDate, NaiveTime, Timelike};
|
use std::time::SystemTime;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
use chrono::{DateTime, Datelike, NaiveDate, NaiveTime, Timelike, Utc};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Date {
|
pub struct Date {
|
||||||
repr: u16,
|
repr: u16,
|
||||||
}
|
}
|
||||||
|
|
@ -20,7 +22,8 @@ impl Date {
|
||||||
Ok(date)
|
Ok(date)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_day_month_year(day: u8, month: u8, year: u16) -> anyhow::Result<Date> {
|
#[allow(dead_code)]
|
||||||
|
pub fn from_day_month_year(day: u8, month: u8, year: u16) -> anyhow::Result<Date> {
|
||||||
anyhow::ensure!(day <= 31, "invalid day: {}", day);
|
anyhow::ensure!(day <= 31, "invalid day: {}", day);
|
||||||
anyhow::ensure!(month <= 12, "invalid month: {}", month);
|
anyhow::ensure!(month <= 12, "invalid month: {}", month);
|
||||||
anyhow::ensure!(1980 <= year && year <= 2107, "invalid year: {}", year);
|
anyhow::ensure!(1980 <= year && year <= 2107, "invalid year: {}", year);
|
||||||
|
|
@ -30,7 +33,10 @@ impl Date {
|
||||||
Ok(Date { repr })
|
Ok(Date { repr })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_datetime(datetime: DateTime<Local>) -> anyhow::Result<Date> {
|
#[allow(dead_code)]
|
||||||
|
pub fn from_system_time(time: SystemTime) -> anyhow::Result<Date> {
|
||||||
|
let datetime: DateTime<Utc> = time.into();
|
||||||
|
|
||||||
let date = datetime.date_naive();
|
let date = datetime.date_naive();
|
||||||
|
|
||||||
Date::from_day_month_year(
|
Date::from_day_month_year(
|
||||||
|
|
@ -40,10 +46,6 @@ impl Date {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn repr(&self) -> u16 {
|
|
||||||
self.repr
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn day(&self) -> u8 {
|
pub fn day(&self) -> u8 {
|
||||||
(self.repr & 0x1F) as u8
|
(self.repr & 0x1F) as u8
|
||||||
}
|
}
|
||||||
|
|
@ -61,7 +63,7 @@ impl Date {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug)]
|
||||||
pub struct Time {
|
pub struct Time {
|
||||||
repr: u16,
|
repr: u16,
|
||||||
}
|
}
|
||||||
|
|
@ -73,7 +75,8 @@ impl Time {
|
||||||
Ok(time)
|
Ok(time)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_seconds_minutes_hours(seconds: u8, minutes: u8, hours: u8) -> anyhow::Result<Time> {
|
#[allow(dead_code)]
|
||||||
|
pub fn from_seconds_minutes_hours(seconds: u8, minutes: u8, hours: u8) -> anyhow::Result<Time> {
|
||||||
anyhow::ensure!(seconds <= 58 && seconds % 2 == 0, "invalid seconds: {}", seconds);
|
anyhow::ensure!(seconds <= 58 && seconds % 2 == 0, "invalid seconds: {}", seconds);
|
||||||
anyhow::ensure!(minutes <= 59, "invalid minutes: {}", minutes);
|
anyhow::ensure!(minutes <= 59, "invalid minutes: {}", minutes);
|
||||||
anyhow::ensure!(hours <= 23, "invalid hours: {}", hours);
|
anyhow::ensure!(hours <= 23, "invalid hours: {}", hours);
|
||||||
|
|
@ -83,7 +86,10 @@ impl Time {
|
||||||
Ok(Time { repr })
|
Ok(Time { repr })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_datetime(datetime: DateTime<Local>) -> anyhow::Result<Time> {
|
#[allow(dead_code)]
|
||||||
|
pub fn from_system_time(time: SystemTime) -> anyhow::Result<Time> {
|
||||||
|
let datetime: DateTime<Utc> = time.into();
|
||||||
|
|
||||||
let time = datetime.time();
|
let time = datetime.time();
|
||||||
|
|
||||||
let seconds = (time.second() as u8) & !0x01;
|
let seconds = (time.second() as u8) & !0x01;
|
||||||
|
|
@ -91,10 +97,6 @@ impl Time {
|
||||||
Time::from_seconds_minutes_hours(seconds, time.minute() as u8, time.hour() as u8)
|
Time::from_seconds_minutes_hours(seconds, time.minute() as u8, time.hour() as u8)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn repr(&self) -> u16 {
|
|
||||||
self.repr
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn second(&self) -> u8 {
|
pub fn second(&self) -> u8 {
|
||||||
2 * (self.repr & 0x1F) as u8
|
2 * (self.repr & 0x1F) as u8
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,10 @@
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::io::{Read, Write};
|
use std::io::Read;
|
||||||
use std::time::SystemTime;
|
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, TimeDelta, Timelike};
|
use chrono::{NaiveDate, NaiveDateTime, TimeDelta};
|
||||||
use compact_str::CompactString;
|
|
||||||
use log::debug;
|
|
||||||
|
|
||||||
use crate::FatFs;
|
|
||||||
use crate::datetime::{Date, Time};
|
use crate::datetime::{Date, Time};
|
||||||
use crate::iter::ClusterChainReader;
|
|
||||||
use crate::subslice::SubSliceMut;
|
|
||||||
use crate::utils::{load_u16_le, load_u32_le};
|
use crate::utils::{load_u16_le, load_u32_le};
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
|
@ -70,29 +64,30 @@ pub struct DirEntry {
|
||||||
|
|
||||||
file_size: u32,
|
file_size: u32,
|
||||||
|
|
||||||
checksum: u8,
|
// buffer for holding short name str representation
|
||||||
long_name: Option<CompactString>,
|
// initial dot if hidden (1)
|
||||||
|
// stem (8)
|
||||||
n_longname_slots: u8,
|
// dot (1)
|
||||||
|
// extension (3)
|
||||||
offset: u64,
|
short_name: [u8; 13],
|
||||||
|
long_name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for DirEntry {
|
impl Display for DirEntry {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let mut name = self.name_string();
|
let name = self.name_str().unwrap_or("<unknown>");
|
||||||
|
|
||||||
if self.attr.contains(Attr::Directory) {
|
// add slash to end of dir_names
|
||||||
name.push('/');
|
let dir_slash = if self.is_dir() { "/" } else { " " };
|
||||||
}
|
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{} {}",
|
"{} {}{}",
|
||||||
self.attr,
|
self.attr,
|
||||||
// self.create_time().format("%a %b %d %H:%M:%S%.3f %Y"),
|
// self.create_time().format("%a %b %d %H:%M:%S%.3f %Y"),
|
||||||
// self.write_time().format("%a %b %d %H:%M:%S%.3f %Y"),
|
// self.write_time().format("%a %b %d %H:%M:%S%.3f %Y"),
|
||||||
name,
|
name,
|
||||||
|
dir_slash,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -100,13 +95,12 @@ impl Display for DirEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DirEntry {
|
impl DirEntry {
|
||||||
pub fn load(bytes: &[u8], offset: u64) -> anyhow::Result<DirEntry> {
|
pub fn load(bytes: &[u8]) -> anyhow::Result<DirEntry> {
|
||||||
assert_eq!(bytes.len(), 32);
|
assert_eq!(bytes.len(), 32);
|
||||||
|
|
||||||
|
let name: [u8; 11] = bytes[..11].try_into().unwrap();
|
||||||
let attr = Attr::from_bits_truncate(bytes[11]);
|
let attr = Attr::from_bits_truncate(bytes[11]);
|
||||||
|
|
||||||
let name = bytes[..11].try_into().unwrap();
|
|
||||||
|
|
||||||
let create_time_tenths = bytes[13];
|
let create_time_tenths = bytes[13];
|
||||||
anyhow::ensure!(
|
anyhow::ensure!(
|
||||||
create_time_tenths <= 199,
|
create_time_tenths <= 199,
|
||||||
|
|
@ -142,6 +136,32 @@ impl DirEntry {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut short_name = [0; 13];
|
||||||
|
|
||||||
|
let mut short_name_iter = short_name.iter_mut();
|
||||||
|
|
||||||
|
if attr.contains(Attr::Hidden) {
|
||||||
|
// if hidden: lead with '.'
|
||||||
|
*short_name_iter.next().unwrap() = b'.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if name[0] != 0 && name[0] != 0xe5 {
|
||||||
|
// dir_entry is not free
|
||||||
|
|
||||||
|
for c in name[..8].iter().filter(|&x| x != &0x20) {
|
||||||
|
*short_name_iter.next().unwrap() = *c;
|
||||||
|
}
|
||||||
|
|
||||||
|
if &name[8..] != &[0x20, 0x20, 0x20] {
|
||||||
|
// if extension is not empty: add dot and extension
|
||||||
|
*short_name_iter.next().unwrap() = b'.';
|
||||||
|
|
||||||
|
for c in name[8..].iter().filter(|&x| x != &0x20) {
|
||||||
|
*short_name_iter.next().unwrap() = *c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(DirEntry {
|
Ok(DirEntry {
|
||||||
name,
|
name,
|
||||||
attr,
|
attr,
|
||||||
|
|
@ -153,95 +173,11 @@ impl DirEntry {
|
||||||
write_time,
|
write_time,
|
||||||
write_date,
|
write_date,
|
||||||
file_size,
|
file_size,
|
||||||
|
short_name,
|
||||||
long_name: None,
|
long_name: None,
|
||||||
n_longname_slots: 0,
|
|
||||||
checksum: Self::checksum(&bytes[..11]),
|
|
||||||
offset,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create(_name: &str, attr: Attr) -> anyhow::Result<Self> {
|
|
||||||
// TODO
|
|
||||||
let now: DateTime<Local> = SystemTime::now().into();
|
|
||||||
|
|
||||||
let create_date = Date::from_datetime(now)?;
|
|
||||||
let create_time = Time::from_datetime(now)?;
|
|
||||||
let create_time_tenths = (now.time().nanosecond() / 100_000_000) as u8;
|
|
||||||
|
|
||||||
let name = [0; 11];
|
|
||||||
|
|
||||||
Ok(DirEntry {
|
|
||||||
name,
|
|
||||||
attr,
|
|
||||||
create_time_tenths,
|
|
||||||
create_time,
|
|
||||||
create_date,
|
|
||||||
last_access_date: create_date,
|
|
||||||
first_cluster: 0,
|
|
||||||
write_time: create_time,
|
|
||||||
write_date: create_date,
|
|
||||||
file_size: 0,
|
|
||||||
checksum: Self::checksum(&name),
|
|
||||||
long_name: None,
|
|
||||||
n_longname_slots: 0,
|
|
||||||
offset: !0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&self, mut writer: impl Write) -> std::io::Result<()> {
|
|
||||||
let mut buf = [0; 32];
|
|
||||||
|
|
||||||
buf[..11].copy_from_slice(self.name());
|
|
||||||
|
|
||||||
buf[11] = self.attr().bits();
|
|
||||||
|
|
||||||
buf[12] = 0;
|
|
||||||
|
|
||||||
buf[13] = self.create_time_tenths;
|
|
||||||
buf[13..15].copy_from_slice(&self.create_time.repr().to_le_bytes());
|
|
||||||
|
|
||||||
buf[16..18].copy_from_slice(&self.create_date.repr().to_le_bytes());
|
|
||||||
|
|
||||||
buf[18..20].copy_from_slice(&self.last_access_date.repr().to_le_bytes());
|
|
||||||
|
|
||||||
buf[20..22].copy_from_slice(&((self.first_cluster() >> 16) as u16).to_le_bytes());
|
|
||||||
|
|
||||||
buf[22..24].copy_from_slice(&self.write_time.repr().to_le_bytes());
|
|
||||||
buf[24..26].copy_from_slice(&self.write_date.repr().to_le_bytes());
|
|
||||||
|
|
||||||
buf[26..28].copy_from_slice(&(self.first_cluster as u16).to_le_bytes());
|
|
||||||
|
|
||||||
buf[28..].copy_from_slice(&self.file_size.to_le_bytes());
|
|
||||||
|
|
||||||
debug!("self: {self:?}");
|
|
||||||
debug!("writing new dir entry: {:?}", buf);
|
|
||||||
|
|
||||||
writer.write_all(&buf)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// write this DisEntry back to the underlying data
|
|
||||||
pub fn write_back(&self, fat_fs: &FatFs) -> std::io::Result<()> {
|
|
||||||
eprintln!("making new SubSliceMut at offset {:#X}", self.offset);
|
|
||||||
let sub_slice = SubSliceMut::new(fat_fs.inner.clone(), self.offset, 32);
|
|
||||||
|
|
||||||
self.write(sub_slice)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// erase this DirEntry
|
|
||||||
pub fn erase(self, fat_fs: &FatFs) -> std::io::Result<()> {
|
|
||||||
let mut sub_slice = SubSliceMut::new(fat_fs.inner.clone(), self.offset, 32);
|
|
||||||
|
|
||||||
// set first byte to 0xE5 (free)
|
|
||||||
sub_slice.write_all(&[0xe5])?;
|
|
||||||
|
|
||||||
// paste over with zeros
|
|
||||||
sub_slice.write_all(&[0; 31])
|
|
||||||
|
|
||||||
// TODO: erase long filename entries as well
|
|
||||||
}
|
|
||||||
|
|
||||||
/// indicates this DirEntry is empty
|
/// indicates this DirEntry is empty
|
||||||
///
|
///
|
||||||
/// can be either simply empty (0xe5) or the sentinel (0x00) that indicates that all following
|
/// can be either simply empty (0xe5) or the sentinel (0x00) that indicates that all following
|
||||||
|
|
@ -270,6 +206,8 @@ impl DirEntry {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// &self.name[..2] == &[b'.', b' ']
|
||||||
|
|
||||||
self.name[0] == b'.' && &self.name[1..] == &[b' '; 10]
|
self.name[0] == b'.' && &self.name[1..] == &[b' '; 10]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -278,6 +216,8 @@ impl DirEntry {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// &self.name[..3] == &[b'.', b'.', b' ']
|
||||||
|
|
||||||
&self.name[..2] == &[b'.', b'.'] && &self.name[2..] == &[b' '; 9]
|
&self.name[..2] == &[b'.', b'.'] && &self.name[2..] == &[b' '; 9]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -309,60 +249,54 @@ impl DirEntry {
|
||||||
std::str::from_utf8(self.extension()).ok()
|
std::str::from_utf8(self.extension()).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn name_string(&self) -> CompactString {
|
fn short_name(&self) -> &[u8] {
|
||||||
// use a CompactString here to allow inlining of short names
|
&self.short_name
|
||||||
// maybe switch to a Cow instead? has disadvantage that we need to alloc for short names
|
}
|
||||||
|
|
||||||
// can't be empty
|
fn short_name_str(&self) -> Option<&str> {
|
||||||
assert!(!self.is_empty());
|
let mut short_name = self.short_name();
|
||||||
|
|
||||||
if let Some(long_filename) = self.long_name() {
|
// remove trailing zeros
|
||||||
return long_filename.into();
|
while short_name.last() == Some(&0) {
|
||||||
|
short_name = &short_name[..short_name.len() - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = &self.name[..8];
|
std::str::from_utf8(short_name).ok()
|
||||||
let ext = &self.name[8..];
|
|
||||||
|
|
||||||
let mut s = CompactString::const_new("");
|
|
||||||
|
|
||||||
if self.attr.contains(Attr::Hidden) {
|
|
||||||
s.push('.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const VALID_SYMBOLS: &[u8] = &[
|
|
||||||
b'$', b'%', b'\'', b'-', b'_', b'@', b'~', b'`', b'!', b'(', b')', b'{', b'}', b'^',
|
|
||||||
b'#', b'&',
|
|
||||||
];
|
|
||||||
|
|
||||||
fn map_chars(c: u8) -> char {
|
|
||||||
if !c.is_ascii()
|
|
||||||
|| c < 0x20
|
|
||||||
|| !(c.is_ascii_alphanumeric() || VALID_SYMBOLS.contains(&c))
|
|
||||||
{
|
|
||||||
'?'
|
|
||||||
} else {
|
|
||||||
(c as char).to_ascii_uppercase()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.extend(name.trim_ascii_end().iter().copied().map(map_chars));
|
|
||||||
|
|
||||||
if !ext.is_empty() {
|
|
||||||
s.push('.');
|
|
||||||
|
|
||||||
s.extend(ext.trim_ascii_end().iter().copied().map(map_chars));
|
|
||||||
}
|
|
||||||
|
|
||||||
s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn long_name(&self) -> Option<&str> {
|
pub fn long_name(&self) -> Option<&str> {
|
||||||
self.long_name.as_deref()
|
self.long_name.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_long_name(&mut self, long_name: CompactString, n_slots: u8) {
|
pub fn set_long_name(&mut self, long_name: String) {
|
||||||
self.long_name = Some(long_name);
|
self.long_name = Some(long_name);
|
||||||
self.n_longname_slots = n_slots;
|
}
|
||||||
|
|
||||||
|
pub fn name_str(&self) -> Option<&str> {
|
||||||
|
if let Some(long_filename) = self.long_name() {
|
||||||
|
return Some(long_filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.short_name_str()
|
||||||
|
|
||||||
|
// let name = std::str::from_utf8(&self.name[..8]).ok()?.trim_ascii_end();
|
||||||
|
// let ext = std::str::from_utf8(&self.name[8..]).ok()?.trim_ascii_end();
|
||||||
|
|
||||||
|
// let mut s = String::new();
|
||||||
|
|
||||||
|
// if self.attr.contains(Attr::Hidden) {
|
||||||
|
// s.push('.');
|
||||||
|
// }
|
||||||
|
|
||||||
|
// s += name;
|
||||||
|
|
||||||
|
// if !ext.is_empty() {
|
||||||
|
// s.push('.');
|
||||||
|
|
||||||
|
// s += ext;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Some(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attr(&self) -> Attr {
|
pub fn attr(&self) -> Attr {
|
||||||
|
|
@ -384,15 +318,6 @@ impl DirEntry {
|
||||||
self.last_access_date.to_naive_date()
|
self.last_access_date.to_naive_date()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_last_access_date(
|
|
||||||
&mut self,
|
|
||||||
time: impl Into<DateTime<Local>>,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
self.last_access_date = Date::from_datetime(time.into())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn first_cluster(&self) -> u32 {
|
pub fn first_cluster(&self) -> u32 {
|
||||||
self.first_cluster
|
self.first_cluster
|
||||||
}
|
}
|
||||||
|
|
@ -404,27 +329,14 @@ impl DirEntry {
|
||||||
NaiveDateTime::new(date, time)
|
NaiveDateTime::new(date, time)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_write_time(&mut self, time: impl Into<DateTime<Local>>) -> anyhow::Result<()> {
|
|
||||||
let time = time.into();
|
|
||||||
|
|
||||||
self.write_date = Date::from_datetime(time)?;
|
|
||||||
self.write_time = Time::from_datetime(time)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn file_size(&self) -> u32 {
|
pub fn file_size(&self) -> u32 {
|
||||||
self.file_size
|
self.file_size
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_file_size(&mut self, file_size: u32) {
|
pub fn checksum(&self) -> u8 {
|
||||||
self.file_size = file_size
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn checksum(name: &[u8]) -> u8 {
|
|
||||||
let mut checksum: u8 = 0;
|
let mut checksum: u8 = 0;
|
||||||
|
|
||||||
for &x in name {
|
for &x in self.name() {
|
||||||
checksum = checksum.rotate_right(1).wrapping_add(x);
|
checksum = checksum.rotate_right(1).wrapping_add(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -435,7 +347,6 @@ impl DirEntry {
|
||||||
/// long filename entry in a directory
|
/// long filename entry in a directory
|
||||||
///
|
///
|
||||||
/// this should not be exposed to end users, only for internal consumption in the DirIter
|
/// this should not be exposed to end users, only for internal consumption in the DirIter
|
||||||
#[derive(Debug)]
|
|
||||||
struct LongNameDirEntry {
|
struct LongNameDirEntry {
|
||||||
ordinal: u8,
|
ordinal: u8,
|
||||||
is_last: bool,
|
is_last: bool,
|
||||||
|
|
@ -510,14 +421,13 @@ impl LongNameDirEntry {
|
||||||
///
|
///
|
||||||
/// should not be exposed publicly, end users only see DirEntries
|
/// should not be exposed publicly, end users only see DirEntries
|
||||||
/// just for making the bytes -> DirEntry loading a bit easier
|
/// just for making the bytes -> DirEntry loading a bit easier
|
||||||
#[derive(Debug)]
|
|
||||||
enum DirEntryWrapper {
|
enum DirEntryWrapper {
|
||||||
Regular(DirEntry),
|
Regular(DirEntry),
|
||||||
LongName(LongNameDirEntry),
|
LongName(LongNameDirEntry),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DirEntryWrapper {
|
impl DirEntryWrapper {
|
||||||
pub fn load(bytes: &[u8], offset: u64) -> anyhow::Result<DirEntryWrapper> {
|
pub fn load(bytes: &[u8]) -> anyhow::Result<DirEntryWrapper> {
|
||||||
assert_eq!(bytes.len(), 32);
|
assert_eq!(bytes.len(), 32);
|
||||||
|
|
||||||
let attr = Attr::from_bits_truncate(bytes[11]);
|
let attr = Attr::from_bits_truncate(bytes[11]);
|
||||||
|
|
@ -525,7 +435,7 @@ impl DirEntryWrapper {
|
||||||
let dir_entry = if attr == Attr::LongName {
|
let dir_entry = if attr == Attr::LongName {
|
||||||
DirEntryWrapper::LongName(LongNameDirEntry::load(bytes)?)
|
DirEntryWrapper::LongName(LongNameDirEntry::load(bytes)?)
|
||||||
} else {
|
} else {
|
||||||
DirEntryWrapper::Regular(DirEntry::load(bytes, offset)?)
|
DirEntryWrapper::Regular(DirEntry::load(bytes)?)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(dir_entry)
|
Ok(dir_entry)
|
||||||
|
|
@ -537,7 +447,6 @@ struct LongFilenameBuf {
|
||||||
rev_buf: Vec<u16>,
|
rev_buf: Vec<u16>,
|
||||||
checksum: Option<u8>,
|
checksum: Option<u8>,
|
||||||
last_ordinal: Option<u8>,
|
last_ordinal: Option<u8>,
|
||||||
n_slots: u8,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LongFilenameBuf {
|
impl LongFilenameBuf {
|
||||||
|
|
@ -545,43 +454,28 @@ impl LongFilenameBuf {
|
||||||
self.rev_buf.clear();
|
self.rev_buf.clear();
|
||||||
self.checksum = None;
|
self.checksum = None;
|
||||||
self.last_ordinal = None;
|
self.last_ordinal = None;
|
||||||
self.n_slots = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next(&mut self, dir_entry: LongNameDirEntry) -> anyhow::Result<()> {
|
pub fn next(&mut self, dir_entry: LongNameDirEntry) -> anyhow::Result<()> {
|
||||||
if dir_entry.is_last() {
|
if dir_entry.is_last() {
|
||||||
// first/lasts entry
|
// first/lasts entry
|
||||||
|
|
||||||
anyhow::ensure!(
|
|
||||||
dir_entry.ordinal() <= 20,
|
|
||||||
"can't have more than 20 long filename dir entries"
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut name = dir_entry.name();
|
let mut name = dir_entry.name();
|
||||||
|
|
||||||
while name.last() == Some(&0xFFFF) {
|
while name.last() == Some(&0xFFFF) {
|
||||||
name = &name[..name.len() - 1];
|
name = &name[..name.len() - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if name.last() == Some(&0) {
|
|
||||||
name = &name[..name.len() - 1];
|
|
||||||
} else {
|
|
||||||
// no null terminator only for names that are multiples of 13, i.e. perfectly fit
|
|
||||||
assert_eq!(name.len(), 13);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(!name.is_empty());
|
assert!(!name.is_empty());
|
||||||
|
|
||||||
self.extend_name(name);
|
self.extend_name(name);
|
||||||
self.checksum = Some(dir_entry.checksum());
|
self.checksum = Some(dir_entry.checksum());
|
||||||
self.last_ordinal = Some(dir_entry.ordinal());
|
self.last_ordinal = Some(dir_entry.ordinal());
|
||||||
|
|
||||||
self.n_slots += 1;
|
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
assert!(self.checksum.is_some() && self.last_ordinal.is_some());
|
assert!(self.checksum.is_some());
|
||||||
|
|
||||||
anyhow::ensure!(
|
anyhow::ensure!(
|
||||||
self.checksum == Some(dir_entry.checksum()),
|
self.checksum == Some(dir_entry.checksum()),
|
||||||
|
|
@ -609,10 +503,7 @@ impl LongFilenameBuf {
|
||||||
self.rev_buf.extend(name.iter().rev());
|
self.rev_buf.extend(name.iter().rev());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_buf(
|
pub fn get_buf(&mut self, checksum: u8) -> anyhow::Result<Option<impl Iterator<Item = u16>>> {
|
||||||
&mut self,
|
|
||||||
checksum: u8,
|
|
||||||
) -> anyhow::Result<Option<(impl Iterator<Item = u16>, u8)>> {
|
|
||||||
if self.checksum.is_none() {
|
if self.checksum.is_none() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
@ -634,97 +525,100 @@ impl LongFilenameBuf {
|
||||||
self.checksum.unwrap()
|
self.checksum.unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
anyhow::ensure!(self.rev_buf.len() <= 255, "long filename too long");
|
Ok(Some(self.rev_buf.iter().copied().rev()))
|
||||||
|
|
||||||
Ok(Some((self.rev_buf.iter().copied().rev(), self.n_slots)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DirIter<'a> {
|
pub struct DirIter<R: Read> {
|
||||||
reader: ClusterChainReader<'a>,
|
reader: R,
|
||||||
|
|
||||||
|
// long_filename_rev_buf: Vec<u16>,
|
||||||
|
// long_filename_checksum: Option<u8>,
|
||||||
|
// long_filename_last_ordinal: Option<u8>,
|
||||||
long_filename_buf: LongFilenameBuf,
|
long_filename_buf: LongFilenameBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DirIter<'a> {
|
impl<R: Read> DirIter<R> {
|
||||||
pub fn new(reader: ClusterChainReader<'a>) -> Self {
|
pub fn new(reader: R) -> DirIter<R> {
|
||||||
DirIter {
|
DirIter {
|
||||||
reader,
|
reader,
|
||||||
|
// long_filename_rev_buf: Vec::new(),
|
||||||
|
// long_filename_checksum: None,
|
||||||
|
// long_filename_last_ordinal: None,
|
||||||
long_filename_buf: Default::default(),
|
long_filename_buf: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_by_name(&mut self, name: &str) -> Option<DirEntry> {
|
/// inner function for iterator
|
||||||
self.find(|dir_entry| &dir_entry.name_string() == name)
|
fn next_impl(&mut self) -> anyhow::Result<Option<DirEntry>> {
|
||||||
|
let mut chunk = [0; 32];
|
||||||
|
if self.reader.read_exact(&mut chunk).is_err() {
|
||||||
|
// reading failed; nothing we can do here
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// let Ok(dir_entry) = DirEntry::load(&chunk) else {
|
||||||
|
// return self.next();
|
||||||
|
// };
|
||||||
|
|
||||||
|
let dir_entry = DirEntryWrapper::load(&chunk)
|
||||||
|
.map_err(|e| anyhow::anyhow!("failed to load dir entry: {e}"))?;
|
||||||
|
|
||||||
|
let mut dir_entry = match dir_entry {
|
||||||
|
DirEntryWrapper::Regular(dir_entry) => dir_entry,
|
||||||
|
DirEntryWrapper::LongName(long_name) => {
|
||||||
|
self.long_filename_buf.next(long_name).map_err(|e| {
|
||||||
|
self.long_filename_buf.reset();
|
||||||
|
anyhow::anyhow!("invalid long filename entry: {e}")
|
||||||
|
})?;
|
||||||
|
|
||||||
|
return self.next_impl();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if dir_entry.is_sentinel() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
if dir_entry.is_empty() {
|
||||||
|
return self.next_impl();
|
||||||
|
}
|
||||||
|
|
||||||
|
match self
|
||||||
|
.long_filename_buf
|
||||||
|
.get_buf(dir_entry.checksum())
|
||||||
|
.map_err(|e| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"failed to get long filename for {}: {}",
|
||||||
|
dir_entry.name_str().as_deref().unwrap_or("<invalid>"),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
})? {
|
||||||
|
Some(iter) => {
|
||||||
|
// attach long filename to dir_entry
|
||||||
|
|
||||||
|
let long_filename: String =
|
||||||
|
char::decode_utf16(iter).filter_map(|x| x.ok()).collect();
|
||||||
|
|
||||||
|
dir_entry.set_long_name(long_filename);
|
||||||
|
}
|
||||||
|
None => {} // no long filename -> do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
self.long_filename_buf.reset();
|
||||||
|
|
||||||
|
Ok(Some(dir_entry))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for DirIter<'_> {
|
impl<R: Read> Iterator for DirIter<R> {
|
||||||
type Item = DirEntry;
|
type Item = DirEntry;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
fn next_impl(me: &mut DirIter<'_>) -> anyhow::Result<Option<DirEntry>> {
|
match self.next_impl() {
|
||||||
let offset = me.reader.current_offset();
|
|
||||||
|
|
||||||
let mut chunk = [0; 32];
|
|
||||||
|
|
||||||
if me.reader.read_exact(&mut chunk).is_err() {
|
|
||||||
// nothing we can do here since we might be in an invalid state after a partial read
|
|
||||||
anyhow::bail!("read failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
let dir_entry = DirEntryWrapper::load(&chunk, offset)
|
|
||||||
.map_err(|e| anyhow::anyhow!("failed to load dir entry: {e}"))?;
|
|
||||||
|
|
||||||
let mut dir_entry = match dir_entry {
|
|
||||||
DirEntryWrapper::Regular(dir_entry) => dir_entry,
|
|
||||||
DirEntryWrapper::LongName(long_name) => {
|
|
||||||
me.long_filename_buf.next(long_name).map_err(|e| {
|
|
||||||
me.long_filename_buf.reset();
|
|
||||||
anyhow::anyhow!("invalid long filename entry: {e}")
|
|
||||||
})?;
|
|
||||||
|
|
||||||
return next_impl(me);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if dir_entry.is_sentinel() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
if dir_entry.is_empty() {
|
|
||||||
return next_impl(me);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some((iter, n_slots)) = me
|
|
||||||
.long_filename_buf
|
|
||||||
.get_buf(dir_entry.checksum)
|
|
||||||
.map_err(|e| {
|
|
||||||
anyhow::anyhow!(
|
|
||||||
"failed to get long filename for {}: {}",
|
|
||||||
dir_entry.name_string(),
|
|
||||||
e
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
{
|
|
||||||
// attach long filename to dir_entry
|
|
||||||
|
|
||||||
let long_filename: CompactString =
|
|
||||||
char::decode_utf16(iter).filter_map(|x| x.ok()).collect();
|
|
||||||
|
|
||||||
dir_entry.set_long_name(long_filename, n_slots);
|
|
||||||
}
|
|
||||||
|
|
||||||
me.long_filename_buf.reset();
|
|
||||||
|
|
||||||
Ok(Some(dir_entry))
|
|
||||||
}
|
|
||||||
|
|
||||||
match next_impl(self) {
|
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// print error message, try next
|
eprintln!("{}", e);
|
||||||
debug!("{}", e);
|
|
||||||
|
|
||||||
self.next()
|
self.next()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,10 @@
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::io::Write as _;
|
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use log::debug;
|
|
||||||
|
|
||||||
use crate::FatType;
|
use crate::FatType;
|
||||||
use crate::subslice::SubSliceMut;
|
|
||||||
|
|
||||||
const FREE_ENTRY: u32 = 0;
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum FatError {
|
pub enum FatError {
|
||||||
|
|
@ -24,24 +19,26 @@ pub enum FatError {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[enum_dispatch]
|
#[enum_dispatch]
|
||||||
trait FatOps {
|
pub trait Fatty {
|
||||||
// get the next cluster
|
// get the next cluster
|
||||||
// assumes the cluster is valid, i.e. allocated
|
// assumes the cluster is valid, i.e. allocated
|
||||||
fn get_entry(&self, cluster: u32) -> u32;
|
fn get_entry(&self, cluster: u32) -> u32;
|
||||||
fn set_entry(&mut self, cluster: u32, entry: u32);
|
|
||||||
|
|
||||||
fn valid_entries(&self) -> RangeInclusive<u32>;
|
fn get_valid_clusters(&self) -> RangeInclusive<u32>;
|
||||||
fn reserved_entries(&self) -> RangeInclusive<u32>;
|
fn get_reserved_clusters(&self) -> RangeInclusive<u32>;
|
||||||
fn defective_entry(&self) -> u32;
|
fn get_defective_cluster(&self) -> u32;
|
||||||
fn reserved_eof_entries(&self) -> RangeInclusive<u32>;
|
fn get_reserved_eof_clusters(&self) -> RangeInclusive<u32>;
|
||||||
fn eof_entry(&self) -> u32;
|
fn get_eof_cluster(&self) -> u32;
|
||||||
|
|
||||||
fn write_to_disk(&self, sub_slice: SubSliceMut) -> std::io::Result<()>;
|
fn count_free_clusters(&self) -> usize {
|
||||||
|
self.get_valid_clusters()
|
||||||
|
.map(|cluster| self.get_entry(cluster))
|
||||||
|
.filter(|&entry| entry == 0)
|
||||||
|
.count()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[enum_dispatch(FatOps)]
|
#[enum_dispatch(Fatty)]
|
||||||
// others should never touch the inner FatNs directly, but instead go through the Fat type
|
|
||||||
#[allow(private_interfaces)]
|
|
||||||
pub enum Fat {
|
pub enum Fat {
|
||||||
Fat12(Fat12),
|
Fat12(Fat12),
|
||||||
Fat16(Fat16),
|
Fat16(Fat16),
|
||||||
|
|
@ -67,32 +64,24 @@ impl Fat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fat_type(&self) -> FatType {
|
|
||||||
match self {
|
|
||||||
Fat::Fat12(_) => FatType::Fat12,
|
|
||||||
Fat::Fat16(_) => FatType::Fat16,
|
|
||||||
Fat::Fat32(_) => FatType::Fat32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_next_cluster(&self, cluster: u32) -> Result<Option<u32>, FatError> {
|
pub fn get_next_cluster(&self, cluster: u32) -> Result<Option<u32>, FatError> {
|
||||||
if cluster == FREE_ENTRY {
|
if cluster == 0x000 {
|
||||||
// can't get next cluster for free cluster
|
// can't get next cluster for free cluster
|
||||||
return Err(FatError::FreeCluster);
|
return Err(FatError::FreeCluster);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.reserved_entries().contains(&cluster) {
|
if self.get_reserved_clusters().contains(&cluster) {
|
||||||
// can't get next cluster for reserved cluster
|
// can't get next cluster for reserved cluster
|
||||||
return Err(FatError::ReservedCluster(cluster));
|
return Err(FatError::ReservedCluster(cluster));
|
||||||
}
|
}
|
||||||
|
|
||||||
// defective cluster
|
// defective cluster
|
||||||
if cluster == self.defective_entry() {
|
if cluster == self.get_defective_cluster() {
|
||||||
// can't get next cluster for defective cluster
|
// can't get next cluster for defective cluster
|
||||||
return Err(FatError::DefectiveCluster);
|
return Err(FatError::DefectiveCluster);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.reserved_eof_entries().contains(&cluster) {
|
if self.get_reserved_eof_clusters().contains(&cluster) {
|
||||||
// Reserved and should not be used. May be interpreted as an allocated cluster and the
|
// Reserved and should not be used. May be interpreted as an allocated cluster and the
|
||||||
// final cluster in the file (indicating end-of-file condition).
|
// final cluster in the file (indicating end-of-file condition).
|
||||||
//
|
//
|
||||||
|
|
@ -104,117 +93,21 @@ impl Fat {
|
||||||
|
|
||||||
let entry = self.get_entry(cluster);
|
let entry = self.get_entry(cluster);
|
||||||
|
|
||||||
if entry == FREE_ENTRY {
|
// interpret second reserved block as EOF here
|
||||||
Ok(None)
|
if entry == self.get_eof_cluster() || self.get_reserved_eof_clusters().contains(&entry) {
|
||||||
} else if entry == self.eof_entry() {
|
return Ok(None);
|
||||||
Ok(None)
|
|
||||||
} else if self.valid_entries().contains(&entry) {
|
|
||||||
Ok(Some(entry))
|
|
||||||
} else if self.reserved_entries().contains(&entry) || entry == 1 {
|
|
||||||
Err(FatError::ReservedCluster(entry))
|
|
||||||
} else if entry == self.defective_entry() {
|
|
||||||
Err(FatError::DefectiveCluster)
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// set the next cluster of `cluster` to either some `next_cluster` or EOF
|
|
||||||
///
|
|
||||||
/// if `cluster` currently points to another cluster, that cluster MUST be the EOF and will be
|
|
||||||
/// freed
|
|
||||||
pub fn set_next_cluster(&mut self, cluster: u32, next_cluster: Option<u32>) {
|
|
||||||
assert!(self.valid_entries().contains(&cluster));
|
|
||||||
|
|
||||||
let cur_next_cluster = self.get_entry(cluster);
|
|
||||||
|
|
||||||
// can't be defective
|
|
||||||
assert_ne!(cur_next_cluster, self.defective_entry());
|
|
||||||
|
|
||||||
if self.valid_entries().contains(&cur_next_cluster) {
|
|
||||||
// cluster currently points to a valid cluster, so we free it
|
|
||||||
|
|
||||||
log::debug!("freeing chain beginning at {cluster}");
|
|
||||||
self.free_chain(cluster);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(next_cluster) = next_cluster {
|
// entry should be in the valid cluster range here; otherwise something went wrong
|
||||||
assert!(self.valid_entries().contains(&next_cluster));
|
if !self.get_valid_clusters().contains(&entry) {
|
||||||
|
return Err(FatError::InvalidEntry(entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(next_cluster) = next_cluster {
|
Ok(Some(entry))
|
||||||
log::debug!("setting {cluster} -> {next_cluster}");
|
|
||||||
} else {
|
|
||||||
log::debug!("setting {cluster} EOF");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.set_entry(cluster, next_cluster.unwrap_or(self.eof_entry()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// free a cluster
|
|
||||||
///
|
|
||||||
/// must be EOF
|
|
||||||
pub fn free_cluster(&mut self, cluster: u32) {
|
|
||||||
debug!("freeing cluster {cluster}");
|
|
||||||
|
|
||||||
let entry = self.get_entry(cluster);
|
|
||||||
|
|
||||||
if entry == FREE_ENTRY {
|
|
||||||
// nothing to be done here
|
|
||||||
debug!("cluster was already free");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// can't be reserved or defective
|
|
||||||
// can't be pointing to another cluster (we'd orphan that one)
|
|
||||||
// use free_chain to free a chain of clusters iteratively
|
|
||||||
assert!(self.is_eof(entry));
|
|
||||||
|
|
||||||
self.set_entry(cluster, FREE_ENTRY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// free `first_cluster` and all following clusters
|
|
||||||
pub fn free_chain(&mut self, mut first_cluster: u32) {
|
|
||||||
debug!("freeing chaing at {first_cluster}");
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let entry = self.get_entry(first_cluster);
|
|
||||||
|
|
||||||
// assert cluster either points to another cluster of is the EOF
|
|
||||||
assert!(self.valid_entries().contains(&entry) || self.is_eof(entry));
|
|
||||||
|
|
||||||
self.set_entry(first_cluster, FREE_ENTRY);
|
|
||||||
|
|
||||||
if self.valid_entries().contains(&entry) {
|
|
||||||
first_cluster = entry;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn count_free_clusters(&self) -> u32 {
|
|
||||||
self.valid_entries()
|
|
||||||
.map(|cluster| self.get_entry(cluster))
|
|
||||||
.filter(|&entry| entry == FREE_ENTRY)
|
|
||||||
.count() as u32
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn first_free_cluster(&self) -> Option<u32> {
|
|
||||||
self.valid_entries()
|
|
||||||
.find(|&cluster| self.get_entry(cluster) == FREE_ENTRY)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_eof(&self, entry: u32) -> bool {
|
|
||||||
entry == self.eof_entry() || self.reserved_eof_entries().contains(&entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_back(&self, sub_slice: SubSliceMut) -> std::io::Result<()> {
|
|
||||||
self.write_to_disk(sub_slice)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Fat12 {
|
pub struct Fat12 {
|
||||||
max: u32,
|
max: u32,
|
||||||
|
|
||||||
next_sectors: Box<[u16]>,
|
next_sectors: Box<[u16]>,
|
||||||
|
|
@ -225,7 +118,7 @@ impl Display for Fat12 {
|
||||||
writeln!(f, "Fat 12 {{")?;
|
writeln!(f, "Fat 12 {{")?;
|
||||||
|
|
||||||
for (i, &x) in self.next_sectors.iter().enumerate() {
|
for (i, &x) in self.next_sectors.iter().enumerate() {
|
||||||
if x != FREE_ENTRY as u16 {
|
if x != 0 {
|
||||||
writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
|
writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -251,10 +144,7 @@ impl Fat12 {
|
||||||
|
|
||||||
// assume bytes.len() is multiple of 3
|
// assume bytes.len() is multiple of 3
|
||||||
// TODO: fix later
|
// TODO: fix later
|
||||||
// assert_eq!(bytes.len() % 3, 0);
|
assert_eq!(bytes.len() % 3, 0);
|
||||||
assert_eq!(max % 2, 0);
|
|
||||||
|
|
||||||
let bytes = &bytes[..next_sectors.len() / 2 * 3];
|
|
||||||
|
|
||||||
let (chunks, rem) = bytes.as_chunks::<3>();
|
let (chunks, rem) = bytes.as_chunks::<3>();
|
||||||
|
|
||||||
|
|
@ -288,7 +178,7 @@ impl Fat12 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FatOps for Fat12 {
|
impl Fatty for Fat12 {
|
||||||
fn get_entry(&self, cluster: u32) -> u32 {
|
fn get_entry(&self, cluster: u32) -> u32 {
|
||||||
let cluster = cluster as usize;
|
let cluster = cluster as usize;
|
||||||
assert!(cluster < self.next_sectors.len());
|
assert!(cluster < self.next_sectors.len());
|
||||||
|
|
@ -296,74 +186,28 @@ impl FatOps for Fat12 {
|
||||||
self.next_sectors[cluster] as u32
|
self.next_sectors[cluster] as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_entry(&mut self, cluster: u32, entry: u32) {
|
fn get_valid_clusters(&self) -> RangeInclusive<u32> {
|
||||||
self.next_sectors[cluster as usize] = entry as u16;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn valid_entries(&self) -> RangeInclusive<u32> {
|
|
||||||
2..=self.max
|
2..=self.max
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reserved_entries(&self) -> RangeInclusive<u32> {
|
fn get_reserved_clusters(&self) -> RangeInclusive<u32> {
|
||||||
(self.max as u32 + 1)..=0xFF6
|
(self.max as u32 + 1)..=0xFF6
|
||||||
}
|
}
|
||||||
|
|
||||||
fn defective_entry(&self) -> u32 {
|
fn get_defective_cluster(&self) -> u32 {
|
||||||
0xFF7
|
0xFF7
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reserved_eof_entries(&self) -> RangeInclusive<u32> {
|
fn get_reserved_eof_clusters(&self) -> RangeInclusive<u32> {
|
||||||
0xFF8..=0xFFE
|
0xFF8..=0xFFE
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eof_entry(&self) -> u32 {
|
fn get_eof_cluster(&self) -> u32 {
|
||||||
0xFFF
|
0xFFF
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_to_disk(&self, mut sub_slice: SubSliceMut) -> std::io::Result<()> {
|
|
||||||
assert!(2 * sub_slice.len() > 3 * self.next_sectors.len());
|
|
||||||
|
|
||||||
let mut iter = self.next_sectors.chunks_exact(2);
|
|
||||||
|
|
||||||
let mut buf: [u8; 3];
|
|
||||||
|
|
||||||
for chunk in &mut iter {
|
|
||||||
// first (even) entry gets truncated
|
|
||||||
// let first = u16::from_le_bytes(triple[..2].try_into().unwrap()) & 0xFFF;
|
|
||||||
// second (odd) entry gets shifted
|
|
||||||
// let second = u16::from_le_bytes(triple[1..].try_into().unwrap()) >> 4;
|
|
||||||
|
|
||||||
// assert!(idx + 1 < next_sectors.len());
|
|
||||||
|
|
||||||
// next_sectors[2 * idx] = first;
|
|
||||||
// next_sectors[2 * idx + 1] = second;
|
|
||||||
|
|
||||||
// sub_slice.write_all(&entry.to_le_bytes())?;
|
|
||||||
|
|
||||||
let first = chunk[0];
|
|
||||||
let second = chunk[1];
|
|
||||||
|
|
||||||
buf = [0; 3];
|
|
||||||
|
|
||||||
// buf[..2] |= &first.to_le_bytes();
|
|
||||||
buf[0] = first.to_le_bytes()[0];
|
|
||||||
buf[1] = first.to_le_bytes()[1] | (second << 4).to_le_bytes()[0];
|
|
||||||
buf[2] = (second << 4).to_le_bytes()[1];
|
|
||||||
sub_slice.write_all(&buf)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
match iter.remainder() {
|
|
||||||
[] => {}
|
|
||||||
[x] => sub_slice.write_all(&x.to_le_bytes())?,
|
|
||||||
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Fat16 {
|
pub struct Fat16 {
|
||||||
max: u32,
|
max: u32,
|
||||||
|
|
||||||
next_sectors: Box<[u16]>,
|
next_sectors: Box<[u16]>,
|
||||||
|
|
@ -374,7 +218,7 @@ impl Display for Fat16 {
|
||||||
writeln!(f, "Fat 16 {{")?;
|
writeln!(f, "Fat 16 {{")?;
|
||||||
|
|
||||||
for (i, &x) in self.next_sectors.iter().enumerate() {
|
for (i, &x) in self.next_sectors.iter().enumerate() {
|
||||||
if x != FREE_ENTRY as u16 {
|
if x != 0 {
|
||||||
writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
|
writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -418,7 +262,7 @@ impl Fat16 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FatOps for Fat16 {
|
impl Fatty for Fat16 {
|
||||||
fn get_entry(&self, cluster: u32) -> u32 {
|
fn get_entry(&self, cluster: u32) -> u32 {
|
||||||
let cluster = cluster as usize;
|
let cluster = cluster as usize;
|
||||||
assert!(cluster < self.next_sectors.len());
|
assert!(cluster < self.next_sectors.len());
|
||||||
|
|
@ -426,42 +270,28 @@ impl FatOps for Fat16 {
|
||||||
self.next_sectors[cluster] as u32
|
self.next_sectors[cluster] as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_entry(&mut self, cluster: u32, entry: u32) {
|
fn get_valid_clusters(&self) -> RangeInclusive<u32> {
|
||||||
self.next_sectors[cluster as usize] = entry as u16;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn valid_entries(&self) -> RangeInclusive<u32> {
|
|
||||||
2..=self.max
|
2..=self.max
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reserved_entries(&self) -> RangeInclusive<u32> {
|
fn get_reserved_clusters(&self) -> RangeInclusive<u32> {
|
||||||
(self.max as u32 + 1)..=0xFFF6
|
(self.max as u32 + 1)..=0xFFF6
|
||||||
}
|
}
|
||||||
|
|
||||||
fn defective_entry(&self) -> u32 {
|
fn get_defective_cluster(&self) -> u32 {
|
||||||
0xFFF7
|
0xFFF7
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reserved_eof_entries(&self) -> RangeInclusive<u32> {
|
fn get_reserved_eof_clusters(&self) -> RangeInclusive<u32> {
|
||||||
0xFFF8..=0xFFFE
|
0xFFF8..=0xFFFE
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eof_entry(&self) -> u32 {
|
fn get_eof_cluster(&self) -> u32 {
|
||||||
0xFFFF
|
0xFFFF
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_to_disk(&self, mut sub_slice: SubSliceMut) -> std::io::Result<()> {
|
|
||||||
assert_eq!(2 * sub_slice.len(), self.next_sectors.len());
|
|
||||||
|
|
||||||
for &entry in self.next_sectors.iter() {
|
|
||||||
sub_slice.write_all(&entry.to_le_bytes())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Fat32 {
|
pub struct Fat32 {
|
||||||
max: u32,
|
max: u32,
|
||||||
|
|
||||||
next_sectors: Box<[u32]>,
|
next_sectors: Box<[u32]>,
|
||||||
|
|
@ -472,7 +302,7 @@ impl Display for Fat32 {
|
||||||
writeln!(f, "Fat 32 {{")?;
|
writeln!(f, "Fat 32 {{")?;
|
||||||
|
|
||||||
for (i, &x) in self.next_sectors.iter().enumerate() {
|
for (i, &x) in self.next_sectors.iter().enumerate() {
|
||||||
if x != FREE_ENTRY {
|
if x != 0 {
|
||||||
writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
|
writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -516,7 +346,7 @@ impl Fat32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FatOps for Fat32 {
|
impl Fatty for Fat32 {
|
||||||
fn get_entry(&self, cluster: u32) -> u32 {
|
fn get_entry(&self, cluster: u32) -> u32 {
|
||||||
let cluster = cluster as usize;
|
let cluster = cluster as usize;
|
||||||
assert!(cluster < self.next_sectors.len());
|
assert!(cluster < self.next_sectors.len());
|
||||||
|
|
@ -524,37 +354,23 @@ impl FatOps for Fat32 {
|
||||||
self.next_sectors[cluster] as u32
|
self.next_sectors[cluster] as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_entry(&mut self, cluster: u32, entry: u32) {
|
fn get_valid_clusters(&self) -> RangeInclusive<u32> {
|
||||||
self.next_sectors[cluster as usize] = entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn valid_entries(&self) -> RangeInclusive<u32> {
|
|
||||||
2..=self.max
|
2..=self.max
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reserved_entries(&self) -> RangeInclusive<u32> {
|
fn get_reserved_clusters(&self) -> RangeInclusive<u32> {
|
||||||
(self.max + 1)..=0xFFFFFFF6
|
(self.max + 1)..=0xFFFFFFF6
|
||||||
}
|
}
|
||||||
|
|
||||||
fn defective_entry(&self) -> u32 {
|
fn get_defective_cluster(&self) -> u32 {
|
||||||
0xFFFFFFF7
|
0xFFFFFFF7
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reserved_eof_entries(&self) -> RangeInclusive<u32> {
|
fn get_reserved_eof_clusters(&self) -> RangeInclusive<u32> {
|
||||||
0xFFFFFFF8..=0xFFFFFFFE
|
0xFFFFFFF8..=0xFFFFFFFE
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eof_entry(&self) -> u32 {
|
fn get_eof_cluster(&self) -> u32 {
|
||||||
0xFFFFFFFF
|
0xFFFFFFFF
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_to_disk(&self, mut sub_slice: SubSliceMut) -> std::io::Result<()> {
|
|
||||||
assert_eq!(4 * sub_slice.len(), self.next_sectors.len());
|
|
||||||
|
|
||||||
for &entry in self.next_sectors.iter() {
|
|
||||||
sub_slice.write_all(&entry.to_le_bytes())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,12 +40,4 @@ impl FsInfo {
|
||||||
next_free,
|
next_free,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn free_count(&self) -> u32 {
|
|
||||||
self.free_count
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next_free(&self) -> Option<u32> {
|
|
||||||
Some(self.next_free)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,94 +1,48 @@
|
||||||
use std::io::{Read, Write};
|
use std::io::Read;
|
||||||
|
|
||||||
use log::debug;
|
use crate::FatFs;
|
||||||
|
use crate::subslice::SubSlice;
|
||||||
use crate::subslice::{SubSlice, SubSliceMut};
|
use crate::utils::replace;
|
||||||
use crate::{FatFs, FatType};
|
|
||||||
|
|
||||||
pub struct ClusterChainReader<'a> {
|
pub struct ClusterChainReader<'a> {
|
||||||
fat_fs: &'a FatFs,
|
sub_slice: SubSlice<'a>,
|
||||||
|
|
||||||
sub_slice: SubSlice,
|
|
||||||
|
|
||||||
next_cluster: Option<u32>,
|
next_cluster: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ClusterChainReader<'a> {
|
impl<'a> ClusterChainReader<'a> {
|
||||||
pub fn new(fat_fs: &'a FatFs, first_cluster: u32) -> Self {
|
pub fn new(fat_fs: &'a FatFs, first_cluster: u32) -> ClusterChainReader<'a> {
|
||||||
let next_cluster = fat_fs.next_cluster(first_cluster).unwrap_or(None);
|
let next_cluster = fat_fs.next_cluster(first_cluster).unwrap_or(None);
|
||||||
|
|
||||||
let sub_slice = fat_fs.cluster_as_subslice(first_cluster);
|
let sub_slice = fat_fs.cluster_as_subslice(first_cluster);
|
||||||
|
|
||||||
ClusterChainReader {
|
ClusterChainReader {
|
||||||
fat_fs,
|
|
||||||
sub_slice,
|
sub_slice,
|
||||||
next_cluster,
|
next_cluster,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn root_dir_reader(fat_fs: &'a FatFs) -> Self {
|
fn next_cluster(&mut self) -> bool {
|
||||||
match fat_fs.fat_type() {
|
|
||||||
FatType::Fat12 | FatType::Fat16 => {
|
|
||||||
// fixed root dir, so no need to chain
|
|
||||||
// get a single SubSlice for it and next_cluster is None
|
|
||||||
|
|
||||||
let sub_slice = fat_fs.root_dir_as_subslice();
|
|
||||||
|
|
||||||
ClusterChainReader {
|
|
||||||
fat_fs,
|
|
||||||
sub_slice,
|
|
||||||
next_cluster: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FatType::Fat32 => {
|
|
||||||
// FAT is directory_like, so get a real chain reader
|
|
||||||
|
|
||||||
Self::new(fat_fs, fat_fs.bpb.root_cluster().unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn move_to_next_cluster(&mut self) -> bool {
|
|
||||||
let Some(next_cluster) = self.next_cluster else {
|
let Some(next_cluster) = self.next_cluster else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.next_cluster = self.fat_fs.next_cluster(next_cluster).unwrap_or(None);
|
replace(&mut self.sub_slice, |sub_slice| {
|
||||||
self.sub_slice = self.fat_fs.cluster_as_subslice(next_cluster);
|
let fat_fs = sub_slice.release();
|
||||||
|
|
||||||
|
self.next_cluster = fat_fs.next_cluster(next_cluster).unwrap_or(None);
|
||||||
|
|
||||||
|
fat_fs.cluster_as_subslice(next_cluster)
|
||||||
|
});
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn skip(&mut self, n: u64) -> u64 {
|
|
||||||
let mut bytes_to_skip = n;
|
|
||||||
|
|
||||||
while bytes_to_skip > self.sub_slice.len() as u64 {
|
|
||||||
bytes_to_skip -= self.sub_slice.len() as u64;
|
|
||||||
if !self.move_to_next_cluster() {
|
|
||||||
// ran out of bytes to seek
|
|
||||||
return n - bytes_to_skip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if bytes_to_skip != 0 {
|
|
||||||
bytes_to_skip -= self.sub_slice.skip(bytes_to_skip as usize) as u64;
|
|
||||||
}
|
|
||||||
|
|
||||||
// n should absolutely be zero here
|
|
||||||
assert_eq!(bytes_to_skip, 0);
|
|
||||||
|
|
||||||
n
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn current_offset(&self) -> u64 {
|
|
||||||
self.sub_slice.offset()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Read for ClusterChainReader<'_> {
|
impl<'a> Read for ClusterChainReader<'a> {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
if self.sub_slice.is_empty() {
|
if self.sub_slice.is_empty() {
|
||||||
if !self.move_to_next_cluster() {
|
if !self.next_cluster() {
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -96,127 +50,3 @@ impl Read for ClusterChainReader<'_> {
|
||||||
self.sub_slice.read(buf)
|
self.sub_slice.read(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ClusterChainWriter<'a> {
|
|
||||||
fat_fs: &'a mut FatFs,
|
|
||||||
|
|
||||||
sub_slice: SubSliceMut,
|
|
||||||
|
|
||||||
// next_cluster: Option<u32>,
|
|
||||||
cur_cluster: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ClusterChainWriter<'a> {
|
|
||||||
pub fn new(fat_fs: &'a mut FatFs, first_cluster: u32) -> Self {
|
|
||||||
// let next_cluster = fat_fs.next_cluster(first_cluster).unwrap_or(None);
|
|
||||||
|
|
||||||
let sub_slice = fat_fs.cluster_as_subslice_mut(first_cluster);
|
|
||||||
|
|
||||||
ClusterChainWriter {
|
|
||||||
fat_fs,
|
|
||||||
sub_slice,
|
|
||||||
cur_cluster: first_cluster,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn root_dir_writer(fat_fs: &'a mut FatFs) -> Self {
|
|
||||||
match fat_fs.fat_type() {
|
|
||||||
FatType::Fat12 | FatType::Fat16 => {
|
|
||||||
// fixed root dir, so no need to chain
|
|
||||||
// get a single SubSliceMut for it and next_cluster is None
|
|
||||||
|
|
||||||
let sub_slice = fat_fs.root_dir_as_subslice_mut();
|
|
||||||
|
|
||||||
ClusterChainWriter {
|
|
||||||
fat_fs,
|
|
||||||
sub_slice,
|
|
||||||
cur_cluster: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FatType::Fat32 => {
|
|
||||||
// FAT is directory_like, so get a real chain writer
|
|
||||||
|
|
||||||
Self::new(fat_fs, fat_fs.bpb.root_cluster().unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn move_to_next_cluster(&mut self) -> bool {
|
|
||||||
// TODO: should allocate a new cluster here!
|
|
||||||
// let Some(next_cluster) = self.next_cluster else {
|
|
||||||
// let Some(new_cluster) = self.fat_fs.alloc_cluster() else {
|
|
||||||
// // cluster allocation failed
|
|
||||||
// return false;
|
|
||||||
// };
|
|
||||||
|
|
||||||
// return false;
|
|
||||||
// };
|
|
||||||
|
|
||||||
let Some(next_cluster) = self
|
|
||||||
.fat_fs
|
|
||||||
.next_cluster(self.cur_cluster)
|
|
||||||
.map_err(|err| {
|
|
||||||
debug!("failed to get next cluster: {err}");
|
|
||||||
err
|
|
||||||
})
|
|
||||||
.unwrap_or(None)
|
|
||||||
.or_else(|| {
|
|
||||||
debug!("allocating new cluster");
|
|
||||||
|
|
||||||
self.fat_fs.alloc_cluster(Some(self.cur_cluster))
|
|
||||||
})
|
|
||||||
else {
|
|
||||||
debug!("failed to allocate next cluster");
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!("next cluster: {next_cluster}");
|
|
||||||
|
|
||||||
self.fat_fs.cluster_as_subslice_mut(next_cluster);
|
|
||||||
self.cur_cluster = next_cluster;
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn skip(&mut self, n: u64) -> u64 {
|
|
||||||
let mut bytes_to_skip = n;
|
|
||||||
|
|
||||||
while bytes_to_skip > self.sub_slice.len() as u64 {
|
|
||||||
bytes_to_skip -= self.sub_slice.len() as u64;
|
|
||||||
if !self.move_to_next_cluster() {
|
|
||||||
// ran out of bytes to seek
|
|
||||||
return n - bytes_to_skip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if bytes_to_skip != 0 {
|
|
||||||
bytes_to_skip -= self.sub_slice.skip(bytes_to_skip as usize) as u64;
|
|
||||||
}
|
|
||||||
|
|
||||||
// n should absolutely be zero here
|
|
||||||
assert_eq!(bytes_to_skip, 0);
|
|
||||||
|
|
||||||
n
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn current_offset(&self) -> u64 {
|
|
||||||
self.sub_slice.offset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Write for ClusterChainWriter<'_> {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
||||||
if self.sub_slice.is_empty() {
|
|
||||||
if !(self.move_to_next_cluster()) {
|
|
||||||
return Ok(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.sub_slice.write(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> std::io::Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::fmt::Display;
|
use std::io::{Read, Seek, SeekFrom, Write};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use log::debug;
|
|
||||||
|
|
||||||
use crate::dir::DirIter;
|
use crate::dir::DirIter;
|
||||||
use crate::fat::FatError;
|
use crate::fat::{FatError, Fatty};
|
||||||
use crate::iter::ClusterChainReader;
|
|
||||||
pub use crate::slice_like::SliceLike;
|
|
||||||
use crate::subslice::{SubSlice, SubSliceMut};
|
use crate::subslice::{SubSlice, SubSliceMut};
|
||||||
|
|
||||||
pub mod bpb;
|
pub mod bpb;
|
||||||
|
|
@ -15,8 +11,7 @@ mod datetime;
|
||||||
pub mod dir;
|
pub mod dir;
|
||||||
pub mod fat;
|
pub mod fat;
|
||||||
pub mod fs_info;
|
pub mod fs_info;
|
||||||
pub mod iter;
|
mod iter;
|
||||||
mod slice_like;
|
|
||||||
mod subslice;
|
mod subslice;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
|
|
@ -27,59 +22,84 @@ pub enum FatType {
|
||||||
Fat32,
|
Fat32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FatFs {
|
pub trait SliceLike {
|
||||||
inner: Rc<RefCell<dyn SliceLike>>,
|
fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> std::io::Result<()>;
|
||||||
|
|
||||||
// fat_offset: u64,
|
fn write_at_offset(&mut self, offset: u64, bytes: &[u8]) -> std::io::Result<()>;
|
||||||
// fat_size: usize,
|
|
||||||
root_dir_offset: Option<u64>,
|
|
||||||
root_dir_size: usize,
|
|
||||||
|
|
||||||
pub data_offset: u64,
|
|
||||||
// data_size: usize,
|
|
||||||
bytes_per_cluster: usize,
|
|
||||||
|
|
||||||
bpb: bpb::Bpb,
|
|
||||||
|
|
||||||
fat: fat::Fat,
|
|
||||||
|
|
||||||
next_free: Option<u32>,
|
|
||||||
free_count: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for FatFs {
|
impl SliceLike for &mut [u8] {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> std::io::Result<()> {
|
||||||
writeln!(f, "{}", self.bpb)?;
|
if offset as usize + buf.len() > self.len() {
|
||||||
writeln!(f, "")?;
|
return Err(std::io::Error::other(anyhow::anyhow!(
|
||||||
writeln!(f, "{}", self.fat)?;
|
"reading {} bytes at offset {} is out of bounds for slice of len {}",
|
||||||
|
buf.len(),
|
||||||
|
offset,
|
||||||
|
self.len()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.copy_from_slice(&self[offset as usize..][..buf.len()]);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_at_offset(&mut self, offset: u64, bytes: &[u8]) -> std::io::Result<()> {
|
||||||
|
if offset as usize + bytes.len() > self.len() {
|
||||||
|
return Err(std::io::Error::other(anyhow::anyhow!(
|
||||||
|
"writing {} bytes at offset {} is out of bounds for slice of len {}",
|
||||||
|
bytes.len(),
|
||||||
|
offset,
|
||||||
|
self.len()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
self[offset as usize..][..bytes.len()].copy_from_slice(bytes);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl Send for FatFs {}
|
impl SliceLike for std::fs::File {
|
||||||
|
fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> std::io::Result<()> {
|
||||||
|
self.seek(SeekFrom::Start(offset))?;
|
||||||
|
|
||||||
impl Drop for FatFs {
|
self.read_exact(buf)?;
|
||||||
fn drop(&mut self) {
|
|
||||||
let fat_slice = SubSliceMut::new(
|
|
||||||
Rc::clone(&self.inner),
|
|
||||||
self.bpb.fat_offset(),
|
|
||||||
self.bpb.fat_len_bytes(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Err(err) = self.fat.write_back(fat_slice) {
|
Ok(())
|
||||||
debug!("writing FAT back to disk failed: {err}");
|
}
|
||||||
}
|
|
||||||
|
fn write_at_offset(&mut self, offset: u64, bytes: &[u8]) -> std::io::Result<()> {
|
||||||
|
self.seek(SeekFrom::Start(offset))?;
|
||||||
|
|
||||||
|
self.write_all(bytes)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FatFs {
|
#[allow(dead_code)]
|
||||||
pub fn load<S>(data: S) -> anyhow::Result<FatFs>
|
pub struct FatFs {
|
||||||
where
|
inner: Rc<RefCell<dyn SliceLike>>,
|
||||||
S: SliceLike + Send + 'static,
|
|
||||||
{
|
|
||||||
let data = Rc::new(RefCell::new(data));
|
|
||||||
|
|
||||||
|
fat_offset: u64,
|
||||||
|
fat_size: usize,
|
||||||
|
|
||||||
|
root_dir_offset: Option<u64>,
|
||||||
|
root_dir_size: usize,
|
||||||
|
|
||||||
|
pub data_offset: u64,
|
||||||
|
data_size: usize,
|
||||||
|
|
||||||
|
bytes_per_cluster: usize,
|
||||||
|
|
||||||
|
bpb: bpb::Bpb,
|
||||||
|
|
||||||
|
fat: fat::Fat,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FatFs {
|
||||||
|
pub fn load(data: Rc<RefCell<dyn SliceLike>>) -> anyhow::Result<FatFs> {
|
||||||
let mut bpb_bytes = [0; 512];
|
let mut bpb_bytes = [0; 512];
|
||||||
|
|
||||||
data.borrow_mut().read_at_offset(0, &mut bpb_bytes)?;
|
data.borrow_mut().read_at_offset(0, &mut bpb_bytes)?;
|
||||||
|
|
@ -108,177 +128,127 @@ impl FatFs {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// let fat_offset = bpb.fat_offset();
|
let fat_offset = bpb.fat_offset();
|
||||||
// let fat_size = bpb.fat_len_bytes();
|
let fat_size = bpb.fat_len_bytes();
|
||||||
|
|
||||||
let root_dir_offset = bpb.root_directory_offset();
|
let root_dir_offset = bpb.root_directory_offset();
|
||||||
let root_dir_size = bpb.root_dir_len_bytes();
|
let root_dir_size = bpb.root_dir_len_bytes();
|
||||||
|
|
||||||
let data_offset = bpb.data_offset();
|
let data_offset = bpb.data_offset();
|
||||||
// let data_size = bpb.data_len_bytes();
|
let data_size = bpb.data_len_bytes();
|
||||||
|
|
||||||
let bytes_per_cluster = bpb.bytes_per_cluster();
|
let bytes_per_cluster = bpb.bytes_per_cluster();
|
||||||
|
|
||||||
let next_free = fat.first_free_cluster();
|
|
||||||
let free_count = fat.count_free_clusters();
|
|
||||||
|
|
||||||
Ok(FatFs {
|
Ok(FatFs {
|
||||||
inner: data,
|
inner: data,
|
||||||
// fat_offset,
|
fat_offset,
|
||||||
// fat_size,
|
fat_size,
|
||||||
root_dir_offset,
|
root_dir_offset,
|
||||||
root_dir_size,
|
root_dir_size,
|
||||||
data_offset,
|
data_offset,
|
||||||
// data_size,
|
data_size,
|
||||||
bytes_per_cluster,
|
bytes_per_cluster,
|
||||||
bpb,
|
bpb,
|
||||||
fat,
|
fat,
|
||||||
next_free,
|
|
||||||
free_count,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fat_type(&self) -> FatType {
|
pub fn bpb(&self) -> &bpb::Bpb {
|
||||||
self.fat.fat_type()
|
&self.bpb
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fat(&self) -> &fat::Fat {
|
||||||
|
&self.fat
|
||||||
}
|
}
|
||||||
|
|
||||||
/// byte offset of data cluster
|
/// byte offset of data cluster
|
||||||
fn data_cluster_to_offset(&self, cluster: u32) -> u64 {
|
pub fn data_cluster_to_offset(&self, cluster: u32) -> u64 {
|
||||||
// assert!(cluster >= 2);
|
// assert!(cluster >= 2);
|
||||||
|
|
||||||
// assert!(self.fat.valid_entries().contains(&cluster));
|
assert!(self.fat().get_valid_clusters().contains(&cluster));
|
||||||
|
|
||||||
self.data_offset + (cluster - 2) as u64 * self.bytes_per_cluster as u64
|
self.data_offset + (cluster - 2) as u64 * self.bytes_per_cluster as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn free_clusters(&self) -> u32 {
|
|
||||||
// self.fat.count_free_clusters()
|
|
||||||
self.free_count
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn alloc_cluster(&mut self, prev_cluster: Option<u32>) -> Option<u32> {
|
|
||||||
let Some(new_cluster) = self.next_free else {
|
|
||||||
// no free cluster
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!("next free cluster: {new_cluster}");
|
|
||||||
|
|
||||||
// set cluster as EOF
|
|
||||||
self.fat.set_next_cluster(new_cluster, None);
|
|
||||||
|
|
||||||
if let Some(prev_cluster) = prev_cluster {
|
|
||||||
self.fat.set_next_cluster(prev_cluster, Some(new_cluster));
|
|
||||||
}
|
|
||||||
|
|
||||||
// something went terribly wrong
|
|
||||||
assert_ne!(self.free_count, 0);
|
|
||||||
|
|
||||||
self.free_count -= 1;
|
|
||||||
|
|
||||||
// find next free cluster
|
|
||||||
self.next_free = self.fat.first_free_cluster();
|
|
||||||
|
|
||||||
Some(new_cluster)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dealloc_cluster(&mut self, cluster: u32) {
|
|
||||||
// assert cluster is actually valid
|
|
||||||
self.fat.free_cluster(cluster);
|
|
||||||
|
|
||||||
if self.next_free.is_none() || self.next_free.unwrap() > cluster {
|
|
||||||
self.next_free = Some(cluster);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.free_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bytes_per_sector(&self) -> u16 {
|
|
||||||
self.bpb.bytes_per_sector()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sectors_per_cluster(&self) -> u8 {
|
|
||||||
self.bpb.sectors_per_cluster()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn root_cluster(&self) -> Option<u32> {
|
|
||||||
self.bpb.root_cluster()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// next data cluster or None is cluster is EOF
|
/// next data cluster or None is cluster is EOF
|
||||||
///
|
///
|
||||||
/// giving an invalid cluster (free, reserved, or defective) returns an appropriate error
|
/// giving an invalid cluster (free, reserved, or defective) returns an appropriate error
|
||||||
pub fn next_cluster(&self, cluster: u32) -> Result<Option<u32>, FatError> {
|
pub fn next_cluster(&self, cluster: u32) -> Result<Option<u32>, FatError> {
|
||||||
self.fat.get_next_cluster(cluster)
|
self.fat().get_next_cluster(cluster)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cluster_as_subslice(&self, cluster: u32) -> SubSlice {
|
pub fn cluster_as_subslice_mut(&mut self, cluster: u32) -> SubSliceMut<'_> {
|
||||||
if cluster == 0 {
|
|
||||||
// for cluster 0 simply return empty subslice
|
|
||||||
// this makes things a bit easier, since cluster 0 is used as a marker that a file/dir
|
|
||||||
// is empty
|
|
||||||
|
|
||||||
return SubSlice::new(self.inner.clone(), 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let offset = self.data_cluster_to_offset(cluster);
|
let offset = self.data_cluster_to_offset(cluster);
|
||||||
|
|
||||||
SubSlice::new(self.inner.clone(), offset, self.bytes_per_cluster)
|
SubSliceMut::new(self, offset, self.bytes_per_cluster)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cluster_as_subslice_mut(&self, cluster: u32) -> SubSliceMut {
|
pub fn cluster_as_subslice(&self, cluster: u32) -> SubSlice<'_> {
|
||||||
if cluster == 0 {
|
|
||||||
// for cluster 0 simply return empty subslice
|
|
||||||
// this makes things a bit easier, since cluster 0 is used as a marker that a file/dir
|
|
||||||
// is empty
|
|
||||||
|
|
||||||
return SubSliceMut::new(self.inner.clone(), 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let offset = self.data_cluster_to_offset(cluster);
|
let offset = self.data_cluster_to_offset(cluster);
|
||||||
|
|
||||||
SubSliceMut::new(self.inner.clone(), offset, self.bytes_per_cluster)
|
SubSlice::new(self, offset, self.bytes_per_cluster)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn root_dir_as_subslice(&self) -> SubSlice {
|
pub fn root_dir_bytes(&mut self) -> std::io::Result<Vec<u8>> {
|
||||||
SubSlice::new(self.inner.clone(), self.root_dir_offset.unwrap(), self.root_dir_size)
|
if let Some(root_dir_offset) = self.root_dir_offset {
|
||||||
|
let mut data = Vec::new();
|
||||||
|
|
||||||
|
let mut subslice = SubSliceMut::new(self, root_dir_offset, self.root_dir_size);
|
||||||
|
|
||||||
|
subslice.read_to_end(&mut data)?;
|
||||||
|
|
||||||
|
return Ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cluster = self.bpb().root_cluster().unwrap();
|
||||||
|
|
||||||
|
let mut data = vec![0; self.bytes_per_cluster];
|
||||||
|
|
||||||
|
let mut inner = self.inner.borrow_mut();
|
||||||
|
|
||||||
|
inner.read_at_offset(self.data_cluster_to_offset(cluster), &mut data)?;
|
||||||
|
|
||||||
|
while let Ok(Some(next_cluster)) = self.next_cluster(cluster) {
|
||||||
|
cluster = next_cluster;
|
||||||
|
|
||||||
|
inner.read_at_offset(self.data_cluster_to_offset(cluster), &mut data)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn root_dir_as_subslice_mut(&self) -> SubSliceMut {
|
fn chain_reader(&self, first_cluster: u32) -> impl Read {
|
||||||
SubSliceMut::new(self.inner.clone(), self.root_dir_offset.unwrap(), self.root_dir_size)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn chain_reader(&'_ self, first_cluster: u32) -> iter::ClusterChainReader<'_> {
|
|
||||||
iter::ClusterChainReader::new(self, first_cluster)
|
iter::ClusterChainReader::new(self, first_cluster)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn chain_writer(&'_ mut self, first_cluster: u32) -> iter::ClusterChainWriter<'_> {
|
pub fn root_dir_iter<'a>(&'a self) -> DirIter<Box<dyn Read + 'a>> {
|
||||||
iter::ClusterChainWriter::new(self, first_cluster)
|
// Box<dyn Iterator<Item = DirEntry> + '_>
|
||||||
|
// TODO: maybe wrap this in another RootDirIter enum, so we don't have to Box<dyn>
|
||||||
|
|
||||||
|
if let Some(root_dir_offset) = self.root_dir_offset {
|
||||||
|
// FAT12/FAT16
|
||||||
|
|
||||||
|
let sub_slice = SubSlice::new(self, root_dir_offset, self.root_dir_size);
|
||||||
|
|
||||||
|
return DirIter::new(Box::new(sub_slice));
|
||||||
|
}
|
||||||
|
|
||||||
|
// FAT32
|
||||||
|
|
||||||
|
// can't fail; we're in the FAT32 case
|
||||||
|
let root_cluster = self.bpb().root_cluster().unwrap();
|
||||||
|
|
||||||
|
let cluster_iter = iter::ClusterChainReader::new(self, root_cluster);
|
||||||
|
|
||||||
|
DirIter::new(Box::new(cluster_iter))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn root_dir_iter<'a>(&self) -> DirIter<'_> {
|
pub fn dir_iter<'a>(&'a self, first_cluster: u32) -> DirIter<Box<dyn Read + 'a>> {
|
||||||
let reader = ClusterChainReader::root_dir_reader(self);
|
// TODO: return type must match root_dir_iter
|
||||||
|
// if the Box<dyn> is changed there, update here as well
|
||||||
|
|
||||||
DirIter::new(reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dir_iter<'a>(&self, first_cluster: u32) -> DirIter<'_> {
|
|
||||||
let cluster_iter = self.chain_reader(first_cluster);
|
let cluster_iter = self.chain_reader(first_cluster);
|
||||||
|
|
||||||
DirIter::new(cluster_iter)
|
DirIter::new(Box::new(cluster_iter))
|
||||||
}
|
|
||||||
|
|
||||||
pub fn file_reader(&self, first_cluster: u32) -> iter::ClusterChainReader<'_> {
|
|
||||||
// TODO: needs to take file size into account
|
|
||||||
assert!(first_cluster >= 2);
|
|
||||||
|
|
||||||
self.chain_reader(first_cluster)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn file_writer(&mut self, first_cluster: u32) -> iter::ClusterChainWriter<'_> {
|
|
||||||
// TODO: needs to take file size into account
|
|
||||||
assert!(first_cluster >= 2);
|
|
||||||
|
|
||||||
self.chain_writer(first_cluster)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Read as _, Seek as _, SeekFrom, Write as _};
|
|
||||||
|
|
||||||
pub trait SliceLike {
|
|
||||||
fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> std::io::Result<()>;
|
|
||||||
|
|
||||||
fn write_at_offset(&mut self, offset: u64, bytes: &[u8]) -> std::io::Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SliceLike for &mut [u8] {
|
|
||||||
fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> std::io::Result<()> {
|
|
||||||
if offset as usize + buf.len() > self.len() {
|
|
||||||
return Err(std::io::Error::other(anyhow::anyhow!(
|
|
||||||
"reading {} bytes at offset {} is out of bounds for slice of len {}",
|
|
||||||
buf.len(),
|
|
||||||
offset,
|
|
||||||
self.len()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.copy_from_slice(&self[offset as usize..][..buf.len()]);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_at_offset(&mut self, offset: u64, bytes: &[u8]) -> std::io::Result<()> {
|
|
||||||
if offset as usize + bytes.len() > self.len() {
|
|
||||||
return Err(std::io::Error::other(anyhow::anyhow!(
|
|
||||||
"writing {} bytes at offset {} is out of bounds for slice of len {}",
|
|
||||||
bytes.len(),
|
|
||||||
offset,
|
|
||||||
self.len()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
self[offset as usize..][..bytes.len()].copy_from_slice(bytes);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SliceLike for File {
|
|
||||||
fn read_at_offset(&mut self, offset: u64, buf: &mut [u8]) -> std::io::Result<()> {
|
|
||||||
self.seek(SeekFrom::Start(offset))?;
|
|
||||||
|
|
||||||
self.read_exact(buf)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_at_offset(&mut self, offset: u64, bytes: &[u8]) -> std::io::Result<()> {
|
|
||||||
self.seek(SeekFrom::Start(offset))?;
|
|
||||||
|
|
||||||
self.write_all(bytes)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +1,16 @@
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use crate::SliceLike;
|
use crate::FatFs;
|
||||||
|
|
||||||
pub struct SubSlice {
|
pub struct SubSliceMut<'a> {
|
||||||
data: Rc<RefCell<dyn SliceLike>>,
|
fat_fs: &'a mut FatFs,
|
||||||
|
|
||||||
offset: u64,
|
offset: u64,
|
||||||
len: usize,
|
len: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for SubSlice {
|
impl Debug for SubSliceMut<'_> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("SubSliceMut")
|
f.debug_struct("SubSliceMut")
|
||||||
.field("offset", &self.offset)
|
.field("offset", &self.offset)
|
||||||
|
|
@ -21,15 +19,17 @@ impl Debug for SubSlice {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SubSlice {
|
impl SubSliceMut<'_> {
|
||||||
pub fn new(data: Rc<RefCell<dyn SliceLike>>, offset: u64, len: usize) -> SubSlice {
|
pub fn new(fat_fs: &mut FatFs, offset: u64, len: usize) -> SubSliceMut<'_> {
|
||||||
SubSlice { data, offset, len }
|
SubSliceMut {
|
||||||
}
|
fat_fs,
|
||||||
|
offset,
|
||||||
pub fn offset(&self) -> u64 {
|
len,
|
||||||
self.offset
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubSliceMut<'_> {
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.len
|
self.len
|
||||||
}
|
}
|
||||||
|
|
@ -37,22 +37,14 @@ impl<'a> SubSlice {
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.len() == 0
|
self.len() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn skip(&mut self, n: usize) -> usize {
|
|
||||||
let n = n.min(self.len());
|
|
||||||
|
|
||||||
self.offset += n as u64;
|
|
||||||
self.len -= n;
|
|
||||||
|
|
||||||
n
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Read for SubSlice {
|
impl Read for SubSliceMut<'_> {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
let bytes_to_read = self.len.min(buf.len());
|
let bytes_to_read = self.len.min(buf.len());
|
||||||
|
|
||||||
self.data
|
self.fat_fs
|
||||||
|
.inner
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.read_at_offset(self.offset, &mut buf[..bytes_to_read])?;
|
.read_at_offset(self.offset, &mut buf[..bytes_to_read])?;
|
||||||
|
|
||||||
|
|
@ -63,72 +55,12 @@ impl Read for SubSlice {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SubSliceMut {
|
impl Write for SubSliceMut<'_> {
|
||||||
// fat_fs: &'a FatFs,
|
|
||||||
data: Rc<RefCell<dyn SliceLike>>,
|
|
||||||
|
|
||||||
offset: u64,
|
|
||||||
len: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for SubSliceMut {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("SubSliceMut")
|
|
||||||
.field("offset", &self.offset)
|
|
||||||
.field("len", &self.len)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SubSliceMut {
|
|
||||||
pub fn new(data: Rc<RefCell<dyn SliceLike>>, offset: u64, len: usize) -> SubSliceMut {
|
|
||||||
SubSliceMut { data, offset, len }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> SubSliceMut {
|
|
||||||
pub fn offset(&self) -> u64 {
|
|
||||||
self.offset
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.len
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.len() == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn skip(&mut self, n: usize) -> usize {
|
|
||||||
let n = n.min(self.len());
|
|
||||||
|
|
||||||
self.offset += n as u64;
|
|
||||||
self.len -= n;
|
|
||||||
|
|
||||||
n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for SubSliceMut {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
|
||||||
let bytes_to_read = self.len.min(buf.len());
|
|
||||||
|
|
||||||
self.data
|
|
||||||
.borrow_mut()
|
|
||||||
.read_at_offset(self.offset, &mut buf[..bytes_to_read])?;
|
|
||||||
|
|
||||||
self.offset += bytes_to_read as u64;
|
|
||||||
self.len -= bytes_to_read;
|
|
||||||
|
|
||||||
Ok(bytes_to_read)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Write for SubSliceMut {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||||
let bytes_to_write = self.len.min(buf.len());
|
let bytes_to_write = self.len.min(buf.len());
|
||||||
|
|
||||||
self.data
|
self.fat_fs
|
||||||
|
.inner
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.write_at_offset(self.offset, &buf[..bytes_to_write])?;
|
.write_at_offset(self.offset, &buf[..bytes_to_write])?;
|
||||||
|
|
||||||
|
|
@ -142,3 +74,67 @@ impl Write for SubSliceMut {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct SubSlice<'a> {
|
||||||
|
fat_fs: &'a FatFs,
|
||||||
|
|
||||||
|
offset: u64,
|
||||||
|
len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for SubSlice<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("SubSliceMut")
|
||||||
|
.field("offset", &self.offset)
|
||||||
|
.field("len", &self.len)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubSlice<'_> {
|
||||||
|
pub fn new(fat_fs: &FatFs, offset: u64, len: usize) -> SubSlice<'_> {
|
||||||
|
SubSlice {
|
||||||
|
fat_fs,
|
||||||
|
offset,
|
||||||
|
len,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fat_fs(&self) -> &FatFs {
|
||||||
|
self.fat_fs
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fat_fs_mut(&self) -> &FatFs {
|
||||||
|
self.fat_fs
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.len() == 0
|
||||||
|
}
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SubSlice<'a> {
|
||||||
|
/// releases the inner &FatFs, consuming self in the process
|
||||||
|
pub fn release(self) -> &'a FatFs {
|
||||||
|
self.fat_fs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for SubSlice<'_> {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
let bytes_to_read = self.len.min(buf.len());
|
||||||
|
|
||||||
|
self.fat_fs
|
||||||
|
.inner
|
||||||
|
.borrow_mut()
|
||||||
|
.read_at_offset(self.offset, &mut buf[..bytes_to_read])?;
|
||||||
|
|
||||||
|
self.offset += bytes_to_read as u64;
|
||||||
|
self.len -= bytes_to_read;
|
||||||
|
|
||||||
|
Ok(bytes_to_read)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,24 @@ pub fn load_u16_le(bytes: &[u8]) -> u16 {
|
||||||
|
|
||||||
u16::from_le_bytes(bytes.try_into().unwrap())
|
u16::from_le_bytes(bytes.try_into().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_u32_le(bytes: &[u8]) -> u32 {
|
pub fn load_u32_le(bytes: &[u8]) -> u32 {
|
||||||
assert_eq!(bytes.len(), 4);
|
assert_eq!(bytes.len(), 4);
|
||||||
|
|
||||||
u32::from_le_bytes(bytes.try_into().unwrap())
|
u32::from_le_bytes(bytes.try_into().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// replace the value at x with f(x)
|
||||||
|
///
|
||||||
|
/// SAFETY:
|
||||||
|
/// should be safe, I guess? MIRI didn't complain about it
|
||||||
|
pub fn replace<T>(x: &mut T, f: impl FnOnce(T) -> T) {
|
||||||
|
unsafe {
|
||||||
|
let x_ptr = x as *mut T;
|
||||||
|
|
||||||
|
let old_x = std::ptr::read(x_ptr);
|
||||||
|
|
||||||
|
let new_x = f(old_x);
|
||||||
|
|
||||||
|
std::ptr::write(x_ptr, new_x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use fat_bits::FatFs;
|
use fat_bits::FatFs;
|
||||||
use fat_bits::dir::DirEntry;
|
use fat_bits::dir::{DirEntry, DirIter};
|
||||||
|
use fat_bits::fat::Fatty as _;
|
||||||
|
|
||||||
pub fn main() -> anyhow::Result<()> {
|
pub fn main() -> anyhow::Result<()> {
|
||||||
let args = std::env::args();
|
let args = std::env::args();
|
||||||
|
|
@ -18,20 +22,18 @@ pub fn main() -> anyhow::Result<()> {
|
||||||
|
|
||||||
// println!("{}", bpb);
|
// println!("{}", bpb);
|
||||||
|
|
||||||
let fat_fs = FatFs::load(file)?;
|
let fat_fs = FatFs::load(Rc::new(RefCell::new(file)))?;
|
||||||
|
|
||||||
// println!("{}", fat_fs.bpb());
|
println!("{}", fat_fs.bpb());
|
||||||
// println!();
|
println!();
|
||||||
// println!("{}", fat_fs.fat());
|
println!("{}", fat_fs.fat());
|
||||||
|
|
||||||
println!("{}", fat_fs);
|
|
||||||
println!();
|
println!();
|
||||||
println!(
|
println!(
|
||||||
"free clusters: {} ({} bytes)",
|
"free clusters: {} ({} bytes)",
|
||||||
fat_fs.free_clusters(),
|
fat_fs.fat().count_free_clusters(),
|
||||||
fat_fs.free_clusters() as usize
|
fat_fs.fat().count_free_clusters()
|
||||||
* fat_fs.bytes_per_sector() as usize
|
* fat_fs.bpb().bytes_per_sector() as usize
|
||||||
* fat_fs.sectors_per_cluster() as usize
|
* fat_fs.bpb().sectors_per_cluster() as usize
|
||||||
);
|
);
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
|
|
|
||||||
|
|
@ -5,20 +5,10 @@ edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.98"
|
anyhow = "1.0.98"
|
||||||
bitflags = "2.9.1"
|
chrono = { version = "0.4.41", default-features = false, features = ["alloc", "clock", "std"] }
|
||||||
chrono = { version = "0.4.41", default-features = false, features = [
|
|
||||||
"alloc",
|
|
||||||
"clock",
|
|
||||||
"std",
|
|
||||||
] }
|
|
||||||
compact_string = "0.1.0"
|
|
||||||
fat-bits = { version = "0.1.0", path = "../fat-bits" }
|
fat-bits = { version = "0.1.0", path = "../fat-bits" }
|
||||||
fuser = "0.15.1"
|
fuser = "0.15.1"
|
||||||
fxhash = "0.2.1"
|
|
||||||
libc = "0.2.174"
|
libc = "0.2.174"
|
||||||
log = "0.4.27"
|
log = "0.4.27"
|
||||||
rand = { version = "0.9.2", default-features = false, features = [
|
rand = { version = "0.9.2", default-features = false, features = ["os_rng", "small_rng"] }
|
||||||
"os_rng",
|
|
||||||
"small_rng",
|
|
||||||
] }
|
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,14 +1,10 @@
|
||||||
use std::cell::{LazyCell, RefCell};
|
use std::cell::{LazyCell, RefCell};
|
||||||
use std::rc::Rc;
|
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use chrono::{NaiveDateTime, NaiveTime};
|
use chrono::{NaiveDateTime, NaiveTime};
|
||||||
use fat_bits::FatFs;
|
use fat_bits::FatFs;
|
||||||
use fat_bits::dir::{DirEntry, DirIter};
|
use fat_bits::dir::{DirEntry, DirIter};
|
||||||
use fat_bits::iter::{ClusterChainReader, ClusterChainWriter};
|
|
||||||
use fuser::FileAttr;
|
use fuser::FileAttr;
|
||||||
use libc::{EISDIR, ENOENT, ENOTDIR};
|
|
||||||
use log::debug;
|
|
||||||
use rand::{Rng, SeedableRng as _};
|
use rand::{Rng, SeedableRng as _};
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
|
|
@ -23,10 +19,13 @@ thread_local! {
|
||||||
static RNG: LazyCell<RefCell<rand::rngs::SmallRng>> = LazyCell::new(|| RefCell::new(rand::rngs::SmallRng::from_os_rng()));
|
static RNG: LazyCell<RefCell<rand::rngs::SmallRng>> = LazyCell::new(|| RefCell::new(rand::rngs::SmallRng::from_os_rng()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_random<T>() -> T
|
fn get_random_u32() -> u32 {
|
||||||
where
|
// RNG.with(|x| unsafe {
|
||||||
rand::distr::StandardUniform: rand::distr::Distribution<T>,
|
// let rng = &mut (*x.get());
|
||||||
{
|
|
||||||
|
// rng.random::<u32>()
|
||||||
|
// })
|
||||||
|
|
||||||
RNG.with(|rng| rng.borrow_mut().random())
|
RNG.with(|rng| rng.borrow_mut().random())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,81 +44,38 @@ impl From<Kind> for fuser::FileType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const ROOT_INO: u64 = 1;
|
const ROOT_INO: u64 = 1;
|
||||||
|
|
||||||
pub type InodeRef = Rc<RefCell<Inode>>;
|
|
||||||
// pub type InodeWeak = Weak<RefCell<Inode>>;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct Inode {
|
pub struct Inode {
|
||||||
ino: u64,
|
ino: u64,
|
||||||
// FUSE uses a u64 for generation, but the Linux kernel only handles u32s anyway, truncating
|
|
||||||
// the high bits, so using more is pretty pointless and possibly even detrimental
|
|
||||||
generation: u32,
|
generation: u32,
|
||||||
|
|
||||||
ref_count: u64,
|
size: u64,
|
||||||
|
|
||||||
uid: u32,
|
|
||||||
gid: u32,
|
|
||||||
|
|
||||||
path: Rc<str>,
|
|
||||||
|
|
||||||
parent: Option<InodeRef>,
|
|
||||||
|
|
||||||
block_size: u32,
|
block_size: u32,
|
||||||
|
|
||||||
kind: Kind,
|
kind: Kind,
|
||||||
|
|
||||||
read_only: bool,
|
read_only: bool,
|
||||||
|
|
||||||
dirty: bool,
|
|
||||||
|
|
||||||
// these are the fields that have to get written back to the DirEntry on write_back
|
|
||||||
size: u64,
|
|
||||||
|
|
||||||
atime: SystemTime,
|
atime: SystemTime,
|
||||||
mtime: SystemTime,
|
mtime: SystemTime,
|
||||||
|
// ctime: SystemTime,
|
||||||
crtime: SystemTime,
|
crtime: SystemTime,
|
||||||
|
|
||||||
|
uid: u32,
|
||||||
|
gid: u32,
|
||||||
|
|
||||||
first_cluster: u32,
|
first_cluster: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Inode {
|
#[allow(dead_code)]
|
||||||
fn drop(&mut self) {
|
|
||||||
// since we don't have a handle on the FatFs we can't do the write-back here
|
|
||||||
assert!(
|
|
||||||
!self.dirty,
|
|
||||||
"inode {} is dirty, but was not written back to FS before being destroyed",
|
|
||||||
self.ino()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Inode {
|
impl Inode {
|
||||||
fn new_generation() -> u32 {
|
pub fn new(fat_fs: &FatFs, dir_entry: DirEntry, uid: u32, gid: u32) -> Inode {
|
||||||
let rand: u16 = get_random();
|
|
||||||
|
|
||||||
let secs = SystemTime::UNIX_EPOCH
|
|
||||||
.elapsed()
|
|
||||||
.map(|dur| dur.as_secs() as u16)
|
|
||||||
.unwrap_or(0);
|
|
||||||
|
|
||||||
((secs as u32) << 16) | rand as u32
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(
|
|
||||||
fat_fs: &FatFs,
|
|
||||||
dir_entry: &DirEntry,
|
|
||||||
ino: u64,
|
|
||||||
uid: u32,
|
|
||||||
gid: u32,
|
|
||||||
path: impl Into<Rc<str>>,
|
|
||||||
parent: InodeRef,
|
|
||||||
) -> Inode {
|
|
||||||
assert!(dir_entry.is_file() || dir_entry.is_dir());
|
assert!(dir_entry.is_file() || dir_entry.is_dir());
|
||||||
|
|
||||||
let generation = Self::new_generation();
|
let generation = get_random_u32();
|
||||||
|
|
||||||
let kind = if dir_entry.is_dir() {
|
let kind = if dir_entry.is_dir() {
|
||||||
Kind::Dir
|
Kind::Dir
|
||||||
|
|
@ -139,138 +95,22 @@ impl Inode {
|
||||||
let mtime = datetime_to_system(dir_entry.write_time());
|
let mtime = datetime_to_system(dir_entry.write_time());
|
||||||
let crtime = datetime_to_system(dir_entry.create_time());
|
let crtime = datetime_to_system(dir_entry.create_time());
|
||||||
|
|
||||||
let path = path.into();
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
"creating new inode: ino: {} name: {} path: {}",
|
|
||||||
ino,
|
|
||||||
dir_entry.name_string(),
|
|
||||||
path
|
|
||||||
);
|
|
||||||
|
|
||||||
Inode {
|
Inode {
|
||||||
ino,
|
ino: dir_entry.first_cluster() as u64,
|
||||||
generation,
|
generation,
|
||||||
ref_count: 0,
|
|
||||||
parent: Some(parent),
|
|
||||||
size: dir_entry.file_size() as u64,
|
size: dir_entry.file_size() as u64,
|
||||||
block_size: fat_fs.bytes_per_sector() as u32,
|
block_size: fat_fs.bpb().bytes_per_sector() as u32,
|
||||||
kind,
|
kind,
|
||||||
read_only: dir_entry.is_readonly(),
|
read_only: dir_entry.is_readonly(),
|
||||||
dirty: false,
|
|
||||||
atime,
|
atime,
|
||||||
mtime,
|
mtime,
|
||||||
crtime,
|
crtime,
|
||||||
uid,
|
uid,
|
||||||
gid,
|
gid,
|
||||||
first_cluster: dir_entry.first_cluster(),
|
first_cluster: dir_entry.first_cluster(),
|
||||||
path,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn root_inode(fat_fs: &FatFs, uid: u32, gid: u32) -> Inode {
|
|
||||||
let root_cluster = fat_fs.root_cluster().unwrap_or(0);
|
|
||||||
|
|
||||||
Inode {
|
|
||||||
ino: ROOT_INO,
|
|
||||||
generation: 0, // root cluster always has constant generation of 0
|
|
||||||
ref_count: 0,
|
|
||||||
parent: None, // parent is self
|
|
||||||
size: 0,
|
|
||||||
block_size: fat_fs.bytes_per_sector() as u32,
|
|
||||||
kind: Kind::Dir,
|
|
||||||
read_only: false,
|
|
||||||
dirty: false,
|
|
||||||
atime: SystemTime::UNIX_EPOCH,
|
|
||||||
mtime: SystemTime::UNIX_EPOCH,
|
|
||||||
crtime: SystemTime::UNIX_EPOCH,
|
|
||||||
uid,
|
|
||||||
gid,
|
|
||||||
first_cluster: root_cluster,
|
|
||||||
path: "/".into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ino(&self) -> u64 {
|
|
||||||
self.ino
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generation(&self) -> u32 {
|
|
||||||
self.generation
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ref_count(&self) -> u64 {
|
|
||||||
self.ref_count
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inc_ref_count(&mut self) {
|
|
||||||
debug!(
|
|
||||||
"increasing ref_count of ino {} by 1 (new ref_count: {})",
|
|
||||||
self.ino(),
|
|
||||||
self.ref_count() + 1
|
|
||||||
);
|
|
||||||
|
|
||||||
self.ref_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dec_ref_count(&mut self, n: u64) -> u64 {
|
|
||||||
debug!(
|
|
||||||
"decreasing ref_count of ino {} by {} (new ref_count: {})",
|
|
||||||
self.ino(),
|
|
||||||
n,
|
|
||||||
self.ref_count().saturating_sub(n),
|
|
||||||
);
|
|
||||||
|
|
||||||
if self.ref_count < n {
|
|
||||||
debug!(
|
|
||||||
"inode {}: tried to decrement refcount by {}, but is only {}",
|
|
||||||
self.ino(),
|
|
||||||
n,
|
|
||||||
self.ref_count
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.ref_count = self.ref_count.saturating_sub(n);
|
|
||||||
|
|
||||||
self.ref_count
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parent(&self) -> Option<&InodeRef> {
|
|
||||||
self.parent.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size(&self) -> u64 {
|
|
||||||
self.size
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn kind(&self) -> Kind {
|
|
||||||
self.kind
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_file(&self) -> bool {
|
|
||||||
self.kind == Kind::File
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_dir(&self) -> bool {
|
|
||||||
self.kind == Kind::Dir
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_read_only(&self) -> bool {
|
|
||||||
self.read_only
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn first_cluster(&self) -> u32 {
|
|
||||||
self.first_cluster
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn path(&self) -> Rc<str> {
|
|
||||||
Rc::clone(&self.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_root(&self) -> bool {
|
|
||||||
self.ino == ROOT_INO
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn file_attr(&self) -> FileAttr {
|
pub fn file_attr(&self) -> FileAttr {
|
||||||
let perm = if self.read_only { 0o555 } else { 0o777 };
|
let perm = if self.read_only { 0o555 } else { 0o777 };
|
||||||
|
|
||||||
|
|
@ -293,123 +133,21 @@ impl Inode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dir_iter<'a>(&'a self, fat_fs: &'a FatFs) -> Result<DirIter<'a>, i32> {
|
pub fn dir_iter(&self, fat_fs: &FatFs) -> anyhow::Result<impl Iterator<Item = DirEntry>> {
|
||||||
if self.kind != Kind::Dir {
|
anyhow::ensure!(self.kind == Kind::Dir, "cannot dir_iter on a file");
|
||||||
return Err(ENOTDIR);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.is_root() {
|
// TODO: the boxing here is not particularly pretty, but neccessary, since the DirIter for
|
||||||
|
// the root holds a
|
||||||
|
|
||||||
|
if self.ino == ROOT_INO {
|
||||||
// root dir
|
// root dir
|
||||||
|
|
||||||
return Ok(fat_fs.root_dir_iter());
|
return Ok(fat_fs.root_dir_iter());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(fat_fs.dir_iter(self.first_cluster))
|
let chain_reader = fat_fs.chain_reader(self.first_cluster);
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_child_by_name(&self, fat_fs: &FatFs, name: &str) -> Result<DirEntry, i32> {
|
// TODO: get rid of this Box if the boxing is removed from root_dir_iter
|
||||||
self.dir_iter(fat_fs)
|
Ok(DirIter::new(Box::new(chain_reader)))
|
||||||
.and_then(|mut dir_iter| dir_iter.find_by_name(name).ok_or(ENOENT))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn file_reader<'a>(&'a self, fat_fs: &'a FatFs) -> Result<ClusterChainReader<'a>, i32> {
|
|
||||||
if self.is_dir() {
|
|
||||||
return Err(EISDIR);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(fat_fs.file_reader(self.first_cluster()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn file_writer<'a>(&'a self, fat_fs: &'a mut FatFs) -> Result<ClusterChainWriter<'a>, i32> {
|
|
||||||
if self.is_dir() {
|
|
||||||
return Err(EISDIR);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(fat_fs.file_writer(self.first_cluster()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_size(&mut self, new_size: u64) {
|
|
||||||
debug!("updating size to {new_size}");
|
|
||||||
|
|
||||||
if new_size == self.size {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.size = new_size;
|
|
||||||
self.dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_atime(&mut self, atime: SystemTime) {
|
|
||||||
if self.atime == atime {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.atime = atime;
|
|
||||||
self.dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_mtime(&mut self, mtime: SystemTime) {
|
|
||||||
if self.mtime == mtime {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.mtime = mtime;
|
|
||||||
self.dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_back(&mut self, fat_fs: &FatFs) -> anyhow::Result<()> {
|
|
||||||
if !self.dirty {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.is_root() {
|
|
||||||
// root dir has no attributes
|
|
||||||
|
|
||||||
self.dirty = false;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(parent_inode) = self.parent() else {
|
|
||||||
anyhow::bail!("parent inode of {} does not exist", self.ino);
|
|
||||||
};
|
|
||||||
|
|
||||||
let parent_inode = parent_inode.borrow();
|
|
||||||
|
|
||||||
// since we just wrote to the file with this inode, first cluster should not be zero
|
|
||||||
let Some(mut dir_entry) = parent_inode
|
|
||||||
.dir_iter(fat_fs)
|
|
||||||
.unwrap()
|
|
||||||
.find(|dir_entry| dir_entry.first_cluster() == self.first_cluster())
|
|
||||||
else {
|
|
||||||
anyhow::bail!("could not find dir_entry corresponding to self in parent inode");
|
|
||||||
};
|
|
||||||
|
|
||||||
drop(parent_inode);
|
|
||||||
|
|
||||||
// update stats on DirEntry
|
|
||||||
assert!(self.size <= u32::MAX as u64);
|
|
||||||
|
|
||||||
dir_entry.update_file_size(self.size as u32);
|
|
||||||
|
|
||||||
dir_entry
|
|
||||||
.update_last_access_date(self.atime)
|
|
||||||
.map_err(|err| {
|
|
||||||
anyhow::anyhow!("failed to update atime for inode {}: {err}", self.ino)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
dir_entry.update_write_time(self.mtime).map_err(|err| {
|
|
||||||
anyhow::anyhow!("failed to update mtime for inode {}: {err}", self.ino)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// update cr_time as well?
|
|
||||||
|
|
||||||
// write DirEntry back to device
|
|
||||||
dir_entry.write_back(fat_fs).map_err(|err| {
|
|
||||||
anyhow::anyhow!("failed to write back dir_entry for inode {}: {err}", self.ino)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
self.dirty = false;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,9 @@ use std::cell::RefCell;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use fat_bits::dir::DirEntry;
|
|
||||||
use fat_bits::{FatFs, SliceLike};
|
use fat_bits::{FatFs, SliceLike};
|
||||||
use fxhash::FxHashMap;
|
|
||||||
use log::{debug, error};
|
|
||||||
|
|
||||||
use crate::inode::{Inode, InodeRef};
|
use crate::inode::Inode;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct FatFuse {
|
pub struct FatFuse {
|
||||||
|
|
@ -19,279 +16,32 @@ pub struct FatFuse {
|
||||||
uid: u32,
|
uid: u32,
|
||||||
gid: u32,
|
gid: u32,
|
||||||
|
|
||||||
next_ino: u64,
|
next_fd: u32,
|
||||||
next_fh: u64,
|
|
||||||
|
|
||||||
inode_table: BTreeMap<u64, InodeRef>,
|
inode_table: BTreeMap<u64, Inode>,
|
||||||
|
|
||||||
ino_by_first_cluster: BTreeMap<u32, u64>,
|
|
||||||
ino_by_fh: BTreeMap<u64, u64>,
|
|
||||||
ino_by_path: FxHashMap<Rc<str>, u64>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SAFETY
|
|
||||||
///
|
|
||||||
/// do NOT leak Rc<str> from this type
|
|
||||||
unsafe impl Send for FatFuse {}
|
|
||||||
|
|
||||||
impl FatFuse {
|
impl FatFuse {
|
||||||
pub fn new<S>(data: S) -> anyhow::Result<FatFuse>
|
pub fn new(data: Rc<RefCell<dyn SliceLike>>) -> anyhow::Result<FatFuse> {
|
||||||
where
|
|
||||||
S: SliceLike + Send + 'static,
|
|
||||||
{
|
|
||||||
let uid = unsafe { libc::getuid() };
|
let uid = unsafe { libc::getuid() };
|
||||||
let gid = unsafe { libc::getgid() };
|
let gid = unsafe { libc::getgid() };
|
||||||
|
|
||||||
let fat_fs = FatFs::load(data)?;
|
let fat_fs = FatFs::load(data)?;
|
||||||
|
|
||||||
let mut fat_fuse = FatFuse {
|
Ok(FatFuse {
|
||||||
fat_fs,
|
fat_fs,
|
||||||
uid,
|
uid,
|
||||||
gid,
|
gid,
|
||||||
next_ino: 2, // 0 is reserved and 1 is root
|
next_fd: 0,
|
||||||
next_fh: 0,
|
|
||||||
inode_table: BTreeMap::new(),
|
inode_table: BTreeMap::new(),
|
||||||
ino_by_first_cluster: BTreeMap::new(),
|
})
|
||||||
ino_by_fh: BTreeMap::new(),
|
|
||||||
ino_by_path: FxHashMap::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: build and insert root dir inode
|
|
||||||
|
|
||||||
let root_inode = Inode::root_inode(&fat_fuse.fat_fs, uid, gid);
|
|
||||||
|
|
||||||
fat_fuse.insert_inode(root_inode);
|
|
||||||
|
|
||||||
Ok(fat_fuse)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_ino(&mut self) -> u64 {
|
fn get_inode(&self, ino: u64) -> Option<&Inode> {
|
||||||
let ino = self.next_ino;
|
|
||||||
|
|
||||||
assert!(!self.inode_table.contains_key(&ino));
|
|
||||||
|
|
||||||
self.next_ino += 1;
|
|
||||||
|
|
||||||
ino
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_fh(&mut self) -> u64 {
|
|
||||||
let fh = self.next_fh;
|
|
||||||
|
|
||||||
assert!(!self.ino_by_fh.contains_key(&fh));
|
|
||||||
|
|
||||||
self.next_fh += 1;
|
|
||||||
|
|
||||||
fh
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert_inode(&mut self, inode: Inode) -> InodeRef {
|
|
||||||
let ino = inode.ino();
|
|
||||||
let generation = inode.generation();
|
|
||||||
let first_cluster = inode.first_cluster();
|
|
||||||
|
|
||||||
// let old_inode = self.inode_table.insert(ino, inode);
|
|
||||||
|
|
||||||
let inode = Rc::new(RefCell::new(inode));
|
|
||||||
|
|
||||||
let entry = self.inode_table.entry(ino);
|
|
||||||
|
|
||||||
let (new_inode, old_inode) = match entry {
|
|
||||||
std::collections::btree_map::Entry::Vacant(vacant_entry) => {
|
|
||||||
let new_inode = vacant_entry.insert(inode);
|
|
||||||
(Rc::clone(new_inode), None)
|
|
||||||
}
|
|
||||||
std::collections::btree_map::Entry::Occupied(occupied_entry) => {
|
|
||||||
let entry_ref = occupied_entry.into_mut();
|
|
||||||
|
|
||||||
let old_inode = std::mem::replace(entry_ref, inode);
|
|
||||||
|
|
||||||
(Rc::clone(entry_ref), Some(old_inode))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
"inserted new inode with ino {} and generation {} (first cluster: {})",
|
|
||||||
ino, generation, first_cluster
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(old_inode) = old_inode {
|
|
||||||
let old_inode = old_inode.borrow();
|
|
||||||
|
|
||||||
debug!("ejected inode {} {}", old_inode.ino(), old_inode.generation());
|
|
||||||
}
|
|
||||||
|
|
||||||
if first_cluster != 0 {
|
|
||||||
if let Some(old_ino) = self.ino_by_first_cluster.insert(first_cluster, ino) {
|
|
||||||
debug!("ejected old {} -> {} cluster to ino mapping", first_cluster, old_ino);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = new_inode.borrow().path();
|
|
||||||
|
|
||||||
if let Some(old_ino) = self.ino_by_path.insert(Rc::clone(&path), ino) {
|
|
||||||
debug!("ejected old {} -> {} path to ino mapping", path, old_ino);
|
|
||||||
}
|
|
||||||
|
|
||||||
new_inode
|
|
||||||
}
|
|
||||||
|
|
||||||
fn drop_inode(&mut self, inode: InodeRef) {
|
|
||||||
let inode = inode.borrow();
|
|
||||||
|
|
||||||
let ino = inode.ino();
|
|
||||||
|
|
||||||
debug!("dropping inode {}", ino);
|
|
||||||
|
|
||||||
if self.inode_table.remove(&ino).is_none() {
|
|
||||||
error!("tried to drop inode with ino {}, but was not in table", ino);
|
|
||||||
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let first_cluster = inode.first_cluster();
|
|
||||||
|
|
||||||
if first_cluster != 0 {
|
|
||||||
let entry = self.ino_by_first_cluster.entry(first_cluster);
|
|
||||||
|
|
||||||
match entry {
|
|
||||||
std::collections::btree_map::Entry::Vacant(_) => debug!(
|
|
||||||
"removed inode with ino {} from table, but it's first cluster did not point to any ino",
|
|
||||||
ino
|
|
||||||
),
|
|
||||||
std::collections::btree_map::Entry::Occupied(occupied_entry) => {
|
|
||||||
let found_ino = *occupied_entry.get();
|
|
||||||
|
|
||||||
if found_ino == ino {
|
|
||||||
// matches our inode, remove it
|
|
||||||
occupied_entry.remove();
|
|
||||||
} else {
|
|
||||||
// does not match removed inode, leave it as is
|
|
||||||
debug!(
|
|
||||||
"removed inode with ino {} from table, but its first cluster pointed to ino {} instead",
|
|
||||||
ino, found_ino
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let entry = self.ino_by_path.entry(inode.path());
|
|
||||||
|
|
||||||
match entry {
|
|
||||||
std::collections::hash_map::Entry::Vacant(_) => debug!(
|
|
||||||
"removed inode with ino {} from table, but it's path did not point to any ino",
|
|
||||||
ino
|
|
||||||
),
|
|
||||||
std::collections::hash_map::Entry::Occupied(occupied_entry) => {
|
|
||||||
let found_ino = *occupied_entry.get();
|
|
||||||
|
|
||||||
if found_ino == ino {
|
|
||||||
// matches our inode, remove it
|
|
||||||
occupied_entry.remove();
|
|
||||||
} else {
|
|
||||||
// does not match removed inode, leave it as is
|
|
||||||
debug!(
|
|
||||||
"removed inode with ino {} from table, but its path pointed to ino {} instead",
|
|
||||||
ino, found_ino
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_inode(&self, ino: u64) -> Option<&InodeRef> {
|
|
||||||
self.inode_table.get(&ino)
|
self.inode_table.get(&ino)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_or_make_inode(&mut self, dir_entry: &DirEntry, parent: &Inode) -> InodeRef {
|
fn get_inode_mut(&mut self, ino: u64) -> Option<&mut Inode> {
|
||||||
// let parent = parent.borrow();
|
self.inode_table.get_mut(&ino)
|
||||||
|
|
||||||
// try to find inode by first cluster first
|
|
||||||
if dir_entry.first_cluster() != 0
|
|
||||||
&& let Some(inode) = self.get_inode_by_first_cluster(dir_entry.first_cluster())
|
|
||||||
{
|
|
||||||
return inode;
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to find inode by path
|
|
||||||
// mostly for empty files/directories which have a first cluster of 0
|
|
||||||
|
|
||||||
let path = {
|
|
||||||
let mut path = parent.path().as_ref().to_owned();
|
|
||||||
|
|
||||||
if parent.ino() != inode::ROOT_INO {
|
|
||||||
// root inode already has trailing slash
|
|
||||||
path.push('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
path += &dir_entry.name_string();
|
|
||||||
|
|
||||||
path
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(inode) = self.get_inode_by_path(&path) {
|
|
||||||
return inode;
|
|
||||||
}
|
|
||||||
|
|
||||||
// no inode found, make a new one
|
|
||||||
let ino = self.next_ino();
|
|
||||||
|
|
||||||
let Some(parent_inode) = self.get_inode(parent.ino()).cloned() else {
|
|
||||||
// TODO: what do we do here? should not happen
|
|
||||||
panic!("parent_ino {} does not lead to inode", parent.ino());
|
|
||||||
};
|
|
||||||
|
|
||||||
let inode =
|
|
||||||
Inode::new(&self.fat_fs, dir_entry, ino, self.uid, self.gid, path, parent_inode);
|
|
||||||
|
|
||||||
self.insert_inode(inode)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_inode_by_first_cluster(&self, first_cluster: u32) -> Option<InodeRef> {
|
|
||||||
if first_cluster == 0 {
|
|
||||||
debug!("trying to get inode by first cluster 0");
|
|
||||||
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let ino = self.ino_by_first_cluster.get(&first_cluster)?;
|
|
||||||
|
|
||||||
if let Some(inode) = self.inode_table.get(ino) {
|
|
||||||
Some(Rc::clone(inode))
|
|
||||||
} else {
|
|
||||||
debug!(
|
|
||||||
"first cluster {} is mapped to ino {}, but inode is not in table",
|
|
||||||
first_cluster, ino
|
|
||||||
);
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_inode_by_fh(&self, fh: u64) -> Option<&InodeRef> {
|
|
||||||
let ino = *self.ino_by_fh.get(&fh)?;
|
|
||||||
|
|
||||||
if let Some(inode) = self.get_inode(ino) {
|
|
||||||
Some(inode)
|
|
||||||
} else {
|
|
||||||
debug!("fh {} is mapped to ino {}, but inode is not in table", fh, ino);
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_inode_by_path(&self, path: &str) -> Option<InodeRef> {
|
|
||||||
let ino = *self.ino_by_path.get(path)?;
|
|
||||||
|
|
||||||
if let Some(inode) = self.get_inode(ino).cloned() {
|
|
||||||
Some(inode)
|
|
||||||
} else {
|
|
||||||
debug!("path {} is mapped to ino {}, but inode is not in table", path, ino);
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "fat-mount"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
anyhow = "1.0.98"
|
|
||||||
ctrlc = "3.4.7"
|
|
||||||
env_logger = "0.11.8"
|
|
||||||
fat-fuse = { version = "0.1.0", path = "../fat-fuse" }
|
|
||||||
fuser = "0.15.1"
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
use std::fs::OpenOptions;
|
|
||||||
use std::sync::mpsc::channel;
|
|
||||||
|
|
||||||
use fat_fuse::FatFuse;
|
|
||||||
use fuser::MountOption;
|
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
|
||||||
env_logger::init();
|
|
||||||
|
|
||||||
let mut args = std::env::args();
|
|
||||||
|
|
||||||
let _prog_name = args.next().unwrap();
|
|
||||||
let path = args.next().ok_or(anyhow::anyhow!("missing fs path"))?;
|
|
||||||
let mountpoint = args.next().ok_or(anyhow::anyhow!("missing mount point"))?;
|
|
||||||
|
|
||||||
// let file = File::open(path)?;
|
|
||||||
let file = OpenOptions::new().read(true).write(true).open(path)?;
|
|
||||||
|
|
||||||
let fat_fuse = FatFuse::new(file)?;
|
|
||||||
|
|
||||||
let options = vec![
|
|
||||||
// MountOption::RO,
|
|
||||||
MountOption::FSName("fat-fuse".to_owned()),
|
|
||||||
MountOption::AutoUnmount,
|
|
||||||
];
|
|
||||||
|
|
||||||
let (tx, rx) = channel();
|
|
||||||
|
|
||||||
ctrlc::set_handler(move || {
|
|
||||||
tx.send(()).unwrap();
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let handle = fuser::spawn_mount2(fat_fuse, mountpoint, &options)?;
|
|
||||||
|
|
||||||
rx.recv().unwrap();
|
|
||||||
|
|
||||||
handle.join();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue