Files
MeowOS-For-R3P2/patch_v2/meow_netmgrd/meow_netmgrd.c
T
MeowOS 79a0fed336 meow_netmgrd 期1:网络管理服务骨架+接口统一层+CLI+IPC+日志框架
NETMGR_DESIGN.md 期1范围(→ M6.0)落地:
- meow_netmgrd 守护:svcd 托管 trigger=boot/restart=on-fail,进程内 HibyOS 只读降级
  (/tmp/meow_boot_mode != meow → 读可查/写拒+日志,热切换)。
- 接口统一层(§3):iface 枚举(/sys/class/net+/proc/net/dev,lo/wlan0/usb-eth 热插拔节拍检测);
  WiFi 状态/扫描/网络列表/连接/断开(wpa_cli popen 有界收割防挂死,257 教训);IP/路由/DNS 只读
  (ip 优先 /proc/net/route+resolv.conf);BT 状态/开关/已配对(hciconfig/bluetoothctl);rfkill 读写;udhcpc 续租/释放(MeowOS)。
- IPC 0x00E0-0x00E7(STATUS/IFACE/WIFI_CTL/BT_CTL+各自 RESULT):读级任意、写级仅系统身份(SO_PEERCRED
  同 svc_power),0xE8-0xEF 期2/3 预留(仅注释)。
- CLI 双形态 meow_net(status/iface/wifi/bt/rfkill/help):IPC 薄壳,help 本地必可用。
- 日志框架:TF MeowOS/logs/net_<bootts>.log(无卡降级 /tmp),接口状态变化/写操作必记。
- 单线程 poll、静态表、热路径禁 malloc;mipsel 静态(0 warning),宿主+qemu-mipsel 全过,
  真机只读验证 status/wifi scan/bt status 正确。

meow_ipc.h 仅追加 0x00E0-0x00E7 段(不改既有行)。
2026-09-01 19:55:26 +08:00

