/* meow_svcd.c — MeowOS 服务管理器守护进程(类 systemd/systemctl 角色)。 * * 定位(用户定案,见 docs/MEOWOS_SPEC_REF §1.2 / DEV_NOTES): * - 独立于 supervisor(meow_bootmenu/launcher)的进程,但由 supervisor 托管 * fork/exec 启动; * - 每个「服务」也是一个独立子进程(fork+exec),由本进程 waitpid 收 SIGCHLD, * 实现严重崩溃隔离:服务崩绝不拖垮 svcd,也绝不连带 supervisor; * - svcd 本身极简:无 dlopen、无外部依赖,仅依赖 POSIX + Linux 基础接口, * 交叉编译为 mipsel 静态(zig-mipsel-cc),打进固件(不入卡)。 * * 配置来源(双源,目录式;板载优先): * - 板载 `/usr/data/meow/services.d/.conf`(Data 区,恒定存在,权威源); * - TF 卡 `$CARD/MeowOS/services.d/.conf`(默认 `CARD=/usr/data/mnt/sd_0`)。 * 扫描**两处都读**;同名服务**板载优先**(扫 TF 时若 name 已在板载 → 跳过 + WARN * 「 板载与 TF 都存在,采用板载」)。无双源冲突时行为不变。 * 卡拔出 → svcd 优雅停止所托管**卡上**服务并保留运行状态(板载服务视 trigger 保留); * 插卡 → 自动重建。 * * .conf 字段(极简 key = value,'#'/';' 注释,见 meow_svcd.md 文档): * name / exec / args / env / enabled / restart / backoff_ms / max_restart / * cap / trigger。字段含义与状态机见文件尾部注释块 + meow_svcd.md。 * * 事件触发(低成本轮询,避免复杂订阅): * - 卡挂载:轮询 `$CARD/MeowOS/services.d` 存在性(每 SVCD_SCAN_MS 扫一次); * - WiFi 状态:可选,读 /sys/class/net//operstate 判定(wifi-up 触发用, * 本平台无 WiFi 接口时恒为 0,属扩展点); * - boot:配置在卡 → 随卡存在即启动(拔卡即停)。 * * 控制通道:AF_UNIX 单行文本协议(默认 /tmp/meow_svcd.sock): * start|stop|restart / status [name|all] / reload / pid * 后台常驻模式默认(无子命令即启动守护进程);`-f/--foreground` 供 supervisor * fork/exec 托管(保证 supervisor 可 waitpid)。 * * status 回显(详细 + ANSI 配色): * 每服务列出 状态/pid/重启次数+max_restart/backoff_ms/trigger/来源(板载|TF)/ * 最近退出码/退出时间/exec 路径;标题+列名+对齐;关键字段着色(RUNNING=绿、 * FAILED=红、STOPPED·STOPPING=黄、DISABLED=灰、服务名=青、数字=黄、表头=dim)。 * ANSI 色由守护进程统一产出,客户端在 stdout 非 TTY(重定向/SSH 管道)时去色。 * * 状态文件:`/tmp/meow_svcd.status`(可 --status 覆盖),状态变更+周期落盘, * 板载 tmpfs 存活期内可读。 * * 日志:svcd 自身日志走 stderr(便于 supervisor 捕获);可选 MEOW_SVCD_LOG 追加 * 文件。服务子进程继承 svcd 的 stdio(其 stdout/stderr 落到同一 sink),无需 * dlopen/管道,天然规避服务输出撑爆 svcd 内存导致的崩溃。 * * 编译(mipsel 静态,见 build_svcd.sh): * ~/Project/R3ProII/.tools/zig-mipsel-cc -Os -Wall -Wextra -static meow_svcd.c -o meow_svcd * 宿主编译(集成测试):gcc -Os -Wall -Wextra meow_svcd.c -o meow_svcd_host */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* ---- 默认路径 / 常量 ---- */ #define SVCD_CARD_DEFAULT "/usr/data/mnt/sd_0" /* TF 卡挂载点(SPEC §1.2) */ #define SVCD_SERVICES_REL "MeowOS/services.d" /* TF 配置目录(用户定案目录式) */ #define SVCD_SERVICES_BOARD "/usr/data/meow/services.d" /* 板载配置目录(首启预置/Data 区) */ #define SVCD_PATH_MAX 256 /* 服务源目录路径缓冲上限 */ #define SVCD_SOCK_DEFAULT "/tmp/meow_svcd.sock" #define SVCD_STATUS_DEFAULT "/tmp/meow_svcd.status" #define SVCD_PIDFILE_DEFAULT "/tmp/meow_svcd.pid" #define SVCD_MAX_SERVICES 64 #define SVCD_NAME_MAX 64 #define SVCD_EXEC_MAX 256 #define SVCD_LINE_MAX 1024 /* .conf / 命令单行上限 */ #define SVCD_ARG_MAX 128 /* 单参数串上限 */ #define SVCD_MAX_ARGS 24 /* args 个数上限 */ #define SVCD_ENV_MAX 24 /* env 条数上限 */ #define SVCD_ENV_MAXLEN 128 /* 单条 env "K=V" 上限 */ #define SVCD_CAP_MAX 128 #define SVCD_RESTART_STR_MAX 16 #define SVCD_TRIG_STR_MAX 16 #define SVCD_SCAN_MS 1000 /* 卡/wifi 轮询间隔 */ #define SVCD_TICK_MAX_MS 500 /* poll 上限(卡变化 ~1s 内感知) */ #define SVCD_STOP_GRACE_MS 2500 /* SIGTERM 后 SIGKILL 兜底 */ #define SVCD_STABLE_MS 30000 /* 连续运行此毫秒 → 重置 restart_count */ #define SVCD_SOCK_PATH_MAX 108 #define SVCD_SIGCHLD_CHAR 'C' /* 自管道字节(拉起 poll) */ #define SVCD_SIGTERM_CHAR 'T' #define SVCD_CRASH_SOCK "/tmp/meow_crash.sock" /* meow_crash 崩溃上报 socket(best-effort) */ #define SVCD_CRASH_POLL_MS 150 /* 上报 connect 的排空等待上限(绝不重试) */ /* ---- 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 { SV_DISABLED = 0, /* enabled=0,永不自动启动 */ SV_STOPPED, /* 未运行(因触发不满足/被停/等待退避) */ SV_RUNNING, /* 子进程存活 */ SV_STOPPING, /* 已发 SIGTERM,等退出(超时 SIGKILL 兜底) */ SV_FAILED /* 崩溃且不再重启(超 max_restart/restart=never) */ } sv_state_t; static const char *g_state_str[] = { "disabled", "stopped", "running", "stopping", "failed" }; typedef struct { char name[SVCD_NAME_MAX]; char exec[SVCD_EXEC_MAX]; char argbuf[SVCD_MAX_ARGS][SVCD_ARG_MAX]; /* args 拷贝 */ char *argv[SVCD_MAX_ARGS + 1]; /* exec + args,末尾 NULL */ int argc; char envbuf[SVCD_ENV_MAX][SVCD_ENV_MAXLEN]; /* "K=V" */ int envc; int enabled; char restart[SVCD_RESTART_STR_MAX]; /* always|on-fail|never */ long backoff_ms; int max_restart; /* <0=无限 */ char cap[SVCD_CAP_MAX]; /* 解析保留;实装为后续任务 */ char trigger[SVCD_TRIG_STR_MAX]; /* boot|card-mount|wifi-up */ int src; /* 0=TF 卡, 1=板载(同名优先源) */ /* 运行状态 */ pid_t pid; sv_state_t state; int restart_count; int last_exit_status; /* waitpid status */ int has_exit; /* 1=曾退出(last_exit_* 有效) */ unsigned long long last_exit_wall; /* 上次退出墙钟秒(0=未知;仅供 status 展示) */ 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; /* 本次扫描仍存在 */ } svc_t; static svc_t g_svcs[SVCD_MAX_SERVICES]; static int g_nsvc = 0; /* 全局环境(可被 --card/--sock/--status 覆盖,亦读 env) */ static char g_card[256] = SVCD_CARD_DEFAULT; static char g_board_dir[256] = SVCD_SERVICES_BOARD; /* 板载服务目录(--board/MEOW_SVCD_BOARD 可覆写) */ static char g_sock[SVCD_SOCK_PATH_MAX] = SVCD_SOCK_DEFAULT; static char g_status[256] = SVCD_STATUS_DEFAULT; static char g_pidfile[256] = SVCD_PIDFILE_DEFAULT; static char g_log[256] = ""; /* 可选日志文件 */ static int g_card_present = 0; static int g_board_online = 0; /* 板载服务目录本帧可读 */ static int g_wifi_up = 0; static int g_shutdown = 0; static int g_fg = 0; /* -f:前台(supervisor 托管) */ static int g_sigpipe[2] = {-1, -1}; /* 信号自管道 */ static int g_listen_fd = -1; /* 控制 socket */ static int g_status_fd = -1; /* 状态文件 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:svcd 自身日志走 stderr(supervisor 捕获),可选追加文件 ---- */ static void svcd_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_svcd] %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); } } } /* ---- 简单工具 ---- */ 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; } /* 让 fd 在 exec 后自动关闭:服务子进程 fork+exec 后绝不继承守护进程的 * 监听 socket / 已接受连接 / 自管道 / 状态文件 fd —— 否则子进程一直持有 * 客户端连接副本,客户端读不到 EOF 而阻塞;监听 socket 也被子进程占着。 */ 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 long 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; } /* ---- 配置解析 ---- */ /* 极简 tokenizer:按空白切分,支持单/双引号与反斜杠转义。int。 */ static int split_args(char *in, char tok[][SVCD_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 < SVCD_ARG_MAX - 1) out[oi++] = c; p++; } out[oi] = '\0'; if (oi > 0) n++; } return n; } static void apply_field(svc_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, SVCD_MAX_ARGS); } else if (strcmp(k, "env") == 0) { if (s->envc < SVCD_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, "cap") == 0) { safe_strcpy(s->cap, sizeof s->cap, v); } else if (strcmp(k, "trigger") == 0) { safe_strcpy(s->trigger, sizeof s->trigger, v); } else if (strcmp(k, "name") == 0) { /* 名字以文件名为准,name 仅备忘 */ } /* 未知键忽略(前向兼容) */ } /* 重建 s->argv[]:argv[0]=exec、argv[i+1]=argbuf[i]、末尾 NULL。 * s->argv 是指针数组;load_conf 里它指向 s 自身的 exec/argbuf(栈局部 tmp 亦成立), * 但 scan_and_reconcile 用 *s=tmp 整体拷贝时只复制指针值,仍指向已销毁的 tmp 栈帧。 * 故赋值后必须重定位到 s 自己的 exec/argbuf,否则 execv 传出的 argv[0]/cmdline 读到脏值。 */ static void svc_rebuild_argv(svc_t *s) { 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; } /* 解析单个 .conf 到 svc(name=basename)。成功返回 0。 */ static int load_conf(const char *path, const char *name, svc_t *s) { char buf[SVCD_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; /* 默认禁用(显式 enabled=1 才启动) */ s->state = SV_STOPPED; safe_strcpy(s->restart, sizeof s->restart, "on-fail"); /* 默认策略 */ s->backoff_ms = 1000; s->max_restart = 5; safe_strcpy(s->trigger, sizeof s->trigger, "card-mount"); /* 默认触发 */ 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]) { svcd_log("service %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"); if (strcmp(s->trigger, "boot") && strcmp(s->trigger, "card-mount") && strcmp(s->trigger, "wifi-up")) safe_strcpy(s->trigger, sizeof s->trigger, "card-mount"); svc_rebuild_argv(s); return 0; } /* ---- 服务表工具 ---- */ static int find_svc(const char *name) { for (int i = 0; i < g_nsvc; i++) if (strcmp(g_svcs[i].name, name) == 0) return i; return -1; } static void remove_svc_idx(int idx) { if (idx < 0 || idx >= g_nsvc) return; int moved = g_nsvc - idx - 1; if (moved > 0) { memmove(&g_svcs[idx], &g_svcs[idx + 1], (size_t)moved * sizeof(svc_t)); /* memmove 整块字节拷贝 svc_t 后,被左移槽位的 argv[] 指针仍指向移动前 * (g_svcs[i+1]) 的 exec/argbuf 成员地址 —— 同 *s=tmp 的悬垂,需重定位到 * 新槽位自身的成员(svc_rebuild_argv)。否则删除一个服务后,其后被左移的 * 槽若在同 tick 内被拉起,execv 会用错位的 argv → cmdline 错乱。 */ for (int i = idx; i < idx + moved; i++) svc_rebuild_argv(&g_svcs[i]); } g_nsvc--; } static void build_status_file(void) { char buf[8192]; int n = snprintf(buf, sizeof buf, "# meow_svcd status\ncard_present=%d\nwifi_up=%d\nservices=%d\n", g_card_present, g_wifi_up, g_nsvc); for (int i = 0; i < g_nsvc && n < (int)sizeof buf - 1; i++) { svc_t *s = &g_svcs[i]; int k = snprintf(buf + n, sizeof buf - (size_t)n, "svc=%s state=%s pid=%d restart=%d exit=%d trigger=%s backoff_ms=%ld\n", s->name, g_state_str[s->state], (int)s->pid, s->restart_count, s->last_exit_status, s->trigger, s->backoff_ms); 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); } } /* ---- 子进程管理 ---- */ /* 实际拉起子进程(fork+exec)。调用方负责 restart_count。返回 0=成功。 */ static int spawn_service(svc_t *s) { if (s->pid > 0) return 0; pid_t pid = fork(); if (pid < 0) { svcd_log("service %s: fork failed: %s", s->name, strerror(errno)); s->state = SV_FAILED; return -1; } if (pid == 0) { /* 子进程:独立进程组,防父进程信号误伤;cap 实装属后续任务(见 meow_svcd.md), * 此处仅保留 s->cap 字符串,不应用(平台支持与否未定)。 */ (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_svcd] service %s execv %s: %s\n", s->name, s->exec, strerror(errno)); _exit(127); } s->pid = pid; s->state = SV_RUNNING; s->run_since_ms = now_ms(); s->stop_requested = 0; s->restart_pending = 0; s->restart_at_ms = 0; svcd_log("service %s start: pid=%d exec=%s", s->name, (int)pid, s->exec); return 0; } /* 停止一个运行/停止中服务。restart_after=1 → 停后立即重启(restart 命令)。 * user=1 → 用户 stop(置 user_stopped,触发自动拉起被抑制)。 */ static void stop_service(svc_t *s, int restart_after, int user) { if (user) s->user_stopped = 1; if (s->pid <= 0) { if (s->state == SV_RUNNING || s->state == SV_STOPPING) s->state = SV_STOPPED; if (restart_after) { s->restart_count = 0; s->user_stopped = 0; spawn_service(s); } return; } if (s->state == SV_STOPPING) { if (restart_after) s->restart_pending = 1; return; } s->stop_requested = 1; s->restart_pending = restart_after; s->state = SV_STOPPING; s->stop_since_ms = now_ms(); (void)kill(s->pid, SIGTERM); svcd_log("service %s: SIGTERM (pid=%d)", s->name, (int)s->pid); } /* 对单个已死亡子进程应用退出(由 reap 调用)。 */ static void apply_exit(svc_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) { /* restart 命令:停后原地重启,无视崩溃策略 */ s->restart_pending = 0; s->stop_requested = 0; s->restart_count = 0; s->state = SV_STOPPED; svcd_log("service %s: restart -> respawn", s->name); spawn_service(s); return; } if (s->stop_requested) { s->stop_requested = 0; s->state = SV_STOPPED; svcd_log("service %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 = SV_FAILED; svcd_log("service %s: exceeded max_restart(%d) -> FAILED (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 = SV_STOPPED; svcd_log("service %s: restart in %ldms (attempt %d, exit=%s)", s->name, wait, s->restart_count, failed ? "fail" : "ok"); } } else { s->state = failed ? SV_FAILED : SV_STOPPED; svcd_log("service %s: exit (policy=%s, %s) -> %s", s->name, s->restart, failed ? "fail" : "ok", failed ? "FAILED" : "STOPPED"); } } /* SIGCHLD 处理器:写自管道唤醒 poll(async-signal-safe)。 */ static void sig_child(int sig) { (void)sig; char c = SVCD_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 = SVCD_SIGTERM_CHAR; ssize_t w = write(g_sigpipe[1], &c, 1); (void)w; } /* /proc/uptime 首字段 → 秒(整数)。失败返回 0。 */ static long svcd_uptime(void) { int fd = open("/proc/uptime", O_RDONLY); if (fd < 0) return 0; char b[64]; ssize_t n = read(fd, b, sizeof b - 1); close(fd); if (n <= 0) return 0; b[n] = '\0'; return (long)atof(b); } /* 服务异常退出 → 上报 meow_crash(best-effort,绝不阻塞/重试 svcd)。 * * 铁律:服务崩不崩 svcd。上报全程非阻塞: * - 客户端 socket 置 O_NONBLOCK; * - connect 失败即放弃;若 EINPROGRESS/EWOULDBLOCK,短 poll(POLLOUT) ≤150ms 排空 * 本地 listen backlog,仍不可写即丢弃; * - 单行小消息一次 write(失败即弃),绝不重试。 * meow_crash 不在线(connect ECONNREFUSED 等)→ 静默丢,不影响 svcd 任何状态。 */ static void notify_crash(const char *name, int status) { const char *kind; int num; if (WIFSIGNALED(status)) { kind = "sig"; num = WTERMSIG(status); } else { kind = "exit"; num = WIFEXITED(status) ? WEXITSTATUS(status) : 1; } char msg[256]; snprintf(msg, sizeof msg, "SVC %s %s %d %ld %ld\n", name, kind, num, (long)time(NULL), svcd_uptime()); int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) return; int fl = fcntl(fd, F_GETFL, 0); if (fl >= 0) fcntl(fd, F_SETFL, fl | O_NONBLOCK); struct sockaddr_un a; memset(&a, 0, sizeof a); a.sun_family = AF_UNIX; safe_strcpy(a.sun_path, sizeof a.sun_path, SVCD_CRASH_SOCK); if (connect(fd, (struct sockaddr *)&a, sizeof a) != 0) { if (errno == EINPROGRESS || errno == EWOULDBLOCK) { struct pollfd pf; pf.fd = fd; pf.events = POLLOUT; pf.revents = 0; int r = poll(&pf, 1, SVCD_CRASH_POLL_MS); if (r <= 0 || !(pf.revents & POLLOUT)) { close(fd); return; } } else { close(fd); return; } } ssize_t w = write(fd, msg, strlen(msg)); (void)w; close(fd); } /* 排空 SIGCHLD 自管道,waitpid 匹配所有已退出子进程。 */ 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_nsvc; i++) if (g_svcs[i].pid == pid) { idx = i; break; } if (idx < 0) { svcd_log("reap unknown pid=%d", (int)pid); continue; } svc_t *s = &g_svcs[idx]; /* 服务异常退出(非 0 / 被信号杀)且非受控 stop/restart → 上报 meow_crash。 * 必须在 apply_exit 前判(apply_exit 会清 stop_requested/restart_pending)。 */ int failed = !(WIFEXITED(status) && WEXITSTATUS(status) == 0); if (failed && !s->stop_requested && !s->restart_pending) notify_crash(s->name, status); apply_exit(s, status); } build_status_file(); } /* ---- 触发判定 ---- */ static int trigger_satisfied(const svc_t *s) { switch (s->trigger[0]) { case 'c': /* card-mount */ return g_card_present; case 'w': /* wifi-up */ return g_card_present && g_wifi_up; case 'b': /* boot:板载服务恒定存在,触发不依赖卡;TF 服务仍需卡 */ default: return (s->src == 1) ? 1 : g_card_present; } } static int wifi_up(void) { DIR *d = opendir("/sys/class/net"); if (!d) return 0; int up = 0; struct dirent *e; while ((e = readdir(d)) != NULL) { if (e->d_name[0] == '.') continue; char wp[256], oper[256]; if (snprintf(wp, sizeof wp, "/sys/class/net/%s/wireless", e->d_name) >= (int)sizeof wp) continue; if (access(wp, F_OK) != 0) continue; /* 非无线接口 */ if (snprintf(oper, sizeof oper, "/sys/class/net/%s/operstate", e->d_name) >= (int)sizeof oper) continue; char buf[16]; if (read_file(oper, buf, sizeof buf) > 0 && strstr(buf, "up")) { up = 1; break; } } closedir(d); return up; } /* ---- 卡挂载检测 / 配置扫描 ---- */ static int card_present(void) { const char *ov = getenv("MEOW_SVCD_CARD_PRESENT"); if (ov && ov[0]) return ov[0] == '1'; struct stat st, pst; if (stat(g_card, &st) != 0 || !S_ISDIR(st.st_mode)) return 0; /* 配置子目录存在 → 有卡内容(设备+宿主 test 通用) */ char sp[512]; if (snprintf(sp, sizeof sp, "%s/%s", g_card, SVCD_SERVICES_REL) >= (int)sizeof sp) return 0; if (access(sp, F_OK) == 0) return 1; /* 真实挂载点:st_dev 与父目录不同(卡文件系统 vs rootfs) */ size_t plen = strlen(g_card); while (plen > 1 && g_card[plen - 1] == '/') g_card[--plen] = '\0'; size_t slash = 0; for (size_t i = plen; i > 0; i--) if (g_card[i - 1] == '/') { slash = i; break; } if (slash == 0) return 0; char parent[384]; if ((size_t)snprintf(parent, sizeof parent, "%.*s", (int)(slash - 1), g_card) >= sizeof parent) return 0; if (stat(parent, &pst) == 0 && pst.st_dev != st.st_dev) return 1; return 0; /* 同文件系统空目录 = 未挂载/未用 */ } static int board_services_exist(void) { struct stat st; if (stat(g_board_dir, &st) != 0 || !S_ISDIR(st.st_mode)) return 0; return 1; } static int name_in_list(const char *name, char list[][SVCD_NAME_MAX], int n) { for (int i = 0; i < n; i++) if (strcmp(list[i], name) == 0) return i; return -1; } /* 枚举某目录下的 *.conf,去除 .conf 得服务名。返回个数。 */ static int scan_conf_names(const char *dir, char out[][SVCD_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; /* 去掉 .conf 得服务名 */ if (nl >= SVCD_NAME_MAX) nl = SVCD_NAME_MAX - 1; memcpy(out[n], e->d_name, nl); out[n][nl] = '\0'; n++; } closedir(d); return n; } /* 双源目录调和:板载(恒定存在、权威优先)先扫,卡上有则叠加扫 TF, * 同名服务 TF 被板载屏蔽(WARN 记录)。无双源冲突时行为不变。 */ static void scan_and_reconcile(void) { char names[SVCD_MAX_SERVICES][SVCD_NAME_MAX]; char srcdir[SVCD_MAX_SERVICES][SVCD_PATH_MAX]; /* 每服务实际 .conf 所在目录 */ int n = 0; if (board_services_exist()) { int nb = scan_conf_names(g_board_dir, names, SVCD_MAX_SERVICES); for (int i = 0; i < nb; i++) safe_strcpy(srcdir[i], sizeof srcdir[0], g_board_dir); n = nb; } if (g_card_present) { char tfdir[SVCD_PATH_MAX]; if (snprintf(tfdir, sizeof tfdir, "%s/%s", g_card, SVCD_SERVICES_REL) < (int)sizeof tfdir) { char tfnames[SVCD_MAX_SERVICES][SVCD_NAME_MAX]; int nt = scan_conf_names(tfdir, tfnames, SVCD_MAX_SERVICES); for (int t = 0; t < nt; t++) { if (name_in_list(tfnames[t], names, n) >= 0) { svcd_log("%s 板载与 TF 都存在,采用板载", tfnames[t]); continue; } if (n >= SVCD_MAX_SERVICES) { svcd_log("service table full, drop %s", tfnames[t]); continue; } safe_strcpy(names[n], sizeof names[0], tfnames[t]); safe_strcpy(srcdir[n], sizeof srcdir[0], tfdir); n++; } } } for (int i = 0; i < g_nsvc; i++) g_svcs[i].in_config = 0; for (int i = 0; i < n; i++) { char path[512]; if (snprintf(path, sizeof path, "%s/%s.conf", srcdir[i], names[i]) >= (int)sizeof path) continue; int idx = find_svc(names[i]); if (idx < 0) { if (g_nsvc >= SVCD_MAX_SERVICES) { svcd_log("service table full, drop %s", names[i]); continue; } idx = g_nsvc++; } svc_t tmp; if (load_conf(path, names[i], &tmp) != 0) { continue; } tmp.src = (strcmp(srcdir[i], g_board_dir) == 0) ? 1 : 0; svc_t *s = &g_svcs[idx]; /* 判定是否需重启(结构字段变化) */ int config_changed = (strcmp(s->exec, tmp.exec) != 0) || (s->argc != tmp.argc) || (s->envc != tmp.envc) || (s->enabled != tmp.enabled); 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; } } /* 保留运行期字段,其余用 tmp 覆盖 */ 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; svc_rebuild_argv(s); /* 重定位 argv[] 到 s 自身成员,防悬垂指针 */ 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; /* 子进程仍存活:配置变 → 停后按新配置重启;不变 → 维持 RUNNING/STOPPING, * 绝不能被 tmp 的 STOPPED 覆盖成「停了却还有 pid」的泄漏态。 */ if (keep_pid > 0) { if (config_changed) { svcd_log("service %s: config changed, restart", s->name); stop_service(s, 1, 0); } else { s->state = (keep_state == SV_STOPPING) ? SV_STOPPING : SV_RUNNING; } } else { s->state = SV_STOPPED; } } /* 已移除配置的服务,倒序删(先停运行中的) */ for (int i = g_nsvc - 1; i >= 0; i--) { if (g_svcs[i].in_config) continue; if (g_svcs[i].pid > 0) stop_service(&g_svcs[i], 0, 0); svcd_log("service %s: removed (no config)", g_svcs[i].name); remove_svc_idx(i); } /* 禁用:运行中 → 停;未运行 → DISABLED */ for (int i = 0; i < g_nsvc; i++) { svc_t *s = &g_svcs[i]; if (!s->in_config) continue; if (!s->enabled) { if (s->pid > 0) { svcd_log("service %s: disabled -> stop", s->name); stop_service(s, 0, 0); } else s->state = SV_DISABLED; } } /* 曾 FAILED 且现启用 → 重置,交 tick 重试 */ for (int i = 0; i < g_nsvc; i++) { svc_t *s = &g_svcs[i]; if (s->in_config && s->enabled && s->state == SV_FAILED) { s->restart_count = 0; s->state = SV_STOPPED; s->user_stopped = 0; } } build_status_file(); } static void on_card_out(void) { for (int i = 0; i < g_nsvc; i++) { svc_t *s = &g_svcs[i]; s->restart_at_ms = 0; /* 拔卡后 trigger 仍满足的服务(板载-boot)不随卡停止(板载服务保留,MDNS 等常驻) */ if (s->pid > 0 && trigger_satisfied(s)) continue; if (s->pid > 0) { stop_service(s, 0, 0); } else if (s->state == SV_RUNNING || s->state == SV_STOPPING) s->state = SV_STOPPED; } svcd_log("card removed: stopping %d services", g_nsvc); build_status_file(); } static void tick_evaluate(void) { for (int i = 0; i < g_nsvc; i++) { svc_t *s = &g_svcs[i]; /* STOPPING 先判(含 SIGKILL 兜底),否则 pid>0 分支会吞掉超时逻辑 */ if (s->state == SV_STOPPING) { if (now_ms() - s->stop_since_ms > SVCD_STOP_GRACE_MS) { if (s->pid > 0) { kill(s->pid, SIGKILL); s->pid = 0; } s->state = SV_STOPPED; svcd_log("service %s: SIGKILL grace timeout", s->name); } continue; } if (s->pid > 0) { if (!s->enabled || !trigger_satisfied(s)) stop_service(s, 0, 0); if (s->pid > 0 && s->state == SV_RUNNING && now_ms() - s->run_since_ms > SVCD_STABLE_MS) s->restart_count = 0; /* 稳定运行 → 清零计数 */ continue; } if (s->state == SV_FAILED || s->state == SV_DISABLED) continue; if (s->restart_at_ms > 0) { if (now_ms() >= s->restart_at_ms && trigger_satisfied(s)) { s->restart_at_ms = 0; svcd_log("service %s: backoff done, respawn", s->name); spawn_service(s); } } else if (s->enabled && trigger_satisfied(s) && !s->user_stopped) { spawn_service(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_nsvc; i++) { g_svcs[i].user_stopped = 0; g_svcs[i].restart_count = 0; g_svcs[i].state = SV_STOPPED; spawn_service(&g_svcs[i]); } return 0; } int idx = find_svc(name); if (idx < 0) return -1; svc_t *s = &g_svcs[idx]; s->user_stopped = 0; if (s->pid <= 0) { s->restart_count = 0; s->state = SV_STOPPED; spawn_service(s); } return 0; } static int cmd_stop(const char *name) { if (strcmp(name, "all") == 0) { for (int i = 0; i < g_nsvc; i++) stop_service(&g_svcs[i], 0, 1); return 0; } int idx = find_svc(name); if (idx < 0) return -1; stop_service(&g_svcs[idx], 0, 1); return 0; } static int cmd_restart(const char *name) { if (strcmp(name, "all") == 0) { for (int i = 0; i < g_nsvc; i++) stop_service(&g_svcs[i], 1, 0); return 0; } int idx = find_svc(name); if (idx < 0) return -1; svc_t *s = &g_svcs[idx]; s->user_stopped = 0; if (s->pid <= 0) { s->restart_count = 0; s->state = SV_STOPPED; spawn_service(s); } else stop_service(s, 1, 0); return 0; } /* ---- status 回显(详细 + ANSI 配色;非 TTY 由客户端去色) ---- */ /* 追加一个字段:可选上色(color 包裹 plain + 复位),再按纯文本宽度补空格对齐。 * width<0 表示最后一列(不补齐)。b 为累积缓冲,*o 为偏移。 */ 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; /* 截断保护: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'; } /* 状态 → 关键色:RUNNING=绿 / FAILED=红 / STOPPED·STOPPING=黄 / DISABLED=灰。 */ static const char *state_color(int st) { switch (st) { case SV_RUNNING: return AN_GREEN; case SV_FAILED: return AN_RED; case SV_STOPPED: return AN_YELLOW; case SV_STOPPING: return AN_YELLOW; case SV_DISABLED: return AN_GREY; default: return ""; } } /* 解码 waitpid status 为短串:exit=N / sig=N / "-"(未曾退出)。 */ static void fmt_exit_str(const svc_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, "-"); } /* 上次退出时间 HH:MM:SS;无则 "-"。 */ static void fmt_exit_time(const svc_t *s, char *out, size_t cap) { if (!s->has_exit || s->last_exit_wall == 0) { snprintf(out, cap, "-"); return; } time_t t = (time_t)s->last_exit_wall; struct tm tm; if (!localtime_r(&t, &tm)) { snprintf(out, cap, "-"); return; } strftime(out, cap, "%H:%M:%S", &tm); } /* 回显状态:`status`(全部+全局)或 `status `(单服务)。 */ static void cmd_status(int fd, const char *name) { int idx = -1; /* -1=全部 */ if (name && strcmp(name, "all") != 0) { idx = find_svc(name); if (idx < 0) { send_resp(fd, "ERR no-such-service\n"); return; } } int shown = (idx >= 0) ? 1 : g_nsvc; /* NAME 列宽(按纯文本计) */ int nw = 4; /* 至少等于 "NAME" */ for (int i = 0; i < g_nsvc; i++) { if (idx >= 0 && i != idx) continue; int l = (int)strlen(g_svcs[i].name); if (l > nw) nw = l; } if (nw > 40) nw = 40; /* 防超宽 */ const int sw = 8, pw = 7, rw = 7, bw = 8, tw = 11, scw = 5, ew = 6, etw = 9; 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; /* 标题 + 全局汇总(services= 恒为已知总数) */ put_field(b, cap, &o, AN_BOLD AN_CYAN, "meow_svcd status", -1); put_str(b, cap, &o, "\n"); char hdr[64]; snprintf(hdr, sizeof hdr, "card_present=%d wifi_up=%d services=%d\n", g_card_present, g_wifi_up, g_nsvc); put_str(b, cap, &o, hdr); /* 表头(dim) */ 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, "TRIGGER", tw); put_str(b, cap, &o, " "); put_field(b, cap, &o, AN_DIM, "SRC", scw); 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, "EXIT_TIME", etw); put_str(b, cap, &o, " "); put_field(b, cap, &o, AN_DIM, "EXEC", -1); put_str(b, cap, &o, "\n"); /* 分隔线(dim) */ int total = nw + 1 + sw + 1 + pw + 1 + rw + 1 + bw + 1 + tw + 1 + scw + 1 + ew + 1 + etw + 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_nsvc; i++) { if (idx >= 0 && i != idx) continue; svc_t *s = &g_svcs[i]; char pidstr[16], rstr[16], bstr[16], estr[16], etstr[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); fmt_exit_time(s, etstr, sizeof etstr); 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, "", s->trigger, tw); put_str(b, cap, &o, " "); put_field(b, cap, &o, "", (s->src == 1) ? "board" : "tf", scw); put_str(b, cap, &o, " "); put_field(b, cap, &o, "", estr, ew); put_str(b, cap, &o, " "); put_field(b, cap, &o, "", etstr, etw); put_str(b, cap, &o, " "); put_field(b, cap, &o, "", s->exec, -1); put_str(b, cap, &o, "\n"); } /* 分块写回(避免单次大 write 阻塞/截断) */ 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[SVCD_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); /* sp=NULL=全部;=单服务 */ } 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-service\n"); } else if (sp && strcmp(line, "stop") == 0) { send_resp(fd, cmd_stop(sp) == 0 ? "OK\n" : "ERR no-such-service\n"); } else if (sp && strcmp(line, "restart") == 0) { send_resp(fd, cmd_restart(sp) == 0 ? "OK\n" : "ERR no-such-service\n"); } else { send_resp(fd, "ERR unknown-command\n"); } } /* ---- Daemon 生命周期 ---- */ /* 绑定控制 socket。返回 0=成功。 */ static int setup_socket(void) { unlink(g_sock); int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { svcd_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) { svcd_log("bind %s: %s", g_sock, strerror(errno)); close(fd); return -1; } if (listen(fd, 4) != 0) { svcd_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) { svcd_log("pipe: %s", strerror(errno)); return -1; } /* 双端 O_NONBLOCK:drain 循环读到空即停,绝不阻塞在 read 上(否则 SIGCHLD * 唤醒 poll → 进入 reap_children → 第二次 read 阻塞,守护进程卡死)。 */ 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; /* 无 SA_RESTART:让 poll 中断 */ 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); /* 服务关管道不杀 svcd(崩溃隔离一环) */ return 0; } static void daemonize(void) { pid_t pid = fork(); if (pid < 0) { svcd_log("fork: %s", strerror(errno)); } if (pid > 0) _exit(0); if (setsid() < 0) { /* ignore */ } pid = fork(); if (pid < 0) { svcd_log("fork2: %s", strerror(errno)); } if (pid > 0) _exit(0); /* 清 cwd + stdio 到 /dev/null(日志走 g_log,若无则静默丢弃) */ if (chdir("/") != 0) { /* ignore */ } int nul = open("/dev/null", O_RDWR); if (nul >= 0) { dup2(nul, 0); dup2(nul, 1); dup2(nul, 2); if (nul > 2) close(nul); } } 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_nsvc; i++) { if (g_svcs[i].pid > 0) { kill(g_svcs[i].pid, SIGTERM); unsigned long long wait_until = now_ms() + 1500; while (g_svcs[i].pid > 0 && now_ms() < wait_until) { int st; pid_t p = waitpid(g_svcs[i].pid, &st, WNOHANG); if (p == g_svcs[i].pid) { g_svcs[i].pid = 0; } else usleep(50000); } if (g_svcs[i].pid > 0) { kill(g_svcs[i].pid, SIGKILL); g_svcs[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; /* 设为子进程回收者:被监管服务若派生出孙进程,在服务(直接子进程)退出后 * 由 svcd 收尸而非 reparent 到 init,强化「服务崩溃隔离(僵死/孤儿)」一环。 * 失败不致命:仅记录并继续。 */ if (prctl(PR_SET_CHILD_SUBREAPER, 1) != 0) svcd_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) svcd_log("status file open fail: %s", strerror(errno)); else set_cloexec(g_status_fd); /* 初始卡探测 + 板载服务目录状态 */ g_card_present = card_present(); g_board_online = board_services_exist(); scan_and_reconcile(); /* 板载服务恒在,无卡也扫;卡上有则叠加扫 TF */ svcd_log("meow_svcd up: card=%s present=%d board=%s sock=%s pid=%d", g_card, g_card_present, g_board_dir, g_sock, (int)getpid()); struct pollfd pfds[2]; nfds_t nfds = 2; unsigned long long last_scan = 0; while (!g_shutdown) { /* 计算 poll 超时:取最早退避到期与扫描周期的最小值 */ unsigned long long soon = 0; for (int i = 0; i < g_nsvc; i++) { if (g_svcs[i].restart_at_ms > 0) { unsigned long long d = g_svcs[i].restart_at_ms > now_ms() ? g_svcs[i].restart_at_ms - now_ms() : 0; if (soon == 0 || d < soon) soon = d; } } int timeout = (soon > 0 && soon < (unsigned long long)SVCD_TICK_MAX_MS) ? (int)soon : SVCD_TICK_MAX_MS; if (timeout < 5) timeout = 5; 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; svcd_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(); /* 周期扫描卡/wifi(节流到 ~1s);tick_evaluate 每醒来都跑(保 backoff 精度) */ if (now_ms() - last_scan >= (unsigned long long)SVCD_SCAN_MS) { last_scan = now_ms(); g_wifi_up = wifi_up(); int present = card_present(); if (present != g_card_present) { if (present) { g_card_present = 1; svcd_log("card mounted, rebuild"); scan_and_reconcile(); } else { g_card_present = 0; on_card_out(); } } int bonline = board_services_exist(); if (bonline != g_board_online) { g_board_online = bonline; svcd_log("board services dir %s %s, rebuild", g_board_dir, bonline ? "online" : "offline"); scan_and_reconcile(); } } tick_evaluate(); build_status_file(); } svcd_log("meow_svcd shutdown"); shutdown_all(); return 0; } /* ---- CLI ---- */ /* 就地去除 ANSI 转义序列(\033[...);用于 stdout 非 TTY 时去色。 */ 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_svcd] 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) { /* daemon 未运行 → 显示状态文件(若有) */ fprintf(stderr, "[meow_svcd] 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("card_present=0 services=0\n"); } return 1; } ssize_t w = write(fd, cmdline, strlen(cmdline)); (void)w; shutdown(fd, SHUT_WR); /* 累积完整响应(可能含 ANSI 色码),再按 stdout 是否 TTY 决定去色:非 TTY/重定向去色。 */ 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 supervisor)\n" " options:\n" " -f, --foreground stay in foreground (supervisor fork/exec 托管)\n" " --card TF card mount point (default /usr/data/mnt/sd_0)\n" " --board board services dir (default /usr/data/meow/services.d)\n" " --sock control socket (default /tmp/meow_svcd.sock)\n" " --status status file (default /tmp/meow_svcd.status)\n" " --pidfile pid file (default /tmp/meow_svcd.pid)\n" " --log also append svcd log to \n", argv0); } int main(int argc, char **argv) { /* 收集命令及其参数:首个非选项 token 为命令,其后非选项 token 拼接为其参数 * (如 `stop all`、`restart foo`)。选项可出现在任意位置。 */ 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, "--card") == 0 && i + 1 < argc) safe_strcpy(g_card, sizeof g_card, argv[++i]); else if (strcmp(a, "--board") == 0 && i + 1 < argc) safe_strcpy(g_board_dir, sizeof g_board_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; } /* env 覆盖 */ const char *e; if ((e = getenv("MEOW_SVCD_CARD")) && e[0]) safe_strcpy(g_card, sizeof g_card, e); if ((e = getenv("MEOW_SVCD_BOARD")) && e[0]) safe_strcpy(g_board_dir, sizeof g_board_dir, e); if ((e = getenv("MEOW_SVCD_SOCK")) && e[0]) safe_strcpy(g_sock, sizeof g_sock, e); if ((e = getenv("MEOW_SVCD_STATUS")) && e[0]) safe_strcpy(g_status, sizeof g_status, e); if ((e = getenv("MEOW_SVCD_LOG")) && e[0]) safe_strcpy(g_log, sizeof g_log, e); setvbuf(stderr, NULL, _IONBF, 0); if (cmd_n > 0) { char buf[SVCD_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(); }