/* 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_.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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* ---- 默认路径 / 常量 ---- */ #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; /* 收纳 meow_/crash_/perf_/hang_(perf_=RMM 基线、hang_=卡死快照,同 logs/ 1GB 纪律)。 */ if (strncmp(nm, "meow_", 5) != 0 && strncmp(nm, "crash_", 6) != 0 && strncmp(nm, "perf_", 5) != 0 && strncmp(nm, "hang_", 5) != 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_.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 */ 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 TF 卡挂载点 (default %s)\n" " --sock 崩溃上报 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; }