feat(pkg): version_code 安卓式版本规则落地 (agent-190, 用户定案 APPS §3.8) — app.json 增 version_code(纯数字唯一比较依据), version 仅显示; nyai install/upgrade/list-upgradable/index 全走 code 比较(缺 code 拒装); launcher meow_pkg_install 同步门禁; 旧点分比较函数移除
nyai f2938d28→4cf5f690; launcher 24c9ee5d→ab8e610f; 宿主 9 场景全过(升/同版/降级拒/缺code拒/index/upgrade一致) 出入: 全新安装缺 code 放行(门禁仅 replacing 分支); export 保留签名的版本比对仍用显示版字符串(nyai+launcher 一致,待后续统一)
This commit is contained in:
1 parent
555c4a458e
commit
60072a08f1
5 files changed
+75
-76
No files matched your search
+55
-76
@@ -5,7 +5,7 @@
|
||||
* APPS.md 附录 v2)。支持镜像源:
|
||||
* - 源配置 MeowOS/opt/nyai/sources.list(每行一个源;local <路径> / http <url> /
|
||||
* deb [trusted=yes] <uri> / file:// ; # 注释)。默认 local MeowOS/opt/repo。
|
||||
* - 每源一个 Packages 索引(行式 Package/Version/Filename/Size/SHA256/Depends/Description),
|
||||
* - 每源一个 Packages 索引(行式 Package/Version/Version-Code/Filename/Size/SHA256/Depends/Description),
|
||||
* 由 `nyai index <目录>` 生成。
|
||||
* - update 缓存索引到 MeowOS/opt/nyai/cache;install 按名称从索引取包(本地文件或 http 拉取),
|
||||
* 处理 Depends(递归)、校验 SHA256 + app.json;同 name 先备份;解压 MeowAPPs/<pkg>/;
|
||||
@@ -58,6 +58,8 @@ typedef struct {
|
||||
typedef struct {
|
||||
char pkg[128];
|
||||
char version[64];
|
||||
long long version_code; /* 索引 Version-Code(正整数;has_=0 表示索引缺该项) */
|
||||
int has_version_code;
|
||||
char filename[256];
|
||||
char sha256[128];
|
||||
char depends[256];
|
||||
@@ -154,67 +156,6 @@ static void human_size(long long b, char *out, int n)
|
||||
else snprintf(out,n,"%.2f MB", b/(1024.0*1024));
|
||||
}
|
||||
|
||||
/* 简版版本比较(点/横分段,数字段按数值):a>b 返回 1,=0,< -1 */
|
||||
static int version_cmp(const char *a, const char *b)
|
||||
{
|
||||
const char *x=a,*y=b;
|
||||
while (*x || *y) {
|
||||
while (*x=='.'||*x=='-') x++;
|
||||
while (*y=='.'||*y=='-') y++;
|
||||
if (!*x || !*y) break;
|
||||
if (isdigit((unsigned char)*x) && isdigit((unsigned char)*y)) {
|
||||
long nx=0, ny=0;
|
||||
while (isdigit((unsigned char)*x)) nx=nx*10+(*x++ - '0');
|
||||
while (isdigit((unsigned char)*y)) ny=ny*10+(*y++ - '0');
|
||||
if (nx!=ny) return nx<ny?-1:1;
|
||||
} else {
|
||||
if (*x!=*y) return *x<*y?-1:1;
|
||||
x++; y++;
|
||||
}
|
||||
}
|
||||
if (!*x && !*y) return 0;
|
||||
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 读取 ---------------- */
|
||||
static int json_str(const char *j, const char *key, char *out, int n)
|
||||
{
|
||||
@@ -278,10 +219,38 @@ static int json_raw(const char *j, const char *key, char *out, int n)
|
||||
out[o]=0;
|
||||
return out[0] ? 0 : -1;
|
||||
}
|
||||
/* 读 JSON 整型字段(键带引号、值必须为纯数字,如 "version_code": 12)。
|
||||
* 缺键 / 值空 / 非数字 / 溢出 → 返回 -1;成功返回 0 并写入 *out。 */
|
||||
static int json_int(const char *j, const char *key, long long *out)
|
||||
{
|
||||
if (!j || !key || !out) return -1;
|
||||
char pat[160];
|
||||
snprintf(pat, sizeof pat, "\"%s\"", key);
|
||||
const char *p = j;
|
||||
while ((p = strstr(p, pat)) != NULL) {
|
||||
p += strlen(pat);
|
||||
while (*p==' '||*p=='\t'||*p=='\n'||*p=='\r') p++;
|
||||
if (*p != ':') { p++; continue; }
|
||||
p++;
|
||||
while (*p==' '||*p=='\t'||*p=='\n'||*p=='\r') p++;
|
||||
if (!isdigit((unsigned char)*p)) return -1; /* 缺值 / 字符串 / 非数字 */
|
||||
long long v = 0;
|
||||
while (isdigit((unsigned char)*p)) {
|
||||
int d = *p++ - '0';
|
||||
if (v > (LLONG_MAX - d) / 10) return -1; /* 溢出 */
|
||||
v = v * 10 + d;
|
||||
}
|
||||
*out = v;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
char name[256];
|
||||
char version[64];
|
||||
char version[64]; /* 仅作显示字符串(versionName 角色,不作比较) */
|
||||
long long version_code; /* app.json "version_code"(正整数,安装/升级比较唯一依据;缺时 has_=0) */
|
||||
int has_version_code; /* app.json 是否显式声明 version_code */
|
||||
char exec[512];
|
||||
char package[64]; /* app.json "package"(权威包名,== 文件名茎;II 期绑定校验用) */
|
||||
char author[128];
|
||||
@@ -309,6 +278,7 @@ static int load_app_meta(const char *path, appmeta *m)
|
||||
if (json_str(j,"exec",m->exec,sizeof m->exec)!=0 || !m->exec[0]) return -1;
|
||||
if (json_str(j,"package",m->package,sizeof m->package)!=0) m->package[0]=0;
|
||||
if (json_str(j,"version",m->version,sizeof m->version)!=0) m->version[0]=0;
|
||||
m->has_version_code = (json_int(j,"version_code",&m->version_code)==0);
|
||||
if (json_str(j,"author",m->author,sizeof m->author)!=0) m->author[0]=0;
|
||||
if (json_str(j,"depends",m->depends,sizeof m->depends)!=0) m->depends[0]=0;
|
||||
if (json_str(j,"description",m->description,sizeof m->description)!=0) m->description[0]=0;
|
||||
@@ -344,6 +314,7 @@ static int mpk_appinfo(const char *file, appmeta *m)
|
||||
j[r]=0;
|
||||
if (json_str(j,"name",m->name,sizeof m->name)!=0 || !m->name[0]) return -1;
|
||||
if (json_str(j,"version",m->version,sizeof m->version)!=0) m->version[0]=0;
|
||||
m->has_version_code = (json_int(j,"version_code",&m->version_code)==0);
|
||||
if (json_str(j,"depends",m->depends,sizeof m->depends)!=0) m->depends[0]=0;
|
||||
if (json_str(j,"description",m->description,sizeof m->description)!=0) m->description[0]=0;
|
||||
return 0;
|
||||
@@ -490,6 +461,7 @@ static int parse_index(const char *path, pkgrec *recs, int max)
|
||||
char *key = trim(t), *val = trim(colon+1);
|
||||
if (!strcmp(key,"Package")) { snprintf(cur.pkg,sizeof cur.pkg,"%s",val); have = (cur.pkg[0]!=0); }
|
||||
else if (!strcmp(key,"Version")) snprintf(cur.version,sizeof cur.version,"%s",val);
|
||||
else if (!strcmp(key,"Version-Code")) { cur.version_code = atoll(val); cur.has_version_code = (val[0]!=0); }
|
||||
else if (!strcmp(key,"Filename")) snprintf(cur.filename,sizeof cur.filename,"%s",val);
|
||||
else if (!strcmp(key,"SHA256")) snprintf(cur.sha256,sizeof cur.sha256,"%s",val);
|
||||
else if (!strcmp(key,"Size")) { cur.size = atoll(val); }
|
||||
@@ -684,6 +656,7 @@ static int cmd_index(const char *dir)
|
||||
char sha[128]=""; sha256_file(fpath, sha, sizeof sha);
|
||||
fprintf(f,"Package: %s\n", stem);
|
||||
fprintf(f,"Version: %s\n", ver[0]?ver:"0.0.0");
|
||||
if (m.has_version_code) fprintf(f,"Version-Code: %lld\n", m.version_code);
|
||||
fprintf(f,"Filename: %s\n", de->d_name);
|
||||
fprintf(f,"Size: %lld\n", (long long)st.st_size);
|
||||
if (sha[0]) fprintf(f,"SHA256: %s\n", sha);
|
||||
@@ -738,7 +711,9 @@ static int cmd_list_upgradable(pkgrec *recs, int nrec)
|
||||
char ap[BUF]; snprintf(ap,sizeof ap,"%s/%s/app.json", r, de->d_name);
|
||||
appmeta m;
|
||||
if (load_app_meta(ap,&m)!=0) break;
|
||||
if (version_cmp(recs[i].version, m.version) > 0) {
|
||||
/* 仅按 version_code 比较(APPS.md §3.8);任一侧缺 code → 不判定为可升级 */
|
||||
if (recs[i].has_version_code && m.has_version_code &&
|
||||
recs[i].version_code > m.version_code) {
|
||||
printf("%-24s %-12s %-12s\n", de->d_name, m.version[0]?m.version:"?", recs[i].version);
|
||||
n++;
|
||||
}
|
||||
@@ -906,17 +881,18 @@ static int install_mpk_file(const char *mpk_path)
|
||||
rm_rf(stg); return 1;
|
||||
}
|
||||
|
||||
/* ② 同包名替换前的防降级门禁:新版必须 >= 已装(相等=重装,允许;<即降级拒装) */
|
||||
/* ② 同包名替换前的防降级门禁(APPS.md §3.8,仅按 version_code 比较):
|
||||
* 新版 code >= 已装 code(相等=重装,允许;<即降级拒装);任一侧缺 version_code → 拒装。 */
|
||||
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:"(缺失)");
|
||||
if (load_app_meta(instj,&im)!=0 || !m.has_version_code || !im.has_version_code) {
|
||||
fprintf(stderr,"nyai: install: 版本号无法判定(新缺 version_code 或已装缺 version_code),拒绝安装\n");
|
||||
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);
|
||||
if (m.version_code < im.version_code) {
|
||||
fprintf(stderr,"nyai: install: 不允许降级: 新版本 code %lld < 已装 code %lld\n",
|
||||
m.version_code, im.version_code);
|
||||
rm_rf(stg); return 1;
|
||||
}
|
||||
rm_rf(bak);
|
||||
@@ -1034,6 +1010,8 @@ static int cmd_info(const char *pkg)
|
||||
printf("Package: %s\n", pkg);
|
||||
printf("Name: %s\n", m.name);
|
||||
printf("Version: %s\n", m.version[0]?m.version:"(none)");
|
||||
if (m.has_version_code) printf("Version-Code: %lld\n", m.version_code);
|
||||
else printf("Version-Code: (none)\n");
|
||||
printf("Author: %s\n", m.author[0]?m.author:"(none)");
|
||||
printf("Exec: %s\n", m.exec);
|
||||
printf("Size: %s\n", hs);
|
||||
@@ -1247,18 +1225,19 @@ static int cmd_upgrade(const char *pkg, const char *file)
|
||||
else if (vr == 2) { if (sysp) { fprintf(stderr,"nyai: upgrade: 系统包 %s 必须签名,拒绝升级\n", pkg); return 1; } printf("nyai: 提示: %s 未签名(第三方放行)\n", pkg); }
|
||||
else if (vr != 0) { if (sysp) { fprintf(stderr,"nyai: upgrade: meow_verify 不可用(rc=%d),os.meow.* 拒升\n", vr); return 1; } printf("nyai: 警告: meow_verify 不可用(rc=%d),%s 未验签放行\n", vr, pkg); }
|
||||
}
|
||||
/* 防降级门禁(与 install 同规范):新版必须 >= 已装(相等=重装允许) */
|
||||
/* 防降级门禁(与 install 同规范,APPS.md §3.8,仅按 version_code 比较):
|
||||
* 新版 code >= 已装 code(相等=重装允许);任一侧缺 version_code → 拒绝。 */
|
||||
{
|
||||
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:"(缺失)");
|
||||
!im.has_version_code || !nm.has_version_code) {
|
||||
fprintf(stderr,"nyai: upgrade: 版本号无法判定(新缺 version_code 或已装缺 version_code),拒绝\n");
|
||||
return 1;
|
||||
}
|
||||
if (version_num_cmp(nm.version, im.version) < 0) {
|
||||
fprintf(stderr,"nyai: upgrade: 不允许降级: 新版本 %s < 已装 %s\n", nm.version, im.version);
|
||||
if (nm.version_code < im.version_code) {
|
||||
fprintf(stderr,"nyai: upgrade: 不允许降级: 新版本 code %lld < 已装 code %lld\n",
|
||||
nm.version_code, im.version_code);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user