feat(sdk): meow_config_* App配置读写API(agent-158)
- 6 API: meow_config_dir/set/get/int/bool/remove; 落 TF MeowData/<pkg>/data/<key>.cfg(env MEOW_DATA_DIR, 回退/tmp/meowcfg_<pid>) - 与系统设置 meow_settings_*(板载settings.conf) 区分; key清洗[A-Za-z0-9._-]→_ 防路径穿越; 值上限4096; 降级不崩(pthread_once/mutex) - MEOW_CFG_* 错误码(避开 meow_ipc.h 枚举命名空间, 防宏展开冲突); 数值与 MEOWOS_API/meow_settings.h 同值 - meow_ui.a=92e61fb8(新增config成员) hello=5e31d919不变(0回归, 静态链未拉入新成员); build_sdk 0warning; 宿主test_meow_ui_config ALL PASS - 未重链launcher(launcher未用config); 遗留真机验证MEOW_DATA_DIR落盘/无卡降级
This commit is contained in:
1 parent
7f36b6f069
commit
257ff7608e
4 files changed
+388
-1
No files matched your search
+1
-1
@@ -10,7 +10,7 @@ AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架
|
||||
rm -f meow_ui.a *.o
|
||||
|
||||
# -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移)
|
||||
OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog stb_image"
|
||||
OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config stb_image"
|
||||
CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd"
|
||||
for f in $OBJS; do
|
||||
echo "== cc $f =="
|
||||
|
||||
@@ -21,6 +21,17 @@
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* meow_config_*(§9.2)专用返回码。数值与 MEOWOS_API.md §1 的同名错误码一致,
|
||||
* 但用 meow_cfg 前缀——因为 MEOW_OK/MEOW_ERR_* 这个命名空间已被共享协议头
|
||||
* meow_ipc.h(枚举)与 launcher meow_settings.h(宏)占用,SDK 的 meow_notify_ipc.c
|
||||
* 同 TU include meow_ui.h + meow_ipc.h,若在此定义同名宏会导致「宏展开枚举标识符」冲突。 */
|
||||
#define MEOW_CFG_OK 0 /* 成功 */
|
||||
#define MEOW_CFG_ERR_INVAL -1 /* 参数/取值非法(空 key、NULL out、outsz<=0 等) */
|
||||
#define MEOW_CFG_ERR_NOENT -2 /* 键未设/文件不存在 */
|
||||
#define MEOW_CFG_ERR_IO -5 /* 读写(存储/文件)失败 */
|
||||
#define MEOW_CFG_ERR_NOMEM -6 /* 路径超长 */
|
||||
#define MEOW_CFG_ERR_NODEV -10 /* 无数据目录(卡不在/只读/不可建) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -611,6 +622,30 @@ int meow_notify_has_active(void); /* 1=尚有活动通知,0=无 *
|
||||
const char *meow_notify_urgency_str(meow_notify_urgency u);
|
||||
const char *meow_notify_category_str(meow_notify_category cat);
|
||||
|
||||
/* ============================================================ */
|
||||
/* §9.2 应用配置读写(meow_config_*,M2.x 最小可用版) */
|
||||
/* ============================================================ */
|
||||
/* App 自有配置读写:与系统设置 meow_settings_*(板载 settings.conf、系统级、带 schema)
|
||||
* 明确区分——这是单个 App 自己的键值偏好,落 TF 数据目录,不占板载、无 schema。
|
||||
*
|
||||
* - 数据目录:优先 env MEOW_DATA_DIR(launcher 注入 /usr/data/mnt/sd_0/MeowData/<pkg>/data,
|
||||
* 见 app_launcher.c launch_app);无 env(手动跑/宿主测/无卡)回退 /tmp/meowcfg_<pid>
|
||||
* (非持久但仍可写)。meow_config_dir() 返回最终目录;空/不可建返回 NULL。
|
||||
* - 文件布局:每键一小文件 <data>/<sanitized_key>.cfg,内容=值字符串(空串=0 字节文件)。
|
||||
* key 只允许 [A-Za-z0-9._-],其余字符替换为 '_'(杜绝路径穿越);key 上限 64。
|
||||
* - 轻量:每次 get 读文件(App 配置小),不做大内存缓存;pthread_once 惰性初始化目录。
|
||||
* - 失败降级:无数据目录/写失败 → 返回负错误码(见上 MEOW_CFG_ERR_*),不崩不阻塞。
|
||||
*
|
||||
* 返回码:MEOW_CFG_OK(0) 成功;(set) 负=失败;(get) MEOW_CFG_ERR_NOENT(-2)=键未设/文件不存在;
|
||||
* meow_config_int/bool 对任何非 MEOW_CFG_OK 一律返回 def。
|
||||
* 错误码数值同 MEOWOS_API.md §1 / meow_settings_*(MEOW_ERR_*)一致,便于 App 习惯。 */
|
||||
const char *meow_config_dir(void); /* 当前 App 数据目录(data);空/不可建 NULL */
|
||||
int meow_config_set(const char *key, const char *val);/* 写 <data>/<key>.cfg;成功 MEOW_CFG_OK(0) */
|
||||
int meow_config_get(const char *key, char *out, int outsz); /* 读;未设/不存在返回 MEOW_CFG_ERR_NOENT */
|
||||
int meow_config_int(const char *key, int def); /* 便捷 int;未设/非法返回 def */
|
||||
int meow_config_bool(const char *key, int def); /* 便捷 bool;未设/非法返回 def */
|
||||
void meow_config_remove(const char *key); /* 删键(无该键/无目录则静默) */
|
||||
|
||||
/* ============================================================ */
|
||||
/* §5.7 应用可读 TF 卡字体列表(M2.58:字体索引 / 查列表 / 丢失降级默认) */
|
||||
/* ============================================================ */
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* meow_ui_config.c — MeowOS 应用配置读写 API(M2.x,§9.2)。
|
||||
*
|
||||
* 背景:第三/系统 App 需要读写「自己的配置」(播放列表偏好、UI 选项、最近列表等)。
|
||||
* 此前 SDK 无统一接口,App 只能自己造轮子(自读 env + snprintf + fopen/fprintf),
|
||||
* 重复、易错、无失败降级。本文件实现 SDK 统一 meow_config_*:
|
||||
*
|
||||
* - app 一行 meow_config_set/get 读写键值,SDK 管「数据目录解析 / 文件布局 / 降级」;
|
||||
* - 数据目录:优先 env MEOW_DATA_DIR(launcher 注入 /usr/data/mnt/sd_0/MeowData/<pkg>/data,
|
||||
* 见 app_launcher.c launch_app,exFAT 无 symlink → env 传址);无 env(手动跑/宿主测/无卡)
|
||||
* 回退 /tmp/meowcfg_<pid>(非持久但仍可写);
|
||||
* - 文件布局:每键一小文件 <data>/<sanitized_key>.cfg,内容=值字符串(空串=0 字节文件);
|
||||
* key 只允许 [A-Za-z0-9._-],其余字符(含 '/')替换为 '_',杜绝路径穿越/子目录注入;
|
||||
* - 轻量:每次 get 读文件(App 配置小),不做大内存缓存;pthread_once 惰性初始化目录;
|
||||
* - 线程安全:pthread_mutex 串行库内单键操作(不同 key 独立文件,无共享读写竞态);
|
||||
* - 失败降级:无数据目录/建目录失败/读写失败 → 返回负错误码(MEOW_CFG_ERR_*),不崩不阻塞。
|
||||
*
|
||||
* 与 meow_settings_*(系统设置)的界限:settings 是「系统/MeowOS 设置」(板载
|
||||
* /usr/data/meow/settings.conf、带 schema/取值域/枚举/listener),config 是「App 自有 KV」
|
||||
* (TF 数据目录、raw 字符串、无 schema)。二者命名/落点/语义不同层;本文件**不 import**
|
||||
* meow_settings,纯 libc 完成,可宿主 gcc 直接编译单测。
|
||||
*
|
||||
* 约定:与 meow_ui_log.c 同风格(M2.x 中文注释 + pthread_once 惰性 init);纯 libc,零依赖。
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <pthread.h>
|
||||
|
||||
/* 内部常量(轻量键值小文件,见头注释) */
|
||||
#define MEOW_CFG_DIR_MAX 320 /* 数据目录绝对路径上限 */
|
||||
#define MEOW_CFG_KEY_MAX 64 /* key 上限(清洗后) */
|
||||
#define MEOW_CFG_VAL_MAX 4096 /* 值上限(set 截断;get 按 outsz-1 读) */
|
||||
#define MEOW_CFG_PATH_MAX (MEOW_CFG_DIR_MAX + MEOW_CFG_KEY_MAX + 8) /* <dir>/<key>.cfg + NUL */
|
||||
|
||||
/* 内部状态(各字段约定见头注释) */
|
||||
static pthread_mutex_t g_cfg_mu = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_once_t g_cfg_once = PTHREAD_ONCE_INIT;
|
||||
|
||||
static char g_cfg_dir[MEOW_CFG_DIR_MAX] = ""; /* 解析后的数据目录(env 或 /tmp 回退) */
|
||||
static int g_cfg_ready = 0; /* 1=目录已解析且 mkdir 成功 */
|
||||
static int g_cfg_env = 0; /* 1=来自 MEOW_DATA_DIR(持久),0=/tmp 回退 */
|
||||
|
||||
/* mkdir -p:逐分量 mkdir(已存在 EEXIST 视为成功)。返回 0=成功,-1=失败。
|
||||
* 仿 meow_ui_log.c log_mkdir_p;用于数据目录不存在时补齐(语义幂等)。 */
|
||||
static int cfg_mkdir_p(const char *path)
|
||||
{
|
||||
char tmp[MEOW_CFG_DIR_MAX];
|
||||
size_t len, i;
|
||||
if (!path || !path[0] || strlen(path) >= sizeof tmp) return -1;
|
||||
len = strlen(path);
|
||||
memcpy(tmp, path, len + 1);
|
||||
if (tmp[len - 1] == '/') tmp[len - 1] = '\0'; /* 去尾斜杠 */
|
||||
for (i = 1; i <= strlen(tmp); i++) {
|
||||
if (tmp[i] == '/' || tmp[i] == '\0') {
|
||||
char ch = tmp[i];
|
||||
tmp[i] = '\0';
|
||||
if (tmp[0] && mkdir(tmp, 0755) != 0 && errno != EEXIST) return -1;
|
||||
tmp[i] = ch;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 一次性初始化(pthread_once 保证仅一次):解析数据目录 + mkdir -p + 置 ready。 */
|
||||
static void cfg_init(void)
|
||||
{
|
||||
const char *env = getenv("MEOW_DATA_DIR");
|
||||
|
||||
/* 层序:1) env MEOW_DATA_DIR(launcher 注入,持久);2) 回退 /tmp/meowcfg_<pid>。 */
|
||||
if (env && env[0]) {
|
||||
snprintf(g_cfg_dir, sizeof g_cfg_dir, "%s", env);
|
||||
g_cfg_env = 1;
|
||||
} else {
|
||||
snprintf(g_cfg_dir, sizeof g_cfg_dir, "/tmp/meowcfg_%d", (int)getpid());
|
||||
g_cfg_env = 0;
|
||||
}
|
||||
|
||||
/* 去尾斜杠(统一 <dir>/<key>.cfg 拼接) */
|
||||
size_t n = strlen(g_cfg_dir);
|
||||
while (n > 1 && g_cfg_dir[n - 1] == '/') g_cfg_dir[--n] = '\0';
|
||||
|
||||
/* mkdir 幂等;失败只降级(g_cfg_ready=0 → meow_config_dir 返回 NULL),不崩。
|
||||
* 注:env 指向 <pkg>/data 时 launcher 已建该目录;卡不在/只读 → 此处失败降级。 */
|
||||
if (cfg_mkdir_p(g_cfg_dir) == 0) g_cfg_ready = 1;
|
||||
}
|
||||
|
||||
/* key 清洗:只保留 [A-Za-z0-9._-],其余(含 '/'、空白、'..')逐字符替换为 '_'。
|
||||
* 返回 0=成功(写入 out),-1=空 key/全非法。清洗后无路径分隔,杜绝路径穿越。 */
|
||||
static int cfg_sanitize_key(const char *key, char *out, int n)
|
||||
{
|
||||
int len = 0;
|
||||
const unsigned char *p;
|
||||
if (!key || n <= 0) return -1;
|
||||
for (p = (const unsigned char *)key; *p && len < n - 1; p++) {
|
||||
unsigned char c = *p;
|
||||
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
|
||||
(c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-')
|
||||
out[len++] = (char)c;
|
||||
else
|
||||
out[len++] = '_';
|
||||
}
|
||||
if (len == 0) return -1; /* 全非法(如纯 '/')→ 拒绝 */
|
||||
out[len] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 拼 <dir>/<key>.cfg 到 path。返回 0=成功,-1=路径超长。 */
|
||||
static int cfg_path(const char *key, char *path, int n)
|
||||
{
|
||||
const char *d = meow_config_dir();
|
||||
if (!d) return -1;
|
||||
if (snprintf(path, (size_t)n, "%s/%s.cfg", d, key) >= n) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *meow_config_dir(void)
|
||||
{
|
||||
/* 不取 g_cfg_mu:pthread_once 保证 cfg_init 只跑一次,且其完成后 g_cfg_dir/g_cfg_ready
|
||||
* 已是稳定快照(once 自带 happens-before/内存序)。set/get/remove 持锁后经 cfg_path 也调
|
||||
* 本函数——若在此加锁会自我死锁(非递归互斥)。故仅返回只读快照。 */
|
||||
pthread_once(&g_cfg_once, cfg_init);
|
||||
return g_cfg_ready ? g_cfg_dir : NULL;
|
||||
}
|
||||
|
||||
int meow_config_set(const char *key, const char *val)
|
||||
{
|
||||
char k[MEOW_CFG_KEY_MAX + 1];
|
||||
char path[MEOW_CFG_PATH_MAX];
|
||||
const char *v = val ? val : ""; /* NULL 视为空串(=清空该键) */
|
||||
size_t vlen, towrite;
|
||||
int fd, rc = MEOW_CFG_OK, s;
|
||||
|
||||
if (cfg_sanitize_key(key, k, sizeof k) != 0) return MEOW_CFG_ERR_INVAL;
|
||||
vlen = strlen(v);
|
||||
towrite = vlen > MEOW_CFG_VAL_MAX ? MEOW_CFG_VAL_MAX : vlen; /* 值上限截断 */
|
||||
|
||||
pthread_mutex_lock(&g_cfg_mu);
|
||||
s = cfg_path(k, path, sizeof path);
|
||||
if (s != 0) { pthread_mutex_unlock(&g_cfg_mu); return MEOW_CFG_ERR_NOMEM; }
|
||||
|
||||
fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (fd < 0) {
|
||||
if (!g_cfg_env) fprintf(stderr, "meow_config: set '%s' open %s: %s\n", k, path, strerror(errno));
|
||||
pthread_mutex_unlock(&g_cfg_mu);
|
||||
return MEOW_CFG_ERR_IO;
|
||||
}
|
||||
/* 循环写完(防短写;配置小,实际单次 write 即完) */
|
||||
size_t off = 0;
|
||||
while (off < towrite) {
|
||||
ssize_t w = write(fd, v + off, towrite - off);
|
||||
if (w < 0) { rc = MEOW_CFG_ERR_IO; break; }
|
||||
if (w == 0) { rc = MEOW_CFG_ERR_IO; break; }
|
||||
off += (size_t)w;
|
||||
}
|
||||
close(fd);
|
||||
pthread_mutex_unlock(&g_cfg_mu);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int meow_config_get(const char *key, char *out, int outsz)
|
||||
{
|
||||
char k[MEOW_CFG_KEY_MAX + 1];
|
||||
char path[MEOW_CFG_PATH_MAX];
|
||||
ssize_t n;
|
||||
|
||||
if (!out || outsz <= 0) return MEOW_CFG_ERR_INVAL;
|
||||
out[0] = '\0';
|
||||
if (cfg_sanitize_key(key, k, sizeof k) != 0) return MEOW_CFG_ERR_INVAL;
|
||||
|
||||
pthread_mutex_lock(&g_cfg_mu);
|
||||
if (cfg_path(k, path, sizeof path) != 0) { pthread_mutex_unlock(&g_cfg_mu); return MEOW_CFG_ERR_NOMEM; }
|
||||
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd < 0) { pthread_mutex_unlock(&g_cfg_mu); return MEOW_CFG_ERR_NOENT; } /* 未设/不存在 */
|
||||
|
||||
n = read(fd, out, (size_t)outsz - 1);
|
||||
if (n < 0) n = 0;
|
||||
close(fd);
|
||||
pthread_mutex_unlock(&g_cfg_mu);
|
||||
|
||||
out[n] = '\0'; /* 截断/短读均安全 NUL 终止 */
|
||||
return MEOW_CFG_OK;
|
||||
}
|
||||
|
||||
int meow_config_int(const char *key, int def)
|
||||
{
|
||||
char buf[32];
|
||||
char *end = NULL;
|
||||
long v;
|
||||
if (meow_config_get(key, buf, sizeof buf) != MEOW_CFG_OK) return def;
|
||||
v = strtol(buf, &end, 10);
|
||||
if (end == buf) return def; /* 非数字 → def */
|
||||
return (int)v;
|
||||
}
|
||||
|
||||
int meow_config_bool(const char *key, int def)
|
||||
{
|
||||
char buf[16];
|
||||
if (meow_config_get(key, buf, sizeof buf) != MEOW_CFG_OK) return def;
|
||||
if (!strcmp(buf, "1") || !strcmp(buf, "true") || !strcmp(buf, "yes") || !strcmp(buf, "on"))
|
||||
return 1;
|
||||
if (!strcmp(buf, "0") || !strcmp(buf, "false") || !strcmp(buf, "no") || !strcmp(buf, "off"))
|
||||
return 0;
|
||||
return def;
|
||||
}
|
||||
|
||||
void meow_config_remove(const char *key)
|
||||
{
|
||||
char k[MEOW_CFG_KEY_MAX + 1];
|
||||
char path[MEOW_CFG_PATH_MAX];
|
||||
|
||||
if (cfg_sanitize_key(key, k, sizeof k) != 0) return; /* 非法 key 静默 */
|
||||
|
||||
pthread_mutex_lock(&g_cfg_mu);
|
||||
if (cfg_path(k, path, sizeof path) == 0)
|
||||
unlink(path); /* 无目录 / 文件不存在:unlink 失败即静默(void 语义) */
|
||||
pthread_mutex_unlock(&g_cfg_mu);
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* test_meow_ui_config.c — meow_config_* 宿主单测(2026-08-30)。
|
||||
*
|
||||
* 编译(宿主,config 为纯 libc,无需 SDK 其它对象):
|
||||
* gcc -Wall -Wextra test_meow_ui_config.c meow_ui_config.c -o test_meow_ui_config -lpthread
|
||||
*
|
||||
* 两种模式(meow_config_dir 经 pthread_once 缓存,目录只在首次调用确定,故分两次跑):
|
||||
* ./test_meow_ui_config env → 设 MEOW_DATA_DIR=<mkdtemp>,验证持久目录读写
|
||||
* ./test_meow_ui_config tmp → unsetenv MEOW_DATA_DIR,验证 /tmp/meowcfg_<pid> 回退
|
||||
*
|
||||
* 任一断言失败 → 打印 FAIL 并以非零退出码结束。
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(cond, msg) do { \
|
||||
if (cond) { printf(" PASS: %s\n", msg); } \
|
||||
else { printf(" FAIL: %s\n", msg); g_fail = 1; } \
|
||||
} while (0)
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *mode = (argc > 1) ? argv[1] : "env";
|
||||
char *tmpdir = NULL;
|
||||
|
||||
if (strcmp(mode, "tmp") == 0) {
|
||||
unsetenv("MEOW_DATA_DIR");
|
||||
} else {
|
||||
tmpdir = strdup("/tmp/meowcfgtestXXXXXX");
|
||||
if (!mkdtemp(tmpdir)) { printf("FATAL: mkdtemp failed\n"); return 2; }
|
||||
setenv("MEOW_DATA_DIR", tmpdir, 1);
|
||||
}
|
||||
|
||||
printf("== meow_config test [%s] ==\n", mode);
|
||||
|
||||
const char *dir = meow_config_dir();
|
||||
CHECK(dir != NULL, "meow_config_dir() 非 NULL");
|
||||
printf(" dir = %s\n", dir ? dir : "(null)");
|
||||
if (strcmp(mode, "tmp") == 0)
|
||||
CHECK(dir && strstr(dir, "/tmp/meowcfg_") != NULL, "tmp 回退目录为 /tmp/meowcfg_<pid>");
|
||||
else
|
||||
CHECK(dir && strcmp(dir, tmpdir) == 0, "env 目录 = MEOW_DATA_DIR");
|
||||
|
||||
/* --- set/get 往返 --- */
|
||||
CHECK(meow_config_set("k1", "hello") == MEOW_CFG_OK, "set k1=hello -> MEOW_CFG_OK");
|
||||
char buf[128];
|
||||
CHECK(meow_config_get("k1", buf, sizeof buf) == MEOW_CFG_OK, "get k1 -> MEOW_CFG_OK");
|
||||
CHECK(strcmp(buf, "hello") == 0, "get k1 值 = hello");
|
||||
|
||||
/* --- 全数字 int / bool 解析 --- */
|
||||
meow_config_set("num", "42");
|
||||
CHECK(meow_config_int("num", -1) == 42, "int(num)=42");
|
||||
meow_config_set("truth", "true");
|
||||
CHECK(meow_config_bool("truth", 0) == 1, "bool(true)=1");
|
||||
meow_config_set("lie", "off");
|
||||
CHECK(meow_config_bool("lie", 1) == 0, "bool(off)=0");
|
||||
CHECK(meow_config_int("nonnum", 7) == 7, "int 非数字 -> def");
|
||||
CHECK(meow_config_bool("nonbool", 3) == 3, "bool 非法 -> def");
|
||||
|
||||
/* --- 未设键 --- */
|
||||
CHECK(meow_config_get("absent", buf, sizeof buf) == MEOW_CFG_ERR_NOENT, "get 未设键 -> MEOW_CFG_ERR_NOENT");
|
||||
CHECK(meow_config_int("absent", 9) == 9, "int 未设键 -> def");
|
||||
CHECK(meow_config_bool("absent", 1) == 1, "bool 未设键 -> def");
|
||||
|
||||
/* --- int 便捷写(经 set)再读 --- */
|
||||
char nbuf[16];
|
||||
meow_config_get("num", nbuf, sizeof nbuf);
|
||||
CHECK(strcmp(nbuf, "42") == 0, "int 存储为十进制 '42'");
|
||||
|
||||
/* --- 空值(NULL=清空为 0 字节文件,与「未设」区分)--- */
|
||||
meow_config_set("empty", "");
|
||||
CHECK(meow_config_get("empty", buf, sizeof buf) == MEOW_CFG_OK, "get 空值键 -> MEOW_CFG_OK");
|
||||
CHECK(buf[0] == '\0', "空值键读回空串");
|
||||
CHECK(meow_config_get("empty", buf, sizeof buf) != MEOW_CFG_ERR_NOENT, "空值键 ≠ 未设");
|
||||
|
||||
/* --- remove --- */
|
||||
meow_config_remove("k1");
|
||||
CHECK(meow_config_get("k1", buf, sizeof buf) == MEOW_CFG_ERR_NOENT, "remove 后 get -> NOENT");
|
||||
|
||||
/* --- key 清洗:'/' 等非法字符替换为 '_',杜绝路径穿越 --- */
|
||||
meow_config_set("a/b", "slash");
|
||||
CHECK(meow_config_get("a_b", buf, sizeof buf) == MEOW_CFG_OK && strcmp(buf, "slash") == 0,
|
||||
"key 'a/b' 清洗为 'a_b'");
|
||||
/* 路径穿越尝试:key 含 '/' 的非法串,不得逃出数据目录('/'→'_','.' 保留为字面量)。 */
|
||||
meow_config_set("../../etc/passwd", "evil");
|
||||
CHECK(meow_config_get(".._.._etc_passwd", buf, sizeof buf) == MEOW_CFG_OK &&
|
||||
strcmp(buf, "evil") == 0, "key '../../etc/passwd' 清洗为 '.._.._etc_passwd'('/'→'_',无穿越)");
|
||||
|
||||
/* --- 参数校验 --- */
|
||||
CHECK(meow_config_get(NULL, buf, sizeof buf) == MEOW_CFG_ERR_INVAL, "get(NULL) -> INVAL");
|
||||
CHECK(meow_config_get("x", NULL, 10) == MEOW_CFG_ERR_INVAL, "get(out=NULL) -> INVAL");
|
||||
CHECK(meow_config_get("x", buf, 0) == MEOW_CFG_ERR_INVAL, "get(outsz=0) -> INVAL");
|
||||
CHECK(meow_config_set("", "v") == MEOW_CFG_ERR_INVAL, "set(空 key) -> INVAL");
|
||||
|
||||
/* --- 多键独立 --- */
|
||||
meow_config_set("ind.a", "1");
|
||||
meow_config_set("ind.b", "2");
|
||||
meow_config_get("ind.a", buf, sizeof buf);
|
||||
CHECK(strcmp(buf, "1") == 0, "多键独立 a");
|
||||
meow_config_get("ind.b", buf, sizeof buf);
|
||||
CHECK(strcmp(buf, "2") == 0, "多键独立 b");
|
||||
meow_config_remove("ind.a");
|
||||
meow_config_get("ind.b", buf, sizeof buf);
|
||||
CHECK(strcmp(buf, "2") == 0, "删除 a 不影响 b");
|
||||
|
||||
/* --- 值截断(>MEOW_CFG_VAL_MAX 4096 截断;读合法)--- */
|
||||
{
|
||||
char big[4096 * 2];
|
||||
char rbuf[4100];
|
||||
memset(big, 'A', sizeof big - 1);
|
||||
big[sizeof big - 1] = '\0';
|
||||
meow_config_set("big", big);
|
||||
meow_config_get("big", rbuf, sizeof rbuf);
|
||||
CHECK(strlen(rbuf) == 4096, "值 >4096 截断为 4096");
|
||||
}
|
||||
|
||||
if (tmpdir) free(tmpdir);
|
||||
if (g_fail) { printf("\nRESULT: FAIL (%d)\n", g_fail); return 1; }
|
||||
printf("\nRESULT: ALL PASS\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user