#define _GNU_SOURCE /* struct ucred / SO_PEERCRED、asprintf 等 GNU/musl 扩展 */ /* meow_powermgr.c — MeowOS 功耗管理专项服务(devdocs/plan/POWER_MANAGER_DESIGN.md,期 1 骨架)。 * * 定位(三服务职责划分,§1):svc_power=一次性系统动作;svc_perf=性能只读聚合; * **powermgr=功耗**——电池深加工(state/cap/mV/续航估算)、经验功耗模型估算与分解。 * 期 1 范围(§9):svcd 托管骨架 + IPC(MSG_PM_QUERY/RESULT) + CLI status/estimate/model + * 权威充电链复用 + 经验模型;history/top/events/policy 为期 2/3 桩。 * * 单一二进制双形态(同 svc_power): * - svcd 空参拉起(板载 services.d/meow_powermgr.conf,trigger=boot/restart=on-fail)→ * IPC 服务常驻:bind /usr/data/meow_pm.sock(--sock/MEOW_PM_SOCK 覆盖), * MSG_HELLO→MSG_ACK、MSG_PM_QUERY(0x00D0)→MSG_PM_RESULT(0x00D1, JSON)。 * - `meow_power `(argv[0] 双形态)或 `meow_powermgr ` → 一次性 CLI(root 免鉴权)。 * CLI 优先走 IPC(拿守护进程的容量差分速率),守护不可达降级为直读 sysfs(rate=null)。 * * HibyOS 只读降级(用户定案 2026-09-01 §附 1):trigger=boot + 进程内读 /tmp/meow_boot_mode: * "meow"=全功能;否则(hiby/缺失)=只采样电池,绝不施策略、不碰 wlan0/bt/声卡(§2 门控语义, * 与 audiod 的 trigger=manual+exit(0) 不同:本服务读侧无害故常驻只读)。每采样拍重判,可热切换。 * * 权威充电链(0-diff 照抄 svc_power charging_status_str,真机实测 2026-09-01): * mp2731-charger/status(权威)→ fusb302-typec-source/online → battery/status+ac/online 兜底。 * * 硬件事实(devdocs/plan/POWER_HW_FACTS.md,期 0 实测): * - battery/ 无 current_now/charge_full/cycle_count/temp → 实时功率不可 measured, * 续航=容量差分 derived + 功耗=经验模型 estimated(confidence 如实标注,不得呈现为实测); * - 背光量程 0-101(max_brightness 实测,非 255);fb0/blank 不可读 → 息屏=actual_brightness==0; * - 无 cpufreq(省电档无降频控制点);无 thermal zones;rfkill 存在(期 3 候选控制点)。 * * 经验模型系数:板载 /usr/data/meow/power_model.conf(key=value,缺失用内置默认); * **全部系数为经验初值,待真机标定**(§7 项 7/8 标定实验未做)。 * * 编译(mipsel 静态):build_powermgr.sh;宿主:build_powermgr.sh host。 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "meow_ipc.h" /* 帧格式/消息号 + MSG_PM 系列 + PM_SUBSET_* + m_pm_*(号段 0x00D0-0x00D7) */ /* ---------------- 路径(全部可经 env 覆盖,宿主/qemu 桩测试用) ---------------- */ static char g_ps_dir[128] = "/sys/class/power_supply"; /* MEOW_PS_DIR */ static char g_bl_dir[128] = "/sys/class/backlight/backlight_pwm0"; /* MEOW_BL_DIR */ static char g_net_dir[128] = "/sys/class/net"; /* MEOW_NET_DIR */ 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_netdev[160] = "/proc/net/dev"; /* MEOW_NETDEV */ static char g_proc_stat[160] = "/proc/stat"; /* MEOW_PROC_STAT */ static char g_boot_mode[160] = "/tmp/meow_boot_mode"; /* MEOW_BOOT_MODE */ static char g_model_conf[160] = "/usr/data/meow/power_model.conf"; /* MEOW_PM_MODEL */ static char g_audio_sock[128] = "/usr/data/meow_audio.sock"; /* MEOW_AUDIO_SOCK */ static const char *g_sock = "/usr/data/meow_pm.sock"; /* MEOW_PM_SOCK/--sock */ #define SAMPLE_SEC_DEF 30 /* 电池采样/状态刷新节拍(秒;MEOW_PM_SAMPLE_SEC 覆盖,测试用) */ #define RATE_MIN_SPAN_DEF 120 /* 容量差分速率的最小时间窗(秒;MEOW_PM_MIN_SPAN_SEC 覆盖) */ #define RING_N 240 /* 采样环容量(30s×240=2h) */ static int g_sample_sec = SAMPLE_SEC_DEF; static int g_rate_min_span = RATE_MIN_SPAN_DEF; /* ---------------- 极简日志(stderr;svcd supervisor 捕获) ---------------- */ static void svc_log(const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "[meow_powermgr] "); 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] = 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; char *p = buf; while (*p == ' ' || *p == '\t') p++; return atoi(p); } /* 拼 power_supply 节点路径:ps_path(b, cap, "battery/capacity") */ static void ps_path(char *buf, size_t cap, const char *node) { snprintf(buf, cap, "%s/%s", g_ps_dir, node); } /* ---------------- 权威充电链(0-diff 照抄 svc_power charging_status_str 口径) ---------------- * mp2731-charger/status(Charging/Full=充电中)为权威;不可读回退 fusb302-typec-source/online==1 * (USB-C PD 控制器插线检测);再回退旧逻辑 battery/status==Charging || ac/online==1。 */ static void charging_status_str(char *st, int cap) { char p[192]; st[0] = 0; ps_path(p, sizeof p, "mp2731-charger/status"); if (read_file_txt(p, st, cap) > 0 && st[0]) return; /* 权威源直用 */ ps_path(p, sizeof p, "fusb302-typec-source/online"); int on = read_file_int_def(p, -1); if (on >= 0) { snprintf(st, cap, "%s", on == 1 ? "Charging" : "Discharging"); return; } ps_path(p, sizeof p, "battery/status"); read_file_txt(p, st, cap); if (!strstr(st, "Charging") && !strstr(st, "Full")) { ps_path(p, sizeof p, "ac/online"); if (read_file_int_def(p, 0) == 1) snprintf(st, cap, "Charging"); } } /* ---------------- 电池深加工(POWER_MANAGER_DESIGN §3.1 + POWER_HW_FACTS 项 1) ---------------- */ typedef struct { int pct; /* -1=未知 */ int mv; /* -1=未知(voltage_now µV→mV) */ char state[32]; /* Charging/Discharging/Full(权威链) */ int charging; /* state 含 Charging/Full */ char health[16]; /* battery/health 字符串(如 Good);""=无 → null */ char tech[24]; /* battery/technology;""=无 → null */ int temp_c10; /* battery/temp(本机无 → -1 → null) */ int charger_temp_c10; /* mp2731-charger/temp(-1=无);÷10=℃ */ int cycle; /* battery/cycle_count(本机无 → -1 → null) */ } batt_t; static void batt_read(batt_t *b) { char p[192]; memset(b, 0, sizeof *b); b->pct = -1; b->mv = -1; b->temp_c10 = -1; b->charger_temp_c10 = -1; b->cycle = -1; ps_path(p, sizeof p, "battery/capacity"); b->pct = read_file_int_def(p, -1); ps_path(p, sizeof p, "battery/voltage_now"); int uv = read_file_int_def(p, -1); if (uv >= 0) b->mv = uv / 1000; charging_status_str(b->state, sizeof b->state); b->charging = (strstr(b->state, "Charging") || strstr(b->state, "Full")) ? 1 : 0; ps_path(p, sizeof p, "battery/health"); read_file_txt(p, b->health, sizeof b->health); ps_path(p, sizeof p, "battery/technology"); read_file_txt(p, b->tech, sizeof b->tech); ps_path(p, sizeof p, "battery/temp"); b->temp_c10 = read_file_int_def(p, -1); ps_path(p, sizeof p, "mp2731-charger/temp"); b->charger_temp_c10 = read_file_int_def(p, -1); ps_path(p, sizeof p, "battery/cycle_count"); b->cycle = read_file_int_def(p, -1); } /* ---------------- 经验功耗模型(§3.2;系数全部经验初值,待真机标定) ---------------- */ typedef struct { int nominal_mah; /* 标称容量 mAh(默认 1600:axp energy_full_design 旁证,待标定) */ int bl_low_mw; /* 背光低档(亮度 1-33%) */ int bl_mid_mw; /* 背光中档(34-66%) */ int bl_high_mw; /* 背光高档(67-100%) */ int panel_on_mw; /* 面板亮(背光>0 时附加) */ int wifi_unassoc_mw; /* WiFi up 未关联(v1 状态机无此档判据,预留) */ int wifi_idle_mw; /* WiFi 关联空闲 */ int wifi_tx_mw; /* WiFi 传输中(netdev 增量判) */ int bt_up_mw; /* BT up 空闲 */ int bt_conn_mw; /* BT 有连接(连接数判据期 2,预留) */ int audio_idle_mw; /* 音频链无流(FPGA 静态) */ int audio_play_mw; /* 音频链播放(DAC+FPGA) */ int cpu_full_mw; /* CPU 满载功耗(util%×此项) */ int baseline_mw; /* 基线(SoC/DDR/PMIC 静态) */ } pm_model_t; static void model_defaults(pm_model_t *m) { /* 经验初值,全部待真机标定(POWER_HW_FACTS §1 项 7/8:标定实验未做)。 */ m->nominal_mah = 1600; m->bl_low_mw = 150; m->bl_mid_mw = 450; m->bl_high_mw = 900; m->panel_on_mw = 80; m->wifi_unassoc_mw = 80; m->wifi_idle_mw = 200; m->wifi_tx_mw = 600; m->bt_up_mw = 60; m->bt_conn_mw = 200; m->audio_idle_mw = 50; m->audio_play_mw = 250; m->cpu_full_mw = 400; m->baseline_mw = 300; } typedef struct { const char *key; int *ptr; } model_ent_t; /* 读模型 conf(key=value,# 注释;缺失键留默认)。返回 0=用了 conf,1=conf 缺失(纯内置默认)。 */ static int model_load(pm_model_t *m) { model_defaults(m); model_ent_t ents[] = { { "nominal_mah", &m->nominal_mah }, { "bl_low_mw", &m->bl_low_mw }, { "bl_mid_mw", &m->bl_mid_mw }, { "bl_high_mw", &m->bl_high_mw }, { "panel_on_mw", &m->panel_on_mw }, { "wifi_unassoc_mw", &m->wifi_unassoc_mw }, { "wifi_idle_mw", &m->wifi_idle_mw }, { "wifi_tx_mw", &m->wifi_tx_mw }, { "bt_up_mw", &m->bt_up_mw }, { "bt_conn_mw", &m->bt_conn_mw }, { "audio_idle_mw", &m->audio_idle_mw }, { "audio_play_mw", &m->audio_play_mw }, { "cpu_full_mw", &m->cpu_full_mw }, { "baseline_mw", &m->baseline_mw }, }; FILE *f = fopen(g_model_conf, "r"); if (!f) return 1; char line[160]; while (fgets(line, sizeof line, f)) { char *p = line; while (*p == ' ' || *p == '\t') p++; if (*p == '#' || *p == '\n' || !*p) continue; char *eq = strchr(p, '='); if (!eq) continue; *eq = 0; char *kend = eq; while (kend > p && (kend[-1] == ' ' || kend[-1] == '\t')) *--kend = 0; int v = atoi(eq + 1); for (size_t i = 0; i < sizeof ents / sizeof ents[0]; i++) if (!strcmp(p, ents[i].key)) { *ents[i].ptr = v; break; } } fclose(f); return 0; } /* ---------------- 采样环(守护进程;容量差分速率 → 续航估算,§3.1/§3.3 derived) ---------------- */ typedef struct { long mono; int pct; int charging; } sample_t; static sample_t g_ring[RING_N]; static int g_rcount = 0; /* 已入环条数(≤RING_N;覆盖写最旧) */ static long mono_now(void) { struct timespec tp; clock_gettime(CLOCK_MONOTONIC, &tp); return tp.tv_sec; } static void ring_push(const batt_t *b) { if (b->pct < 0) return; sample_t s; s.mono = mono_now(); s.pct = b->pct; s.charging = b->charging; if (g_rcount < RING_N) g_ring[g_rcount++] = s; else { memmove(g_ring, g_ring + 1, sizeof g_ring - sizeof g_ring[0]); g_ring[RING_N - 1] = s; } } /* 容量差分平均速率:rate_x100 = (Δpct×100)/min,放电为负、充电为正。 * 用环内最旧↔最新同向(同 charging 态)样本对;跨度 < g_rate_min_span 或 |Δpct|<1 → 不可得。 * 返回 1=有速率。 */ static int ring_rate(int *rate_x100) { if (g_rcount < 2) return 0; const sample_t *nw = &g_ring[g_rcount - 1]; const sample_t *ol = NULL; for (int i = 0; i < g_rcount - 1; i++) if (g_ring[i].charging == nw->charging) { ol = &g_ring[i]; break; } if (!ol) return 0; long span = nw->mono - ol->mono; int dpct = nw->pct - ol->pct; if (span < g_rate_min_span || dpct == 0) return 0; *rate_x100 = (int)((long)dpct * 100 * 60 / span); if (*rate_x100 == 0) return 0; return 1; } /* ---------------- 子系统状态读取(经验模型输入;全部只读) ---------------- */ /* 背光/面板:actual_brightness 归一 0-100%(量程读 max_brightness,本机 0-101,HW_FACTS 项 6)。 * 返回亮度%(-1=未知);*on = 面板亮(亮度>0;fb0/blank 不可读故以此代理息屏判定)。 */ static int bl_read_pct(int *on) { char p[192]; snprintf(p, sizeof p, "%s/max_brightness", g_bl_dir); int mx = read_file_int_def(p, -1); if (mx <= 0) { *on = -1; return -1; } snprintf(p, sizeof p, "%s/actual_brightness", g_bl_dir); int v = read_file_int_def(p, -1); if (v < 0) { snprintf(p, sizeof p, "%s/brightness", g_bl_dir); v = read_file_int_def(p, -1); } if (v < 0) { *on = -1; return -1; } if (v > mx) v = mx; *on = v > 0 ? 1 : 0; return v * 100 / mx; } /* WiFi:0=off(接口不存在/operstate 非 up)1=idle 2=tx(netdev 增量>阈值)。 * 关联/未关联档 v1 无判据(要 wpa_cli,期 2 再细化)——up 即按 idle 起算,诚实标注。 */ static int wifi_read_state(long *rx_out, long *tx_out) { char p[192]; snprintf(p, sizeof p, "%s/wlan0/operstate", g_net_dir); char os[32] = ""; if (read_file_txt(p, os, sizeof os) <= 0 || strcmp(os, "up") != 0) return 0; FILE *f = fopen(g_netdev, "r"); if (!f) return 1; char line[256]; long rx = -1, tx = -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, "wlan0") != 0) continue; rx = strtol(c + 1, NULL, 10); char *tk = c + 1; for (int i = 0; i < 8; i++) { tk = strchr(tk, ' '); if (!tk) break; while (*tk == ' ') tk++; } /* 第 9 列 = tx bytes */ char *save = NULL; char *w = strtok_r(c + 1, " \t", &save); for (int i = 0; w && i < 8; i++) w = strtok_r(NULL, " \t", &save); if (w) tx = strtol(w, NULL, 10); break; } fclose(f); if (rx_out) *rx_out = rx; if (tx_out) *tx_out = tx; return 1; } /* BT:rfkill soft 块→off;hci0 存在且未被块→up。连接数判据期 2。 */ static int bt_read_state(void) { char p[448]; /* rfkill_dir + "/" + d_name(≤255) + "/type|/soft" 充裕 */ DIR *d = opendir(g_rfkill_dir); int blocked = 0, seen = 0; if (d) { struct dirent *de; while ((de = readdir(d))) { if (strncmp(de->d_name, "rfkill", 6) != 0) continue; char t[32] = ""; snprintf(p, sizeof p, "%s/%s/type", g_rfkill_dir, de->d_name); if (read_file_txt(p, t, sizeof t) <= 0 || strcmp(t, "bluetooth") != 0) continue; seen = 1; snprintf(p, sizeof p, "%s/%s/soft", g_rfkill_dir, de->d_name); if (read_file_int_def(p, 0) == 1) blocked = 1; } closedir(d); } snprintf(p, sizeof p, "%s/hci0", g_bt_dir); int hci0 = access(p, F_OK) == 0; if (blocked) return 0; if (!hci0 && !seen) return 0; /* 无任何 BT 踪迹 → off */ if (!hci0) return 0; return 1; /* up 空闲档(连接判定期 2) */ } /* 音频链:meow 模式下经 audiod IPC(MSG_AUDIO_QUERY→MSG_AUDIO_STATE 文本)判 active。 * -1=audiod 不可达(unknown→0mW) 0=无流 1=播放中。Hiby 只读模式绝不调用(门控红线)。 * 注意:audiod dispatch 无 MSG_HELLO 分支(查询无需握手),直接发 QUERY。 */ static int x_write_full(int fd, const uint8_t *buf, size_t n); static int x_read_full(int fd, uint8_t *buf, size_t n); static int audio_query_active(void) { int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) return -1; struct timeval tv = { 1, 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_audio_sock, sizeof a.sun_path - 1); if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; } /* 直接 QUERY → STATE(无 HELLO 握手,见上注) */ uint8_t hdr[MEOW_IPC_HDR_LEN]; uint32_t magic = MEOW_IPC_MAGIC, zero = 0, rid = 1; uint16_t ver = MEOW_IPC_VERSION, t = MSG_AUDIO_QUERY; memcpy(hdr + 0, &magic, 4); memcpy(hdr + 4, &ver, 2); memcpy(hdr + 6, &t, 2); memcpy(hdr + 8, &rid, 4); memcpy(hdr + 12, &zero, 4); if (x_write_full(fd, hdr, sizeof hdr) < 0) { close(fd); return -1; } if (x_read_full(fd, hdr, sizeof hdr) < 0) { close(fd); return -1; } uint32_t rlen; uint16_t rt; memcpy(&rt, hdr + 6, 2); memcpy(&rlen, hdr + 12, 4); if (rt != MSG_AUDIO_STATE || rlen > MEOW_IPC_MAX) { close(fd); return -1; } uint8_t pl[MEOW_IPC_MAX]; if (rlen && x_read_full(fd, pl, rlen) < 0) { close(fd); return -1; } close(fd); pl[rlen < MEOW_IPC_MAX ? rlen : MEOW_IPC_MAX - 1] = 0; return strstr((const char *)pl, "active=1") ? 1 : 0; } /* CPU util%(/proc/stat 差分;首次/失败 → -1) */ static int cpu_util_pct(long prev_total, long prev_idle, long *out_total, long *out_idle) { FILE *f = fopen(g_proc_stat, "r"); if (!f) return -1; char line[256]; if (!fgets(line, sizeof line, f)) { fclose(f); return -1; } fclose(f); if (strncmp(line, "cpu ", 4) != 0) return -1; long long v[8] = {0}; int n = sscanf(line + 4, "%lld %lld %lld %lld %lld %lld %lld %lld", &v[0], &v[1], &v[2], &v[3], &v[4], &v[5], &v[6], &v[7]); if (n < 4) return -1; long idle = (long)(v[3] + v[4]); /* idle + iowait */ long total = 0; for (int i = 0; i < 8; i++) total += (long)v[i]; *out_total = total; *out_idle = idle; if (prev_total < 0 || total <= prev_total) return -1; long dt = total - prev_total, di = idle - prev_idle; if (dt <= 0) return -1; int u = (int)((dt - di) * 100 / dt); return u < 0 ? 0 : (u > 100 ? 100 : u); } /* ---------------- 估算结果 ---------------- */ typedef struct { int total_mw; int baseline_mw, cpu_mw, bl_mw, panel_mw, wifi_mw, bt_mw, audio_mw; int cpu_util; /* -1=未知 */ int brightness_pct; /* -1=未知 */ int wifi_state; /* 0/1/2 */ int bt_state; /* 0/1 */ int audio_state; /* -1/0/1 */ int degraded; /* 1=Hiby 只读模式:不碰 wlan0/bt/声卡,分量全 0 */ } est_t; /* 经验模型合成。full=0(Hiby 只读模式)→ 分量全 0 + degraded 标记(不碰 wlan0/bt/声卡)。 * prev_rx/prev_tx 非 NULL 时:作 netdev 增量基准(>4096B/拍→tx 档)并回存新计数(守护用); * NULL(CLI 降级直读)→ 无基准,up 即 idle。 */ static void est_compute(const pm_model_t *m, int full, int cpu_util, long *prev_rx, long *prev_tx, est_t *e) { memset(e, 0, sizeof *e); e->cpu_util = -1; e->brightness_pct = -1; e->audio_state = -1; if (!full) { e->degraded = 1; return; } e->baseline_mw = m->baseline_mw; /* 屏幕 */ int on = -1; int bp = bl_read_pct(&on); e->brightness_pct = bp; if (on == 1) { e->panel_mw = m->panel_on_mw; e->bl_mw = bp <= 33 ? m->bl_low_mw : (bp <= 66 ? m->bl_mid_mw : m->bl_high_mw); } /* WiFi */ long rx = -1, tx = -1; e->wifi_state = wifi_read_state(&rx, &tx); if (e->wifi_state > 0) { if (prev_rx && *prev_rx >= 0 && rx >= 0 && (rx - *prev_rx) + (tx - *prev_tx) > 4096) e->wifi_state = 2; e->wifi_mw = e->wifi_state == 2 ? m->wifi_tx_mw : m->wifi_idle_mw; } if (prev_rx) { *prev_rx = rx; *prev_tx = tx; } /* BT */ e->bt_state = bt_read_state(); e->bt_mw = e->bt_state ? m->bt_up_mw : 0; /* 音频链 */ e->audio_state = audio_query_active(); e->audio_mw = e->audio_state == 1 ? m->audio_play_mw : (e->audio_state == 0 ? m->audio_idle_mw : 0); /* CPU */ e->cpu_util = cpu_util; if (cpu_util >= 0) e->cpu_mw = cpu_util * m->cpu_full_mw / 100; e->total_mw = e->baseline_mw + e->cpu_mw + e->bl_mw + e->panel_mw + e->wifi_mw + e->bt_mw + e->audio_mw; } /* ---------------- JSON 构建(vsnprintf 助手法,截断分析免疫) ---------------- */ 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; } } 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(w, "\"%s\":\"%s\",", key, s); } /* 续航/速率段(battery 子集内)。rate_x100 不可得 → 三字段全 null。 */ static void jadd_rate(jw_t *w, const batt_t *b, int have_rate, int rate_x100) { if (!have_rate || b->pct < 0) { jadd(w, "\"rate_pct_min_x100\":null,\"est_minutes_left\":null," "\"est_minutes_full\":null,\"rate_confidence\":null,"); return; } jadd(w, "\"rate_pct_min_x100\":%d,", rate_x100); if (rate_x100 < 0) { /* 放电 → 剩余分钟 */ jadd(w, "\"est_minutes_left\":%d,\"est_minutes_full\":null,", b->pct * 100 / -rate_x100); } else { /* 充电 → 充满分钟 */ jadd(w, "\"est_minutes_left\":null,\"est_minutes_full\":%d,", (100 - b->pct) * 100 / rate_x100); } jadd(w, "\"rate_confidence\":\"derived\","); } /* 聚合 JSON(subset 位选段;0=全部)。守护/CLI 降级直读共用。 * have_rate/cpu_util/prev_rx/prev_tx 由调用方提供(守护=环/差分态;直读=瞬测)。 */ static void build_json(uint32_t subset, int full, const batt_t *b, int have_rate, int rate_x100, const est_t *e, char *out, size_t cap) { if (subset == 0) subset = PM_SUBSET_BATTERY | PM_SUBSET_ESTIMATE | PM_SUBSET_BREAKDOWN | PM_SUBSET_POLICY; jw_t w = { out, cap, 0 }; out[0] = 0; jadd(&w, "{\"mode\":\"%s\",\"ts\":%ld,", full ? "meow" : "readonly", (long)time(NULL)); if (subset & PM_SUBSET_BATTERY) { jadd(&w, "\"battery\":{"); jadd_str_or_null(&w, "state", b->state); jadd_int_or_null(&w, "pct", b->pct); jadd_int_or_null(&w, "mv", b->mv); jadd_str_or_null(&w, "health", b->health); jadd_str_or_null(&w, "technology", b->tech); jadd_int_or_null(&w, "temp_c10", b->temp_c10); jadd_int_or_null(&w, "charger_temp_c10", b->charger_temp_c10); jadd_int_or_null(&w, "cycle_count", b->cycle); jadd_rate(&w, b, have_rate, rate_x100); jadd(&w, "\"nominal_mah_note\":\"nominal_mah 为模型系数(默认1600,待标定)\"}"); } if (subset & PM_SUBSET_ESTIMATE) { jadd(&w, ",\"estimate\":{"); if (e->degraded) { jadd(&w, "\"est_mw\":null,\"confidence\":\"none\",\"degraded\":true}"); } else { jadd(&w, "\"est_mw\":%d,\"confidence\":\"estimated\",\"calibrated\":false}", e->total_mw); } } if (subset & PM_SUBSET_BREAKDOWN) { jadd(&w, ",\"breakdown\":{"); if (e->degraded) { jadd(&w, "\"degraded\":true}"); } else { jadd(&w, "\"baseline_mw\":%d,", e->baseline_mw); jadd(&w, "\"cpu_mw\":%d,", e->cpu_mw); jadd_int_or_null(&w, "cpu_util_pct", e->cpu_util); jadd(&w, "\"backlight_mw\":%d,\"panel_mw\":%d,", e->bl_mw, e->panel_mw); jadd_int_or_null(&w, "brightness_pct", e->brightness_pct); jadd(&w, "\"wifi_mw\":%d,\"wifi_state\":\"%s\",", e->wifi_mw, e->wifi_state == 2 ? "tx" : (e->wifi_state == 1 ? "idle" : "off")); jadd(&w, "\"bt_mw\":%d,\"bt_state\":\"%s\",", e->bt_mw, e->bt_state ? "up" : "off"); jadd(&w, "\"audio_mw\":%d,\"audio_state\":\"%s\"", e->audio_mw, e->audio_state == 1 ? "playing" : (e->audio_state == 0 ? "idle" : "unknown")); jadd(&w, "}"); } } if (subset & PM_SUBSET_POLICY) jadd(&w, ",\"policy\":null"); /* 期 3 桩 */ jadd(&w, "}"); } /* ---------------- 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); } static int send_pm_result(int fd, uint32_t req_id, uint32_t subset, const char *json) { m_pm_result_t r; memset(&r, 0, sizeof r); r.code = MEOW_OK; r.subset = subset; size_t jl = strlen(json); if (jl > sizeof r.json - 1) jl = sizeof r.json - 1; r.json_len = (uint16_t)jl; memcpy(r.json, json, jl); r.json[jl] = 0; return send_frame(fd, MSG_PM_RESULT, req_id, &r, (uint32_t)(12 + jl + 1)); } /* ---------------- 守护进程态 ---------------- */ static int g_full_mode = 0; /* meow=1 全功能;0=只读降级(hiby/缺失) */ static long g_cpu_prev_total = -1, g_cpu_prev_idle = 0; static long g_wifi_prev_rx = -1, g_wifi_prev_tx = 0; static pm_model_t g_model; 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; } /* 采样拍:模式热切换 + 电池入环 + CPU 差分基准推进。(WiFi 计数基准由 est_compute 回存。) */ static void daemon_tick(void) { int fm = boot_mode_is_meow(); if (fm != g_full_mode) { svc_log("mode -> %s (%s %s)", fm ? "full(meow)" : "readonly", g_boot_mode, fm ? "=meow" : "!=meow/missing"); g_full_mode = fm; } batt_t b; batt_read(&b); ring_push(&b); long t = -1, i = 0; cpu_util_pct(g_cpu_prev_total, g_cpu_prev_idle, &t, &i); g_cpu_prev_total = t; g_cpu_prev_idle = i; } static void handle_conn(int c) { 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_PM_QUERY) { /* 读级:任意调用者放行(§6.1 鉴权分级:QUERY=读级;POLICY_SET 期 3 才接动作级)。 * 查询不推进采样环(速率由节拍样本积累,防查询刷屏污染时间窗);电池现读保证新鲜。 */ m_pm_query_t q; memset(&q, 0, sizeof q); if (rlen > sizeof q) rlen = sizeof q; memcpy(&q, payload, rlen); batt_t b; batt_read(&b); int rate_x100 = 0; int have_rate = ring_rate(&rate_x100); long t = -1, i = 0; int cpu_util = cpu_util_pct(g_cpu_prev_total, g_cpu_prev_idle, &t, &i); if (t >= 0) { g_cpu_prev_total = t; g_cpu_prev_idle = i; } est_t e; est_compute(&g_model, g_full_mode, cpu_util, &g_wifi_prev_rx, &g_wifi_prev_tx, &e); char json[MEOW_IPC_MAX]; build_json(q.subset, g_full_mode, &b, have_rate, rate_x100, &e, json, sizeof json); send_pm_result(c, req_id, q.subset, json); continue; } send_err(c, req_id, MEOW_ERR_INVAL, "unknown type (期1仅 PM_QUERY)"); break; } } 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; } model_load(&g_model); g_full_mode = boot_mode_is_meow(); svc_log("meow_powermgr listening on %s (mode=%s, sample=%ds, model=%s)", sock, g_full_mode ? "full" : "readonly", g_sample_sec, access(g_model_conf, R_OK) == 0 ? g_model_conf : "builtin-default(待标定)"); long next_tick = 0; /* 立即首拍 */ while (!g_stop) { long now = mono_now(); if (now >= next_tick) { daemon_tick(); next_tick = now + g_sample_sec; } int wait_ms = (int)((next_tick - mono_now()) * 1000); if (wait_ms < 50) wait_ms = 50; if (wait_ms > g_sample_sec * 1000) wait_ms = g_sample_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_powermgr exit"); close(lfd); unlink(sock); return 0; } /* ---------------- CLI:极简 JSON 字段提取(自产 JSON 固定形状,不做通用解析) ---------------- */ 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; } /* 0=取到整数;1=字段为 null/缺失(*out 不动) */ static int jint(const char *js, const char *key, int *out) { const char *p = jfind(js, key); if (!p || strncmp(p, "null", 4) == 0) 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; } /* CLI 查询:优先 IPC(守护有容量差分速率);守护不可达 → 直读 sysfs 降级(rate=null, * cpu_util 用 200ms 双采样瞬测,wifi 无增量基准则 idle)。返回 0=来自守护,1=降级直读。 */ static int pm_query_json(uint32_t subset, char *out, size_t cap) { int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd >= 0) { struct timeval tv = { 1, 500000 }; 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) { if (send_frame(fd, MSG_HELLO, 1, NULL, 0) == 0) { uint8_t hdr[MEOW_IPC_HDR_LEN]; uint8_t pl[sizeof(m_pm_result_t)]; if (x_read_full(fd, hdr, sizeof hdr) == 0) { uint32_t rl; memcpy(&rl, hdr + 12, 4); if (rl > sizeof pl) rl = sizeof pl; if (rl && x_read_full(fd, pl, rl) < 0) { close(fd); goto direct; } m_pm_query_t q; memset(&q, 0, sizeof q); q.subset = subset; if (send_frame(fd, MSG_PM_QUERY, 2, &q, sizeof q) == 0 && x_read_full(fd, hdr, sizeof hdr) == 0) { uint16_t rt; memcpy(&rt, hdr + 6, 2); memcpy(&rl, hdr + 12, 4); if (rt == MSG_PM_RESULT && rl >= 12 && rl <= sizeof pl && x_read_full(fd, pl, rl) == 0) { m_pm_result_t r; memset(&r, 0, sizeof r); memcpy(&r, pl, rl); size_t jl = r.json_len; if (jl >= cap) jl = cap - 1; memcpy(out, r.json, jl); out[jl] = 0; close(fd); return 0; } } } } } close(fd); } direct: { batt_t b; batt_read(&b); pm_model_t m; model_load(&m); long t1 = -1, i1 = 0, t2, i2; cpu_util_pct(-1, 0, &t1, &i1); usleep(200 * 1000); int cu = cpu_util_pct(t1, i1, &t2, &i2); int full = boot_mode_is_meow(); est_t e; est_compute(&m, full, cu, NULL, NULL, &e); build_json(subset, full, &b, 0, 0, &e, out, cap); return 1; } } /* ---------------- CLI 子命令 ---------------- */ static void fmt_min(char *out, size_t cap, int min) { if (min >= 120) snprintf(out, cap, "%dh%02dm", min / 60, min % 60); else snprintf(out, cap, "%dmin", min); } static int cli_status(void) { char js[MEOW_IPC_MAX]; int via = pm_query_json(PM_SUBSET_BATTERY, js, sizeof js); char state[32], health[16], tech[24]; int pct = -1, mv = -1, rate = 0, el = -1, ef = -1, ctemp = -1, cycle = -1, temp = -1; jstr(js, "state", state, sizeof state); jstr(js, "health", health, sizeof health); jstr(js, "technology", tech, sizeof tech); jint(js, "pct", &pct); jint(js, "mv", &mv); jint(js, "temp_c10", &temp); jint(js, "charger_temp_c10", &ctemp); jint(js, "cycle_count", &cycle); int have_rate = jint(js, "rate_pct_min_x100", &rate) == 0; jint(js, "est_minutes_left", &el); jint(js, "est_minutes_full", &ef); char mode[16]; jstr(js, "mode", mode, sizeof mode); printf("state=%s cap=%d%%", state[0] ? state : "?", pct); if (mv >= 0) printf(" mv=%d", mv); printf(" health=%s", health[0] ? health : "null"); printf(" tech=%s", tech[0] ? tech : "null"); if (temp >= 0) printf(" temp=%d.%dC", temp / 10, temp % 10); else printf(" temp=null"); if (ctemp >= 0) printf(" charger_temp=%d.%dC", ctemp / 10, ctemp % 10); if (cycle >= 0) printf(" cycle=%d", cycle); if (have_rate) { printf(" rate=%s%d.%02d%%/min", rate < 0 ? "-" : "+", rate < 0 ? -rate / 100 : rate / 100, rate < 0 ? -rate % 100 : rate % 100); char tb[24]; if (el >= 0) { fmt_min(tb, sizeof tb, el); printf(" est_left=%s", tb); } else printf(" est_left=null"); if (ef >= 0) { fmt_min(tb, sizeof tb, ef); printf(" est_full=%s", tb); } else printf(" est_full=null"); printf(" conf=derived"); } else { printf(" rate=null est_left=null est_full=null"); } printf(" mode=%s src=%s\n", mode[0] ? mode : "?", via == 0 ? "daemon" : "direct"); if (via != 0) fprintf(stderr, "note: powermgr 守护不可达,降级直读(无容量差分速率 → rate=null)\n"); return 0; } static int cli_estimate(void) { char js[MEOW_IPC_MAX]; int via = pm_query_json(PM_SUBSET_ESTIMATE | PM_SUBSET_BREAKDOWN, js, sizeof js); if (strstr(js, "\"degraded\":true")) { printf("degraded: HibyOS 只读模式——只采样电池,不施功耗估算(不碰 wlan0/bt/声卡)\n"); return 0; } int total = -1, base = 0, cpu = 0, cu = -1, bl = 0, panel = 0, bp = -1, wifi = 0, bt = 0, audio = 0; char conf[24], ws[12], bs[12], as[12]; jint(js, "est_mw", &total); jstr(js, "confidence", conf, sizeof conf); jint(js, "baseline_mw", &base); jint(js, "cpu_mw", &cpu); jint(js, "cpu_util_pct", &cu); jint(js, "backlight_mw", &bl); jint(js, "panel_mw", &panel); jint(js, "brightness_pct", &bp); jint(js, "wifi_mw", &wifi); jint(js, "bt_mw", &bt); jint(js, "audio_mw", &audio); jstr(js, "wifi_state", ws, sizeof ws); jstr(js, "bt_state", bs, sizeof bs); jstr(js, "audio_state", as, sizeof as); printf("est_mw=%d confidence=%s calibrated=false (系数全部经验初值,待真机标定)\n", total, conf[0] ? conf : "estimated"); char cu_s[16], bp_s[16]; if (cu >= 0) snprintf(cu_s, sizeof cu_s, "%d%%", cu); else snprintf(cu_s, sizeof cu_s, "null"); if (bp >= 0) snprintf(bp_s, sizeof bp_s, "%d%%", bp); else snprintf(bp_s, sizeof bp_s, "null"); printf("baseline=%d cpu=%d(util=%s) backlight=%d panel=%d(bri=%s) wifi=%d(%s) bt=%d(%s) audio=%d(%s)\n", base, cpu, cu_s, bl, panel, bp_s, wifi, ws[0] ? ws : "?", bt, bs[0] ? bs : "?", audio, as[0] ? as : "?"); if (via != 0) fprintf(stderr, "note: powermgr 守护不可达,降级直读(cpu_util=200ms 瞬测,wifi 无增量判 tx)\n"); return 0; } static int cli_model(void) { pm_model_t m; int def = model_load(&m); printf("# meow_powermgr 经验功耗模型(POWER_MANAGER_DESIGN §3.2)\n"); printf("# 系数来源: %s\n", def ? "内置默认(conf 缺失)" : g_model_conf); printf("# ⚠ 全部系数为经验初值,待真机标定(POWER_HW_FACTS §1 项 7/8 标定实验未做)\n"); printf("nominal_mah=%d\n", m.nominal_mah); printf("baseline_mw=%d\n", m.baseline_mw); printf("bl_low_mw=%d\n", m.bl_low_mw); printf("bl_mid_mw=%d\n", m.bl_mid_mw); printf("bl_high_mw=%d\n", m.bl_high_mw); printf("panel_on_mw=%d\n", m.panel_on_mw); printf("wifi_unassoc_mw=%d\n", m.wifi_unassoc_mw); printf("wifi_idle_mw=%d\n", m.wifi_idle_mw); printf("wifi_tx_mw=%d\n", m.wifi_tx_mw); printf("bt_up_mw=%d\n", m.bt_up_mw); printf("bt_conn_mw=%d\n", m.bt_conn_mw); printf("audio_idle_mw=%d\n", m.audio_idle_mw); printf("audio_play_mw=%d\n", m.audio_play_mw); printf("cpu_full_mw=%d\n", m.cpu_full_mw); return 0; } static int cli_stub(const char *what, const char *phase) { printf("meow_power %s: 期 %s 落地,当前未实现(POWER_MANAGER_DESIGN §9 分期)\n", what, phase); return 1; } static const char *cli_usage(void) { return "status|estimate|model|history[小时]|top|events|policy(后四者期2/3桩)"; } static int cli_run(int n, char **v) { const char *cmd = v[0]; (void)n; /* 子命令参数期 2/3 才用(history[小时] 等) */ if (!strcmp(cmd, "status")) return cli_status(); if (!strcmp(cmd, "estimate")) return cli_estimate(); if (!strcmp(cmd, "model")) return cli_model(); if (!strcmp(cmd, "history")) return cli_stub("history", "2"); if (!strcmp(cmd, "top")) return cli_stub("top", "2"); if (!strcmp(cmd, "events")) return cli_stub("events", "2"); if (!strcmp(cmd, "policy")) return cli_stub("policy", "3"); fprintf(stderr, "usage: meow_power <%s>\n", cli_usage()); return 1; } /* ---------------- main:单一二进制双形态 ---------------- */ int main(int argc, char **argv) { const char *e; if ((e = getenv("MEOW_PS_DIR")) && e[0]) snprintf(g_ps_dir, sizeof g_ps_dir, "%s", e); if ((e = getenv("MEOW_BL_DIR")) && e[0]) snprintf(g_bl_dir, sizeof g_bl_dir, "%s", e); if ((e = getenv("MEOW_NET_DIR")) && e[0]) snprintf(g_net_dir, sizeof g_net_dir, "%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_NETDEV")) && e[0]) snprintf(g_netdev, sizeof g_netdev, "%s", e); if ((e = getenv("MEOW_PROC_STAT")) && e[0]) snprintf(g_proc_stat, sizeof g_proc_stat, "%s", e); if ((e = getenv("MEOW_BOOT_MODE")) && e[0]) snprintf(g_boot_mode, sizeof g_boot_mode, "%s", e); if ((e = getenv("MEOW_PM_MODEL")) && e[0]) snprintf(g_model_conf, sizeof g_model_conf, "%s", e); if ((e = getenv("MEOW_AUDIO_SOCK")) && e[0]) snprintf(g_audio_sock, sizeof g_audio_sock, "%s", e); if ((e = getenv("MEOW_PM_SOCK")) && e[0]) g_sock = e; if ((e = getenv("MEOW_PM_SAMPLE_SEC")) && atoi(e) > 0) g_sample_sec = atoi(e); if ((e = getenv("MEOW_PM_MIN_SPAN_SEC")) && atoi(e) > 0) g_rate_min_span = 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_powermgr / meow_power (%s)\n", cli_usage()); return 0; } else break; } /* argv[0] basename=meow_power → 强制 CLI(双形态);meow_powermgr 空参 → 守护常驻。 */ const char *base = strrchr(argv[0], '/'); base = base ? base + 1 : argv[0]; if (!strcmp(base, "meow_power")) { if (i >= argc) { fprintf(stderr, "usage: meow_power <%s>\n", cli_usage()); return 1; } return cli_run(argc - i, argv + i); } if (i >= argc) return run_daemon(g_sock); /* svcd 空参 → IPC 常驻 */ return cli_run(argc - i, argv + i); }