用户定案(2026-11-12):/data/meow_boot_default 是「设置里的默认系统」, 只有显式设默认才许写;普通重启与 OTA 重启都不碰它,重启后选单按配置默认 (含 5s 倒计时)。「切换到 HibyOS」是这一次去 Hiby,用一次性指示 /data/meow_boot_once(bootmenu 开机读一次即删),不改持久默认。 meow_svc_power.c - 新增 boot_default_intent()/apply_boot_default_intent():唯一写入点, CLI 与 IPC 共用一张意图表(SET_DEFAULT_* -> 持久;BACK_HIBY -> 一次性; REBOOT/UPGRADE/SHUTDOWN -> 两个都不写)。 - POWER_REBOOT 与 CLI `reboot` 删掉 write_boot_default(0)(病根)。 - POWER_BACK_HIBY 与 CLI `reboot --hiby` 改走同一条一次性指示路径。 - 写失败照常重启,只把消息说清楚(不静默假装成功)。 meow_bootmenu.c - 新增 consume_boot_once():严格解析 0/1、读到即 unlink;删不掉时退一步 清空内容并记日志,本次照用,绝不让开机失败。 - load_config() 优先级写明并落地:一次性指示 > 持久默认 > meow.conf > 编译默认。 - DEF_BOOT_DEFAULT 1 -> 0(MeowOS):与设置页 boot.default 默认值一致, 避免「没设过默认/刷完固件首次开机」被丢进 Hiby。 测试(宿主,路径重定向到临时目录,不碰 /data) - svc_power_bootdefault_test.c:意图表 + 落盘副作用 + 反向控制(改前会写)。 - test_bm_bootdefault.c:一次性指示优先/读一次即删/删后回落/非法内容/删不掉仍可开机。 未提交二进制、未改 scripts/build/fw_content.conf(钉值仍对应现有二进制, 下一轮重编再把 md5 回写)。
198 lines
6.6 KiB
C
198 lines
6.6 KiB
C
/* test_bm_bootdefault.c — 宿主自测:启动菜单「默认项从哪来」+ 一次性指示(M69)。
|
||
*
|
||
* 覆盖的验收推演:
|
||
* ① 文件全不存在 → 编译默认 MeowOS(DEF_BOOT_DEFAULT=0);
|
||
* ② /data/meow_boot_default(设置里的「默认系统」)压过 meow.conf 兜底;
|
||
* ③ 一次性指示 /data/meow_boot_once 压过持久默认,且**读一次即删**;
|
||
* ④ 删掉之后的下一次开机回落持久默认;
|
||
* ⑤ 内容非法 → 忽略并删除;
|
||
* ⑥ 删不掉时「能用就用、不许开机失败」,并尽量清空内容让下一次开机不生效。
|
||
*
|
||
* 编译/运行(Termux 上 /tmp 不可写,带 TMPDIR 最稳):
|
||
* mkdir -p "$HOME/tmp/m69test"
|
||
* cc -Wall -Wextra -Os -I../meow_launcherd -Wno-unused-function -Wno-unused-parameter \
|
||
* -Wno-unused-variable test_bm_bootdefault.c ../meow_launcherd/meow_touch.c \
|
||
* -o "$HOME/tmp/m69test/bm_bt" &&
|
||
* TMPDIR=$HOME/tmp/m69test "$HOME/tmp/m69test/bm_bt" # 全 PASS -> exit 0
|
||
*/
|
||
#define BM_NO_MAIN
|
||
#include "meow_bootmenu.c"
|
||
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
#include <sys/stat.h>
|
||
|
||
static int g_pass = 0, g_fail = 0;
|
||
|
||
static void check(const char *name, int got, int want)
|
||
{
|
||
if (got == want) { g_pass++; printf("PASS %s\n", name); }
|
||
else { g_fail++; printf("FAIL %s: got %d want %d\n", name, got, want); }
|
||
}
|
||
|
||
static char g_dir[512], g_conf[600], g_def[600], g_once[600];
|
||
|
||
/* 临时目录:$TMPDIR → /tmp → $HOME/tmp/m69test(Termux 上 /tmp 属别的用户,不可写)。 */
|
||
static int mk_tmp_dir(void)
|
||
{
|
||
const char *cand[3];
|
||
char homebuf[512] = "";
|
||
int n = 0;
|
||
const char *t = getenv("TMPDIR");
|
||
if (t && t[0]) cand[n++] = t;
|
||
cand[n++] = "/tmp";
|
||
const char *home = getenv("HOME");
|
||
if (home && home[0]) {
|
||
snprintf(homebuf, sizeof homebuf, "%s/tmp/m69test", home);
|
||
mkdir(homebuf, 0700);
|
||
cand[n++] = homebuf;
|
||
}
|
||
for (int i = 0; i < n; i++) {
|
||
snprintf(g_dir, sizeof g_dir, "%s/m69_bm_XXXXXX", cand[i]);
|
||
if (!mkdtemp(g_dir)) continue;
|
||
snprintf(g_conf, sizeof g_conf, "%s/meow.conf", g_dir);
|
||
snprintf(g_def, sizeof g_def, "%s/boot_default", g_dir);
|
||
snprintf(g_once, sizeof g_once, "%s/boot_once", g_dir);
|
||
g_conf_path = g_conf;
|
||
g_boot_default_path = g_def;
|
||
g_boot_once_path = g_once;
|
||
return 0;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
static void put(const char *path, const char *body)
|
||
{
|
||
FILE *f = fopen(path, "w");
|
||
if (!f) { g_fail++; printf("FAIL cannot write %s\n", path); return; }
|
||
fputs(body, f);
|
||
fclose(f);
|
||
}
|
||
|
||
static void reset(void)
|
||
{
|
||
unlink(g_conf);
|
||
unlink(g_def);
|
||
unlink(g_once);
|
||
}
|
||
|
||
static long file_size(const char *path)
|
||
{
|
||
struct stat st;
|
||
if (stat(path, &st) != 0) return -1;
|
||
return (long)st.st_size;
|
||
}
|
||
|
||
/* ---- ① 什么都没有 → 编译默认 MeowOS ---- */
|
||
static void case_compile_default(void)
|
||
{
|
||
printf("== no files -> compile default ==\n");
|
||
reset();
|
||
load_config();
|
||
check("compile default is MeowOS (0)", cfg_boot_default, 0);
|
||
check("DEF_BOOT_DEFAULT == 0", DEF_BOOT_DEFAULT, 0);
|
||
}
|
||
|
||
/* ---- ② 持久默认 = 设置里的「默认系统」,压过 conf 兜底 ---- */
|
||
static void case_persistent_is_authoritative(void)
|
||
{
|
||
printf("== persistent boot default ==\n");
|
||
reset();
|
||
put(g_def, "1\n");
|
||
load_config();
|
||
check("persistent=1 -> Hiby", cfg_boot_default, 1);
|
||
|
||
put(g_conf, "boot_default=0\n");
|
||
load_config();
|
||
check("persistent=1 overrides conf boot_default=0", cfg_boot_default, 1);
|
||
|
||
put(g_def, "0\n");
|
||
load_config();
|
||
check("persistent=0 -> MeowOS", cfg_boot_default, 0);
|
||
}
|
||
|
||
/* ---- ③④ 一次性指示:优先、读一次即删、下次回落持久默认 ---- */
|
||
static void case_once_overrides_and_is_consumed(void)
|
||
{
|
||
printf("== one-shot boot_once ==\n");
|
||
reset();
|
||
put(g_def, "0\n"); /* 用户设的默认 = MeowOS */
|
||
put(g_once, "1\n"); /* 「切换到 HibyOS」= 这一次去 Hiby */
|
||
|
||
load_config();
|
||
check("once=1 overrides persistent=0", cfg_boot_default, 1);
|
||
check("once file removed after use", access(g_once, F_OK), -1);
|
||
|
||
load_config(); /* 模拟下一次开机 */
|
||
check("next boot falls back to persistent 0", cfg_boot_default, 0);
|
||
}
|
||
|
||
/* ---- ⑤ 非法内容:忽略 + 删除(不留垃圾反复干扰) ---- */
|
||
static void case_invalid_once(void)
|
||
{
|
||
printf("== invalid boot_once ==\n");
|
||
reset();
|
||
put(g_def, "1\n");
|
||
put(g_once, "garbage\n");
|
||
load_config();
|
||
check("invalid once ignored (persistent=1 wins)", cfg_boot_default, 1);
|
||
check("invalid once removed", access(g_once, F_OK), -1);
|
||
}
|
||
|
||
/* ---- ⑥ 删不掉:能用就用(不许开机失败),并尽量清空让下次不生效 ----
|
||
* 构造「目录不可写 + 文件可写」:unlink 需要目录写权限 → 失败;O_TRUNC 只需文件写权限 → 成功。
|
||
* (以 root 跑时 unlink 会成功,本用例自动 SKIP。) */
|
||
static void case_unlink_failure(void)
|
||
{
|
||
printf("== unlink failure is survivable ==\n");
|
||
reset();
|
||
put(g_def, "0\n");
|
||
put(g_once, "1\n");
|
||
|
||
chmod(g_dir, 0555);
|
||
if (unlink(g_once) == 0) { /* 探测:权限没生效(多半是 root) */
|
||
chmod(g_dir, 0755);
|
||
put(g_once, "1\n");
|
||
printf("SKIP unlink-failure case (unlink succeeded, uid=%d)\n", (int)getuid());
|
||
return;
|
||
}
|
||
|
||
load_config();
|
||
check("unremovable once still used this boot", cfg_boot_default, 1);
|
||
check("once content truncated to empty", (int)file_size(g_once), 0);
|
||
|
||
chmod(g_dir, 0755);
|
||
load_config(); /* 模拟下一次开机 */
|
||
check("next boot ignores emptied once (persistent=0)", cfg_boot_default, 0);
|
||
}
|
||
|
||
/* ---- 组件契约:两个组件必须用同一对生产路径 ---- */
|
||
static void case_prod_paths(void)
|
||
{
|
||
printf("== production paths ==\n");
|
||
check("BOOT_DEFAULT_FILE", strcmp(BOOT_DEFAULT_FILE, "/data/meow_boot_default"), 0);
|
||
check("BOOT_ONCE_FILE", strcmp(BOOT_ONCE_FILE, "/data/meow_boot_once"), 0);
|
||
check("CONF_FILE on TF card", strcmp(CONF_FILE, "/usr/data/mnt/sd_0/MeowAPPs/meow.conf"), 0);
|
||
}
|
||
|
||
int main(void)
|
||
{
|
||
if (mk_tmp_dir() != 0) { printf("FATAL: no writable tmp dir\n"); return 1; }
|
||
|
||
case_compile_default();
|
||
case_persistent_is_authoritative();
|
||
case_once_overrides_and_is_consumed();
|
||
case_invalid_once();
|
||
case_unlink_failure();
|
||
case_prod_paths();
|
||
|
||
reset();
|
||
rmdir(g_dir);
|
||
|
||
if (g_fail) { printf("\nBOOTMENU BOOTDEFAULT TEST: %d FAIL (%d pass)\n", g_fail, g_pass); return 1; }
|
||
printf("\nBOOTMENU BOOTDEFAULT TEST: ALL PASS (%d checks)\n", g_pass);
|
||
return 0;
|
||
}
|