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 <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2026-01-18 23:31:45 +01:00
parent d105178992
commit f23b14d15e
2 changed files with 144 additions and 14 deletions
@@ -0,0 +1,131 @@
From c4463ec64f9732bd67b8cfd2b73adc10e50d1178 Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
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 <troglobit@gmail.com>
---
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
+13 -14
View File
@@ -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):