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:
1 parent
efbd1d5a2f
commit
228e9e79e0
7 files changed
+86
-48
No files matched your search
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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.
@@ -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 };
|
||||
|
||||
Reference in new issue
Block a user