diff --git a/patch_v2/meow_amsd/am b/patch_v2/meow_amsd/am new file mode 100755 index 0000000..24129af Binary files /dev/null and b/patch_v2/meow_amsd/am differ diff --git a/patch_v2/meow_amsd/am.c b/patch_v2/meow_amsd/am.c new file mode 100644 index 0000000..ee92078 --- /dev/null +++ b/patch_v2/meow_amsd/am.c @@ -0,0 +1,140 @@ +/* am.c — MeowOS 活动管理器用户侧 CLI(AMSD_DESIGN.md §3,安卓习惯,进固件 /usr/bin/am)。 + * + * 连 amsd sock(--sock/MEOW_AMS_SOCK,默认 /usr/data/meow_ams.sock),发一条命令行, + * 读响应直到 EOF,格式化输出。root 免鉴权(读);写操作(start/kill)一期仅 root/系统身份。 + * + * 子命令: + * am status → 在跑 App 表(pkg/状态/pid/运行时长/activity) + * am list → 注册表 + 状态(pkg/name/state/pid/launches/crashes) + * am history [pkg] → 事件历史(每 App 最近 8 条;无 pkg=全部) + * am start [/] → 经 amsd→launcher 拉起 + * am kill → 经 amsd→launcher 终止(SIGTERM 语义) + * + * 编译(mipsel 静态):zig-mipsel-cc -Os -Wall -Wextra -static am.c -o am + * 宿主:gcc -Os -Wall -Wextra am.c -o am_host + */ +#include +#include +#include +#include +#include +#include +#include + +#define AMS_SOCK_DEFAULT "/usr/data/meow_ams.sock" +#define AMS_SOCK_PATH_MAX 108 +#define AMS_RESP_MAX 65536 + +static const char *g_sock = NULL; + +static int connect_path(const char *path) +{ + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { perror("socket"); return -1; } + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + snprintf(a.sun_path, sizeof a.sun_path, "%s", path); + if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { + fprintf(stderr, "connect %s: %s\n", path, strerror(errno)); + close(fd); + return -1; + } + return fd; +} + +/* 发送单行命令,读取响应直到 EOF,打印。返回 0=ok。 */ +static int am_query(const char *cmd) +{ + int fd = connect_path(g_sock); + if (fd < 0) return 1; + ssize_t w = write(fd, cmd, strlen(cmd)); + (void)w; + static char resp[AMS_RESP_MAX]; + size_t n = 0; + char tmp[2048]; + ssize_t r; + while ((r = read(fd, tmp, sizeof tmp)) > 0) { + if (n + (size_t)r < sizeof resp - 1) { memcpy(resp + n, tmp, (size_t)r); n += (size_t)r; } + } + close(fd); + resp[n] = '\0'; + fputs(resp, stdout); + return 0; +} + +static int cmd_status(void) +{ + return am_query("status\n"); +} + +static int cmd_list(void) +{ + return am_query("list\n"); +} + +static int cmd_history(const char *pkg) +{ + char line[512]; + if (pkg && pkg[0]) + snprintf(line, sizeof line, "history %s\n", pkg); + else + snprintf(line, sizeof line, "history\n"); + return am_query(line); +} + +static int cmd_start(const char *spec) +{ + if (geteuid() != 0) { printf("ERR permission denied (root only)\n"); return 1; } + char line[512]; + snprintf(line, sizeof line, "start %s\n", spec); + return am_query(line); +} + +static int cmd_kill(const char *pkg) +{ + if (geteuid() != 0) { printf("ERR permission denied (root only)\n"); return 1; } + char line[512]; + snprintf(line, sizeof line, "kill %s\n", pkg); + return am_query(line); +} + +static void usage(const char *a0) +{ + fprintf(stderr, + "usage: %s [--sock ] status|list|history [pkg]|start [/]|kill \n" + " --sock amsd socket (default %s, or $MEOW_AMS_SOCK)\n", + a0, AMS_SOCK_DEFAULT); +} + +int main(int argc, char **argv) +{ + const char *env = getenv("MEOW_AMS_SOCK"); + g_sock = (env && env[0]) ? env : AMS_SOCK_DEFAULT; + + const char *cmd = NULL, *arg = NULL; + for (int i = 1; i < argc; i++) { + const char *a = argv[i]; + if (!strcmp(a, "--sock") && i + 1 < argc) g_sock = argv[++i]; + else if (!strcmp(a, "-h") || !strcmp(a, "--help")) { usage(argv[0]); return 0; } + else if (a[0] == '-') { usage(argv[0]); return 2; } + else if (!cmd) cmd = a; + else if (!arg) arg = a; + else { fprintf(stderr, "extra arg %s\n", a); return 2; } + } + if (!cmd) { usage(argv[0]); return 2; } + + if (!strcmp(cmd, "status")) return cmd_status(); + if (!strcmp(cmd, "list")) return cmd_list(); + if (!strcmp(cmd, "history")) return cmd_history(arg); + if (!strcmp(cmd, "start")) { + if (!arg) { fprintf(stderr, "start needs [/]\n"); return 2; } + return cmd_start(arg); + } + if (!strcmp(cmd, "kill")) { + if (!arg) { fprintf(stderr, "kill needs \n"); return 2; } + return cmd_kill(arg); + } + usage(argv[0]); + return 2; +} diff --git a/patch_v2/meow_amsd/am_host b/patch_v2/meow_amsd/am_host new file mode 100755 index 0000000..a3e5fa6 Binary files /dev/null and b/patch_v2/meow_amsd/am_host differ diff --git a/patch_v2/meow_amsd/ams_stub.c b/patch_v2/meow_amsd/ams_stub.c new file mode 100644 index 0000000..8f866d9 --- /dev/null +++ b/patch_v2/meow_amsd/ams_stub.c @@ -0,0 +1,70 @@ +/* ams_stub.c — meow_amsd 宿主测试桩(不入固件)。 + * + * 用途:在宿主集成测试里,把 stdin 的每一行原样发到 amsd socket(command 与 EV 事件均可), + * 然后读取响应直到 EOF 并打印。既当「launcher 上报桩」(注入 EV LAUNCH/EV OUT/EV EXIT), + * 也当通用命令客户端(status/list/history/start/kill)。 + * + * 用法: + * printf 'EV LAUNCH os.meow.hello home - 123 12345 100\n' | ./ams_stub_host --sock /tmp/sim_ams.sock + * echo status | ./ams_stub_host --sock /tmp/sim_ams.sock + * + * 编译(宿主):gcc -Os -Wall -Wextra ams_stub.c -o ams_stub_host + */ +#include +#include +#include +#include +#include +#include +#include + +#define AMS_SOCK_DEFAULT "/usr/data/meow_ams.sock" +#define AMS_SOCK_PATH_MAX 108 +#define AMS_RESP_MAX 65536 + +static const char *g_sock = AMS_SOCK_DEFAULT; + +int main(int argc, char **argv) +{ + const char *env = getenv("MEOW_AMS_SOCK"); + if (env && env[0]) g_sock = env; + for (int i = 1; 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")) { + fprintf(stderr, "usage: %s [--sock ] (reads lines from stdin -> amsd)\n", argv[0]); + return 0; + } else { fprintf(stderr, "bad arg %s\n", argv[i]); return 2; } + } + + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { perror("socket"); return 1; } + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + snprintf(a.sun_path, sizeof a.sun_path, "%s", g_sock); + if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { + fprintf(stderr, "connect %s: %s\n", g_sock, strerror(errno)); + return 1; + } + + char line[2048]; + while (fgets(line, sizeof line, stdin)) { + ssize_t w = write(fd, line, strlen(line)); + (void)w; + } + /* half-close 写端:告知 amsd「无更多数据」(= launcher fire-and-forget 的 close 语义), + * 事件连接 amsd 即收尾关闭;命令连接 amsd 写回响应后才关闭。 */ + shutdown(fd, SHUT_WR); + /* read response until EOF */ + static char resp[AMS_RESP_MAX]; + size_t n = 0; + char tmp[2048]; + ssize_t r; + while ((r = read(fd, tmp, sizeof tmp)) > 0) { + if (n + (size_t)r < sizeof resp - 1) { memcpy(resp + n, tmp, (size_t)r); n += (size_t)r; } + } + close(fd); + resp[n] = '\0'; + if (n > 0) fputs(resp, stdout); + return 0; +} diff --git a/patch_v2/meow_amsd/ams_stub_host b/patch_v2/meow_amsd/ams_stub_host new file mode 100755 index 0000000..b396c59 Binary files /dev/null and b/patch_v2/meow_amsd/ams_stub_host differ diff --git a/patch_v2/meow_amsd/build_amsd.sh b/patch_v2/meow_amsd/build_amsd.sh new file mode 100644 index 0000000..123f35d --- /dev/null +++ b/patch_v2/meow_amsd/build_amsd.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# build_amsd.sh — meow_amsd 交叉编译(mipsel 静态)+ 宿主多目标可选。 +# +# 目标:R3 Pro II / MeowOS(mipsel-linux-muslsf,静态)。amsd/am 极简仅 POSIX + Linux 基础接口 +# + 复用 launcher 的 meow_ipc.h(-I../meow_launcherd),单文件即可固件内/usr/bin。 +# +# 用法: +# ./build_amsd.sh # 交叉编译 → meow_amsd(mipsel 静态) +# ./build_amsd.sh am # 交叉编译 am CLI → am(mipsel 静态) +# ./build_amsd.sh host # 宿主编译 meow_amsd_host + am_host + ams_stub_host(集成测试) +set -e +cd "$(dirname "$0")" +ROOT="$HOME/Project/R3ProII" +CC="$ROOT/.tools/zig-mipsel-cc" + +if [ "$1" = "host" ]; then + HOST_CC="${CC_HOST:-cc}" + "$HOST_CC" -I"$ROOT/patch_v2/meow_launcherd" -Os -Wall -Wextra meow_amsd.c -o meow_amsd_host + "$HOST_CC" -Os -Wall -Wextra am.c -o am_host + "$HOST_CC" -Os -Wall -Wextra ams_stub.c -o ams_stub_host + echo "== host builds ==" + ls -la meow_amsd_host am_host ams_stub_host + file meow_amsd_host am_host ams_stub_host + exit 0 +fi + +if [ "$1" = "am" ]; then + "$CC" -Os -Wall -Wextra -static am.c -o am + echo "== am (mipsel static) ==" + ls -la am + md5sum am + file am + exit 0 +fi + +"$CC" -I"$ROOT/patch_v2/meow_launcherd" -Os -Wall -Wextra -static meow_amsd.c -o meow_amsd +echo "== meow_amsd (mipsel static) ==" +ls -la meow_amsd +md5sum meow_amsd +file meow_amsd diff --git a/patch_v2/meow_amsd/meow_amsd b/patch_v2/meow_amsd/meow_amsd new file mode 100755 index 0000000..4626f8d Binary files /dev/null and b/patch_v2/meow_amsd/meow_amsd differ diff --git a/patch_v2/meow_amsd/meow_amsd.c b/patch_v2/meow_amsd/meow_amsd.c new file mode 100644 index 0000000..e32a0a3 --- /dev/null +++ b/patch_v2/meow_amsd/meow_amsd.c @@ -0,0 +1,1068 @@ +/* meow_amsd.c — MeowOS 活动管理器守护(AMSD_DESIGN.md,一期)。 + * + * 定位(用户定案,见 devdocs/plan/AMSD_DESIGN.md / FRAMEWORK_DESIGN §10 / + * devdocs/plan/SYS_BASELINE.md / APPS.md 附录): + * - svcd 托管的小守护(板载 /usr/bin/meow_amsd + services.d 板载 conf,trigger=boot); + * svcd fork+exec 直接拉起,故本进程【前台常驻】(不 daemonize),与 meow_crash 同款。 + * - 一期:生命周期监控中心 + 应用注册表。launcher 经 AF_UNIX `/usr/data/meow_ams.sock`(0666) + * 上报 launch/exit/crash 事件;`am` CLI 同 socket 查询/下发 start/kill。 + * - 核心目的:从侧面详细抓 App 日志(尤其异常崩溃日志)。launcher 给 App 建 stdout/stderr + * 管道,前台循环按行转发(EV OUT);amsd 落盘 `logs//stdout__.log`。 + * - 崩溃双保险:amsd 检测到异常退出时另以 meow_crash 认识的 `SVC ...` 格式转发 + * `/tmp/meow_crash.sock`(meow_crash.c handle_conn 只认 SVC 前缀;launcher 现行 `app ...` + * 格式落不了盘,amsd 的 SVC 转发正好补上)。 + * - 落盘:TF 卡 `MeowOS/logs/`(amsd 只清 `ams_` 与 `stdout_` 前缀文件,不与 meow_crash 的 + * `meow_` 与 `crash_` 前缀清理冲突)。无卡回退 /tmp(256KB 滚动);卡出现自动迁移。 + * - 铁律:上报/转发全程非阻塞、可失败(amsd 不在,launcher 照常跑);amsd 自身绝不因 + * App 事件而崩(只改内存表 + poll,不 exit)。 + * + * 编译(mipsel 静态,见 build_amsd.sh): + * zig-mipsel-cc -I../meow_launcherd -Os -Wall -Wextra -static meow_amsd.c -o meow_amsd + * 宿主编译(集成测试):gcc -I../meow_launcherd -Os -Wall -Wextra meow_amsd.c -o meow_amsd_host + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "meow_ipc.h" /* MEOW_IPC 帧/MAGIC/m_ams_* 结构(与 launcher 同源,杜绝漂移) */ + +/* ---- 默认路径 / 常量 ---- */ +#define AMS_CARD_DEFAULT "/usr/data/mnt/sd_0" /* TF 卡挂载点 */ +#define AMS_LOGS_REL "MeowOS/logs" /* 卡上日志目录(同 launcher/crash) */ +#define AMS_SOCK_DEFAULT "/usr/data/meow_ams.sock" +#define AMS_IPC_SOCK_DEFAULT "/usr/data/meow_ipc.sock" /* 连 launcher IPC 用 */ +#define AMS_APPS_DEFAULT "/usr/data/mnt/sd_0/MeowAPPs" /* 注册表扫描源 */ +#define AMS_CRASH_SOCK_DEFAULT "/tmp/meow_crash.sock" +#define AMS_TMP_LOG "/tmp/meow_ams.log" +#define AMS_TMP_MAX (256u * 1024u) +#define AMS_LOG_SIZE_LIMIT (1u << 30) /* logs/ 总量 1GB 纪律 */ +#define AMS_FILE_MAX (1u << 20) /* stdout 单文件 1MB 轮转 */ +#define AMS_LINE_MAX 2048 /* 单行 read 缓冲 */ +#define AMS_HIST 8 /* 每 App 最近事件数 */ +#define AMS_MAX_APPS 64 +#define AMS_MAX_CONNS 32 +#define AMS_TICK_MS 1000 +#define AMS_CLEANUP_MS 60000 +#define AMS_PATH_MAX 512 +#define AMS_SOCK_PATH_MAX 108 +#define AMS_BOOTTS_LEN 64 +#define AMS_REQ_MAX 512 /* 命令响应缓冲 */ + +/* 每 App 状态(一期:STOPPED/FG;BG 预留二期) */ +enum { AMS_STOPPED = 0, AMS_FG = 1, AMS_BG = 2 }; + +struct ams_ev { + char detail[192]; /* 事件描述(exit=0 / sig=11 / ams_start=... 等) */ + long epoch; /* 墙钟秒 */ + long up; /* uptime 秒 */ + char kind[16]; /* launch / exit / crash / ams_start / ams_kill */ +}; + +struct ams_app { + char pkg[MEOW_APP_ID_MAX]; /* app_id=package */ + char name[17]; + char exec[129]; + int known; /* 注册表(app.json 扫描)命中 */ + int state; /* AMS_* */ + pid_t pid; + char activity[MEOW_ACT_MAX]; + char chain[MEOW_CHAIN_MAX]; + long long launch_ms; /* CLOCK_MONOTONIC ms */ + long launch_epoch; /* 墙钟秒 */ + long launch_up; + int launches; + int crashes; /* 异常退出次数(sig 或 exit!=0) */ + int last_kind; /* 0=干净 exit 1=exit 2=sig */ + int last_code; + long long last_dur_ms; + struct ams_ev hist[AMS_HIST]; + int hist_n, hist_idx; +}; + +/* stdout 捕获会话(一期同一时刻仅一个前台 App → 单槽) */ +struct ams_out { + int active; + char pkg[MEOW_APP_ID_MAX]; + pid_t pid; + int fd; /* 打开的日志文件 fd */ + char base[AMS_PATH_MAX]; /* 当前文件路径 */ + long long bytes; /* 当前文件字节数 */ +}; +struct ams_out g_out = { .active = 0, .fd = -1 }; + +/* ---- 全局状态 ---- */ +static char g_card[256] = AMS_CARD_DEFAULT; +static char g_sock[AMS_SOCK_PATH_MAX] = AMS_SOCK_DEFAULT; +static char g_ipc_sock[AMS_SOCK_PATH_MAX] = AMS_IPC_SOCK_DEFAULT; +static char g_apps_dir[AMS_PATH_MAX] = AMS_APPS_DEFAULT; +static char g_crash_sock[AMS_SOCK_PATH_MAX] = AMS_CRASH_SOCK_DEFAULT; +static char g_bootts[AMS_BOOTTS_LEN] = ""; +static int g_log_fd = -1; /* 卡日志 fd;-1=未开(落 /tmp) */ +static int g_on_card = 0; +static int g_shutdown = 0; +static int g_sigpipe[2] = {-1, -1}; +static int g_listen_fd = -1; +static struct ams_app g_apps[AMS_MAX_APPS]; +static int g_napps = 0; + +struct ams_conn { + int fd; + char buf[AMS_LINE_MAX]; + int n; /* buf 有效字节(可能含未完结行,跨 poll 保留) */ + char resp[AMS_REQ_MAX]; + int resp_n; /* 已产生的命令响应(写回后即关连接) */ +}; + +/* ---- 工具 ---- */ +static unsigned long long now_ms(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (unsigned long long)ts.tv_sec * 1000 + (unsigned long long)ts.tv_nsec / 1000000; +} + +static int safe_strcpy(char *dst, size_t cap, const char *src) +{ + if (!dst || cap == 0) return -1; + int n = snprintf(dst, cap, "%s", src ? src : ""); + return (n >= 0 && (size_t)n < cap) ? 0 : -1; +} + +/* 有界字符串拷贝(memcpy 实现,不触发 gcc -Wformat-truncation)。 */ +static size_t scopy(char *dst, size_t cap, const char *src) +{ + if (!dst || cap == 0) { if (dst) dst[0] = '\0'; return 0; } + size_t l = strnlen(src ? src : "", cap - 1); + if (src) memcpy(dst, src, l); + dst[l] = '\0'; + return l; +} + +/* 把 src 追加到 dst 末尾(有界,memcpy 实现)。返回 0=成功;-1=溢出(截断到 cap-1)。 */ +static int path_append(char *dst, size_t cap, const char *src) +{ + size_t dl = strnlen(dst, cap); + size_t sl = strnlen(src ? src : "", cap > dl + 1 ? cap - dl - 1 : 0); + if (dl + sl + 1 > cap) { + /* 溢出:截断并保存 NUL */ + if (cap > 0) { dst[cap - 1] = '\0'; } + return -1; + } + if (sl) memcpy(dst + dl, src, sl); + dst[dl + sl] = '\0'; + return 0; +} + +static long read_uptime_sec(void) +{ + int fd = open("/proc/uptime", O_RDONLY); + if (fd < 0) return 0; + char b[64]; + ssize_t n = read(fd, b, sizeof b - 1); + close(fd); + if (n <= 0) return 0; + b[n] = '\0'; + return (long)atof(b); +} + +/* 本 boot 时间戳 "%Y%m%d_%H%M%S":复用 /tmp/meow_bootts(launcher/crash 共用,跨进程同一 boot)。 */ +static void boot_ts(char *out, size_t n) +{ + out[0] = '\0'; + int fd = open("/tmp/meow_bootts", O_RDONLY); + if (fd >= 0) { + char b[AMS_BOOTTS_LEN]; + ssize_t r = read(fd, b, sizeof b - 1); + close(fd); + if (r > 0) { + b[r] = '\0'; + char *p = b; + if (strncmp(p, "meow_", 5) == 0) p += 5; + snprintf(out, n, "%s", p); + if (out[0]) return; + } + } + time_t now = time(NULL); + time_t up = (time_t)read_uptime_sec(); + time_t boot = now - up; + struct tm tm; + localtime_r(&boot, &tm); + strftime(out, n, "%Y%m%d_%H%M%S", &tm); +} + +static void ams_append_line(const char *line) +{ + if (g_log_fd >= 0) { + ssize_t w = write(g_log_fd, line, strlen(line)); + if (w < 0) { close(g_log_fd); g_log_fd = -1; g_on_card = 0; } + else return; + } + struct stat st; + if (stat(AMS_TMP_LOG, &st) == 0 && st.st_size > (long long)AMS_TMP_MAX) + unlink(AMS_TMP_LOG); + int fd = open(AMS_TMP_LOG, O_WRONLY | O_APPEND | O_CREAT, 0644); + if (fd >= 0) { ssize_t w = write(fd, line, strlen(line)); (void)w; close(fd); } +} + +static void emit_log(const char *fmt, ...) +{ + char line[AMS_LINE_MAX]; + long epoch = (long)time(NULL); + long up = read_uptime_sec(); + va_list ap; + va_start(ap, fmt); + int l = snprintf(line, sizeof line, "%ld %ld amsd ", epoch, up); + if (l < 0) l = 0; + int m = vsnprintf(line + l, sizeof line - l, fmt, ap); + va_end(ap); + if (m > 0) { + l += m; + if ((size_t)l < sizeof line) { line[l++] = '\n'; line[l] = 0; ams_append_line(line); } + } +} + +/* ---- 卡可用性 / 持久日志 ---- */ +static int card_available(void) +{ + const char *nc = getenv("MEOW_AMSD_NOCARD"); + if (nc && nc[0] == '1') return 0; + + char logs[AMS_PATH_MAX]; + if (snprintf(logs, sizeof logs, "%s/%s", g_card, AMS_LOGS_REL) >= (int)sizeof logs) return 0; + if (strcmp(g_card, AMS_CARD_DEFAULT) == 0) { + FILE *f = fopen("/proc/mounts", "r"); + if (!f) return 0; + char needle[AMS_PATH_MAX], line[AMS_PATH_MAX]; + snprintf(needle, sizeof needle, " %s ", g_card); + int found = 0; + while (fgets(line, sizeof line, f)) + if (strstr(line, needle)) { found = 1; break; } + fclose(f); + if (!found) return 0; + } + (void)mkdir(logs, 0755); + return access(logs, W_OK) == 0; +} + +/* 只对 ams_ 前缀与 stdout_ 前缀文件做 1GB 清理(不与 meow_crash 的 meow_/crash_ 前缀冲突)。 */ +static void log_cleanup_old(void) +{ + char logs[AMS_PATH_MAX]; + if (snprintf(logs, sizeof logs, "%s/%s", g_card, AMS_LOGS_REL) >= (int)sizeof logs) return; + DIR *d = opendir(logs); + if (!d) return; + struct lf { char name[96]; long long size, mtime; } files[128]; + int n = 0; + struct dirent *de; + while ((de = readdir(d)) != NULL && n < 128) { + const char *nm = de->d_name; + if (strncmp(nm, "ams_", 4) != 0 && strncmp(nm, "stdout_", 7) != 0) continue; + char path[AMS_PATH_MAX]; + if (snprintf(path, sizeof path, "%s/%s", logs, nm) >= (int)sizeof path) continue; + struct stat st; + if (stat(path, &st) != 0 || !S_ISREG(st.st_mode)) continue; + size_t nl = strlen(nm); + if (nl >= sizeof files[n].name) nl = sizeof files[n].name - 1; + memcpy(files[n].name, nm, nl); + files[n].name[nl] = '\0'; + files[n].size = (long long)st.st_size; + files[n].mtime = (long long)st.st_mtime; + n++; + } + closedir(d); + long long total = 0; + for (int i = 0; i < n; i++) total += files[i].size; + for (int i = 0; i < n; i++) + for (int j = i + 1; j < n; j++) + if (files[j].mtime < files[i].mtime) { struct lf t = files[i]; files[i] = files[j]; files[j] = t; } + int deleted = 0; + for (int i = 0; i < n && total > (long long)AMS_LOG_SIZE_LIMIT; i++) { + char path[AMS_PATH_MAX]; + if (snprintf(path, sizeof path, "%s/%s", logs, files[i].name) >= (int)sizeof path) continue; + if (unlink(path) == 0) { total -= files[i].size; deleted++; } + } + if (deleted) fprintf(stderr, "[meow_amsd] log_cleanup deleted %d old log(s)\n", deleted); +} + +static int log_open_card(void) +{ + if (g_log_fd >= 0) return 0; + if (!card_available()) return -1; + char ts[AMS_BOOTTS_LEN]; + boot_ts(ts, sizeof ts); + char path[AMS_PATH_MAX]; + if (snprintf(path, sizeof path, "%s/%s/ams_%s.log", g_card, AMS_LOGS_REL, ts) + >= (int)sizeof path) return -1; + int fd = open(path, O_WRONLY | O_APPEND | O_CREAT, 0644); + if (fd < 0) return -1; + g_log_fd = fd; g_on_card = 1; + return 0; +} + +static void poll_card(void) +{ + if (g_log_fd >= 0) return; + if (log_open_card() != 0) return; + int fd = open(AMS_TMP_LOG, O_RDONLY); + if (fd >= 0) { + char chunk[1024]; ssize_t cn; + while ((cn = read(fd, chunk, sizeof chunk)) > 0) { + if (write(g_log_fd, chunk, cn) < 0) { close(fd); g_log_fd = -1; g_on_card = 0; return; } + } + close(fd); + unlink(AMS_TMP_LOG); + fprintf(stderr, "[meow_amsd] /tmp buffered log moved to card\n"); + } +} + +/* ---- 注册表扫描(app.json 迷你解析:package/name/exec) ---- */ +static void appjson_get(const char *content, const char *key, char *out, size_t n) +{ + out[0] = '\0'; + char pat[64]; + snprintf(pat, sizeof pat, "\"%s\"", key); + const char *p = strstr(content, pat); + if (!p) return; + p += strlen(pat); + const char *c = strchr(p, ':'); + if (!c) return; + c++; + while (*c == ' ' || *c == '\t') c++; + if (*c != '"') return; + c++; + const char *e = strchr(c, '"'); + if (!e) return; + size_t l = (size_t)(e - c); + if (l >= n) l = n - 1; + memcpy(out, c, l); + out[l] = '\0'; +} + +/* 扫描 --apps 下每个目录(app_id=目录名;name/exec 从 app.json 抽,缺省回退)。 */ +static void registry_scan(void) +{ + DIR *d = opendir(g_apps_dir); + if (!d) { emit_log("registry scan: %s not reachable (%s)", g_apps_dir, strerror(errno)); return; } + struct dirent *de; + int n = 0; + while ((de = readdir(d)) != NULL && n < AMS_MAX_APPS) { + if (de->d_name[0] == '.') continue; + char nm[96]; + scopy(nm, sizeof nm, de->d_name); + char dir[AMS_PATH_MAX + 64]; + scopy(dir, sizeof dir, g_apps_dir); + path_append(dir, sizeof dir, "/"); + if (path_append(dir, sizeof dir, nm) != 0) continue; /* 路径过长 → 跳过 */ + struct stat st; + if (stat(dir, &st) != 0 || !S_ISDIR(st.st_mode)) continue; + char jpath[AMS_PATH_MAX + 64]; + scopy(jpath, sizeof jpath, dir); + if (path_append(jpath, sizeof jpath, "/app.json") != 0) continue; + char content[2048]; + content[0] = '\0'; + FILE *jf = fopen(jpath, "r"); + if (jf) { + size_t rd = fread(content, 1, sizeof content - 1, jf); + fclose(jf); + content[rd] = '\0'; + } + struct ams_app *a = &g_apps[n]; + memset(a, 0, sizeof *a); + /* app_id 权威 = 目录名(APPS.md §2);app.json package 字段作为兜底校验(不一致时用目录名) */ + scopy(a->pkg, sizeof a->pkg, de->d_name); + a->known = 1; + char tmp[96]; + appjson_get(content, "name", tmp, sizeof tmp); + if (!tmp[0]) scopy(tmp, sizeof tmp, de->d_name); + scopy(a->name, sizeof a->name, tmp); + appjson_get(content, "exec", tmp, sizeof tmp); + if (tmp[0]) scopy(a->exec, sizeof a->exec, tmp); + else { + scopy(a->exec, sizeof a->exec, dir); + path_append(a->exec, sizeof a->exec, "/meow"); + } + n++; + } + closedir(d); + g_napps = n; + emit_log("registry scan: %d app(s) from %s", g_napps, g_apps_dir); + for (int i = 0; i < g_napps; i++) + emit_log(" app %s name=%s exec=%s", g_apps[i].pkg, g_apps[i].name, g_apps[i].exec); +} + +/* ---- App 表辅助 ---- */ +static struct ams_app *app_find(const char *pkg) +{ + if (!pkg) return NULL; + for (int i = 0; i < g_napps; i++) + if (!strcmp(g_apps[i].pkg, pkg)) return &g_apps[i]; + return NULL; +} + +/* get-or-create:launcher 上报可能先于注册表扫描(或 pkg 未扫到),追加建项。 */ +static struct ams_app *app_get(const char *pkg) +{ + struct ams_app *a = app_find(pkg); + if (a) return a; + if (g_napps >= AMS_MAX_APPS) return NULL; + a = &g_apps[g_napps++]; + memset(a, 0, sizeof *a); + snprintf(a->pkg, sizeof a->pkg, "%s", pkg); + snprintf(a->name, sizeof a->name, "%s", pkg); + snprintf(a->exec, sizeof a->exec, "%s", "?"); + return a; +} + +static void hist_push(struct ams_app *a, const char *kind, const char *detail, + long epoch, long up) +{ + struct ams_ev *e = &a->hist[a->hist_idx]; + snprintf(e->kind, sizeof e->kind, "%s", kind); + snprintf(e->detail, sizeof e->detail, "%s", detail); + e->epoch = epoch; + e->up = up; + a->hist_idx = (a->hist_idx + 1) % AMS_HIST; + if (a->hist_n < AMS_HIST) a->hist_n++; +} + +/* ---- meow_crash 转发(SVC 格式,best-effort) ---- */ +static void crash_forward(const char *pkg, const char *kind, int code, long epoch, long up) +{ + char msg[256]; + int l = snprintf(msg, sizeof msg, "SVC %s %s %d %ld %ld\n", pkg, kind, code, epoch, up); + if (l <= 0) return; + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) return; + int fl = fcntl(fd, F_GETFL, 0); + if (fl >= 0) fcntl(fd, F_SETFL, fl | O_NONBLOCK); + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + strncpy(a.sun_path, g_crash_sock, sizeof a.sun_path - 1); + if (connect(fd, (struct sockaddr *)&a, sizeof a) != 0) { + if (errno == EINPROGRESS || errno == EWOULDBLOCK) { + struct pollfd pf = { fd, POLLOUT, 0 }; + int r = poll(&pf, 1, 150); + if (r <= 0 || !(pf.revents & POLLOUT)) { close(fd); return; } + } else { close(fd); return; } + } + ssize_t w = write(fd, msg, (size_t)l); + (void)w; + close(fd); +} + +/* ---- stdout 捕获会话 ---- */ +static void out_close(void) +{ + if (g_out.fd >= 0) close(g_out.fd); + memset(&g_out, 0, sizeof g_out); + g_out.fd = -1; +} + +static void out_open(const char *pkg, pid_t pid, long epoch) +{ + out_close(); + /* 路径:/MeowOS/logs//stdout__.log(与 SDK 日志同目录) */ + char logs[AMS_PATH_MAX], pdir[AMS_PATH_MAX], base[AMS_PATH_MAX]; + if (snprintf(logs, sizeof logs, "%s/%s", g_card, AMS_LOGS_REL) >= (int)sizeof logs) return; + if (snprintf(pdir, sizeof pdir, "%s/%s", logs, pkg) >= (int)sizeof pdir) return; + (void)mkdir(logs, 0755); + (void)mkdir(pdir, 0755); + if (snprintf(base, sizeof base, "%s/stdout_%ld_%ld.log", pdir, epoch, (long)pid) + >= (int)sizeof base) return; + int fd = open(base, O_WRONLY | O_APPEND | O_CREAT, 0644); + if (fd < 0) { emit_log("stdout open fail %s: %s", base, strerror(errno)); return; } + snprintf(g_out.pkg, sizeof g_out.pkg, "%s", pkg); + g_out.pid = pid; + g_out.fd = fd; + snprintf(g_out.base, sizeof g_out.base, "%s", base); + struct stat st; + g_out.bytes = (stat(base, &st) == 0) ? (long long)st.st_size : 0; + g_out.active = 1; + emit_log("stdout session open: %s (pid=%ld) -> %s", pkg, (long)pid, base); +} + +/* 超 1MB 轮转:base -> base.1(覆盖),重开 base 续写。 */ +static void out_rotate(void) +{ + if (g_out.fd < 0) return; + close(g_out.fd); + char old[AMS_PATH_MAX]; + if (snprintf(old, sizeof old, "%s.1", g_out.base) < (int)sizeof old) + (void)rename(g_out.base, old); + int fd = open(g_out.base, O_WRONLY | O_APPEND | O_CREAT, 0644); + g_out.fd = fd; + g_out.bytes = 0; + emit_log("stdout rotate -> %s.1", g_out.base); +} + +static void out_append(const char *pkg, pid_t pid, long epoch, long up, const char *text) +{ + if (!g_out.active || g_out.fd < 0) return; + if (strcmp(g_out.pkg, pkg) != 0 || g_out.pid != pid) return; /* 会话不匹配,忽略 */ + char line[AMS_LINE_MAX]; + int l = snprintf(line, sizeof line, "[%ld|up=%ld] %s\n", epoch, up, text ? text : ""); + if (l <= 0) return; + if (g_out.bytes + l > (long long)AMS_FILE_MAX) out_rotate(); + ssize_t w = write(g_out.fd, line, (size_t)l); + if (w > 0) g_out.bytes += w; +} + +/* ---- launcher IPC 转发(xchg 一帧,读回一帧) ---- */ +static int ipc_xchg(uint16_t type, const void *payload, uint32_t len, + uint8_t *rbuf, uint32_t rcap) +{ + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) return -1; + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + strncpy(a.sun_path, g_ipc_sock, sizeof a.sun_path - 1); + if (connect(fd, (struct sockaddr *)&a, sizeof a) != 0) { close(fd); return -1; } + uint8_t hdr[MEOW_IPC_HDR_LEN]; + uint32_t magic = MEOW_IPC_MAGIC, rmagic, rlen; + uint32_t reqid = 0; /* amsd→launcher 控制请求无需回显 */ + uint16_t ver = MEOW_IPC_VERSION, rver, rtype; + memcpy(hdr + 0, &magic, 4); memcpy(hdr + 4, &ver, 2); + memcpy(hdr + 6, &type, 2); memcpy(hdr + 8, &reqid, 4); + memcpy(hdr + 12, &len, 4); + size_t off = 0; + while (off < sizeof hdr) { + ssize_t w = write(fd, hdr + off, sizeof hdr - off); + if (w <= 0) { close(fd); return -1; } + off += (size_t)w; + } + if (len && payload) { + size_t p = 0; + while (p < len) { + ssize_t w = write(fd, (const uint8_t *)payload + p, len - p); + if (w <= 0) { close(fd); return -1; } + p += (size_t)w; + } + } + off = 0; + while (off < sizeof hdr) { + ssize_t r = read(fd, hdr + off, sizeof hdr - off); + if (r <= 0) { close(fd); return -1; } + off += (size_t)r; + } + memcpy(&rmagic, hdr, 4); memcpy(&rver, hdr + 4, 2); + memcpy(&rtype, hdr + 6, 2); memcpy(&rlen, hdr + 12, 4); + if (rmagic != MEOW_IPC_MAGIC || rver != MEOW_IPC_VERSION) { close(fd); return -1; } + if (rlen > rcap) rlen = rcap; + off = 0; + while (off < rlen) { + ssize_t r = read(fd, rbuf + off, rlen - off); + if (r <= 0) { close(fd); return -1; } + off += (size_t)r; + } + close(fd); + return (int)rtype; +} + +/* 向 launcher 申请 start/kill。返回 0=launcher 回 code(a_out 输出);-1=launcher 不通。 */ +static int ams_request_ctl(uint16_t type, const void *payload, uint32_t len, uint32_t *code) +{ + uint8_t rbuf[64]; + int rt = ipc_xchg(type, payload, len, rbuf, sizeof rbuf); + if (rt == MSG_ACK) { + m_ack_t a; + memcpy(&a, rbuf, sizeof a); + if (code) *code = a.code; + return 0; + } + if (rt == MSG_ERR) return MEOW_ERR_INTERNAL; + return -1; +} + +/* ---- 事件处理(launcher 上报) ---- */ +static void handle_launch(char **t, int nt) +{ + if (nt < 4) return; + const char *pkg = t[0], *act = t[1], *chain = t[2]; + pid_t pid = (pid_t)atoi(t[3]); + long epoch = (nt > 4) ? atol(t[4]) : (long)time(NULL); + long up = (nt > 5) ? atol(t[5]) : read_uptime_sec(); + struct ams_app *a = app_get(pkg); + if (!a) return; + a->state = AMS_FG; + a->pid = pid; + snprintf(a->activity, sizeof a->activity, "%s", strcmp(act, "-") ? act : ""); + snprintf(a->chain, sizeof a->chain, "%s", strcmp(chain, "-") ? chain : ""); + a->launch_ms = (long long)now_ms(); + a->launch_epoch = epoch; + a->launch_up = up; + a->launches++; + a->last_kind = 0; a->last_code = 0; a->last_dur_ms = 0; + char detail[192]; + snprintf(detail, sizeof detail, "act=%s chain=%.64s pid=%ld", a->activity, a->chain, (long)pid); + hist_push(a, "launch", detail, epoch, up); + emit_log("LAUNCH %s %s %s pid=%ld", pkg, a->activity, a->chain, (long)pid); + out_open(pkg, pid, epoch); +} + +static void handle_exit(char **t, int nt) +{ + if (nt < 3) return; + const char *pkg = t[0]; + const char *kind = t[1]; /* exit | sig */ + int code = atoi(t[2]); + long long dur = (nt > 3) ? atoll(t[3]) : 0; + long epoch = (nt > 4) ? atol(t[4]) : (long)time(NULL); + long up = (nt > 5) ? atol(t[5]) : read_uptime_sec(); + struct ams_app *a = app_find(pkg); + if (!a) { if (g_out.active && !strcmp(g_out.pkg, pkg)) out_close(); return; } + int abnormal = (strcmp(kind, "sig") == 0) || (strcmp(kind, "exit") == 0 && code != 0); + a->state = AMS_STOPPED; + a->pid = 0; + a->last_kind = (strcmp(kind, "sig") == 0) ? 2 : (code == 0 ? 0 : 1); + a->last_code = code; + a->last_dur_ms = dur; + if (abnormal) { + a->crashes++; + crash_forward(pkg, kind, code, epoch, up); /* 双保险:并入 meow_crash 日志 */ + } + char detail[192]; + if (abnormal) + snprintf(detail, sizeof detail, "%s=%d dur=%lldms crash=%d", kind, code, dur, a->crashes); + else + snprintf(detail, sizeof detail, "%s=%d dur=%lldms", kind, code, dur); + hist_push(a, abnormal ? "crash" : "exit", detail, epoch, up); + emit_log("EXIT %s %s=%d dur=%lld abnormal=%d", pkg, kind, code, dur, abnormal); + if (g_out.active && !strcmp(g_out.pkg, pkg)) out_close(); +} + +static void handle_out(char **t, int nt) +{ + if (nt < 3) return; + const char *pkg = t[0]; + pid_t pid = (pid_t)atoi(t[1]); + long epoch = atol(t[2]); + long up = atol(t[3]); + /* 拼接剩余 tokens(text 可含空格) */ + char text[AMS_LINE_MAX]; + text[0] = '\0'; + int off = 0; + for (int i = 4; i < nt; i++) { + int w = snprintf(text + off, sizeof text - off, "%s%s", off ? " " : "", t[i]); + if (w < 0) break; + off += w; + if ((size_t)off >= sizeof text - 1) { off = (int)sizeof text - 1; break; } + } + text[off] = '\0'; + out_append(pkg, pid, epoch, up, text); +} + +/* ---- am CLI 命令处理 ---- */ +static int cmd_status(char *resp, int rn) +{ + int l = snprintf(resp, rn, "# amd status (running apps)\n"); + int found = 0; + for (int i = 0; i < g_napps; i++) { + struct ams_app *a = &g_apps[i]; + if (a->state == AMS_STOPPED || a->pid <= 0) continue; + found = 1; + unsigned long long dur = a->launch_ms ? (now_ms() - (unsigned long long)a->launch_ms) : 0; + const char *st = (a->state == AMS_FG) ? "fg" : (a->state == AMS_BG) ? "bg" : "stop"; + int m = snprintf(resp + l, rn - l, "RUN %s %s %ld %llu %s\n", + a->pkg, st, (long)a->pid, (unsigned long long)dur, + a->activity); + if (m > 0) l += m; + } + if (!found) { int m = snprintf(resp + l, rn - l, "(none running)\n"); if (m > 0) l += m; } + return l; +} + +static int cmd_list(char *resp, int rn) +{ + int l = snprintf(resp, rn, "# amd register+state\n"); + for (int i = 0; i < g_napps; i++) { + struct ams_app *a = &g_apps[i]; + const char *st = (a->state == AMS_FG) ? "fg" : (a->state == AMS_BG) ? "bg" : "stop"; + const char *lk = (a->last_kind == 2) ? "sig" : (a->last_kind == 1) ? "exit" : "clean"; + int m = snprintf(resp + l, rn - l, "APP %s %s %s %ld %d %d %s %d\n", + a->pkg, a->name, st, (long)a->pid, a->launches, a->crashes, lk, a->last_code); + if (m > 0) l += m; + } + return l; +} + +static int cmd_history(const char *pkg, char *resp, int rn) +{ + int l = snprintf(resp, rn, "# amd history %s\n", pkg && pkg[0] ? pkg : "(all)"); + if (pkg && pkg[0]) { + struct ams_app *a = app_find(pkg); + if (!a) { int m = snprintf(resp + l, rn - l, "(no such app %s)\n", pkg); if (m > 0) l += m; return l; } + for (int k = 0; k < a->hist_n; k++) { + int idx = (a->hist_idx - a->hist_n + k + AMS_HIST) % AMS_HIST; + struct ams_ev *e = &a->hist[idx]; + int m = snprintf(resp + l, rn - l, "EVT %ld %ld %s %s %s\n", + e->epoch, e->up, e->kind, a->pkg, e->detail); + if (m > 0) l += m; + } + } else { + for (int i = 0; i < g_napps; i++) { + struct ams_app *a = &g_apps[i]; + for (int k = 0; k < a->hist_n; k++) { + int idx = (a->hist_idx - a->hist_n + k + AMS_HIST) % AMS_HIST; + struct ams_ev *e = &a->hist[idx]; + int m = snprintf(resp + l, rn - l, "EVT %ld %ld %s %s %s\n", + e->epoch, e->up, e->kind, a->pkg, e->detail); + if (m > 0) l += m; + } + } + } + return l; +} + +static int cmd_start(const char *spec, char *resp, int rn) +{ + /* spec: or / */ + char pkg[MEOW_APP_ID_MAX], act[MEOW_ACT_MAX]; + snprintf(pkg, sizeof pkg, "%s", spec); + act[0] = '\0'; + char *slash = strchr(pkg, '/'); + if (slash) { *slash = 0; snprintf(act, sizeof act, "%s", slash + 1); } + struct ams_app *a = app_find(pkg); + if (!a) { + int m = snprintf(resp, rn, "ERR pkg not registered: %s\n", pkg); + return m > 0 ? m : 0; + } + m_ams_start_t s; + memset(&s, 0, sizeof s); + snprintf(s.app_id, sizeof s.app_id, "%s", pkg); + if (act[0]) snprintf(s.activity, sizeof s.activity, "%s", act); + uint32_t code = MEOW_ERR_INTERNAL; + int rt = ams_request_ctl(MSG_AMS_START, &s, sizeof s, &code); + char detail[192]; + if (rt == 0 && code == MEOW_OK) { + hist_push(a, "ams_start", "intent (forwarded)", (long)time(NULL), read_uptime_sec()); + snprintf(detail, sizeof detail, "ok forwarded"); + emit_log("AMS start %s%s forwarded to launcher", pkg, act[0] ? "/" : ""); + } else if (rt == 0 && code == (uint32_t)MEOW_ERR_BUSY) { + snprintf(detail, sizeof detail, "busy (app running)"); + emit_log("AMS start %s -> BUSY (app running)", pkg); + } else { + snprintf(detail, sizeof detail, "launcher offline"); + emit_log("AMS start %s -> launcher offline", pkg); + } + snprintf(resp, rn, "%s %s/%s\n", strstr(detail, "ok forwarded") ? "OK" : "ERR", + pkg, act[0] ? act : "(default)"); + return (int)strlen(resp); +} + +static int cmd_kill(const char *pkg, char *resp, int rn) +{ + struct ams_app *a = app_find(pkg); + if (!a) { int m = snprintf(resp, rn, "ERR pkg not registered: %s\n", pkg); return m > 0 ? m : 0; } + if (a->state == AMS_STOPPED || a->pid <= 0) { + int m = snprintf(resp, rn, "ERR not running: %s\n", pkg); + return m > 0 ? m : 0; + } + m_ams_kill_t k; + memset(&k, 0, sizeof k); + snprintf(k.app_id, sizeof k.app_id, "%s", pkg); + uint32_t code = 0; + int rt = ams_request_ctl(MSG_AMS_KILL, &k, sizeof k, &code); + if (rt != 0 && a->pid > 0) { + /* 兜底:launcher 不在,直接 SIGTERM(与 launcher 的 SIGTERM 语义一致) */ + kill(a->pid, SIGTERM); + emit_log("AMS kill %s -> direct SIGTERM (launcher offline)", pkg); + } else if (rt != 0) { + return snprintf(resp, rn, "ERR launcher offline: %s\n", pkg); + } else if (code != MEOW_OK) { + emit_log("AMS kill %s -> launcher refused code=%u", pkg, code); + return snprintf(resp, rn, "ERR launcher refused: %s (code=%u)\n", pkg, code); + } + hist_push(a, "ams_kill", "terminate (via launcher)", (long)time(NULL), read_uptime_sec()); + int m = snprintf(resp, rn, "OK terminate %s\n", pkg); + emit_log("AMS kill %s -> terminate", pkg); + return m > 0 ? m : 0; +} + +/* 把一「行」分发。命令 → 生成响应返回 1;事件 → 返回 0。 */ +static int dispatch_line(char *line, char *resp, int rn) +{ + int l = (int)strlen(line); + while (l > 0 && (line[l - 1] == '\n' || line[l - 1] == '\r')) line[--l] = '\0'; + if (l == 0) return 0; + + /* 事件:EV */ + if (strncmp(line, "EV ", 3) == 0) { + char *tok[16]; + int nt = 0; + char *save = NULL, *cur = line + 3; + for (int i = 0; i < (int)(sizeof tok / sizeof tok[0]); i++) { + char *tk = strtok_r(i == 0 ? cur : NULL, " ", &save); + if (!tk) break; + tok[nt++] = tk; + } + if (nt < 1) return 0; + if (!strcmp(tok[0], "LAUNCH")) { handle_launch(tok + 1, nt - 1); } + else if (!strcmp(tok[0], "EXIT")) { handle_exit(tok + 1, nt - 1); } + else if (!strcmp(tok[0], "OUT")) { handle_out(tok + 1, nt - 1); } + return 0; + } + + /* 命令:status/list/history/start/kill */ + char *save = NULL; + char *cmd = strtok_r(line, " ", &save); + if (!cmd) return 0; + char *arg = strtok_r(NULL, " ", &save); + if (!strcmp(cmd, "status")) return cmd_status(resp, rn) > 0; + if (!strcmp(cmd, "list")) return cmd_list(resp, rn) > 0; + if (!strcmp(cmd, "history")) { + if (arg && arg[0]) cmd_history(arg, resp, rn); + else cmd_history(NULL, resp, rn); + return 1; + } + if (!strcmp(cmd, "start")) { + if (!arg) { snprintf(resp, rn, "ERR start needs [/]\n"); return 1; } + cmd_start(arg, resp, rn); + return 1; + } + if (!strcmp(cmd, "kill")) { + if (!arg) { snprintf(resp, rn, "ERR kill needs \n"); return 1; } + cmd_kill(arg, resp, rn); + return 1; + } + snprintf(resp, rn, "ERR unknown cmd: %s\n", cmd); + return 1; +} + +/* ---- 连接处理(读可用字节,处理完整行,命令回响应) ---- */ +/* 读入可用字节并处理所有「完整行」(以 \n 结尾);未完结的尾巴保留在 buf(跨 poll 累积)。 + * 命令行 → 响应累积到 c->resp(返回后由调用方写回)。 + * 返回 1=读到 EOF(对端已关闭写端,应随后收尾关闭);0=还有后续。 */ +static int conn_read(struct ams_conn *c) +{ + int eof = 0; + while (c->n < (int)sizeof c->buf - 1) { + ssize_t r = read(c->fd, c->buf + c->n, sizeof c->buf - 1 - c->n); + if (r == 0) { eof = 1; break; } + if (r < 0) break; /* EAGAIN / EINTR 等:还有后续 */ + c->n += (int)r; + } + c->buf[c->n] = '\0'; + + /* 从 buf 头部逐条切「完整行」,完成 >=1 行后 memmove 保留未完结尾巴。 */ + size_t consumed = 0; + size_t buflen = (size_t)c->n; + while (consumed < buflen) { + char *nl = memchr(c->buf + consumed, '\n', buflen - consumed); + if (!nl) break; /* 剩余为未完结尾巴,保留 */ + *nl = '\0'; + size_t linelen = (size_t)(nl - (c->buf + consumed)); + if (linelen > 0) { + int got = dispatch_line(c->buf + consumed, c->resp + c->resp_n, + (int)sizeof c->resp - c->resp_n); + if (got) c->resp_n += (int)strlen(c->resp + c->resp_n); + } + consumed += linelen + 1; + } + /* 保留未完结尾巴(无 \n)到 buf 头部;丢弃已消费部分。 */ + if (consumed > 0 && consumed < buflen) { + memmove(c->buf, c->buf + consumed, buflen - consumed); + c->n = (int)(buflen - consumed); + c->buf[c->n] = '\0'; + } else if (consumed >= buflen) { + c->n = 0; c->buf[0] = '\0'; + } + return eof; +} + +/* 连接关闭(EOF)后:处理可能残留的最后一行(无 \n),再写回累积响应。 */ +static void conn_finish(struct ams_conn *c) +{ + if (c->n > 0) { + c->buf[c->n] = '\0'; + int got = dispatch_line(c->buf, c->resp + c->resp_n, (int)sizeof c->resp - c->resp_n); + if (got) c->resp_n += (int)strlen(c->resp + c->resp_n); + c->n = 0; + } + if (c->resp_n > 0) { + ssize_t w = write(c->fd, c->resp, (size_t)c->resp_n); + (void)w; + c->resp_n = 0; + } +} + +/* ---- 信号 / socket ---- */ +static void sig_handler(int sig) +{ + g_shutdown = 1; + char ch = 'T'; + ssize_t w = write(g_sigpipe[1], &ch, 1); + (void)w; + (void)sig; +} + +static int setup_signals(void) +{ + if (pipe(g_sigpipe) != 0) return -1; + int rf = fcntl(g_sigpipe[0], F_GETFL, 0); + if (rf >= 0) fcntl(g_sigpipe[0], F_SETFL, rf | O_NONBLOCK); + int wf = fcntl(g_sigpipe[1], F_GETFL, 0); + if (wf >= 0) fcntl(g_sigpipe[1], F_SETFL, wf | O_NONBLOCK); + struct sigaction sa; + memset(&sa, 0, sizeof sa); + sa.sa_handler = sig_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + signal(SIGPIPE, SIG_IGN); + return 0; +} + +static int setup_socket(void) +{ + unlink(g_sock); + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { fprintf(stderr, "[meow_amsd] socket: %s\n", strerror(errno)); return -1; } + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + safe_strcpy(a.sun_path, sizeof a.sun_path, g_sock); + if (bind(fd, (struct sockaddr *)&a, sizeof a) != 0) { + fprintf(stderr, "[meow_amsd] bind %s: %s\n", g_sock, strerror(errno)); + close(fd); + return -1; + } + (void)chmod(g_sock, 0666); + if (listen(fd, 8) != 0) { fprintf(stderr, "[meow_amsd] listen: %s\n", strerror(errno)); close(fd); return -1; } + g_listen_fd = fd; + return 0; +} + +static int run_daemon(void) +{ + if (setup_signals() != 0) return 1; + if (setup_socket() != 0) return 1; + boot_ts(g_bootts, sizeof g_bootts); + log_open_card(); + log_cleanup_old(); + registry_scan(); + + fprintf(stderr, "[meow_amsd] up: sock=%s ipc=%s apps=%s card=%s bootts=%s log=%s\n", + g_sock, g_ipc_sock, g_apps_dir, g_card, g_bootts, + g_on_card ? "card" : AMS_TMP_LOG); + + struct pollfd pfds[2 + AMS_MAX_CONNS]; + struct ams_conn conns[AMS_MAX_CONNS]; + memset(conns, 0, sizeof conns); + for (int i = 0; i < AMS_MAX_CONNS; i++) conns[i].fd = -1; + int nfds = 2; + + unsigned long long last_cleanup = 0; + while (!g_shutdown) { + pfds[0].fd = g_listen_fd; pfds[0].events = POLLIN; pfds[0].revents = 0; + pfds[1].fd = g_sigpipe[0]; pfds[1].events = POLLIN; pfds[1].revents = 0; + for (int i = 0; i < nfds - 2; i++) { + pfds[2 + i].fd = conns[i].fd; + pfds[2 + i].events = POLLIN; + pfds[2 + i].revents = 0; + } + int r = poll(pfds, (nfds_t)nfds, AMS_TICK_MS); + if (r < 0) { + if (errno == EINTR) continue; + fprintf(stderr, "[meow_amsd] poll: %s\n", strerror(errno)); + break; + } + if (g_shutdown) break; + + if (pfds[0].revents & POLLIN) { + int cfd = accept(g_listen_fd, NULL, NULL); + if (cfd >= 0) { + int fl = fcntl(cfd, F_GETFL, 0); + if (fl >= 0) fcntl(cfd, F_SETFL, fl | O_NONBLOCK); + if (nfds - 2 < AMS_MAX_CONNS) { + conns[nfds - 2].fd = cfd; + conns[nfds - 2].n = 0; + conns[nfds - 2].resp_n = 0; + nfds++; + } else close(cfd); + } + } + if (pfds[1].revents & POLLIN) { + char tmp[64]; + while (read(g_sigpipe[0], tmp, sizeof tmp) > 0) {} + } + for (int i = 0; i < nfds - 2; i++) { + struct ams_conn *c = &conns[i]; + int ev = pfds[2 + i].revents; + int done = 0; + if (ev & (POLLIN | POLLHUP)) { + if (conn_read(c)) { conn_finish(c); done = 1; } /* EOF:处理残行+回响应后关 */ + } + if (!done && (ev & (POLLHUP | POLLERR | POLLNVAL))) { conn_finish(c); done = 1; } + /* 命令行 → 响应已就绪:写回并关闭(am 单命令即回)。 */ + if (c->resp_n > 0) { + ssize_t w = write(c->fd, c->resp, (size_t)c->resp_n); + (void)w; + c->resp_n = 0; + done = 1; + } + if (done) { + close(c->fd); + c->fd = -1; + for (int j = i; j < nfds - 3; j++) conns[j] = conns[j + 1]; + nfds--; + i--; + } + } + if (now_ms() - last_cleanup >= (unsigned long long)AMS_CLEANUP_MS) { + last_cleanup = now_ms(); + poll_card(); + log_cleanup_old(); + } + } + + if (g_listen_fd >= 0) close(g_listen_fd); + unlink(g_sock); + out_close(); + if (g_log_fd >= 0) close(g_log_fd); + fprintf(stderr, "[meow_amsd] shutdown\n"); + return 0; +} + +int main(int argc, char **argv) +{ + for (int i = 1; i < argc; i++) { + const char *a = argv[i]; + if (!strcmp(a, "--sock") && i + 1 < argc) safe_strcpy(g_sock, sizeof g_sock, argv[++i]); + else if (!strcmp(a, "--ipc-sock") && i + 1 < argc) safe_strcpy(g_ipc_sock, sizeof g_ipc_sock, argv[++i]); + else if (!strcmp(a, "--apps") && i + 1 < argc) safe_strcpy(g_apps_dir, sizeof g_apps_dir, argv[++i]); + else if (!strcmp(a, "--card") && i + 1 < argc) safe_strcpy(g_card, sizeof g_card, argv[++i]); + else if (!strcmp(a, "--crash-sock") && i + 1 < argc) safe_strcpy(g_crash_sock, sizeof g_crash_sock, argv[++i]); + else if (!strcmp(a, "-h") || !strcmp(a, "--help")) { + fprintf(stderr, "usage: %s [--sock

] [--ipc-sock

] [--apps

] " + "[--card

] [--crash-sock

]\n", argv[0]); + return 0; + } else if (a[0] == '-') { fprintf(stderr, "bad option %s\n", a); return 2; } + } + const char *e; + if ((e = getenv("MEOW_AMS_SOCK")) && e[0]) safe_strcpy(g_sock, sizeof g_sock, e); + if ((e = getenv("MEOW_IPC_SOCK")) && e[0]) safe_strcpy(g_ipc_sock, sizeof g_ipc_sock, e); + if ((e = getenv("MEOW_AMSD_APPS")) && e[0]) safe_strcpy(g_apps_dir, sizeof g_apps_dir, e); + if ((e = getenv("MEOW_AMSD_CARD")) && e[0]) safe_strcpy(g_card, sizeof g_card, e); + if ((e = getenv("MEOW_CRASH_SOCK")) && e[0]) safe_strcpy(g_crash_sock, sizeof g_crash_sock, e); + + setvbuf(stdout, NULL, _IONBF, 0); + return run_daemon(); /* svcd 空参拉起 → 前台常驻 */ +} diff --git a/patch_v2/meow_amsd/meow_amsd_host b/patch_v2/meow_amsd/meow_amsd_host new file mode 100755 index 0000000..0ade591 Binary files /dev/null and b/patch_v2/meow_amsd/meow_amsd_host differ diff --git a/patch_v2/meow_launcherd/app_launcher.c b/patch_v2/meow_launcherd/app_launcher.c index 8abbcc1..8fe090b 100644 --- a/patch_v2/meow_launcherd/app_launcher.c +++ b/patch_v2/meow_launcherd/app_launcher.c @@ -10,6 +10,189 @@ int g_naccts = 0; volatile int g_app_session_active = 0; /* §10.5:1=launch_app waitpid 前台会话中(util.c 读) */ +/* ---- amsd 上报 / 控制挂点(AMSD_DESIGN §4):best-effort,amsd 不在不影响 launcher ---- */ +#define MEOW_AMS_SOCK_DEFAULT "/usr/data/meow_ams.sock" + +/* amsd socket 路径:运行时读 env MEOW_AMS_SOCK(宿主/qemu 覆写),缺省用默认。 */ +static const char *ams_sock_path(void) +{ + static char p[108]; + static int once = 0; + if (!once) { + const char *e = getenv("MEOW_AMS_SOCK"); + snprintf(p, sizeof p, "%s", (e && e[0]) ? e : MEOW_AMS_SOCK_DEFAULT); + once = 1; + } + return p; +} + +/* up 秒(/proc/uptime 首字段)。失败 0。 */ +static long ams_uptime(void) +{ + FILE *f = fopen("/proc/uptime", "r"); + if (!f) return 0; + char b[64]; + long up = 0; + if (fgets(b, sizeof b, f)) up = (long)atof(b); + fclose(f); + return up; +} + +/* 向 amsd 发一条多行负载:非阻塞 connect + 单次 write + close;失败静默丢。 */ +static void ams_send(const char *payload, int len) +{ + if (!payload || len <= 0) return; + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) return; + int fl = fcntl(fd, F_GETFL, 0); + if (fl >= 0) fcntl(fd, F_SETFL, fl | O_NONBLOCK); + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + strncpy(a.sun_path, ams_sock_path(), sizeof a.sun_path - 1); + if (connect(fd, (struct sockaddr *)&a, sizeof a) != 0) { + if (errno == EINPROGRESS || errno == EWOULDBLOCK) { + struct pollfd pf; + pf.fd = fd; pf.events = POLLOUT; pf.revents = 0; + int r = poll(&pf, 1, 150); + if (r <= 0 || !(pf.revents & POLLOUT)) { close(fd); return; } + } else { close(fd); return; } + } + ssize_t w = write(fd, payload, (size_t)len); + (void)w; + close(fd); +} + +/* launch 上报:pkg/activity/chain/pid。amsd 建表项 + 开 stdout 会话。 */ +static void ams_report_launch(const struct app *a, const char *chain, pid_t pid) +{ + if (!a) return; + char act[MEOW_ACT_MAX + 1] = ""; + char ch[MEOW_CHAIN_MAX + 1] = "-"; + if (chain && chain[0]) { + const char *top = strrchr(chain, ';'); + top = top ? top + 1 : chain; + snprintf(act, sizeof act, "%s", top); + snprintf(ch, sizeof ch, "%s", chain); + } + char line[512]; + int l = snprintf(line, sizeof line, "EV LAUNCH %s %s %s %d %ld %ld\n", + a->id, act, ch, (int)pid, (long)time(NULL), ams_uptime()); + if (l > 0) ams_send(line, l); +} + +/* exit 上报:kind(exit|sig)/code/dur_ms。异常退出由 amsd 判 crash + 转发 meow_crash。 */ +static void ams_report_exit(const struct app *a, int status, long long dur_ms) +{ + if (!a) return; + const char *kind; + int code; + if (WIFSIGNALED(status)) { kind = "sig"; code = WTERMSIG(status); } + else { kind = "exit"; code = WIFEXITED(status) ? WEXITSTATUS(status) : 1; } + char line[256]; + int l = snprintf(line, sizeof line, "EV EXIT %s %s %d %lld %ld %ld\n", + a->id, kind, code, dur_ms, (long)time(NULL), ams_uptime()); + if (l > 0) ams_send(line, l); +} + +/* ---- stdout/stderr 管道捕获(AMSD_DESIGN §5):前台 waitpid 循环排空 + 按行转发 ---- */ +static char g_ams_out_line[1024]; /* 未完结行缓冲(无 \n) */ +static int g_ams_out_n = 0; +static char g_ams_out_flush[4096]; /* 已完结、待转发行 */ +static int g_ams_out_flush_n = 0; +static int g_ams_out_had = 0; /* 本次迭代是否排空到数据(驱动短睡) */ + +static void ams_out_flush(void) +{ + if (g_ams_out_flush_n > 0) { + ams_send(g_ams_out_flush, g_ams_out_flush_n); + g_ams_out_flush_n = 0; + } +} + +/* 追加一行(line 为无 \n 的 NUL 串)到 flush 缓冲;满了先 flush。 */ +static void ams_out_line(const struct app *a, pid_t pid, const char *line) +{ + if (!a || !line) return; + int need = snprintf(NULL, 0, "EV OUT %s %d %ld %ld %s\n", + a->id, (int)pid, (long)time(NULL), ams_uptime(), line); + if (g_ams_out_flush_n + need + 1 > (int)sizeof g_ams_out_flush) ams_out_flush(); + if (need < (int)sizeof g_ams_out_flush - 1 - g_ams_out_flush_n) + g_ams_out_flush_n += snprintf(g_ams_out_flush + g_ams_out_flush_n, + sizeof g_ams_out_flush - g_ams_out_flush_n, + "EV OUT %s %d %ld %ld %s\n", + a->id, (int)pid, (long)time(NULL), ams_uptime(), line); +} + +/* 排空读端全部可用字节,按行切分转发;超长行截断标记。 */ +static void ams_drain_stdout(const struct app *a, int fd, pid_t pid) +{ + char tmp[2048]; + ssize_t n; + while ((n = read(fd, tmp, sizeof tmp)) > 0) { + g_ams_out_had = 1; + for (ssize_t i = 0; i < n; i++) { + char c = tmp[i]; + if (c == '\n') { + g_ams_out_line[g_ams_out_n] = '\0'; + ams_out_line(a, pid, g_ams_out_line); + g_ams_out_n = 0; + } else if (c == '\r') { + /* 去尾 \r */ + } else if (g_ams_out_n < (int)sizeof g_ams_out_line - 1) { + g_ams_out_line[g_ams_out_n++] = c; + } else { + /* 超长行:截断标记 + 重置(当前字符丢弃) */ + g_ams_out_line[ (int)sizeof g_ams_out_line - 2 ] = '\0'; + ams_out_line(a, pid, g_ams_out_line); + ams_out_line(a, pid, "[...trunc]"); + g_ams_out_n = 0; + } + } + } +} + +/* ---- amsd start/kill 挂点(主循环单写者)---- */ +static struct { char pkg[MEOW_APP_ID_MAX]; char act[MEOW_ACT_MAX]; int pending; } g_ams_start; +static struct { char pkg[MEOW_APP_ID_MAX]; int pending; } g_ams_kill; + +int meow_ams_pending_start_set(const char *pkg, const char *act) +{ + if (!pkg || !pkg[0]) return -MEOW_ERR_INVAL; + if (g_app_session_active) return -MEOW_ERR_BUSY; + snprintf(g_ams_start.pkg, sizeof g_ams_start.pkg, "%s", pkg); + g_ams_start.act[0] = 0; + if (act && act[0]) snprintf(g_ams_start.act, sizeof g_ams_start.act, "%s", act); + g_ams_start.pending = 1; + return 0; +} + +int meow_ams_take_start(char *pkg, int pn, char *act, int an) +{ + if (!g_ams_start.pending) return 0; + g_ams_start.pending = 0; + if (pkg && pn > 0) snprintf(pkg, pn, "%s", g_ams_start.pkg); + if (act && an > 0) snprintf(act, an, "%s", g_ams_start.act); + return 1; +} + +void meow_ams_pending_kill_set(const char *pkg) +{ + if (!pkg || !pkg[0]) return; + snprintf(g_ams_kill.pkg, sizeof g_ams_kill.pkg, "%s", pkg); + g_ams_kill.pending = 1; +} + +int meow_ams_kill_pending(const char *pkg) +{ + if (!g_ams_kill.pending) return 0; + if (pkg && strcmp(g_ams_kill.pkg, pkg) != 0) return 0; + g_ams_kill.pending = 0; + g_ams_kill.pkg[0] = 0; + return 1; +} + + /* ---- 阶段 D · M1:路由注册表辅助(主循环单写者,无锁)---- */ struct meow_acct *meow_acct_find(const char *app_id) { @@ -990,9 +1173,23 @@ static int launch_app_chain(struct app *a, const char *chain) if (pipe(execpipe) < 0) { logmsg("exec pipe failed: %s", strerror(errno)); return -1; } fcntl(execpipe[1], F_SETFD, FD_CLOEXEC); + /* AMSD_DESIGN §5:stdout/stderr 管道捕获(logcat 思路)。fork 前建管道;子进程 dup2 到 + * stdout/stderr;父进程保留读端(O_NONBLOCK)+ 前台 waitpid 循环排空转发 amsd。 + * 管道建失败仅 log 不崩(App 照常无捕获运行)。写端设 CLOEXEC:exec 成功后关闭写端, + * App 死 → 读端 EOF(崩溃尾巴可排空)。 */ + int out_pipe[2] = { -1, -1 }; + if (pipe(out_pipe) != 0) { + out_pipe[0] = -1; out_pipe[1] = -1; + logmsg("ams: stdout pipe fail: %s", strerror(errno)); + } else { + fcntl(out_pipe[1], F_SETFD, FD_CLOEXEC); + } + pid_t pid = fork(); if (pid < 0) { close(execpipe[0]); close(execpipe[1]); + if (out_pipe[0] >= 0) close(out_pipe[0]); + if (out_pipe[1] >= 0) close(out_pipe[1]); logmsg("fork failed: %s", strerror(errno)); return -1; } if (pid == 0) { @@ -1002,6 +1199,14 @@ static int launch_app_chain(struct app *a, const char *chain) * (读全局 log.level),使 SDK meow_log() 按 app 分离写 logs//_.log 并做级别过滤。 * M2.x 简化:MEOW_LOG_DIR 从 logs/apps//logs 改注入 logs/(目录不存在则建)。 */ close(execpipe[0]); + /* AMSD_DESIGN §5:stdout/stderr 合并重定向到管道写端(dup2 不清 CLOEXEC), + * 原始 out_pipe 两根 fd 关闭;成功 exec 后仅 fd1/2 持有写端,App 死 → EOF。 */ + if (out_pipe[1] >= 0) { + dup2(out_pipe[1], STDOUT_FILENO); + dup2(out_pipe[1], STDERR_FILENO); + close(out_pipe[1]); + close(out_pipe[0]); + } char *argv[] = { a->exec, NULL }; char env_path[320], env_log[320], env_data[320], env_cache[320]; char app_log_dir[288]; @@ -1130,6 +1335,12 @@ static int launch_app_chain(struct app *a, const char *chain) _exit(127); } close(execpipe[1]); + if (out_pipe[1] >= 0) close(out_pipe[1]); /* parent: 关闭写端 */ + int out_fd = out_pipe[0]; /* parent 保留读端,前台循环排空 */ + if (out_fd >= 0) { + int fl = fcntl(out_fd, F_GETFL, 0); + if (fl >= 0) fcntl(out_fd, F_SETFL, fl | O_NONBLOCK); + } int exe_err = 0; ssize_t er = read(execpipe[0], &exe_err, sizeof exe_err); close(execpipe[0]); @@ -1138,9 +1349,13 @@ static int launch_app_chain(struct app *a, const char *chain) strerror(exe_err), exe_err); int st = 0; waitpid(pid, &st, 0); /* reap the failed child */ + if (out_fd >= 0) close(out_fd); return -1; } + /* AMSD_DESIGN §4.1:启动上报(pkg/activity/chain/pid),amsd 建表项 + 开 stdout 捕获会话。 */ + ams_report_launch(a, chain, pid); + /* GESTURE_BACK §2.4:应用启动即写 immersive 状态,watchdog 依此隐藏/保留虚拟返回键 */ meow_write_app_state(a->immersive == '1' ? 1 : 0); @@ -1181,6 +1396,8 @@ static int launch_app_chain(struct app *a, const char *chain) * never fires on this unit, §18.12). Volume is snapped on the first * tap and restored when the sequence completes. */ int status = 0; + unsigned long long ams_t0 = now_ms(); /* AMSD_DESIGN §4.1:运行时长起点 */ + int ams_status_valid = 0; /* 仅真实 waitpid 回收才置 1(规避 sigterm/重启路径 status=0 假干净退出) */ int nav_clean_exit = 0; /* M3:MSG_NAV{EXIT} 主动退出 → 不按崩溃上报 */ int app_prev_vold = 0; int app_seq_hits = 0; @@ -1218,18 +1435,31 @@ static int launch_app_chain(struct app *a, const char *chain) meow_notif_open_clear(); for (;;) { int r = waitpid(pid, &status, WNOHANG); - if (r == pid) break; + if (r == pid) { ams_status_valid = 1; break; } if (r < 0 && errno != EINTR) break; if (sigterm_flag) { logmsg("sigterm during app"); kill(pid, SIGTERM); break; } + /* AMSD_DESIGN §5.1:排空 App stdout/stderr 管道(非阻塞),按行转发 amsd;App 写满不阻塞。 */ + g_ams_out_had = 0; + if (out_fd >= 0) ams_drain_stdout(a, out_fd, pid); /* M3 导航栈:前台会话排空 IPC(App 经 MSG_NAV REPORT/EXIT 上报导航/请求退出)。 * REPORT 更新 nav_stack(meow_ipc_process 内单写者);EXIT → 终止应用回主屏。 */ meow_ipc_process(); + /* AMSD_DESIGN §4.3:amsd `am kill` → 前台循环消费 → SIGTERM(执行器=launcher)。 */ + if (meow_ams_kill_pending(a->id)) { + logmsg("ams: kill %s -> SIGTERM", a->name); + kill(pid, SIGTERM); + usleep(300000); + if (waitpid(pid, &status, WNOHANG) != pid) { kill(pid, SIGKILL); waitpid(pid, &status, 0); } + ams_status_valid = 1; + break; + } if (meow_nav_exit_pending()) { logmsg("nav: app %s MSG_NAV{EXIT} -> terminate, return to home", a->name); nav_clean_exit = 1; kill(pid, SIGTERM); usleep(300000); if (waitpid(pid, &status, WNOHANG) != pid) { kill(pid, SIGKILL); waitpid(pid, &status, 0); } + ams_status_valid = 1; break; } /* §10.5:App 前台顶部下滑(MSG_NOTIF_OPEN)→ 展开通知中心叠加会话。复用电源菜单 @@ -1248,6 +1478,7 @@ static int launch_app_chain(struct app *a, const char *chain) /* App 会话中崩溃(overlay 内 proc_is_zombie 探测):回收僵尸取真实状态, * 走统一应用退出路径(不 SIGCONT 死进程、不恢复死帧;launcher 不崩)。 */ waitpid(pid, &status, 0); + ams_status_valid = 1; break; } } @@ -1259,6 +1490,7 @@ static int launch_app_chain(struct app *a, const char *chain) kill(pid, SIGTERM); usleep(300000); if (waitpid(pid, &status, WNOHANG) != pid) { kill(pid, SIGKILL); waitpid(pid, &status, 0); } + ams_status_valid = 1; break; } /* in-app kill: VOL_DOWN x3 quick taps */ @@ -1323,6 +1555,7 @@ static int launch_app_chain(struct app *a, const char *chain) usleep(500000); kill(pid, SIGKILL); waitpid(pid, &status, 0); + ams_status_valid = 1; break; } } @@ -1373,12 +1606,31 @@ static int launch_app_chain(struct app *a, const char *chain) app_overlay_statusbar(); } } - /* 顶条收合/恢复过渡期把循环睡得短些(~20ms),驱动更平滑的上移出屏;稳定后回 100ms。 */ - usleep(meow_ui_statusbar_animating() ? 20000 : 100000); + /* 顶条收合/恢复过渡期把循环睡得短些(~20ms),驱动更平滑的上移出屏;稳定后回 100ms。 + * AMSD_DESIGN §5.1:本轮排空到 stdout 数据 → 也睡短些,防止 App 写满管道被阻塞。 */ + usleep((meow_ui_statusbar_animating() || g_ams_out_had) ? 20000 : 100000); } /* §10.5:App 前台会话结束,清会话标记(ipc_dispatch 不再受理 MSG_NOTIF_OPEN) */ g_app_session_active = 0; meow_notif_open_clear(); + /* AMSD_DESIGN §5.1:崩溃尾巴不丢 —— App 已死,终局排空剩余 stdout/stderr(含未完结行) + * 先转发 amsd,再报 EXIT / crash。仅当有读端(管道建立成功)才 drain。 */ + if (out_fd >= 0) { + g_ams_out_had = 0; + ams_drain_stdout(a, out_fd, pid); /* 排空全部剩余(App 死 → read 返回 0=EOF) */ + if (g_ams_out_n > 0) { /* 未完结尾行:强制提交 */ + g_ams_out_line[g_ams_out_n] = '\0'; + ams_out_line(a, pid, g_ams_out_line); + g_ams_out_n = 0; + } + ams_out_flush(); + close(out_fd); + out_fd = -1; + } + /* AMSD_DESIGN §4.1:waitpid 回收上报(真实回收才报;规避 sigterm/电源菜单重启 status=0 的 + * 假干净退出)。amsd 依 kind/code 判 crash 并转发 meow_crash(SVC 格式)。 */ + if (ams_status_valid) + ams_report_exit(a, status, (long long)(now_ms() - ams_t0)); /* M2.x:App 异常退出(信号杀 / 非 0 退出码)→ best-effort 直报 meow_crash。 * 判据与 svcd reap_children 一致:failed = !(WIFEXITED && WEXITSTATUS==0)。干净 exit(0) * 不上报;sigterm 关停/电源菜单重启路径 status 保持 0(未 waitpid)→ 视为干净退出不上报。 */ diff --git a/patch_v2/meow_launcherd/main_menu.c b/patch_v2/meow_launcherd/main_menu.c index 43b6ac3..6791401 100644 --- a/patch_v2/meow_launcherd/main_menu.c +++ b/patch_v2/meow_launcherd/main_menu.c @@ -683,6 +683,33 @@ static unsigned long long apps_dir_sig(void) return sig; } +/* M2.83 修补:字库目录签名(根+各 family 目录 mtime)。meow_font_scan_presets 内含逐套 + * sha256 校验(gbk 套 6.8MB 级),绝不能每 2s 轮询全量扫——只在目录签名变化时扫。 */ +static unsigned long long fonts_dir_sig(void) +{ + char root[300]; + snprintf(root, sizeof root, "/usr/data/mnt/sd_0/%s", MEOW_FONTS_CFG); + unsigned long long sig = 1469598103934665603ULL; + struct stat st; + if (stat(root, &st) != 0) return 0; + sig ^= (unsigned long long)st.st_mtime; sig *= 1099511628211ULL; + DIR *d = opendir(root); + if (d) { + struct dirent *de; + while ((de = readdir(d))) { + if (de->d_name[0] == '.') continue; + char p[400]; + snprintf(p, sizeof p, "%s/%s", root, de->d_name); + if (stat(p, &st) == 0 && S_ISDIR(st.st_mode)) { + sig ^= (unsigned long long)st.st_mtime * 256 + (unsigned char)de->d_name[0]; + sig *= 1099511628211ULL; + } + } + closedir(d); + } + return sig; +} + static void run_menu(struct app apps[MAX_APPS], int napps, int state, long sim_tap_ms, long sim_exit_ms) { @@ -700,6 +727,7 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state, long sim_tap_done = 0, sim_exit_done = 0; long meow_last_poll = 0; /* M2.2:MeowOS 后台拔营(卡/网)轮询时间戳 */ unsigned long long apps_sig = 0; /* M2.77:应用目录签名(0=未初始化,首圈建立基线) */ + unsigned long long fonts_sig = 0; /* M2.83:字库目录签名(门控 scan_presets,防 2s 轮询 sha256 风暴) */ load_config(); /* meow.conf on the card: bg_color etc. */ menu_icons_load(apps, napps); /* MD3:应用图标解码一次进缓存(不每帧解码) */ @@ -824,6 +852,45 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state, /* 阶段 D · M0:菜单/主屏循环内排空 IPC(非阻塞;应用可随时查询/声明路由) */ meow_ipc_process(); + /* AMSD_DESIGN §4.3:amsd `am start` → 消费 pending-start(仅菜单态/无前台 App 时拉起)。 + * 镜像 sim-tap 启动路径:launch 后 menu_after_app_exit + dirty 重绘菜单。 */ + { + char spkg[MEOW_APP_ID_MAX], sact[MEOW_ACT_MAX]; + if (meow_ams_take_start(spkg, sizeof spkg, sact, sizeof sact)) { + if (state != 0) { + snprintf(toast, sizeof toast, "AMS START: app list not ready"); + toast_ms = TOAST_MS; + } else { + int idx = -1; + for (int i = 0; i < napps; i++) + if (!strcmp(apps[i].id, spkg)) { idx = i; break; } + if (idx < 0) { + snprintf(toast, sizeof toast, "AMS START: NO APP %s", spkg); + toast_ms = TOAST_MS; + } else { + int ret; + if (sact[0]) { + char chain[MEOW_CHAIN_MAX]; int depth; + if (resolve_chain(spkg, sact, chain, &depth) == MEOW_OK) + ret = launch_activity(&apps[idx], chain); + else { + snprintf(toast, sizeof toast, "AMS: NO ROUTE %s/%s", spkg, sact); + toast_ms = TOAST_MS; ret = 0; + } + } else { + ret = launch_app(&apps[idx]); + } + if (ret != 0) { + snprintf(toast, sizeof toast, "CANNOT START: %s", apps[idx].name); + toast_ms = TOAST_MS; + } + menu_after_app_exit(ret == 0); + last_input_ms = now_ms(); /* M2.17:回主界面即视为输入 */ + dirty = 1; + } + } + } + } meow_notif_tick(); /* M2a:通知 TTL 10s 转储(§8)*/ meow_media_tick(); /* 阶段D附录B:媒体源死亡(播放中无 META 超时)清槽回空态 */ @@ -898,7 +965,15 @@ static void run_menu(struct app apps[MAX_APPS], int napps, int state, int was_nocard = (state == 1); if (!mfont_ready && meow_font_ensure()) { dirty = 1; logmsg("hotplug: font loaded late"); } if (mfont_ready) meow_font_reload_multifont(); /* M2.5x:卡挂载后重配多字体(此前无全局可配) */ - meow_font_scan_presets(); /* M2.82+:卡就绪后重扫字库(启动时卡未挂载会只有默认项) */ + /* M2.83 修补:字库重扫改为签名门控——扫描含逐套 sha256(gbk 套 6.8MB 级), + * 每 2s 全量扫会把 CPU 烧满(真机实测 100%)。只在字体目录签名变化时扫。 */ + { + unsigned long long fsig = fonts_dir_sig(); + if (fsig != fonts_sig) { + fonts_sig = fsig; + meow_font_scan_presets(); + } + } if (was_nocard || state != 0 || napps == 0) { struct app fresh[MAX_APPS]; int fn = scan_apps(fresh); diff --git a/patch_v2/meow_launcherd/meow_ipc.h b/patch_v2/meow_launcherd/meow_ipc.h index 23f4bb4..bc2f97a 100644 --- a/patch_v2/meow_launcherd/meow_ipc.h +++ b/patch_v2/meow_launcherd/meow_ipc.h @@ -72,6 +72,10 @@ enum { MSG_PICKER_RESULT = 0x0071, /* ↓ picker 返回值(候选结果回 A) */ MSG_META = 0x0072, /* ↑ 媒体会话上报元数据/进度/状态(阶段 D 附录 B) */ MSG_PLAYCTL = 0x0073, /* ↓ 播放控制下发/↑ 控制器转发到最近活跃媒体会话 */ + /* ---- amsd 控制(AMSD_DESIGN §4.2):amsd→launcher,申请拉起/终止某 App ---- + * 语义:amsd 为「启动/终止」决策者 → launcher(执行器)执行。payload 见下。 */ + MSG_AMS_START = 0x00A0, /* → 请求 launcher 拉起某 App(pkg + 可选 activity) */ + MSG_AMS_KILL = 0x00A1, /* → 请求 launcher 终止某 App(SIGTERM 语义) */ MSG_SYS = 0x00F0 /* ↓ 系统事件(守护关闭/服务死亡/播放器状态) */ }; @@ -319,4 +323,18 @@ typedef struct m_power_status { char msg[96]; /* 结果/状态文本 */ } m_power_status_t; +/* ---- amsd 控制(AMSD_DESIGN §4.2)---- */ + +/* MSG_AMS_START →:请求 launcher 拉起某 App。app_id=包名(权威);activity 空=裸启动 + * (launcher launch_app),非空=resolve_chain+launch_activity。 */ +typedef struct m_ams_start { + char app_id[MEOW_APP_ID_MAX]; /* 32 */ + char activity[MEOW_ACT_MAX]; /* 64;空=裸启动 */ +} m_ams_start_t; /* 96 B */ + +/* MSG_AMS_KILL →:请求 launcher 终止某 App(SIGTERM 语义,执行器=launcher)。 */ +typedef struct m_ams_kill { + char app_id[MEOW_APP_ID_MAX]; /* 32 */ +} m_ams_kill_t; /* 32 B */ + #endif /* MEOW_IPC_H */ diff --git a/patch_v2/meow_launcherd/meow_launcher_internal.h b/patch_v2/meow_launcherd/meow_launcher_internal.h index ebd6b78..b794f75 100644 --- a/patch_v2/meow_launcherd/meow_launcher_internal.h +++ b/patch_v2/meow_launcherd/meow_launcher_internal.h @@ -642,6 +642,12 @@ void meow_nav_report(const char *app_id, uint32_t depth, const char *current); void meow_nav_exit(void); /* 清栈(应用退出/EXIT) */ void meow_nav_request_exit(void); /* MSG_NAV{EXIT}:置位让前台 waitpid 循环终止 app */ int meow_nav_exit_pending(void); /* 读取并清除退出请求(前台 launch_* 循环轮询) */ +/* ---- amsd 控制挂点(AMSD_DESIGN §4.3):主循环单写者,跨模块声明 ---- + * pending-start/kill 仅在主循环上下文写入与消费(ipc_dispatch 写、run_menu 前台循环/菜单循环读)。 */ +int meow_ams_pending_start_set(const char *pkg, const char *act); /* 有前台 App 时拒绝(返回 -MEOW_ERR_BUSY) */ +int meow_ams_take_start(char *pkg, int pn, char *act, int an); /* 1=取到待拉起 App(清位);0=无 */ +void meow_ams_pending_kill_set(const char *pkg); /* 请求终止某 pkg */ +int meow_ams_kill_pending(const char *pkg); /* 匹配则清除并返回 1(前台循环轮询) */ void meow_dac_vol_write(int pct); void meow_dac_vol_write_throttled(void); diff --git a/patch_v2/meow_launcherd/meow_launcherd b/patch_v2/meow_launcherd/meow_launcherd index 521aa37..9e2ad18 100755 Binary files a/patch_v2/meow_launcherd/meow_launcherd and b/patch_v2/meow_launcherd/meow_launcherd differ diff --git a/patch_v2/meow_launcherd/util.c b/patch_v2/meow_launcherd/util.c index 8163f50..606f08c 100644 --- a/patch_v2/meow_launcherd/util.c +++ b/patch_v2/meow_launcherd/util.c @@ -1190,6 +1190,39 @@ static void ipc_dispatch(struct meow_ipc_msg *m) n.app_id, n.nav_type, n.depth, n.current); break; } + /* AMSD_DESIGN §4.3:amsd `am start`/`am kill` 控制请求。主循环单写者仅置位 pending + * (run_menu 前台循环 / launch_* waitpid 循环消费),不回执启动结果(一期 best-effort)。 */ + case MSG_AMS_START: { + m_ams_start_t s; + memset(&s, 0, sizeof s); + memcpy(&s, m->payload, m->plen < (uint16_t)sizeof s ? m->plen : (uint16_t)sizeof s); + s.app_id[MEOW_APP_ID_MAX - 1] = 0; + s.activity[MEOW_ACT_MAX - 1] = 0; + int rc = meow_ams_pending_start_set(s.app_id, s.activity); + m_ack_t a; + a.code = (uint32_t)(rc < 0 ? -rc : MEOW_OK); /* 0=OK;MEOW_ERR_BUSY=有前台 App */ + a.status = 0; + ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a); + logmsg("ipc: AMS_START app=%s act=\"%s\" -> ACK code=%u", s.app_id, s.activity, a.code); + break; + } + case MSG_AMS_KILL: { + m_ams_kill_t k; + memset(&k, 0, sizeof k); + memcpy(&k, m->payload, m->plen < (uint16_t)sizeof k ? m->plen : (uint16_t)sizeof k); + k.app_id[MEOW_APP_ID_MAX - 1] = 0; + m_ack_t a; + if (g_app_session_active) { + meow_ams_pending_kill_set(k.app_id); + a.code = MEOW_OK; + } else { + a.code = (uint32_t)MEOW_ERR_NOENT; /* 无前台 App 可终止 */ + } + a.status = 0; + ipc_send(m->fd, MSG_ACK, m->req_id, &a, sizeof a); + logmsg("ipc: AMS_KILL app=%s -> ACK code=%u", k.app_id, a.code); + break; + } /* M2a:通知存储(NOTIFICATION_SYSTEM.md §4/§7/§8)。IPC 线程只入队,此处在主循环 * 单写者落存储 + 回 ACK(与 HELLO/QUERY 同一线程模型,绝不在 socket IO 阻塞)。 */ case MSG_NOTIFY: { diff --git a/patch_v2/meow_svcd/S89meow_svcd b/patch_v2/meow_svcd/S89meow_svcd index 7f4a4b3..679b7e1 100755 --- a/patch_v2/meow_svcd/S89meow_svcd +++ b/patch_v2/meow_svcd/S89meow_svcd @@ -110,6 +110,22 @@ EOF chmod 644 /usr/data/meow/services.d/meow_crash.conf echo -n "provisioned crash.conf " fi + if [ -f /usr/data/meow/services.d/meow_amsd.conf ]; then + echo -n "amsd.conf exists " + else + cat > /usr/data/meow/services.d/meow_amsd.conf <<'EOF' +exec=/usr/bin/meow_amsd +args= +enabled=1 +restart=on-fail +backoff_ms=2000 +max_restart=5 +trigger=boot +cap= +EOF + chmod 644 /usr/data/meow/services.d/meow_amsd.conf + echo -n "provisioned amsd.conf " + fi else echo -n "skip svc confs (/usr/data/meow not writable) " fi