diff --git a/patch_v2/meow_launcherd/app_launcher.c b/patch_v2/meow_launcherd/app_launcher.c index 8fe090b..58328a2 100644 --- a/patch_v2/meow_launcherd/app_launcher.c +++ b/patch_v2/meow_launcherd/app_launcher.c @@ -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; } diff --git a/patch_v2/meow_launcherd/audio.c b/patch_v2/meow_launcherd/audio.c index 1eff816..52c8a94 100644 --- a/patch_v2/meow_launcherd/audio.c +++ b/patch_v2/meow_launcherd/audio.c @@ -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; } - diff --git a/patch_v2/meow_launcherd/font_theme.c b/patch_v2/meow_launcherd/font_theme.c index 525819a..b428bd5 100644 --- a/patch_v2/meow_launcherd/font_theme.c +++ b/patch_v2/meow_launcherd/font_theme.c @@ -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; } diff --git a/patch_v2/meow_launcherd/meow_launcherd b/patch_v2/meow_launcherd/meow_launcherd index 9e2ad18..5bfa89d 100755 Binary files a/patch_v2/meow_launcherd/meow_launcherd and b/patch_v2/meow_launcherd/meow_launcherd differ diff --git a/patch_v2/meow_launcherd/power_menu.c b/patch_v2/meow_launcherd/power_menu.c index e9da6aa..ff6d05a 100644 --- a/patch_v2/meow_launcherd/power_menu.c +++ b/patch_v2/meow_launcherd/power_menu.c @@ -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 }; diff --git a/patch_v2/ncm_app/core/ncm_store.c b/patch_v2/ncm_app/core/ncm_store.c index 637d735..4006da5 100644 --- a/patch_v2/ncm_app/core/ncm_store.c +++ b/patch_v2/ncm_app/core/ncm_store.c @@ -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" diff --git a/patch_v2/ncm_app/ncm b/patch_v2/ncm_app/ncm index 1f10a10..158d208 100755 Binary files a/patch_v2/ncm_app/ncm and b/patch_v2/ncm_app/ncm differ