rpm: ensure stage dir exists + don't report success on empty unpack

- Create the rpm stage dir (mkdir -p) before unpacking, so fopen(stage/usr/...)
  always has a writable ancestor.
- pmm_cpio_unpack_cmd now verifies the unpacked dir isn't empty; a decompress
  that produced nothing (e.g. wrong codec/binaries) returns an error instead of
  letting pmm sudo-copy an empty dir and 'install' nothing.
- Add <dirent.h> for the empt dir check.

Verified on WSL (Ubuntu-26.04, gcc 15.2): full build passes, and the rpm branch
correctly unpacks an RHEL9 zstd fzf rpm into 15 files including usr/bin/fzf.
这个提交包含在:
JGZYES
2026-09-04 07:16:40 +08:00
父节点 bd3a70633b
当前提交 009917dda7
+34 -1
查看文件
@@ -29,6 +29,7 @@
#else #else
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> /* unlink/symlink (cpio unpacker) */ #include <unistd.h> /* unlink/symlink (cpio unpacker) */
#include <dirent.h> /* opendir/readdir (empty-stage check) */
#define PMM_MKDIR(p) mkdir((p), 0755) #define PMM_MKDIR(p) mkdir((p), 0755)
#endif #endif
@@ -378,13 +379,30 @@ int pmm_cpio_unpack(const char *dest, FILE *in) {
/* Run `decompcmd` (e.g. "gzip -dc file") and unpack its cpio output into dest. /* 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. */ * Needs decompcmd to emit a cpio stream on stdout. Returns 0 on success. */
/* True if `dir` exists and contains no entries (best-effort). */
static int dir_is_empty(const char *dir) {
DIR *d = opendir(dir);
if (!d) return 0; /* missing dir is NOT "empty ok" */
int empty = 1; struct dirent *e;
while ((e = readdir(d)) != NULL) {
if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) continue;
empty = 0; break;
}
closedir(d);
return empty;
}
static int pmm_cpio_unpack_cmd(const char *dest, const char *decompcmd) { static int pmm_cpio_unpack_cmd(const char *dest, const char *decompcmd) {
FILE *p = popen(decompcmd, "r"); FILE *p = popen(decompcmd, "r");
if (!p) return -1; if (!p) return -1;
int rc = pmm_cpio_unpack(dest, p); int rc = pmm_cpio_unpack(dest, p);
int prc = pclose(p); int prc = pclose(p);
if (rc != 0) return -1; if (rc != 0) return -1;
return (prc == 0) ? 0 : -1; if (prc != 0) return -1;
/* Never report success if the unpacked stream produced nothing — otherwise
* the caller would sudo-copy an empty dir and "install" nothing. */
if (dir_is_empty(dest)) return -1;
return 0;
} }
#endif /* !_WIN32 */ /* end pure-C cpio unpacker */ #endif /* !_WIN32 */ /* end pure-C cpio unpacker */
@@ -515,6 +533,10 @@ static int install_path(const char *path, const char *name) {
(ct == 2) ? "xz" : (ct == 2) ? "xz" :
(ct == 3) ? "bzip2" : (ct == 3) ? "bzip2" :
(ct == 4) ? "lzma" : "gzip"; (ct == 4) ? "lzma" : "gzip";
if (getenv("PMM_DEBUG"))
fprintf(stderr, "[pmm-debug] rpm codec=%d dc=%s fdata=%s fstage=%s\n",
ct, dc, fdata, fstage);
(void)dc;
char dcmd[1500]; char dcmd[1500];
snprintf(dcmd, sizeof(dcmd), "%s -dc \"%s\"", dc, fdata); snprintf(dcmd, sizeof(dcmd), "%s -dc \"%s\"", dc, fdata);
snprintf(cmd, sizeof(cmd), snprintf(cmd, sizeof(cmd),
@@ -533,6 +555,17 @@ static int install_path(const char *path, const char *name) {
if (system(cmd) != 0) { fprintf(stderr, "pmm: rpm install failed\n"); return -1; } if (system(cmd) != 0) { fprintf(stderr, "pmm: rpm install failed\n"); return -1; }
(void)dcmd; (void)ct; (void)dcmd; (void)ct;
#else #else
/* create the (possibly multi-level) stage dir first; pmm_cpio_unpack
* only mkdir's the per-file parent components, not the root itself,
* so without this fopen("stage/usr/...") fails and data is skipped. */
{
char mkstag[1400];
snprintf(mkstag, sizeof(mkstag), "mkdir -p \"%s\"", fstage);
if (system(mkstag) != 0) {
fprintf(stderr, "pmm: cannot create rpm stage dir: %s\n", fstage);
return -1;
}
}
/* unpack into stage (no sudo needed; stage is user-writable) */ /* unpack into stage (no sudo needed; stage is user-writable) */
if (pmm_cpio_unpack_cmd(fstage, dcmd) != 0) { if (pmm_cpio_unpack_cmd(fstage, dcmd) != 0) {
fprintf(stderr, "pmm: rpm payload decompress/cpio failed (need %s)\n", dc); fprintf(stderr, "pmm: rpm payload decompress/cpio failed (need %s)\n", dc);