feat(nyai): 更新规范+导出过滤 — 防降级安装(version点分比较,新版<已装拒装/缺失拒装,先解 staging 再替换) + 导出两层模式(黑名单 find+tar -T 排除 *.pre_*/.bak_*/logs//*.log/.DS_Store 且去 ./ 前缀 / 白名单 export_files 逐项安全校验) + 导出体检(app.json合法+exec存在可执行) + install/upgrade 成功后自清 .bak_<pid> + os.meow.* 签名提示与 2A 验签挂点注释 (agent-180)
nyai e5514929→新值见 fw_content.conf; 宿主 17/17 场景全过(首装/升/同版/降级拒/无版本拒/黑白名单/体检/round-trip); cmd_upgrade 同步加防降级门禁(堵绕过) 出入: 版本严格解析(1.2.3-beta 判不可比拒装, 与定案一致); busybox tar --exclude 未真机验证→用 find|tar -T 兜底
This commit is contained in:
1 parent
f2786552d5
commit
7a1c78c5de
4 files changed
+253
-23
No files matched your search
Executable
+28
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# build_nyai.sh — nyai 包管理器交叉编译(mipsel 静态)+ 宿主编译可选。
|
||||||
|
#
|
||||||
|
# 目标:R3 Pro II / MeowOS(mipsel-linux-muslsf,静态)。单文件、无外部依赖(POSIX + Linux)。
|
||||||
|
#
|
||||||
|
# 用法:
|
||||||
|
# ./build_nyai.sh # 交叉编译 → patch_v2/meowpkg/nyai(mipsel 静态)
|
||||||
|
# ./build_nyai.sh host # 宿主编译 → patch_v2/meowpkg/nyai_host
|
||||||
|
set -e
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
ROOT="$HOME/Project/R3ProII"
|
||||||
|
CC="$ROOT/.tools/zig-mipsel-cc"
|
||||||
|
|
||||||
|
if [ "$1" = "host" ]; then
|
||||||
|
HOST_CC="${CC_HOST:-cc}"
|
||||||
|
"$HOST_CC" -Os -Wall -Wextra nyai.c -o nyai_host
|
||||||
|
echo "== host build =="
|
||||||
|
ls -la nyai_host
|
||||||
|
md5sum nyai_host
|
||||||
|
file nyai_host
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$CC" -Os -Wall -Wextra -static nyai.c -o nyai
|
||||||
|
echo "== nyai (mipsel static) =="
|
||||||
|
ls -la nyai
|
||||||
|
md5sum nyai
|
||||||
|
file nyai
|
||||||
Binary file not shown.
+225
-23
@@ -19,6 +19,7 @@
|
|||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <limits.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
@@ -36,11 +37,8 @@ static const char *meow_root(void)
|
|||||||
const char *r = getenv("MEOWROOT");
|
const char *r = getenv("MEOWROOT");
|
||||||
return (r && *r) ? r : "/usr/data/mnt/sd_0";
|
return (r && *r) ? r : "/usr/data/mnt/sd_0";
|
||||||
}
|
}
|
||||||
static void path_apps(char *out, int n, const char *pkg)
|
|
||||||
{ snprintf(out, n, "%s/MeowAPPs/%s", meow_root(), pkg ? pkg : ""); }
|
|
||||||
static void path_data(char *out, int n, const char *pkg, const char *sub)
|
static void path_data(char *out, int n, const char *pkg, const char *sub)
|
||||||
{ snprintf(out, n, "%s/MeowData/%s/%s", meow_root(), pkg ? pkg : "", sub ? sub : ""); }
|
{ snprintf(out, n, "%s/MeowData/%s/%s", meow_root(), pkg ? pkg : "", sub ? sub : ""); }
|
||||||
static void path_apps_root(char *out, int n){ snprintf(out,n,"%s/MeowAPPs", meow_root()); }
|
|
||||||
static void path_sources(char *out, int n)
|
static void path_sources(char *out, int n)
|
||||||
{ snprintf(out,n,"%s/MeowOS/opt/nyai/sources.list", meow_root()); }
|
{ snprintf(out,n,"%s/MeowOS/opt/nyai/sources.list", meow_root()); }
|
||||||
static void path_cache(char *out, int n)
|
static void path_cache(char *out, int n)
|
||||||
@@ -177,6 +175,45 @@ static int version_cmp(const char *a, const char *b)
|
|||||||
return *x ? 1 : -1;
|
return *x ? 1 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 点分数字版本:APPS.md 附录 §3「更新规范」升级门禁专用(install_mpk_file)。
|
||||||
|
* 规则:以 '.' 或 '-' 分段,每段必须为纯数字;空串/含非数字段 = 不可解析(返回负数)。
|
||||||
|
* 段数不齐时短者补 0(如 1.2 == 1.2.0)。 */
|
||||||
|
#define MAXVSEG 16
|
||||||
|
static int version_parse(const char *v, unsigned long *segs, int max)
|
||||||
|
{
|
||||||
|
if (!v || !*v) return -1;
|
||||||
|
int n = 0;
|
||||||
|
const char *p = v;
|
||||||
|
while (*p) {
|
||||||
|
while (*p=='.'||*p=='-') p++;
|
||||||
|
if (!*p) break;
|
||||||
|
const char *start = p;
|
||||||
|
while (*p && isdigit((unsigned char)*p)) p++;
|
||||||
|
if (p == start) return -1; /* 空段或非数字段 */
|
||||||
|
unsigned long x = 0;
|
||||||
|
for (const char *q=start; q<p; q++) { x = x*10 + (*q-'0'); if (x > 0x7fffffffUL) return -1; }
|
||||||
|
if (n < max) segs[n++] = x;
|
||||||
|
}
|
||||||
|
return n ? n : -1;
|
||||||
|
}
|
||||||
|
static int version_ok(const char *v)
|
||||||
|
{ unsigned long s[MAXVSEG]; return version_parse(v, s, MAXVSEG) >= 0; }
|
||||||
|
/* 返回:a>b=1, a==b=0, a<b=-1;任一不可解析返回 INT_MIN(不可比) */
|
||||||
|
static int version_num_cmp(const char *a, const char *b)
|
||||||
|
{
|
||||||
|
unsigned long na[MAXVSEG], nb[MAXVSEG];
|
||||||
|
int ca = version_parse(a, na, MAXVSEG);
|
||||||
|
int cb = version_parse(b, nb, MAXVSEG);
|
||||||
|
if (ca < 0 || cb < 0) return INT_MIN;
|
||||||
|
int n = ca > cb ? ca : cb;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
unsigned long xa = i < ca ? na[i] : 0UL; /* 段数不齐,短者补 0 */
|
||||||
|
unsigned long xb = i < cb ? nb[i] : 0UL;
|
||||||
|
if (xa != xb) return xa < xb ? -1 : 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------------- JSON 读取 ---------------- */
|
/* ---------------- JSON 读取 ---------------- */
|
||||||
static int json_str(const char *j, const char *key, char *out, int n)
|
static int json_str(const char *j, const char *key, char *out, int n)
|
||||||
{
|
{
|
||||||
@@ -250,7 +287,8 @@ typedef struct {
|
|||||||
char services[512];
|
char services[512];
|
||||||
char depends[256];
|
char depends[256];
|
||||||
char description[256];
|
char description[256];
|
||||||
int has_routes, has_services;
|
char export_files[1024]; /* raw JSON 数组(导出白名单;无则黑名单模式) */
|
||||||
|
int has_routes, has_services, has_export_files;
|
||||||
} appmeta;
|
} appmeta;
|
||||||
|
|
||||||
static int load_app_meta(const char *path, appmeta *m)
|
static int load_app_meta(const char *path, appmeta *m)
|
||||||
@@ -273,6 +311,7 @@ static int load_app_meta(const char *path, appmeta *m)
|
|||||||
if (json_str(j,"description",m->description,sizeof m->description)!=0) m->description[0]=0;
|
if (json_str(j,"description",m->description,sizeof m->description)!=0) m->description[0]=0;
|
||||||
m->has_routes = json_raw(j,"routes",m->routes,sizeof m->routes)==0;
|
m->has_routes = json_raw(j,"routes",m->routes,sizeof m->routes)==0;
|
||||||
m->has_services = json_raw(j,"services",m->services,sizeof m->services)==0;
|
m->has_services = json_raw(j,"services",m->services,sizeof m->services)==0;
|
||||||
|
m->has_export_files = json_raw(j,"export_files",m->export_files,sizeof m->export_files)==0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -724,34 +763,68 @@ static int install_mpk_file(const char *mpk_path)
|
|||||||
if (!valid_pkg(pkg)) { fprintf(stderr,"nyai: install: 包名含非法字符: %s\n", pkg); return 1; }
|
if (!valid_pkg(pkg)) { fprintf(stderr,"nyai: install: 包名含非法字符: %s\n", pkg); return 1; }
|
||||||
|
|
||||||
printf("Reading package lists... Done\n");
|
printf("Reading package lists... Done\n");
|
||||||
char dst[BUF], bak[BUF];
|
char dst[BUF], bak[BUF], stg[BUF];
|
||||||
snprintf(dst,sizeof dst,"%s/MeowAPPs/%s", meow_root(), pkg);
|
snprintf(dst,sizeof dst,"%s/MeowAPPs/%s", meow_root(), pkg);
|
||||||
snprintf(bak,sizeof bak,"%s.bak_%d", dst, (int)getpid());
|
snprintf(bak,sizeof bak,"%s.bak_%d", dst, (int)getpid());
|
||||||
|
snprintf(stg,sizeof stg,"%s/MeowAPPs/.stg_%d_%s", meow_root(), (int)getpid(), pkg);
|
||||||
int replacing = (access(dst,F_OK)==0);
|
int replacing = (access(dst,F_OK)==0);
|
||||||
if (replacing) {
|
|
||||||
rm_rf(bak);
|
/* 签名分级预留(未实现验签):os.meow.* 系统包二期须有有效签名,无签名/验签失败一律拒装 */
|
||||||
if (rename(dst,bak)!=0) { fprintf(stderr,"nyai: install: 备份 %s 失败: %s\n", dst, strerror(errno)); return 1; }
|
if (!strncmp(pkg,"os.meow.", 8)) printf("nyai: 系统包 %s 需签名(二期)\n", pkg);
|
||||||
printf("Backing up existing %s -> %s\n", pkg, bak);
|
|
||||||
}
|
/* ① 先解到临时目录读元数据,再决定替换(防降级 + 校验 app.json) */
|
||||||
printf("Preparing to unpack %s ...\n", pkg);
|
rm_rf(stg);
|
||||||
if (mkdir_p(dst)!=0) { if (replacing) rename(bak,dst); return 1; }
|
if (mkdir_p(stg)!=0) { fprintf(stderr,"nyai: install: 无法建临时目录 %s\n", stg); return 1; }
|
||||||
char cmd[1024];
|
char cmd[1024];
|
||||||
snprintf(cmd,sizeof cmd,"gzip -dc '%s' | tar -x -C '%s' 2>/dev/null && [ -f '%s/app.json' ]", mpk_path, dst, dst);
|
snprintf(cmd,sizeof cmd,"gzip -dc '%s' | tar -x -C '%s' 2>/dev/null && [ -f '%s/app.json' ]", mpk_path, stg, stg);
|
||||||
printf("Unpacking %s ...\n", pkg);
|
printf("Preparing to unpack %s ...\n", pkg);
|
||||||
if (system(cmd)!=0) {
|
if (system(cmd)!=0) {
|
||||||
fprintf(stderr,"nyai: install: 解压/校验 app.json 失败(%s 可能损坏或非 .mpk)\n", mpk_path);
|
fprintf(stderr,"nyai: install: 解压/校验 app.json 失败(%s 可能损坏或非 .mpk)\n", mpk_path);
|
||||||
rm_rf(dst); if (replacing) rename(bak,dst); return 1;
|
rm_rf(stg); return 1;
|
||||||
}
|
}
|
||||||
char appj[BUF]; snprintf(appj,sizeof appj,"%s/app.json", dst);
|
char stgj[BUF]; snprintf(stgj,sizeof stgj,"%s/app.json", stg);
|
||||||
appmeta m;
|
appmeta m;
|
||||||
if (load_app_meta(appj,&m)!=0) {
|
if (load_app_meta(stgj,&m)!=0) {
|
||||||
fprintf(stderr,"nyai: install: app.json 不合法(缺 name 或 exec)\n");
|
fprintf(stderr,"nyai: install: app.json 不合法(缺 name 或 exec)\n");
|
||||||
rm_rf(dst); if (replacing) rename(bak,dst); return 1;
|
rm_rf(stg); return 1;
|
||||||
}
|
}
|
||||||
|
/* TODO(2A): meow_verify 验签挂点,见 MPK_SIGNATURE_DESIGN.md */
|
||||||
|
|
||||||
|
/* ② 同包名替换前的防降级门禁:新版必须 >= 已装(相等=重装,允许;<即降级拒装) */
|
||||||
|
if (replacing) {
|
||||||
|
char instj[BUF]; snprintf(instj,sizeof instj,"%s/app.json", dst);
|
||||||
|
appmeta im;
|
||||||
|
if (load_app_meta(instj,&im)!=0 || !version_ok(im.version) || !version_ok(m.version)) {
|
||||||
|
fprintf(stderr,"nyai: install: 版本无法判定(新=「%s」 已装=「%s」),拒绝安装\n",
|
||||||
|
m.version[0]?m.version:"(缺失)", im.version[0]?im.version:"(缺失)");
|
||||||
|
rm_rf(stg); return 1;
|
||||||
|
}
|
||||||
|
if (version_num_cmp(m.version, im.version) < 0) {
|
||||||
|
fprintf(stderr,"nyai: install: 不允许降级: 新版本 %s < 已装 %s\n", m.version, im.version);
|
||||||
|
rm_rf(stg); return 1;
|
||||||
|
}
|
||||||
|
rm_rf(bak);
|
||||||
|
if (rename(dst,bak)!=0) { fprintf(stderr,"nyai: install: 备份 %s 失败: %s\n", dst, strerror(errno)); rm_rf(stg); return 1; }
|
||||||
|
printf("Backing up existing %s -> %s\n", pkg, bak);
|
||||||
|
} else {
|
||||||
|
rm_rf(dst); /* 防御:全新安装前清残留 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ③ 部署临时目录 -> 正式目录 */
|
||||||
|
if (rename(stg,dst)!=0) {
|
||||||
|
fprintf(stderr,"nyai: install: 部署 %s 失败: %s\n", dst, strerror(errno));
|
||||||
|
rm_rf(stg); if (replacing) rename(bak,dst); /* 回滚到旧版 */
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ④ 数据 init(MeowData 数据保留,仅保证目录存在) */
|
||||||
char d1[BUF],d2[BUF];
|
char d1[BUF],d2[BUF];
|
||||||
path_data(d1,sizeof d1,pkg,"data"); path_data(d2,sizeof d2,pkg,"cache");
|
path_data(d1,sizeof d1,pkg,"data"); path_data(d2,sizeof d2,pkg,"cache");
|
||||||
mkdir_p(d1); mkdir_p(d2);
|
mkdir_p(d1); mkdir_p(d2);
|
||||||
printf("Setting up %s (%s) ...\n", pkg, m.version[0]?m.version:"?");
|
printf("Setting up %s (%s) ...\n", pkg, m.version[0]?m.version:"?");
|
||||||
|
|
||||||
|
/* ⑤ 安装成功后清理本次创建的 .bak_<pid>(失败回滚路径不动) */
|
||||||
|
if (replacing && bak[0]) { rm_rf(bak); printf("Removed backup %s\n", bak); }
|
||||||
printf("OK\n");
|
printf("OK\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -869,25 +942,138 @@ static int cmd_remove(const char *pkg, int purge)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- 导出过滤助手(APPS.md 附录 §3「导出过滤」) ---- */
|
||||||
|
/* 判断 p 是否含独立 '..' 路径组件 */
|
||||||
|
static int has_dotdot_comp(const char *p)
|
||||||
|
{
|
||||||
|
const char *s = p;
|
||||||
|
while (*s) {
|
||||||
|
if (s[0]=='.' && s[1]=='.' &&
|
||||||
|
(s==p || s[-1]=='/') && (s[2]==0 || s[2]=='/'))
|
||||||
|
return 1;
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/* 校验并相对化导出成员:去前导 '/' 与重复 './',禁绝对路径、禁 '..' 组件、禁 shell 危险字符 */
|
||||||
|
static int sanitize_member(const char *in, char *out, int n)
|
||||||
|
{
|
||||||
|
if (!in || !*in) return -1;
|
||||||
|
const char *p = in;
|
||||||
|
while (*p=='/' || (p[0]=='.'&&p[1]=='/')) p++;
|
||||||
|
if (*p=='/' || !*p) return -1; /* 去完仍是绝对/空 */
|
||||||
|
if (has_dotdot_comp(p)) return -1;
|
||||||
|
if (unsafe_path(p)) return -1;
|
||||||
|
if ((int)strlen(p)+1 > n) return -1;
|
||||||
|
snprintf(out,n,"%s", p);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/* 从 JSON 数组串(含 '[' ']')提取字符串成员;返回项数,负=格式错 */
|
||||||
|
static int json_array_strings(const char *arr, char items[][256], int max)
|
||||||
|
{
|
||||||
|
int n = 0;
|
||||||
|
const char *p = arr;
|
||||||
|
while (*p && *p!='[') p++;
|
||||||
|
if (*p!='[') return -1;
|
||||||
|
p++;
|
||||||
|
while (*p && n<max) {
|
||||||
|
while (*p==' '||*p=='\t'||*p=='\n'||*p=='\r'||*p==',') p++;
|
||||||
|
if (*p==']') break;
|
||||||
|
if (*p!='"') return -1; /* 数组元素必须是字符串 */
|
||||||
|
p++;
|
||||||
|
int o=0;
|
||||||
|
while (*p && *p!='"') {
|
||||||
|
if (*p=='\\' && p[1]) { if (o<255) items[n][o++]=p[1]; p+=2; continue; }
|
||||||
|
if (o<255) items[n][o++]=*p;
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
if (*p!='"') return -1;
|
||||||
|
p++;
|
||||||
|
items[n][o]=0;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
/* 黑名单(默认):排除 *.pre_* *.bak_* logs/ *.log .DS_Store,其余全打 */
|
||||||
|
static int export_blacklist(const char *src, const char *outtmp)
|
||||||
|
{
|
||||||
|
char listf[BUF]; snprintf(listf,sizeof listf,"%s.wl", outtmp);
|
||||||
|
char cmd[2048];
|
||||||
|
snprintf(cmd,sizeof cmd,
|
||||||
|
"(cd '%s' && find . \\( -name '*.pre_*' -o -name '*.bak_*' -o -name '*.log' -o "
|
||||||
|
"-name '.DS_Store' -o -name logs \\) -prune -o -type f -print | sed 's|^\\./||') > '%s'", src, listf);
|
||||||
|
int rc = system(cmd);
|
||||||
|
if (rc!=0) { rm_rf(listf); return -1; }
|
||||||
|
snprintf(cmd,sizeof cmd,"tar -c -C '%s' -T '%s' 2>/dev/null | gzip -c > '%s' 2>/dev/null", src, listf, outtmp);
|
||||||
|
rc = system(cmd);
|
||||||
|
rm_rf(listf);
|
||||||
|
return rc==0 ? 0 : -1;
|
||||||
|
}
|
||||||
|
/* 白名单:只打 app.json + exec + export_files 声明的项(逐项安全校验) */
|
||||||
|
static int export_whitelist(const char *src, const char *exec_rel, const char *export_json, const char *outtmp)
|
||||||
|
{
|
||||||
|
char items[16][256];
|
||||||
|
int nit = json_array_strings(export_json, items, 16);
|
||||||
|
if (nit < 0) { fprintf(stderr,"nyai: export: export_files 格式非法(须为字符串数组)\n"); return -1; }
|
||||||
|
char listf[BUF]; snprintf(listf,sizeof listf,"%s.wl", outtmp);
|
||||||
|
FILE *lf = fopen(listf,"w");
|
||||||
|
if (!lf) return -1;
|
||||||
|
fprintf(lf,"app.json\n%s\n", exec_rel);
|
||||||
|
for (int i=0;i<nit;i++) {
|
||||||
|
char mem[BUF];
|
||||||
|
if (sanitize_member(items[i], mem, sizeof mem)!=0) {
|
||||||
|
fclose(lf); rm_rf(listf);
|
||||||
|
fprintf(stderr,"nyai: export: 白名单项含非法路径: %s\n", items[i]); return -1;
|
||||||
|
}
|
||||||
|
char p[BUF]; snprintf(p,sizeof p,"%s/%s", src, mem);
|
||||||
|
if (access(p,F_OK)!=0) {
|
||||||
|
fclose(lf); rm_rf(listf);
|
||||||
|
fprintf(stderr,"nyai: export: 白名单项不存在: %s\n", mem); return -1;
|
||||||
|
}
|
||||||
|
fprintf(lf,"%s\n", mem);
|
||||||
|
}
|
||||||
|
fclose(lf);
|
||||||
|
char cmd[2048];
|
||||||
|
snprintf(cmd,sizeof cmd,"tar -c -C '%s' -T '%s' 2>/dev/null | gzip -c > '%s' 2>/dev/null", src, listf, outtmp);
|
||||||
|
int rc = system(cmd);
|
||||||
|
rm_rf(listf);
|
||||||
|
return rc==0 ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
static int cmd_export(const char *pkg, const char *out)
|
static int cmd_export(const char *pkg, const char *out)
|
||||||
{
|
{
|
||||||
if (!pkg || !valid_pkg(pkg)) { fprintf(stderr,"nyai: export: 包名无效\n"); return 2; }
|
if (!pkg || !valid_pkg(pkg)) { fprintf(stderr,"nyai: export: 包名无效\n"); return 2; }
|
||||||
char src[BUF],dst[BUF];
|
char src[BUF],dst[BUF];
|
||||||
snprintf(src,sizeof src,"%s/MeowAPPs/%s", meow_root(), pkg);
|
snprintf(src,sizeof src,"%s/MeowAPPs/%s", meow_root(), pkg);
|
||||||
if (access(src,R_OK)!=0) { fprintf(stderr,"nyai: export: 未安装 %s\n", pkg); return 1; }
|
if (access(src,R_OK)!=0) { fprintf(stderr,"nyai: export: 未安装 %s\n", pkg); return 1; }
|
||||||
|
/* 导出前体检:app.json 存在且合法(有 name+exec) */
|
||||||
|
char appj[BUF]; snprintf(appj,sizeof appj,"%s/app.json", src);
|
||||||
|
appmeta m;
|
||||||
|
if (load_app_meta(appj,&m)!=0) { fprintf(stderr,"nyai: export: %s/app.json 不合法(缺 name 或 exec)\n", pkg); return 1; }
|
||||||
|
/* 导出前体检:exec 文件存在且可执行 */
|
||||||
|
char exec_rel[BUF];
|
||||||
|
if (sanitize_member(m.exec, exec_rel, sizeof exec_rel)!=0) {
|
||||||
|
fprintf(stderr,"nyai: export: exec 字段非法: %s\n", m.exec); return 1;
|
||||||
|
}
|
||||||
|
char exepath[BUF]; snprintf(exepath,sizeof exepath,"%s/%s", src, exec_rel);
|
||||||
|
if (access(exepath, X_OK)!=0) {
|
||||||
|
fprintf(stderr,"nyai: export: exec 文件 %s 不存在或不可执行,拒绝导出\n", exepath); return 1;
|
||||||
|
}
|
||||||
char outbuf[BUF];
|
char outbuf[BUF];
|
||||||
if (out && *out && unsafe_path(out)) { fprintf(stderr,"nyai: export: 输出路径含非法字符\n"); return 1; }
|
if (out && *out && unsafe_path(out)) { fprintf(stderr,"nyai: export: 输出路径含非法字符\n"); return 1; }
|
||||||
snprintf(outbuf,sizeof outbuf,"%s", out && *out ? out : pkg);
|
snprintf(outbuf,sizeof outbuf,"%s", out && *out ? out : pkg);
|
||||||
if (!strstr(outbuf,".mpk")) strncat(outbuf,".mpk", sizeof outbuf-strlen(outbuf)-1);
|
if (!strstr(outbuf,".mpk")) strncat(outbuf,".mpk", sizeof outbuf-strlen(outbuf)-1);
|
||||||
snprintf(dst,sizeof dst,"%s", outbuf);
|
snprintf(dst,sizeof dst,"%s", outbuf);
|
||||||
printf("Exporting %s -> %s ...\n", pkg, dst);
|
printf("Exporting %s -> %s ...\n", pkg, dst);
|
||||||
char cmd[1024];
|
|
||||||
snprintf(cmd,sizeof cmd,"tar -c -C '%s' . 2>/dev/null | gzip -c > '%s.tmp' 2>/dev/null", src, dst);
|
|
||||||
system(cmd);
|
|
||||||
char tmpp[BUF]; snprintf(tmpp,sizeof tmpp,"%s.tmp", dst);
|
char tmpp[BUF]; snprintf(tmpp,sizeof tmpp,"%s.tmp", dst);
|
||||||
if (access(tmpp,F_OK)!=0 || rename(tmpp,dst)!=0) {
|
int rc;
|
||||||
fprintf(stderr,"nyai: export: tar/gzip 失败\n"); rm_rf(tmpp); return 1;
|
if (m.has_export_files && m.export_files[0]) {
|
||||||
|
rc = export_whitelist(src, exec_rel, m.export_files, tmpp);
|
||||||
|
} else {
|
||||||
|
rc = export_blacklist(src, tmpp);
|
||||||
}
|
}
|
||||||
|
if (rc!=0 || access(tmpp,F_OK)!=0) { fprintf(stderr,"nyai: export: 打包失败\n"); rm_rf(tmpp); return 1; }
|
||||||
|
if (rename(tmpp,dst)!=0) { fprintf(stderr,"nyai: export: 写 %s 失败\n", dst); rm_rf(tmpp); return 1; }
|
||||||
printf("OK\n");
|
printf("OK\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -929,6 +1115,21 @@ static int cmd_upgrade(const char *pkg, const char *file)
|
|||||||
if (access(mpk,R_OK)!=0) { fprintf(stderr,"nyai: upgrade: 找不到包文件 %s\n", mpk); return 1; }
|
if (access(mpk,R_OK)!=0) { fprintf(stderr,"nyai: upgrade: 找不到包文件 %s\n", mpk); return 1; }
|
||||||
char dst[BUF]; snprintf(dst,sizeof dst,"%s/MeowAPPs/%s", meow_root(), pkg);
|
char dst[BUF]; snprintf(dst,sizeof dst,"%s/MeowAPPs/%s", meow_root(), pkg);
|
||||||
if (access(dst,F_OK)!=0) { fprintf(stderr,"nyai: upgrade: %s 未安装(如需全新安装请用 install)\n", pkg); return 1; }
|
if (access(dst,F_OK)!=0) { fprintf(stderr,"nyai: upgrade: %s 未安装(如需全新安装请用 install)\n", pkg); return 1; }
|
||||||
|
/* 防降级门禁(与 install 同规范):新版必须 >= 已装(相等=重装允许) */
|
||||||
|
{
|
||||||
|
char aj[BUF]; snprintf(aj,sizeof aj,"%s/app.json", dst);
|
||||||
|
appmeta im, nm;
|
||||||
|
if (load_app_meta(aj,&im)!=0 || mpk_appinfo(mpk,&nm)!=0 ||
|
||||||
|
!version_ok(im.version) || !version_ok(nm.version)) {
|
||||||
|
fprintf(stderr,"nyai: upgrade: 版本无法判定(新=「%s」 已装=「%s」),拒绝\n",
|
||||||
|
nm.version[0]?nm.version:"(缺失)", im.version[0]?im.version:"(缺失)");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (version_num_cmp(nm.version, im.version) < 0) {
|
||||||
|
fprintf(stderr,"nyai: upgrade: 不允许降级: 新版本 %s < 已装 %s\n", nm.version, im.version);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
char bak[BUF]; snprintf(bak,sizeof bak,"%s.bak_%d", dst,(int)getpid());
|
char bak[BUF]; snprintf(bak,sizeof bak,"%s.bak_%d", dst,(int)getpid());
|
||||||
rm_rf(bak);
|
rm_rf(bak);
|
||||||
if (rename(dst,bak)!=0) { fprintf(stderr,"nyai: upgrade: 备份失败: %s\n", strerror(errno)); return 1; }
|
if (rename(dst,bak)!=0) { fprintf(stderr,"nyai: upgrade: 备份失败: %s\n", strerror(errno)); return 1; }
|
||||||
@@ -943,6 +1144,7 @@ static int cmd_upgrade(const char *pkg, const char *file)
|
|||||||
char d1[BUF],d2[BUF];
|
char d1[BUF],d2[BUF];
|
||||||
path_data(d1,sizeof d1,pkg,"data"); path_data(d2,sizeof d2,pkg,"cache");
|
path_data(d1,sizeof d1,pkg,"data"); path_data(d2,sizeof d2,pkg,"cache");
|
||||||
mkdir_p(d1); mkdir_p(d2);
|
mkdir_p(d1); mkdir_p(d2);
|
||||||
|
if (bak[0]) { rm_rf(bak); } /* 升级成功后清理本次 .bak_<pid> */
|
||||||
printf("OK (%s) — data preserved\n", m.version[0]?m.version:"?");
|
printf("OK (%s) — data preserved\n", m.version[0]?m.version:"?");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
BIN
Binary file not shown.
Reference in new issue
Block a user