v6.2.3-rc.2
Fixes
-
runtimeWarn about untracked VFS rules instead of aborting the bootcheck_untracked_vfsfailed the boot when any live VFS rule was not derived from the plan, which is the opposite of what the mount path does deliberately:report_legacylogs an unattributable live mount and lets the pipeline continue, because APatch and KernelSU both drop a metamodule script that exits non-zero, so an abort costs every module its mounts. A rule that was not placed by this plan is the same kind of ambiguity — it can be a leftover whose ledger is gone, or another tool's rule — and nothing needs to fail over it. Log each rule and continue, and keep returning the kernel's original uid list for the ownership ledger. Nothing later adopts these rules either: new rules are installed alongside them, a collision surfaces as a batch failure with its offset, and cleanup reconciles only the rules this ledger recorded. This host has no linker for any Rust target, so CI is the first compiler to see this change. -
overlayfsSkip a refused sub-mount instead of failing the bootmount_overlayrebuilds the pre-existing sub-mounts of an overlay root as overlays of their own, passing the child mount point itself as the last lowerdir. Some kernels reject such a layer. A module's ownpost-fs-data.shbind-mounts content from/data, that bind is detected as a sub-mount of the partition the module overlays, and on the reported device the f2fs bind at/my_product/media/theme/uxicons/hdpimade overlayfs answerfilesystem on './theme/uxicons/hdpi' not supported:fsconfig createfailed with EINVAL, the legacymount(2)fallback failed the same way, and the child loop returned that error. A returned child error aborts the entire overlay phase, so the pipeline rolled back every registered target and the metamodule exited non-zero — which costs every module its mounts (docs/RUNTIME.md). This is the same ambiguity37b778faand92ad95afalready downgraded to a warning. Log the failing sub-mount, count it and continue. The root overlay, which stays fatal so the transaction can still roll back, already serves that subtree's module content from the staged module layers, and the pre-existing bind hangs off the superseded mount, so copying it back is not possible either.enable_next_child_overlay_mount_failurefault injection covers the new path with a real child mount. Host tests pass except the tenvfs::clipath-separator andruntime::mountsmountinfo failures that fail on the unmodified tree on Windows too; the Linux-gated test needs CI. -
magicRegister real file binds in the KernelSU try-umount list A magic file override without a tmpfs skeleton binds the module file directly onto the real system path, so per-app unmounting needs that path in the KernelSU try-umount list. The registration guard looked at the staging directory instead, withself.umount && !self.work_dir_path.starts_with("/mnt"). The staging root is/mnt/<random>on every Android boot, so the condition was always false and a direct file bind was never registered; only the directory mounts were. Decide from where the bind landed instead of where the staging root lives.unmountable_target(umount, has_tmpfs, target)yields the target only when the bind went onto the real path. With a tmpfs skeleton the file is bound inside the staging tree, which the owning directory mount later moves onto the real path and registers there, so an entry naming the staging path would outlive its mount and the kernel would replay it on every later umount for the rest of the boot. Host tests pass except the tenvfs::clipath-separator andruntime::mountsmountinfo failures that fail on the unmodified tree on Windows too; the Linux-gated test needs CI. -
scannerStop a mismatched directory from reserving its declared module id The directory-name-vs-declared-id check ran afterdeclared_ids.insert, so a directory skipped for a name mismatch still reserved its declared id. A later, correctly namedmodules/<id>directory then died on the fatalError::DuplicateModuleIdeven though no duplicate module existed. Move the check before the insert. The regression test buildsroot/shared(idshared, name matches) besideroot/other_dir(idshared, name mismatched) and asserts that exactly one module is listed, the one whose directory name matches.
Documentation
-
Drop redundant reports and deduplicate provenance prose
docs/RUST_BACKEND_REVIEW.mdrestated materialdocs/ARCHITECTURE.mdanddocs/RUNTIME.mdalready cover, anddocs/README_EN.mdwas an 88 B stub pointing at the root README that nothing referenced.module/vfs/README.mdrepeated, divergence for divergence, the ledger thatmodule/vfs/src/PROVENANCErecords as the normative source, plus the GPL rationale that THIRD_PARTY.md owns. Keep the ledger in PROVENANCE and reduce README.md to a summary that points at it. Record the local scratch directory in .gitignore while here. -
licenseAttribute meta-magic_mount-rs and the vendored skillssrc/magic_mount/mod.rsdocuments the backend as behaviour-compatible with meta-magic_mount-rs8b85c9e, README.md and every locale credit it, andwebui/src/ui/miuix/components/StatusCard.vueis derived from it — but THIRD_PARTY.md, which does list the comparable lkmloader, had no entry for it. Add one, record the vendored skill trees that THIRD_PARTY.md already pointed at, and qualify the "the WebUI is Apache-2.0" claim for the one file that is not.GPL-v3is not a valid SPDX identifier, so correct that header to theGPL-3.0-onlythe rest of the project uses. -
Resync the translated readmes and the skill references with the VFS backend The ten translated readmes still carried the pre-45402cab loader paragraph: they claimed the bundled VFS module is not loaded when no rule selects VFS, and that selection is an exact kernel-line plus Android/GKI match. Both are false.
src/vfs/mod.rsloads the bundled module on every boot before planning, andsrc/vfs/lkm_target.rsfalls back to other builds on the same kernel line. Resync them with README.md: the boot-logic paragraph, the loader paragraph (missing from all ten entirely), the Android userspace note, the WebUI note and the Runtime CLI note. Also record the VFS backend in CLAUDE.md, correct the LKM selection description in docs/ARCHITECTURE.md, and refresh the CLI list, module map and lifecycle helpers in the hybrid-mount-overview and hm-runtime skills.
Tests
overlayfsDrive the refused sub-mount skip from a synthetic mount list The sub-mount test built a real tmpfs mount and expected the kernel to report it as a child of the fixture overlay root.require_mount_namespaceunshares only the calling test thread, while the mount list is read through/proc/self, which resolves to the thread-group leader, so a test thread's own mount is invisible and the assertion failed in CI with "the sub-mount was not discovered". The sibling mount tests pass vacuously for the same reason. Extract the sub-mount rebuild loop intorebuild_sub_mountsand drive it from a synthetic discovered list, so the skip path is covered without a real mount or a private mount namespace. Production behaviour is unchanged: a sub-mount the kernel refuses is warned and skipped, while a root mount failure stays fatal and rolls the transaction back.
Miscellaneous
-
releaseStop shipping debug data in the package The release zip grew from 3.27 MiB to 6.40 MiB in two steps: v6.2.1 started shipping the prebuilt hybridmount modules, and v6.2.2 added the riscv64 binary. What made both steps so expensive is that nothing was ever stripped. The Rust binaries carried a full symbol table — 1152 KiB in the arm64 build, 1956 KiB in riscv64, which is 47% of that file. The kernel modules carried DWARF that dwarfs them: the 5.15 module is 640 KiB, of which 25 KiB is .text. Strip all three places: *[profile.release] strip = "symbols"in Cargo.toml.debuginfowould only recover 98 KiB of the ~4.7 MiB of symbol tables, because these binaries carry no DWARF at all. *llvm-strip --strip-debuginside the DDK build, so the committed modules and their list.txt stay a description of what actually ships, which is the invariant lints.yml checks. * the ten committed modules re-stripped in place, keeping .symtab, __versions, .modinfo and every run-time relocation section. Measured on a rebuild of the v6.2.3-rc.1 archive with the stripped artifacts: 6,728,704 B -> about 4,862,891 B, -1.78 MiB (-27.7%). Also pin the digest manifests to LF and mark *.ko -text, so a Windows checkout stops rewriting bytes that scripts compare verbatim. -
Validate the DSH skills and retire the license-header workflow
.dsh/validate-skills.mjswas documented in.dsh/README.mdbut run by nothing, so skill frontmatter and relative-link drift could only be noticed by hand. Add it to lints.yml, whose job already runs node for the WebUI, and document the gate in hm-verify. Deletelicense_header.yml. It invoked hawkeye withlicenserc.toml, which exists on no current branch — it and theLICENCE_HEADERtemplate were lost in thedf3dc990history-splicing merge — so the weekly job had failed five times in a row (exit 101, "cannot load config: licenserc.toml"). One hawkeye config can express exactly one license text and this repository has three (core GPL-3.0-only, WebUI Apache-2.0, kernel GPL-2.0-only), so a replacement gate needs several scoped configs, not just the missing file. Track the skill trees and the validator as well: they were untracked while.dsh/README.md, THIRD_PARTY.md and docs/ referenced them, so a fresh clone lost the MIT attribution targets. The vendored trees keep their own LICENSE and their frontmatter provenance. Fix the stale skill descriptions while here: hm-rust-lsp described awebprofile that does not exist on this machine and a dsh-lsp-actions version that cannot install, and hm-ci still counted eight workflows and described a KernelSU repository step that is disabled. -
licenseGive the remaining files an SPDX identifierclippy.toml,webui/vite.config.tsandwebui/index.htmlcarried no license header at all, and the eightwebui/src/ui/md3/v420/*.cssfiles carried the Apache-2.0 prose header without the machine-readable identifier that the rest of the tree uses. Add it to each, in the block stylewebui/src/ui/md3/icons.tsalready uses.webui/pnpm-lock.yamlstays as it is: pnpm regenerates the file whole, so a header there would be dropped by the next dependency bump. -
vfsRefresh the prebuilt hybridmount modules -
Deduplicate the mount, planning and VFS control paths Collapse the mechanical duplication found by the full-project review without changing behaviour: - pipeline: the five failure epilogues become
record_mount_failure, which keeps the state persist, the unmounted-module snapshot and the returned error together so the WebUI summary cannot drift from state.json. - sys/faults: onetake(gate)helper replaces four hand-written#[cfg(test)]/#[cfg(not(test))]pairs. - sys/process:start_drainreplaces the duplicated stdout/stderr capture setup. - overlayfs/utils: one genericretry_ebusyover a smallBusytrait replaces theio::Errorandrustix::io::Errnovariants. - overlayfs/overlayfs: onebindclosure for the two bind-mount failure arms. - plan:child_targetreplaces three identical target builders,MountPlan::vfs_module_id_stringsreplaces four collect sites, anddefs::MOUNT_ERROR_REASONreplaces the repeated marker literal. - state/mount_tree/utils/config/errors: a shared mount-error directory walker, a delegated mount-tree collector, one xattr reader and one config loader, and the test-only error classifiers. - vfs:paginate, the version classification aroundload_lkmand the read-back closure inController::mutateare shared; the production-deadapply_rules_with_policyand a stale#[allow(dead_code)]are gone. Also cache/proc/config.gzin aOnceLockinstead of gunzipping it twice per boot, and look the mount snapshot up in its id map instead of scanning every mount point. Retire the four assertions that stronger siblings already cover:faults::gates_are_consumed_exactly_once,backend_tests::upstream_nomount_version_is_rejected,timing::timer_without_finish_drops_as_aborted(asserted nothing), and the trailingassert!(module.disabled)inruntime::policy::temporary_load_preserves_disabled_marker_semantics. -
Run the emulated soft-reboot hook test tests/shell/runtime_hooks.sh was executed by no workflow; only shellcheck's glob parsed it. Run it beside the boot-lock gate. That is safe because module/emulated-soft-reboot.sh execs exactly the
runtime prepare-rebootinvocation the test's fake binary accepts. -
skillsVendor the rust-unsafe-review and rust-guidelines skills Vendor two upstream skill sets under .dsh/skills and record them in the skill README: - rust-unsafe-review (google/rust-skills, Apache-2.0): the upstream name is renamed to match its directory, and EVAL.yml, the testcases and the experimental tree are dropped. - rust-guidelines (microsoft/rust-guidelines, MIT): curated to the correctness, universal, ffi and checklist chapters because the rules already vendored from rust-skills cover the rest. Both carry a Hybrid Mount precedence section, andnode .dsh/validate-skills.mjsreports 19 skills and 0 problems.