From 79d8f18f3ad4c765835a975f7a94189f295a67cf Mon Sep 17 00:00:00 2001 From: user01010111 <12504630+user01010111@users.noreply.github.com> Date: Wed, 9 Sep 2026 07:27:09 +1200 Subject: [PATCH 1/8] nut-driver-enumerator: preserve and decode ups.conf values once Preserve quotes and backslashes during ups.conf normalization, then decode the first value token when queried. Keep full and section caches consistent for values, media classification and checksums, including quoted multiline values. Document possible one-time reconciliation after upgrading. Extend existing self-tests and fixtures. Validated across macOS/Linux shells and awk implementations, corrected-C comparisons, generated/staged packages, documentation checks and distcheck-light. Related: #3629 AI assistance: OpenAI Codex with gpt-6-astra (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> --- NEWS.adoc | 7 + UPGRADING.adoc | 8 + docs/man/nut-driver-enumerator.txt | 13 +- .../upsdrvsvcctl/nut-driver-enumerator.sh.in | 150 ++++++++++++---- tests/nut-driver-enumerator-test--ups.conf | 36 +++- tests/nut-driver-enumerator-test.sh | 166 +++++++++++++++--- 6 files changed, 313 insertions(+), 67 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index e54e3f1c81..a97285c01d 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -58,6 +58,13 @@ https://github.com/networkupstools/nut/milestone/13 complete descriptor available on affected River 3 Plus firmware while retaining the shorter length as a fallback. + - `nut-driver-enumerator` now decodes `ups.conf` values using NUT token + syntax, preserving quoted hashes and escaped characters and ignoring + trailing comments. For example, `port = "auto" # comment` now selects + USB media for `nutdrv_qx`. Normalized configuration output retains + quotes and backslashes; changed section checksums can cause a one-time + reconciliation after upgrading. + - Windows serial compatibility: fixed timeout handling to return data already received as a successful short read, while safely canceling and completing pending overlapped I/O before reusing its state. diff --git a/UPGRADING.adoc b/UPGRADING.adoc index 0485efadcb..1260279d9a 100644 --- a/UPGRADING.adoc +++ b/UPGRADING.adoc @@ -31,6 +31,14 @@ command line, in order to quickly pick up any other removed option flags. Changes from 2.8.5 to 2.8.6 --------------------------- +- `nut-driver-enumerator` now preserves quotes and backslashes in normalized + configuration output and decodes values once using NUT token syntax. + Value queries omit trailing comments, take the first value token and + treat single quotes literally. Corrected parsing can change media + dependencies. Section checksums may change on upgrade and trigger the + usual reconciliation, including driver restarts; subsequent checks of + unchanged configuration remain stable. + - PLANNED: Keep track of any further API clean-up? - Potentially a breaking change for C++ clients that rushed to use the new diff --git a/docs/man/nut-driver-enumerator.txt b/docs/man/nut-driver-enumerator.txt index 5e02cbb55c..c2693f4dd8 100644 --- a/docs/man/nut-driver-enumerator.txt +++ b/docs/man/nut-driver-enumerator.txt @@ -108,13 +108,22 @@ NUT device names which each such unit wraps *nut-driver-enumerator.sh --show-all-configs*:: Show the complete normalized list of device configuration blocks -(same as used later by the parser in the script to make decisions) +(same as used later by the parser in the script to make decisions). +Quotes and backslashes are retained until values are queried. Physical +lines joined by a backslash continuation or inside double quotes are +combined. Section checksums +use this normalized text, including a final newline. *nut-driver-enumerator.sh --show-device-config DEV*:: Show configuration block of the specified NUT device *nut-driver-enumerator.sh --show-device-config-value DEV KEY*:: -Show single configuration key of the specified NUT device +Show the decoded value of a configuration key of the specified NUT device. +The first value token is used: double quotes group a token, backslash +escapes the next character, and an unquoted hash starts a comment. +Single quotes are literal characters. A flag without a value prints its +name. Multiple keys may be requested; missing keys print an empty line +and cause a nonzero exit status. ENVIRONMENT VARIABLES --------------------- diff --git a/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in b/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in index 12d9bcbd97..3ea7d704fb 100644 --- a/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in +++ b/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in @@ -502,7 +502,7 @@ upsconf_getSection_content() { SECTION_CONTENT="" RES=1 [ -n "$1" ] || RES=0 - while read LINE ; do + while IFS= read -r LINE ; do case "$LINE" in \["$1"\]) if [ "$RES" = 0 ]; then @@ -539,7 +539,7 @@ $LINE" done if [ "${#SECTION_CONTENT}" != 0 ]; then - echo "$SECTION_CONTENT" + printf '%s\n' "$SECTION_CONTENT" fi [ "$RES" = 0 ] || log_error "Section [$1] was not found in the '$UPSCONF' file" @@ -579,8 +579,7 @@ upsconf_getValue() { CURR_SECTION="" # Gets set by a GETSECTION implementation RES=0 - # Note: Primary aim of this egrep is to pick either assignments or flags - # As a by-product it can be used to test if a particular value is set ;) + # Keep the normalized syntax intact until this final value lookup. SECTION_CONTENT="`$GETSECTION \"$1\"`" || return shift KEYS="$*" @@ -589,13 +588,54 @@ upsconf_getValue() { RES_L=0 VALUE="" - LINE="`echo \"$SECTION_CONTENT\" | $EGREP '(^'\"$1\"'=|^'\"$1\"'$)'`" \ - && VALUE="$(echo "$LINE" | $SED -e "s,^$1=,," -e 's,^\"\(.*\)\"$,\1,' -e "s,^'\(.*\)'\$,\1,")" \ - || RES_L=$? + # Like common/upsconf.c, take the first value token after the + # assignment. Quotes only open at token start; single quotes are + # ordinary characters. Decode backslash escapes exactly once. + VALUE="$(printf '%s\n' "$SECTION_CONTENT" | NDE_KEY="$1" LC_ALL=C awk ' + BEGIN { key = ENVIRON["NDE_KEY"] } + { + if (index($0, key "=") == 1) { + line = substr($0, length(key) + 2) + } else if ($0 == key || (index($0, key) == 1 && + substr($0, length(key) + 1) ~ /^[ \t]*#/)) { + print key + found = 1 + next + } else next + + value = "" + started = quoted = escaped = 0 + for (i = 1; i <= length(line); i++) { + c = substr(line, i, 1) + if (escaped) { + if (c ~ /^[ -\177]$/) value = value c + escaped = 0 + } else if (c == "\\") { + escaped = started = 1 + } else if (quoted) { + if (c == "\"") break + if (c ~ /^[ -\177]$/) value = value c + } else if (c == "#") break + else if (c ~ /^[[:space:]]$/) { + if (started) break + } else if (c == "=" && started) break + else if (c == "\"" && !started) { + quoted = started = 1 + } else { + if (c ~ /^[ -\177]$/) value = value c + started = 1 + if (c == "=") break + } + } + print value + found = 1 + } + END { if (!found) exit 1 } + ')" || RES_L=$? [ "$RES_L" = 0 ] || { RES="$RES_L" ; log_error "Section [$CURR_SECTION] or key '$1' in it was not found in the '$UPSCONF' file" ; } - echo "$VALUE" + printf '%s\n' "$VALUE" shift done @@ -692,9 +732,9 @@ upsconf_getDriverMedia() { CURR_PORT="`upsconf_getPort \"$1\"`" || CURR_PORT="" case "$CURR_PORT" in *@localhost|*@|*@127.0.0.1|*@::1) - printf '%s\n%s\n' "$CURR_DRV" "network-localhost,drivers=`echo "${CURR_PORT}" | $SED 's,@.*$,,'`" ; return ;; + printf '%s\n%s\n' "$CURR_DRV" "network-localhost,drivers=`printf '%s\n' "${CURR_PORT}" | $SED 's,@.*$,,'`" ; return ;; *@"`hostname`"*) # Technically also local host, but via public host name (so networking must be up) - printf '%s\n%s\n' "$CURR_DRV" "network,drivers=`echo "${CURR_PORT}" | $SED 's,@.*$,,'`" ; return ;; + printf '%s\n%s\n' "$CURR_DRV" "network,drivers=`printf '%s\n' "${CURR_PORT}" | $SED 's,@.*$,,'`" ; return ;; *@*) # Might be local host via public IP address, but harder to detect portably printf '%s\n%s\n' "$CURR_DRV" "network" ; return ;; *) # Assume DEV/SEQ file: @@ -710,7 +750,7 @@ upsconf_getDriverMedia() { upsconf_getMedia() { _DRVMED="`upsconf_getDriverMedia \"$1\"`" || return - echo "${_DRVMED}" | @TAIL@ @TAIL_ARGS_FROM_NTH_LINE@ +2 + printf '%s\n' "${_DRVMED}" | @TAIL@ @TAIL_ARGS_FROM_NTH_LINE@ +2 return 0 } @@ -738,13 +778,13 @@ upsconf_debug() { NAME_MD5="`calc_md5 \"$1\"`" # Not public, not in usage() # Used by tests, echo as formatted here to stdout - echo "INST: ${NAME_MD5}~[$1]: DRV='${_DRV}' PORT='${_PRT}' MEDIA='${_MED}' SECTIONMD5='${_MD5}'" + printf '%s\n' "INST: ${NAME_MD5}~[$1]: DRV='${_DRV}' PORT='${_PRT}' MEDIA='${_MED}' SECTIONMD5='${_MD5}'" } calc_md5() { # Tries several ways to produce an MD5 of the "$1" argument - _MD5="`echo \"$1\" | md5sum 2>/dev/null | awk '{print $1}'`" && [ -n "${_MD5}" ] || \ - { _MD5="`echo \"$1\" | openssl dgst -md5 2>/dev/null | awk '{print $NF}'`" && [ -n "${_MD5}" ]; } || \ + _MD5="`printf '%s\n' \"$1\" | md5sum 2>/dev/null | awk '{print $1}'`" && [ -n "${_MD5}" ] || \ + { _MD5="`printf '%s\n' \"$1\" | openssl dgst -md5 2>/dev/null | awk '{print $NF}'`" && [ -n "${_MD5}" ]; } || \ return 1 echo "${_MD5}" @@ -1291,25 +1331,65 @@ upslist_normalizeFile_filter() { # See upslist_normalizeFile() detailed comments below; this routine # is a pipe worker to prepare the text into a simpler expected form. - # Pick the lines which contain a bracket or assignment character, - # or a single token (certain keywords come as just NUT "flags"), - # trim leading and trailing whitespace, comment-only lines, and in - # assignment lines trim the spaces around equality character and - # quoting characters around assignment of values without whitespaces. - # Any whitespace characters around a section name (single token that - # starts the line and is enclosed in brackets) and a trailing comment - # are dropped. Note that brackets with spaces inside, and brackets - # that do not start the non-whitespace payload of the line, are not - # sections. - $EGREP -v '(^$|^#)' | \ - $SED -e 's,^['"$TABCHAR"'\ ]*,,' \ - -e 's,^\#.*$,,' \ - -e 's,['"$TABCHAR"'\ ]*$,,' \ - -e 's,^\([^=\ '"$TABCHAR"']*\)['"$TABCHAR"'\ ]*=['"$TABCHAR"'\ ]*,\1=,g' \ - -e 's,=\"\([^\ '"$TABCHAR"']*\)\"$,=\1,' \ - -e 's,^\(\[[^]'"$TABCHAR"'\ ]*\]\)['"$TABCHAR"'\ ]*\(#.*\)*$,\1,' \ - | $EGREP -v '^$' \ - | $EGREP '([\[\=]|^[^ '"$TABCHAR"']*$|^[^ '"$TABCHAR"']*[ '"$TABCHAR"']*#.*$)' + # Preserve quotes and escapes for upsconf_getValue(). Join physical + # continuations, and trim only whitespace outside tokens, so escaped + # trailing spaces survive. Keep the existing normalized text format + # (not a serialization of decoded values) for section checksums. + awk ' + BEGIN { start = 1 } + { + offset = length(line) + line = line $0 + for (i = 1; i <= length($0); i++) { + c = substr($0, i, 1) + if (comment) { + if (c !~ /^[ \t]$/) last = offset + i + } else if (escaped) { + last = offset + i + escaped = 0 + } else if (c == "\\") { + last = offset + i + escaped = 1 + start = 0 + } else if (quoted) { + last = offset + i + if (c == "\"") { quoted = 0; start = 1 } + } else if (c == "#") { + last = offset + i + comment = 1 + } else if (c ~ /^[ \t]$/) start = 1 + else { + last = offset + i + if (c == "\"" && start) quoted = 1 + start = (c == "=") + } + } + if (escaped) { + line = substr(line, 1, length(line) - 1) + last = length(line) + escaped = 0 + next + } + # Like parseconf, ignore physical newlines inside quotes. + if (quoted) next + line = substr(line, 1, last) + sub(/^[ \t]*/, "", line) + # Normalize only the assignment prefix, never its value. + if (match(line, /^[^= \t]+[ \t]*=[ \t]*/)) { + prefix = substr(line, 1, RLENGTH) + gsub(/[ \t]/, "", prefix) + line = prefix substr(line, RLENGTH + 1) + } + if (line ~ /^\[[^] \t]*\][ \t]*(#.*)?$/) + sub(/\][ \t]*(#.*)?$/, "]", line) + if (line !~ /^#/ && (index(line, "[") || index(line, "=") || + line ~ /^[^ \t]+([ \t]*#.*)?$/)) print line + line = "" + last = quoted = comment = 0 + start = 1 + } + END { if (line != "") print line } + ' } upslist_normalizeFile() { @@ -1368,7 +1448,7 @@ upslist_readFile() { if [ "${#UPSCONF_DATA}" != 0 ] ; then # Note that section-name brackets should contain a single token - UPSLIST_FILE="$(echo "$UPSCONF_DATA_SDP" | $EGREP '^\[[^'"$TABCHAR"'\ ]*\]$' | $SED 's,^\[\(.*\)\]$,\1,' | sort -k1n -k1)" \ + UPSLIST_FILE="$(printf '%s\n' "$UPSCONF_DATA_SDP" | $EGREP '^\[[^'"$TABCHAR"'\ ]*\]$' | $SED 's,^\[\(.*\)\]$,\1,' | sort -k1n -k1)" \ || UPSLIST_FILE="" if [ "${#UPSLIST_FILE}" = 0 ] ; then log_error "Could not read the '$UPSCONF' file or it does not declare any device configurations: no section declarations in parsed normalized contents" @@ -2056,7 +2136,7 @@ while [ $# -gt 0 ]; do [ "${#UPSLIST_FILE}" != 0 ] \ || { log_warn "No devices detected in '$UPSCONF'" ; RES=1 ; } # echo this to stdout: - echo "$UPSCONF_DATA" + printf '%s\n' "$UPSCONF_DATA" exit $RES ;; --show-config|--show-device-config) diff --git a/tests/nut-driver-enumerator-test--ups.conf b/tests/nut-driver-enumerator-test--ups.conf index e3d9f3f144..f7f9354d5c 100644 --- a/tests/nut-driver-enumerator-test--ups.conf +++ b/tests/nut-driver-enumerator-test--ups.conf @@ -9,6 +9,32 @@ globalflag driver = dummy-ups port = file1.dev desc = "This is ups-1" + empty = "" + spaces = " " + quote = "\"edge\"" + backslashes = "C:\\new\\test.dev" + adjacent = "a\\\"b" + doubleescape = "\\n\\t\\r\\#" + apostrophe = "it's literal" + equals = one\=two=ignored + equalfirst = =ignored + quoteinword = one"two + closecommits = "first"second + escapedhash = file\#1.dev # ignored + quotedhash = "file#1.dev" # ignored + quotedescapedhash = "file\#1.dev" + escapedspace = two\ words ignored + trailing = word\ + continued = "two\ + words" + continuedbare = two\ +words + multiline = "a +b" + comment = value # not a continuation \ + aftercomment = intact + repeat = first + repeat = "second value" [epdu-2] # This is an ePDU driver=netxml-ups @@ -59,8 +85,10 @@ driver = 'dummy-ups ' port = /dev/ttyb [qx-usb1] - driver=nutdrv_qx - port = auto + driver=nutdrv\ +_qx + port = "au +to" # USB auto-detection [qx-usb2] driver=nutdrv_qx port = /dev/usb/8 @@ -73,3 +101,7 @@ driver = 'dummy-ups ' driver=nutdrv_qx # comment port = /dev/usb/8 # comment commentedDriverFlag # This flag gotta mean something + +[escapedPath] + driver = dummy-ups + port = "C:\\new\\test.dev" # path diff --git a/tests/nut-driver-enumerator-test.sh b/tests/nut-driver-enumerator-test.sh index 3b284793d1..dbe9a0e83f 100755 --- a/tests/nut-driver-enumerator-test.sh +++ b/tests/nut-driver-enumerator-test.sh @@ -145,8 +145,8 @@ run_testcase_generic() { printf '\t--- expected ---\n%s\n\t--- received ---\n%s\n\t--- MISMATCH ABOVE\n\n' "$EXPECT_TEXT" "$OUT" >&2 # Give a nice output to help track the problem: ( rm -f "/tmp/.nde.text.expected.$$" "/tmp/.nde.text.actual.$$" \ - && echo "$EXPECT_TEXT" > "/tmp/.nde.text.expected.$$" \ - && echo "$OUT" > "/tmp/.nde.text.actual.$$" \ + && printf '%s\n' "$EXPECT_TEXT" > "/tmp/.nde.text.expected.$$" \ + && printf '%s\n' "$OUT" > "/tmp/.nde.text.actual.$$" \ && { OUTD="`diff -u \"/tmp/.nde.text.expected.$$\" \"/tmp/.nde.text.actual.$$\" 2>/dev/null`" if echo "$OUTD" | head -1 | ${EGREP} '^[-+]' >/dev/null ; then echo "$OUTD" @@ -195,6 +195,7 @@ dummy-proxy-localhost dummy1 epdu-2 epdu-2-snmp +escapedPath qx-serial qx-usb1 qx-usb2 @@ -210,7 +211,8 @@ valueHasQuotedHashtag" \ } testcase_show_all_configs() { - # We expect whitespace trimmed, comment-only lines removed + # Trim formatting whitespace and comments on their own lines; retain + # quotes, escapes and escaped trailing spaces for later value decoding. run_testcase "Show all configs" 0 \ 'maxstartdelay=180 globalflag @@ -218,17 +220,40 @@ globalflag driver=dummy-ups port=file1.dev desc="This is ups-1" +empty="" +spaces=" " +quote="\"edge\"" +backslashes="C:\\new\\test.dev" +adjacent="a\\\"b" +doubleescape="\\n\\t\\r\\#" +apostrophe="it'"'"'s literal" +equals=one\=two=ignored +equalfirst==ignored +quoteinword=one"two +closecommits="first"second +escapedhash=file\#1.dev # ignored +quotedhash="file#1.dev" # ignored +quotedescapedhash="file\#1.dev" +escapedspace=two\ words ignored +trailing=word\'" "' +continued="two words" +continuedbare=twowords +multiline="ab" +comment=value # not a continuation \ +aftercomment=intact +repeat=first +repeat="second value" [epdu-2] driver=netxml-ups -port=http://172.16.1.2 +port="http://172.16.1.2" synchronous=yes [epdu-2-snmp] driver=snmp-ups port=172.16.1.2 synchronous=no [usb_3] -driver=usbhid-ups -port=auto +driver="usbhid-ups" +port="auto" [serial.4] driver=serial-ups driverflag @@ -241,7 +266,7 @@ driver="dummy-ups " port=remoteUPS@RemoteHost.local [dummy-proxy-localhost] driver='"'dummy-ups '"' -port=localUPS@127.0.0.1 +port="localUPS@127.0.0.1" [valueHasEquals] driver=dummy=ups port=file1.dev # key = val, right? @@ -250,13 +275,13 @@ driver=dummy-ups port=file#1.dev [valueHasQuotedHashtag] driver=dummy-ups -port=file#1.dev +port="file#1.dev" [qx-serial] driver=nutdrv_qx port=/dev/ttyb [qx-usb1] driver=nutdrv_qx -port=auto +port="auto" # USB auto-detection [qx-usb2] driver=nutdrv_qx port=/dev/usb/8 @@ -268,7 +293,10 @@ desc="value with [brackets]" [sectionWithCommentWhitespace] driver=nutdrv_qx # comment port=/dev/usb/8 # comment -commentedDriverFlag # This flag gotta mean something' \ +commentedDriverFlag # This flag gotta mean something +[escapedPath] +driver=dummy-ups +port="C:\\new\\test.dev" # path' \ --show-all-configs } @@ -277,30 +305,25 @@ testcase_upslist_debug() { run_testcase "List decided MEDIA and config checksums for all devices" 0 \ "INST: 68b329da9893e34099c7d8ad5cb9c940~[]: DRV='' PORT='' MEDIA='' SECTIONMD5='9a1f372a850f1ee3ab1fc08b185783e0' INST: 010cf0aed6dd49865bb49b70267946f5~[dummy-proxy]: DRV='dummy-ups ' PORT='remoteUPS@RemoteHost.local' MEDIA='network' SECTIONMD5='aff543fc07d7fbf83e81001b181c8b97' -INST: 1ea79c6eea3681ba73cc695f3253e605~[dummy-proxy-localhost]: DRV='dummy-ups ' PORT='localUPS@127.0.0.1' MEDIA='network-localhost,drivers=localUPS' SECTIONMD5='73e6b7e3e3b73558dc15253d8cca51b2' -INST: 76b645e28b0b53122b4428f4ab9eb4b9~[dummy1]: DRV='dummy-ups' PORT='file1.dev' MEDIA='' SECTIONMD5='9e0a326b67e00d455494f8b4258a01f1' -INST: a293d65e62e89d6cc3ac6cb88bc312b8~[epdu-2]: DRV='netxml-ups' PORT='http://172.16.1.2' MEDIA='network' SECTIONMD5='0d9a0147dcf87c7c720e341170f69ed4' +INST: 1ea79c6eea3681ba73cc695f3253e605~[dummy-proxy-localhost]: DRV=''dummy-ups' PORT='localUPS@127.0.0.1' MEDIA='network-localhost,drivers=localUPS' SECTIONMD5='ba7de199978fe89f5f2e3341ede71df4' +INST: 76b645e28b0b53122b4428f4ab9eb4b9~[dummy1]: DRV='dummy-ups' PORT='file1.dev' MEDIA='' SECTIONMD5='548240a045d5647bcf8c219e1bac66d6' +INST: a293d65e62e89d6cc3ac6cb88bc312b8~[epdu-2]: DRV='netxml-ups' PORT='http://172.16.1.2' MEDIA='network' SECTIONMD5='dc396adae98faff8d424f273478f49c7' INST: 9a5561464ff8c78dd7cb544740ce2adc~[epdu-2-snmp]: DRV='snmp-ups' PORT='172.16.1.2' MEDIA='network' SECTIONMD5='2631b6c21140cea0dd30bb88b942ce3f' +INST: eec97fcac3f2ac0bb3db76001e000b1d~[escapedPath]: DRV='dummy-ups' PORT='C:\\new\\test.dev' MEDIA='' SECTIONMD5='f249649add564a91f9bd04c296106a57' INST: 16adbdafb22d9fdff1d09038520eb32e~[qx-serial]: DRV='nutdrv_qx' PORT='/dev/ttyb' MEDIA='serial' SECTIONMD5='e3e6e586fbe5b3c0a89432f4b993f4ad' -INST: a21bd2b786228b9619f6adba6db8fa83~[qx-usb1]: DRV='nutdrv_qx' PORT='auto' MEDIA='usb' SECTIONMD5='a6139c5da35bef89dc5b96e2296f5369' +INST: a21bd2b786228b9619f6adba6db8fa83~[qx-usb1]: DRV='nutdrv_qx' PORT='auto' MEDIA='usb' SECTIONMD5='59e8b68feca187fc3e2e3ffafe81009d' INST: 0066605e07c66043a17eccecbeea1ac5~[qx-usb2]: DRV='nutdrv_qx' PORT='/dev/usb/8' MEDIA='usb' SECTIONMD5='5722dd9c21d07a1f5bcb516dbc458deb' INST: b377c8ec5f35b3c3df361686627e1625~[ragtech-serial]: DRV='ragtech' PORT='/dev/ttyACM0' MEDIA='serial' SECTIONMD5='bd2de13822ec93bde2aeeeca450fc228' -INST: 1280a731e03116f77290e51dd2a2f37e~[sectionWithComment]: DRV='nutdrv_qx#comment' PORT='/dev/usb/8' MEDIA='' SECTIONMD5='be30e15e17d0579c85eecaf176b4a064' -INST: 770abd5659061a29ed3ae4f7c0b00915~[sectionWithCommentWhitespace]: DRV='nutdrv_qx # comment' PORT='/dev/usb/8 # comment' MEDIA='' SECTIONMD5='c757822a331521cdc97310d0241eba28' -INST: efdb1b4698215fdca36b9bc06d24661d~[serial.4]: DRV='serial-ups' PORT='/dev/ttyS1 # some path' MEDIA='' SECTIONMD5='9c485f733aa6d6c85c1724f162929443' -INST: f4a1c33db201c2ca897a3337993c10fc~[usb_3]: DRV='usbhid-ups' PORT='auto' MEDIA='usb' SECTIONMD5='1f6a24becde9bd31c9852610658ef84a' -INST: 8e5686f92a5ba11901996c813e7bb23d~[valueHasEquals]: DRV='dummy=ups' PORT='file1.dev # key = val, right?' MEDIA='' SECTIONMD5='2f04d65da53e3b13771bb65422f0f4c0' -INST: 99da99b1e301e84f34f349443aac545b~[valueHasHashtag]: DRV='dummy-ups' PORT='file#1.dev' MEDIA='' SECTIONMD5='6029bda216de0cf1e81bd55ebd4a0fff' -INST: d50c3281f9b68a94bf9df72a115fbb5c~[valueHasQuotedHashtag]: DRV='dummy-ups' PORT='file#1.dev' MEDIA='' SECTIONMD5='af59c3c0caaa68dcd796d7145ae403ee'" \ +INST: 1280a731e03116f77290e51dd2a2f37e~[sectionWithComment]: DRV='nutdrv_qx' PORT='/dev/usb/8' MEDIA='usb' SECTIONMD5='be30e15e17d0579c85eecaf176b4a064' +INST: 770abd5659061a29ed3ae4f7c0b00915~[sectionWithCommentWhitespace]: DRV='nutdrv_qx' PORT='/dev/usb/8' MEDIA='usb' SECTIONMD5='c757822a331521cdc97310d0241eba28' +INST: efdb1b4698215fdca36b9bc06d24661d~[serial.4]: DRV='serial-ups' PORT='/dev/ttyS1' MEDIA='' SECTIONMD5='9c485f733aa6d6c85c1724f162929443' +INST: f4a1c33db201c2ca897a3337993c10fc~[usb_3]: DRV='usbhid-ups' PORT='auto' MEDIA='usb' SECTIONMD5='072d77825673bc1ce5d99d3e848697ed' +INST: 8e5686f92a5ba11901996c813e7bb23d~[valueHasEquals]: DRV='dummy' PORT='file1.dev' MEDIA='' SECTIONMD5='2f04d65da53e3b13771bb65422f0f4c0' +INST: 99da99b1e301e84f34f349443aac545b~[valueHasHashtag]: DRV='dummy-ups' PORT='file' MEDIA='' SECTIONMD5='6029bda216de0cf1e81bd55ebd4a0fff' +INST: d50c3281f9b68a94bf9df72a115fbb5c~[valueHasQuotedHashtag]: DRV='dummy-ups' PORT='file#1.dev' MEDIA='' SECTIONMD5='4c1a0f08861adbdb62e5de5525b1618c'" \ upslist_debug - # FIXME : in [valueHasEquals] and [serial.4] the PORT value is quite bogus - # with its embedded comments. Check vs. binary config parser, whether in - # unquoted case only first token is the valid value, and how comments are - # handled in general? - # FIXME : in [valueHasHashtag] the line after "#" should likely be dropped - # (check in binary config parser first) while in [valueHasQuotedHashtag] - # it should stay. + } testcase_getValue() { @@ -355,6 +378,92 @@ globalflag" \ --show-config-value '' nosuchflag } +callNDE_SDP() ( + GETSECTION=upsconf_getSection_SDP + export GETSECTION + callNDE "$@" +) + +callNDE_stable() ( + # Repeated parsing and parsing the normalized text must agree, including + # section hashes. Use only our test configuration, never service actions. + mkdir -p "$NUT_CONFPATH" || exit + NORMALIZED="$NUT_CONFPATH/normalized-$$.conf" + trap 'rm -f "$NORMALIZED"' 0 + callNDE --show-all-configs > "$NORMALIZED" || exit + FIRST="`callNDE upslist_debug`" || exit + SECOND="`callNDE upslist_debug`" || exit + [ "$FIRST" = "$SECOND" ] || exit 1 + UPSCONF="$NORMALIZED" + export UPSCONF + SECOND="`callNDE upslist_debug`" || exit + [ "$FIRST" = "$SECOND" ] || exit 1 + echo stable +) + +testcase_valueSyntax() { + run_testcase "Decode values once using NUT token syntax" 0 \ +' +'" "' +"edge" +C:\new\test.dev +a\"b +\n\t\r\# +it'"'"'s literal +one=two += +one"two +first +file#1.dev +file#1.dev +file#1.dev +two words +word'" "' +two words +twowords +ab +value +intact +first +second value' \ + --show-device-config-value dummy1 empty spaces quote backslashes \ + adjacent doubleescape apostrophe equals equalfirst quoteinword \ + closecommits escapedhash quotedhash quotedescapedhash escapedspace \ + trailing continued continuedbare multiline comment aftercomment repeat + + run_testcase "Only the first unquoted value token is used" 0 'dummy' \ + --show-device-config-value valueHasEquals driver + run_testcase "Single quotes are literal" 0 "'dummy-ups" \ + --show-device-config-value dummy-proxy-localhost driver + run_testcase "Flags may have trailing comments" 0 'commentedDriverFlag' \ + --show-device-config-value sectionWithCommentWhitespace commentedDriverFlag + run_testcase "Missing section retains failure status" 1 '' \ + --show-device-config-value missingSection port + run_testcase "Key lookup is literal" 1 '' \ + --show-device-config-value dummy1 '.*' + run_testcase "Missing value between present keys retains output and status" 1 \ +'C:\new\test.dev + +'" "' +intact' \ + --show-device-config-value dummy1 backslashes missing spaces aftercomment + + # Same externally visible values from the complete and smaller caches. + for CASE_CMD in callNDE callNDE_SDP ; do + run_testcase_generic "$CASE_CMD" "Quoted multiline auto with comment ($CASE_CMD)" 0 \ +'nutdrv_qx +auto' --show-device-config-value qx-usb1 driver port + run_testcase_generic "$CASE_CMD" "Escaped path ($CASE_CMD)" 0 \ + 'C:\new\test.dev' --show-device-config-value escapedPath port + run_testcase_generic "$CASE_CMD" "Quoted hash ($CASE_CMD)" 0 \ + 'file#1.dev' --show-device-config-value valueHasQuotedHashtag port + run_testcase_generic "$CASE_CMD" "Unquoted hash ($CASE_CMD)" 0 \ + 'file' --show-device-config-value valueHasHashtag port + done + run_testcase_generic callNDE_stable "Repeated and normalized parsing keeps checksums stable" \ + 0 stable +} + # This one is not about NDE as such, but piggy-backs on our ability to test # multiple shells at once, with a test relevant for how we write scripts # (with backticks to remain compatible with older Bourne-like shells). @@ -651,6 +760,7 @@ testsuite() { testcase_show_all_configs testcase_getValue testcase_globalSection + testcase_valueSyntax # This one can take a while, put it last testcase_upslist_debug # Something very different From cb02364d280f9e04e2bdcb1202dce115bc98b1c5 Mon Sep 17 00:00:00 2001 From: user01010111 <12504630+user01010111@users.noreply.github.com> Date: Wed, 9 Sep 2026 13:44:07 +1200 Subject: [PATCH 2/8] nut-driver-enumerator: recognize whitespace with old mawk Use the explicit six C whitespace bytes in the value lexer because Trusty's mawk 1.3.3 does not implement the named whitespace class. Preserve the existing C locale, token rules and test expectations. The existing Trusty runtime tests improve from 133 pass/3 fail to 136/0 under each of sh, bash and dash. A 256-byte oracle, modern shell/awk checks, build, make check, staged package checks, spelling/style and complete-manual distcheck-light pass. These runtime checks do not reconstruct the exact Jenkins image; authenticated Jenkins failure logs remain unavailable, so this does not claim every CI error is resolved. Related: #3635 AI assistance: OpenAI Codex with gpt-6-astra (high reasoning). The human contributor remains responsible for reviewing and submitting the change. Signed-off-by: user01010111 <12504630+user01010111@users.noreply.github.com> --- scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in b/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in index 3ea7d704fb..88392c3a86 100644 --- a/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in +++ b/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in @@ -616,7 +616,7 @@ upsconf_getValue() { if (c == "\"") break if (c ~ /^[ -\177]$/) value = value c } else if (c == "#") break - else if (c ~ /^[[:space:]]$/) { + else if (c ~ /^[ \t\r\n\013\014]$/) { if (started) break } else if (c == "=" && started) break else if (c == "\"" && !started) { From 1a43c51f751d206b1036239e99260a026ba88789 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 10 Sep 2026 09:13:56 +0200 Subject: [PATCH 3/8] docs/man/nut-driver-enumerator.txt: fix markup (line lengths/breaks) [#3635] Signed-off-by: Jim Klimov --- docs/man/nut-driver-enumerator.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/man/nut-driver-enumerator.txt b/docs/man/nut-driver-enumerator.txt index c2693f4dd8..f07c61e2f7 100644 --- a/docs/man/nut-driver-enumerator.txt +++ b/docs/man/nut-driver-enumerator.txt @@ -109,10 +109,10 @@ NUT device names which each such unit wraps *nut-driver-enumerator.sh --show-all-configs*:: Show the complete normalized list of device configuration blocks (same as used later by the parser in the script to make decisions). -Quotes and backslashes are retained until values are queried. Physical -lines joined by a backslash continuation or inside double quotes are -combined. Section checksums -use this normalized text, including a final newline. +Quotes and backslashes are retained until values are queried. +Physical lines joined by a backslash continuation or inside double +quotes are combined. Section checksums use this normalized text, +including a final newline. *nut-driver-enumerator.sh --show-device-config DEV*:: Show configuration block of the specified NUT device From 5342fda3b3daf99bceb0c62272a40c2a019be5b8 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 10 Sep 2026 09:18:45 +0200 Subject: [PATCH 4/8] scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in: fix nested double-quotes missed earlier [#3196, #3635] Signed-off-by: Jim Klimov --- .../upsdrvsvcctl/nut-driver-enumerator.sh.in | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in b/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in index 88392c3a86..16a5955b55 100644 --- a/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in +++ b/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in @@ -13,7 +13,7 @@ # is to just pick out some strings relevant for tracking config changes. # # Copyright (C) 2016-2020 Eaton -# Copyright (C) 2020-2025 Jim Klimov +# Copyright (C) 2020-2026 Jim Klimov # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -732,9 +732,9 @@ upsconf_getDriverMedia() { CURR_PORT="`upsconf_getPort \"$1\"`" || CURR_PORT="" case "$CURR_PORT" in *@localhost|*@|*@127.0.0.1|*@::1) - printf '%s\n%s\n' "$CURR_DRV" "network-localhost,drivers=`printf '%s\n' "${CURR_PORT}" | $SED 's,@.*$,,'`" ; return ;; + printf '%s\n%s\n' "$CURR_DRV" "network-localhost,drivers=`printf '%s\n' \"${CURR_PORT}\" | $SED 's,@.*$,,'`" ; return ;; *@"`hostname`"*) # Technically also local host, but via public host name (so networking must be up) - printf '%s\n%s\n' "$CURR_DRV" "network,drivers=`printf '%s\n' "${CURR_PORT}" | $SED 's,@.*$,,'`" ; return ;; + printf '%s\n%s\n' "$CURR_DRV" "network,drivers=`printf '%s\n' \"${CURR_PORT}\" | $SED 's,@.*$,,'`" ; return ;; *@*) # Might be local host via public IP address, but harder to detect portably printf '%s\n%s\n' "$CURR_DRV" "network" ; return ;; *) # Assume DEV/SEQ file: @@ -808,7 +808,7 @@ smf_validFullUnitName() { esac } smf_validInstanceName() { - echo "MD5_`calc_md5 "$1"`" + echo "MD5_`calc_md5 \"$1\"`" } smf_validInstanceSuffixName() { case "$1" in @@ -866,7 +866,7 @@ smf_registerInstance() { for OTHERDEV in $UPSLIST_FILE ; do case "${DEPDRV}" in *-"${OTHERDEV}") - OTHERDRV="'`echo "${OTHERLIST}" | awk '($1 == "'"${OTHERDEV}"'") { print $2 }'`'" || OTHERDRV="" + OTHERDRV="'`echo \"${OTHERLIST}\" | awk '($1 == \"'\"${OTHERDEV}\"'\") { print $2 }'`'" || OTHERDRV="" log_warn "Device ${DEVICE} depends on another as '${DEPDRV}', but the other possible device '${OTHERDEV}' uses an unexpected driver: ${OTHERDRV}" ;; esac @@ -1087,7 +1087,7 @@ systemd_validFullUnitName() { esac } systemd_validInstanceName() { - echo "MD5_`calc_md5 "$1"`" + echo "MD5_`calc_md5 \"$1\"`" } systemd_validInstanceSuffixName() { echo "$1" | $SED -e 's,^.*@,,' -e 's,\.service$,,' @@ -1152,7 +1152,7 @@ systemd_registerInstance() { for OTHERDEV in $UPSLIST_FILE ; do case "${DEPDRV}" in *-"${OTHERDEV}") - OTHERDRV="'`echo "${OTHERLIST}" | awk '($1 == "'"${OTHERDEV}"'") { print $2 }'`'" || OTHERDRV="" + OTHERDRV="'`echo \"${OTHERLIST}\" | awk '($1 == \"'\"${OTHERDEV}\"'\") { print $2 }'`'" || OTHERDRV="" log_warn "Device ${DEVICE} depends on another as '${DEPDRV}', but the other possible device '${OTHERDEV}' uses an unexpected driver: ${OTHERDRV}" ;; esac @@ -1631,7 +1631,7 @@ nut_driver_enumerator_main() { if [ "$RESTART_ALL" = yes ] ; then # Save new checksum of global config - $hook_setSavedMD5 "" "`upsconf_getSection_MD5 ""`" + $hook_setSavedMD5 "" "`upsconf_getSection_MD5 ''`" fi if [ "${#UPSLIST_FILE}" != 0 ]; then @@ -1708,7 +1708,7 @@ nut_driver_enumerator_full_reconfigure() { fi # Save new checksum of global config - $hook_setSavedMD5 "" "`upsconf_getSection_MD5 ""`" + $hook_setSavedMD5 "" "`upsconf_getSection_MD5 ''`" # Service units were manipulated, including saving of checksums; # refresh the service management daemon if needed From 4d4ded50603f2dedc89bdf53801e3a3f18d2cd6a Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 10 Sep 2026 09:37:27 +0200 Subject: [PATCH 5/8] NEWS.adoc: NDE syntax parser fix: move the section lower, refer to PR [#3635] Signed-off-by: Jim Klimov --- NEWS.adoc | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index a97285c01d..55702121e0 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -58,13 +58,6 @@ https://github.com/networkupstools/nut/milestone/13 complete descriptor available on affected River 3 Plus firmware while retaining the shorter length as a fallback. - - `nut-driver-enumerator` now decodes `ups.conf` values using NUT token - syntax, preserving quoted hashes and escaped characters and ignoring - trailing comments. For example, `port = "auto" # comment` now selects - USB media for `nutdrv_qx`. Normalized configuration output retains - quotes and backslashes; changed section checksums can cause a one-time - reconciliation after upgrading. - - Windows serial compatibility: fixed timeout handling to return data already received as a successful short read, while safely canceling and completing pending overlapped I/O before reusing its state. @@ -627,6 +620,14 @@ https://github.com/networkupstools/nut/milestone/13 aspect of that, it should now abort with an explanation (and not proceed with insecure start-up like it could do before). [issue #3331, PR #3435] + - The `nut-driver-enumerator.sh` script (NDE) updates: + * The shell/awk based parser now decodes `ups.conf` values using NUT token + syntax, preserving quoted hashes and escaped characters and ignoring + trailing comments. For example, `port = "auto" # comment` now properly + selects USB media for `nutdrv_qx`. The normalized configuration output + retains quotes and backslashes; changed section checksums can cause a + one-time reconciliation after upgrading. [PR #3635] + - Recipes, CI and helper script updates not classified above: * Introduced `AGENTS.md` for AI agents to help in a consistent manner with minimal friction. [#3608] From 4f743df624e15b1b70fbba39fd3980edb920c9fa Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 10 Sep 2026 09:38:48 +0200 Subject: [PATCH 6/8] UPGRADING.adoc: NDE syntax parser fix: move the section lower, refer to PR [#3635] Signed-off-by: Jim Klimov --- UPGRADING.adoc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/UPGRADING.adoc b/UPGRADING.adoc index 1260279d9a..c72e36f161 100644 --- a/UPGRADING.adoc +++ b/UPGRADING.adoc @@ -31,14 +31,6 @@ command line, in order to quickly pick up any other removed option flags. Changes from 2.8.5 to 2.8.6 --------------------------- -- `nut-driver-enumerator` now preserves quotes and backslashes in normalized - configuration output and decodes values once using NUT token syntax. - Value queries omit trailing comments, take the first value token and - treat single quotes literally. Corrected parsing can change media - dependencies. Section checksums may change on upgrade and trigger the - usual reconciliation, including driver restarts; subsequent checks of - unchanged configuration remain stable. - - PLANNED: Keep track of any further API clean-up? - Potentially a breaking change for C++ clients that rushed to use the new @@ -82,6 +74,14 @@ The new `-A filename` option defaults to trying to use a `nutauth.conf` file reading one ('none' as the legacy default). See the updated manual pages for more details. [issues #3329, #3411] +- `nut-driver-enumerator` now preserves quotes and backslashes in normalized + configuration output and decodes values once using NUT token syntax. + Value queries omit trailing comments, take the first value token and + treat single quotes literally. This corrected parsing can change media + dependencies. Section checksums may change on upgrade and trigger the + usual reconciliation, including driver restarts; subsequent checks of + unchanged configuration remain stable. [PR #3635] + - The `powervar_cx_usb`, `tripplite_usb` drivers used a built-in limit on reconnection attempts after which they exited ('60' and '10' respectively). This was revised to follow the new common setting `reconnect_max_tries`, From fe32d9cb859cb2bf2fc2586b0bac39eb360733f2 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 10 Sep 2026 09:48:02 +0200 Subject: [PATCH 7/8] scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in: refactor with upsconf_getValue_awk() to avoid questions about nested quoting [#3635, #3196] Signed-off-by: Jim Klimov --- .../upsdrvsvcctl/nut-driver-enumerator.sh.in | 95 ++++++++++--------- 1 file changed, 51 insertions(+), 44 deletions(-) diff --git a/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in b/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in index 16a5955b55..232521e65d 100644 --- a/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in +++ b/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in @@ -570,6 +570,56 @@ ${UPSCONF_DATA_SDP} EOF } +upsconf_getValue_awk() { + # Helper for upsconf_getValue() below + + # Like common/upsconf.c, take the first value token after the + # assignment. Quotes only open at token start; single quotes are + # ordinary characters. Decode backslash escapes exactly once. + + NDE_KEY="$1" LC_ALL=C awk ' +BEGIN { key = ENVIRON["NDE_KEY"] } +{ + if (index($0, key "=") == 1) { + line = substr($0, length(key) + 2) + } else if ($0 == key || (index($0, key) == 1 && + substr($0, length(key) + 1) ~ /^[ \t]*#/)) { + print key + found = 1 + next + } else next + + value = "" + started = quoted = escaped = 0 + for (i = 1; i <= length(line); i++) { + c = substr(line, i, 1) + if (escaped) { + if (c ~ /^[ -\177]$/) value = value c + escaped = 0 + } else if (c == "\\") { + escaped = started = 1 + } else if (quoted) { + if (c == "\"") break + if (c ~ /^[ -\177]$/) value = value c + } else if (c == "#") break + else if (c ~ /^[ \t\r\n\013\014]$/) { + if (started) break + } else if (c == "=" && started) break + else if (c == "\"" && !started) { + quoted = started = 1 + } else { + if (c ~ /^[ -\177]$/) value = value c + started = 1 + if (c == "=") break + } + } + print value + found = 1 +} +END { if (!found) exit 1 } +' +} + upsconf_getValue() { # "$1" = name of ups.conf section, may be empty for global config # "$2..$N" = name of config key; we will echo its value @@ -588,50 +638,7 @@ upsconf_getValue() { RES_L=0 VALUE="" - # Like common/upsconf.c, take the first value token after the - # assignment. Quotes only open at token start; single quotes are - # ordinary characters. Decode backslash escapes exactly once. - VALUE="$(printf '%s\n' "$SECTION_CONTENT" | NDE_KEY="$1" LC_ALL=C awk ' - BEGIN { key = ENVIRON["NDE_KEY"] } - { - if (index($0, key "=") == 1) { - line = substr($0, length(key) + 2) - } else if ($0 == key || (index($0, key) == 1 && - substr($0, length(key) + 1) ~ /^[ \t]*#/)) { - print key - found = 1 - next - } else next - - value = "" - started = quoted = escaped = 0 - for (i = 1; i <= length(line); i++) { - c = substr(line, i, 1) - if (escaped) { - if (c ~ /^[ -\177]$/) value = value c - escaped = 0 - } else if (c == "\\") { - escaped = started = 1 - } else if (quoted) { - if (c == "\"") break - if (c ~ /^[ -\177]$/) value = value c - } else if (c == "#") break - else if (c ~ /^[ \t\r\n\013\014]$/) { - if (started) break - } else if (c == "=" && started) break - else if (c == "\"" && !started) { - quoted = started = 1 - } else { - if (c ~ /^[ -\177]$/) value = value c - started = 1 - if (c == "=") break - } - } - print value - found = 1 - } - END { if (!found) exit 1 } - ')" || RES_L=$? + VALUE="$(printf '%s\n' "$SECTION_CONTENT" | upsconf_getValue_awk "$1")" || RES_L=$? [ "$RES_L" = 0 ] || { RES="$RES_L" ; log_error "Section [$CURR_SECTION] or key '$1' in it was not found in the '$UPSCONF' file" ; } From ceb6751fe313f2e046e4208a2d91ec33ebd5b5ec Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Thu, 10 Sep 2026 09:50:44 +0200 Subject: [PATCH 8/8] scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in: fix the new $(...) into old backticks [#3635, #3196] This script is expressly not necessarily parsed by BASH or shells with the newer POSIX syntax, but may be handled by "system shells" adhering to an older standard on some operating systems. So while backticks are less flexible than parentheses, they are more portable. Some earlier revisions of this script missed the introduction of this regression, fixing now that these lines were touched in PR #3635. Signed-off-by: Jim Klimov --- scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in b/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in index 232521e65d..76fa27cdf0 100644 --- a/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in +++ b/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in @@ -638,7 +638,7 @@ upsconf_getValue() { RES_L=0 VALUE="" - VALUE="$(printf '%s\n' "$SECTION_CONTENT" | upsconf_getValue_awk "$1")" || RES_L=$? + VALUE="`printf '%s\n' \"${SECTION_CONTENT}\" | upsconf_getValue_awk \"$1\"`" || RES_L=$? [ "$RES_L" = 0 ] || { RES="$RES_L" ; log_error "Section [$CURR_SECTION] or key '$1' in it was not found in the '$UPSCONF' file" ; } @@ -1422,7 +1422,7 @@ upslist_normalizeFile() { # Store a normalized version of NUT configuration file contents. # Also use a SDP subset with just section, driver and port info # for faster parsing when determining driver-required media etc. - UPSCONF_DATA="$(upslist_normalizeFile_filter < "$UPSCONF")" \ + UPSCONF_DATA="`upslist_normalizeFile_filter < \"${UPSCONF}\"`" \ && [ "${#UPSCONF_DATA}" != 0 ] \ && UPSCONF_DATA_SDP="`$EGREP '^(\[.*\]|driver=|port=)' << EOF $UPSCONF_DATA @@ -1455,7 +1455,7 @@ upslist_readFile() { if [ "${#UPSCONF_DATA}" != 0 ] ; then # Note that section-name brackets should contain a single token - UPSLIST_FILE="$(printf '%s\n' "$UPSCONF_DATA_SDP" | $EGREP '^\[[^'"$TABCHAR"'\ ]*\]$' | $SED 's,^\[\(.*\)\]$,\1,' | sort -k1n -k1)" \ + UPSLIST_FILE="`printf '%s\n' \"$UPSCONF_DATA_SDP\" | $EGREP '^\[[^'\"$TABCHAR\"'\ ]*\]$' | $SED 's,^\[\(.*\)\]$,\1,' | sort -k1n -k1`" \ || UPSLIST_FILE="" if [ "${#UPSLIST_FILE}" = 0 ] ; then log_error "Could not read the '$UPSCONF' file or it does not declare any device configurations: no section declarations in parsed normalized contents"