🦄 Sync 2026-09-27 22:25:23

This commit is contained in:
github-actions[bot] committed 2026-09-27 22:25:23 +08:00
1 parent 67a7165b7e
commit 4c6d0df36f
67 files changed
+415 -8397

No files matched your search

@@ -197,7 +197,7 @@ function summary_json(out) {
out = out ",\"top_drops\":" summary_top(summary_drop_counts, 3)
out = out ",\"top_rules\":" summary_top(summary_rule_counts, 3) "}"
# LC_ALL=C makes this a byte budget; utf8_prefix keeps field cuts valid.
if (length(out) > 256) return "{\"scope\":\"top of shown sample\",\"truncated\":true}"
if (length(out) > 1024) return "{\"scope\":\"top of shown sample\",\"truncated\":true}"
return out
}
BEGIN {
@@ -6,9 +6,10 @@
# Log messages are treated as data (jsonfilter + awk stdin); never interpolated
# into shell command strings. Usage: ubus call log read '...' | fwlive-log-filter.sh
#
# Performance: one jsonfilter for @.log[*] plus one awk classify. The same
# awk pass counts enumerated entries for messages_received; process count is
# constant per poll, not O(entries).
# Performance: one jsonfilter for @.log[*] plus one awk classify. An empty
# @.log array pays a second jsonfilter -t to tell healthy empty from a
# parse failure. The same awk pass counts enumerated entries for
# messages_received; process count is not O(entries).
#
# Entry point (pipeline). The classifier sibling is sourced and must not set
# strict mode itself.
@@ -37,10 +38,12 @@ fi
# (or paused FETCH_LINES_MAX poll) can exceed that and make jsonfilter fail
# while this script still returns a valid empty result.
# A valid input with no log entries is not fatal; the classifier closes JSON
# and reports messages_received:0. A jsonfilter failure is different:
# it must remain an error so rpcd does not record a fast healthy sample. Keep
# jsonfilter output in a secure temporary file before classification so a
# partial pipeline cannot produce malformed JSON on failure.
# and reports messages_received:0. OpenWrt jsonfilter exits 1 when @.log[*]
# matches nothing, including a real empty array, so that rc alone is not a
# parse failure. A missing log key or malformed JSON must remain an error so
# rpcd does not record a fast healthy sample. Keep jsonfilter output in a
# secure temporary file before classification so a partial pipeline cannot
# produce malformed JSON on failure.
# This script runs from rpcd as root and reopens the path for classification.
# Match _fwlive_mktemp: only use a verified, sticky dump directory. The
# production rpcd caller passes /tmp; the explicit argument lets host tests
@@ -50,17 +53,40 @@ if [ ! -d "$FILTER_TMP_DIR" ] || [ -L "$FILTER_TMP_DIR" ] || ! [ -k "$FILTER_TMP
printf '%s' '{"log":[],"error":"filter_tempfile_failed"}'
exit 1
fi
_filter_tmp=$(mktemp "$FILTER_TMP_DIR/fwlive-filter.XXXXXX") || {
_filter_in=$(mktemp "$FILTER_TMP_DIR/fwlive-filter-in.XXXXXX") || {
printf '%s' '{"log":[],"error":"filter_tempfile_failed"}'
exit 1
}
trap 'rm -f "$_filter_tmp"' 0
trap 'rm -f "$_filter_tmp"; exit 129' 1
trap 'rm -f "$_filter_tmp"; exit 130' 2
trap 'rm -f "$_filter_tmp"; exit 131' 3
trap 'rm -f "$_filter_tmp"; exit 143' 15
if ! jsonfilter -e '@.log[*]' >"$_filter_tmp" 2>/dev/null; then
_filter_tmp=$(mktemp "$FILTER_TMP_DIR/fwlive-filter.XXXXXX") || {
rm -f "$_filter_in"
printf '%s' '{"log":[],"error":"filter_tempfile_failed"}'
exit 1
}
trap 'rm -f "$_filter_in" "$_filter_tmp"' 0
trap 'rm -f "$_filter_in" "$_filter_tmp"; exit 129' 1
trap 'rm -f "$_filter_in" "$_filter_tmp"; exit 130' 2
trap 'rm -f "$_filter_in" "$_filter_tmp"; exit 131' 3
trap 'rm -f "$_filter_in" "$_filter_tmp"; exit 143' 15
cat >"$_filter_in" || {
printf '%s' '{"log":[],"error":"filter_failed"}'
exit 1
}
_jf_rc=0
jsonfilter -e '@.log[*]' <"$_filter_in" >"$_filter_tmp" 2>/dev/null || _jf_rc=$?
if [ "$_jf_rc" -ne 0 ]; then
# Empty @.log array: extract rc=1 and empty stdout on OpenWrt jsonfilter.
# Confirm the member is an array before treating it as a healthy empty poll.
if [ ! -s "$_filter_tmp" ]; then
_log_type=$(jsonfilter -t '@.log' <"$_filter_in" 2>/dev/null) || _log_type=
if [ "$_log_type" = "array" ]; then
: >"$_filter_tmp"
else
printf '%s' '{"log":[],"error":"filter_failed"}'
exit 1
fi
else
printf '%s' '{"log":[],"error":"filter_failed"}'
exit 1
fi
fi
_fwlive_filter_json_reply <"$_filter_tmp"
+20 -6
View File
@@ -263,8 +263,8 @@ EOF
# One awk pass: prefix<TAB>comment. Last quoted log prefix / comment on
# the line, including \" units (same capture as the former sed).
# Raw tabs in prefix/comment become the two-character sequence \t so the
# separator tab is unique. Backslashes are not re-encoded.
# Raw tabs become \t and backslashes become \\ so the separator tab is
# unique. Walk bytes: BusyBox awk ≥1.37 gsub cannot emit those sequences.
nft_dump_fields() {
LC_ALL=C awk '
function last_at(s, needle, p, i, found) {
@@ -276,6 +276,22 @@ nft_dump_fields() {
}
return found
}
function tsv_escape(s, out, i, n, c) {
# BusyBox awk ≥1.37 treats gsub replacement "\\t" as a tab
# (and "\\\\" as one backslash). Walk bytes instead.
out = ""
n = length(s)
for (i = 1; i <= n; i++) {
c = substr(s, i, 1)
if (c == "\\")
out = out "\\\\"
else if (c == "\t")
out = out "\\t"
else
out = out c
}
return out
}
function quoted_at(s, needle, i, rest, out, c, n, esc, j) {
i = last_at(s, needle)
if (i == 0)
@@ -307,10 +323,8 @@ nft_dump_fields() {
comment = quoted_at($0, "comment \"")
if (prefix == "" && comment == "")
next
gsub(/\\/, "\\\\", prefix)
gsub(/\t/, "\\t", prefix)
gsub(/\\/, "\\\\", comment)
gsub(/\t/, "\\t", comment)
prefix = tsv_escape(prefix)
comment = tsv_escape(comment)
print prefix "\t" comment
}
' || return 1