1069 lines
40 KiB
C
1069 lines
40 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_TICK_MS 1000
|
||
#define AMS_CLEANUP_MS 60000
|
||
#define AMS_PATH_MAX 512
|
||
#define AMS_SOCK_PATH_MAX 108
|
||
#define AMS_BOOTTS_LEN 64
|
||
#define AMS_REQ_MAX 512 /* 命令响应缓冲 */
|
||
|
||
/* 每 App 状态(一期:STOPPED/FG;BG 预留二期) */
|
||
enum { AMS_STOPPED = 0, AMS_FG = 1, AMS_BG = 2 };
|
||
|
||
struct ams_ev {
|
||
char detail[192]; /* 事件描述(exit=0 / sig=11 / ams_start=... 等) */
|
||
long epoch; /* 墙钟秒 */
|
||
long up; /* uptime 秒 */
|
||
char kind[16]; /* launch / exit / crash / ams_start / ams_kill */
|
||
};
|
||
|
||
struct ams_app {
|
||
char pkg[MEOW_APP_ID_MAX]; /* app_id=package */
|
||
char name[17];
|
||
char exec[129];
|
||
int known; /* 注册表(app.json 扫描)命中 */
|
||
int state; /* AMS_* */
|
||
pid_t pid;
|
||
char activity[MEOW_ACT_MAX];
|
||
char chain[MEOW_CHAIN_MAX];
|
||
long long launch_ms; /* CLOCK_MONOTONIC ms */
|
||
long launch_epoch; /* 墙钟秒 */
|
||
long launch_up;
|
||
int launches;
|
||
int crashes; /* 异常退出次数(sig 或 exit!=0) */
|
||
int last_kind; /* 0=干净 exit 1=exit 2=sig */
|
||
int last_code;
|
||
long long last_dur_ms;
|
||
struct ams_ev hist[AMS_HIST];
|
||
int hist_n, hist_idx;
|
||
};
|
||
|
||
/* stdout 捕获会话(一期同一时刻仅一个前台 App → 单槽) */
|
||
struct ams_out {
|
||
int active;
|
||
char pkg[MEOW_APP_ID_MAX];
|
||
pid_t pid;
|
||
int fd; /* 打开的日志文件 fd */
|
||
char base[AMS_PATH_MAX]; /* 当前文件路径 */
|
||
long long bytes; /* 当前文件字节数 */
|
||
};
|
||
struct ams_out g_out = { .active = 0, .fd = -1 };
|
||
|
||
/* ---- 全局状态 ---- */
|
||
static char g_card[256] = AMS_CARD_DEFAULT;
|
||
static char g_sock[AMS_SOCK_PATH_MAX] = AMS_SOCK_DEFAULT;
|
||
static char g_ipc_sock[AMS_SOCK_PATH_MAX] = AMS_IPC_SOCK_DEFAULT;
|
||
static char g_apps_dir[AMS_PATH_MAX] = AMS_APPS_DEFAULT;
|
||
static char g_crash_sock[AMS_SOCK_PATH_MAX] = AMS_CRASH_SOCK_DEFAULT;
|
||
static char g_bootts[AMS_BOOTTS_LEN] = "";
|
||
static int g_log_fd = -1; /* 卡日志 fd;-1=未开(落 /tmp) */
|
||
static int g_on_card = 0;
|
||
static int g_shutdown = 0;
|
||
static int g_sigpipe[2] = {-1, -1};
|
||
static int g_listen_fd = -1;
|
||
static struct ams_app g_apps[AMS_MAX_APPS];
|
||
static int g_napps = 0;
|
||
|
||
struct ams_conn {
|
||
int fd;
|
||
char buf[AMS_LINE_MAX];
|
||
int n; /* buf 有效字节(可能含未完结行,跨 poll 保留) */
|
||
char resp[AMS_REQ_MAX];
|
||
int resp_n; /* 已产生的命令响应(写回后即关连接) */
|
||
};
|
||
|
||
/* ---- 工具 ---- */
|
||
static unsigned long long now_ms(void)
|
||
{
|
||
struct timespec ts;
|
||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||
return (unsigned long long)ts.tv_sec * 1000 + (unsigned long long)ts.tv_nsec / 1000000;
|
||
}
|
||
|
||
static int safe_strcpy(char *dst, size_t cap, const char *src)
|
||
{
|
||
if (!dst || cap == 0) return -1;
|
||
int n = snprintf(dst, cap, "%s", src ? src : "");
|
||
return (n >= 0 && (size_t)n < cap) ? 0 : -1;
|
||
}
|
||
|
||
/* 有界字符串拷贝(memcpy 实现,不触发 gcc -Wformat-truncation)。 */
|
||
static size_t scopy(char *dst, size_t cap, const char *src)
|
||
{
|
||
if (!dst || cap == 0) { if (dst) dst[0] = '\0'; return 0; }
|
||
size_t l = strnlen(src ? src : "", cap - 1);
|
||
if (src) memcpy(dst, src, l);
|
||
dst[l] = '\0';
|
||
return l;
|
||
}
|
||
|
||
/* 把 src 追加到 dst 末尾(有界,memcpy 实现)。返回 0=成功;-1=溢出(截断到 cap-1)。 */
|
||
static int path_append(char *dst, size_t cap, const char *src)
|
||
{
|
||
size_t dl = strnlen(dst, cap);
|
||
size_t sl = strnlen(src ? src : "", cap > dl + 1 ? cap - dl - 1 : 0);
|
||
if (dl + sl + 1 > cap) {
|
||
/* 溢出:截断并保存 NUL */
|
||
if (cap > 0) { dst[cap - 1] = '\0'; }
|
||
return -1;
|
||
}
|
||
if (sl) memcpy(dst + dl, src, sl);
|
||
dst[dl + sl] = '\0';
|
||
return 0;
|
||
}
|
||
|
||
static long read_uptime_sec(void)
|
||
{
|
||
int fd = open("/proc/uptime", O_RDONLY);
|
||
if (fd < 0) return 0;
|
||
char b[64];
|
||
ssize_t n = read(fd, b, sizeof b - 1);
|
||
close(fd);
|
||
if (n <= 0) return 0;
|
||
b[n] = '\0';
|
||
return (long)atof(b);
|
||
}
|
||
|
||
/* 本 boot 时间戳 "%Y%m%d_%H%M%S":复用 /tmp/meow_bootts(launcher/crash 共用,跨进程同一 boot)。 */
|
||
static void boot_ts(char *out, size_t n)
|
||
{
|
||
out[0] = '\0';
|
||
int fd = open("/tmp/meow_bootts", O_RDONLY);
|
||
if (fd >= 0) {
|
||
char b[AMS_BOOTTS_LEN];
|
||
ssize_t r = read(fd, b, sizeof b - 1);
|
||
close(fd);
|
||
if (r > 0) {
|
||
b[r] = '\0';
|
||
char *p = b;
|
||
if (strncmp(p, "meow_", 5) == 0) p += 5;
|
||
snprintf(out, n, "%s", p);
|
||
if (out[0]) return;
|
||
}
|
||
}
|
||
time_t now = time(NULL);
|
||
time_t up = (time_t)read_uptime_sec();
|
||
time_t boot = now - up;
|
||
struct tm tm;
|
||
localtime_r(&boot, &tm);
|
||
strftime(out, n, "%Y%m%d_%H%M%S", &tm);
|
||
}
|
||
|
||
static void ams_append_line(const char *line)
|
||
{
|
||
if (g_log_fd >= 0) {
|
||
ssize_t w = write(g_log_fd, line, strlen(line));
|
||
if (w < 0) { close(g_log_fd); g_log_fd = -1; g_on_card = 0; }
|
||
else return;
|
||
}
|
||
struct stat st;
|
||
if (stat(AMS_TMP_LOG, &st) == 0 && st.st_size > (long long)AMS_TMP_MAX)
|
||
unlink(AMS_TMP_LOG);
|
||
int fd = open(AMS_TMP_LOG, O_WRONLY | O_APPEND | O_CREAT, 0644);
|
||
if (fd >= 0) { ssize_t w = write(fd, line, strlen(line)); (void)w; close(fd); }
|
||
}
|
||
|
||
static void emit_log(const char *fmt, ...)
|
||
{
|
||
char line[AMS_LINE_MAX];
|
||
long epoch = (long)time(NULL);
|
||
long up = read_uptime_sec();
|
||
va_list ap;
|
||
va_start(ap, fmt);
|
||
int l = snprintf(line, sizeof line, "%ld %ld amsd ", epoch, up);
|
||
if (l < 0) l = 0;
|
||
int m = vsnprintf(line + l, sizeof line - l, fmt, ap);
|
||
va_end(ap);
|
||
if (m > 0) {
|
||
l += m;
|
||
if ((size_t)l < sizeof line) { line[l++] = '\n'; line[l] = 0; ams_append_line(line); }
|
||
}
|
||
}
|
||
|
||
/* ---- 卡可用性 / 持久日志 ---- */
|
||
static int card_available(void)
|
||
{
|
||
const char *nc = getenv("MEOW_AMSD_NOCARD");
|
||
if (nc && nc[0] == '1') return 0;
|
||
|
||
char logs[AMS_PATH_MAX];
|
||
if (snprintf(logs, sizeof logs, "%s/%s", g_card, AMS_LOGS_REL) >= (int)sizeof logs) return 0;
|
||
if (strcmp(g_card, AMS_CARD_DEFAULT) == 0) {
|
||
FILE *f = fopen("/proc/mounts", "r");
|
||
if (!f) return 0;
|
||
char needle[AMS_PATH_MAX], line[AMS_PATH_MAX];
|
||
snprintf(needle, sizeof needle, " %s ", g_card);
|
||
int found = 0;
|
||
while (fgets(line, sizeof line, f))
|
||
if (strstr(line, needle)) { found = 1; break; }
|
||
fclose(f);
|
||
if (!found) return 0;
|
||
}
|
||
(void)mkdir(logs, 0755);
|
||
return access(logs, W_OK) == 0;
|
||
}
|
||
|
||
/* 只对 ams_ 前缀与 stdout_ 前缀文件做 1GB 清理(不与 meow_crash 的 meow_/crash_ 前缀冲突)。 */
|
||
static void log_cleanup_old(void)
|
||
{
|
||
char logs[AMS_PATH_MAX];
|
||
if (snprintf(logs, sizeof logs, "%s/%s", g_card, AMS_LOGS_REL) >= (int)sizeof logs) return;
|
||
DIR *d = opendir(logs);
|
||
if (!d) return;
|
||
struct lf { char name[96]; long long size, mtime; } files[128];
|
||
int n = 0;
|
||
struct dirent *de;
|
||
while ((de = readdir(d)) != NULL && n < 128) {
|
||
const char *nm = de->d_name;
|
||
if (strncmp(nm, "ams_", 4) != 0 && strncmp(nm, "stdout_", 7) != 0) continue;
|
||
char path[AMS_PATH_MAX];
|
||
if (snprintf(path, sizeof path, "%s/%s", logs, nm) >= (int)sizeof path) continue;
|
||
struct stat st;
|
||
if (stat(path, &st) != 0 || !S_ISREG(st.st_mode)) continue;
|
||
size_t nl = strlen(nm);
|
||
if (nl >= sizeof files[n].name) nl = sizeof files[n].name - 1;
|
||
memcpy(files[n].name, nm, nl);
|
||
files[n].name[nl] = '\0';
|
||
files[n].size = (long long)st.st_size;
|
||
files[n].mtime = (long long)st.st_mtime;
|
||
n++;
|
||
}
|
||
closedir(d);
|
||
long long total = 0;
|
||
for (int i = 0; i < n; i++) total += files[i].size;
|
||
for (int i = 0; i < n; i++)
|
||
for (int j = i + 1; j < n; j++)
|
||
if (files[j].mtime < files[i].mtime) { struct lf t = files[i]; files[i] = files[j]; files[j] = t; }
|
||
int deleted = 0;
|
||
for (int i = 0; i < n && total > (long long)AMS_LOG_SIZE_LIMIT; i++) {
|
||
char path[AMS_PATH_MAX];
|
||
if (snprintf(path, sizeof path, "%s/%s", logs, files[i].name) >= (int)sizeof path) continue;
|
||
if (unlink(path) == 0) { total -= files[i].size; deleted++; }
|
||
}
|
||
if (deleted) fprintf(stderr, "[meow_amsd] log_cleanup deleted %d old log(s)\n", deleted);
|
||
}
|
||
|
||
static int log_open_card(void)
|
||
{
|
||
if (g_log_fd >= 0) return 0;
|
||
if (!card_available()) return -1;
|
||
char ts[AMS_BOOTTS_LEN];
|
||
boot_ts(ts, sizeof ts);
|
||
char path[AMS_PATH_MAX];
|
||
if (snprintf(path, sizeof path, "%s/%s/ams_%s.log", g_card, AMS_LOGS_REL, ts)
|
||
>= (int)sizeof path) return -1;
|
||
int fd = open(path, O_WRONLY | O_APPEND | O_CREAT, 0644);
|
||
if (fd < 0) return -1;
|
||
g_log_fd = fd; g_on_card = 1;
|
||
return 0;
|
||
}
|
||
|
||
static void poll_card(void)
|
||
{
|
||
if (g_log_fd >= 0) return;
|
||
if (log_open_card() != 0) return;
|
||
int fd = open(AMS_TMP_LOG, O_RDONLY);
|
||
if (fd >= 0) {
|
||
char chunk[1024]; ssize_t cn;
|
||
while ((cn = read(fd, chunk, sizeof chunk)) > 0) {
|
||
if (write(g_log_fd, chunk, cn) < 0) { close(fd); g_log_fd = -1; g_on_card = 0; return; }
|
||
}
|
||
close(fd);
|
||
unlink(AMS_TMP_LOG);
|
||
fprintf(stderr, "[meow_amsd] /tmp buffered log moved to card\n");
|
||
}
|
||
}
|
||
|
||
/* ---- 注册表扫描(app.json 迷你解析:package/name/exec) ---- */
|
||
static void appjson_get(const char *content, const char *key, char *out, size_t n)
|
||
{
|
||
out[0] = '\0';
|
||
char pat[64];
|
||
snprintf(pat, sizeof pat, "\"%s\"", key);
|
||
const char *p = strstr(content, pat);
|
||
if (!p) return;
|
||
p += strlen(pat);
|
||
const char *c = strchr(p, ':');
|
||
if (!c) return;
|
||
c++;
|
||
while (*c == ' ' || *c == '\t') c++;
|
||
if (*c != '"') return;
|
||
c++;
|
||
const char *e = strchr(c, '"');
|
||
if (!e) return;
|
||
size_t l = (size_t)(e - c);
|
||
if (l >= n) l = n - 1;
|
||
memcpy(out, c, l);
|
||
out[l] = '\0';
|
||
}
|
||
|
||
/* 扫描 --apps 下每个目录(app_id=目录名;name/exec 从 app.json 抽,缺省回退)。 */
|
||
static void registry_scan(void)
|
||
{
|
||
DIR *d = opendir(g_apps_dir);
|
||
if (!d) { emit_log("registry scan: %s not reachable (%s)", g_apps_dir, strerror(errno)); return; }
|
||
struct dirent *de;
|
||
int n = 0;
|
||
while ((de = readdir(d)) != NULL && n < AMS_MAX_APPS) {
|
||
if (de->d_name[0] == '.') continue;
|
||
char nm[96];
|
||
scopy(nm, sizeof nm, de->d_name);
|
||
char dir[AMS_PATH_MAX + 64];
|
||
scopy(dir, sizeof dir, g_apps_dir);
|
||
path_append(dir, sizeof dir, "/");
|
||
if (path_append(dir, sizeof dir, nm) != 0) continue; /* 路径过长 → 跳过 */
|
||
struct stat st;
|
||
if (stat(dir, &st) != 0 || !S_ISDIR(st.st_mode)) continue;
|
||
char jpath[AMS_PATH_MAX + 64];
|
||
scopy(jpath, sizeof jpath, dir);
|
||
if (path_append(jpath, sizeof jpath, "/app.json") != 0) continue;
|
||
char content[2048];
|
||
content[0] = '\0';
|
||
FILE *jf = fopen(jpath, "r");
|
||
if (jf) {
|
||
size_t rd = fread(content, 1, sizeof content - 1, jf);
|
||
fclose(jf);
|
||
content[rd] = '\0';
|
||
}
|
||
struct ams_app *a = &g_apps[n];
|
||
memset(a, 0, sizeof *a);
|
||
/* app_id 权威 = 目录名(APPS.md §2);app.json package 字段作为兜底校验(不一致时用目录名) */
|
||
scopy(a->pkg, sizeof a->pkg, de->d_name);
|
||
a->known = 1;
|
||
char tmp[96];
|
||
appjson_get(content, "name", tmp, sizeof tmp);
|
||
if (!tmp[0]) scopy(tmp, sizeof tmp, de->d_name);
|
||
scopy(a->name, sizeof a->name, tmp);
|
||
appjson_get(content, "exec", tmp, sizeof tmp);
|
||
if (tmp[0]) scopy(a->exec, sizeof a->exec, tmp);
|
||
else {
|
||
scopy(a->exec, sizeof a->exec, dir);
|
||
path_append(a->exec, sizeof a->exec, "/meow");
|
||
}
|
||
n++;
|
||
}
|
||
closedir(d);
|
||
g_napps = n;
|
||
emit_log("registry scan: %d app(s) from %s", g_napps, g_apps_dir);
|
||
for (int i = 0; i < g_napps; i++)
|
||
emit_log(" app %s name=%s exec=%s", g_apps[i].pkg, g_apps[i].name, g_apps[i].exec);
|
||
}
|
||
|
||
/* ---- App 表辅助 ---- */
|
||
static struct ams_app *app_find(const char *pkg)
|
||
{
|
||
if (!pkg) return NULL;
|
||
for (int i = 0; i < g_napps; i++)
|
||
if (!strcmp(g_apps[i].pkg, pkg)) return &g_apps[i];
|
||
return NULL;
|
||
}
|
||
|
||
/* get-or-create:launcher 上报可能先于注册表扫描(或 pkg 未扫到),追加建项。 */
|
||
static struct ams_app *app_get(const char *pkg)
|
||
{
|
||
struct ams_app *a = app_find(pkg);
|
||
if (a) return a;
|
||
if (g_napps >= AMS_MAX_APPS) return NULL;
|
||
a = &g_apps[g_napps++];
|
||
memset(a, 0, sizeof *a);
|
||
snprintf(a->pkg, sizeof a->pkg, "%s", pkg);
|
||
snprintf(a->name, sizeof a->name, "%s", pkg);
|
||
snprintf(a->exec, sizeof a->exec, "%s", "?");
|
||
return a;
|
||
}
|
||
|
||
static void hist_push(struct ams_app *a, const char *kind, const char *detail,
|
||
long epoch, long up)
|
||
{
|
||
struct ams_ev *e = &a->hist[a->hist_idx];
|
||
snprintf(e->kind, sizeof e->kind, "%s", kind);
|
||
snprintf(e->detail, sizeof e->detail, "%s", detail);
|
||
e->epoch = epoch;
|
||
e->up = up;
|
||
a->hist_idx = (a->hist_idx + 1) % AMS_HIST;
|
||
if (a->hist_n < AMS_HIST) a->hist_n++;
|
||
}
|
||
|
||
/* ---- meow_crash 转发(SVC 格式,best-effort) ---- */
|
||
static void crash_forward(const char *pkg, const char *kind, int code, long epoch, long up)
|
||
{
|
||
char msg[256];
|
||
int l = snprintf(msg, sizeof msg, "SVC %s %s %d %ld %ld\n", pkg, kind, code, epoch, up);
|
||
if (l <= 0) return;
|
||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
if (fd < 0) return;
|
||
int fl = fcntl(fd, F_GETFL, 0);
|
||
if (fl >= 0) fcntl(fd, F_SETFL, fl | O_NONBLOCK);
|
||
struct sockaddr_un a;
|
||
memset(&a, 0, sizeof a);
|
||
a.sun_family = AF_UNIX;
|
||
strncpy(a.sun_path, g_crash_sock, sizeof a.sun_path - 1);
|
||
if (connect(fd, (struct sockaddr *)&a, sizeof a) != 0) {
|
||
if (errno == EINPROGRESS || errno == EWOULDBLOCK) {
|
||
struct pollfd pf = { fd, POLLOUT, 0 };
|
||
int r = poll(&pf, 1, 150);
|
||
if (r <= 0 || !(pf.revents & POLLOUT)) { close(fd); return; }
|
||
} else { close(fd); return; }
|
||
}
|
||
ssize_t w = write(fd, msg, (size_t)l);
|
||
(void)w;
|
||
close(fd);
|
||
}
|
||
|
||
/* ---- stdout 捕获会话 ---- */
|
||
static void out_close(void)
|
||
{
|
||
if (g_out.fd >= 0) close(g_out.fd);
|
||
memset(&g_out, 0, sizeof g_out);
|
||
g_out.fd = -1;
|
||
}
|
||
|
||
static void out_open(const char *pkg, pid_t pid, long epoch)
|
||
{
|
||
out_close();
|
||
/* 路径:<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);
|
||
}
|
||
|
||
/* ---- 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;
|
||
}
|
||
|
||
/* 命令:status/list/history/start/kill */
|
||
char *save = NULL;
|
||
char *cmd = strtok_r(line, " ", &save);
|
||
if (!cmd) return 0;
|
||
char *arg = strtok_r(NULL, " ", &save);
|
||
if (!strcmp(cmd, "status")) return cmd_status(resp, rn) > 0;
|
||
if (!strcmp(cmd, "list")) return cmd_list(resp, rn) > 0;
|
||
if (!strcmp(cmd, "history")) {
|
||
if (arg && arg[0]) cmd_history(arg, resp, rn);
|
||
else cmd_history(NULL, resp, rn);
|
||
return 1;
|
||
}
|
||
if (!strcmp(cmd, "start")) {
|
||
if (!arg) { snprintf(resp, rn, "ERR start needs <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;
|
||
}
|
||
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 空参拉起 → 前台常驻 */
|
||
}
|