Files
MeowOS-For-R3P2/patch_v2/meowpanel/src/mp_logs.c
T

242 lines
11 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.
/* mp_logs.c — 日志域:/api/logs 列表 + 读 + 崩溃汇总(meow_crash 落盘格式)。 */
#include "mp.h"
#define CRASH_TAIL_MAX (256u * 1024u) /* 崩溃日志单次读取上限 */
#define CRASH_ENT_MAX 64 /* 实体汇总上限 */
#define CRASH_DMESG_TAIL 5 /* dmesg 保留最近行数 */
const char *log_group(const char *name)
{
if (!strncmp(name, "crash_", 6)) return "crash";
if (!strncmp(name, "ams_", 4)) return "ams";
if (!strncmp(name, "audio_monitor_", 14)) return "audio_monitor";
if (!strncmp(name, "net_", 4)) return "net";
if (!strncmp(name, "power_", 6)) return "power";
if (!strncmp(name, "meow_", 5)) return "system";
return "app";
}
int api_logs_list(int sock)
{
if (!tf_present()) return send_err(sock, 503, "NO_CARD", "未识别到 TF 卡");
DIR *d = opendir(LOGS_DIR);
if (!d) return send_err(sock, 404, "NOT_FOUND", "日志目录不存在");
sbuf_t s = {0};
sA(&s, "{\"dir\":");
sA_str(&s, LOGS_DIR);
sA(&s, ",\"files\":[");
int first = 1;
struct dirent *e;
while ((e = readdir(d))) {
if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) continue;
if (is_sensitive_name(e->d_name)) continue;
char full[1024];
snprintf(full, sizeof full, "%s/%s", LOGS_DIR, e->d_name);
struct stat st;
if (stat(full, &st) < 0 || !S_ISREG(st.st_mode)) continue;
if (!first) sA(&s, ",");
first = 0;
sA(&s, "{\"name\":");
sA_str(&s, e->d_name);
sA(&s, ",\"size\":%lld,\"mtime\":%lld,\"group\":", (long long)st.st_size, (long long)st.st_mtime);
sA_str(&s, log_group(e->d_name));
sA(&s, "}");
}
closedir(d);
sA(&s, "]}");
int rc = send_json(sock, 200, s.b ? s.b : "");
sbuf_free(&s);
return rc;
}
int api_logs_read(int sock, const req_t *r)
{
if (!tf_present()) return send_err(sock, 503, "NO_CARD", "未识别到 TF 卡");
char file[512], lines[16];
if (!qparam(r->query, "file", file, sizeof file))
return send_err(sock, 400, "BAD_REQUEST", "缺 file 参数");
int maxlines = 200;
if (qparam(r->query, "lines", lines, sizeof lines)) {
int n = atoi(lines);
if (n > 0) maxlines = n;
if (maxlines > LOG_MAX_LINES) maxlines = LOG_MAX_LINES;
}
/* 防逃逸:file 相对 LOGS_DIR,禁止 .. 与绝对路径 */
if (file[0] == '/' || strstr(file, "..")) return send_err(sock, 403, "FORBIDDEN", "文件路径逃逸");
if (is_sensitive_name(strrchr(file, '/') ? strrchr(file, '/') + 1 : file))
return send_err(sock, 403, "FORBIDDEN", "敏感文件");
char full[1024];
snprintf(full, sizeof full, "%s/%s", LOGS_DIR, file);
int fd = open(full, O_RDONLY);
if (fd < 0) return send_err(sock, 404, "NOT_FOUND", "日志文件不存在");
struct stat st;
if (fstat(fd, &st) < 0 || !S_ISREG(st.st_mode)) { close(fd); return send_err(sock, 404, "NOT_FOUND", "非普通文件"); }
/* 逐行读,保留末尾 maxlines 行 */
const char *buf = malloc((size_t)st.st_size + 1);
if (!buf) { close(fd); return send_err(sock, 500, "INTERNAL", "内存不足"); }
ssize_t n = read(fd, (char *)buf, (size_t)st.st_size);
close(fd);
if (n < 0) { free((void *)buf); return send_err(sock, 500, "INTERNAL", "读日志失败"); }
sbuf_t s = {0};
sA(&s, "{\"file\":");
sA_str(&s, file);
sA(&s, ",\"lines\":[");
/* 直接扫一遍,保留末尾 maxlines 行文本 */
const char *p = buf;
const char *end = buf + n;
int total = 0, truncated = 0;
int gotlines = 0;
int linecnt = 0;
for (const char *c = buf; c < end; c++) if (*c == '\n') linecnt++;
if (linecnt > 0 && end[-1] != '\n') linecnt++;
int startrow = linecnt - maxlines;
if (startrow < 0) startrow = 0;
if (startrow > 0) truncated = 1;
int rowidx = 0;
p = buf;
while (p < end) {
const char *nl = memchr(p, '\n', (size_t)(end - p));
const char *line_end = nl ? nl : end;
if (rowidx >= startrow) {
if (gotlines) sA(&s, ",");
/* sA_str_n:转义 + UTF-8 消毒(任意长度安全;勿 sA("%s")——单次超 511B 会截断坏 JSON) */
sA_str_n(&s, p, (size_t)(line_end - p));
gotlines++;
}
rowidx++;
if (!nl) break;
p = nl + 1;
}
total = linecnt;
free((void *)buf);
sA(&s, "],\"total\":%d,\"truncated\":%s}", total, truncated ? "true" : "false");
int rc = send_json(sock, 200, s.b ? s.b : "");
sbuf_free(&s);
return rc;
}
int api_logs_crash(int sock)
{
if (!tf_present()) return send_err(sock, 503, "NO_CARD", "未识别到 TF 卡");
/* 找最新 crash_*.log(按 mtime);无 → 空汇总(不 404,不谎报)。 */
DIR *d = opendir(LOGS_DIR);
if (!d) return send_json(sock, 200, "{\"file\":\"\",\"count\":0,\"dmesg_count\":0,\"dmesg_tail\":[],\"entities\":[]}");
char best[512] = ""; long best_mt = -1;
struct dirent *e;
while ((e = readdir(d))) {
if (strncmp(e->d_name, "crash_", 6) != 0 || e->d_name[0] == '.') continue;
char full[1024];
snprintf(full, sizeof full, "%s/%s", LOGS_DIR, e->d_name);
struct stat st;
if (stat(full, &st) < 0 || !S_ISREG(st.st_mode)) continue;
if (st.st_mtime > best_mt) { best_mt = st.st_mtime; snprintf(best, sizeof best, "%s", e->d_name); }
}
closedir(d);
if (!best[0])
return send_json(sock, 200, "{\"file\":\"\",\"count\":0,\"dmesg_count\":0,\"dmesg_tail\":[],\"entities\":[]}");
char full[1024];
snprintf(full, sizeof full, "%s/%s", LOGS_DIR, best);
int fd = open(full, O_RDONLY);
struct stat st;
if (fd < 0 || fstat(fd, &st) < 0 || !S_ISREG(st.st_mode)) { if (fd >= 0) close(fd); return send_err(sock, 500, "INTERNAL", "崩溃日志不可读"); }
off_t off = st.st_size > (off_t)CRASH_TAIL_MAX ? st.st_size - (off_t)CRASH_TAIL_MAX : 0;
size_t buflen = (size_t)(st.st_size - off);
if (buflen == 0) { close(fd); return send_json(sock, 200, "{\"file\":\"\",\"count\":0,\"dmesg_count\":0,\"dmesg_tail\":[],\"entities\":[]}"); }
if (off) (void)lseek(fd, off, SEEK_SET);
char *buf = malloc(buflen + 1);
if (!buf) { close(fd); return send_err(sock, 500, "INTERNAL", "内存不足"); }
ssize_t n = read(fd, buf, buflen);
close(fd);
if (n < 0) { free(buf); return send_err(sock, 500, "INTERNAL", "读崩溃日志失败"); }
buf[n] = 0;
/* 每实体聚合(svc 行);dmesg 单独计数 + 留最近几行。
* 有界缓冲(det ≤ 127 字符)保证 snprintf 到 name/detail 不越界且 -Wformat-truncation 零告警。 */
struct { char name[128]; int count; long long last_epoch; char kind[8]; int code; char detail[160]; } ents[CRASH_ENT_MAX];
int entn = 0, svc_count = 0, dmesg_count = 0;
char dmesg_tail[CRASH_DMESG_TAIL][128]; int dmesg_tail_n = 0;
char *ln = buf;
while (*ln && (*ln == '\n')) ln++;
while (*ln) {
char *nl = strchr(ln, '\n');
if (nl) *nl = 0;
long long epoch = 0, up = 0; char src[16] = "", det[128] = "";
int f = sscanf(ln, "%lld %lld %15s %127[^\n]", &epoch, &up, src, det);
if (f >= 3) {
if (!strcmp(src, "svc")) {
svc_count++;
/* det = `<name> exit=<code>` 或 `<name> signal=<sig>`;name 为第一 token(就地切分) */
char name[128] = "", kind[8] = ""; int code = 0, k2 = 0;
char *sp = strchr(det, ' ');
if (sp) {
*sp = 0;
snprintf(name, sizeof name, "%s", det); /* det ≤127 → name[128] 不截断 */
char *kp = sp + 1;
if (!strncmp(kp, "exit=", 5)) { snprintf(kind, sizeof kind, "exit"); code = atoi(kp + 5); k2 = 1; }
else if (!strncmp(kp, "signal=", 7)) { snprintf(kind, sizeof kind, "sig"); code = atoi(kp + 7); k2 = 1; }
else { snprintf(kind, sizeof kind, "crash"); k2 = 0; }
} else {
snprintf(name, sizeof name, "%s", det);
snprintf(kind, sizeof kind, "crash");
}
/* 聚合到 ents(按 name 匹配;超限则丢弃剩余陌生实体,仅计入总数) */
int found = -1;
for (int i = 0; i < entn; i++) if (!strcmp(ents[i].name, name)) { found = i; break; }
if (found < 0 && entn < CRASH_ENT_MAX) {
found = entn++;
snprintf(ents[found].name, sizeof ents[found].name, "%s", name);
ents[found].count = 0; ents[found].last_epoch = 0;
ents[found].kind[0] = 0; ents[found].code = 0; ents[found].detail[0] = 0;
}
if (found >= 0) {
ents[found].count++;
if (epoch >= ents[found].last_epoch) {
ents[found].last_epoch = epoch;
snprintf(ents[found].kind, sizeof ents[found].kind, "%s", k2 ? kind : "crash");
ents[found].code = code;
snprintf(ents[found].detail, sizeof ents[found].detail, "%s", det);
}
}
} else if (!strcmp(src, "dmesg")) {
dmesg_count++;
if (dmesg_tail_n < CRASH_DMESG_TAIL) snprintf(dmesg_tail[dmesg_tail_n++], sizeof dmesg_tail[0], "%s", det);
else { /* 前移一行,末尾放最新 */ for (int i = 0; i < dmesg_tail_n - 1; i++) memcpy(dmesg_tail[i], dmesg_tail[i + 1], sizeof dmesg_tail[0]); snprintf(dmesg_tail[dmesg_tail_n - 1], sizeof dmesg_tail[0], "%s", det); }
}
}
if (nl) { *nl = '\n'; ln = nl + 1; } else break;
while (*ln == '\n') ln++;
}
free(buf);
sbuf_t s = {0};
sA(&s, "{\"file\":");
sA_str(&s, best);
sA(&s, ",\"count\":%d,\"dmesg_count\":%d,\"dmesg_tail\":[", svc_count, dmesg_count);
for (int i = 0; i < dmesg_tail_n; i++) { if (i) sA(&s, ","); sA_str(&s, dmesg_tail[i]); }
sA(&s, "],\"entities\":[");
int first = 1;
for (int i = 0; i < entn; i++) {
if (!ents[i].count) continue;
if (!first) sA(&s, ",");
first = 0;
sA(&s, "{\"name\":");
sA_str(&s, ents[i].name);
sA(&s, ",\"source\":\"svc\",\"count\":%d,\"last_epoch\":%lld,\"last_kind\":", ents[i].count, ents[i].last_epoch);
sA_str(&s, ents[i].kind[0] ? ents[i].kind : "crash");
sA(&s, ",\"last_code\":%d,\"last_detail\":", ents[i].code);
sA_str(&s, ents[i].detail);
/* 最近崩溃时间(epoch → 可读);strftime 本地时区,失败回退 epoch */
char tbuf[32] = ""; time_t t = (time_t)ents[i].last_epoch;
struct tm tmv;
if (ents[i].last_epoch > 0 && localtime_r(&t, &tmv)) strftime(tbuf, sizeof tbuf, "%Y-%m-%d %H:%M:%S", &tmv);
sA(&s, ",\"last_time\":");
sA_str(&s, tbuf);
sA(&s, "}");
}
sA(&s, "]}");
int rc = send_json(sock, 200, s.b ? s.b : "");
sbuf_free(&s);
return rc;
}