#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 `(argv[0]=meow_net 双形态)或 `meow_netmgrd ` → 一次 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_.log`(无卡降级 /tmp),每次开机新文件(复用 /tmp/meow_bootts * 共享 bootts,一次开机一份);接口状态变化/写操作必记。行格式 `bootts lvl tag kv...`。 * * 编译(mipsel 静态):build_netmgrd.sh;宿主:build_netmgrd.sh host。 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 `;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: 用法 (如 `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 中迭代 `"":"` 开头的扁平条目,取每个对象切片(到 `}`)。首调 *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 [,psk]|disconnect|on|off" " | bt status|on|off|paired | rfkill [ ] | 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 需 [,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 需 \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); }