903 lines
34 KiB
C
903 lines
34 KiB
C
#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);
|
||
}
|