feat(crash): meow_crash 系统级崩溃捕获 (agent-187) — svcd 服务异常退出上报(AF_UNIX best-effort,不崩svcd)+dmesg增量扫segfault/oops; 落盘 TF MeowOS/logs/crash_<bootts>.log 无卡回退/tmp+迁移; CLI dump/tail; S89 预置 crash.conf(主会话补)
meow_crash 62781084; meow_svcd da6ebc66→605646c4(仅+68行上报挂点); 宿主5场景全过(上报链路/dmesg增量/无卡迁移/铁律验证/CLI)
This commit is contained in:
1 parent
243148e440
commit
cf86cbd387
10 files changed
+885
-1
No files matched your search
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
# build_crash.sh — meow_crash 交叉编译(mipsel 静态)+ 宿主编译可选。
|
||||
#
|
||||
# 目标:R3 Pro II / MeowOS(mipsel-linux-muslsf,静态)。meow_crash 极简无外部依赖,
|
||||
# 仅 POSIX + Linux 基础接口(同一套自管道/poll/AF_UNIX 模式,同 meow_svcd)。
|
||||
#
|
||||
# 用法:
|
||||
# ./build_crash.sh # 交叉编译 → patch_v2/meow_crash/meow_crash(mipsel 静态)
|
||||
# ./build_crash.sh host # 宿主编译 → patch_v2/meow_crash/meow_crash_host(集成测试用)
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
ROOT="$HOME/Project/R3ProII"
|
||||
CC="$ROOT/.tools/zig-mipsel-cc"
|
||||
|
||||
if [ "$1" = "host" ]; then
|
||||
HOST_CC="${CC_HOST:-cc}"
|
||||
"$HOST_CC" -Os -Wall -Wextra meow_crash.c -o meow_crash_host
|
||||
echo "== host build =="
|
||||
ls -la meow_crash_host
|
||||
file meow_crash_host
|
||||
exit 0
|
||||
fi
|
||||
|
||||
"$CC" -Os -Wall -Wextra -static meow_crash.c -o meow_crash
|
||||
echo "== meow_crash (mipsel static) =="
|
||||
ls -la meow_crash
|
||||
md5sum meow_crash
|
||||
file meow_crash
|
||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,676 @@
|
||||
/* meow_crash.c — MeowOS 系统级崩溃捕获守护进程(二期组件 meow_crash)。
|
||||
*
|
||||
* 定位(用户定案,见 FRAMEWORK_DESIGN §10 / APPS.md §4.5 /
|
||||
* devdocs/plan/SYS_BASELINE.md / devdocs/plan/APP_LOG_MANAGER.md):
|
||||
* - svcd 托管的小守护(板载 `/usr/bin/meow_crash` + services.d 板载 conf,
|
||||
* trigger=boot);svcd fork+exec 直接拉起,故本进程【前台常驻】(不 daemonize)。
|
||||
* - 捕获源:
|
||||
* ① svcd 服务异常退出(非 0 / 被信号杀)——svcd 在 reap_children 检测到异常
|
||||
* 退出时经 AF_UNIX `/tmp/meow_crash.sock` 单行上报(SVC 行,best-effort);
|
||||
* ② 内核 oops / 应用崩溃——周期性扫描 dmesg 尾部【新增】,记录上次读取偏移,
|
||||
* 增量捕获含 "segfault"/"oops" 的行(v1 以 ① 为主,② 为辅助;不强求扫 core)。
|
||||
* - 落盘:TF 卡 `MeowOS/logs/crash_<bootts>.log` 追加写(一行一条:时间戳/uptime/
|
||||
* 来源/详情);无卡 → `/tmp/meow_crash.log`(256KB 滚动);卡出现后自动迁移
|
||||
* (语义对齐 launcher log_poll_card);总量并入 logs/ 1GB 上限纪律(按 mtime 删最旧)。
|
||||
* - CLI:`dump`(打印本 boot 崩溃清单)/ `tail N`;root 免鉴权。读日志文件,
|
||||
* 不依赖守护进程在线。
|
||||
* - 铁律:svcd 死亡通知全程非阻塞、可失败(connect 失败即放弃,绝不重试);
|
||||
* 服务崩绝不能带崩 svcd(本进程只读 svcd 的一条消息做落盘,无共享内存)。
|
||||
*
|
||||
* 编译(mipsel 静态,见 build_crash.sh):
|
||||
* ~/Project/R3ProII/.tools/zig-mipsel-cc -Os -Wall -Wextra -static meow_crash.c -o meow_crash
|
||||
* 宿主编译(集成测试):gcc -Os -Wall -Wextra meow_crash.c -o meow_crash_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 <ctype.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <poll.h>
|
||||
|
||||
/* ---- 默认路径 / 常量 ---- */
|
||||
#define CRASH_CARD_DEFAULT "/usr/data/mnt/sd_0" /* TF 卡挂载点 */
|
||||
#define CRASH_LOGS_REL "MeowOS/logs" /* 卡上日志目录(同 launcher) */
|
||||
#define CRASH_SOCK_DEFAULT "/tmp/meow_crash.sock" /* svcd 崩溃上报 socket */
|
||||
#define CRASH_TMP_LOG "/tmp/meow_crash.log" /* 无卡回退(tmpfs) */
|
||||
#define CRASH_TMP_MAX (256u * 1024u) /* tmp 滚动上限(对齐 boot_buf) */
|
||||
#define CRASH_LOG_SIZE_LIMIT (1u << 30) /* logs/ 总量 1GB 纪律 */
|
||||
#define CRASH_LINE_MAX 1024 /* 单行上限 */
|
||||
#define CRASH_SCAN_MS 1000 /* poll_card/dmesg 扫描间隔 */
|
||||
#define CRASH_TICK_MAX_MS 1000 /* poll 上限 */
|
||||
#define CRASH_CLEANUP_MS 60000 /* 1GB 清理节流 */
|
||||
#define CRASH_PATH_MAX 512
|
||||
#define CRASH_SOCK_PATH_MAX 108
|
||||
#define CRASH_BOOTTS_LEN 64
|
||||
#define CRASH_DMESG_MAX (128u * 1024u) /* dmesg 单次抓取上限 */
|
||||
#define CRASH_SEEN_MAX 64 /* 关键字行去重环深度 */
|
||||
#define CRASH_SIGTERM_CHAR 'T'
|
||||
|
||||
/* ---- 全局状态 ---- */
|
||||
static char g_card[256] = CRASH_CARD_DEFAULT;
|
||||
static char g_sock[CRASH_SOCK_PATH_MAX] = CRASH_SOCK_DEFAULT;
|
||||
static char g_bootts[CRASH_BOOTTS_LEN] = ""; /* "%Y%m%d_%H%M%S"(去 meow_ 前缀) */
|
||||
static int g_log_fd = -1; /* 卡日志 fd;-1=未开(落 /tmp) */
|
||||
static int g_on_card = 0;
|
||||
static int g_shutdown = 0;
|
||||
static int g_sigpipe[2] = {-1, -1}; /* 信号自管道 */
|
||||
static int g_listen_fd = -1;
|
||||
|
||||
/* dmesg 增量扫描状态 */
|
||||
static char g_dmesg_prev[CRASH_DMESG_MAX]; /* 上次完整输出(前缀比对) */
|
||||
static size_t g_dmesg_prev_len = 0;
|
||||
static int g_seen_idx = 0; /* 去重环游标 */
|
||||
static char g_seen[CRASH_SEEN_MAX][CRASH_LINE_MAX]; /* 最近关键字行(防环回重复) */
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* /proc/uptime 首字段 → 秒(整数)。失败返回 0。 */
|
||||
static long read_uptime_sec(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);
|
||||
}
|
||||
|
||||
/* 本 boot 时间戳 "%Y%m%d_%H%M%S":复用 /tmp/meow_bootts(launcher 缓存),
|
||||
* 同一 boot 会话跨进程共用。读不到则按 墙钟 - uptime 计算,并以 launcher 同款
|
||||
* "meow_%Y%m%d_%H%M%S" 格式缓存,保证 launcher/meow_crash 取到同一 bootts。 */
|
||||
static void boot_ts(char *out, size_t n)
|
||||
{
|
||||
out[0] = '\0';
|
||||
int fd = open("/tmp/meow_bootts", O_RDONLY);
|
||||
if (fd >= 0) {
|
||||
char b[CRASH_BOOTTS_LEN];
|
||||
ssize_t r = read(fd, b, sizeof b - 1);
|
||||
close(fd);
|
||||
if (r > 0) {
|
||||
b[r] = '\0';
|
||||
char *p = b;
|
||||
if (strncmp(p, "meow_", 5) == 0) p += 5; /* 剥 launcher 前缀,只留 ts */
|
||||
snprintf(out, n, "%s", p);
|
||||
if (out[0]) return;
|
||||
}
|
||||
}
|
||||
time_t now = time(NULL);
|
||||
time_t up = (time_t)read_uptime_sec();
|
||||
time_t boot = now - up;
|
||||
struct tm tm;
|
||||
localtime_r(&boot, &tm);
|
||||
strftime(out, n, "%Y%m%d_%H%M%S", &tm);
|
||||
char cached[CRASH_BOOTTS_LEN];
|
||||
snprintf(cached, sizeof cached, "meow_%s", out);
|
||||
fd = open("/tmp/meow_bootts", O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (fd >= 0) { ssize_t w = write(fd, cached, strlen(cached)); (void)w; close(fd); }
|
||||
}
|
||||
|
||||
/* ---- 卡可用性 / 日志打开 ---- */
|
||||
|
||||
static int card_available(void)
|
||||
{
|
||||
const char *nc = getenv("MEOW_CRASH_NOCARD");
|
||||
if (nc && nc[0] == '1') return 0; /* 测试钩子:强制无卡 */
|
||||
|
||||
char logs[CRASH_PATH_MAX];
|
||||
if (snprintf(logs, sizeof logs, "%s/%s", g_card, CRASH_LOGS_REL)
|
||||
>= (int)sizeof logs) return 0;
|
||||
|
||||
/* 默认卡路径 → 必须真实挂载(防把 rootfs 当卡);被 --card/env 覆写则视为普通目录
|
||||
* (供宿主集成测试把 --card 指向临时目录即可走「有卡」路径)。 */
|
||||
if (strcmp(g_card, CRASH_CARD_DEFAULT) == 0) {
|
||||
FILE *f = fopen("/proc/mounts", "r");
|
||||
if (!f) return 0;
|
||||
char needle[CRASH_PATH_MAX];
|
||||
char line[CRASH_PATH_MAX];
|
||||
snprintf(needle, sizeof needle, " %s ", g_card);
|
||||
int found = 0;
|
||||
while (fgets(line, sizeof line, f)) {
|
||||
if (strstr(line, needle)) { found = 1; break; }
|
||||
}
|
||||
fclose(f);
|
||||
if (!found) return 0;
|
||||
}
|
||||
|
||||
(void)mkdir(logs, 0755);
|
||||
return access(logs, W_OK) == 0;
|
||||
}
|
||||
|
||||
/* 把 logs/ 下超过 1GB 的部分按 mtime 删最旧(统计 meow_* 与 crash_*)。 */
|
||||
static void log_cleanup_old(void)
|
||||
{
|
||||
char logs[CRASH_PATH_MAX];
|
||||
if (snprintf(logs, sizeof logs, "%s/%s", g_card, CRASH_LOGS_REL)
|
||||
>= (int)sizeof logs) return;
|
||||
DIR *d = opendir(logs);
|
||||
if (!d) return;
|
||||
|
||||
struct lf { char name[64]; long long size; long long mtime; } files[128];
|
||||
int n = 0;
|
||||
struct dirent *de;
|
||||
while ((de = readdir(d)) != NULL && n < 128) {
|
||||
const char *nm = de->d_name;
|
||||
if (strncmp(nm, "meow_", 5) != 0 && strncmp(nm, "crash_", 6) != 0) continue;
|
||||
char path[CRASH_PATH_MAX];
|
||||
if (snprintf(path, sizeof path, "%s/%s", logs, nm) >= (int)sizeof path) continue;
|
||||
struct stat st;
|
||||
if (stat(path, &st) != 0 || !S_ISREG(st.st_mode)) continue;
|
||||
size_t nl = strlen(nm);
|
||||
if (nl >= sizeof files[n].name) nl = sizeof files[n].name - 1;
|
||||
memcpy(files[n].name, nm, nl);
|
||||
files[n].name[nl] = '\0';
|
||||
files[n].size = (long long)st.st_size;
|
||||
files[n].mtime = (long long)st.st_mtime;
|
||||
n++;
|
||||
}
|
||||
closedir(d);
|
||||
|
||||
long long total = 0;
|
||||
for (int i = 0; i < n; i++) total += files[i].size;
|
||||
/* 选择排序:mtime 升序(最旧在前) */
|
||||
for (int i = 0; i < n; i++)
|
||||
for (int j = i + 1; j < n; j++)
|
||||
if (files[j].mtime < files[i].mtime) {
|
||||
struct lf t = files[i]; files[i] = files[j]; files[j] = t;
|
||||
}
|
||||
int deleted = 0;
|
||||
for (int i = 0; i < n && total > (long long)CRASH_LOG_SIZE_LIMIT; i++) {
|
||||
char path[CRASH_PATH_MAX];
|
||||
if (snprintf(path, sizeof path, "%s/%s", logs, files[i].name)
|
||||
>= (int)sizeof path) continue;
|
||||
if (unlink(path) == 0) { total -= files[i].size; deleted++; }
|
||||
}
|
||||
if (deleted)
|
||||
fprintf(stderr, "[meow_crash] log_cleanup deleted %d old log(s)\n", deleted);
|
||||
}
|
||||
|
||||
/* 打开卡日志 crash_<bootts>.log。成功返回 0(置 g_log_fd/g_on_card)。 */
|
||||
static int log_open_card(void)
|
||||
{
|
||||
if (g_log_fd >= 0) return 0;
|
||||
if (!card_available()) return -1;
|
||||
char ts[CRASH_BOOTTS_LEN];
|
||||
boot_ts(ts, sizeof ts);
|
||||
char path[CRASH_PATH_MAX];
|
||||
if (snprintf(path, sizeof path, "%s/%s/crash_%s.log", g_card, CRASH_LOGS_REL, ts)
|
||||
>= (int)sizeof path) return -1;
|
||||
int fd = open(path, O_WRONLY | O_APPEND | O_CREAT, 0644);
|
||||
if (fd < 0) return -1;
|
||||
g_log_fd = fd;
|
||||
g_on_card = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 追加一行:优先卡日志;卡日志未开/写失败 → /tmp(256KB 滚动)。 */
|
||||
static void append_line(const char *line)
|
||||
{
|
||||
if (g_log_fd >= 0) {
|
||||
ssize_t w = write(g_log_fd, line, strlen(line));
|
||||
if (w < 0) { close(g_log_fd); g_log_fd = -1; g_on_card = 0; }
|
||||
else return;
|
||||
}
|
||||
/* /tmp 回退(tmpfs,滚动到 256KB) */
|
||||
struct stat st;
|
||||
if (stat(CRASH_TMP_LOG, &st) == 0 && st.st_size > (long long)CRASH_TMP_MAX)
|
||||
unlink(CRASH_TMP_LOG);
|
||||
int fd = open(CRASH_TMP_LOG, O_WRONLY | O_APPEND | O_CREAT, 0644);
|
||||
if (fd >= 0) { ssize_t w = write(fd, line, strlen(line)); (void)w; close(fd); }
|
||||
}
|
||||
|
||||
/* 卡出现后把 /tmp 缓冲迁移进卡日志(语义对齐 launcher log_poll_card)。 */
|
||||
static void poll_card(void)
|
||||
{
|
||||
if (g_log_fd >= 0) return;
|
||||
if (log_open_card() != 0) return;
|
||||
int fd = open(CRASH_TMP_LOG, O_RDONLY);
|
||||
if (fd >= 0) {
|
||||
char chunk[1024];
|
||||
ssize_t cn;
|
||||
while ((cn = read(fd, chunk, sizeof chunk)) > 0) {
|
||||
if (write(g_log_fd, chunk, cn) < 0) {
|
||||
close(fd);
|
||||
g_log_fd = -1; g_on_card = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
unlink(CRASH_TMP_LOG);
|
||||
fprintf(stderr, "[meow_crash] /tmp buffered crash log moved to card\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* 以当前时间/uptime 落一条「自身来源」事件(dmesg 用)。 */
|
||||
static void emit_event(const char *source, const char *detail)
|
||||
{
|
||||
char line[CRASH_LINE_MAX];
|
||||
long epoch = (long)time(NULL);
|
||||
long up = read_uptime_sec();
|
||||
int l = snprintf(line, sizeof line, "%ld %ld %s %s\n", epoch, up, source, detail);
|
||||
if (l > 0) append_line(line);
|
||||
}
|
||||
|
||||
/* 落 svcd 上报的来源事件(用 svcd 拍下的 epoch/uptime,即崩溃时刻)。 */
|
||||
static void emit_svc(const char *detail, long epoch, long up)
|
||||
{
|
||||
char line[CRASH_LINE_MAX];
|
||||
int l = snprintf(line, sizeof line, "%ld %ld svc %s\n", epoch, up, detail);
|
||||
if (l > 0) append_line(line);
|
||||
}
|
||||
|
||||
/* ---- 崩溃上报 socket(svcd → meow_crash) ---- */
|
||||
|
||||
/* 解析一行 svcd 死亡通知:SVC <name> <exit|sig> <num> <epoch> <uptime> */
|
||||
static void handle_conn(int fd)
|
||||
{
|
||||
char buf[CRASH_LINE_MAX];
|
||||
ssize_t n = read(fd, buf, sizeof buf - 1);
|
||||
if (n <= 0) return;
|
||||
buf[n] = '\0';
|
||||
while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == '\r')) buf[--n] = '\0';
|
||||
if (n == 0) return;
|
||||
|
||||
char name[128], kind[8];
|
||||
int num;
|
||||
long epoch, up;
|
||||
if (sscanf(buf, "SVC %127s %7s %d %ld %ld", name, kind, &num, &epoch, &up) == 5) {
|
||||
char detail[CRASH_LINE_MAX];
|
||||
if (strcmp(kind, "exit") == 0) snprintf(detail, sizeof detail, "%s exit=%d", name, num);
|
||||
else if (strcmp(kind, "sig") == 0) snprintf(detail, sizeof detail, "%s signal=%d", name, num);
|
||||
else return; /* 未知 kind,丢弃 */
|
||||
emit_svc(detail, epoch, up);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- dmesg 增量扫描(来源②) ---- */
|
||||
|
||||
static char *read_dmesg(void)
|
||||
{
|
||||
const char *src = getenv("MEOW_CRASH_DMESG_FILE");
|
||||
if (src && src[0]) { /* 测试钩子:从文件读假 dmesg */
|
||||
FILE *f = fopen(src, "r");
|
||||
if (!f) return NULL;
|
||||
char *out = malloc(CRASH_DMESG_MAX);
|
||||
if (!out) { fclose(f); return NULL; }
|
||||
int off = 0;
|
||||
while (off < (int)CRASH_DMESG_MAX - 1) {
|
||||
int r = (int)fread(out + off, 1, CRASH_DMESG_MAX - 1 - off, f);
|
||||
if (r <= 0) break;
|
||||
off += r;
|
||||
}
|
||||
fclose(f);
|
||||
out[off] = '\0';
|
||||
return out;
|
||||
}
|
||||
FILE *f = popen("PATH=/bin:/sbin:/usr/bin:/usr/sbin dmesg 2>/dev/null", "r");
|
||||
if (!f) return NULL;
|
||||
char *out = malloc(CRASH_DMESG_MAX);
|
||||
if (!out) { pclose(f); return NULL; }
|
||||
int off = 0;
|
||||
while (off < (int)CRASH_DMESG_MAX - 1) {
|
||||
int r = (int)fread(out + off, 1, CRASH_DMESG_MAX - 1 - off, f);
|
||||
if (r <= 0) break;
|
||||
off += r;
|
||||
}
|
||||
pclose(f);
|
||||
out[off] = '\0';
|
||||
return out;
|
||||
}
|
||||
|
||||
/* 行内是否含 "segfault"/"oops"(大小写不敏感)。 */
|
||||
static int is_crash_line(const char *s)
|
||||
{
|
||||
static const char *keys[] = {"segfault", "oops"};
|
||||
size_t len = strlen(s);
|
||||
for (int k = 0; k < 2; k++) {
|
||||
size_t kl = strlen(keys[k]);
|
||||
for (size_t i = 0; i + kl <= len; i++) {
|
||||
size_t j = 0;
|
||||
int m = 1;
|
||||
for (; j < kl; j++) {
|
||||
char c = s[i + j];
|
||||
if (c >= 'A' && c <= 'Z') c += (char)('a' - 'A');
|
||||
if (c != keys[k][j]) { m = 0; break; }
|
||||
}
|
||||
if (m) return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 去重环:行已在环内则返回 0(不重复落盘),否则入环返回 1。 */
|
||||
static int seen_push(const char *line)
|
||||
{
|
||||
for (int i = 0; i < CRASH_SEEN_MAX; i++)
|
||||
if (g_seen[i][0] && strcmp(g_seen[i], line) == 0) return 0;
|
||||
snprintf(g_seen[g_seen_idx], CRASH_LINE_MAX, "%s", line);
|
||||
g_seen_idx = (g_seen_idx + 1) % CRASH_SEEN_MAX;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 扫描 dmesg 尾部新增:记录上次读取偏移(g_dmesg_prev_len),正常追加只扫新增段;
|
||||
* 环回/替换则全量扫并靠去重环防重复。 */
|
||||
static void scan_dmesg(void)
|
||||
{
|
||||
char *cur = read_dmesg();
|
||||
if (!cur) return;
|
||||
|
||||
const char *start = cur;
|
||||
if (g_dmesg_prev_len > 0) {
|
||||
size_t curlen = strlen(cur);
|
||||
if (curlen >= g_dmesg_prev_len &&
|
||||
memcmp(cur, g_dmesg_prev, g_dmesg_prev_len) == 0) {
|
||||
start = cur + g_dmesg_prev_len; /* 正常追加:只扫新增 */
|
||||
} else {
|
||||
start = cur; /* 环回/替换:全量扫 + 去重 */
|
||||
}
|
||||
}
|
||||
|
||||
const char *p = start;
|
||||
while (*p) {
|
||||
const char *nl = strchr(p, '\n');
|
||||
size_t linelen = nl ? (size_t)(nl - p) : strlen(p);
|
||||
if (linelen > 0) {
|
||||
char tmp[CRASH_LINE_MAX];
|
||||
size_t cl = linelen < CRASH_LINE_MAX - 1 ? linelen : CRASH_LINE_MAX - 1;
|
||||
memcpy(tmp, p, cl);
|
||||
tmp[cl] = '\0';
|
||||
while (cl > 0 && tmp[cl - 1] == '\r') tmp[--cl] = '\0'; /* 去尾 \r */
|
||||
if (is_crash_line(tmp)) {
|
||||
if (seen_push(tmp)) emit_event("dmesg", tmp);
|
||||
}
|
||||
}
|
||||
if (!nl) break;
|
||||
p = nl + 1;
|
||||
}
|
||||
|
||||
size_t curlen = strlen(cur);
|
||||
size_t keep = curlen < CRASH_DMESG_MAX - 1 ? curlen : CRASH_DMESG_MAX - 1;
|
||||
memcpy(g_dmesg_prev, cur, keep);
|
||||
g_dmesg_prev[keep] = '\0';
|
||||
g_dmesg_prev_len = keep;
|
||||
free(cur);
|
||||
}
|
||||
|
||||
/* ---- 信号 / socket ---- */
|
||||
|
||||
static void sig_handler(int sig)
|
||||
{
|
||||
g_shutdown = 1;
|
||||
char c = CRASH_SIGTERM_CHAR;
|
||||
ssize_t w = write(g_sigpipe[1], &c, 1);
|
||||
(void)w;
|
||||
(void)sig;
|
||||
}
|
||||
|
||||
static int setup_signals(void)
|
||||
{
|
||||
if (pipe(g_sigpipe) != 0) 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);
|
||||
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof sa);
|
||||
sa.sa_handler = sig_handler;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = 0;
|
||||
sigaction(SIGTERM, &sa, NULL);
|
||||
sigaction(SIGINT, &sa, NULL);
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int setup_socket(void)
|
||||
{
|
||||
unlink(g_sock);
|
||||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (fd < 0) { fprintf(stderr, "[meow_crash] socket: %s\n", strerror(errno)); return -1; }
|
||||
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) {
|
||||
fprintf(stderr, "[meow_crash] bind %s: %s\n", g_sock, strerror(errno));
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
(void)chmod(g_sock, 0666);
|
||||
if (listen(fd, 8) != 0) {
|
||||
fprintf(stderr, "[meow_crash] listen: %s\n", strerror(errno));
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
g_listen_fd = fd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run_daemon(void)
|
||||
{
|
||||
if (setup_signals() != 0) return 1;
|
||||
if (setup_socket() != 0) return 1;
|
||||
boot_ts(g_bootts, sizeof g_bootts);
|
||||
log_open_card(); /* 有卡即写卡;否则落 /tmp */
|
||||
log_cleanup_old();
|
||||
|
||||
fprintf(stderr, "[meow_crash] up: card=%s sock=%s bootts=%s log=%s\n",
|
||||
g_card, g_sock, g_bootts, g_on_card ? "card" : CRASH_TMP_LOG);
|
||||
|
||||
struct pollfd pfds[2];
|
||||
nfds_t nfds = 2;
|
||||
unsigned long long last_scan = 0;
|
||||
unsigned long long last_cleanup = 0;
|
||||
|
||||
while (!g_shutdown) {
|
||||
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, (int)CRASH_TICK_MAX_MS);
|
||||
if (r < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
fprintf(stderr, "[meow_crash] poll: %s\n", strerror(errno));
|
||||
break;
|
||||
}
|
||||
if (g_shutdown) break;
|
||||
|
||||
if (pfds[0].revents & POLLIN) {
|
||||
int cfd = accept(g_listen_fd, NULL, NULL);
|
||||
if (cfd >= 0) { handle_conn(cfd); close(cfd); }
|
||||
}
|
||||
if (pfds[1].revents & POLLIN) {
|
||||
char tmp[64];
|
||||
while (read(g_sigpipe[0], tmp, sizeof tmp) > 0) { /* drain */ }
|
||||
}
|
||||
|
||||
if (now_ms() - last_scan >= (unsigned long long)CRASH_SCAN_MS) {
|
||||
last_scan = now_ms();
|
||||
poll_card(); /* 卡出现 → 迁移 /tmp 缓冲 */
|
||||
scan_dmesg(); /* 增量捕获 dmesg 新增 oops/segfault */
|
||||
}
|
||||
if (now_ms() - last_cleanup >= (unsigned long long)CRASH_CLEANUP_MS) {
|
||||
last_cleanup = now_ms();
|
||||
log_cleanup_old(); /* 总量并入 logs/ 1GB 上限 */
|
||||
}
|
||||
}
|
||||
|
||||
if (g_listen_fd >= 0) close(g_listen_fd);
|
||||
unlink(g_sock);
|
||||
if (g_log_fd >= 0) close(g_log_fd);
|
||||
fprintf(stderr, "[meow_crash] shutdown\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- CLI:dump / tail(读日志文件,免鉴权,不依赖守护在线) ---- */
|
||||
|
||||
/* 定位本 boot 崩溃日志文件(卡优先,无卡/不存在回退 /tmp)。返回 0=找到可读文件。 */
|
||||
static int find_log_path(char *out, size_t n)
|
||||
{
|
||||
if (card_available()) {
|
||||
char ts[CRASH_BOOTTS_LEN];
|
||||
boot_ts(ts, sizeof ts);
|
||||
if (snprintf(out, n, "%s/%s/crash_%s.log", g_card, CRASH_LOGS_REL, ts) < (int)n
|
||||
&& access(out, R_OK) == 0) return 0;
|
||||
}
|
||||
if (access(CRASH_TMP_LOG, R_OK) == 0) { snprintf(out, n, "%s", CRASH_TMP_LOG); return 0; }
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 解析行首三字段:epoch / uptime / source,返回剩余 detail 指针。原地改写入参(写 NUL)。 */
|
||||
static int parse_parts(char *line, time_t *epoch, long *up, char *src, size_t srcn, char **rest)
|
||||
{
|
||||
char *p = line;
|
||||
char *sp = strchr(p, ' ');
|
||||
if (!sp) return -1;
|
||||
*sp = '\0';
|
||||
*epoch = (time_t)atol(p);
|
||||
p = sp + 1;
|
||||
sp = strchr(p, ' ');
|
||||
if (!sp) return -1;
|
||||
*sp = '\0';
|
||||
*up = atol(p);
|
||||
p = sp + 1;
|
||||
sp = strchr(p, ' ');
|
||||
if (!sp) return -1;
|
||||
*sp = '\0';
|
||||
snprintf(src, srcn, "%s", p);
|
||||
p = sp + 1;
|
||||
*rest = p;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 打印时把 epoch 转可读时间戳。 */
|
||||
static void print_reformatted(char *line)
|
||||
{
|
||||
time_t epoch;
|
||||
long up;
|
||||
char src[16], *rest;
|
||||
if (parse_parts(line, &epoch, &up, src, sizeof src, &rest) == 0) {
|
||||
struct tm tm;
|
||||
char tbuf[32];
|
||||
if (localtime_r(&epoch, &tm)) strftime(tbuf, sizeof tbuf, "%Y-%m-%d %H:%M:%S", &tm);
|
||||
else snprintf(tbuf, sizeof tbuf, "%lld", (long long)epoch);
|
||||
printf("%s up=%lds [%s] %s", tbuf, up, src, rest);
|
||||
} else {
|
||||
fputs(line, stdout);
|
||||
}
|
||||
}
|
||||
|
||||
static int cmd_dump(void)
|
||||
{
|
||||
char path[CRASH_PATH_MAX];
|
||||
if (find_log_path(path, sizeof path) != 0) { printf("(no crash events this boot)\n"); return 0; }
|
||||
printf("# crash log: %s\n", path);
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f) { printf("(cannot open %s)\n", path); return 0; }
|
||||
char line[CRASH_LINE_MAX];
|
||||
int cnt = 0;
|
||||
while (fgets(line, sizeof line, f)) { print_reformatted(line); cnt++; }
|
||||
fclose(f);
|
||||
if (cnt == 0) printf("(empty)\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_tail(int n)
|
||||
{
|
||||
char path[CRASH_PATH_MAX];
|
||||
if (find_log_path(path, sizeof path) != 0) { printf("(no crash events this boot)\n"); return 0; }
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f) { printf("(cannot open %s)\n", path); return 0; }
|
||||
if (n <= 0) n = 20;
|
||||
if (n > 100) n = 100;
|
||||
|
||||
enum { CAP = 100 };
|
||||
char ring[CAP][CRASH_LINE_MAX];
|
||||
int cnt = 0;
|
||||
char line[CRASH_LINE_MAX];
|
||||
while (fgets(line, sizeof line, f)) {
|
||||
snprintf(ring[cnt % CAP], CRASH_LINE_MAX, "%s", line);
|
||||
cnt++;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
int avail = cnt < CAP ? cnt : CAP; /* 环内实际存的条数 */
|
||||
int shown = cnt < CAP ? cnt : n;
|
||||
int startat;
|
||||
if (cnt < CAP) startat = 0;
|
||||
else startat = cnt - n;
|
||||
if (startat < 0) startat = 0;
|
||||
if (shown > avail) shown = avail;
|
||||
|
||||
char buf[CRASH_LINE_MAX];
|
||||
for (int i = 0; i < shown; i++) {
|
||||
snprintf(buf, sizeof buf, "%s", ring[(startat + i) % CAP]);
|
||||
print_reformatted(buf);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void usage(const char *a0)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"usage: %s [options] [command]\n"
|
||||
" commands: dump | tail [N] (root 免鉴权;无命令=启动守护进程)\n"
|
||||
" options:\n"
|
||||
" --card <path> TF 卡挂载点 (default %s)\n"
|
||||
" --sock <path> 崩溃上报 socket (default %s)\n"
|
||||
" -h, --help\n",
|
||||
a0, CRASH_CARD_DEFAULT, CRASH_SOCK_DEFAULT);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *cmd_tok[8];
|
||||
int cmd_n = 0;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
const char *a = argv[i];
|
||||
if (strcmp(a, "--card") == 0 && i + 1 < argc) safe_strcpy(g_card, sizeof g_card, argv[++i]);
|
||||
else if (strcmp(a, "--sock") == 0 && i + 1 < argc) safe_strcpy(g_sock, sizeof g_sock, argv[++i]);
|
||||
else if (strcmp(a, "-h") == 0 || strcmp(a, "--help") == 0) { usage(argv[0]); return 0; }
|
||||
else if (a[0] == '-') { usage(argv[0]); return 2; }
|
||||
else if (cmd_n < 8) cmd_tok[cmd_n++] = a;
|
||||
}
|
||||
|
||||
const char *e;
|
||||
if ((e = getenv("MEOW_CRASH_CARD")) && e[0]) safe_strcpy(g_card, sizeof g_card, e);
|
||||
if ((e = getenv("MEOW_CRASH_SOCK")) && e[0]) safe_strcpy(g_sock, sizeof g_sock, e);
|
||||
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
|
||||
if (cmd_n == 0) return run_daemon(); /* svcd 空参拉起 → 前台常驻 */
|
||||
if (strcmp(cmd_tok[0], "dump") == 0) return cmd_dump();
|
||||
if (strcmp(cmd_tok[0], "tail") == 0) {
|
||||
int n = 20;
|
||||
if (cmd_n > 1) n = atoi(cmd_tok[1]);
|
||||
return cmd_tail(n);
|
||||
}
|
||||
usage(argv[0]);
|
||||
return 2;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# meow_crash.conf — meow_crash 板载服务配置(services.d,trigger=boot)
|
||||
# 部署目标:/usr/data/meow/services.d/meow_crash.conf(Data 区,首启预置)
|
||||
#
|
||||
# 字段语义与 meow_svcd 一致:
|
||||
# exec/args/enabled/restart/backoff_ms/max_restart/trigger/cap
|
||||
# - trigger=boot:板载服务恒存在即启动(拔卡保留)。
|
||||
# - restart=on-fail:崩溃后退避重启(默认 backoff 2000ms,最多 5 次)。
|
||||
# - caps:本机平台未定,保留为空。
|
||||
exec=/usr/bin/meow_crash
|
||||
args=
|
||||
enabled=1
|
||||
restart=on-fail
|
||||
backoff_ms=2000
|
||||
max_restart=5
|
||||
trigger=boot
|
||||
cap=
|
||||
@@ -0,0 +1,81 @@
|
||||
# meow_crash — MeowOS 系统级崩溃捕获(二期组件)
|
||||
|
||||
> 状态:设计落盘 + 实现(agent 任务)。`meow_crash` 是 svcd 托管的小守护,负责把
|
||||
> svcd 服务的异常退出与内核 oops/应用崩溃记录到日志,供事后排查。
|
||||
> 定位见 FRAMEWORK_DESIGN §10(Supervisor 树 / 崩溃隔离)、APPS.md §4.5(统一日志系统)、
|
||||
> devdocs/plan/SYS_BASELINE.md(svcd 子进程化,监护挂点)、devdocs/plan/APP_LOG_MANAGER.md。
|
||||
|
||||
## 1. 角色
|
||||
|
||||
`meow_crash` = 崩溃事件**记录器**(只读一条上报消息落盘,无共享内存、无 dlopen),
|
||||
由 svcd 以 `fork+exec+setpgid` 托管(`/usr/bin/meow_crash` + `services.d/meow_crash.conf`
|
||||
trigger=boot)。svcd 托管服务均为前台常驻(svcd waitpid),故 meow_crash
|
||||
**不 daemonize**,svcd 崩则它是子进程被 waitpid 感知;它崩则 svcd 按 `on-fail` 重启它,
|
||||
且其崩溃不影响 svcd(崩溃隔离)。
|
||||
|
||||
## 2. 捕获源
|
||||
|
||||
### ① svcd 服务异常退出(主源)
|
||||
svcd 在 `reap_children()` 检测到**异常退出**(非 0 退出码 / 被信号杀,且非受控
|
||||
stop/restart)时,经 AF_UNIX `/tmp/meow_crash.sock` 发**单行**文本(best-effort):
|
||||
|
||||
```
|
||||
SVC <service_name> <exit|sig> <num> <epoch_sec> <uptime_sec>
|
||||
```
|
||||
|
||||
- 全程非阻塞:客户端 socket 置 O_NONBLOCK;connect 失败即放弃(可短 poll POLLOUT
|
||||
≤150ms 用于本地 listen backlog 排空,绝不重试)。**svcd 绝不被此带崩。**
|
||||
- meow_crash 不在线 → 该条丢弃(符合「若 crash 不在线则跳过」)。
|
||||
|
||||
### ② 内核 oops / 应用崩溃(辅助,v1 不强求)
|
||||
周期性(1s)`dmesg` 抓取,记录上次读取偏移(`g_dmesg_prev_len`),只扫**新增**段,
|
||||
捕获含 `segfault`/`oops`(大小写不敏感)的行。环回/替换时全量扫并靠去重环防重复。
|
||||
v1 不扫描 `/tmp` core 转储(不强求,留作后续)。
|
||||
|
||||
## 3. 落盘
|
||||
|
||||
- 有卡:`<card>/MeowOS/logs/crash_<bootts>.log` 追加写。`<bootts>` 复用
|
||||
`/tmp/meow_bootts`(launcher 缓存的 boot 时间戳,`meow_` 前缀剥掉),同一 boot
|
||||
会话跨进程共用;读不到则按 墙钟-uptime 计算并缓存。
|
||||
- 无卡/写失败:`/tmp/meow_crash.log`(tmpfs,256KB 滚动,超出即重建)。
|
||||
- 卡出现:`poll_card()` 把 /tmp 缓冲整段搬入卡日志后删除(对齐 launcher
|
||||
`log_poll_card` 语义)。
|
||||
- 总量:`log_cleanup_old()` 统计 logs/ 下 `meow_*` 与 `crash_*`,超过 1GB 按 mtime
|
||||
删最旧(与 launcher 的 LOG_SIZE_LIMIT 同纪律,launcher 只管 meow_*,本守护并管)。
|
||||
|
||||
一行一条格式:
|
||||
```
|
||||
<epoch_sec> <uptime_sec> <source> <detail...>
|
||||
```
|
||||
- `source`:`svc`(来自 svcd)或 `dmesg`(内核行)。
|
||||
- `detail`:`<name> exit=<code>` / `<name> signal=<sig>` / 内核行原文。
|
||||
|
||||
## 4. CLI(root 免鉴权,读文件,不依赖守护在线)
|
||||
|
||||
| 命令 | 说明 |
|
||||
|---|---|
|
||||
| `meow_crash dump` | 打印本 boot 崩溃清单(卡 `crash_<bootts>.log`;无卡/无文件回退 /tmp) |
|
||||
| `meow_crash tail [N]` | 打印最近 N 条(默认 20,上限 100) |
|
||||
| `meow_crash`(无命令) | svcd 拉起 → 前台常驻守护 |
|
||||
|
||||
`dump`/`tail` 把 epoch 转可读 `YYYY-MM-DD HH:MM:SS` 展示。
|
||||
|
||||
## 5. 测试钩子(宿主集成测试)
|
||||
|
||||
| 钩子 | 作用 |
|
||||
|---|---|
|
||||
| `--card <path>` / `MEOW_CRASH_CARD` | 覆写卡挂载点(宿主指向临时目录即走「有卡」路径) |
|
||||
| `--sock <path>` / `MEOW_CRASH_SOCK` | 覆写上报 socket |
|
||||
| `MEOW_CRASH_NOCARD=1` | 强制无卡(验证 /tmp 回退 + 后续迁移) |
|
||||
| `MEOW_CRASH_DMESG_FILE=<path>` | 从文件读「假 dmesg」(宿主注入内核行) |
|
||||
|
||||
## 6. 边界 / 诚实标注
|
||||
|
||||
- **v1 不扫描 core 转储**(任务:「不强求」)。当前只做 svcd 上报 + dmesg 增量。
|
||||
- **svcd 上报的 skip 语义**:受控 stop(stop_requested)与 restart 命令
|
||||
(restart_pending)的退出**不**上报,只记真实异常退出(避免把管理员停服务当崩溃)。
|
||||
- **dmesg 捕获**依赖设备 `dmesg` 可用(busybox;root)。宿主用 `MEOW_CRASH_DMESG_FILE`
|
||||
注入。首扫会捕获本 boot 已存在的 crash 行(内核环在重启时清空,不会跨 boot 混入)。
|
||||
- **固件打包需知 / 板载 conf 预置**:本任务按约束未改 `S89meow_svcd` 与固件脚本。
|
||||
需在**打包/首启预置**阶段把 `patch_v2/meow_crash/meow_crash.conf` 落到
|
||||
`/usr/data/meow/services.d/meow_crash.conf`(参照 S89 对 mdnsd/perf/power 的预置方式)。
|
||||
Executable
BIN
Binary file not shown.
@@ -94,6 +94,22 @@ EOF
|
||||
chmod 644 /usr/data/meow/services.d/meow_svc_power.conf
|
||||
echo -n "provisioned power.conf "
|
||||
fi
|
||||
if [ -f /usr/data/meow/services.d/meow_crash.conf ]; then
|
||||
echo -n "crash.conf exists "
|
||||
else
|
||||
cat > /usr/data/meow/services.d/meow_crash.conf <<'EOF'
|
||||
exec=/usr/bin/meow_crash
|
||||
args=
|
||||
enabled=1
|
||||
restart=on-fail
|
||||
backoff_ms=2000
|
||||
max_restart=5
|
||||
trigger=boot
|
||||
cap=
|
||||
EOF
|
||||
chmod 644 /usr/data/meow/services.d/meow_crash.conf
|
||||
echo -n "provisioned crash.conf "
|
||||
fi
|
||||
else
|
||||
echo -n "skip svc confs (/usr/data/meow not writable) "
|
||||
fi
|
||||
|
||||
Binary file not shown.
@@ -97,6 +97,8 @@
|
||||
#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"
|
||||
@@ -532,6 +534,65 @@ static void sig_term(int sig)
|
||||
(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)
|
||||
{
|
||||
@@ -545,7 +606,13 @@ static void reap_children(void)
|
||||
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);
|
||||
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();
|
||||
}
|
||||
|
||||
Binary file not shown.
Reference in new issue
Block a user