镜像自地址
https://github.com/JGZYES/ParlzPackageManger.git
已同步 2026-09-11 05:42:44 +08:00
feat(err): 结构化错误码系统 — 每个报错带错误码与解决办法
- 新增 src/pmm_err.h: 稳定错误码枚举 PMM_E_* (USAGE/NOT_FOUND/DOWNLOAD/ NETWORK/NO_MIRROR/REGISTRY/CHECKSUM/EXTRACT/CONFLICT/NO_TAR/NO_ROOT/CACHE/LANG/INTERNAL) - out.c: 新增 pmm_error_c(code,hint,fmt,...) 统一输出 [PMM]:[E<code>]: msg (hint: hint) - 所有高频报错点(main/pdm/install/json/ppdm)接入错误码+hint - 杜绝空 [PMM]:[ERROR]: (空消息兜底) 与裸 msg.xxx key (i18n 互补内置包回退)
这个提交包含在:
@@ -0,0 +1,27 @@
|
||||
# PMM v0.6.0
|
||||
|
||||
## 新增
|
||||
- **错误码系统(结构化报错)**:新增 `pmm_err.h` 定义稳定的错误码枚举
|
||||
`PMM_E_*`(`PMM_E_USAGE=1`、`PMM_E_NOT_FOUND=2`、`PMM_E_DOWNLOAD=3`、
|
||||
`PMM_E_NETWORK=4`、`PMM_E_NO_MIRROR=5`、`PMM_E_REGISTRY=6`、`PMM_E_CHECKSUM=7`、
|
||||
`PMM_E_EXTRACT=8`、`PMM_E_CONFLICT=9`、`PMM_E_NO_TAR=10`、`PMM_E_NO_ROOT=11`、
|
||||
`PMM_E_CACHE=12`、`PMM_E_LANG=13`、`PMM_E_INTERNAL=99`),每个报错都可以被
|
||||
脚本 / 工具 / 用户可靠识别。
|
||||
- **`pmm_error_c(code, hint, fmt, ...)`**:统一输出
|
||||
`[PMM]:[E<code>]: <消息> (hint: <解决办法>)`,每条错误都带**回答**(怎么修)。
|
||||
- **高频报错点全部接入错误码**:`main.c` / `pdm.c` / `install.c` / `json.c` /
|
||||
`ppdm.c` 的用法、找不到、下载失败、校验和、解包、冲突、缺镜像、缺 tar、
|
||||
权限、缓存、语言包等报错统一走 `pmm_error_c`。
|
||||
|
||||
## 修复
|
||||
- **杜绝裸/空报错**:
|
||||
- `pmm_error` 在消息为空时不再输出 `[PMM]:[ERROR]:`(兜底为 `(no message)`)。
|
||||
- `pmm_tr` 增加“互补内置包回退”:中文内置表是英文全表的子集,缺键时再回退到
|
||||
英文内置表,任何 `msg.xxx` 键都不会裸显示原始 key。
|
||||
- 报错格式统一为 `[PMM]:[E<code>]: 消息 (hint: 提示)`,颜色仅在有 TTY 时包裹,
|
||||
日志/管道下输出纯文本,方便机器解析。
|
||||
|
||||
## 说明
|
||||
- 兼容性:错误码在 0.6.0 起稳定;旧脚本若匹配 `[PMM]:[ERROR]:` 前缀,建议改用
|
||||
`[PMM]:[E<NN>]:`(`<NN>` 为两位数字错误码)。
|
||||
- `ppdm` 仍为独立版本 `0.0.1`,本次同步接入错误码格式。
|
||||
+11
-2
@@ -9,6 +9,7 @@
|
||||
|
||||
static JsonValue *g_lang = NULL;
|
||||
static JsonValue *g_builtin = NULL;
|
||||
static JsonValue *g_builtin_alt = NULL;
|
||||
static char g_locale[64] = "";
|
||||
|
||||
/* Detect locale from LANG / LC_ALL env, else default "zh-CN". */
|
||||
@@ -92,8 +93,10 @@ int pmm_lang_load_active(void) {
|
||||
}
|
||||
|
||||
const char *pmm_tr(const char *key) {
|
||||
/* Look up in the loaded pack first, then fall back to the built-in zh-CN
|
||||
* table so a stale .pjson never shows a raw "msg.xxx" key. */
|
||||
/* Look up in the loaded pack first, then the locale-matched built-in, then
|
||||
* the *other* built-in. The zh table is a small subset of the full en table,
|
||||
* so without this last fallback a stale pack or a short zh table would leak
|
||||
* a raw "msg.xxx" key. */
|
||||
if (g_lang) {
|
||||
const char *t = json_str(g_lang, key);
|
||||
if (t) return t;
|
||||
@@ -103,6 +106,12 @@ const char *pmm_tr(const char *key) {
|
||||
const char *b = json_str(g_builtin, key);
|
||||
if (b) return b;
|
||||
}
|
||||
if (!g_builtin_alt)
|
||||
g_builtin_alt = json_parse((builtin_source() == kBuiltinEn) ? kBuiltinZh : kBuiltinEn);
|
||||
if (g_builtin_alt) {
|
||||
const char *b = json_str(g_builtin_alt, key);
|
||||
if (b) return b;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
+21
-19
@@ -14,6 +14,7 @@
|
||||
#include "install.h"
|
||||
#include "pmm.h"
|
||||
#include "out.h"
|
||||
#include "pmm_err.h"
|
||||
#include "i18n.h"
|
||||
#include "http.h"
|
||||
#include "json.h"
|
||||
@@ -123,7 +124,7 @@ static int verify_checksums(const char *url, const char *path, const char *name)
|
||||
}
|
||||
/* case-insensitive compare */
|
||||
if (strcasecmp(expect, hex) != 0) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.checksum-mismatch", algos[i].ext, expect, hex));
|
||||
pmm_error_c(PMM_E_CHECKSUM, "文件校验和不匹配, 可能损坏, 重新下载或换镜像", "%s", pmm_tr_fmt("msg.checksum-mismatch", algos[i].ext, expect, hex));
|
||||
return -1;
|
||||
}
|
||||
pmm_success("%s", pmm_tr_fmt("msg.checksum-ok", algos[i].algo == 256 ? "sha256" : "sha1", hex));
|
||||
@@ -421,7 +422,7 @@ int install_file(const char *url, const char *name) {
|
||||
if (pmm_offline) {
|
||||
FILE *cf = fopen(path, "rb");
|
||||
if (!cf) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.not-cached", name));
|
||||
pmm_error_c(PMM_E_CACHE, "离线模式但本地没有该包缓存, 先联网 'pmm fetch <包名>'", "%s", pmm_tr_fmt("msg.err.not-cached", name));
|
||||
return -1;
|
||||
}
|
||||
fclose(cf);
|
||||
@@ -445,7 +446,7 @@ int install_file(const char *url, const char *name) {
|
||||
free(cands);
|
||||
mirrors_free(ml);
|
||||
if (ok != 0) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.download-failed", url));
|
||||
pmm_error_c(PMM_E_DOWNLOAD, "检查网络连接或镜像地址, 用 'pmm mirror check' 排障", "%s", pmm_tr_fmt("msg.err.download-failed", url));
|
||||
return -1;
|
||||
}
|
||||
pmm_success("%s", pmm_tr_fmt("msg.downloaded", path));
|
||||
@@ -457,7 +458,7 @@ int install_file(const char *url, const char *name) {
|
||||
if (pmm_sha1_file(path, hex) == 0)
|
||||
pmm_info("%s", pmm_tr_fmt("msg.hash-sha1", hex));
|
||||
if (verify_checksums(url, path, name) != 0) {
|
||||
pmm_error(pmm_tr("msg.err.checksum-refuse"));
|
||||
pmm_error_c(PMM_E_CHECKSUM, "校验和不匹配, 拒绝安装, 尝试 'pmm fetch --fresh' 重新下载", "%s", pmm_tr("msg.err.checksum-refuse"));
|
||||
remove(path);
|
||||
return -1;
|
||||
}
|
||||
@@ -499,7 +500,7 @@ static int install_path(const char *path, const char *name) {
|
||||
extern int pdm_install_file(const char *path);
|
||||
pmm_info("%s", pmm_tr_fmt("msg.installing", bname));
|
||||
if (pdm_install_file(path) != 0) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.failed-install", bname));
|
||||
pmm_error_c(PMM_E_INTERNAL, ".pdm 安装失败, 详情见上方错误", "%s", pmm_tr_fmt("msg.err.failed-install", bname));
|
||||
return -1;
|
||||
}
|
||||
pmm_success("%s", pmm_tr_fmt("msg.installed", bname));
|
||||
@@ -571,7 +572,7 @@ static int install_path(const char *path, const char *name) {
|
||||
"elif command -v alien >/dev/null 2>&1; then sudo alien -i \"%s\"; "
|
||||
"else echo -e \"\\033[31m[PMM]:[ERROR]cannot unpack .rpm (need rpm/cpio)\\033[0m\" 1>&2; exit 1; fi",
|
||||
path, path, path);
|
||||
if (system(cmd) != 0) { pmm_error(pmm_tr("msg.err.failed-install")); return -1; }
|
||||
if (system(cmd) != 0) { pmm_error_c(PMM_E_INTERNAL, "rpm 安装失败, 检查是否缺少 rpm/rpm2cpio", "%s", pmm_tr("msg.err.failed-install")); return -1; }
|
||||
(void)dcmd; (void)ct;
|
||||
#else
|
||||
/* create the (possibly multi-level) stage dir first; pmm_cpio_unpack
|
||||
@@ -581,18 +582,18 @@ static int install_path(const char *path, const char *name) {
|
||||
char mkstag[1400];
|
||||
snprintf(mkstag, sizeof(mkstag), "mkdir -p \"%s\"", fstage);
|
||||
if (system(mkstag) != 0) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.rpm-stage", fstage));
|
||||
pmm_error_c(PMM_E_EXTRACT, "无法创建解包目录, 检查缓存目录权限", "%s", pmm_tr_fmt("msg.err.rpm-stage", fstage));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
/* unpack into stage (no sudo needed; stage is user-writable) */
|
||||
if (pmm_cpio_unpack_cmd(fstage, dcmd) != 0) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.rpm-decompress", dc));
|
||||
pmm_error_c(PMM_E_EXTRACT, "rpm 解压失败, 可能需要 gzip/zstd 支持", "%s", pmm_tr_fmt("msg.err.rpm-decompress", dc));
|
||||
return -1;
|
||||
}
|
||||
/* copy the unpacked payload into /, then clean up */
|
||||
if (system(cmd) != 0) {
|
||||
pmm_error(pmm_tr("msg.err.rpm-copy"));
|
||||
pmm_error_c(PMM_E_INTERNAL, "rpm 拷贝到系统目录失败(可能权限不足, 需 sudo)", "%s", pmm_tr("msg.err.rpm-copy"));
|
||||
return -1;
|
||||
}
|
||||
(void)ct;
|
||||
@@ -656,7 +657,7 @@ static int install_path(const char *path, const char *name) {
|
||||
|
||||
pmm_info("%s", pmm_tr_fmt("msg.installing", bname));
|
||||
if (run_cmd_quiet(cmd) != 0) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.failed-install", cmd));
|
||||
pmm_error_c(PMM_E_INTERNAL, "安装命令执行失败, 检查文件格式/权限(系统模式需 sudo)", "%s", pmm_tr_fmt("msg.err.failed-install", cmd));
|
||||
return -1;
|
||||
}
|
||||
pmm_success("%s", pmm_tr_fmt("msg.installed", bname));
|
||||
@@ -667,9 +668,9 @@ static int install_path(const char *path, const char *name) {
|
||||
/* Install a local file by its extension — e.g. `pmm install -dpkg foo.deb`
|
||||
* (Linux) or `pmm install -msi foo.msi` (Windows). Returns 0 on success. */
|
||||
int install_local_file(const char *path) {
|
||||
if (!path || !*path) { pmm_error(pmm_tr("msg.err.empty-path")); return -1; }
|
||||
if (!path || !*path) { pmm_error_c(PMM_E_USAGE, "请提供要安装的文件路径", "%s", pmm_tr("msg.err.empty-path")); return -1; }
|
||||
FILE *chk = fopen(path, "rb");
|
||||
if (!chk) { pmm_error("%s", pmm_tr_fmt("msg.err.cannot-open", path)); return -1; }
|
||||
if (!chk) { pmm_error_c(PMM_E_NOT_FOUND, "检查文件路径是否存在且可读", "%s", pmm_tr_fmt("msg.err.cannot-open", path)); return -1; }
|
||||
fclose(chk);
|
||||
pmm_info("%s", pmm_tr_fmt("msg.installing-file", path));
|
||||
return install_path(path, path);
|
||||
@@ -818,7 +819,8 @@ int install_from_registry(const char *name, const char *spec, const char *mirror
|
||||
if (ml->items[i].registry && *ml->items[i].registry) has_reg = 1;
|
||||
|
||||
if (!has_reg) {
|
||||
pmm_error(
|
||||
pmm_error_c(PMM_E_NO_MIRROR,
|
||||
"配置镜像: 在 ~/.pmm/mirror.ini 添加 [名称] + registry 地址, 或用 'pmm setting mirror add'",
|
||||
"no registry mirror configured. Add to ~/.pmm/mirror.ini:\n"
|
||||
" [name]\n registry = https://host/pmm\n"
|
||||
" (apt-style: mirrors are tried by priority; the first with the\n"
|
||||
@@ -841,7 +843,7 @@ int install_from_registry(const char *name, const char *spec, const char *mirror
|
||||
if (!seen[i] && ml->items[i].registry && *ml->items[i].registry)
|
||||
bases[nb++] = ml->items[i].registry;
|
||||
|
||||
if (nb == 0) { pmm_error(pmm_tr("msg.err.no-registry-mirror")); mirrors_free(ml); return -1; }
|
||||
if (nb == 0) { pmm_error_c(PMM_E_NO_MIRROR, "检查镜像配置, 添加可用镜像", "%s", pmm_tr("msg.err.no-registry-mirror")); mirrors_free(ml); return -1; }
|
||||
|
||||
/* fetch the <pkg>.json latest pointer (carries the variants list) */
|
||||
char url[2048];
|
||||
@@ -873,14 +875,14 @@ int install_from_registry(const char *name, const char *spec, const char *mirror
|
||||
if (used) break;
|
||||
}
|
||||
if (!body) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.registry-not-found", name));
|
||||
pmm_error_c(PMM_E_NOT_FOUND, "检查包名拼写, 或 'pmm search' 搜索; 若确认存在, 可能镜像未同步", "%s", pmm_tr_fmt("msg.err.registry-not-found", name));
|
||||
mirrors_free(ml);
|
||||
return -1;
|
||||
}
|
||||
JsonValue *meta = json_parse(body);
|
||||
free(body);
|
||||
if (!meta || meta->type != JSON_OBJECT) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.bad-registry-entry", name, used_base));
|
||||
pmm_error_c(PMM_E_REGISTRY, "镜像条目损坏, 尝试刷新镜像或切换镜像源", "%s", pmm_tr_fmt("msg.err.bad-registry-entry", name, used_base));
|
||||
json_free(meta);
|
||||
mirrors_free(ml);
|
||||
return -1;
|
||||
@@ -936,7 +938,7 @@ int install_from_registry(const char *name, const char *spec, const char *mirror
|
||||
const char *s = json_str(v, "sha256"); free(want_sha); want_sha = s ? strdup(s) : NULL; }
|
||||
}
|
||||
if (!chosen) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.no-version", name, osn, arch,
|
||||
pmm_error_c(PMM_E_NOT_FOUND, "当前系统/架构下没有匹配的版本, 或版本约束过于严格", "%s", pmm_tr_fmt("msg.no-version", name, osn, arch,
|
||||
(spec && *spec)) ? " satisfying '" : "", (spec && *spec) ? spec : "");
|
||||
json_free(meta); mirrors_free(ml); return -1;
|
||||
}
|
||||
@@ -948,7 +950,7 @@ int install_from_registry(const char *name, const char *spec, const char *mirror
|
||||
pmm_info("%s", pmm_tr_fmt("msg.selected", name, json_str(meta, "version"), osn, ""));
|
||||
}
|
||||
if (!dl) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.registry-entry-no-url", name));
|
||||
pmm_error_c(PMM_E_REGISTRY, "镜像中该条目缺少下载地址(url), 包可能未发布或损坏", "%s", pmm_tr_fmt("msg.err.registry-entry-no-url", name));
|
||||
json_free(meta); mirrors_free(ml); return -1;
|
||||
}
|
||||
char *url_cp = strdup(dl);
|
||||
@@ -964,7 +966,7 @@ int install_from_registry(const char *name, const char *spec, const char *mirror
|
||||
snprintf(path, sizeof(path), "%s/%s", cache, file_cp);
|
||||
if (pmm_sha256_file(path, hex) == 0) {
|
||||
if (strcasecmp(want_sha, hex) != 0) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.checksum-mismatch", name, want_sha, hex));
|
||||
pmm_error_c(PMM_E_CHECKSUM, "下载文件校验和不匹配, 可能被篡改或损坏, 重新下载", "%s", pmm_tr_fmt("msg.checksum-mismatch", name, want_sha, hex));
|
||||
remove(path);
|
||||
rc = -1;
|
||||
} else {
|
||||
|
||||
+4
-3
@@ -1,6 +1,7 @@
|
||||
/* json.c - minimal recursive-descent JSON parser (no external deps) */
|
||||
#include "json.h"
|
||||
#include "out.h"
|
||||
#include "pmm_err.h"
|
||||
#include "i18n.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -13,7 +14,7 @@ static JsonValue *parse_value(Parser *ps);
|
||||
|
||||
static JsonValue *jnew(JsonType t) {
|
||||
JsonValue *v = calloc(1, sizeof(JsonValue));
|
||||
if (!v) { pmm_error(pmm_tr("msg.err.oom")); exit(1); }
|
||||
if (!v) { pmm_error_c(PMM_E_INTERNAL, "内存不足, 请释放一些资源后重试", "%s", pmm_tr("msg.err.oom")); exit(1); }
|
||||
v->type = t;
|
||||
return v;
|
||||
}
|
||||
@@ -37,7 +38,7 @@ static void jadd(JsonValue *v, char *key, JsonValue *item) {
|
||||
if (v->type == JSON_OBJECT)
|
||||
v->keys = realloc(v->keys, v->capacity * sizeof(char *));
|
||||
if (!v->items || (v->type == JSON_OBJECT && !v->keys)) {
|
||||
pmm_error(pmm_tr("msg.err.oom")); exit(1);
|
||||
pmm_error_c(PMM_E_INTERNAL, "内存不足, 请释放一些资源后重试", "%s", pmm_tr("msg.err.oom")); exit(1);
|
||||
}
|
||||
}
|
||||
if (v->type == JSON_OBJECT) v->keys[v->count] = key;
|
||||
@@ -49,7 +50,7 @@ static void skip_ws(Parser *ps) {
|
||||
}
|
||||
|
||||
static void fail(const char *msg) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.json-parse", msg));
|
||||
pmm_error_c(PMM_E_INTERNAL, "JSON 解析失败, 数据可能损坏或被篡改", "%s", pmm_tr_fmt("msg.err.json-parse", msg));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
+44
-40
@@ -30,6 +30,7 @@
|
||||
|
||||
#include "pmm.h"
|
||||
#include "out.h"
|
||||
#include "pmm_err.h"
|
||||
#include "i18n.h"
|
||||
#include "json.h"
|
||||
#include "ini.h"
|
||||
@@ -223,13 +224,13 @@ static char *registry_fetch(const char *rel, int *outstatus) {
|
||||
|
||||
/* pmm search <keyword> — list registry packages matching keyword. */
|
||||
static int cmd_search(int argc, char **argv) {
|
||||
if (argc < 1) { pmm_error("%s", pmm_tr("msg.err.usage")); return 1; }
|
||||
if (argc < 1) { pmm_error_c(PMM_E_USAGE, "先参考 'pmm help' 查看命令用法", "%s", pmm_tr("msg.err.usage")); return 1; }
|
||||
int status = 0;
|
||||
char *body = registry_fetch("packages.json", &status);
|
||||
if (!body) { pmm_error("%s", pmm_tr("msg.err.no-registry-index")); return 1; }
|
||||
if (!body) { pmm_error_c(PMM_E_REGISTRY, "检查镜像地址是否可达: 'pmm mirror' 查看当前镜像", "%s", pmm_tr("msg.err.no-registry-index")); return 1; }
|
||||
JsonValue *root = json_parse(body);
|
||||
free(body);
|
||||
if (!root || root->type != JSON_ARRAY) { pmm_error("%s", pmm_tr("msg.err.bad-registry-index")); json_free(root); return 1; }
|
||||
if (!root || root->type != JSON_ARRAY) { pmm_error_c(PMM_E_REGISTRY, "镜像索引损坏, 尝试 'pmm fetch --fresh' 刷新", "%s", pmm_tr("msg.err.bad-registry-index")); json_free(root); return 1; }
|
||||
const char *kw = argv[0];
|
||||
int found = 0;
|
||||
for (int i = 0; i < root->count; i++) {
|
||||
@@ -245,7 +246,7 @@ static int cmd_search(int argc, char **argv) {
|
||||
|
||||
/* pmm info <package|file.pdm> — registry package info, or a local .pdm's control. */
|
||||
static int cmd_info(int argc, char **argv) {
|
||||
if (argc < 1) { pmm_error("usage: pmm info <package|file.pdm>\n"); return 1; }
|
||||
if (argc < 1) { pmm_error_c(PMM_E_USAGE, "用法: pmm info <软件包名|文件.pdm>", "usage: pmm info <package|file.pdm>\n"); return 1; }
|
||||
const char *pkg = argv[0];
|
||||
/* local .pdm -> control info */
|
||||
FILE *chk = fopen(pkg, "rb");
|
||||
@@ -259,10 +260,10 @@ static int cmd_info(int argc, char **argv) {
|
||||
char rel[512]; snprintf(rel, sizeof(rel), "%s.json", pkg);
|
||||
int status = 0;
|
||||
char *body = registry_fetch(rel, &status);
|
||||
if (!body) { pmm_error("%s", pmm_tr_fmt("msg.err.not-found", pkg)); return 1; }
|
||||
if (!body) { pmm_error_c(PMM_E_NOT_FOUND, "检查包名拼写, 或先用 'pmm search' 搜索", "%s", pmm_tr_fmt("msg.err.not-found", pkg)); return 1; }
|
||||
JsonValue *root = json_parse(body);
|
||||
free(body);
|
||||
if (!root || root->type != JSON_OBJECT) { pmm_error("%s", pmm_tr_fmt("msg.err.bad-entry", pkg)); json_free(root); return 1; }
|
||||
if (!root || root->type != JSON_OBJECT) { pmm_error_c(PMM_E_REGISTRY, "该包在镜像中的条目异常, 可尝试刷新镜像", "%s", pmm_tr_fmt("msg.err.bad-entry", pkg)); json_free(root); return 1; }
|
||||
const char *name = json_str(root, "name");
|
||||
const char *ver = json_str(root, "version");
|
||||
pmm_info("%s%s%s\n", name ? name : pkg, ver ? " " : "", ver ? ver : "");
|
||||
@@ -443,7 +444,7 @@ static int cmd_upgrade(int argc, char **argv) {
|
||||
if (!(ans[0] == 'y' || ans[0] == 'Y')) continue;
|
||||
}
|
||||
if (install_from_registry(pkg, NULL, mirror.name) == 0) { upgraded++; }
|
||||
else pmm_error("%s", pmm_tr_fmt("msg.err.failed-install", pkg));
|
||||
else pmm_error_c(PMM_E_INTERNAL, "安装失败, 可先 'pmm fetch <包名>' 手动下载排查", "%s", pmm_tr_fmt("msg.err.failed-install", pkg));
|
||||
}
|
||||
|
||||
free(cfg.registry_url); free(cfg.mirror_name);
|
||||
@@ -546,10 +547,10 @@ static int save_registry_cache(const char *rel, const char *body) {
|
||||
static int cmd_update(void) {
|
||||
int status = 0;
|
||||
char *body = registry_fetch("packages.json", &status);
|
||||
if (!body) { pmm_error("no registry index (packages.json) available\n"); return 1; }
|
||||
if (!body) { pmm_error_c(PMM_E_REGISTRY, "检查镜像地址: 'pmm mirror' 查看当前镜像", "no registry index (packages.json) available\n"); return 1; }
|
||||
JsonValue *root = json_parse(body);
|
||||
if (!root || root->type != JSON_ARRAY) {
|
||||
pmm_error("bad registry index\n"); if (root) json_free(root); free(body); return 1;
|
||||
pmm_error_c(PMM_E_REGISTRY, "镜像索引损坏, 尝试 'pmm fetch --fresh' 刷新", "bad registry index\n"); if (root) json_free(root); free(body); return 1;
|
||||
}
|
||||
int total = root->count, ok = 0;
|
||||
if (total == 0) { pmm_info("%s", pmm_tr("msg.registry-empty")); json_free(root); free(body); return 0; }
|
||||
@@ -579,11 +580,11 @@ static int cmd_update(void) {
|
||||
|
||||
/* pmm verify <file> — print sha256 (and sha1) of a downloaded package file. */
|
||||
static int cmd_verify(int argc, char **argv) {
|
||||
if (argc < 1) { pmm_error("usage: pmm verify <file>\n"); return 1; }
|
||||
if (argc < 1) { pmm_error_c(PMM_E_USAGE, "用法: pmm verify <文件>", "usage: pmm verify <file>\n"); return 1; }
|
||||
const char *file = argv[0];
|
||||
char hex[128];
|
||||
if (pmm_sha256_file(file, hex) == 0) pmm_success("%s", pmm_tr_fmt("msg.sha256", hex, file));
|
||||
else { pmm_error("%s", pmm_tr_fmt("msg.err.cannot-read", file)); return 1; }
|
||||
else { pmm_error_c(PMM_E_NOT_FOUND, "检查文件路径是否存在且可读", "%s", pmm_tr_fmt("msg.err.cannot-read", file)); return 1; }
|
||||
if (pmm_sha1_file(file, hex) == 0) pmm_success("%s", pmm_tr_fmt("msg.sha1", hex, file));
|
||||
return 0;
|
||||
}
|
||||
@@ -646,7 +647,7 @@ static int cmd_install_git(int argc, char **argv, const char *flag) {
|
||||
if (strcmp(argv[i], "--host") == 0 && i + 1 < argc) {
|
||||
host = host_from_name(argv[++i]);
|
||||
if (host == HOST_UNKNOWN) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.unknown-host", argv[i]));
|
||||
pmm_error_c(PMM_E_USAGE, "可用主机: github / gitlab / gitea / forgejo", "%s", pmm_tr_fmt("msg.err.unknown-host", argv[i]));
|
||||
return 1;
|
||||
}
|
||||
} else if ((strcmp(argv[i], "-b") == 0 || strcmp(argv[i], "--branch") == 0) && i + 1 < argc) {
|
||||
@@ -656,7 +657,8 @@ static int cmd_install_git(int argc, char **argv, const char *flag) {
|
||||
}
|
||||
}
|
||||
if (!repo) {
|
||||
pmm_error(
|
||||
pmm_error_c(PMM_E_USAGE,
|
||||
"用法: pmm install --git <repo-url.git> / --github <owner/repo> / --gitlab <owner/repo> / --gitea <url/owner/repo>",
|
||||
"usage:\n"
|
||||
" pmm install --git <repo-url.git> any git host (auto-detected API)\n"
|
||||
" pmm install --github <owner/repo> GitHub\n"
|
||||
@@ -680,7 +682,7 @@ static int cmd_install_git(int argc, char **argv, const char *flag) {
|
||||
"(tried gitea/gitlab/github shapes)\n", repo);
|
||||
if (!asset) {
|
||||
if (host != HOST_AUTO)
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.no-suitable-asset", pmm_os_name(os), repo));
|
||||
pmm_error_c(PMM_E_NOT_FOUND, "该仓库没有适配当前系统的发布文件, 换用 --git 完整 URL", "%s", pmm_tr_fmt("msg.err.no-suitable-asset", pmm_os_name(os), repo));
|
||||
repo_close(ctx);
|
||||
return 1;
|
||||
}
|
||||
@@ -736,7 +738,7 @@ static int cmd_mirror(int argc, char **argv) {
|
||||
* (apt-style source check). Reports priority + ok/fail for each source. */
|
||||
if (strcmp(argv[0], "check") == 0) {
|
||||
Ini *ini = ini_load(path);
|
||||
if (!ini || !ini->head) { pmm_error("%s", pmm_tr("msg.err.no-mirror")); ini_free(ini); return 1; }
|
||||
if (!ini || !ini->head) { pmm_error_c(PMM_E_NO_MIRROR, "先添加镜像: 'pmm mirror add <名称> <api地址>'", "%s", pmm_tr("msg.err.no-mirror")); ini_free(ini); return 1; }
|
||||
printf("checking registry mirrors...\n");
|
||||
char lastsec[512] = "";
|
||||
int reachable = 0, total = 0;
|
||||
@@ -770,7 +772,7 @@ static int cmd_mirror(int argc, char **argv) {
|
||||
}
|
||||
if (strcmp(argv[0], "add") == 0 && argc >= 3) {
|
||||
FILE *f = fopen(path, "a");
|
||||
if (!f) { pmm_error("%s", pmm_tr_fmt("msg.err.cannot-write", path)); return 1; }
|
||||
if (!f) { pmm_error_c(PMM_E_INTERNAL, "检查配置文件目录是否可写(系统模式需 sudo)", "%s", pmm_tr_fmt("msg.err.cannot-write", path)); return 1; }
|
||||
fprintf(f, "\n[%s]\napi = %s\n", argv[1], argv[2]);
|
||||
fclose(f);
|
||||
pmm_success("%s", pmm_tr_fmt("msg.mirror-added", argv[1]));
|
||||
@@ -790,7 +792,7 @@ static int cmd_mirror(int argc, char **argv) {
|
||||
fclose(rf);
|
||||
}
|
||||
FILE *wf = fopen(cfgpath, "w");
|
||||
if (!wf) { pmm_error("%s", pmm_tr_fmt("msg.err.cannot-write", cfgpath)); return 1; }
|
||||
if (!wf) { pmm_error_c(PMM_E_INTERNAL, "检查配置文件目录是否可写(系统模式需 sudo)", "%s", pmm_tr_fmt("msg.err.cannot-write", cfgpath)); return 1; }
|
||||
int wrote = 0;
|
||||
for (int i = 0; i < n; i++) fputs(lines[i], wf);
|
||||
fprintf(wf, "mirror = %s\n", argv[1]);
|
||||
@@ -801,9 +803,9 @@ static int cmd_mirror(int argc, char **argv) {
|
||||
}
|
||||
if (strcmp(argv[0], "remove") == 0 && argc >= 2) {
|
||||
Ini *ini = ini_load(path);
|
||||
if (!ini) { pmm_error("%s", pmm_tr("msg.err.no-mirror-file")); return 1; }
|
||||
if (!ini) { pmm_error_c(PMM_E_NO_MIRROR, "检查镜像配置文件是否存在", "%s", pmm_tr("msg.err.no-mirror-file")); return 1; }
|
||||
FILE *f = fopen(path, "w");
|
||||
if (!f) { pmm_error("%s", pmm_tr_fmt("msg.err.cannot-write", path)); ini_free(ini); return 1; }
|
||||
if (!f) { pmm_error_c(PMM_E_INTERNAL, "检查配置文件目录是否可写(系统模式需 sudo)", "%s", pmm_tr_fmt("msg.err.cannot-write", path)); ini_free(ini); return 1; }
|
||||
char cursec[512] = "";
|
||||
for (IniEntry *e = ini->head; e; e = e->next) {
|
||||
if (strcmp(e->section, cursec) != 0) {
|
||||
@@ -819,7 +821,7 @@ static int cmd_mirror(int argc, char **argv) {
|
||||
pmm_success("%s", pmm_tr_fmt("msg.mirror-removed", argv[1]));
|
||||
return 0;
|
||||
}
|
||||
pmm_error("%s", pmm_tr("msg.err.usage"));
|
||||
pmm_error_c(PMM_E_USAGE, "用法: pmm mirror <add|use|remove|check|list> ...", "%s", pmm_tr("msg.err.usage"));
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -923,7 +925,7 @@ static int cmd_cache(int argc, char **argv) {
|
||||
|
||||
/* ---- pmm fetch <pkg>... : predownload packages into the cache (no install) ---- */
|
||||
static int cmd_fetch(int argc, char **argv) {
|
||||
if (argc < 1) { pmm_error("用法: pmm fetch <pkg>...\n"); return 1; }
|
||||
if (argc < 1) { pmm_error_c(PMM_E_USAGE, "用法: pmm fetch <软件包名>...", "用法: pmm fetch <pkg>...\n"); return 1; }
|
||||
pmm_fetch_only = 1;
|
||||
int ok = 0;
|
||||
for (int i = 0; i < argc; i++)
|
||||
@@ -1151,7 +1153,7 @@ static void mkdir_p_local(char *path) {
|
||||
/* pmm setting lang ... */
|
||||
static int cmd_setting(int argc, char **argv) {
|
||||
if (argc < 1) {
|
||||
pmm_error("%s", pmm_tr("msg.err.usage"));
|
||||
pmm_error_c(PMM_E_USAGE, "用法: pmm setting <get|set|mirror|lang> ...", "%s", pmm_tr("msg.err.usage"));
|
||||
return 1;
|
||||
}
|
||||
char dir[1024];
|
||||
@@ -1171,9 +1173,9 @@ static int cmd_setting(int argc, char **argv) {
|
||||
|
||||
/* setting set <key> <value> — persist a key=value in pmm.conf */
|
||||
if (strcmp(argv[0], "set") == 0) {
|
||||
if (argc < 3) { pmm_error("%s", pmm_tr("msg.err.usage")); return 1; }
|
||||
if (argc < 3) { pmm_error_c(PMM_E_USAGE, "用法: pmm setting set <键> <值>", "%s", pmm_tr("msg.err.usage")); return 1; }
|
||||
const char *key = argv[1], *val = argv[2];
|
||||
if (!*key || strstr(key, " ") || strstr(val, "\n")) { pmm_error("%s", pmm_tr("msg.err.usage")); return 1; }
|
||||
if (!*key || strstr(key, " ") || strstr(val, "\n")) { pmm_error_c(PMM_E_USAGE, "键不能含空格, 值不能包含换行", "%s", pmm_tr("msg.err.usage")); return 1; }
|
||||
char lines[256][1024]; int nn = 0;
|
||||
FILE *rf = fopen(cfgpath, "r");
|
||||
if (rf) {
|
||||
@@ -1185,7 +1187,7 @@ static int cmd_setting(int argc, char **argv) {
|
||||
fclose(rf);
|
||||
}
|
||||
FILE *wf = fopen(cfgpath, "w");
|
||||
if (!wf) { pmm_error("%s", pmm_tr_fmt("msg.err.cannot-write", cfgpath)); return 1; }
|
||||
if (!wf) { pmm_error_c(PMM_E_INTERNAL, "检查配置文件目录是否可写(系统模式需 sudo)", "%s", pmm_tr_fmt("msg.err.cannot-write", cfgpath)); return 1; }
|
||||
for (int i = 0; i < nn; i++) fputs(lines[i], wf);
|
||||
fprintf(wf, "%s = %s\n", key, val);
|
||||
fclose(wf);
|
||||
@@ -1213,7 +1215,7 @@ static int cmd_setting(int argc, char **argv) {
|
||||
}
|
||||
|
||||
if (strcmp(argv[0], "lang") != 0) {
|
||||
pmm_error("%s", pmm_tr("msg.err.usage"));
|
||||
pmm_error_c(PMM_E_USAGE, "可用子命令: get / set / mirror / lang", "%s", pmm_tr("msg.err.usage"));
|
||||
return 1;
|
||||
}
|
||||
char langdir[1200];
|
||||
@@ -1244,9 +1246,9 @@ static int cmd_setting(int argc, char **argv) {
|
||||
}
|
||||
|
||||
/* install / activate a specific locale: requires a value */
|
||||
if (argc < 2) { pmm_error("%s", pmm_tr("msg.err.usage")); return 1; }
|
||||
if (argc < 2) { pmm_error_c(PMM_E_USAGE, "用法: pmm setting lang <语言代码>", "%s", pmm_tr("msg.err.usage")); return 1; }
|
||||
const char *loc = argv[1];
|
||||
if (strchr(loc, '/') || strchr(loc, '\\') || strstr(loc, "..")) { pmm_error("%s", pmm_tr_fmt("msg.err.invalid-locale", loc)); return 1; }
|
||||
if (strchr(loc, '/') || strchr(loc, '\\') || strstr(loc, "..")) { pmm_error_c(PMM_E_LANG, "语言代码非法, 应为不含路径的纯名称(如 zh-CN)", "%s", pmm_tr_fmt("msg.err.invalid-locale", loc)); return 1; }
|
||||
|
||||
/* Try every registry mirror in priority order; a pack may only be reachable
|
||||
* on a secondary mirror. Language packs live under {host}/mirror/lang/, NOT
|
||||
@@ -1277,7 +1279,7 @@ static int cmd_setting(int argc, char **argv) {
|
||||
if (http_download(url, out) == 0) { got = 1; break; }
|
||||
}
|
||||
if (!got) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.download-failed", loc));
|
||||
pmm_error_c(PMM_E_DOWNLOAD, "检查网络连接或镜像地址, 或 'pmm mirror check'", "%s", pmm_tr_fmt("msg.err.download-failed", loc));
|
||||
mirrors_free(ml); return 1;
|
||||
}
|
||||
mirrors_free(ml);
|
||||
@@ -1293,7 +1295,7 @@ static int cmd_setting(int argc, char **argv) {
|
||||
fclose(rf);
|
||||
}
|
||||
FILE *wf = fopen(cfgpath, "w");
|
||||
if (!wf) { pmm_error("%s", pmm_tr_fmt("msg.err.cannot-write", cfgpath)); return 1; }
|
||||
if (!wf) { pmm_error_c(PMM_E_INTERNAL, "检查配置文件目录是否可写(系统模式需 sudo)", "%s", pmm_tr_fmt("msg.err.cannot-write", cfgpath)); return 1; }
|
||||
for (int i = 0; i < nn; i++) fputs(lines[i], wf);
|
||||
fprintf(wf, "language = %s\n", loc);
|
||||
fclose(wf);
|
||||
@@ -1415,8 +1417,8 @@ int main(int argc, char **argv) {
|
||||
if (strcmp(argv[1], "setting") == 0) return cmd_setting(argc - 2, argv + 2);
|
||||
/* pmm remove <pkg> (also used by the Windows "Uninstall" registration) */
|
||||
if (strcmp(argv[1], "remove") == 0) {
|
||||
if (argc < 3) { pmm_error("%s", pmm_tr("msg.err.usage")); return 1; }
|
||||
if (argv[2][0] == '-') { pmm_error("%s", pmm_tr_fmt("msg.err.unknown-cmd", argv[2])); return 1; }
|
||||
if (argc < 3) { pmm_error_c(PMM_E_USAGE, "用法: pmm remove <软件包名>", "%s", pmm_tr("msg.err.usage")); return 1; }
|
||||
if (argv[2][0] == '-') { pmm_error_c(PMM_E_USAGE, "remove 后面应是包名而不是选项", "%s", pmm_tr_fmt("msg.err.unknown-cmd", argv[2])); return 1; }
|
||||
return pdm_remove(argv[2]) == 0 ? 0 : 1;
|
||||
}
|
||||
/* pmm self-update: install the latest 'pmm' tool package (auto os+arch) */
|
||||
@@ -1471,7 +1473,9 @@ int main(int argc, char **argv) {
|
||||
strcmp(argv[2], "--forgejo") == 0))
|
||||
return cmd_install_git(argc - 3, argv + 3, argv[2] + 2);
|
||||
if (argc < 3) {
|
||||
pmm_error("usage: pmm install <pkg|file.deb|file.msi|file.rpm|file.pdm> [pkg2 ...]\n"
|
||||
pmm_error_c(PMM_E_USAGE,
|
||||
"用法: pmm install <软件包名|文件.deb|文件.msi|文件.rpm|文件.pdm> ...",
|
||||
"usage: pmm install <pkg|file.deb|file.msi|file.rpm|file.pdm> [pkg2 ...]\n"
|
||||
" | pmm install -dpkg <x.deb> install a .deb (Linux)\n"
|
||||
" | pmm install -rpm <x.rpm> install an .rpm (any Linux, incl. Debian/RPM)\n"
|
||||
" | pmm install --git <repo>\n");
|
||||
@@ -1501,7 +1505,7 @@ int main(int argc, char **argv) {
|
||||
/* A bare leading option (e.g. `install --gits`) is not a package; report
|
||||
* an unknown command instead of treating the flag as a name. */
|
||||
if (argv[2][0] == '-')
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.unknown-cmd", argv[2]));
|
||||
pmm_error_c(PMM_E_USAGE, "install 后应跟软件包名, 或可识别的选项(如 --git)", "%s", pmm_tr_fmt("msg.err.unknown-cmd", argv[2]));
|
||||
else items[ni++] = argv[2];
|
||||
if (ni == 0) return 1;
|
||||
}
|
||||
@@ -1532,7 +1536,7 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
size_t la = strlen(pkg);
|
||||
if (la > 4 && (strcmp(pkg + la - 4, ".pdm") == 0 || strcmp(pkg + la - 4, ".PDM") == 0)) {
|
||||
if (pdm_install_file(pkg) == 0) ok++; else pmm_error("%s", pmm_tr_fmt("msg.err.failed-install", pkg));
|
||||
if (pdm_install_file(pkg) == 0) ok++; else pmm_error_c(PMM_E_INTERNAL, "安装失败, 可先 'pmm fetch <包名>' 手动下载排查", "%s", pmm_tr_fmt("msg.err.failed-install", pkg));
|
||||
continue;
|
||||
}
|
||||
/* direct URL install: pmm install https://.../foo.zip
|
||||
@@ -1544,14 +1548,14 @@ int main(int argc, char **argv) {
|
||||
else if (forced == 2) bn = "download.msi";
|
||||
else if (forced == 3) bn = "download.rpm";
|
||||
if (install_file(pkg, bn) == 0) ok++;
|
||||
else pmm_error("%s", pmm_tr_fmt("msg.err.failed-install", pkg));
|
||||
else pmm_error_c(PMM_E_INTERNAL, "下载或安装失败, 检查网络/文件格式", "%s", pmm_tr_fmt("msg.err.failed-install", pkg));
|
||||
continue;
|
||||
}
|
||||
/* local file install: forced -dpkg/-msi, or an existing installer file
|
||||
* (e.g. `pmm install foo.deb` / `pmm install foo.msi`) */
|
||||
if (forced == 1 || forced == 2 || forced == 3 || has_installer_ext(pkg)) {
|
||||
if (install_local_file(pkg) == 0) ok++;
|
||||
else pmm_error("%s", pmm_tr_fmt("msg.err.failed-install", pkg));
|
||||
else pmm_error_c(PMM_E_INTERNAL, "安装失败, 检查文件是否为有效安装包", "%s", pmm_tr_fmt("msg.err.failed-install", pkg));
|
||||
continue;
|
||||
}
|
||||
if (install_from_registry(pkg, spec[0] ? spec : NULL, mirror.name) == 0) ok++;
|
||||
@@ -1563,7 +1567,7 @@ int main(int argc, char **argv) {
|
||||
|
||||
if (strcmp(argv[1], "pack") == 0) {
|
||||
if (argc < 3) {
|
||||
pmm_error("%s", pmm_tr("msg.err.usage"));
|
||||
pmm_error_c(PMM_E_USAGE, "用法: pmm pack <目录> [输出.pdm]", "%s", pmm_tr("msg.err.usage"));
|
||||
return 1;
|
||||
}
|
||||
return pdm_pack(argv[2], argc >= 4 ? argv[3] : NULL) == 0 ? 0 : 1;
|
||||
@@ -1573,13 +1577,13 @@ int main(int argc, char **argv) {
|
||||
return cmd_search(argc - 2, argv + 2);
|
||||
if (strcmp(argv[1], "cache") == 0) {
|
||||
if (argc >= 3 && strcmp(argv[2], "clean") == 0) return cmd_cache_clean();
|
||||
pmm_error("%s", pmm_tr("msg.err.usage")); return 1;
|
||||
pmm_error_c(PMM_E_USAGE, "用法: pmm cache <list|clean>", "%s", pmm_tr("msg.err.usage")); return 1;
|
||||
}
|
||||
if (strcmp(argv[1], "info") == 0)
|
||||
return cmd_info(argc - 2, argv + 2);
|
||||
if (strcmp(argv[1], "verify") == 0)
|
||||
return cmd_verify(argc - 2, argv + 2);
|
||||
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.unknown-cmd", argv[1]));
|
||||
pmm_error_c(PMM_E_USAGE, "未知子命令, 用 'pmm help' 查看可用命令", "%s", pmm_tr_fmt("msg.err.unknown-cmd", argv[1]));
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* out.c - colored/structured console output implementation */
|
||||
#include "out.h"
|
||||
#include "pmm_err.h"
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
@@ -30,6 +31,11 @@ static void pmmsg(FILE *stream, int fd, const char *level, int wrap_color, const
|
||||
* this, "[PMM]:[INFO]msg\n" would print a blank line after every message. */
|
||||
size_t len = strlen(buf);
|
||||
while (len && (buf[len-1] == '\n' || buf[len-1] == '\r')) buf[--len] = '\0';
|
||||
/* never emit an empty error/warn line */
|
||||
if (len == 0 && (strcmp(level, "ERROR") == 0 || strcmp(level, "WARN") == 0)) {
|
||||
snprintf(buf, sizeof(buf), "(no message)");
|
||||
len = strlen(buf);
|
||||
}
|
||||
|
||||
int tty = IS_TTY_FD(fd) && !pmm_no_color;
|
||||
if (tty && wrap_color)
|
||||
@@ -44,6 +50,25 @@ void pmm_error(const char *fmt, ...) {
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/* Structured error: [PMM]:[E<code>]: <message> (hint: <hint>)
|
||||
* Guarantees a non-empty message (falls back to "unknown error"). */
|
||||
void pmm_error_c(int code, const char *hint, const char *fmt, ...) {
|
||||
char msg[4096];
|
||||
va_list ap; va_start(ap, fmt);
|
||||
vsnprintf(msg, sizeof(msg), fmt ? fmt : "", ap);
|
||||
va_end(ap);
|
||||
size_t len = strlen(msg);
|
||||
while (len && (msg[len-1] == '\n' || msg[len-1] == '\r')) msg[--len] = '\0';
|
||||
if (len == 0) snprintf(msg, sizeof(msg), "unknown error");
|
||||
if (hint && *hint) {
|
||||
size_t ml = strlen(msg);
|
||||
snprintf(msg + ml, sizeof(msg) - ml, " (hint: %s)", hint);
|
||||
}
|
||||
int tty = IS_TTY_FD(2) && !pmm_no_color;
|
||||
if (tty) fprintf(stderr, "\033[31m[PMM]:[E%d]:%s\033[0m\n", code, msg);
|
||||
else fprintf(stderr, "[PMM]:[E%d]:%s\n", code, msg);
|
||||
}
|
||||
|
||||
void pmm_success(const char *fmt, ...) {
|
||||
if (pmm_log_level == 1) return; /* quiet: suppress success; verbose(2) keeps it */
|
||||
va_list ap; va_start(ap, fmt);
|
||||
|
||||
@@ -26,6 +26,9 @@ extern int pmm_no_color;
|
||||
extern int pmm_log_level;
|
||||
|
||||
void pmm_error(const char *fmt, ...);
|
||||
/* Structured error: [PMM]:[E<code>]: <message> (hint: <hint>). code is a PMM_E_*.
|
||||
* Guarantees a non-empty message. */
|
||||
void pmm_error_c(int code, const char *hint, const char *fmt, ...);
|
||||
void pmm_success(const char *fmt, ...);
|
||||
void pmm_info(const char *fmt, ...);
|
||||
void pmm_warn(const char *fmt, ...);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "pdm.h"
|
||||
#include "pmm.h"
|
||||
#include "out.h"
|
||||
#include "pmm_err.h"
|
||||
#include "i18n.h"
|
||||
#include "install.h"
|
||||
#include "sha256.h"
|
||||
@@ -138,13 +139,13 @@ int pdm_pack(const char *dir, const char *out) {
|
||||
snprintf(cp_, sizeof(cp_), "%s/pdm-control", dir);
|
||||
char *control = read_file(cp_, NULL);
|
||||
if (!control) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.no-control", dir));
|
||||
pmm_error_c(PMM_E_NOT_FOUND, "打包目录缺少 pdm-control 文件, 需先创建", "%s", pmm_tr_fmt("msg.err.no-control", dir));
|
||||
return -1;
|
||||
}
|
||||
char *pkg = control_get(control, "Package");
|
||||
char *ver = control_get(control, "Version");
|
||||
if (!pkg || !*pkg) {
|
||||
pmm_error(pmm_tr("msg.err.no-package"));
|
||||
pmm_error_c(PMM_E_INTERNAL, "pdm-control 中缺少 Package 字段", "%s", pmm_tr("msg.err.no-package"));
|
||||
free(control); return -1;
|
||||
}
|
||||
if (!ver || !*ver) ver = strdup("0.0.0");
|
||||
@@ -165,13 +166,13 @@ int pdm_pack(const char *dir, const char *out) {
|
||||
|
||||
/* control.tar.gz (contains pdm-control; packed from the source dir) */
|
||||
snprintf(cmd, sizeof(cmd), "tar -czf \"%s/control.tar.gz\" -C \"%s\" pdm-control", stage, dir);
|
||||
if (system(cmd) != 0) { pmm_error(pmm_tr("msg.err.tar-control")); rmtree(stage); return -1; }
|
||||
if (system(cmd) != 0) { pmm_error_c(PMM_E_EXTRACT, "检查系统是否安装 tar, 目录是否可读/写", "%s", pmm_tr("msg.err.tar-control")); rmtree(stage); return -1; }
|
||||
|
||||
/* data.tar.gz (everything except pdm-control and the scratch dir) */
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"tar -czf \"%s/data.tar.gz\" -C \"%s\" --exclude pdm-control --exclude %s .",
|
||||
stage, dir, stage);
|
||||
if (system(cmd) != 0) { pmm_error(pmm_tr("msg.err.tar-data")); rmtree(stage); return -1; }
|
||||
if (system(cmd) != 0) { pmm_error_c(PMM_E_EXTRACT, "检查系统是否安装 tar, 目录是否可读/写", "%s", pmm_tr("msg.err.tar-data")); rmtree(stage); return -1; }
|
||||
|
||||
/* sha256sums of the two members */
|
||||
char sums_path[1100], hex[128];
|
||||
@@ -190,7 +191,7 @@ int pdm_pack(const char *dir, const char *out) {
|
||||
snprintf(cmd, sizeof(cmd), "tar -cf \"%s\" -C \"%s\" control.tar.gz data.tar.gz sha256sums", out, stage);
|
||||
int rc = system(cmd);
|
||||
rmtree(stage);
|
||||
if (rc != 0) { pmm_error("%s", pmm_tr_fmt("msg.err.cannot-write", out)); return -1; }
|
||||
if (rc != 0) { pmm_error_c(PMM_E_INTERNAL, "检查输出目录是否可写(系统模式需 sudo)", "%s", pmm_tr_fmt("msg.err.cannot-write", out)); return -1; }
|
||||
|
||||
pmm_success("%s", pmm_tr_fmt("msg.packed", dir, pkg, ver, out));
|
||||
free(control); free(pkg);
|
||||
@@ -458,7 +459,7 @@ int pdm_info(const char *pdmfile) {
|
||||
|
||||
int pdm_install_file(const char *pdmfile) {
|
||||
if (!ends_with(pdmfile, ".pdm")) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.not-pdm", pdmfile));
|
||||
pmm_error_c(PMM_E_USAGE, "请提供 .pdm 格式的安装包(如 <包名>_<版本>.pdm)", "%s", pmm_tr_fmt("msg.err.not-pdm", pdmfile));
|
||||
return -1;
|
||||
}
|
||||
char home[1024], db[1024], root[1024], cmd[2600];
|
||||
@@ -478,11 +479,11 @@ int pdm_install_file(const char *pdmfile) {
|
||||
char tmpname[1400], tmprel[] = "_pmm_install.pdm";
|
||||
snprintf(tmpname, sizeof(tmpname), "%s/_pmm_install.pdm", home);
|
||||
if (copy_file(pdmfile, tmpname) != 0) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.cannot-read", pdmfile));
|
||||
pmm_error_c(PMM_E_NOT_FOUND, "检查文件路径是否存在且可读", "%s", pmm_tr_fmt("msg.err.cannot-read", pdmfile));
|
||||
return -1;
|
||||
}
|
||||
if (chdir_save(home) != 0) { remove(tmpname); return -1; }
|
||||
if (system(NULL) == 0) { pmm_error(pmm_tr("msg.err.no-tar")); chdir_restore(); remove(tmpname); return -1; }
|
||||
if (system(NULL) == 0) { pmm_error_c(PMM_E_NO_TAR, "系统缺少 tar, 请先安装 tar(如 apt install tar)", "%s", pmm_tr("msg.err.no-tar")); chdir_restore(); remove(tmpname); return -1; }
|
||||
|
||||
const char *stage = "installed/.stage";
|
||||
rmtree(stage);
|
||||
@@ -491,7 +492,7 @@ int pdm_install_file(const char *pdmfile) {
|
||||
/* extract members (all relative) */
|
||||
snprintf(cmd, sizeof(cmd), "tar -xf \"%s\" -C \"%s\"", tmprel, stage);
|
||||
if (system(cmd) != 0) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.bad-archive", pdmfile));
|
||||
pmm_error_c(PMM_E_EXTRACT, "该 .pdm 文件可能损坏或不是有效包, 重新下载", "%s", pmm_tr_fmt("msg.err.bad-archive", pdmfile));
|
||||
chdir_restore(); remove(tmpname); return -1;
|
||||
}
|
||||
|
||||
@@ -509,7 +510,7 @@ int pdm_install_file(const char *pdmfile) {
|
||||
char mp[1200];
|
||||
snprintf(mp, sizeof(mp), "installed/.stage/%s", fname);
|
||||
if (pmm_sha256_file(mp, hex) != 0 || strcasecmp(hex, expect) != 0) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.checksum-mismatch-file", fname, pdmfile));
|
||||
pmm_error_c(PMM_E_CHECKSUM, "文件校验和不匹配, 包可能损坏, 重新下载或换镜像", "%s", pmm_tr_fmt("msg.err.checksum-mismatch-file", fname, pdmfile));
|
||||
fclose(sf); chdir_restore(); remove(tmpname); return -1;
|
||||
}
|
||||
}
|
||||
@@ -523,7 +524,7 @@ int pdm_install_file(const char *pdmfile) {
|
||||
snprintf(cmd, sizeof(cmd), "tar -xzOf \"%s/control.tar.gz\" pdm-control", stage);
|
||||
FILE *cf = PMM_POPEN_READ(cmd);
|
||||
if (!cf) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.no-control-tar", pdmfile));
|
||||
pmm_error_c(PMM_E_EXTRACT, "包内缺少 control.tar.gz, 不是有效 .pdm", "%s", pmm_tr_fmt("msg.err.no-control-tar", pdmfile));
|
||||
chdir_restore(); remove(tmpname); return -1;
|
||||
}
|
||||
char ctl[4096];
|
||||
@@ -536,7 +537,7 @@ int pdm_install_file(const char *pdmfile) {
|
||||
char *pkg = control_get(ctl, "Package");
|
||||
char *ver = control_get(ctl, "Version");
|
||||
if (!pkg || !*pkg) {
|
||||
pmm_error(pmm_tr("msg.err.no-package"));
|
||||
pmm_error_c(PMM_E_INTERNAL, "包内 pdm-control 缺少 Package 字段, 不是有效 .pdm", "%s", pmm_tr("msg.err.no-package"));
|
||||
chdir_restore(); remove(tmpname); return -1;
|
||||
}
|
||||
|
||||
@@ -560,7 +561,7 @@ int pdm_install_file(const char *pdmfile) {
|
||||
/* reject an install that would conflict with a package already present */
|
||||
char *confl = control_get(ctl, "Conflicts");
|
||||
if (confl && *confl && any_installed_conflict(confl)) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.conflict", pkg, confl));
|
||||
pmm_error_c(PMM_E_CONFLICT, "先移除冲突包, 或使用 --force 覆盖安装", "%s", pmm_tr_fmt("msg.err.conflict", pkg, confl));
|
||||
free(confl); free(pkg); if (ver) free(ver);
|
||||
chdir_restore(); remove(tmpname); return -1;
|
||||
}
|
||||
@@ -582,7 +583,7 @@ int pdm_install_file(const char *pdmfile) {
|
||||
int self_moved = move_self_aside(self_target);
|
||||
snprintf(cmd, sizeof(cmd), "tar -xzf \"%s/data.tar.gz\" -C \"%s\"", stage, tgt);
|
||||
if (system(cmd) != 0) {
|
||||
pmm_error(pmm_tr("msg.err.extract"));
|
||||
pmm_error_c(PMM_E_EXTRACT, "解包失败, 下载文件可能损坏, 重新下载或换镜像", "%s", pmm_tr("msg.err.extract"));
|
||||
if (self_moved) restore_aside(self_target); /* put old binary back */
|
||||
chdir_restore(); remove(tmpname); return -1;
|
||||
}
|
||||
@@ -767,7 +768,7 @@ int pdm_remove(const char *name) {
|
||||
long len = 0;
|
||||
char *info = read_file(ipath, &len);
|
||||
if (!info) {
|
||||
pmm_error("%s", pmm_tr_fmt("msg.err.not-installed", name));
|
||||
pmm_error_c(PMM_E_NOT_FOUND, "该包未安装, 用 'pmm list' 查看已安装的包", "%s", pmm_tr_fmt("msg.err.not-installed", name));
|
||||
return -1;
|
||||
}
|
||||
char *files = strstr(info, "Files:\n");
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/* pmm_err.h — stable PMM error codes.
|
||||
*
|
||||
* Every user-facing error carries one of these codes so scripts/tools and users
|
||||
* can reliably identify it. Pair it with a human hint via pmm_error_c(). */
|
||||
#ifndef PMM_ERR_H
|
||||
#define PMM_ERR_H
|
||||
|
||||
enum {
|
||||
PMM_E_OK = 0, /* 成功 */
|
||||
PMM_E_USAGE = 1, /* 用法/参数错误 */
|
||||
PMM_E_NOT_FOUND = 2, /* 找不到软件包/文件 */
|
||||
PMM_E_DOWNLOAD = 3, /* 下载失败 */
|
||||
PMM_E_NETWORK = 4, /* 网络/连接问题 */
|
||||
PMM_E_NO_MIRROR = 5, /* 未配置/无可用镜像 */
|
||||
PMM_E_REGISTRY = 6, /* 注册表条目异常 */
|
||||
PMM_E_CHECKSUM = 7, /* 校验和不匹配/缺失 */
|
||||
PMM_E_EXTRACT = 8, /* 解包/解压失败 */
|
||||
PMM_E_CONFLICT = 9, /* 依赖/冲突 */
|
||||
PMM_E_NO_TAR = 10, /* 缺少 tar */
|
||||
PMM_E_NO_ROOT = 11, /* 需要 root/权限不足 */
|
||||
PMM_E_CACHE = 12, /* 缓存问题(未缓存/写入) */
|
||||
PMM_E_LANG = 13, /* 语言包错误 */
|
||||
PMM_E_INTERNAL = 99 /* 内部/未知错误 */
|
||||
};
|
||||
|
||||
#endif
|
||||
+18
-17
@@ -11,6 +11,7 @@
|
||||
* Config is stored in ~/.ppdm/config (Windows: D:\.ppdm\config).
|
||||
*/
|
||||
#include "out.h"
|
||||
#include "pmm_err.h"
|
||||
#include "sha256.h"
|
||||
#include "pdm.h"
|
||||
#include "http.h"
|
||||
@@ -83,7 +84,7 @@ static void cfg_save_token(const char *token, const char *email) {
|
||||
snprintf(g_token, sizeof(g_token), "%s", token ? token : "");
|
||||
snprintf(g_email, sizeof(g_email), "%s", email ? email : "");
|
||||
FILE *f = fopen(g_cfg, "w");
|
||||
if (!f) { pmm_error("无法写入 %s\n", g_cfg); return; }
|
||||
if (!f) { pmm_error_c(PMM_E_INTERNAL, "检查配置目录是否可写", "无法写入 %s\n", g_cfg); return; }
|
||||
fprintf(f, "server = %s\n", g_server);
|
||||
fprintf(f, "token = %s\n", g_token);
|
||||
fprintf(f, "email = %s\n", g_email);
|
||||
@@ -99,7 +100,7 @@ static int http_post(const char *url, const char *auth, const char *body,
|
||||
auth ? auth : "", file, url); }
|
||||
else { i += snprintf(cmd + i, sizeof(cmd) - (size_t)i, "curl -s -X POST %s -H 'Content-Type: application/json' --data-binary '%s' '%s'",
|
||||
auth ? auth : "", body ? body : "", url); }
|
||||
if (i < 0 || (size_t)i >= sizeof(cmd)) { pmm_error("请求过长\n"); return -1; }
|
||||
if (i < 0 || (size_t)i >= sizeof(cmd)) { pmm_error_c(PMM_E_INTERNAL, "请求内容过长, 请简化输入", "请求过长\n"); return -1; }
|
||||
FILE *f = popen(cmd, "r");
|
||||
if (!f) return -1;
|
||||
size_t got = fread(out, 1, outs - 1, f);
|
||||
@@ -148,9 +149,9 @@ static void js_err_print(const char *body) {
|
||||
if (q && ((size_t)(q - e)) < n) n = (size_t)(q - e);
|
||||
if (n >= sizeof(buf)) n = sizeof(buf) - 1;
|
||||
memcpy(buf, e, n); buf[n] = 0;
|
||||
pmm_error("%s\n", buf);
|
||||
pmm_error_c(PMM_E_INTERNAL, "服务器返回了错误信息, 请检查账号或服务状态", "%s\n", buf);
|
||||
} else {
|
||||
pmm_error("%s\n", body ? body : "(no response)");
|
||||
pmm_error_c(PMM_E_NETWORK, "服务器无响应, 检查网络或服务是否在线", "%s\n", body ? body : "(no response)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +170,7 @@ static int captcha(void) {
|
||||
fflush(stdout);
|
||||
char in[64];
|
||||
if (!fgets(in, sizeof(in), stdin)) return 0;
|
||||
if (atoi(in) != ans) { pmm_error("验证错误\n"); return 0; }
|
||||
if (atoi(in) != ans) { pmm_error_c(PMM_E_USAGE, "验证码不对, 请重新输入正确的计算结果", "验证错误\n"); return 0; }
|
||||
pmm_success("人机验证通过\n");
|
||||
return 1;
|
||||
}
|
||||
@@ -252,9 +253,9 @@ int main(int argc, char **argv) {
|
||||
|
||||
/* ---- register ---- */
|
||||
if (strcmp(argv[1], "register") == 0) {
|
||||
if (argc < 4) { pmm_error("用法: ppdm register <email> <password>\n"); return 1; }
|
||||
if (argc < 4) { pmm_error_c(PMM_E_USAGE, "用法: ppdm register <邮箱> <密码>", "用法: ppdm register <email> <password>\n"); return 1; }
|
||||
const char *email = argv[2], *pass = argv[3];
|
||||
if (strchr(email, '\'') || strchr(pass, '\'')) { pmm_error("邮箱/密码不能含单引号\n"); return 1; }
|
||||
if (strchr(email, '\'') || strchr(pass, '\'')) { pmm_error_c(PMM_E_USAGE, "邮箱/密码不能包含单引号", "邮箱/密码不能含单引号\n"); return 1; }
|
||||
if (!captcha()) return 1;
|
||||
snprintf(url, sizeof(url), "%s/register.php", g_server);
|
||||
char json[1024];
|
||||
@@ -267,9 +268,9 @@ int main(int argc, char **argv) {
|
||||
|
||||
/* ---- login ---- */
|
||||
if (strcmp(argv[1], "login") == 0) {
|
||||
if (argc < 4) { pmm_error("用法: ppdm login <email> <password>\n"); return 1; }
|
||||
if (argc < 4) { pmm_error_c(PMM_E_USAGE, "用法: ppdm login <邮箱> <密码>", "用法: ppdm login <email> <password>\n"); return 1; }
|
||||
const char *email = argv[2], *pass = argv[3];
|
||||
if (strchr(email, '\'') || strchr(pass, '\'')) { pmm_error("邮箱/密码不能含单引号\n"); return 1; }
|
||||
if (strchr(email, '\'') || strchr(pass, '\'')) { pmm_error_c(PMM_E_USAGE, "邮箱/密码不能包含单引号", "邮箱/密码不能含单引号\n"); return 1; }
|
||||
snprintf(url, sizeof(url), "%s/login.php", g_server);
|
||||
char json[1024];
|
||||
snprintf(json, sizeof(json), "{\"email\":\"%s\",\"password\":\"%s\"}", email, pass);
|
||||
@@ -305,7 +306,7 @@ int main(int argc, char **argv) {
|
||||
|
||||
/* ---- pack <dir> [out] : build a .pdm from a staging dir (pdm-control required) ---- */
|
||||
if (strcmp(argv[1], "pack") == 0) {
|
||||
if (argc < 3) { pmm_error("用法: ppdm pack <dir> [out]\n"); return 1; }
|
||||
if (argc < 3) { pmm_error_c(PMM_E_USAGE, "用法: ppdm pack <目录> [输出.pdm]", "用法: ppdm pack <dir> [out]\n"); return 1; }
|
||||
return pdm_pack(argv[2], argc >= 4 ? argv[3] : NULL) == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
@@ -319,11 +320,11 @@ int main(int argc, char **argv) {
|
||||
PPDM_MKDIR(bdir);
|
||||
pmm_info("正在下载最新 ppdm ...\n");
|
||||
if (http_download("https://github.com/JGZYES/ParlzPackageManger/releases/latest/download/ppdm", tmp) != 0) {
|
||||
pmm_error("更新下载失败\n"); return 1;
|
||||
pmm_error_c(PMM_E_DOWNLOAD, "检查网络或稍后重试, 也可手动从 release 下载 ppdm", "更新下载失败\n"); return 1;
|
||||
}
|
||||
chmod(tmp, 0755); /* ignore result; run-permission is fine on POSIX */
|
||||
remove(bin);
|
||||
if (rename(tmp, bin) != 0) { pmm_error("无法替换 %s\n", bin); return 1; }
|
||||
if (rename(tmp, bin) != 0) { pmm_error_c(PMM_E_INTERNAL, "检查 ~/.ppdm/bin 是否可写", "无法替换 %s\n", bin); return 1; }
|
||||
pmm_success("已更新到 %s(把 %s 加入 PATH,下次运行即新版)\n", bin, bdir);
|
||||
return 0;
|
||||
}
|
||||
@@ -333,12 +334,12 @@ int main(int argc, char **argv) {
|
||||
if (strcmp(argv[1], "publish") == 0) file = argc > 2 ? argv[2] : NULL;
|
||||
else if (strlen(argv[1]) > 4 && strcmp(argv[1] + strlen(argv[1]) - 4, ".pdm") == 0) file = argv[1];
|
||||
if (file) {
|
||||
if (!g_token[0]) { pmm_error("请先 ppdm login\n"); return 1; }
|
||||
if (!g_token[0]) { pmm_error_c(PMM_E_USAGE, "请先执行 ppdm login", "请先 ppdm login\n"); return 1; }
|
||||
char pkg[256], ver[128], arch[64];
|
||||
pdm_meta(file, pkg, sizeof(pkg), ver, sizeof(ver), arch, sizeof(arch));
|
||||
if (!pkg[0] || !ver[0]) { pmm_error("无法解析 %s 的 Package/Version\n", file); return 1; }
|
||||
if (!pkg[0] || !ver[0]) { pmm_error_c(PMM_E_INTERNAL, "该 .pdm 缺少 Package/Version, 请用 ppdm pack 正确打包", "无法解析 %s 的 Package/Version\n", file); return 1; }
|
||||
char hex[128];
|
||||
if (pmm_sha256_file(file, hex) != 0) { pmm_error("无法计算 sha256\n"); return 1; }
|
||||
if (pmm_sha256_file(file, hex) != 0) { pmm_error_c(PMM_E_INTERNAL, "无法读取该文件, 检查权限", "无法计算 sha256\n"); return 1; }
|
||||
/* URL-encode description (name/version/arch/os are safe charsets) */
|
||||
char desc[512];
|
||||
snprintf(desc, sizeof(desc), "%s %s", pkg, ver);
|
||||
@@ -364,7 +365,7 @@ int main(int argc, char **argv) {
|
||||
g_server, pkg, ver, cpuarch[0] ? cpuarch : "amd64", os, sp);
|
||||
snprintf(auth, sizeof(auth), "-H 'Authorization: Bearer %s'", g_token);
|
||||
pmm_info("正在发布 %s %s ...\n", pkg, ver);
|
||||
if (http_post(url, auth, NULL, file, resp, sizeof(resp)) <= 0) { pmm_error("发布失败(无响应)\n"); return 1; }
|
||||
if (http_post(url, auth, NULL, file, resp, sizeof(resp)) <= 0) { pmm_error_c(PMM_E_NETWORK, "服务器无响应, 检查网络或服务是否在线", "发布失败(无响应)\n"); return 1; }
|
||||
if (js_ok(resp)) {
|
||||
char u[2200] = ""; js_val(resp, "url", u, sizeof(u));
|
||||
pmm_success("发布成功: %s\n", u[0] ? u : file);
|
||||
@@ -372,6 +373,6 @@ int main(int argc, char **argv) {
|
||||
return js_ok(resp) ? 0 : 1;
|
||||
}
|
||||
|
||||
pmm_error("未知命令: %s (试试 ppdm help)\n", argv[1]);
|
||||
pmm_error_c(PMM_E_USAGE, "未知子命令, 用 'ppdm help' 查看", "未知命令: %s (试试 ppdm help)\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户