- reader分段(只txt): core/seg.{h,c} 512KB码位对齐切段(尾字完整)+manifest+按需读单段; book.c rd_book_open只布局每页所在段(治CPU100%/返回无效) load_seg只读当前段(非txt原路径); reader.c 进度实时保存(翻页/刮擦/字号/编码dirty+on_tick节流落盘+退出即时写,锚点存段号,崩溃不丢进度); rdtxt.c fread替代fgetc(去开书扫描卡顿); Phase2(双段拼接+4%释放)留待
- 面板: /api/ams/bg加fg/fg_count(后台保持向后兼容)+POST /api/ams/:pkg/killfg(前台救命:优先am kill,launcher拒则pid直信号SIGTERM->SIGKILL绕过门控)+前端/#/bg前台组'终止'绿徽标+确认弹窗(一键清仍只后台不误杀前台); POST /api/input/gesture(返回=左缘右滑/回桌面=底部上滑,注入滑动轨迹复用SDK手势)+前端屏幕控制页加返回/回桌面MD3按钮
0warning;宿主test_reader_core 72-0/seg_smoke 19-0/loaders_smoke/book_smoke/formats_smoke PASS;面板selftest/node check PASS
445 lines
18 KiB
C
445 lines
18 KiB
C
/* mp_ams.c — App 运行表域:am list 解析 + amd 历史 + start/kill 控制。 */
|
||
#include "mp.h"
|
||
|
||
|
||
const char *ams_bin(void)
|
||
{
|
||
const char *e = getenv("MEOW_AMS_BIN");
|
||
if (e && e[0]) return e;
|
||
if (!access(AMS_BIN, X_OK)) return AMS_BIN;
|
||
return AMS_BIN_CARD;
|
||
}
|
||
|
||
void ams_fill(sbuf_t *s)
|
||
{
|
||
char out[16384];
|
||
char cmd[300];
|
||
snprintf(cmd, sizeof cmd, "%s list 2>/dev/null", ams_bin());
|
||
int code = run_cmd(cmd, out, sizeof out);
|
||
if (code != 0 || !strstr(out, "APP")) { sA(s, "{\"available\":false}"); return; }
|
||
panel_strip_ansi(out);
|
||
char *toks[256];
|
||
sbuf_t apps = {0};
|
||
int first = 1, count = 0;
|
||
char *ln = out;
|
||
while (*ln) {
|
||
char *nl = strchr(ln, '\n');
|
||
if (nl) *nl = 0;
|
||
if (strncmp(ln, "APP ", 4) == 0) {
|
||
int n = txt_tokens(ln, toks, 256);
|
||
if (n >= 9) {
|
||
/* 布局:APP <pkg> <name...> <state> <pid> <launches> <crashes> <last_kind> <last_code>
|
||
* tok[0]="APP" 前缀;自右向左固定:state=tok[n-6], pid=n-5, launches=n-4, crashes=n-3,
|
||
* last_kind=n-2, last_code=n-1;pkg=tok[1],name=join(tok[2..n-6))。 */
|
||
char nm[128];
|
||
join_tokens(toks, 2, n - 6, nm, sizeof nm);
|
||
if (!first) sA(&apps, ",");
|
||
first = 0; count++;
|
||
sA(&apps, "{\"pkg\":"); sA_str(&apps, toks[1]);
|
||
sA(&apps, ",\"name\":"); sA_str(&apps, nm);
|
||
sA(&apps, ",\"state\":"); sA_str(&apps, toks[n-6]);
|
||
sA(&apps, ",\"pid\":%d", atoi(toks[n-5]));
|
||
sA(&apps, ",\"launches\":%d", atoi(toks[n-4]));
|
||
sA(&apps, ",\"crashes\":%d", atoi(toks[n-3]));
|
||
sA(&apps, ",\"last_kind\":"); sA_str(&apps, toks[n-2]);
|
||
sA(&apps, ",\"last_code\":%d}", atoi(toks[n-1]));
|
||
}
|
||
}
|
||
if (nl) { *nl = '\n'; ln = nl + 1; } else break;
|
||
}
|
||
sA(s, "{\"available\":true,\"count\":%d,\"apps\":[%s]", count, apps.b ? apps.b : "");
|
||
sbuf_free(&apps);
|
||
ams_fg_fill(s); /* 追加 `,"fg":{..},"running":[..]`(首页前台 App 高亮;additive) */
|
||
sA(s, "}");
|
||
}
|
||
|
||
void ams_fg_fill(sbuf_t *s)
|
||
{
|
||
char out[16384];
|
||
char cmd[300];
|
||
snprintf(cmd, sizeof cmd, "%s status 2>/dev/null", ams_bin());
|
||
int code = run_cmd(cmd, out, sizeof out);
|
||
if (code != 0 || !strstr(out, "RUN")) { sA(s, ",\"fg\":null,\"running\":[]"); return; }
|
||
panel_strip_ansi(out);
|
||
char *toks[256];
|
||
char fg_pkg[128] = "", fg_act[160] = ""; int fg_pid = 0;
|
||
sbuf_t run = {0}; int first = 1;
|
||
char *ln = out;
|
||
while (*ln) {
|
||
char *nl = strchr(ln, '\n');
|
||
if (nl) *nl = 0;
|
||
if (strncmp(ln, "RUN ", 4) == 0) {
|
||
int n = txt_tokens(ln, toks, 256);
|
||
if (n >= 6) { /* RUN pkg state pid dur activity... */
|
||
char act[160];
|
||
join_tokens(toks, 5, n, act, sizeof act);
|
||
const char *st = toks[2];
|
||
if (!first) sA(&run, ",");
|
||
first = 0;
|
||
sA(&run, "{\"pkg\":"); sA_str(&run, toks[1]);
|
||
sA(&run, ",\"state\":"); sA_str(&run, st);
|
||
sA(&run, ",\"pid\":%d", atoi(toks[3]));
|
||
sA(&run, ",\"activity\":"); sA_str(&run, act);
|
||
sA(&run, "}");
|
||
if (strcmp(st, "fg") == 0 && !fg_pkg[0]) {
|
||
snprintf(fg_pkg, sizeof fg_pkg, "%s", toks[1]);
|
||
snprintf(fg_act, sizeof fg_act, "%s", act);
|
||
fg_pid = atoi(toks[3]);
|
||
}
|
||
}
|
||
}
|
||
if (nl) { *nl = '\n'; ln = nl + 1; } else break;
|
||
}
|
||
sA(s, ",\"fg\":");
|
||
if (fg_pkg[0]) { sA(s, "{\"pkg\":"); sA_str(s, fg_pkg); sA(s, ",\"activity\":"); sA_str(s, fg_act); sA(s, ",\"pid\":%d}", fg_pid); }
|
||
else sA(s, "null");
|
||
sA(s, ",\"running\":[%s]", run.b ? run.b : "");
|
||
sbuf_free(&run);
|
||
}
|
||
|
||
int api_ams(int sock)
|
||
{
|
||
sbuf_t s = {0};
|
||
ams_fill(&s);
|
||
int rc = send_json(sock, 200, s.b ? s.b : "{\"available\":false}");
|
||
sbuf_free(&s);
|
||
return rc;
|
||
}
|
||
|
||
int ams_pkg_ok(const char *pkg)
|
||
{
|
||
if (!pkg || !pkg[0]) return 0;
|
||
if (strlen(pkg) >= MEOW_APP_ID_MAX) return 0;
|
||
for (const char *p = pkg; *p; p++) {
|
||
unsigned char c = (unsigned char)*p;
|
||
if (c == '/' || c == ':' || c == ' ' || c == '\t' || c == '\n' || c == '\r') return 0;
|
||
if (c < 0x21 || c > 0x7e) return 0; /* 包名仅 ASCII 可见字符 */
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
void ams_history_parse(char *out, sbuf_t *s, const char *pkg) /* out 被 panel_strip_ansi 就地改 */
|
||
{
|
||
if (!utf8_valid_n(out, strlen(out))) { sA(s, "{\"available\":false}"); return; } /* 崩溃 detail 坏字节 → 降级 */
|
||
panel_strip_ansi(out);
|
||
char *toks[256];
|
||
sbuf_t ev = {0};
|
||
int first = 1, count = 0;
|
||
char *ln = out;
|
||
while (*ln) {
|
||
char *nl = strchr(ln, '\n');
|
||
if (nl) *nl = 0;
|
||
if (strncmp(ln, "EVT ", 4) == 0) {
|
||
int n = txt_tokens(ln, toks, 256);
|
||
if (n >= 5) { /* EVT epoch up kind pkg detail... */
|
||
char det[256];
|
||
join_tokens(toks, 5, n, det, sizeof det);
|
||
if (!first) sA(&ev, ",");
|
||
first = 0; count++;
|
||
sA(&ev, "{\"ts\":%ld,\"uptime_sec\":%ld,\"kind\":", atol(toks[1]), atol(toks[2]));
|
||
sA_str(&ev, toks[3]);
|
||
sA(&ev, ",\"detail\":"); sA_str(&ev, det);
|
||
sA(&ev, "}");
|
||
}
|
||
}
|
||
if (nl) { *nl = '\n'; ln = nl + 1; } else break;
|
||
}
|
||
sA(s, "{\"available\":true,\"pkg\":"); sA_str(s, pkg);
|
||
sA(s, ",\"count\":%d,\"events\":[%s]}", count, ev.b ? ev.b : "");
|
||
sbuf_free(&ev);
|
||
}
|
||
|
||
void ams_history_fill(sbuf_t *s, const char *pkg)
|
||
{
|
||
char out[16384];
|
||
char cmd[320];
|
||
snprintf(cmd, sizeof cmd, "%s history %s 2>/dev/null", ams_bin(), pkg);
|
||
int code = run_cmd(cmd, out, sizeof out);
|
||
if (code != 0 || !strstr(out, "EVT")) { sA(s, "{\"available\":false}"); return; }
|
||
ams_history_parse(out, s, pkg);
|
||
}
|
||
|
||
int api_ams_history(int sock, const char *pkg)
|
||
{
|
||
if (!ams_pkg_ok(pkg)) return send_err(sock, 400, "BAD_REQUEST", "包名非法");
|
||
sbuf_t s = {0};
|
||
ams_history_fill(&s, pkg);
|
||
int rc = send_json(sock, 200, s.b ? s.b : "{\"available\":false}");
|
||
sbuf_free(&s);
|
||
return rc;
|
||
}
|
||
|
||
int api_ams_ctl(int sock, const char *pkg, int kill)
|
||
{
|
||
if (!ams_pkg_ok(pkg)) return send_err(sock, 400, "BAD_REQUEST", "包名非法");
|
||
char out[256];
|
||
char cmd[320];
|
||
snprintf(cmd, sizeof cmd, "%s %s %s 2>/dev/null", ams_bin(), kill ? "kill" : "start", pkg);
|
||
int code = run_cmd(cmd, out, sizeof out);
|
||
if (code == 0 && strstr(out, "OK ")) {
|
||
sbuf_t s = {0};
|
||
sA(&s, "{\"accepted\":true,\"op\":\"%s\",\"pkg\":", kill ? "kill" : "start");
|
||
sA_str(&s, pkg);
|
||
sA(&s, ",\"message\":"); sA_str(&s, out);
|
||
sA(&s, "}");
|
||
int rc2 = send_json(sock, 200, s.b ? s.b : "");
|
||
sbuf_free(&s);
|
||
return rc2;
|
||
}
|
||
/* am CLI 以文本报告失败(ERR ...);按内容映射 HTTP 码(如实,绝不掩盖)。 */
|
||
const char *detail = out[0] ? out : (kill ? "am kill 失败" : "am start 失败");
|
||
if (strstr(detail, "not registered")) return send_err(sock, 404, "NOT_FOUND", detail);
|
||
if (strstr(detail, "busy")) return send_err(sock, 409, "CONFLICT", detail);
|
||
if (strstr(detail, "not running")) return send_err(sock, 409, "CONFLICT", detail);
|
||
if (strstr(detail, "launcher offline")) return send_err(sock, 503, "AMS_UNAVAILABLE", "amsd/launcher 不可达");
|
||
return send_err(sock, 500, "INTERNAL", detail);
|
||
}
|
||
|
||
/* ---- 批④ 后台活动列表:/api/ams/bg(只读) ----
|
||
* 数据源=amsd FG/BG 注册表(am list 的 `state=bg` 行)+ 每进程 pcpu/rss(读 /proc/<pid>,
|
||
* 与 launcher bg_page 的 bgm_proc_sample 同源同口径,单线程静态差分缓存取 CPU 占用)。
|
||
* CPU/内存诚实原则:pid 读不到 / 首采样无差分基线 → 对应字段为 null(前端显示「—」),绝不编造。 */
|
||
#define BG_CACHE_MAX 8
|
||
static long g_bg_pid[BG_CACHE_MAX]; /* /proc/<pid>/stat utime+stime 差分缓存(单线程串行持有) */
|
||
static unsigned long long g_bg_tick[BG_CACHE_MAX];
|
||
|
||
/* 采样单进程 CPU/内存:*pcpu<0=未知(无基线/读不到)、*rss_kb<0=未知。 */
|
||
static void bg_proc_sample(int pid, double *pcpu, long *rss_kb)
|
||
{
|
||
*pcpu = -1.0;
|
||
*rss_kb = -1;
|
||
if (pid <= 0) return;
|
||
char path[64], line[1024];
|
||
/* 内存:VmRSS(KB) */
|
||
snprintf(path, sizeof path, "/proc/%d/status", pid);
|
||
if (read_small_file(path, line, sizeof line) >= 0) {
|
||
char *p = strstr(line, "VmRSS:");
|
||
if (p) {
|
||
p = strchr(p, ' ');
|
||
if (p) { while (*p == ' ' || *p == '\t') p++; if (*p) *rss_kb = atol(p); }
|
||
}
|
||
}
|
||
/* CPU:/proc/<pid>/stat 的 utime(14)+stime(15) 差分 / hz(%) */
|
||
snprintf(path, sizeof path, "/proc/%d/stat", pid);
|
||
if (read_small_file(path, line, sizeof line) >= 0) {
|
||
char *rp = strrchr(line, ')');
|
||
if (rp) {
|
||
char *p = rp + 2;
|
||
unsigned long long ut = 0, st = 0;
|
||
for (int f = 0; f < 14 && *p; f++) {
|
||
while (*p == ' ' || *p == '\t') p++;
|
||
if (!*p) break;
|
||
char *end;
|
||
unsigned long long v = strtoull(p, &end, 10);
|
||
if (end == p) { p++; continue; }
|
||
p = end;
|
||
if (f == 11) ut = v; /* utime */
|
||
if (f == 12) st = v; /* stime */
|
||
}
|
||
unsigned long long cur = ut + st;
|
||
static double hz = 0.0;
|
||
if (hz <= 0) { hz = (double)sysconf(_SC_CLK_TCK); if (hz <= 0) hz = 100.0; }
|
||
int slot = -1, had = 0;
|
||
unsigned long long prev = 0;
|
||
for (int i = 0; i < BG_CACHE_MAX; i++)
|
||
if (g_bg_pid[i] == pid) { had = 1; slot = i; prev = g_bg_tick[i]; break; }
|
||
if (!had) {
|
||
for (int i = 0; i < BG_CACHE_MAX; i++) if (g_bg_pid[i] == 0) { slot = i; break; }
|
||
if (slot < 0) slot = 0;
|
||
}
|
||
if (slot >= 0) { g_bg_pid[slot] = pid; g_bg_tick[slot] = cur; }
|
||
if (had) { *pcpu = 100.0 * (double)(cur - prev) / hz; if (*pcpu < 0) *pcpu = 0; }
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 发射单条 APP 行 JSON(复用于 bg/fg 列表;带 state + is_fg + cpu_pct/rss_kb)。
|
||
* toks 为已切分的 `APP ...` 行(n>=9);*first 控制数组元素逗号(调用方初始化为 1)。
|
||
* cpu_pct/rss_kb 诚实原则:pid 读不到/不作为可用进程 → null(前端显「—」),绝不编造。 */
|
||
static void ams_entry_emit(sbuf_t *out, char **toks, int n, int *first)
|
||
{
|
||
const char *st = toks[n - 6]; /* 自右向左:state=tok[n-6], pid=n-5(与 ams_fill 同布局) */
|
||
char nm[128];
|
||
join_tokens(toks, 2, n - 6, nm, sizeof nm);
|
||
int pid = atoi(toks[n - 5]);
|
||
double pcpu = -1.0; long rss = -1;
|
||
if (pid > 0) bg_proc_sample(pid, &pcpu, &rss);
|
||
if (!*first) sA(out, ",");
|
||
*first = 0;
|
||
sA(out, "{\"pkg\":"); sA_str(out, toks[1]);
|
||
sA(out, ",\"name\":"); sA_str(out, nm);
|
||
sA(out, ",\"pid\":%d", pid);
|
||
sA(out, ",\"state\":"); sA_str(out, st);
|
||
sA(out, ",\"is_fg\":%s", strcmp(st, "fg") == 0 ? "true" : "false");
|
||
sA(out, ",\"cpu_pct\":");
|
||
if (pcpu < 0) sA(out, "null"); else sA(out, "%.1f", pcpu);
|
||
sA(out, ",\"rss_kb\":");
|
||
if (rss < 0) sA(out, "null"); else sA(out, "%ld", rss);
|
||
sA(out, "}");
|
||
}
|
||
|
||
/* 解析已 strip 的 `am list` 全文 → bg 列表 JSON(state=bg 的 APP 行;向后兼容专用,selftest 覆盖)。 */
|
||
void ams_bg_parse(char *list_text, sbuf_t *s)
|
||
{
|
||
char *toks[256];
|
||
sbuf_t apps = {0};
|
||
int first = 1, count = 0;
|
||
char *ln = list_text;
|
||
while (*ln) {
|
||
char *nl = strchr(ln, '\n');
|
||
if (nl) *nl = 0;
|
||
if (strncmp(ln, "APP ", 4) == 0) {
|
||
int n = txt_tokens(ln, toks, 256);
|
||
if (n >= 9) {
|
||
const char *st = toks[n - 6];
|
||
if (strcmp(st, "bg") == 0) {
|
||
count++;
|
||
ams_entry_emit(&apps, toks, n, &first);
|
||
}
|
||
}
|
||
}
|
||
if (nl) { *nl = '\n'; ln = nl + 1; } else break;
|
||
}
|
||
sA(s, "{\"available\":true,\"count\":%d,\"ts\":%ld,\"apps\":[%s]}",
|
||
count, (long)time(NULL), apps.b ? apps.b : "");
|
||
sbuf_free(&apps);
|
||
}
|
||
|
||
/* am list 全文 → bg+fg 双列表 JSON。`apps`(bg)保持向后兼容(count=bg 数量);
|
||
* 追加 `fg` + `fg_count`(前台 App,前端救命分组)。state=stop 无进程,不在本页(proc 页可见)。 */
|
||
void ams_bg_fill(sbuf_t *s)
|
||
{
|
||
char out[16384];
|
||
char cmd[300];
|
||
snprintf(cmd, sizeof cmd, "%s list 2>/dev/null", ams_bin());
|
||
int code = run_cmd(cmd, out, sizeof out);
|
||
if (code != 0 || !strstr(out, "APP")) { sA(s, "{\"available\":false}"); return; }
|
||
panel_strip_ansi(out);
|
||
char *toks[256];
|
||
sbuf_t bg = {0}, fg = {0};
|
||
int bg_first = 1, fg_first = 1, bg_count = 0, fg_count = 0;
|
||
char *ln = out;
|
||
while (*ln) {
|
||
char *nl = strchr(ln, '\n');
|
||
if (nl) *nl = 0;
|
||
if (strncmp(ln, "APP ", 4) == 0) {
|
||
int n = txt_tokens(ln, toks, 256);
|
||
if (n >= 9) {
|
||
const char *st = toks[n - 6];
|
||
if (strcmp(st, "bg") == 0) { bg_count++; ams_entry_emit(&bg, toks, n, &bg_first); }
|
||
else if (strcmp(st, "fg") == 0) { fg_count++; ams_entry_emit(&fg, toks, n, &fg_first); }
|
||
}
|
||
}
|
||
if (nl) { *nl = '\n'; ln = nl + 1; } else break;
|
||
}
|
||
sA(s, "{\"available\":true,\"count\":%d,\"ts\":%ld,\"apps\":[%s]",
|
||
bg_count, (long)time(NULL), bg.b ? bg.b : "");
|
||
sA(s, ",\"fg_count\":%d,\"fg\":[%s]}", fg_count, fg.b ? fg.b : "");
|
||
sbuf_free(&bg);
|
||
sbuf_free(&fg);
|
||
}
|
||
|
||
int api_ams_bg(int sock)
|
||
{
|
||
sbuf_t s = {0};
|
||
ams_bg_fill(&s);
|
||
int rc = send_json(sock, 200, s.b ? s.b : "{\"available\":false}");
|
||
sbuf_free(&s);
|
||
return rc;
|
||
}
|
||
|
||
/* 从 am list 解析某 pkg 的当前 pid(未注册/无进程 → 0)。 */
|
||
static int ams_pkg_pid(const char *pkg)
|
||
{
|
||
char out[16384];
|
||
char cmd[300];
|
||
snprintf(cmd, sizeof cmd, "%s list 2>/dev/null", ams_bin());
|
||
if (run_cmd(cmd, out, sizeof out) != 0 || !strstr(out, "APP")) return 0;
|
||
panel_strip_ansi(out);
|
||
char *toks[256];
|
||
char *ln = out;
|
||
while (*ln) {
|
||
char *nl = strchr(ln, '\n');
|
||
if (nl) *nl = 0;
|
||
if (strncmp(ln, "APP ", 4) == 0) {
|
||
int n = txt_tokens(ln, toks, 256);
|
||
if (n >= 9 && strcmp(toks[1], pkg) == 0) {
|
||
int pid = atoi(toks[n - 5]);
|
||
if (nl) *nl = '\n';
|
||
return pid;
|
||
}
|
||
}
|
||
if (nl) { *nl = '\n'; ln = nl + 1; } else break;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* /proc/<pid>/stat 进程是否已消失/僵尸:-1=已消失,1=僵尸,0=存活。 */
|
||
static int proc_zombie(int pid)
|
||
{
|
||
char path[64], line[256];
|
||
snprintf(path, sizeof path, "/proc/%d/stat", pid);
|
||
if (read_small_file(path, line, sizeof line) < 0) return -1;
|
||
char *rp = strrchr(line, ')');
|
||
if (!rp) return -1;
|
||
const char *p = rp + 2;
|
||
while (*p == ' ' || *p == '\t') p++;
|
||
return (*p == 'Z') ? 1 : 0;
|
||
}
|
||
|
||
/* 前台强制终止(救命):优先走 `am kill`(launcher 干净 SIGTERM 序列,accepted);若 launcher 拒
|
||
* (framework / 无前台会话 → "launcher refused"),回退:解析 am list 拿当前 pid → 直接 SIGTERM,
|
||
* 宽限后仍存活走 SIGKILL(与 launcher 杀序列同语义)。这是用户明示「前台卡死救命」的强通道——
|
||
* 前端已二次确认 + 标注「前台」。require_auth 在路由层已护。 */
|
||
int api_ams_kill_fg(int sock, const char *pkg)
|
||
{
|
||
if (!ams_pkg_ok(pkg)) return send_err(sock, 400, "BAD_REQUEST", "包名非法");
|
||
|
||
/* ① 干净通道:am kill(launcher 执行 SIGTERM 序列)。 */
|
||
{
|
||
char out[256];
|
||
char cmd[320];
|
||
snprintf(cmd, sizeof cmd, "%s kill %s 2>/dev/null", ams_bin(), pkg);
|
||
int code = run_cmd(cmd, out, sizeof out);
|
||
if (code == 0 && strstr(out, "OK ")) {
|
||
sbuf_t s = {0};
|
||
sA(&s, "{\"accepted\":true,\"op\":\"kill-fg\",\"pkg\":");
|
||
sA_str(&s, pkg);
|
||
sA(&s, ",\"force\":false,\"message\":"); sA_str(&s, out);
|
||
sA(&s, "}");
|
||
int rc = send_json(sock, 200, s.b ? s.b : "");
|
||
sbuf_free(&s);
|
||
return rc;
|
||
}
|
||
panel_log("ams kill-fg %s via am refused: %s -> direct signal fallback", pkg,
|
||
out[0] ? out : "(empty)");
|
||
}
|
||
|
||
/* ② 回退:直接按 pid 信号(绕过 launcher 门控)。 */
|
||
int pid = ams_pkg_pid(pkg);
|
||
if (pid <= 0) return send_err(sock, 409, "CONFLICT", "该 App 当前无进程可终止");
|
||
|
||
kill(pid, SIGTERM);
|
||
int killed_hard = 0;
|
||
for (int i = 0; i < 5; i++) { /* SIGTERM 宽限 ~0.6s(与 launcher 杀序列同语义) */
|
||
struct timespec ts = {0, 120 * 1000 * 1000};
|
||
nanosleep(&ts, NULL);
|
||
int z = proc_zombie(pid);
|
||
if (z < 0 || z == 1) break; /* 消失/僵尸 → 已终止 */
|
||
}
|
||
if (proc_zombie(pid) == 0) { kill(pid, SIGKILL); killed_hard = 1; }
|
||
|
||
char msg[192];
|
||
snprintf(msg, sizeof msg, "force terminated pid %d via direct signal%s", pid,
|
||
killed_hard ? " (SIGKILL 强杀)" : " (SIGTERM)");
|
||
sbuf_t s = {0};
|
||
sA(&s, "{\"accepted\":true,\"op\":\"kill-fg\",\"pkg\":");
|
||
sA_str(&s, pkg);
|
||
sA(&s, ",\"force\":%s,\"pid\":%d,\"message\":", killed_hard ? "true" : "false", pid);
|
||
sA_str(&s, msg);
|
||
sA(&s, "}");
|
||
int rc = send_json(sock, 200, s.b ? s.b : "");
|
||
sbuf_free(&s);
|
||
return rc;
|
||
}
|