fix(launcher): App暴毙后卡死根因——execpipe 裸阻塞 read 改 poll+2s超时(agent-214) + ams_drain_stdout 64KB有界 + reap_wait兜底5处裸waitpid + watchdog关继承fd + font_sha256_file popen改有界 + 既有警告清理; fix(ncm): cookie盐不随包名(旧登录态可解)

launcher 97c1b222→333d6f53; ncm→1124dbf2; 宿主harness stall/ok/orphan全过
This commit is contained in:
MeowOS committed 2026-08-31 13:36:23 +08:00
1 parent efbd1d5a2f
commit 228e9e79e0
7 files changed
+86 -48

No files matched your search

+63 -9
View File
@@ -124,13 +124,26 @@ static void ams_out_line(const struct app *a, pid_t pid, const char *line)
a->id, (int)pid, (long)time(NULL), ams_uptime(), line);
}
/* 排空读端全部可用字节,按行切分转发;超长行截断标记。 */
/* 单次排空上限:App 被 OOM/信号杀后,其子孙进程可能仍继承持有 out_pipe 写端
* (fd1/fd2),并继续向管道写数据(读端永远不会 EOF)。此时若排空不设限会一直
* 忙转(主循环卡在 ams_drain_stdout,UI 假死)。设上界保证「无论孤儿怎么写,
* 一次排空最多处理 64KB」即放行,主循环依旧能回主界面。 */
#define AMS_DRAIN_MAX (64 * 1024)
/* exec 状态确认超时:fork 后 child 到 execve 的等待上界。超过即视为 exec 正常进行,
* 放行到 waitpid 循环(见 launch_app_chain)。有界,杜绝裸阻塞 read 的 pipe_wait 死锁。 */
#define EXEC_CONFIRM_MS 2000
/* 排空读端尽可能多的字节,按行切分转发;超长行截断标记。有界:单次调用最多
* 读 AMS_DRAIN_MAX 字节(见上)。读端已 O_NONBLOCK;App 死且写端全关 → read 返 0=EOF。 */
static void ams_drain_stdout(const struct app *a, int fd, pid_t pid)
{
char tmp[2048];
ssize_t n;
size_t drained = 0;
while ((n = read(fd, tmp, sizeof tmp)) > 0) {
g_ams_out_had = 1;
drained += (size_t)n;
for (ssize_t i = 0; i < n; i++) {
char c = tmp[i];
if (c == '\n') {
@@ -149,6 +162,24 @@ static void ams_drain_stdout(const struct app *a, int fd, pid_t pid)
g_ams_out_n = 0;
}
}
if (drained >= AMS_DRAIN_MAX) break; /* 有界:孤儿持续写也不卡死主循环 */
}
}
/* waitpid 兜底:SIGKILL 后回收孩子。绝不裸阻塞 waitpid(pid, …, 0)——孩子若因 I/O 卡在
* D-state(不可杀不可收)会无限阻塞 → launcher 卡死。改为 WNOHANG 轮询 + 上界,
* 超时即放弃(孩子交给 init/subreaper 稍后收割,launcher 无条件回主界面)。
* 返回 1=已回收。超时上限 REAP_WAIT_MS。 */
#define REAP_WAIT_MS 2000
static int reap_wait(pid_t pid, int *status)
{
unsigned long long t0 = now_ms();
for (;;) {
int r = waitpid(pid, status, WNOHANG);
if (r == pid) return 1;
if (r < 0 && errno != EINTR) return 0;
if (now_ms() - t0 >= REAP_WAIT_MS) return 0;
usleep(5000);
}
}
@@ -1340,15 +1371,32 @@ static int launch_app_chain(struct app *a, const char *chain)
if (out_fd >= 0) {
int fl = fcntl(out_fd, F_GETFL, 0);
if (fl >= 0) fcntl(out_fd, F_SETFL, fl | O_NONBLOCK);
else fcntl(out_fd, F_SETFL, O_NONBLOCK); /* 兜底:绝不因 F_GETFL 失败退回阻塞读 */
}
/* M2.84-bugfix:exec 状态确认绝不能是裸阻塞 read。child 在 execve 前要做
* meow_mkdir_p(app_log_dir) / 字库定向 / 设置读取等预处理;TF 卡慢/挂起时 child 迟迟
* 到不了 exec,execpipe[1](CLOEXEC)写端一直开着 → 裸 read 永久阻塞在 pipe_wait
* (App 暴毙后 launcher 卡死、电源键无响应、UI 全死)。改为非阻塞 + 有界 poll:
* 超时即视为「exec 正常进行」放行到 waitpid 循环——若真 exec 失败 child 会写 errno
* 后 _exit(127),waitpid 循环照常回收并上报非 0 退出,不再漏错误、更不再卡死。 */
fcntl(execpipe[0], F_SETFL, O_NONBLOCK); /* 读端 O_NONBLOCK;写端 CLOEXEC 由 child 持有 */
int exe_err = 0;
ssize_t er = read(execpipe[0], &exe_err, sizeof exe_err);
int exec_failed = 0; /* 1=已确认 exec 失败(拿到 errno) */
{
struct pollfd epf; epf.fd = execpipe[0]; epf.events = POLLIN; epf.revents = 0;
int pr = poll(&epf, 1, EXEC_CONFIRM_MS);
if (pr > 0 && (epf.revents & (POLLIN | POLLHUP))) {
ssize_t er = read(execpipe[0], &exe_err, sizeof exe_err);
if (er > 0) exec_failed = 1; /* child 写回 exec 失败 errno */
/* er==0(EOF)= exec 成功,写端 CLOEXEC 关闭;er<0(EAGAIN 竞态)= 未定,放行 */
}
}
close(execpipe[0]);
if (er > 0) {
if (exec_failed) {
logmsg("launch FAILED execve(%s): %s (errno %d)", a->exec,
strerror(exe_err), exe_err);
int st = 0;
waitpid(pid, &st, 0); /* reap the failed child */
reap_wait(pid, &st); /* reap the failed child(有界) */
if (out_fd >= 0) close(out_fd);
return -1;
}
@@ -1372,6 +1420,12 @@ static int launch_app_chain(struct app *a, const char *chain)
if (wd1[0]) {
wpid = fork();
if (wpid == 0) {
/* M2.84-bugfix 看门狗不持有不该有的 fd:fork 继承的 launcher 管道读端
* out_fd(App stdout/stderr 捕获读端)与日志 fd log_fd 全部关闭——execve
* 后只带它自己要的(MEOW_LOG_FILE/MEOW_LOG_DIR env 另开自身 log)。
* 防看门狗成为某条管道的读端/写端持有者,干扰 App 死后 EOF 判定。 */
if (out_fd >= 0) close(out_fd);
if (log_fd >= 0) close(log_fd);
/* pass the same boot log file + dir so all MeowAPPs components
* log to one place */
char *argv[] = { wd1, pidstr, NULL };
@@ -1449,7 +1503,7 @@ static int launch_app_chain(struct app *a, const char *chain)
logmsg("ams: kill %s -> SIGTERM", a->name);
kill(pid, SIGTERM);
usleep(300000);
if (waitpid(pid, &status, WNOHANG) != pid) { kill(pid, SIGKILL); waitpid(pid, &status, 0); }
if (waitpid(pid, &status, WNOHANG) != pid) { kill(pid, SIGKILL); reap_wait(pid, &status); }
ams_status_valid = 1;
break;
}
@@ -1458,7 +1512,7 @@ static int launch_app_chain(struct app *a, const char *chain)
nav_clean_exit = 1;
kill(pid, SIGTERM);
usleep(300000);
if (waitpid(pid, &status, WNOHANG) != pid) { kill(pid, SIGKILL); waitpid(pid, &status, 0); }
if (waitpid(pid, &status, WNOHANG) != pid) { kill(pid, SIGKILL); reap_wait(pid, &status); }
ams_status_valid = 1;
break;
}
@@ -1477,7 +1531,7 @@ static int launch_app_chain(struct app *a, const char *chain)
if (nr != 0) {
/* App 会话中崩溃(overlay 内 proc_is_zombie 探测):回收僵尸取真实状态,
* 走统一应用退出路径(不 SIGCONT 死进程、不恢复死帧;launcher 不崩)。 */
waitpid(pid, &status, 0);
reap_wait(pid, &status); /* 有界,防 D-state 挂死 */
ams_status_valid = 1;
break;
}
@@ -1489,7 +1543,7 @@ static int launch_app_chain(struct app *a, const char *chain)
logmsg("hotplug: card removed during app run -> SIGTERM app ");
kill(pid, SIGTERM);
usleep(300000);
if (waitpid(pid, &status, WNOHANG) != pid) { kill(pid, SIGKILL); waitpid(pid, &status, 0); }
if (waitpid(pid, &status, WNOHANG) != pid) { kill(pid, SIGKILL); reap_wait(pid, &status); }
ams_status_valid = 1;
break;
}
@@ -1554,7 +1608,7 @@ static int launch_app_chain(struct app *a, const char *chain)
kill(pid, SIGTERM);
usleep(500000);
kill(pid, SIGKILL);
waitpid(pid, &status, 0);
reap_wait(pid, &status); /* 有界,防 D-state 挂死 */
ams_status_valid = 1;
break;
}
+2 -15
View File
@@ -98,8 +98,8 @@ int settings_cache_registered = 0; /* M2.24:监听只注册一次 */
* Volume"(numid 1/2,0-255)→ codec 驱动 cs43198_vol_*_put → I2C 直写 CS43198
* 寄存器(0x9000-0x9002/0x7000-0x7001,双片 L/R)。我们复刻同路径(amixer
* cset = SNDRV_CTL_IOCTL_ELEM_WRITE),0-100 → 0-255 映射直写两声道。
* 读回不可用(驱动 get 为 dummy 恒 0;/dev/i2c-3 被驱动占用),故 meow_vol_get()
* 返回我们写入的跟踪值(= 真实 DAC 映射值)。 */
* 读回不可用(驱动 get 为 dummy 恒 0;/dev/i2c-3 被驱动占用),故系统音量以
* settings_volume(写入 DAC 的映射值)为权威跟踪值。 */
void meow_dac_vol_write(int pct)
{
int dac = pct * 255 / 100;
@@ -142,14 +142,6 @@ int vol_clamp(int v)
return v;
}
static void settings_volume_set(int v)
{
v = vol_clamp(v); /* 0..MEOW_VOL_MAX,再 clamp 到上限 */
meow_settings_set_int("audio.volume", v); /* clamp 集中在 backend;listener 同步缓存 */
meow_dac_vol_write(settings_volume); /* 每次变更即直写真实 DAC(CS43198) */
meow_settings_save(); /* 单点变更即落盘 */
}
int settings_volume_get(void) { return settings_volume; }
int vol_ramp_dir = 0; /* +1 上 / -1 下 / 0 停 */
@@ -217,8 +209,3 @@ int vol_ramp_tick(int key_held)
void settings_volume_adj(int delta) { vol_ramp_begin(delta); }
/* MeowOS 音量 API:get 返回当前(= 写入 DAC 的映射值);set 写真实 DAC + 持久化。 */
static void meow_vol_set(int pct) { settings_volume_set(pct); }
static int meow_vol_get(void) { return settings_volume; }
+16 -17
View File
@@ -353,6 +353,7 @@ void meow_font_reload_multifont(void)
/* 计算文件 sha256(走 busybox/宿主 sha256sum;输出小写 64hex)。返回 0 成功。 */
static int font_sha256_file(const char *path, char *out, int olen)
{
if (!out || olen <= 0) return -1;
out[0] = 0;
/* 单引号转义防 shell 注入(路径含 ' 时安全;转义后超长则截断 → sha256sum 失败走 WARN) */
char esc[1024]; int j = 0;
@@ -363,24 +364,22 @@ static int font_sha256_file(const char *path, char *out, int olen)
esc[j] = 0;
char cmd[1080];
snprintf(cmd, sizeof cmd, "sha256sum '%s' 2>/dev/null", esc);
FILE *f = popen(cmd, "r");
if (!f) return -1;
char line[160];
int ok = -1;
if (fgets(line, sizeof line, f)) {
int i = 0, k = 0;
while (line[i] == ' ' || line[i] == '\t') i++; /* 跳前导空白 */
while (line[i] && k < 64) {
char c = line[i++];
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
out[k++] = c;
else break;
}
out[k] = 0;
ok = (k == 64) ? 0 : -1;
/* M2.84-bugfix:禁止裸 popen+fgets——sha256sum 在卡慢/挂起时可能长时间不返回,
* fgets 会阻塞在 pipe_wait(主循环卡死、电源键无响应)。改用有界 popen_read_tmo
* (O_NONBLOCK + poll + 超时,超时 SIGKILL 子进程),绝不阻塞主循环。 */
char *o = popen_read_tmo(1500, cmd);
if (!o) return -1;
int i = 0, k = 0;
while (o[i] == ' ' || o[i] == '\t') i++; /* 跳前导空白 */
while (o[i] && k < 64 && k + 1 < olen) {
char c = o[i++];
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
out[k++] = c;
else break;
}
pclose(f);
return ok;
out[k] = 0;
free(o);
return (k == 64) ? 0 : -1;
}
Binary file not shown.
+4 -6
View File
@@ -212,12 +212,11 @@ int show_power_menu(void)
struct keypoll kp_power, kp_keys;
kp_open(&kp_power, DEV_POWER);
kp_open(&kp_keys, DEV_KEYS);
int prev_vold = 0, prev_volu = 0, prev_play = 0, prev_pwr = 0;
int prev_vold = 0, prev_volu = 0, prev_pwr = 0;
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
prev_pwr = kp_is_down(&kp_power, KEY_POWER) ? 1 : 0;
prev_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN) ? 1 : 0;
prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP) ? 1 : 0;
prev_play = kp_is_down(&kp_keys, KEY_PLAYPAUSE) ? 1 : 0;
int act = -1;
unsigned long long pm_t0 = now_ms();
@@ -261,7 +260,7 @@ int show_power_menu(void)
* 已由电源键负责(下行),播放键删除,重复确认移除。 */
(void)play;
if (pwr && !prev_pwr) { act = sel; pm_interacted = 1; pm_last_interact = now_ms(); } /* 电源新按=确认(非取消) */
prev_vold = vold; prev_volu = volu; prev_play = play; prev_pwr = pwr;
prev_vold = vold; prev_volu = volu; prev_pwr = pwr;
/* 触摸:按下(meow_touch 原点锁定,goodix 时序免疫),抬起按按钮命中确认 */
if (tfd >= 0) {
@@ -409,12 +408,11 @@ int confirm_reboot_player(void)
struct keypoll kp_power, kp_keys;
kp_open(&kp_power, DEV_POWER);
kp_open(&kp_keys, DEV_KEYS);
int prev_vold = 0, prev_volu = 0, prev_play = 0, prev_pwr = 0;
int prev_vold = 0, prev_volu = 0, prev_pwr = 0;
kp_poll(&kp_power, NULL, NULL); kp_poll(&kp_keys, NULL, NULL);
prev_pwr = kp_is_down(&kp_power, KEY_POWER) ? 1 : 0;
prev_vold = kp_is_down(&kp_keys, KEY_VOLUMEDOWN) ? 1 : 0;
prev_volu = kp_is_down(&kp_keys, KEY_VOLUMEUP) ? 1 : 0;
prev_play = kp_is_down(&kp_keys, KEY_PLAYPAUSE) ? 1 : 0;
int sel = 1; /* 默认「否」:不误重启 */
int act = -1;
@@ -450,7 +448,7 @@ int confirm_reboot_player(void)
* 走触摸点选(下方 tfd 命中),电源键=取消(下行);播放键删除。 */
(void)play;
if (pwr && !prev_pwr) act = 3; /* 电源=取消 */
prev_vold = vold; prev_volu = volu; prev_play = play; prev_pwr = pwr;
prev_vold = vold; prev_volu = volu; prev_pwr = pwr;
if (tfd >= 0) {
struct pollfd pfd = { tfd, POLLIN, 0 };
+1 -1
View File
@@ -17,7 +17,7 @@
#define SESSION_FILE "session.enc"
#define SESSION_MAGIC "NCM1"
#define SESSION_MAGIC_LEN 4
#define SESSION_SALT "dev.player.ncm.session.v1"
#define SESSION_SALT "os.meow.ncm.session.v1" /* 盐恒定(不随包名;改名后旧会话仍可解) */
#define DEVICE_FILE "deviceId"
#define MKEY_FILE ".mkey"
Binary file not shown.