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 互补内置包回退)
这个提交包含在:
JGZYES
2026-09-06 17:54:55 +08:00
父节点 06d7f2011a
当前提交 2d7fcab70f
修改 10 个文件,包含 195 行新增96 行删除
+27
查看文件
@@ -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;
}
+25
查看文件
@@ -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);
+3
查看文件
@@ -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, ...);
+16 -15
查看文件
@@ -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;
}