From e0bc4a6ae00a72e4d3f30793aa8b925fdbca2c7d Mon Sep 17 00:00:00 2001 From: Songlx516 Date: Sun, 6 Sep 2026 22:25:09 +0800 Subject: [PATCH] fix(zip): fall back to plain path calls when *at() fails at runtime Real-machine diagnosis of the Frostpunk 2 failure: mkdirat() returns -1 with errno 0 ("cannot create directory ... (No error)") on PS5 hardware. The *at() symbols link against the SDK libc but misbehave at runtime, while plain path-based calls work (the staging root open() succeeds and small flat archives extract fine). Every *at() call in the extract phase now retries as a full-path call built from the staging root before reporting an I/O error: - open_parent_dirs: mkdirat -> mkdir, openat -> open - write_entry: openat -> open, renameat -> rename, unlinkat -> unlink Error messages now append (errno=%d) so errno=0 can no longer hide behind a misleading "No error" strerror string. Host suite stays green: 94 checks, 0 failures. --- src/zip_extract.c | 108 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 85 insertions(+), 23 deletions(-) diff --git a/src/zip_extract.c b/src/zip_extract.c index 7d0bd81..0671af1 100644 --- a/src/zip_extract.c +++ b/src/zip_extract.c @@ -766,11 +766,34 @@ scan_archive(void *zip, zipx_ctx_t *c) { * extract phase **************************************************************************/ +/* Builds "/[/]" for the plain-call fallbacks below. + rel may be empty (staging root); leaf may be NULL. */ +static void +staging_path(const zipx_ctx_t *c, const char *rel, const char *leaf, + char *out, size_t cap) { + if(rel[0] && leaf) { + snprintf(out, cap, "%s/%s/%s", c->staging, rel, leaf); + } else if(rel[0]) { + snprintf(out, cap, "%s/%s", c->staging, rel); + } else if(leaf) { + snprintf(out, cap, "%s/%s", c->staging, leaf); + } else { + snprintf(out, cap, "%s", c->staging); + } +} + +/* The *at() family can be present in the target libc yet fail at runtime + without setting errno (observed on PS5 hardware: mkdirat() returns -1 + with errno 0, while plain path-based calls work). Every *at() call in + the extract phase therefore falls back to a full-path call built from + the staging root before reporting an I/O error. */ + /* Opens (creating when needed) every directory of rel below root_fd. Returns an open descriptor for the deepest directory. */ static int open_parent_dirs(int root_fd, const char *rel, zipx_ctx_t *c) { char buf[ZIPX_PATH_MAX]; + char cur[ZIPX_PATH_MAX] = ""; int fd = root_fd; char *seg; char *save = NULL; @@ -783,31 +806,48 @@ open_parent_dirs(int root_fd, const char *rel, zipx_ctx_t *c) { for(seg = strtok_r(buf, "/", &save); seg; seg = strtok_r(NULL, "/", &save)) { int next; + int made = 0; + + if(cur[0]) { + strncat(cur, "/", sizeof(cur) - strlen(cur) - 1); + } + strncat(cur, seg, sizeof(cur) - strlen(cur) - 1); if(!mkdirat(fd, seg, 0777)) { - next = openat(fd, seg, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); - if(next >= 0) { - chmod_0777_fd(next); - c->dirs_created++; + made = 1; + } else if(errno != EEXIST) { + char full[ZIPX_PATH_MAX]; + + staging_path(c, cur, NULL, full, sizeof(full)); + if(mkdir(full, 0777) && errno != EEXIST) { + ctx_fail(c, ZIPX_ERR_IO, rel, "cannot create directory '%s': %s " + "(errno=%d)", seg, strerror(errno), errno); + if(fd != root_fd) { + close(fd); + } + return -1; } - } else if(errno == EEXIST) { - next = openat(fd, seg, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); - } else { - ctx_fail(c, ZIPX_ERR_IO, rel, "cannot create directory '%s': %s", seg, - strerror(errno)); - if(fd != root_fd) { - close(fd); - } - return -1; + made = 1; } + next = openat(fd, seg, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); if(next < 0) { - ctx_fail(c, ZIPX_ERR_IO, rel, "cannot open directory '%s': %s", seg, - strerror(errno)); - if(fd != root_fd) { - close(fd); + char full[ZIPX_PATH_MAX]; + + staging_path(c, cur, NULL, full, sizeof(full)); + next = open(full, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); + if(next < 0) { + ctx_fail(c, ZIPX_ERR_IO, rel, "cannot open directory '%s': %s " + "(errno=%d)", seg, strerror(errno), errno); + if(fd != root_fd) { + close(fd); + } + return -1; } - return -1; + } + if(made) { + chmod_0777_fd(next); + c->dirs_created++; } if(fd != root_fd) { close(fd); @@ -857,7 +897,15 @@ write_entry(void *zip, zipx_ctx_t *c, int root_fd, const char *name, snprintf(tmp, sizeof(tmp), "%s%u", ZIPX_PART_PREFIX, ++c->part_counter); fd = openat(dir_fd, tmp, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, 0600); if(fd < 0) { - ctx_fail(c, ZIPX_ERR_IO, name, "cannot create file: %s", strerror(errno)); + /* *at() fallback (see open_parent_dirs). */ + char full[ZIPX_PATH_MAX]; + + staging_path(c, dir_part, tmp, full, sizeof(full)); + fd = open(full, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW, 0600); + } + if(fd < 0) { + ctx_fail(c, ZIPX_ERR_IO, name, "cannot create file '%s': %s (errno=%d)", + tmp, strerror(errno), errno); if(dir_fd != root_fd) { close(dir_fd); } @@ -943,15 +991,29 @@ done: fd = -1; } if(!ret && renameat(dir_fd, tmp, dir_fd, base)) { - ctx_fail(c, ZIPX_ERR_IO, name, "cannot move file into place: %s", - strerror(errno)); - ret = -1; + /* *at() fallback (see open_parent_dirs). */ + char src_full[ZIPX_PATH_MAX]; + char dst_full[ZIPX_PATH_MAX]; + + staging_path(c, dir_part, tmp, src_full, sizeof(src_full)); + staging_path(c, dir_part, base, dst_full, sizeof(dst_full)); + if(rename(src_full, dst_full)) { + ctx_fail(c, ZIPX_ERR_IO, name, "cannot move file into place: %s " + "(errno=%d)", strerror(errno), errno); + ret = -1; + } } if(fd >= 0) { close(fd); } if(ret) { - unlinkat(dir_fd, tmp, 0); + if(unlinkat(dir_fd, tmp, 0)) { + /* *at() fallback (see open_parent_dirs). */ + char full[ZIPX_PATH_MAX]; + + staging_path(c, dir_part, tmp, full, sizeof(full)); + unlink(full); + } } else { c->files_created++; }