/* meow_bgplay.c — M4 后台播放保活 + 无头后台服务运行时表(见 meow_bgplay.h 头注释)。 */ #include "meow_bgplay.h" #include #include #include #include #include #include #include #include #include static meow_bgplay_ops g_ops; static meow_bg_entry_t g_bg[MEOW_BG_MAX]; static meow_svc_rt_t g_svc[MEOW_SVC_RT_MAX]; static uint32_t g_change_seq; static const uint32_t k_backoff[] = { MEOW_SVC_BACKOFF_1, MEOW_SVC_BACKOFF_2, MEOW_SVC_BACKOFF_4, MEOW_SVC_BACKOFF_10 }; #define BACKOFF_STEPS ((int)(sizeof(k_backoff) / sizeof(k_backoff[0]))) /* SIGTERM 后 SIGKILL 兜底宽限(ms) */ #define TERM_GRACE_MS 500u /* ------------------------------------------------------------------ */ static void bg_log(const char *fmt, ...) { va_list ap; char buf[384]; if (!g_ops.log) return; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); g_ops.log("%s", buf); } static uint64_t bg_now_ms(void) { struct timespec ts; if (g_ops.now_ms) return g_ops.now_ms(); clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t)ts.tv_sec * 1000u + (uint64_t)ts.tv_nsec / 1000000u; } static int bg_card_present(void) { return g_ops.card_present ? g_ops.card_present() : 1; } void meow_bgplay_init(const meow_bgplay_ops *ops) { memset(&g_ops, 0, sizeof(g_ops)); memset(g_bg, 0, sizeof(g_bg)); memset(g_svc, 0, sizeof(g_svc)); g_change_seq = 0; if (ops) g_ops = *ops; } /* ================= 后台播放保活 ================= */ int meow_bg_should_keep(int bg_audio_declared, int media_playing) { return bg_audio_declared && media_playing; } static meow_bg_entry_t *bg_find(const char *app_id) { int i; for (i = 0; i < MEOW_BG_MAX; i++) if (g_bg[i].used && strcmp(g_bg[i].app_id, app_id) == 0) return &g_bg[i]; return NULL; } int meow_bg_add(const char *app_id, pid_t pid, int out_fd, int on_card) { int i; if (!app_id || !app_id[0] || pid <= 0) return MEOW_ERR_INVAL; if (bg_find(app_id)) return MEOW_ERR_EXISTS; for (i = 0; i < MEOW_BG_MAX; i++) { if (!g_bg[i].used) { meow_bg_entry_t *e = &g_bg[i]; memset(e, 0, sizeof(*e)); e->used = 1; snprintf(e->app_id, sizeof(e->app_id), "%s", app_id); e->pid = pid; e->out_fd = out_fd; e->on_card = on_card; g_change_seq++; bg_log("[bgplay] 转后台保活: %s pid=%d on_card=%d", app_id, (int)pid, on_card); return 0; } } return MEOW_ERR_NOMEM; } int meow_bg_take(const char *app_id, int *out_fd, pid_t *pid) { meow_bg_entry_t *e; if (!app_id || !app_id[0]) return -1; e = bg_find(app_id); if (!e) return -1; if (out_fd) *out_fd = e->out_fd; if (pid) *pid = e->pid; bg_log("[bgplay] 回前台: %s pid=%d", app_id, (int)e->pid); memset(e, 0, sizeof(*e)); g_change_seq++; return 0; } int meow_bg_is_backgrounded(const char *app_id) { return app_id && app_id[0] && bg_find(app_id) != NULL; } int meow_bg_count(void) { int i, n = 0; for (i = 0; i < MEOW_BG_MAX; i++) if (g_bg[i].used) n++; return n; } const meow_bg_entry_t *meow_bg_at(int i) { if (i < 0 || i >= MEOW_BG_MAX || !g_bg[i].used) return NULL; return &g_bg[i]; } uint32_t meow_bgplay_change_seq(void) { return g_change_seq; } void meow_bg_tick(void) { uint64_t now = bg_now_ms(); int i; for (i = 0; i < MEOW_BG_MAX; i++) { meow_bg_entry_t *e = &g_bg[i]; int st; pid_t r; if (!e->used) continue; /* 卡拔例外:二进制在卡上 → SIGTERM(防 SIGBUS),500ms 未死 SIGKILL */ if (e->on_card && !bg_card_present()) { if (e->term_at_ms == 0) { bg_log("[bgplay] 卡拔出,终止卡上后台 App: %s pid=%d", e->app_id, (int)e->pid); kill(e->pid, SIGTERM); e->term_at_ms = now; } else if (now - e->term_at_ms >= TERM_GRACE_MS) { kill(e->pid, SIGKILL); e->term_at_ms = now; /* 500ms 节流重复 SIGKILL 直至 reap */ } } r = waitpid(e->pid, &st, WNOHANG); if (r == e->pid) { char id[MEOW_APP_ID_MAX]; pid_t pid = e->pid; snprintf(id, sizeof(id), "%s", e->app_id); memset(e, 0, sizeof(*e)); g_change_seq++; bg_log("[bgplay] 后台 App 退出: %s pid=%d status=0x%x", id, (int)pid, st); if (g_ops.on_bg_dead) g_ops.on_bg_dead(id, pid, st); } } } /* ================= 无头后台服务 ================= */ static meow_svc_rt_t *svc_find_mut(const char *app_id, const char *svc) { int i; for (i = 0; i < MEOW_SVC_RT_MAX; i++) if (g_svc[i].used && strcmp(g_svc[i].app_id, app_id) == 0 && strcmp(g_svc[i].svc, svc) == 0) return &g_svc[i]; return NULL; } const meow_svc_rt_t *meow_svc_find(const char *app_id, const char *svc) { if (!app_id || !svc) return NULL; return svc_find_mut(app_id, svc); } /* spawn 服务子进程:stdio→/dev/null,env={PATH,HOME,MEOW_SERVICE[,MEOW_LOG_DIR/MEOW_LOG_APP]}。 * 父进程返回 pid;子进程不返回(execve 或 _exit(127))。 */ static pid_t svc_spawn(meow_svc_rt_t *s) { char env_service[32]; char env_logdir[160]; char env_logapp[64]; char *envp[6]; int n = 0; pid_t pid; envp[n++] = (char *)"PATH=/usr/bin:/bin:/usr/sbin:/sbin"; envp[n++] = (char *)"HOME=/root"; snprintf(env_service, sizeof(env_service), "MEOW_SERVICE=%s", s->svc); envp[n++] = env_service; if (g_ops.log_dir && g_ops.log_dir[0]) { snprintf(env_logdir, sizeof(env_logdir), "MEOW_LOG_DIR=%s", g_ops.log_dir); envp[n++] = env_logdir; snprintf(env_logapp, sizeof(env_logapp), "MEOW_LOG_APP=%s", s->app_id); envp[n++] = env_logapp; } envp[n] = NULL; pid = fork(); if (pid != 0) return pid; /* 父:pid 或 -1 */ /* 子进程:脱离终端会话,stdio 全部重定向 /dev/null(无头服务) */ setsid(); { int dn = open("/dev/null", O_RDWR); if (dn >= 0) { dup2(dn, STDIN_FILENO); dup2(dn, STDOUT_FILENO); dup2(dn, STDERR_FILENO); if (dn > STDERR_FILENO) close(dn); } } { char *argv[2]; argv[0] = s->exec; argv[1] = NULL; execve(s->exec, argv, envp); } _exit(127); /* exec 失败:父进程 tick 回收后按退避/熔断处理 */ } int meow_svc_register(const char *app_id, const char *svc, const char *exec, int restart, int max_restart, int window_ms, int on_card, int reg_fd) { meow_svc_rt_t *s; pid_t pid; int i; if (!app_id || !app_id[0] || !svc || !svc[0] || !exec || !exec[0]) return MEOW_ERR_INVAL; if (access(exec, X_OK) != 0) return MEOW_ERR_NOENT; s = svc_find_mut(app_id, svc); if (s) { if (s->pid > 0) return MEOW_ERR_EXISTS; if (!s->circuit) return MEOW_ERR_EXISTS; /* 退避排程中也视为在册 */ /* 熔断槽重注册:复位熔断,重新武装 */ bg_log("[bgplay] 服务熔断复位重注册: %s/%s", app_id, svc); s->circuit = 0; s->stopping = 0; s->restart_count = 0; s->window_start_ms = bg_now_ms(); s->next_retry_ms = 0; s->reg_fd = reg_fd; s->on_card = on_card; } else { for (i = 0; i < MEOW_SVC_RT_MAX; i++) if (!g_svc[i].used) break; if (i >= MEOW_SVC_RT_MAX) return MEOW_ERR_NOMEM; s = &g_svc[i]; memset(s, 0, sizeof(*s)); s->used = 1; snprintf(s->app_id, sizeof(s->app_id), "%s", app_id); snprintf(s->svc, sizeof(s->svc), "%s", svc); snprintf(s->exec, sizeof(s->exec), "%s", exec); s->restart = restart; s->max_restart = max_restart; s->window_ms = window_ms; s->on_card = on_card; s->reg_fd = reg_fd; s->window_start_ms = bg_now_ms(); } pid = svc_spawn(s); if (pid < 0) { if (s->pid == 0 && s->restart_count == 0 && !s->circuit) memset(s, 0, sizeof(*s)); /* 新槽首次 fork 即败:不留尸槽 */ return MEOW_ERR_IO; } s->pid = pid; s->term_at_ms = 0; bg_log("[bgplay] 服务拉起: %s/%s pid=%d restart=%d max=%d", app_id, svc, (int)pid, restart, max_restart); return (int)pid; } int meow_svc_stop(const char *app_id, const char *svc) { meow_svc_rt_t *s; if (!app_id || !svc) return MEOW_ERR_INVAL; s = svc_find_mut(app_id, svc); if (!s) return MEOW_ERR_NOENT; bg_log("[bgplay] 服务停止: %s/%s pid=%d", app_id, svc, (int)s->pid); if (s->pid > 0) { s->stopping = 1; kill(s->pid, SIGTERM); s->term_at_ms = bg_now_ms(); } else { /* 未在跑(退避中/熔断):直接清槽 */ if (g_ops.on_svc_dead) g_ops.on_svc_dead(s->app_id, s->svc); memset(s, 0, sizeof(*s)); } return 0; } /* 服务死亡后的退避/熔断判定。返回 1=已排程重启;0=槽已清或熔断。 */ static int svc_on_exit(meow_svc_rt_t *s, int st) { uint64_t now = bg_now_ms(); bg_log("[bgplay] 服务退出: %s/%s pid=%d status=0x%x stopping=%d", s->app_id, s->svc, (int)s->pid, st, s->stopping); s->pid = 0; if (s->stopping || !s->restart) { if (g_ops.on_svc_dead) g_ops.on_svc_dead(s->app_id, s->svc); memset(s, 0, sizeof(*s)); return 0; } /* 滑窗:超窗复位计数 */ if (s->window_ms > 0 && now - s->window_start_ms >= (uint64_t)s->window_ms) { s->restart_count = 0; s->window_start_ms = now; } s->restart_count++; if (s->max_restart > 0 && s->restart_count > s->max_restart) { /* 熔断:清通知 + 广播 MSG_SYS{SVC_DEAD},槽保留待重注册复位 */ m_sys_t m; s->circuit = 1; s->next_retry_ms = 0; bg_log("[bgplay] 服务熔断: %s/%s 窗口内重启 %d 次超 max=%d", s->app_id, s->svc, s->restart_count, s->max_restart); if (g_ops.notif_clear_app) g_ops.notif_clear_app(s->app_id); if (g_ops.send_frame && s->reg_fd >= 0) { memset(&m, 0, sizeof(m)); m.event = MEOW_SYS_SVC_DEAD; snprintf(m.app_id, sizeof(m.app_id), "%s", s->app_id); snprintf(m.svc, sizeof(m.svc), "%s", s->svc); g_ops.send_frame(s->reg_fd, MSG_SYS, 0, &m, sizeof(m)); } if (g_ops.on_svc_dead) g_ops.on_svc_dead(s->app_id, s->svc); return 0; } { int step = s->restart_count - 1; uint32_t dly; if (step >= BACKOFF_STEPS) step = BACKOFF_STEPS - 1; dly = k_backoff[step]; s->next_retry_ms = now + dly; bg_log("[bgplay] 服务退避重启: %s/%s 第 %d 次,%ums 后拉起", s->app_id, s->svc, s->restart_count, (unsigned)dly); } return 1; } void meow_svc_tick(void) { uint64_t now = bg_now_ms(); int i; for (i = 0; i < MEOW_SVC_RT_MAX; i++) { meow_svc_rt_t *s = &g_svc[i]; if (!s->used) continue; /* 卡拔例外:exec 在卡上 → SIGTERM + stopping=1(不重启防空转死循环) */ if (s->on_card && s->pid > 0 && !bg_card_present() && !s->stopping) { bg_log("[bgplay] 卡拔出,终止卡上服务: %s/%s pid=%d", s->app_id, s->svc, (int)s->pid); s->stopping = 1; kill(s->pid, SIGTERM); s->term_at_ms = now; } /* SIGTERM 宽限超时 → SIGKILL */ if (s->stopping && s->pid > 0 && s->term_at_ms && now - s->term_at_ms >= TERM_GRACE_MS) { kill(s->pid, SIGKILL); s->term_at_ms = now; } if (s->pid > 0) { int st; pid_t r = waitpid(s->pid, &st, WNOHANG); if (r == s->pid) svc_on_exit(s, st); continue; } /* 退避到点 → 重新拉起 */ if (!s->circuit && !s->stopping && s->restart && s->next_retry_ms && now >= s->next_retry_ms) { pid_t pid = svc_spawn(s); s->next_retry_ms = 0; if (pid > 0) { s->pid = pid; bg_log("[bgplay] 服务重启: %s/%s pid=%d(第 %d 次)", s->app_id, s->svc, (int)pid, s->restart_count); } else { /* fork 失败:压到下一档重试 */ s->next_retry_ms = now + k_backoff[BACKOFF_STEPS - 1]; } } } } /* ================= 关机前回收 ================= */ void meow_bgplay_kill_all(void) { uint64_t deadline; int i; /* best-effort 广播 SHUTDOWN */ if (g_ops.send_frame) { m_sys_t m; memset(&m, 0, sizeof(m)); m.event = MEOW_SYS_SHUTDOWN; for (i = 0; i < MEOW_SVC_RT_MAX; i++) if (g_svc[i].used && g_svc[i].reg_fd >= 0) g_ops.send_frame(g_svc[i].reg_fd, MSG_SYS, 0, &m, sizeof(m)); } for (i = 0; i < MEOW_BG_MAX; i++) if (g_bg[i].used && g_bg[i].pid > 0) kill(g_bg[i].pid, SIGTERM); for (i = 0; i < MEOW_SVC_RT_MAX; i++) if (g_svc[i].used && g_svc[i].pid > 0) kill(g_svc[i].pid, SIGTERM); /* 有界等 1s(轮询 reap,期间 tick 语义不在——直接 waitpid) */ deadline = bg_now_ms() + 1000u; while (bg_now_ms() < deadline) { int alive = 0; for (i = 0; i < MEOW_BG_MAX; i++) { if (!g_bg[i].used) continue; if (waitpid(g_bg[i].pid, NULL, WNOHANG) == g_bg[i].pid) memset(&g_bg[i], 0, sizeof(g_bg[i])); else alive++; } for (i = 0; i < MEOW_SVC_RT_MAX; i++) { if (!g_svc[i].used) continue; if (waitpid(g_svc[i].pid, NULL, WNOHANG) == g_svc[i].pid) memset(&g_svc[i], 0, sizeof(g_svc[i])); else alive++; } if (!alive) break; usleep(50 * 1000); } /* 兜底 SIGKILL + 有界 reap(waitpid 阻塞上限由 SIGKILL 必死保证) */ for (i = 0; i < MEOW_BG_MAX; i++) if (g_bg[i].used) { kill(g_bg[i].pid, SIGKILL); waitpid(g_bg[i].pid, NULL, 0); memset(&g_bg[i], 0, sizeof(g_bg[i])); } for (i = 0; i < MEOW_SVC_RT_MAX; i++) if (g_svc[i].used) { kill(g_svc[i].pid, SIGKILL); waitpid(g_svc[i].pid, NULL, 0); memset(&g_svc[i], 0, sizeof(g_svc[i])); } g_change_seq++; bg_log("[bgplay] kill_all 完成(关机/退出回收)"); } void meow_bgplay_dump(const char *prefix) { int i; for (i = 0; i < MEOW_BG_MAX; i++) if (g_bg[i].used) bg_log("%s bg[%d] %s pid=%d on_card=%d", prefix, i, g_bg[i].app_id, (int)g_bg[i].pid, g_bg[i].on_card); for (i = 0; i < MEOW_SVC_RT_MAX; i++) if (g_svc[i].used) bg_log("%s svc[%d] %s/%s pid=%d restart=%d count=%d circuit=%d", prefix, i, g_svc[i].app_id, g_svc[i].svc, (int)g_svc[i].pid, g_svc[i].restart, g_svc[i].restart_count, g_svc[i].circuit); }