Files
MeowOS-For-R3P2/patch_v2/meow_svc_power/meow_svc_power.c
T

903 lines
34 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#define _GNU_SOURCE /* 启用 struct ucred / SO_PEERCRED、asprintf 等 GNU/musl 扩展 */
/* meow_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 <cmd>` → 一次性 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/<pkg>/` 判定包名,或命中系统二进制 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.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 <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#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"
#define BATT_CAP "/sys/class/power_supply/battery/capacity"
#define BATT_STATUS "/sys/class/power_supply/battery/status"
#define BATT_VOLT "/sys/class/power_supply/battery/voltage_now"
#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 数据源) ---------------- */
static void battery_fill(char *out, int cap)
{
int pct = read_file_int_def(BATT_CAP, -1);
char st[32] = "";
read_file_txt(BATT_STATUS, st, sizeof st);
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/<pkg>/ 取包名;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;
}
/* 由 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/<pkg>/(与 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 (strstr(exe, "/MeowPanel/") || strstr(exe, "meowpanel")) {
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/<pid>/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 <cmd> (%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);
}