Files
MeowOS-For-R3P2/patch_v2/meow_sysui/meow_sysui.c
T
MeowOS 2a6c4e97c5 系统基建线(agent-139): svcd subreaper加固 + meow_sysui总监 + 启动链
- svcd: 审计确认现网已子进程化(fork+execv+setpgid+waitpid+SIGCHLD+set_cloexec,无system()); 增量 prctl(PR_SET_CHILD_SUBREAPER) 代收孤儿孙进程 → fb2d608b
- meow_sysui(新增): 系统进程总监, 仿svcd成熟模式(信号自管道/poll/双探测kill/waitpid/subreaper/状态机), 监护自己fork+exec进程, 按sysui.d/<name>.conf逐条拉起, 无级联/有限次兜底/主循环绝不exit/空配置空转 → 173013cc
- 启动链 S70meow_sysui → S89meow_svcd → S90meow_bootmenu; svcd拉起权判归(sysui在→交监管,不在→回退现网重挂循环)
- 宿主集成测试过(常崩/稳定隔离/无级联/subreaper收尸/端到端); meow_ui.a未动
- 需加到固件打包(build_fw_apps_m267.sh): sysui→/usr/bin/meow_sysui, svcd覆盖(fb2d608b), S70+S89; 校验 svcd 762e84d0→fb2d608b, 新增 sysui 173013cc
2026-08-30 19:07:47 +08:00

