Stop bash arrays ending at the first ) inside a comment
Both the reader and the writer ran to the first closing paren, which is only the array's own close if nothing inside contains one. HOST1_WATCHDOG_SCAN_IGNORE has carried "(exit 127 — bad image)" for weeks: writes spliced into the middle of it and were refused by bash -n with a bare false, and reads returned the entries above it — the Rsync tab showed no intermediate scripts at all and 10 of 17 daily. Arrays now close on a ) that starts its own line, as confform already did.
This commit is contained in:
@@ -836,10 +836,14 @@ function vv_log_tail(string $path, int $lines): string {
|
|||||||
return implode("\n", array_slice($all, -$lines));
|
return implode("\n", array_slice($all, -$lines));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See VV_CONF_ARRAY_BODY in config.php for why this cannot stop at the first `)`.
|
||||||
function vv_parse_bash_array(string $raw, string $varName): array {
|
function vv_parse_bash_array(string $raw, string $varName): array {
|
||||||
if (!preg_match('/^\s*' . preg_quote($varName, '/') . '\s*=\s*\(([^)]*)\)/ms', $raw, $m)) return [];
|
if (!preg_match('/^\s*' . preg_quote($varName, '/') . '\s*=\s*\(' . VV_CONF_ARRAY_BODY . '/ms',
|
||||||
|
$raw, $m)) return [];
|
||||||
|
// Group 2 exists only when the multi-line branch matched; group 1 is the single-line body.
|
||||||
|
$body = isset($m[2]) ? $m[2] : ($m[1] ?? '');
|
||||||
$items = [];
|
$items = [];
|
||||||
foreach (explode("\n", $m[1]) as $line) {
|
foreach (explode("\n", $body) as $line) {
|
||||||
$line = trim(preg_replace('/#.*$/', '', $line), " \t\"'");
|
$line = trim(preg_replace('/#.*$/', '', $line), " \t\"'");
|
||||||
if ($line !== '') $items[] = $line;
|
if ($line !== '') $items[] = $line;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -813,16 +813,18 @@ function vv_conf_write_file(string $file, array $fileChanges): bool {
|
|||||||
$raw
|
$raw
|
||||||
) ?? $raw;
|
) ?? $raw;
|
||||||
|
|
||||||
|
// VV_CONF_ARRAY_CLOSE, not [^)]*\) — see the constant for what the greedy-to-first-paren
|
||||||
|
// version did to a list whose comments contain one.
|
||||||
} elseif ($type === 'array') {
|
} elseif ($type === 'array') {
|
||||||
$raw = preg_replace_callback(
|
$raw = preg_replace_callback(
|
||||||
'/^(\s*)(' . $qKey . '\s*=\s*\()[^)]*\)/ms',
|
'/^(\s*)(' . $qKey . '\s*=\s*\()' . VV_CONF_ARRAY_CLOSE . '/ms',
|
||||||
fn($m) => $m[1] . $m[2] . "\n" . $value . "\n" . $m[1] . ")",
|
fn($m) => $m[1] . $m[2] . "\n" . $value . "\n" . $m[1] . ")",
|
||||||
$raw
|
$raw
|
||||||
) ?? $raw;
|
) ?? $raw;
|
||||||
|
|
||||||
} elseif ($type === 'assoc_array') {
|
} elseif ($type === 'assoc_array') {
|
||||||
$raw = preg_replace_callback(
|
$raw = preg_replace_callback(
|
||||||
'/^(\s*)(declare\s+-A\s+' . $qKey . '\s*=\s*\()[^)]*\)/ms',
|
'/^(\s*)(declare\s+-A\s+' . $qKey . '\s*=\s*\()' . VV_CONF_ARRAY_CLOSE . '/ms',
|
||||||
fn($m) => $m[1] . $m[2] . "\n" . $value . "\n" . $m[1] . ")",
|
fn($m) => $m[1] . $m[2] . "\n" . $value . "\n" . $m[1] . ")",
|
||||||
$raw
|
$raw
|
||||||
) ?? $raw;
|
) ?? $raw;
|
||||||
|
|||||||
@@ -645,6 +645,31 @@ function vv_format_uptime(int $seconds): string {
|
|||||||
return ($d ? "{$d}d " : '') . ($h ? "{$h}h " : '') . "{$m}m";
|
return ($d ? "{$d}d " : '') . ($h ? "{$h}h " : '') . "{$m}m";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Where a bash array ends ──────────────────────────────────────────────────────────────────
|
||||||
|
// Both halves of this had the same bug independently, which is why the rule now lives in one
|
||||||
|
// place: `[^)]*\)` — run to the first closing paren — is only the array's own close if no entry
|
||||||
|
// or comment contains one. HOST1_WATCHDOG_SCAN_IGNORE has carried
|
||||||
|
// `# broken test container (exit 127 — bad image)` for weeks, and the two sides failed differently
|
||||||
|
// against it:
|
||||||
|
//
|
||||||
|
// writing vv_conf_write_file() spliced the new body into the middle of that comment and left
|
||||||
|
// the real `)` stranded below as a stray token. `bash -n` caught it and vv_conf_edit()
|
||||||
|
// rolled the file back, so no conf was corrupted — but the caller got a bare `false`
|
||||||
|
// with an empty $rejected, and every future save of that list would have failed the
|
||||||
|
// same silent way.
|
||||||
|
// reading vv_parse_bash_array() stopped there and returned the entries above it, so the array
|
||||||
|
// was quietly short. Only the PHP layer was affected: bash sources the file itself and
|
||||||
|
// always saw every entry, so the watchdogs behaved correctly while the pages under-
|
||||||
|
// reported what they were configured with — the failure mode with no symptom.
|
||||||
|
//
|
||||||
|
// confform.php's own reader was always right, and this is its rule: a multi-line array closes on
|
||||||
|
// a `)` that starts its own line. Parens anywhere else are just text.
|
||||||
|
//
|
||||||
|
// CLOSE is non-capturing, for splicing a new body in. BODY captures — group 1 when the array is
|
||||||
|
// written on one line, group 2 when it spans several.
|
||||||
|
const VV_CONF_ARRAY_CLOSE = '(?:[^)\n]*\)|.*?\n[ \t]*\))';
|
||||||
|
const VV_CONF_ARRAY_BODY = '(?:([^)\n]*)\)|\n(.*?)^[ \t]*\)[ \t]*$)';
|
||||||
|
|
||||||
// Parse a scalar value from raw conf text. Matches KEY="value" or KEY=value.
|
// Parse a scalar value from raw conf text. Matches KEY="value" or KEY=value.
|
||||||
// Identical logic was previously duplicated as vv_arr_scalar / vv_wd_scalar /
|
// Identical logic was previously duplicated as vv_arr_scalar / vv_wd_scalar /
|
||||||
// vv_fb_scalar / vv_media_conf_scalar — all reduce to this one regex.
|
// vv_fb_scalar / vv_media_conf_scalar — all reduce to this one regex.
|
||||||
|
|||||||
Reference in New Issue
Block a user