v6.2.1-rc.1
Features
-
configAdd vfs mount mode -
planRecord vfs modules in mount plan -
planReject vfs targets shadowed by mounted ancestors -
vfsMap mount tree to vfs rules -
errorsAdd structured vfs error variants -
vfsEncode nomount wire payloads -
vfsAdd keyring page buffer and add_key sender -
vfsSelect a single kernel provider -
stateExpose vfs modules and provider -
vfsApply plan rules and report stats -
configAdd vfs strict and uid isolation options -
pipelineRun vfs backend after magic mount -
webuiExpose vfs mode and status -
vfsKeep the first ADD_RULE failure and its record offset The upstream kernel overwrites payload->status for every record of an ADD_RULE batch, so a failure in the middle is silently masked by a later success. K2 now latches the first negative status together with the offset of the record that failed, and leaves arg1 as the consumed cursor only on success. ensure_consumed surfaces that offset in its protocol error, so a partially applied batch is both detected and locatable for rollback. The kernel half cannot be compiled here and still needs the kernel build matrix before it is verified. The userspace half is covered by a new unit test (262 workspace tests pass), clippy is clean and the aarch64, armv7 and x86_64 Android targets check with no warnings. Source: maxsteeel/nomount@016375c Co-authored-by: maxsteeel 109047395+maxsteeel@users.noreply.github.com -
vfsImplement .replace as an opaque directory subtree Add HM_FLAG_OPAQUE: a rule that keeps its directory visible, hides every real child and shows only the injected ones. hm_alloc_rule expands it to the virtual-directory representation and does not resolve a real path for it. On the userspace side a replace directory now yields an OpaqueDir rule for itself and for every directory below it, matching Magisk replace semantics: a virtual directory makes a child without a rule invisible, so descendant directories must carry one even when their own files are injected. The plan stage no longer rejects vfs .replace. It enforces subtree exclusivity instead: a vfs .replace subtree must not contain overlay or magic sources, because the opaque rule would hide their mounts. Error::VfsReplaceUnsupported is removed. The kernel half cannot be compiled here and still needs the kernel build matrix plus a device check. The userspace half is covered by three new tests (265 workspace tests pass), clippy is clean and the aarch64, armv7 and x86_64 Android targets check with no warnings. Source: maxsteeel/nomount@016375c Co-authored-by: maxsteeel 109047395+maxsteeel@users.noreply.github.com -
vfsLoad the bundled K2 module at boot The runtime previously had a no-op loader, so a built hybridmount.ko could never be attached. VfsLkmLoader now selects the module for this kernel, arms a boot guard, loads it through the usual insmod candidates (ksud/busybox/insmod) and treats the keyring response as authoritative rather than the insmod exit code. A load failure is not fatal: the caller re-probes and degrades or fails according to vfs_strict, exactly like a device that never had K2. The release-to-filename mapping lives in vfs::lkm_target without platform conditionals, so it is unit tested on the host, and it matches the DDK matrix in .github/workflows/kernel-module.yml one to one. Packaging follows: customize.sh drops module/vfs/src (development input for setup.sh) on every platform while keeping module/vfs/binaries, xtask keeps the source out of the release zip, and the installer test asserts both. Verified: actionlint, shellcheck, both shell tests, cargo fmt, clippy -D warnings, 268 workspace tests and the three Android target checks all pass. -
vfsGive K2 its own wire magic and document the real fork divergences The payload magic was still upstream's 0x4E4F4D4F554E54 ("NOMOUNT"), a public constant, so the only thing keeping a stock nm CLI out was the key type name. It is now 0x4859425249444D4F (ASCII "HYBRIDMO"), which makes the separation hold at the wire level: a foreign payload is rejected during preparse with -EFAULT regardless of whether the caller checks the version string. The constant is also the full_name_hash seed, so both sides moved together, and a mismatch is harmless at boot: probing fails and the backend degrades exactly like a device without K2. The documented divergence list was also wrong or incomplete. module/vfs/README.md and PROVENANCE did not mention HM_SIG_16 being 'hm' rather than upstream's 'nm' (the high half of the packed virtual loff_t, an in-kernel ABI of its own) or HM_FLAG_OPAQUE being a flag upstream does not have, and both still described the magic as upstream's. The themes from the previous fix are recorded too: unresolvable real paths, out-of-range cursors and first-error reporting. THIRD_PARTY.md now states how non-interoperability is actually enforced, and the spec's open decision 17.2 is closed with the reasoning. Stale statements elsewhere: select_provider still claimed LkmLoader was a no-op, which stopped being true when the loader landed; docs/ARCHITECTURE.md listed anomountvalue for vfs_provider that v2 can never produce; README and README_ZH called K2 "when delivered" while it already ships, and omitted the unresolved upstream license question that every other document raises. The plan document's frozen wire constants still had the old magic, no OPAQUE flag, a K1 provider and version "20". The new kernel_header_magic_and_version_match_userspace test reads hybridmount.h and compares both constants, so a one-sided edit fails CI instead of silently degrading VFS on device. The existing page[0..8] == MAGIC.to_le_bytes() assertion could not catch that: it is self-referential and passes whichever value the constant holds. Verified by mutating the header both ways and watching the respective assertion fail. Consequence for artifacts: the committed .ko files were built with the old magic, so module/vfs/binaries must be refreshed by re-running kernel-module.yml with commit_binaries=true before a release. Nothing here compiles the kernel module; this machine has no kernel tree or Docker. Verified: cargo fmt, clippy -D warnings, 284 workspace tests, the three Android target checks, shellcheck, both shell tests, WebUI lint/test/build and both list.txt digests. -
vfsAdd the K2 list control plane and a vfs-doctor diagnostic GET_LIST and GET_UIDS decode and paginate through the kernel's two listing commands so user space can enumerate what it has installed. A stall detector rejects a kernel that reports a non-advancing cursor, which the upstream NoMount loop only detects by chance. The new vfs-doctor command probes the registered key type, classifies hybridmount as built-in, loadable or absent, and prints a one-line diagnosis plus the current rule and uid tables. It is read-only: a built-in kernel never triggers a bundled-module load. Co-Authored-By: Claude Fable 5.1 noreply@anthropic.com -
vfsLoad the bundled hybridmount module and gate VFS on it Selectingvfsused to produce a plan the executor could not run on a kernel without the module, so a user saw a VFS option that silently did nothing. The module now gets loaded on demand, and everything that advertises VFS asks one question first: does the key type answer? -vfs::lkmselects the.komatching the kernel release and Android version, arms the shared boot guard, andinsmods it throughsys::lkm. A failed load is not fatal: the caller probes again and takes the normal degradation path -vfs::lkm_target::select_lkm_filenamemaps release plus Android major to the seven DDK targets, and a test reads the workflow so the matrix and the packaged names cannot drift apart -vfs::available()is the single authority;planthreads it throughPlanInputso avfsrule degrades toignoreinstead of planning work that cannot run, andstate/pipelinereport the same answer -vfs::doctor::responds_now()stays probe-only.insmodhappens in the mount pipeline alone, so a status or doctor query never loads a module -select_providertakes the loader as a closure, keeping the "probe, load, probe again" order in one place The VFS module gets its own override and boot-guard path (HYBRID_MOUNT_VFS_LKM_PATH,vfs_lkm_boot_guard) so a nuke crash and a VFS crash never mask each other. Rule tests now share oneMountSourcefixture instead of two copies. -
vfsLog enough to tell whether a device mount worked A boot log could not answer the first question a device session asks: did VFS actually install anything? The phase had a single completion line and no way to see the decision that led to it. Report the whole control path instead. The probe result is logged next to whether rules even wanted VFS, both plan lines now carry vfs_modules, and the phase announces its start, its batch size and the reason it was skipped. The completion line is followed by a read-back of the provider table: an acknowledged batch is not proof the rules are installed, so the virtual paths that did not come back are listed. That comparison is by virtual path because the module keys rules that way, and it deliberately does not require the table to match exactly, since rules from an earlier run in the same boot stay installed until reboot.list_rulesmoves onto the VfsKernel trait to make the read-back possible through the same double the tests already use. It was only ever called by vfs-doctor before, which is why the trait had no use for it. Every record now carries the logcat tag HybridMount, so one boot can be followed withlogcat -s HybridMount; the module path moves into the message and stays visible. -
vfsLog why the kernel rejected a payload A rejected rule and an absent module were indistinguishable on a device: both showed up as a bare -EFAULT or a nonzero status, with nothing in dmesg to say which one happened. Print the page-pin failure, the payload magic on a mismatch, and the status/offset of a failed add or delete batch. The magic is the valuable one, since a userspace build talking a different protocol now announces itself instead of looking like a module that never loaded. These run on the keyring control path, a handful of times per boot, so the printk cost is irrelevant next to the lookup paths, which stay untouched. The added warning sits outside the write lock: __hybridmount_add_rule takes and releases the rwsem itself. The prebuilt modules are now stale and are refreshed by the next kernel-module workflow run.
Fixes
-
releaseMark only two-digit patch tags as prereleases -
mountDetect mount source from the root backend -
planInclude module id in vfs shadow conflict source -
vfsGuard exchange length and cover provider branches -
pipelineClear vfs boot guard on handled failures and register rollback first -
vfsTreat ECANCELED as success, reject vfs replace, degrade on version and validate batch cursor -
vfsDelete only this run's rules on rollback and dedupe targets PR #471 review findings: - P1: rollback now issues targeted DEL_RULE for the virtual paths this run installed (tolerating ENOENT) instead of CLEAR_RULES, so pre-existing NoMount rules from other modules survive a failed pipeline. - P2: one rule per virtual path; the last Vfs source in node.sources wins, matching module precedence instead of emitting duplicate targets. -
vfsRegister the full rule batch before applying it apply_rules is not atomic, but the rollback closure previously only learned what it should delete after apply_plan returned Ok. A failure partway through the batch therefore rolled back nothing and left the already-live prefix of rules installed in the provider, contradicting the comment on the closure. Build and register the batch before the first kernel call: every rule is now deleted on failure, and rules that never took effect are tolerated via the kernel's -ENOENT reply for a missing (vpath, uid) match. plan_rules and apply_rules replace the apply_plan wrapper so callers cannot skip the registration step. -
vfsForce gnu11 so the module builds on pre-5.18 kernels The first DDK run compiled android14-6.1, android15-6.6 and android16-6.12 but failed on android12-5.10, android13-5.10, android13-5.15 and android14-5.15. The split is exactly kernel 5.18, after which kbuild defaults to gnu11; older trees default to gnu89, which rejects the C99 declarations used throughout the sources. The upstream build always passed -std=gnu11 and this was lost when the build was rewritten for DDK. Restore it in both the out-of-tree Makefile and the in-tree fragment written by setup.sh, and assert it in the integration test. Source: maxsteeel/nomount@016375c Co-authored-by: maxsteeel 109047395+maxsteeel@users.noreply.github.com -
vfsStop silent rule failures and contain the vfs blast radius Four defects on the K2 path made failures look like successes. hm_alloc_rule resolved the real path with kern_path and silently skipped the real inode when it failed: the rule was still inserted and ADD_RULE still reported success, yet neither the injected nor the fallback branch of resolve matched, so the mount never happened. It now fails with -ENOENT. d_backing_inode was also dereferenced unchecked on both the real and virtual paths; the virtual path now falls back to v_hash and the real path is guarded. this_dir allocation failure was ignored for directory rules. An opaque rule then had VIRTUAL_DIR set with no directory node, making every injected child unreachable while ADD_RULE still reported success. It now fails with -ENOMEM and tears down the rule the same way hm_free_rule does. ADD_RULE and DEL_RULE both advanced their cursor from userspace-supplied arg1, so an out-of-range value made (size_t)(buf_end - buf_ptr) wrap and the loop walk off the payload. Both commands now reject arg1 > data_size. DEL_RULE additionally reported status 0 while its cursor stopped mid-batch, so a truncated rollback looked complete and the leftover rules stayed live for the boot; it now reports the first error and its offset, matching ADD_RULE. The same silent break in ADD_RULE got the same treatment. Two failure paths could take down the whole boot. The new -ENOENT is a per-rule error, so a single unreadable path aborted the pipeline and rolled back working Overlay and Magic mounts; apply_rules_with_policy now follows vfs_strict, removing the applied prefix and degrading to no VFS when VFS is optional. UID isolation failed on the second pipeline run of a boot because ADD_UID returns -EEXIST for an already-isolated UID; the list is deduplicated and -EEXIST is treated as the idempotent success it is. ensure_status_allow_enoent also validates the batch cursor now, since ENOENT with a short cursor means records remain. Verified: cargo fmt, clippy -D warnings, 282 workspace tests, the three Android target checks, shellcheck, both shell tests, WebUI lint/test/build and both list.txt digest checks. The kernel change is not compiled here: this machine has no kernel tree or Docker, so module/vfs/binaries still holds the previous build until kernel-module.yml is re-run with commit_binaries=true. -
vfsShip the K2 sources with the package module/vfs and module/lkm are not duplicates: separate upstreams, separate licences, separate .ko sets. What differed was packaging. lkm/src shipped in the release ZIP and survived installation, while xtask deleted vfs/src from the stage and customize.sh deleted it again on every device. The installed module therefore carried a setup.sh that could not work and a README describing sources that were not there: $ sh /path/to/installed/vfs/setup.sh cp: cannot stat '.../vfs/src/*.c': No such file or directory exit 1 Ship vfs/src like lkm/src, so an installed module can still be integrated into a kernel tree and a prebuilt .ko stays accompanied by its source, as THIRD_PARTY.md already claims for both components. Shipping the directory exposes what deleting it had masked: build copies the working tree, and module/vfs/src is where an in-place build writes its .ko, .o, .mod and .cmd files, so those would have been packaged. prune_kernel_build_output strips Kbuild output from both source trees instead of dropping a whole directory. Also fix a flaky helper found while gating this: notify's make_temp_output_dir derived its path from SystemTime alone, which returns the same value for about 96% of back-to-back reads here, so the two parallel tests that use it shared one directory and deleted each other's fixtures. 11 of 40 runs failed before the change and 0 of 40 after. Verified: a package built with vfs/src added runs setup.sh to exit 0 and wires fs/Makefile and fs/Kconfig, where the current release artifact exits 1. Both new guards fail under mutation. fmt, clippy -D warnings, 285 workspace tests, three Android targets, shellcheck, both shell tests, lints.yml assertions, the LKM and VFS digests and WebUI lint/70 tests all pass. -
p3Count magic directory mounts and whiteouts in mount stats; fix storage_mode display HM-RUST-013: Magic Mount directories (Move/Replace) were not added to total_mounts/successful_mounts, so the WebUI reported an inconsistent count when only directories were mounted. Added magic_dirs to MountStatistics and counted them at the Move/Replace exit; also wired the missing whiteout counter into ignored_files. HM-RUST-014: RunState.storage_mode was seeded from config.overlay_mode in from_plan instead of the actual storage backend created by the pipeline. A Magic-only run with a configured ext4 mode showed "Ext4" in the module description even though no overlay storage was created. Changed from_plan to seed an empty string; running_description now renders "none" as an empty tag instead of falling through to "Ext4". Co-Authored-By: Claude Fable 5.1 noreply@anthropic.com -
releaseGate K2 binaries and report VFS counts -
vfsUse as_chunks when parsing a uid batch CI's nightly clippy is newer than the local one and flagschunks_exactwith a constant size, which it can turn into a slice pattern with no per-chunk bounds checks. Same result, one fewer way for the length check and the parse to disagree. -
moduleAdvertise the module.prop id to modules being installedmetainstall.shexportedKSU_METAMODULE="hybrid-mount"and the same for APatch, but this module'smodule.propid ishybrid_mount. The hyphenated spelling is the binary name and the/data/adb/hybrid-mountruntime directory, not the id. KernelSU does not read these variables itself, so the mount chain was never broken: it finds the metamodule throughmetamodule=1and the/data/adb/metamodulesymlink. They exist for the other module's installer, which compares the value against the directory it sees under/data/adb/modulesto work out which metamodule it is installing under.mountify, which this installer follows and whose LKM this project ships, exports its own id —id=mountifyin itsmodule.prop, andKSU_METAMODULE="mountify"here. The two must match. A test now reads both the constant and the script and fails if they diverge, the same way theMANAGED_PARTITIONSpair is already guarded. -
vfsHandle kernel allocation failures -
vfsLoad the bundled module before the plan is built The plan rewrites everyvfsrule toignorewhile the key type does not answer, andapply_vfs_phasereturns early on an empty vfs module set. The only call toload_hm_vfssits behind that early return, so on a kernel without the module built in -- the exact device the bundled modules exist for -- the load was unreachable and vfs could never come up.vfs::ensure_loaded_for_plannow probes, loads when a rule asks for vfs and the probe is still silent, and hands the post-load probe result toPlanInput. The load stays out ofavailable(), so status and doctor queries still never insmod.vfs_stricthad the same shape of defect: it promised that an unavailable VFS fails the boot, but the plan's degradation meant the executor's own strict checks were never reached. It is now decided before planning, and only applies when a rule actually selects vfs, so a device configured for overlay or magic keeps booting with the option left on. Covered by tests for the predicate, the ordering, and the strict decision; the ordering test fails if the load is moved back belowbuild_plan. -
ciRestore the gates the dependency bumps broke The three Dependabot PRs merged in sequence and left dev red on both lint jobs, for reasons that had nothing to do with each other. rust-lints: the tgbot bump to 0.48.0 reverted an exact pin that exists because 0.47 and 0.48 JSON-quote flattened multipart strings, so a single-file upload sends parse_mode as '"HTML"' and Telegram answers400 unsupported parse_mode. The pin was added in 5122e1c together with a reversion of lib.rs to the 0.46 API, and the wire-level tests that guard it still pass on 0.46.0. Dependabot only saw the version line: its ignore list covered 0.47.x alone, which is why 0.48 came through. Pin back to =0.46.0 and widen the ignore to every version, so the upgrade becomes a deliberate change that must satisfy those tests. webui-lints: pnpm writes pnpm-lock.yaml in its own style, andprettier --check .insists on double quotes, so each npm bump regenerated the lockfile unconformingly and failed the gate. Reformatting it rewrites ~2000 lines that the next bump throws away, so ignore the lockfile instead: it is generated, has no hand-edited content, and pnpm already validates it throughinstall --frozen-lockfile. The lockfile itself is untouched, so this commit carries no dependency change. -
ciIgnore tgbot updates by name, not by an undocumented wildcardversions: ["*"]is accepted by the config schema but is not a form GitHub documents: theversionsreference lists package-manager ranges only, while ignoring a dependency outright is expressed by naming it with noversionsat all (the schema's first anyOf branch requires exactlydependency-name). Use the documented form so the intent survives the next person reading the file. -
releaseValidate the tag as semver before it becomes the versionv6.2.01passed the release workflow's own pattern, was written into Cargo.toml, and was rejected only afterwards by Cargo as "invalid leading zero in patch version number". The build job failed and the release never published, but the tag was already pushed. The workflow now asks xtask for every version fact it needs, so the pattern that accepts a tag and the parser that consumes it cannot disagree: cargo xtask release-version v6.2.1 # version, version_code, prereleaseReleaseVersionvalidates the tag as semver first, which rejectsv6.2.01by name while the run is still just a failed run, and it is the single source of the version injected into Cargo.toml and module.prop, the versionCode published in update.json and the pre-release flag. The shell regex and the awk versionCode in the workflow are gone. versionCode now reserves its low three digits for candidate ordering, so a pre-release and the release it leads to are distinguishable: v6.2.0 602000999 v6.2.1-rc.1 602001301 v6.2.1 602001999 Candidates sit below their release because a device is only offered a higher code: placed above, a candidate would outrank its own release forever and nobody who tried it would ever be offered that release. Every code stays above the whole-number codes already published, so devices on 6.2.0 and earlier still see the next release as an update. Pre-releases are written<stage>.<number>with the stage from [alpha, beta, rc]; any other shape is refused with the supported spelling rather than given an approximate order. The policy that made a two-digit patch mean "pre-release" is gone with the pattern that read it.
Documentation
-
Default to English README and restore v4.2.0 logo
-
增加 NoMount VFS 后端接入设计文档 设计 Hybrid Mount 第三执行后端 vfs:兼容 NoMount wire 协议(keyring add_key) 并自带独立内核实现(fork NoMount,GPL-3.0-only),两者在集成层与运行期严格二选一。 Refs: https://github.com/maxsteeel/nomount
-
增加 NoMount VFS 用户态实施计划 覆盖 Mode::Vfs、tree->rules 纯函数、nm_payload 编解码、keyring 发送、 Provider 二选一、状态/流水线/WebUI 接入与全量门禁,共 14 个任务。 K2 内核实现另立计划。
-
Fix VfsExecStats summary in vfs plan
-
Document the vfs backend
-
Correct vfs kernel and backend wording
-
vfsRevise design to K2-only and add third-party attribution - Rewrite the VFS backend design as v2: drop NoMount kernel support (K1) and keep only Hybrid Mount's own kernel implementation (K2), which gets its own key type, protocol version, CLI and kernel-side optimizations. - Record the upstream .replace defect (whiteout of a directory yields -ENOTDIR and the batch status silently keeps the last result) as the rationale. - Fix README and README_ZH wording that still advertised a NoMount-compatible provider and a K1-only implementation, which contradicted the v2 design. - Add THIRD_PARTY.md with the NoMount fork commit, the upstream licence inconsistency to clarify with the author, and the attribution commitments. -
Write every source comment in English Comments across the tree were Chinese, which does not match the English module docs, README and commit log. Translate them, and delete the ones that only restated the item's name in the other language. 373 comment lines removed, 366 added. The seven with no replacement were pure restatements and carried nothing: "shared paths and constants" above defs.rs, "the hybrid mount plan" above struct MountPlan, "creates a directory and checks it is a directory" above ensure_dir_exists, and four more module headers that only repeated the filename. Comments carrying information the code cannot were translated rather than cut: the independent total/drain timeout rationale in sys/process.rs, the "unmount means the syscall, not try-umount registration" boundary in sys/mount.rs and utils/ksu.rs, the 64-lowerdir limit, errno semantics, ext4 sizing arithmetic, and every SAFETY note (still 4, unchanged). References to v4.2.0, e20f9c1 and 8b85c9e are preserved verbatim. Verified comment-only: stripping comments from all 33 changed Rust files leaves the code byte-identical to HEAD. fmt, clippy -D warnings, 282 tests, three Android targets, shellcheck, the shell tests, WebUI lint/70 tests/build and the LKM digests all pass.
-
Describe the two kernel modules and the VFS probe
ARCHITECTURE.mdstill said the release package ships no kernel module, andCLAUDE.mddocumented only the ext4 one. Both now describe what the code does: two bundled modules, the probe-then-load order, and the fact that the probe result also decides whether the planner offers VFS at all. Thevfs-doctorcommand was undocumented in the command table. -
vfsDrop the last K2 references from the module sources Three comments still called the VFS subsystem K2 — the Kconfig and Makefile banners and the license note in PROVENANCE. The subsystem is namedhybridmount; the kernel module already reportsdescription=Hybrid Mount VFS Path Redirection Subsystem. Comment-only, no functional change. A repo-wide sweep forK2now finds nothing outside the historical records in.superpowers/and an unrelatedsha512-...tK2Vq...fragment in the WebUI lockfile. -
Add an acknowledgements section to every README Thanks Anatdx, Tools-cx-app, KernelSU, MKSU by 5ec1cff, ReSukiSU, meta-magic_mount-rs and NoMount. Added to all twelve READMEs ahead of the license section, translated per file, so the language set stays in step.
-
Explain the VFS provider and how to integrate it The README described what VFS does but not how the provider is found, what happens at boot when it is missing, or how to build it into a kernel. Add a "VFS backend" section to every language, covering the read-only key-type probe the boot decision keys on, the /proc/modules vs /sys/module classification that only vfs-doctor performs, the probe-load-reprobe order and why the load precedes planning, and the circuit-breaker marker a crash leaves behind. The integration part names setup.sh, the CONFIG_HYBRIDMOUNT=y / =m choice, what --cleanup reverts, and the refusal to touch a tree that already integrates NoMount. It also states that the prebuilt module makes the step unnecessary on the supported targets, which is the case most readers are in.
-
vfsSay what the new kernel warnings actually mean Three comments described the wrong branch or the wrong cause: the pin failure blamed page straddling without mentioning the offset that tells the two cases apart, the delete warning pointed at an ENOENT that is set in the other branch, and the magic line omitted the load-boundary case. Messages only, no behaviour change.
Tests
- Guard the boot lock path and teach hm-verify the new checks
module/metamount.shandmodule/boot-completed.sheach spell the single instance lock path out in full. If one drifts the lock stops working and nothing notices, soboot_lock.shasserts both scripts agree, that the path is absolute, and that its mkdir/rmdir lifecycle is paired.hm-verifygains the new VFS licence assertions, the boot-lock test andmodule/vfs/setup.shunder ShellCheck, so the local gate matcheslints.yml.
Miscellaneous
-
Configure DSH Rust skills and clean up review references
-
vfsAllow unused nomount wire command variants -
Build and notify Telegram on the vfs feature branch build.yml only watched dev, so pushes to feat/nomount-vfs never triggered a packaging run and the Telegram step (gated on non-pull_request) never fired. Add the branch to push.branches with a note to drop it once merged.
-
vfsImport the NoMount kernel sources as the K2 baseline Import kernel/src/nomount.c, nomount.h, Makefile and Kconfig plus the upstream LICENSE verbatim from NoMount, as the starting point for Hybrid Mount's own VFS kernel subsystem (K2). The sources are unmodified: PROVENANCE records the fork commit and per-file SHA-256 digests, and UPSTREAM_README.md retains the upstream integration README. Nothing is wired into the build yet; the ABI divergence and the built-in integration script are separate, later commits. Source: maxsteeel/nomount@016375c Co-authored-by: maxsteeel 109047395+maxsteeel@users.noreply.github.com -
vfsGive the K2 kernel subsystem its own identity Rename the imported NoMount kernel sources to the Hybrid Mount identity so K2 can be distinguished from, and enforced against, upstream NoMount: - rename nomount.c/.h to hybridmount.c/.h and update the include - register the key type as "hybridmount" instead of "nomount" - report protocol version "hm1" instead of "20" - rename the Kconfig symbol to HYBRIDMOUNT and the module object to hybridmount.o - rename the NOMOUNT_* macros and the header guard - describe the module as the Hybrid Mount VFS path redirection subsystem MODULE_AUTHOR("maxsteeel"), the upstream licence text and the attribution notices are kept unchanged. The wire payload layout and the internal nomount_/nm_ symbol prefixes are deliberately untouched here and follow in separate commits. Source: maxsteeel/nomount@016375c Co-authored-by: maxsteeel 109047395+maxsteeel@users.noreply.github.com -
vfsRename the K2 internal symbols to the Hybrid Mount prefix Scrub the remaining upstream naming from the forked kernel sources so K2 does not share any symbol with a co-loaded NoMount module: - nomount_* -> hybridmount_* (functions, structs, globals, including the nomount* helpers) - nm* -> hm_* (helpers and structs, including the nm* ones) - NM* -> HM_* (flags, commands, clear masks and actor return codes) The rename is textual and does not change behaviour: the wire payload layout, the magic value and the driver logic are untouched. A real kernel build is still required before this can be treated as verified. Source: maxsteeel/nomount@016375c Co-authored-by: maxsteeel 109047395+maxsteeel@users.noreply.github.com -
vfsDrive only the Hybrid Mount kernel and guard against NoMount Align the userspace VFS backend with the v2 design, which drops support for a device-provided NoMount kernel: - send rules to the "hybridmount" key type instead of "nomount" - support protocol version "hm1" only; the upstream "20" is rejected - drop VfsProvider::Nomount and the loaded-flag heuristic that guessed which implementation was behind a shared key type - add a one-way guard: probe the "nomount" key type once and refuse to attach when a foreign NoMount implementation is present, recording the result in the run state as vfs_foreign_nomount - replace Error::VfsProviderConflict with Error::VfsForeignNomount - keep the probe channel separate: KeyringChannel::Nomount is used for detection only and never delivers rules The LKM loader is still a no-op, so the K2 module is not actually loaded yet; the selection flow is ready for it and needs no further change when it lands. Verified: cargo fmt, clippy -D warnings and cargo test --workspace pass; the aarch64, armv7 and x86_64 Android targets check with no warnings. -
vfsClean up the K2 kernel comments and branding - drop the informal mixed-declaration comment and two redundant section labels - print "hybridmount:" instead of "NoMount:" from the kernel log macros - rename the VFS offset protocol header and move its synthetic position marker from 'nm' to 'hm' - fix the "Operaction" typo and the stale magic-value note The offset marker is internal to the module, since pack, unpack and the virtual-position test all use the same constant, so the payload protocol is unaffected. Source: maxsteeel/nomount@016375c Co-authored-by: maxsteeel 109047395+maxsteeel@users.noreply.github.com -
vfsBuild the K2 module with DDK and integrate it into kernels Two supported routes for getting K2 onto a device: - Loadable module: module/vfs/src/Makefile now builds out-of-tree against a prepared kernel tree, and .github/workflows/kernel-module.yml runs it through DDK (ghcr.io/ylarod/ddk) for every Android/GKI target, uploading the resulting hybridmount-.ko as a separate artifact. - Built-in: module/vfs/setup.sh copies the sources into fs/hybridmount/, patches fs/Makefile and fs/Kconfig, and refuses to touch a kernel tree that already integrates NoMount. --cleanup reverts all of it. tests/shell/vfs_setup.sh covers integration, the re-run guard, the NoMount guard and cleanup. lints.yml now lints module/vfs/setup.sh explicitly, since module/*.sh never matched it. The module sources stay outside the offline cargo test surface: they need a real kernel tree, which is what the DDK job provides. Verified locally: actionlint, shellcheck, tests/shell/vfs_setup.sh, tests/shell/customize_lkm.sh, cargo fmt, clippy -D warnings, 265 workspace tests and the three Android target checks all pass. The DDK compile itself has not run yet and can only be exercised on CI. Source: maxsteeel/nomount@016375c Co-authored-by: maxsteeel 109047395+maxsteeel@users.noreply.github.com -
vfsAssemble the prebuilt K2 modules like the ext4 LKM subtree module/vfs/binaries now mirrors module/lkm/binaries: one hybridmount--.ko per DDK target plus a list.txt digest file. The kernel-module workflow gains a packaging job that downloads every matrix artifact, assembles that directory and uploads it; running the workflow manually with commit_binaries=true commits the refreshed copies back to the branch. lints.yml verifies list.txt whenever it is committed. customize.sh keeps the prebuilt modules on KernelSU too, because module loading is available there through ksud and the VFS backend has no ioctl equivalent, and drops them only on non-arm64 installs where the aarch64 modules cannot load. The dev-only sources are pruned on every platform. The installer test covers the KernelSU arm64, APatch arm64 and APatch armv7 branches. Verified: actionlint, shellcheck, both shell tests, cargo fmt, clippy -D warnings, 268 workspace tests and the three Android target checks. -
vfsRefresh the prebuilt K2 modules -
vfsDedupe the test doubles and drop the self-justifying comments The VFS subsystem had grown four separate kernel doubles in exec_tests.rs and seven copies of the plan boilerplate, plus three target-parsing helpers that existed byte-for-byte in both lkm_target.rs and nuke.rs. Collapse each to one reusable definition and translate the kernel-side comments, which were the only Chinese text in module/vfs/. - exec_tests.rs: four fake kernels -> one RecordingKernel, one plan_for helper (352 -> 194 lines) - protocol.rs: onefielddecoder replaces four copies of the slice-decode, and oneresponsetest helper replaces ten hand-mutated header blocks - lkm_target.rs: absorbs device_android_major; nuke.rs and lkm.rs call it instead of keeping private copies (no duplicated function bodies remain) - drop plan_rules_holds_full_batch_before_any_kernel_call: plan_rules takes no kernel and returns owned rules, so the property is enforced by the signature - merge the two wire-magic tests, keeping the byte-order assertion the integer comparison cannot make - drop a deadlet stats = planned.stats.clone()shadowed by the later binding - comments: English in the kernel module (99% of its comments already were), and remove text that argues for the design rather than explaining the code Net -254 lines. fmt, clippy -D warnings, 282 tests, three Android targets and shellcheck all pass. The bundled .ko still carry the old magic and must be rebuilt via kernel-module.yml with commit_binaries=true. -
vfsRefresh the prebuilt K2 modules -
sysRead mountinfo through one helperpipeline,mountandoverlayfseach opened procfs and walkedProcess::myself().mountinfo()on their own. Move that intosys::mountinfoso one place owns the read, the ordering rule and the fault-injection hook that the tests rely on. AddsMountEntry,mount_entries(),mount_entry_at()anddeepest_first();MountSnapshot::read()now delegates to the same reader. Behaviour is unchanged, including the deepest-first unmount order and theshould_fail_mountinfo_read()test hook. -
vfsRelicense the hybridmount module as GPL-2.0-only The VFS module was forked from NoMount, which is GPL-3.0. Relicensing it as GPL-2.0-only is what lets a GPL-2.0-only kernel load it without a licence conflict, and it matches the ext4 nuke module that already ships this way. - swapmodule/vfs/src/LICENSEfor the GPL-2.0 text already used bymodule/lkm, and putSPDX-License-Identifier: GPL-2.0-onlyon every source, make and Kconfig file in the module - declareMODULE_LICENSE("GPL v2")inhybridmount.c. Perinclude/linux/module.hthis is a free-software ident equivalent to"GPL"; neither spelling distinguishes "v2 only" from "v2 or later", so the "only" is stated where it is legally operative: the SPDX headers and the LICENSE text - record the relicence inPROVENANCEandTHIRD_PARTY.md, and say plainly that the module no longer interoperates with NoMount's kernel or itsnmCLI -lints.ymlnow asserts the GPL-2.0-only provenance instead of a bare SPDX presence, and only checks the binary digest when binaries exist Also retire the "K2" label, which was left over from a dropped v1 design and named no code path, in favour ofhybridmountthroughout. -
sysShare the kernel-module loading machinerynuke.rsowned the whole load path —.koselection, boot guard,insmodcandidates, unload — and the VFS module needs exactly the same steps. Lift it intosys::lkmso one implementation keeps the boot guard and theinsmodfallback order honest for both modules. Only two things are genuinely product-specific and stay with the caller: the filename matrix and the acceptance test.nuke.rskeeps its eight ext4 entries, including the 4.14 one the VFS module does not build for.nuke.rsalso stops opening procfs itself and reads the mount table throughsys::mountinfo::mount_entry_at()like everything else.HYBRID_MOUNT_LKM_PATHstill overrides the nuke module alone. -
vfsShip the prebuilt hybridmount modules in the package The DDK workflow compiled the module and then deleted the.ko, so the package only ever carried sources and no device could load the backend. Build it for real and ship it. -kernel-module.ymlkeeps each target's module as an artifact, assembles them intomodule/vfs/binarieswith alist.txtdigest file in the same formatmodule/lkm/binariesalready uses, and verifies the digests. The commit-back step is opt-in: it runs only on a manual dispatch withcommit_binaries=true, and pushes as a separate commit -xtaskno longer stripsvfs/binarieswhile staging, and a test asserts every shipped kernel module is listed in its manifest so a module cannot be packaged without a digest - the installer dropsvfs/binariesonly on non-arm64, where the prebuilt modules cannot load. Unlike the ext4 LKM they are kept on KernelSU, where module loading works and the VFS backend has no ioctl equivalent The sources ship everywhere, so the installed tree stays licence-complete. -
Remove dead workflows, duplicated docs and a stale changelog copy Audit of what nothing referenced any more: -
.github/workflows/notify.ymlran fmt, clippy and tests for the notify crate.lints.ymlruns the same three commands across the whole workspace, andnotifyis a workspace member, so this was a second, narrower copy of the same gate on every PR -scripts/lint_fix.shwas a clippy--fixconvenience script that no workflow, doc or test called -auto-label.ymlcarried five label regexes for a bug-report template that no longer has those fields, so they could never match -changelog.mdended with a second complete "Hybrid Mount Changelog" section holding an unreleased v6.0.0 draft that v6.0.0's own released entry already covers -docs/RUST_BACKEND_REVIEW.mdkept 470 lines of findings that were either fixed or restated elsewhere; it now lists only the two open items and the device-verification boundary - the twodocs/superpowers/design and plan documents described the VFS backend that has since been implemented undersrc/vfs/release.ymlalso stops hand-writing the update.json heredoc and callscargo xtask update-json, the serializer the local path already uses, so the two cannot drift. -
dshVendor the rust-skills rule library Adds leonardomso/rust-skills (MIT, commit fd2a861a) as a project skill: 265 rules in 26 categories, each a standalone file, plus its validator. It complements what is already here rather than replacing it. The upstream rules are generic and their examples use crates this project does not depend on, soSKILL.mdgains a precedence section up front: the repository's own contracts —CLAUDE.md, the workspace lints, the synchronous no-async target list, the banned symbols — win wherever the two disagree..dsh/README.mdrecords the three-way split between this, therust-best-practicesguide and thehm-*gate skills, and documents the upstream refresh procedure so a future update does not silently drop either the frontmatter or the precedence section. -
vfsRefresh the prebuilt hybridmount modules -
Scope flate2 to the platforms that read /proc/config.gz
flate2was a dependency on every target, but its only use is the GzDecoder insidesys::fs::check_kernel_config, which exists only undercfg(any(target_os = "linux", target_os = "android")); the non-Unix stub takes no key and never decompresses anything. A host build therefore compiled it for nothing. Moving it into the existing target-scoped table matches how the other Unix-only dependencies are already declared. This surfaced only after refreshing the local nightly from 1.94 to 1.100, whose cargo newly warns about unused dependencies — CI runs on Linux, where the crate is used, so it had no way to see this. -
Drop the VFS feature-branch trigger now that it is merged
build.ymlandkernel-module.ymllistedfeat/nomount-vfsalongsidedevso the experiment branch got the same packaging and DDK builds before on-device testing. Both comments asked for the entry to be removed once the branch merged, which it now has.devalready covers every push these jobs need, andpull_requestondevkeeps the pre-merge checks, so the removal loses no coverage. -
vfsRefresh the prebuilt hybridmount modules -
vfsKeep the prebuilt modules in step with their sources Commit 54bba2d changed module/vfs/src but left all seven shipped .ko files at the previous build. Nothing noticed: list.txt only proves the binaries are unchanged since they were written, and release.yml does not rebuild the LKM, so a stale module goes straight into the package. Two guards, as the drift can arrive two ways. kernel-module.yml now commits the rebuilt modules on a push to dev that touches module/vfs/**, which is the normal route and needs no manual dispatch. It also records sources.sha256, a digest of the build inputs, from the job that actually compiles them, so the stamp cannot be hand-edited into agreeing with a stale binary. tests/shell/vfs_sources_digest.sh recomputes that digest and lints.yml runs it on every change, so a set that no longer matches its sources fails the build rather than shipping. Its own test covers a matching tree, a source change, a missing input, a malformed stamp and the no-stamp skip. The commit step'sifis parenthesised:a && b || cwould have committed on every workflow_dispatch regardless of commit_binaries. -
vfsRefresh the prebuilt hybridmount modules -
vfsRefresh the prebuilt hybridmount modules