/* 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 / reload / pid * 后台常驻模式默认(无子命令即启动守护进程);`-f/--foreground` 供 supervisor * fork/exec 托管(保证 supervisor 可 waitpid)。 * * 状态文件:`/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 /* ---- 默认路径 / 常量 ---- */ #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' /* ---- 服务状态机 ---- */ 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 */ 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 仅备忘 */ } /* 未知键忽略(前向兼容) */ } /* 解析单个 .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"); /* argv[0]=exec;随后 args */ 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_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; if (g_nsvc - idx - 1 > 0) memmove(&g_svcs[idx], &g_svcs[idx + 1], (size_t)(g_nsvc - idx - 1) * sizeof(svc_t)); 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; 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; } /* 排空 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; } apply_exit(&g_svcs[idx], 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; 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->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; } static void cmd_status(int fd) { send_resp(fd, "# meow_svcd status\n"); char line[384]; int k = snprintf(line, sizeof line, "card_present=%d wifi_up=%d services=%d\n", g_card_present, g_wifi_up, g_nsvc); send_resp(fd, line); (void)k; for (int i = 0; i < g_nsvc; i++) { svc_t *s = &g_svcs[i]; snprintf(line, sizeof line, "svc=%.63s state=%.16s pid=%d restart=%d exit=%d trigger=%.15s\n", s->name, g_state_str[s->state], (int)s->pid, s->restart_count, s->last_exit_status, s->trigger); send_resp(fd, line); } } /* 读一条命令并分发。返回响应内容(静态/栈缓冲由调用方使用)。 */ 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); } 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; 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 ---- */ 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); char rbuf[8192]; ssize_t n; while ((n = read(fd, rbuf, sizeof rbuf - 1)) > 0) { rbuf[n] = '\0'; fputs(rbuf, stdout); } close(fd); return 0; } static void usage(const char *argv0) { fprintf(stderr, "usage: %s [options] [command]\n" " commands: start | stop | restart \n" " | status | 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(); }