meow_ui.a→3e0ee8f7 launcher→9d0efc2f hello→9609ea6e files→90f5cf07 ncm→be8be221(均已上卡); 真机截图证据 .tmp/meowbug/fix1.png+fix_b2_after.png
1462 lines
57 KiB
C
1462 lines
57 KiB
C
/* 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/<pkg>/stdout_<ts>_<pid>.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 <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <stdarg.h>
|
||
#include <stdint.h>
|
||
#include <unistd.h>
|
||
#include <fcntl.h>
|
||
#include <errno.h>
|
||
#include <signal.h>
|
||
#include <time.h>
|
||
#include <dirent.h>
|
||
#include <ctype.h>
|
||
#include <sys/types.h>
|
||
#include <sys/stat.h>
|
||
#include <sys/socket.h>
|
||
#include <sys/un.h>
|
||
#include <poll.h>
|
||
#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_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 / picker_result */
|
||
};
|
||
|
||
/* 路由行(来自 app.json route.<id>=<chain>;运行时经 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[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;
|
||
/* §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.<action>=<chain>;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) ---- */
|
||
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';
|
||
}
|
||
|
||
/* §6.2 二期:在 content 二分/扁平解析 route.<id>=<chain>(或 JSON "route.<id>":"<chain>")到 app。
|
||
* 跳过 route.<id>.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.<id>.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.<action>=<chain> 到全局 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;
|
||
}
|
||
}
|
||
|
||
/* 扫描 --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");
|
||
}
|
||
/* §6.2 二期:路由权威表(静态 app.json route.*)+ picker 表(picker.<action>=<chain>) */
|
||
ams_parse_routes(content, a);
|
||
ams_parse_pickers(content, a->pkg);
|
||
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++) {
|
||
struct ams_app *a = &g_apps[i];
|
||
emit_log(" app %s name=%s exec=%s routes=%d", a->pkg, a->name, a->exec, 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++)
|
||
emit_log(" picker %s -> %s (chain=%s)", g_pickers[i].action, g_pickers[i].app_id, g_pickers[i].chain);
|
||
}
|
||
|
||
/* ---- 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();
|
||
/* 路径:<card>/MeowOS/logs/<pkg>/stdout_<epoch>_<pid>.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);
|
||
}
|
||
|
||
/* ---- §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 <action>`: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 <action>\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 resolve <pkg> <act>`:查 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 <pkg> <activity>\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: <pkg> or <pkg>/<activity> */
|
||
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 <TYPE> <tokens...> */
|
||
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;
|
||
}
|
||
|
||
/* §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 */
|
||
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 <pkg>[/<act>]\n"); return 1; }
|
||
cmd_start(arg, resp, rn);
|
||
return 1;
|
||
}
|
||
if (!strcmp(cmd, "kill")) {
|
||
if (!arg) { snprintf(resp, rn, "ERR kill needs <pkg>\n"); return 1; }
|
||
cmd_kill(arg, resp, rn);
|
||
return 1;
|
||
}
|
||
if (!strcmp(cmd, "resolve")) {
|
||
if (!arg) { snprintf(resp, rn, "ERR resolve needs <pkg> <activity>\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 <action>\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();
|
||
|
||
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 <p>] [--ipc-sock <p>] [--apps <dir>] "
|
||
"[--card <p>] [--crash-sock <p>]\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 空参拉起 → 前台常驻 */
|
||
}
|