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 段(不改既有行)。
1757 lines
66 KiB
C
1757 lines
66 KiB
C
#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);
|
||
}
|