Files
MeowOS-For-R3P2/patch_v2/bootmenu/test_bm_bootdefault.c
T
AskaEth 84c7446aa6 fix(boot): M69 普通重启/OTA 重启不再改写持久启动默认,切 Hiby 走一次性指示
用户定案(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 回写)。
2026-11-12 10:18:09 +08:00

198 lines
6.6 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.
/* 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;
}