diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c62ab01 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,139 @@ +name: Build Nix Packages + +on: + push: + branches: [ main, master ] + tags: + - '[0-9]*.[0-9]*.[0-9]*' + pull_request: + branches: [ main, master ] + workflow_dispatch: + +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + - system: aarch64-linux + package: microhop + runs-on: ubuntu-latest + - system: aarch64-linux + package: microgen + runs-on: ubuntu-latest + - system: x86_64-linux + package: microgen + runs-on: ubuntu-latest + + runs-on: ${{ matrix.runs-on }} + name: Build ${{ matrix.package }} (${{ matrix.system }}) + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Nix + uses: cachix/install-nix-action@v27 + with: + extra_nix_config: | + experimental-features = nix-command flakes + extra-platforms = aarch64-linux x86_64-linux + + - name: Setup Cachix + uses: cachix/cachix-action@v15 + with: + name: microhop + authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' + skipPush: ${{ github.event_name == 'pull_request' }} + continue-on-error: true + + - name: Configure QEMU for cross-compilation + if: runner.os == 'Linux' && matrix.system == 'aarch64-linux' + run: | + sudo apt-get update -q + sudo apt-get install -y qemu-user-static binfmt-support + + - name: Build package + run: | + nix build .#packages.${{ matrix.system }}.${{ matrix.package }} -L + + - name: Check build output + run: | + ls -lah result/ + nix path-info .#packages.${{ matrix.system }}.${{ matrix.package }} + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.package }}-${{ matrix.system }} + path: result/ + retention-days: 7 + + release: + name: Create Release + if: startsWith(github.ref, 'refs/tags/') && contains(github.ref, '.') + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Extract version from tag + id: version + run: | + VERSION=${GITHUB_REF#refs/tags/} + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "tag=$VERSION" >> $GITHUB_OUTPUT + + - name: Extract changelog for version + id: changelog + run: | + VERSION="${{ steps.version.outputs.version }}" + + CHANGELOG=$(awk "/## \[$VERSION\]/,/## \[/" CHANGELOG.md | sed '1d;$d' | sed '/^$/d') + + if [ -z "$CHANGELOG" ]; then + CHANGELOG=$(awk "/## \[Unreleased\]/,/## \[/" CHANGELOG.md | sed '1d;$d' | sed '/^$/d') + fi + + if [ -z "$CHANGELOG" ]; then + CHANGELOG="Release version $VERSION" + fi + + echo "$CHANGELOG" > changelog_excerpt.txt + + echo "content<> $GITHUB_OUTPUT + echo "$CHANGELOG" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts/ + + - name: Prepare release assets + run: | + mkdir -p release-assets + cd artifacts + for dir in */; do + artifact_name="${dir%/}" + if [ -d "$dir/bin" ]; then + tar -czf "../release-assets/${artifact_name}.tar.gz" -C "$dir" . + fi + done + cd .. + ls -lah release-assets/ + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + name: Release ${{ steps.version.outputs.tag }} + body_path: changelog_excerpt.txt + files: release-assets/*.tar.gz + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 48f5779..e891796 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target /tmp /package +/result diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6b44dc8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,62 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.2.0] - 2026-01-24 +### Added +- Nix flake support for building packages + (support for `aarch64-linux` and `x86_64-linux` platforms) +- GitHub workflow for building Nix packages across multiple platforms + (darwin not supported due to 'nixbld' user cleanup issue) +- Add support for `overlayfs` to support read-only rootfs +- Handle missing kernel modules gracefully in runtime +- Add kernel config validation +- Add arguments to specify and validate the filesystem + and block device for rootfs during creation +- Support block devices using also labels +- Add Nix definitions for u-boot and latest stable kernel +- Add support to run qemu-system-aarch64 +- Add example minimal Nixos system to boot + +## [0.1.0] - 2024-07-10 + +### Added +- Add analyser of a current system + +## [0.0.9] - 2024-07-04 + +### Added +- Rework CLI into sub-commands + +## [0.0.8] - 2024-07-03 + +### Added +- Bugfix: Default init should be `/sbin/init` +- Bugfix: dependencies ordering + +## [0.0.7] - 2024-05-13 + +### Added +- Implements native CPIO/zstd generator of the initramfs.zst file + (rather then rely on external tools to shell-out) + +## [0.0.6] - 2024-05-07 + +### Added +- Added ability to mount by disk labels +- Fixed bugs for Debian family + +## [0.0.5] - 2024-05-05 + +### Added +- TBD by @isbm + +## [0.0.4] - 2024-05-02 + +### Added +- Mount disks by UUID +- Initial release of microhop - Minimal initramfs /init binary +- Initial release of microgen - Initramfs generator tool diff --git a/Cargo.lock b/Cargo.lock index 8b1b52b..4dd590e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,21 +1,21 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -28,62 +28,63 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.0" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "once_cell_polyfill", + "windows-sys 0.61.2", ] [[package]] name = "arbitrary" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bindgen" -version = "0.69.4" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ "bitflags", "cexpr", "clang-sys", "itertools", - "lazy_static", - "lazycell", + "log", + "prettyplease", "proc-macro2", "quote", "regex", @@ -94,28 +95,35 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.6.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "borsh" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" dependencies = [ "cfg_aliases 0.2.1", ] +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + [[package]] name = "cc" -version = "1.0.104" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ + "find-msvc-tools", "jobserver", "libc", - "once_cell", + "shlex", ] [[package]] @@ -129,9 +137,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -158,9 +166,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.8" +version = "4.5.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" +checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e" dependencies = [ "clap_builder", "clap_derive", @@ -168,9 +176,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.8" +version = "4.5.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" +checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0" dependencies = [ "anstream", "anstyle", @@ -183,9 +191,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.8" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ "heck", "proc-macro2", @@ -195,46 +203,37 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "cpio" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e3adec7390c7643049466136117057188edf5f23efc5c8b4fc8079c8dc34a6" - -[[package]] -name = "crossbeam-channel" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" -dependencies = [ - "crossbeam-utils", -] +checksum = "938e716cb1ade5d6c8f959c13a7248b889c07491fc7e41167c3afe20f8f0de1e" [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -251,54 +250,72 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "glob" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" [[package]] name = "heck" @@ -306,17 +323,11 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "indexmap" -version = "2.2.6" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "arbitrary", "borsh", @@ -330,39 +341,49 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ + "getrandom 0.3.4", "libc", ] +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "kmoddep" version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a528234d2fb5af089792b8ef886aa3dac31c99764ac31e6d2571ab2c4b4b39ae" +source = "git+https://github.com/phodina/kmoddep?branch=todo-implementation#de0d87e662295645df5ca0111fdde69b66b8abb1" [[package]] name = "lazy_static" @@ -370,12 +391,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libblkid-rs" version = "0.3.2" @@ -390,47 +405,48 @@ dependencies = [ [[package]] name = "libblkid-rs-sys" -version = "0.3.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e2a4facc1d764d39c5126817def019f29fca51e965db1bb826f1730c0068d8" +checksum = "5a49302d1b1c51dd61f0ae7bc3f3700ab812250866716013eb1b0921ba089476" dependencies = [ "bindgen", "cc", + "pkg-config", ] [[package]] name = "libc" -version = "0.2.155" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libloading" -version = "0.8.4" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-link", ] [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "log" -version = "0.4.22" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memoffset" @@ -502,20 +518,16 @@ dependencies = [ ] [[package]] -name = "num_cpus" -version = "1.16.0" +name = "once_cell" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] -name = "once_cell" -version = "1.19.0" +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "pin-utils" @@ -525,15 +537,25 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -559,13 +581,19 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -581,14 +609,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.17", ] [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -596,9 +624,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -606,9 +634,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.5" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -618,9 +646,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -629,21 +657,21 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rustc-hash" -version = "1.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc-rayon" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81aadc8837ca6ecebe0fe1353f15df83b3b3cc2cf7a8afd571bc22aa121710" +checksum = "2cd9fb077db982d7ceb42a90471e5a69a990b58f71e06f0d8340bb2cf35eb751" dependencies = [ "either", "rustc-rayon-core", @@ -651,34 +679,38 @@ dependencies = [ [[package]] name = "rustc-rayon-core" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67668daaf00e359c126f6dcb40d652d89b458a008c8afa727a42a2d20fca0b7f" +checksum = "2f42932dcd3bcbe484b38a3ccf79b7906fac41c02d408b5b1bac26da3416efdb" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] name = "rustix" -version = "0.38.34" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "same-file" @@ -691,18 +723,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -736,9 +778,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.68" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -757,12 +799,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.3.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ "rustix", - "windows-sys 0.48.0", + "windows-sys 0.60.2", ] [[package]] @@ -776,24 +818,21 @@ dependencies = [ [[package]] name = "unicase" -version = "2.7.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unsafe-libyaml" @@ -809,15 +848,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" - -[[package]] -name = "version_check" -version = "0.9.4" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] [[package]] name = "walkdir" @@ -831,50 +868,104 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.53.5", ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-link", ] [[package]] @@ -886,7 +977,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", @@ -894,10 +985,21 @@ dependencies = [ ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" +name = "windows-targets" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] [[package]] name = "windows_aarch64_gnullvm" @@ -906,10 +1008,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" +name = "windows_aarch64_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -918,10 +1020,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "windows_i686_gnu" -version = "0.48.5" +name = "windows_aarch64_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -929,6 +1031,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" @@ -936,10 +1044,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "windows_i686_msvc" -version = "0.48.5" +name = "windows_i686_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -948,10 +1056,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" +name = "windows_i686_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -960,10 +1068,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" +name = "windows_x86_64_gnu" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -972,10 +1080,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +name = "windows_x86_64_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -983,29 +1091,41 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + [[package]] name = "zstd" -version = "0.13.1" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.1.0" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.11+zstd.1.5.6" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75652c55c0b6f3e6f12eb786fe1bc960396bf05a1eb3bf1f3691c3610ac2e6d4" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", diff --git a/Makefile b/Makefile index 63f9c58..02c959e 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .DEFAULT_GOAL := build .PHONY:build microhop-release-static microhop-debug-static microgen-release microgen-debug _reset_placeholder -ARCH := $(shell uname -p) +ARCH := $(shell uname -m) ARC_VERSION := $(shell cat src/microhop.rs | grep 'static VERSION' | sed -e 's/.*=//g' -e 's/[" ;]//g') ARC_NAME := microhop-${ARC_VERSION} diff --git a/README.md b/README.md index d326100..04c352c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Microhop - initramfs helper +![NixOS booting using microhop (video)](./nixos-aarch64-qemu.mp4) + You do not always need Dracut. 😉 Sometimes you want it really-really small, tiny and completely stripped from everything. This is what `microhop` is for: use mainline Linux Kernel straight to the point, @@ -46,6 +48,36 @@ you will need the following packages on openSUSE Leap: - `libblkid-devel-static` - `glibc-devel-static` +### Building using Nix + +It's possible to build the binaries using [Nix](https://nixos.org/). + +The package is statically linked and uses [musl](https://www.musl-libc.org/) due to dependency on +`util-linuxMinimal` which provides `libblkid`. + +```shell +# Build the aarch64 variant +$ nix build .#packages.aarch64-linux.microhop + +$ file result/bin/microhop +result/bin/microhop: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, stripped + +$ du -b result/bin/microhop +1186064 result/bin/microhop +``` + +To run the demo shown in the captured [asciinema](https://asciinema.org/) do the following: +``` +# To exit QEMU type Ctrl+A X +nix run .#boot-overlayfs-musl +``` + +To list available nix derivations run: +``` +nix flake show +``` + +To speed up the build there's a [Cachix](https://app.cachix.org/cache/mobile-nixos-next#search) available. ### Configuration @@ -116,3 +148,7 @@ To achieve this, do the following: ``` That's basically it and hopefully it will even boot... 😉 + +## [Changelog](./CHANGELOG.md) + +Feel free to checkout the changes between the releases. diff --git a/etc/microhop.conf b/etc/microhop.conf index cddc5a6..e825f78 100644 --- a/etc/microhop.conf +++ b/etc/microhop.conf @@ -10,12 +10,22 @@ modules: - ext4 # Devices mounting +# NOTE: If kernel command line includes root= parameter, it will override +# the disk configuration below. This allows bootloader to specify rootfs. disks: - # Preferred method: - # /dev/vda3: ext4,/,rw +# Preferred methods (most reliable): +# By UUID (recommended): + uuid=24e1daee-e09b-4fd5-97f3-dde8aba6ad8a: ext4,/,rw - # Mounting by UUID - 24e1daee-e09b-4fd5-97f3-dde8aba6ad8a: ext4,/,rw +# By Label: +# label=my-root: ext4,/,rw + +# Legacy formats (also supported but will show warnings): +# Plain UUID without prefix (backward compatibility): +# 24e1daee-e09b-4fd5-97f3-dde8aba6ad8a: ext4,/,rw + +# Device path (supported but not recommended): +# /dev/vda1: ext4,/,rw # Optionally, define another init app, if it is not /sbin/init # This app will be launched with PID 1 and should never quit. @@ -31,3 +41,30 @@ sysroot: /sysroot # - info (default) # - quiet (errors only) log: debug + +# Optionally, enable overlayfs support +# This creates a writable layer on top of the read-only root filesystem. +# Useful for systems with read-only filesystems like squashfs or when booting from +# read-only media. Requires kernel CONFIG_OVERLAY_FS support. +# +# overlayfs: +# device: uuid=12345678-1234-1234-1234-123456789abc # or label=overlay-storage or /dev/vdb1 +# upper: upper # Path on the mounted device for the upper layer +# workdir: work # Path on the mounted device for overlayfs work directory + +# Firmware configuration +# Defines the base path for firmware files in the initramfs +# Default: /lib/firmware +firmware: + base: /lib/firmware + +# Optionally, mask (ignore) specific kernel cmdline parameters +# This is useful when the bootloader passes conflicting parameters that you want to override +# with values from this config file instead. +# +# Common use case: Android bootloaders often pass a hardcoded root=PARTUUID=... that doesn't +# match the actual filesystem. By masking "root", microhop will ignore the bootloader's +# parameter and use the disk configuration above instead. +# +# mask_cmdline: +# - root # Ignore root= from bootloader, use disks config instead diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..6bae7c7 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1769461804, + "narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..ff4e312 --- /dev/null +++ b/flake.nix @@ -0,0 +1,214 @@ +{ + description = "microhop - Minimal initramfs /init binary and generator"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + { + nixConfig = { + extra-substituters = [ "https://mobile-nixos-next.cachix.org" ]; + extra-trusted-public-keys = [ "mobile-nixos-next.cachix.org-1:tPehb3T4X8DKn3sVsOUu010Tw8MFElOSizi2w3AQc5Y=" ]; + }; + } // flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + + bootComponents = if system == "aarch64-linux" then { + kernel = import ./nixos/kernel.nix { inherit pkgs; }; + u-boot = import ./nixos/u-boot.nix { inherit pkgs; }; + + microhopConfig = import ./nixos/microhop-config.nix { inherit pkgs; }; + + nixos-rootfs = import ./nixos/nixos-rootfs.nix { + inherit pkgs nixpkgs; + }; + } else {}; + in + { + packages = { + microhop = pkgs.pkgsStatic.rustPlatform.buildRustPackage rec { + pname = "microhop"; + version = "0.1.0"; + + src = ./.; + + cargoLock = { + lockFile = ./Cargo.lock; + outputHashes = { + "kmoddep-0.1.5" = "sha256-8Q2cL2YYpItJ/aIwiqhT3iAsMwzBemAem0deKHRxXDs="; + }; + }; + + nativeBuildInputs = with pkgs.pkgsStatic; [ + pkg-config + rustPlatform.bindgenHook + ]; + + buildInputs = with pkgs.pkgsStatic; [ + util-linuxMinimal + ]; + + buildType = "release"; + + cargoBuildFlags = [ "-p" "microhop" ]; + + doCheck = false; + + stripAllList = [ "bin" ]; + + meta = with pkgs.lib; { + description = "Minimal initramfs /init binary"; + homepage = "https://github.com/tinythings/microhop"; + license = licenses.asl20; + maintainers = []; + platforms = [ "aarch64-linux" "x86_64-linux" ]; + }; + }; + + microgen = + let + microhopPkg = self.packages.${system}.microhop; + in + pkgs.pkgsStatic.rustPlatform.buildRustPackage rec { + pname = "microgen"; + version = "0.1.0"; + + src = ./.; + + cargoLock = { + lockFile = ./Cargo.lock; + outputHashes = { + "kmoddep-0.1.5" = "sha256-8Q2cL2YYpItJ/aIwiqhT3iAsMwzBemAem0deKHRxXDs="; + }; + }; + + nativeBuildInputs = (with pkgs.pkgsStatic; [ + pkg-config + rustPlatform.bindgenHook + ]) ++ [ + microhopPkg + ]; + + buildInputs = with pkgs.pkgsStatic; [ + util-linuxMinimal + ]; + + buildType = "release"; + + cargoBuildFlags = [ "-p" "microgen" ]; + + # Set environment variable to point to microhop binary for include_bytes!() + # The nativeBuildInputs ensures this is built first + MICROHOP_BINARY_PATH = "${microhopPkg}/bin/microhop"; + + doCheck = false; + + meta = with pkgs.lib; { + description = "Initramfs generator tool for microhop"; + homepage = "https://github.com/tinythings/microhop"; + license = licenses.asl20; + maintainers = []; + platforms = [ "aarch64-linux" "x86_64-linux" ]; + }; + }; + + default = self.packages.${system}.microgen; + } // (if system == "aarch64-linux" then { + + kernel = bootComponents.kernel; + u-boot = bootComponents.u-boot; + nixos-rootfs = bootComponents.nixos-rootfs; + + initramfs-microgen = import ./nixos/initramfs-microgen.nix { + inherit pkgs; + kernel = bootComponents.kernel; + microgen = self.packages.${system}.microgen; + microhop = self.packages.${system}.microhop; + microhopConfig = bootComponents.microhopConfig; + }; + + boot-overlayfs-musl = pkgs.writeScriptBin "boot-qemu-overlayfs-musl" '' + #!${pkgs.bash}/bin/bash + + UBOOT="${bootComponents.u-boot}/u-boot.bin" + KERNEL="${bootComponents.kernel}/Image" + INITRD="${self.packages.${system}.initramfs-microgen}/initrd" + SQUASHFS="${bootComponents.nixos-rootfs}/rootfs.squashfs" + + OVERLAY_IMG=$(${pkgs.coreutils}/bin/mktemp -u /tmp/overlay-XXXXXX.img) + trap 'rm -f "$OVERLAY_IMG"' EXIT + + ${pkgs.coreutils}/bin/truncate -s 512M "$OVERLAY_IMG" + ${pkgs.e2fsprogs}/bin/mkfs.ext4 -F -L overlay-storage "$OVERLAY_IMG" + + echo "==========================================" + echo " Microhop Boot Flow" + echo "==========================================" + echo "Booting with musl-based rootfs and overlayfs..." + echo "" + echo "U-Boot: $UBOOT" + echo "Kernel: $KERNEL" + echo "Initrd: $INITRD" + echo "Rootfs: $SQUASHFS" + echo "Overlay: $OVERLAY_IMG" + echo "" + echo "Boot flow: U-Boot -> Kernel -> Microhop initramfs -> NixOS musl rootfs" + echo "==========================================" + echo "" + + ${pkgs.qemu}/bin/qemu-system-aarch64 \ + -M virt \ + -cpu cortex-a57 \ + -m 1024M \ + -smp 2 \ + -bios "$UBOOT" \ + -kernel "$KERNEL" \ + -initrd "$INITRD" \ + -drive file="$SQUASHFS",if=none,format=raw,readonly=on,id=hd0 \ + -device virtio-blk-device,drive=hd0 \ + -drive file="$OVERLAY_IMG",if=none,format=raw,id=hd1 \ + -device virtio-blk-device,drive=hd1 \ + -append "console=ttyAMA0" \ + -nographic \ + -no-reboot + + rm -f "$OVERLAY_IMG" + ''; + } else {}); + + apps = if system == "aarch64-linux" then { + boot-overlayfs-musl = { + type = "app"; + program = "${self.packages.${system}.boot-overlayfs-musl}/bin/boot-qemu-overlayfs-musl"; + }; + default = { + type = "app"; + program = "${self.packages.${system}.boot-overlayfs-musl}/bin/boot-qemu-overlayfs-musl"; + }; + } else {}; + + + devShells.default = pkgs.mkShell { + buildInputs = with pkgs; [ + gnumake + cargo + rustc + rustfmt + clippy + pkg-config + util-linux + llvmPackages_20.libclang + llvmPackages_20.clang + ]; + + shellHook = '' + export LIBCLANG_PATH=${pkgs.libclang.lib}/lib + export BINDGEN_EXTRA_CLANG_ARGS="--sysroot=${pkgs.glibc.dev} -I${pkgs.util-linux.dev}/include" + ''; + }; + } + ); +} diff --git a/microgen/Cargo.toml b/microgen/Cargo.toml index 79a4807..429bf05 100644 --- a/microgen/Cargo.toml +++ b/microgen/Cargo.toml @@ -14,8 +14,8 @@ clap = { version = "4.5.4", features = [ ] } colored = "2.1.0" cpio = "0.4.0" -kmoddep = "0.1.5" -#kmoddep = { path = "../../kmoddep" } +#kmoddep = "0.1.5" +kmoddep = { git = "https://github.com/phodina/kmoddep", branch = "todo-implementation" } nix = { version = "0.28.0", features = [ "kmod", "default", diff --git a/microgen/build.rs b/microgen/build.rs new file mode 100644 index 0000000..989ba75 --- /dev/null +++ b/microgen/build.rs @@ -0,0 +1,42 @@ +use std::fs::File; +use std::io::Read; + +fn main() { + if let Ok(binary_path) = std::env::var("MICROHOP_BINARY_PATH") { + let path = std::path::Path::new(&binary_path); + if !path.exists() { + panic!("MICROHOP_BINARY_PATH points to non-existent file: {}", binary_path); + } + + if !path.is_file() { + panic!("MICROHOP_BINARY_PATH is not a regular file: {}", binary_path); + } + + match File::open(path) { + Ok(mut file) => { + let mut magic = [0u8; 4]; + match file.read_exact(&mut magic) { + Ok(_) => { + if magic != [0x7F, 0x45, 0x4C, 0x46] { + panic!( + "MICROHOP_BINARY_PATH is not an ELF executable: {}\n\ + Expected ELF magic header [0x7F, 'E', 'L', 'F'], but found [{:#04x}, {:#04x}, {:#04x}, {:#04x}]", + binary_path, magic[0], magic[1], magic[2], magic[3] + ); + } + } + Err(e) => { + panic!("Failed to read magic header from MICROHOP_BINARY_PATH ({}): {}", binary_path, e); + } + } + } + Err(e) => { + panic!("Failed to open MICROHOP_BINARY_PATH ({}): {}", binary_path, e); + } + } + + println!("cargo::rustc-cfg=microhop_binary_path"); + } + + println!("cargo::rustc-check-cfg=cfg(microhop_binary_path)"); +} diff --git a/microgen/src/analyser.rs b/microgen/src/analyser.rs index d92e7ed..beb083d 100644 --- a/microgen/src/analyser.rs +++ b/microgen/src/analyser.rs @@ -27,7 +27,7 @@ impl SysAnalyser { fn get_root_device_path(&self) -> Result { for data in BufReader::new(File::open("/proc/mounts")?).lines().map_while(Result::ok) { let mpt = data.split_whitespace().collect::>(); - if mpt[1].eq("/") { + if mpt.len() >= 2 && mpt[1].eq("/") { return Ok(mpt[0].to_string()); } } diff --git a/microgen/src/clidef.rs b/microgen/src/clidef.rs index b6ab6bd..67aeeba 100644 --- a/microgen/src/clidef.rs +++ b/microgen/src/clidef.rs @@ -23,11 +23,29 @@ pub fn clidef(version: &'static str, appname: &'static str) -> Command { .long("list") .value_name("PATH") .help("List available kernel versions in a given root filesystem") - .conflicts_with_all(["lsmod"]), + .conflicts_with_all(["lsmod", "list-filesystems", "list-block-devices"]), ) - .arg(Arg::new("lsmod").short('m').long("lsmod").action(clap::ArgAction::SetTrue).help("Just a fancy lsmod")), + .arg(Arg::new("lsmod").short('m').long("lsmod").action(clap::ArgAction::SetTrue).help("Just a fancy lsmod") + .conflicts_with_all(["list-filesystems", "list-block-devices"])) + .arg(Arg::new("list-filesystems").long("list-filesystems").action(clap::ArgAction::SetTrue) + .help("Show supported filesystems and their kernel config options")) + .arg(Arg::new("list-block-devices").long("list-block-devices").action(clap::ArgAction::SetTrue) + .help("Show supported block devices and their kernel config options")), ) .subcommand(Command::new("analyse").about("Analyse current system and generate a profile from it")) + .subcommand( + Command::new("validate") + .about("Validate microhop.conf configuration file") + .arg_required_else_help(true) + .arg( + Arg::new("config") + .short('c') + .long("config") + .value_name("PATH") + .help("Path to the microhop.conf file to validate") + .default_value("./etc/microhop.conf"), + ), + ) .subcommand( Command::new("new") .about("Create a new initramfs from a specified profile") @@ -41,6 +59,40 @@ pub fn clidef(version: &'static str, appname: &'static str) -> Command { .conflicts_with_all(["extract"]) .help("Path to the initramfs configuration (profile)"), ) + .arg( + Arg::new("kernel-config") + .long("kernel-config") + .value_name("PATH") + .help("Path to kernel .config file for validation against microhop.conf requirements"), + ) + .arg( + Arg::new("firmware-list") + .long("firmware-list") + .value_name("PATH") + .help("Path to a file containing list of firmware files to include in initramfs"), + ) + .arg( + Arg::new("validate-only") + .long("validate-only") + .action(clap::ArgAction::SetTrue) + .requires("kernel-config") + .requires("config") + .help("Only validate kernel config against microhop.conf, don't generate initramfs"), + ) + .arg( + Arg::new("filesystems") + .long("filesystems") + .value_name("FS_TYPES") + .help("Comma-separated list of filesystem types to validate (e.g., squashfs,ext4). If not specified, filesystem validation will issue warnings only") + .value_delimiter(','), + ) + .arg( + Arg::new("block-devices") + .long("block-devices") + .value_name("BLK_TYPES") + .help("Comma-separated list of block device types to validate (e.g., virtio_blk,nvme). If not specified, block device validation will issue warnings only") + .value_delimiter(','), + ) .arg( Arg::new("extract") .short('x') diff --git a/microgen/src/kconfig_validator.rs b/microgen/src/kconfig_validator.rs new file mode 100644 index 0000000..f7aa514 --- /dev/null +++ b/microgen/src/kconfig_validator.rs @@ -0,0 +1,350 @@ +use colored::Colorize; +use std::{ + collections::HashMap, + fs::File, + io::{BufRead, BufReader, Error}, + path::Path, +}; + +pub const SUPPORTED_FILESYSTEMS: &[(&str, &str, &str)] = &[ + ("ext4", "CONFIG_EXT4_FS", "Fourth Extended Filesystem"), + ("ext2", "CONFIG_EXT2_FS", "Second Extended Filesystem"), + ("squashfs", "CONFIG_SQUASHFS", "Compressed read-only filesystem"), + ("btrfs", "CONFIG_BTRFS_FS", "B-tree filesystem"), + ("xfs", "CONFIG_XFS_FS", "SGI XFS filesystem"), + ("f2fs", "CONFIG_F2FS_FS", "Flash-Friendly File System"), + ("jffs2", "CONFIG_JFFS2_FS", "Journalling Flash File System v2"), + ("ubifs", "CONFIG_UBIFS_FS", "UBIFS file system"), + ("tmpfs", "CONFIG_TMPFS", "Temporary filesystem"), + ("iso9660", "CONFIG_ISO9660_FS", "ISO9660 filesystem"), + ("vfat", "CONFIG_VFAT_FS", "VFAT filesystem"), + ("ntfs", "CONFIG_NTFS_FS", "NTFS filesystem"), +]; + +pub const SUPPORTED_BLOCK_DEVICES: &[(&str, &str, &str)] = &[ + ("virtio_blk", "CONFIG_VIRTIO_BLK", "Virtio block driver"), + ("virtio_mmio", "CONFIG_VIRTIO_MMIO", "Platform bus driver for memory mapped virtio devices"), + ("nvme", "CONFIG_BLK_DEV_NVME", "NVM Express block device"), + ("usb_storage", "CONFIG_USB_STORAGE", "USB Mass Storage support"), + ("uas", "CONFIG_USB_UAS", "USB Attached SCSI"), + ("sd_mod", "CONFIG_BLK_DEV_SD", "SCSI disk support"), + ("sr_mod", "CONFIG_BLK_DEV_SR", "SCSI CDROM support"), + ("mmc_block", "CONFIG_MMC_BLOCK", "MMC block device driver"), + ("nand_block", "CONFIG_MTD_NAND_CORE", "MTD NAND core device driver"), + ("sdhci", "CONFIG_MMC_SDHCI", "Secure Digital Host Controller Interface support"), + ("ahci", "CONFIG_SATA_AHCI", "AHCI SATA support"), + ("nvme", "CONFIG_BLK_DEV_NVME", "NVME support"), + ("ufs", "CONFIG_SCSI_UFSHCD", "UFS support"), + ("ata_piix", "CONFIG_ATA_PIIX", "Intel PIIX/ICH SATA support"), + ("loop", "CONFIG_BLK_DEV_LOOP", "Loopback device support"), +]; + +/// Get the kernel config option name for a filesystem +pub fn get_filesystem_config(fstype: &str) -> Option<&'static str> { + let fstype_lower = fstype.to_lowercase(); + SUPPORTED_FILESYSTEMS.iter().find(|(name, _, _)| name.eq_ignore_ascii_case(&fstype_lower)).map(|(_, config, _)| *config) +} + +/// Get the kernel config option name for a block device +pub fn get_block_device_config(blktype: &str) -> Option<&'static str> { + let blktype_lower = blktype.to_lowercase(); + SUPPORTED_BLOCK_DEVICES.iter().find(|(name, _, _)| name.eq_ignore_ascii_case(&blktype_lower)).map(|(_, config, _)| *config) +} + +pub fn print_supported_filesystems() { + println!("\n{}", "=== Supported Filesystems ===".bright_cyan().bold()); + println!("{}", "Use these names with --filesystems option during validation\n".white()); + + for (name, config, desc) in SUPPORTED_FILESYSTEMS { + println!(" {:<15} {:<25} {}", name.bright_green().bold(), config.bright_yellow(), desc.white()); + } + println!(); +} + +pub fn print_supported_block_devices() { + println!("\n{}", "=== Supported Block Devices ===".bright_cyan().bold()); + println!("{}", "Use these names with --block-devices option during validation\n".white()); + + for (name, config, desc) in SUPPORTED_BLOCK_DEVICES { + println!(" {:<20} {:<30} {}", name.bright_green().bold(), config.bright_yellow(), desc.white()); + } + println!(); +} + +pub struct KConfigValidator { + config: HashMap, +} + +impl KConfigValidator { + pub fn from_file>(path: P) -> Result { + let file = File::open(path)?; + let reader = BufReader::new(file); + let mut config = HashMap::new(); + + for line in reader.lines() { + let line = line?; + let trimmed = line.trim(); + + if trimmed.is_empty() || trimmed.starts_with('#') { + continue; + } + + if let Some(eq_pos) = trimmed.find('=') { + let key = trimmed[..eq_pos].trim().to_string(); + let value = trimmed[eq_pos + 1..].trim().to_string(); + config.insert(key, value); + } + } + + Ok(KConfigValidator { config }) + } + + pub fn is_enabled(&self, option: &str) -> bool { + let option = if option.starts_with("CONFIG_") { option.to_string() } else { format!("CONFIG_{}", option) }; + + match self.config.get(&option) { + Some(val) => val == "y" || val == "m", + None => false, + } + } + + pub fn is_module(&self, option: &str) -> bool { + let option = if option.starts_with("CONFIG_") { option.to_string() } else { format!("CONFIG_{}", option) }; + + match self.config.get(&option) { + Some(val) => val == "m", + None => false, + } + } + + /// Validate kernel configuration against microhop requirements + /// + /// # Arguments + /// * `mh_config` - The microhop configuration to validate against + /// * `filesystems` - Optional list of filesystem types to validate. If None, filesystem checks will be warnings only. + /// * `block_devices` - Optional list of block device types to validate. If None, block device checks will be warnings only. + pub fn validate( + &self, mh_config: &profile::cfg::MhConfig, filesystems: Option<&[String]>, block_devices: Option<&[String]>, + ) -> Result { + let mut result = ValidationResult::new(); + + if let Some(overlay_cfg) = mh_config.get_overlayfs() { + if self.is_enabled("OVERLAY_FS") { + result.add_success("OVERLAY_FS", &format!("Overlayfs support is enabled (device: {})", overlay_cfg.device)); + } else { + result.add_error( + "OVERLAY_FS", + "Overlayfs is configured in microhop.conf but CONFIG_OVERLAY_FS is not enabled in kernel. System will fail to boot!", + ); + } + } + + // Check if CONFIG_MODULES is enabled if modules are required + let modules = mh_config.get_modules(); + if !modules.is_empty() { + if !self.is_enabled("MODULES") { + result.add_error( + "CONFIG_MODULES", + "Kernel module support is disabled, but microhop.conf requires modules to be loaded", + ); + } else { + result.add_info("CONFIG_MODULES", "Kernel module support is enabled"); + } + } + + for module_name in modules { + self.validate_module(module_name, &mut result); + } + + // Validate user-specified filesystems or those from disk config + if let Some(fs_list) = filesystems { + for fstype in fs_list { + if let Some(config_name) = get_filesystem_config(fstype) { + let config_option = config_name.trim_start_matches("CONFIG_"); + if self.is_enabled(config_option) { + result.add_success(config_option, &format!("Filesystem {} is supported", fstype.to_lowercase())); + } else { + result.add_error( + config_option, + &format!("Filesystem {} is not enabled. Required for specified rootfs", fstype.to_lowercase()), + ); + } + } else { + result.add_error( + fstype, + &format!("Unknown filesystem type '{}'. Use --list-filesystems to see supported types", fstype), + ); + } + } + } else if let Ok(disks) = mh_config.get_disks() { + for disk in disks { + let fstype = disk.get_fstype(); + if let Some(config_name) = get_filesystem_config(fstype) { + let config_option = config_name.trim_start_matches("CONFIG_"); + if self.is_enabled(config_option) { + result.add_success(config_option, &format!("Filesystem {} is supported", fstype.to_lowercase())); + } else { + result.add_warning( + config_option, + &format!("Filesystem {} is not enabled in kernel", fstype.to_lowercase()), + ); + } + } else { + result.add_warning( + fstype, + &format!("Unknown filesystem type '{}'. Use --list-filesystems to see supported types", fstype), + ); + } + } + } + + // Validate block devices if specified + if let Some(blk_list) = block_devices { + for blktype in blk_list { + if let Some(config_name) = get_block_device_config(blktype) { + let config_option = config_name.trim_start_matches("CONFIG_"); + if self.is_enabled(config_option) { + result.add_success(config_option, &format!("Block device {} is supported", blktype.to_lowercase())); + } else { + result.add_error( + config_option, + &format!( + "Block device {} is not enabled. Required for specified block device", + blktype.to_lowercase() + ), + ); + } + } else { + result.add_error( + blktype, + &format!("Unknown block device type '{}'. Use --list-block-devices to see supported types", blktype), + ); + } + } + } + + self.validate_base_features(&mut result); + + Ok(result) + } + + fn validate_module(&self, module_name: &str, result: &mut ValidationResult) { + let config_name = module_name.to_uppercase().replace('-', "_"); + + if self.is_module(&config_name) { + result.add_success(&config_name, &format!("Module {} can be loaded", module_name)); + } else if self.is_enabled(&config_name) { + result.add_warning( + &config_name, + &format!( + "Module {} is built-in (=y) instead of loadable module (=m). It will be available but cannot be loaded dynamically", + module_name + ), + ); + } else { + result.add_error( + &config_name, + &format!("Module {} is not enabled in kernel config. Required by microhop.conf", module_name), + ); + } + } + + fn validate_base_features(&self, result: &mut ValidationResult) { + let required_features = vec![ + ("BLK_DEV_INITRD", "Initial RAM filesystem support"), + ("DEVTMPFS", "Maintain a devtmpfs filesystem"), + ("TMPFS", "Tmpfs virtual memory filesystem"), + ("PROC_FS", "Proc filesystem"), + ("SYSFS", "Sysfs filesystem"), + ]; + + for (feature, description) in required_features { + if self.is_enabled(feature) { + result.add_info(feature, &format!("{} is enabled", description)); + } else { + result.add_warning(feature, &format!("{} is not enabled. This may cause boot issues", description)); + } + } + } +} + +#[derive(Debug)] +pub struct ValidationResult { + errors: Vec<(String, String)>, + warnings: Vec<(String, String)>, + successes: Vec<(String, String)>, + info: Vec<(String, String)>, +} + +impl ValidationResult { + pub fn new() -> Self { + ValidationResult { errors: Vec::new(), warnings: Vec::new(), successes: Vec::new(), info: Vec::new() } + } + + pub fn add_error(&mut self, option: &str, message: &str) { + self.errors.push((option.to_string(), message.to_string())); + } + + pub fn add_warning(&mut self, option: &str, message: &str) { + self.warnings.push((option.to_string(), message.to_string())); + } + + pub fn add_success(&mut self, option: &str, message: &str) { + self.successes.push((option.to_string(), message.to_string())); + } + + pub fn add_info(&mut self, option: &str, message: &str) { + self.info.push((option.to_string(), message.to_string())); + } + + pub fn has_errors(&self) -> bool { + !self.errors.is_empty() + } + + pub fn has_warnings(&self) -> bool { + !self.warnings.is_empty() + } + + pub fn print(&self) { + println!("\n{}", "=== Kernel Configuration Validation ===".bright_cyan().bold()); + + if !self.info.is_empty() { + println!("\n{}", "Information:".bright_blue().bold()); + for (option, msg) in &self.info { + println!(" {} {}", "ℹ".bright_blue(), format!("[{}] {}", option, msg).white()); + } + } + + if !self.warnings.is_empty() { + println!("\n{}", "Warnings:".bright_yellow().bold()); + for (option, msg) in &self.warnings { + println!(" {} {}", "âš ".bright_yellow(), format!("[{}] {}", option, msg).yellow()); + } + } + + if !self.errors.is_empty() { + println!("\n{}", "Errors:".bright_red().bold()); + for (option, msg) in &self.errors { + println!(" {} {}", "✗".bright_red(), format!("[{}] {}", option, msg).red()); + } + } + + println!("\n{}", "=== Validation Summary ===".bright_cyan().bold()); + println!( + " {} {} | {} {}", + "Errors:".bright_red().bold(), + self.errors.len().to_string().bright_red(), + "Warnings:".bright_yellow().bold(), + self.warnings.len().to_string().bright_yellow() + ); + + if self.has_errors() { + println!("\n{}", "Validation FAILED - kernel configuration is incompatible".bright_red().bold()); + } else if self.has_warnings() { + println!("\n{}", "Validation passed with warnings - check configuration".bright_yellow().bold()); + } else { + println!("\n{}", "Validation PASSED - kernel configuration is compatible".bright_green().bold()); + } + println!(); + } +} diff --git a/microgen/src/main.rs b/microgen/src/main.rs index dccd48d..ec936b0 100644 --- a/microgen/src/main.rs +++ b/microgen/src/main.rs @@ -1,5 +1,6 @@ mod analyser; mod clidef; +mod kconfig_validator; mod rdgen; mod rdpack; @@ -12,9 +13,19 @@ use std::{error::Error, io, path::PathBuf}; static VERSION: &str = "0.1.0"; static APPNAME: &str = "microgen"; -/// Run information section fn run_info(params: &ArgMatches) -> Result<(), Box> { let rfs = params.get_one::("list").map(|v| v.as_str()); + + if params.get_flag("list-filesystems") { + kconfig_validator::print_supported_filesystems(); + return Ok(()); + } + + if params.get_flag("list-block-devices") { + kconfig_validator::print_supported_block_devices(); + return Ok(()); + } + let k_info = kmoddep::get_kernel_infos(rfs)?; if rfs.is_some() { @@ -41,6 +52,129 @@ fn run_info(params: &ArgMatches) -> Result<(), Box> { Ok(()) } +/// Run validation of microhop.conf +fn run_validate(params: &ArgMatches) -> Result<(), Box> { + let config_path = params.get_one::("config").unwrap(); + + let cfg = match profile::cfg::get_mh_config(Some(config_path)) { + Ok(cfg) => { + println!("Configuration file parsed successfully"); + cfg + } + Err(e) => { + println!("Failed to parse configuration file:"); + println!(" {}", e.to_string().red()); + return Err(e.into()); + } + }; + + let mut has_errors = false; + let mut has_warnings = false; + + println!("\n{}", "=== Modules ===".bright_yellow().bold()); + let modules = cfg.get_modules(); + if modules.is_empty() { + println!(" No modules configured (assuming monolithic kernel)"); + } else { + println!(" {} module(s) configured:", modules.len()); + for module in modules { + println!(" - {}", module.bright_white()); + } + } + + println!("\n{}", "=== Disks ===".bright_yellow().bold()); + match cfg.get_disks() { + Ok(disks) => { + if disks.is_empty() { + println!(" No disks configured"); + println!(" Note: Disks can be specified via kernel command line (root=)"); + has_warnings = true; + } else { + println!(" {} disk(s) configured:", disks.len()); + for disk in &disks { + let device = disk.get_device(); + let fstype = disk.get_fstype(); + let mountpoint = disk.get_mountpoint(); + let mode = disk.get_mode(); + + let device_status = if device.starts_with("uuid=") || device.starts_with("label=") { + format!("Preferred format ({})", device) + } else if device.starts_with("/dev/") { + has_warnings = true; + format!("Legacy format: {}. Consider using uuid= or label= instead", device) + } else if device.len() == 36 && device.chars().filter(|c| *c == '-').count() == 4 { + has_warnings = true; + format!("Plain UUID detected: {}. Consider prefixing with 'uuid=' for clarity", device) + } else { + has_warnings = true; + format!("Unknown device format: {}", device) + }; + + println!(" {}", device_status); + println!(" Filesystem: {}", fstype.bright_white()); + println!(" Mountpoint: {}", mountpoint.bright_white()); + println!(" Mode: {}", mode.bright_white()); + } + } + } + Err(e) => { + println!(" Invalid disk configuration:"); + println!(" {}", e.to_string().red()); + has_errors = true; + } + } + + println!("\n{}", "=== Init ===".bright_yellow().bold()); + let init_path = cfg.get_init_path(); + println!(" Init path: {}", init_path.bright_white()); + + println!("\n{}", "=== Sysroot ===".bright_yellow().bold()); + let sysroot_path = cfg.get_sysroot_path(); + println!(" Sysroot path: {}", sysroot_path.bright_white()); + + println!("\n{}", "=== Logging ===".bright_yellow().bold()); + if let Some(log_level) = cfg.get_log_level_as_str() { + let valid_levels = ["debug", "info", "quiet"]; + if valid_levels.contains(&log_level.as_str()) { + println!(" Log level: {}", log_level.bright_white()); + } else { + println!(" Invalid log level: '{}'. Valid options: debug, info, quiet", log_level); + has_warnings = true; + } + } else { + println!(" Log level: {} (default)", "info".bright_white()); + } + + println!("\n{}", "=== Overlayfs ===".bright_yellow().bold()); + if let Some(overlay_cfg) = cfg.get_overlayfs() { + println!(" Overlayfs is configured:"); + println!(" Device: {}", overlay_cfg.device.bright_white()); + println!(" Upper: {}", overlay_cfg.upper.bright_white()); + println!(" Workdir: {}", overlay_cfg.workdir.bright_white()); + println!(" Note: Requires CONFIG_OVERLAY_FS in kernel"); + } else { + println!(" Overlayfs not configured"); + } + + println!("\n{}", "=== Firmware ===".bright_yellow().bold()); + let firmware_base = cfg.get_firmware_base(); + println!(" Firmware base path: {}", firmware_base.bright_white()); + + println!("\n{}", "=== Validation Summary ===".bright_cyan().bold()); + if has_errors { + println!(" Validation FAILED - configuration has errors"); + return Err(Box::new(io::Error::new(io::ErrorKind::InvalidData, "Configuration validation failed"))); + } else if has_warnings { + println!(" Validation passed with warnings"); + println!(" Review warnings above for potential improvements"); + } else { + println!(" Validation PASSED - configuration is valid"); + } + println!(); + + Ok(()) +} + /// Run analysis and profile generator fn run_analyse(_params: &ArgMatches) -> Result<(), Box> { if !nix::unistd::Uid::effective().is_root() { @@ -55,16 +189,45 @@ fn run_analyse(_params: &ArgMatches) -> Result<(), Box> { /// Create a new initramfs fn run_new(params: &ArgMatches) -> Result<(), Box> { let x_mods: Vec = params.get_many::("extract").unwrap_or_default().map(|s| s.to_string()).collect(); - let k_info = kmoddep::get_kernel_infos(Some(params.get_one::("root").unwrap())); let profile = params.get_one::("config"); - if let Err(k_info) = k_info { - println!("Unable to get the information about the kernel: {}", k_info); - return Ok(()); - } + let kernel_config = params.get_one::("kernel-config"); + let validate_only = params.get_flag("validate-only"); + + // Only get kernel info if we actually need it (when extracting modules or if modules are configured) + let need_kernel_info = !x_mods.is_empty() + || (profile.is_some() && { + // Check if profile has modules configured + match profile::cfg::get_mh_config(profile.map(|x| x.as_str())) { + Ok(cfg) => !cfg.get_modules().is_empty(), + Err(_) => true, // If we can't read config, assume we might need kernel info + } + }); + + let k_info = if need_kernel_info { + match kmoddep::get_kernel_infos(Some(params.get_one::("root").unwrap())) { + Ok(info) => Some(info), + Err(e) => { + println!("Unable to get the information about the kernel: {}", e); + if !x_mods.is_empty() { + return Ok(()); + } + None + } + } + } else { + None + }; if !x_mods.is_empty() { let krel = params.get_one::("kernel").unwrap().replace('"', ""); - for knfo in k_info.unwrap() { + let kernels = k_info.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "No kernel information available"))?; + if kernels.is_empty() { + return Err(Box::new(io::Error::new( + io::ErrorKind::NotFound, + "No kernel modules found in the specified root filesystem", + ))); + } + for knfo in kernels { let kn = knfo.get_kernel_path().file_name().unwrap().to_str().unwrap().to_string(); if krel == kn { println!("{:?}", knfo.get_deps_for(&x_mods.iter().map(|x| x.to_string()).collect::>())); @@ -72,21 +235,97 @@ fn run_new(params: &ArgMatches) -> Result<(), Box> { } } else if let Some(profile) = profile { let cfg = profile::cfg::get_mh_config(Some(profile))?; - if let Ok(k_info) = k_info { - let kfo: KernelInfo; - // Rewrite this better - if k_info.len() > 1 { - panic!("Need to implement matching a proper kernel from CLI") - } else { - kfo = k_info[0].to_owned(); + if let Some(kconfig_path) = kernel_config { + println!("{}", "Validating kernel configuration...".bright_cyan().bold()); + + let filesystems: Option> = + params.get_many::("filesystems").map(|values| values.map(|s| s.to_string()).collect()); + + // Get optional block devices list from CLI + let block_devices: Option> = + params.get_many::("block-devices").map(|values| values.map(|s| s.to_string()).collect()); + + match kconfig_validator::KConfigValidator::from_file(kconfig_path) { + Ok(validator) => { + match validator.validate(&cfg, filesystems.as_deref(), block_devices.as_deref()) { + Ok(result) => { + result.print(); + + if validate_only { + // Exit after validation + if result.has_errors() { + return Err(Box::new(io::Error::new( + io::ErrorKind::InvalidData, + "Kernel configuration validation failed", + ))); + } + return Ok(()); + } + + if result.has_errors() { + return Err(Box::new(io::Error::new( + io::ErrorKind::InvalidData, + "Cannot generate initramfs: kernel configuration is incompatible", + ))); + } + + if result.has_warnings() { + println!("{}", "âš  Continuing with warnings...".bright_yellow()); + } + } + Err(e) => { + eprintln!("{}", format!("Validation error: {}", e).bright_red()); + return Err(Box::new(e)); + } + } + } + Err(e) => { + eprintln!("{}", format!("Failed to parse kernel config: {}", e).bright_red()); + return Err(Box::new(e)); + } } + } else if validate_only { + return Err(Box::new(io::Error::new( + io::ErrorKind::InvalidInput, + "--validate-only requires --kernel-config to be specified", + ))); + } + + // Generate initramfs if not validation-only + if !validate_only { + let kfo: Option = if let Some(k_info) = k_info { + match k_info.len() { + 0 => { + println!("ℹ No kernel modules found, assuming monolithic kernel"); + None + } + 1 => Some(k_info[0].to_owned()), + _ => { + return Err(Box::new(io::Error::new( + io::ErrorKind::InvalidInput, + "Multiple kernels found; please select one explicitly", + ))) + } + } + } else { + // No kernel info available (no modules configured) + println!("ℹ No kernel modules configured, assuming monolithic kernel"); + None + }; + println!("Generating initramfs"); + + let firmware_list_path = params.get_one::("firmware-list").map(PathBuf::from); + let firmware_path = PathBuf::from(params.get_one::("root").unwrap()); + IrfsGen::generate( - &kfo, + kfo.as_ref(), cfg, PathBuf::from(params.get_one::("output").unwrap()), PathBuf::from(params.get_one::("file").unwrap()), + firmware_list_path, + firmware_path, )?; } } else { @@ -106,6 +345,7 @@ fn main() -> Result<(), Box> { match match params.subcommand() { Some(("new", args)) => run_new(args), Some(("analyse", args)) => run_analyse(args), + Some(("validate", args)) => run_validate(args), Some(("info", args)) => run_info(args), _ => Ok(cli.print_help()?), } { diff --git a/microgen/src/rdgen.rs b/microgen/src/rdgen.rs index ea78682..ad7783c 100644 --- a/microgen/src/rdgen.rs +++ b/microgen/src/rdgen.rs @@ -4,13 +4,19 @@ use std::{ collections::HashSet, env, fs::{self, File}, - io::{BufWriter, Error, ErrorKind::InvalidData, Write}, + io::{BufRead, BufReader, BufWriter, Error, ErrorKind::InvalidData, Write}, os::unix::fs::{symlink, PermissionsExt}, path::{Path, PathBuf}, }; use crate::rdpack; +// Use MICROHOP_BINARY_PATH environment variable if set (for Nix builds), +// otherwise default to "microhop" in the source directory (for Make builds) +#[cfg(microhop_binary_path)] +const MICROHOP: &[u8] = include_bytes!(env!("MICROHOP_BINARY_PATH")); + +#[cfg(not(microhop_binary_path))] const MICROHOP: &[u8] = include_bytes!("microhop"); const BLINKENLICHTEN: &str = "# Achtung Alles Lookenskepers! # @@ -23,8 +29,8 @@ const BLINKENLICHTEN: &str = "# Achtung Alles Lookenskepers! # Relaxen und watchen das blinkenlichten."; pub struct IrfsGen { - /// Target kernel - kinfo: KernelInfo, + /// Target kernel (optional: monolithic kernel has none) + kinfo: Option, /// Profile (config) cfg: MhConfig, @@ -40,21 +46,49 @@ pub struct IrfsGen { /// Main modules _kmod_m: Vec, + + /// Firmware list file path + firmware_list_path: Option, + + /// Root filesystem path + firmware_path: PathBuf, } impl IrfsGen { - pub fn generate(kinfo: &KernelInfo, cfg: MhConfig, dst: PathBuf, fname: PathBuf) -> Result<(), Error> { - if !dst.exists() { - fs::create_dir_all(&dst)?; - } else { + pub fn generate( + kinfo: Option<&KernelInfo>, cfg: MhConfig, dst: PathBuf, fname: PathBuf, firmware_list_path: Option, + firmware_path: PathBuf, + ) -> Result<(), Error> { + if dst.exists() { return Err(Error::new(InvalidData, format!("Given destination path {:?} already exists", dst))); } - let mut irfsg = IrfsGen { kinfo: kinfo.to_owned(), cfg, dst, dst_fn: fname, _kmod_d: vec![], _kmod_m: vec![] }; + fs::create_dir_all(&dst)?; + + let mut irfsg = IrfsGen { + kinfo: kinfo.cloned(), + cfg, + dst, + dst_fn: fname, + _kmod_d: vec![], + _kmod_m: vec![], + firmware_list_path, + firmware_path, + }; let kroot = irfsg.create_ramfs_dirs()?; irfsg.setup_microhop()?; - irfsg.copy_kernel_modules(kroot.as_str())?; + + if irfsg.kinfo.is_some() { + irfsg.copy_kernel_modules(kroot.as_str())?; + } else { + println!("ℹ No kernel modules found, skipping module copy"); + } + + if irfsg.firmware_list_path.is_some() { + irfsg.copy_firmware_files()?; + } + irfsg.write_boot_config()?; irfsg.pack()?; @@ -80,22 +114,44 @@ impl IrfsGen { /// Create directories for the ramfs. fn create_ramfs_dirs(&self) -> Result { - let kroot = format!("lib/modules/{}", self.kinfo.get_kernel_path().as_path().file_name().unwrap().to_str().unwrap()); - for d in ["bin", "etc", "proc", "dev", "sys", self.cfg.get_sysroot_path().trim_start_matches('/'), kroot.as_str()] { - fs::create_dir_all(self.dst.join(d.trim_start_matches('/')))?; + let mut dirs: Vec = vec![ + "bin".to_string(), + "etc".to_string(), + "proc".to_string(), + "dev".to_string(), + "sys".to_string(), + self.cfg.get_sysroot_path().trim_start_matches('/').to_string(), + ]; + + let kroot = if let Some(kinfo) = &self.kinfo { + let kr = format!("lib/modules/{}", kinfo.get_kernel_path().file_name().unwrap().to_str().unwrap()); + dirs.push(kr.clone()); + kr + } else { + String::new() + }; + + for d in dirs { + fs::create_dir_all(self.dst.join(d))?; } + Ok(kroot) } /// This will find what modules are needed in the source kernel and will copy to the target only those fn copy_kernel_modules(&mut self, kroot: &str) -> Result<(), Error> { + let kinfo = match &self.kinfo { + Some(k) => k, + None => return Ok(()), + }; + // First get only main modules, and then get dependencies for them - for (kmod, kmod_deps) in self.kinfo.get_deps_for( + for (kmod, kmod_deps) in kinfo.get_deps_for( &self .cfg .get_modules() .iter() - .filter(|e| !self.kinfo.is_dep(e)) + .filter(|e| !kinfo.is_dep(e)) .collect::>() .into_iter() .cloned() @@ -117,7 +173,12 @@ impl IrfsGen { /// Copy one kernel module fn _copy_kmod(&self, kmod: &str, kroot: &str) -> Result<(), Error> { - let msrc = self.kinfo.get_kernel_path().join(kmod); + let kinfo = match &self.kinfo { + Some(k) => k, + None => return Ok(()), + }; + + let msrc = kinfo.get_kernel_path().join(kmod); let mdst = self.dst.join(kroot).join(kmod); fs::create_dir_all(mdst.as_path().parent().unwrap())?; @@ -126,6 +187,106 @@ impl IrfsGen { Ok(()) } + /// Copy firmware files based on firmware list file + fn copy_firmware_files(&self) -> Result<(), Error> { + let firmware_list_path = match &self.firmware_list_path { + Some(p) => p, + None => return Ok(()), + }; + + let file = File::open(firmware_list_path).map_err(|e| { + Error::new(std::io::ErrorKind::NotFound, format!("Failed to open firmware list file {:?}: {}", firmware_list_path, e)) + })?; + let reader = BufReader::new(file); + let firmware_base = self.cfg.get_firmware_base(); + + for (line_num, line) in reader.lines().enumerate() { + let line = line?; + let line = line.trim(); + + if line.is_empty() || line.starts_with('#') { + continue; + } + + // Parse SOURCE_PATH:DESTINATION_PATH + let parts: Vec<&str> = line.splitn(2, ':').collect(); + if parts.len() != 2 { + return Err(Error::new( + InvalidData, + format!("Invalid firmware entry format at line {}: '{}' (expected SOURCE:DEST)", line_num + 1, line), + )); + } + + let source_path = self.firmware_path.join(parts[0].trim()); + let dest_rel_path = parts[1].trim(); + let dest_path = self.dst.join(firmware_base.trim_start_matches('/')).join(dest_rel_path); + + if !source_path.exists() { + return Err(Error::new( + std::io::ErrorKind::NotFound, + format!("Firmware file not found at line {}: {:?}", line_num + 1, source_path), + )); + } + + let real_source = if source_path.is_symlink() { + let target = fs::read_link(&source_path).map_err(|e| { + Error::new( + std::io::ErrorKind::InvalidInput, + format!("Failed to read symlink at line {}: {:?} - {}", line_num + 1, source_path, e), + ) + })?; + + let resolved = if target.is_absolute() { target } else { source_path.parent().unwrap().join(target) }; + + if !resolved.exists() { + return Err(Error::new( + std::io::ErrorKind::NotFound, + format!("Broken symlink at line {}: {:?} -> {:?}", line_num + 1, source_path, resolved), + )); + } + resolved + } else { + source_path.clone() + }; + + if dest_path.exists() { + return Err(Error::new( + InvalidData, + format!("Destination firmware file already exists at line {}: {:?}", line_num + 1, dest_path), + )); + } + + let metadata = fs::metadata(&real_source).map_err(|e| { + Error::other(format!("Failed to get metadata at line {}: {:?} - {}", line_num + 1, real_source, e)) + })?; + + let size = metadata.len(); + + if let Some(parent) = dest_path.parent() { + fs::create_dir_all(parent)?; + } + + fs::copy(&real_source, &dest_path).map_err(|e| { + Error::other(format!( + "Failed to copy firmware file at line {}: {:?} -> {:?} - {}", + line_num + 1, + real_source, + dest_path, + e + )) + })?; + + // Set read-only permissions + let mut perms = fs::metadata(&dest_path)?.permissions(); + perms.set_mode(0o444); + fs::set_permissions(&dest_path, perms)?; + + println!("Firmware: {} ({} bytes)", dest_rel_path, size); + } + + Ok(()) + } + /// Write boot config fn write_boot_config(&self) -> Result<(), Error> { let f = File::create(self.dst.join("etc/microhop.conf"))?; @@ -163,6 +324,11 @@ impl IrfsGen { writeln!(fp, "log: {}", l)?; } + if let Some(firmware) = self.cfg.get_firmware() { + writeln!(fp, "\nfirmware:")?; + writeln!(fp, " base: {}", firmware.base)?; + } + fp.flush()?; Ok(()) } diff --git a/microgen/src/rdpack.rs b/microgen/src/rdpack.rs index 03cbcac..b15760a 100644 --- a/microgen/src/rdpack.rs +++ b/microgen/src/rdpack.rs @@ -71,7 +71,13 @@ impl InitRamfsPacker { } let out = trailer(out).unwrap(); - let mut encoder = Encoder::new(File::create(format!("../{}", output)).unwrap(), 10).unwrap(); + + let output_path = PathBuf::from(output); + if let Some(parent) = output_path.parent() { + fs::create_dir_all(parent)?; + } + + let mut encoder = Encoder::new(File::create(output)?, 10)?; encoder.write_all(&out.into_inner().unwrap().into_inner())?; encoder.finish()?; diff --git a/nixos-aarch64-qemu.mp4 b/nixos-aarch64-qemu.mp4 new file mode 100644 index 0000000..38ea53f Binary files /dev/null and b/nixos-aarch64-qemu.mp4 differ diff --git a/nixos/example-system.nix b/nixos/example-system.nix new file mode 100644 index 0000000..de82a65 --- /dev/null +++ b/nixos/example-system.nix @@ -0,0 +1,79 @@ +{ config, pkgs, lib, ... }: + +# Example NixOS system configuration using microhop +# This is for internal development/testing purposes only +# Not intended for production use + +let + kernel = import ./kernel.nix { inherit pkgs; }; + uboot = import ./u-boot.nix { inherit pkgs; }; + + microhop = pkgs.callPackage ../. { }; + +in { + system.stateVersion = "25.11"; + + boot.kernelPackages = pkgs.linuxPackagesFor kernel; + + boot.loader = { + grub.enable = false; + generic-extlinux-compatible.enable = false; + }; + + boot.initrd = { + enable = true; + + compressor = "gzip"; + + extraUtilsCommands = '' + copy_bin_and_libs ${microhop}/bin/microhop + ''; + + preLVMCommands = lib.mkBefore '' + echo "Starting microhop minimal init..." + ''; + }; + + environment.systemPackages = with pkgs; [ + microhop + ]; + + boot.kernelParams = [ + "console=ttyAMA0" + "root=/dev/vda1" + "init=/init" + ]; + + fileSystems = { + "/" = { + device = "/dev/vda1"; + fsType = "ext4"; + }; + }; + + networking = { + hostName = "microhop-test"; + useDHCP = false; + firewall.enable = false; + }; + + services.xserver.enable = false; + + services.getty.autologinUser = "root"; + + users.users.root = { }; + + documentation.enable = false; + documentation.nixos.enable = false; + + environment.noXlibs = true; + security.polkit.enable = false; + security.rtkit.enable = false; + + system.build = { + inherit uboot; + + kernel = config.boot.kernelPackages.kernel; + initrd = config.system.build.initialRamdisk; + }; +} diff --git a/nixos/initramfs-microgen.nix b/nixos/initramfs-microgen.nix new file mode 100644 index 0000000..135411a --- /dev/null +++ b/nixos/initramfs-microgen.nix @@ -0,0 +1,30 @@ +{ pkgs, kernel, microgen, microhop, microhopConfig }: + +pkgs.stdenv.mkDerivation { + name = "initramfs-microgen"; + + nativeBuildInputs = [ microgen pkgs.cpio pkgs.gzip ]; + + unpackPhase = "true"; + + buildPhase = '' + mkdir -p $TMPDIR/rootfs/lib/modules + + if [ -d "${kernel}/lib/modules" ] && [ "$(ls -A ${kernel}/lib/modules)" ]; then + cp -r ${kernel}/lib/modules/* $TMPDIR/rootfs/lib/modules/ + fi + + mkdir -p $out + + ${microgen}/bin/microgen new \ + --root $TMPDIR/rootfs \ + --config ${microhopConfig} \ + --kernel-config ${kernel.configfile} \ + --filesystems squashfs,ext4 \ + --block-devices virtio_blk,virtio_mmio \ + --output $TMPDIR/build \ + --file $out/initrd + ''; + + installPhase = "true"; +} diff --git a/nixos/kernel.nix b/nixos/kernel.nix new file mode 100644 index 0000000..6a1d2be --- /dev/null +++ b/nixos/kernel.nix @@ -0,0 +1,222 @@ +{ pkgs ? import {} }: + +let + isNative = pkgs.stdenv.hostPlatform.isAarch64; + + kernelConfig = pkgs.writeText "kernel.config" '' + # Architecture + CONFIG_ARM64=y + CONFIG_64BIT=y + CONFIG_ARCH_VIRT=y + + # General setup + CONFIG_LOCALVERSION="" + CONFIG_DEFAULT_HOSTNAME="qemu-aarch64" + CONFIG_SWAP=n + CONFIG_SYSVIPC=y + CONFIG_POSIX_MQUEUE=y + CONFIG_CROSS_MEMORY_ATTACH=y + CONFIG_USELIB=y + CONFIG_AUDIT=n + CONFIG_IKCONFIG=n + CONFIG_LOG_BUF_SHIFT=17 + CONFIG_PRINTK=y + CONFIG_BUG=y + CONFIG_ELF_CORE=y + CONFIG_BASE_FULL=y + CONFIG_FUTEX=y + CONFIG_EPOLL=y + CONFIG_SIGNALFD=y + CONFIG_TIMERFD=y + CONFIG_EVENTFD=y + CONFIG_SHMEM=y + CONFIG_AIO=y + CONFIG_ADVISE_SYSCALLS=y + CONFIG_EMBEDDED=n + + # Kernel compression + CONFIG_KERNEL_GZIP=n + CONFIG_KERNEL_BZIP2=n + CONFIG_KERNEL_LZMA=n + CONFIG_KERNEL_XZ=n + CONFIG_KERNEL_LZO=n + CONFIG_KERNEL_LZ4=y + + # Kernel features + CONFIG_SMP=y + CONFIG_NR_CPUS=4 + CONFIG_HOTPLUG_CPU=y + + # Platform selection + CONFIG_ARCH_VIRT=y + + # Kernel Features + CONFIG_ARM64_PAGE_SHIFT=12 + CONFIG_ARM64_VA_BITS=39 + CONFIG_ARM64_4K_PAGES=y + + # Boot options + CONFIG_CMDLINE="console=ttyAMA0" + CONFIG_CMDLINE_FROM_BOOTLOADER=y + + # Virtualization + CONFIG_VIRTUALIZATION=n + + # General architecture-dependent options + CONFIG_HAVE_OPROFILE=y + CONFIG_KPROBES=n + CONFIG_JUMP_LABEL=n + CONFIG_MODULES=y + CONFIG_MODULE_UNLOAD=y + + # Executable file formats + CONFIG_BINFMT_ELF=y + CONFIG_BINFMT_SCRIPT=y + CONFIG_COREDUMP=y + + # Networking support + CONFIG_NET=n + + # Device Drivers + CONFIG_PCI=n + CONFIG_DEVTMPFS=y + CONFIG_DEVTMPFS_MOUNT=y + CONFIG_STANDALONE=y + CONFIG_PREVENT_FIRMWARE_BUILD=y + CONFIG_FW_LOADER=n + + # Generic Driver Options + CONFIG_BLK_DEV=y + + # Character devices + CONFIG_TTY=y + CONFIG_VT=y + CONFIG_VT_CONSOLE=y + CONFIG_CONSOLE_TRANSLATIONS=y + CONFIG_UNIX98_PTYS=y + CONFIG_LEGACY_PTYS=n + + # Serial drivers + CONFIG_SERIAL_AMBA_PL011=y + CONFIG_SERIAL_AMBA_PL011_CONSOLE=y + CONFIG_SERIAL_CORE=y + CONFIG_SERIAL_CORE_CONSOLE=y + + # Input device support + CONFIG_INPUT=y + CONFIG_INPUT_KEYBOARD=y + CONFIG_INPUT_MOUSE=y + CONFIG_INPUT_EVDEV=y + + # Hardware I/O ports + CONFIG_SERIO=y + CONFIG_SERIO_LIBPS2=y + + # Graphics support + CONFIG_FB=y + CONFIG_FB_SIMPLE=y + CONFIG_FRAMEBUFFER_CONSOLE=y + CONFIG_DUMMY_CONSOLE=y + CONFIG_DRM=y + CONFIG_DRM_SIMPLEDRM=y + + # Virtio drivers + CONFIG_VIRTIO=y + CONFIG_VIRTIO_MMIO=y + CONFIG_VIRTIO_BLK=y + CONFIG_VIRTIO_CONSOLE=n + + # File systems + CONFIG_EXT4_FS=y + CONFIG_EXT4_USE_FOR_EXT2=y + CONFIG_EXT4_FS_POSIX_ACL=y + CONFIG_EXT4_FS_SECURITY=y + CONFIG_JBD2=y + CONFIG_FS_MBCACHE=y + CONFIG_SQUASHFS=y + CONFIG_SQUASHFS_ZLIB=y + CONFIG_SQUASHFS_ZSTD=y + CONFIG_SQUASHFS_XZ=y + CONFIG_SQUASHFS_LZO=y + CONFIG_SQUASHFS_LZ4=y + CONFIG_SQUASHFS_FILE_CACHE=y + CONFIG_SQUASHFS_FILE_DIRECT=y + CONFIG_OVERLAY_FS=y + CONFIG_FILE_LOCKING=y + CONFIG_FSNOTIFY=y + CONFIG_DNOTIFY=y + CONFIG_INOTIFY_USER=y + CONFIG_PROC_FS=y + CONFIG_PROC_SYSCTL=y + CONFIG_PROC_PAGE_MONITOR=y + CONFIG_KERNFS=y + CONFIG_SYSFS=y + CONFIG_TMPFS=y + CONFIG_TMPFS_POSIX_ACL=y + CONFIG_TMPFS_XATTR=y + CONFIG_HUGETLBFS=n + CONFIG_MISC_FILESYSTEMS=y + CONFIG_DEVTMPFS_MOUNT=y + + # Pseudo filesystems + CONFIG_PROC_KCORE=n + + # Initial RAM filesystem and RAM disk (initramfs/initrd) support + CONFIG_BLK_DEV_INITRD=y + CONFIG_RD_GZIP=y + CONFIG_RD_BZIP2=y + CONFIG_RD_LZMA=y + CONFIG_RD_XZ=y + CONFIG_RD_LZO=y + CONFIG_RD_LZ4=y + + # Kernel hacking + CONFIG_DEBUG_KERNEL=y + CONFIG_DEBUG_INFO=n + CONFIG_DEBUG_FS=y + CONFIG_MAGIC_SYSRQ=y + CONFIG_PANIC_ON_OOPS=n + CONFIG_PANIC_TIMEOUT=0 + CONFIG_SCHEDSTATS=n + CONFIG_DEBUG_BUGVERBOSE=y + + # Security options + CONFIG_SECURITY=n + CONFIG_SECURITYFS=n + CONFIG_SECURITY_NETWORK=n + + # Cryptographic API + CONFIG_CRYPTO=y + CONFIG_CRYPTO_HASH=y + CONFIG_CRYPTO_HASH2=y + CONFIG_CRYPTO_CRC32C=y + + # Library routines + CONFIG_CRC_CCITT=n + CONFIG_CRC16=y + CONFIG_CRC32=y + CONFIG_CRC32C=y + CONFIG_ZLIB_INFLATE=y + CONFIG_ZLIB_DEFLATE=y + CONFIG_LZO_COMPRESS=y + CONFIG_LZO_DECOMPRESS=y + CONFIG_XZ_DEC=y + CONFIG_ZSTD_COMPRESS=y + CONFIG_ZSTD_DECOMPRESS=y + ''; + +in pkgs.linuxManualConfig { + inherit (pkgs) stdenv lib; + + version = "6.18.6"; + modDirVersion = "6.18.6"; + + src = pkgs.fetchurl { + url = "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.18.6.tar.xz"; + sha256 = "06x3z649mzwwkb1hvsy0yh7j5jk9qrnwqcmwy7dx8s1ggccrf927"; + }; + + configfile = kernelConfig; + + allowImportFromDerivation = true; +} diff --git a/nixos/microhop-config.nix b/nixos/microhop-config.nix new file mode 100644 index 0000000..98422e3 --- /dev/null +++ b/nixos/microhop-config.nix @@ -0,0 +1,28 @@ +{ pkgs ? import {} }: + +pkgs.writeText "microhop.conf" '' + # Microhop configuration for QEMU aarch64 with overlayfs over squashfs + # Kernel modules to load + # Note: virtio_blk, virtio_mmio, squashfs, and overlay are built-in (=y) in kernel.nix + modules: [] + + # Devices mounting + # Mount the squashfs rootfs from /dev/vdb (read-only) - this is the base layer + # The mountpoint should be empty to mount directly to sysroot + disks: + /dev/vdb: squashfs,,ro + + # Execute systemd/init after switching root + # Note: After chroot, /sysroot becomes /, so the init is at /init + init: /init + + # Temporary sysroot location + sysroot: /sysroot + + # Overlayfs configuration + # /dev/vda is the ext4 partition for the overlay upper/work dirs + overlay_dev: /dev/vda + + # Debug log output + log: debug +'' diff --git a/nixos/nixos-rootfs.nix b/nixos/nixos-rootfs.nix new file mode 100644 index 0000000..ec222b1 --- /dev/null +++ b/nixos/nixos-rootfs.nix @@ -0,0 +1,73 @@ +{ pkgs, nixpkgs }: + +let + pkgsMusl = import nixpkgs { + localSystem = { system = "aarch64-linux"; }; + crossSystem = { + config = "aarch64-unknown-linux-musl"; + isStatic = false; + }; + }; + + muslBash = pkgsMusl.bash; + muslCoreutils = pkgsMusl.coreutils; + muslUtilLinux = pkgsMusl.util-linux; + muslBusybox = pkgsMusl.busybox; + + initScript = pkgsMusl.writeScript "init" '' + #!${muslBash}/bin/bash + set -e + + echo "===========================================" + echo " Nixos rootfs Init for 'microhop'" + echo " Architecture: $(${muslCoreutils}/bin/uname -m)" + echo "===========================================" + echo "" + + ${muslUtilLinux}/bin/mount -t proc proc /proc + ${muslUtilLinux}/bin/mount -t sysfs sysfs /sys + ${muslUtilLinux}/bin/mount -t devtmpfs devtmpfs /dev + + echo "System initialized successfully!" + echo "" + + # Set up environment variables + export PATH="/bin:/sbin:/usr/bin:/usr/sbin:${muslBash}/bin:${muslCoreutils}/bin:${muslUtilLinux}/bin:${muslBusybox}/bin" + export HOME="/root" + export TERM="linux" + + echo "Starting interactive shell..." + exec ${muslBash}/bin/bash + ''; + + closure = pkgsMusl.closureInfo { + rootPaths = [ muslBash muslCoreutils muslUtilLinux muslBusybox initScript ]; + }; + +in pkgsMusl.runCommand "nixos-rootfs-musl" { + nativeBuildInputs = with pkgs; [ squashfsTools ]; +} '' + mkdir -p $out + mkdir -p $TMPDIR/rootfs + + mkdir -p $TMPDIR/rootfs/{dev,proc,sys,run,tmp,var,home,root,etc,bin,sbin,usr/bin,usr/sbin} + chmod 1777 $TMPDIR/rootfs/tmp + + mkdir -p $TMPDIR/rootfs/nix/store + for storePath in $(< ${closure}/store-paths); do + echo " $storePath" + cp -a $storePath $TMPDIR/rootfs/nix/store/ + done + + ln -s ${initScript} $TMPDIR/rootfs/init + + echo "root:x:0:0:root:/root:${muslBash}/bin/bash" > $TMPDIR/rootfs/etc/passwd + echo "root:x:0:" > $TMPDIR/rootfs/etc/group + echo "localhost" > $TMPDIR/rootfs/etc/hostname + + ${pkgs.squashfsTools}/bin/mksquashfs $TMPDIR/rootfs $out/rootfs.squashfs \ + -comp zstd \ + -Xcompression-level 15 \ + -noappend \ + -no-progress +'' diff --git a/nixos/u-boot.nix b/nixos/u-boot.nix new file mode 100644 index 0000000..3326fdf --- /dev/null +++ b/nixos/u-boot.nix @@ -0,0 +1,26 @@ +{ pkgs ? import {} }: + +pkgs.buildUBoot { + version = "2026.01"; + + src = pkgs.fetchurl { + url = "https://ftp.denx.de/pub/u-boot/u-boot-2026.01.tar.bz2"; + sha256 = "sha256-tg1YZc79vHXajaQVbFbEWOAN51pJuAwaLlipbjCtDVQ="; + }; + + defconfig = "qemu_arm64_defconfig"; + + extraMeta.platforms = [ "aarch64-linux" ]; + + filesToInstall = [ "u-boot.bin" "u-boot" ]; + + preBuild = '' + mkdir -p arch/arm64/include/asm + ''; + + postInstall = '' + if [ ! -f $out/u-boot.bin ]; then + echo "Warning: u-boot.bin not found in output" + fi + ''; +} diff --git a/profile/src/cfg.rs b/profile/src/cfg.rs index c566dfe..86fb1b3 100644 --- a/profile/src/cfg.rs +++ b/profile/src/cfg.rs @@ -44,6 +44,19 @@ impl MhConfDisk { } } +/// Overlayfs configuration +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct OverlayfsConfig { + pub device: String, + pub upper: String, + pub workdir: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct FirmwareConfig { + pub base: String, +} + /// Main configuration struct #[derive(Debug, Serialize, Deserialize, Default)] pub struct MhConfig { @@ -52,6 +65,9 @@ pub struct MhConfig { init: Option, sysroot: Option, log: Option, + overlayfs: Option, + firmware: Option, + mask_cmdline: Option>, } impl MhConfig { @@ -116,6 +132,27 @@ impl MhConfig { pub fn get_sysroot_path(&self) -> String { self.sysroot.to_owned().unwrap_or("/sysroot".to_string()) } + + pub fn use_overlayfs(&self) -> bool { + self.overlayfs.is_some() + } + + pub fn get_overlayfs(&self) -> Option<&OverlayfsConfig> { + self.overlayfs.as_ref() + } + + pub fn get_firmware(&self) -> Option<&FirmwareConfig> { + self.firmware.as_ref() + } + + pub fn get_firmware_base(&self) -> String { + self.firmware.as_ref().map(|f| f.base.clone()).unwrap_or("/lib/firmware".to_string()) + } + + /// Get list of cmdline parameters to mask/ignore + pub fn get_mask_cmdline(&self) -> Vec { + self.mask_cmdline.clone().unwrap_or_default() + } } /// Get the configuration diff --git a/rustfmt.toml b/rustfmt.toml index 1e342b0..e930e7e 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -19,5 +19,5 @@ tab_spaces = 4 # Perks use_field_init_shorthand = true -fn_args_layout = "Compressed" +fn_params_layout = "Compressed" use_try_shorthand = true diff --git a/src/cmdline.rs b/src/cmdline.rs new file mode 100644 index 0000000..7b8b5f4 --- /dev/null +++ b/src/cmdline.rs @@ -0,0 +1,50 @@ +use std::collections::HashMap; +use std::fs; +use std::io::Error; + +pub struct CmdLine { + params: HashMap, +} + +impl CmdLine { + pub fn new() -> Result { + Self::new_with_mask(&[]) + } + + pub fn new_with_mask(mask: &[String]) -> Result { + let cmdline = fs::read_to_string("/proc/cmdline")?; + let mut params = HashMap::new(); + + for param in cmdline.split_whitespace() { + if let Some((key, value)) = param.split_once('=') { + if !mask.contains(&key.to_string()) { + params.insert(key.to_string(), value.to_string()); + } + } else { + if !mask.contains(¶m.to_string()) { + params.insert(param.to_string(), String::new()); + } + } + } + + Ok(CmdLine { params }) + } + + pub fn get_root_device(&self) -> Option<&str> { + self.params.get("root").map(|s| s.as_str()) + } + + pub fn get_root_fstype(&self) -> Option<&str> { + self.params.get("rootfstype").map(|s| s.as_str()) + } + + pub fn get_root_options(&self) -> Option<&str> { + self.params.get("rootflags").map(|s| s.as_str()) + } +} + +impl Default for CmdLine { + fn default() -> Self { + Self::new().unwrap_or_else(|_| CmdLine { params: HashMap::new() }) + } +} diff --git a/src/kmodprobe.rs b/src/kmodprobe.rs index a99b8e4..dc40b3c 100644 --- a/src/kmodprobe.rs +++ b/src/kmodprobe.rs @@ -40,7 +40,13 @@ impl KModProbe { /// Load a kernel module pub fn modprobe(&self, name: &str) { let mp: PathBuf = if !name.contains('/') || !name.contains('.') { - self.find_module(name).unwrap_or_default() + match self.find_module(name) { + Some(path) => path, + None => { + log::error!("Kernel module {} not found in {}", name, self.km_path.display()); + return; + } + } } else { self.km_path.join(name) }; diff --git a/src/main.rs b/src/main.rs index 57186e7..bb89ece 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,57 @@ +mod cmdline; mod kmodprobe; mod logger; mod microhop; use crate::microhop::{get_blk_devices, greet, mount_fs, SYS_MPT}; -use nix::{mount::MsFlags, sys::stat, unistd}; -use std::{ffi::CString, io::Error, path::Path}; +use nix::{mount::MsFlags, unistd}; +use std::{ffi::CString, fs::DirBuilder, io::Error, os::unix::fs::DirBuilderExt, path::Path}; static LOGGER: logger::STDOUTLogger = logger::STDOUTLogger; fn main() -> Result<(), Error> { // Set logger - let cfg = profile::cfg::get_mh_config(None)?; - log::set_logger(&LOGGER).map(|()| log::set_max_level(cfg.get_log_level())).unwrap(); + let cfg = match profile::cfg::get_mh_config(None) { + Ok(cfg) => cfg, + Err(err) => { + eprintln!("ERROR: Failed to load configuration file /etc/microhop.conf"); + match err.kind() { + std::io::ErrorKind::NotFound => { + eprintln!(" Reason: Configuration file not found"); + } + std::io::ErrorKind::InvalidData => { + eprintln!(" Reason: Configuration file is invalid or contains syntax errors"); + eprintln!(" Details: {}", err); + } + _ => { + eprintln!(" Details: {}", err); + } + } + return Err(err); + } + }; + + // Set up logger, defaulting to Info level if there's an issue + if let Err(err) = log::set_logger(&LOGGER) { + eprintln!("WARNING: Failed to set up logger: {}", err); + } + + // Set log level from config, with validation + let log_level = cfg.get_log_level(); + if let Some(level_str) = cfg.get_log_level_as_str() { + match level_str.as_str() { + "debug" | "info" | "quiet" => { + log::set_max_level(log_level); + } + _ => { + eprintln!("WARNING: Invalid log level '{}' in configuration. Using 'info' as default.", level_str); + eprintln!(" Valid options are: debug, info, quiet"); + log::set_max_level(log::LevelFilter::Info); + } + } + } else { + log::set_max_level(log_level); + } greet(&cfg)?; @@ -27,7 +67,7 @@ fn main() -> Result<(), Error> { // Create sysroot entry point let temp_mpt = &cfg.get_sysroot_path(); if !Path::new(temp_mpt).exists() { - unistd::mkdir(temp_mpt.as_str(), stat::Mode::S_IRUSR)?; + DirBuilder::new().recursive(true).mode(0o755).create(temp_mpt.as_str())?; log::debug!("Init sysroot path: {}", temp_mpt); } @@ -39,24 +79,77 @@ fn main() -> Result<(), Error> { } mount_fs(&blk_mpt); + let use_overlayfs = cfg.use_overlayfs(); + let final_root = if let Some(overlay_cfg) = cfg.get_overlayfs() { + log::info!("Overlayfs enabled in configuration"); + + if !syslib::fs::is_overlayfs_supported() { + log::error!("Overlayfs requested but not supported by kernel!"); + log::error!("Make sure CONFIG_OVERLAY_FS is enabled in kernel config"); + return Err(Error::new(std::io::ErrorKind::Unsupported, "Overlayfs not supported")); + } + + use crate::microhop::resolve_device_path; + let mut blkid = syslib::blk::BlkInfo::new(); + blkid.probe_devices()?; + + let overlay_dev_path = resolve_device_path(&overlay_cfg.device, &blkid).ok_or_else(|| { + Error::new(std::io::ErrorKind::NotFound, format!("Could not resolve overlayfs device: {}", overlay_cfg.device)) + })?; + + log::info!("Using overlayfs device: {} ({})", overlay_dev_path, overlay_cfg.device); + + let lower_dir = temp_mpt; + let overlay_mount = "/overlay"; + let merged_dir = "/overlay/merged"; + + DirBuilder::new().recursive(true).mode(0o755).create(overlay_mount)?; + + syslib::fs::mount("ext4", overlay_dev_path, overlay_mount)?; + log::info!("Mounted overlayfs backing device at {}", overlay_mount); + + let upper_full = format!("{}/{}", overlay_mount, overlay_cfg.upper); + let work_full = format!("{}/{}", overlay_mount, overlay_cfg.workdir); + + DirBuilder::new().recursive(true).mode(0o755).create(merged_dir)?; + + syslib::fs::mount_overlayfs(lower_dir, &upper_full, &work_full, merged_dir)?; + + merged_dir + } else { + temp_mpt + }; + // Remount sysfs, switch root log::debug!("switching root"); for t in SYS_MPT { - let tgt = format!("{}{}", temp_mpt, t.dst); + let tgt = format!("{}{}", final_root, t.dst); nix::mount::mount(Some(t.dst), tgt.as_str(), Some(t.fstype), MsFlags::MS_MOVE, Option::<&str>::None)?; } // Pivot the system - syslib::fs::pivot(temp_mpt, root_fstype.as_str())?; + syslib::fs::pivot(final_root, if use_overlayfs { "overlay" } else { root_fstype.as_str() })?; // Start external init log::info!("Launching init at {}", cfg.get_init_path()); - let argv: Vec = vec![CString::new(cfg.get_init_path()).unwrap()]; + let init_path = cfg.get_init_path(); + let init_cstring = match CString::new(init_path.clone()) { + Ok(s) => s, + Err(err) => { + log::error!("Failed to create init path argument: contains null bytes"); + log::error!("Init path: {}", init_path); + log::error!("Details: {}", err); + return Err(Error::new(std::io::ErrorKind::InvalidInput, format!("Init path '{}' contains null bytes", init_path))); + } + }; + + let argv: Vec = vec![init_cstring.clone()]; #[allow(irrefutable_let_patterns)] - if let Err(err) = unistd::execv(&CString::new(cfg.get_init_path()).unwrap(), &argv) { - log::error!("{:?}", err); + if let Err(err) = unistd::execv(&init_cstring, &argv) { + log::error!("Failed to execute init process: {:?}", err); + log::error!("Init path was: {}", init_path); } Ok(()) diff --git a/src/microhop.rs b/src/microhop.rs index 825f35f..beee5a0 100644 --- a/src/microhop.rs +++ b/src/microhop.rs @@ -9,11 +9,16 @@ pub struct SystemDir> { pub fstype: T, pub dev: T, pub dst: T, + pub mode: Option, } impl> SystemDir { const fn new(fstype: T, dev: T, dst: T) -> Self { - Self { fstype, dev, dst } + Self { fstype, dev, dst, mode: None } + } + + pub fn new_with_mode(fstype: T, dev: T, dst: T, mode: T) -> Self { + Self { fstype, dev, dst, mode: Some(mode) } } } @@ -51,13 +56,95 @@ pub fn greet(cfg: &MhConfig) -> Result<(), Error> { /// Mount configured filesystems in a batch pub fn mount_fs>(filesystems: &[SystemDir]) { + use nix::mount::MsFlags; + for t in filesystems { - if let Err(err) = syslib::fs::mount(t.fstype.as_ref(), t.dev.as_ref(), t.dst.as_ref()) { + let mut flags = MsFlags::MS_NOATIME; + if let Some(mode) = &t.mode { + if mode.as_ref() == "ro" { + flags |= MsFlags::MS_RDONLY; + } + } + + if let Err(err) = syslib::fs::mount_with_flags(t.fstype.as_ref(), t.dev.as_ref(), t.dst.as_ref(), flags) { log::error!("Error mounting {}: {}", t.dst.as_ref(), err); }; } } +/// Parse device specification and resolve to device path +/// Supports: uuid=, label=