Work
This commit is contained in:
parent
c60dea71ba
commit
6f0094c039
@ -1,6 +1,5 @@
|
|||||||
[build]
|
[build]
|
||||||
target = "x86_64-unknown-none"
|
target = "x86_64-unknown-mikros"
|
||||||
rustflags = ["-C", "relocation-model=static"]
|
|
||||||
|
|
||||||
[install]
|
[install]
|
||||||
root = "../kernel/sysroot"
|
root = "../os_build/sysroot"
|
||||||
|
395
Cargo.lock
generated
395
Cargo.lock
generated
@ -2,204 +2,23 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ahash"
|
|
||||||
version = "0.8.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"once_cell",
|
|
||||||
"version_check",
|
|
||||||
"zerocopy",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "arrayvec"
|
|
||||||
version = "0.7.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "binread"
|
|
||||||
version = "2.2.0"
|
|
||||||
dependencies = [
|
|
||||||
"binread_derive",
|
|
||||||
"rustversion",
|
|
||||||
"std",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "binread_derive"
|
|
||||||
version = "2.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 1.0.109",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bit_field"
|
|
||||||
version = "0.10.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitflags"
|
|
||||||
version = "1.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cobs"
|
|
||||||
version = "0.2.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "core2"
|
|
||||||
version = "0.4.0"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossbeam-queue"
|
|
||||||
version = "0.3.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crossbeam-utils"
|
|
||||||
version = "0.8.20"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "derive-try-from-primitive"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "302ccf094df1151173bb6f5a2282fcd2f45accd5eae1bdf82dcbfefbc501ad5c"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 1.0.109",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dev_driver_rpc"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"postcard",
|
|
||||||
"serde",
|
|
||||||
"spin",
|
|
||||||
"std",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "either"
|
|
||||||
version = "1.12.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "elf"
|
|
||||||
version = "0.7.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "elfloader"
|
|
||||||
version = "0.16.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6a7b18d35bf8ec3bac59c3ec29cf1f1b46e764e00b42a9c0c754d06e38e78f3b"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 1.3.2",
|
|
||||||
"log",
|
|
||||||
"xmas-elf",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "embedded-io"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.13.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
|
||||||
dependencies = [
|
|
||||||
"ahash",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "init"
|
name = "init"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"binread",
|
|
||||||
"bitflags 1.3.2",
|
|
||||||
"dev_driver_rpc",
|
|
||||||
"itertools",
|
|
||||||
"postcard",
|
|
||||||
"serde",
|
|
||||||
"spin",
|
|
||||||
"std",
|
|
||||||
"tap",
|
|
||||||
"tar-no-std",
|
"tar-no-std",
|
||||||
"x86_64 0.15.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itertools"
|
|
||||||
version = "0.10.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "linked_list_allocator"
|
|
||||||
version = "0.10.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286"
|
|
||||||
dependencies = [
|
|
||||||
"spinning_top",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lock_api"
|
|
||||||
version = "0.4.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"scopeguard",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -215,216 +34,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "num-traits"
|
||||||
version = "1.19.0"
|
version = "0.2.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "postcard"
|
|
||||||
version = "1.0.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a55c51ee6c0db07e68448e336cf8ea4131a620edefebf9893e759b2d793420f8"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cobs",
|
"autocfg",
|
||||||
"embedded-io",
|
|
||||||
"serde",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.84"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.36"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustversion"
|
|
||||||
version = "1.0.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "scopeguard"
|
|
||||||
version = "1.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde"
|
|
||||||
version = "1.0.203"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
|
|
||||||
dependencies = [
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive"
|
|
||||||
version = "1.0.203"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.66",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "spin"
|
|
||||||
version = "0.9.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
|
||||||
dependencies = [
|
|
||||||
"lock_api",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "spinning_top"
|
|
||||||
version = "0.2.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5b9eb1a2f4c41445a3a0ff9abc5221c5fcd28e1f13cd7c0397706f9ac938ddb0"
|
|
||||||
dependencies = [
|
|
||||||
"lock_api",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "std"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"core2",
|
|
||||||
"crossbeam-queue",
|
|
||||||
"derive-try-from-primitive",
|
|
||||||
"elf",
|
|
||||||
"elfloader",
|
|
||||||
"hashbrown",
|
|
||||||
"linked_list_allocator",
|
|
||||||
"postcard",
|
|
||||||
"serde",
|
|
||||||
"spin",
|
|
||||||
"x86_64 0.14.12",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "1.0.109"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "2.0.66"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tap"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tar-no-std"
|
name = "tar-no-std"
|
||||||
version = "0.1.7"
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4d897790ee033615752cc7bf882343881ad748438c01bc7e1b9d6242bf14a2c6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"bitflags",
|
||||||
"bitflags 1.3.2",
|
|
||||||
"log",
|
"log",
|
||||||
]
|
"memchr",
|
||||||
|
"num-traits",
|
||||||
[[package]]
|
|
||||||
name = "unicode-ident"
|
|
||||||
version = "1.0.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "version_check"
|
|
||||||
version = "0.9.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "volatile"
|
|
||||||
version = "0.4.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "442887c63f2c839b346c192d047a7c87e73d0689c9157b00b53dcc27dd5ea793"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "x86_64"
|
|
||||||
version = "0.14.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "96cb6fd45bfeab6a5055c5bffdb08768bd0c069f1d946debe585bbb380a7c062"
|
|
||||||
dependencies = [
|
|
||||||
"bit_field",
|
|
||||||
"bitflags 2.5.0",
|
|
||||||
"rustversion",
|
|
||||||
"volatile",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "x86_64"
|
|
||||||
version = "0.15.1"
|
|
||||||
source = "git+https://gitea.pterpstra.com/mikros/x86_64#8debcc3504d7e5c39c10b28f2a0661b19ee3a6f0"
|
|
||||||
dependencies = [
|
|
||||||
"bit_field",
|
|
||||||
"bitflags 2.5.0",
|
|
||||||
"rustversion",
|
|
||||||
"volatile",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "xmas-elf"
|
|
||||||
version = "0.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8d29b4d8e7beaceb4e77447ba941a7600d23d0319ab52da0461abea214832d5a"
|
|
||||||
dependencies = [
|
|
||||||
"zero",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zero"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2fe21bcc34ca7fe6dd56cc2cb1261ea59d6b93620215aefb5ea6032265527784"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zerocopy"
|
|
||||||
version = "0.7.34"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087"
|
|
||||||
dependencies = [
|
|
||||||
"zerocopy-derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zerocopy-derive"
|
|
||||||
version = "0.7.34"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.66",
|
|
||||||
]
|
]
|
||||||
|
12
Cargo.toml
12
Cargo.toml
@ -6,17 +6,7 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tar-no-std = { path = "../tar-no-std" }
|
tar-no-std = "0.3.1"
|
||||||
std = { path = "../std" }
|
|
||||||
bitflags = "1.3.2"
|
|
||||||
spin = "0.9.4"
|
|
||||||
tap = "1.0.1"
|
|
||||||
x86_64 = { git = "https://gitea.pterpstra.com/mikros/x86_64" }
|
|
||||||
binread = { version = "2.2.0", path = "../binread/binread", default-features = false }
|
|
||||||
itertools = { version = "0.10.3", default-features = false, features = ["use_alloc"] }
|
|
||||||
serde = { version = "1.0.144", default-features = false, features = ["alloc", "derive"] }
|
|
||||||
postcard = { version = "1.0.2", default-features = false, features = ["alloc"] }
|
|
||||||
dev_driver_rpc = { version = "0.1.0", path = "../dev_driver_rpc" }
|
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "nightly"
|
channel = "dev-x86_64-unknown-mikros"
|
||||||
|
412
src/ata.rs
412
src/ata.rs
@ -1,412 +0,0 @@
|
|||||||
use std::cmp::Ordering;
|
|
||||||
|
|
||||||
use bitflags::bitflags;
|
|
||||||
use spin::{Lazy, Mutex};
|
|
||||||
use std::io::{self, Read, Seek};
|
|
||||||
use tap::Tap;
|
|
||||||
use x86_64::instructions::port::{
|
|
||||||
PortRead, PortSafe, PortSafeReadOnly, PortSafeWriteOnly, PortWrite,
|
|
||||||
};
|
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
pub struct RawError: u8 {
|
|
||||||
const AMNF = 0b0000_0001;
|
|
||||||
const TKZNF = 0b0000_0010;
|
|
||||||
const ABRT = 0b0000_0100;
|
|
||||||
const MCR = 0b0000_1000;
|
|
||||||
const IDNF = 0b0001_0000;
|
|
||||||
const MC = 0b0010_0000;
|
|
||||||
const UNC = 0b0100_0000;
|
|
||||||
const BBK = 0b1000_0000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<RawError> for Error {
|
|
||||||
fn from(err: RawError) -> Self {
|
|
||||||
if err.contains(RawError::AMNF) {
|
|
||||||
Self::AddressMarkNotFound
|
|
||||||
} else if err.contains(RawError::TKZNF) {
|
|
||||||
Self::TrackZeroNotFound
|
|
||||||
} else if err.contains(RawError::ABRT) {
|
|
||||||
Self::AbortedCommand
|
|
||||||
} else if err.contains(RawError::MCR) {
|
|
||||||
Self::MediaChangeRequest
|
|
||||||
} else if err.contains(RawError::IDNF) {
|
|
||||||
Self::IDMarkNotFound
|
|
||||||
} else if err.contains(RawError::MC) {
|
|
||||||
Self::MediaChanged
|
|
||||||
} else if err.contains(RawError::UNC) {
|
|
||||||
Self::UncorrectableData
|
|
||||||
} else if err.contains(RawError::BBK) {
|
|
||||||
Self::BadBlock
|
|
||||||
} else {
|
|
||||||
Self::Generic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PortRead for RawError {
|
|
||||||
unsafe fn read_from_port(port: u16) -> Self {
|
|
||||||
unsafe { Self::from_bits_unchecked(u8::read_from_port(port)) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PortWrite for RawError {
|
|
||||||
unsafe fn write_to_port(port: u16, value: Self) {
|
|
||||||
unsafe { u8::write_to_port(port, value.bits) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
pub struct Status: u8 {
|
|
||||||
const ERR = 0b0000_0001;
|
|
||||||
const DRQ = 0b0000_1000;
|
|
||||||
const SRV = 0b0001_0000;
|
|
||||||
const DF = 0b0010_0000;
|
|
||||||
const RDY = 0b0100_0000;
|
|
||||||
const BSY = 0b1000_0000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PortRead for Status {
|
|
||||||
unsafe fn read_from_port(port: u16) -> Self {
|
|
||||||
unsafe { Self::from_bits_unchecked(u8::read_from_port(port)) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PortWrite for Status {
|
|
||||||
unsafe fn write_to_port(port: u16, value: Self) {
|
|
||||||
unsafe { u8::write_to_port(port, value.bits) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
pub struct DeviceControl: u8 {
|
|
||||||
const N_IEN = 0b0000_0010;
|
|
||||||
const SRST = 0b0000_0100;
|
|
||||||
const HOB = 0b1000_0000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PortRead for DeviceControl {
|
|
||||||
unsafe fn read_from_port(port: u16) -> Self {
|
|
||||||
unsafe { Self::from_bits_unchecked(u8::read_from_port(port)) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PortWrite for DeviceControl {
|
|
||||||
unsafe fn write_to_port(port: u16, value: Self) {
|
|
||||||
unsafe { u8::write_to_port(port, value.bits) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct DriveHead: u8 {
|
|
||||||
const DT0 = 0b0000_0001;
|
|
||||||
const DT1 = 0b0000_0010;
|
|
||||||
const DT2 = 0b0000_0100;
|
|
||||||
const DT3 = 0b0000_1000;
|
|
||||||
const DRV = 0b0001_0000;
|
|
||||||
const LBA = 0b0100_0000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DriveHead {
|
|
||||||
fn set_head_block(&mut self, data: u8) {
|
|
||||||
assert!(data < 16);
|
|
||||||
self.bits = (self.bits & 0xF0) | data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PortRead for DriveHead {
|
|
||||||
unsafe fn read_from_port(port: u16) -> Self {
|
|
||||||
unsafe { Self::from_bits_unchecked(u8::read_from_port(port)) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PortWrite for DriveHead {
|
|
||||||
unsafe fn write_to_port(port: u16, value: Self) {
|
|
||||||
unsafe { u8::write_to_port(port, value.bits) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u8)]
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum DriveNumber {
|
|
||||||
Master = 0,
|
|
||||||
Slave = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u8)]
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
||||||
#[allow(unused)]
|
|
||||||
enum Command {
|
|
||||||
Identify = 0xEC,
|
|
||||||
IdentifyPacket = 0xA1,
|
|
||||||
ReadSectors = 0x20,
|
|
||||||
WriteSectors = 0x30,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum Error {
|
|
||||||
NonexistentDrive,
|
|
||||||
Generic,
|
|
||||||
AbortedCommand,
|
|
||||||
TrackZeroNotFound,
|
|
||||||
AddressMarkNotFound,
|
|
||||||
MediaChangeRequest,
|
|
||||||
IDMarkNotFound,
|
|
||||||
MediaChanged,
|
|
||||||
UncorrectableData,
|
|
||||||
BadBlock,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Error> for io::Error {
|
|
||||||
fn from(_: Error) -> Self {
|
|
||||||
Self::new(io::ErrorKind::Other, "ATA read error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
||||||
#[allow(clippy::upper_case_acronyms)]
|
|
||||||
pub enum DriveKind {
|
|
||||||
ATA,
|
|
||||||
ATAPI,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum IdentifyError {
|
|
||||||
SATADrive,
|
|
||||||
Error(Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Error> for IdentifyError {
|
|
||||||
fn from(err: Error) -> Self {
|
|
||||||
Self::Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Bus {
|
|
||||||
data: PortSafe<u16>,
|
|
||||||
error: PortSafeReadOnly<RawError>,
|
|
||||||
sector_count: PortSafe<u8>,
|
|
||||||
sector_num_lba_lo: PortSafe<u8>,
|
|
||||||
cyl_low_lba_mid: PortSafe<u8>,
|
|
||||||
cyl_high_lba_hi: PortSafe<u8>,
|
|
||||||
drive_head: PortSafe<DriveHead>,
|
|
||||||
status: PortSafeReadOnly<Status>,
|
|
||||||
command: PortSafeWriteOnly<u8>,
|
|
||||||
selected_drive: Option<DriveNumber>, // None represents the initial state with an unknown drive selected
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Bus {
|
|
||||||
/// SAFETY
|
|
||||||
/// io_base must be a valid base for the ATA bus's IO registers
|
|
||||||
const unsafe fn new(io_base: u16) -> Self {
|
|
||||||
unsafe {
|
|
||||||
Self {
|
|
||||||
data: PortSafe::new(io_base),
|
|
||||||
error: PortSafeReadOnly::new(io_base + 1),
|
|
||||||
sector_count: PortSafe::new(io_base + 2),
|
|
||||||
sector_num_lba_lo: PortSafe::new(io_base + 3),
|
|
||||||
cyl_low_lba_mid: PortSafe::new(io_base + 4),
|
|
||||||
cyl_high_lba_hi: PortSafe::new(io_base + 5),
|
|
||||||
drive_head: PortSafe::new(io_base + 6),
|
|
||||||
status: PortSafeReadOnly::new(io_base + 7),
|
|
||||||
command: PortSafeWriteOnly::new(io_base + 7),
|
|
||||||
selected_drive: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn select(&mut self, drive: DriveNumber, lba: bool, data: u8) {
|
|
||||||
if self.selected_drive != Some(drive) {
|
|
||||||
self.drive_head.write(DriveHead::empty().tap_mut(|v| {
|
|
||||||
v.set(DriveHead::DRV, drive == DriveNumber::Slave);
|
|
||||||
v.set(DriveHead::LBA, lba);
|
|
||||||
v.set_head_block(data);
|
|
||||||
}));
|
|
||||||
for _ in 0..14 {
|
|
||||||
self.status.read();
|
|
||||||
}
|
|
||||||
self.selected_drive = Some(drive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_data(&mut self) -> [u16; 256] {
|
|
||||||
let mut arr = [0; 256];
|
|
||||||
arr.fill_with(|| self.data.read());
|
|
||||||
arr
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_command(&mut self, command: Command) -> Result<(), Error> {
|
|
||||||
self.command.write(command as u8);
|
|
||||||
while self.status.read().contains(Status::BSY) {}
|
|
||||||
while !(self.status.read().intersects(Status::DRQ | Status::ERR)) {}
|
|
||||||
self.error()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn identify(&mut self) -> Result<(DriveKind, [u16; 256]), IdentifyError> {
|
|
||||||
self.command.write(Command::Identify as u8);
|
|
||||||
if self.status.read().bits() == 0 {
|
|
||||||
return Err(IdentifyError::Error(Error::NonexistentDrive));
|
|
||||||
};
|
|
||||||
while self.status.read().contains(Status::BSY) {}
|
|
||||||
while !(self.status.read().intersects(Status::DRQ | Status::ERR)) {}
|
|
||||||
if self.status.read().contains(Status::ERR) {
|
|
||||||
if self.cyl_low_lba_mid.read() == 0x14 && self.cyl_high_lba_hi.read() == 0xEB {
|
|
||||||
self.command.write(Command::IdentifyPacket as u8);
|
|
||||||
if self.status.read().bits() == 0 {
|
|
||||||
return Err(IdentifyError::Error(Error::NonexistentDrive));
|
|
||||||
};
|
|
||||||
while self.status.read().contains(Status::BSY) {}
|
|
||||||
while !(self.status.read().intersects(Status::DRQ | Status::ERR)) {}
|
|
||||||
self.error()?;
|
|
||||||
Ok((DriveKind::ATAPI, self.read_data()))
|
|
||||||
} else {
|
|
||||||
Err(IdentifyError::SATADrive)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok((DriveKind::ATA, self.read_data()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn error(&mut self) -> Result<(), Error> {
|
|
||||||
if self.status.read().contains(Status::ERR) {
|
|
||||||
Err(self.error.read().into())
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
// SAFETY
|
|
||||||
// This is safe because 0x1F0 is the standard primary bus base address
|
|
||||||
static PRIMARY_BUS: Mutex<Bus> = Mutex::new(unsafe { Bus::new(0x1F0) });
|
|
||||||
#[allow(unused)]
|
|
||||||
// SAFETY
|
|
||||||
// This is safe because 0x170 is the standard secondary bus base address
|
|
||||||
static SECONDARY_BUS: Mutex<Bus> = Mutex::new(unsafe { Bus::new(0x170) });
|
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub static PRIMARY_MASTER: Lazy<Result<Device, NewDeviceError>> =
|
|
||||||
Lazy::new(|| Device::new(&PRIMARY_BUS, DriveNumber::Master));
|
|
||||||
#[allow(unused)]
|
|
||||||
pub static PRIMARY_SLAVE: Lazy<Result<Device, NewDeviceError>> =
|
|
||||||
Lazy::new(|| Device::new(&PRIMARY_BUS, DriveNumber::Slave));
|
|
||||||
#[allow(unused)]
|
|
||||||
pub static SECONDARY_MASTER: Lazy<Result<Device, NewDeviceError>> =
|
|
||||||
Lazy::new(|| Device::new(&SECONDARY_BUS, DriveNumber::Master));
|
|
||||||
#[allow(unused)]
|
|
||||||
pub static SECONDARY_SLAVE: Lazy<Result<Device, NewDeviceError>> =
|
|
||||||
Lazy::new(|| Device::new(&SECONDARY_BUS, DriveNumber::Slave));
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub struct Device {
|
|
||||||
bus: &'static Mutex<Bus>,
|
|
||||||
drive_number: DriveNumber,
|
|
||||||
lba: bool,
|
|
||||||
pos: u64,
|
|
||||||
len: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum NewDeviceError {
|
|
||||||
IdentifyError(IdentifyError),
|
|
||||||
ATAPIDevice,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Device {
|
|
||||||
fn new(bus: &'static Mutex<Bus>, drive_number: DriveNumber) -> Result<Self, NewDeviceError> {
|
|
||||||
bus.lock().select(drive_number, true, 0);
|
|
||||||
let id_result = bus
|
|
||||||
.lock()
|
|
||||||
.identify()
|
|
||||||
.map_err(NewDeviceError::IdentifyError)?;
|
|
||||||
if let (DriveKind::ATA, ident_data) = id_result {
|
|
||||||
Ok(Self {
|
|
||||||
bus,
|
|
||||||
drive_number,
|
|
||||||
lba: true,
|
|
||||||
pos: 0,
|
|
||||||
len: (u64::from(ident_data[60]) * 512),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(NewDeviceError::ATAPIDevice)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn select(&self, data: u8) {
|
|
||||||
self.bus.lock().select(self.drive_number, self.lba, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for Device {
|
|
||||||
#[allow(clippy::similar_names)]
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
let avail_len = u64::min(u64::min(self.len - self.pos, buf.len() as u64), 512 * 250);
|
|
||||||
if avail_len == 0 {
|
|
||||||
return Ok(0);
|
|
||||||
}
|
|
||||||
let sector = self.pos / 512;
|
|
||||||
assert!(sector < 0x1_0000_0000_0000);
|
|
||||||
let offset = (self.pos % 512) as usize;
|
|
||||||
let length = avail_len as usize + offset;
|
|
||||||
let num_sectors = length.div_ceil(512);
|
|
||||||
assert!(num_sectors < 256);
|
|
||||||
if sector < 0x1000_0000 {
|
|
||||||
self.select(((sector & 0xF00_0000) >> 24) as u8);
|
|
||||||
let mut bus = self.bus.lock();
|
|
||||||
bus.sector_count.write(num_sectors as u8);
|
|
||||||
bus.sector_num_lba_lo.write(sector as u8);
|
|
||||||
bus.cyl_low_lba_mid.write((sector >> 8) as u8);
|
|
||||||
bus.cyl_high_lba_hi.write((sector >> 16) as u8);
|
|
||||||
bus.send_command(Command::ReadSectors)?;
|
|
||||||
} else {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
let mut byte_num = 0;
|
|
||||||
let mut i = 0;
|
|
||||||
for _ in 0..num_sectors {
|
|
||||||
for byte in self
|
|
||||||
.bus
|
|
||||||
.lock()
|
|
||||||
.read_data()
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(u16::to_le_bytes)
|
|
||||||
{
|
|
||||||
if byte_num >= offset && byte_num < (offset + buf.len()) {
|
|
||||||
buf[i] = byte;
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
byte_num += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.pos += avail_len as u64;
|
|
||||||
Ok(avail_len as usize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Seek for Device {
|
|
||||||
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
|
||||||
match pos {
|
|
||||||
io::SeekFrom::Start(x) => self.pos = x,
|
|
||||||
io::SeekFrom::End(_) => {
|
|
||||||
return Err(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"End seek not implemented yet",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
io::SeekFrom::Current(x) => match x.cmp(&0) {
|
|
||||||
Ordering::Equal => (),
|
|
||||||
Ordering::Greater => self.pos += x.unsigned_abs(),
|
|
||||||
Ordering::Less => self.pos -= x.unsigned_abs(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Ok(self.pos)
|
|
||||||
}
|
|
||||||
}
|
|
164
src/ext2.rs
164
src/ext2.rs
@ -1,164 +0,0 @@
|
|||||||
mod block_group_table;
|
|
||||||
mod block_reader;
|
|
||||||
mod dir;
|
|
||||||
mod file;
|
|
||||||
mod metadata;
|
|
||||||
mod structs;
|
|
||||||
|
|
||||||
use block_group_table::BlockGroupDescriptorTable;
|
|
||||||
use block_reader::BlockReader;
|
|
||||||
use std::dbg;
|
|
||||||
use std::io::{self, Read};
|
|
||||||
use std::path::{Component, Path};
|
|
||||||
use std::string::{String, ToString};
|
|
||||||
use std::vec::Vec;
|
|
||||||
use structs::{Inode, Superblock};
|
|
||||||
|
|
||||||
pub use dir::{DirEntry, ReadDir};
|
|
||||||
pub use file::File;
|
|
||||||
pub use metadata::{FileType, Metadata, Permissions};
|
|
||||||
use std::fs::File as StdFile;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Ext2 {
|
|
||||||
reader: BlockReader,
|
|
||||||
descriptor_table: BlockGroupDescriptorTable,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ext2 {
|
|
||||||
pub fn new(mut disk: StdFile) -> io::Result<Self> {
|
|
||||||
let superblock = Superblock::read_from_disk(&mut disk)?;
|
|
||||||
let reader = BlockReader::new(disk, &superblock);
|
|
||||||
Ok(Self {
|
|
||||||
descriptor_table: dbg!(BlockGroupDescriptorTable::new(&superblock, &reader)?),
|
|
||||||
reader,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn path_inode<P: AsRef<Path>>(&self, path: P) -> io::Result<u32> {
|
|
||||||
let path = path.as_ref();
|
|
||||||
path.parent().map_or(Ok(2), |parent| {
|
|
||||||
self.read_dir_no_path(parent)?
|
|
||||||
.get_entry(path.file_name().ok_or_else(|| {
|
|
||||||
io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"A path ending in .. or . was passed to path_inode",
|
|
||||||
)
|
|
||||||
})?)
|
|
||||||
.map(|x| x.inode)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an iterator over the entries within a directory.
|
|
||||||
///
|
|
||||||
/// The iterator will yield instances of <code>[`io::Result`]<[`DirEntry`]></code>.
|
|
||||||
/// New errors may be encountered after an iterator is initially constructed.
|
|
||||||
/// Entries for the current and parent directories (`.` and `..`) are
|
|
||||||
/// skipped.
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// This function will return an error in the following situations, but is not
|
|
||||||
/// limited to just these cases:
|
|
||||||
///
|
|
||||||
/// * The provided `path` doesn't exist.
|
|
||||||
/// * The process lacks permissions to view the contents.
|
|
||||||
/// * The `path` points at a non-directory file.
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn read_dir<P: AsRef<Path>>(&self, path: P) -> io::Result<ReadDir> {
|
|
||||||
path.as_ref().components().try_fold(
|
|
||||||
ReadDir::read_inode(self, 2, Some("/".to_string().into()))?,
|
|
||||||
|dir, component| match component {
|
|
||||||
Component::Normal(os_str) => dir.get_entry(os_str)?.read_dir(),
|
|
||||||
_ => Ok(dir),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an iterator over the entries within a directory, without allocating the full path
|
|
||||||
/// for the directory on the heap. This saves unnecessary allocations when you will not use
|
|
||||||
/// `DirEntry::path`.
|
|
||||||
///
|
|
||||||
/// The iterator will yield instances of <code>[`io::Result`]<[`DirEntry`]></code>.
|
|
||||||
/// New errors may be encountered after an iterator is initially constructed.
|
|
||||||
/// Entries for the current and parent directories (`.` and `..`) are
|
|
||||||
/// skipped.
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// This function will return an error in the following situations, but is not
|
|
||||||
/// limited to just these cases:
|
|
||||||
///
|
|
||||||
/// * The provided `path` doesn't exist.
|
|
||||||
/// * The process lacks permissions to view the contents.
|
|
||||||
/// * The `path` points at a non-directory file.
|
|
||||||
pub fn read_dir_no_path<P: AsRef<Path>>(&self, path: P) -> io::Result<ReadDir> {
|
|
||||||
path.as_ref().components().try_fold(
|
|
||||||
ReadDir::read_inode(self, 2, None)?,
|
|
||||||
|dir, component| match component {
|
|
||||||
Component::Normal(os_str) => dir.get_entry(os_str)?.read_dir(),
|
|
||||||
_ => Ok(dir),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempts to open a file in read-only mode.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// This function will return an error if `path` does not already exist.
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn open<P: AsRef<Path>>(&self, path: P) -> io::Result<File> {
|
|
||||||
File::from_inode(Inode::read(self, self.path_inode(path)?)?, self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read the entire contents of a file into a string.
|
|
||||||
///
|
|
||||||
/// This is a convenience function for using [`File::open`] and [`read_to_string`]
|
|
||||||
/// with fewer imports and without an intermediate variable.
|
|
||||||
///
|
|
||||||
/// [`read_to_string`]: core2::io::Read::read_to_string
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// This function will return an error if `path` does not already exist.
|
|
||||||
/// Other errors may also be returned according to [`OpenOptions::open`].
|
|
||||||
///
|
|
||||||
/// It will also return an error if it encounters while reading an error
|
|
||||||
/// of a kind other than [`io::ErrorKind::Interrupted`],
|
|
||||||
/// or if the contents of the file are not valid UTF-8.
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn read_to_string<P: AsRef<Path>>(&self, path: P) -> io::Result<String> {
|
|
||||||
let mut string = String::new();
|
|
||||||
self.open(path)?.read_to_string(&mut string)?;
|
|
||||||
Ok(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read the entire contents of a file into a bytes vector.
|
|
||||||
///
|
|
||||||
/// This is a convenience function for using [`File::open`] and [`read_to_end`]
|
|
||||||
/// with fewer imports and without an intermediate variable.
|
|
||||||
///
|
|
||||||
/// [`read_to_end`]: core2::io::Read::read_to_end
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// This function will return an error if `path` does not already exist.
|
|
||||||
/// Other errors may also be returned according to [`OpenOptions::open`].
|
|
||||||
///
|
|
||||||
/// It will also return an error if it encounters while reading an error
|
|
||||||
/// of a kind other than [`io::ErrorKind::Interrupted`].
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn read<P: AsRef<Path>>(&self, path: P) -> io::Result<Vec<u8>> {
|
|
||||||
let mut bytes = Vec::new();
|
|
||||||
self.open(path)?.read_to_end(&mut bytes)?;
|
|
||||||
Ok(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn metadata<P: AsRef<Path>>(&self, path: P) -> io::Result<Metadata> {
|
|
||||||
Inode::read(self, self.path_inode(path)?).map(|x| Metadata::from_inode(&x))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
use super::{
|
|
||||||
block_reader::BlockReader,
|
|
||||||
structs::{BlockGroupDescriptor, Inode, Superblock},
|
|
||||||
};
|
|
||||||
use binread::prelude::*;
|
|
||||||
use std::vec::Vec;
|
|
||||||
use std::{dbg, io};
|
|
||||||
|
|
||||||
const INODE_SIZE: usize = 256;
|
|
||||||
|
|
||||||
#[derive(Debug, BinRead)]
|
|
||||||
#[br(import(len: usize, block_group_inode_count: u32, block_size: usize))]
|
|
||||||
pub struct BlockGroupDescriptorTable {
|
|
||||||
#[br(count = len)]
|
|
||||||
table: Vec<BlockGroupDescriptor>,
|
|
||||||
#[br(calc = block_group_inode_count)]
|
|
||||||
block_group_inode_count: u32,
|
|
||||||
#[br(calc = block_size)]
|
|
||||||
block_size: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BlockGroupDescriptorTable {
|
|
||||||
pub fn new(superblock: &Superblock, reader: &BlockReader) -> io::Result<Self> {
|
|
||||||
let num_block_groups = superblock
|
|
||||||
.total_blocks
|
|
||||||
.div_ceil(superblock.block_group_block_count) as usize;
|
|
||||||
let table_blocks = num_block_groups.div_ceil(reader.block_size / 32);
|
|
||||||
Ok(reader
|
|
||||||
.read_blocks(superblock.superblock_block + 1, table_blocks)?
|
|
||||||
.read_le_args((
|
|
||||||
num_block_groups,
|
|
||||||
superblock.block_group_inode_count,
|
|
||||||
1024 << superblock.block_size_raw,
|
|
||||||
))
|
|
||||||
.expect("Parsing the block group descriptor table should never fail"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn inode_group(&self, inode: u32) -> &BlockGroupDescriptor {
|
|
||||||
&self.table[((inode - 1) / self.block_group_inode_count) as usize]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn inode_group_offset(&self, inode: u32) -> usize {
|
|
||||||
((inode - 1) % self.block_group_inode_count) as usize
|
|
||||||
}
|
|
||||||
|
|
||||||
fn inode_block(&self, inode: u32) -> u32 {
|
|
||||||
(((self.inode_group_offset(inode) * INODE_SIZE) / self.block_size) as u32)
|
|
||||||
+ self.inode_group(inode).inode_table_start
|
|
||||||
}
|
|
||||||
|
|
||||||
fn inode_block_byte_start(&self, inode: u32) -> usize {
|
|
||||||
(self.inode_group_offset(inode) * INODE_SIZE) % self.block_size
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inode_loc(&self, inode: u32) -> InodeLoc {
|
|
||||||
// dbg!(self);
|
|
||||||
InodeLoc {
|
|
||||||
block: self.inode_block(inode),
|
|
||||||
byte_offset: self.inode_block_byte_start(inode),
|
|
||||||
num: inode,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct InodeLoc {
|
|
||||||
block: u32,
|
|
||||||
byte_offset: usize,
|
|
||||||
num: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InodeLoc {
|
|
||||||
pub fn read(&self, reader: &BlockReader) -> io::Result<Inode> {
|
|
||||||
// dbg!(self.num);
|
|
||||||
reader
|
|
||||||
.read_block_offset(self.block, self.byte_offset as u32)?
|
|
||||||
.read_le_args((self.num,))
|
|
||||||
.map_err(|err| match err {
|
|
||||||
binread::Error::Io(_) => io::Error::new(io::ErrorKind::Other, "Binread IO error"),
|
|
||||||
_ => io::Error::new(io::ErrorKind::InvalidData, "Inode data was invalid"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
use std::dbg;
|
|
||||||
use std::vec::Vec;
|
|
||||||
|
|
||||||
use super::structs::Superblock;
|
|
||||||
use std::fs::File as StdFile;
|
|
||||||
use std::io::{self, Cursor};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct BlockReader {
|
|
||||||
pub disk: StdFile,
|
|
||||||
pub block_size: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BlockReader {
|
|
||||||
pub fn new(disk: StdFile, superblock: &Superblock) -> Self {
|
|
||||||
Self {
|
|
||||||
disk,
|
|
||||||
block_size: 1024 << superblock.block_size_raw,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_block(&self, block: u32) -> io::Result<Cursor<Vec<u8>>> {
|
|
||||||
self.read_blocks_offset(block, 0, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_block_offset(&self, block: u32, offset: u32) -> io::Result<Cursor<Vec<u8>>> {
|
|
||||||
self.read_blocks_offset(block, offset, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_blocks(&self, block: u32, count: usize) -> io::Result<Cursor<Vec<u8>>> {
|
|
||||||
self.read_blocks_offset(block, 0, count)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_blocks_offset(
|
|
||||||
&self,
|
|
||||||
block: u32,
|
|
||||||
offset: u32,
|
|
||||||
count: usize,
|
|
||||||
) -> io::Result<Cursor<Vec<u8>>> {
|
|
||||||
// dbg!(block, offset, count, self.block_size);
|
|
||||||
let start = self.block_to_byte_offset(block) + u64::from(offset);
|
|
||||||
let size = (self.block_size * count) - (offset as usize);
|
|
||||||
// dbg!(start, size);
|
|
||||||
let mut vec = Vec::new();
|
|
||||||
vec.resize(size, 0);
|
|
||||||
self.disk.read_at(&mut vec, start)?;
|
|
||||||
// dbg!(&vec[0..16]);
|
|
||||||
Ok(Cursor::new(vec))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn block_to_byte_offset(&self, block: u32) -> u64 {
|
|
||||||
u64::from(block) * (self.block_size as u64)
|
|
||||||
}
|
|
||||||
}
|
|
180
src/ext2/dir.rs
180
src/ext2/dir.rs
@ -1,180 +0,0 @@
|
|||||||
use super::{
|
|
||||||
file::File,
|
|
||||||
metadata::{FileType, Metadata},
|
|
||||||
structs::{DirEntryDisk, Inode},
|
|
||||||
Ext2,
|
|
||||||
};
|
|
||||||
use binread::BinReaderExt;
|
|
||||||
use std::io;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::string::String;
|
|
||||||
|
|
||||||
/// Entries returned by the [`ReadDir`] iterator.
|
|
||||||
///
|
|
||||||
/// An instance of `DirEntry` represents an entry inside of a directory on the
|
|
||||||
/// filesystem. Each entry can be inspected via methods to learn about the full
|
|
||||||
/// path or possibly other metadata through per-platform extension traits.
|
|
||||||
#[allow(unused)]
|
|
||||||
#[allow(clippy::module_name_repetitions)]
|
|
||||||
pub struct DirEntry<'a> {
|
|
||||||
pub(super) inode: u32,
|
|
||||||
pub(super) name: String,
|
|
||||||
pub(super) metadata: Metadata,
|
|
||||||
pub(super) directory_path: Option<PathBuf>,
|
|
||||||
fs: &'a Ext2,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> core::fmt::Debug for DirEntry<'a> {
|
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
||||||
f.debug_struct("DirEntry")
|
|
||||||
.field("inode", &self.inode)
|
|
||||||
.field("name", &self.name)
|
|
||||||
.field("metadata", &self.metadata)
|
|
||||||
.field("directory_path", &self.directory_path)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
impl<'a> DirEntry<'a> {
|
|
||||||
/// Returns the bare file name of this directory entry without any other
|
|
||||||
/// leading path component.
|
|
||||||
pub fn file_name(&self) -> String {
|
|
||||||
self.name.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the file type for the file that this entry points at.
|
|
||||||
///
|
|
||||||
/// This function will not traverse symlinks if this entry points at a
|
|
||||||
/// symlink.
|
|
||||||
pub fn file_type(&self) -> FileType {
|
|
||||||
self.metadata.file_type()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the metadata for the file that this entry points at.
|
|
||||||
///
|
|
||||||
/// This function will not traverse symlinks if this entry points at a
|
|
||||||
/// symlink.
|
|
||||||
pub fn metadata(&self) -> Metadata {
|
|
||||||
self.metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the full path to the file that this entry represents.
|
|
||||||
///
|
|
||||||
/// The full path is created by joining the original path to `read_dir`
|
|
||||||
/// with the filename of this entry.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// This function will panic if this entry came either directly from a `ReadDir` made with
|
|
||||||
/// `Ext2::read_dir_no_path`, or indirectly from such a `ReadDir` by use of the
|
|
||||||
/// `DirEntry::read_dir` function. This is an explicit decision, so such a panic indicates a
|
|
||||||
/// definite bug in your code.
|
|
||||||
pub fn path(&self) -> PathBuf {
|
|
||||||
self.try_path().expect("Path was None")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the full path to the file that this entry represents.
|
|
||||||
///
|
|
||||||
/// The full path is created by joining the original path to `read_dir`
|
|
||||||
/// with the filename of this entry.
|
|
||||||
pub fn try_path(&self) -> Option<PathBuf> {
|
|
||||||
self.directory_path
|
|
||||||
.as_ref()
|
|
||||||
.map(|x| x.join(self.name.clone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn open(&self) -> io::Result<File> {
|
|
||||||
File::from_inode(Inode::read(self.fs, self.inode)?, self.fs)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_dir(&self) -> io::Result<ReadDir<'a>> {
|
|
||||||
ReadDir::read_inode(self.fs, self.inode, self.try_path())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterator over the entries in a directory.
|
|
||||||
///
|
|
||||||
/// This iterator is returned from the [`super::Ext2::read_dir`] function and
|
|
||||||
/// will yield instances of <code>[`io::Result`]<[`DirEntry`]></code>. Through a [`DirEntry`]
|
|
||||||
/// information like the entry's path and possibly other metadata can be
|
|
||||||
/// learned.
|
|
||||||
///
|
|
||||||
/// The order in which this iterator returns entries is platform and filesystem
|
|
||||||
/// dependent.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// This [`io::Result`] will be an `Err` if there's some sort of intermittent
|
|
||||||
/// IO error during iteration.
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[allow(clippy::module_name_repetitions)]
|
|
||||||
pub struct ReadDir<'a> {
|
|
||||||
fs: &'a Ext2,
|
|
||||||
file: File<'a>,
|
|
||||||
directory_path: Option<PathBuf>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ReadDir<'a> {
|
|
||||||
pub fn read_inode(fs: &'a Ext2, inode: u32, path: Option<PathBuf>) -> io::Result<ReadDir<'a>> {
|
|
||||||
Ok(Self {
|
|
||||||
fs,
|
|
||||||
file: File::from_inode_dir(Inode::read(fs, inode)?, fs)?,
|
|
||||||
directory_path: path,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn get_entry(mut self, name: &str) -> io::Result<DirEntry<'a>> {
|
|
||||||
self.find(|entry| {
|
|
||||||
if let Ok(entry) = entry {
|
|
||||||
entry.name == name
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| Err(io::Error::new(io::ErrorKind::NotFound, "File not found")))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_impl(&mut self) -> io::Result<Option<DirEntry<'a>>> {
|
|
||||||
let entry: DirEntryDisk = match self.file.read_le() {
|
|
||||||
Ok(entry) => entry,
|
|
||||||
Err(binread::Error::Io(io_error)) => {
|
|
||||||
return if io_error.kind() == io::ErrorKind::UnexpectedEof {
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
Err(io_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
return Err(io::Error::new(
|
|
||||||
io::ErrorKind::InvalidData,
|
|
||||||
"Directory entry data was invalid",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let name = String::from_utf8(entry.name).map_err(|_| {
|
|
||||||
io::Error::new(
|
|
||||||
io::ErrorKind::InvalidData,
|
|
||||||
"Non UTF-8 directory entry names are unspported",
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
if name == "." || name == ".." || entry.inode == 0 {
|
|
||||||
return self.next_impl();
|
|
||||||
}
|
|
||||||
let entry_inode = Inode::read(self.fs, entry.inode)?;
|
|
||||||
Ok(Some(DirEntry {
|
|
||||||
inode: entry.inode,
|
|
||||||
name,
|
|
||||||
metadata: Metadata::from_inode(&entry_inode),
|
|
||||||
directory_path: self.directory_path.clone(),
|
|
||||||
fs: self.fs,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for ReadDir<'a> {
|
|
||||||
type Item = io::Result<DirEntry<'a>>;
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
self.next_impl().transpose()
|
|
||||||
}
|
|
||||||
}
|
|
133
src/ext2/file.rs
133
src/ext2/file.rs
@ -1,133 +0,0 @@
|
|||||||
use super::Ext2;
|
|
||||||
use super::{structs::Inode, Metadata};
|
|
||||||
use core::{cmp::Ordering, str};
|
|
||||||
use std::io::{self, Read};
|
|
||||||
use std::string::String;
|
|
||||||
|
|
||||||
/// A reference to an open file on the filesystem.
|
|
||||||
///
|
|
||||||
/// An instance of a `File` can be read and/or written depending on what options
|
|
||||||
/// it was opened with. Files also implement [`Seek`] to alter the logical cursor
|
|
||||||
/// that the file contains internally.
|
|
||||||
///
|
|
||||||
/// Files are automatically closed when they go out of scope. Errors detected
|
|
||||||
/// on closing are ignored by the implementation of `Drop`.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct File<'a> {
|
|
||||||
pos: u64,
|
|
||||||
inode: Inode,
|
|
||||||
fs: &'a Ext2,
|
|
||||||
block_size: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> File<'a> {
|
|
||||||
pub(super) fn from_inode(inode: Inode, fs: &'a Ext2) -> io::Result<Self> {
|
|
||||||
// if !Metadata::from_inode(&inode).file_type().is_file() {
|
|
||||||
// return Err(io::Error::new(io::ErrorKind::Other, "Not a file"));
|
|
||||||
// }
|
|
||||||
Ok(Self {
|
|
||||||
pos: 0,
|
|
||||||
block_size: fs.reader.block_size,
|
|
||||||
fs,
|
|
||||||
inode,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This version of `from_inode` skips checks that the provided inode is a directory, not a file. It
|
|
||||||
/// should, only be used by `ReadDir::read_inode` to read the raw directory data.
|
|
||||||
pub(super) fn from_inode_dir(inode: Inode, fs: &'a Ext2) -> io::Result<Self> {
|
|
||||||
if !Metadata::from_inode(&inode).file_type().is_dir() {
|
|
||||||
return Err(io::Error::new(io::ErrorKind::Other, "Not a directory"));
|
|
||||||
}
|
|
||||||
Ok(Self {
|
|
||||||
pos: 0,
|
|
||||||
block_size: fs.reader.block_size,
|
|
||||||
fs,
|
|
||||||
inode,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Queries metadata about the underlying file.
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn metadata(&self) -> Metadata {
|
|
||||||
Metadata::from_inode(&self.inode)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_at(&mut self, buf: &mut [u8], pos: u64) -> io::Result<usize> {
|
|
||||||
let bytes_to_end = u64::from(self.inode.size_lower32).saturating_sub(pos);
|
|
||||||
if bytes_to_end == 0 {
|
|
||||||
return Ok(0);
|
|
||||||
}
|
|
||||||
let start_block = (pos / self.block_size as u64) as u32;
|
|
||||||
let offset = (pos % self.block_size as u64) as usize;
|
|
||||||
let length = core::cmp::min(bytes_to_end, buf.len() as u64);
|
|
||||||
self.fs
|
|
||||||
.reader
|
|
||||||
.read_block_offset(
|
|
||||||
self.inode
|
|
||||||
.log_to_phys_block(start_block)
|
|
||||||
.ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, "EOF reached"))?,
|
|
||||||
offset as u32,
|
|
||||||
)?
|
|
||||||
.read(buf)?;
|
|
||||||
let mut remaining_bytes = length.saturating_sub((self.block_size - offset) as u64);
|
|
||||||
for block in (start_block + 1)..=u32::MAX {
|
|
||||||
if remaining_bytes == 0 {
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
self.fs
|
|
||||||
.reader
|
|
||||||
.read_block(
|
|
||||||
self.inode.log_to_phys_block(block).ok_or_else(|| {
|
|
||||||
io::Error::new(io::ErrorKind::UnexpectedEof, "EOF reached")
|
|
||||||
})?,
|
|
||||||
)?
|
|
||||||
.read(buf)?;
|
|
||||||
remaining_bytes = remaining_bytes.saturating_sub(self.block_size as u64);
|
|
||||||
}
|
|
||||||
Ok((length - remaining_bytes) as usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
|
|
||||||
unsafe {
|
|
||||||
let buf = buf.as_mut_vec();
|
|
||||||
let ret = self.read_to_end(buf)?;
|
|
||||||
str::from_utf8(buf).map_err(|_| {
|
|
||||||
io::Error::new(
|
|
||||||
io::ErrorKind::InvalidData,
|
|
||||||
"stream did not contain valid UTF-8",
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
Ok(ret)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl io::Seek for File<'_> {
|
|
||||||
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
|
||||||
match pos {
|
|
||||||
io::SeekFrom::Start(x) => self.pos = x,
|
|
||||||
io::SeekFrom::End(x) => match x.cmp(&0) {
|
|
||||||
Ordering::Equal => self.pos = u64::from(self.inode.size_lower32),
|
|
||||||
Ordering::Greater => unimplemented!(),
|
|
||||||
Ordering::Less => self.pos = u64::from(self.inode.size_lower32) - x.unsigned_abs(),
|
|
||||||
},
|
|
||||||
io::SeekFrom::Current(x) => match x.cmp(&0) {
|
|
||||||
Ordering::Equal => (),
|
|
||||||
Ordering::Greater => self.pos += x.unsigned_abs(),
|
|
||||||
Ordering::Less => self.pos -= x.unsigned_abs(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Ok(self.pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl io::Read for File<'_> {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
let res = self.read_at(buf, self.pos);
|
|
||||||
if let Ok(bytes_read) = res {
|
|
||||||
self.pos += bytes_read as u64;
|
|
||||||
}
|
|
||||||
res
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,252 +0,0 @@
|
|||||||
#![allow(unused)]
|
|
||||||
use super::structs::Inode;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub struct Permissions(u32);
|
|
||||||
|
|
||||||
impl Permissions {
|
|
||||||
/// Returns `true` if these permissions describe a readonly (unwritable) file.
|
|
||||||
pub fn readonly(self) -> bool {
|
|
||||||
// check if any class (owner, group, others) has write permission
|
|
||||||
self.0 & 0o222 == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Modifies the readonly flag for this set of permissions. If the
|
|
||||||
/// `readonly` argument is `true`, using the resulting `Permission` will
|
|
||||||
/// update file permissions to forbid writing. Conversely, if it's `false`,
|
|
||||||
/// using the resulting `Permission` will update file permissions to allow
|
|
||||||
/// writing.
|
|
||||||
///
|
|
||||||
/// This operation does **not** modify the filesystem.
|
|
||||||
pub fn set_readonly(mut self, readonly: bool) {
|
|
||||||
if readonly {
|
|
||||||
// remove write permission for all classes; equivalent to `chmod a-w <file>`
|
|
||||||
self.0 &= !0o222;
|
|
||||||
} else {
|
|
||||||
// add write permission for all classes; equivalent to `chmod a+w <file>`
|
|
||||||
self.0 |= 0o222;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the underlying raw `st_mode` bits that contain the standard
|
|
||||||
/// Unix permissions for this file.
|
|
||||||
pub fn mode(self) -> u32 {
|
|
||||||
self.0 as u32
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the underlying raw bits for this set of permissions.
|
|
||||||
pub fn set_mode(mut self, mode: u32) {
|
|
||||||
self.0 = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new instance of `Permissions` from the given set of Unix
|
|
||||||
/// permission bits.
|
|
||||||
pub fn from_mode(mode: u32) -> Self {
|
|
||||||
Self(mode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
|
||||||
pub enum FileType {
|
|
||||||
Fifo,
|
|
||||||
CharDev,
|
|
||||||
Directory,
|
|
||||||
BlockDev,
|
|
||||||
File,
|
|
||||||
SymLink,
|
|
||||||
Socket,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileType {
|
|
||||||
/// Tests whether this file type represents a directory. The
|
|
||||||
/// result is mutually exclusive to the results of
|
|
||||||
/// [`is_file`] and [`is_symlink`]; only zero or one of these
|
|
||||||
/// tests may pass.
|
|
||||||
pub fn is_dir(self) -> bool {
|
|
||||||
self == Self::Directory
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tests whether this file type represents a regular file.
|
|
||||||
/// The result is mutually exclusive to the results of
|
|
||||||
/// [`is_dir`] and [`is_symlink`]; only zero or one of these
|
|
||||||
/// tests may pass.
|
|
||||||
pub fn is_file(self) -> bool {
|
|
||||||
self == Self::File
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tests whether this file type represents a symbolic link.
|
|
||||||
/// The result is mutually exclusive to the results of
|
|
||||||
/// [`is_dir`] and [`is_file`]; only zero or one of these
|
|
||||||
/// tests may pass.
|
|
||||||
pub fn is_symlink(self) -> bool {
|
|
||||||
self == Self::SymLink
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if this file type is a fifo.
|
|
||||||
pub fn is_fifo(self) -> bool {
|
|
||||||
self == Self::Fifo
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if this file type is a block device.
|
|
||||||
pub fn is_block_device(self) -> bool {
|
|
||||||
self == Self::BlockDev
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if this file type is a char device.
|
|
||||||
pub fn is_char_device(self) -> bool {
|
|
||||||
self == Self::CharDev
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if this file type is a socket.
|
|
||||||
pub fn is_socket(self) -> bool {
|
|
||||||
self == Self::Socket
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub struct Metadata {
|
|
||||||
accessed: u32,
|
|
||||||
created: u32,
|
|
||||||
file_type: FileType,
|
|
||||||
length: u64,
|
|
||||||
modified: u32,
|
|
||||||
permissions: Permissions,
|
|
||||||
ino: u64,
|
|
||||||
nlink: u64,
|
|
||||||
uid: u32,
|
|
||||||
gid: u32,
|
|
||||||
// blksize: u64,
|
|
||||||
blocks: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Metadata {
|
|
||||||
pub(super) fn from_inode(inode: &Inode) -> Self {
|
|
||||||
let file_type = match (inode.type_perms & 0xF000) >> 12 {
|
|
||||||
1 => FileType::Fifo,
|
|
||||||
2 => FileType::CharDev,
|
|
||||||
4 => FileType::Directory,
|
|
||||||
6 => FileType::BlockDev,
|
|
||||||
8 => FileType::File,
|
|
||||||
0xA => FileType::SymLink,
|
|
||||||
0xC => FileType::Socket,
|
|
||||||
x => panic!("Invalid inode file type {}", x),
|
|
||||||
};
|
|
||||||
let permissions = Permissions(u32::from(inode.type_perms & 0xFFF));
|
|
||||||
Self {
|
|
||||||
accessed: inode.last_access_time,
|
|
||||||
created: inode.creation_time,
|
|
||||||
file_type,
|
|
||||||
length: u64::from(inode.size_lower32),
|
|
||||||
modified: inode.last_modification_time,
|
|
||||||
permissions,
|
|
||||||
ino: u64::from(inode.number),
|
|
||||||
nlink: u64::from(inode.hard_links_count),
|
|
||||||
uid: u32::from(inode.uid),
|
|
||||||
gid: u32::from(inode.gid),
|
|
||||||
// blksize: inode.reader.block_size as u64,
|
|
||||||
blocks: u64::from(inode.sectors_used),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the last access time of this metadata.
|
|
||||||
///
|
|
||||||
/// The returned value corresponds to the `atime` field of `stat`
|
|
||||||
pub fn accessed(&self) -> u32 {
|
|
||||||
self.accessed
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the creation time listed in this metadata.
|
|
||||||
///
|
|
||||||
/// The returned value corresponds to the `btime` field of `statx` on
|
|
||||||
/// Linux kernel starting from to 4.11, and the `birthtime` field of `stat` on
|
|
||||||
/// other Unix platforms.
|
|
||||||
pub fn created(&self) -> u32 {
|
|
||||||
self.created
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the last modification time listed in this metadata.
|
|
||||||
///
|
|
||||||
/// The returned value corresponds to the `mtime` field of `stat`
|
|
||||||
pub fn modified(&self) -> u32 {
|
|
||||||
self.modified
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the file type for this metadata.
|
|
||||||
pub fn file_type(&self) -> FileType {
|
|
||||||
self.file_type
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if this metadata is for a directory. The
|
|
||||||
/// result is mutually exclusive to the result of
|
|
||||||
/// [`Metadata::is_file`], and will be false for symlink metadata
|
|
||||||
/// obtained from [`symlink_metadata`].
|
|
||||||
pub fn is_dir(&self) -> bool {
|
|
||||||
self.file_type.is_dir()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if this metadata is for a regular file. The
|
|
||||||
/// result is mutually exclusive to the result of
|
|
||||||
/// [`Metadata::is_dir`], and will be false for symlink metadata
|
|
||||||
/// obtained from [`symlink_metadata`].
|
|
||||||
///
|
|
||||||
/// When the goal is simply to read from (or write to) the source, the most0.227s
|
|
||||||
/// reliable way to test the source can be read (or written to) is to open
|
|
||||||
/// it. Only using `is_file` can break workflows like `diff <( prog_a )` on
|
|
||||||
/// a Unix-like system for example. See [`File::open`] or
|
|
||||||
/// [`OpenOptions::open`] for more information.
|
|
||||||
pub fn is_file(&self) -> bool {
|
|
||||||
self.file_type.is_file()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if this metadata is for a symbolic link.
|
|
||||||
pub fn is_symlink(&self) -> bool {
|
|
||||||
self.file_type.is_symlink()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the size of the file, in bytes, this metadata is for.
|
|
||||||
pub fn len(&self) -> u64 {
|
|
||||||
self.length
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the permissions of the file this metadata is for.
|
|
||||||
pub fn permissions(&self) -> Permissions {
|
|
||||||
self.permissions
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the inode number.
|
|
||||||
pub fn ino(&self) -> u64 {
|
|
||||||
self.ino
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the rights applied to this file.
|
|
||||||
pub fn mode(&self) -> u32 {
|
|
||||||
self.permissions.0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the number of hard links pointing to this file.
|
|
||||||
pub fn nlink(&self) -> u64 {
|
|
||||||
self.nlink
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the user ID of the owner of this file.
|
|
||||||
pub fn uid(&self) -> u32 {
|
|
||||||
self.uid
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the group ID of the owner of this file.
|
|
||||||
pub fn gid(&self) -> u32 {
|
|
||||||
self.gid
|
|
||||||
}
|
|
||||||
|
|
||||||
// /// Returns the block size for filesystem I/O.
|
|
||||||
// pub fn blksize(&self) -> u64 {
|
|
||||||
// self.blksize
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// Returns the number of blocks allocated to the file, in 512-byte units.
|
|
||||||
///
|
|
||||||
/// Please note that this may be smaller than `st_size / 512` when the file has holes.
|
|
||||||
pub fn blocks(&self) -> u64 {
|
|
||||||
self.blocks
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,159 +0,0 @@
|
|||||||
use super::{block_reader::BlockReader, Ext2};
|
|
||||||
use binread::prelude::*;
|
|
||||||
use itertools::Itertools;
|
|
||||||
use std::dbg;
|
|
||||||
use std::io::{self, Read, Seek, SeekFrom};
|
|
||||||
use std::vec::Vec;
|
|
||||||
|
|
||||||
use std::fs::File as StdFile;
|
|
||||||
|
|
||||||
#[repr(u16)]
|
|
||||||
#[derive(Debug, BinRead)]
|
|
||||||
#[br(repr=u16)]
|
|
||||||
pub enum FSState {
|
|
||||||
Clean = 1,
|
|
||||||
HasErrors = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u16)]
|
|
||||||
#[derive(Debug, BinRead)]
|
|
||||||
#[br(repr=u16)]
|
|
||||||
pub enum ErrorHandlingMethod {
|
|
||||||
Ignore = 1,
|
|
||||||
RemountRO = 2,
|
|
||||||
Panic = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, BinRead)]
|
|
||||||
#[allow(unused)]
|
|
||||||
pub struct Superblock {
|
|
||||||
pub total_inodes: u32,
|
|
||||||
pub total_blocks: u32,
|
|
||||||
pub superuser_blocks: u32,
|
|
||||||
pub unallocated_blocks: u32,
|
|
||||||
pub unallocated_inodes: u32,
|
|
||||||
pub superblock_block: u32,
|
|
||||||
pub block_size_raw: u32,
|
|
||||||
pub fragment_size_raw: u32,
|
|
||||||
pub block_group_block_count: u32,
|
|
||||||
pub block_group_fragment_count: u32,
|
|
||||||
pub block_group_inode_count: u32,
|
|
||||||
pub last_mount_time: u32,
|
|
||||||
pub last_written_time: u32,
|
|
||||||
pub times_mounted_since_last_check: u16,
|
|
||||||
pub max_mounts_before_check: u16,
|
|
||||||
pub ext2_sig: u16,
|
|
||||||
pub fs_state: FSState,
|
|
||||||
pub error_handling_method: ErrorHandlingMethod,
|
|
||||||
pub version_minor: u16,
|
|
||||||
pub last_check_time: u32,
|
|
||||||
pub max_interval_between_check: u32,
|
|
||||||
pub creation_os_id: u32,
|
|
||||||
pub version_major: u32,
|
|
||||||
pub reserved_uid: u16,
|
|
||||||
pub reserved_gid: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Superblock {
|
|
||||||
pub fn read_from_disk(disk: &mut StdFile) -> io::Result<Self> {
|
|
||||||
disk.seek(SeekFrom::Start(1024))?;
|
|
||||||
Ok(disk.read_le().unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, BinRead)]
|
|
||||||
#[allow(unused)]
|
|
||||||
pub struct BlockGroupDescriptor {
|
|
||||||
pub block_usage_block: u32,
|
|
||||||
pub inode_usage_block: u32,
|
|
||||||
pub inode_table_start: u32,
|
|
||||||
pub unallocated_blocks: u16,
|
|
||||||
pub unallocated_inodes: u16,
|
|
||||||
pub num_directories: u16,
|
|
||||||
pub reserved: [u8; 14],
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, BinRead)]
|
|
||||||
#[allow(unused)]
|
|
||||||
pub struct DirEntryDisk {
|
|
||||||
pub inode: u32,
|
|
||||||
pub entry_size: u16,
|
|
||||||
pub name_len: u8,
|
|
||||||
pub high_len_or_type: u8,
|
|
||||||
#[br(count = name_len)]
|
|
||||||
#[br(pad_after(i64::from(entry_size - u16::from(name_len) - 8)))]
|
|
||||||
pub name: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, BinRead)]
|
|
||||||
#[br(import(number: u32))]
|
|
||||||
#[allow(unused)]
|
|
||||||
pub struct Inode {
|
|
||||||
pub type_perms: u16,
|
|
||||||
pub uid: u16,
|
|
||||||
pub size_lower32: u32,
|
|
||||||
pub last_access_time: u32,
|
|
||||||
pub creation_time: u32,
|
|
||||||
pub last_modification_time: u32,
|
|
||||||
pub deletion_time: u32,
|
|
||||||
pub gid: u16,
|
|
||||||
pub hard_links_count: u16,
|
|
||||||
pub sectors_used: u32,
|
|
||||||
pub flags: u32,
|
|
||||||
pub os_specific_1: u32,
|
|
||||||
pub direct_block_pointers: [u32; 12],
|
|
||||||
pub singly_indirect_block_pointer: u32,
|
|
||||||
pub doubly_indirect_block_pointer: u32,
|
|
||||||
pub triply_indirect_block_pointer: u32,
|
|
||||||
pub generation_number: u32,
|
|
||||||
pub file_acl: u32,
|
|
||||||
pub file_size_upper32_dir_acl: u32,
|
|
||||||
pub fragment_block_address: u32,
|
|
||||||
pub os_specific_2: [u8; 12],
|
|
||||||
#[br(calc = number)]
|
|
||||||
pub number: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Inode {
|
|
||||||
pub(super) fn read(fs: &Ext2, inode_num: u32) -> io::Result<Self> {
|
|
||||||
// dbg!();
|
|
||||||
let ino = fs.descriptor_table.inode_loc(inode_num).read(&fs.reader);
|
|
||||||
// dbg!();
|
|
||||||
ino
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn pointer_block_blocks(
|
|
||||||
&self,
|
|
||||||
block: u32,
|
|
||||||
reader: &mut BlockReader,
|
|
||||||
) -> io::Result<impl Iterator<Item = Result<u32, io::Error>>> {
|
|
||||||
Ok(reader
|
|
||||||
.read_block(block)?
|
|
||||||
.bytes()
|
|
||||||
.tuples::<(_, _, _, _)>()
|
|
||||||
.map(|raw| {
|
|
||||||
if let (Ok(b1), Ok(b2), Ok(b3), Ok(b4)) = raw {
|
|
||||||
Ok(u32::from_le_bytes([b1, b2, b3, b4]))
|
|
||||||
} else {
|
|
||||||
Err([raw.0, raw.1, raw.2, raw.3]
|
|
||||||
.into_iter()
|
|
||||||
.find_map(Result::err)
|
|
||||||
.expect("Not all bytes were Ok, yet all are not Err"))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(|x| if let &Ok(x) = x { x != 0 } else { true }))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn log_to_phys_block(&self, block: u32) -> Option<u32> {
|
|
||||||
if block < 12 {
|
|
||||||
if self.direct_block_pointers[block as usize] == 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(self.direct_block_pointers[block as usize])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
59
src/main.rs
59
src/main.rs
@ -1,59 +1,18 @@
|
|||||||
#![no_std]
|
|
||||||
#![no_main]
|
|
||||||
#![feature(int_roundings)]
|
#![feature(int_roundings)]
|
||||||
#![deny(unsafe_op_in_unsafe_fn)]
|
#![deny(unsafe_op_in_unsafe_fn)]
|
||||||
|
|
||||||
use ext2::Ext2;
|
use std::os::mikros::loader::Loader;
|
||||||
use std::fmt::Debug;
|
use std::os::mikros::syscalls::{get_initrd, new_process};
|
||||||
use std::fs::File;
|
|
||||||
use std::loader::Loader;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::prelude::*;
|
|
||||||
use std::syscalls::{get_initrd, new_process};
|
|
||||||
use tar_no_std::TarArchiveRef;
|
use tar_no_std::TarArchiveRef;
|
||||||
|
|
||||||
mod ata;
|
fn main() {
|
||||||
mod ext2;
|
let initrd = TarArchiveRef::new(get_initrd()).unwrap();
|
||||||
|
let vfs =
|
||||||
main!({
|
|
||||||
dbg!();
|
|
||||||
// let primary_slave = PRIMARY_SLAVE.unwrap();
|
|
||||||
let initrd = TarArchiveRef::new(get_initrd());
|
|
||||||
dbg!();
|
|
||||||
let test_proc =
|
|
||||||
initrd
|
initrd
|
||||||
.entries()
|
.entries()
|
||||||
.find(|entry| entry.filename() == *"bin/test_proc")
|
.find(|entry| entry.filename().as_str().unwrap() == "bin/vfs")
|
||||||
.expect("test_proc not found")
|
.expect("vfs not found")
|
||||||
.data();
|
.data();
|
||||||
dbg!();
|
let (space, entry) = Loader::load(&vfs);
|
||||||
let (space, entry) = Loader::load(&test_proc);
|
new_process(entry as _, space).unwrap();
|
||||||
dbg!();
|
|
||||||
let pid = new_process(entry as _, space).expect("Failed to create process");
|
|
||||||
dbg!();
|
|
||||||
let client = dev_driver_rpc::Client::new(pid);
|
|
||||||
dbg!();
|
|
||||||
let fd = client.open("sdb").unwrap();
|
|
||||||
dbg!();
|
|
||||||
let sdb = File::from_pid_fd(pid, fd);
|
|
||||||
dbg!();
|
|
||||||
let fs = Ext2::new(sdb).unwrap();
|
|
||||||
dbg!();
|
|
||||||
print_all_files("/", &fs);
|
|
||||||
dbg!();
|
|
||||||
});
|
|
||||||
|
|
||||||
fn print_all_files<P: AsRef<Path> + Debug>(path: P, fs: &Ext2) {
|
|
||||||
for entry in fs.read_dir(path).unwrap() {
|
|
||||||
let entry = entry.unwrap();
|
|
||||||
println!("{:?}", entry.path());
|
|
||||||
// if entry.metadata().file_type().is_dir() {
|
|
||||||
// // print_all_files(entry.path(), fs);
|
|
||||||
// } else {
|
|
||||||
// // let mut file = fs.open(entry.path()).unwrap();
|
|
||||||
// // let mut buf = String::new();
|
|
||||||
// // file.read_to_string(&mut buf).unwrap();
|
|
||||||
// // print!("{:?}:\n{}", entry.path(), buf);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user