# Fork provenance

This directory is a fork of NoMount's kernel sources.

upstream  = https://github.com/maxsteeel/nomount
commit    = 016375cd4a9e7da07b0519dd7bc492101de2a834
date      = 2026-09-13
protocol  = "20"
imported  = Hybrid Mount commit 4485e41c (verbatim import, original filenames nomount.c / nomount.h)
license   = GPL-2.0-only, see LICENSE in this directory. Upstream shipped a GPL-3.0 text
            while the kernel module declared a bare MODULE_LICENSE("GPL"); this module is
            distributed under the GPL-2.0-only grant and declares MODULE_LICENSE("GPL v2")
            so the declaration, the SPDX headers and the license text agree.
            See ../../THIRD_PARTY.md.

## Imported baseline (sha256, exactly as imported)

f2bc043af419e857e920fb193e42f3b98b32b432ff58ecc4fd722526f97c5c77  nomount.c
21e34e1c16a2870d464a0f1cf669f976695d2435cbd661c7b3e40f4f8303b6cd  nomount.h
c85c0ebe247c77882c5ac9f776941bf3a50f859b7d6bfb5bca1e1ccf9bed5b47  Makefile
1df7ad3589a282128f797f7a46b28aee315d080550aae26b681a898f11c7abc7  Kconfig
3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986  LICENSE
c42cad879c216d643444b448e8bd461a757cc2754e716520c9d36da6e799324f  UPSTREAM_README.md

These digests describe the files exactly as imported, under their original names. The
working tree has since diverged; in particular LICENSE no longer matches the imported
GPL-3.0 text. To compare against the pristine baseline, use the import commit:

    git show 4485e41c:module/vfs/src/nomount.c

## Hybrid Mount divergence applied so far

- License: GPL-2.0-only text in LICENSE, `SPDX-License-Identifier: GPL-2.0-only` in
  every source file, and MODULE_LICENSE("GPL v2") in the module.
- Identity: files, include guard, key type name, protocol version, Kconfig symbol,
  Makefile object, module description, and the kernel log prefix, now "hybridmount:".
- Internal symbols: nomount_* -> hybridmount_*, nm_* -> hm_*, NM_* -> HM_*.
- Wire magic: HYBRIDMOUNT_MAGIC_SIG is now the HM-exclusive 0x4859425249444D4F
  (ASCII "HYBRIDMO"), replacing upstream's 0x4E4F4D4F554E54 ("NOMOUNT"). It is also the
  full_name_hash seed and must match src/vfs/protocol.rs::MAGIC byte for byte.
- Virtual offset signature: HM_SIG_16 is 'hm' (0x686D), replacing 'nm' (0x6E6D). It is
  the high half of the packed virtual loff_t, so it is an in-kernel ABI in its own right.
- HM_FLAG_OPAQUE (bit 3) is a new flag: an opaque directory replaces its whole subtree,
  stays visible and shows only the injected children. The userspace emits it for a
  .replace directory and for every directory below it, matching Magisk semantics.
- Batch ADD_RULE reports the first failure and the offset of the failing record;
  DEL_RULE does the same and no longer leaves status 0 on a truncated batch. A rule
  whose real path cannot be resolved is rejected with -ENOENT, an out-of-range cursor
  with -EINVAL, and a failed directory-node allocation with -ENOMEM.
- The isolated-uid table stays sorted, so the per-lookup isolation check bisects instead
  of scanning every entry.
- Builds for pre-5.18 kernels need -std=gnu11, which the Makefile sets.
- Rules attached to a real directory retain its path for their registered lifetime.
  This keeps the inode carrying the lookup/iterate hooks alive under cache reclaim;
  generated virtual ancestors retain the same reference when they attach to a real
  parent. Retired rules release it after the existing RCU/SRCU reader drain, including
  replacement, rollback, deletion and clear before module teardown restores VFS ops.
- Selective upstream review through NoMount dev
  71ff9c4cd3037e3c968263a1d82b447f511cc658 (2026-10-04 review):
  - 60073edc / fd1201a4: initialize virtual inodes before publishing them and reuse
    existing inodes without allocating a spare. Unlike upstream's initializing
    sentinel, fully initialized candidates compete via cmpxchg; the loser detaches
    its directory association before eviction. Failed igrab is handled explicitly.
    Eviction protects the node with RCU and retires an empty inode slot atomically
    before freeing orphan topology, so an old inode cannot free a new owner's node.
    Rule retirement publishes orphan ownership before checking the inode slot, so
    concurrent eviction cannot leave an empty orphan node without a free claimant.
  - 53e4c93e: clean up in evict_inode, retaining the original destroy_inode callback.
    Virtual inodes restore their own mapping and use generic eviction; real inodes
    restore their filesystem operations before calling the original evict_inode.
  - 6a899d50: reuse existing virtual-parent topology without hooking it again or
    retaining a parent-path reference to our own virtual inode.
  - fea33dd0: skip the UID-table RCU section when no isolation table is installed.
  The imported baseline above remains unchanged. ART, seqcount and SRCU are retained;
  this is a selective port, not a wholesale upstream snapshot replacement.

Behaviour is no longer byte-identical to upstream, and the wire magic no longer matches
it: a stock nm CLI is rejected at preparse with -EFAULT. The payload field order, sizes
and command numbering are still the upstream ones.

## Syncing with upstream

1. Fetch the upstream revision you want to compare against.
2. Diff it against the commit recorded above and review every upstream change.
3. Re-apply the Hybrid Mount divergences on top; never overwrite them blindly.
4. Update this file (commit, date, digests) in the same commit as the sync.