1106 lines
38 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* meow_sysui.c — MeowOS 系统进程总监(Supervisor Tree 顶层)。
*
* 定位(FRAMEWORK_DESIGN §10.1 / 用户定案[源:2026-08-30]):
* - 系统进程*总监*:监管系统进程(launcher / meow_svcd / meow_mdns / 未来媒体服务 /
* MeowPanel),**一个崩 → 只拉起它,绝不级联、绝不拖垮自身**。
* - 自身**最小依赖、不渲染、不承载重逻辑** → 最稳。它只做「监护 + 逐个拉起」的轻活,
* **只监护它自己 fork+exec 出来的进程**(waitpid 需为父进程,这是「只拉起它」的可靠前提)。
* - 由开机启动链 `etc/init.d/S70meow_sysui` 拉起(早于 S89meow_svcd),居顶层监管;
* 监管方向固定:sysui 总监 → svcd 被监管 → 服务为 svcd 子进程。
*
* 配置(板载权威,目录式,复用 services.d 语义):`/usr/data/meow/sysui.d/<name>.conf`
* key=value,'#'/';' 注释,未知键忽略(前向兼容)。键:exec / args / env / enabled /
* restart(always|on-fail|never) / backoff_ms / max_restart。**无 trigger**——sysui 只做
* 常驻系统进程的监护,`enabled=1` 即拉起/监管,拔卡无关(系统进程不认卡)。
* 说明:TF 卡 `MeowOS/sysui.d/` 作为扩展点本阶段不读,代码只读板载(最小化)。
*
* 控制通道:AF_UNIX 单行文本(默认 /tmp/meow_sysui.sock)。
* start|stop|restart <name|all> / status [name|all] / reload / pid
* 守护默认后台常驻(无子命令即启动);`-f/--foreground` 供 init/调试。
*
* 状态文件:/tmp/meow_sysui.status;pidfile:/tmp/meow_sysui.pid。
* 日志:stderr(-f)+ 默认追加 /tmp/meow_sysui.log + syslog(LOG_DAEMON) 尝试。
*
* 护固件设计(防真机开不了机):
* - 任一子进程崩溃只处理该条目(填表/重启),主循环绝不因此退出;不级联、不碰邻居。
* - 失败兜底(有限次):某进程起不来 → restart_count++,超 max_restart → FAILED 停止
* 自动重启,仅记日志;绝不形成崩溃-重启热循环拖垮系统。
* - 空配置可跑:无 sysui.d/ 或全 enabled=0 → 空转存活(只写日志/status),不报错。
* - 最小依赖:无 dlopen/无渲染/无卡热点扫描;仅 POSIX + Linux 基础接口(同 svcd)。
*
* 编译(mipsel 静态,见 build_sysui.sh):
* ~/Project/R3ProII/.tools/zig-mipsel-cc -Os -Wall -Wextra -static meow_sysui.c -o meow_sysui
* 宿主编译(集成测试):cc -Os -Wall -Wextra meow_sysui.c -o meow_sysui_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 <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/prctl.h>
#include <syslog.h>
#include <poll.h>
/* ---- 默认路径 / 常量 ---- */
#define SYSUI_DIR_DEFAULT "/usr/data/meow/sysui.d" /* 板载监管配置目录(权威) */
#define SYSUI_SOCK_DEFAULT "/tmp/meow_sysui.sock"
#define SYSUI_STATUS_DEFAULT "/tmp/meow_sysui.status"
#define SYSUI_PIDFILE_DEFAULT "/tmp/meow_sysui.pid"
#define SYSUI_LOG_DEFAULT "/tmp/meow_sysui.log"
#define SYSUI_MAX_SUP 32
#define SYSUI_NAME_MAX 64
#define SYSUI_EXEC_MAX 256
#define SYSUI_LINE_MAX 1024
#define SYSUI_ARG_MAX 128
#define SYSUI_MAX_ARGS 24
#define SYSUI_ENV_MAX 24
#define SYSUI_ENV_MAXLEN 128
#define SYSUI_RESTART_STR_MAX 16
#define SYSUI_TICK_MAX_MS 1000 /* poll 上限(监护探测 ~1s) */
#define SYSUI_STOP_GRACE_MS 2500 /* SIGTERM 后 SIGKILL 兜底 */
#define SYSUI_STABLE_MS 30000 /* 连续运行此毫秒 → 清零 restart_count */
#define SYSUI_SOCK_PATH_MAX 108
#define SYSUI_SIGCHLD_CHAR 'C'
#define SYSUI_SIGTERM_CHAR 'T'
/* ---- ANSI 终端配色(status 回显用;仅客户端 stdout 为 TTY 时保留,非 TTY 去色) ---- */
#define AN_RESET "\033[0m"
#define AN_BOLD "\033[1m"
#define AN_DIM "\033[2m"
#define AN_RED "\033[31m"
#define AN_GREEN "\033[32m"
#define AN_YELLOW "\033[33m"
#define AN_CYAN "\033[36m"
#define AN_GREY "\033[90m"
/* ---- 被监管进程状态机 ---- */
typedef enum {
SU_DISABLED = 0, /* enabled=0,永不自动拉起 */
SU_STOPPED, /* 未运行(被停/等待退避) */
SU_RUNNING, /* 子进程存活 */
SU_STOPPING, /* 已发 SIGTERM,等退出(超时 SIGKILL 兜底) */
SU_FAILED /* 起不来/反复崩且超 max_restart,停止自动重启 */
} su_state_t;
static const char *g_state_str[] = { "disabled", "stopped", "running",
"stopping", "failed" };
typedef struct {
char name[SYSUI_NAME_MAX];
char exec[SYSUI_EXEC_MAX];
char argbuf[SYSUI_MAX_ARGS][SYSUI_ARG_MAX]; /* args 拷贝 */
char *argv[SYSUI_MAX_ARGS + 1]; /* exec + args,末尾 NULL */
int argc;
char envbuf[SYSUI_ENV_MAX][SYSUI_ENV_MAXLEN]; /* "K=V" */
int envc;
int enabled;
char restart[SYSUI_RESTART_STR_MAX]; /* always|on-fail|never */
long backoff_ms;
int max_restart; /* <0=无限 */
/* 运行状态 */
pid_t pid;
su_state_t state;
int restart_count;
int last_exit_status; /* waitpid status */
int has_exit; /* 1=曾退出 */
unsigned long long last_exit_wall; /* 上次退出墙钟秒 */
unsigned long long restart_at_ms; /* 退避到期;0=无 */
unsigned long long stop_since_ms; /* SIGTERM 发出时刻 */
unsigned long long run_since_ms; /* 本轮启动时刻 */
int stop_requested;
int restart_pending; /* 停后需重启(restart 命令) */
int user_stopped; /* 用户 stop,勿自动拉起 */
int in_config; /* 本次扫描仍存在 */
} sup_t;
static sup_t g_sups[SYSUI_MAX_SUP];
static int g_nsup = 0;
static char g_dir[256] = SYSUI_DIR_DEFAULT;
static char g_sock[SYSUI_SOCK_PATH_MAX] = SYSUI_SOCK_DEFAULT;
static char g_status[256] = SYSUI_STATUS_DEFAULT;
static char g_pidfile[256] = SYSUI_PIDFILE_DEFAULT;
static char g_log[256] = SYSUI_LOG_DEFAULT;
static int g_shutdown = 0;
static int g_fg = 0;
static int g_sigpipe[2] = {-1, -1};
static int g_listen_fd = -1;
static int g_status_fd = -1;
static void handle_connection(int fd);
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;
}
/* log:stderr(-f)+ 默认追加文件 + syslog 尝试 */
static void su_log(const char *fmt, ...)
{
char buf[512], line[560];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof buf, fmt, ap);
va_end(ap);
int l = snprintf(line, sizeof line, "%llu [meow_sysui] %s\n", now_ms(), buf);
fprintf(stderr, "%s", line);
fflush(stderr);
if (g_log[0]) {
int fd = open(g_log, O_WRONLY | O_APPEND | O_CREAT, 0644);
if (fd >= 0) { ssize_t w = write(fd, line, (size_t)l); (void)w; close(fd); }
}
syslog(LOG_DAEMON | LOG_INFO, "[meow_sysui] %s", buf);
}
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;
}
static char *trim(char *s)
{
char *end;
while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n') s++;
if (*s == '\0') return s;
end = s + strlen(s) - 1;
while (end > s && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) *end-- = '\0';
return s;
}
static void set_cloexec(int fd)
{
if (fd < 0) return;
int fl = fcntl(fd, F_GETFD, 0);
if (fl >= 0) fcntl(fd, F_SETFD, fl | FD_CLOEXEC);
}
static int read_file(const char *path, char *buf, size_t sz)
{
int fd = open(path, O_RDONLY);
if (fd < 0) return -1;
ssize_t n = read(fd, buf, sz - 1);
close(fd);
if (n < 0) return -1;
buf[n] = '\0';
return (long)n;
}
static int write_file(const char *path, const char *data, size_t len)
{
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) return -1;
ssize_t n = write(fd, data, len);
close(fd);
return (n == (ssize_t)len) ? 0 : -1;
}
/* ---- 配置解析(复用 svcd 的 tokenizer / apply_field 思路) ---- */
static int split_args(char *in, char tok[][SYSUI_ARG_MAX], int maxtok)
{
int n = 0;
char *p = in;
while (*p && n < maxtok) {
while (*p == ' ' || *p == '\t') p++;
if (!*p) break;
char *out = tok[n];
int oi = 0;
char quote = 0;
while (*p) {
char c = *p;
if (quote) {
if (c == quote) { quote = 0; p++; continue; }
if (c == '\\' && p[1]) { p++; c = *p; }
} else {
if (c == ' ' || c == '\t') break;
if (c == '"' || c == '\'') { quote = c; p++; continue; }
if (c == '\\' && p[1]) { p++; c = *p; }
}
if (oi < SYSUI_ARG_MAX - 1) out[oi++] = c;
p++;
}
out[oi] = '\0';
if (oi > 0) n++;
}
return n;
}
static void apply_field(sup_t *s, const char *k, const char *v)
{
if (strcmp(k, "exec") == 0) { safe_strcpy(s->exec, sizeof s->exec, v); }
else if (strcmp(k, "args") == 0) { s->argc = split_args((char *)v, s->argbuf, SYSUI_MAX_ARGS); }
else if (strcmp(k, "env") == 0) {
if (s->envc < SYSUI_ENV_MAX)
safe_strcpy(s->envbuf[s->envc++], sizeof s->envbuf[0], v);
}
else if (strcmp(k, "enabled") == 0) { s->enabled = (atoi(v) != 0); }
else if (strcmp(k, "restart") == 0) { safe_strcpy(s->restart, sizeof s->restart, v); }
else if (strcmp(k, "backoff_ms") == 0) { s->backoff_ms = atol(v); }
else if (strcmp(k, "max_restart") == 0){ s->max_restart = atoi(v); }
else if (strcmp(k, "name") == 0) { /* 名字以文件名为准,name 仅备忘 */ }
}
static int load_conf(const char *path, const char *name, sup_t *s)
{
char buf[SYSUI_LINE_MAX];
FILE *f = fopen(path, "r");
if (!f) return -1;
memset(s, 0, sizeof *s);
safe_strcpy(s->name, sizeof s->name, name);
s->enabled = 0;
s->state = SU_STOPPED;
safe_strcpy(s->restart, sizeof s->restart, "on-fail");
s->backoff_ms = 2000;
s->max_restart = 5;
while (fgets(buf, sizeof buf, f)) {
char *line = trim(buf);
if (!*line || *line == '#' || *line == ';') continue;
char *eq = strchr(line, '=');
if (!eq) continue;
*eq = '\0';
char *k = trim(line);
char *v = trim(eq + 1);
for (char *q = v; *q; q++) {
if ((*q == '#' || *q == ';') && (q == v || *(q - 1) != '\\')) { *q = '\0'; break; }
}
v = trim(v);
if (k[0]) apply_field(s, k, v);
}
fclose(f);
if (!s->exec[0]) { su_log("supervised %s: no exec, skip", name); return -1; }
if (s->backoff_ms < 0) s->backoff_ms = 0;
if (s->max_restart < 0) s->max_restart = -1;
if (strcmp(s->restart, "always") && strcmp(s->restart, "on-fail") &&
strcmp(s->restart, "never"))
safe_strcpy(s->restart, sizeof s->restart, "on-fail");
s->argv[0] = s->exec;
for (int i = 0; i < s->argc; i++) s->argv[i + 1] = s->argbuf[i];
s->argv[s->argc + 1] = NULL;
return 0;
}
/* ---- 监管表工具 ---- */
static int find_sup(const char *name)
{
for (int i = 0; i < g_nsup; i++)
if (strcmp(g_sups[i].name, name) == 0) return i;
return -1;
}
static void remove_sup_idx(int idx)
{
if (idx < 0 || idx >= g_nsup) return;
if (g_nsup - idx - 1 > 0)
memmove(&g_sups[idx], &g_sups[idx + 1],
(size_t)(g_nsup - idx - 1) * sizeof(sup_t));
g_nsup--;
}
static void build_status_file(void)
{
char buf[8192];
int n = snprintf(buf, sizeof buf,
"# meow_sysui status\nsupervised=%d\n", g_nsup);
for (int i = 0; i < g_nsup && n < (int)sizeof buf - 1; i++) {
sup_t *s = &g_sups[i];
int k = snprintf(buf + n, sizeof buf - (size_t)n,
"sup=%s state=%s pid=%d restart=%d exit=%d backoff_ms=%ld enabled=%d\n",
s->name, g_state_str[s->state], (int)s->pid, s->restart_count,
s->last_exit_status, s->backoff_ms, s->enabled);
if (k <= 0) break;
n += k;
}
if (g_status_fd >= 0) {
if (lseek(g_status_fd, 0, SEEK_SET) == 0 && ftruncate(g_status_fd, 0) == 0) {
ssize_t w = write(g_status_fd, buf, (size_t)n);
(void)w;
}
} else {
(void)write_file(g_status, buf, (size_t)n);
}
}
/* ---- 子进程管理 ---- */
static int spawn_sup(sup_t *s)
{
if (s->pid > 0) return 0;
pid_t pid = fork();
if (pid < 0) {
su_log("supervised %s: fork failed: %s", s->name, strerror(errno));
s->state = SU_FAILED;
return -1;
}
if (pid == 0) {
/* 子进程:独立进程组,防父进程信号误伤。 */
(void)setpgid(0, 0);
for (int i = 0; i < s->envc; i++) {
char *eq = strchr(s->envbuf[i], '=');
if (!eq) continue;
*eq = '\0';
(void)setenv(s->envbuf[i], eq + 1, 1);
}
execv(s->exec, s->argv);
fprintf(stderr, "[meow_sysui] supervised %s execv %s: %s\n",
s->name, s->exec, strerror(errno));
_exit(127);
}
s->pid = pid;
s->state = SU_RUNNING;
s->run_since_ms = now_ms();
s->stop_requested = 0;
s->restart_pending = 0;
s->restart_at_ms = 0;
su_log("supervised %s start: pid=%d exec=%s", s->name, (int)pid, s->exec);
return 0;
}
static void stop_sup(sup_t *s, int restart_after, int user)
{
if (user) s->user_stopped = 1;
if (s->pid <= 0) {
if (s->state == SU_RUNNING || s->state == SU_STOPPING) s->state = SU_STOPPED;
if (restart_after) { s->restart_count = 0; s->user_stopped = 0; spawn_sup(s); }
return;
}
if (s->state == SU_STOPPING) { if (restart_after) s->restart_pending = 1; return; }
s->stop_requested = 1;
s->restart_pending = restart_after;
s->state = SU_STOPPING;
s->stop_since_ms = now_ms();
(void)kill(s->pid, SIGTERM);
su_log("supervised %s: SIGTERM (pid=%d)", s->name, (int)s->pid);
}
static void apply_exit(sup_t *s, int status)
{
int failed = !(WIFEXITED(status) && WEXITSTATUS(status) == 0);
s->pid = 0;
s->last_exit_status = status;
s->has_exit = 1;
s->last_exit_wall = (unsigned long long)time(NULL);
if (s->restart_pending) {
s->restart_pending = 0;
s->stop_requested = 0;
s->restart_count = 0;
s->state = SU_STOPPED;
su_log("supervised %s: restart -> respawn", s->name);
spawn_sup(s);
return;
}
if (s->stop_requested) {
s->stop_requested = 0;
s->state = SU_STOPPED;
su_log("supervised %s: stopped (exit=%s)", s->name, failed ? "fail" : "ok");
return;
}
/* 非受控退出 → 崩溃处理(只处理本条目,无级联) */
int want = 0;
if (strcmp(s->restart, "always") == 0) want = 1;
else if (strcmp(s->restart, "on-fail") == 0) want = failed;
/* never → want=0 */
if (want) {
s->restart_count++;
if (s->max_restart >= 0 && s->restart_count > s->max_restart) {
s->state = SU_FAILED;
su_log("supervised %s: exceeded max_restart(%d) -> FAILED, stop auto-restart (exit=%s)",
s->name, s->max_restart, failed ? "fail" : "ok");
} else {
long wait = s->backoff_ms;
s->restart_at_ms = now_ms() + (unsigned long long)wait;
s->state = SU_STOPPED;
su_log("supervised %s: restart in %ldms (attempt %d, exit=%s)",
s->name, wait, s->restart_count, failed ? "fail" : "ok");
}
} else {
s->state = failed ? SU_FAILED : SU_STOPPED;
su_log("supervised %s: exit (policy=%s, %s) -> %s",
s->name, s->restart, failed ? "fail" : "ok",
failed ? "FAILED" : "STOPPED");
}
}
static void sig_child(int sig)
{
(void)sig;
char c = SYSUI_SIGCHLD_CHAR;
ssize_t w = write(g_sigpipe[1], &c, 1);
(void)w;
}
static void sig_term(int sig)
{
(void)sig;
g_shutdown = 1;
char c = SYSUI_SIGTERM_CHAR;
ssize_t w = write(g_sigpipe[1], &c, 1);
(void)w;
}
static void reap_children(void)
{
char buf[64];
while (read(g_sigpipe[0], buf, sizeof buf) > 0) { /* drain */ }
int status;
pid_t pid;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
int idx = -1;
for (int i = 0; i < g_nsup; i++)
if (g_sups[i].pid == pid) { idx = i; break; }
if (idx < 0) { su_log("reap unknown pid=%d", (int)pid); continue; }
apply_exit(&g_sups[idx], status);
}
build_status_file();
}
/* --- 配置扫描 / 调和 --- */
static int scan_conf_names(const char *dir, char out[][SYSUI_NAME_MAX], int max)
{
DIR *d = opendir(dir);
if (!d) return 0;
int n = 0;
struct dirent *e;
while ((e = readdir(d)) != NULL && n < max) {
if (e->d_name[0] == '.') continue;
size_t l = strlen(e->d_name);
if (l < 6 || strcmp(e->d_name + l - 5, ".conf") != 0) continue;
size_t nl = l - 5;
if (nl >= SYSUI_NAME_MAX) nl = SYSUI_NAME_MAX - 1;
memcpy(out[n], e->d_name, nl);
out[n][nl] = '\0';
n++;
}
closedir(d);
return n;
}
static void scan_and_reconcile(void)
{
char names[SYSUI_MAX_SUP][SYSUI_NAME_MAX];
int n = scan_conf_names(g_dir, names, SYSUI_MAX_SUP);
for (int i = 0; i < g_nsup; i++) g_sups[i].in_config = 0;
for (int i = 0; i < n; i++) {
char path[512];
if (snprintf(path, sizeof path, "%s/%s.conf", g_dir, names[i]) >= (int)sizeof path) continue;
int idx = find_sup(names[i]);
if (idx < 0) {
if (g_nsup >= SYSUI_MAX_SUP) { su_log("supervise table full, drop %s", names[i]); continue; }
idx = g_nsup++;
}
sup_t tmp;
if (load_conf(path, names[i], &tmp) != 0) { continue; }
sup_t *s = &g_sups[idx];
int config_changed = (strcmp(s->exec, tmp.exec) != 0)
|| (s->argc != tmp.argc) || (s->envc != tmp.envc)
|| (s->enabled != tmp.enabled)
|| (strcmp(s->restart, tmp.restart) != 0)
|| (s->backoff_ms != tmp.backoff_ms) || (s->max_restart != tmp.max_restart);
if (!config_changed) {
for (int a = 0; a < tmp.argc; a++)
if (strcmp(s->argv[a + 1], tmp.argv[a + 1]) != 0) { config_changed = 1; break; }
for (int e2 = 0; e2 < tmp.envc; e2++)
if (strcmp(s->envbuf[e2], tmp.envbuf[e2]) != 0) { config_changed = 1; break; }
}
int keep_count = s->restart_count;
int keep_exit = s->last_exit_status;
int keep_has = s->has_exit;
unsigned long long keep_wall = s->last_exit_wall;
pid_t keep_pid = s->pid;
int keep_stop = s->stop_requested;
int keep_rp = s->restart_pending;
int keep_user = s->user_stopped;
int keep_state = s->state;
*s = tmp;
s->restart_count = keep_count;
s->last_exit_status = keep_exit;
s->has_exit = keep_has;
s->last_exit_wall = keep_wall;
s->pid = keep_pid;
s->stop_requested = keep_stop;
s->restart_pending = keep_rp;
s->user_stopped = keep_user;
s->in_config = 1;
s->restart_at_ms = 0;
if (keep_pid > 0) {
if (config_changed) {
su_log("supervised %s: config changed, restart", s->name);
stop_sup(s, 1, 0);
} else {
s->state = (keep_state == SU_STOPPING) ? SU_STOPPING : SU_RUNNING;
}
} else {
s->state = SU_STOPPED;
}
}
/* 已移除配置的条目,倒序删(先停运行中的) */
for (int i = g_nsup - 1; i >= 0; i--) {
if (g_sups[i].in_config) continue;
if (g_sups[i].pid > 0) stop_sup(&g_sups[i], 0, 0);
su_log("supervised %s: removed (no config)", g_sups[i].name);
remove_sup_idx(i);
}
/* 禁用:运行中 → 停;未运行 → DISABLED */
for (int i = 0; i < g_nsup; i++) {
sup_t *s = &g_sups[i];
if (!s->in_config) continue;
if (!s->enabled) {
if (s->pid > 0) { su_log("supervised %s: disabled -> stop", s->name); stop_sup(s, 0, 0); }
else s->state = SU_DISABLED;
}
}
/* 曾 FAILED 且现启用 → 重置,交 tick 重试 */
for (int i = 0; i < g_nsup; i++) {
sup_t *s = &g_sups[i];
if (s->in_config && s->enabled && s->state == SU_FAILED) {
s->restart_count = 0;
s->state = SU_STOPPED;
s->user_stopped = 0;
}
}
build_status_file();
}
static void tick_evaluate(void)
{
for (int i = 0; i < g_nsup; i++) {
sup_t *s = &g_sups[i];
if (s->state == SU_STOPPING) {
if (now_ms() - s->stop_since_ms > SYSUI_STOP_GRACE_MS) {
if (s->pid > 0) { kill(s->pid, SIGKILL); s->pid = 0; }
s->state = SU_STOPPED;
su_log("supervised %s: SIGKILL grace timeout", s->name);
}
continue;
}
if (s->pid > 0) {
/* kill(pid,0) 存活探测:子进程已死但尚未收尸(或异常)→ 视同死亡 */
if (!s->stop_requested && !s->restart_pending && kill(s->pid, 0) != 0 && errno == ESRCH) {
su_log("supervised %s: probe pid=%d dead (ESRCH)", s->name, (int)s->pid);
apply_exit(s, 1 << 8); /* 视为非零退出(fail)走重建策略 */
} else if (s->state == SU_RUNNING &&
now_ms() - s->run_since_ms > SYSUI_STABLE_MS) {
s->restart_count = 0; /* 稳定运行 → 清零计数 */
}
continue;
}
if (s->state == SU_FAILED || s->state == SU_DISABLED) continue;
if (s->restart_at_ms > 0) {
if (now_ms() >= s->restart_at_ms) {
s->restart_at_ms = 0;
su_log("supervised %s: backoff done, respawn", s->name);
spawn_sup(s);
}
} else if (s->enabled && !s->user_stopped) {
spawn_sup(s);
}
}
}
/* ---- 控制通道 ---- */
static void send_resp(int fd, const char *s)
{
ssize_t w = write(fd, s, strlen(s));
(void)w;
}
static int cmd_start(const char *name)
{
if (strcmp(name, "all") == 0) {
for (int i = 0; i < g_nsup; i++) {
g_sups[i].user_stopped = 0;
g_sups[i].restart_count = 0;
g_sups[i].state = SU_STOPPED;
spawn_sup(&g_sups[i]);
}
return 0;
}
int idx = find_sup(name);
if (idx < 0) return -1;
sup_t *s = &g_sups[idx];
s->user_stopped = 0;
if (s->pid <= 0) { s->restart_count = 0; s->state = SU_STOPPED; spawn_sup(s); }
return 0;
}
static int cmd_stop(const char *name)
{
if (strcmp(name, "all") == 0) {
for (int i = 0; i < g_nsup; i++) stop_sup(&g_sups[i], 0, 1);
return 0;
}
int idx = find_sup(name);
if (idx < 0) return -1;
stop_sup(&g_sups[idx], 0, 1);
return 0;
}
static int cmd_restart(const char *name)
{
if (strcmp(name, "all") == 0) {
for (int i = 0; i < g_nsup; i++) stop_sup(&g_sups[i], 1, 0);
return 0;
}
int idx = find_sup(name);
if (idx < 0) return -1;
sup_t *s = &g_sups[idx];
s->user_stopped = 0;
if (s->pid <= 0) { s->restart_count = 0; s->state = SU_STOPPED; spawn_sup(s); }
else stop_sup(s, 1, 0);
return 0;
}
/* ---- status 回显(详细 + ANSI 配色;非 TTY 由客户端去色) ---- */
static void put_field(char *b, size_t cap, size_t *o, const char *color, const char *plain, int width)
{
size_t pl = strlen(plain);
int w;
if (color && color[0])
w = snprintf(b + *o, cap - *o, "%s%s" AN_RESET, color, plain);
else
w = snprintf(b + *o, cap - *o, "%s", plain);
if (w > 0) *o += (size_t)w;
if (*o >= cap) *o = cap - 1;
if (width > 0) {
int pad = width - (int)pl;
while (pad-- > 0 && *o < cap - 1) b[(*o)++] = ' ';
}
b[*o] = '\0';
}
static void put_str(char *b, size_t cap, size_t *o, const char *s)
{
int w = snprintf(b + *o, cap - *o, "%s", s);
if (w > 0) *o += (size_t)w;
if (*o >= cap) *o = cap - 1;
b[*o] = '\0';
}
static const char *state_color(int st)
{
switch (st) {
case SU_RUNNING: return AN_GREEN;
case SU_FAILED: return AN_RED;
case SU_STOPPED: return AN_YELLOW;
case SU_STOPPING: return AN_YELLOW;
case SU_DISABLED: return AN_GREY;
default: return "";
}
}
static void fmt_exit_str(const sup_t *s, char *out, size_t cap)
{
if (!s->has_exit) { snprintf(out, cap, "-"); return; }
int st = s->last_exit_status;
if (WIFEXITED(st)) snprintf(out, cap, "%d", WEXITSTATUS(st));
else if (WIFSIGNALED(st)) snprintf(out, cap, "sig%d", WTERMSIG(st));
else snprintf(out, cap, "-");
}
static void cmd_status(int fd, const char *name)
{
int idx = -1;
if (name && strcmp(name, "all") != 0) {
idx = find_sup(name);
if (idx < 0) { send_resp(fd, "ERR no-such-supervised\n"); return; }
}
int shown = (idx >= 0) ? 1 : g_nsup;
int nw = 4;
for (int i = 0; i < g_nsup; i++) {
if (idx >= 0 && i != idx) continue;
int l = (int)strlen(g_sups[i].name);
if (l > nw) nw = l;
}
if (nw > 40) nw = 40;
const int sw = 8, pw = 7, rw = 7, bw = 9, ew = 6;
size_t cap = (size_t)(shown + 3) * 512 + 1024;
char *b = malloc(cap);
if (!b) { send_resp(fd, "ERR out-of-memory\n"); return; }
size_t o = 0;
put_field(b, cap, &o, AN_BOLD AN_CYAN, "meow_sysui status", -1);
put_str(b, cap, &o, "\n");
char hdr[64];
snprintf(hdr, sizeof hdr, "supervised=%d\n", g_nsup);
put_str(b, cap, &o, hdr);
put_field(b, cap, &o, AN_DIM, "NAME", nw); put_str(b, cap, &o, " ");
put_field(b, cap, &o, AN_DIM, "STATE", sw); put_str(b, cap, &o, " ");
put_field(b, cap, &o, AN_DIM, "PID", pw); put_str(b, cap, &o, " ");
put_field(b, cap, &o, AN_DIM, "RESTART", rw); put_str(b, cap, &o, " ");
put_field(b, cap, &o, AN_DIM, "BACKOFF", bw); put_str(b, cap, &o, " ");
put_field(b, cap, &o, AN_DIM, "EXIT", ew); put_str(b, cap, &o, " ");
put_field(b, cap, &o, AN_DIM, "ENABLED", 8); put_str(b, cap, &o, " ");
put_field(b, cap, &o, AN_DIM, "EXEC", -1);
put_str(b, cap, &o, "\n");
int total = nw + 1 + sw + 1 + pw + 1 + rw + 1 + bw + 1 + ew + 1 + 8 + 1 + 4;
put_str(b, cap, &o, AN_DIM);
for (int x = 0; x < total && o < cap - 1; x++) b[o++] = '-';
put_str(b, cap, &o, AN_RESET "\n");
for (int i = 0; i < g_nsup; i++) {
if (idx >= 0 && i != idx) continue;
sup_t *s = &g_sups[i];
char pidstr[16], rstr[16], bstr[16], estr[16];
snprintf(pidstr, sizeof pidstr, "%d", (int)s->pid);
if (s->max_restart < 0) snprintf(rstr, sizeof rstr, "%d/inf", s->restart_count);
else snprintf(rstr, sizeof rstr, "%d/%d", s->restart_count, s->max_restart);
snprintf(bstr, sizeof bstr, "%ldms", s->backoff_ms);
fmt_exit_str(s, estr, sizeof estr);
put_field(b, cap, &o, AN_BOLD AN_CYAN, s->name, nw); put_str(b, cap, &o, " ");
put_field(b, cap, &o, state_color(s->state), g_state_str[s->state], sw); put_str(b, cap, &o, " ");
put_field(b, cap, &o, AN_YELLOW, pidstr, pw); put_str(b, cap, &o, " ");
put_field(b, cap, &o, AN_YELLOW, rstr, rw); put_str(b, cap, &o, " ");
put_field(b, cap, &o, AN_YELLOW, bstr, bw); put_str(b, cap, &o, " ");
put_field(b, cap, &o, "", estr, ew); put_str(b, cap, &o, " ");
put_field(b, cap, &o, (s->enabled ? AN_GREEN : AN_GREY), (s->enabled ? "yes" : "no"), 8);
put_str(b, cap, &o, " ");
put_field(b, cap, &o, "", s->exec, -1);
put_str(b, cap, &o, "\n");
}
size_t sent = 0;
while (sent < o) {
ssize_t w = write(fd, b + sent, o - sent);
if (w <= 0) break;
sent += (size_t)w;
}
free(b);
}
static void handle_connection(int fd)
{
char buf[SYSUI_LINE_MAX];
ssize_t n = read(fd, buf, sizeof buf - 1);
if (n <= 0) { send_resp(fd, "ERR empty\n"); return; }
buf[n] = '\0';
char *line = trim(buf);
if (!line[0]) { send_resp(fd, "ERR empty\n"); return; }
char *sp = strchr(line, ' ');
if (sp) *sp++ = '\0';
if (strcmp(line, "pid") == 0) {
char r[64]; snprintf(r, sizeof r, "%d\n", (int)getpid()); send_resp(fd, r);
} else if (strcmp(line, "status") == 0) {
cmd_status(fd, sp);
} else if (strcmp(line, "reload") == 0) {
scan_and_reconcile();
send_resp(fd, "OK\n");
} else if (sp && strcmp(line, "start") == 0) {
send_resp(fd, cmd_start(sp) == 0 ? "OK\n" : "ERR no-such-supervised\n");
} else if (sp && strcmp(line, "stop") == 0) {
send_resp(fd, cmd_stop(sp) == 0 ? "OK\n" : "ERR no-such-supervised\n");
} else if (sp && strcmp(line, "restart") == 0) {
send_resp(fd, cmd_restart(sp) == 0 ? "OK\n" : "ERR no-such-supervised\n");
} else {
send_resp(fd, "ERR unknown-command\n");
}
}
/* ---- Daemon 生命周期 ---- */
static int setup_socket(void)
{
unlink(g_sock);
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) { su_log("socket: %s", strerror(errno)); return -1; }
set_cloexec(fd);
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) {
su_log("bind %s: %s", g_sock, strerror(errno));
close(fd); return -1;
}
if (listen(fd, 4) != 0) { su_log("listen: %s", strerror(errno)); close(fd); return -1; }
g_listen_fd = fd;
return 0;
}
static int setup_signals(void)
{
if (pipe(g_sigpipe) != 0) { su_log("pipe: %s", strerror(errno)); 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);
set_cloexec(g_sigpipe[0]);
set_cloexec(g_sigpipe[1]);
struct sigaction sa;
memset(&sa, 0, sizeof sa);
sa.sa_handler = sig_child;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGCHLD, &sa, NULL);
struct sigaction st;
memset(&st, 0, sizeof st);
st.sa_handler = sig_term;
sigemptyset(&st.sa_mask);
st.sa_flags = 0;
sigaction(SIGTERM, &st, NULL);
sigaction(SIGINT, &st, NULL);
signal(SIGPIPE, SIG_IGN);
return 0;
}
static void daemonize(void)
{
pid_t pid = fork();
if (pid < 0) { su_log("fork: %s", strerror(errno)); }
if (pid > 0) _exit(0);
if (setsid() < 0) { /* ignore */ }
pid = fork();
if (pid < 0) { su_log("fork2: %s", strerror(errno)); }
if (pid > 0) _exit(0);
if (chdir("/") != 0) { /* ignore */ }
int nul = open("/dev/null", O_RDWR);
if (nul >= 0) { dup2(nul, 0); dup2(nul, 1); if (nul > 2) close(nul); }
/* 保留 stderr=2(g_log 仍会写文件;stderr 落到 /dev/null 由调用方决定) */
}
static void write_pidfile(void)
{
char buf[32];
int l = snprintf(buf, sizeof buf, "%d\n", (int)getpid());
(void)write_file(g_pidfile, buf, (size_t)l);
}
static void shutdown_all(void)
{
for (int i = 0; i < g_nsup; i++) {
if (g_sups[i].pid > 0) {
kill(g_sups[i].pid, SIGTERM);
unsigned long long wait_until = now_ms() + 1500;
while (g_sups[i].pid > 0 && now_ms() < wait_until) {
int st; pid_t p = waitpid(g_sups[i].pid, &st, WNOHANG);
if (p == g_sups[i].pid) { g_sups[i].pid = 0; }
else usleep(50000);
}
if (g_sups[i].pid > 0) { kill(g_sups[i].pid, SIGKILL); g_sups[i].pid = 0; }
}
}
build_status_file();
if (g_listen_fd >= 0) close(g_listen_fd);
if (g_status_fd >= 0) close(g_status_fd);
unlink(g_sock);
unlink(g_pidfile);
}
static int start_daemon(void)
{
if (setup_signals() != 0) return 1;
/* 子进程回收者:被监管进程若派生孙进程,在直接子进程退出后由 sysui 收尸,强化「孤儿/僵死隔离」。 */
if (prctl(PR_SET_CHILD_SUBREAPER, 1) != 0)
su_log("PR_SET_CHILD_SUBREAPER failed: %s (continue)", strerror(errno));
if (setup_socket() != 0) return 1;
write_pidfile();
g_status_fd = open(g_status, O_RDWR | O_CREAT, 0644);
if (g_status_fd < 0) su_log("status file open fail: %s", strerror(errno));
else set_cloexec(g_status_fd);
scan_and_reconcile();
su_log("meow_sysui up: dir=%s sock=%s pid=%d supervised=%d",
g_dir, g_sock, (int)getpid(), g_nsup);
struct pollfd pfds[2];
nfds_t nfds = 2;
unsigned long long last_tick = 0;
while (!g_shutdown) {
unsigned long long soon = 0;
for (int i = 0; i < g_nsup; i++) {
if (g_sups[i].restart_at_ms > 0) {
unsigned long long d = g_sups[i].restart_at_ms > now_ms()
? g_sups[i].restart_at_ms - now_ms() : 0;
if (soon == 0 || d < soon) soon = d;
}
}
int timeout = (soon > 0 && soon < (unsigned long long)SYSUI_TICK_MAX_MS)
? (int)soon : SYSUI_TICK_MAX_MS;
if (timeout < 20) timeout = 20;
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;
int r = poll(pfds, nfds, timeout);
if (r < 0) {
if (errno == EINTR) continue;
su_log("poll: %s", strerror(errno));
break;
}
if (g_shutdown) break;
if (pfds[0].revents & POLLIN) {
int cfd = accept(g_listen_fd, NULL, NULL);
if (cfd >= 0) {
set_cloexec(cfd);
handle_connection(cfd);
close(cfd);
}
}
if (pfds[1].revents & POLLIN) reap_children();
if (now_ms() - last_tick >= (unsigned long long)SYSUI_TICK_MAX_MS) {
last_tick = now_ms();
/* 仅做监护评估(tick_evaluate),不在此处定期 scan:scan_and_reconcile 会把
* FAILED(超 max_restart) 重置回 STOPPED,若每 tick 调会导致「无限次重试」,
* 破坏「有限次兜底」。配置扫描只在 启动 + reload 命令 执行(同 svcd)。 */
}
tick_evaluate();
build_status_file();
}
su_log("meow_sysui shutdown");
shutdown_all();
return 0;
}
/* ---- CLI ---- */
static char *strip_ansi(char *s)
{
char *r = s, *w = s;
while (*r) {
if (*r == '\033' && r[1] == '[') {
r += 2;
while (*r && !(*r >= '@' && *r <= '~')) r++;
if (*r) r++;
} else {
*w++ = *r++;
}
}
*w = '\0';
return s;
}
static int run_client(const char *cmdline)
{
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) { fprintf(stderr, "[meow_sysui] socket: %s\n", strerror(errno)); return 2; }
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 (connect(fd, (struct sockaddr *)&a, sizeof a) != 0) {
fprintf(stderr, "[meow_sysui] daemon not running (%s): %s\n", g_sock, strerror(errno));
close(fd);
char *buf = malloc(8192);
if (buf && read_file(g_status, buf, 8192) > 0) { fputs(buf, stdout); free(buf); }
else if (buf) { free(buf); printf("supervised=0\n"); }
return 1;
}
ssize_t w = write(fd, cmdline, strlen(cmdline));
(void)w;
shutdown(fd, SHUT_WR);
size_t cap = 65536, len = 0;
char *rbuf = malloc(cap);
if (!rbuf) { close(fd); return 2; }
ssize_t n;
while ((n = read(fd, rbuf + len, cap - len - 1)) > 0) {
len += (size_t)n;
if (len >= cap - 1) break;
}
close(fd);
rbuf[len] = '\0';
if (!isatty(STDOUT_FILENO)) strip_ansi(rbuf);
fputs(rbuf, stdout);
free(rbuf);
return 0;
}
static void usage(const char *argv0)
{
fprintf(stderr,
"usage: %s [options] [command]\n"
" commands: start <name|all> | stop <name|all> | restart <name|all>\n"
" | status [name|all] | reload | pid\n"
" no command -> start daemon (default; -f/--foreground keeps fg for init)\n"
" options:\n"
" -f, --foreground stay in foreground\n"
" --dir <path> supervised config dir (default /usr/data/meow/sysui.d)\n"
" --sock <path> control socket (default /tmp/meow_sysui.sock)\n"
" --status <path> status file (default /tmp/meow_sysui.status)\n"
" --pidfile <path> pid file (default /tmp/meow_sysui.pid)\n"
" --log <path> also append log to <path> (default /tmp/meow_sysui.log)\n",
argv0);
}
int main(int argc, char **argv)
{
const char *cmd_tok[16];
int cmd_n = 0;
for (int i = 1; i < argc; i++) {
const char *a = argv[i];
if (strcmp(a, "-f") == 0 || strcmp(a, "--foreground") == 0) g_fg = 1;
else if (strcmp(a, "--dir") == 0 && i + 1 < argc) safe_strcpy(g_dir, sizeof g_dir, argv[++i]);
else if (strcmp(a, "--sock") == 0 && i + 1 < argc) safe_strcpy(g_sock, sizeof g_sock, argv[++i]);
else if (strcmp(a, "--status") == 0 && i + 1 < argc) safe_strcpy(g_status, sizeof g_status, argv[++i]);
else if (strcmp(a, "--pidfile") == 0 && i + 1 < argc) safe_strcpy(g_pidfile, sizeof g_pidfile, argv[++i]);
else if (strcmp(a, "--log") == 0 && i + 1 < argc) safe_strcpy(g_log, sizeof g_log, argv[++i]);
else if (a[0] == '-') { usage(argv[0]); return 2; }
else if (cmd_n < 16) cmd_tok[cmd_n++] = a;
}
setvbuf(stderr, NULL, _IONBF, 0);
if (cmd_n > 0) {
char buf[SYSUI_LINE_MAX];
buf[0] = '\0';
size_t off = 0;
for (int i = 0; i < cmd_n; i++) {
int k = snprintf(buf + off, sizeof buf - off, "%s%s", i ? " " : "", cmd_tok[i]);
if (k < 0 || (size_t)k >= sizeof buf - off) break;
off += (size_t)k;
}
return run_client(buf);
}
if (!g_fg) daemonize();
return start_daemon();
}