- 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
1106 lines
38 KiB
C
1106 lines
38 KiB
C
/* 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();
|
||
}
|