1757 lines
66 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#define _GNU_SOURCE /* struct ucred / SO_PEERCRED、asprintf 等 GNU/musl 扩展 */
/* meow_netmgrd.c — MeowOS 网络管理专项服务(devdocs/plan/NETMGR_DESIGN.md,期 1 骨架)。
*
* 定位(与 powermgr/audiod 同级的系统服务):接口统一层——WiFi/BT/USB-eth/rfkill/IP/路由/DNS
* 的查询与控制收口为一个系统权威;系统设置、MeowPanel、未来系统组件一律走它的 IPC,不再各自直调
* wpa_cli/hciconfig(设计 §1/§3)。期 1 范围(设计 §13 表,→ M6.0):骨架 + 接口统一层 +
* CLI(status/iface/wifi/bt/rfkill) + IPC(E0-E7) + 日志框架 + svcd conf + HibyOS 只读降级。
*
* 单一二进制双形态(同 powermgr/svc_power):
* - svcd 空参拉起(板载 services.d/meow_netmgrd.conf,trigger=boot/restart=on-fail)→
* IPC 服务常驻:bind /usr/data/meow_net.sock(--sock/MEOW_NET_SOCK 覆盖),收
* MSG_HELLO→MSG_ACK、MSG_NET_STATUS_REQ/IFACE_REQ/WIFI_CTL/BT_CTL。
* - `meow_net <cmd>`(argv[0]=meow_net 双形态)或 `meow_netmgrd <cmd>` → 一次 CLI(IPC 薄壳)。
* CLI 是 IPC 客户端:查询经守护,写操作经守护鉴权;守护不可达 → 报错(help 除外,help 本地必可用)。
*
* HibyOS 只读降级(用户定案 2026-09-01):进程内读 /tmp/meow_boot_mode,"meow"=全功能;
* 否则(hiby/缺失)=只读——读级查询放行,一切写操作(wifi/bt on-off 等)拒 + 记日志。
* (与 audiod 的 exit(0) 门控不同:本服务读侧无害故常驻只读,每采样拍重判可热切换,同 powermgr。)
*
* 内核事实(NETWORK_HW_FACTS.md,期 0 实测):无 iptables/cgroup/conntrack;内存 available≈27MB →
* 本服务 RSS 预算 ≤1MB(单线程 poll、静态表、热路径禁 malloc;popen 有界收割防 wedge,257 教训)。
*
* 日志(§8):TF `MeowOS/logs/net_<bootts>.log`(无卡降级 /tmp),每次开机新文件(复用 /tmp/meow_bootts
* 共享 bootts,一次开机一份);接口状态变化/写操作必记。行格式 `bootts lvl tag kv...`。
*
* 编译(mipsel 静态):build_netmgrd.sh;宿主:build_netmgrd.sh host。
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <poll.h>
#include <time.h>
#include <ctype.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "meow_ipc.h" /* 帧格式/消息号 + MSG_NET_*(0x00E0-0x00E7) + m_net_*(号段 0x00E0-0x00EF) */
/* ---------------- 路径(全部可经 env 覆盖,宿主/qemu 桩测试用) ---------------- */
static char g_net_dir[128] = "/sys/class/net"; /* MEOW_NET_DIR */
static char g_netdev[160] = "/proc/net/dev"; /* MEOW_NETDEV */
static char g_net_route[160] = "/proc/net/route"; /* MEOW_NET_ROUTE */
static char g_fibtrie[160] = "/proc/net/fib_trie"; /* MEOW_FIBTRIE(无 ip 工具兜底) */
static char g_resolv[160] = "/etc/resolv.conf"; /* MEOW_RESOLV */
static char g_rfkill_dir[128] = "/sys/class/rfkill"; /* MEOW_RFKILL_DIR */
static char g_bt_dir[128] = "/sys/class/bluetooth"; /* MEOW_BT_DIR */
static char g_boot_mode[160] = "/tmp/meow_boot_mode"; /* MEOW_BOOT_MODE */
static char g_log_dir[192] = "/usr/data/mnt/sd_0/MeowOS/logs"; /* MEOW_LOG_DIR(TF 卡日志根) */
static char g_bootts_cache[160]= "/tmp/meow_bootts"; /* MEOW_BOOTTS_CACHE */
static const char *g_sock = "/usr/data/meow_net.sock"; /* MEOW_NET_SOCK/--sock */
/* 后端命令路径(宿主测试用桩脚本覆盖;默认走设备 PATH 的真工具) */
static const char *g_wpa_cli = "wpa_cli"; /* MEOW_WPA_CLI */
static const char *g_hciconfig = "hciconfig"; /* MEOW_HCICONFIG */
static const char *g_bluetoothctl= "bluetoothctl"; /* MEOW_BLUETOOTHCTL */
static const char *g_ip = "ip"; /* MEOW_IP */
static const char *g_wifi_if = "wlan0"; /* MEOW_WIFI_IF */
#define NET_POLL_SEC_DEF 2 /* 接口轮询/状态节拍(秒;MEOW_NET_POLL_SEC 覆盖,测试用) */
#define RUNCMD_TMO_MS 1500 /* 后端命令(wpa_cli/hciconfig/etc)有界收割限时(ms) */
#define IFACE_MAX 16
#define RFKILL_MAX 8
#define DNS_MAX 8
static int g_poll_sec = NET_POLL_SEC_DEF;
static long mono_now(void)
{
struct timespec tp;
clock_gettime(CLOCK_MONOTONIC, &tp);
return tp.tv_sec;
}
/* ---------------- 极简日志(stderr;svcd supervisor 捕获) ---------------- */
static void svc_log(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "[meow_netmgrd] ");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
}
/* ---------------- 文件小工具(同 svc_power) ---------------- */
static int read_file_txt(const char *path, char *buf, int cap)
{
int fd = open(path, O_RDONLY);
if (fd < 0) return -1;
int n = 0;
while (n < cap - 1) {
ssize_t r = read(fd, buf + n, (size_t)(cap - 1 - n));
if (r <= 0) break;
n += (int)r;
}
close(fd);
buf[n] = 0;
while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == '\r' || buf[n - 1] == ' ')) buf[--n] = 0;
return n;
}
static int read_file_int_def(const char *path, int def)
{
char buf[32];
if (read_file_txt(path, buf, sizeof buf) <= 0) return def;
return atoi(buf);
}
/* ---------------- 日志框架(§8:每次开机新文件,TF 优先降级 /tmp) ---------------- */
static int g_logfd = -1;
static char g_logfile[192];
/* bootts 复用(与 launcher/audiod 共用 /tmp/meow_bootts 缓存,一次开机一份日志) */
static void get_bootts(char *out, size_t n)
{
int fd = open(g_bootts_cache, O_RDONLY);
if (fd >= 0) {
int r = read(fd, out, (int)n - 1);
close(fd);
if (r > 0) { out[r] = 0; return; }
}
time_t now = time(NULL), up = 0;
fd = open("/proc/uptime", O_RDONLY);
if (fd >= 0) {
char buf[64];
int r = read(fd, buf, sizeof buf - 1);
close(fd);
if (r > 0) { buf[r] = 0; char *p = strchr(buf, ' '); if (p) *p = 0; up = (time_t)atof(buf); }
}
struct tm tm;
localtime_r(&now - up, &tm);
strftime(out, n, "meow_%Y%m%d_%H%M%S", &tm);
fd = open(g_bootts_cache, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd >= 0) { (void)write(fd, out, strlen(out)); close(fd); }
}
static void log_open(void)
{
char ts[64];
get_bootts(ts, sizeof ts);
char path[260];
int use_tf = 0;
if (access(g_log_dir, W_OK) == 0) {
snprintf(path, sizeof path, "%s/net_%s.log", g_log_dir, ts);
use_tf = 1;
} else {
snprintf(path, sizeof path, "/tmp/net_%s.log", ts);
}
snprintf(g_logfile, sizeof g_logfile, "%s", path);
g_logfd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0644);
svc_log("log: %s (%s)", g_logfile, use_tf ? "TF" : "/tmp fallback");
}
/* 日志行:`bootts lvl tag kv...`;stderr 与文件双写(svcd 捕获 stderr + 文件归档)。 */
static void net_log(const char *lvl, const char *tag, const char *fmt, ...)
{
char msg[512];
va_list ap;
va_start(ap, fmt);
vsnprintf(msg, sizeof msg, fmt, ap);
va_end(ap);
char bootts[64];
get_bootts(bootts, sizeof bootts);
if (g_logfd >= 0) {
char line[600];
int n = snprintf(line, sizeof line, "%s %s %s %s\n", bootts, lvl, tag, msg);
if (n > 0 && n < (int)sizeof line) (void)write(g_logfd, line, (size_t)n);
}
fprintf(stderr, "[net] %s %s %s\n", lvl, tag, msg);
}
/* ---------------- 有界 popen 收割(257 教训:命令 wedge 时无条件 waitpid 会拖死调用线程) ----------------
* 非阻塞读子进程 stdout(poll 限时);读完后 WNOHANG 优雅等 300ms,不死 SIGKILL 再等 300ms;
* 仍不死(D 态)放弃收割(泄漏一子进程换线程不卡死,已记日志)。返回读到的字节数(-1=起不来)。
* cmd 为完整 shell 行;buf 由调用方提供(静态/栈),不 malloc——热路径禁 malloc。 */
static void reap_bounded(pid_t pid, const char *cmd)
{
for (int i = 0; i < 30; i++) {
if (waitpid(pid, NULL, WNOHANG) == pid) return;
struct timespec ts = { 0, 10 * 1000000L };
nanosleep(&ts, NULL);
}
svc_log("run_cmd: child %ld not exit in 300ms, SIGKILL (%s)", (long)pid, cmd);
kill(pid, SIGKILL);
for (int i = 0; i < 30; i++) {
if (waitpid(pid, NULL, WNOHANG) == pid) return;
struct timespec ts = { 0, 10 * 1000000L };
nanosleep(&ts, NULL);
}
svc_log("run_cmd: child %ld unkillable (kernel D-state?) — abandon reap (%s)", (long)pid, cmd);
}
static int run_cmd(char *buf, int cap, int tmo_ms, const char *cmd)
{
int p[2];
if (pipe(p) < 0) return -1;
pid_t pid = fork();
if (pid < 0) { close(p[0]); close(p[1]); return -1; }
if (pid == 0) {
dup2(p[1], 1);
int dn = open("/dev/null", O_WRONLY);
if (dn >= 0) { dup2(dn, 2); close(dn); }
close(p[0]); close(p[1]);
execl("/bin/sh", "sh", "-c", cmd, (char *)0);
_exit(127);
}
close(p[1]);
fcntl(p[0], F_SETFL, fcntl(p[0], F_GETFL, 0) | O_NONBLOCK);
int off = 0;
int remain = tmo_ms;
while (remain > 0 && off < cap - 1) {
struct pollfd pf = { p[0], POLLIN, 0 };
int r = poll(&pf, 1, remain);
if (r < 0) { if (errno == EINTR) continue; break; }
if (r == 0) break;
ssize_t rd = read(p[0], buf + off, (size_t)(cap - 1 - off));
if (rd == 0) break;
if (rd < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) continue; break; }
off += (int)rd;
}
close(p[0]);
buf[off] = 0;
reap_bounded(pid, cmd);
return off;
}
/* ---------------- JSON 构建(vsnprintf 助手法,截断分析免疫;同 powermgr) ---------------- */
typedef struct { char *buf; size_t cap, off; } jw_t;
static void jadd(jw_t *w, const char *fmt, ...)
{
if (w->off >= w->cap - 1) return;
va_list ap;
va_start(ap, fmt);
int n = vsnprintf(w->buf + w->off, w->cap - w->off, fmt, ap);
va_end(ap);
if (n > 0) {
w->off += (size_t)n;
if (w->off >= w->cap) w->off = w->cap - 1;
}
}
/* 带引号的字符串值(转义 `"`/`\`/换行)。空串 → ""(不当 null,调用方自决)。 */
static void jadd_q(jw_t *w, const char *key, const char *val)
{
jadd(w, "\"%s\":\"", key);
for (const char *p = val; *p && w->off < w->cap - 3; p++) {
if (*p == '"' || *p == '\\') jadd(w, "\\%c", *p);
else if (*p == '\n') jadd(w, "\\n");
else if (*p == '\t') jadd(w, "\\t");
else w->buf[w->off++] = *p;
}
jadd(w, "\",");
}
static void jadd_int_or_null(jw_t *w, const char *key, int v)
{
if (v < 0) jadd(w, "\"%s\":null,", key);
else jadd(w, "\"%s\":%d,", key, v);
}
static void jadd_str_or_null(jw_t *w, const char *key, const char *s)
{
if (!s || !s[0]) jadd(w, "\"%s\":null,", key);
else jadd_q(w, key, s);
}
/* ---------------- 动作执行结果容器 ---------------- */
typedef struct {
int code;
char text[MEOW_IPC_MAX - 8];
} ctl_result_t;
static void ctl_reset(ctl_result_t *r, int code) { r->code = code; r->text[0] = 0; }
static void ctl_sets(ctl_result_t *r, int code, const char *s)
{
r->code = code;
snprintf(r->text, sizeof r->text, "%s", s);
}
/* ---------------- 接口统一层:模型(设计 §3 统一模型) ---------------- */
typedef struct {
char name[32];
char kind[8]; /* "lo" | "wifi" | "usb-eth" */
int up; /* 1=up 0=down */
char ip[40]; /* ""=null */
char gw[40]; /* ""=null */
long rx, tx; /* 累计字节(/proc/net/dev) */
} iface_t;
static iface_t g_ifaces[IFACE_MAX];
static int g_iface_n = 0;
typedef struct {
int connected; /* 0/1 */
char state[16]; /* wpa_state 值;""=unknown */
char ssid[64];
char ip[40];
int rssi; /* 信号强度 dBm(可为负);has_rssi=0 时无效 */
int has_rssi; /* wpa_cli 是否给出 signal= */
} wifi_t;
typedef struct {
int available;
int powered;
char address[32];
int paired_n; /* -1=null */
} bt_t;
typedef struct {
char type[16]; /* wlan/bluetooth/... */
int soft; /* 0/1/-1 */
int hard; /* 0/1/-1 */
} rfkill_t;
static rfkill_t g_rfkill[RFKILL_MAX];
static int boot_mode_is_meow(void)
{
char buf[16] = "";
if (read_file_txt(g_boot_mode, buf, sizeof buf) <= 0) return 0; /* 缺失=只读等落地 */
return strcmp(buf, "meow") == 0;
}
/* kind 判定:0=lo 1=wifi 2=usb-eth(热插拔出现即记录)。 */
static int iface_parse_kind(const char *name)
{
if (strcmp(name, "lo") == 0) return 0;
if (strcmp(name, g_wifi_if) == 0) return 1;
return 2;
}
static const char *kind_str(int k)
{
return k == 0 ? "lo" : (k == 1 ? "wifi" : "usb-eth");
}
/* /proc/net/dev 单接口行 → rx(列1)/tx(列9) 累计字节;返回 0=找到。 */
static int netdev_iface(const char *ifname, long *rx, long *tx)
{
FILE *f = fopen(g_netdev, "r");
if (!f) return -1;
char line[256];
int found = -1;
while (fgets(line, sizeof line, f)) {
char *c = strchr(line, ':');
if (!c) continue;
*c = 0;
char *n = line;
while (*n == ' ' || *n == '\t') n++;
if (strcmp(n, ifname) != 0) continue;
char *save = NULL;
char *tk = strtok_r(c + 1, " \t", &save); /* 列1 = rx bytes */
long vr = tk ? strtol(tk, NULL, 10) : 0;
for (int i = 0; i < 8; i++) tk = strtok_r(NULL, " \t", &save); /* 推进到列9 */
long vt = tk ? strtol(tk, NULL, 10) : 0;
if (rx) *rx = vr;
if (tx) *tx = vt;
found = 0;
break;
}
fclose(f);
return found;
}
/* 全局 /proc/net/dev RX/TX 累计(所有接口求和)。 */
static void netdev_totals(long *rx, long *tx)
{
long trx = 0, ttx = 0;
FILE *f = fopen(g_netdev, "r");
if (f) {
char line[256];
while (fgets(line, sizeof line, f)) {
char *c = strchr(line, ':');
if (!c) continue;
char *save = NULL;
char *tk = strtok_r(c + 1, " \t", &save);
long vr = 0;
if (tk) {
vr = strtol(tk, NULL, 10);
for (int i = 0; i < 8; i++) tk = strtok_r(NULL, " \t", &save);
if (tk) ttx += strtol(tk, NULL, 10);
}
trx += vr;
}
fclose(f);
}
if (rx) *rx = trx;
if (tx) *tx = ttx;
}
/* iface IP:优先 `ip -o -4 addr show dev <if>`;ip 缺失/无输出 → fib_trie 兜底(作用于主口)。
* 内核不提供 per-iface 主机 IP 的 /proc 文件,fib_trie 兜底无法映射接口——仅作用于 wifi 主口。 */
static int ip_parse_addr(char *out, size_t cap, const char *buf)
{
const char *p = buf;
while (*p) {
while (*p == ' ' || *p == '\t' || *p == '\n') p++;
if (!*p) break;
const char *tk = p;
while (*p && *p != ' ' && *p != '\t' && *p != '\n') p++;
size_t tl = (size_t)(p - tk);
const char *dot = memchr(tk, '.', tl);
if (!dot) continue;
const char *sl = memchr(tk, '/', tl);
if (!sl) continue;
size_t len = (size_t)(sl - tk);
if (len >= cap) len = cap - 1;
memcpy(out, tk, len); out[len] = 0;
return 0;
}
return -1;
}
static int fib_trie_first_ip(char *out, size_t cap)
{
FILE *f = fopen(g_fibtrie, "r");
if (!f) return -1;
char line[160];
char last_local[48] = "";
while (fgets(line, sizeof line, f)) {
char *p = line;
while (*p == ' ' || *p == '\t') p++;
if (!isdigit((unsigned char)*p)) continue;
char ip[48]; int i = 0;
while (p[i] && p[i] != ' ' && p[i] != '\n' && i < 47) { ip[i] = p[i]; i++; }
ip[i] = 0;
if (strchr(ip, '.')) snprintf(last_local, sizeof last_local, "%s", ip);
}
fclose(f);
if (last_local[0]) { snprintf(out, cap, "%s", last_local); return 0; }
return -1;
}
static void iface_ip(const char *ifname, int want_fallback, char *out, size_t cap)
{
out[0] = 0;
char cmd[192];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s -o -4 addr show dev %s 2>/dev/null",
g_ip, ifname);
char buf[256];
if (run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd) > 0 && ip_parse_addr(out, cap, buf) == 0) return;
if (want_fallback) (void)fib_trie_first_ip(out, cap);
}
/* /proc/net/route → 该接口网关(RTF_GATEWAY,优取默认路由)。 */
static void iface_gw(const char *ifname, char *out, size_t cap)
{
out[0] = 0;
FILE *f = fopen(g_net_route, "r");
if (!f) return;
char line[256];
char best[40] = "";
while (fgets(line, sizeof line, f)) {
char ifc[32]; unsigned long dest = 0, gw = 0, flags = 0;
int n = sscanf(line, "%31s %lx %lx %lx", ifc, &dest, &gw, &flags);
if (n < 4) continue;
if (strcmp(ifc, ifname) != 0) continue;
if (!(flags & 0x2)) continue; /* RTF_GATEWAY */
unsigned char b0 = (unsigned char)(gw & 0xff), b1 = (unsigned char)((gw >> 8) & 0xff);
unsigned char b2 = (unsigned char)((gw >> 16) & 0xff), b3 = (unsigned char)((gw >> 24) & 0xff);
snprintf(best, sizeof best, "%u.%u.%u.%u", b0, b1, b2, b3);
if (dest == 0) break; /* 默认路由:最佳 */
}
fclose(f);
if (best[0]) snprintf(out, cap, "%s", best);
}
/* resolv.conf → nameserver 列表(≤DNS_MAX)。 */
static int dns_read(char out[DNS_MAX][40], int *n)
{
FILE *f = fopen(g_resolv, "r");
*n = 0;
if (!f) return -1;
char line[160];
while (fgets(line, sizeof line, f) && *n < DNS_MAX) {
char *p = line;
while (*p == ' ' || *p == '\t') p++;
if (strncmp(p, "nameserver", 10) != 0) continue;
p += 10;
while (*p == ' ' || *p == '\t') p++;
char ip[40]; int i = 0;
while (*p && *p != ' ' && *p != '\t' && *p != '\n' && i < 39) ip[i++] = *p++;
ip[i] = 0;
if (strchr(ip, '.')) { snprintf(out[*n], 40, "%s", ip); (*n)++; }
}
fclose(f);
return *n > 0 ? 0 : -1;
}
/* 扫描 /sys/class/net。detail=1 补 ip/gw(触发 ip popen);detail=0 仅 name/kind/up/rx/tx(节拍用,无 popen)。 */
static void enum_ifaces(int detail)
{
DIR *d = opendir(g_net_dir);
g_iface_n = 0;
if (d) {
struct dirent *de;
while ((de = readdir(d)) && g_iface_n < IFACE_MAX) {
if (de->d_name[0] == '.') continue;
iface_t *f = &g_ifaces[g_iface_n];
memset(f, 0, sizeof *f);
snprintf(f->name, sizeof f->name, "%s", de->d_name);
int ki = iface_parse_kind(de->d_name);
snprintf(f->kind, sizeof f->kind, "%s", kind_str(ki));
char p[192];
snprintf(p, sizeof p, "%s/%s/operstate", g_net_dir, de->d_name);
char os[16] = "";
read_file_txt(p, os, sizeof os);
/* lo 的 operstate 常为 "unknown"(真机实测),但回环恒 up。 */
f->up = (ki == 0 || strcmp(os, "up") == 0) ? 1 : 0;
long rx = -1, tx = -1;
netdev_iface(de->d_name, &rx, &tx);
f->rx = rx >= 0 ? rx : 0;
f->tx = tx >= 0 ? tx : 0;
if (detail) {
iface_ip(f->name, ki == 1, f->ip, sizeof f->ip); /* fib_trie 兜底仅 wifi 主口 */
iface_gw(f->name, f->gw, sizeof f->gw);
}
g_iface_n++;
}
closedir(d);
}
/* 排序稳定:lo 首、wifi 次、usb-eth 末(冒泡,量小)。 */
for (int i = 0; i < g_iface_n; i++)
for (int j = i + 1; j < g_iface_n; j++) {
if (iface_parse_kind(g_ifaces[i].name) > iface_parse_kind(g_ifaces[j].name)) {
iface_t t = g_ifaces[i]; g_ifaces[i] = g_ifaces[j]; g_ifaces[j] = t;
}
}
}
/* ---------------- WiFi:wpa_cli 包装(popen 有界) ---------------- */
static void wifi_status(wifi_t *w)
{
memset(w, 0, sizeof *w);
w->rssi = -1;
char cmd[160];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s status 2>/dev/null", g_wpa_cli);
char buf[512];
if (run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd) <= 0) return;
char *ln = buf;
while (ln && *ln) {
char *nl = strchr(ln, '\n');
if (nl) *nl = 0;
if (!strncmp(ln, "wpa_state=", 10)) {
snprintf(w->state, sizeof w->state, "%s", ln + 10);
if (!strcmp(ln + 10, "COMPLETED")) w->connected = 1;
} else if (!strncmp(ln, "ssid=", 5)) {
snprintf(w->ssid, sizeof w->ssid, "%s", ln + 5);
} else if (!strncmp(ln, "ip_address=", 11)) {
snprintf(w->ip, sizeof w->ip, "%s", ln + 11);
} else if (!strncmp(ln, "signal=", 7)) {
w->rssi = atoi(ln + 7);
w->has_rssi = 1;
}
if (!nl) break;
ln = nl + 1;
}
}
/* ---------------- 蓝牙:hciconfig/bluetoothctl 包装 ---------------- */
static void bt_status(bt_t *b)
{
memset(b, 0, sizeof *b);
b->paired_n = -1;
char p[192];
snprintf(p, sizeof p, "%s/hci0", g_bt_dir);
if (access(p, F_OK) == 0) b->available = 1;
if (!b->available) return;
/* powered:/sys/.../power(on/off)优先,回退 hciconfig 行首 UP/DOWN */
snprintf(p, sizeof p, "%s/hci0/power", g_bt_dir);
char pw[8] = "";
if (read_file_txt(p, pw, sizeof pw) > 0 && pw[0]) {
b->powered = !strcmp(pw, "on");
} else {
char cmd[160];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s hci0 2>/dev/null", g_hciconfig);
char buf[256];
if (run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd) > 0) {
char *ln = buf;
while (ln && *ln) {
char *nl = strchr(ln, '\n');
if (nl) *nl = 0;
char *q = ln;
while (*q == ' ' || *q == '\t') q++;
if (!strncmp(q, "UP", 2)) { b->powered = 1; break; }
if (!strncmp(q, "DOWN", 4)) { b->powered = 0; break; }
if (!nl) break;
ln = nl + 1;
}
}
}
/* address:/sys/.../address 优先,回退 hciconfig "BD Address:" */
snprintf(p, sizeof p, "%s/hci0/address", g_bt_dir);
read_file_txt(p, b->address, sizeof b->address);
if (!b->address[0]) {
char cmd[160];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s hci0 2>/dev/null", g_hciconfig);
char buf[256];
if (run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd) > 0) {
char *q = strstr(buf, "BD Address:");
if (q) {
q += 11;
while (*q == ' ') q++;
int i = 0;
while (*q && *q != ' ' && *q != '\n' && i < (int)sizeof b->address - 1) b->address[i++] = *q++;
b->address[i] = 0;
}
}
}
/* paired 数:bluetoothctl paired(BlueZ 5.54);失败/空 → -1。 */
char cmd[160];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s paired 2>/dev/null", g_bluetoothctl);
char buf[512];
if (run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd) > 0) {
int n = 0;
char *ln = buf;
while (ln && *ln) {
char *nl = strchr(ln, '\n');
if (nl) *nl = 0;
char *q = ln;
while (*q == ' ' || *q == '\t') q++;
if (!strncmp(q, "Device", 6)) n++;
if (!nl) break;
ln = nl + 1;
}
b->paired_n = n;
}
}
/* ---------------- rfkill:统一软开关 ---------------- */
static void rfkill_list(void)
{
int n = 0;
DIR *d = opendir(g_rfkill_dir);
if (d) {
struct dirent *de;
while ((de = readdir(d)) && n < RFKILL_MAX) {
if (strncmp(de->d_name, "rfkill", 6) != 0) continue;
char p[448];
rfkill_t *r = &g_rfkill[n];
memset(r, 0, sizeof *r);
snprintf(p, sizeof p, "%s/%s/type", g_rfkill_dir, de->d_name);
read_file_txt(p, r->type, sizeof r->type);
snprintf(p, sizeof p, "%s/%s/soft", g_rfkill_dir, de->d_name);
r->soft = read_file_int_def(p, -1);
snprintf(p, sizeof p, "%s/%s/hard", g_rfkill_dir, de->d_name);
r->hard = read_file_int_def(p, -1);
n++;
}
closedir(d);
}
for (int i = n; i < RFKILL_MAX; i++) memset(&g_rfkill[i], 0, sizeof(rfkill_t));
}
/* 找某类型 rfkill 设备并写 soft=block(1)/unblock(0)。返回 0=ok;-1=无该类型/写失败。 */
static int rfkill_set_block(const char *type, int block)
{
char norm[16] = "";
if (!strcmp(type, "wifi")) snprintf(norm, sizeof norm, "%s", "wlan");
else if (!strcmp(type, "bt")) snprintf(norm, sizeof norm, "%s", "bluetooth");
else snprintf(norm, sizeof norm, "%s", type);
DIR *d = opendir(g_rfkill_dir);
int rc = -1;
if (d) {
struct dirent *de;
while ((de = readdir(d))) {
if (strncmp(de->d_name, "rfkill", 6) != 0) continue;
char p[448], t[16] = "";
snprintf(p, sizeof p, "%s/%s/type", g_rfkill_dir, de->d_name);
if (read_file_txt(p, t, sizeof t) <= 0 || strcmp(t, norm) != 0) continue;
snprintf(p, sizeof p, "%s/%s/soft", g_rfkill_dir, de->d_name);
int fd = open(p, O_WRONLY);
if (fd < 0) continue;
char one = block ? '1' : '0';
if (write(fd, &one, 1) == 1) rc = 0;
close(fd);
break;
}
closedir(d);
}
return rc;
}
/* ---------------- udhcpc:续租/释放(SIGUSR1/SIGUSR2,不改启动路径) ---------------- */
static int udhcpc_ctl(const char *op, const char *ifname)
{
char pidfile[160];
const char *iff = (ifname && ifname[0]) ? ifname : g_wifi_if;
snprintf(pidfile, sizeof pidfile, "/var/run/udhcpc.%s.pid", iff);
char buf[32];
if (read_file_txt(pidfile, buf, sizeof buf) <= 0) return -1;
int pid = atoi(buf);
if (pid <= 0) return -1;
int sig = (strcmp(op, "release") == 0) ? SIGUSR2 : SIGUSR1;
return kill(pid, sig) == 0 ? 0 : -1;
}
/* ---------------- IPC 帧读写(同 svc_power 约定,全小端) ---------------- */
static int x_read_full(int fd, uint8_t *buf, size_t n)
{
size_t off = 0;
while (off < n) {
ssize_t r = read(fd, buf + off, n - off);
if (r <= 0) return -1;
off += (size_t)r;
}
return 0;
}
static int x_write_full(int fd, const uint8_t *buf, size_t n)
{
size_t off = 0;
while (off < n) {
ssize_t w = write(fd, buf + off, n - off);
if (w <= 0) return -1;
off += (size_t)w;
}
return 0;
}
static int send_frame(int fd, uint16_t type, uint32_t req_id, const void *payload, uint32_t len)
{
uint8_t hdr[MEOW_IPC_HDR_LEN];
uint32_t magic = MEOW_IPC_MAGIC, rlen = len;
uint16_t ver = MEOW_IPC_VERSION, t = type;
memcpy(hdr + 0, &magic, 4);
memcpy(hdr + 4, &ver, 2);
memcpy(hdr + 6, &t, 2);
memcpy(hdr + 8, &req_id, 4);
memcpy(hdr + 12, &rlen, 4);
if (x_write_full(fd, hdr, MEOW_IPC_HDR_LEN) < 0) return -1;
if (len && payload && x_write_full(fd, payload, len) < 0) return -1;
return 0;
}
static int send_ack(int fd, uint32_t req_id, uint32_t code, uint32_t status)
{
m_ack_t a;
memset(&a, 0, sizeof a);
a.code = code;
a.status = status;
return send_frame(fd, MSG_ACK, req_id, &a, sizeof a);
}
static int send_err(int fd, uint32_t req_id, uint32_t code, const char *msg)
{
m_err_t e;
memset(&e, 0, sizeof e);
e.code = code;
uint16_t ml = msg ? (uint16_t)strlen(msg) : 0;
if (ml > 63) ml = 63;
e.msg_len = ml;
if (ml) memcpy(e.msg, msg, ml);
return send_frame(fd, MSG_ERR, req_id, &e, sizeof e);
}
/* json/text 结果通发(m_net_status_result_t / m_net_iface_result_t / m_net_cmd_result_t 布局同构
* code(4)+len(2)+pad(2)+payload,payload ≤ MEOW_IPC_MAX-8)。 */
static int send_payload_result(int fd, uint16_t type, uint32_t req_id, uint32_t code,
const char *payload, size_t pl)
{
m_net_cmd_result_t r; /* 复用布局最大的那个(payload 区 4088B)。 */
memset(&r, 0, sizeof r);
r.code = code;
if (pl > sizeof r.text - 1) pl = sizeof r.text - 1;
r.text_len = (uint16_t)pl;
memcpy(r.text, payload, pl);
r.text[pl] = 0;
return send_frame(fd, type, req_id, &r, (uint32_t)(8 + pl + 1));
}
static int send_net_status_result(int fd, uint32_t req_id, const char *json)
{
return send_payload_result(fd, MSG_NET_STATUS_RESULT, req_id, MEOW_OK, json, strlen(json));
}
static int send_net_iface_result(int fd, uint32_t req_id, uint32_t code, const char *json)
{
return send_payload_result(fd, MSG_NET_IFACE_RESULT, req_id, code, json, strlen(json));
}
static int send_net_wifi_result(int fd, uint32_t req_id, const ctl_result_t *r)
{
return send_payload_result(fd, MSG_NET_WIFI_RESULT, req_id, (uint32_t)r->code, r->text, strlen(r->text));
}
static int send_net_bt_result(int fd, uint32_t req_id, const ctl_result_t *r)
{
return send_payload_result(fd, MSG_NET_BT_RESULT, req_id, (uint32_t)r->code, r->text, strlen(r->text));
}
/* ---------------- 守护进程态 ---------------- */
static int g_mode_meow = 0; /* meow=1 全功能;0=只读降级 */
static long g_net_last_rx = -1, g_net_last_tx = 0, g_net_last_mono = 0;
static long g_net_rate_rx = -1, g_net_rate_tx = -1; /* B/s;-1=未知 */
static char g_prev_if[IFACE_MAX][32];
static int g_prev_if_up[IFACE_MAX];
static int g_prev_if_n = 0;
/* ---------------- JSON 构建:status/iface ---------------- */
static void iface_json_obj(jw_t *w, const iface_t *f)
{
jadd(w, "{\"name\":\"%s\",\"kind\":\"%s\",\"up\":%s,", f->name, f->kind,
f->up ? "true" : "false");
jadd_str_or_null(w, "ip", f->ip);
jadd_str_or_null(w, "gw", f->gw);
jadd(w, "\"rx_bytes\":%ld,\"tx_bytes\":%ld}", f->rx, f->tx);
}
static void build_status_json(char *out, size_t cap)
{
jw_t w = { out, cap, 0 };
out[0] = 0;
jadd(&w, "{\"mode\":\"%s\",\"boot_ts\":\"", g_mode_meow ? "meow" : "readonly");
{
char ts[64]; get_bootts(ts, sizeof ts);
for (const char *p = ts; *p; p++) w.buf[w.off++] = *p;
}
jadd(&w, "\",\"ts\":%ld,", (long)time(NULL));
enum_ifaces(1);
jadd(&w, "\"ifaces\":[");
for (int i = 0; i < g_iface_n; i++) { if (i) jadd(&w, ","); iface_json_obj(&w, &g_ifaces[i]); }
jadd(&w, "],");
wifi_t wi;
wifi_status(&wi);
jadd(&w, "\"wifi\":{");
jadd_q(&w, "state", wi.state);
jadd(&w, "\"connected\":%s,", wi.connected ? "true" : "false");
jadd_str_or_null(&w, "ssid", wi.ssid);
jadd_str_or_null(&w, "ip", wi.ip);
if (wi.has_rssi) jadd(&w, "\"rssi\":%d,", wi.rssi);
else jadd(&w, "\"rssi\":null,");
jadd(&w, "\"networks\":null},");
bt_t bt;
bt_status(&bt);
jadd(&w, "\"bt\":{");
jadd(&w, "\"available\":%s,\"powered\":%s,", bt.available ? "true" : "false",
bt.powered ? "true" : "false");
jadd_str_or_null(&w, "address", bt.address);
jadd_int_or_null(&w, "paired", bt.paired_n);
jadd(&w, "\"dns\":[");
{
char ns[DNS_MAX][40];
int n = 0;
dns_read(ns, &n);
for (int i = 0; i < n; i++) { if (i) jadd(&w, ","); jadd(&w, "\"%s\"", ns[i]); }
}
jadd(&w, "],");
rfkill_list();
jadd(&w, "\"rfkill\":[");
for (int i = 0; i < RFKILL_MAX && g_rfkill[i].type[0]; i++) {
if (i) jadd(&w, ",");
jadd(&w, "{\"type\":\"%s\",\"soft\":%d,\"hard\":%d}", g_rfkill[i].type,
g_rfkill[i].soft, g_rfkill[i].hard);
}
jadd(&w, "],");
jadd(&w, "\"rate\":{\"rx_bps\":");
if (g_net_rate_rx < 0) jadd(&w, "null,");
else jadd(&w, "%ld,", g_net_rate_rx);
jadd(&w, "\"tx_bps\":");
if (g_net_rate_tx < 0) jadd(&w, "null}}");
else jadd(&w, "%ld}}", g_net_rate_tx);
}
/* 单/全接口详情 JSON。name 空=全部(数组);否则单对象。返回 MEOW_OK / MEOW_ERR_NOENT。 */
static int build_iface_json(const char *name, char *out, size_t cap)
{
enum_ifaces(1);
jw_t w = { out, cap, 0 };
out[0] = 0;
if (!name || !name[0]) {
jadd(&w, "[");
for (int i = 0; i < g_iface_n; i++) {
if (i) jadd(&w, ",");
iface_json_obj(&w, &g_ifaces[i]);
}
jadd(&w, "]");
return MEOW_OK;
}
for (int i = 0; i < g_iface_n; i++) {
if (strcmp(g_ifaces[i].name, name) == 0) {
iface_json_obj(&w, &g_ifaces[i]);
/* 去掉尾 } 补 dns(期 1 不分接口,统一读 resolv.conf)。 */
if (w.off >= 2 && w.buf[w.off - 1] == '}') w.buf[--w.off] = 0;
jadd(&w, ",\"dns\":[");
char ns[DNS_MAX][40];
int n = 0;
dns_read(ns, &n);
for (int i2 = 0; i2 < n; i2++) { if (i2) jadd(&w, ","); jadd(&w, "\"%s\"", ns[i2]); }
jadd(&w, "]}");
return MEOW_OK;
}
}
return MEOW_ERR_NOENT;
}
/* ---------------- 节拍:模式热切换 + 接口变化检测(日志)+ 全局速率差分 ---------------- */
static void daemon_tick(void)
{
int fm = boot_mode_is_meow();
if (fm != g_mode_meow) {
svc_log("mode -> %s", fm ? "full(meow)" : "readonly");
g_mode_meow = fm;
}
/* 接口热插拔/状态变化:节拍检测 → 日志(读侧无 popen)。 */
enum_ifaces(0);
int cur = g_iface_n;
for (int i = 0; i < cur; i++) {
int found = -1;
for (int j = 0; j < g_prev_if_n; j++)
if (strcmp(g_prev_if[j], g_ifaces[i].name) == 0) { found = j; break; }
if (found < 0)
net_log("info", "iface", "add kind=%s name=%s", g_ifaces[i].kind, g_ifaces[i].name);
else if (g_prev_if_up[found] != g_ifaces[i].up)
net_log("info", "iface", "state name=%s up=%d", g_ifaces[i].name, g_ifaces[i].up);
if (found >= 0) g_prev_if_up[found] = g_ifaces[i].up;
}
for (int j = 0; j < g_prev_if_n; j++) {
int gone = 1;
for (int i = 0; i < cur; i++) if (strcmp(g_prev_if[j], g_ifaces[i].name) == 0) { gone = 0; break; }
if (gone) net_log("info", "iface", "del name=%s", g_prev_if[j]);
}
for (int i = 0; i < cur; i++) {
snprintf(g_prev_if[i], sizeof g_prev_if[i], "%s", g_ifaces[i].name);
g_prev_if_up[i] = g_ifaces[i].up;
}
g_prev_if_n = cur;
/* 全局速率差分(2s 拍):/proc/net/dev 累计。 */
long rx = -1, tx = 0;
netdev_totals(&rx, &tx);
if (rx >= 0) {
long now = mono_now();
if (g_net_last_rx >= 0 && now > g_net_last_mono) {
long dt = now - g_net_last_mono;
if (dt > 0) {
g_net_rate_rx = (rx - g_net_last_rx) / dt;
g_net_rate_tx = (tx - g_net_last_tx) / dt;
if (g_net_rate_rx < 0) g_net_rate_rx = 0;
if (g_net_rate_tx < 0) g_net_rate_tx = 0;
}
}
g_net_last_rx = rx; g_net_last_tx = tx; g_net_last_mono = now;
}
}
/* ---------------- IPC 鉴权(连接级,MPK_SIGNATURE_DESIGN §6.2;同 svc_power) ---------------- */
typedef enum { CALL_OTHER, CALL_PKG, CALL_SYS } caller_kind_t;
typedef struct {
caller_kind_t kind;
char pkg[MEOW_APP_ID_MAX];
uid_t uid;
} caller_t;
static int is_sys_identity(const caller_t *c)
{
if (c->kind == CALL_SYS) return 1;
if (c->kind == CALL_PKG && !strncmp(c->pkg, "os.meow.", 8)) return 1;
return 0;
}
static void classify_exe(const char *exe, caller_t *out)
{
static const char *mp = "/usr/data/mnt/sd_0/MeowAPPs/";
if (strncmp(exe, mp, strlen(mp)) == 0) {
const char *p = exe + strlen(mp);
if (*p == '/') p++;
const char *end = strchr(p, '/');
int plen = end ? (int)(end - p) : (int)strlen(p);
if (plen > 0 && plen < MEOW_APP_ID_MAX) {
memcpy(out->pkg, p, (size_t)plen); out->pkg[plen] = 0;
out->kind = CALL_PKG;
return;
}
}
char tmp[MEOW_APP_ID_MAX];
if (strcmp(exe, "/usr/bin/meow_launcherd") == 0) {
snprintf(tmp, sizeof tmp, "%s", "os.meow.launcher");
} else if (strncmp(exe, "/usr/bin/meow_", 14) == 0) {
size_t l = strlen(exe + 9); /* "/usr/bin/" 长 9 → 取 "meow_xyz" */
if (l >= sizeof tmp) l = sizeof tmp - 1;
memcpy(tmp, exe + 9, l); tmp[l] = 0;
} else if (strncmp(exe, "/usr/data/meow/", 15) == 0) {
snprintf(tmp, sizeof tmp, "%s", "os.meow.core");
} else {
return; /* CALL_OTHER */
}
snprintf(out->pkg, sizeof out->pkg, "%s", tmp);
out->kind = CALL_SYS;
}
static void classify_caller(int fd, caller_t *out)
{
memset(out, 0, sizeof *out);
out->uid = (uid_t)-1;
struct ucred cred;
socklen_t cl = sizeof cred;
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &cl) != 0) return;
out->uid = cred.uid;
char procpath[64], exe[256];
snprintf(procpath, sizeof procpath, "/proc/%d/exe", (int)cred.pid);
ssize_t n = readlink(procpath, exe, sizeof exe - 1);
if (n <= 0) return;
exe[n] = 0;
classify_exe(exe, out);
}
/* CTL 写级分类(设计 §10/HibyOS:写级仅系统身份 + MeowOS 模式才允许)。 */
static int wifi_cmd_is_write(const char *cmd, const char *arg)
{
if (!strcmp(cmd, "status") || !strcmp(cmd, "networks")) return 0; /* 读级 */
if (!strcmp(cmd, "rfkill")) return arg && strcmp(arg, "list") == 0 ? 0 : 1;
return 1; /* scan/connect/disconnect/on/off/udhcpc */
}
static int bt_cmd_is_write(const char *cmd)
{
if (!strcmp(cmd, "status") || !strcmp(cmd, "paired")) return 0;
return 1; /* on/off */
}
/* 写级授权决策(纯函数,宿主自测用;handle_conn 复用):读级任意;写级仅系统身份 + MeowOS 模式。
* 返回 MEOW_OK(放行)或 MEOW_ERR_PERM(拒绝)。 */
static int wifi_write_authorize(const caller_t *c, int mode_meow, const char *cmd, const char *arg)
{
if (!wifi_cmd_is_write(cmd, arg)) return MEOW_OK;
if (!is_sys_identity(c)) return MEOW_ERR_PERM;
if (!mode_meow) return MEOW_ERR_PERM;
return MEOW_OK;
}
static int bt_write_authorize(const caller_t *c, int mode_meow, const char *cmd)
{
if (!bt_cmd_is_write(cmd)) return MEOW_OK;
if (!is_sys_identity(c)) return MEOW_ERR_PERM;
if (!mode_meow) return MEOW_ERR_PERM;
return MEOW_OK;
}
/* ---------------- WiFi/BT/rfkill/udhcpc 动作执行(dæmon 侧;写级已在上游鉴权) ---------------- */
static const char *str_capsafe(const char *s, char *tmp, size_t cap)
{
size_t n = strlen(s);
while (n > 0 && (s[0] == ' ' || s[0] == '\t')) { s++; n--; }
while (n > 0 && (s[n-1] == ' ' || s[n-1] == '\t' || s[n-1] == '\n')) n--;
if (n >= cap) n = cap - 1;
memcpy(tmp, s, n); tmp[n] = 0;
return tmp;
}
static void wifi_scan(ctl_result_t *r)
{
char cmd[160];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s scan 2>/dev/null", g_wpa_cli);
char buf[128];
run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd);
struct timespec ts = { 0, 300 * 1000000L }; /* 触发后稍候再读结果(有界)。 */
nanosleep(&ts, NULL);
char bufout[MEOW_IPC_MAX - 16];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s scan_results 2>/dev/null", g_wpa_cli);
int n = run_cmd(bufout, sizeof bufout, RUNCMD_TMO_MS, cmd);
if (n <= 0) { ctl_sets(r, MEOW_ERR_NODEV, "scan: no results (wpa_cli 空/失败)"); return; }
ctl_sets(r, MEOW_OK, bufout);
}
static void wifi_networks(ctl_result_t *r)
{
char cmd[160];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s list_networks 2>/dev/null", g_wpa_cli);
char buf[MEOW_IPC_MAX - 16];
int n = run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd);
if (n <= 0) { ctl_sets(r, MEOW_ERR_NODEV, "networks: no output (wpa_cli 空/失败)"); return; }
ctl_sets(r, MEOW_OK, buf);
}
static void wifi_connect(const char *arg, ctl_result_t *r)
{
char ssid[96] = "", psk[96] = "";
if (arg && arg[0]) {
const char *comma = strchr(arg, ',');
if (comma) {
size_t sl = (size_t)(comma - arg);
if (sl >= sizeof ssid) sl = sizeof ssid - 1;
memcpy(ssid, arg, sl); ssid[sl] = 0;
snprintf(psk, sizeof psk, "%s", comma + 1);
} else {
snprintf(ssid, sizeof ssid, "%s", arg);
}
}
char tmp_ssid[96], tmp_psk[96];
str_capsafe(ssid, tmp_ssid, sizeof tmp_ssid);
str_capsafe(psk, tmp_psk, sizeof tmp_psk);
if (!tmp_ssid[0]) { ctl_sets(r, MEOW_ERR_INVAL, "connect: ssid 缺失"); return; }
/* 找既有网络:list_networks → 匹配 ssid(剥引号比较);命中则复用其 id。 */
char cmd[200];
char list[1024];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s list_networks 2>/dev/null", g_wpa_cli);
int n = run_cmd(list, sizeof list, RUNCMD_TMO_MS, cmd);
int id = -1;
if (n > 0) {
char *ln = strtok(list, "\n");
while (ln) {
char *tab0 = strchr(ln, '\t');
if (tab0) {
int this_id = atoi(ln);
char *ss = tab0 + 1;
char *tab1 = strchr(ss, '\t');
if (tab1) *tab1 = 0;
char clean[128], norm_clean[128];
size_t cl = strlen(ss);
if (cl >= 2 && ss[0] == '"' && ss[cl-1] == '"') {
memcpy(clean, ss + 1, cl - 2); clean[cl - 2] = 0;
} else {
snprintf(clean, sizeof clean, "%s", ss);
}
str_capsafe(clean, norm_clean, sizeof norm_clean);
if (strcmp(norm_clean, tmp_ssid) == 0) id = this_id;
}
ln = strtok(NULL, "\n");
}
}
char out[128];
if (id < 0) {
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s add_network 2>/dev/null", g_wpa_cli);
if (run_cmd(out, sizeof out, RUNCMD_TMO_MS, cmd) <= 0) {
ctl_sets(r, MEOW_ERR_NODEV, "connect: add_network 失败 (wpa_cli 不可用?)");
return;
}
id = atoi(out);
if (id < 0) { ctl_sets(r, MEOW_ERR_INVAL, "connect: add_network 无 id"); return; }
snprintf(cmd, sizeof cmd,
"PATH=/bin:/sbin:/usr/bin:/usr/sbin %s set_network %d ssid '\"%s\"' 2>/dev/null",
g_wpa_cli, id, tmp_ssid);
run_cmd(out, sizeof out, RUNCMD_TMO_MS, cmd);
if (tmp_psk[0]) {
snprintf(cmd, sizeof cmd,
"PATH=/bin:/sbin:/usr/bin:/usr/sbin %s set_network %d psk '\"%s\"' 2>/dev/null",
g_wpa_cli, id, tmp_psk);
} else {
snprintf(cmd, sizeof cmd,
"PATH=/bin:/sbin:/usr/bin:/usr/sbin %s set_network %d key_mgmt NONE 2>/dev/null",
g_wpa_cli, id);
}
run_cmd(out, sizeof out, RUNCMD_TMO_MS, cmd);
}
{ /* enable + select 该网络(复用/新建皆用)。 */
char en[256], sel[256];
snprintf(en, sizeof en, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s enable_network %d 2>/dev/null",
g_wpa_cli, id);
run_cmd(out, sizeof out, RUNCMD_TMO_MS, en);
snprintf(sel, sizeof sel, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s select_network %d 2>/dev/null",
g_wpa_cli, id);
run_cmd(out, sizeof out, RUNCMD_TMO_MS, sel);
}
char msg[140];
snprintf(msg, sizeof msg, "connect: ssid=%s network_id=%d (已 enable+select)", tmp_ssid, id);
ctl_sets(r, MEOW_OK, msg);
}
static void wifi_onoff(const char *cmd, ctl_result_t *r)
{
int block = strcmp(cmd, "off") == 0;
int rc = rfkill_set_block("wlan", block);
if (rc == 0) {
char msg[64];
snprintf(msg, sizeof msg, "wifi %s: rfkill wlan %s", cmd, block ? "blocked" : "unblocked");
ctl_sets(r, MEOW_OK, msg);
} else {
ctl_sets(r, MEOW_ERR_NODEV, "wifi on/off: 无 wlan rfkill 设备或写失败");
}
}
static void rfkill_ctl(const char *arg, ctl_result_t *r)
{
char tmp[64];
str_capsafe(arg ? arg : "", tmp, sizeof tmp);
char *sp = strchr(tmp, ' ');
if (!sp) { ctl_sets(r, MEOW_ERR_INVAL, "rfkill: 用法 <type> <on|off>(如 `rfkill wlan on`)"); return; }
*sp = 0;
char *type = tmp;
char *state = sp + 1;
int block = strcmp(state, "off") == 0 ? 1 : (strcmp(state, "on") == 0 ? 0 : -1);
if (block < 0) { ctl_sets(r, MEOW_ERR_INVAL, "rfkill: state 须 on/off"); return; }
int rc = rfkill_set_block(type, block);
if (rc == 0) {
char msg[64];
snprintf(msg, sizeof msg, "rfkill %s %s: done", type, block ? "blocked" : "unblocked");
ctl_sets(r, MEOW_OK, msg);
} else {
ctl_sets(r, MEOW_ERR_NODEV, "rfkill: 无该类型设备或写失败");
}
}
static void udhcpc_ctl_svc(const char *arg, ctl_result_t *r)
{
char tmp[96];
str_capsafe(arg ? arg : "", tmp, sizeof tmp);
char *sp = strchr(tmp, ' ');
char op[16], iff[32] = "";
if (sp) { *sp = 0; snprintf(op, sizeof op, "%s", tmp); snprintf(iff, sizeof iff, "%s", sp + 1); }
else snprintf(op, sizeof op, "%s", tmp);
if (strcmp(op, "renew") != 0 && strcmp(op, "release") != 0) {
ctl_sets(r, MEOW_ERR_INVAL, "udhcpc: op 须 renew/release");
return;
}
int rc = udhcpc_ctl(op, iff[0] ? iff : NULL);
if (rc == 0) {
char msg[80];
snprintf(msg, sizeof msg, "udhcpc %s %s: sent SIGUSR%s", op, iff[0] ? iff : g_wifi_if,
strcmp(op, "release") == 0 ? "2" : "1");
ctl_sets(r, MEOW_OK, msg);
} else {
ctl_sets(r, MEOW_ERR_NOENT, "udhcpc: 无运行进程 (pid 文件缺失)");
}
}
static void wifi_ctl_execute(const char *cmd, const char *arg, ctl_result_t *r)
{
ctl_reset(r, MEOW_OK);
if (!strcmp(cmd, "status")) {
wifi_t w;
wifi_status(&w);
char msg[256];
snprintf(msg, sizeof msg, "wifi state=%s connected=%d ssid=%s ip=%s rssi=%d",
w.state[0] ? w.state : "unknown", w.connected, w.ssid[0] ? w.ssid : "-",
w.ip[0] ? w.ip : "-", w.rssi);
ctl_sets(r, MEOW_OK, msg);
} else if (!strcmp(cmd, "scan")) {
wifi_scan(r);
} else if (!strcmp(cmd, "networks")) {
wifi_networks(r);
} else if (!strcmp(cmd, "connect")) {
wifi_connect(arg, r);
} else if (!strcmp(cmd, "disconnect")) {
char cmd2[160];
snprintf(cmd2, sizeof cmd2, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s disconnect 2>/dev/null", g_wpa_cli);
char buf[128];
int n = run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd2);
ctl_sets(r, n >= 0 ? MEOW_OK : MEOW_ERR_NODEV, "wifi disconnect: 已下发");
} else if (!strcmp(cmd, "on") || !strcmp(cmd, "off")) {
wifi_onoff(cmd, r);
} else if (!strcmp(cmd, "rfkill")) {
rfkill_ctl(arg, r);
} else if (!strcmp(cmd, "udhcpc")) {
udhcpc_ctl_svc(arg, r);
} else {
ctl_sets(r, MEOW_ERR_INVAL, "wifi: 未知子命令");
}
}
static void bt_onoff(const char *cmd, ctl_result_t *r)
{
int up = strcmp(cmd, "on") == 0;
char cmd2[160];
snprintf(cmd2, sizeof cmd2, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s hci0 %s 2>/dev/null",
g_hciconfig, up ? "up" : "down");
char buf[128];
int n = run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd2);
rfkill_set_block("bluetooth", up ? 0 : 1);
ctl_sets(r, n >= 0 ? MEOW_OK : MEOW_ERR_NODEV,
up ? "bt on: hci0 up + rfkill unblocked" : "bt off: hci0 down + rfkill blocked");
}
static void bt_ctl_execute(const char *cmd, const char *arg, ctl_result_t *r)
{
(void)arg;
ctl_reset(r, MEOW_OK);
if (!strcmp(cmd, "status")) {
bt_t b;
bt_status(&b);
char msg[192];
snprintf(msg, sizeof msg, "bt available=%d powered=%d address=%s paired=%d",
b.available, b.powered, b.address[0] ? b.address : "-", b.paired_n);
ctl_sets(r, MEOW_OK, msg);
} else if (!strcmp(cmd, "on") || !strcmp(cmd, "off")) {
bt_onoff(cmd, r);
} else if (!strcmp(cmd, "paired")) {
char cmd2[160];
snprintf(cmd2, sizeof cmd2, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s paired 2>/dev/null", g_bluetoothctl);
char buf[MEOW_IPC_MAX - 16];
int n = run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd2);
if (n <= 0) ctl_sets(r, MEOW_ERR_NODEV, "bt paired: 无输出 (bluetoothctl 不可用/未开)");
else ctl_sets(r, MEOW_OK, buf);
} else {
ctl_sets(r, MEOW_ERR_INVAL, "bt: 未知子命令");
}
}
/* ---------------- 处理单条连接 ---------------- */
static void handle_conn(int c)
{
caller_t caller;
classify_caller(c, &caller);
for (;;) {
uint8_t hdr[MEOW_IPC_HDR_LEN];
if (x_read_full(c, hdr, sizeof hdr) < 0) break;
uint32_t magic, rlen, req_id;
uint16_t ver, type;
memcpy(&magic, hdr, 4);
memcpy(&ver, hdr + 4, 2);
memcpy(&type, hdr + 6, 2);
memcpy(&req_id, hdr + 8, 4);
memcpy(&rlen, hdr + 12, 4);
if (magic != MEOW_IPC_MAGIC || ver != MEOW_IPC_VERSION) {
send_err(c, req_id, MEOW_ERR_INVAL, "bad magic/ver"); break;
}
if (rlen > MEOW_IPC_MAX) { send_err(c, req_id, MEOW_ERR_INVAL, "payload too big"); break; }
uint8_t payload[MEOW_IPC_MAX];
if (rlen && x_read_full(c, payload, rlen) < 0) break;
if (type == MSG_HELLO) { send_ack(c, req_id, MEOW_OK, 0); continue; }
if (type == MSG_NET_STATUS_REQ) {
char json[MEOW_IPC_MAX];
build_status_json(json, sizeof json);
send_net_status_result(c, req_id, json);
continue;
}
if (type == MSG_NET_IFACE_REQ) {
m_net_iface_req_t q;
memset(&q, 0, sizeof q);
if (rlen > sizeof q) rlen = sizeof q;
memcpy(&q, payload, rlen);
char json[MEOW_IPC_MAX];
int rc = build_iface_json(q.name, json, sizeof json);
send_net_iface_result(c, req_id, rc == MEOW_OK ? MEOW_OK : MEOW_ERR_NOENT, json);
continue;
}
if (type == MSG_NET_WIFI_CTL) {
m_net_cmd_t q;
memset(&q, 0, sizeof q);
if (rlen > sizeof q) rlen = sizeof q;
memcpy(&q, payload, rlen);
ctl_result_t r;
int wr = wifi_write_authorize(&caller, g_mode_meow, q.cmd, q.arg);
if (wr != MEOW_OK) {
ctl_sets(&r, MEOW_ERR_PERM,
!is_sys_identity(&caller)
? "permission denied (wifi ctl write needs system identity)"
: "readonly (HibyOS mode): wifi write denied");
net_log("warn", "write", "denied wifi cmd=%s arg=%s caller_kind=%d uid=%d", q.cmd,
q.arg, (int)caller.kind, (int)caller.uid);
send_net_wifi_result(c, req_id, &r);
continue;
}
wifi_ctl_execute(q.cmd, q.arg, &r);
if (wifi_cmd_is_write(q.cmd, q.arg))
net_log("info", "write", "wifi cmd=%s arg=%s rc=%d", q.cmd, q.arg, r.code);
send_net_wifi_result(c, req_id, &r);
continue;
}
if (type == MSG_NET_BT_CTL) {
m_net_cmd_t q;
memset(&q, 0, sizeof q);
if (rlen > sizeof q) rlen = sizeof q;
memcpy(&q, payload, rlen);
ctl_result_t r;
int wr = bt_write_authorize(&caller, g_mode_meow, q.cmd);
if (wr != MEOW_OK) {
ctl_sets(&r, MEOW_ERR_PERM,
!is_sys_identity(&caller)
? "permission denied (bt ctl write needs system identity)"
: "readonly (HibyOS mode): bt write denied");
net_log("warn", "write", "denied bt cmd=%s caller_kind=%d uid=%d", q.cmd,
(int)caller.kind, (int)caller.uid);
send_net_bt_result(c, req_id, &r);
continue;
}
bt_ctl_execute(q.cmd, q.arg, &r);
if (bt_cmd_is_write(q.cmd)) net_log("info", "write", "bt cmd=%s rc=%d", q.cmd, r.code);
send_net_bt_result(c, req_id, &r);
continue;
}
send_err(c, req_id, MEOW_ERR_INVAL, "unknown type (期1仅 STATUS/IFACE/WIFI_CTL/BT_CTL)");
break;
}
}
/* ---------------- 守护主循环(单线程 poll;节拍 2s) ---------------- */
static volatile sig_atomic_t g_stop = 0;
static void on_sig(int sig) { (void)sig; g_stop = 1; }
static int run_daemon(const char *sock)
{
signal(SIGPIPE, SIG_IGN);
signal(SIGTERM, on_sig);
signal(SIGINT, on_sig);
int lfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (lfd < 0) { svc_log("socket fail: %s", strerror(errno)); return 1; }
struct sockaddr_un a;
memset(&a, 0, sizeof a);
a.sun_family = AF_UNIX;
strncpy(a.sun_path, sock, sizeof a.sun_path - 1);
unlink(sock);
if (bind(lfd, (struct sockaddr *)&a, sizeof a) < 0) {
svc_log("bind %s fail: %s", sock, strerror(errno));
close(lfd);
return 1;
}
chmod(sock, 0666);
if (listen(lfd, 8) < 0) { svc_log("listen fail: %s", strerror(errno)); close(lfd); return 1; }
g_mode_meow = boot_mode_is_meow();
log_open();
svc_log("meow_netmgrd listening on %s (mode=%s, poll=%ds, log=%s)",
sock, g_mode_meow ? "full" : "readonly", g_poll_sec, g_logfile);
long next_tick = 0;
while (!g_stop) {
long now = mono_now();
if (now >= next_tick) {
daemon_tick();
next_tick = now + g_poll_sec;
}
int wait_ms = (int)((next_tick - mono_now()) * 1000);
if (wait_ms < 50) wait_ms = 50;
if (wait_ms > g_poll_sec * 1000) wait_ms = g_poll_sec * 1000;
struct pollfd pfd = { lfd, POLLIN, 0 };
int pr = poll(&pfd, 1, wait_ms);
if (pr > 0 && (pfd.revents & POLLIN)) {
int c = accept(lfd, NULL, NULL);
if (c >= 0) { handle_conn(c); close(c); }
}
}
svc_log("meow_netmgrd exit");
close(lfd);
unlink(sock);
return 0;
}
/* ---------------- CLI(IPC 薄壳) ---------------- */
static int ipc_connect(void)
{
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) return -1;
struct timeval tv = { 2, 0 };
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
struct sockaddr_un a;
memset(&a, 0, sizeof a);
a.sun_family = AF_UNIX;
strncpy(a.sun_path, g_sock, sizeof a.sun_path - 1);
if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; }
return fd;
}
/* 收发一轮:req(可有可无)+ resp type/code + payload 文本(json/text)。返回 0=OK。 */
static int ipc_xchg(int fd, uint16_t req_type, const void *req, uint32_t req_len,
uint16_t *resp_type, uint32_t *resp_code, char *out, size_t outcap)
{
if (send_frame(fd, req_type, 1, req, req_len) < 0) return -1;
uint8_t hdr[MEOW_IPC_HDR_LEN];
if (x_read_full(fd, hdr, sizeof hdr) < 0) return -1;
uint32_t rl; uint16_t rt;
memcpy(&rt, hdr + 6, 2);
memcpy(&rl, hdr + 12, 4);
if (resp_type) *resp_type = rt;
if (rl > MEOW_IPC_MAX) return -1;
uint8_t pl[MEOW_IPC_MAX];
if (rl && x_read_full(fd, pl, rl) < 0) return -1;
if (rl >= 8) {
uint32_t code; uint16_t jl;
memcpy(&code, pl, 4);
memcpy(&jl, pl + 4, 2);
if (resp_code) *resp_code = code;
if (out && outcap) {
if (jl >= outcap) jl = (uint16_t)(outcap - 1);
memcpy(out, pl + 8, jl);
out[jl] = 0;
}
return 0;
}
return -1;
}
static int cli_ipc_json(uint16_t req_type, const void *req, uint32_t req_len, char *json, size_t cap)
{
int fd = ipc_connect();
if (fd < 0) return -1;
uint16_t rt; uint32_t code;
int rc = ipc_xchg(fd, req_type, req, req_len, &rt, &code, json, cap);
close(fd);
return rc == 0 ? 0 : -1;
}
static int cli_ipc_cmd(uint16_t req_type, const char *cmd, const char *arg, char *out, size_t cap)
{
m_net_cmd_t q;
memset(&q, 0, sizeof q);
snprintf(q.cmd, sizeof q.cmd, "%s", cmd);
if (arg) snprintf(q.arg, sizeof q.arg, "%s", arg);
int fd = ipc_connect();
if (fd < 0) { snprintf(out, cap, "meow_netmgrd 守护不可达 (%s)", g_sock); return -1; }
uint16_t rt; uint32_t code;
int rc = ipc_xchg(fd, req_type, &q, sizeof q, &rt, &code, out, cap);
close(fd);
if (rc != 0) { snprintf(out, cap, "IPC 往返失败"); return -1; }
return (int)code;
}
static int cli_cmd_result(uint16_t req_type, const char *cmd, const char *arg)
{
char out[MEOW_IPC_MAX - 8];
int code = cli_ipc_cmd(req_type, cmd, arg, out, sizeof out);
printf("%s\n", out);
return code == MEOW_OK ? 0 : 1;
}
/* ---- 简版 JSON 字段提取(自产 JSON 固定形状,不做通用解析;同 powermgr) ---- */
static const char *jfind(const char *js, const char *key)
{
char pat[48];
snprintf(pat, sizeof pat, "\"%s\":", key);
const char *p = strstr(js, pat);
return p ? p + strlen(pat) : NULL;
}
static int jint(const char *js, const char *key, int *out)
{
const char *p = jfind(js, key);
if (!p || strncmp(p, "null", 4) == 0) { *out = -1; return 1; }
*out = atoi(p);
return 0;
}
static void jstr(const char *js, const char *key, char *out, size_t cap)
{
const char *p = jfind(js, key);
if (!p || strncmp(p, "null", 4) == 0 || *p != '"') { out[0] = 0; return; }
p++;
size_t n = 0;
while (p[n] && p[n] != '"' && n < cap - 1) { out[n] = p[n]; n++; }
out[n] = 0;
}
/* 定位 `"key":{` 的子对象并切到其闭合 `}`(扁平对象)。返回切片进 out。 */
static int slice_obj(const char *js, const char *key, char *out, size_t cap)
{
char pat[64];
snprintf(pat, sizeof pat, "\"%s\":", key);
const char *p = strstr(js, pat);
if (!p) { out[0] = 0; return -1; }
const char *q = strchr(p, '}');
if (!q) { out[0] = 0; return -1; }
size_t n = (size_t)(q - p + 1);
if (n >= cap) n = cap - 1;
memcpy(out, p, n); out[n] = 0;
return 0;
}
/* 在 json 中迭代 `"<key>":"` 开头的扁平条目,取每个对象切片(到 `}`)。首调 *ctx=NULL。 */
static int next_obj(const char *json, const char *key, const char **ctx, char *out, size_t cap)
{
char pat[64];
snprintf(pat, sizeof pat, "\"%s\":\"", key);
const char *p = *ctx ? *ctx : json;
const char *q = strstr(p, pat);
if (!q) { *ctx = NULL; return -1; }
q += strlen(pat);
const char *e = strchr(q, '}');
if (!e) { *ctx = NULL; return -1; }
size_t n = (size_t)(e - q + 1);
if (n >= cap) n = cap - 1;
memcpy(out, q, n); out[n] = 0;
*ctx = e + 1;
return 0;
}
static int cli_status(void)
{
char js[MEOW_IPC_MAX];
if (cli_ipc_json(MSG_NET_STATUS_REQ, NULL, 0, js, sizeof js) != 0) {
fprintf(stderr, "meow_net status: 守护不可达(%s)\n", g_sock);
return 1;
}
char mode[16];
jstr(js, "mode", mode, sizeof mode);
printf("mode=%s src=daemon\n", mode[0] ? mode : "?");
/* ifaces 数组 */
const char *ctx = NULL;
for (;;) {
char tmp[256];
if (next_obj(js, "name", &ctx, tmp, sizeof tmp) != 0) break;
char name[32]; int i = 0;
while (tmp[i] && tmp[i] != '"' && i < 31) { name[i] = tmp[i]; i++; }
name[i] = 0;
char kind[12] = "", ip[40] = "", gw[40] = "";
int up = strstr(tmp, "\"up\":true") != NULL;
jstr(tmp, "kind", kind, sizeof kind);
jstr(tmp, "ip", ip, sizeof ip);
jstr(tmp, "gw", gw, sizeof gw);
printf("iface:%s kind=%s up=%s ip=%s gw=%s\n", name, kind[0] ? kind : "?",
up ? "true" : "false", ip[0] ? ip : "null", gw[0] ? gw : "null");
}
/* wifi */
{
char obj[256];
slice_obj(js, "wifi", obj, sizeof obj);
char ssid[64], wstate[16], wip[40];
int wc = strstr(obj, "\"connected\":true") != NULL;
jstr(obj, "state", wstate, sizeof wstate);
jstr(obj, "ssid", ssid, sizeof ssid);
jstr(obj, "ip", wip, sizeof wip);
int rssi = -1;
jint(obj, "rssi", &rssi);
printf("wifi connected=%d state=%s ssid=%s ip=%s rssi=%d\n",
wc, wstate[0] ? wstate : "unknown", ssid[0] ? ssid : "-",
wip[0] ? wip : "-", rssi);
}
/* bt */
{
char obj[192];
slice_obj(js, "bt", obj, sizeof obj);
char baddr[32];
int bavail = strstr(obj, "\"available\":true") != NULL;
int bpow = strstr(obj, "\"powered\":true") != NULL;
jstr(obj, "address", baddr, sizeof baddr);
int bpaired = -1;
jint(obj, "paired", &bpaired);
printf("bt available=%d powered=%d address=%s paired=%d\n",
bavail, bpow, baddr[0] ? baddr : "-", bpaired);
}
/* rfkill 数组 */
{
const char *rf = strstr(js, "\"rfkill\":[");
if (rf) {
const char *ctx2 = NULL;
for (;;) {
char tmp[96];
if (next_obj(rf, "type", &ctx2, tmp, sizeof tmp) != 0) break;
char type[16]; int i = 0;
while (tmp[i] && tmp[i] != '"' && i < 15) { type[i] = tmp[i]; i++; }
type[i] = 0;
int soft = -1, hard = -1;
jint(tmp, "soft", &soft);
jint(tmp, "hard", &hard);
printf("rfkill:%s soft=%d hard=%d\n", type, soft, hard);
}
}
}
return 0;
}
static int cli_iface(const char *name)
{
char js[MEOW_IPC_MAX];
m_net_iface_req_t q;
memset(&q, 0, sizeof q);
if (name) snprintf(q.name, sizeof q.name, "%s", name);
if (cli_ipc_json(MSG_NET_IFACE_REQ, &q, sizeof q, js, sizeof js) != 0) {
fprintf(stderr, "meow_net iface: 守护不可达(%s)\n", g_sock);
return 1;
}
printf("%s\n", js);
return 0;
}
static const char *usage(void)
{
return "status | iface [name] | wifi scan|networks|connect <ssid>[,psk]|disconnect|on|off"
" | bt status|on|off|paired | rfkill [<type> <on|off>] | udhcpc renew|release [if] | help";
}
static int cli_help(void)
{
printf("meow_net — MeowOS 网络管理 CLI(meow_netmgrd 的 IPC 薄壳)\n");
printf("usage: meow_net <%s>\n", usage());
printf("\n读级任意调用者;写级(wifi/bt on|off、connect、rfkill set、udhcpc)仅系统身份,且 HibyOS 模式只读。\n");
return 0;
}
static int cli_run(int n, char **v)
{
const char *cmd = v[0];
const char *arg = (n > 1) ? v[1] : NULL;
const char *arg2 = (n > 2) ? v[2] : NULL;
if (!strcmp(cmd, "status")) return cli_status();
if (!strcmp(cmd, "iface")) return cli_iface(arg);
if (!strcmp(cmd, "help") || !strcmp(cmd, "-h") || !strcmp(cmd, "--help")) return cli_help();
if (!strcmp(cmd, "wifi")) {
if (!arg) { fprintf(stderr, "wifi 子命令缺失(%s)\n", usage()); return 1; }
if (!strcmp(arg, "on") || !strcmp(arg, "off")) return cli_cmd_result(MSG_NET_WIFI_CTL, arg, NULL);
if (!strcmp(arg, "connect")) {
if (!arg2) { fprintf(stderr, "connect 需 <ssid>[,psk]\n"); return 1; }
return cli_cmd_result(MSG_NET_WIFI_CTL, "connect", arg2);
}
return cli_cmd_result(MSG_NET_WIFI_CTL, arg, NULL);
}
if (!strcmp(cmd, "bt")) {
if (!arg) { fprintf(stderr, "bt 子命令缺失(%s)\n", usage()); return 1; }
return cli_cmd_result(MSG_NET_BT_CTL, arg, NULL);
}
if (!strcmp(cmd, "rfkill")) {
if (!arg) return cli_status(); /* 读 rfkill 表 → status JSON 的 rfkill[] */
if (!arg2) { fprintf(stderr, "rfkill 需 <type> <on|off>\n"); return 1; }
char combined[64];
snprintf(combined, sizeof combined, "%s %s", arg, arg2);
return cli_cmd_result(MSG_NET_WIFI_CTL, "rfkill", combined);
}
if (!strcmp(cmd, "udhcpc")) {
if (!arg) { fprintf(stderr, "udhcpc 需 renew|release [if]\n"); return 1; }
char combined[64];
snprintf(combined, sizeof combined, "%s %s", arg, arg2 ? arg2 : "");
return cli_cmd_result(MSG_NET_WIFI_CTL, "udhcpc", combined);
}
fprintf(stderr, "usage: meow_net <%s>\n", usage());
return 1;
}
/* ---------------- main:单一二进制双形态 ---------------- */
int main(int argc, char **argv)
{
const char *e;
if ((e = getenv("MEOW_NET_DIR")) && e[0]) snprintf(g_net_dir, sizeof g_net_dir, "%s", e);
if ((e = getenv("MEOW_NETDEV")) && e[0]) snprintf(g_netdev, sizeof g_netdev, "%s", e);
if ((e = getenv("MEOW_NET_ROUTE")) && e[0]) snprintf(g_net_route, sizeof g_net_route, "%s", e);
if ((e = getenv("MEOW_FIBTRIE")) && e[0]) snprintf(g_fibtrie, sizeof g_fibtrie, "%s", e);
if ((e = getenv("MEOW_RESOLV")) && e[0]) snprintf(g_resolv, sizeof g_resolv, "%s", e);
if ((e = getenv("MEOW_RFKILL_DIR")) && e[0]) snprintf(g_rfkill_dir, sizeof g_rfkill_dir, "%s", e);
if ((e = getenv("MEOW_BT_DIR")) && e[0]) snprintf(g_bt_dir, sizeof g_bt_dir, "%s", e);
if ((e = getenv("MEOW_BOOT_MODE")) && e[0]) snprintf(g_boot_mode, sizeof g_boot_mode, "%s", e);
if ((e = getenv("MEOW_LOG_DIR")) && e[0]) snprintf(g_log_dir, sizeof g_log_dir, "%s", e);
if ((e = getenv("MEOW_BOOTTS_CACHE")) && e[0]) snprintf(g_bootts_cache, sizeof g_bootts_cache, "%s", e);
if ((e = getenv("MEOW_WPA_CLI")) && e[0]) g_wpa_cli = e;
if ((e = getenv("MEOW_HCICONFIG")) && e[0]) g_hciconfig = e;
if ((e = getenv("MEOW_BLUETOOTHCTL")) && e[0]) g_bluetoothctl = e;
if ((e = getenv("MEOW_IP")) && e[0]) g_ip = e;
if ((e = getenv("MEOW_WIFI_IF")) && e[0]) g_wifi_if = e;
if ((e = getenv("MEOW_NET_SOCK")) && e[0]) g_sock = e;
if ((e = getenv("MEOW_NET_POLL_SEC")) && atoi(e) > 0) g_poll_sec = atoi(e);
int i = 1;
for (; i < argc; i++) {
if (!strcmp(argv[i], "--sock") && i + 1 < argc) g_sock = argv[++i];
else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
printf("meow_netmgrd / meow_net (%s)\n", usage());
return 0;
}
else break;
}
/* argv[0] basename=meow_net → 强制 CLI(双形态);meow_netmgrd 空参 → 守护常驻。 */
const char *base = strrchr(argv[0], '/');
base = base ? base + 1 : argv[0];
if (!strcmp(base, "meow_net")) {
if (i >= argc) return cli_help();
return cli_run(argc - i, argv + i);
}
if (i >= argc) return run_daemon(g_sock); /* svcd 空参 → IPC 常驻 */
return cli_run(argc - i, argv + i);
}