From 110559ce6ac719131d4607dd7a0282fdbb458a57 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Tue, 15 Aug 2023 18:24:53 -0700 Subject: [PATCH] Make ZipOS and Qemu work better This change improves the dirstream library in a lot of respects, especially for /zip/... files. Also turn off MAP_STACK on Aarch64 because Qemu seems to implement it differently than Linux and it's probably responsible for a lot of mysterious crashes. --- build/bootstrap/zipobj.com | Bin 201988 -> 209156 bytes libc/calls/execve.c | 4 +- libc/calls/faccessat.c | 4 +- libc/calls/fstat.c | 6 +- libc/calls/fstatat.c | 6 +- libc/calls/lseek.c | 2 +- libc/calls/openat.c | 4 +- libc/integral/c.inc | 8 - libc/integral/normalize.inc | 2 +- libc/intrin/ftrace_enabled.c | 2 +- libc/intrin/strace_enabled.c | 2 +- libc/runtime/cosmo2.c | 2 +- libc/runtime/mmap.c | 1 - libc/runtime/zipos-access.c | 57 ++- libc/runtime/zipos-close.c | 5 +- libc/runtime/zipos-fcntl.c | 18 +- libc/runtime/zipos-find.c | 45 +-- libc/runtime/zipos-fstat.c | 13 +- libc/runtime/zipos-get.c | 7 +- libc/runtime/zipos-lseek.c | 56 +-- libc/runtime/zipos-mmap.c | 33 +- .../{zipos-free.c => zipos-normpath.c} | 75 +++- libc/runtime/zipos-open.c | 155 ++++---- libc/runtime/zipos-parseuri.c | 12 +- libc/runtime/zipos-read.c | 42 ++- libc/runtime/zipos-stat-impl.c | 8 +- libc/runtime/zipos-stat.c | 23 +- libc/runtime/zipos.internal.h | 37 +- libc/stdio/dirstream.c | 353 +++++++++--------- libc/sysv/consts.sh | 12 - libc/sysv/consts/DT_BLK.S | 2 - libc/sysv/consts/DT_CHR.S | 2 - libc/sysv/consts/DT_DIR.S | 2 - libc/sysv/consts/DT_FIFO.S | 2 - libc/sysv/consts/DT_LNK.S | 2 - libc/sysv/consts/DT_REG.S | 2 - libc/sysv/consts/DT_SOCK.S | 2 - libc/sysv/consts/DT_UNKNOWN.S | 2 - libc/sysv/consts/dt.h | 14 - libc/zipos/zipos.internal.h | 0 test/libc/calls/access_test.c | 1 - test/libc/calls/read_test.c | 9 + test/libc/calls/stat_test.c | 9 +- test/libc/runtime/zipos_test.c | 39 ++ test/libc/stdio/dirstream_test.c | 83 +++- test/libc/stdio/zipdir_test.c | 69 ++++ third_party/unzip/process.c | 2 +- tool/build/lib/elfwriter_zip.c | 12 +- 48 files changed, 748 insertions(+), 500 deletions(-) rename libc/runtime/{zipos-free.c => zipos-normpath.c} (57%) delete mode 100644 libc/sysv/consts/DT_BLK.S delete mode 100644 libc/sysv/consts/DT_CHR.S delete mode 100644 libc/sysv/consts/DT_DIR.S delete mode 100644 libc/sysv/consts/DT_FIFO.S delete mode 100644 libc/sysv/consts/DT_LNK.S delete mode 100644 libc/sysv/consts/DT_REG.S delete mode 100644 libc/sysv/consts/DT_SOCK.S delete mode 100644 libc/sysv/consts/DT_UNKNOWN.S delete mode 100755 libc/zipos/zipos.internal.h create mode 100644 test/libc/stdio/zipdir_test.c diff --git a/build/bootstrap/zipobj.com b/build/bootstrap/zipobj.com index 26128d2a73735d3b1e5aa6aa6d753c716e994dae..9b2bbd30fc2b24baaec538b03c75736566f03244 100755 GIT binary patch delta 45069 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 k(M8QB!Tq{= 6 #pragma GCC diagnostic error "-Wnonnull-compare" -#if defined(_COSMO_SOURCE) && !defined(MODE_DBG) && \ - !defined(STACK_FRAME_UNLIMITED) -#pragma GCC diagnostic error "-Wframe-larger-than=131072" -#if __GNUC__ >= 9 -// #pragma GCC diagnostic error "-Walloca-larger-than=1024" -// #pragma GCC diagnostic error "-Wvla-larger-than=1024" -#endif /* GCC 9+ */ -#endif /* STACK_FRAME_UNLIMITED */ #elif __GNUC__ >= 9 #pragma GCC diagnostic error /* e.g. fabs not abs */ "-Wabsolute-value" #endif /* GCC 6+ */ diff --git a/libc/integral/normalize.inc b/libc/integral/normalize.inc index a0dcc69fa..6fed2b693 100644 --- a/libc/integral/normalize.inc +++ b/libc/integral/normalize.inc @@ -74,7 +74,7 @@ #define __BIGGEST_ALIGNMENT__ 16 #endif -#define APE_STACKSIZE 4194304 /* default 4mb stack */ +#define APE_STACKSIZE 8388608 /* default 8mb stack */ #define APE_PAGESIZE 0x10000 /* i386+ */ #ifdef _COSMO_SOURCE #define FRAMESIZE 0x10000 diff --git a/libc/intrin/ftrace_enabled.c b/libc/intrin/ftrace_enabled.c index 1e448fbe6..287c20245 100644 --- a/libc/intrin/ftrace_enabled.c +++ b/libc/intrin/ftrace_enabled.c @@ -25,7 +25,7 @@ * @param delta is added to enabled state * @return enabled state before `delta` was applied */ -dontinstrument int ftrace_enabled(int delta) { +dontasan dontubsan dontinstrument int ftrace_enabled(int delta) { int res; struct CosmoTib *tib; if (__tls_enabled) { diff --git a/libc/intrin/strace_enabled.c b/libc/intrin/strace_enabled.c index e3783b36c..c5c42a1f1 100644 --- a/libc/intrin/strace_enabled.c +++ b/libc/intrin/strace_enabled.c @@ -25,7 +25,7 @@ * @param delta is added to enabled state * @return enabled state before `delta` was applied */ -dontinstrument int strace_enabled(int delta) { +dontasan dontubsan dontinstrument int strace_enabled(int delta) { int res; struct CosmoTib *tib; if (__tls_enabled) { diff --git a/libc/runtime/cosmo2.c b/libc/runtime/cosmo2.c index bbae7d7ef..6af619190 100644 --- a/libc/runtime/cosmo2.c +++ b/libc/runtime/cosmo2.c @@ -164,7 +164,7 @@ wontreturn textstartup void cosmo(long *sp, struct Syslib *m1) { __switch_stacks(argc, argv, envp, auxv, cosmo2, (char *)mmap(ape_stack_vaddr, (uintptr_t)ape_stack_memsz, MAP_FIXED | PROT_READ | PROT_WRITE, - MAP_ANONYMOUS | MAP_STACK, -1, 0) + + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) + (uintptr_t)ape_stack_memsz); } diff --git a/libc/runtime/mmap.c b/libc/runtime/mmap.c index 291147ad1..bfd8ea60c 100644 --- a/libc/runtime/mmap.c +++ b/libc/runtime/mmap.c @@ -54,7 +54,6 @@ #include "libc/sysv/consts/ss.h" #include "libc/sysv/errfuns.h" #include "libc/thread/thread.h" -#include "libc/zipos/zipos.internal.h" #define MAP_ANONYMOUS_linux 0x00000020 #define MAP_ANONYMOUS_openbsd 0x00001000 diff --git a/libc/runtime/zipos-access.c b/libc/runtime/zipos-access.c index 93c0d898e..535dead40 100644 --- a/libc/runtime/zipos-access.c +++ b/libc/runtime/zipos-access.c @@ -18,10 +18,10 @@ โ•šโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€*/ #include "libc/calls/calls.h" #include "libc/calls/struct/stat.h" +#include "libc/runtime/zipos.internal.h" #include "libc/sysv/consts/ok.h" #include "libc/sysv/errfuns.h" #include "libc/zip.internal.h" -#include "libc/runtime/zipos.internal.h" // TODO: this should check parent directory components @@ -31,37 +31,30 @@ * @param uri is obtained via __zipos_parseuri() * @asyncsignalsafe */ -int __zipos_access(const struct ZiposUri *name, int amode) { - ssize_t cf; - int rc, mode; +int __zipos_access(struct ZiposUri *name, int amode) { + struct Zipos *z; - if ((z = __zipos_get()) && (cf = __zipos_find(z, name)) != -1) { - mode = GetZipCfileMode(z->map + cf); - if (amode == F_OK) { - rc = 0; - } else if (amode == R_OK) { - if (mode & 0444) { - rc = 0; - } else { - rc = eacces(); - } - } else if (amode == W_OK) { - if (mode & 0222) { - rc = 0; - } else { - rc = eacces(); - } - } else if (amode == X_OK) { - if (mode & 0111) { - rc = 0; - } else { - rc = eacces(); - } - } else { - rc = einval(); - } - } else { - rc = enoent(); + if (!(z = __zipos_get())) { + return enoexec(); } - return rc; + + ssize_t cf; + if ((cf = __zipos_find(z, name)) == -1) { + return enoent(); + } + + int mode = GetZipCfileMode(z->map + cf); + if (amode == F_OK) { + return 0; + } + if (amode & ~(R_OK | W_OK | X_OK)) { + return einval(); + } + if (((amode & X_OK) && !(mode & 0111)) || + ((amode & W_OK) && !(mode & 0222)) || + ((amode & R_OK) && !(mode & 0444))) { + return eacces(); + } + + return 0; } diff --git a/libc/runtime/zipos-close.c b/libc/runtime/zipos-close.c index cd82453c9..c70ed746a 100644 --- a/libc/runtime/zipos-close.c +++ b/libc/runtime/zipos-close.c @@ -16,7 +16,6 @@ โ”‚ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR โ”‚ โ”‚ PERFORMANCE OF THIS SOFTWARE. โ”‚ โ•šโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€*/ -#include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/calls/state.internal.h" #include "libc/calls/syscall-sysv.internal.h" @@ -38,10 +37,10 @@ int __zipos_close(int fd) { if (!IsWindows()) { rc = sys_close(fd); } else { - rc = 0; /* no system file descriptor needed on nt */ + rc = 0; // no system file descriptor needed on nt } if (!__vforked) { - __zipos_free(__zipos_get(), h); + __zipos_free(h); } return rc; } diff --git a/libc/runtime/zipos-fcntl.c b/libc/runtime/zipos-fcntl.c index b2d3ea833..7424dde12 100644 --- a/libc/runtime/zipos-fcntl.c +++ b/libc/runtime/zipos-fcntl.c @@ -17,34 +17,28 @@ โ”‚ PERFORMANCE OF THIS SOFTWARE. โ”‚ โ•šโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€*/ #include "libc/calls/internal.h" +#include "libc/runtime/zipos.internal.h" #include "libc/sysv/consts/f.h" #include "libc/sysv/consts/fd.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/errfuns.h" -#include "libc/zip.internal.h" -#include "libc/runtime/zipos.internal.h" - -#define ZIPOS __zipos_get() -#define HANDLE ((struct ZiposHandle *)(intptr_t)g_fds.p[fd].handle) int __zipos_fcntl(int fd, int cmd, uintptr_t arg) { - int rc; if (cmd == F_GETFD) { if (g_fds.p[fd].flags & O_CLOEXEC) { - rc = FD_CLOEXEC; + return FD_CLOEXEC; } else { - rc = 0; + return 0; } } else if (cmd == F_SETFD) { if (arg & FD_CLOEXEC) { g_fds.p[fd].flags |= O_CLOEXEC; - rc = FD_CLOEXEC; + return FD_CLOEXEC; } else { g_fds.p[fd].flags &= ~O_CLOEXEC; - rc = 0; + return 0; } } else { - rc = einval(); + return einval(); } - return rc; } diff --git a/libc/runtime/zipos-find.c b/libc/runtime/zipos-find.c index 0ab8b78db..647f14f6f 100644 --- a/libc/runtime/zipos-find.c +++ b/libc/runtime/zipos-find.c @@ -16,39 +16,30 @@ โ”‚ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR โ”‚ โ”‚ PERFORMANCE OF THIS SOFTWARE. โ”‚ โ•šโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€*/ -#include "ape/relocations.h" -#include "libc/assert.h" -#include "libc/runtime/runtime.h" -#include "libc/str/str.h" -#include "libc/zip.internal.h" #include "libc/runtime/zipos.internal.h" +#include "libc/zip.internal.h" -// TODO(jart): improve time complexity here - -ssize_t __zipos_find(struct Zipos *zipos, const struct ZiposUri *name) { - const char *zname; - size_t i, n, c, znamesize; +ssize_t __zipos_find(struct Zipos *zipos, struct ZiposUri *name) { if (!name->len) { - return 0; + return ZIPOS_SYNTHETIC_DIRECTORY; } - c = GetZipCdirOffset(zipos->cdir); - n = GetZipCdirRecords(zipos->cdir); - for (i = 0; i < n; ++i, c += ZIP_CFILE_HDRSIZE(zipos->map + c)) { - npassert(ZIP_CFILE_MAGIC(zipos->map + c) == kZipCfileHdrMagic); - zname = ZIP_CFILE_NAME(zipos->map + c); - znamesize = ZIP_CFILE_NAMESIZE(zipos->map + c); - if ((name->len == znamesize && !memcmp(name->path, zname, name->len)) || - (name->len + 1 == znamesize && !memcmp(name->path, zname, name->len) && - zname[name->len] == '/')) { + bool found_subfile = false; + size_t c = GetZipCdirOffset(zipos->cdir); + size_t n = GetZipCdirRecords(zipos->cdir); + for (size_t i = 0; i < n; ++i, c += ZIP_CFILE_HDRSIZE(zipos->map + c)) { + const char *zname = ZIP_CFILE_NAME(zipos->map + c); + size_t zsize = ZIP_CFILE_NAMESIZE(zipos->map + c); + if ((name->len == zsize || + (name->len + 1 == zsize && zname[name->len] == '/')) && + !memcmp(name->path, zname, name->len)) { return c; - } else if ((name->len < znamesize && - !memcmp(name->path, zname, name->len) && - zname[name->len - 1] == '/') || - (name->len + 1 < znamesize && - !memcmp(name->path, zname, name->len) && - zname[name->len] == '/')) { - return 0; + } else if (name->len + 1 < zsize && zname[name->len] == '/' && + !memcmp(name->path, zname, name->len)) { + found_subfile = true; } } + if (found_subfile) { + return ZIPOS_SYNTHETIC_DIRECTORY; + } return -1; } diff --git a/libc/runtime/zipos-fstat.c b/libc/runtime/zipos-fstat.c index 8b051eb9c..822b90185 100644 --- a/libc/runtime/zipos-fstat.c +++ b/libc/runtime/zipos-fstat.c @@ -16,9 +16,6 @@ โ”‚ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR โ”‚ โ”‚ PERFORMANCE OF THIS SOFTWARE. โ”‚ โ•šโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€*/ -#include "libc/assert.h" -#include "libc/sysv/errfuns.h" -#include "libc/zip.internal.h" #include "libc/runtime/zipos.internal.h" /** @@ -27,12 +24,6 @@ * @param uri is obtained via __zipos_parseuri() * @asyncsignalsafe */ -int __zipos_fstat(const struct ZiposHandle *h, struct stat *st) { - int rc; - if (st) { - rc = __zipos_stat_impl(__zipos_get(), h->cfile, st); - } else { - rc = efault(); - } - return rc; +int __zipos_fstat(struct ZiposHandle *h, struct stat *st) { + return __zipos_stat_impl(h->zipos, h->cfile, st); } diff --git a/libc/runtime/zipos-get.c b/libc/runtime/zipos-get.c index 4a232c208..b5a1b8ae7 100644 --- a/libc/runtime/zipos-get.c +++ b/libc/runtime/zipos-get.c @@ -20,16 +20,17 @@ #include "libc/calls/metalfile.internal.h" #include "libc/fmt/conv.h" #include "libc/intrin/cmpxchg.h" +#include "libc/intrin/kprintf.h" #include "libc/intrin/promises.internal.h" #include "libc/intrin/strace.internal.h" #include "libc/macros.internal.h" #include "libc/runtime/runtime.h" +#include "libc/runtime/zipos.internal.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/prot.h" #include "libc/thread/thread.h" #include "libc/zip.internal.h" -#include "libc/runtime/zipos.internal.h" #ifdef __x86_64__ __static_yoink(APE_COM_NAME); @@ -81,7 +82,9 @@ struct Zipos *__zipos_get(void) { } if (fd != -1 || PLEDGED(RPATH)) { if (fd == -1) { - progpath = GetProgramExecutableName(); + if (!progpath) { + progpath = GetProgramExecutableName(); + } fd = open(progpath, O_RDONLY); } if (fd != -1) { diff --git a/libc/runtime/zipos-lseek.c b/libc/runtime/zipos-lseek.c index baddee427..42f56fddc 100644 --- a/libc/runtime/zipos-lseek.c +++ b/libc/runtime/zipos-lseek.c @@ -17,10 +17,39 @@ โ”‚ PERFORMANCE OF THIS SOFTWARE. โ”‚ โ•šโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€*/ #include "libc/calls/calls.h" +#include "libc/runtime/zipos.internal.h" +#include "libc/stdckdint.h" +#include "libc/sysv/consts/s.h" #include "libc/sysv/errfuns.h" #include "libc/thread/thread.h" #include "libc/zip.internal.h" -#include "libc/runtime/zipos.internal.h" + +static int64_t __zipos_lseek_impl(struct ZiposHandle *h, int64_t offset, + unsigned whence) { + int64_t pos; + if (h->cfile == ZIPOS_SYNTHETIC_DIRECTORY || + S_ISDIR(GetZipCfileMode(h->zipos->map + h->cfile))) { + return eisdir(); + } + switch (whence) { + case SEEK_SET: + return offset; + case SEEK_CUR: + if (!ckd_add(&pos, h->pos, offset)) { + return pos; + } else { + return eoverflow(); + } + case SEEK_END: + if (!ckd_sub(&pos, h->size, offset)) { + return pos; + } else { + return eoverflow(); + } + default: + return einval(); + } +} /** * Changes current position of zip file handle. @@ -31,27 +60,12 @@ * @asyncsignalsafe */ int64_t __zipos_lseek(struct ZiposHandle *h, int64_t offset, unsigned whence) { - int64_t rc; + int64_t pos; + if (offset < 0) return einval(); pthread_mutex_lock(&h->lock); - switch (whence) { - case SEEK_SET: - rc = offset; - break; - case SEEK_CUR: - rc = h->pos + offset; - break; - case SEEK_END: - rc = h->size - offset; - break; - default: - rc = -1; - break; - } - if (rc >= 0) { - h->pos = rc; - } else { - rc = einval(); + if ((pos = __zipos_lseek_impl(h, offset, whence)) != -1) { + h->pos = pos; } pthread_mutex_unlock(&h->lock); - return rc; + return pos; } diff --git a/libc/runtime/zipos-mmap.c b/libc/runtime/zipos-mmap.c index efc383bf9..1c8fd9351 100644 --- a/libc/runtime/zipos-mmap.c +++ b/libc/runtime/zipos-mmap.c @@ -20,14 +20,15 @@ #include "libc/calls/struct/iovec.h" #include "libc/dce.h" #include "libc/errno.h" -#include "libc/intrin/likely.h" #include "libc/intrin/strace.internal.h" #include "libc/runtime/internal.h" #include "libc/runtime/runtime.h" #include "libc/runtime/zipos.internal.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/prot.h" +#include "libc/sysv/consts/s.h" #include "libc/sysv/errfuns.h" +#include "libc/zip.internal.h" #define IP(X) (intptr_t)(X) #define VIP(X) (void *)IP(X) @@ -50,25 +51,36 @@ */ dontasan void *__zipos_mmap(void *addr, size_t size, int prot, int flags, struct ZiposHandle *h, int64_t off) { - if (!(flags & MAP_PRIVATE) || - (flags & ~(MAP_PRIVATE | MAP_FILE | MAP_FIXED | MAP_FIXED_NOREPLACE)) || - (!!(flags & MAP_FIXED) ^ !!(flags & MAP_FIXED_NOREPLACE))) { - STRACE( - "zipos mappings currently only support MAP_PRIVATE with select flags"); + + if (off < 0) { + STRACE("negative zipos mmap offset"); return VIP(einval()); } - if (VERY_UNLIKELY(off < 0)) { - STRACE("neg off"); + if (h->cfile == ZIPOS_SYNTHETIC_DIRECTORY || + S_ISDIR(GetZipCfileMode(h->zipos->map + h->cfile))) { + return VIP(eisdir()); + } + + if (flags & (MAP_SHARED | MAP_ANONYMOUS)) { + STRACE("ZipOS bad flags"); return VIP(einval()); } + if (prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC)) { + STRACE("ZipOS bad protection"); + return VIP(einval()); + } + + flags &= MAP_FIXED | MAP_FIXED_NOREPLACE; + flags |= MAP_PRIVATE | MAP_ANONYMOUS; + const int tempProt = !IsXnu() ? prot | PROT_WRITE : PROT_WRITE; - void *outAddr = __mmap_unlocked(addr, size, tempProt, - (flags & (~MAP_FILE)) | MAP_ANONYMOUS, -1, 0); + void *outAddr = __mmap_unlocked(addr, size, tempProt, flags, -1, 0); if (outAddr == MAP_FAILED) { return MAP_FAILED; } + do { if (__zipos_read(h, &(struct iovec){outAddr, size}, 1, off) == -1) { strace_enabled(-1); @@ -82,6 +94,7 @@ dontasan void *__zipos_mmap(void *addr, size_t size, int prot, int flags, } return outAddr; } while (0); + const int e = errno; __munmap_unlocked(outAddr, size); errno = e; diff --git a/libc/runtime/zipos-free.c b/libc/runtime/zipos-normpath.c similarity index 57% rename from libc/runtime/zipos-free.c rename to libc/runtime/zipos-normpath.c index 4404fdc86..96590254d 100644 --- a/libc/runtime/zipos-free.c +++ b/libc/runtime/zipos-normpath.c @@ -1,7 +1,7 @@ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-โ”‚ โ”‚vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :viโ”‚ โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก -โ”‚ Copyright 2022 Justine Alexandra Roberts Tunney โ”‚ +โ”‚ Copyright 2020 Justine Alexandra Roberts Tunney โ”‚ โ”‚ โ”‚ โ”‚ Permission to use, copy, modify, and/or distribute this software for โ”‚ โ”‚ any purpose with or without fee is hereby granted, provided that the โ”‚ @@ -16,22 +16,65 @@ โ”‚ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR โ”‚ โ”‚ PERFORMANCE OF THIS SOFTWARE. โ”‚ โ•šโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€*/ -#include "libc/dce.h" -#include "libc/intrin/asan.internal.h" -#include "libc/intrin/asancodes.h" -#include "libc/intrin/cmpxchg.h" -#include "libc/str/str.h" -#include "libc/thread/thread.h" #include "libc/runtime/zipos.internal.h" -void __zipos_free(struct Zipos *z, struct ZiposHandle *h) { - if (IsAsan()) { - __asan_poison((char *)h + sizeof(struct ZiposHandle), - h->mapsize - sizeof(struct ZiposHandle), kAsanHeapFree); +static size_t __zipos_trimpath(char *s, int *isabs) { + char *p = s, *q = s; + for (; *q; ++q) { + if (*q == '/') { + while (q[1] == '/') ++q; + if (q[1] == '.' && (q[2] == '/' || q[2] == '\0')) { + ++q; + } else { + *p++ = '/'; + } + } else { + *p++ = *q; + } } - pthread_mutex_destroy(&h->lock); - __zipos_lock(); - do h->next = z->freelist; - while (!_cmpxchg(&z->freelist, h->next, h)); - __zipos_unlock(); + if (s < p && p[-1] == '.' && p[-2] == '.' && (p - 2 == s || p[-3] == '/')) { + *p++ = '/'; + } + *p = '\0'; + if (isabs) { + *isabs = *s == '/'; + } + return p - s; +} + +size_t __zipos_normpath(char *s) { + int isabs; + char *p = s, *q = s; + __zipos_trimpath(s, &isabs); + if (!*s) return 0; + for (; *q != '\0'; ++q) { + if (q[0] == '/' && q[1] == '.' && q[2] == '.' && + (q[3] == '/' || q[3] == '\0')) { + char *ep = p; + while (s < ep && *--ep != '/') donothing; + if (ep != p && + (p[-1] != '.' || p[-2] != '.' || (s < p - 3 && p[-3] != '/'))) { + p = ep; + q += 2; + continue; + } else if (ep == s && isabs) { + q += 2; + continue; + } + } + if (q[0] != '/' || p != s || isabs) { + *p++ = *q; + } + } + if (p == s) { + *p++ = isabs ? '/' : '.'; + } + if (p == s + 1 && s[0] == '.') { + *p++ = '/'; + } + while (p - s > 1 && p[-1] == '/') { + --p; + } + *p = '\0'; + return p - s; } diff --git a/libc/runtime/zipos-open.c b/libc/runtime/zipos-open.c index 0bedca9c4..42df16626 100644 --- a/libc/runtime/zipos-open.c +++ b/libc/runtime/zipos-open.c @@ -23,6 +23,7 @@ #include "libc/calls/state.internal.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/syscall-sysv.internal.h" +#include "libc/calls/syscall_support-sysv.internal.h" #include "libc/dce.h" #include "libc/errno.h" #include "libc/intrin/asan.internal.h" @@ -30,19 +31,21 @@ #include "libc/intrin/cmpxchg.h" #include "libc/intrin/directmap.internal.h" #include "libc/intrin/extend.internal.h" +#include "libc/intrin/strace.internal.h" #include "libc/intrin/weaken.h" #include "libc/runtime/internal.h" #include "libc/runtime/memtrack.internal.h" +#include "libc/runtime/zipos.internal.h" #include "libc/sysv/consts/f.h" #include "libc/sysv/consts/fd.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/prot.h" +#include "libc/sysv/consts/s.h" #include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" #include "libc/thread/thread.h" #include "libc/zip.internal.h" -#include "libc/runtime/zipos.internal.h" static char *mapend; static size_t maptotal; @@ -60,6 +63,18 @@ static void *__zipos_mmap_space(size_t mapsize) { return start + offset; } +void __zipos_free(struct ZiposHandle *h) { + if (IsAsan()) { + __asan_poison((char *)h + sizeof(struct ZiposHandle), + h->mapsize - sizeof(struct ZiposHandle), kAsanHeapFree); + } + pthread_mutex_destroy(&h->lock); + __zipos_lock(); + do h->next = h->zipos->freelist; + while (!_cmpxchg(&h->zipos->freelist, h->next, h)); + __zipos_unlock(); +} + static struct ZiposHandle *__zipos_alloc(struct Zipos *zipos, size_t size) { size_t mapsize; struct ZiposHandle *h, **ph; @@ -88,6 +103,7 @@ StartOver: } if (h) { h->size = size; + h->zipos = zipos; h->mapsize = mapsize; pthread_mutex_init(&h->lock, 0); } @@ -95,65 +111,63 @@ StartOver: } static int __zipos_mkfd(int minfd) { - int e, fd; - static bool demodernize; - if (!demodernize) { - e = errno; - if ((fd = __sys_fcntl(2, F_DUPFD_CLOEXEC, minfd)) != -1) { - return fd; - } else if (errno == EINVAL) { - demodernize = true; - errno = e; - } else { - return fd; - } + int fd, e = errno; + if ((fd = __sys_fcntl(2, F_DUPFD_CLOEXEC, minfd)) != -1) { + return fd; + } else if (errno == EINVAL) { + errno = e; + return __fixupnewfd(__sys_fcntl(2, F_DUPFD, minfd), O_CLOEXEC); + } else { + return fd; } - if ((fd = __sys_fcntl(2, F_DUPFD, minfd)) != -1) { - __sys_fcntl(fd, F_SETFD, FD_CLOEXEC); - } - return fd; } -static int __zipos_setfd(int fd, struct ZiposHandle *h, unsigned flags, - int mode) { +static int __zipos_setfd(int fd, struct ZiposHandle *h, unsigned flags) { int want = fd; atomic_compare_exchange_strong_explicit( &g_fds.f, &want, fd + 1, memory_order_release, memory_order_relaxed); g_fds.p[fd].kind = kFdZip; g_fds.p[fd].handle = (intptr_t)h; g_fds.p[fd].flags = flags | O_CLOEXEC; - g_fds.p[fd].mode = mode; g_fds.p[fd].extra = 0; __fds_unlock(); return fd; } -static int __zipos_load(struct Zipos *zipos, size_t cf, unsigned flags, - int mode) { +static int __zipos_load(struct Zipos *zipos, size_t cf, int flags, + struct ZiposUri *name) { size_t lf; size_t size; int rc, fd, minfd; struct ZiposHandle *h; - lf = GetZipCfileOffset(zipos->map + cf); - npassert((ZIP_LFILE_MAGIC(zipos->map + lf) == kZipLfileHdrMagic)); - size = GetZipLfileUncompressedSize(zipos->map + lf); - switch (ZIP_LFILE_COMPRESSIONMETHOD(zipos->map + lf)) { - case kZipCompressionNone: - if (!(h = __zipos_alloc(zipos, 0))) return -1; - h->mem = ZIP_LFILE_CONTENT(zipos->map + lf); - break; - case kZipCompressionDeflate: - if (!(h = __zipos_alloc(zipos, size))) return -1; - if (!__inflate(h->data, size, ZIP_LFILE_CONTENT(zipos->map + lf), - GetZipLfileCompressedSize(zipos->map + lf))) { - h->mem = h->data; - } else { - h->mem = 0; - eio(); - } - break; - default: - return eio(); + if (cf == ZIPOS_SYNTHETIC_DIRECTORY) { + size = name->len; + if (!(h = __zipos_alloc(zipos, size + 1))) return -1; + if (size) memcpy(h->data, name->path, size); + h->data[size] = 0; + h->mem = h->data; + } else { + lf = GetZipCfileOffset(zipos->map + cf); + npassert((ZIP_LFILE_MAGIC(zipos->map + lf) == kZipLfileHdrMagic)); + size = GetZipLfileUncompressedSize(zipos->map + lf); + switch (ZIP_LFILE_COMPRESSIONMETHOD(zipos->map + lf)) { + case kZipCompressionNone: + if (!(h = __zipos_alloc(zipos, 0))) return -1; + h->mem = ZIP_LFILE_CONTENT(zipos->map + lf); + break; + case kZipCompressionDeflate: + if (!(h = __zipos_alloc(zipos, size))) return -1; + if (!__inflate(h->data, size, ZIP_LFILE_CONTENT(zipos->map + lf), + GetZipLfileCompressedSize(zipos->map + lf))) { + h->mem = h->data; + } else { + h->mem = 0; + eio(); + } + break; + default: + return eio(); + } } h->pos = 0; h->cfile = cf; @@ -164,7 +178,7 @@ static int __zipos_load(struct Zipos *zipos, size_t cf, unsigned flags, TryAgain: if (IsWindows() || IsMetal()) { if ((fd = __reservefd_unlocked(-1)) != -1) { - return __zipos_setfd(fd, h, flags, mode); + return __zipos_setfd(fd, h, flags); } } else if ((fd = __zipos_mkfd(minfd)) != -1) { if (__ensurefds_unlocked(fd) != -1) { @@ -173,16 +187,45 @@ static int __zipos_load(struct Zipos *zipos, size_t cf, unsigned flags, minfd = fd + 1; goto TryAgain; } - return __zipos_setfd(fd, h, flags, mode); + return __zipos_setfd(fd, h, flags); } sys_close(fd); } __fds_unlock(); } - __zipos_free(zipos, h); + __zipos_free(h); return -1; } +static int __zipos_open_impl(struct ZiposUri *name, int flags) { + struct Zipos *zipos; + if (!(zipos = __zipos_get())) { + return enoexec(); + } + ssize_t cf; + if ((cf = __zipos_find(zipos, name)) == -1) { + return enoent(); + } + if ((flags & O_ACCMODE) != O_RDONLY || (flags & O_TRUNC)) { + return erofs(); + } + if (flags & O_EXCL) { + return eexist(); + } + if (cf != ZIPOS_SYNTHETIC_DIRECTORY) { + int mode = GetZipCfileMode(zipos->map + cf); +#if 0 + if ((flags & O_DIRECTORY) && !S_ISDIR(mode)) { + return enotdir(); + } +#endif + if (!(mode & 0444)) { + return eacces(); + } + } + return __zipos_load(zipos, cf, flags, name); +} + /** * Loads compressed file from ฮฑcฯ„ยตฮฑlly pฮดrฯ„ฮฑblฮต ฮตxฮตcยตฯ„ฮฑblฮต object store. * @@ -190,33 +233,15 @@ static int __zipos_load(struct Zipos *zipos, size_t cf, unsigned flags, * @asyncsignalsafe * @threadsafe */ -int __zipos_open(const struct ZiposUri *name, unsigned flags, int mode) { +int __zipos_open(struct ZiposUri *name, int flags) { int rc; - ssize_t cf; - struct Zipos *zipos; if (_weaken(pthread_testcancel_np) && (rc = _weaken(pthread_testcancel_np)())) { errno = rc; return -1; } BLOCK_SIGNALS; - if ((flags & O_ACCMODE) == O_RDONLY) { - if ((zipos = __zipos_get())) { - if ((cf = __zipos_find(zipos, name)) != -1) { - rc = __zipos_load(zipos, cf, flags, mode); - assert(rc != 0); - } else { - rc = enoent(); - assert(rc != 0); - } - } else { - rc = enoexec(); - assert(rc != 0); - } - } else { - rc = einval(); - assert(rc != 0); - } + rc = __zipos_open_impl(name, flags); ALLOW_SIGNALS; return rc; } diff --git a/libc/runtime/zipos-parseuri.c b/libc/runtime/zipos-parseuri.c index 4892a9cc4..7764959fa 100644 --- a/libc/runtime/zipos-parseuri.c +++ b/libc/runtime/zipos-parseuri.c @@ -16,19 +16,21 @@ โ”‚ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR โ”‚ โ”‚ PERFORMANCE OF THIS SOFTWARE. โ”‚ โ•šโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€*/ -#include "libc/str/str.h" #include "libc/runtime/zipos.internal.h" +#include "libc/str/str.h" /** * Extracts information about ZIP URI if it is one. */ ssize_t __zipos_parseuri(const char *uri, struct ZiposUri *out) { size_t len; - if ((uri[0] == '/' && uri[1] == 'z' && uri[2] == 'i' && uri[3] == 'p' && + if ((uri[0] == '/' && // + uri[1] == 'z' && // + uri[2] == 'i' && // + uri[3] == 'p' && // (!uri[4] || uri[4] == '/')) && - (len = strlen(uri)) < PATH_MAX) { - out->path = uri + 4 + !!uri[4]; - return (out->len = len - 4 - !!uri[4]); + strlcpy(out->path, uri + 4 + !!uri[4], ZIPOS_PATH_MAX) < ZIPOS_PATH_MAX) { + return (out->len = __zipos_normpath(out->path)); } else { return -1; } diff --git a/libc/runtime/zipos-read.c b/libc/runtime/zipos-read.c index f2801fa7b..748555d9e 100644 --- a/libc/runtime/zipos-read.c +++ b/libc/runtime/zipos-read.c @@ -18,11 +18,10 @@ โ•šโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€*/ #include "libc/assert.h" #include "libc/calls/struct/iovec.h" -#include "libc/intrin/safemacros.internal.h" -#include "libc/str/str.h" -#include "libc/thread/thread.h" -#include "libc/zip.internal.h" #include "libc/runtime/zipos.internal.h" +#include "libc/sysv/consts/s.h" +#include "libc/sysv/errfuns.h" +#include "libc/zip.internal.h" static size_t GetIovSize(const struct iovec *iov, size_t iovlen) { size_t i, r; @@ -30,6 +29,29 @@ static size_t GetIovSize(const struct iovec *iov, size_t iovlen) { return r; } +static ssize_t __zipos_read_impl(struct ZiposHandle *h, const struct iovec *iov, + size_t iovlen, ssize_t opt_offset) { + int i; + int64_t b, x, y; + if (h->cfile == ZIPOS_SYNTHETIC_DIRECTORY || + S_ISDIR(GetZipCfileMode(h->zipos->map + h->cfile))) { + return eisdir(); + } + if (opt_offset == -1) { + x = y = h->pos; + } else { + x = y = opt_offset; + } + for (i = 0; i < iovlen && y < h->size; ++i, y += b) { + b = MIN(iov[i].iov_len, h->size - y); + if (b) memcpy(iov[i].iov_base, h->mem + y, b); + } + if (opt_offset == -1) { + h->pos = y; + } + return y - x; +} + /** * Reads data from zip store object. * @@ -39,14 +61,10 @@ static size_t GetIovSize(const struct iovec *iov, size_t iovlen) { */ ssize_t __zipos_read(struct ZiposHandle *h, const struct iovec *iov, size_t iovlen, ssize_t opt_offset) { - size_t i, b, x, y; + ssize_t rc; + unassert(opt_offset >= 0 || opt_offset == -1); pthread_mutex_lock(&h->lock); - x = y = opt_offset != -1 ? opt_offset : h->pos; - for (i = 0; i < iovlen && y < h->size; ++i, y += b) { - b = min(iov[i].iov_len, h->size - y); - if (b) memcpy(iov[i].iov_base, h->mem + y, b); - } - if (opt_offset == -1) h->pos = y; + rc = __zipos_read_impl(h, iov, iovlen, opt_offset); pthread_mutex_unlock(&h->lock); - return y - x; + return rc; } diff --git a/libc/runtime/zipos-stat-impl.c b/libc/runtime/zipos-stat-impl.c index a9930de97..61b0f2e7f 100644 --- a/libc/runtime/zipos-stat-impl.c +++ b/libc/runtime/zipos-stat-impl.c @@ -18,17 +18,19 @@ โ•šโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€*/ #include "libc/calls/struct/stat.h" #include "libc/intrin/safemacros.internal.h" +#include "libc/runtime/zipos.internal.h" #include "libc/str/str.h" #include "libc/sysv/consts/s.h" #include "libc/sysv/errfuns.h" #include "libc/zip.internal.h" -#include "libc/runtime/zipos.internal.h" int __zipos_stat_impl(struct Zipos *zipos, size_t cf, struct stat *st) { size_t lf; if (zipos && st) { bzero(st, sizeof(*st)); - if (cf) { + if (cf == ZIPOS_SYNTHETIC_DIRECTORY) { + st->st_mode = S_IFDIR | 0555; + } else { lf = GetZipCfileOffset(zipos->map + cf); st->st_mode = GetZipCfileMode(zipos->map + cf); st->st_size = GetZipLfileUncompressedSize(zipos->map + lf); @@ -37,8 +39,6 @@ int __zipos_stat_impl(struct Zipos *zipos, size_t cf, struct stat *st) { GetZipCfileTimestamps(zipos->map + cf, &st->st_mtim, &st->st_atim, &st->st_ctim, 0); st->st_birthtim = st->st_ctim; - } else { - st->st_mode = 0444 | S_IFDIR | 0111; } return 0; } else { diff --git a/libc/runtime/zipos-stat.c b/libc/runtime/zipos-stat.c index 77733d91a..048508198 100644 --- a/libc/runtime/zipos-stat.c +++ b/libc/runtime/zipos-stat.c @@ -16,9 +16,8 @@ โ”‚ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR โ”‚ โ”‚ PERFORMANCE OF THIS SOFTWARE. โ”‚ โ•šโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€*/ -#include "libc/stdio/stdio.h" -#include "libc/sysv/errfuns.h" #include "libc/runtime/zipos.internal.h" +#include "libc/sysv/errfuns.h" /** * Reads file metadata from ฮฑcฯ„ยตฮฑlly pฮดrฯ„ฮฑblฮต ฮตxฮตcยตฯ„ฮฑblฮต object store. @@ -26,22 +25,10 @@ * @param uri is obtained via __zipos_parseuri() * @asyncsignalsafe */ -int __zipos_stat(const struct ZiposUri *name, struct stat *st) { - int rc; +int __zipos_stat(struct ZiposUri *name, struct stat *st) { ssize_t cf; struct Zipos *zipos; - if (st) { - if ((zipos = __zipos_get())) { - if ((cf = __zipos_find(zipos, name)) != -1) { - rc = __zipos_stat_impl(zipos, cf, st); - } else { - rc = enoent(); - } - } else { - rc = enoexec(); - } - } else { - rc = efault(); - } - return rc; + if (!(zipos = __zipos_get())) return enoexec(); + if ((cf = __zipos_find(zipos, name)) == -1) return enoent(); + return __zipos_stat_impl(zipos, cf, st); } diff --git a/libc/runtime/zipos.internal.h b/libc/runtime/zipos.internal.h index b107964f4..cddb10cde 100644 --- a/libc/runtime/zipos.internal.h +++ b/libc/runtime/zipos.internal.h @@ -6,23 +6,29 @@ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ +#define ZIPOS_PATH_MAX 1024 + +#define ZIPOS_SYNTHETIC_DIRECTORY 0 + struct stat; struct iovec; +struct Zipos; struct ZiposUri { - const char *path; - size_t len; + uint32_t len; + char path[ZIPOS_PATH_MAX]; }; struct ZiposHandle { struct ZiposHandle *next; pthread_mutex_t lock; - size_t size; /* byte length of `mem` */ - size_t mapsize; /* total size of this struct */ - size_t pos; /* read/write byte offset state */ - uint32_t cfile; /* central directory entry rva */ - uint8_t *mem; /* points to inflated data or uncompressed image */ - uint8_t data[]; /* uncompressed file memory */ + struct Zipos *zipos; + size_t size; + size_t mapsize; + size_t pos; + size_t cfile; + uint8_t *mem; + uint8_t data[]; }; struct Zipos { @@ -31,17 +37,18 @@ struct Zipos { struct ZiposHandle *freelist; }; +int __zipos_close(int); void __zipos_lock(void); void __zipos_unlock(void); -int __zipos_close(int); +size_t __zipos_normpath(char *); struct Zipos *__zipos_get(void) pureconst; -void __zipos_free(struct Zipos *, struct ZiposHandle *); +void __zipos_free(struct ZiposHandle *); ssize_t __zipos_parseuri(const char *, struct ZiposUri *); -ssize_t __zipos_find(struct Zipos *, const struct ZiposUri *); -int __zipos_open(const struct ZiposUri *, unsigned, int); -int __zipos_access(const struct ZiposUri *, int); -int __zipos_stat(const struct ZiposUri *, struct stat *); -int __zipos_fstat(const struct ZiposHandle *, struct stat *); +ssize_t __zipos_find(struct Zipos *, struct ZiposUri *); +int __zipos_open(struct ZiposUri *, int); +int __zipos_access(struct ZiposUri *, int); +int __zipos_stat(struct ZiposUri *, struct stat *); +int __zipos_fstat(struct ZiposHandle *, struct stat *); int __zipos_stat_impl(struct Zipos *, size_t, struct stat *); ssize_t __zipos_read(struct ZiposHandle *, const struct iovec *, size_t, ssize_t); diff --git a/libc/stdio/dirstream.c b/libc/stdio/dirstream.c index 22b55723d..d9b308b40 100644 --- a/libc/stdio/dirstream.c +++ b/libc/stdio/dirstream.c @@ -22,8 +22,9 @@ #include "libc/calls/struct/dirent.h" #include "libc/calls/struct/stat.h" #include "libc/calls/syscall_support-nt.internal.h" +#include "libc/dce.h" #include "libc/errno.h" -#include "libc/intrin/asan.internal.h" +#include "libc/intrin/kprintf.h" #include "libc/intrin/nopl.internal.h" #include "libc/intrin/strace.internal.h" #include "libc/intrin/weaken.h" @@ -32,6 +33,7 @@ #include "libc/nt/enum/filetype.h" #include "libc/nt/files.h" #include "libc/nt/struct/win32finddata.h" +#include "libc/runtime/zipos.internal.h" #include "libc/str/str.h" #include "libc/sysv/consts/dt.h" #include "libc/sysv/consts/o.h" @@ -39,7 +41,6 @@ #include "libc/sysv/errfuns.h" #include "libc/thread/thread.h" #include "libc/zip.internal.h" -#include "libc/runtime/zipos.internal.h" /** * @fileoverview Directory Streams for Linux+Mac+Windows+FreeBSD+OpenBSD. @@ -57,15 +58,17 @@ int sys_getdents(unsigned, void *, unsigned, long *); * Directory stream object. */ struct dirstream { + int fd; bool iszip; - int64_t fd; + int64_t hand; int64_t tell; + char16_t *name; pthread_mutex_t lock; struct { uint64_t offset; uint64_t records; - uint8_t *prefix; size_t prefixlen; + uint8_t prefix[ZIPOS_PATH_MAX]; } zip; struct dirent ent; union { @@ -76,7 +79,6 @@ struct dirstream { }; struct { bool isdone; - char16_t *name; struct NtWin32FindData windata; }; }; @@ -148,7 +150,7 @@ static textwindows DIR *opendir_nt_impl(char16_t *name, size_t len) { } name[len] = u'\0'; if ((res = calloc(1, sizeof(DIR)))) { - if ((res->fd = FindFirstFile(name, &res->windata)) != -1) { + if ((res->hand = FindFirstFile(name, &res->windata)) != -1) { return res; } __fix_enotdir(-1, name); @@ -160,35 +162,17 @@ static textwindows DIR *opendir_nt_impl(char16_t *name, size_t len) { return NULL; } -static textwindows dontinline DIR *opendir_nt(const char *path) { - int len; - DIR *res; - char16_t *name; - if (*path) { - if ((name = malloc(PATH_MAX * 2))) { - if ((len = __mkntpath(path, name)) != -1 && - (res = opendir_nt_impl(name, len))) { - res->name = name; - return res; - } - free(name); - } - } else { - enoent(); - } - return NULL; -} - static textwindows dontinline DIR *fdopendir_nt(int fd) { DIR *res; char16_t *name; if (__isfdkind(fd, kFdFile)) { - if ((name = malloc(PATH_MAX * 2))) { + if ((name = calloc(1, PATH_MAX * 2))) { if ((res = opendir_nt_impl( name, GetFinalPathNameByHandle( g_fds.p[fd].handle, name, PATH_MAX, kNtFileNameNormalized | kNtVolumeNameDos)))) { res->name = name; + res->fd = -1; close(fd); return res; } @@ -235,86 +219,13 @@ static textwindows dontinline struct dirent *readdir_nt(DIR *dir) { } } dir->ent.d_type = GetNtDirentType(&dir->windata); - dir->isdone = !FindNextFile(dir->fd, &dir->windata); + dir->isdone = !FindNextFile(dir->hand, &dir->windata); return &dir->ent; } else { return NULL; } } -/** - * Opens directory, e.g. - * - * DIR *d; - * struct dirent *e; - * CHECK((d = opendir(path))); - * while ((e = readdir(d))) { - * printf("%s/%s\n", path, e->d_name); - * } - * LOGIFNEG1(closedir(d)); - * - * @returns newly allocated DIR object, or NULL w/ errno - * @errors ENOENT, ENOTDIR, EACCES, EMFILE, ENFILE, ENOMEM - * @raise ECANCELED if thread was cancelled in masked mode - * @raise EINTR if we needed to block and a signal was delivered instead - * @cancellationpoint - * @see glob() - */ -DIR *opendir(const char *name) { - DIR *res; - int fd, rc; - struct stat st; - struct Zipos *zip; - struct ZiposUri zipname; - if (_weaken(pthread_testcancel_np) && - (rc = _weaken(pthread_testcancel_np)())) { - errno = rc; - return 0; - } - if (!name || (IsAsan() && !__asan_is_valid_str(name))) { - efault(); - res = 0; - } else if (_weaken(__zipos_get) && - _weaken(__zipos_parseuri)(name, &zipname) != -1) { - if (_weaken(__zipos_stat)(&zipname, &st) != -1) { - if (S_ISDIR(st.st_mode)) { - zip = _weaken(__zipos_get)(); - if ((res = calloc(1, sizeof(DIR)))) { - res->iszip = true; - res->fd = -1; - res->zip.offset = GetZipCdirOffset(zip->cdir); - res->zip.records = GetZipCdirRecords(zip->cdir); - res->zip.prefix = malloc(zipname.len + 2); - if (zipname.len) { - memcpy(res->zip.prefix, zipname.path, zipname.len); - } - if (zipname.len && res->zip.prefix[zipname.len - 1] != '/') { - res->zip.prefix[zipname.len++] = '/'; - } - res->zip.prefix[zipname.len] = '\0'; - res->zip.prefixlen = zipname.len; - } - } else { - enotdir(); - res = 0; - } - } else { - res = 0; - } - } else if (!IsWindows()) { - res = 0; - if ((fd = open(name, O_RDONLY | O_NOCTTY | O_DIRECTORY | O_CLOEXEC)) != - -1) { - if (!(res = fdopendir(fd))) { - close(fd); - } - } - } else { - res = opendir_nt(name); - } - return res; -} - /** * Creates directory object for file descriptor. * @@ -324,19 +235,98 @@ DIR *opendir(const char *name) { * @errors ENOMEM and fd is closed */ DIR *fdopendir(int fd) { + + // allocate directory iterator object DIR *dir; - if (!IsWindows()) { - if (!(dir = calloc(1, sizeof(*dir)))) return NULL; - dir->fd = fd; - } else { - dir = fdopendir_nt(fd); + if (!(dir = calloc(1, sizeof(*dir)))) { + return NULL; } + + // on unix, file descriptor isn't required to be tracked + dir->fd = fd; + if (!__isfdkind(fd, kFdZip)) { + if (IsWindows()) { + free(dir); + return fdopendir_nt(fd); + } + return dir; + } + dir->iszip = true; + + // ensure open /zip/... file is a directory + struct ZiposHandle *h = (struct ZiposHandle *)(intptr_t)g_fds.p[fd].handle; + if (h->cfile != ZIPOS_SYNTHETIC_DIRECTORY && + !S_ISDIR(GetZipCfileMode(h->zipos->map + h->cfile))) { + free(dir); + enotdir(); + return 0; + } + + // get path of this file descriptor and ensure trailing slash + size_t len; + char *name; + if (h->cfile != ZIPOS_SYNTHETIC_DIRECTORY) { + len = ZIP_CFILE_NAMESIZE(h->zipos->map + h->cfile); + name = ZIP_CFILE_NAME(h->zipos->map + h->cfile); + } else { + len = h->size; + name = (char *)h->data; + } + if (len + 2 > ZIPOS_PATH_MAX) { + free(dir); + enametoolong(); + return 0; + } + if (len) memcpy(dir->zip.prefix, name, len); + if (len && dir->zip.prefix[len - 1] != '/') { + dir->zip.prefix[len++] = '/'; + } + dir->zip.prefix[len] = 0; + dir->zip.prefixlen = len; + + // setup state values for directory iterator + dir->zip.offset = GetZipCdirOffset(h->zipos->cdir); + dir->zip.records = GetZipCdirRecords(h->zipos->cdir); + return dir; } +/** + * Opens directory, e.g. + * + * DIR *d; + * struct dirent *e; + * d = opendir(path); + * while ((e = readdir(d))) { + * printf("%s/%s\n", path, e->d_name); + * } + * closedir(d); + * + * @returns newly allocated DIR object, or NULL w/ errno + * @errors ENOENT, ENOTDIR, EACCES, EMFILE, ENFILE, ENOMEM + * @raise ECANCELED if thread was cancelled in masked mode + * @raise EINTR if we needed to block and a signal was delivered instead + * @cancellationpoint + * @see glob() + */ +DIR *opendir(const char *name) { + int rc; + if (_weaken(pthread_testcancel_np) && + (rc = _weaken(pthread_testcancel_np)())) { + errno = rc; + return 0; + } + int fd; + if ((fd = open(name, O_RDONLY | O_DIRECTORY | O_NOCTTY | O_CLOEXEC)) == -1) { + return 0; + } + DIR *res = fdopendir(fd); + if (!res) close(fd); + return res; +} + static struct dirent *readdir_impl(DIR *dir) { size_t n; - long basep; int rc, mode; uint8_t *s, *p; struct Zipos *zip; @@ -348,53 +338,68 @@ static struct dirent *readdir_impl(DIR *dir) { if (dir->iszip) { ent = 0; zip = _weaken(__zipos_get)(); - while (!ent && dir->tell < dir->zip.records) { - npassert(ZIP_CFILE_MAGIC(zip->map + dir->zip.offset) == - kZipCfileHdrMagic); - s = ZIP_CFILE_NAME(zip->map + dir->zip.offset); - n = ZIP_CFILE_NAMESIZE(zip->map + dir->zip.offset); - if (dir->zip.prefixlen < n && - !memcmp(dir->zip.prefix, s, dir->zip.prefixlen)) { - s += dir->zip.prefixlen; - n -= dir->zip.prefixlen; - p = memchr(s, '/', n); - if (!p || p + 1 - s == n) { - if (p + 1 - s == n) --n; - mode = GetZipCfileMode(zip->map + dir->zip.offset); - ent = (struct dirent *)dir->buf; - ent->d_ino++; - ent->d_off = dir->zip.offset; - ent->d_reclen = MIN(n, 255); - ent->d_type = S_ISDIR(mode) ? DT_DIR : DT_REG; - if (ent->d_reclen) { - memcpy(ent->d_name, s, ent->d_reclen); - } - ent->d_name[ent->d_reclen] = 0; - } else { - lastent = (struct dirent *)dir->buf; - n = p - s; - n = MIN(n, 255); - if (!lastent->d_ino || (n != lastent->d_reclen) || - memcmp(lastent->d_name, s, n)) { - ent = lastent; + while (!ent && dir->tell < dir->zip.records + 2) { + if (!dir->tell) { + ent = (struct dirent *)dir->buf; + ent->d_ino++; + ent->d_off = dir->tell; + ent->d_reclen = 1; + ent->d_type = DT_DIR; + strcpy(ent->d_name, "."); + } else if (dir->tell == 1) { + ent = (struct dirent *)dir->buf; + ent->d_ino++; + ent->d_off = dir->tell; + ent->d_reclen = 2; + ent->d_type = DT_DIR; + strcpy(ent->d_name, ".."); + } else { + s = ZIP_CFILE_NAME(zip->map + dir->zip.offset); + n = ZIP_CFILE_NAMESIZE(zip->map + dir->zip.offset); + if (dir->zip.prefixlen < n && + !memcmp(dir->zip.prefix, s, dir->zip.prefixlen)) { + s += dir->zip.prefixlen; + n -= dir->zip.prefixlen; + p = memchr(s, '/', n); + if (!p || p + 1 - s == n) { + if (p + 1 - s == n) --n; + mode = GetZipCfileMode(zip->map + dir->zip.offset); + ent = (struct dirent *)dir->buf; ent->d_ino++; - ent->d_off = -1; - ent->d_reclen = n; - ent->d_type = DT_DIR; + ent->d_off = dir->tell; + ent->d_reclen = MIN(n, 255); + ent->d_type = S_ISDIR(mode) ? DT_DIR : DT_REG; if (ent->d_reclen) { memcpy(ent->d_name, s, ent->d_reclen); } ent->d_name[ent->d_reclen] = 0; + } else { + lastent = (struct dirent *)dir->buf; + n = p - s; + n = MIN(n, 255); + if (!lastent->d_ino || (n != lastent->d_reclen) || + memcmp(lastent->d_name, s, n)) { + ent = lastent; + mode = GetZipCfileMode(zip->map + dir->zip.offset); + ent->d_ino++; + ent->d_off = dir->tell; + ent->d_reclen = n; + ent->d_type = S_ISDIR(mode) ? DT_DIR : DT_REG; + if (ent->d_reclen) { + memcpy(ent->d_name, s, ent->d_reclen); + } + ent->d_name[ent->d_reclen] = 0; + } } } + dir->zip.offset += ZIP_CFILE_HDRSIZE(zip->map + dir->zip.offset); } - dir->zip.offset += ZIP_CFILE_HDRSIZE(zip->map + dir->zip.offset); dir->tell++; } return ent; } else if (!IsWindows()) { if (dir->buf_pos >= dir->buf_end) { - basep = dir->tell; /* TODO(jart): what does xnu do */ + long basep = dir->tell; rc = sys_getdents(dir->fd, dir->buf, sizeof(dir->buf) - 256, &basep); STRACE("sys_getdents(%d) โ†’ %d% m", dir->fd, rc); if (!rc || rc == -1) return NULL; @@ -408,6 +413,7 @@ static struct dirent *readdir_impl(DIR *dir) { } else if (IsOpenbsd()) { obsd = (struct dirent_openbsd *)((char *)dir->buf + dir->buf_pos); dir->buf_pos += obsd->d_reclen; + dir->tell = obsd->d_off; ent = &dir->ent; ent->d_ino = obsd->d_fileno; ent->d_off = obsd->d_off; @@ -419,7 +425,7 @@ static struct dirent *readdir_impl(DIR *dir) { dir->buf_pos += nbsd->d_reclen; ent = &dir->ent; ent->d_ino = nbsd->d_fileno; - ent->d_off = dir->tell++; + ent->d_off = (dir->tell += nbsd->d_reclen); ent->d_reclen = nbsd->d_reclen; ent->d_type = nbsd->d_type; memcpy(ent->d_name, nbsd->d_name, MAX(256, nbsd->d_namlen + 1)); @@ -428,7 +434,7 @@ static struct dirent *readdir_impl(DIR *dir) { dir->buf_pos += bsd->d_reclen; ent = &dir->ent; ent->d_ino = bsd->d_fileno; - ent->d_off = IsXnu() ? (dir->tell = basep) : dir->tell++; + ent->d_off = dir->tell++; ent->d_reclen = bsd->d_reclen; ent->d_type = bsd->d_type; memcpy(ent->d_name, bsd->d_name, bsd->d_namlen + 1); @@ -450,9 +456,14 @@ static struct dirent *readdir_impl(DIR *dir) { */ struct dirent *readdir(DIR *dir) { struct dirent *e; - _lockdir(dir); - e = readdir_impl(dir); - _unlockdir(dir); + if (dir) { + _lockdir(dir); + e = readdir_impl(dir); + _unlockdir(dir); + } else { + efault(); + e = 0; + } return e; } @@ -496,20 +507,18 @@ errno_t readdir_r(DIR *dir, struct dirent *output, struct dirent **result) { * @return 0 on success or -1 w/ errno */ int closedir(DIR *dir) { - int rc; + int rc = 0; if (dir) { - if (dir->iszip) { - free(dir->zip.prefix); - rc = 0; - } else if (!IsWindows()) { - rc = close(dir->fd); - } else { - free(dir->name); - rc = FindClose(dir->fd) ? 0 : __winerr(); + if (dir->fd != -1) { + rc |= close(dir->fd); + } + free(dir->name); + if (IsWindows() && !dir->iszip) { + if (!FindClose(dir->hand)) { + rc = __winerr(); + } } free(dir); - } else { - rc = 0; } return rc; } @@ -533,13 +542,7 @@ long telldir(DIR *dir) { int dirfd(DIR *dir) { int rc; _lockdir(dir); - if (dir->iszip) { - rc = eopnotsupp(); - } else if (IsWindows()) { - rc = eopnotsupp(); - } else { - rc = dir->fd; - } + rc = dir->fd; _unlockdir(dir); return rc; } @@ -559,8 +562,8 @@ void rewinddir(DIR *dir) { dir->tell = 0; } } else { - FindClose(dir->fd); - if ((dir->fd = FindFirstFile(dir->name, &dir->windata)) != -1) { + FindClose(dir->hand); + if ((dir->hand = FindFirstFile(dir->name, &dir->windata)) != -1) { dir->isdone = false; dir->tell = 0; } else { @@ -582,11 +585,27 @@ void seekdir(DIR *dir, long off) { if (dir->iszip) { dir->zip.offset = GetZipCdirOffset(_weaken(__zipos_get)()->cdir); for (i = 0; i < off && i < dir->zip.records; ++i) { - dir->zip.offset += ZIP_CFILE_HDRSIZE(zip->map + dir->zip.offset); + if (i >= 2) { + dir->zip.offset += ZIP_CFILE_HDRSIZE(zip->map + dir->zip.offset); + } } - } else { + } else if (!IsWindows()) { i = lseek(dir->fd, off, SEEK_SET); dir->buf_pos = dir->buf_end = 0; + } else { + i = 0; + dir->isdone = false; + FindClose(dir->hand); + if ((dir->hand = FindFirstFile(dir->name, &dir->windata)) != -1) { + for (; i < off; ++i) { + if (!FindNextFile(dir->hand, &dir->windata)) { + dir->isdone = true; + break; + } + } + } else { + dir->isdone = true; + } } dir->tell = i; _unlockdir(dir); diff --git a/libc/sysv/consts.sh b/libc/sysv/consts.sh index 6b7df79cd..2a0fd647f 100755 --- a/libc/sysv/consts.sh +++ b/libc/sysv/consts.sh @@ -957,18 +957,6 @@ syscon pf PF_VSOCK 40 40 0 0 0 0 0 0 syscon pf PF_WANPIPE 25 25 0 0 0 0 0 0 syscon pf PF_X25 9 9 0 0 0 0 0 0 -# getdents() constants -# -# group name GNU/Systemd GNU/Systemd (Aarch64) XNU's Not UNIX! MacOS (Arm64) FreeBSD OpenBSD NetBSD The New Technology Commentary -syscon dt DT_UNKNOWN 0 0 0 0 0 0 0 0 # consensus -syscon dt DT_FIFO 1 1 1 1 1 1 1 1 # unix consensus & faked nt -syscon dt DT_CHR 2 2 2 2 2 2 2 2 # unix consensus & faked nt -syscon dt DT_DIR 4 4 4 4 4 4 4 4 # unix consensus & faked nt -syscon dt DT_BLK 6 6 6 6 6 6 6 6 # unix consensus & faked nt -syscon dt DT_REG 8 8 8 8 8 8 8 8 # unix consensus & faked nt -syscon dt DT_LNK 10 10 10 10 10 10 10 10 # unix consensus & faked nt -syscon dt DT_SOCK 12 12 12 12 12 12 12 12 # unix consensus & faked nt - # msync() flags # # group name GNU/Systemd GNU/Systemd (Aarch64) XNU's Not UNIX! MacOS (Arm64) FreeBSD OpenBSD NetBSD The New Technology Commentary diff --git a/libc/sysv/consts/DT_BLK.S b/libc/sysv/consts/DT_BLK.S deleted file mode 100644 index 55c779ba8..000000000 --- a/libc/sysv/consts/DT_BLK.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon dt,DT_BLK,6,6,6,6,6,6,6,6 diff --git a/libc/sysv/consts/DT_CHR.S b/libc/sysv/consts/DT_CHR.S deleted file mode 100644 index d1bf974bd..000000000 --- a/libc/sysv/consts/DT_CHR.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon dt,DT_CHR,2,2,2,2,2,2,2,2 diff --git a/libc/sysv/consts/DT_DIR.S b/libc/sysv/consts/DT_DIR.S deleted file mode 100644 index 29464fd2a..000000000 --- a/libc/sysv/consts/DT_DIR.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon dt,DT_DIR,4,4,4,4,4,4,4,4 diff --git a/libc/sysv/consts/DT_FIFO.S b/libc/sysv/consts/DT_FIFO.S deleted file mode 100644 index aea615599..000000000 --- a/libc/sysv/consts/DT_FIFO.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon dt,DT_FIFO,1,1,1,1,1,1,1,1 diff --git a/libc/sysv/consts/DT_LNK.S b/libc/sysv/consts/DT_LNK.S deleted file mode 100644 index 4e4b06b3c..000000000 --- a/libc/sysv/consts/DT_LNK.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon dt,DT_LNK,10,10,10,10,10,10,10,10 diff --git a/libc/sysv/consts/DT_REG.S b/libc/sysv/consts/DT_REG.S deleted file mode 100644 index e95a3f0cf..000000000 --- a/libc/sysv/consts/DT_REG.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon dt,DT_REG,8,8,8,8,8,8,8,8 diff --git a/libc/sysv/consts/DT_SOCK.S b/libc/sysv/consts/DT_SOCK.S deleted file mode 100644 index ff4792032..000000000 --- a/libc/sysv/consts/DT_SOCK.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon dt,DT_SOCK,12,12,12,12,12,12,12,12 diff --git a/libc/sysv/consts/DT_UNKNOWN.S b/libc/sysv/consts/DT_UNKNOWN.S deleted file mode 100644 index da50d9482..000000000 --- a/libc/sysv/consts/DT_UNKNOWN.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon dt,DT_UNKNOWN,0,0,0,0,0,0,0,0 diff --git a/libc/sysv/consts/dt.h b/libc/sysv/consts/dt.h index 4e1b7e7ff..bf3a8b96b 100644 --- a/libc/sysv/consts/dt.h +++ b/libc/sysv/consts/dt.h @@ -1,19 +1,5 @@ #ifndef COSMOPOLITAN_LIBC_SYSV_CONSTS_DT_H_ #define COSMOPOLITAN_LIBC_SYSV_CONSTS_DT_H_ -#if !(__ASSEMBLER__ + __LINKER__ + 0) -COSMOPOLITAN_C_START_ - -extern const uint8_t DT_UNKNOWN; -extern const uint8_t DT_FIFO; -extern const uint8_t DT_CHR; -extern const uint8_t DT_DIR; -extern const uint8_t DT_BLK; -extern const uint8_t DT_REG; -extern const uint8_t DT_LNK; -extern const uint8_t DT_SOCK; - -COSMOPOLITAN_C_END_ -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #define DT_UNKNOWN 0 #define DT_FIFO 1 diff --git a/libc/zipos/zipos.internal.h b/libc/zipos/zipos.internal.h deleted file mode 100755 index e69de29bb..000000000 diff --git a/test/libc/calls/access_test.c b/test/libc/calls/access_test.c index fe110c374..86fc4038f 100644 --- a/test/libc/calls/access_test.c +++ b/test/libc/calls/access_test.c @@ -35,7 +35,6 @@ void SetUpOnce(void) { } TEST(access, efault) { - ASSERT_SYS(EFAULT, -1, access(0, F_OK)); if (IsWindows() || !IsAsan()) return; // not possible ASSERT_SYS(EFAULT, -1, access((void *)77, F_OK)); } diff --git a/test/libc/calls/read_test.c b/test/libc/calls/read_test.c index 006002b0f..f147352f1 100644 --- a/test/libc/calls/read_test.c +++ b/test/libc/calls/read_test.c @@ -71,6 +71,15 @@ TEST(read_pipe, canBeInterruptedByAlarm) { close(fds[0]); } +TEST(read_directory, eisdir) { + // TODO(jart): what + if (IsWindows() || IsFreebsd()) return; + ASSERT_SYS(0, 0, mkdir("boop", 0755)); + ASSERT_SYS(0, 3, open("boop", O_RDONLY | O_DIRECTORY)); + ASSERT_SYS(EISDIR, -1, read(3, 0, 0)); + ASSERT_SYS(0, 0, close(3)); +} + //////////////////////////////////////////////////////////////////////////////// BENCH(read, bench) { diff --git a/test/libc/calls/stat_test.c b/test/libc/calls/stat_test.c index 92722924c..af38ebdc7 100644 --- a/test/libc/calls/stat_test.c +++ b/test/libc/calls/stat_test.c @@ -21,6 +21,7 @@ #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/calls/struct/metastat.internal.h" +#include "libc/calls/struct/stat.h" #include "libc/dce.h" #include "libc/errno.h" #include "libc/mem/gc.internal.h" @@ -47,13 +48,15 @@ TEST(stat_010, testEmptyFile_sizeIsZero) { } TEST(stat, enoent) { - ASSERT_SYS(ENOENT, -1, stat("hi", 0)); - ASSERT_SYS(ENOENT, -1, stat("o/doesnotexist", 0)); + struct stat st; + ASSERT_SYS(ENOENT, -1, stat("hi", &st)); + ASSERT_SYS(ENOENT, -1, stat("o/doesnotexist", &st)); } TEST(stat, enotdir) { + struct stat st; ASSERT_SYS(0, 0, close(creat("yo", 0644))); - ASSERT_SYS(ENOTDIR, -1, stat("yo/there", 0)); + ASSERT_SYS(ENOTDIR, -1, stat("yo/there", &st)); } TEST(stat, zipos) { diff --git a/test/libc/runtime/zipos_test.c b/test/libc/runtime/zipos_test.c index e8d49e873..3d8d475d9 100644 --- a/test/libc/runtime/zipos_test.c +++ b/test/libc/runtime/zipos_test.c @@ -17,9 +17,12 @@ โ”‚ PERFORMANCE OF THIS SOFTWARE. โ”‚ โ•šโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€*/ #include "libc/calls/calls.h" +#include "libc/errno.h" +#include "libc/limits.h" #include "libc/mem/gc.h" #include "libc/mem/mem.h" #include "libc/runtime/runtime.h" +#include "libc/runtime/zipos.internal.h" #include "libc/str/str.h" #include "libc/sysv/consts/o.h" #include "libc/testlib/hyperion.h" @@ -53,3 +56,39 @@ TEST(zipos, test) { for (i = 0; i < n; ++i) EXPECT_SYS(0, 0, _join(t + i)); __print_maps(); } + +TEST(zipos, normpath) { + { + char s[] = ""; + __zipos_normpath(s); + ASSERT_STREQ("", s); + } + { + char s[] = "usr/"; + __zipos_normpath(s); + ASSERT_STREQ("usr", s); + } + { + char s[] = "usr/./"; + __zipos_normpath(s); + ASSERT_STREQ("usr", s); + } +} + +#if 0 +TEST(zipos_O_DIRECTORY, blocksOpeningOfNormalFiles) { + ASSERT_SYS(ENOTDIR, -1, + open("/zip/libc/testlib/hyperion.txt", O_RDONLY | O_DIRECTORY)); +} +#endif + +TEST(zipos, readPastEof) { + char buf[512]; + ASSERT_SYS(0, 3, open("/zip/libc/testlib/hyperion.txt", O_RDONLY)); + EXPECT_SYS(EINVAL, -1, pread(3, buf, 512, UINT64_MAX)); + EXPECT_SYS(0, 0, pread(3, buf, 512, INT64_MAX)); + EXPECT_SYS(EINVAL, -1, lseek(3, UINT64_MAX, SEEK_SET)); + EXPECT_SYS(0, INT64_MAX, lseek(3, INT64_MAX, SEEK_SET)); + EXPECT_SYS(0, 0, read(3, buf, 512)); + EXPECT_SYS(0, 0, close(3)); +} diff --git a/test/libc/stdio/dirstream_test.c b/test/libc/stdio/dirstream_test.c index cf4909779..82730e5d6 100644 --- a/test/libc/stdio/dirstream_test.c +++ b/test/libc/stdio/dirstream_test.c @@ -18,13 +18,18 @@ โ•šโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€*/ #include "libc/calls/calls.h" #include "libc/calls/struct/dirent.h" +#include "libc/calls/struct/stat.h" #include "libc/dce.h" #include "libc/errno.h" +#include "libc/intrin/kprintf.h" #include "libc/mem/gc.h" +#include "libc/mem/gc.internal.h" #include "libc/runtime/runtime.h" #include "libc/stdio/rand.h" #include "libc/str/str.h" #include "libc/sysv/consts/dt.h" +#include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/s.h" #include "libc/testlib/testlib.h" #include "libc/x/xasprintf.h" #include "libc/x/xiso8601.h" @@ -61,6 +66,10 @@ TEST(opendir, enotdir) { TEST(opendir, zipTest_fake) { ASSERT_NE(NULL, (dir = opendir("/zip"))); EXPECT_NE(NULL, (ent = readdir(dir))); + EXPECT_STREQ(".", ent->d_name); + EXPECT_NE(NULL, (ent = readdir(dir))); + EXPECT_STREQ("..", ent->d_name); + EXPECT_NE(NULL, (ent = readdir(dir))); EXPECT_STREQ("echo.com", ent->d_name); EXPECT_NE(NULL, (ent = readdir(dir))); EXPECT_STREQ("usr", ent->d_name); @@ -70,6 +79,10 @@ TEST(opendir, zipTest_fake) { EXPECT_EQ(0, closedir(dir)); ASSERT_NE(NULL, (dir = opendir("/zip/"))); EXPECT_NE(NULL, (ent = readdir(dir))); + EXPECT_STREQ(".", ent->d_name); + EXPECT_NE(NULL, (ent = readdir(dir))); + EXPECT_STREQ("..", ent->d_name); + EXPECT_NE(NULL, (ent = readdir(dir))); EXPECT_STREQ("echo.com", ent->d_name); EXPECT_NE(NULL, (ent = readdir(dir))); EXPECT_STREQ("usr", ent->d_name); @@ -79,11 +92,19 @@ TEST(opendir, zipTest_fake) { EXPECT_EQ(0, closedir(dir)); ASSERT_NE(NULL, (dir = opendir("/zip/usr"))); EXPECT_NE(NULL, (ent = readdir(dir))); + EXPECT_STREQ(".", ent->d_name); + EXPECT_NE(NULL, (ent = readdir(dir))); + EXPECT_STREQ("..", ent->d_name); + EXPECT_NE(NULL, (ent = readdir(dir))); EXPECT_STREQ("share", ent->d_name); EXPECT_EQ(NULL, (ent = readdir(dir))); EXPECT_EQ(0, closedir(dir)); ASSERT_NE(NULL, (dir = opendir("/zip/usr/"))); EXPECT_NE(NULL, (ent = readdir(dir))); + EXPECT_STREQ(".", ent->d_name); + EXPECT_NE(NULL, (ent = readdir(dir))); + EXPECT_STREQ("..", ent->d_name); + EXPECT_NE(NULL, (ent = readdir(dir))); EXPECT_STREQ("share", ent->d_name); EXPECT_EQ(NULL, (ent = readdir(dir))); EXPECT_EQ(0, closedir(dir)); @@ -91,6 +112,28 @@ TEST(opendir, zipTest_fake) { EXPECT_EQ(NULL, (dir = opendir("/zip/us/"))); } +TEST(opendir, openSyntheticDirEntry) { + struct stat st; + ASSERT_SYS(0, 3, open("/zip", O_RDONLY | O_DIRECTORY)); + ASSERT_SYS(0, 0, fstat(3, &st)); + ASSERT_TRUE(S_ISDIR(st.st_mode)); + EXPECT_SYS(EISDIR, -1, read(3, 0, 0)); + ASSERT_NE(NULL, (dir = fdopendir(3))); + EXPECT_NE(NULL, (ent = readdir(dir))); + EXPECT_EQ(0, closedir(dir)); +} + +TEST(opendir, openRealDirEntry) { + struct stat st; + ASSERT_SYS(0, 3, open("/zip/usr/share/zoneinfo", O_RDONLY | O_DIRECTORY)); + ASSERT_SYS(0, 0, fstat(3, &st)); + ASSERT_TRUE(S_ISDIR(st.st_mode)); + EXPECT_SYS(EISDIR, -1, read(3, 0, 0)); + ASSERT_NE(NULL, (dir = fdopendir(3))); + EXPECT_NE(NULL, (ent = readdir(dir))); + EXPECT_EQ(0, closedir(dir)); +} + TEST(dirstream, testDots) { int hasdot = 0; int hasdotdot = 0; @@ -115,9 +158,9 @@ TEST(dirstream, test) { bool hasfoo = false; bool hasbar = false; char *dpath, *file1, *file2; - dpath = _gc(xasprintf("%s.%d", "dirstream", rand())); - file1 = _gc(xasprintf("%s/%s", dpath, "foo")); - file2 = _gc(xasprintf("%s/%s", dpath, "bar")); + dpath = gc(xasprintf("%s.%d", "dirstream", rand())); + file1 = gc(xasprintf("%s/%s", dpath, "foo")); + file2 = gc(xasprintf("%s/%s", dpath, "bar")); EXPECT_NE(-1, mkdir(dpath, 0755)); EXPECT_NE(-1, touch(file1, 0644)); EXPECT_NE(-1, touch(file2, 0644)); @@ -143,12 +186,11 @@ TEST(dirstream, test) { TEST(dirstream, zipTest) { bool foundNewYork = false; const char *path = "/zip/usr/share/zoneinfo/"; - ASSERT_NE(0, _gc(xiso8601ts(NULL))); ASSERT_NE(NULL, (dir = opendir(path))); while ((ent = readdir(dir))) { foundNewYork |= !strcmp(ent->d_name, "New_York"); } - closedir(dir); + EXPECT_SYS(0, 0, closedir(dir)); EXPECT_TRUE(foundNewYork); } @@ -156,9 +198,9 @@ TEST(rewinddir, test) { bool hasfoo = false; bool hasbar = false; char *dpath, *file1, *file2; - dpath = _gc(xasprintf("%s.%d", "dirstream", rand())); - file1 = _gc(xasprintf("%s/%s", dpath, "foo")); - file2 = _gc(xasprintf("%s/%s", dpath, "bar")); + dpath = gc(xasprintf("%s.%d", "dirstream", rand())); + file1 = gc(xasprintf("%s/%s", dpath, "foo")); + file2 = gc(xasprintf("%s/%s", dpath, "bar")); EXPECT_NE(-1, mkdir(dpath, 0755)); EXPECT_NE(-1, touch(file1, 0644)); EXPECT_NE(-1, touch(file2, 0644)); @@ -183,3 +225,28 @@ TEST(dirstream, zipTest_notDir) { ASSERT_EQ(NULL, opendir("/zip/usr/share/zoneinfo/New_York")); ASSERT_EQ(ENOTDIR, errno); } + +TEST(dirstream, seek) { + if (IsNetbsd()) return; // omg + ASSERT_SYS(0, 0, mkdir("boop", 0755)); + EXPECT_SYS(0, 0, touch("boop/a", 0644)); + EXPECT_SYS(0, 0, touch("boop/b", 0644)); + EXPECT_SYS(0, 0, touch("boop/c", 0644)); + ASSERT_NE(NULL, (dir = opendir("boop"))); + ASSERT_NE(NULL, (ent = readdir(dir))); // #1 + ASSERT_NE(NULL, (ent = readdir(dir))); // #2 + long pos = telldir(dir); + ASSERT_NE(NULL, (ent = readdir(dir))); // #3 + char name[32]; + strlcpy(name, ent->d_name, sizeof(name)); + ASSERT_NE(NULL, (ent = readdir(dir))); // #4 + ASSERT_NE(NULL, (ent = readdir(dir))); // #5 + ASSERT_EQ(NULL, (ent = readdir(dir))); // eod + seekdir(dir, pos); + ASSERT_NE(NULL, (ent = readdir(dir))); // #2 + ASSERT_STREQ(name, ent->d_name); + ASSERT_NE(NULL, (ent = readdir(dir))); // #3 + ASSERT_NE(NULL, (ent = readdir(dir))); // #4 + ASSERT_EQ(NULL, (ent = readdir(dir))); // eod + ASSERT_SYS(0, 0, closedir(dir)); +} diff --git a/test/libc/stdio/zipdir_test.c b/test/libc/stdio/zipdir_test.c new file mode 100644 index 000000000..786bef995 --- /dev/null +++ b/test/libc/stdio/zipdir_test.c @@ -0,0 +1,69 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-โ”‚ +โ”‚vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :viโ”‚ +โ•žโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ก +โ”‚ Copyright 2023 Justine Alexandra Roberts Tunney โ”‚ +โ”‚ โ”‚ +โ”‚ Permission to use, copy, modify, and/or distribute this software for โ”‚ +โ”‚ any purpose with or without fee is hereby granted, provided that the โ”‚ +โ”‚ above copyright notice and this permission notice appear in all copies. โ”‚ +โ”‚ โ”‚ +โ”‚ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL โ”‚ +โ”‚ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED โ”‚ +โ”‚ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE โ”‚ +โ”‚ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL โ”‚ +โ”‚ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR โ”‚ +โ”‚ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER โ”‚ +โ”‚ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR โ”‚ +โ”‚ PERFORMANCE OF THIS SOFTWARE. โ”‚ +โ•šโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€*/ +#include "libc/calls/struct/dirent.h" +#include "libc/mem/gc.internal.h" +#include "libc/runtime/runtime.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/dt.h" +#include "libc/testlib/testlib.h" + +__static_yoink("zipos"); +__static_yoink("libc/testlib/hyperion.txt"); +__static_yoink("libc/testlib/moby.txt"); +__static_yoink("usr/share/zoneinfo/New_York"); + +DIR *dir; +struct dirent *ent; + +TEST(zipdir, test) { + const char *path = "/zip/libc/testlib///"; + ASSERT_NE(NULL, (dir = opendir(path))); + ASSERT_EQ(0, telldir(dir)); + ASSERT_NE(NULL, (ent = readdir(dir))); + ASSERT_EQ(0, strcmp(ent->d_name, ".")); + ASSERT_EQ(DT_DIR, ent->d_type); + ASSERT_NE(NULL, (ent = readdir(dir))); + ASSERT_EQ(0, strcmp(ent->d_name, "..")); + ASSERT_EQ(DT_DIR, ent->d_type); + ASSERT_NE(NULL, (ent = readdir(dir))); + ASSERT_EQ(0, strcmp(ent->d_name, "hyperion.txt")); + ASSERT_EQ(DT_REG, ent->d_type); + long pos = telldir(dir); + ASSERT_NE(NULL, (ent = readdir(dir))); + ASSERT_EQ(0, strcmp(ent->d_name, "moby.txt")); + ASSERT_EQ(DT_REG, ent->d_type); + ASSERT_EQ(NULL, (ent = readdir(dir))); + seekdir(dir, pos); + ASSERT_NE(NULL, (ent = readdir(dir))); + ASSERT_EQ(0, strcmp(ent->d_name, "moby.txt")); + ASSERT_EQ(DT_REG, ent->d_type); + ASSERT_EQ(NULL, (ent = readdir(dir))); + ASSERT_SYS(0, 0, closedir(dir)); +} + +TEST(dirstream, hasDirectoryEntry) { + bool gotsome = false; + const char *path = "/zip/usr/share/zoneinfo"; + ASSERT_NE(NULL, (dir = opendir(path))); + while ((ent = readdir(dir))) { + gotsome = true; + } + ASSERT_SYS(0, 0, closedir(dir)); + EXPECT_TRUE(gotsome); +} diff --git a/third_party/unzip/process.c b/third_party/unzip/process.c index 5f1a78e98..40a8fccb2 100644 --- a/third_party/unzip/process.c +++ b/third_party/unzip/process.c @@ -1330,7 +1330,7 @@ static int find_ecrec64(__G__ searchlen) /* return PK-class error */ if (memcmp((char *)byterec, end_central64_sig, 4) ) { /* Zip64 EOCD Record not found */ /* Since we already have seen the Zip64 EOCD Locator, it's - possible we got here because there are bytes prepended + possible we got h-ere because there are bytes prepended to the archive, like the sfx prefix. */ /* Make a guess as to where the Zip64 EOCD Record might be */ diff --git a/tool/build/lib/elfwriter_zip.c b/tool/build/lib/elfwriter_zip.c index a1692b0c9..a2298339d 100644 --- a/tool/build/lib/elfwriter_zip.c +++ b/tool/build/lib/elfwriter_zip.c @@ -22,8 +22,11 @@ #include "libc/limits.h" #include "libc/log/check.h" #include "libc/mem/gc.h" +#include "libc/mem/gc.internal.h" +#include "libc/mem/mem.h" #include "libc/nexgen32e/crc32.h" #include "libc/nt/enum/fileflagandattributes.h" +#include "libc/runtime/zipos.internal.h" #include "libc/stdio/rand.h" #include "libc/str/str.h" #include "libc/sysv/consts/s.h" @@ -130,7 +133,7 @@ static void EmitZipCdirHdr(unsigned char *p, const void *name, size_t namesize, /** * Embeds zip file in elf object. */ -void elfwriter_zip(struct ElfWriter *elf, const char *symbol, const char *name, +void elfwriter_zip(struct ElfWriter *elf, const char *symbol, const char *cname, size_t namesize, const void *data, size_t size, uint32_t mode, struct timespec mtim, struct timespec atim, struct timespec ctim, bool nocompress) { @@ -144,6 +147,13 @@ void elfwriter_zip(struct ElfWriter *elf, const char *symbol, const char *name, CHECK_NE(0, mtim.tv_sec); + char *name = gc(strndup(cname, namesize)); + namesize = __zipos_normpath(name); + if (S_ISDIR(mode) && namesize && name[namesize - 1] != '/') { + name[namesize++] = '/'; + name[namesize] = 0; + } + gflags = 0; iattrs = 0; compsize = size;