diff --git a/Cargo.lock b/Cargo.lock index 75516233810..43ed0248290 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -185,15 +185,6 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" -[[package]] -name = "arc-swap" -version = "1.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" -dependencies = [ - "rustversion", -] - [[package]] name = "argon2" version = "0.5.3" @@ -391,17 +382,6 @@ dependencies = [ "uefi-services", ] -[[package]] -name = "bstr" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" -dependencies = [ - "memchr", - "regex-automata", - "serde_core", -] - [[package]] name = "bumpalo" version = "3.20.2" @@ -420,12 +400,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - [[package]] name = "byteorder_slice" version = "3.0.0" @@ -595,15 +569,6 @@ dependencies = [ "inout 0.2.2", ] -[[package]] -name = "clru" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" -dependencies = [ - "hashbrown 0.16.1", -] - [[package]] name = "cmov" version = "0.5.3" @@ -708,12 +673,6 @@ dependencies = [ "libc", ] -[[package]] -name = "core_detect" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" - [[package]] name = "coreaudio-rs" version = "0.14.2" @@ -807,15 +766,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.23" @@ -961,20 +911,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "dashmap" -version = "6.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - [[package]] name = "dasp_sample" version = "0.11.0" @@ -1175,12 +1111,6 @@ name = "dpi" version = "0.1.1" source = "git+https://github.com/ToyOSOrg/winit?branch=toyos-0.30.13#f58e1f3bca784d97056fe47475c2dca8c6386816" -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - [[package]] name = "ecdsa" version = "0.16.9" @@ -1313,20 +1243,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "encoding_rs" -version = "0.8.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" -dependencies = [ - "cfg-if", - "core_detect", - "multiversion_no_op", - "rustversion", - "scopeguard", - "simdutf8", -] - [[package]] name = "enum_dispatch" version = "0.3.13" @@ -1373,18 +1289,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "faster-hex" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04839bdf9d8c10f66806fad16b852fc72aab80873aebc3cb69d85b4fa41543ed" -dependencies = [ - "autocfg", - "defmt 0.3.100", - "heapless", - "serde", -] - [[package]] name = "fastrand" version = "2.5.0" @@ -1507,24 +1411,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - [[package]] name = "fontdue" version = "0.9.3" @@ -1614,944 +1506,135 @@ checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" name = "futures-macro" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "generator" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" -dependencies = [ - "cc", - "cfg-if", - "libc", - "log", - "rustversion", - "windows-link", - "windows-result", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", - "zeroize", -] - -[[package]] -name = "generic-array" -version = "1.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf57c49a95fd1fe24b90b3033bee6dc7e8f1288d51494cb44e627c295e38542" -dependencies = [ - "generic-array 0.14.7", - "rustversion", - "typenum", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "git+https://github.com/ToyOSOrg/getrandom?branch=toyos-0.2-sdk-0.12#2092d1ccd2833f00559ed27850c576ab37fe9347" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "toyos-abi", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "git+https://github.com/ToyOSOrg/getrandom?branch=toyos-0.3-sdk-0.12#67c17e0a7cd6d29e8ce231073746970cbd7dade7" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "toyos-abi", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.2" -source = "git+https://github.com/ToyOSOrg/getrandom?branch=toyos-0.4#f0286c8b3f0cd6d9fa9124ee837fa404e66313a7" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", - "rand_core 0.10.0", - "toyos-abi", - "wasip2", - "wasip3", -] - -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval 0.6.2", -] - -[[package]] -name = "ghash" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" -dependencies = [ - "polyval 0.7.1", -] - -[[package]] -name = "gix" -version = "0.88.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d6dd6028308ad2f2662207506e396b97f9ec899f74bd537c0775248c774f21" -dependencies = [ - "gix-actor", - "gix-attributes", - "gix-command", - "gix-commitgraph", - "gix-config", - "gix-date", - "gix-diff", - "gix-dir", - "gix-discover", - "gix-error", - "gix-features", - "gix-filter", - "gix-fs", - "gix-glob", - "gix-hash", - "gix-hashtable", - "gix-ignore", - "gix-index", - "gix-lock", - "gix-note", - "gix-object", - "gix-odb", - "gix-pack", - "gix-path", - "gix-pathspec", - "gix-protocol", - "gix-quote", - "gix-ref", - "gix-refspec", - "gix-revision", - "gix-revwalk", - "gix-sec", - "gix-shallow", - "gix-status", - "gix-submodule", - "gix-tempfile", - "gix-trace", - "gix-traverse", - "gix-url", - "gix-utils", - "gix-validate", - "gix-worktree", - "gix-worktree-stream", - "gix-zlib", - "nonempty", - "smallvec", -] - -[[package]] -name = "gix-actor" -version = "0.43.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e046e9a929e8e1f40f9a34f736408c4c079f9cf004429f3e718d270e847be96" -dependencies = [ - "bstr", - "gix-date", - "gix-error", -] - -[[package]] -name = "gix-attributes" -version = "0.36.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d57bcf18768dc259868e335fae99a9063174178b78b00348603f0791cd780a" -dependencies = [ - "bstr", - "gix-error", - "gix-features", - "gix-glob", - "gix-path", - "gix-quote", - "gix-trace", - "smallvec", - "unicode-bom", -] - -[[package]] -name = "gix-bitmap" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e9d28a8333a322df5c4fdc7b86a9928965e50794523c0edff57d1fbf2a520cf" -dependencies = [ - "gix-error", -] - -[[package]] -name = "gix-chunk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25303d07ce2b8f98aab48ce4251317d785a3208b7ed65698928e4a2f31889936" -dependencies = [ - "gix-error", -] - -[[package]] -name = "gix-command" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3c2e61f97adc08764060e9becb0c819d8f2ba27896434710e039ae57b1d2a7" -dependencies = [ - "bstr", - "gix-error", - "gix-path", - "gix-quote", - "gix-trace", -] - -[[package]] -name = "gix-commitgraph" -version = "0.40.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0462cbffd5828447f376559d397d9b29f1979dac3d980b5212b17fa174b174eb" -dependencies = [ - "bstr", - "gix-chunk", - "gix-error", - "gix-hash", - "memmap2", - "nonempty", -] - -[[package]] -name = "gix-config" -version = "0.61.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2cb454dce2f1895cf9def1df8b0a6bbc579d86d8313bdf60f1ab429d52df12" -dependencies = [ - "bstr", - "gix-config-value", - "gix-error", - "gix-features", - "gix-glob", - "gix-path", - "gix-ref", - "gix-sec", - "gix-utils", - "smallvec", - "unicode-bom", -] - -[[package]] -name = "gix-config-value" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1894e816c774650b5157d965853b50d7e8f7137ea74055a6bac2f2ec051cf997" -dependencies = [ - "bitflags 2.11.0", - "bstr", - "gix-error", - "gix-path", - "libc", -] - -[[package]] -name = "gix-date" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb8beceb16a77fb222923592864b94beb4ad4c2b175a7055c2cac6815ee0370a" -dependencies = [ - "bstr", - "gix-error", - "itoa", - "jiff", -] - -[[package]] -name = "gix-diff" -version = "0.68.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "939aee7bbc9f233de858839e865c82886e0baceb8f1fe792ea1703fa895f09e1" -dependencies = [ - "bstr", - "gix-attributes", - "gix-command", - "gix-error", - "gix-filter", - "gix-fs", - "gix-hash", - "gix-imara-diff", - "gix-index", - "gix-object", - "gix-path", - "gix-pathspec", - "gix-tempfile", - "gix-trace", - "gix-traverse", - "gix-worktree", -] - -[[package]] -name = "gix-dir" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e41d125c2c6f981aad27299a6233a7ace3413289c1e2994f57e5bab1c34847" -dependencies = [ - "bstr", - "gix-discover", - "gix-error", - "gix-fs", - "gix-ignore", - "gix-index", - "gix-object", - "gix-path", - "gix-pathspec", - "gix-trace", - "gix-utils", - "gix-worktree", -] - -[[package]] -name = "gix-discover" -version = "0.56.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4476ba055ec5fbb05f59dd98380b596428da060beac5f9dccd7374302ad1fbfb" -dependencies = [ - "bstr", - "dunce", - "gix-error", - "gix-fs", - "gix-path", - "gix-ref", - "gix-sec", -] - -[[package]] -name = "gix-error" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4a5c4cd326ddfe7f046ad9eed05461c59af6ea98b681648bac73954317addd" -dependencies = [ - "bstr", -] - -[[package]] -name = "gix-features" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb3a430dbc02d6c5e30a9f92083459593495ea22a476e837d248b22d800eed80" -dependencies = [ - "bytes", - "crc32fast", - "crossbeam-channel", - "gix-path", - "gix-trace", - "gix-utils", - "libc", - "once_cell", - "parking_lot", - "prodash", - "walkdir", -] - -[[package]] -name = "gix-filter" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f31a39e2c9b04843709d1f72b2c39c98dc0ee591e2f67d21302dbfbd06d2956" -dependencies = [ - "bstr", - "encoding_rs", - "gix-attributes", - "gix-command", - "gix-error", - "gix-hash", - "gix-object", - "gix-packetline", - "gix-path", - "gix-quote", - "gix-trace", - "gix-utils", - "smallvec", -] - -[[package]] -name = "gix-fs" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0105ab1cef4542d092c27766d2daf1548fcbad7e460dc2208642c120bb19b3ad" -dependencies = [ - "bstr", - "gix-error", - "gix-features", - "gix-path", - "gix-utils", -] - -[[package]] -name = "gix-glob" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bba50803dea16646cafb9a2d67e95b8cdf5b21c7e8c45bcd18f38bafe02fb81f" -dependencies = [ - "bitflags 2.11.0", - "bstr", - "gix-features", - "gix-path", - "gix-utils", -] - -[[package]] -name = "gix-hash" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a22b6febaaee8546eb0c9dd21ecdd93aca269a6cb3897e1250f0f60933c0174" -dependencies = [ - "faster-hex", - "gix-error", - "gix-features", - "sha1dc", -] - -[[package]] -name = "gix-hashtable" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55bff2f32f6f5a95bdd43e5e5e01b0656250cffbc48eda19db0afa3d73adf2f" -dependencies = [ - "gix-hash", - "hashbrown 0.17.1", - "parking_lot", -] - -[[package]] -name = "gix-ignore" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bfd910e271ecd1a20e4df4009a0d9d844a0c51ee5444aaa3fadd1b1b077930b" -dependencies = [ - "bstr", - "gix-glob", - "gix-path", - "gix-trace", - "unicode-bom", -] - -[[package]] -name = "gix-imara-diff" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305657ad44356151d73c145f567c25b24fdf2466bfb3e90727e1d5461a4e9748" -dependencies = [ - "bstr", - "hashbrown 0.16.1", -] - -[[package]] -name = "gix-index" -version = "0.56.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfd5b484b5b4e218a5ac114f0982949f9bd7582ff67986665a0cf85895332433" -dependencies = [ - "bitflags 2.11.0", - "bstr", - "filetime", - "fnv", - "gix-bitmap", - "gix-error", - "gix-features", - "gix-fs", - "gix-hash", - "gix-lock", - "gix-object", - "gix-traverse", - "gix-utils", - "gix-validate", - "hashbrown 0.17.1", - "itoa", - "libc", - "memmap2", - "rustix 1.1.5", - "smallvec", -] - -[[package]] -name = "gix-lock" -version = "25.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd5ddfd0cc476fef744ac544783b96a9ff5d49f8de541e6e9d6d6002aa90720" -dependencies = [ - "gix-error", - "gix-tempfile", - "gix-utils", -] - -[[package]] -name = "gix-macros" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f11f5429ca73cc3758c9f53af56a9fba100a8701c5b4bc30e29b8b0b15288991" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "gix-note" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6879cc9e5a9a0429e40b05970af6c8dad8734098a3142fec23e32eeb567580" -dependencies = [ - "gix-error", - "gix-hash", - "gix-object", -] - -[[package]] -name = "gix-object" -version = "0.65.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a72909eeeed845721a143ef3dbeb4d6cd5df79c839fe7eefc138cb4f982eba3e" -dependencies = [ - "bstr", - "gix-actor", - "gix-command", - "gix-date", - "gix-error", - "gix-features", - "gix-hash", - "gix-hashtable", - "gix-path", - "gix-tempfile", - "gix-utils", - "gix-validate", - "itoa", - "smallvec", -] - -[[package]] -name = "gix-odb" -version = "0.85.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5794b85ff563a290ea18867fa1f0a5b0b84709f2f34dddbb3614556fbe88c629" -dependencies = [ - "arc-swap", - "gix-error", - "gix-features", - "gix-fs", - "gix-hash", - "gix-hashtable", - "gix-object", - "gix-pack", - "gix-path", - "gix-quote", - "gix-zlib", - "memmap2", - "parking_lot", - "tempfile", -] - -[[package]] -name = "gix-pack" -version = "0.75.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0451a48faef8e0e7a8f6d903c8329c7d18a793b3030907ca841f889dc695d1df" -dependencies = [ - "clru", - "gix-chunk", - "gix-error", - "gix-features", - "gix-hash", - "gix-hashtable", - "gix-object", - "gix-path", - "gix-zlib", - "memmap2", - "smallvec", -] - -[[package]] -name = "gix-packetline" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ba540112a3fcc6388eb2aa4dc4837040478c285a35178cd9a44b07345cd6636" -dependencies = [ - "bstr", - "faster-hex", - "gix-error", - "gix-trace", -] - -[[package]] -name = "gix-path" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd051ac60c5dbce2228daabf25b6a76408ac732d416c678cfa0a4914d13f60b8" -dependencies = [ - "bstr", - "gix-error", - "gix-trace", - "gix-validate", -] - -[[package]] -name = "gix-pathspec" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee883ab64bad641bf31ff338044edd7aa010605f19e866d98c9164e635c38b1" -dependencies = [ - "bitflags 2.11.0", - "bstr", - "gix-attributes", - "gix-config-value", - "gix-error", - "gix-glob", - "gix-path", -] - -[[package]] -name = "gix-protocol" -version = "0.66.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331ac489c461790b6b692afb322085c42041c6ff7621c30ac3dbb00b12e05e89" -dependencies = [ - "bstr", - "gix-date", - "gix-error", - "gix-features", - "gix-hash", - "gix-macros", - "gix-ref", - "gix-shallow", - "gix-transport", - "gix-utils", - "nonempty", -] - -[[package]] -name = "gix-quote" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b040cfa7af50f131bfa34119cceee804c3cc40c766daaba8e0c8149340aea7" -dependencies = [ - "bstr", - "gix-error", - "gix-utils", -] - -[[package]] -name = "gix-ref" -version = "0.68.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c90ceeec67de03b737c30b1b69116ca56fec803edde022f3d25181f0f597083d" -dependencies = [ - "gix-actor", - "gix-error", - "gix-features", - "gix-fs", - "gix-hash", - "gix-lock", - "gix-object", - "gix-path", - "gix-tempfile", - "gix-utils", - "gix-validate", - "memmap2", -] - -[[package]] -name = "gix-refspec" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4caa89a034a055c0578150092d3edb2f1c3f74cca86b883263a28caa26c5812" -dependencies = [ - "bstr", - "gix-error", - "gix-hash", - "gix-validate", - "smallvec", -] - -[[package]] -name = "gix-revision" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8438d38e197fa3ea7bb297302db1701879a64a343f23a3d150a602a609a8751" -dependencies = [ - "bstr", - "gix-commitgraph", - "gix-date", - "gix-error", - "gix-hash", - "gix-object", - "gix-revwalk", - "nonempty", -] - -[[package]] -name = "gix-revwalk" -version = "0.36.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fa6d913e7cf8c6407d3954f9794b4b148c2113c672c34b63c59ea654a1e060e" -dependencies = [ - "gix-commitgraph", - "gix-date", - "gix-error", - "gix-hash", - "gix-hashtable", - "gix-object", - "smallvec", -] - -[[package]] -name = "gix-sec" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d8eb983a830ba586b61f7005deaf44609e1a4186cbf13a0e8668154ca910461" -dependencies = [ - "bitflags 2.11.0", - "gix-path", - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "gix-shallow" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "119bae1301715df40e8fd136deeff4fe8a3be4cfc001ed5e72bad7b47ebbc090" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ - "bstr", - "gix-error", - "gix-hash", - "gix-lock", - "nonempty", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "gix-status" -version = "0.35.0" +name = "futures-sink" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb7e035b6d9dce932f6b6c8d33d29b31b46e05da544bf810e324bf61aaa5b84" -dependencies = [ - "bstr", - "filetime", - "gix-diff", - "gix-dir", - "gix-error", - "gix-features", - "gix-filter", - "gix-fs", - "gix-hash", - "gix-index", - "gix-object", - "gix-path", - "gix-pathspec", - "gix-worktree", - "hashbrown 0.16.1", - "portable-atomic", - "windows-sys 0.61.2", -] +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] -name = "gix-submodule" -version = "0.35.0" +name = "futures-task" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b498192e5f89453fbd728263d52088aeec634a390e732f1da6d0a491c552091" -dependencies = [ - "bstr", - "gix-config", - "gix-error", - "gix-path", - "gix-pathspec", - "gix-refspec", - "gix-url", -] +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] -name = "gix-tempfile" -version = "25.0.0" +name = "futures-util" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dd7dd8df3345933caddd6ce67821c9eec17192564f1011cf0bd0b8cb0d07ffb" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ - "dashmap", - "gix-fs", - "libc", - "parking_lot", - "tempfile", - "windows-sys 0.61.2", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", ] [[package]] -name = "gix-trace" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df45b2cf6052d9e52681130c17d67505b023e8933ad974f94a2be0b0d24f636a" - -[[package]] -name = "gix-transport" -version = "0.60.0" +name = "generator" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d2beb7b0387bbf4d7c13763c6e168f9957319aa811f09349dc59601be3b2951" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" dependencies = [ - "bstr", - "gix-command", - "gix-error", - "gix-features", - "gix-packetline", - "gix-path", - "gix-quote", - "gix-sec", - "gix-url", + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result", ] [[package]] -name = "gix-traverse" -version = "0.62.0" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4864934e4efd92082ecc37107bce0c389b58d82888db634817f9c711efb7ed" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "bitflags 2.11.0", - "gix-commitgraph", - "gix-date", - "gix-error", - "gix-hash", - "gix-hashtable", - "gix-object", - "gix-revwalk", - "smallvec", + "typenum", + "version_check", + "zeroize", ] [[package]] -name = "gix-url" -version = "0.39.0" +name = "generic-array" +version = "1.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f30fe2a228d7de5f9d582463d9b724debcb870162d9fbe908cd5d76ccdecf0d" +checksum = "eaf57c49a95fd1fe24b90b3033bee6dc7e8f1288d51494cb44e627c295e38542" dependencies = [ - "bstr", - "gix-error", - "gix-path", - "gix-utils", - "percent-encoding", + "generic-array 0.14.7", + "rustversion", + "typenum", ] [[package]] -name = "gix-utils" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70525766f53426ca4cd2ea4d58b0b31488dfedb416b3254e2efdd9dc184cc880" +name = "getrandom" +version = "0.2.17" +source = "git+https://github.com/ToyOSOrg/getrandom?branch=toyos-0.2-sdk-0.12#2092d1ccd2833f00559ed27850c576ab37fe9347" dependencies = [ - "bstr", - "fastrand", - "getrandom 0.4.2", - "unicode-normalization", + "cfg-if", + "js-sys", + "libc", + "toyos-abi", + "wasi", + "wasm-bindgen", ] [[package]] -name = "gix-validate" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30574f81fc41b2ca3582ece2bdf8696bdfe9c170aa7e8bcafb81199c86b1ceea" +name = "getrandom" +version = "0.3.4" +source = "git+https://github.com/ToyOSOrg/getrandom?branch=toyos-0.3-sdk-0.12#67c17e0a7cd6d29e8ce231073746970cbd7dade7" dependencies = [ - "bstr", - "gix-error", + "cfg-if", + "libc", + "r-efi 5.3.0", + "toyos-abi", + "wasip2", ] [[package]] -name = "gix-worktree" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04fa19b9b3961aafb3b9e60005b9bcef0100f8b211f46ab5269a6ac896073780" +name = "getrandom" +version = "0.4.2" +source = "git+https://github.com/ToyOSOrg/getrandom?branch=toyos-0.4#f0286c8b3f0cd6d9fa9124ee837fa404e66313a7" dependencies = [ - "bstr", - "gix-attributes", - "gix-error", - "gix-fs", - "gix-glob", - "gix-hash", - "gix-ignore", - "gix-index", - "gix-object", - "gix-path", - "gix-validate", + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.0", + "toyos-abi", + "wasip2", + "wasip3", ] [[package]] -name = "gix-worktree-stream" -version = "0.37.0" +name = "ghash" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2021e1609beae0edbeefa76ac641be0571b725afacb77ac5b12f33b436e0d3d9" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" dependencies = [ - "gix-attributes", - "gix-error", - "gix-features", - "gix-filter", - "gix-fs", - "gix-hash", - "gix-object", - "gix-path", - "gix-traverse", - "parking_lot", + "opaque-debug", + "polyval 0.6.2", ] [[package]] -name = "gix-zlib" -version = "0.2.0" +name = "ghash" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44a1a9c82ec5b712082566cbef8f5782472a0d3973132911e5a83380a5298fe2" +checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" dependencies = [ - "gix-error", - "zlib-rs", + "polyval 0.7.1", ] [[package]] @@ -2586,12 +1669,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - [[package]] name = "hashbrown" version = "0.15.5" @@ -2600,7 +1677,7 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash 0.1.5", + "foldhash", ] [[package]] @@ -2608,22 +1685,6 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.2.0", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.2.0", -] [[package]] name = "heapless" @@ -2757,20 +1818,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" -[[package]] -name = "image" -version = "0.25.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" -dependencies = [ - "bytemuck", - "byteorder-lite", - "moxcms", - "num-traits", - "zune-core", - "zune-jpeg", -] - [[package]] name = "imagesize" version = "0.14.0" @@ -2865,60 +1912,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" -[[package]] -name = "jiff" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb" -dependencies = [ - "defmt 1.1.1", - "jiff-core", - "jiff-static", - "jiff-tzdb-platform", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", - "windows-link", -] - -[[package]] -name = "jiff-core" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" -dependencies = [ - "defmt 1.1.1", - "log", -] - -[[package]] -name = "jiff-static" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212" -dependencies = [ - "jiff-core", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "jiff-tzdb" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" - -[[package]] -name = "jiff-tzdb-platform" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" -dependencies = [ - "jiff-tzdb", -] - [[package]] name = "jni" version = "0.21.1" @@ -3158,15 +2151,6 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - [[package]] name = "log" version = "0.4.29" @@ -3312,22 +2296,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "moxcms" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" -dependencies = [ - "num-traits", - "pxfm", -] - -[[package]] -name = "multiversion_no_op" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" - [[package]] name = "ndk" version = "0.9.0" @@ -3386,12 +2354,6 @@ dependencies = [ "libc", ] -[[package]] -name = "nonempty" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9737e026353e5cd0736f98eddae28665118eb6f6600902a7f50db585621fecb6" - [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -3936,29 +2898,6 @@ dependencies = [ "toyos-window", ] -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.18", - "smallvec", - "windows-link", -] - [[package]] name = "password-hash" version = "0.5.0" @@ -4217,21 +3156,6 @@ dependencies = [ "universal-hash 0.6.1", ] -[[package]] -name = "portable-atomic" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" - -[[package]] -name = "portable-atomic-util" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" -dependencies = [ - "portable-atomic", -] - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -4314,15 +3238,6 @@ dependencies = [ name = "proctest" version = "0.1.0" -[[package]] -name = "prodash" -version = "31.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "962200e2d7d551451297d9fdce85138374019ada198e30ea9ede38034e27604c" -dependencies = [ - "parking_lot", -] - [[package]] name = "ptr_meta" version = "0.2.0" @@ -4343,12 +3258,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "pxfm" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" - [[package]] name = "quick-xml" version = "0.41.0" @@ -4915,12 +3824,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "scrypt" version = "0.12.0-rc.10" @@ -5051,12 +3954,6 @@ dependencies = [ "digest 0.11.1", ] -[[package]] -name = "sha1dc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fdf1713fb0547b120571e5f7b37dc2650dc03d2abf46a18928f54372000947f" - [[package]] name = "sha2" version = "0.10.9" @@ -5476,19 +4373,6 @@ dependencies = [ "xattr", ] -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.3.4", - "once_cell", - "rustix 1.1.5", - "windows-sys 0.61.2", -] - [[package]] name = "terminal" version = "0.1.0" @@ -5582,12 +4466,6 @@ dependencies = [ "strict-num", ] -[[package]] -name = "tinyvec" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" - [[package]] name = "tokio" version = "1.51.1" @@ -5758,11 +4636,10 @@ dependencies = [ "flate2", "fontdue", "getrandom 0.3.4", - "gix", "gpt", - "image", "kernel", "libc", + "libm", "serde", "serde_json", "sha2 0.10.9", @@ -5788,7 +4665,6 @@ dependencies = [ "toyos-wallclock", "toyos-xhci", "ureq", - "uuid", ] [[package]] @@ -6276,27 +5152,12 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab14ea9660d240e7865ce9d54ecdbd1cd9fa5802ae6f4512f093c7907e921533" -[[package]] -name = "unicode-bom" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" - [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-normalization" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-segmentation" version = "1.12.0" @@ -7204,18 +6065,3 @@ name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" - -[[package]] -name = "zune-core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" - -[[package]] -name = "zune-jpeg" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec5f41c76397b7da451efd19915684f727d7e1d516384ca6bd0ec43ec94de23c" -dependencies = [ - "zune-core", -] diff --git a/Cargo.toml b/Cargo.toml index 983504f7503..46dfe08246a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -155,12 +155,9 @@ license = "MIT OR Apache-2.0" default-run = "toyos-build" [dependencies] -fatfs = "0.3.6" fontdue = "0.9" -gpt = "3.1.0" libc = "0.2" toml = "0.8" -uuid = { version = "1", features = ["v4"] } serde = { version = "1", features = ["derive"] } serde_json = "1" bcachefs = { path = "bcachefs" } @@ -197,12 +194,15 @@ toyos-blackbox = { path = "toyos-blackbox" } # The signed image, the slot table and the signer: `src/signing.rs` signs with # the same crate the loader and `/system/bin/update` verify with. toyos-update = { path = "toyos-update", features = ["sign"] } -# A throwaway key's seed, from the operating system. Already resolved here. +# A throwaway key's seed and every GUID a partition table names, from the +# operating system. getrandom = "0.3" +# The wallpaper's `powf` and `expf`, in Rust and the same on every host, where +# `f32`'s own call the host's C library and ROOT's UUID is derived from its bytes. +libm = "0.2.16" # `classify`, so the host reading a log volume and `logkeeper` writing it cannot # disagree about which files on it are `logkeeper`'s. toyos-wallclock = { path = "toyos-wallclock" } -image = { version = "0.25", default-features = false, features = ["jpeg"] } # The digest behind `NOTICE`'s record of every committed binary file # (`src/sourcegate.rs`). sha2 = "0.10" @@ -215,11 +215,12 @@ flate2 = { version = "1", default-features = false, features = ["rust_backend"] # takes (`issues/the-internet-clients-work-unchanged.md`), whose C # and assembly the host's `cc` compiles. ureq = { version = "3", default-features = false, features = ["rustls"] } -# The commit an image is built from, its time and whether the tree is that -# commit's, where `git` is a host's C tool (`src/build.rs`'s `release`). -gix = { version = "0.88", default-features = false, features = ["status", "sha1"] } [dev-dependencies] +# The FAT implementation `src/fatformat.rs` is judged against, byte for byte. +fatfs = "0.3.6" +# The GPT writer `src/gptwrite.rs` is judged against, byte for byte. +gpt = "3.1.0" # `USER_TOP`, so the harness judges a held `rsp` against the bound the kernel # checks every user pointer against and not against a second copy of it. toyos-userbound = { path = "toyos-userbound" } diff --git a/README.md b/README.md index 53175ad69d6..9c64ba1416f 100644 --- a/README.md +++ b/README.md @@ -333,7 +333,7 @@ Licensed under either of at your option. Some of what the repository carries is not ours and is under other terms: -`userland/doom` is GPL-2.0, `assets/` holds a font, a set of icons, a wallpaper +`userland/doom` is GPL-2.0, `assets/` holds a font, a set of icons and id Software's Doom shareware IWAD, and `tests/testcases/` holds TinyCC's test corpus. Third-party crates keep their own upstream licenses. **[NOTICE](NOTICE) is the list**, item by item, diff --git a/assets/wallpaper.jpg b/assets/wallpaper.jpg deleted file mode 100644 index b0863088b14..00000000000 Binary files a/assets/wallpaper.jpg and /dev/null differ diff --git a/issues/the-build-runs-host-tools-outside-rust-and-qemu.md b/issues/the-build-runs-host-tools-outside-rust-and-qemu.md index d45c5a44edf..b37ef9fdbbc 100644 --- a/issues/the-build-runs-host-tools-outside-rust-and-qemu.md +++ b/issues/the-build-runs-host-tools-outside-rust-and-qemu.md @@ -19,6 +19,7 @@ arrives and is not one. M4 and M5 are stages of `issues/toyos-builds-itself.md`. | `sh` running LLVM's `config.guess`, and the POSIX tools and `cc` it runs | LLVM's CMake, whenever this host builds an LLVM, and the C++ runtime's, in every sysroot build, ask it the host's triple, unconditionally (`get_host_triple` in `rust/src/llvm-project/llvm/cmake/modules/GetHostTriple.cmake`, which runs `sh` by name) | refused: a Rust tool does the shell's part, brush 0.4.0: on the development host (macOS, arm64) `config.guess` printed `/bin/sh`'s triple under it, `arm64-apple-darwin27.0.0`, exit 0 each. The script runs `sed`, `uname`, `mktemp`, `grep`, `rm`, `rmdir` and `cc` there under either shell, and that `cc` is the `cc` rows'. Five of the other six are refused, uutils' doing each: under brush with sed 0.2.0, grep 0.2.0 and coreutils 0.12.0's `mktemp`, `rm` and `rmdir`, and nothing else on `PATH` but the host's `uname` and `cc`, it printed that triple, exit 0. `uname` is admitted: coreutils 0.12.0's answers `-p` with `unknown` where macOS's answers `arm`, and `config.guess` reads that as PowerPC, `powerpc-apple-darwin27.0.0`, exit 0 | CMake finds brush as its `sh`, uutils' `sed`, `grep`, `mktemp`, `rm` and `rmdir`, and a Rust `uname` that answers `-p` as the host's does; or M5 runs it in the guest | | `git` for worktrees, submodules, checkouts, fixtures and rustc's bootstrap | adds worktrees (`src/sysroot.rs`); updates submodules (`src/lib.rs`, `src/sysroot.rs`); fetches the fork from the primary's and checks it out (`src/sysroot.rs`); makes the tests' fixture repositories; runs inside rustc's bootstrap | admitted: no Rust tool does the job, gitoxide 0.85 adds, removes and prunes no worktree, updates no submodule, stages, resets and pushes nothing, checks out only a fresh clone and fetches a local path by spawning `git`; a fixture must be what `git` makes, and bootstrap runs `git` itself | M4 runs it in the guest | | `git` for reads, a config write, a commit's paths written out, and clones and fetches over HTTPS | `rev-parse`, `merge-base`, `ls-tree`, `ls-files`, `cat-file`, `config --get-regexp`, `config --blob`, `status`, `diff`, `ls-remote` and `grep`, in the build system and its tests; `config --global --add safe.directory` in CI's containers; `checkout -- ` through an index of its own, which writes the C++ runtime's sources out of the LLVM commit into the stored LLVM (`src/llvm.rs`); every workflow's checkout | refused: a Rust tool does it, gitoxide 0.85, which reads refs, objects, the index, config, worktrees and status, adds a value to a config file and writes it (gix-config 0.58's `File::section_mut_or_create_new`, `SectionMut::push`, `File::write_to`), walks history, diffs, and lists, fetches and clones a remote over HTTPS; `grep` is a search of the files its index names; and gitoxide's CLI 0.59 (gix 0.88) wrote the runtimes' sources of LLVM `849da7d6` into an empty directory, each path's tree through `gix rev parse`, `gix index from-tree` and `gix free index checkout-exclusive`, exit 0 each: the 18759 files `git` writes there, byte for byte and mode for mode | those are gitoxide's | +| `git log` and `git status` for os-release | the commit, its committer time and whether the tree is that commit's, which `src/build.rs`'s `release` writes into `/system/etc/os-release` in every image build | admitted: the owner's ruling on the dependency audit (2026-10-09), "Drop gix, use git", which covers these two reads and nothing else this table names: they were gitoxide's, gitoxide is no tool the build sheds while `git` stays for the worktree row, and for them it held 84 of the root `Cargo.lock`'s packages | M4 runs it in the guest | | `cc`, `c++` and `ar` on a Linux host, `build-essential` in CI's containers | rustc links every host binary through `cc`; `cc` and `c++` compile LLVM, clang, LLD and `rustc_llvm` (`src/llvm.rs` names both to bootstrap); `cc` compiles ring's C and assembly in every build of the build system itself, whose HTTP agent is rustls on ring (`src/release.rs`); `ar` archives what `cc::Build` compiles, ring's included | admitted: no Rust tool compiles C or C++, or takes rustc's host link; ring is the one TLS provider the tree takes (owner, 2026-10-02), over a provider in Rust alone | M5: no host in the loop | | the toolchain's own `clang`, `llvm-ar`, `rust-lld` and `llvm-config`, built from `ToyOSOrg/llvm-project` | rustc links every guest binary with `rust-lld`; `clang` compiles the C corpus (`tests/common/compile.rs`) and, with `llvm-ar`, doomgeneric through `cc::Build` (`src/clang.rs`); rustc's bootstrap asks `llvm-config` how to link LLVM | admitted: our fork's C++, which ToyOS can one day build and run; no Rust tool compiles C, `cc::Build` archives with an `ar`, bootstrap reads LLVM through `llvm-config`, and `CLAUDE.md` links everything with `rust-lld` | M5: no host in the loop | | `ovmf-generic`, `qemu-efi-aarch64` | the x86-64 and AArch64 UEFI firmware of CI's guest containers (`src/firmware.rs`), packaged by Debian apart from QEMU | admitted: QEMU's own firmware, and no Rust firmware does its job | the instrument's QEMU carries its own firmware | diff --git a/issues/the-tree-says-who-uses-each-thing.md b/issues/the-tree-says-who-uses-each-thing.md index 07e68c8d47b..9695fc2ee79 100644 --- a/issues/the-tree-says-who-uses-each-thing.md +++ b/issues/the-tree-says-who-uses-each-thing.md @@ -121,7 +121,7 @@ Cargo.toml Cargo.lock clippy.toml .cargo/config.toml rule step 1: on loader/ the UEFI loader (today bootloader/) kernel/ CLAUDE.md; src/ pure/ loom/ sim/ pci/ gicv3/ ps2/ dma/ cpuvuln/ microcode/ (+ intel-ucode, licence) system/ CLAUDE.md (the server doctrine); one directory per /system/bin name: - supervisor compositor (desktop/ sprite/ wallpaper.jpg) + supervisor compositor (desktop/ sprite/) netstack (mdns/ dns/ and the net family) soundserver (mixer/) console diskserver fileserver logkeeper filepicker pkg update swap sshserver shell terminal toybox inspect host apps/ CLAUDE.md (sdk/ and registries only; Linux under Wayland, macOS, Windows) diff --git a/src/assets.rs b/src/assets.rs index 55f3fb77201..b48b58c1616 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -242,16 +242,30 @@ fn absentees(dir: &Path, declared: &BTreeSet) -> Vec { declared.iter().map(|name| dir.join(name)).filter(|path| !path.exists()).collect() } -/// An asset on ROOT, and the one program that opens it. +/// A file of ROOT's `share/`, and the one program that opens it. /// /// **`assets = [..]` names a directory and sweeps it whole**, so a config that -/// builds no reader for a file still shipped it: these two are 19.7 MB of the -/// 20.8 MB `assets/` holds. Named here rather than per config, because which program -/// opens a file is a property of the program and not of any one image, and a -/// list repeated in five configs is a list that goes stale in four of them. -/// The names are ROOT's, which [`collect`] lower-cases. -/// `only_doom_opens_doom_s_assets` is what keeps the right-hand column true. -const OPENED_BY: &[(&str, &str)] = &[("doom1.wad", "doom"), ("soundfont.sf2", "doom")]; +/// builds no reader for a file would still ship it. Named here rather than per +/// config, because which program opens a file is a property of the program and +/// not of any one image, and a list repeated in five configs is a list that goes +/// stale in four of them. The names are ROOT's, which [`collect`] lower-cases; +/// `wallpaper.rgb` is the build's own drawing ([`crate::wallpaper`]) rather than +/// a swept file. `only_its_reader_opens_an_owned_file` is what keeps the +/// right-hand column true. +const OPENED_BY: &[(&str, &str)] = + &[("doom1.wad", "doom"), ("soundfont.sf2", "doom"), ("wallpaper.rgb", "compositor")]; + +/// Whether ROOT's `share/` goes into an image building exactly +/// `programs`: unless [`OPENED_BY`] names a reader the image does not build. +pub fn wanted(name: &str, programs: &BTreeSet<&str>) -> bool { + match OPENED_BY.iter().find(|(file, _)| *file == name) { + Some((_, reader)) if !programs.contains(reader) => { + eprintln!("assets: leaving out share/{name} — only /system/bin/{reader} opens it and this image builds no {reader}"); + false + } + _ => true, + } +} /// ROOT's asset files, for an image building exactly `programs`. pub fn collect(dirs: &[String], programs: &BTreeSet<&str>) -> Vec<(String, Vec)> { @@ -274,18 +288,7 @@ pub fn collect(dirs: &[String], programs: &BTreeSet<&str>) -> Vec<(String, Vec) -> Vec<(String, Vec) -> Vec<(String, Vec| -> BTreeSet { collect(&[dir.display().to_string()], &programs) @@ -481,13 +467,13 @@ mod tests { BTreeSet::from([ "share/doom1.wad".to_string(), "share/soundfont.sf2".to_string(), - "share/wallpaper.rgb".to_string(), + "share/readme.txt".to_string(), ]), "an image that builds doom did not get doom's assets" ); assert_eq!( without, - BTreeSet::from(["share/wallpaper.rgb".to_string()]), + BTreeSet::from(["share/readme.txt".to_string()]), "an image with no doom in it still carries what only doom opens" ); } @@ -552,10 +538,9 @@ mod tests { /// /// The claim is that one program opens the file, so a second program naming /// its path is an image losing a file it needs. `userland/` only: the - /// harness names both paths in assertions about doom, and it runs on the - /// host. + /// harness names doom's paths in its assertions, and it runs on the host. #[test] - fn only_doom_opens_doom_s_assets() { + fn only_its_reader_opens_an_owned_file() { let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); for (asset, reader) in OPENED_BY { let out = Command::new("git") diff --git a/src/build.rs b/src/build.rs index b383792baab..2b7614e56fb 100644 --- a/src/build.rs +++ b/src/build.rs @@ -664,43 +664,27 @@ fn render_manifest(config: &SystemConfig) -> Vec { /// Which build `root`'s tree makes for `arch` against the sysroot whose key /// for it is `toolchain`, as `/system/etc/os-release` records it, read with -/// gitoxide (`issues/the-build-runs-host-tools-outside-rust-and-qemu.md`, -/// row 21). Untracked files are dirty whatever `status.showUntrackedFiles` +/// `git`. Untracked files are dirty whatever `status.showUntrackedFiles` /// says; submodules are not read, because the fork's state is the toolchain /// key's. fn release(root: &Path, toolchain: &crate::keystore::Key, arch: Arch) -> toyos_osrelease::Release { - let repo = gix::open(root).unwrap_or_else(|e| panic!("{} is no git checkout: {e}", root.display())); - let head = repo.head_commit().unwrap_or_else(|e| panic!("{}'s HEAD names no commit: {e}", root.display())); - let commit = toyos_osrelease::Hex::parse(&head.id.to_string()).expect("a SHA-1 commit is forty hex digits"); - let time = head.time().unwrap_or_else(|e| panic!("commit {} names no committer time: {e}", head.id)); - let committed = u64::try_from(time.seconds) - .unwrap_or_else(|_| panic!("commit {} was committed before 1970: {}", head.id, time.seconds)); - // Set whole: the platform `status` makes has no walk at all for a - // checkout configured to show no untracked files. - let walk = repo - .dirwalk_options() - .unwrap_or_else(|e| panic!("the status of {}: {e}", root.display())) - .emit_untracked(gix::dir::walk::EmissionMode::CollapseDirectory); - let changes = repo - .status(gix::progress::Discard) - .and_then(|status| { - status - .index_worktree_options_mut(|options| options.dirwalk_options = Some(walk)) - .index_worktree_submodules(None) - .into_iter(None) - }) - .unwrap_or_else(|e| panic!("the status of {}: {e}", root.display())); - // An index entry whose stat alone moved, and an ignored file the walk - // passed, summarise to nothing. - let dirty = changes.map(|item| item.unwrap_or_else(|e| panic!("the status of {}: {e}", root.display()))).any( - |item| match item { - gix::status::Item::IndexWorktree(change) => change.summary().is_some(), - gix::status::Item::TreeIndex(_) => true, - }, + let head = crate::sysroot::git_out(root, &["log", "-1", "--no-show-signature", "--format=%H %ct", "HEAD"]); + let (id, time) = head + .trim() + .split_once(' ') + .unwrap_or_else(|| panic!("{}'s HEAD names no commit and time: {head:?}", root.display())); + let commit = toyos_osrelease::Hex::parse(id).expect("a SHA-1 commit is forty hex digits"); + let committed: u64 = + time.parse().unwrap_or_else(|_| panic!("commit {id} was not committed after 1970: {time}")); + // `--no-optional-locks`, because a status otherwise rewrites the index it + // refreshes, under the lock a concurrent `git` in this checkout takes. + let status = crate::sysroot::git_bytes( + root, + &["--no-optional-locks", "status", "--porcelain", "--untracked-files=normal", "--ignore-submodules=all"], ); toyos_osrelease::Release { commit, - tree: if dirty { toyos_osrelease::Tree::Dirty } else { toyos_osrelease::Tree::Clean }, + tree: if status.is_empty() { toyos_osrelease::Tree::Clean } else { toyos_osrelease::Tree::Dirty }, toolchain: toyos_osrelease::Hex::parse(toolchain.as_str()).expect("a key is sixteen hex digits"), arch: match arch { Arch::X86_64 => toyos_osrelease::Arch::X86_64, @@ -726,10 +710,13 @@ fn build_and_assemble( build_programs(root, config, env, quiet, arch, &mut root_files); root_files.push((toyos_manifest::PATH.to_string(), render_manifest(config))); + let programs: BTreeSet<&str> = config.programs.keys().map(String::as_str).collect(); if !config.assets.is_empty() { - let programs: BTreeSet<&str> = config.programs.keys().map(String::as_str).collect(); root_files.extend(assets::collect(&config.assets, &programs)); } + if assets::wanted(crate::wallpaper::NAME, &programs) { + root_files.push((format!("share/{}", crate::wallpaper::NAME), crate::wallpaper::rgb())); + } // Extra files (test binaries, shared libs) for (name, data) in extra_files { @@ -2105,7 +2092,7 @@ mod tests { .status() .unwrap(); assert!(committed.success()); - // The ref as `git` wrote it, for an oracle that is not gitoxide. + // The ref as `git` wrote it, read off the file rather than asked of `git`. let head = fs::read_to_string(work.join(".git/refs/heads/wt")).unwrap(); let key = crate::keystore::Key::parse("0123456789abcdef").unwrap(); diff --git a/src/fatformat.rs b/src/fatformat.rs new file mode 100644 index 00000000000..b1219263b52 --- /dev/null +++ b/src/fatformat.rs @@ -0,0 +1,194 @@ +//! An empty FAT32 volume, the one the build lays every FAT partition down as +//! before `toyos-fat32` writes its files: a boot sector and its backup, an +//! FSInfo sector, two FATs and a root directory holding the volume label. +//! +//! Here and not in `toyos-fat32`, which is the kernel's driver and has no +//! format path by design. The geometry is fatgen103's with the choices of the +//! `fatfs` crate this replaced, so every volume is laid out as before: +//! 512-byte sectors, eight reserved sectors, the cluster size by volume size, +//! and the FAT sized for the clusters left once the FATs are paid for. +//! `differs_from_the_fatfs_format_only_in_the_bios_stub` holds that, against +//! `fatfs` itself. +//! +//! The boot sector carries no BIOS boot code: the jump fatgen103 requires is +//! there, and the 420 bytes after the BPB are zero. + +const SECTOR: usize = 512; +const RESERVED_SECTORS: u32 = 8; +const FATS: u32 = 2; +const FSINFO_SECTOR: usize = 1; +const BACKUP_BOOT_SECTOR: usize = 6; +const ROOT_CLUSTER: u32 = 2; +/// A fixed disk, which is what every FAT32 volume is. +const MEDIA: u8 = 0xF8; +/// The largest cluster number FAT32 can name, less the two reserved entries. +const MAX_FAT32_CLUSTERS: u32 = 0x0FFF_FFF4; +const END_OF_CHAIN: u32 = 0x0FFF_FFFF; +const ATTR_VOLUME_ID: u8 = 0x08; + +/// An empty FAT32 volume of `bytes` under `label`. +pub(crate) fn format(bytes: usize, label: [u8; 11]) -> Vec { + assert_eq!(bytes % SECTOR, 0, "a {bytes}-byte volume is not whole {SECTOR}-byte sectors"); + let total = u32::try_from(bytes / SECTOR) + .unwrap_or_else(|_| panic!("a {bytes}-byte volume has more sectors than FAT32 counts")); + let per_cluster = sectors_per_cluster(bytes as u64); + let per_fat = sectors_per_fat(total, per_cluster); + let first_data = RESERVED_SECTORS + FATS * per_fat; + let clusters = (total - first_data) / per_cluster; + assert!( + (toyos_fat32::MIN_FAT32_CLUSTERS..=MAX_FAT32_CLUSTERS).contains(&clusters), + "a {bytes}-byte volume has {clusters} clusters, which is no FAT32" + ); + + let mut volume = vec![0u8; bytes]; + let boot = boot_sector(total, per_cluster, per_fat, label); + volume[..SECTOR].copy_from_slice(&boot); + let backup = BACKUP_BOOT_SECTOR * SECTOR; + volume[backup..backup + SECTOR].copy_from_slice(&boot); + + let fsinfo = &mut volume[FSINFO_SECTOR * SECTOR..][..SECTOR]; + put(fsinfo, 0, &0x4161_5252u32.to_le_bytes()); + put(fsinfo, 484, &0x6141_7272u32.to_le_bytes()); + // Free count and next free: "unknown", which `toyos-fat32` counts and records. + put(fsinfo, 488, &u32::MAX.to_le_bytes()); + put(fsinfo, 492, &u32::MAX.to_le_bytes()); + put(fsinfo, 508, &0xAA55_0000u32.to_le_bytes()); + + let entries = per_fat as usize * SECTOR / 4; + for copy in 0..FATS { + let at = (RESERVED_SECTORS + copy * per_fat) as usize * SECTOR; + let fat = &mut volume[at..at + per_fat as usize * SECTOR]; + let mut set = |cluster: usize, value: u32| put(fat, cluster * 4, &value.to_le_bytes()); + set(0, 0x0FFF_FF00 | u32::from(MEDIA)); + set(1, u32::MAX); + set(ROOT_CLUSTER as usize, END_OF_CHAIN); + // The entries past the last cluster that the FAT's last sector still holds. + for cluster in clusters as usize + 2..entries { + set(cluster, END_OF_CHAIN); + } + } + + let root = first_data as usize * SECTOR; + put(&mut volume[root..], 0, &label); + volume[root + 11] = ATTR_VOLUME_ID; + volume +} + +/// 512 bytes up to 260 MiB and 4 KiB up to 8 GiB, past anything the build +/// formats. +fn sectors_per_cluster(bytes: u64) -> u32 { + const MIB: u64 = 1024 * 1024; + assert!(bytes <= 8 * 1024 * MIB, "a {bytes}-byte volume is larger than this format lays out"); + let cluster = if bytes <= 260 * MIB { 512 } else { 4096 }; + (cluster / SECTOR as u64) as u32 +} + +/// The smallest FAT whose entries cover every cluster the volume has left +/// once the reserved sectors and both FATs are taken out, the two reserved +/// entries included. +fn sectors_per_fat(total: u32, per_cluster: u32) -> u32 { + let rest = u64::from(total - RESERVED_SECTORS) + 2 * u64::from(per_cluster); + let per = u64::from(per_cluster * SECTOR as u32 * 8 / 32 + FATS); + rest.div_ceil(per) as u32 +} + +fn boot_sector(total: u32, per_cluster: u32, per_fat: u32, label: [u8; 11]) -> [u8; SECTOR] { + let mut s = [0u8; SECTOR]; + put(&mut s, 0, &[0xEB, 0x58, 0x90]); + put(&mut s, 3, b"MSWIN4.1"); + put(&mut s, 11, &(SECTOR as u16).to_le_bytes()); + s[13] = per_cluster as u8; + put(&mut s, 14, &(RESERVED_SECTORS as u16).to_le_bytes()); + s[16] = FATS as u8; + // The FAT12/16 root entry count and 16-bit sizes stay zero on FAT32, whose + // volumes `format` holds to more sectors than 16 bits count. + s[21] = MEDIA; + // Sectors per track and heads: no reader of a FAT32 volume uses either. + put(&mut s, 24, &0x20u16.to_le_bytes()); + put(&mut s, 26, &0x40u16.to_le_bytes()); + put(&mut s, 32, &total.to_le_bytes()); + put(&mut s, 36, &per_fat.to_le_bytes()); + put(&mut s, 44, &ROOT_CLUSTER.to_le_bytes()); + put(&mut s, 48, &(FSINFO_SECTOR as u16).to_le_bytes()); + put(&mut s, 50, &(BACKUP_BOOT_SECTOR as u16).to_le_bytes()); + s[64] = 0x80; + s[66] = 0x29; + put(&mut s, 67, &0x1234_5678u32.to_le_bytes()); + put(&mut s, 71, &label); + put(&mut s, 82, b"FAT32 "); + put(&mut s, 510, &[0x55, 0xAA]); + s +} + +fn put(into: &mut [u8], at: usize, bytes: &[u8]) { + into[at..at + bytes.len()].copy_from_slice(bytes); +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + + /// Where a FAT32 boot sector's boot code lies, after the BPB. + const BOOT_CODE: std::ops::Range = 90..510; + + /// Every size the build formats at, and the cluster sizes' edges. + const SIZES: [usize; 6] = [ + 34 << 20, + (34 << 20) + 4096, + 100 << 20, + 260 << 20, + (260 << 20) + 4096, + 300 << 20, + ]; + + fn fatfs_format(bytes: usize, label: [u8; 11]) -> Vec { + let mut volume = vec![0u8; bytes]; + fatfs::format_volume( + Cursor::new(&mut volume), + fatfs::FormatVolumeOptions::new().fat_type(fatfs::FatType::Fat32).volume_label(label), + ) + .expect("fatfs formats the volume"); + volume + } + + /// **The differential oracle**: `fatfs`, a third party's FAT + /// implementation sharing no code with this one, formats the same size + /// under the same label into the same bytes, but for the BIOS boot code + /// it writes into both copies of the boot sector and this module does not. + #[test] + fn differs_from_the_fatfs_format_only_in_the_bios_stub() { + for bytes in SIZES { + let ours = format(bytes, *b"TOYOS-SLOT "); + let theirs = fatfs_format(bytes, *b"TOYOS-SLOT "); + let mut masked = theirs.clone(); + for sector in [0, BACKUP_BOOT_SECTOR] { + let code = sector * SECTOR + BOOT_CODE.start..sector * SECTOR + BOOT_CODE.end; + assert!(ours[code.clone()].iter().all(|b| *b == 0), "{bytes}: boot code in sector {sector}"); + assert!(theirs[code.clone()].iter().any(|b| *b != 0), "{bytes}: fatfs wrote no boot code in sector {sector}"); + masked[code].fill(0); + } + let differing = (0..bytes).filter(|&at| ours[at] != masked[at]).count(); + assert!(differing == 0, "{bytes}: {differing} bytes differ from fatfs's"); + } + } + + /// `toyos-fat32` mounts the volume as empty, with every cluster but the + /// root's free, and once it has recorded that count, as the build has it + /// do, `toyos-fat32-check` finds nothing wrong with it. + #[test] + fn an_empty_volume_mounts_free_and_checks_clean() { + for bytes in SIZES { + let mut volume = format(bytes, *b"TOYOS-LOG "); + let mut fs = toyos_fat32::Fat32::mount(crate::image::VolumeIo(&mut volume)).expect("it mounts"); + assert!(fs.walk("", 8).expect("it walks").is_empty(), "{bytes}: an empty volume lists files"); + let geometry = *fs.geometry(); + let cluster = u64::from(geometry.bytes_per_cluster()); + assert_eq!(fs.free_bytes().expect("it counts"), (u64::from(geometry.cluster_count) - 1) * cluster, "{bytes}"); + fs.sync().expect("it records the count"); + drop(fs); + let complaints = toyos_fat32_check::check(&volume); + assert!(complaints.is_empty(), "{bytes}:\n{}", toyos_fat32_check::describe(&complaints)); + } + } +} diff --git a/src/flags.rs b/src/flags.rs index d9173d79433..20445c0fa8a 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -66,7 +66,6 @@ declare_flags!(pub CARGO_RUN = { pub BOOT_CONFIG = "--boot-config", Next; pub ARCH = "--arch", Next; pub REGEN_FONT = "--regen-font", None; - pub REGEN_WALLPAPER = "--regen-wallpaper", None; pub REGEN_SOUNDFONT = "--regen-soundfont", Next; /// Mint the owner's image-signing key where `signing::owner_key_path` /// says, refusing to replace one. diff --git a/src/gptwrite.rs b/src/gptwrite.rs new file mode 100644 index 00000000000..6f2e5e44f46 --- /dev/null +++ b/src/gptwrite.rs @@ -0,0 +1,237 @@ +//! The partition table the build writes onto every disk it makes: a +//! protective MBR, the primary GPT header and its entry array at the front, +//! and the backup array and header at the back, in 512-byte logical blocks +//! with the 128 entries of 128 bytes UEFI §5.3 requires room for. +//! +//! Here and not in `toyos-gpt`, which is the parser the kernel and the loader +//! read the table with; the build is the one program that writes one. +//! `toyos-gpt`'s CRC-32 and GUIDs are shared, because they are the format's +//! own and the parser is what proves them. `the_gpt_crate_writes_the_same_table` +//! holds every byte against the `gpt` crate this replaced. + +use toyos_gpt::Guid; + +const LBA: u64 = crate::image::LBA as u64; +const ENTRIES: u64 = 128; +const ENTRY_BYTES: u64 = 128; +const ARRAY_LBAS: u64 = ENTRIES * ENTRY_BYTES / LBA; +/// The MBR, the header and the array before it. +const FIRST_USABLE: u64 = 2 + ARRAY_LBAS; +const HEADER_BYTES: usize = 92; +/// A name is at most 36 UTF-16 code units. +const NAME_UNITS: usize = 36; + +/// One partition: its type, its own GUID, its first and last block, and its name. +pub(crate) struct Entry<'a> { + pub kind: Guid, + pub unique: Guid, + pub first_lba: u64, + pub last_lba: u64, + pub name: &'a str, +} + +/// A table's two halves: `primary` goes at byte 0 of the disk and `backup` +/// ends at its last byte. +pub(crate) struct Table { + pub primary: Vec, + pub backup: Vec, +} + +/// The last block a partition on a disk of `lbas` blocks may use. +fn last_usable(lbas: u64) -> u64 { + lbas - 2 - ARRAY_LBAS +} + +/// The table of a disk of `lbas` blocks named `disk`, carrying `entries` in +/// their order. +pub(crate) fn table(lbas: u64, disk: Guid, entries: &[Entry<'_>]) -> Table { + assert!(entries.len() as u64 <= ENTRIES, "{} partitions in a table of {ENTRIES}", entries.len()); + let last_usable = last_usable(lbas); + let mut array = vec![0u8; (ENTRIES * ENTRY_BYTES) as usize]; + for (entry, slot) in entries.iter().zip(array.chunks_mut(ENTRY_BYTES as usize)) { + assert!( + FIRST_USABLE <= entry.first_lba && entry.first_lba <= entry.last_lba && entry.last_lba <= last_usable, + "partition {:?} at blocks {}..={} is not inside {FIRST_USABLE}..={last_usable}", + entry.name, + entry.first_lba, + entry.last_lba + ); + let name: Vec = entry.name.encode_utf16().collect(); + assert!(name.len() <= NAME_UNITS, "the partition name {:?} is over {NAME_UNITS} UTF-16 units", entry.name); + slot[..16].copy_from_slice(&entry.kind.0); + slot[16..32].copy_from_slice(&entry.unique.0); + slot[32..40].copy_from_slice(&entry.first_lba.to_le_bytes()); + slot[40..48].copy_from_slice(&entry.last_lba.to_le_bytes()); + for (unit, at) in name.iter().zip((56..).step_by(2)) { + slot[at..at + 2].copy_from_slice(&unit.to_le_bytes()); + } + } + let array_crc = toyos_gpt::crc32(&array); + let header = |at: u64, other: u64, array_at: u64| { + let mut block = vec![0u8; LBA as usize]; + block[..8].copy_from_slice(b"EFI PART"); + block[8..12].copy_from_slice(&0x0001_0000u32.to_le_bytes()); + block[12..16].copy_from_slice(&(HEADER_BYTES as u32).to_le_bytes()); + block[24..32].copy_from_slice(&at.to_le_bytes()); + block[32..40].copy_from_slice(&other.to_le_bytes()); + block[40..48].copy_from_slice(&FIRST_USABLE.to_le_bytes()); + block[48..56].copy_from_slice(&last_usable.to_le_bytes()); + block[56..72].copy_from_slice(&disk.0); + block[72..80].copy_from_slice(&array_at.to_le_bytes()); + block[80..84].copy_from_slice(&(ENTRIES as u32).to_le_bytes()); + block[84..88].copy_from_slice(&(ENTRY_BYTES as u32).to_le_bytes()); + block[88..92].copy_from_slice(&array_crc.to_le_bytes()); + let crc = toyos_gpt::crc32(&block[..HEADER_BYTES]); + block[16..20].copy_from_slice(&crc.to_le_bytes()); + block + }; + + let mut primary = protective_mbr(lbas); + primary.extend(header(1, lbas - 1, 2)); + primary.extend(&array); + let mut backup = array; + backup.extend(header(lbas - 1, 1, last_usable + 1)); + Table { primary, backup } +} + +/// LBA 0: one partition of type 0xEE over the whole disk but its first +/// block, or as much of it as 32 bits count, at the CHS UEFI §5.2.3 gives it. +fn protective_mbr(lbas: u64) -> Vec { + let mut block = vec![0u8; LBA as usize]; + block[446..454].copy_from_slice(&[0x00, 0x00, 0x02, 0x00, 0xEE, 0xFF, 0xFF, 0xFF]); + block[454..458].copy_from_slice(&1u32.to_le_bytes()); + block[458..462].copy_from_slice(&u32::try_from(lbas - 1).unwrap_or(u32::MAX).to_le_bytes()); + block[510..512].copy_from_slice(&[0x55, 0xAA]); + block +} + +/// A version 4 GUID from the operating system's random source, in the +/// on-disk order: the version is the high nibble of byte 7, the third field +/// being little-endian, and the variant the top two bits of byte 8. +pub(crate) fn random_guid() -> Guid { + let mut bytes = [0u8; 16]; + getrandom::fill(&mut bytes).expect("the operating system's random source"); + bytes[7] = (bytes[7] & 0x0F) | 0x40; + bytes[8] = (bytes[8] & 0x3F) | 0x80; + Guid(bytes) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::image::ImageSectors; + use std::collections::BTreeMap; + use std::io::{Cursor, Read, Seek, SeekFrom}; + + const ALIGN: u64 = 2048; + + /// The partitions of a boot image with the DATA an install puts first, + /// one of them a length the 1 MiB alignment does not round. + const ROWS: [(&str, Guid, u64); 6] = [ + ("ToyOS data", Guid::TOYOS_DATA, 3 << 20), + ("ESP", Guid::EFI_SYSTEM, 34 << 20), + ("ToyOS slots", Guid::TOYOS_SLOTS, 1 << 20), + ("ToyOS log", Guid::MICROSOFT_BASIC, 34 << 20), + ("ToyOS slot A", Guid::TOYOS_BOOT, (40 << 20) + 4096), + ("ToyOS root A", Guid::TOYOS_ROOT, 7 << 20), + ]; + + /// `ROWS` under `uniques`, each on the first 1 MiB boundary after the last. + fn entries(uniques: &[Guid]) -> Vec> { + let mut at = ALIGN; + ROWS.iter() + .zip(uniques) + .map(|((name, kind, len), unique)| { + let first = at; + at = (first + len / LBA).next_multiple_of(ALIGN); + Entry { kind: *kind, unique: *unique, first_lba: first, last_lba: first + len / LBA - 1, name } + }) + .collect() + } + + fn disk_of(total: u64, table: &Table) -> Vec { + let mut disk = vec![0u8; total as usize]; + disk[..table.primary.len()].copy_from_slice(&table.primary); + let back = disk.len() - table.backup.len(); + disk[back..].copy_from_slice(&table.backup); + disk + } + + /// The `gpt` crate's table for `ROWS`, laid as `src/image.rs` laid it with + /// that crate, on a disk of `total` bytes: its bytes, its disk GUID and + /// the unique GUIDs it drew. + fn theirs(total: u64) -> (Vec, Guid, Vec) { + let mut bytes = vec![0u8; total as usize]; + let mut cursor = Cursor::new(&mut bytes); + gpt::mbr::ProtectiveMBR::with_lb_size(u32::try_from(total / LBA - 1).unwrap_or(u32::MAX)) + .overwrite_lba0(&mut cursor) + .expect("the protective MBR"); + let mut disk = gpt::GptConfig::default() + .initialized(false) + .writable(true) + .logical_block_size(gpt::disk::LogicalBlockSize::Lb512) + .create_from_device(Box::new(cursor), None) + .expect("a table"); + disk.update_partitions(BTreeMap::new()).expect("an empty table"); + for (name, kind, len) in ROWS { + let kind = gpt::partition_types::Type { + guid: kind.to_string().leak(), + os: gpt::partition_types::OperatingSystem::None, + }; + disk.add_partition(name, len, kind, 0, Some(ALIGN)).expect("a partition"); + } + let mut device = disk.write().expect("the table"); + let mut out = vec![0u8; total as usize]; + device.seek(SeekFrom::Start(0)).and_then(|_| device.read_exact(&mut out)).expect("read back"); + let guid = |at: usize| Guid(out[at..at + 16].try_into().unwrap()); + let disk_guid = guid(LBA as usize + 56); + let uniques = (0..ROWS.len()).map(|i| guid(2 * LBA as usize + i * ENTRY_BYTES as usize + 16)).collect(); + (out, disk_guid, uniques) + } + + /// **The differential oracle**: the `gpt` crate, a third party's GPT + /// writer sharing no code with this one, lays the same partitions on the + /// same disk under the same GUIDs into the same bytes, every one of them, + /// on a disk the alignment rounds and on one it does not. + #[test] + fn the_gpt_crate_writes_the_same_table() { + for total in [128u64 << 20, (160 << 20) + 3 * 4096] { + let (theirs, disk, uniques) = theirs(total); + let ours = disk_of(total, &table(total / LBA, disk, &entries(&uniques))); + let differing = (0..total as usize).filter(|&i| ours[i] != theirs[i]).count(); + assert_eq!(differing, 0, "{total}: {differing} bytes differ from the gpt crate's"); + } + } + + /// RFC 9562's version and variant, where the canonical text shows them. + #[test] + fn a_random_guid_is_version_4_of_the_rfc_variant() { + let text = random_guid().to_string(); + assert_eq!(&text[14..15], "4", "{text}"); + assert!(matches!(&text[19..20], "8" | "9" | "A" | "B"), "{text}"); + } + + /// `toyos-gpt`, the parser the loader and the kernel read with, finds + /// every partition at its blocks under its type, from the primary and, + /// with the primary torn, from the backup. + #[test] + fn toyos_gpt_reads_it_from_either_copy() { + let total: u64 = 128 << 20; + let uniques: Vec = ROWS.iter().map(|_| random_guid()).collect(); + let entries = entries(&uniques); + let disk = disk_of(total, &table(total / LBA, random_guid(), &entries)); + let mut torn = disk.clone(); + torn[LBA as usize + 30] ^= 1; + for image in [&disk, &torn] { + for entry in &entries { + let found = toyos_gpt::locate(&mut ImageSectors(image), entry.unique).expect("the partition").partition(); + assert_eq!( + (found.type_guid(), found.first_lba(), found.lba_count().get()), + (entry.kind, entry.first_lba, entry.last_lba - entry.first_lba + 1), + "{}", + entry.name + ); + } + } + } +} diff --git a/src/image.rs b/src/image.rs index 7b28e9ad0fa..c156a7e7a68 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,4 +1,3 @@ -use std::collections::BTreeMap; use std::io::{Cursor, Read, Seek, SeekFrom, Write}; use std::num::NonZeroU64; use std::path::Path; @@ -8,6 +7,7 @@ use bcachefs::{BlockBuf, BlockIO, BlockNum, Formatted, FsUuid, Superblock, VecBl use crate::arch::Arch; use sha2::{Digest, Sha256}; use toyos_fat32::{BlockAccess, Fat32, FatTime, IoError}; +use toyos_gpt::Guid; /// The image that goes on the ROOT partition, named by a UUID **derived, never /// drawn**: two builds of one tree have to agree on the name the kernel @@ -237,18 +237,16 @@ pub fn create_boot_image( // kernel. The kernel is given the partition by name; nothing anywhere goes // looking for one by type or by format. Every slot partition is named the // same way, by the slot table. - let guid = uuid::Uuid::new_v4; - let (log_guid, esp_guid, table_guid) = (guid(), guid(), guid()); - let a = (guid(), guid()); - let b = second.map(|room| (guid(), guid(), room)); - let raw = |g: uuid::Uuid| g.to_bytes_le(); + let (log_guid, esp_guid, table_guid) = (crate::gptwrite::random_guid(), crate::gptwrite::random_guid(), crate::gptwrite::random_guid()); + let a = (crate::gptwrite::random_guid(), crate::gptwrite::random_guid()); + let b = second.map(|room| (crate::gptwrite::random_guid(), crate::gptwrite::random_guid(), room)); let s = sections(kernel_bytes, root_bytes, params, signing); // Room for a kernel twice this one's size beside it, so an update can // write a new kernel into a slot's volume before the old one is gone. let slot_bytes = round_up_sectors(((kernel_bytes.len() * 2 + ESP_FREE_BYTES) * 64 / 63).max(FAT32_MIN_BYTES)); let a_boot = create_slot_volume(Some(&s), slot_bytes); - let slot = |(boot, root): (uuid::Uuid, uuid::Uuid), version| toyos_update::slots::Slot { boot: raw(boot), root: raw(root), version }; + let slot = |(boot, root): (Guid, Guid), version| toyos_update::slots::Slot { boot: boot.0, root: root.0, version }; let table = toyos_update::slots::Table { sequence: 1, marked: toyos_update::slots::Which::A, @@ -258,21 +256,21 @@ pub fn create_boot_image( table_volume[..toyos_update::slots::BLOCK].copy_from_slice(&table.encode()); let mut parts = vec![ - Part::full("ESP", "EFI System", gpt::partition_types::EFI, esp_guid, create_esp_volume(arch, bl_bytes, log_guid), Some(Volume::Fat32)), - Part::full("slot table", "ToyOS slots", TOYOS_SLOTS, table_guid, table_volume, None), + Part::full("ESP", "EFI System", Guid::EFI_SYSTEM, esp_guid, create_esp_volume(arch, bl_bytes, log_guid), Some(Volume::Fat32)), + Part::full("slot table", "ToyOS slots", Guid::TOYOS_SLOTS, table_guid, table_volume, None), // Microsoft Basic Data, and that type is the whole reason this is a // partition of its own: macOS never auto-mounts an EFI-typed partition // and this host refuses even a manual non-root mount of one, so a log on // the ESP is unreachable without the admin account. This type mounts in // Finder, in Windows and in Linux on plug-in, with nothing configured. - Part::full("log partition", "ToyOS log", gpt::partition_types::BASIC, log_guid, create_log_volume(), Some(Volume::Fat32)), - Part::full("slot A's volume", "ToyOS slot A", TOYOS_BOOT, a.0, a_boot, Some(Volume::Fat32)), - Part::full("slot A's ROOT", "ToyOS root A", TOYOS_ROOT, a.1, root_bytes.to_vec(), Some(Volume::Root)), + Part::full("log partition", "ToyOS log", Guid::MICROSOFT_BASIC, log_guid, create_log_volume(), Some(Volume::Fat32)), + Part::full("slot A's volume", "ToyOS slot A", Guid::TOYOS_BOOT, a.0, a_boot, Some(Volume::Fat32)), + Part::full("slot A's ROOT", "ToyOS root A", Guid::TOYOS_ROOT, a.1, root_bytes.to_vec(), Some(Volume::Root)), ]; if let Some((boot, root, room)) = b { - parts.push(Part::full("slot B's volume", "ToyOS slot B", TOYOS_BOOT, boot, create_slot_volume(None, slot_bytes), Some(Volume::Fat32))); + parts.push(Part::full("slot B's volume", "ToyOS slot B", Guid::TOYOS_BOOT, boot, create_slot_volume(None, slot_bytes), Some(Volume::Fat32))); let len = align_up(room.root_bytes as usize, PARTITION_ALIGN) as u64; - parts.push(Part { what: "slot B's ROOT", name: "ToyOS root B", kind: TOYOS_ROOT, guid: root, bytes: Vec::new(), len, judge: None }); + parts.push(Part { what: "slot B's ROOT", name: "ToyOS root B", kind: Guid::TOYOS_ROOT, guid: root, bytes: Vec::new(), len, judge: None }); } create_gpt_disk(&parts) } @@ -456,8 +454,8 @@ const PARTITION_ALIGN: usize = 1024 * 1024; /// The smallest volume there is a FAT32 for. /// -/// FAT32 *is* the format with at least 65,525 clusters, and `fatfs` gives a -/// volume this size 512-byte clusters — so the data area alone is 33.5 MiB +/// FAT32 *is* the format with at least 65,525 clusters, and [`crate::fatformat`] +/// gives a volume this size 512-byte clusters — so the data area alone is 33.5 MiB /// before the two FATs and the reserved sectors. Measured at exactly this size: /// the format succeeds and `fsck_msdos` reports 68,551 free clusters. const FAT32_MIN_BYTES: usize = 34 * 1024 * 1024; @@ -466,7 +464,7 @@ const FAT32_MIN_BYTES: usize = 34 * 1024 * 1024; /// /// The `64/63` beside this at the one call site is headroom for the two FAT /// copies, which cost a byte of table per 64 bytes of volume at the 512-byte -/// clusters `fatfs` gives a small one — the worst case, so a volume whose +/// clusters [`crate::fatformat`] gives a small one — the worst case, so a volume whose /// clusters are larger is over-provisioned rather than under. A flat slack /// leaves a guest whatever the rounding did, and an fsync that fails while the /// host-side volume reports megabytes free is the symptom of getting it wrong. @@ -479,7 +477,7 @@ fn align_up(n: usize, to: usize) -> usize { /// A FAT volume label: eleven bytes of space-padded OEM text. /// /// Without one every host calls the volume `NO NAME`, which is what the ESP -/// showed as. `format_volume` writes it into both places the format keeps it, +/// showed as. The format writes it into both places the format keeps it, /// the BPB field and a `VOLUME_ID` entry in the root directory, and the mount /// on macOS is named from it — measured, `/Volumes/TOYOS-LOG`. fn fat_label(text: &str) -> [u8; 11] { @@ -495,15 +493,7 @@ fn fat_label(text: &str) -> [u8; 11] { /// An empty FAT32 volume of `bytes`, under `label`. fn format_fat32(bytes: usize, label: &str) -> Vec { - let mut volume = vec![0u8; bytes]; - fatfs::format_volume( - Cursor::new(&mut volume), - fatfs::FormatVolumeOptions::new() - .fat_type(fatfs::FatType::Fat32) - .volume_label(fat_label(label)), - ) - .unwrap_or_else(|e| panic!("failed to format the {label} volume: {e}")); - volume + crate::fatformat::format(bytes, fat_label(label)) } /// The volume being built, as [`toyos_fat32`] sees it. @@ -512,7 +502,7 @@ fn format_fat32(bytes: usize, label: &str) -> Vec { /// volume is a `Vec` in this process. The kernel's adapter is where the /// read-modify-write against 4 KiB device blocks lives, and `toyos-fat32`'s own /// host suite is where that shape is exercised. -struct VolumeIo<'a>(&'a mut [u8]); +pub(crate) struct VolumeIo<'a>(pub(crate) &'a mut [u8]); impl VolumeIo<'_> { fn range(&self, offset: u64, len: usize) -> Result, IoError> { @@ -563,17 +553,11 @@ fn build_time() -> FatTime { /// Write `files` onto a formatted volume, creating the directories they name, /// and leave the free-cluster count recorded. /// -/// Written with **our** FAT32 driver rather than with the crate that formatted -/// the volume: `fatfs`'s `create_dir` writes a long-name entry ahead of each -/// `.` and `..`, which the format requires to be a subdirectory's first two -/// entries, and gives `..` the root's cluster number where the format requires -/// zero — neither reachable through that crate's API. `toyos-fat32` is also the -/// driver the kernel appends `kernel.log` with and the one its own host suite -/// runs the volume checker against, so "the image we build is clean" is a claim -/// about the writer that is judged. `fatfs` keeps the format call, where an -/// empty volume has no subdirectory for either bug to live in. +/// Written with `toyos-fat32`, the driver the kernel appends `kernel.log` with +/// and the one its own host suite runs the volume checker against, so "the image +/// we build is clean" is a claim about the writer that is judged. /// -/// `format_volume` leaves FSInfo's free-cluster field 0xFFFFFFFF, which FAT32 +/// The format leaves FSInfo's free-cluster field 0xFFFFFFFF, which FAT32 /// defines as "unknown" and every host reports free space from; `free_bytes` /// counts the FAT when the volume arrived without a hint and `sync` writes it. fn populate(volume: &mut [u8], label: &str, files: &[(&str, &[u8])]) { @@ -602,7 +586,7 @@ fn populate(volume: &mut [u8], label: &str, files: &[(&str, &[u8])]) { /// partition the kernel's log goes on. The kernel and its parameter are a /// slot's (`create_slot_volume`), because the loader is the one part of the /// machine that is not slotted. -fn create_esp_volume(arch: Arch, bootloader: &[u8], log_guid: uuid::Uuid) -> Vec { +fn create_esp_volume(arch: Arch, bootloader: &[u8], log_guid: Guid) -> Vec { let total_size = round_up_sectors(((bootloader.len() + ESP_FREE_BYTES) * 64 / 63).max(FAT32_MIN_BYTES)); let mut volume = format_fat32(total_size, "TOYOS-BOOT"); populate( @@ -616,7 +600,7 @@ fn create_esp_volume(arch: Arch, bootloader: &[u8], log_guid: uuid::Uuid) -> Vec // order: nothing converts them on the way to the kernel and nothing // converts the table's, so the comparison that decides which // partition holds the log cannot be got backwards. - ("toyos/log.guid", &log_guid.to_bytes_le()), + ("toyos/log.guid", &log_guid.0), ], ); volume @@ -657,34 +641,6 @@ fn create_log_volume() -> Vec { volume } -/// `B350BC93-…`, the TOYOS-ROOT partition type, as the writer names a type. -/// -/// `toyos_gpt::Guid::TOYOS_ROOT` is the same sixteen bytes as the kernel -/// matches them, and `toyos_root_text_is_the_type_guid` is what keeps the two -/// spellings one constant — and the same for each type below. -const TOYOS_ROOT: gpt::partition_types::Type = gpt::partition_types::Type { - guid: toyos_gpt::Guid::TOYOS_ROOT_TEXT, - os: gpt::partition_types::OperatingSystem::None, -}; - -/// `064E3777-…`, the TOYOS-DATA partition type, the same way round. -const TOYOS_DATA: gpt::partition_types::Type = gpt::partition_types::Type { - guid: toyos_gpt::Guid::TOYOS_DATA_TEXT, - os: gpt::partition_types::OperatingSystem::None, -}; - -/// `94464329-…`, the slot table's partition type. -const TOYOS_SLOTS: gpt::partition_types::Type = gpt::partition_types::Type { - guid: toyos_gpt::Guid::TOYOS_SLOTS_TEXT, - os: gpt::partition_types::OperatingSystem::None, -}; - -/// `037719D7-…`, a slot's FAT volume. -const TOYOS_BOOT: gpt::partition_types::Type = gpt::partition_types::Type { - guid: toyos_gpt::Guid::TOYOS_BOOT_TEXT, - os: gpt::partition_types::OperatingSystem::None, -}; - /// Lay a table on the disk at `path`, already `len` bytes long, carrying one /// TOYOS-DATA partition, stamp the designation at its first block, and answer /// where it landed. The table and the stamp are all this writes, so a sparse @@ -698,26 +654,26 @@ pub fn designate_data_disk(path: &Path, len: u64) -> (u64, u64) { else { panic!("a {len}-byte disk has no room for a DATA partition"); }; - let file = std::fs::OpenOptions::new() + let mut file = std::fs::OpenOptions::new() .read(true) .write(true) .open(path) .unwrap_or_else(|e| panic!("open {} to partition it: {e}", path.display())); - let (mut device, starts) = lay(Box::new(file), len, &[data_row(uuid::Uuid::new_v4(), data_bytes)]); - designate(&mut device, starts[0], data_bytes); - device.flush().expect("flush the data disk"); + let starts = lay(&mut file, len, &[data_row(crate::gptwrite::random_guid(), data_bytes)]); + write_at(&mut file, starts[0], &designation(data_bytes / SECTOR as u64), "DATA partition's designation"); (starts[0], data_bytes) } /// A TOYOS-DATA partition's entry. -fn data_row(guid: uuid::Uuid, len: u64) -> Row { - Row { what: "DATA partition".into(), name: "ToyOS data".into(), kind: TOYOS_DATA, guid, len } +fn data_row(guid: Guid, len: u64) -> Row { + Row { what: "DATA partition".into(), name: "ToyOS data".into(), kind: Guid::TOYOS_DATA, guid, len } } -/// Stamp the designation on the `bytes`-long partition at byte `at` of `device`. -fn designate(device: &mut gpt::DiskDeviceObject<'_>, at: u64, bytes: u64) { - device.seek(SeekFrom::Start(at)).expect("seek to the data partition"); - device.write_all(&designation(bytes / SECTOR as u64)).expect("stamp the data partition"); +/// Write `bytes` at byte `at` of `disk`; `what` they are is what a refusal names. +fn write_at(disk: &mut (impl Write + Seek), at: u64, bytes: &[u8], what: &str) { + disk.seek(SeekFrom::Start(at)) + .and_then(|_| disk.write_all(bytes)) + .unwrap_or_else(|e| panic!("write the {what} at byte {at}: {e}")); } /// What a machine's own disk holds once the boot image `image` is installed @@ -730,29 +686,21 @@ fn designate(device: &mut gpt::DiskDeviceObject<'_>, at: u64, bytes: u64) { /// blocks that are not zero are written, so the file costs the host what the /// image does. pub fn install(image: &[u8], disk: &Path, data_bytes: u64) { - use gpt::disk::LogicalBlockSize::Lb512; let mut listed = [None; 16]; let scan = toyos_gpt::list(&mut ImageSectors(image), &mut listed) .unwrap_or_else(|e| panic!("the image to install carries no partition table: {e:?}")); assert!(scan.matched as usize <= listed.len(), "the image to install carries {} partitions", scan.matched); - // The names, which the reader above does not read; the types, which this one does not keep. - let mut names: BTreeMap = gpt::header::read_header_from_arbitrary_device(&mut Cursor::new(image), Lb512) - .and_then(|header| gpt::partition::file_read_partitions(&mut Cursor::new(image), &header, Lb512)) - .unwrap_or_else(|e| panic!("the image to install carries no readable partition entries: {e}")) - .into_values() - .map(|part| (part.part_guid, part.name)) - .collect(); let carried: Vec<(Row, &[u8])> = listed .iter() .flatten() .map(|entry| { let part = entry.unwrap_or_else(|unplaced| panic!("the image to install states no partition: {unplaced:?}")); - let guid = uuid::Uuid::from_bytes_le(part.unique_guid().0); - let kind = [gpt::partition_types::EFI, gpt::partition_types::BASIC, TOYOS_SLOTS, TOYOS_BOOT, TOYOS_ROOT] - .into_iter() - .find(|kind| uuid::Uuid::parse_str(kind.guid).is_ok_and(|text| text.to_bytes_le() == part.type_guid().0)) - .unwrap_or_else(|| panic!("partition {guid} is of type {}, which no boot image carries", part.type_guid())); - let name = names.remove(&guid).unwrap_or_else(|| panic!("partition {guid} has no name")); + let (guid, kind) = (part.unique_guid(), part.type_guid()); + assert!( + [Guid::EFI_SYSTEM, Guid::MICROSOFT_BASIC, Guid::TOYOS_SLOTS, Guid::TOYOS_BOOT, Guid::TOYOS_ROOT].contains(&kind), + "partition {guid} is of type {kind}, which no boot image carries" + ); + let name = name_of(&part); let at = part.first_lba() as usize * LBA as usize; let bytes = &image[at..at + part.lba_count().get() as usize * LBA as usize]; (Row { what: format!("installed {name}"), name, kind, guid, len: bytes.len() as u64 }, bytes) @@ -760,10 +708,10 @@ pub fn install(image: &[u8], disk: &Path, data_bytes: u64) { .collect(); let (mut rows, volumes): (Vec, Vec<&[u8]>) = carried.into_iter().unzip(); - rows.insert(0, data_row(uuid::Uuid::new_v4(), data_bytes)); + rows.insert(0, data_row(crate::gptwrite::random_guid(), data_bytes)); let total = disk_bytes(rows.iter().map(|row| row.len)); - let file = std::fs::OpenOptions::new() + let mut file = std::fs::OpenOptions::new() .read(true) .write(true) .create_new(true) @@ -771,18 +719,18 @@ pub fn install(image: &[u8], disk: &Path, data_bytes: u64) { .unwrap_or_else(|e| panic!("{} is not installed on, being no file this install made: {e}", disk.display())); file.set_len(total).unwrap_or_else(|e| panic!("size {} to {total} bytes: {e}", disk.display())); - let (mut device, starts) = lay(Box::new(file), total, &rows); - designate(&mut device, starts[0], data_bytes); + let starts = lay(&mut file, total, &rows); + write_at(&mut file, starts[0], &designation(data_bytes / SECTOR as u64), "DATA partition's designation"); for ((row, volume), start) in rows[1..].iter().zip(volumes).zip(&starts[1..]) { for (i, block) in volume.chunks(PARTITION_ALIGN).enumerate().filter(|(_, block)| block.iter().any(|byte| *byte != 0)) { - let at = start + (i * PARTITION_ALIGN) as u64; - device - .seek(SeekFrom::Start(at)) - .and_then(|_| device.write_all(block)) - .unwrap_or_else(|e| panic!("write the {} at byte {at}: {e}", row.what)); + write_at(&mut file, start + (i * PARTITION_ALIGN) as u64, block, &row.what); } } - device.flush().unwrap_or_else(|e| panic!("flush {}: {e}", disk.display())); +} + +/// The name a table this build wrote gives `part`. +fn name_of(part: &toyos_gpt::Partition) -> String { + String::from_utf16(part.name()).unwrap_or_else(|e| panic!("partition {}'s name is no UTF-16: {e}", part.unique_guid())) } /// Block 0 of a volume: the magic and its block count. @@ -821,8 +769,8 @@ impl toyos_gpt::Sectors for FileSectors<'_> { struct Part { what: &'static str, name: &'static str, - kind: gpt::partition_types::Type, - guid: uuid::Uuid, + kind: Guid, + guid: Guid, bytes: Vec, len: u64, judge: Option, @@ -830,14 +778,7 @@ struct Part { impl Part { /// A partition exactly as long as its bytes. - fn full( - what: &'static str, - name: &'static str, - kind: gpt::partition_types::Type, - guid: uuid::Uuid, - bytes: Vec, - judge: Option, - ) -> Self { + fn full(what: &'static str, name: &'static str, kind: Guid, guid: Guid, bytes: Vec, judge: Option) -> Self { let len = bytes.len() as u64; Self { what, name, kind, guid, bytes, len, judge } } @@ -848,8 +789,8 @@ impl Part { struct Row { what: String, name: String, - kind: gpt::partition_types::Type, - guid: uuid::Uuid, + kind: Guid, + guid: Guid, len: u64, } @@ -861,87 +802,40 @@ fn disk_bytes(lens: impl Iterator) -> u64 { round_up_sectors(align_up(end, PARTITION_ALIGN) + PARTITION_ALIGN) as u64 } -/// Write a protective MBR and a GPT on `device`, `total` bytes long, placing -/// `rows` in their order, each on a [`PARTITION_ALIGN`] boundary and under -/// its own GUID: the device, and the byte each row starts at. -fn lay<'a>( - mut device: gpt::DiskDeviceObject<'a>, - total: u64, - rows: &[Row], -) -> (gpt::DiskDeviceObject<'a>, Vec) { - assert_eq!(total % 512, 0, "image must be a whole number of 512-byte sectors to be flashable"); - let mbr = gpt::mbr::ProtectiveMBR::with_lb_size(u32::try_from(total / 512 - 1).unwrap_or(0xFF_FF_FF_FF)); - mbr.overwrite_lba0(&mut device).expect("failed to write MBR"); - - let mut gdisk = gpt::GptConfig::default() - .initialized(false) - .writable(true) - .logical_block_size(gpt::disk::LogicalBlockSize::Lb512) - .create_from_device(device, None) - .expect("failed to create GPT disk"); - - gdisk - .update_partitions(BTreeMap::::new()) - .expect("failed to initialize partition table"); - - let align = Some((PARTITION_ALIGN / 512) as u64); - let ids: Vec = rows +/// Write a protective MBR and a GPT on `disk`, `total` bytes long, placing +/// `rows` in their order, each on the first [`PARTITION_ALIGN`] boundary after +/// the last and under its own GUID: the byte each row starts at. +fn lay(disk: &mut (impl Write + Seek), total: u64, rows: &[Row]) -> Vec { + assert_eq!(total % u64::from(LBA), 0, "image must be a whole number of {LBA}-byte sectors to be flashable"); + let mut end = 0; + let starts: Vec = rows .iter() .map(|row| { - gdisk - .add_partition(&row.name, row.len, row.kind.clone(), 0, align) - .unwrap_or_else(|e| panic!("failed to add the {}: {e}", row.what)) + // The invariant [`PARTITION_ALIGN`] exists for: the kernel mounts + // several at once over one 4 KiB block device, and a device block + // belonging to two volumes would be cached twice. + assert_eq!(row.len % SECTOR as u64, 0, "the {} is {} bytes, not whole {SECTOR}-byte blocks", row.what, row.len); + let start = align_up(end.max(1), PARTITION_ALIGN) as u64; + end = (start + row.len) as usize; + start }) .collect(); - - // The GUID `add_partition` drew for each is discarded for the one its row - // names: the log's is written on the ESP, every slot partition's in the - // slot table, and the ESP's own because a firmware boot entry names a - // partition by GUID and every name is drawn once. - let mut table = gdisk.partitions().clone(); - for (row, id) in rows.iter().zip(&ids) { - table.get_mut(id).expect("a partition that was just added").part_guid = row.guid; - } - gdisk.update_partitions(table).expect("failed to stamp the partitions' unique GUIDs"); - - let placed: Vec<(&str, u64, u64)> = rows + let lba = u64::from(LBA); + let entries: Vec> = rows .iter() - .zip(&ids) - .map(|(row, id)| { - let entry = gdisk.partitions().get(id).expect("a partition that was just added"); - let start = entry.bytes_start(gpt::disk::LogicalBlockSize::Lb512).expect("a partition's start"); - let len = entry.bytes_len(gpt::disk::LogicalBlockSize::Lb512).expect("a partition's length"); - // A slot's ROOT is read for exactly its signed length, and a - // partition the table made any other length than asked is one - // whose contents nothing here placed. - assert_eq!(len, row.len, "the table gives the {} {len} bytes for a {}-byte partition", row.what, row.len); - (row.what.as_str(), start, row.len) + .zip(&starts) + .map(|(row, start)| crate::gptwrite::Entry { + kind: row.kind, + unique: row.guid, + first_lba: start / lba, + last_lba: (start + row.len) / lba - 1, + name: &row.name, }) .collect(); - - // The invariant [`PARTITION_ALIGN`] exists for, checked rather than - // assumed: the kernel mounts several at once over one 4 KiB block device, - // and a device block belonging to two volumes would be cached twice. - let sector = SECTOR as u64; - for (what, start, len) in &placed { - assert_eq!(start % sector, 0, "the {what} starts at byte {start}, off a {SECTOR}-byte block"); - assert_eq!(len % sector, 0, "the {what} is {len} bytes, not whole {SECTOR}-byte blocks"); - } - let mut by_start = placed.clone(); - by_start.sort_by_key(|(_, start, _)| *start); - for (before, after) in by_start.iter().zip(&by_start[1..]) { - assert!( - before.1 + before.2 <= after.1, - "the {} runs to {} and the {} starts at {}", - before.0, - before.1 + before.2, - after.0, - after.1 - ); - } - - let device = gdisk.write().expect("failed to write GPT"); - (device, placed.iter().map(|(_, start, _)| *start).collect()) + let table = crate::gptwrite::table(total / lba, crate::gptwrite::random_guid(), &entries); + write_at(disk, 0, &table.primary, "partition table"); + write_at(disk, total - table.backup.len() as u64, &table.backup, "backup partition table"); + starts } fn create_gpt_disk(parts: &[Part]) -> Vec { @@ -950,32 +844,24 @@ fn create_gpt_disk(parts: &[Part]) -> Vec { } let rows: Vec = parts .iter() - .map(|part| Row { what: part.what.into(), name: part.name.into(), kind: part.kind.clone(), guid: part.guid, len: part.len }) + .map(|part| Row { what: part.what.into(), name: part.name.into(), kind: part.kind, guid: part.guid, len: part.len }) .collect(); let total_size = disk_bytes(rows.iter().map(|row| row.len)); let mut disk = vec![0u8; total_size as usize]; - let (mut disk_device, starts) = lay(Box::new(Cursor::new(&mut disk)), total_size, &rows); - - disk_device.seek(std::io::SeekFrom::Start(0)).expect("failed to seek"); - let mut final_bytes = vec![0u8; total_size as usize]; - disk_device.read_exact(&mut final_bytes).expect("failed to read disk"); - + let starts = lay(&mut Cursor::new(&mut disk), total_size, &rows); for (part, start) in parts.iter().zip(&starts) { let start = *start as usize; - final_bytes[start..start + part.bytes.len()].copy_from_slice(&part.bytes); + disk[start..start + part.bytes.len()].copy_from_slice(&part.bytes); } - let judged: Vec<(&str, toyos_gpt::Guid, Volume)> = parts - .iter() - .filter_map(|part| part.judge.map(|kind| (part.what, toyos_gpt::Guid(part.guid.to_bytes_le()), kind))) - .collect(); - certify(&final_bytes, &judged).unwrap_or_else(|refusal| panic!("{refusal}")); - - final_bytes + let judged: Vec<(&str, Guid, Volume)> = + parts.iter().filter_map(|part| part.judge.map(|kind| (part.what, part.guid, kind))).collect(); + certify(&disk, &judged).unwrap_or_else(|refusal| panic!("{refusal}")); + disk } /// A disk image as logical blocks, so a GPT parser can read it without a file. -struct ImageSectors<'a>(&'a [u8]); +pub(crate) struct ImageSectors<'a>(pub(crate) &'a [u8]); impl toyos_gpt::Sectors for ImageSectors<'_> { fn lba_bytes(&self) -> u32 { @@ -1113,7 +999,7 @@ mod tests { let key = key(); let s = sections(b"kernel", &tiny_root(), "", signing(&key)); for (what, volume) in [ - ("ESP", create_esp_volume(Arch::X86_64, b"bootloader", uuid::Uuid::new_v4())), + ("ESP", create_esp_volume(Arch::X86_64, b"bootloader", crate::gptwrite::random_guid())), ("slot volume", create_slot_volume(Some(&s), FAT32_MIN_BYTES)), ("empty slot volume", create_slot_volume(None, FAT32_MIN_BYTES)), ("log volume", create_log_volume()), @@ -1200,7 +1086,7 @@ mod tests { #[test] fn the_esp_and_a_slot_carry_what_the_bootloader_looks_for() { assert_eq!( - files_of(create_esp_volume(Arch::X86_64, b"bootloader", uuid::Uuid::new_v4())), + files_of(create_esp_volume(Arch::X86_64, b"bootloader", crate::gptwrite::random_guid())), ["EFI/BOOT/BOOTx64.EFI", "toyos/log.guid"] ); let key = key(); @@ -1257,9 +1143,11 @@ mod tests { assert_eq!(parts.signed, &signed); } - /// Every partition of `image` is on `disk` under its GUID with its bytes, - /// found by the reader the kernel and `diskserver` use. - fn carries_every_partition_of(disk: &mut std::fs::File, image: &[u8]) { + /// Every partition of `image` is on `disk` under its GUID, its name and with + /// its bytes, found by the reader the kernel and `diskserver` use: the + /// names, in the image's order. + fn carries_every_partition_of(disk: &mut std::fs::File, image: &[u8]) -> Vec { + let mut names = Vec::new(); let mut listed = [None; 16]; toyos_gpt::list(&mut ImageSectors(image), &mut listed).expect("the image's table"); let parts: Vec = listed.iter().flatten().map(|entry| entry.expect("a placed entry")).collect(); @@ -1275,7 +1163,10 @@ mod tests { .and_then(|_| disk.read_exact(&mut installed)) .expect("read it back"); assert!(installed == image[at..at + len], "{} was installed as other bytes", part.unique_guid()); + assert_eq!(name_of(&on_disk), name_of(&part), "{}", part.unique_guid()); + names.push(name_of(&part)); } + names } /// An install puts a designated DATA first and the image after it, on a @@ -1291,7 +1182,10 @@ mod tests { install(&image, &path, DATA); let mut disk = std::fs::File::open(&path).expect("open the disk"); - carries_every_partition_of(&mut disk, &image); + assert_eq!( + carries_every_partition_of(&mut disk, &image), + ["EFI System", "ToyOS slots", "ToyOS log", "ToyOS slot A", "ToyOS root A"] + ); let data = only_partition(&mut FileSectors(&mut disk), toyos_gpt::Guid::TOYOS_DATA).expect("one DATA partition"); assert_eq!( (data.first_lba() * u64::from(LBA), data.lba_count().get() * u64::from(LBA)), @@ -1363,9 +1257,9 @@ mod tests { /// **The independent oracle for ROOT**, asked of the finished image by /// readers that did not write it: `toyos-gpt` finds the partition by type - /// where the `gpt` crate placed it, `bcachefs`'s mount-and-read path lists + /// where the table placed it, `bcachefs`'s mount-and-read path lists /// what its format-and-write path put there, and `toyos-fat32` reads the - /// boot parameter off the ESP that `fatfs` formatted. + /// boot parameter off the ESP. /// /// Every name, size, content hash and symlink target against the list /// handed to [`create_root_image`] — a listing that merely parsed would diff --git a/src/lib.rs b/src/lib.rs index 45229856279..66c74b7592b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,9 +12,11 @@ pub mod cicache; pub mod clang; pub mod clippy; pub mod compiler; +pub mod fatformat; pub mod firmware; #[cfg(test)] pub mod gitfixture; +pub mod gptwrite; pub mod flags; pub mod hostws; pub mod identity; diff --git a/src/licence.rs b/src/licence.rs index 6bfe3e1e1fd..4fe48462c25 100644 --- a/src/licence.rs +++ b/src/licence.rs @@ -467,12 +467,6 @@ pub const COMMITTED_FILES: &[(&str, &str, &str, Terms)] = &[ "NOTICE", Terms::Spdx("LicenseRef-GeneralUser-GS-2.0"), ), - ( - "assets/wallpaper.jpg", - "b6f0c89bf966cfb458333b280614f0c7723615e42e340b9d43a760a64fe05976", - "ours: `cargo run -- --regen-wallpaper` writes it from src/wallpaper.rs", - Terms::Spdx("MIT OR Apache-2.0"), - ), ( "doom.jpg", "ae22f71dc732580bd4f789937c9fe564969029413fc2092f27bdae8d1ceaf8e3", diff --git a/src/main.rs b/src/main.rs index fb9ccd2ed8a..d44de033d5f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -189,11 +189,6 @@ fn main() { return; } - if asked(&flags::REGEN_WALLPAPER) { - toyos_build::wallpaper::regen(&root); - return; - } - if let Some(bank) = CARGO_RUN.value(&args, &flags::REGEN_SOUNDFONT) { toyos_build::soundfont::regen(&root, Path::new(bank)); return; diff --git a/src/metal.rs b/src/metal.rs index c2abba366bf..3d4af51a98d 100644 --- a/src/metal.rs +++ b/src/metal.rs @@ -2681,38 +2681,27 @@ mod tests { /// A disk carrying `parts` in order, so `admit`'s refusals are exercised on /// tables rather than on prose about tables. - fn disk(parts: &[(&str, &gpt::partition_types::Type)]) -> Vec { - use std::io::Cursor; - const TOTAL: usize = 16 * 1024 * 1024; - let mut bytes = vec![0u8; TOTAL]; - let mut cursor = Cursor::new(&mut bytes); - gpt::mbr::ProtectiveMBR::with_lb_size((TOTAL / 512 - 1) as u32) - .overwrite_lba0(&mut cursor) - .expect("the protective MBR"); - let mut gdisk = gpt::GptConfig::default() - .initialized(false) - .writable(true) - .logical_block_size(gpt::disk::LogicalBlockSize::Lb512) - .create_from_device(Box::new(cursor), None) - .expect("a table"); - gdisk.update_partitions(std::collections::BTreeMap::new()).expect("an empty table"); - for (name, kind) in parts { - gdisk - .add_partition(name, 256 * 1024, (*kind).clone(), 0, Some(2048)) - .expect("a partition"); - } - let mut device = gdisk.write().expect("the table"); - device.seek(SeekFrom::Start(0)).expect("rewind"); - let mut out = vec![0u8; TOTAL]; - device.read_exact(&mut out).expect("read back"); + fn disk(parts: &[(&str, toyos_gpt::Guid)]) -> Vec { + const TOTAL: u64 = 16 * 1024 * 1024; + const ALIGN: u64 = 2048; + let entries: Vec> = (1..) + .zip(parts) + .map(|(i, (name, kind))| crate::gptwrite::Entry { + kind: *kind, + unique: crate::gptwrite::random_guid(), + first_lba: i * ALIGN, + last_lba: i * ALIGN + 511, + name, + }) + .collect(); + let table = crate::gptwrite::table(TOTAL / u64::from(LBA), crate::gptwrite::random_guid(), &entries); + let mut out = vec![0u8; TOTAL as usize]; + out[..table.primary.len()].copy_from_slice(&table.primary); + let back = out.len() - table.backup.len(); + out[back..].copy_from_slice(&table.backup); out } - const TOYOS_ROOT: gpt::partition_types::Type = gpt::partition_types::Type { - guid: toyos_gpt::Guid::TOYOS_ROOT_TEXT, - os: gpt::partition_types::OperatingSystem::None, - }; - #[test] fn an_image_admits_only_the_table_the_installed_rule_names() { let dir = toyos_tmpdir::TempDir::new("metal"); @@ -2722,8 +2711,7 @@ mod tests { std::fs::write(&at, bytes).expect("write"); at }; - let esp = gpt::partition_types::EFI; - let basic = gpt::partition_types::BASIC; + let (esp, basic, root) = (toyos_gpt::Guid::EFI_SYSTEM, toyos_gpt::Guid::MICROSOFT_BASIC, toyos_gpt::Guid::TOYOS_ROOT); assert_eq!( admit(&write("ragged.img", &vec![0u8; 1000]), &t), @@ -2743,24 +2731,24 @@ mod tests { Err(Refusal::BackupHeader { .. }) )); - let two = disk(&[("a", &esp), ("b", &esp), ("log", &basic), ("root", &TOYOS_ROOT)]); + let two = disk(&[("a", esp), ("b", esp), ("log", basic), ("root", root)]); assert_eq!( admit(&write("two-esps.img", &two), &t), Err(Refusal::Partitions { what: "ESP", matched: 2 }) ); - let no_log = disk(&[("esp", &esp), ("root", &TOYOS_ROOT)]); + let no_log = disk(&[("esp", esp), ("root", root)]); assert_eq!( admit(&write("no-log.img", &no_log), &t), Err(Refusal::Partitions { what: "TOYOS-LOG", matched: 0 }) ); // The log where the installed rule does not name it: p2, not p3. - let moved = disk(&[("esp", &esp), ("log", &basic), ("root", &TOYOS_ROOT)]); + let moved = disk(&[("esp", esp), ("log", basic), ("root", root)]); assert_eq!( admit(&write("moved-log.img", &moved), &t), Err(Refusal::PartitionIndex { what: "TOYOS-LOG", want: 3, got: 2 }) ); // And the shape `src/image.rs` writes, which is the one admitted. - let built = disk(&[("esp", &esp), ("root", &TOYOS_ROOT), ("log", &basic)]); + let built = disk(&[("esp", esp), ("root", root), ("log", basic)]); let ok = admit(&write("built.img", &built), &t).expect("the built shape is admitted"); assert_eq!((ok.esp.index, ok.log.index), (1, 3)); assert_eq!(ok.bytes % u64::from(LBA), 0); diff --git a/src/wallpaper.rs b/src/wallpaper.rs index 90cba2a336f..41a6bb94651 100644 --- a/src/wallpaper.rs +++ b/src/wallpaper.rs @@ -1,27 +1,20 @@ -//! The desktop background, written down as a description instead of fetched. -//! -//! `assets/wallpaper.jpg` used to be a file of unknown origin — an aggregator -//! upload naming no author and no copyright holder, under a "wallpaper use -//! only" restriction nobody on that page had the standing to grant. What -//! replaces it is the output of [`draw`], so provenance is answered by source -//! anyone can re-run rather than by a paragraph in `NOTICE`. +//! The desktop background, drawn by the build: what the compositor reads as +//! `/system/share/wallpaper.rgb` is [`draw`]'s output, byte for byte, so its +//! provenance is source anyone can re-run and no file stands between the two. //! //! The picture is a lit horizon: a night sky, a glow behind it, and three -//! ridges receding into haze. It is nothing but the constants below — change -//! one, run `cargo run -- --regen-wallpaper`, and the committed artifact moves -//! with it. +//! ridges receding into haze. It is nothing but the constants below. //! //! All compositing is in **linear light**, converted to sRGB once per pixel at //! the end. Mixing two colours in sRGB mixes two numbers that were never linear //! to begin with, which is what sends a naive gradient grey through its middle. +//! +//! **The bytes are the same on every host**, because ROOT's UUID is derived +//! from them: every transcendental is `libm`'s, never the host C library's, and +//! `the_wallpaper_is_the_same_on_every_host` pins their digest. -use std::path::Path; - -use image::codecs::jpeg::JpegEncoder; -use image::ExtendedColorType; - -/// Where the artifact lives, relative to the repository root. -pub const WALLPAPER_PATH: &str = "assets/wallpaper.jpg"; +/// The wallpaper's name in ROOT's `share/`. +pub const NAME: &str = "wallpaper.rgb"; /// Drawn at the panel this project targets, the T14's 1920x1080. The /// compositor scales whatever it is handed, so this is the size at which it @@ -29,20 +22,6 @@ pub const WALLPAPER_PATH: &str = "assets/wallpaper.jpg"; pub const WIDTH: u32 = 1920; pub const HEIGHT: u32 = 1080; -/// **The quantization table is why this is up here and not at the usual 90.** -/// libjpeg's scaling leaves every entry of both tables at 1 by 99, and `image`'s -/// encoder never subsamples chroma (`h: 1, v: 1` on all three components), so -/// the round trip is the DCT's own rounding and nothing else. Anywhere below -/// that the DC coefficient alone is quantized in steps of two or three levels, -/// which lays an 8x8 staircase across exactly the smooth field this picture is -/// made of — and the same quantizer crushes the dither that was there to stop -/// one. `the_wallpaper_neither_bands_nor_blocks` is that stated as a bound. -/// -/// Not 100, which is the same file size to within 0.2% and worse on every other -/// measure. Both readings come from a sweep of the encoder across the quality -/// range at this picture's own dimensions. -pub const QUALITY: u8 = 99; - /// The sky, top to horizon, as sRGB. /// /// Both nearly black on purpose. The glow is what the eye sees; a sky bright @@ -168,7 +147,7 @@ const RIDGE_OCTAVES: u32 = 4; /// span the screen. /// /// This is the difference between a sky and a diagram of three ellipses. Both -/// octaves are slow: anything faster costs JPEG bits and reads as texture. +/// octaves are slow: anything faster reads as texture. const DRIFT: f32 = 0.42; const DRIFT_CELLS: f32 = 2.4; @@ -184,7 +163,7 @@ const VIGNETTE_OUT: f32 = 1.25; /// This is dither and not decoration. Eight bits a channel over a field this /// smooth puts a contour every few dozen rows; a triangular perturbation of /// this size turns the contour into noise the eye integrates away, and it is -/// what lets `the_wallpaper_neither_bands_nor_blocks` hold. +/// what lets `the_wallpaper_does_not_band` hold. const GRAIN: f32 = 1.6; fn srgb_to_linear(c: u8) -> f32 { @@ -192,7 +171,7 @@ fn srgb_to_linear(c: u8) -> f32 { if s <= 0.040_45 { s / 12.92 } else { - ((s + 0.055) / 1.055).powf(2.4) + libm::powf((s + 0.055) / 1.055, 2.4) } } @@ -201,7 +180,7 @@ fn linear_to_srgb(v: f32) -> f32 { if v <= 0.003_130_8 { v * 12.92 } else { - 1.055 * v.powf(1.0 / 2.4) - 0.055 + 1.055 * libm::powf(v, 1.0 / 2.4) - 0.055 } } @@ -315,7 +294,7 @@ pub fn draw(width: u32, height: u32) -> Vec { let dx = (u - light.x) / light.rx; let dy = (v - light.y) / light.ry; let warped = (dx * dx + dy * dy) * (1.0 + DRIFT * drift(u, v, light.seed)); - let fall = (-warped).exp() * light.gain; + let fall = libm::expf(-warped) * light.gain; for c in 0..3 { rgb[c] += color[c] * fall; } @@ -353,25 +332,17 @@ pub fn draw(width: u32, height: u32) -> Vec { out } -/// [`draw`] at [`WIDTH`]x[`HEIGHT`], JPEG-encoded at [`QUALITY`]. -pub fn encoded() -> Vec { - let rgb = draw(WIDTH, HEIGHT); - let mut jpeg = Vec::new(); - JpegEncoder::new_with_quality(&mut jpeg, QUALITY) - .encode(&rgb, WIDTH, HEIGHT, ExtendedColorType::Rgb8) - .expect("encode the wallpaper"); - jpeg +/// The file ROOT carries as `share/`[`NAME`]: the width and the height as +/// little-endian `u32`s, then [`draw`]'s RGB triples at that size, unencoded — +/// a lossy codec here would throw away the dither first. +pub fn rgb() -> Vec { + let mut file = Vec::with_capacity(8 + WIDTH as usize * HEIGHT as usize * 3); + file.extend(WIDTH.to_le_bytes()); + file.extend(HEIGHT.to_le_bytes()); + file.extend(draw(WIDTH, HEIGHT)); + file } -/// Rewrite `assets/wallpaper.jpg` from the constants in this file. -pub fn regen(root: &Path) { - let jpeg = encoded(); - let path = root.join(WALLPAPER_PATH); - std::fs::write(&path, &jpeg).unwrap_or_else(|e| panic!("write {}: {e}", path.display())); - println!("wrote {} ({} bytes)", path.display(), jpeg.len()); -} - - #[cfg(test)] mod tests { use super::*; @@ -386,15 +357,6 @@ mod tests { /// almost four times it. const MAX_FLAT_RUN: usize = 40; - /// How much more a pixel may differ from the one above it across an 8x8 - /// block boundary than inside one. - /// - /// JPEG's characteristic artifact *is* this ratio going up — the blocks are - /// quantized independently, so what a person sees as blocking is a step - /// that appears only where two of them meet. A picture with none of it - /// sits at 1.0, and the value this bound separates from is a doubling. - const MAX_BLOCKING: f64 = 1.25; - fn longest_column_run(rgb: &[u8], width: usize, height: usize, channel: usize) -> usize { let mut worst = 0; for x in 0..width { @@ -409,71 +371,30 @@ mod tests { worst } - fn blocking(rgb: &[u8], width: usize, height: usize) -> f64 { - let (mut across, mut across_n) = (0.0f64, 0u64); - let (mut within, mut within_n) = (0.0f64, 0u64); - for y in 1..height { - for x in 0..width { - for c in 0..3 { - let step = (rgb[(y * width + x) * 3 + c] as i32 - - rgb[((y - 1) * width + x) * 3 + c] as i32) - .abs() as f64; - if y % 8 == 0 { - across += step; - across_n += 1; - } else { - within += step; - within_n += 1; - } - } - } - } - (across / across_n as f64) / (within / within_n as f64) + /// The file that ships is the one every host draws: a host whose floating + /// point answers differently reds here rather than naming a different ROOT. + #[test] + fn the_wallpaper_is_the_same_on_every_host() { + use sha2::{Digest, Sha256}; + let digest: String = Sha256::digest(rgb()).iter().map(|b| format!("{b:02x}")).collect(); + assert_eq!(digest, "675f4fbc92985dc5e60b899fd869fa8e574b1147e2b1c9b42808a72bcfff1d91"); } - /// What ships is the decoded JPEG, so the decoded JPEG is what is measured. - /// - /// Asking either question of [`draw`]'s own output would pass at an encoder - /// setting that undoes both: the dither is the highest-frequency thing in - /// the picture and therefore the first thing a quantizer throws away, and - /// the staircase it was holding off comes back as blocking. Between the two - /// they pin the encoder as well as the drawing — nothing here can be - /// weakened without one of them saying so. + /// The file that ships, read the way the compositor reads it, does not band. #[test] - fn the_wallpaper_neither_bands_nor_blocks() { - let decoded = image::load_from_memory_with_format(&encoded(), image::ImageFormat::Jpeg) - .expect("decode what we just encoded") - .to_rgb8(); - let (w, h) = (decoded.width() as usize, decoded.height() as usize); + fn the_wallpaper_does_not_band() { + let file = rgb(); + let (head, pixels) = file.split_at(8); + let w = u32::from_le_bytes(head[..4].try_into().unwrap()) as usize; + let h = u32::from_le_bytes(head[4..].try_into().unwrap()) as usize; + assert_eq!(pixels.len(), w * h * 3, "a {w}x{h} wallpaper of {} bytes", pixels.len()); for (channel, name) in ["red", "green", "blue"].iter().enumerate() { - let run = longest_column_run(decoded.as_raw(), w, h, channel); + let run = longest_column_run(pixels, w, h, channel); assert!( run <= MAX_FLAT_RUN, "{run} rows of one {name} value down a column, over a bound of \ {MAX_FLAT_RUN}: this wallpaper bands" ); } - let blocking = blocking(decoded.as_raw(), w, h); - assert!( - blocking <= MAX_BLOCKING, - "steps across the 8x8 block boundaries are {blocking:.2}x the steps inside them, \ - over a bound of {MAX_BLOCKING}: QUALITY = {QUALITY} is not enough for a field \ - this smooth" - ); - } - - /// The committed file is [`draw`]'s output and not a picture somebody put - /// there — the property this whole module exists to establish, and the - /// answer to the licence question that replacing it was about. - #[test] - fn the_committed_wallpaper_is_the_one_this_file_describes() { - let root = Path::new(env!("CARGO_MANIFEST_DIR")); - let on_disk = std::fs::read(root.join(WALLPAPER_PATH)).expect("read the wallpaper"); - assert!( - on_disk == encoded(), - "{WALLPAPER_PATH} is {} bytes and is not what this file draws — run \ - `cargo run -- --regen-wallpaper`", - on_disk.len() - ); } } diff --git a/toyos-gpt/src/guid.rs b/toyos-gpt/src/guid.rs index 0564ef80de6..dec1c9895e3 100644 --- a/toyos-gpt/src/guid.rs +++ b/toyos-gpt/src/guid.rs @@ -49,11 +49,6 @@ impl Guid { [0x95, 0x89, 0xA5, 0xC3, 0xCF, 0xD5, 0x55, 0xFD], ); - /// [`Guid::TOYOS_ROOT`]'s canonical text, for a host partition-table writer - /// that names a type by string. `toyos_root_text_is_the_type_guid` is what - /// holds the two spellings together. - pub const TOYOS_ROOT_TEXT: &'static str = "B350BC93-BB6A-4C5E-9589-A5C3CFD555FD"; - /// `064E3777-5076-4C71-8E07-90AD24CFE8D6` — the TOYOS-DATA partition type, /// the writable volume `/apps` and `/home` are two paths into. pub const TOYOS_DATA: Self = Self::from_fields( @@ -63,8 +58,6 @@ impl Guid { [0x8E, 0x07, 0x90, 0xAD, 0x24, 0xCF, 0xE8, 0xD6], ); - pub const TOYOS_DATA_TEXT: &'static str = "064E3777-5076-4C71-8E07-90AD24CFE8D6"; - /// `94464329-E06E-4288-A9DA-7FC7154F5E92` — the slot table's partition /// type: which partitions make each slot, and which slot is marked /// (`toyos_update::slots`). A disk carries one. @@ -75,8 +68,6 @@ impl Guid { [0xA9, 0xDA, 0x7F, 0xC7, 0x15, 0x4F, 0x5E, 0x92], ); - pub const TOYOS_SLOTS_TEXT: &'static str = "94464329-E06E-4288-A9DA-7FC7154F5E92"; - /// `037719D7-DEA5-481A-AA07-6AF8BE6D51E2` — a slot's FAT partition: its /// kernel, its boot parameter and its signed header. A *type*, so it /// selects nothing; the slot table names each one by its unique GUID. @@ -87,8 +78,6 @@ impl Guid { [0xAA, 0x07, 0x6A, 0xF8, 0xBE, 0x6D, 0x51, 0xE2], ); - pub const TOYOS_BOOT_TEXT: &'static str = "037719D7-DEA5-481A-AA07-6AF8BE6D51E2"; - pub const fn from_fields(a: u32, b: u16, c: u16, d: [u8; 8]) -> Self { let a = a.to_le_bytes(); let b = b.to_le_bytes(); @@ -142,21 +131,6 @@ mod tests { assert_eq!(&text[..], b"C12A7328-F81F-11D2-BA4B-00A0C93EC93B"); } - /// The host writes the partition table by naming the type in text and the - /// kernel matches it as bytes; nothing else compares the two spellings. - #[test] - fn toyos_root_text_is_the_type_guid() { - let text = heapless_format(Guid::TOYOS_ROOT); - assert_eq!(&text[..], Guid::TOYOS_ROOT_TEXT.as_bytes()); - let text = heapless_format(Guid::TOYOS_DATA); - assert_eq!(&text[..], Guid::TOYOS_DATA_TEXT.as_bytes()); - let text = heapless_format(Guid::TOYOS_SLOTS); - assert_eq!(&text[..], Guid::TOYOS_SLOTS_TEXT.as_bytes()); - let text = heapless_format(Guid::TOYOS_BOOT); - assert_eq!(&text[..], Guid::TOYOS_BOOT_TEXT.as_bytes()); - assert_ne!(Guid::TOYOS_ROOT, Guid::TOYOS_DATA); - } - #[test] fn zero_is_zero() { assert!(Guid::ZERO.is_zero()); diff --git a/toyos-gpt/src/lib.rs b/toyos-gpt/src/lib.rs index bef84611d09..90ba80cb907 100644 --- a/toyos-gpt/src/lib.rs +++ b/toyos-gpt/src/lib.rs @@ -54,6 +54,11 @@ pub const MAX_ENTRY_ARRAY_BYTES: u64 = 128 * 1024; const MIN_HEADER_BYTES: u32 = 92; /// The smallest a partition entry may be, from the UEFI specification. const MIN_ENTRY_BYTES: u32 = 128; +/// UTF-16 units in an entry's name field, from byte 56 to the end of the +/// 128 bytes every entry has. +const NAME_UNITS: usize = 36; +/// An entry's name field whole, zero units included. +type Name = [u16; NAME_UNITS]; const HEADER_SIGNATURE: &[u8; 8] = b"EFI PART"; const HEADER_REVISION_1_0: u32 = 0x0001_0000; /// The MBR partition type that says "this disk is GPT, keep out". @@ -164,11 +169,12 @@ pub struct Partition { first_lba: u64, last_lba: u64, lba_count: NonZeroU64, + name: Name, } impl Partition { /// `stated`, if its blocks are a partition inside `header`'s usable range. - fn place(stated: Stated, header: &Header) -> Result { + fn place(stated: Stated, name: Name, header: &Header) -> Result { if stated.first < header.first_usable_lba || stated.last > header.last_usable_lba { return Err(stated); } @@ -185,6 +191,7 @@ impl Partition { first_lba: stated.first, last_lba: stated.last, lba_count, + name, }) } @@ -214,6 +221,12 @@ impl Partition { self.lba_count } + /// The entry's name, UTF-16 as the table states it, up to the first zero + /// unit: what it says is the caller's to decode. + pub fn name(&self) -> &[u16] { + self.name.split(|unit| *unit == 0).next().unwrap_or_default() + } + /// Whether this is an ESP *by type*. A sanity check for a log line and /// nothing more — the selection has already happened, by unique GUID. pub fn is_efi_system(&self) -> bool { @@ -455,13 +468,13 @@ fn scan_type_at( let mut slots = out.iter_mut(); // Meaningless unless `walk_entries` returns `Ok`: the array's CRC is // checked at the end of the walk, and an `Err` hands the caller nothing. - let used_entries = walk_entries(dev, &header, disk.lba, &mut |stated| { + let used_entries = walk_entries(dev, &header, disk.lba, &mut |stated, name| { if !keep(stated.type_guid) { return; } matched = matched.saturating_add(1); if let Some(slot) = slots.next() { - *slot = Some(Partition::place(stated, &header)); + *slot = Some(Partition::place(stated, name, &header)); } })?; @@ -483,10 +496,10 @@ fn locate_at( let header = parse_header(block, disk, header_lba)?; let (found, used_entries) = scan_entries(dev, &header, target, disk.lba)?; - let Some(stated) = found else { + let Some((stated, name)) = found else { return Err(GptError::NotFound { used_entries }); }; - let partition = Partition::place(stated, &header) + let partition = Partition::place(stated, name, &header) .map_err(|stated| GptError::PartitionRange { first: stated.first, last: stated.last })?; check_no_overlap(dev, &header, &partition, disk.lba)?; @@ -682,7 +695,7 @@ fn walk_entries( dev: &mut dyn Sectors, header: &Header, lba: LbaSize, - visit: &mut dyn FnMut(Stated), + visit: &mut dyn FnMut(Stated, Name), ) -> Result { let mut block: Block = [0; 4096]; let block = lba.of_block(&mut block); @@ -699,12 +712,12 @@ fn walk_entries( for entry in block.chunks_exact(header.entry_len) { let Some(index) = indices.next() else { break }; crc.update(entry); - let Some(stated) = Stated::decode(index, entry) else { + let (Some(stated), Some(name)) = (Stated::decode(index, entry), name(entry)) else { return Err(GptError::EntrySize(header.entry_bytes)); }; if !stated.type_guid.is_zero() { used = used.saturating_add(1); - visit(stated); + visit(stated, name); } } } @@ -735,17 +748,17 @@ fn scan_entries( header: &Header, target: Guid, lba: LbaSize, -) -> Result<(Option, u32), GptError> { - let mut found: Option = None; +) -> Result<(Option<(Stated, Name)>, u32), GptError> { + let mut found: Option<(Stated, Name)> = None; let mut duplicate: Option<(u32, u32)> = None; - let used = walk_entries(dev, header, lba, &mut |stated| { + let used = walk_entries(dev, header, lba, &mut |stated, name| { if stated.unique_guid != target { return; } match &found { - None => found = Some(stated), - Some(first) if duplicate.is_none() => duplicate = Some((first.index, stated.index)), + None => found = Some((stated, name)), + Some((first, _)) if duplicate.is_none() => duplicate = Some((first.index, stated.index)), Some(_) => {} } })?; @@ -769,7 +782,7 @@ fn check_no_overlap( lba: LbaSize, ) -> Result<(), GptError> { let mut overlap = None; - walk_entries(dev, header, lba, &mut |other| { + walk_entries(dev, header, lba, &mut |other, _| { if overlap.is_none() && other.index != matched.index && other.first <= other.last @@ -790,6 +803,16 @@ fn bytes(buf: &[u8], at: usize) -> Option<[u8; N]> { buf.get(at..)?.first_chunk::().copied() } +/// An entry's name field as UTF-16 units, `None` only where `entry` ends first. +fn name(entry: &[u8]) -> Option { + let field: [u8; 72] = bytes(entry, 56)?; + let mut units = [0; NAME_UNITS]; + for (unit, pair) in units.iter_mut().zip(field.as_chunks::<2>().0) { + *unit = u16::from_le_bytes(*pair); + } + Some(units) +} + fn le_u32(buf: &[u8], at: usize) -> Option { bytes(buf, at).map(u32::from_le_bytes) } diff --git a/toyos-gpt/tests/mutate.rs b/toyos-gpt/tests/mutate.rs index 84d68f18164..e8f5baf106a 100644 --- a/toyos-gpt/tests/mutate.rs +++ b/toyos-gpt/tests/mutate.rs @@ -29,6 +29,7 @@ struct Seen { first: u64, last: u64, count: u64, + name: Vec, } /// An entry the parser handed back as no partition. @@ -59,6 +60,7 @@ fn observe(img: &mut Image, targets: &[Guid]) -> (Result, Answ first: p.first_lba(), last: p.last_lba(), count: p.lba_count().get(), + name: p.name().to_vec(), }; let mut out = [None; 64]; let listed = toyos_gpt::list(img, &mut out).map(|scan| Listed { @@ -78,7 +80,8 @@ fn observe(img: &mut Image, targets: &[Guid]) -> (Result, Answ } fn is(e: &RawEntry, s: &Seen) -> bool { - (e.index, e.type_guid, e.unique, e.first, e.last) == (s.index, s.type_guid, s.unique, s.first, s.last) + let name: Vec = e.name.iter().copied().take_while(|unit| *unit != 0).collect(); + (e.index, e.type_guid, e.unique, e.first, e.last, &name) == (s.index, s.type_guid, s.unique, s.first, s.last, &s.name) } /// A partition's own invariant, against the copy it came from and the device. diff --git a/toyos-gpt/tests/oracle.rs b/toyos-gpt/tests/oracle.rs index 998c115ef6c..26c56f9afcd 100644 --- a/toyos-gpt/tests/oracle.rs +++ b/toyos-gpt/tests/oracle.rs @@ -25,14 +25,15 @@ const VALID: u64 = 2_000; const BENT: u64 = 4_000; const SEED: u64 = 0x6770_745F_6F72_636C; -/// One entry: index, type as text, unique GUID, first and last block. -type Row = (u32, String, Guid, u64, u64); +/// One entry: index, type as text, unique GUID, first and last block, name. +type Row = (u32, String, Guid, u64, u64, String); /// toyos-gpt's reading. struct Ours { disk: Guid, placed: BTreeMap, - unplaced: BTreeMap, + /// Unique GUID, first and last block. + unplaced: BTreeMap, } fn ours(img: &mut Image) -> Result { @@ -43,11 +44,11 @@ fn ours(img: &mut Image) -> Result { for entry in out.iter().flatten() { match entry { Ok(p) => { - let row = (p.index(), p.type_guid().to_string(), p.unique_guid(), p.first_lba(), p.last_lba()); + let row = (p.index(), p.type_guid().to_string(), p.unique_guid(), p.first_lba(), p.last_lba(), String::from_utf16_lossy(p.name())); read.placed.insert(p.index(), row); } Err(u) => { - read.unplaced.insert(u.index, (u.index, u.type_guid.to_string(), u.unique_guid, u.first, u.last)); + read.unplaced.insert(u.index, (u.unique_guid, u.first, u.last)); } } } @@ -80,7 +81,7 @@ fn theirs(img: &Image) -> Theirs { rows: parts .iter() .map(|(&key, p)| { - let row = (key - 1, p.part_type_guid.guid.to_string(), Guid(p.part_guid.to_bytes_le()), p.first_lba, p.last_lba); + let row = (key - 1, p.part_type_guid.guid.to_string(), Guid(p.part_guid.to_bytes_le()), p.first_lba, p.last_lba, p.name.clone()); (key - 1, row) }) .collect(), @@ -128,10 +129,10 @@ fn differ(layout: &Layout, img: &Image, ours: &Result, theirs: & match (o.placed.get(index), o.unplaced.get(index)) { (Some(mine), None) if mine == row => {} // A type only toyos-gpt names: gpt answers the zero GUID. - (Some(mine), None) if row.1 == unnamed && (&mine.0, &mine.2, mine.3, mine.4) == (&row.0, &row.2, row.3, row.4) => { + (Some(mine), None) if row.1 == unnamed && (&mine.0, &mine.2, mine.3, mine.4, &mine.5) == (&row.0, &row.2, row.3, row.4, &row.5) => { named = "a type gpt's own table does not name"; } - (None, Some(stated)) if (stated.2, stated.3, stated.4) == (row.2, row.3, row.4) => { + (None, Some(stated)) if *stated == (row.2, row.3, row.4) => { named = "an entry toyos-gpt places as no partition: gpt checks no range"; } // gpt counts an entry by any non-zero byte, toyos-gpt by its type GUID. diff --git a/toyos-gpt/tests/parse.rs b/toyos-gpt/tests/parse.rs index fbdbc1d9676..9fe8565964a 100644 --- a/toyos-gpt/tests/parse.rs +++ b/toyos-gpt/tests/parse.rs @@ -30,7 +30,7 @@ fn guid(n: u8) -> Guid { } fn entry(index: u32, type_guid: Guid, unique: Guid, first: u64, last: u64) -> RawEntry { - RawEntry { index, type_guid, unique, first, last } + RawEntry { index, type_guid, unique, first, last, name: [0; 36] } } /// The disk every test here breaks one thing of, `edit`ed, with no backup diff --git a/toyos-gpt/tests/table/mod.rs b/toyos-gpt/tests/table/mod.rs index 4d1c0ebd481..252caedf1af 100644 --- a/toyos-gpt/tests/table/mod.rs +++ b/toyos-gpt/tests/table/mod.rs @@ -44,6 +44,15 @@ impl Rng { b[0] |= 1; Guid(b) } + + /// A name field: up to 36 units of any non-zero value, the rest zero. + pub fn name(&mut self) -> [u16; 36] { + let mut name = [0; 36]; + for unit in name.iter_mut().take(self.below(37) as usize) { + *unit = self.range(1, 0xFFFF) as u16; + } + name + } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -53,6 +62,8 @@ pub struct RawEntry { pub unique: Guid, pub first: u64, pub last: u64, + /// The name field whole, as UTF-16 units. + pub name: [u16; 36], } /// One copy of the table, every field as the image will state it. @@ -136,7 +147,7 @@ pub fn valid(rng: &mut Rng, shape: &Shape<'_>) -> Layout { break; } floor = last + 1; - entries.push(RawEntry { index, type_guid: rng.pick(shape.types), unique: rng.guid(), first, last }); + entries.push(RawEntry { index, type_guid: rng.pick(shape.types), unique: rng.guid(), first, last, name: rng.name() }); } let primary = Table { @@ -218,7 +229,7 @@ fn mutate_entry(rng: &mut Rng, t: &mut Table, lba_count: u64) { let index = rng.below(u64::from(t.entry_count.clamp(1, 256))) as u32; t.entries.retain(|e| e.index != index); let (first, last) = (edge(rng, t, lba_count), edge(rng, t, lba_count)); - t.entries.push(RawEntry { index, type_guid: Guid::EFI_SYSTEM, unique: rng.guid(), first, last }); + t.entries.push(RawEntry { index, type_guid: Guid::EFI_SYSTEM, unique: rng.guid(), first, last, name: rng.name() }); return; } let k = rng.below(t.entries.len() as u64) as usize; @@ -280,6 +291,14 @@ pub fn image(layout: &Layout) -> Image { slot[16..32].copy_from_slice(&e.unique.0); slot[32..40].copy_from_slice(&e.first.to_le_bytes()); slot[40..48].copy_from_slice(&e.last.to_le_bytes()); + // A nameless entry writes its first 48 bytes alone, so an array laid + // over LBA 0 leaves the MBR's records as they are. + let name = at.and_then(|at| disk.get_mut(at + 56..at + 128)); + if let Some(name) = name.filter(|_| e.name != [0; 36]) { + for (pair, unit) in name.as_chunks_mut::<2>().0.iter_mut().zip(e.name) { + *pair = unit.to_le_bytes(); + } + } } } for &(header_lba, t) in &copies { @@ -393,6 +412,9 @@ impl OnDisk { unique: Guid(e[16..32].try_into().expect("16")), first: u64_at(e, 32), last: u64_at(e, 40), + name: e.get(56..128).map_or([0; 36], |field| { + std::array::from_fn(|i| u16::from_le_bytes([field[2 * i], field[2 * i + 1]])) + }), }) .filter(|e| !e.type_guid.is_zero()) .collect();