Compare commits
18 commits
short-name
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9cb6ee6446 | |||
| 343e8c6c77 | |||
| 4df3713c9d | |||
| bdd01bd70e | |||
| c9e8833ac6 | |||
| 2ed107478e | |||
| ea3e2a76c4 | |||
| 2b01b9ff0e | |||
| e1d458a384 | |||
| 19340bd4ee | |||
| 7f6c304709 | |||
| bdcda26d77 | |||
| 7a4d89a09a | |||
| df20ae6f0c | |||
| f708ab0b50 | |||
| 8b55d8d13c | |||
| 026e145ccb | |||
| 833fb71108 |
21 changed files with 2685 additions and 784 deletions
393
Cargo.lock
generated
393
Cargo.lock
generated
|
|
@ -2,6 +2,15 @@
|
||||||
# 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"
|
||||||
|
|
@ -17,6 +26,56 @@ 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"
|
||||||
|
|
@ -41,6 +100,21 @@ 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"
|
||||||
|
|
@ -74,12 +148,52 @@ 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"
|
||||||
|
|
@ -92,6 +206,29 @@ 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"
|
||||||
|
|
@ -99,9 +236,11 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"compact_str",
|
||||||
"enum_dispatch",
|
"enum_dispatch",
|
||||||
|
"log",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
"thiserror",
|
"thiserror 2.0.12",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -117,13 +256,27 @@ 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",
|
"thiserror 2.0.12",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fat-mount"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"ctrlc",
|
||||||
|
"env_logger",
|
||||||
|
"fat-fuse",
|
||||||
|
"fuser",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -135,13 +288,22 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"memchr",
|
"memchr",
|
||||||
"nix",
|
"nix 0.29.0",
|
||||||
"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"
|
||||||
|
|
@ -178,6 +340,42 @@ 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"
|
||||||
|
|
@ -218,6 +416,18 @@ 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"
|
||||||
|
|
@ -233,6 +443,12 @@ 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"
|
||||||
|
|
@ -249,6 +465,21 @@ 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"
|
||||||
|
|
@ -291,12 +522,67 @@ 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"
|
||||||
|
|
@ -326,13 +612,33 @@ 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",
|
"thiserror-impl 2.0.12",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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]]
|
||||||
|
|
@ -352,6 +658,12 @@ 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"
|
||||||
|
|
@ -500,6 +812,79 @@ 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"]
|
members = ["fat-bits", "fat-dump", "fat-fuse", "fat-mount"]
|
||||||
|
|
|
||||||
10
README.md
10
README.md
|
|
@ -1 +1,9 @@
|
||||||
specification: [https://academy.cba.mit.edu/classes/networking_communications/SD/FAT.pdf](https://academy.cba.mit.edu/classes/networking_communications/SD/FAT.pdf)
|
# FUSE implementation for the FAT file system
|
||||||
|
|
||||||
|
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,8 +8,11 @@ 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,6 +236,7 @@ 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,8 +1,6 @@
|
||||||
use std::time::SystemTime;
|
use chrono::{DateTime, Datelike, Local, NaiveDate, NaiveTime, Timelike};
|
||||||
|
|
||||||
use chrono::{DateTime, Datelike, NaiveDate, NaiveTime, Timelike, Utc};
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Date {
|
pub struct Date {
|
||||||
repr: u16,
|
repr: u16,
|
||||||
}
|
}
|
||||||
|
|
@ -22,8 +20,7 @@ impl Date {
|
||||||
Ok(date)
|
Ok(date)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
fn from_day_month_year(day: u8, month: u8, year: u16) -> anyhow::Result<Date> {
|
||||||
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);
|
||||||
|
|
@ -33,10 +30,7 @@ impl Date {
|
||||||
Ok(Date { repr })
|
Ok(Date { repr })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
pub fn from_datetime(datetime: DateTime<Local>) -> anyhow::Result<Date> {
|
||||||
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(
|
||||||
|
|
@ -46,6 +40,10 @@ 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
|
||||||
}
|
}
|
||||||
|
|
@ -63,7 +61,7 @@ impl Date {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Time {
|
pub struct Time {
|
||||||
repr: u16,
|
repr: u16,
|
||||||
}
|
}
|
||||||
|
|
@ -75,8 +73,7 @@ impl Time {
|
||||||
Ok(time)
|
Ok(time)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
fn from_seconds_minutes_hours(seconds: u8, minutes: u8, hours: u8) -> anyhow::Result<Time> {
|
||||||
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);
|
||||||
|
|
@ -86,10 +83,7 @@ impl Time {
|
||||||
Ok(Time { repr })
|
Ok(Time { repr })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
pub fn from_datetime(datetime: DateTime<Local>) -> anyhow::Result<Time> {
|
||||||
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;
|
||||||
|
|
@ -97,6 +91,10 @@ 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,10 +1,16 @@
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::io::Read;
|
use std::io::{Read, Write};
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use chrono::{NaiveDate, NaiveDateTime, TimeDelta};
|
use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, TimeDelta, Timelike};
|
||||||
|
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! {
|
||||||
|
|
@ -64,12 +70,17 @@ pub struct DirEntry {
|
||||||
|
|
||||||
file_size: u32,
|
file_size: u32,
|
||||||
|
|
||||||
long_name: Option<String>,
|
checksum: u8,
|
||||||
|
long_name: Option<CompactString>,
|
||||||
|
|
||||||
|
n_longname_slots: u8,
|
||||||
|
|
||||||
|
offset: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
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().unwrap_or_else(|| "<unknown>".to_owned());
|
let mut name = self.name_string();
|
||||||
|
|
||||||
if self.attr.contains(Attr::Directory) {
|
if self.attr.contains(Attr::Directory) {
|
||||||
name.push('/');
|
name.push('/');
|
||||||
|
|
@ -89,12 +100,13 @@ impl Display for DirEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DirEntry {
|
impl DirEntry {
|
||||||
pub fn load(bytes: &[u8]) -> anyhow::Result<DirEntry> {
|
pub fn load(bytes: &[u8], offset: u64) -> anyhow::Result<DirEntry> {
|
||||||
assert_eq!(bytes.len(), 32);
|
assert_eq!(bytes.len(), 32);
|
||||||
|
|
||||||
let name = 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,9 +154,94 @@ impl DirEntry {
|
||||||
write_date,
|
write_date,
|
||||||
file_size,
|
file_size,
|
||||||
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
|
||||||
|
|
@ -173,8 +270,6 @@ 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]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,8 +278,6 @@ 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]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,37 +309,60 @@ impl DirEntry {
|
||||||
std::str::from_utf8(self.extension()).ok()
|
std::str::from_utf8(self.extension()).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn name_string(&self) -> Option<String> {
|
pub fn name_string(&self) -> CompactString {
|
||||||
|
// use a CompactString here to allow inlining of short names
|
||||||
|
// maybe switch to a Cow instead? has disadvantage that we need to alloc for short names
|
||||||
|
|
||||||
|
// can't be empty
|
||||||
|
assert!(!self.is_empty());
|
||||||
|
|
||||||
if let Some(long_filename) = self.long_name() {
|
if let Some(long_filename) = self.long_name() {
|
||||||
return Some(long_filename.to_owned());
|
return long_filename.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = std::str::from_utf8(&self.name[..8]).ok()?.trim_ascii_end();
|
let name = &self.name[..8];
|
||||||
let ext = std::str::from_utf8(&self.name[8..]).ok()?.trim_ascii_end();
|
let ext = &self.name[8..];
|
||||||
|
|
||||||
let mut s = String::new();
|
let mut s = CompactString::const_new("");
|
||||||
|
|
||||||
if self.attr.contains(Attr::Hidden) {
|
if self.attr.contains(Attr::Hidden) {
|
||||||
s.push('.');
|
s.push('.');
|
||||||
}
|
}
|
||||||
|
|
||||||
s += name;
|
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() {
|
if !ext.is_empty() {
|
||||||
s.push('.');
|
s.push('.');
|
||||||
|
|
||||||
s += ext;
|
s.extend(ext.trim_ascii_end().iter().copied().map(map_chars));
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(s)
|
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: String) {
|
pub fn set_long_name(&mut self, long_name: CompactString, n_slots: u8) {
|
||||||
self.long_name = Some(long_name);
|
self.long_name = Some(long_name);
|
||||||
|
self.n_longname_slots = n_slots;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attr(&self) -> Attr {
|
pub fn attr(&self) -> Attr {
|
||||||
|
|
@ -268,6 +384,15 @@ 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
|
||||||
}
|
}
|
||||||
|
|
@ -279,14 +404,27 @@ 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 checksum(&self) -> u8 {
|
pub fn update_file_size(&mut self, file_size: u32) {
|
||||||
|
self.file_size = file_size
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn checksum(name: &[u8]) -> u8 {
|
||||||
let mut checksum: u8 = 0;
|
let mut checksum: u8 = 0;
|
||||||
|
|
||||||
for &x in self.name() {
|
for &x in name {
|
||||||
checksum = checksum.rotate_right(1).wrapping_add(x);
|
checksum = checksum.rotate_right(1).wrapping_add(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -297,6 +435,7 @@ 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,
|
||||||
|
|
@ -371,13 +510,14 @@ 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]) -> anyhow::Result<DirEntryWrapper> {
|
pub fn load(bytes: &[u8], offset: u64) -> 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]);
|
||||||
|
|
@ -385,7 +525,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)?)
|
DirEntryWrapper::Regular(DirEntry::load(bytes, offset)?)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(dir_entry)
|
Ok(dir_entry)
|
||||||
|
|
@ -397,6 +537,7 @@ 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 {
|
||||||
|
|
@ -404,28 +545,43 @@ 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());
|
assert!(self.checksum.is_some() && self.last_ordinal.is_some());
|
||||||
|
|
||||||
anyhow::ensure!(
|
anyhow::ensure!(
|
||||||
self.checksum == Some(dir_entry.checksum()),
|
self.checksum == Some(dir_entry.checksum()),
|
||||||
|
|
@ -453,7 +609,10 @@ impl LongFilenameBuf {
|
||||||
self.rev_buf.extend(name.iter().rev());
|
self.rev_buf.extend(name.iter().rev());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_buf(&mut self, checksum: u8) -> anyhow::Result<Option<impl Iterator<Item = u16>>> {
|
pub fn get_buf(
|
||||||
|
&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);
|
||||||
}
|
}
|
||||||
|
|
@ -475,54 +634,57 @@ impl LongFilenameBuf {
|
||||||
self.checksum.unwrap()
|
self.checksum.unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(Some(self.rev_buf.iter().copied().rev()))
|
anyhow::ensure!(self.rev_buf.len() <= 255, "long filename too long");
|
||||||
|
|
||||||
|
Ok(Some((self.rev_buf.iter().copied().rev(), self.n_slots)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DirIter<R: Read> {
|
pub struct DirIter<'a> {
|
||||||
reader: R,
|
reader: ClusterChainReader<'a>,
|
||||||
|
|
||||||
// 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<R: Read> DirIter<R> {
|
impl<'a> DirIter<'a> {
|
||||||
pub fn new(reader: R) -> DirIter<R> {
|
pub fn new(reader: ClusterChainReader<'a>) -> Self {
|
||||||
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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// inner function for iterator
|
pub fn find_by_name(&mut self, name: &str) -> Option<DirEntry> {
|
||||||
fn next_impl(&mut self) -> anyhow::Result<Option<DirEntry>> {
|
self.find(|dir_entry| &dir_entry.name_string() == name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for DirIter<'_> {
|
||||||
|
type Item = DirEntry;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
fn next_impl(me: &mut DirIter<'_>) -> anyhow::Result<Option<DirEntry>> {
|
||||||
|
let offset = me.reader.current_offset();
|
||||||
|
|
||||||
let mut chunk = [0; 32];
|
let mut chunk = [0; 32];
|
||||||
if self.reader.read_exact(&mut chunk).is_err() {
|
|
||||||
// reading failed; nothing we can do here
|
if me.reader.read_exact(&mut chunk).is_err() {
|
||||||
return Ok(None);
|
// nothing we can do here since we might be in an invalid state after a partial read
|
||||||
|
anyhow::bail!("read failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
// let Ok(dir_entry) = DirEntry::load(&chunk) else {
|
let dir_entry = DirEntryWrapper::load(&chunk, offset)
|
||||||
// return self.next();
|
|
||||||
// };
|
|
||||||
|
|
||||||
let dir_entry = DirEntryWrapper::load(&chunk)
|
|
||||||
.map_err(|e| anyhow::anyhow!("failed to load dir entry: {e}"))?;
|
.map_err(|e| anyhow::anyhow!("failed to load dir entry: {e}"))?;
|
||||||
|
|
||||||
let mut dir_entry = match dir_entry {
|
let mut dir_entry = match dir_entry {
|
||||||
DirEntryWrapper::Regular(dir_entry) => dir_entry,
|
DirEntryWrapper::Regular(dir_entry) => dir_entry,
|
||||||
DirEntryWrapper::LongName(long_name) => {
|
DirEntryWrapper::LongName(long_name) => {
|
||||||
self.long_filename_buf.next(long_name).map_err(|e| {
|
me.long_filename_buf.next(long_name).map_err(|e| {
|
||||||
self.long_filename_buf.reset();
|
me.long_filename_buf.reset();
|
||||||
anyhow::anyhow!("invalid long filename entry: {e}")
|
anyhow::anyhow!("invalid long filename entry: {e}")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
return self.next_impl();
|
return next_impl(me);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -531,44 +693,38 @@ impl<R: Read> DirIter<R> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if dir_entry.is_empty() {
|
if dir_entry.is_empty() {
|
||||||
return self.next_impl();
|
return next_impl(me);
|
||||||
}
|
}
|
||||||
|
|
||||||
match self
|
if let Some((iter, n_slots)) = me
|
||||||
.long_filename_buf
|
.long_filename_buf
|
||||||
.get_buf(dir_entry.checksum())
|
.get_buf(dir_entry.checksum)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
anyhow::anyhow!(
|
anyhow::anyhow!(
|
||||||
"failed to get long filename for {}: {}",
|
"failed to get long filename for {}: {}",
|
||||||
dir_entry.name_string().as_deref().unwrap_or("<invalid>"),
|
dir_entry.name_string(),
|
||||||
e
|
e
|
||||||
)
|
)
|
||||||
})? {
|
})?
|
||||||
Some(iter) => {
|
{
|
||||||
// attach long filename to dir_entry
|
// attach long filename to dir_entry
|
||||||
|
|
||||||
let long_filename: String =
|
let long_filename: CompactString =
|
||||||
char::decode_utf16(iter).filter_map(|x| x.ok()).collect();
|
char::decode_utf16(iter).filter_map(|x| x.ok()).collect();
|
||||||
|
|
||||||
dir_entry.set_long_name(long_filename);
|
dir_entry.set_long_name(long_filename, n_slots);
|
||||||
}
|
|
||||||
None => {} // no long filename -> do nothing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.long_filename_buf.reset();
|
me.long_filename_buf.reset();
|
||||||
|
|
||||||
Ok(Some(dir_entry))
|
Ok(Some(dir_entry))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: Read> Iterator for DirIter<R> {
|
match next_impl(self) {
|
||||||
type Item = DirEntry;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
match self.next_impl() {
|
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("{}", e);
|
// print error message, try next
|
||||||
|
debug!("{}", e);
|
||||||
|
|
||||||
self.next()
|
self.next()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
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 {
|
||||||
|
|
@ -19,26 +24,24 @@ pub enum FatError {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[enum_dispatch]
|
#[enum_dispatch]
|
||||||
pub trait Fatty {
|
trait FatOps {
|
||||||
// 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 get_valid_clusters(&self) -> RangeInclusive<u32>;
|
fn valid_entries(&self) -> RangeInclusive<u32>;
|
||||||
fn get_reserved_clusters(&self) -> RangeInclusive<u32>;
|
fn reserved_entries(&self) -> RangeInclusive<u32>;
|
||||||
fn get_defective_cluster(&self) -> u32;
|
fn defective_entry(&self) -> u32;
|
||||||
fn get_reserved_eof_clusters(&self) -> RangeInclusive<u32>;
|
fn reserved_eof_entries(&self) -> RangeInclusive<u32>;
|
||||||
fn get_eof_cluster(&self) -> u32;
|
fn eof_entry(&self) -> u32;
|
||||||
|
|
||||||
fn count_free_clusters(&self) -> usize {
|
fn write_to_disk(&self, sub_slice: SubSliceMut) -> std::io::Result<()>;
|
||||||
self.get_valid_clusters()
|
|
||||||
.map(|cluster| self.get_entry(cluster))
|
|
||||||
.filter(|&entry| entry == 0)
|
|
||||||
.count()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[enum_dispatch(Fatty)]
|
#[enum_dispatch(FatOps)]
|
||||||
|
// 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),
|
||||||
|
|
@ -64,24 +67,32 @@ 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 == 0x000 {
|
if cluster == FREE_ENTRY {
|
||||||
// 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.get_reserved_clusters().contains(&cluster) {
|
if self.reserved_entries().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.get_defective_cluster() {
|
if cluster == self.defective_entry() {
|
||||||
// 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.get_reserved_eof_clusters().contains(&cluster) {
|
if self.reserved_eof_entries().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).
|
||||||
//
|
//
|
||||||
|
|
@ -93,21 +104,117 @@ impl Fat {
|
||||||
|
|
||||||
let entry = self.get_entry(cluster);
|
let entry = self.get_entry(cluster);
|
||||||
|
|
||||||
// interpret second reserved block as EOF here
|
if entry == FREE_ENTRY {
|
||||||
if entry == self.get_eof_cluster() || self.get_reserved_eof_clusters().contains(&entry) {
|
Ok(None)
|
||||||
return Ok(None);
|
} else if entry == self.eof_entry() {
|
||||||
}
|
Ok(None)
|
||||||
|
} else if self.valid_entries().contains(&entry) {
|
||||||
// entry should be in the valid cluster range here; otherwise something went wrong
|
|
||||||
if !self.get_valid_clusters().contains(&entry) {
|
|
||||||
return Err(FatError::InvalidEntry(entry));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Some(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 {
|
||||||
|
assert!(self.valid_entries().contains(&next_cluster));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(next_cluster) = next_cluster {
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Fat12 {
|
struct Fat12 {
|
||||||
max: u32,
|
max: u32,
|
||||||
|
|
||||||
next_sectors: Box<[u16]>,
|
next_sectors: Box<[u16]>,
|
||||||
|
|
@ -118,7 +225,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 != 0 {
|
if x != FREE_ENTRY as u16 {
|
||||||
writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
|
writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +251,10 @@ 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>();
|
||||||
|
|
||||||
|
|
@ -178,7 +288,7 @@ impl Fat12 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fatty for Fat12 {
|
impl FatOps 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());
|
||||||
|
|
@ -186,28 +296,74 @@ impl Fatty for Fat12 {
|
||||||
self.next_sectors[cluster] as u32
|
self.next_sectors[cluster] as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_valid_clusters(&self) -> RangeInclusive<u32> {
|
fn set_entry(&mut self, cluster: u32, entry: u32) {
|
||||||
|
self.next_sectors[cluster as usize] = entry as u16;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_entries(&self) -> RangeInclusive<u32> {
|
||||||
2..=self.max
|
2..=self.max
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_reserved_clusters(&self) -> RangeInclusive<u32> {
|
fn reserved_entries(&self) -> RangeInclusive<u32> {
|
||||||
(self.max as u32 + 1)..=0xFF6
|
(self.max as u32 + 1)..=0xFF6
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_defective_cluster(&self) -> u32 {
|
fn defective_entry(&self) -> u32 {
|
||||||
0xFF7
|
0xFF7
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_reserved_eof_clusters(&self) -> RangeInclusive<u32> {
|
fn reserved_eof_entries(&self) -> RangeInclusive<u32> {
|
||||||
0xFF8..=0xFFE
|
0xFF8..=0xFFE
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_eof_cluster(&self) -> u32 {
|
fn eof_entry(&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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Fat16 {
|
struct Fat16 {
|
||||||
max: u32,
|
max: u32,
|
||||||
|
|
||||||
next_sectors: Box<[u16]>,
|
next_sectors: Box<[u16]>,
|
||||||
|
|
@ -218,7 +374,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 != 0 {
|
if x != FREE_ENTRY as u16 {
|
||||||
writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
|
writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -262,7 +418,7 @@ impl Fat16 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fatty for Fat16 {
|
impl FatOps 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());
|
||||||
|
|
@ -270,28 +426,42 @@ impl Fatty for Fat16 {
|
||||||
self.next_sectors[cluster] as u32
|
self.next_sectors[cluster] as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_valid_clusters(&self) -> RangeInclusive<u32> {
|
fn set_entry(&mut self, cluster: u32, entry: u32) {
|
||||||
|
self.next_sectors[cluster as usize] = entry as u16;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_entries(&self) -> RangeInclusive<u32> {
|
||||||
2..=self.max
|
2..=self.max
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_reserved_clusters(&self) -> RangeInclusive<u32> {
|
fn reserved_entries(&self) -> RangeInclusive<u32> {
|
||||||
(self.max as u32 + 1)..=0xFFF6
|
(self.max as u32 + 1)..=0xFFF6
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_defective_cluster(&self) -> u32 {
|
fn defective_entry(&self) -> u32 {
|
||||||
0xFFF7
|
0xFFF7
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_reserved_eof_clusters(&self) -> RangeInclusive<u32> {
|
fn reserved_eof_entries(&self) -> RangeInclusive<u32> {
|
||||||
0xFFF8..=0xFFFE
|
0xFFF8..=0xFFFE
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_eof_cluster(&self) -> u32 {
|
fn eof_entry(&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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Fat32 {
|
struct Fat32 {
|
||||||
max: u32,
|
max: u32,
|
||||||
|
|
||||||
next_sectors: Box<[u32]>,
|
next_sectors: Box<[u32]>,
|
||||||
|
|
@ -302,7 +472,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 != 0 {
|
if x != FREE_ENTRY {
|
||||||
writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
|
writeln!(f, " 0x{:03X} -> 0x{:03X}", i, x)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -346,7 +516,7 @@ impl Fat32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fatty for Fat32 {
|
impl FatOps 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());
|
||||||
|
|
@ -354,23 +524,37 @@ impl Fatty for Fat32 {
|
||||||
self.next_sectors[cluster] as u32
|
self.next_sectors[cluster] as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_valid_clusters(&self) -> RangeInclusive<u32> {
|
fn set_entry(&mut self, cluster: u32, entry: u32) {
|
||||||
|
self.next_sectors[cluster as usize] = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_entries(&self) -> RangeInclusive<u32> {
|
||||||
2..=self.max
|
2..=self.max
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_reserved_clusters(&self) -> RangeInclusive<u32> {
|
fn reserved_entries(&self) -> RangeInclusive<u32> {
|
||||||
(self.max + 1)..=0xFFFFFFF6
|
(self.max + 1)..=0xFFFFFFF6
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_defective_cluster(&self) -> u32 {
|
fn defective_entry(&self) -> u32 {
|
||||||
0xFFFFFFF7
|
0xFFFFFFF7
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_reserved_eof_clusters(&self) -> RangeInclusive<u32> {
|
fn reserved_eof_entries(&self) -> RangeInclusive<u32> {
|
||||||
0xFFFFFFF8..=0xFFFFFFFE
|
0xFFFFFFF8..=0xFFFFFFFE
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_eof_cluster(&self) -> u32 {
|
fn eof_entry(&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,4 +40,12 @@ 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,48 +1,94 @@
|
||||||
use std::io::Read;
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
use crate::FatFs;
|
use log::debug;
|
||||||
use crate::subslice::SubSlice;
|
|
||||||
use crate::utils::replace;
|
use crate::subslice::{SubSlice, SubSliceMut};
|
||||||
|
use crate::{FatFs, FatType};
|
||||||
|
|
||||||
pub struct ClusterChainReader<'a> {
|
pub struct ClusterChainReader<'a> {
|
||||||
sub_slice: SubSlice<'a>,
|
fat_fs: &'a FatFs,
|
||||||
|
|
||||||
|
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) -> ClusterChainReader<'a> {
|
pub fn new(fat_fs: &'a FatFs, first_cluster: u32) -> Self {
|
||||||
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_cluster(&mut self) -> bool {
|
pub fn root_dir_reader(fat_fs: &'a FatFs) -> Self {
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
replace(&mut self.sub_slice, |sub_slice| {
|
self.next_cluster = self.fat_fs.next_cluster(next_cluster).unwrap_or(None);
|
||||||
let fat_fs = sub_slice.release();
|
self.sub_slice = self.fat_fs.cluster_as_subslice(next_cluster);
|
||||||
|
|
||||||
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<'a> Read for ClusterChainReader<'a> {
|
impl Read for ClusterChainReader<'_> {
|
||||||
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.next_cluster() {
|
if !self.move_to_next_cluster() {
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -50,3 +96,127 @@ impl<'a> Read for ClusterChainReader<'a> {
|
||||||
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,9 +1,13 @@
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::io::{Read, Seek, SeekFrom, Write};
|
use std::fmt::Display;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use log::debug;
|
||||||
|
|
||||||
use crate::dir::DirIter;
|
use crate::dir::DirIter;
|
||||||
use crate::fat::{FatError, Fatty};
|
use crate::fat::FatError;
|
||||||
|
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;
|
||||||
|
|
@ -11,7 +15,8 @@ mod datetime;
|
||||||
pub mod dir;
|
pub mod dir;
|
||||||
pub mod fat;
|
pub mod fat;
|
||||||
pub mod fs_info;
|
pub mod fs_info;
|
||||||
mod iter;
|
pub mod iter;
|
||||||
|
mod slice_like;
|
||||||
mod subslice;
|
mod subslice;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
|
|
@ -22,84 +27,59 @@ pub enum FatType {
|
||||||
Fat32,
|
Fat32,
|
||||||
}
|
}
|
||||||
|
|
||||||
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 std::fs::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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub struct FatFs {
|
pub struct FatFs {
|
||||||
inner: Rc<RefCell<dyn SliceLike>>,
|
inner: Rc<RefCell<dyn SliceLike>>,
|
||||||
|
|
||||||
fat_offset: u64,
|
// fat_offset: u64,
|
||||||
fat_size: usize,
|
// fat_size: usize,
|
||||||
|
|
||||||
root_dir_offset: Option<u64>,
|
root_dir_offset: Option<u64>,
|
||||||
root_dir_size: usize,
|
root_dir_size: usize,
|
||||||
|
|
||||||
pub data_offset: u64,
|
pub data_offset: u64,
|
||||||
data_size: usize,
|
// data_size: usize,
|
||||||
|
|
||||||
bytes_per_cluster: usize,
|
bytes_per_cluster: usize,
|
||||||
|
|
||||||
bpb: bpb::Bpb,
|
bpb: bpb::Bpb,
|
||||||
|
|
||||||
fat: fat::Fat,
|
fat: fat::Fat,
|
||||||
|
|
||||||
|
next_free: Option<u32>,
|
||||||
|
free_count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for FatFs {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
writeln!(f, "{}", self.bpb)?;
|
||||||
|
writeln!(f, "")?;
|
||||||
|
writeln!(f, "{}", self.fat)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for FatFs {}
|
||||||
|
|
||||||
|
impl Drop for FatFs {
|
||||||
|
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) {
|
||||||
|
debug!("writing FAT back to disk failed: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FatFs {
|
impl FatFs {
|
||||||
pub fn load(data: Rc<RefCell<dyn SliceLike>>) -> anyhow::Result<FatFs> {
|
pub fn load<S>(data: S) -> anyhow::Result<FatFs>
|
||||||
|
where
|
||||||
|
S: SliceLike + Send + 'static,
|
||||||
|
{
|
||||||
|
let data = Rc::new(RefCell::new(data));
|
||||||
|
|
||||||
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)?;
|
||||||
|
|
@ -128,127 +108,177 @@ 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 bpb(&self) -> &bpb::Bpb {
|
pub fn fat_type(&self) -> FatType {
|
||||||
&self.bpb
|
self.fat.fat_type()
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fat(&self) -> &fat::Fat {
|
|
||||||
&self.fat
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// byte offset of data cluster
|
/// byte offset of data cluster
|
||||||
pub fn data_cluster_to_offset(&self, cluster: u32) -> u64 {
|
fn data_cluster_to_offset(&self, cluster: u32) -> u64 {
|
||||||
// assert!(cluster >= 2);
|
// assert!(cluster >= 2);
|
||||||
|
|
||||||
assert!(self.fat().get_valid_clusters().contains(&cluster));
|
// assert!(self.fat.valid_entries().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 {
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cluster_as_subslice_mut(&mut self, cluster: u32) -> SubSliceMut<'_> {
|
|
||||||
let offset = self.data_cluster_to_offset(cluster);
|
let offset = self.data_cluster_to_offset(cluster);
|
||||||
|
|
||||||
SubSliceMut::new(self, offset, self.bytes_per_cluster)
|
SubSlice::new(self.inner.clone(), offset, self.bytes_per_cluster)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cluster_as_subslice_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 SubSliceMut::new(self.inner.clone(), 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cluster_as_subslice(&self, cluster: u32) -> SubSlice<'_> {
|
|
||||||
let offset = self.data_cluster_to_offset(cluster);
|
let offset = self.data_cluster_to_offset(cluster);
|
||||||
|
|
||||||
SubSlice::new(self, offset, self.bytes_per_cluster)
|
SubSliceMut::new(self.inner.clone(), offset, self.bytes_per_cluster)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn root_dir_bytes(&mut self) -> std::io::Result<Vec<u8>> {
|
fn root_dir_as_subslice(&self) -> SubSlice {
|
||||||
if let Some(root_dir_offset) = self.root_dir_offset {
|
SubSlice::new(self.inner.clone(), self.root_dir_offset.unwrap(), self.root_dir_size)
|
||||||
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();
|
fn root_dir_as_subslice_mut(&self) -> SubSliceMut {
|
||||||
|
SubSliceMut::new(self.inner.clone(), self.root_dir_offset.unwrap(), self.root_dir_size)
|
||||||
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 chain_reader(&'_ self, first_cluster: u32) -> iter::ClusterChainReader<'_> {
|
||||||
}
|
|
||||||
|
|
||||||
fn chain_reader(&self, first_cluster: u32) -> impl Read {
|
|
||||||
iter::ClusterChainReader::new(self, first_cluster)
|
iter::ClusterChainReader::new(self, first_cluster)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn root_dir_iter<'a>(&'a self) -> DirIter<Box<dyn Read + 'a>> {
|
fn chain_writer(&'_ mut self, first_cluster: u32) -> iter::ClusterChainWriter<'_> {
|
||||||
// Box<dyn Iterator<Item = DirEntry> + '_>
|
iter::ClusterChainWriter::new(self, first_cluster)
|
||||||
// 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
|
pub fn root_dir_iter<'a>(&self) -> DirIter<'_> {
|
||||||
|
let reader = ClusterChainReader::root_dir_reader(self);
|
||||||
|
|
||||||
// can't fail; we're in the FAT32 case
|
DirIter::new(reader)
|
||||||
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 dir_iter<'a>(&'a self, first_cluster: u32) -> DirIter<Box<dyn Read + 'a>> {
|
pub fn dir_iter<'a>(&self, first_cluster: u32) -> DirIter<'_> {
|
||||||
// TODO: return type must match root_dir_iter
|
|
||||||
// if the Box<dyn> is changed there, update here as well
|
|
||||||
|
|
||||||
let cluster_iter = self.chain_reader(first_cluster);
|
let cluster_iter = self.chain_reader(first_cluster);
|
||||||
|
|
||||||
DirIter::new(Box::new(cluster_iter))
|
DirIter::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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
58
fat-bits/src/slice_like.rs
Normal file
58
fat-bits/src/slice_like.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
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,16 +1,18 @@
|
||||||
|
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::FatFs;
|
use crate::SliceLike;
|
||||||
|
|
||||||
pub struct SubSliceMut<'a> {
|
pub struct SubSlice {
|
||||||
fat_fs: &'a mut FatFs,
|
data: Rc<RefCell<dyn SliceLike>>,
|
||||||
|
|
||||||
offset: u64,
|
offset: u64,
|
||||||
len: usize,
|
len: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for SubSliceMut<'_> {
|
impl Debug for SubSlice {
|
||||||
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)
|
||||||
|
|
@ -19,17 +21,15 @@ impl Debug for SubSliceMut<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SubSliceMut<'_> {
|
impl<'a> SubSlice {
|
||||||
pub fn new(fat_fs: &mut FatFs, offset: u64, len: usize) -> SubSliceMut<'_> {
|
pub fn new(data: Rc<RefCell<dyn SliceLike>>, offset: u64, len: usize) -> SubSlice {
|
||||||
SubSliceMut {
|
SubSlice { data, offset, len }
|
||||||
fat_fs,
|
}
|
||||||
offset,
|
|
||||||
len,
|
pub fn offset(&self) -> u64 {
|
||||||
|
self.offset
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SubSliceMut<'_> {
|
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.len
|
self.len
|
||||||
}
|
}
|
||||||
|
|
@ -37,14 +37,22 @@ impl SubSliceMut<'_> {
|
||||||
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 SubSliceMut<'_> {
|
impl Read for SubSlice {
|
||||||
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.fat_fs
|
self.data
|
||||||
.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])?;
|
||||||
|
|
||||||
|
|
@ -55,12 +63,72 @@ impl Read for SubSliceMut<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Write for SubSliceMut<'_> {
|
pub struct 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.fat_fs
|
self.data
|
||||||
.inner
|
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.write_at_offset(self.offset, &buf[..bytes_to_write])?;
|
.write_at_offset(self.offset, &buf[..bytes_to_write])?;
|
||||||
|
|
||||||
|
|
@ -74,67 +142,3 @@ 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,24 +3,9 @@ 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,9 +1,5 @@
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use fat_bits::FatFs;
|
use fat_bits::FatFs;
|
||||||
use fat_bits::dir::{DirEntry, DirIter};
|
use fat_bits::dir::DirEntry;
|
||||||
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();
|
||||||
|
|
@ -22,18 +18,20 @@ pub fn main() -> anyhow::Result<()> {
|
||||||
|
|
||||||
// println!("{}", bpb);
|
// println!("{}", bpb);
|
||||||
|
|
||||||
let fat_fs = FatFs::load(Rc::new(RefCell::new(file)))?;
|
let fat_fs = FatFs::load(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.fat().count_free_clusters(),
|
fat_fs.free_clusters(),
|
||||||
fat_fs.fat().count_free_clusters()
|
fat_fs.free_clusters() as usize
|
||||||
* fat_fs.bpb().bytes_per_sector() as usize
|
* fat_fs.bytes_per_sector() as usize
|
||||||
* fat_fs.bpb().sectors_per_cluster() as usize
|
* fat_fs.sectors_per_cluster() as usize
|
||||||
);
|
);
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,20 @@ edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.98"
|
anyhow = "1.0.98"
|
||||||
chrono = { version = "0.4.41", default-features = false, features = ["alloc", "clock", "std"] }
|
bitflags = "2.9.1"
|
||||||
|
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 = ["os_rng", "small_rng"] }
|
rand = { version = "0.9.2", default-features = false, features = [
|
||||||
|
"os_rng",
|
||||||
|
"small_rng",
|
||||||
|
] }
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,10 +1,14 @@
|
||||||
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! {
|
||||||
|
|
@ -19,13 +23,10 @@ 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_u32() -> u32 {
|
fn get_random<T>() -> T
|
||||||
// RNG.with(|x| unsafe {
|
where
|
||||||
// let rng = &mut (*x.get());
|
rand::distr::StandardUniform: rand::distr::Distribution<T>,
|
||||||
|
{
|
||||||
// rng.random::<u32>()
|
|
||||||
// })
|
|
||||||
|
|
||||||
RNG.with(|rng| rng.borrow_mut().random())
|
RNG.with(|rng| rng.borrow_mut().random())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,38 +45,81 @@ impl From<Kind> for fuser::FileType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ROOT_INO: u64 = 1;
|
pub 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,
|
||||||
|
|
||||||
size: u64,
|
ref_count: 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
impl Drop for Inode {
|
||||||
|
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 {
|
||||||
pub fn new(fat_fs: &FatFs, dir_entry: DirEntry, uid: u32, gid: u32) -> Inode {
|
fn new_generation() -> u32 {
|
||||||
|
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 = get_random_u32();
|
let generation = Self::new_generation();
|
||||||
|
|
||||||
let kind = if dir_entry.is_dir() {
|
let kind = if dir_entry.is_dir() {
|
||||||
Kind::Dir
|
Kind::Dir
|
||||||
|
|
@ -95,22 +139,138 @@ 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: dir_entry.first_cluster() as u64,
|
ino,
|
||||||
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.bpb().bytes_per_sector() as u32,
|
block_size: fat_fs.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 };
|
||||||
|
|
||||||
|
|
@ -133,21 +293,123 @@ impl Inode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dir_iter(&self, fat_fs: &FatFs) -> anyhow::Result<impl Iterator<Item = DirEntry>> {
|
pub fn dir_iter<'a>(&'a self, fat_fs: &'a FatFs) -> Result<DirIter<'a>, i32> {
|
||||||
anyhow::ensure!(self.kind == Kind::Dir, "cannot dir_iter on a file");
|
if self.kind != Kind::Dir {
|
||||||
|
return Err(ENOTDIR);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: the boxing here is not particularly pretty, but neccessary, since the DirIter for
|
if self.is_root() {
|
||||||
// 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());
|
||||||
}
|
}
|
||||||
|
|
||||||
let chain_reader = fat_fs.chain_reader(self.first_cluster);
|
Ok(fat_fs.dir_iter(self.first_cluster))
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: get rid of this Box if the boxing is removed from root_dir_iter
|
pub fn find_child_by_name(&self, fat_fs: &FatFs, name: &str) -> Result<DirEntry, i32> {
|
||||||
Ok(DirIter::new(Box::new(chain_reader)))
|
self.dir_iter(fat_fs)
|
||||||
|
.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,9 +5,12 @@ 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;
|
use crate::inode::{Inode, InodeRef};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct FatFuse {
|
pub struct FatFuse {
|
||||||
|
|
@ -16,32 +19,279 @@ pub struct FatFuse {
|
||||||
uid: u32,
|
uid: u32,
|
||||||
gid: u32,
|
gid: u32,
|
||||||
|
|
||||||
next_fd: u32,
|
next_ino: u64,
|
||||||
|
next_fh: u64,
|
||||||
|
|
||||||
inode_table: BTreeMap<u64, Inode>,
|
inode_table: BTreeMap<u64, InodeRef>,
|
||||||
|
|
||||||
|
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(data: Rc<RefCell<dyn SliceLike>>) -> anyhow::Result<FatFuse> {
|
pub fn new<S>(data: S) -> 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)?;
|
||||||
|
|
||||||
Ok(FatFuse {
|
let mut fat_fuse = FatFuse {
|
||||||
fat_fs,
|
fat_fs,
|
||||||
uid,
|
uid,
|
||||||
gid,
|
gid,
|
||||||
next_fd: 0,
|
next_ino: 2, // 0 is reserved and 1 is root
|
||||||
|
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 get_inode(&self, ino: u64) -> Option<&Inode> {
|
fn next_ino(&mut self) -> u64 {
|
||||||
|
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_inode_mut(&mut self, ino: u64) -> Option<&mut Inode> {
|
fn get_or_make_inode(&mut self, dir_entry: &DirEntry, parent: &Inode) -> InodeRef {
|
||||||
self.inode_table.get_mut(&ino)
|
// let parent = parent.borrow();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
fat-mount/Cargo.toml
Normal file
11
fat-mount/Cargo.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
[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"
|
||||||
41
fat-mount/src/main.rs
Normal file
41
fat-mount/src/main.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
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