#define _GNU_SOURCE /* 启用 struct ucred / SO_PEERCRED、asprintf 等 GNU/musl 扩展 */ /* meow_svc_power.c — MeowOS 电源管理专项子服务(SYSTEM_FRAMEWORK_SERVICES §1.2/§2.3/§2.4)。 * * 单一二进制双形态: * - svcd 以**空参**拉起(§1.3 conf `args=` 空)→ IPC 服务常驻:bind `/usr/data/meow_power.sock` * (`--sock`/`MEOW_IPC_POWER_SOCK` 可覆盖),收 MSG_HELLO→MSG_ACK、MSG_POWER_ACTION→MSG_POWER_STATUS。 * - root shell `meow_svc_power ` → 一次性 CLI(免鉴权)执行对应动作(不常驻)。 * * 动作逻辑与 launcher `power_menu.c` 的 `sys_do_reboot`/`set_boot_default_hiby`/`bootmode.sh Recovery` * **同源 0-diff 搬移**,仅入口(CLI vs IPC)与鉴权层不同。 * * 鉴权(II 期,MPK_SIGNATURE_DESIGN §6):IPC 服务端按【连接级身份】分级——`SO_PEERCRED` 取 pid → * `readlink /proc/pid/exe` → 命中 `MeowAPPs//` 判定包名,或命中系统二进制 allowlist * (/usr/bin/meow_*、/usr/data/meow/meow*、MeowPanel 后端)判定为系统身份;其余为 OTHER。 * 命令分「读级/动作级」(用户定案 2026-08-31):读级(status/battery/reboot_reason/upgrade_status/ * dac status,只读无副作用)放宽到**任意调用者**(不查 uid/身份);动作级维持现状——系统动作 * (shutdown/reboot/upgrade/back_hiby/set-default-*)仅放行 `os.meow.*` 包或系统身份,非系统动作 * (screen/dac on|off)仍须 root;非 root 一律拒。 * `MSG_HELLO` 自报 app_id 不在此作权威(仅交叉校验,见 §6.1)。CLI=root 免鉴权。 * * 通知转储归属(§3.2/§3.3 已定案):通知存储为 launcher 进程内存(meow_notif.c g_slots[]),服务跨进程 * 不可达——**转储由调用方 launcher 先做**,服务对关机/重启/升级一律 `sync()` 落盘,不重实现转储。 * * 编译(mipsel 静态):build_svc_power.sh;宿主编译 build_svc_power.sh host。 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "meow_ipc.h" /* 帧格式/消息号 + POWER_* 枚举 + m_power_* 负载(与 launcher 同源) */ /* ---- 路径常量(与 launcher 同源)---- */ #define FB_DEV "/dev/fb0" #define FBIOBLANK 0x4611 #define BACKLIGHT "/sys/class/backlight/backlight_pwm0/brightness" #define BACKLIGHT_POWER "/sys/class/backlight/backlight_pwm0/bl_power" #define BOOT_DEFAULT "/data/meow_boot_default" /* 0=MeowOS(默认),1=HibyOS */ #define COLD_BOOT "/data/meow_cold_boot" /* 冷启标记:1=上次关机,0=上次重启 */ #define MTD_OTA "/dev/mtd5" /* ota 标记分区(8M) */ #define SETTINGS_FILE "/usr/data/meow/settings.conf" #define SETTINGS_HDR "# meow-settings 1" #define VERSION_FILE "/etc/meowos_version" /* sysfs 根可经 MEOW_PS_DIR 覆盖(宿主自测用;生产恒 /sys/class/power_supply) */ #ifndef MEOW_PS_DIR #define MEOW_PS_DIR "/sys/class/power_supply" #endif #define BATT_CAP MEOW_PS_DIR "/battery/capacity" #define BATT_STATUS MEOW_PS_DIR "/battery/status" #define BATT_VOLT MEOW_PS_DIR "/battery/voltage_now" /* 充电态权威源(真机实测 2026-09-01:battery/status 恒 Discharging 撒谎): * mp2731-charger/status(Charging/Full=充电中)为权威;不可读回退 fusb302-typec-source/online==1 * (USB-C PD 控制器插线检测);再回退旧逻辑 battery/status==Charging || ac/online==1。 */ #define CHG_MP2731_STATUS MEOW_PS_DIR "/mp2731-charger/status" #define CHG_FUSB302_ONLINE MEOW_PS_DIR "/fusb302-typec-source/online" #define CHG_AC_ONLINE MEOW_PS_DIR "/ac/online" #define UPGRADE_FW "/usr/data/mnt/sd_0/r3proii.upt" /* 卡根升级固件(写 mtd5 前完整性校验) */ /* 音量语义(与 launcher MEOW_VOL_DEFAULT/MEOW_VOL_MAX_DEFAULT、SDK MEOW_UI_VOL_* 一致): * audio.volume 默认 60(写 DAC 映射值 0-100);audio.volume_max 默认 100(软上限 clamp)。 */ #ifndef MEOW_VOL_DEFAULT #define MEOW_VOL_DEFAULT 60 #endif #ifndef MEOW_VOL_MAX_DEFAULT #define MEOW_VOL_MAX_DEFAULT 100 #endif #define LINUX_REBOOT_MAGIC1 0xfee1dead #define LINUX_REBOOT_MAGIC2 672274793 /* 0x28121969 */ #define LINUX_REBOOT_CMD_RESTART 0x01234567 #define LINUX_REBOOT_CMD_POWER_OFF 0x4321fedc static const char *g_sock = "/usr/data/meow_power.sock"; /* ---------------- 极简日志(stderr;svcd supervisor 捕获) ---------------- */ static void svc_log(const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "[meow_svc_power] "); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); } /* ---------------- 文件小工具 ---------------- */ /* 读整个文本文件到 buf(NUL 结尾),返回字节数(不含 NUL);失败 -1。 */ 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; } /* 读 int(atoi 首值);失败默认 def。 */ 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++; int v = atoi(p); return v; } static int write_file_str(const char *path, const char *s, mode_t mode) { int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, mode); if (fd < 0) return -1; size_t l = strlen(s); ssize_t w = write(fd, s, l); int ok = (w == (ssize_t)l) ? 0 : -1; close(fd); return ok; } /* 读 settings.conf 某键值(key=value,行首忽略空白),找不到→def。 */ static int settings_get_int(const char *key, int def) { FILE *f = fopen(SETTINGS_FILE, "r"); if (!f) return def; char line[256]; int val = def, found = 0; size_t klen = strlen(key); while (fgets(line, sizeof line, f)) { char *p = line; while (*p == ' ' || *p == '\t') p++; if (strncmp(p, key, klen) == 0 && p[klen] == '=') { val = atoi(p + klen + 1); found = 1; break; } } fclose(f); return found ? val : def; } /* 更新/插入 settings.conf 某键(保留头,O_TRUNC 重写)。成功 0。 */ static int settings_set_int(const char *key, int val) { FILE *f = fopen(SETTINGS_FILE, "r"); char tmp[1024]; size_t klen = strlen(key); int found = 0; if (!f) { /* 无文件 → 新建头 + 键 */ FILE *nf = fopen(SETTINGS_FILE, "w"); if (!nf) return -1; fprintf(nf, "%s\n%s=%d\n", SETTINGS_HDR, key, val); fclose(nf); return 0; } /* 先把原文件读入 tmp(限 1KB,settings 为 KB 级小文件) */ size_t n = 0; int c; while (n < sizeof tmp - 1 && (c = fgetc(f)) != EOF) tmp[n++] = (char)c; fclose(f); tmp[n] = 0; FILE *nf = fopen(SETTINGS_FILE, "w"); if (!nf) return -1; /* 逐行写:头照抄;命中 key 行替换 */ char *save = NULL; for (char *ln = strtok_r(tmp, "\n", &save); ln; ln = strtok_r(NULL, "\n", &save)) { char *p = ln; while (*p == ' ' || *p == '\t') p++; if (strncmp(p, key, klen) == 0 && p[klen] == '=') { fprintf(nf, "%s=%d\n", key, val); found = 1; } else { fprintf(nf, "%s\n", ln); } } if (!found) fprintf(nf, "%s=%d\n", key, val); fclose(nf); return 0; } /* 读 settings.conf 某 int 键:兼容落盘 "key = value"(带空格/多空格),'=' 切分、key 去首尾空白精确匹配 * (避免 audio.volume 误配 audio.volume_max)。打开失败/键缺失/解析失败 → def。 * 与 launcher 读循环、SDK meow_ui_vol.c meow_ui_vol_read 同款解析。 */ static int settings_get_int_trim(const char *key, int def) { FILE *f = fopen(SETTINGS_FILE, "r"); if (!f) return def; size_t kl = strlen(key); char line[160]; char *found = NULL; while (fgets(line, sizeof line, f)) { char *eq = strchr(line, '='); if (!eq) continue; /* 非 key=value 行(注释/空行)跳过 */ char *k = line; while (*k == ' ' || *k == '\t') k++; /* key 左去空白 */ long n = (long)(eq - k); while (n > 0 && (k[n - 1] == ' ' || k[n - 1] == '\t')) n--; /* key 右去空白 */ if ((size_t)n != kl || strncmp(k, key, kl) != 0) continue; /* key 不精确匹配 */ found = eq + 1; /* 命中所求键 */ break; } fclose(f); if (!found) return def; while (*found == ' ' || *found == '\t') found++; /* value 左去空白;atoi 天然停于非数字 */ return atoi(found); } /* ---------------- 升级写保护(MeowOS 侧;固件只完整性校验,不签名) ---------------- * upgrade 信号(CLI 与 IPC POWER_UPGRADE 同路径)触发写 mtd5 前先校验卡根升级固件 * r3proii.upt(TF 卡挂载点 /usr/data/mnt/sd_0): * 1. 存在(access F_OK)——无文件不写 mtd5; * 2. 可读(access R_OK); * 3. 常规文件 + 非空 + size>0(stat)——防 0 字节/损坏。 * 校验失败 → 返回非 MEOW_OK + why 提示;调用方绝不写 mtd5、不 reboot。 * 待补(二期):期望 md5/hash 校验——经 IPC/配置传期望值,或读卡上 MeowOS/config/ 固件 fingerprint; * 本批只完整性(防"无文件/0 字节/损坏")。**固件不签名**(海贝官方 r3proii.upt 无签名,签名会挡自刷)。 */ static int upgrade_fw_check(char *why, int cap) { const char *path = UPGRADE_FW; if (access(path, F_OK) < 0) { svc_log("upgrade fw missing: %s (%s)", path, strerror(errno)); snprintf(why, cap, "卡上无升级文件 %s", path); return MEOW_ERR_NOENT; } if (access(path, R_OK) < 0) { svc_log("upgrade fw unreadable: %s (%s)", path, strerror(errno)); snprintf(why, cap, "升级文件不可读 %s", path); return MEOW_ERR_IO; } struct stat st; if (stat(path, &st) < 0) { svc_log("upgrade fw stat fail: %s (%s)", path, strerror(errno)); snprintf(why, cap, "升级文件状态读取失败 %s", path); return MEOW_ERR_IO; } if (!S_ISREG(st.st_mode) || st.st_size <= 0) { svc_log("upgrade fw invalid: %s size=%lld", path, (long long)st.st_size); snprintf(why, cap, "升级文件为空或损坏 %s", path); return MEOW_ERR_IO; } svc_log("upgrade fw ok: %s size=%lld", path, (long long)st.st_size); return MEOW_OK; } /* 前置声明:CLI 用法串(cli_run 用到)。 */ static const char *cli_usage(void); /* ---------------- reboot / 关机 syscall ---------------- */ static void sys_reboot_do(int cmd) { /* 与 power_menu.c sys_do_reboot 同源:sync() 落盘 → reboot(2) syscall → 永不返回。 */ sync(); svc_log("sys_reboot_do cmd=0x%x", cmd); syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, cmd, NULL); svc_log("sys_reboot_do returned: %s", strerror(errno)); for (;;) usleep(1000); } /* ---------------- boot 默认 ---------------- */ /* 写 /data/meow_boot_default(0=MeowOS 默认 / 1=HibyOS 默认)。 */ static void write_boot_default(int v) { char b[8]; snprintf(b, sizeof b, "%d\n", v); if (write_file_str(BOOT_DEFAULT, b, 0644) < 0) svc_log("write %s fail: %s", BOOT_DEFAULT, strerror(errno)); else svc_log("boot_default=%d", v); } /* ---------------- 关机 logo ---------------- */ static void show_shutdown_logo(void) { /* 独立二进制 cmd_jpeg_display 能显图;SSH 无 fb 时重定向无害。 */ int rc = system("PATH=/bin:/sbin:/usr/bin:/usr/sbin cmd_jpeg_display /etc/shutdown_logo.jpeg >/dev/null 2>&1"); (void)rc; } /* ---------------- 升级(mtd5=ota:kernel2) ---------------- */ static void enter_upgrade(void) { int rc = system("PATH=/bin:/sbin:/usr/bin:/usr/sbin /usr/bin/bootmode.sh Recovery >/dev/null 2>&1"); (void)rc; } /* 读 /dev/mtd5 头,命中 "ota:kernel2" → 升级模式已设。 */ static int mtd_ota_is_upgrade(void) { int fd = open(MTD_OTA, O_RDONLY); if (fd < 0) return 0; char buf[16]; memset(buf, 0, sizeof buf); ssize_t n = read(fd, buf, sizeof buf - 1); close(fd); if (n <= 0) return 0; return strstr(buf, "ota:kernel2") != NULL; } /* ---------------- 有界拼接(printf 截断分析免疫;host/cross 均 0 warning) ---------------- */ static void cat_b(char *dst, size_t cap, const char *src) { size_t dl = strlen(dst); if (dl >= cap - 1) return; size_t sl = strlen(src); if (sl > cap - 1 - dl) sl = cap - 1 - dl; if (sl) memcpy(dst + dl, src, sl); dst[dl + sl] = 0; } /* ---------------- 电池/状态读组(同 util.c/about.c/bootmenu 数据源) ---------------- */ /* 充电态权威判定(见上方 CHG_* 注释):返回写入 st 的状态串(空=无法判定)。 */ static void charging_status_str(char *st, int cap) { st[0] = 0; if (read_file_txt(CHG_MP2731_STATUS, st, cap) > 0 && st[0]) return; /* 权威源直用 */ int on = read_file_int_def(CHG_FUSB302_ONLINE, -1); if (on >= 0) { snprintf(st, cap, "%s", on == 1 ? "Charging" : "Discharging"); return; } read_file_txt(BATT_STATUS, st, cap); if (!strstr(st, "Charging") && !strstr(st, "Full") && read_file_int_def(CHG_AC_ONLINE, 0) == 1) snprintf(st, cap, "Charging"); } static void battery_fill(char *out, int cap) { int pct = read_file_int_def(BATT_CAP, -1); char st[32] = ""; charging_status_str(st, sizeof st); /* 权威充电态(battery/status 会撒谎) */ int mv = read_file_int_def(BATT_VOLT, -1) / 1000; /* µV → mV */ if (pct < 0) { snprintf(out, cap, "battery=unknown"); return; } char one[64]; snprintf(one, sizeof one, "battery=%d%%(%s)", pct, st[0] ? st : "?"); out[0] = 0; cat_b(out, cap, one); if (mv >= 0) { char more[24]; snprintf(more, sizeof more, " %dmV", mv); cat_b(out, cap, more); } } static const char *boot_default_str(void) { int v = read_file_int_def(BOOT_DEFAULT, -1); if (v < 0) return "unknown"; return v ? "hiby" : "meow"; } static int cold_boot_flag(void) { return read_file_int_def(COLD_BOOT, -1); } static void status_fill(char *out, int cap) { char ver[32] = "-", batt[48] = ""; if (read_file_txt(VERSION_FILE, ver, sizeof ver) <= 0 || !ver[0]) snprintf(ver, sizeof ver, "-"); battery_fill(batt, sizeof batt); int cold = cold_boot_flag(); char head[96]; snprintf(head, sizeof head, "system=%s boot=%s upgrade=%s coldboot=%s ", ver, boot_default_str(), mtd_ota_is_upgrade() ? "yes" : "no", cold < 0 ? "unknown" : (cold ? "shutdown" : "reboot")); out[0] = 0; cat_b(out, cap, head); cat_b(out, cap, batt); } /* ---------------- 屏幕电源(同 launcher keys.c meow_panel_off/on + backlight.c) ---------------- */ static int backlight_write(int val) { char b[16]; snprintf(b, sizeof b, "%d", val); return write_file_str(BACKLIGHT, b, 0644) < 0 ? -1 : 0; } static int screen_on(void) { int f = open(FB_DEV, O_RDWR); if (f >= 0) { ioctl(f, FBIOBLANK, 0); close(f); } int bri = settings_get_int("display.brightness", 60); int bmin = settings_get_int("display.brightness_min", 1); if (bri < bmin) bri = bmin; if (bri > 100) bri = 100; backlight_write(bri); svc_log("screen ON (brightness %d)", bri); return 0; } static int screen_off(void) { int f = open(FB_DEV, O_RDWR); if (f >= 0) { ioctl(f, FBIOBLANK, 4); close(f); } backlight_write(0); svc_log("screen OFF (blank 4 + brightness 0)"); return 0; } /* ---------------- DAC(CS43198) ---------------- */ /* 原机 cs43198 驱动 pwr_gpio=-1:无上电/下电控制点(sysfs/amixer),on/off 不支持。 * status 报**真实音量**:读板载 /usr/data/meow/settings.conf 的 audio.volume(meow_dac_vol_write * 写入映射值 0-100),按 audio.volume_max clamp(与 launcher vol_clamp / SDK meow_ui_vol_clamp 同口径)。 * 原 amixer -c 0 cget numid=1/2 为驱动 dummy 恒 0(不可靠),弃用——不再走 amixer 虚拟上报。 */ static int dac_status(char *out, int cap) { int vol = settings_get_int_trim("audio.volume", MEOW_VOL_DEFAULT); int vmax = settings_get_int_trim("audio.volume_max", MEOW_VOL_MAX_DEFAULT); if (vol < 0) vol = 0; /* 硬下限 0 */ if (vol > 100) vol = 100; /* 硬上限 100(0-100 口径) */ if (vmax < 0) vmax = 0; /* 异常上限兜底:不进负值 */ if (vol > vmax) vol = vmax; /* 尊重 audio.volume_max 软上限 */ /* 左右同值(单个 DAC 声道容量;L=R=vol%);settings 读不到 → 默认 MEOW_VOL_DEFAULT(60) 即真实默认。 */ snprintf(out, cap, "dac=cs43198 vol=%d%% L=R", vol); svc_log("dac status: audio.volume=%d (volume_max=%d)", vol, vmax); return 0; } static int dac_cmd(const char *sub, char *out, int cap) { if (!sub) return MEOW_ERR_INVAL; if (!strcmp(sub, "status")) { return dac_status(out, cap); } if (!strcmp(sub, "on") || !strcmp(sub, "off")) { snprintf(out, cap, "DAC %s: unsupported (cs43198 pwr_gpio=-1, no on/off control point)", sub); return MEOW_ERR_NODEV; } return MEOW_ERR_INVAL; } /* ---------------- 动作执行(CLI + IPC 共用) ---------------- * 返回 0 = 已同步执行并填好 st(调用方直接应答 st 给客户端/CLI 已打印); * 返回 1 = 系统动作(关机/重启/升级/回HiBy),调用方应「先应答 MEOW_OK 受理,再执行」(执行后通常不返回)。 */ static int execute_action(const m_power_action_t *a, m_power_status_t *st) { memset(st, 0, sizeof *st); st->cmd = a->cmd; switch (a->cmd) { case POWER_SHUTDOWN: sync(); /* 通知转储由 launcher 先做;服务 sync() 落盘 */ st->code = MEOW_OK; snprintf(st->msg, sizeof st->msg, "shutdown accepted"); return 1; /* 应答后:logo + poweroff(不返回) */ case POWER_REBOOT: write_boot_default(0); /* 普通重启恒回 MeowOS(无视之前) */ sync(); st->code = MEOW_OK; snprintf(st->msg, sizeof st->msg, "reboot accepted (boot=meow)"); return 1; case POWER_UPGRADE: { /* 写 mtd5 前先校验卡上升级固件(升级写保护);校验失败→非 OK、绝不写 mtd5/不 reboot。 */ char why[64]; int rc = upgrade_fw_check(why, sizeof why); if (rc != MEOW_OK) { st->code = rc; snprintf(st->msg, sizeof st->msg, "upgrade rejected: %s", why); return 0; /* 已填 st;调用方直接应答非 OK(不触发 run_system_action) */ } sync(); st->code = MEOW_OK; snprintf(st->msg, sizeof st->msg, "upgrade accepted"); return 1; } case POWER_BACK_HIBY: write_boot_default(1); sync(); st->code = MEOW_OK; snprintf(st->msg, sizeof st->msg, "reboot -> hiby accepted"); return 1; case POWER_SET_DEFAULT_MEOW: write_boot_default(0); st->code = MEOW_OK; snprintf(st->msg, sizeof st->msg, "boot_default=meow (next boot)"); return 0; case POWER_SET_DEFAULT_HIBY: write_boot_default(1); st->code = MEOW_OK; snprintf(st->msg, sizeof st->msg, "boot_default=hiby (next boot)"); return 0; case POWER_STATUS: status_fill(st->msg, sizeof st->msg); st->code = MEOW_OK; return 0; case POWER_BATTERY: battery_fill(st->msg, sizeof st->msg); st->code = MEOW_OK; return 0; case POWER_REBOOT_REASON: { int c = cold_boot_flag(); snprintf(st->msg, sizeof st->msg, "coldboot=%s", c < 0 ? "unknown" : (c ? "shutdown" : "reboot")); st->code = MEOW_OK; } return 0; case POWER_UPGRADE_STATUS: snprintf(st->msg, sizeof st->msg, "upgrade=%s", mtd_ota_is_upgrade() ? "yes" : "no"); st->code = MEOW_OK; return 0; case POWER_SCREEN: if (a->flag == 0) { screen_off(); st->code = MEOW_OK; snprintf(st->msg, sizeof st->msg, "screen off"); return 0; } if (a->flag == 1) { screen_on(); st->code = MEOW_OK; snprintf(st->msg, sizeof st->msg, "screen on"); return 0; } if (a->flag == 2) { /* timeout 秒(arg) */ int sec = a->arg[0] ? atoi(a->arg) : -1; if (sec < 0 || sec > 86400) { st->code = MEOW_ERR_INVAL; snprintf(st->msg, sizeof st->msg, "screen timeout: bad sec"); return 0; } settings_set_int("display.screen_off_timeout", sec); st->code = MEOW_OK; snprintf(st->msg, sizeof st->msg, "screen_off_timeout=%ds", sec); return 0; } st->code = MEOW_ERR_INVAL; snprintf(st->msg, sizeof st->msg, "screen: bad flag"); return 0; case POWER_DAC: { int rc = dac_cmd(a->flag == 2 ? "status" : (a->flag == 1 ? "on" : "off"), st->msg, sizeof st->msg); st->code = rc; } return 0; default: st->code = MEOW_ERR_INVAL; snprintf(st->msg, sizeof st->msg, "unknown cmd %u", a->cmd); return 0; } } /* 执行「系统动作」(在应答 MEOW_OK 之后调用;通常不返回)。 */ static void run_system_action(uint32_t cmd) { switch (cmd) { case POWER_SHUTDOWN: show_shutdown_logo(); sys_reboot_do(LINUX_REBOOT_CMD_POWER_OFF); break; case POWER_REBOOT: sys_reboot_do(LINUX_REBOOT_CMD_RESTART); break; case POWER_UPGRADE: enter_upgrade(); sys_reboot_do(LINUX_REBOOT_CMD_RESTART); break; case POWER_BACK_HIBY: sys_reboot_do(LINUX_REBOOT_CMD_RESTART); break; default: break; } } /* ---------------- IPC 帧读写(同 meow_ipc 客户端约定,全小端) ---------------- */ 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_power_status(int fd, uint32_t req_id, const m_power_status_t *st) { m_power_status_t s = *st; uint16_t ml = (uint16_t)strlen(s.msg); if (ml >= sizeof s.msg) ml = (uint16_t)(sizeof s.msg - 1); s.msg_len = ml; return send_frame(fd, MSG_POWER_STATUS, req_id, &s, sizeof s); } /* ---------------- II 期:调用方身份解析(连接级,MPK_SIGNATURE_DESIGN §6.2) ---------------- */ typedef enum { CALL_OTHER, CALL_PKG, CALL_SYS } caller_kind_t; typedef struct { caller_kind_t kind; char pkg[MEOW_APP_ID_MAX]; /* CALL_PKG=MeowAPPs// 取包名;CALL_SYS=系统身份别名 */ uid_t uid; } caller_t; /* 系统动作(SYSTEM_FRAMEWORK_SERVICES §2.4 + 设计 §6.3 表):须 os.meow.* 包或系统身份。 */ static int is_system_action(uint32_t cmd) { switch (cmd) { case POWER_SHUTDOWN: case POWER_REBOOT: case POWER_UPGRADE: case POWER_BACK_HIBY: case POWER_SET_DEFAULT_MEOW: case POWER_SET_DEFAULT_HIBY: return 1; default: return 0; } } /* 读级命令(用户定案 2026-08-31):只读查询、无副作用——status/battery/reboot_reason/upgrade_status, * 及 DAC 的 status(flag=2)。读级放宽到任意调用者(不查 uid/身份);screen/dac on|off 等有副作用 → 动作级。 */ static int is_read_level(const m_power_action_t *a) { switch (a->cmd) { case POWER_STATUS: case POWER_BATTERY: case POWER_REBOOT_REASON: case POWER_UPGRADE_STATUS: return 1; case POWER_DAC: return a->flag == 2; /* 仅 dac status 为读;on/off(flag=0/1) 是动作 */ default: return 0; } } /* 是否可信系统身份:CALL_SYS(受信系统二进制)或 CALL_PKG 且包前缀 os.meow.*。 */ 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; } /* MeowPanel 精确绑定(2B):仅当 exe **精确命中登记路径** 才予 os.meow.panel 系统身份。 * 替代 2A 的启发式 `strstr(exe,"/MeowPanel/")||strstr(exe,"meowpanel")`——后者过宽(任何路径含该 * 子串即被放行,如 `/.../MeowPanel/evil` 或 `/usr/bin/foomeowpanel`)。面板本体为外置卡组件 * (无卡不 import),路径登记 = `meowpanel.conf` 的 `exec`(`MeowOS/opt/MeowPanel/meowpanel`)。 * 面板二进制更新(换 md5)不影响路径登记,无需重编本服务;「精确绑定」靠路径唯一性 + 唯一二进制位。 * 诚实标注:若攻击者以 root 在本路径放恶意二进制,仍获 os.meow.panel——但该情形 root 本可为所欲为 * (MPK_SIGNATURE_DESIGN §8 天花板);本绑定只把「启发式误放行」收紧为「明确路径登记」。 * 如需更强(绑定面板二进制 md5),可在此按路径读文件算 md5 比对登记表——首版以路径登记为准。 */ static const char *const k_panel_exe[] = { "/usr/data/mnt/sd_0/MeowOS/opt/MeowPanel/meowpanel", NULL }; static int exe_is_panel(const char *exe) { int i; for (i = 0; k_panel_exe[i]; i++) if (strcmp(exe, k_panel_exe[i]) == 0) return 1; return 0; } /* 由 exe 路径判身份(纯函数,便于宿主自测 + 生产共用)。前提:out 已 memset(kind 缺省 CALL_OTHER)。 * - exe 前缀 /usr/data/mnt/sd_0/MeowAPPs/ → MeowAPP 包(kind=CALL_PKG,pkg=目录名) * - 否则命中系统二进制 allowlist → CALL_SYS * - 其余保持 CALL_OTHER * msg 自报 app_id 仅作交叉校验(不在此处做权威判定,见设计 §6.1/§6.5)。 */ static void classify_exe(const char *exe, caller_t *out) { /* MeowAPPs//(与 app_launcher.c meow_pkg_name 同口径:去前缀取第一段目录名) */ 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; } } /* 系统二进制 allowlist(/usr/bin/meow_*、/usr/data/meow_*、MeowPanel 后端) */ char tmp[MEOW_APP_ID_MAX]; if (strcmp(exe, "/usr/bin/meow_launcherd") == 0) { snprintf(tmp, sizeof tmp, "%s", "os.meow.launcher"); } else if (exe_is_panel(exe)) { snprintf(tmp, sizeof tmp, "%s", "os.meow.panel"); } else if (strncmp(exe, "/usr/bin/meow_", 14) == 0) { /* 截取 "meow_xyz"(去 "/usr/bin/" 前缀)作近似别名 */ size_t l = strlen(exe + 11); if (l >= sizeof tmp) l = sizeof tmp - 1; memcpy(tmp, exe + 11, 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; } /* 由连接 SO_PEERCRED 取 pid → readlink /proc//exe → classify_exe(连接创建时缓存,循环内复用)。 */ 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); } /* 处理单条连接(连接内循环读帧:客户端先 MSG_HELLO 握手,再 MSG_POWER_ACTION)。 */ static void handle_conn(int c) { /* II 期鉴权升级(MPK_SIGNATURE_DESIGN §6.2):uid==0 判定升级为连接级身份解析(pid→exe→包名/系统身份)。 */ 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; /* EOF/错误 */ 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_POWER_ACTION) { m_power_action_t a; memset(&a, 0, sizeof a); if (rlen > sizeof a) rlen = sizeof a; memcpy(&a, payload, rlen); /* 动作级策略(设计 §6.3 + 用户定案 2026-08-31):读级(只读无副作用)放宽到任意调用者, * 不查 uid/身份;动作级维持现状——系统动作(shutdown/reboot/upgrade/back_hiby/set-default-*) * 须 os.meow.* 包或受信系统身份,非系统动作(screen/dac on|off)仍须 root。 */ int allow = 0; if (is_read_level(&a)) { allow = 1; /* 读级:任意调用者直接放行 */ } else if (caller.uid == 0) { if (is_system_action(a.cmd)) allow = is_sys_identity(&caller); else allow = 1; /* 动作级非系统动作(screen/dac on|off):root */ } if (!allow) { m_power_status_t st; memset(&st, 0, sizeof st); st.code = MEOW_ERR_PERM; st.cmd = a.cmd; snprintf(st.msg, sizeof st.msg, "permission denied (caller kind=%d uid=%d)", (int)caller.kind, (int)caller.uid); send_power_status(c, req_id, &st); continue; } m_power_status_t st; int act = execute_action(&a, &st); if (act == 0) { send_power_status(c, req_id, &st); continue; } /* 系统动作:先应答受理(客户端不阻塞),再执行(常不返回) */ send_power_status(c, req_id, &st); run_system_action(a.cmd); break; } send_err(c, req_id, MEOW_ERR_INVAL, "unknown type"); break; } } static int run_daemon(const char *sock) { signal(SIGPIPE, SIG_IGN); 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; } svc_log("meow_svc_power listening on %s", sock); for (;;) { int c = accept(lfd, NULL, NULL); if (c < 0) { if (errno == EINTR) continue; continue; } handle_conn(c); close(c); } return 0; } /* ---------------- CLI ---------------- */ static int cli_run(int n, char **v) { const char *cmd = v[0]; if (!strcmp(cmd, "shutdown")) { show_shutdown_logo(); sys_reboot_do(LINUX_REBOOT_CMD_POWER_OFF); return 0; /* 不返回 */ } if (!strcmp(cmd, "reboot")) { if (n > 1 && !strcmp(v[1], "--hiby")) { write_boot_default(1); } else write_boot_default(0); /* 普通重启恒回 MeowOS */ sys_reboot_do(LINUX_REBOOT_CMD_RESTART); return 0; } if (!strcmp(cmd, "upgrade")) { char why[64]; int rc = upgrade_fw_check(why, sizeof why); if (rc != MEOW_OK) { fprintf(stderr, "upgrade aborted: %s (firmware NOT written to mtd5)\n", why); return 1; } enter_upgrade(); sys_reboot_do(LINUX_REBOOT_CMD_RESTART); return 0; } if (!strcmp(cmd, "set-default-hiby")) { write_boot_default(1); printf("boot_default=hiby (next boot)\n"); return 0; } if (!strcmp(cmd, "set-default-meow")) { write_boot_default(0); printf("boot_default=meow (next boot)\n"); return 0; } if (!strcmp(cmd, "status")) { char out[128]; status_fill(out, sizeof out); printf("%s\n", out); return 0; } if (!strcmp(cmd, "battery")) { char out[64]; battery_fill(out, sizeof out); printf("%s\n", out); return 0; } if (!strcmp(cmd, "reboot-reason")) { int c = cold_boot_flag(); printf("coldboot=%s\n", c < 0 ? "unknown" : (c ? "shutdown" : "reboot")); return 0; } if (!strcmp(cmd, "upgrade-status")) { printf("upgrade=%s\n", mtd_ota_is_upgrade() ? "yes" : "no"); return 0; } if (!strcmp(cmd, "screen")) { if (n < 2) { fprintf(stderr, "screen needs on|off|timeout<秒>\n"); return 1; } if (!strcmp(v[1], "on")) return screen_on(); if (!strcmp(v[1], "off")) return screen_off(); if (!strcmp(v[1], "timeout")) { if (n < 3) { fprintf(stderr, "screen timeout needs <秒>\n"); return 1; } int sec = atoi(v[2]); if (sec < 0 || sec > 86400) { fprintf(stderr, "screen timeout: bad sec\n"); return 1; } settings_set_int("display.screen_off_timeout", sec); printf("screen_off_timeout=%ds\n", sec); return 0; } fprintf(stderr, "screen: bad sub %s\n", v[1]); return 1; } if (!strcmp(cmd, "dac")) { if (n < 2) { fprintf(stderr, "dac needs on|off|status\n"); return 1; } char out[160]; int rc = dac_cmd(v[1], out, sizeof out); printf("%s\n", out); return rc == MEOW_OK ? 0 : 1; } fprintf(stderr, "usage: meow_svc_power (%s)\n", cli_usage()); return 1; } static const char *cli_usage(void) { return "shutdown|reboot[--hiby]|upgrade|set-default-hiby|set-default-meow|" "status|battery|reboot-reason|upgrade-status|screen on|off|timeout<秒>|dac on|off|status"; } int main(int argc, char **argv) { const char *env = getenv("MEOW_IPC_POWER_SOCK"); if (env && env[0]) g_sock = env; 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_svc_power (%s)\n", cli_usage()); return 0; } else break; } if (i >= argc) return run_daemon(g_sock); /* svcd 空参 → IPC 常驻 */ return cli_run(argc - i, argv + i); }