From f23b14d15ef2d6a795e20e310b5159c0811006fa Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 18 Jan 2026 23:31:45 +0100 Subject: [PATCH] statd: backport finit patch to fix 'show services' Service descriptions or command arguments may contain special characters that need to be escaped in JSON strings. Example: { ... "command": "udhcpc -f -p /run/dhcp-client-wan.pid -t 3 -T 5 -A 30 -a1000 -S -R -o -O 1 -O 3 -O 6 -O 12 -O 15 -O 28 -O 42 -i wan -V "NanoPi R2S"", ... } Also, wrap add_services() in a try/except instead of testing for keys. Signed-off-by: Joachim Wiberg --- ...pe-special-characters-in-JSON-output.patch | 131 ++++++++++++++++++ src/statd/python/yanger/ietf_system.py | 27 ++-- 2 files changed, 144 insertions(+), 14 deletions(-) create mode 100644 package/finit/0003-initctl-escape-special-characters-in-JSON-output.patch diff --git a/package/finit/0003-initctl-escape-special-characters-in-JSON-output.patch b/package/finit/0003-initctl-escape-special-characters-in-JSON-output.patch new file mode 100644 index 00000000..e0ce598e --- /dev/null +++ b/package/finit/0003-initctl-escape-special-characters-in-JSON-output.patch @@ -0,0 +1,131 @@ +From c4463ec64f9732bd67b8cfd2b73adc10e50d1178 Mon Sep 17 00:00:00 2001 +From: Joachim Wiberg +Date: Sun, 18 Jan 2026 23:26:37 +0100 +Subject: [PATCH] initctl: escape special characters in JSON output +Organization: Wires + +Strings like command, description, and environment may contain characters +that need escaping for valid JSON, e.g., embedded quotes in command line +arguments like -V "NanoPi R2S". + +Add json_escape() helper to handle quotes, backslashes, and control chars. + +Signed-off-by: Joachim Wiberg +--- + src/initctl.c | 78 +++++++++++++++++++++++++++++++++++++++++++++------ + 1 file changed, 69 insertions(+), 9 deletions(-) + +diff --git a/src/initctl.c b/src/initctl.c +index 25c52cf..435e2da 100644 +--- a/src/initctl.c ++++ b/src/initctl.c +@@ -1036,6 +1036,62 @@ static int svc_compare(svc_t *svc, char *arg) + return 0; + } + ++/* ++ * Escape a string for safe JSON output. Handles quotes, backslashes, ++ * and control characters. Returns pointer to static buffer. ++ */ ++static char *json_escape(const char *str) ++{ ++ static char buf[1024]; ++ char *ptr = buf; ++ size_t left = sizeof(buf) - 1; ++ ++ if (!str) ++ return ""; ++ ++ while (*str && left > 1) { ++ char c = *str++; ++ ++ switch (c) { ++ case '"': ++ case '\\': ++ *ptr++ = '\\'; ++ *ptr++ = c; ++ left -= 2; ++ break; ++ case '\n': ++ *ptr++ = '\\'; ++ *ptr++ = 'n'; ++ left -= 2; ++ break; ++ case '\r': ++ *ptr++ = '\\'; ++ *ptr++ = 'r'; ++ left -= 2; ++ break; ++ case '\t': ++ *ptr++ = '\\'; ++ *ptr++ = 't'; ++ left -= 2; ++ break; ++ default: ++ if ((unsigned char)c < 0x20) { ++ /* Other control chars: use \uXXXX */ ++ int n = snprintf(ptr, left, "\\u%04x", (unsigned char)c); ++ ptr += n; ++ left -= n; ++ } else { ++ *ptr++ = c; ++ left--; ++ } ++ break; ++ } ++ } ++ *ptr = '\0'; ++ ++ return buf; ++} ++ + static int json_status_one(FILE *fp, svc_t *svc, char *indent, int prev) + { + long now = jiffies(); +@@ -1051,14 +1107,16 @@ static int json_status_one(FILE *fp, svc_t *svc, char *indent, int prev) + fprintf(fp, + "%s" + "%s{\n" +- "%s \"identity\": \"%s\",\n" +- "%s \"description\": \"%s\",\n" ++ "%s \"identity\": \"%s\",\n", ++ prev ? ",\n" : indent, prev ? indent : "", ++ indent, svc_ident(svc, NULL, 0)); ++ fprintf(fp, ++ "%s \"description\": \"%s\",\n", ++ indent, json_escape(svc->desc)); ++ fprintf(fp, + "%s \"type\": \"%s\",\n" + "%s \"forking\": %s,\n" + "%s \"status\": \"%s\",\n", +- prev ? ",\n" : indent, prev ? indent : "", +- indent, svc_ident(svc, NULL, 0), +- indent, svc->desc, + indent, svc_typestr(svc), + indent, svc->forking ? "true" : "false", + indent, svc_status(svc)); +@@ -1080,15 +1138,17 @@ static int json_status_one(FILE *fp, svc_t *svc, char *indent, int prev) + } + + fprintf(fp, +- "%s \"origin\": \"%s\",\n" ++ "%s \"origin\": \"%s\",\n", ++ indent, svc->file[0] ? svc->file : "built-in"); ++ svc_command(svc, buf, sizeof(buf), 0); ++ fprintf(fp, + "%s \"command\": \"%s\",\n", +- indent, svc->file[0] ? svc->file : "built-in", +- indent, svc_command(svc, buf, sizeof(buf), 0)); ++ indent, json_escape(buf)); + + svc_environ(svc, buf, sizeof(buf), 0); + if (buf[0]) + fprintf(fp, +- "%s \"environment\": \"%s\",\n", indent, buf); ++ "%s \"environment\": \"%s\",\n", indent, json_escape(buf)); + + svc_cond(svc, buf, sizeof(buf), 0); + if (buf[0]) +-- +2.43.0 + diff --git a/src/statd/python/yanger/ietf_system.py b/src/statd/python/yanger/ietf_system.py index 5cff7ed7..0ca57ef3 100644 --- a/src/statd/python/yanger/ietf_system.py +++ b/src/statd/python/yanger/ietf_system.py @@ -183,22 +183,21 @@ def add_services(out): services = [] for d in data: - if "pid" not in d or "status" not in d or "identity" not in d or "description" not in d: + try: + services.append({ + "pid": d["pid"], + "name": d["identity"], + "status": d["status"], + "description": d["description"], + "statistics": { + "memory-usage": str(d.get("memory", 0)), + "uptime": str(d.get("uptime", 0)), + "restart-count": int(d.get("restarts", 0)) + } + }) + except KeyError: continue - entry = { - "pid": d["pid"], - "name": d["identity"], - "status": d["status"], - "description": d["description"], - "statistics": { - "memory-usage": str(d.get("memory", 0)), - "uptime": str(d.get("uptime", 0)), - "restart-count": int(d.get("restarts", 0)) - } - } - services.append(entry) - insert(out, "infix-system:services", "service", services) def add_software(out):