/* 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/.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 / 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* ---- 默认路径 / 常量 ---- */ #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 | stop | restart \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 supervised config dir (default /usr/data/meow/sysui.d)\n" " --sock control socket (default /tmp/meow_sysui.sock)\n" " --status status file (default /tmp/meow_sysui.status)\n" " --pidfile pid file (default /tmp/meow_sysui.pid)\n" " --log also append log to (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(); }