diff --git a/CHANGELOG.md b/CHANGELOG.md index e46733d..c983f61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -146,6 +146,25 @@ only record that an interrupted run's snapshot tree is still on disk; both scripts now name the snapshot (`zfs destroy -r ...`) before clearing it. +- **The native-nested probe could never detect the guard, silently disabling the + whole module.** Stock splits the message across adjacent string literals: + + ```python + verrors.add(f"{name}.snapshot", "This option is only available for datasets that have no further " + "nesting") + ``` + + Python concatenates those at runtime — so the *errmsg* is contiguous and the + runtime filter works — but the **source never contains the whole phrase**. The + probe's substring search found nothing, concluded iX had removed the guard, and + skipped the nested module as "already native". `apply.log` would report + *"TrueNAS now handles nesting natively"* and the feature would never work. + It fails safe (the stock guard stays, so no data is at risk) but the module was + 100% dead. The probe now strips whitespace and quotes before matching, which is + robust to any wrapping style. Caught only by running the probe against real + middlewared; there is now a regression test that executes apply.sh's own probe + code against the real wrapped source. + ### Refactored - Staging teardown had been copy-pasted into `uninstall.sh` and `recover.sh` — diff --git a/patch/apply.sh b/patch/apply.sh index 412a5b1..dff2795 100755 --- a/patch/apply.sh +++ b/patch/apply.sh @@ -167,7 +167,20 @@ try: crud = os.path.join(result['mw_dir'], 'plugins', 'cloud', 'crud.py') with open(crud, encoding='utf-8', errors='replace') as fh: stock_src = fh.read().split('\n# TRUECLOUD_PATCH', 1)[0] - if 'no further nesting' not in stock_src: + # The guard message is SPLIT across adjacent string literals in the source: + # + # verrors.add(..., 'This option is only available for datasets that have no further ' + # 'nesting') + # + # Python concatenates those at runtime, so the errmsg is contiguous -- but the + # SOURCE never contains the whole phrase. A raw search finds nothing, concludes + # iX removed the guard, and silently skips this module FOREVER. (Caught only by + # running the probe against real middlewared.) + # + # Strip whitespace and quote characters, then match the compacted phrase. That + # is robust to any wrapping or concatenation style iX may use. + _drop = str.maketrans('', '', ' \\t\\n\\r' + chr(34) + chr(39)) + if 'nofurthernesting' not in stock_src.translate(_drop): result['native_nested'] = 'yes' except Exception: pass diff --git a/tests/test_apply_blocks.py b/tests/test_apply_blocks.py index 4dbac85..d8fe6d5 100644 --- a/tests/test_apply_blocks.py +++ b/tests/test_apply_blocks.py @@ -8,6 +8,7 @@ these tests do. import ast import os import re +import textwrap import pytest @@ -46,6 +47,35 @@ def extract_blocks(): return blocks +def _nested_native_detector(): + """The REAL native-nested probe, lifted out of apply.sh. + + Extracted rather than reimplemented: a reimplementation would happily pass + while the shipped probe stayed broken, which is precisely the bug this guards. + """ + with open(APPLY_SH, encoding="utf-8") as fh: + sh = fh.read() + + m = re.search( + r"^(\s*)_drop = str\.maketrans\(.*?\n\s*if 'nofurthernesting' not in " + r"stock_src\.translate\(_drop\):\n\s*result\['native_nested'\] = 'yes'", + sh, re.S | re.M, + ) + assert m, "could not find the native-nested probe in apply.sh" + + # The block lives inside a double-quoted shell string; undo bash's escaping. + body = m.group(0) + body = body.replace("\\\\", "\x00").replace('\\"', '"').replace("\x00", "\\") + body = textwrap.dedent(body) + + def detect(stock_src): + ns = {"stock_src": stock_src, "result": {"native_nested": "no"}, "chr": chr} + exec(body, ns) # noqa: S102 - executing our own shipped code, on purpose + return ns["result"]["native_nested"] + + return detect + + def test_heredoc_itself_compiles(): compile(heredoc_source(), "apply.sh:PYEOF", "exec") @@ -153,6 +183,38 @@ class TestIndependentModules: assert "'ok': (not providers_needed) or bool(b2_ok and restic_ok)" in src assert "'active': nested_needed" in src + def test_nested_native_probe_matches_the_real_wrapped_source(self): + """Stock splits the guard message across adjacent string literals. + + Python concatenates them at runtime, so the errmsg is contiguous -- but the + SOURCE never contains the whole phrase. A raw substring search finds + nothing, concludes iX removed the guard, and silently skips this module + forever. This is exactly what happened, and only a run against real + middlewared caught it. + """ + detect = _nested_native_detector() + + # Verbatim shape from TrueNAS plugins/cloud/crud.py. + stock_wrapped = ( + ' verrors.add(f"{name}.snapshot", ' + '"This option is only available for datasets that have no further "\n' + ' "nesting")\n' + ) + assert detect(stock_wrapped) == "no", "guard is present; must NOT report native" + + # Same message on a single line — must also be detected. + assert detect('verrors.add(x, "... have no further nesting")\n') == "no" + + # Single-quoted, three-way split — still the guard. + assert detect( + "verrors.add(x, 'This option is only available for '\n" + " 'datasets that have no further '\n" + " 'nesting')\n" + ) == "no" + + # Guard genuinely gone -> native support. + assert detect("def _validate(self):\n pass\n") == "yes" + def test_nested_native_probe_ignores_our_own_block(self): # CRUD_BLOCK quotes the guard message, so scanning the whole file would # find the string in our own patch and never detect native support.