parseconf: accept literal hashes inside double quotes - #3633
parseconf: accept literal hashes inside double quotes#3633user01010111 wants to merge 3 commits into
Conversation
Accept quoted hashes in the shared parser while retaining unquoted comments and escaped output for older peers. Keep default upsc text output unchanged and document configuration, protocol and dummy-ups behavior. Add portable parser regressions and localhost text/JSON integration. Validated with C89/C99/C11 GCC and Clang builds, sanitizers, full NIT, package/install checks, documentation checks and distcheck-light. Related: networkupstools#607, networkupstools#1306, networkupstools#3629 AI assistance: OpenAI Codex with gpt-6-astra (x-high reasoning) and gpt-daybreak-blue-latest (high reasoning). The human contributor remains responsible for reviewing and submitting the change. Signed-off-by: user01010111 <12504630+user01010111@users.noreply.github.com>
|
A ZIP file with standard source tarball and another tarball with pre-built docs for commit fd8c0f6 is temporarily available: NUT-tarballs-PR-3633.zip. |
|
✅ Build nut 2.8.5.5259-master completed (commit 744fe2319b by @user01010111)
|
|
✅ Build nut 2.8.5.5259-master completed (commit 744fe2319b by @user01010111) |
Probe the selected Python interpreter for json before preparing the parser fixtures. Report a skip in the default suite if the prerequisite is absent, while preserving failure for an explicitly requested parser test group. Keep the existing parser and JSON assertions unchanged. Jenkins exposed a Python 3.7 installation unable to import its standard json module. The worker still needs repair to restore this integration coverage. Validated the missing-module failure and skip paths, native macOS and Linux parser integration, Linux make check (13 tests), style/spelling checks, and default make distcheck-light. The missing module was simulated locally; the exact Jenkins worker and physical UPS hardware were not tested. Related: networkupstools#3633 AI assistance: OpenAI Codex with gpt-6-astra. The human contributor remains responsible for reviewing and submitting the change. Signed-off-by: user01010111 <12504630+user01010111@users.noreply.github.com>
|
✅ Build nut 2.8.5.5270-master completed (commit 35cfb0fa6d by @user01010111)
|
|
Something not right in new test script (expecting empty UPDATE: Reading the script code, I think the fault was not due to parsing an unexpected "Init SSL..." as was my original theory, but It would help troubleshooting if the test case reported itself starting (and BTW |
| /* set up the error reporting details */ | ||
| static void pconf_seterr(PCONF_CTX_t *ctx, const char *errmsg) | ||
| { | ||
| snprintf(ctx->errmsg, PCONF_ERR_LEN, "%s", errmsg); | ||
|
|
||
| ctx->error = 1; | ||
| } | ||
|
|
There was a problem hiding this comment.
I feel uneasy about dropping this method, it was with us for 20+ years. This drop also makes STATE_PARSEERR value un-used. There are a couple of other uses for ctx->errmsg at least (although those sanity-checkers probably don't want to change ctx->error value nor their return codes).
I'd rather hide these removed codes into #if 0 fences as an example how to revive this functionality should our parser report errors again, or find actual use for them in error handling. We should have some other cases, right?
There was a problem hiding this comment.
One of these discussions about the parser mentioned handling multi-line strings where properly quoted, and qc_literal() confirms this is intentional (with removal of the actual newline character from the collected string). I first thought about reporting these as errors in a quoted context, but not sure about that now (the code probably meant to handle lines ending with a backslash+newline escape, no idea if it was about a simple or escaped newline inside the quotes too).
But we could certainly pconf_seterr() if there is an EOF while we are inside the quoted string context.
Not sure about other non-ASCII values (unprintable < 32 other than 10==newline, or locale-specific > 127, or potentially unicode) - it is probably a bit too late to start rejecting outright or handling them somehow specially :)
There was a problem hiding this comment.
On the EOF and multiline point, fd8c0f65 preserves the existing behaviour. The current tests deliberately retain partial-input acceptance, and the character-at-a-time entry point has no EOF signal. I suggest agreeing EOF handling across the parser entry points in a separate follow-up, keeping multiline and character handling unchanged here.
There was a problem hiding this comment.
Agreed. fd8c0f65 restores pconf_seterr() under #if 0, preserving the example alongside the existing STATE_PARSEERR handling. Active parser behaviour is unchanged by this follow-up.
| outlet.1.desc: Outlet #1 | ||
| outlet.2.desc: "Outlet \#2" | ||
| outlet.3.desc: "Outlet #3" | ||
| outlet.4.desc: Outlet \#4 | ||
|
|
||
| These values become `Outlet`, `Outlet #2`, `Outlet #3`, and `Outlet #4`, | ||
| respectively. Older versions of the parser require `\#` even inside | ||
| double quotes. Backslashes quote the next permitted character once, so | ||
| `\n` becomes `n`, while `\\` becomes a single literal backslash. | ||
| A backslash followed by a physical newline continues the logical line. | ||
| An unescaped `=` outside double quotes is a separate token; quote or | ||
| escape it when it is part of a value. | ||
|
|
There was a problem hiding this comment.
Can you please double-check and clarify whether "Outlet #4" (upon reading from such data in e.g. config files) becomes two tokens, while others would be one token. When reading from a .dev/.seq file in dummy-ups, do we actually inject only the first value token for the key token?
Maybe add an example with a backslash-escaped space as mentioned in config-notes.txt now?
There was a problem hiding this comment.
Confirmed: unquoted Outlet \#4 produces two value tokens, Outlet and #4. dummy-ups joins all value tokens with spaces, so it stores Outlet #4; it does not discard the second token.
fd8c0f65 explains this and adds Outlet\ \#4 as the single-token equivalent, plus a real integration fixture for that form. The configuration notes also show escaped spaces in SHUTDOWNCMD. The updated examples render correctly and the parser integration checks pass.
| { | ||
| /* user is trying to break us */ | ||
| if (ctx->ch == '#') { | ||
| pconf_seterr(ctx, "Unbalanced word due to unescaped # in quotes"); | ||
| endofword(ctx); | ||
|
|
||
| /* this makes us drop all the way out of the caller */ | ||
| return STATE_PARSEERR; | ||
| } | ||
|
|
There was a problem hiding this comment.
CC @aquette @arjendekorte @clepple : do you remember any rationale for rejecting an unescaped hash sign INSIDE double-quoted values (e.g. passwords, descriptions like "Outlet #4" etc.)?
How here a "user is trying to break us" ?
There was a problem hiding this comment.
@user01010111 : if we do go through with this, it is a significant enough change to mention also in UPGRADING.adoc.
There was a problem hiding this comment.
I cannot establish the original rationale from the code alone, so I would welcome the historical context. The change permits literal hashes inside double quotes while preserving unquoted comments. pconf_encode() continues escaping hashes for compatibility with older peers. I have left this question open for the maintainers who know the history.
There was a problem hiding this comment.
Agreed. fd8c0f65 adds an UPGRADING.adoc entry under 2.8.5 to 2.8.6. It explains literal hashes inside double quotes, unchanged unquoted comments, continued \# support and the need to retain escaped forms when sharing configuration with older versions. Encoded protocol output still escapes hashes for older peers. The PR description is updated accordingly.
| /* set up the error reporting details */ | ||
| static void pconf_seterr(PCONF_CTX_t *ctx, const char *errmsg) | ||
| { | ||
| snprintf(ctx->errmsg, PCONF_ERR_LEN, "%s", errmsg); | ||
|
|
||
| ctx->error = 1; | ||
| } | ||
|
|
There was a problem hiding this comment.
One of these discussions about the parser mentioned handling multi-line strings where properly quoted, and qc_literal() confirms this is intentional (with removal of the actual newline character from the collected string). I first thought about reporting these as errors in a quoted context, but not sure about that now (the code probably meant to handle lines ending with a backslash+newline escape, no idea if it was about a simple or escaped newline inside the quotes too).
But we could certainly pconf_seterr() if there is an EOF while we are inside the quoted string context.
Not sure about other non-ASCII values (unprintable < 32 other than 10==newline, or locale-specific > 127, or potentially unicode) - it is probably a bit too late to start rejecting outright or handling them somehow specially :)
| #!/usr/bin/env python | ||
| # Exact values through configuration, dummy-ups, upsd and upsc. |
There was a problem hiding this comment.
Similarly to changes in some other PR of this bunch, makes sense to add a comment here that:
- normally the caller (
nit.sh) selects which$PYTHONinterpreter runs this script, so we do not bother with.intemplating - expected to be, and should remain, compatible with Py2 and Py3
There was a problem hiding this comment.
Added beside the shebang in fd8c0f65: nit.sh selects $PYTHON, and the script must remain compatible with Python 2.6+ and Python 3. The watchdog continues using the older-compatible timer and subprocess APIs. Python 3 helper/integration checks pass.
I also built an isolated CPython 2.6.9 runtime and ran the published helper self-test plus the real parser integration group with that interpreter. The self-test passes; plaintext and certificate-verified TLS each pass with five passes, zero failures and zero skips, covering all 13 fixtures. The TLS run negotiated TLS 1.3. No source changes were needed.
Validation assistance: OpenAI Codex (gpt-6-astra, high reasoning).
Report query starts, watchdog expiry and captured process output. Allow a bounded per-query NIT_PARSECONF_TIMEOUT, defaulting to 60 seconds, and preserve per-client debug settings in the named parser testcase. Retain the parser error helper under #if 0. Clarify dummy-ups token joining, escaped spaces, Python compatibility and the quoted-hash upgrade behaviour. Keep EOF, multiline and active parser semantics unchanged. AI assistance: OpenAI Codex with gpt-6-astra. Signed-off-by: user01010111 <12504630+user01010111@users.noreply.github.com>
|
Following up on the NetBSD test result, fd8c0f65 moves execution and result reporting into The old ten-second watchdog can produce the reported The distribution build/install/uninstall check and all 12 tests also pass with spellcheck disabled; the default gate rejects existing Review and implementation assistance: OpenAI Codex ( |
|
✅ Build nut 2.8.5.5281-master completed (commit 06bf6b5834 by @user01010111)
|
|
✅ Build nut 2.8.5.5281-master completed (commit 06bf6b5834 by @user01010111) |
A description such as
desc = "Rack #3", or a dummy-ups value such asoutlet.3.desc: "Outlet #3", triggers a parse error with the original parser and loses the configured value. Accept a literal#inside double quotes. Unquoted hashes still start comments, and escaped characters still decode once inside or outside double quotes. Retain the historical error helper under#if 0for future parser error handling.Add portable regression coverage for all three parser entry points and a named NIT testcase checking exact text and decoded JSON through real localhost
upsd,dummy-upsandupsc. The group handles sandbox preparation and cleanup. Each query reports its start and distinguishes watchdog expiry from an ordinary process failure, retaining both output streams in failure diagnostics.NIT_PARSECONF_TIMEOUTcontrols the overall per-query watchdog, defaulting to 60 seconds, independently of the connection timeout. Per-client debug settings and missing-Python/JSON skip/failure behaviour are preserved.Update the configuration, network protocol and dummy-ups documentation, NEWS and UPGRADING. Explain how dummy-ups joins multiple value tokens and show the escaped-space alternative. The parser API, character filters, limits and incomplete-input behaviour remain unchanged.
pconf_encode()still escapes hashes for older peers; defaultupscoutput and driver code are unchanged. No protocol, ABI or driver version bump is needed. This remains independent of #3629.Related: #607, #1306.
Validation of the original implementation, before this review follow-up:
2caa3c87501a1aba25a3070ad66757470bd1bf03through line, file and character parsing. All 23 fixtures and boundary/encoder checks passed with the fix; the real NIT regression also failed before the fix.Validation of the review follow-up at
fd8c0f65bcf311f89a62524a61a5d82be145f2a2:-Werror, the C parser test, and the helper self-test pass. The same two-second child succeeds with a five-second watchdog and fails with a one-second watchdog. Checks cover benign stderr, ordinary nonzero exit, captured output, invalid limits and the before/after timeout diagnostics.make stylecheck, source ASCII and whitespace checks pass. The real user-manual, dummy-ups manual and UPGRADING render, and the changed documents pass their spelling targets.make distcheck-lightwithAM_DISTCHECK_CONFIGURE_FLAGS=--disable-spellcheckpasses the distribution build, all 12 tests and install/uninstall checks. All six follow-up source files match the generated archive. The unmodified default spelling gate rejectskernel'sandfirmware'sin unchanged NEWS and apcmicrolink documentation; that baseline dictionary failure is retained as a limitation.The successful socket-dependent checks ran with localhost access enabled. Earlier isolated attempts were denied ordinary local sockets. Python 2 was unavailable for the follow-up runtime checks. No physical UPS hardware, live deployment or native NetBSD/NSS reproduction was exercised in the follow-up. The reported NetBSD delay still needs a native rerun; the improved watchdog diagnostics do not establish its underlying cause. EOF and multiline semantics remain a separate discussion.
AI assistance: the original implementation used OpenAI Codex with
gpt-6-astraandgpt-daybreak-blue-latest. This review follow-up used OpenAI Codex withgpt-6-astraat high reasoning. The human contributor authorised the DCO sign-off and remains responsible for the contribution.