镜像自地址
https://github.com/JGZYES/ParlzPackageManger.git
已同步 2026-09-11 05:42:44 +08:00
rpm: pure-C cpio unpacker + codec-aware decompress (works on systems without cpio/rpm/rpm2cpio/alien)
- Add pmm_cpio_unpack(): parses newc/070701, crc/070702 and odc/070707 archives in pure C (no external cpio), extracting paths under a stage dir. - rpm install now detects the payload codec (gzip/zstd/xz/bz2/lzma) and feeds the decompressor's stdout through the built-in cpio unpacker, then sudo-copies the payload into /. No host cpio/rpm/rpm2cpio/alien required. - Fix RHEL9/Fedora zstd rpms failing with "not in gzip format" (gzip was blindly assumed). A failed decompress now returns non-zero instead of faking a successful install.
这个提交包含在:
+187
-6
@@ -28,6 +28,7 @@
|
||||
#define PMM_MKDIR(p) _mkdir(p)
|
||||
#else
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h> /* unlink/symlink (cpio unpacker) */
|
||||
#define PMM_MKDIR(p) mkdir((p), 0755)
|
||||
#endif
|
||||
|
||||
@@ -229,6 +230,158 @@ static int rpm_extract_payload(const char *rpm, const char *out) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Detect the compression codec of an RPM payload so we can pick the right
|
||||
* decompressor without assuming gzip. Returns:
|
||||
* 0=gzip 1=zstd 2=xz 3=bzip2 4=lzma, or -1 if unrecognized. */
|
||||
static int rpm_payload_compress(const char *payload) {
|
||||
FILE *f = fopen(payload, "rb");
|
||||
if (!f) return -1;
|
||||
unsigned char m[8];
|
||||
size_t n = fread(m, 1, 8, f);
|
||||
fclose(f);
|
||||
if (n >= 2 && m[0] == 0x1f && m[1] == 0x8b) return 0; /* gzip */
|
||||
if (n >= 4 && m[0] == 0x28 && m[1] == 0xb5 && m[2] == 0x2f && m[3] == 0xfd) return 1; /* zstd */
|
||||
if (n >= 6 && m[0] == 0xfd && m[1] == 0x37 && m[2] == 0x7a && m[3] == 0x58 &&
|
||||
m[4] == 0x5a && m[5] == 0x00) return 2; /* xz */
|
||||
if (n >= 3 && m[0] == 0x42 && m[1] == 0x5a && m[2] == 0x68) return 3; /* bzip2 */
|
||||
if (n >= 5 && m[0] == 0x5d && m[1] == 0x00 && m[2] == 0x00 && m[3] == 0x00 && m[4] == 0x80) return 4; /* lzma */
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ---------- pure-C cpio unpacker (no system cpio required) ----------
|
||||
* Reads a cpio archive from FILE* `in` (typically a popen decompression pipe)
|
||||
* and writes paths relative to `dest`. Handles newc (070701), newc-crc (070702)
|
||||
* and old odc (070707). Archive names are always kept under dest ('.' and '/'
|
||||
* are stripped; '..' names are skipped). Returns 0 on success. */
|
||||
|
||||
static unsigned c_hexn(const char *s, int n) {
|
||||
unsigned v = 0; int i;
|
||||
for (i = 0; i < n; i++) { char ch = s[i]; v <<= 4;
|
||||
if (ch >= '0' && ch <= '9') v |= (unsigned)(ch - '0');
|
||||
else if (ch >= 'a' && ch <= 'f') v |= (unsigned)(ch - 'a' + 10);
|
||||
else if (ch >= 'A' && ch <= 'F') v |= (unsigned)(ch - 'A' + 10); }
|
||||
return v;
|
||||
}
|
||||
|
||||
/* Create every directory component under the (final) path. */
|
||||
static void cpio_mkdirs(char *path) {
|
||||
for (char *p = path + 1; *p; p++) {
|
||||
if (*p == '/') { char c = *p; *p = '\0'; mkdir(path, 0755); *p = c; }
|
||||
}
|
||||
}
|
||||
|
||||
/* Skip exactly n bytes from a possibly non-seekable stream. */
|
||||
static void cpio_skip(FILE *in, unsigned long n) {
|
||||
char b[2048];
|
||||
while (n) { unsigned long c = n > sizeof(b) ? sizeof(b) : n;
|
||||
size_t g = fread(b, 1, c, in); if (!g) break; n -= g; }
|
||||
}
|
||||
|
||||
int pmm_cpio_unpack(const char *dest, FILE *in) {
|
||||
char name[8192];
|
||||
int first = 1, is_odc = 0;
|
||||
|
||||
for (;;) {
|
||||
/* read the 6-byte magic */
|
||||
char hdr[110];
|
||||
if (fread(hdr, 1, 6, in) != 6) break; /* EOF -> done (no trailer) */
|
||||
if (!(memcmp(hdr,"070701",6)==0 || memcmp(hdr,"070702",6)==0 ||
|
||||
memcmp(hdr,"070707",6)==0)) break; /* not a cpio magic -> done */
|
||||
if (first) { is_odc = (memcmp(hdr,"070707",6)==0); first = 0; }
|
||||
int hlen;
|
||||
if (is_odc) { hlen = 76; if (fread(hdr+6,1,70,in)!=70) return -1; }
|
||||
else { hlen = 110; if (fread(hdr+6,1,104,in)!=104) return -1; }
|
||||
|
||||
unsigned mode, data_size, namesize;
|
||||
if (is_odc) {
|
||||
mode = c_hexn(hdr+6, 6);
|
||||
data_size = c_hexn(hdr+6+30, 6);
|
||||
namesize = 0; /* odc name is NUL-terminated */
|
||||
} else {
|
||||
/* newc (070701) / crc (070702): fields are 8-hex-char ASCII words
|
||||
* that follow the 6-byte magic directly:
|
||||
* ino(6) mode(14) uid(22) gid(30) nlink(38) mtime(46) filesize(54)
|
||||
* devmaj(62) devmin(70) rdevmaj(78) rdevmin(86) name(94) check(102)
|
||||
* (offsets are relative to hdr; magic occupies hdr[0..5]). */
|
||||
mode = c_hexn(hdr+14, 8);
|
||||
data_size = c_hexn(hdr+54, 8);
|
||||
namesize = c_hexn(hdr+94, 8);
|
||||
}
|
||||
|
||||
if (is_odc) {
|
||||
/* NUL-terminated name of unbounded length */
|
||||
int pos = 0;
|
||||
for (;;) {
|
||||
int ch = fgetc(in);
|
||||
if (ch == EOF) return -1;
|
||||
if (pos < (int)sizeof(name)-1) name[pos++] = (char)ch;
|
||||
if (ch == 0) break;
|
||||
}
|
||||
name[pos] = 0;
|
||||
} else {
|
||||
if (namesize == 0 || namesize >= sizeof(name)) namesize = (unsigned)sizeof(name)-1;
|
||||
if (fread(name, 1, namesize, in) != namesize) return -1;
|
||||
name[namesize] = 0;
|
||||
/* align to 4 on the (header+name) boundary */
|
||||
unsigned padh = (unsigned)(4 - (((unsigned)hlen + namesize) & 3)) & 3;
|
||||
cpio_skip(in, padh);
|
||||
}
|
||||
|
||||
if (name[0]==0 || strcmp(name,"TRAILER!!!")==0) break; /* end of archive */
|
||||
|
||||
/* make safe relative path */
|
||||
const char *rel = name;
|
||||
while (*rel=='/') rel++;
|
||||
while (rel[0]=='.' && rel[1]=='/') rel += 2;
|
||||
if (strncmp(rel,"../",3)==0 || strstr(rel,"/../") || strcmp(rel,"..")==0) {
|
||||
cpio_skip(in, data_size); /* refuse .. traversal */
|
||||
if (!is_odc) cpio_skip(in, (4-(data_size&3))&3);
|
||||
continue;
|
||||
}
|
||||
|
||||
char full[8400];
|
||||
snprintf(full, sizeof(full), "%s/%s", dest, rel);
|
||||
unsigned ft = mode & 0170000;
|
||||
|
||||
if (S_ISREG(ft)) {
|
||||
cpio_mkdirs(full);
|
||||
FILE *of = fopen(full, "wb");
|
||||
if (of) {
|
||||
char b[65536]; unsigned long left = data_size;
|
||||
while (left) { unsigned long c = left > sizeof(b) ? sizeof(b) : left;
|
||||
size_t g = fread(b,1,c,in); if (!g) break; fwrite(b,1,g,of); left -= g; }
|
||||
fclose(of);
|
||||
chmod(full, mode & 07777);
|
||||
} else cpio_skip(in, data_size);
|
||||
} else if (S_ISDIR(ft)) {
|
||||
cpio_mkdirs(full); mkdir(full, mode & 07777);
|
||||
} else if (S_ISLNK(ft)) {
|
||||
char t[4096];
|
||||
if (data_size < sizeof(t)-1) {
|
||||
if (fread(t,1,data_size,in)!=data_size) return -1;
|
||||
t[data_size]=0; cpio_mkdirs(full); unlink(full);
|
||||
if (symlink(t, full) != 0) { /* best-effort symlink */ }
|
||||
} else cpio_skip(in, data_size);
|
||||
} else {
|
||||
cpio_skip(in, data_size); /* device/fifo/unknown */
|
||||
}
|
||||
|
||||
if (!is_odc) cpio_skip(in, (4-(data_size&3))&3); /* data->4 pad */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Run `decompcmd` (e.g. "gzip -dc file") and unpack its cpio output into dest.
|
||||
* Needs decompcmd to emit a cpio stream on stdout. Returns 0 on success. */
|
||||
static int pmm_cpio_unpack_cmd(const char *dest, const char *decompcmd) {
|
||||
FILE *p = popen(decompcmd, "r");
|
||||
if (!p) return -1;
|
||||
int rc = pmm_cpio_unpack(dest, p);
|
||||
int prc = pclose(p);
|
||||
if (rc != 0) return -1;
|
||||
return (prc == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
int install_file(const char *url, const char *name) {
|
||||
char cache[1024], path[1200];
|
||||
pmm_cache_dir(cache, sizeof(cache));
|
||||
@@ -334,16 +487,44 @@ static int install_path(const char *path, const char *name) {
|
||||
path, path, path);
|
||||
}
|
||||
} else if (has_suffix(bname, ".rpm") && os == OS_LINUX) {
|
||||
/* Self-extract in C (no rpm/rpm2cpio): pull cpio payload, gunzip, cpio it into / */
|
||||
char cache[1024], fdata[1300];
|
||||
/* Self-extract in C (no rpm/rpm2cpio): pull cpio payload, decompress,
|
||||
* unpack into a cache dir, then sudo-copy to /. The decompressor is
|
||||
* chosen from the payload's real codec (NOT assumed gzip -- RHEL9+ and
|
||||
* Fedora ship zstd, some srpms use xz/lzma). `set -e` + each stage
|
||||
* chained with && means a failed decompress aborts and returns non-zero
|
||||
* instead of pmm falsely reporting success. */
|
||||
char cache[1024], fdata[1300], fstage[1300];
|
||||
pmm_cache_dir(cache, sizeof(cache));
|
||||
snprintf(fdata, sizeof(fdata), "%s/.pmm-rpm-payload", cache);
|
||||
snprintf(fstage, sizeof(fstage), "%s/.pmm-rpm-stage", cache);
|
||||
if (rpm_extract_payload(path, fdata) == 0) {
|
||||
/* Decompress with pmm's own pure-C cpio unpacker so no system
|
||||
* cpio/rpm/rpm2cpio/alien is required. We only need ONE of
|
||||
* gzip/zstd/xz/bzip2/lzma on the host (virtually every Linux has at
|
||||
* least one). The decompressor is chosen from the payload's real
|
||||
* codec -- gzip is NOT assumed (RHEL9+/Fedora ship zstd; some srpms
|
||||
* use xz/lzma). */
|
||||
int ct = rpm_payload_compress(fdata);
|
||||
const char *dc = (ct == 1) ? "zstd" :
|
||||
(ct == 2) ? "xz" :
|
||||
(ct == 3) ? "bzip2" :
|
||||
(ct == 4) ? "lzma" : "gzip";
|
||||
char dcmd[1500];
|
||||
snprintf(dcmd, sizeof(dcmd), "%s -dc \"%s\"", dc, fdata);
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"if command -v gzip >/dev/null 2>&1; then gzip -dc \"%s\" | (cd / && cpio -idm --quiet 2>/dev/null); "
|
||||
"elif command -v zstd >/dev/null 2>&1; then zstd -dc \"%s\" | (cd / && cpio -idm --quiet 2>/dev/null); "
|
||||
"else echo 'pmm: need gzip/zstd+cpio for .rpm payload' 1>&2; exit 1; fi; rm -f \"%s\"",
|
||||
fdata, fdata, fdata);
|
||||
"sudo cp -a \"%s\"/. / && rm -rf \"%s\" \"%s\"",
|
||||
fstage, fstage, fdata);
|
||||
/* unpack into stage (no sudo needed; stage is user-writable) */
|
||||
if (pmm_cpio_unpack_cmd(fstage, dcmd) != 0) {
|
||||
fprintf(stderr, "pmm: rpm payload decompress/cpio failed (need %s)\n", dc);
|
||||
return -1;
|
||||
}
|
||||
/* copy the unpacked payload into /, then clean up */
|
||||
if (system(cmd) != 0) {
|
||||
fprintf(stderr, "pmm: failed to copy rpm payload into /\n");
|
||||
return -1;
|
||||
}
|
||||
(void)ct;
|
||||
} else {
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"if command -v rpm >/dev/null 2>&1; then sudo rpm -Uvh \"%s\"; "
|
||||
|
||||
在新工单中引用
屏蔽一个用户