Files
MeowOS-For-R3P2/patch_v2/meow_crash/meow_crash.c
T
AskaEth 3d70a3dd2c feat: M6.10–M6.19 批量收编 + M6.19 推卡验收
- M7 launcher 重写 5 期(虚拟化宫格/meow_theme 单一权威/横幅动画/自定义 PIN 键盘)
- OOM 通用化(cachegov 上收 SDK)、自动切歌根治、meowpanel jv_free 泄漏修复、Hello 滚动卡顿修复
- ncm 歌词 UI / 媒体通知封面 / 封面分辨率 640
- 5 bug 修复:ncm 后台化 UI 叠层+点击穿透、再进 ncm 桌面叠层、播放页新旧 UI 叠层抢刷脏区(meow_ui_gfx 恢复边沿整帧重画)、PIN 亮屏亮度跳(keys.c meow_panel_on 按 settings_brightness_clamped 恢复)、通知中心封面显歌单封面(cover.c save_cover_raw 加 is_song + 命中块守卫)
- 四 App 统一重链新 meow_ui.a 51caee4a;M6.19=0.8.19 固件 2d2331f2 推卡自刷验收全绿
2026-09-03 12:16:01 +08:00

679 lines
24 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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;
/* 收纳 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_<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;
}