/* 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。 * - 注册表 = 启动首扫 + 周期合并重扫(卡未挂载扫空 3s 快重试、常态 30s 自愈; * `am rescan` 手动触发)。重扫合并式,绝不清运行时状态(M2.90 真机扫空缺陷修复)。 * - 核心目的:从侧面详细抓 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_MAX_ROUTES 8 /* 每 App 路由数(MEOW_MAX_ROUTES 对齐) */ #define AMS_MAX_PICKERS 8 /* picker action → handler 注册表 */ #define AMS_MAX_PENDING 8 /* 进行中 picker 请求(结果关联) */ #define AMS_TICK_MS 1000 #define AMS_CLEANUP_MS 60000 #define AMS_RESCAN_MS 30000 /* 常态合并重扫周期(卡热插拔/App 热安装后注册表自愈) */ #define AMS_RESCAN_RETRY_MS 3000 /* 扫描源尚未成功打开(开机早于卡挂载扫空)→ 快重试 */ #define AMS_PATH_MAX 512 #define AMS_SOCK_PATH_MAX 108 #define AMS_BOOTTS_LEN 64 #define AMS_REQ_MAX 512 /* 命令响应缓冲 */ #define AMS_IPC_TIMEOUT_MS 1200 /* launcher IPC 单次收发超时:amsd 绝不为一次转发阻塞无限久 */ #define AMS_IPC_RETRY 2 /* launcher 一次性不可达/超时 → 短退避重连一次 */ #define AMS_IPC_BACKOFF_MS 250 /* 重连退避基数 */ /* 每 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 / picker_result */ }; /* 路由行(来自 app.json route.=;运行时经 DECLARE 增量并集)。 */ struct ams_route { char act[16]; /* route 键后缀 */ char chain[MEOW_CHAIN_MAX]; }; struct ams_app { char pkg[MEOW_APP_ID_MAX]; /* app_id=package */ char name[49]; /* 显示名(UTF-8;截断必须整序列,见 utf8_trim_incomplete) */ 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; /* §6.2 二期:路由权威(resolve 用;静态 app.json + 运行时 DECLARE 并集覆盖) */ int nroute; struct ams_route routes[AMS_MAX_ROUTES]; struct ams_ev hist[AMS_HIST]; int hist_n, hist_idx; }; /* picker action → handler app(app.json picker.=;amsd 权威路由)。 */ struct ams_picker { char action[24]; char app_id[MEOW_APP_ID_MAX]; char chain[MEOW_CHAIN_MAX]; /* handler 入口 activity 链(v2 常空=裸启动,handler 读 env) */ int used; /* 注册表命中 */ }; /* 进行中 picker 请求(`am picker`/App 发起;结果经 PICKER_RESULT 关联)。 */ struct ams_pending { int active; uint32_t req_id; char caller[MEOW_APP_ID_MAX]; char action[24]; char app_id[MEOW_APP_ID_MAX]; /* handler app(记录用) */ int done; /* 结果已回 */ int code; char value[256]; }; /* 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; static struct ams_picker g_pickers[AMS_MAX_PICKERS]; /* §6.2 二期:picker 权威路由表 */ static int g_npickers = 0; static struct ams_pending g_pending[AMS_MAX_PENDING]; /* §6.2 二期:进行中 picker */ static uint32_t g_pick_req_seq = 1; /* req_id 单调分配 */ 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) ---- */ /* 把字符串末尾不完整的 UTF-8 序列砍掉(截断到最后一个完整码点边界)。 * 根因记录:name 截断若落在多字节序列中间,下游 JSON 嵌进面板会被浏览器 * JSON.parse 以非法 UTF-8 拒收(「非 JSON 响应」事故 2026-09-02)。 */ static void utf8_trim_incomplete(char *s) { size_t n = strlen(s), i = 0, last_good = 0; while (i < n) { unsigned char c = (unsigned char)s[i]; int len = (c < 0x80) ? 1 : (c >= 0xF0) ? 4 : (c >= 0xE0) ? 3 : (c >= 0xC0) ? 2 : 1; if (i + (size_t)len > n) break; /* 不完整序列 */ i += (size_t)len; last_good = i; } s[last_good] = '\0'; } 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'; utf8_trim_incomplete(out); } /* §6.2 二期:在 content 二分/扁平解析 route.=(或 JSON "route.":"")到 app。 * 跳过 route..fullscreen / .keep_awake 子键;chain 取直链(';' 分隔保持原样)。 */ static void ams_parse_routes(const char *content, struct ams_app *a) { if (!content || !a) return; const char *p = content; while ((p = strstr(p, "route.")) != NULL) { const char *q = p + 6; char id[16] = ""; int idl = 0; while (*q && *q != '"' && *q != ':' && *q != '=' && *q != '.' && idl < 15) id[idl++] = *q++; id[idl] = 0; if (idl > 0 && *q == '.') { p = q; continue; } /* route..xxx 子键,跳过 */ if (idl > 0) { const char *vp = q; while (*vp && *vp != ':' && *vp != '=') vp++; if (*vp == ':' || *vp == '=') vp++; while (*vp == ' ' || *vp == '\t') vp++; if (*vp == '"') vp++; char chain[MEOW_CHAIN_MAX] = ""; int vl = 0; while (*vp && *vp != '"' && *vp != ',' && *vp != '\n' && *vp != '\r' && vl < MEOW_CHAIN_MAX - 1) chain[vl++] = *vp++; chain[vl] = 0; while (vl > 0 && (chain[vl - 1] == ' ' || chain[vl - 1] == '\t')) chain[--vl] = 0; int ri = -1; for (int i = 0; i < a->nroute; i++) if (!strcmp(a->routes[i].act, id)) { ri = i; break; } if (ri < 0) { if (a->nroute >= AMS_MAX_ROUTES) { p = vp; continue; } ri = a->nroute++; } scopy(a->routes[ri].act, sizeof a->routes[ri].act, id); scopy(a->routes[ri].chain, sizeof a->routes[ri].chain, chain); p = vp; } else { p += 6; } } } /* §6.2 二期:解析 picker.= 到全局 picker 表(action → handler app_id)。 * app_id = 当前扫描目录名(=该 app.json 所属 App)。链 v2 常空=裸启动(handler 读 env)。 */ static void ams_parse_pickers(const char *content, const char *app_id) { if (!content || !app_id) return; const char *p = content; while ((p = strstr(p, "picker.")) != NULL) { const char *q = p + 7; char action[24] = ""; int al = 0; while (*q && *q != '"' && *q != ':' && *q != '=' && *q != '.' && al < 23) action[al++] = *q++; action[al] = 0; if (al > 0) { const char *vp = q; while (*vp && *vp != ':' && *vp != '=') vp++; if (*vp == ':' || *vp == '=') vp++; while (*vp == ' ' || *vp == '\t') vp++; if (*vp == '"') vp++; char chain[MEOW_CHAIN_MAX] = ""; int vl = 0; while (*vp && *vp != '"' && *vp != ',' && *vp != '\n' && *vp != '\r' && vl < MEOW_CHAIN_MAX - 1) chain[vl++] = *vp++; chain[vl] = 0; struct ams_picker *pk = NULL; for (int i = 0; i < g_npickers; i++) if (!strcmp(g_pickers[i].action, action)) { pk = &g_pickers[i]; break; } if (!pk && g_npickers < AMS_MAX_PICKERS) { pk = &g_pickers[g_npickers++]; memset(pk, 0, sizeof *pk); scopy(pk->action, sizeof pk->action, action); } if (pk) { scopy(pk->app_id, sizeof pk->app_id, app_id); scopy(pk->chain, sizeof pk->chain, chain); pk->used = 1; } } p = q; } } /* 扫描源至少成功打开过一次(空表快重试的终止条件;卡未挂载时恒 0)。 */ static int g_scan_ok = 0; static struct ams_app *app_find(const char *pkg); /* 定义在后(App 表辅助) */ /* 扫描 --apps 下每个目录(app_id=目录名;name/exec 从 app.json 抽,缺省回退)。 * 合并式(merge-preserving):已存在表项只刷新静态字段(known/name/exec/route 表/picker 表), * 运行时状态(state/pid/launches/crashes/history、运行时 DECLARE 并集进来的路由)一律保留; * 新目录追加建项。扫描源不可达(开机早于卡挂载/卡热拔)→ 保留现有表直接返回,绝不清空。 * verbose=1(启动/`am rescan`)记全量明细;周期调用仅在结果变化时记一行摘要(防刷屏)。 */ static void registry_scan(int verbose) { static int last_found = -1, last_napps = -1, last_npick = -1; DIR *d = opendir(g_apps_dir); if (!d) { if (verbose || !g_scan_ok) emit_log("registry scan: %s not reachable (%s)", g_apps_dir, strerror(errno)); return; } g_scan_ok = 1; for (int i = 0; i < g_napps; i++) g_apps[i].known = 0; for (int i = 0; i < g_npickers; i++) g_pickers[i].used = 0; struct dirent *de; int found = 0; while ((de = readdir(d)) != NULL) { 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'; } /* app_id 权威 = 目录名(APPS.md §2);已有表项(含运行时 EV/DECLARE 建的)原位刷新 */ struct ams_app *a = app_find(nm); if (!a) { if (g_napps >= AMS_MAX_APPS) continue; a = &g_apps[g_napps++]; memset(a, 0, sizeof *a); scopy(a->pkg, sizeof a->pkg, nm); } a->known = 1; char tmp[96]; appjson_get(content, "name", tmp, sizeof tmp); if (!tmp[0]) scopy(tmp, sizeof tmp, nm); scopy(a->name, sizeof a->name, tmp); utf8_trim_incomplete(a->name); /* scopy 截断同样走整序列边界 */ appjson_get(content, "exec", tmp, sizeof tmp); if (tmp[0]) scopy(a->exec, sizeof a->exec, tmp); else if (!a->exec[0] || !strcmp(a->exec, "?")) { scopy(a->exec, sizeof a->exec, dir); path_append(a->exec, sizeof a->exec, "/meow"); } /* §6.2 二期:路由权威表(静态 app.json route.*)+ picker 表(picker.=) */ ams_parse_routes(content, a); ams_parse_pickers(content, a->pkg); found++; } closedir(d); if (verbose || found != last_found || g_napps != last_napps || g_npickers != last_npick) { emit_log("registry scan: %d dir(s) from %s, %d app(s) registered", found, g_apps_dir, g_napps); if (verbose) { for (int i = 0; i < g_napps; i++) { struct ams_app *a = &g_apps[i]; emit_log(" app %s name=%s exec=%s known=%d routes=%d", a->pkg, a->name, a->exec, a->known, a->nroute); for (int r = 0; r < a->nroute; r++) emit_log(" route %s=%s", a->routes[r].act, a->routes[r].chain); } for (int i = 0; i < g_npickers; i++) if (g_pickers[i].used) emit_log(" picker %s -> %s (chain=%s)", g_pickers[i].action, g_pickers[i].app_id, g_pickers[i].chain); } } last_found = found; last_napps = g_napps; last_npick = g_npickers; } /* ---- 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; } /* ---- §6.2 二期:路由权威(resolve/declare/picker)---- */ /* 路由规则与 launcher resolve_chain 一致(FRAMEWORK_DESIGN §2): * route 缺失(app 未声明任何路由)→ chain=activity、深度 1 兜底; * 声明了路由但 activity 非 route 键 → -MEOW_ERR_NOENT。返回 0=成功。 */ static int amsd_resolve_chain(const char *app_id, const char *activity, char *out, int *depth) { if (!out || !depth) return MEOW_ERR_INVAL; if (!activity) activity = ""; struct ams_app *a = app_find(app_id); if (!a) return MEOW_ERR_NOENT; if (a->nroute == 0) { snprintf(out, MEOW_CHAIN_MAX, "%s", activity); *depth = activity[0] ? 1 : 0; return MEOW_OK; } for (int i = 0; i < a->nroute; i++) { if (!strcmp(a->routes[i].act, activity)) { snprintf(out, MEOW_CHAIN_MAX, "%s", a->routes[i].chain); const char *c = a->routes[i].chain; int d = c[0] ? 1 : 0; for (; *c; c++) if (*c == ';') d++; *depth = d; return MEOW_OK; } } return MEOW_ERR_NOENT; } /* 应用一组运行时 route entries(NUL 分隔 "id=chain")到 ams_app(并集覆盖,同 launcher)。 */ static void amsd_apply_routes(struct ams_app *a, const char *entries) { const char *p = entries; while (*p) { const char *eq = strchr(p, '='); if (eq && eq > p) { int idlen = (int)(eq - p); if (idlen >= 16) idlen = 15; char id[16]; memcpy(id, p, idlen); id[idlen] = 0; const char *chain = eq + 1; int ri = -1; for (int i = 0; i < a->nroute; i++) if (!strcmp(a->routes[i].act, id)) { ri = i; break; } if (ri < 0) { if (a->nroute >= AMS_MAX_ROUTES) { p += strlen(p) + 1; continue; } ri = a->nroute++; } scopy(a->routes[ri].act, sizeof a->routes[ri].act, id); scopy(a->routes[ri].chain, sizeof a->routes[ri].chain, chain); } p += strlen(p) + 1; } } /* hex 解码(小写/大写 2 字符/字节)到 out[outsz],返回字节数(含尾部 NUL 由调用方处理)。 */ static int ams_hex_decode(const char *hex, char *out, int outsz) { int n = 0; const char *p = hex; while (p[0] && p[1] && n < outsz - 1) { int hi = (p[0] >= '0' && p[0] <= '9') ? p[0] - '0' : (p[0] >= 'a' && p[0] <= 'f') ? p[0] - 'a' + 10 : (p[0] >= 'A' && p[0] <= 'F') ? p[0] - 'A' + 10 : -1; int lo = (p[1] >= '0' && p[1] <= '9') ? p[1] - '0' : (p[1] >= 'a' && p[1] <= 'f') ? p[1] - 'a' + 10 : (p[1] >= 'A' && p[1] <= 'F') ? p[1] - 'A' + 10 : -1; if (hi < 0 || lo < 0) break; out[n++] = (char)((hi << 4) | lo); p += 2; } out[n] = 0; return n; } 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; /* 收发加超时:launcher 刚刚重启/主循环未就绪(接受但不回包)时,amsd 的阻塞 * read 会永远挂起,冻结整个单线程守护。设 SO_SNDTIMEO/SO_RCVTIMEO 让单次转发 * 有界,超时即返回失败 → ams_request_ctl 退避重连,绝不无限阻塞。 * 注意:tv_usec 必须 < 1000000(否则 setsockopt 返回 EINVAL,超时静默失效)。 */ struct timeval tv; tv.tv_sec = AMS_IPC_TIMEOUT_MS / 1000; tv.tv_usec = (AMS_IPC_TIMEOUT_MS % 1000) * 1000; (void)setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv); (void)setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); 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 不通。 * launcher 重启窗口(svcd/sysui 重启 launcher、或 amsd 先行启动)内连接会暂不可达/超时: * 这里做「指数式短退避重试」——launcher 数秒内回来即自动恢复转发(无需人工重发)。 */ static int ams_request_ctl(uint16_t type, const void *payload, uint32_t len, uint32_t *code) { uint8_t rbuf[64]; for (int attempt = 0; attempt < AMS_IPC_RETRY; attempt++) { 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; /* 一次连接失败/超时(launcher 未就绪)→ 短退避再试;仍失败则返回 -1。 */ if (attempt + 1 < AMS_IPC_RETRY) usleep((useconds_t)(AMS_IPC_BACKOFF_MS * (attempt + 1)) * 1000); } return -1; } /* ---- 事件处理(launcher 上报) ---- */ static void handle_launch(char **t, int nt) { if (nt < 4) return; const char *pkg = t[0], *act = t[1], *chain = "-"; pid_t pid; long epoch, up; if (nt == 5) { /* 兼容旧格式(M2.90 固件 launcher 实证):EV LAUNCH * ——无 chain 字段。不兼容会把 epoch 错当 pid(am status 显示假 pid、 * `am kill` 兜底直发 SIGTERM 打错进程),故按 nt==5 识别旧布局。 */ pid = (pid_t)atoi(t[2]); epoch = atol(t[3]); up = atol(t[4]); } else { chain = t[2]; pid = (pid_t)atoi(t[3]); epoch = (nt > 4) ? atol(t[4]) : (long)time(NULL); 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); } /* 批①后台常驻上报(单向 best-effort,launcher → amsd): * EV BG → AMS_BG(转后台保活) * EV FG → AMS_FG(回前台)。旧 launcher 不发、本层不识别也不崩。 */ static void handle_bg(char **t, int nt) { if (nt < 2) return; const char *pkg = t[0]; pid_t pid = (pid_t)atoi(t[1]); long epoch = (nt > 2) ? atol(t[2]) : (long)time(NULL); long up = (nt > 3) ? atol(t[3]) : read_uptime_sec(); struct ams_app *a = app_get(pkg); if (!a) return; a->state = AMS_BG; a->pid = pid; char detail[192]; snprintf(detail, sizeof detail, "pid=%ld", (long)pid); hist_push(a, "bg", detail, epoch, up); emit_log("BG %s pid=%ld", pkg, (long)pid); } static void handle_fg(char **t, int nt) { if (nt < 1) return; const char *pkg = t[0]; long epoch = (nt > 1) ? atol(t[1]) : (long)time(NULL); long up = (nt > 2) ? atol(t[2]) : read_uptime_sec(); struct ams_app *a = app_find(pkg); if (!a) return; a->state = AMS_FG; char detail[192]; snprintf(detail, sizeof detail, "bg->fg"); hist_push(a, "fg", detail, epoch, up); emit_log("FG %s", pkg); } /* ---- §6.2 二期:picker 编排(amsd 路由权威) ---- */ static struct ams_picker *picker_find(const char *action) { if (!action || !action[0]) return NULL; for (int i = 0; i < g_npickers; i++) if (g_pickers[i].used && !strcmp(g_pickers[i].action, action)) return &g_pickers[i]; return NULL; } static void pending_record(uint32_t req_id, const char *action, int code, const char *value) { for (int i = 0; i < AMS_MAX_PENDING; i++) { struct ams_pending *p = &g_pending[i]; if (!p->active) continue; if (p->req_id != req_id) continue; if (action[0] && strcmp(p->action, action) != 0) continue; p->done = 1; p->code = code; scopy(p->value, sizeof p->value, value); emit_log("picker result: req=%u action=%s code=%d value=%s", req_id, p->action, code, p->value); struct ams_app *h = app_find(p->app_id); if (h) { char detail[192]; snprintf(detail, sizeof detail, "req=%u caller=%s act=%s code=%d val=%.96s", req_id, p->caller, action, code, value); hist_push(h, "picker_result", detail, (long)time(NULL), read_uptime_sec()); } return; } emit_log("picker result: no pending req=%u (dropped)", req_id); } /* picker 启动:解析 action → handler;alloc/更新 pending;经 launcher MSG_AMS_START(picker) 拉起。 * write_resp:写响应到 resp(cmd_picker 用;PICKER_CALL 事件传 NULL 不写)。返回 0=ok启动。 */ static int picker_start(const char *caller, uint32_t req_id, const char *action, char *resp, int rn) { struct ams_picker *pk = picker_find(action); if (!pk) { if (resp) snprintf(resp, rn, "ERR no picker handler for action: %s\n", action ? action : ""); emit_log("AMS picker %s -> no handler", action ? action : ""); return -1; } struct ams_app *h = app_find(pk->app_id); if (!h) { if (resp) snprintf(resp, rn, "ERR handler app not registered: %s\n", pk->app_id); emit_log("AMS picker %s -> handler %s not registered", action, pk->app_id); return -1; } /* 找/建 pending 槽(同 req_id 去重;满则覆盖最旧 done) */ struct ams_pending *p = NULL; for (int i = 0; i < AMS_MAX_PENDING; i++) if (g_pending[i].active && g_pending[i].req_id == req_id) { p = &g_pending[i]; break; } if (!p) { for (int i = 0; i < AMS_MAX_PENDING; i++) if (!g_pending[i].active || g_pending[i].done) { p = &g_pending[i]; break; } } if (!p) p = &g_pending[0]; /* 全占满→覆盖 0(最旧) */ memset(p, 0, sizeof *p); p->active = 1; p->req_id = req_id; scopy(p->caller, sizeof p->caller, caller ? caller : ""); scopy(p->action, sizeof p->action, action ? action : ""); scopy(p->app_id, sizeof p->app_id, pk->app_id); /* 裸启动 handler(activity 空;picker 上下文经 MSG_AMS_START 扩载荷注入 env) */ m_ams_start_t s; memset(&s, 0, sizeof s); scopy(s.app_id, sizeof s.app_id, pk->app_id); if (pk->chain[0]) scopy(s.activity, sizeof s.activity, pk->chain); s.picker = 1; s.picker_req_id = req_id; scopy(s.picker_action, sizeof s.picker_action, pk->action); scopy(s.picker_caller, sizeof s.picker_caller, caller ? caller : ""); uint32_t code = MEOW_ERR_INTERNAL; int rt = ams_request_ctl(MSG_AMS_START, &s, sizeof s, &code); if (rt != 0) { p->active = 0; if (resp) snprintf(resp, rn, "ERR launcher offline\n"); emit_log("AMS picker %s -> launcher offline", action); return -1; } if (code != MEOW_OK) { if (resp) snprintf(resp, rn, "ERR launcher refused: %s (code=%u)\n", pk->app_id, code); emit_log("AMS picker %s -> launcher refused code=%u", action, code); return -1; } if (resp) snprintf(resp, rn, "OK pending %u %s\n", req_id, pk->app_id); emit_log("AMS picker %s -> %s (req=%u, caller=%s)", action, pk->app_id, req_id, caller ? caller : ""); return 0; } /* `am picker `:alloc req_id(CLI 无 caller),启动,回 OK pending。 */ static int cmd_picker(const char *action, char *resp, int rn) { if (!action || !action[0]) { snprintf(resp, rn, "ERR picker needs \n"); return 1; } uint32_t req_id = g_pick_req_seq++; if (g_pick_req_seq == 0) g_pick_req_seq = 1; picker_start("", req_id, action, resp, rn); /* 内部写 OK pending / ERR */ return 1; } /* `picker-status [reqid]`:列正在/已决 picker。 */ static int cmd_picker_status(const char *reqstr, char *resp, int rn) { int l = snprintf(resp, rn, "# amd picker status\n"); int found = 0; for (int i = 0; i < AMS_MAX_PENDING; i++) { struct ams_pending *p = &g_pending[i]; if (!p->active) continue; if (reqstr && reqstr[0] && (uint32_t)atoi(reqstr) != p->req_id) continue; found = 1; const char *state = p->done ? "done" : "pending"; int m = snprintf(resp + l, rn - l, "PICK %u %s %s %d %s\n", p->req_id, p->action, p->app_id, p->done ? p->code : -1, p->done ? p->value : state); if (m > 0) l += m; } if (!found) { int m = snprintf(resp + l, rn - l, "(none)\n"); if (m > 0) l += m; } return l; } /* `am rescan`:手动触发注册表重扫(卡刚挂载/热装 App 后立即可见,不等 30s 周期)。 */ static int cmd_rescan(char *resp, int rn) { registry_scan(1); return snprintf(resp, rn, "OK rescan %d app(s)\n", g_napps); } /* `am resolve `:查 amsd 权威路由表。 */ static int cmd_resolve(const char *pkg, const char *act, char *resp, int rn) { if (!pkg || !pkg[0] || !act || !act[0]) { snprintf(resp, rn, "ERR resolve needs \n"); return 1; } char chain[MEOW_CHAIN_MAX]; int depth = 0; int rc = amsd_resolve_chain(pkg, act, chain, &depth); if (rc != 0) { snprintf(resp, rn, "ERR resolve %s/%s failed (%d)\n", pkg, act, rc); return 1; } snprintf(resp, rn, "CHAIN %s %s %d %s\n", pkg, act, depth, chain); return 1; } /* ---- 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)) { /* launcher ACK ack.code 携带正幅度(busy=8,见 util.c `rc<0?-rc:MEOW_OK`),非二补。 */ 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); } else if (!strcmp(tok[0], "BG")) { handle_bg(tok + 1, nt - 1); } else if (!strcmp(tok[0], "FG")) { handle_fg(tok + 1, nt - 1); } return 0; } /* §6.2 二期事件(launcher → amsd 一次性转发,无响应):DECLARE / PICKER_CALL / PICKER_RESULT */ if (strncmp(line, "DECLARE ", 8) == 0 || strncmp(line, "PICKER_", 7) == 0) { char *tok[8]; int nt = 0; char *save = NULL; for (int i = 0; i < (int)(sizeof tok / sizeof tok[0]); i++) { char *tk = strtok_r(i == 0 ? line : NULL, " ", &save); if (!tk) break; tok[nt++] = tk; } if (nt >= 1) { if (!strcmp(tok[0], "DECLARE") && nt >= 4) { struct ams_app *a = app_get(tok[1]); char entries[MEOW_IPC_CARGO]; if (a) { int bl = ams_hex_decode(tok[3], entries, sizeof entries); (void)bl; amsd_apply_routes(a, entries); emit_log("DECLARE %s n=%s routes=%d", tok[1], tok[2], a->nroute); } } else if (!strcmp(tok[0], "PICKER_CALL") && nt >= 4) { uint32_t req = (uint32_t)atoi(tok[2]); (void)picker_start(tok[1], req, tok[3], NULL, 0); } else if (!strcmp(tok[0], "PICKER_RESULT") && nt >= 5) { uint32_t req = (uint32_t)atoi(tok[1]); int code = atoi(tok[3]); char value[256]; (void)ams_hex_decode(tok[4], value, sizeof value); pending_record(req, tok[2], code, value); } } return 0; } /* 命令:status/list/history/start/kill/resolve/picker/picker-status/rescan */ 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, "rescan")) return cmd_rescan(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; } if (!strcmp(cmd, "resolve")) { if (!arg) { snprintf(resp, rn, "ERR resolve needs \n"); return 1; } char *act = strtok_r(NULL, " ", &save); return cmd_resolve(arg, act, resp, rn) > 0; } if (!strcmp(cmd, "picker")) { if (!arg) { snprintf(resp, rn, "ERR picker needs \n"); return 1; } cmd_picker(arg, resp, rn); return 1; } if (!strcmp(cmd, "picker-status")) { return cmd_picker_status(arg, resp, rn) > 0; } 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(1); /* 启动首扫:卡未挂载扫空也照常常驻,主循环 3s 快重试兜底 */ 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, last_rescan = 0; while (!g_shutdown) { /* 每圈清零:谨防上一轮已关闭连接残留的 POLLHUP/POLLERR 落在 pfds[2+i] 上。 * 若不清,accept 后同一轮就用「陈旧 revents」处理新连接——误判其 HUP/ERR * 而提前 close(am CLI 收到 SIGPIPE、命令被丢弃),launcher 重启后 EV 事件 * 连接密集必触发,表现为 `am start` 不再转发到 launcher。 */ memset(pfds, 0, sizeof pfds); 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(); } /* 注册表自愈重扫:扫描源从未成功打开(开机早于卡挂载扫空,M2.90 真机实证 * 「registry scan: MeowAPPs not reachable」后注册表永久为空 → am start 报 * pkg not registered)→ 3s 快重试直到扫到;常态 30s 合并重扫(卡热插拔 / * App 热安装自愈,对齐 launcher 的应用列表目录热重载)。合并式重扫不清 * 运行时状态,见 registry_scan 注释。 */ { unsigned long long nm = now_ms(); unsigned long long iv = g_scan_ok ? (unsigned long long)AMS_RESCAN_MS : (unsigned long long)AMS_RESCAN_RETRY_MS; if (nm - last_rescan >= iv) { last_rescan = nm; registry_scan(0); } } } 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 空参拉起 → 前台常驻 */ }