Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions board/aarch64/friendlyarm-nanopi-r2s/Config.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ config BR2_PACKAGE_FRIENDLYARM_NANOPI_R2S
select SDCARD_AUX
select BR2_PACKAGE_INPUT_EVENT_DAEMON
select BR2_PACKAGE_LINUX_FIRMWARE_RTL_815X
select BR2_PACKAGE_INPUT_EVENT_DAEMON
help
FriendlyElec NanoPi R2S is a compact router board based on
the Rockchip RK3328 SoC with dual Gigabit Ethernet ports.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ define FRIENDLYARM_NANOPI_R2S_LINUX_CONFIG_FIXUPS
$(call KCONFIG_ENABLE_OPT,CONFIG_ROCKCHIP_IOMMU)
$(call KCONFIG_ENABLE_OPT,CONFIG_ROCKCHIP_PM_DOMAINS)
$(call KCONFIG_ENABLE_OPT,CONFIG_ROCKCHIP_IODOMAIN)
# TODO: for some reason this just locks up the device
# so we'll have to rely on the softdog :-(
# $(call KCONFIG_ENABLE_OPT,CONFIG_DW_WATCHDOG)

# PHY drivers
$(call KCONFIG_ENABLE_OPT,CONFIG_PHY_ROCKCHIP_EMMC)
Expand Down Expand Up @@ -51,6 +54,10 @@ define FRIENDLYARM_NANOPI_R2S_LINUX_CONFIG_FIXUPS
$(call KCONFIG_SET_OPT,CONFIG_NVMEM_ROCKCHIP_EFUSE,m)
$(call KCONFIG_SET_OPT,CONFIG_NVMEM_ROCKCHIP_OTP,m)

# Input layer
$(call KCONFIG_ENABLE_OPT,CONFIG_INPUT_MISC)
$(call KCONFIG_ENABLE_OPT,CONFIG_INPUT_RK805_PWRKEY)

# Network: STMMAC Ethernet (WAN port - RK3328 GMAC with RTL8211E PHY)
$(call KCONFIG_ENABLE_OPT,CONFIG_NET_VENDOR_STMICRO)
$(call KCONFIG_ENABLE_OPT,CONFIG_STMMAC_ETH)
Expand Down
Original file line number Diff line number Diff line change
@@ -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

61 changes: 24 additions & 37 deletions src/statd/python/cli_pretty/cli_pretty.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ class PadDhcpServer:

class PadSensor:
name = 30
value = 20
value = 22
status = 10

@classmethod
Expand All @@ -186,14 +186,6 @@ class PadLldp:
port_id = 20


class PadDiskUsage:
mount = 18
size = 12
used = 12
avail = 12
percent = 6


def format_memory_bytes(bytes_val):
"""Convert bytes to human-readable format"""
if bytes_val == 0:
Expand Down Expand Up @@ -221,11 +213,6 @@ def format_uptime_seconds(seconds):
else:
return f"{seconds // 86400}d"

@classmethod
def table_width(cls):
"""Total width of disk usage table"""
return cls.mount + cls.size + cls.used + cls.avail + cls.percent


class Column:
"""Column definition for SimpleTable"""
Expand Down Expand Up @@ -2134,7 +2121,7 @@ def show_hardware(json):
sensors = [c for c in components if c.get("class") == "iana-hardware:sensor"]
wifi_radios = [c for c in components if c.get("class") == "infix-hardware:wifi"]

width = max(PadSensor.table_width(), 100)
width = max(PadSensor.table_width(), 62)

# Display full-width inverted heading
print(Decore.invert(f"{'HARDWARE COMPONENTS':<{width}}"))
Expand All @@ -2158,12 +2145,12 @@ def show_hardware(json):
Decore.title("WiFi radios", width)

radios_table = SimpleTable([
Column('NAME'),
Column('MANUFACTURER'),
Column('NAME', flexible=True),
Column('MANUFACTURER', flexible=True),
Column('BANDS', 'right'),
Column('STANDARDS', 'right'),
Column('MAX AP', 'right')
])
], min_width=width)

for component in wifi_radios:
phy = component.get("name", "")
Expand Down Expand Up @@ -2208,10 +2195,10 @@ def show_hardware(json):
Decore.title("USB Ports", width)

usb_table = SimpleTable([
Column('NAME'),
Column('NAME', flexible=True),
Column('STATE'),
Column('OPER')
])
], min_width=width)

for component in usb_ports:
port = USBport(component)
Expand Down Expand Up @@ -2939,7 +2926,7 @@ def show_system(json):
except (ValueError, KeyError):
pass

width = PadDiskUsage.table_width()
width = 62
print(Decore.invert(f"{'SYSTEM INFORMATION':<{width}}"))
print(f"{'OS Name':<20}: {platform.get('os-name', 'Unknown')}")
print(f"{'OS Version':<20}: {platform.get('os-version', 'Unknown')}")
Expand Down Expand Up @@ -3022,23 +3009,23 @@ def show_system(json):
disk_filtered = [d for d in disk if d.get("mount") != "/"]
if disk_filtered:
Decore.title("Disk Usage", width)
hdr = (f"{'MOUNTPOINT':<{PadDiskUsage.mount}}"
f"{'SIZE':>{PadDiskUsage.size}}"
f"{'USED':>{PadDiskUsage.used}}"
f"{'AVAIL':>{PadDiskUsage.avail}}"
f"{'USE%':>{PadDiskUsage.percent}}")
print(Decore.invert(hdr))
disk_table = SimpleTable([
Column('MOUNTPOINT', flexible=True),
Column('SIZE', 'right'),
Column('USED', 'right'),
Column('AVAIL', 'right'),
Column('USE%', 'right')
], min_width=width)

for d in disk_filtered:
mount = d.get("mount", "?")
size = d.get("size", "?")
used = d.get("used", "?")
avail = d.get("available", "?")
percent = d.get("percent", "?")
print(f"{mount:<{PadDiskUsage.mount}}"
f"{size:>{PadDiskUsage.size}}"
f"{used:>{PadDiskUsage.used}}"
f"{avail:>{PadDiskUsage.avail}}"
f"{percent:>{PadDiskUsage.percent}}")
disk_table.row(
d.get("mount", "?"),
d.get("size", "?"),
d.get("used", "?"),
d.get("available", "?"),
d.get("percent", "?")
)
disk_table.print()


def show_dhcp_server(json, stats):
Expand Down
27 changes: 13 additions & 14 deletions src/statd/python/yanger/ietf_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
12 changes: 6 additions & 6 deletions test/case/statd/system/cli/show-hardware
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
HARDWARE COMPONENTS 
────────────────────────────────────────────────────────────────────────────────────────────────────
HARDWARE COMPONENTS 
──────────────────────────────────────────────────────────────
Board Information
Model : Standard PC (i440FX + PIIX, 1996)
Manufacturer : QEMU
Base MAC Address : 00:a0:85:00:03:00
────────────────────────────────────────────────────────────────────────────────────────────────────
──────────────────────────────────────────────────────────────
USB Ports
NAME STATE OPER 
USB1 locked enabled
USB2 locked enabled
NAME STATE OPER 
USB1 locked enabled
USB2 locked enabled