Rewrite ZipOS

This reduces the virtual memory usage of Emacs for me by 30%. We now
have a simpler implementation that uses read(), rather mmap()ing the
whole executable.
This commit is contained in:
Justine Tunney 2023-10-03 07:27:25 -07:00
parent ff77f2a6af
commit b01282e23e
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
21 changed files with 408 additions and 421 deletions

View file

@ -12,6 +12,7 @@
#include "dsp/tty/tty.h" #include "dsp/tty/tty.h"
#include "libc/assert.h" #include "libc/assert.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/struct/dirent.h"
#include "libc/calls/struct/itimerval.h" #include "libc/calls/struct/itimerval.h"
#include "libc/calls/struct/winsize.h" #include "libc/calls/struct/winsize.h"
#include "libc/calls/termios.h" #include "libc/calls/termios.h"
@ -28,7 +29,6 @@
#include "libc/mem/arraylist2.internal.h" #include "libc/mem/arraylist2.internal.h"
#include "libc/mem/mem.h" #include "libc/mem/mem.h"
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
#include "libc/runtime/zipos.internal.h"
#include "libc/sock/sock.h" #include "libc/sock/sock.h"
#include "libc/sock/struct/pollfd.h" #include "libc/sock/struct/pollfd.h"
#include "libc/stdio/stdio.h" #include "libc/stdio/stdio.h"
@ -1808,19 +1808,12 @@ void GetOpts(int argc, char* argv[]) {
} }
size_t FindZipGames(void) { size_t FindZipGames(void) {
char* name; DIR* dir;
size_t i, cf; if ((dir = opendir("/zip/usr/share/rom"))) {
struct Zipos* zipos; struct dirent* ent;
if ((zipos = __zipos_get())) { while ((ent = readdir(dir))) {
for (i = 0, cf = ZIP_CDIR_OFFSET(zipos->cdir); if (endswith(ent->d_name, ".nes")) {
i < ZIP_CDIR_RECORDS(zipos->cdir); char* name = xasprintf("/zip/usr/share/rom/%s", ent->d_name);
++i, cf += ZIP_CFILE_HDRSIZE(zipos->map + cf)) {
if (ZIP_CFILE_NAMESIZE(zipos->map + cf) > 4 &&
!memcmp((ZIP_CFILE_NAME(zipos->map + cf) +
ZIP_CFILE_NAMESIZE(zipos->map + cf) - 4),
".nes", 4) &&
(name = xasprintf("/zip/%.*s", ZIP_CFILE_NAMESIZE(zipos->map + cf),
ZIP_CFILE_NAME(zipos->map + cf)))) {
APPEND(&zipgames_.p, &zipgames_.i, &zipgames_.n, &name); APPEND(&zipgames_.p, &zipgames_.i, &zipgames_.n, &name);
} }
} }

View file

@ -28,9 +28,14 @@
#include "libc/sysv/consts/rlim.h" #include "libc/sysv/consts/rlim.h"
#include "libc/sysv/consts/rlimit.h" #include "libc/sysv/consts/rlimit.h"
// Hack for guessing boundaries of _start()'s stack // Hack for guessing the unknowable boundaries of _start()'s stack
// //
// Every UNIX system in our support vector creates arg blocks like: // This code always guesses correctly on Windows because WinMain()
// is written to allocate a stack ourself. Local testing on Linux,
// XNU, FreeBSD, OpenBSD, and NetBSD says that accuracy is ±1 page
// and that error rate applies to both beginning and end addresses
//
// Every UNIX system in our support vector creates arg blocks like
// //
// <HIGHEST-STACK-ADDRESS> // <HIGHEST-STACK-ADDRESS>
// last environ string // last environ string
@ -55,11 +60,6 @@
// up to the microprocessor page size (this computes the top addr) // up to the microprocessor page size (this computes the top addr)
// and the bottom is computed by subtracting RLIMIT_STACK rlim_cur // and the bottom is computed by subtracting RLIMIT_STACK rlim_cur
// It's simple but gets tricky if we consider environ can be empty // It's simple but gets tricky if we consider environ can be empty
//
// This code always guesses correctly on Windows because WinMain()
// is written to allocate a stack ourself. Local testing on Linux,
// XNU, FreeBSD, OpenBSD, and NetBSD says that accuracy is ±1 page
// and that error rate applies to both beginning and end addresses
static char *__get_last(char **list) { static char *__get_last(char **list) {
char *res = 0; char *res = 0;

View file

@ -135,7 +135,6 @@ __msabi extern typeof(WriteFile) *const __imp_WriteFile;
// clang-format on // clang-format on
long __klog_handle; long __klog_handle;
extern struct SymbolTable *__symtab;
__funline char *kadvance(char *p, char *e, long n) { __funline char *kadvance(char *p, char *e, long n) {
intptr_t t = (intptr_t)p; intptr_t t = (intptr_t)p;
@ -755,11 +754,11 @@ privileged static size_t kformat(char *b, size_t n, const char *fmt,
// can be manually consulted to look up the faulting code. // can be manually consulted to look up the faulting code.
int idx; int idx;
x = va_arg(va, intptr_t); x = va_arg(va, intptr_t);
if (_weaken(__symtab) && *_weaken(__symtab) && if (_weaken(__symtab) && _weaken(__symtab)->st &&
(idx = _weaken(__get_symbol)(0, x)) != -1) { (idx = _weaken(__get_symbol)(0, x)) != -1) {
if (p + 1 <= e) *p++ = '&'; if (p + 1 <= e) *p++ = '&';
s = (*_weaken(__symtab))->name_base + s = _weaken(__symtab)->st->name_base +
(*_weaken(__symtab))->names[idx]; _weaken(__symtab)->st->names[idx];
goto FormatString; goto FormatString;
} }
base = 4; base = 4;

View file

@ -19,8 +19,6 @@
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
#include "libc/runtime/symbols.internal.h" #include "libc/runtime/symbols.internal.h"
extern struct SymbolTable *__symtab;
/** /**
* Returns low index into symbol table for address. * Returns low index into symbol table for address.
* *
@ -33,8 +31,8 @@ privileged int __get_symbol(struct SymbolTable *t, intptr_t a) {
// we don't want function tracing because: // we don't want function tracing because:
// function tracing depends on this function via kprintf // function tracing depends on this function via kprintf
unsigned l, m, r, n, k; unsigned l, m, r, n, k;
if (!t && __symtab) { if (!t && __symtab.st) {
t = __symtab; t = __symtab.st;
} }
if (t) { if (t) {
l = 0; l = 0;

View file

@ -16,127 +16,89 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/assert.h" #include "libc/atomic.h"
#include "libc/intrin/bits.h" #include "libc/calls/calls.h"
#include "libc/cosmo.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/promises.internal.h" #include "libc/intrin/promises.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/macros.internal.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
#include "libc/runtime/symbols.internal.h" #include "libc/runtime/symbols.internal.h"
#include "libc/runtime/zipos.internal.h" #include "libc/sysv/consts/map.h"
#include "libc/str/str.h" #include "libc/sysv/consts/o.h"
#include "libc/thread/thread.h" #include "libc/sysv/consts/prot.h"
#include "libc/x/x.h"
#include "libc/zip.internal.h"
#include "third_party/puff/puff.h"
__static_yoink("__get_symbol"); __static_yoink("__get_symbol");
static pthread_spinlock_t g_lock; struct SymbolTableLoader __symtab;
struct SymbolTable *__symtab; // for kprintf
static ssize_t GetZipFile(struct Zipos *zipos, const char *name) { static struct SymbolTable *GetSymbolTableFromZip(void) {
size_t i, n, c, z; int fd;
z = strlen(name);
c = GetZipCdirOffset(zipos->cdir);
n = GetZipCdirRecords(zipos->cdir);
for (i = 0; i < n; ++i, c += ZIP_CFILE_HDRSIZE(zipos->map + c)) {
if (ZIP_CFILE_NAMESIZE(zipos->map + c) == z &&
!memcmp(ZIP_CFILE_NAME(zipos->map + c), name, z)) {
return c;
}
}
return -1;
}
/**
* Reads symbol table from zip directory.
* @note This code can't depend on dlmalloc()
*/
static struct SymbolTable *GetSymbolTableFromZip(struct Zipos *zipos) {
ssize_t cf, lf;
size_t size, size2;
struct SymbolTable *res = 0; struct SymbolTable *res = 0;
if ((cf = GetZipFile(zipos, ".symtab." _ARCH_NAME)) != -1 || if ((fd = open("/zip/.symtab." _ARCH_NAME, O_RDONLY)) != -1 ||
(cf = GetZipFile(zipos, ".symtab")) != -1) { (fd = open("/zip/.symtab", O_RDONLY)) != -1) {
lf = GetZipCfileOffset(zipos->map + cf); void *map;
size = GetZipLfileUncompressedSize(zipos->map + lf); ssize_t size;
size2 = ROUNDUP(size, FRAMESIZE); if ((size = lseek(fd, 0, SEEK_END)) != -1 &&
if ((res = _mapanon(size2))) { (map = mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0)) !=
switch (ZIP_LFILE_COMPRESSIONMETHOD(zipos->map + lf)) { MAP_FAILED) {
case kZipCompressionNone: res = map;
memcpy(res, (void *)ZIP_LFILE_CONTENT(zipos->map + lf), size);
break;
case kZipCompressionDeflate:
if (__inflate((void *)res, size,
(void *)ZIP_LFILE_CONTENT(zipos->map + lf),
GetZipLfileCompressedSize(zipos->map + lf))) {
munmap(res, size2);
res = 0;
}
break;
default:
munmap(res, size2);
res = 0;
break;
}
} }
close(fd);
} }
STRACE("GetSymbolTableFromZip() → %p", res);
return res; return res;
} }
/**
* Reads symbol table from .com.dbg file.
* @note This code can't depend on dlmalloc()
*/
static struct SymbolTable *GetSymbolTableFromElf(void) { static struct SymbolTable *GetSymbolTableFromElf(void) {
const char *s; const char *path;
if (PLEDGED(RPATH) && (s = FindDebugBinary())) { if ((path = FindDebugBinary())) {
return OpenSymbolTable(s); return OpenSymbolTable(path);
} else { } else {
return 0; return 0;
} }
} }
static void GetSymbolTableInit(void) {
if (!PLEDGED(RPATH)) return;
int e = errno;
if ((__symtab.st = GetSymbolTableFromZip())) {
__symtab.st->names =
(uint32_t *)((char *)__symtab.st + __symtab.st->names_offset);
__symtab.st->name_base =
(char *)((char *)__symtab.st + __symtab.st->name_base_offset);
}
if (!__symtab.st) {
__symtab.st = GetSymbolTableFromElf();
}
errno = e;
}
/** /**
* Returns symbol table singleton. * Returns symbol table singleton.
* *
* This uses multiple strategies to find the symbol table. The first * This uses multiple strategies to find the symbol table. The first
* strategy, depends on whether or not the following is linked: * strategy, depends on whether or not the following is linked:
* *
* __static_yoink("__zipos_get"); * __static_yoink("zipos");
* *
* In that case, the symbol table may be read from `/zip/.symtab` which * In that case, the symbol table may be read from `/zip/.symtab.ARCH`
* is generated by `o//tool/build/symtab.com`. The second strategy is to * or `/zip/.symtab` which are generated by `o//tool/build/symtab.com`
* look for the concomitant `.com.dbg` executable, which may very well * or `o//tool/build/apelink.com`.
* be the one currently executing, or it could be placed in the same
* folder as your `.com` binary, or lastly, it could be explicitly
* specified via the `COMDBG` environment variable.
* *
* Function tracing is disabled throughout the duration of this call. * The second strategy is to look for the ELF executable for the current
* Backtraces and other core runtime functionality depend on this. * program. If you're running a .com binary, it'll look for the .com.dbg
* file. If it's running the .com.dbg or .elf file, then it'll just read
* the symbols from itself. In other cases, you can explicitly specify a
* debug symbol binary via the `COMDBG` environment variable.
*
* When using pledge() security, it's recommended that this function get
* called *before* calling pledge() if it lacks the rpath promise, so it
* can load the symbols into memory before the filesystem goes away.
* *
* @return symbol table, or NULL if not found * @return symbol table, or NULL if not found
*/ */
struct SymbolTable *GetSymbolTable(void) { struct SymbolTable *GetSymbolTable(void) {
struct Zipos *z; cosmo_once(&__symtab.once, GetSymbolTableInit);
if (pthread_spin_trylock(&g_lock)) return 0; return __symtab.st;
if (!__symtab && !__isworker) {
if (_weaken(__zipos_get) && (z = _weaken(__zipos_get)())) {
if ((__symtab = GetSymbolTableFromZip(z))) {
__symtab->names =
(uint32_t *)((char *)__symtab + __symtab->names_offset);
__symtab->name_base =
(char *)((char *)__symtab + __symtab->name_base_offset);
}
}
if (!__symtab) {
__symtab = GetSymbolTableFromElf();
}
}
pthread_spin_unlock(&g_lock);
return __symtab;
} }

View file

@ -26,6 +26,12 @@ struct SymbolTable {
struct Symbol symbols[]; /* sorted and non-overlapping intervals */ struct Symbol symbols[]; /* sorted and non-overlapping intervals */
}; };
struct SymbolTableLoader {
_Atomic(unsigned) once;
struct SymbolTable *st;
};
extern struct SymbolTableLoader __symtab;
struct SymbolTable *GetSymbolTable(void); struct SymbolTable *GetSymbolTable(void);
const char *FindComBinary(void); const char *FindComBinary(void);
const char *FindDebugBinary(void); const char *FindDebugBinary(void);

View file

@ -39,14 +39,14 @@ int __zipos_access(struct ZiposUri *name, int amode) {
return enoexec(); return enoexec();
} }
ssize_t cf; int cf;
if ((cf = __zipos_find(z, name)) == -1) { if ((cf = __zipos_find(z, name)) == -1) {
return -1; return -1;
} }
int mode; int mode;
if (cf != ZIPOS_SYNTHETIC_DIRECTORY) { if (cf != ZIPOS_SYNTHETIC_DIRECTORY) {
mode = GetZipCfileMode(z->map + cf); mode = GetZipCfileMode(z->cdir + cf);
} else { } else {
mode = S_IFDIR | 0555; mode = S_IFDIR | 0555;
} }

View file

@ -16,6 +16,7 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/calls/calls.h"
#include "libc/calls/internal.h" #include "libc/calls/internal.h"
#include "libc/calls/state.internal.h" #include "libc/calls/state.internal.h"
#include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/syscall-sysv.internal.h"
@ -30,16 +31,14 @@
* @vforksafe * @vforksafe
*/ */
int __zipos_close(int fd) { int __zipos_close(int fd) {
int rc; if (__vforked) {
struct ZiposHandle *h; sys_close(fd);
h = (struct ZiposHandle *)(intptr_t)g_fds.p[fd].handle; return 0;
if (!IsWindows()) {
rc = sys_close(fd);
} else {
rc = 0; // no system file descriptor needed on nt
}
if (!__vforked) {
__zipos_free(h);
} }
struct ZiposHandle *h = (struct ZiposHandle *)(intptr_t)g_fds.p[fd].handle;
g_fds.p[fd].handle = h->handle;
g_fds.p[fd].kind = kFdFile;
int rc = close(fd);
__zipos_free(h);
return rc; return rc;
} }

View file

@ -24,11 +24,11 @@
#include "libc/sysv/errfuns.h" #include "libc/sysv/errfuns.h"
#include "libc/zip.internal.h" #include "libc/zip.internal.h"
static ssize_t __zipos_match(struct Zipos *z, struct ZiposUri *name, int len, static int __zipos_match(struct Zipos *z, struct ZiposUri *name, int len,
int i) { int i) {
size_t cfile = z->index[i]; int cfile = z->index[i];
const char *zname = ZIP_CFILE_NAME(z->map + cfile); const char *zname = ZIP_CFILE_NAME(z->cdir + cfile);
int zsize = ZIP_CFILE_NAMESIZE(z->map + cfile); int zsize = ZIP_CFILE_NAMESIZE(z->cdir + cfile);
if ((len == zsize || (len + 1 == zsize && zname[len] == '/')) && if ((len == zsize || (len + 1 == zsize && zname[len] == '/')) &&
!memcmp(name->path, zname, len)) { !memcmp(name->path, zname, len)) {
return cfile; return cfile;
@ -40,7 +40,7 @@ static ssize_t __zipos_match(struct Zipos *z, struct ZiposUri *name, int len,
} }
} }
ssize_t __zipos_scan(struct Zipos *zipos, struct ZiposUri *name) { int __zipos_scan(struct Zipos *zipos, struct ZiposUri *name) {
// strip trailing slash from search name // strip trailing slash from search name
int len = name->len; int len = name->len;
@ -55,12 +55,12 @@ ssize_t __zipos_scan(struct Zipos *zipos, struct ZiposUri *name) {
// binary search for leftmost name in central directory // binary search for leftmost name in central directory
int l = 0; int l = 0;
int r = zipos->records; int r = zipos->cnt;
while (l < r) { while (l < r) {
int m = (l & r) + ((l ^ r) >> 1); // floor((a+b)/2) int m = (l & r) + ((l ^ r) >> 1); // floor((a+b)/2)
const char *xp = ZIP_CFILE_NAME(zipos->map + zipos->index[m]); const char *xp = ZIP_CFILE_NAME(zipos->cdir + zipos->index[m]);
const char *yp = name->path; const char *yp = name->path;
int xn = ZIP_CFILE_NAMESIZE(zipos->map + zipos->index[m]); int xn = ZIP_CFILE_NAMESIZE(zipos->cdir + zipos->index[m]);
int yn = len; int yn = len;
int n = MIN(xn, yn); int n = MIN(xn, yn);
int c; int c;
@ -78,25 +78,25 @@ ssize_t __zipos_scan(struct Zipos *zipos, struct ZiposUri *name) {
} }
} }
if (l < zipos->records) { if (l < zipos->cnt) {
int dx; int dx;
size_t cfile = zipos->index[l]; int cfile = zipos->index[l];
const char *zname = ZIP_CFILE_NAME(zipos->map + cfile); const char *zname = ZIP_CFILE_NAME(zipos->cdir + cfile);
int zsize = ZIP_CFILE_NAMESIZE(zipos->map + cfile); int zsize = ZIP_CFILE_NAMESIZE(zipos->cdir + cfile);
if (zsize > len && (dx = '/' - (zname[len] & 255))) { if (zsize > len && (dx = '/' - (zname[len] & 255))) {
// since the index is asciibetical, we need to specially handle // since the index is asciibetical, we need to specially handle
// the case where, when searching for a directory, regular files // the case where, when searching for a directory, regular files
// exist whose names share the same prefix as the directory name. // exist whose names share the same prefix as the directory name.
dx = dx > +1 ? +1 : dx; dx = dx > +1 ? +1 : dx;
dx = dx < -1 ? -1 : dx; dx = dx < -1 ? -1 : dx;
for (l += dx; 0 <= l && l < zipos->records; l += dx) { for (l += dx; 0 <= l && l < zipos->cnt; l += dx) {
ssize_t cf; int cf;
if ((cf = __zipos_match(zipos, name, len, l)) != -1) { if ((cf = __zipos_match(zipos, name, len, l)) != -1) {
return cf; return cf;
} }
cfile = zipos->index[l]; cfile = zipos->index[l];
zname = ZIP_CFILE_NAME(zipos->map + cfile); zname = ZIP_CFILE_NAME(zipos->cdir + cfile);
zsize = ZIP_CFILE_NAMESIZE(zipos->map + cfile); zsize = ZIP_CFILE_NAMESIZE(zipos->cdir + cfile);
if (zsize < len || (len && zname[len - 1] != name->path[len - 1])) { if (zsize < len || (len && zname[len - 1] != name->path[len - 1])) {
break; break;
} }
@ -112,8 +112,8 @@ ssize_t __zipos_scan(struct Zipos *zipos, struct ZiposUri *name) {
} }
// support code for open(), stat(), and access() // support code for open(), stat(), and access()
ssize_t __zipos_find(struct Zipos *zipos, struct ZiposUri *name) { int __zipos_find(struct Zipos *zipos, struct ZiposUri *name) {
ssize_t cf; int cf;
if ((cf = __zipos_scan(zipos, name)) == -1) { if ((cf = __zipos_scan(zipos, name)) == -1) {
// test if parent component exists that isn't a directory // test if parent component exists that isn't a directory
char *p; char *p;
@ -121,7 +121,7 @@ ssize_t __zipos_find(struct Zipos *zipos, struct ZiposUri *name) {
name->path[name->len = p - name->path] = 0; name->path[name->len = p - name->path] = 0;
if ((cf = __zipos_scan(zipos, name)) != -1 && if ((cf = __zipos_scan(zipos, name)) != -1 &&
cf != ZIPOS_SYNTHETIC_DIRECTORY && cf != ZIPOS_SYNTHETIC_DIRECTORY &&
!S_ISDIR(GetZipCfileMode(zipos->map + cf))) { !S_ISDIR(GetZipCfileMode(zipos->cdir + cf))) {
return enotdir(); return enotdir();
} }
} }
@ -130,7 +130,7 @@ ssize_t __zipos_find(struct Zipos *zipos, struct ZiposUri *name) {
// test if we're opening "foo/" and "foo" isn't a directory // test if we're opening "foo/" and "foo" isn't a directory
if (cf != ZIPOS_SYNTHETIC_DIRECTORY && // if (cf != ZIPOS_SYNTHETIC_DIRECTORY && //
name->len && name->path[name->len - 1] == '/' && name->len && name->path[name->len - 1] == '/' &&
!S_ISDIR(GetZipCfileMode(zipos->map + cf))) { !S_ISDIR(GetZipCfileMode(zipos->cdir + cf))) {
return enotdir(); return enotdir();
} }
return cf; return cf;

View file

@ -16,25 +16,17 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/atomic.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/metalfile.internal.h" #include "libc/calls/metalfile.internal.h"
#include "libc/calls/struct/stat.h" #include "libc/calls/struct/stat.h"
#include "libc/cosmo.h" #include "libc/cosmo.h"
#include "libc/fmt/conv.h"
#include "libc/intrin/cmpxchg.h"
#include "libc/intrin/promises.internal.h"
#include "libc/intrin/strace.internal.h" #include "libc/intrin/strace.internal.h"
#include "libc/macros.internal.h" #include "libc/limits.h"
#include "libc/mem/alg.h" #include "libc/mem/alg.h"
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
#include "libc/runtime/stack.h"
#include "libc/runtime/zipos.internal.h" #include "libc/runtime/zipos.internal.h"
#include "libc/sysv/consts/auxv.h"
#include "libc/sysv/consts/f.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/o.h" #include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/posix.h"
#include "libc/sysv/consts/prot.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "libc/zip.internal.h" #include "libc/zip.internal.h"
@ -42,118 +34,162 @@
__static_yoink(APE_COM_NAME); __static_yoink(APE_COM_NAME);
#endif #endif
struct ZiposPlanner {
uint8_t buf[kZipLookbehindBytes];
struct stat st;
};
static struct Zipos __zipos; static struct Zipos __zipos;
static atomic_uint __zipos_once;
static void __zipos_dismiss(uint8_t *map, const uint8_t *cdir, long pg) { static void __zipos_wipe(void) {
uint64_t i, n, c, ef, lf, mo, lo, hi; pthread_mutex_init(&__zipos.lock, 0);
// determine the byte range of zip file content (excluding central dir)
c = GetZipCdirOffset(cdir);
n = GetZipCdirRecords(cdir);
for (lo = c, hi = i = 0; i < n; ++i, c += ZIP_CFILE_HDRSIZE(map + c)) {
lf = GetZipCfileOffset(map + c);
if (lf < lo) lo = lf;
ef = lf + ZIP_LFILE_HDRSIZE(map + lf) + GetZipLfileCompressedSize(map + lf);
if (ef > hi) hi = ef;
}
// unmap the executable portion beneath the local files
mo = ROUNDDOWN(lo, FRAMESIZE);
if (mo) munmap(map, mo);
// this is supposed to reduce our rss usage but does it really?
lo = ROUNDDOWN(lo, pg);
hi = MIN(ROUNDUP(hi, pg), ROUNDDOWN(c, pg));
if (hi > lo) {
posix_madvise(map + lo, hi - lo, POSIX_MADV_DONTNEED);
}
} }
static int __zipos_compare_names(const void *a, const void *b, void *c) { void __zipos_lock(void) {
const size_t *x = (const size_t *)a; pthread_mutex_lock(&__zipos.lock);
const size_t *y = (const size_t *)b; }
struct Zipos *z = (struct Zipos *)c;
int xn = ZIP_CFILE_NAMESIZE(z->map + *x); void __zipos_unlock(void) {
int yn = ZIP_CFILE_NAMESIZE(z->map + *y); pthread_mutex_unlock(&__zipos.lock);
}
static int __zipos_compare(const void *a, const void *b, void *c) {
uint8_t *cdir = (uint8_t *)c;
const int *x = (const int *)a;
const int *y = (const int *)b;
int xn = ZIP_CFILE_NAMESIZE(cdir + *x);
int yn = ZIP_CFILE_NAMESIZE(cdir + *y);
int n = MIN(xn, yn); int n = MIN(xn, yn);
if (n) { if (n) {
int res = int res = memcmp(ZIP_CFILE_NAME(cdir + *x), ZIP_CFILE_NAME(cdir + *y), n);
memcmp(ZIP_CFILE_NAME(z->map + *x), ZIP_CFILE_NAME(z->map + *y), n);
if (res) return res; if (res) return res;
} }
return xn - yn; // xn and yn are 16-bit return xn - yn; // xn and yn are 16-bit
} }
// creates binary searchable array of file offsets to cdir records static dontinline int __zipos_plan(int fd, struct ZiposPlanner *p) {
static void __zipos_generate_index(struct Zipos *zipos) {
size_t c, i; // get file size and dev/inode
zipos->records = GetZipCdirRecords(zipos->cdir); // this might fail if a bad fd was passed via environment
zipos->index = _mapanon(zipos->records * sizeof(size_t)); if (fstat(fd, &p->st)) {
for (i = 0, c = GetZipCdirOffset(zipos->cdir); i < zipos->records; return kZipErrorOpenFailed;
++i, c += ZIP_CFILE_HDRSIZE(zipos->map + c)) {
zipos->index[i] = c;
} }
// read the last 64kb of file
// the zip file format magic can be anywhere in there
int amt;
int64_t off;
if (p->st.st_size <= kZipLookbehindBytes) {
off = 0;
amt = p->st.st_size;
} else {
off = p->st.st_size - kZipLookbehindBytes;
amt = p->st.st_size - off;
}
if (pread(fd, p->buf, amt, off) != amt) {
return kZipErrorReadFailed;
}
// search backwards for the end-of-central-directory record
// the eocd (cdir) says where the central directory (cfile) array is located
// we consistency check some legacy fields, to be extra sure that it is eocd
int cnt = 0;
for (int i = amt - MIN(kZipCdirHdrMinSize, kZipCdir64LocatorSize); i; --i) {
uint32_t magic = READ32LE(p->buf + i);
if (magic == kZipCdir64LocatorMagic && i + kZipCdir64LocatorSize <= amt &&
pread(fd, p->buf, kZipCdirHdrMinSize,
ZIP_LOCATE64_OFFSET(p->buf + i)) == kZipCdirHdrMinSize &&
READ32LE(p->buf) == kZipCdir64HdrMagic &&
ZIP_CDIR64_RECORDS(p->buf) == ZIP_CDIR64_RECORDSONDISK(p->buf) &&
ZIP_CDIR64_RECORDS(p->buf) && ZIP_CDIR64_SIZE(p->buf) <= INT_MAX) {
cnt = ZIP_CDIR64_RECORDS(p->buf);
off = ZIP_CDIR64_OFFSET(p->buf);
amt = ZIP_CDIR64_SIZE(p->buf);
break;
}
if (magic == kZipCdirHdrMagic && i + kZipCdirHdrMinSize <= amt &&
ZIP_CDIR_RECORDS(p->buf + i) == ZIP_CDIR_RECORDSONDISK(p->buf + i) &&
ZIP_CDIR_RECORDS(p->buf + i) && ZIP_CDIR_SIZE(p->buf + i) <= INT_MAX) {
cnt = ZIP_CDIR_RECORDS(p->buf + i);
off = ZIP_CDIR_OFFSET(p->buf + i);
amt = ZIP_CDIR_SIZE(p->buf + i);
break;
}
}
if (cnt <= 0) {
return kZipErrorEocdNotFound;
}
// we'll store the entire central directory in memory
// in addition to a file name index that's bisectable
void *memory;
int cdirsize = amt;
size_t indexsize = cnt * sizeof(int);
size_t memorysize = indexsize + cdirsize;
if (!(memory = _mapanon(memorysize))) {
return kZipErrorMapFailed;
}
int *index = memory;
uint8_t *cdir = (uint8_t *)memory + indexsize;
// read the central directory
if (pread(fd, cdir, cdirsize, off) != cdirsize) {
return kZipErrorReadFailed;
}
// generate our file name index
// smoothsort() isn't the fastest algorithm, but it guarantees // smoothsort() isn't the fastest algorithm, but it guarantees
// o(logn), won't smash the stack and doesn't depend on malloc // o(logn), won't smash the stack and doesn't depend on malloc
smoothsort_r(zipos->index, zipos->records, sizeof(size_t), int entry_index, entry_offset;
__zipos_compare_names, zipos); for (entry_index = entry_offset = 0;
entry_index < cnt && entry_offset + kZipCfileHdrMinSize <= cdirsize &&
entry_offset + ZIP_CFILE_HDRSIZE(cdir + entry_offset) <= cdirsize;
++entry_index, entry_offset += ZIP_CFILE_HDRSIZE(cdir + entry_offset)) {
index[entry_index] = entry_offset;
}
if (cnt != entry_index) {
return kZipErrorZipCorrupt;
}
smoothsort_r(index, cnt, sizeof(int), __zipos_compare, cdir);
// finally populate the global singleton
__zipos.cnt = cnt;
__zipos.cdir = cdir;
__zipos.index = index;
__zipos.dev = p->st.st_ino;
__zipos.cdirsize = cdirsize;
return kZipOk;
}
static dontinline int __zipos_setup(int fd) {
// allocates 64kb on the stack as scratch memory
// this should only be used from the main thread
#pragma GCC push_options
#pragma GCC diagnostic ignored "-Wframe-larger-than="
struct ZiposPlanner p;
CheckLargeStackAllocation(&p, sizeof(p));
#pragma GCC pop_options
return __zipos_plan(fd, &p);
} }
static void __zipos_init(void) { static void __zipos_init(void) {
char *endptr; int status;
const char *s; if (getenv("COSMOPOLITAN_DISABLE_ZIPOS")) return;
struct stat st; if (!(__zipos.progpath = getenv("COSMOPOLITAN_INIT_ZIPOS"))) {
int x, fd, err, msg; __zipos.progpath = GetProgramExecutableName();
uint8_t *map, *cdir;
const char *progpath;
if (!(s = getenv("COSMOPOLITAN_DISABLE_ZIPOS"))) {
// this environment variable may be a filename or file descriptor
if ((progpath = getenv("COSMOPOLITAN_INIT_ZIPOS")) &&
(x = strtol(progpath, &endptr, 10)) >= 0 && !*endptr) {
fd = x;
} else {
fd = -1;
}
if (fd != -1 || PLEDGED(RPATH)) {
if (fd == -1) {
if (!progpath) {
progpath = GetProgramExecutableName();
}
fd = open(progpath, O_RDONLY);
}
if (fd != -1) {
if (!fstat(fd, &st) && (map = mmap(0, st.st_size, PROT_READ, MAP_SHARED,
fd, 0)) != MAP_FAILED) {
if ((cdir = GetZipEocd(map, st.st_size, &err))) {
long pagesz = getauxval(AT_PAGESZ);
__zipos_dismiss(map, cdir, pagesz);
__zipos.map = map;
__zipos.cdir = cdir;
__zipos.dev = st.st_ino;
__zipos.pagesz = pagesz;
__zipos_generate_index(&__zipos);
msg = kZipOk;
} else {
munmap(map, st.st_size);
msg = !cdir ? err : kZipErrorRaceCondition;
}
} else {
msg = kZipErrorMapFailed;
}
close(fd);
} else {
msg = kZipErrorOpenFailed;
}
} else {
msg = -666;
}
} else {
progpath = 0;
msg = -777;
} }
(void)msg; int fd = open(__zipos.progpath, O_RDONLY);
STRACE("__zipos_get(%#s) → %d% m", progpath, msg); if (fd != -1) {
if (!(status = __zipos_setup(fd))) {
__zipos_wipe();
pthread_atfork(__zipos_lock, __zipos_unlock, __zipos_wipe);
}
close(fd);
} else {
status = kZipErrorOpenFailed;
}
(void)status;
STRACE("__zipos_init(%#s) → %d% m", __zipos.progpath, status);
} }
/** /**
@ -161,6 +197,6 @@ static void __zipos_init(void) {
* @asyncsignalsafe * @asyncsignalsafe
*/ */
struct Zipos *__zipos_get(void) { struct Zipos *__zipos_get(void) {
cosmo_once(&__zipos_once, __zipos_init); cosmo_once(&__zipos.once, __zipos_init);
return __zipos.cdir ? &__zipos : 0; return __zipos.cnt ? &__zipos : 0;
} }

View file

@ -33,7 +33,6 @@ static uint64_t __zipos_fnv(const char *s, int len) {
uint64_t __zipos_inode(struct Zipos *zipos, int64_t cfile, // uint64_t __zipos_inode(struct Zipos *zipos, int64_t cfile, //
const void *name, size_t namelen) { const void *name, size_t namelen) {
unassert(cfile >= 0);
if (cfile == ZIPOS_SYNTHETIC_DIRECTORY) { if (cfile == ZIPOS_SYNTHETIC_DIRECTORY) {
if (namelen && ((char *)name)[namelen - 1] == '/') --namelen; if (namelen && ((char *)name)[namelen - 1] == '/') --namelen;
cfile = INT64_MIN | __zipos_fnv(name, namelen); cfile = INT64_MIN | __zipos_fnv(name, namelen);

View file

@ -50,7 +50,7 @@
* @return virtual base address of new mapping, or MAP_FAILED w/ errno * @return virtual base address of new mapping, or MAP_FAILED w/ errno
*/ */
void *__zipos_mmap(void *addr, size_t size, int prot, int flags, void *__zipos_mmap(void *addr, size_t size, int prot, int flags,
struct ZiposHandle *h, int64_t off) { struct ZiposHandle *h, int64_t off) {
if (off < 0) { if (off < 0) {
STRACE("negative zipos mmap offset"); STRACE("negative zipos mmap offset");
@ -58,7 +58,7 @@ void *__zipos_mmap(void *addr, size_t size, int prot, int flags,
} }
if (h->cfile == ZIPOS_SYNTHETIC_DIRECTORY || if (h->cfile == ZIPOS_SYNTHETIC_DIRECTORY ||
S_ISDIR(GetZipCfileMode(h->zipos->map + h->cfile))) { S_ISDIR(GetZipCfileMode(h->zipos->cdir + h->cfile))) {
return VIP(eisdir()); return VIP(eisdir());
} }

View file

@ -16,68 +16,56 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/assert.h"
#include "libc/calls/blocksigs.internal.h" #include "libc/calls/blocksigs.internal.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/internal.h" #include "libc/calls/internal.h"
#include "libc/calls/state.internal.h" #include "libc/calls/struct/fd.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/errno.h"
#include "libc/intrin/asan.internal.h" #include "libc/intrin/asan.internal.h"
#include "libc/intrin/asancodes.h"
#include "libc/intrin/atomic.h" #include "libc/intrin/atomic.h"
#include "libc/intrin/cmpxchg.h" #include "libc/intrin/cmpxchg.h"
#include "libc/intrin/directmap.internal.h"
#include "libc/intrin/extend.internal.h" #include "libc/intrin/extend.internal.h"
#include "libc/intrin/strace.internal.h" #include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h" #include "libc/intrin/weaken.h"
#include "libc/runtime/internal.h" #include "libc/runtime/internal.h"
#include "libc/runtime/memtrack.internal.h" #include "libc/runtime/memtrack.internal.h"
#include "libc/runtime/zipos.internal.h" #include "libc/runtime/zipos.internal.h"
#include "libc/sysv/consts/f.h" #include "libc/str/str.h"
#include "libc/sysv/consts/fd.h" #include "libc/sysv/consts/at.h"
#include "libc/sysv/consts/map.h" #include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/o.h" #include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/prot.h"
#include "libc/sysv/consts/s.h" #include "libc/sysv/consts/s.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h" #include "libc/sysv/errfuns.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "libc/thread/tls.h"
#include "libc/zip.internal.h" #include "libc/zip.internal.h"
#include "third_party/zlib/zconf.h"
#include "third_party/zlib/zlib.h"
static char *__zipos_mapend; static bool __zipos_method_is_supported(int method) {
static size_t __zipos_maptotal; switch (method) {
static pthread_mutex_t __zipos_lock_obj; case kZipCompressionNone:
case kZipCompressionDeflate:
static void __zipos_wipe(void) { return true;
pthread_mutex_init(&__zipos_lock_obj, 0); default:
return false;
}
} }
static void __zipos_lock(void) { static void *__zipos_mmap_space(struct Zipos *zipos, size_t mapsize) {
pthread_mutex_lock(&__zipos_lock_obj);
}
static void __zipos_unlock(void) {
pthread_mutex_unlock(&__zipos_lock_obj);
}
static void *__zipos_mmap_space(size_t mapsize) {
char *start; char *start;
size_t offset; size_t offset;
unassert(mapsize); offset = zipos->maptotal;
offset = __zipos_maptotal; zipos->maptotal += mapsize;
__zipos_maptotal += mapsize;
start = (char *)kMemtrackZiposStart; start = (char *)kMemtrackZiposStart;
if (!__zipos_mapend) __zipos_mapend = start; if (!zipos->mapend) zipos->mapend = start;
__zipos_mapend = _extend(start, __zipos_maptotal, __zipos_mapend, MAP_PRIVATE, zipos->mapend = _extend(start, zipos->maptotal, zipos->mapend, MAP_PRIVATE,
kMemtrackZiposStart + kMemtrackZiposSize); kMemtrackZiposStart + kMemtrackZiposSize);
return start + offset; return start + offset;
} }
void __zipos_free(struct ZiposHandle *h) { void __zipos_free(struct ZiposHandle *h) {
if (!h) return;
if (IsAsan()) { if (IsAsan()) {
__asan_poison((char *)h + sizeof(struct ZiposHandle), __asan_poison((char *)h + sizeof(struct ZiposHandle),
h->mapsize - sizeof(struct ZiposHandle), kAsanHeapFree); h->mapsize - sizeof(struct ZiposHandle), kAsanHeapFree);
@ -105,7 +93,7 @@ StartOver:
ph = &h->next; ph = &h->next;
} }
if (!h) { if (!h) {
h = __zipos_mmap_space(mapsize); h = __zipos_mmap_space(zipos, mapsize);
} }
__zipos_unlock(); __zipos_unlock();
if (IsAsan()) { if (IsAsan()) {
@ -122,91 +110,85 @@ StartOver:
return h; return h;
} }
static int __zipos_mkfd(int minfd) { static int __zipos_load_fd(struct Zipos *zipos, int cf, int fd,
int fd, e = errno; struct ZiposUri *name,
if ((fd = __sys_fcntl(2, F_DUPFD_CLOEXEC, minfd)) != -1) { struct ZiposHandle **out_handle) {
return fd; uint64_t size;
} else if (errno == EINVAL) {
errno = e;
return __fixupnewfd(__sys_fcntl(2, F_DUPFD, minfd), O_CLOEXEC);
} else {
return fd;
}
}
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].extra = 0;
__fds_unlock();
return fd;
}
static int __zipos_load(struct Zipos *zipos, size_t cf, int flags,
struct ZiposUri *name) {
size_t lf;
size_t size;
int fd, minfd;
struct ZiposHandle *h; struct ZiposHandle *h;
if (cf == ZIPOS_SYNTHETIC_DIRECTORY) { if (cf == ZIPOS_SYNTHETIC_DIRECTORY) {
size = name->len; size = name->len;
if (!(h = __zipos_alloc(zipos, size + 1))) return -1; if (!(h = __zipos_alloc(zipos, size + 1))) return -1;
if (size) memcpy(h->data, name->path, size); if (size) memcpy(h->data, name->path, size);
h->data[size] = 0; h->data[size] = 0;
h->mem = h->data;
} else { } else {
lf = GetZipCfileOffset(zipos->map + cf); uint8_t lfile[kZipLfileHdrMinSize];
npassert((ZIP_LFILE_MAGIC(zipos->map + lf) == kZipLfileHdrMagic)); uint64_t lf = GetZipCfileOffset(zipos->cdir + cf);
size = GetZipLfileUncompressedSize(zipos->map + lf); int compressed = ZIP_CFILE_COMPRESSIONMETHOD(zipos->cdir + cf);
switch (ZIP_LFILE_COMPRESSIONMETHOD(zipos->map + lf)) { if (!__zipos_method_is_supported(compressed) ||
case kZipCompressionNone: pread(fd, lfile, kZipLfileHdrMinSize, lf) != kZipLfileHdrMinSize ||
if (!(h = __zipos_alloc(zipos, 0))) return -1; ZIP_LFILE_MAGIC(lfile) != kZipLfileHdrMagic) {
h->mem = ZIP_LFILE_CONTENT(zipos->map + lf); return eio(); // this corruption
break; }
case kZipCompressionDeflate: size = GetZipCfileUncompressedSize(zipos->cdir + cf);
if (!(h = __zipos_alloc(zipos, size))) return -1; if (!(h = __zipos_alloc(zipos, size))) return -1;
if (!__inflate(h->data, size, ZIP_LFILE_CONTENT(zipos->map + lf), uint64_t off = lf + ZIP_LFILE_HDRSIZE(lfile);
GetZipLfileCompressedSize(zipos->map + lf))) { if (!compressed) {
h->mem = h->data; if (pread(fd, h->data, size, off) != size) {
} else { __zipos_free(h);
h->mem = 0;
eio();
}
break;
default:
return eio(); return eio();
}
} else {
struct ZiposHandle *h2;
uint64_t compsize = GetZipCfileCompressedSize(zipos->cdir + cf);
if (!(h2 = __zipos_alloc(zipos, compsize))) {
__zipos_free(h);
return -1;
}
if (pread(fd, h2->data, compsize, off) != compsize ||
__inflate(h->data, size, h2->data, compsize)) {
__zipos_free(h2);
__zipos_free(h);
return eio();
}
__zipos_free(h2);
} }
} }
h->pos = 0; h->pos = 0;
h->cfile = cf; h->cfile = cf;
h->size = size; h->size = size;
if (h->mem) { *out_handle = h;
minfd = 3; return 0;
__fds_lock(); }
TryAgain:
if (IsWindows() || IsMetal()) { static int __zipos_load(struct Zipos *zipos, int cf, int flags,
if ((fd = __reservefd_unlocked(-1)) != -1) { struct ZiposUri *name) {
return __zipos_setfd(fd, h, flags); int fd;
if ((fd = openat(AT_FDCWD, zipos->progpath,
O_RDONLY | O_CLOEXEC |
(flags & (O_NONBLOCK | O_RANDOM | O_SEQUENTIAL)),
0)) != -1) {
struct ZiposHandle *h = 0;
if (__zipos_load_fd(zipos, cf, fd, name, &h) != -1) {
if (!IsWindows() && !IsMetal()) {
// unix doesn't use the g_fds table by default, so we need to
// explicitly ensure an entry exists for our new open()d file
__ensurefds(fd);
} }
} else if ((fd = __zipos_mkfd(minfd)) != -1) { // turn it from a file fd to a zip fd
if (__ensurefds_unlocked(fd) != -1) { // layer the handle on windows so it can be restored on close
if (g_fds.p[fd].kind) { h->handle = g_fds.p[fd].handle;
sys_close(fd); g_fds.p[fd].kind = kFdZip;
minfd = fd + 1; g_fds.p[fd].handle = (intptr_t)h;
goto TryAgain; g_fds.p[fd].flags &= ~O_CLOEXEC;
} g_fds.p[fd].flags |= flags & O_CLOEXEC;
return __zipos_setfd(fd, h, flags); return fd;
} } else {
sys_close(fd); close(fd);
return -1;
} }
__fds_unlock(); } else {
return -1;
} }
__zipos_free(h);
return -1;
} }
/** /**
@ -231,6 +213,10 @@ int __zipos_open(struct ZiposUri *name, int flags) {
(flags & O_ACCMODE) != O_RDONLY) { (flags & O_ACCMODE) != O_RDONLY) {
return erofs(); return erofs();
} }
if ((flags & ~(O_CLOEXEC | O_NONBLOCK | O_NOFOLLOW | O_NOCTTY | O_DIRECTORY |
O_NOATIME | O_RANDOM | O_SEQUENTIAL))) {
return einval();
}
// get the zipos global singleton // get the zipos global singleton
struct Zipos *zipos; struct Zipos *zipos;
@ -242,15 +228,12 @@ int __zipos_open(struct ZiposUri *name, int flags) {
// majority of these calls will return ENOENT or ENOTDIR. we need to // majority of these calls will return ENOENT or ENOTDIR. we need to
// perform two extremely costly sigprocmask() calls below. thanks to // perform two extremely costly sigprocmask() calls below. thanks to
// zipos being a read-only filesystem, we can avoid it in many cases // zipos being a read-only filesystem, we can avoid it in many cases
ssize_t cf; int cf;
if ((cf = __zipos_find(zipos, name)) == -1) { if ((cf = __zipos_find(zipos, name)) == -1) {
return -1; return -1;
} }
if (flags & O_EXCL) {
return eexist();
}
if (cf != ZIPOS_SYNTHETIC_DIRECTORY) { if (cf != ZIPOS_SYNTHETIC_DIRECTORY) {
int mode = GetZipCfileMode(zipos->map + cf); int mode = GetZipCfileMode(zipos->cdir + cf);
if ((flags & O_DIRECTORY) && !S_ISDIR(mode)) { if ((flags & O_DIRECTORY) && !S_ISDIR(mode)) {
return enotdir(); return enotdir();
} }
@ -265,8 +248,3 @@ int __zipos_open(struct ZiposUri *name, int flags) {
ALLOW_SIGNALS; ALLOW_SIGNALS;
return rc; return rc;
} }
__attribute__((__constructor__)) static void __zipos_ctor(void) {
__zipos_wipe();
pthread_atfork(__zipos_lock, __zipos_unlock, __zipos_wipe);
}

View file

@ -29,7 +29,7 @@ static ssize_t __zipos_read_impl(struct ZiposHandle *h, const struct iovec *iov,
int i; int i;
int64_t b, x, y; int64_t b, x, y;
if (h->cfile == ZIPOS_SYNTHETIC_DIRECTORY || if (h->cfile == ZIPOS_SYNTHETIC_DIRECTORY ||
S_ISDIR(GetZipCfileMode(h->zipos->map + h->cfile))) { S_ISDIR(GetZipCfileMode(h->zipos->cdir + h->cfile))) {
return eisdir(); return eisdir();
} }
if (opt_offset == -1) { if (opt_offset == -1) {
@ -39,7 +39,7 @@ static ssize_t __zipos_read_impl(struct ZiposHandle *h, const struct iovec *iov,
} }
for (i = 0; i < iovlen && y < h->size; ++i, y += b) { for (i = 0; i < iovlen && y < h->size; ++i, y += b) {
b = MIN(iov[i].iov_len, h->size - y); b = MIN(iov[i].iov_len, h->size - y);
if (b) memcpy(iov[i].iov_base, h->mem + y, b); if (b) memcpy(iov[i].iov_base, h->data + y, b);
} }
if (opt_offset == -1) { if (opt_offset == -1) {
h->pos = y; h->pos = y;

View file

@ -25,8 +25,7 @@
#include "libc/sysv/consts/s.h" #include "libc/sysv/consts/s.h"
#include "libc/zip.internal.h" #include "libc/zip.internal.h"
int __zipos_stat_impl(struct Zipos *zipos, size_t cf, struct stat *st) { int __zipos_stat_impl(struct Zipos *zipos, int cf, struct stat *st) {
size_t lf;
bzero(st, sizeof(*st)); bzero(st, sizeof(*st));
st->st_nlink = 1; st->st_nlink = 1;
st->st_dev = zipos->dev; st->st_dev = zipos->dev;
@ -35,12 +34,11 @@ int __zipos_stat_impl(struct Zipos *zipos, size_t cf, struct stat *st) {
st->st_mode = S_IFDIR | (0555 & ~atomic_load_explicit( st->st_mode = S_IFDIR | (0555 & ~atomic_load_explicit(
&__umask, memory_order_acquire)); &__umask, memory_order_acquire));
} else { } else {
lf = GetZipCfileOffset(zipos->map + cf); st->st_mode = GetZipCfileMode(zipos->cdir + cf);
st->st_mode = GetZipCfileMode(zipos->map + cf); st->st_size = GetZipCfileUncompressedSize(zipos->cdir + cf);
st->st_size = GetZipLfileUncompressedSize(zipos->map + lf);
st->st_blocks = st->st_blocks =
roundup(GetZipLfileCompressedSize(zipos->map + lf), 512) / 512; roundup(GetZipCfileCompressedSize(zipos->cdir + cf), 512) / 512;
GetZipCfileTimestamps(zipos->map + cf, &st->st_mtim, &st->st_atim, GetZipCfileTimestamps(zipos->cdir + cf, &st->st_mtim, &st->st_atim,
&st->st_ctim, 0); &st->st_ctim, 0);
st->st_birthtim = st->st_ctim; st->st_birthtim = st->st_ctim;
} }

View file

@ -27,7 +27,7 @@
* @asyncsignalsafe * @asyncsignalsafe
*/ */
int __zipos_stat(struct ZiposUri *name, struct stat *st) { int __zipos_stat(struct ZiposUri *name, struct stat *st) {
ssize_t cf; int cf;
struct Zipos *zipos; struct Zipos *zipos;
if (!(zipos = __zipos_get())) return enoexec(); if (!(zipos = __zipos_get())) return enoexec();
if ((cf = __zipos_find(zipos, name)) == -1) return -1; if ((cf = __zipos_find(zipos, name)) == -1) return -1;

View file

@ -1,11 +1,12 @@
#ifndef COSMOPOLITAN_LIBC_ZIPOS_ZIPOS_H_ #ifndef COSMOPOLITAN_ZIPOS_H_
#define COSMOPOLITAN_LIBC_ZIPOS_ZIPOS_H_ #define COSMOPOLITAN_ZIPOS_H_
#include "libc/thread/thread.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0) #if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_ COSMOPOLITAN_C_START_
#define ZIPOS_PATH_MAX 1024 #define ZIPOS_PATH_MAX 1024
#define ZIPOS_SYNTHETIC_DIRECTORY 0 #define ZIPOS_SYNTHETIC_DIRECTORY -2
struct stat; struct stat;
struct iovec; struct iovec;
@ -19,37 +20,43 @@ struct ZiposUri {
struct ZiposHandle { struct ZiposHandle {
struct ZiposHandle *next; struct ZiposHandle *next;
struct Zipos *zipos; struct Zipos *zipos;
size_t size; int64_t handle; /* original upstream open()'d handle */
size_t mapsize; uint64_t size; /* accessible bytes stored at data[] */
size_t pos; uint64_t mapsize; /* the full byte size of this struct */
size_t cfile; uint64_t pos; /* the file position, relative start */
uint8_t *mem; int cfile; /* byte offset into zipos->cdir ents */
uint8_t data[]; uint8_t data[]; /* original file content or dir name */
}; };
struct Zipos { struct Zipos {
long pagesz; _Atomic(unsigned) once;
uint8_t *map; int cnt; /* element count, accessible via `index` */
uint8_t *cdir; int cdirsize; /* number of bytes accessible via `cdir` */
uint64_t dev; uint64_t dev; /* remembers st_dev, of zipos image file */
size_t *index; int *index; /* points to cdirsize+cnt*4 mmap'd bytes */
size_t records; uint8_t *cdir; /* points after index, in the above mmap */
const char *progpath;
struct ZiposHandle *freelist; struct ZiposHandle *freelist;
char *mapend;
size_t maptotal;
pthread_mutex_t lock;
}; };
int __zipos_close(int); int __zipos_close(int);
void __zipos_lock(void);
void __zipos_unlock(void);
void __zipos_free(struct ZiposHandle *); void __zipos_free(struct ZiposHandle *);
struct Zipos *__zipos_get(void) pureconst; struct Zipos *__zipos_get(void) pureconst;
size_t __zipos_normpath(char *, const char *, size_t); size_t __zipos_normpath(char *, const char *, size_t);
ssize_t __zipos_find(struct Zipos *, struct ZiposUri *); int __zipos_find(struct Zipos *, struct ZiposUri *);
ssize_t __zipos_scan(struct Zipos *, struct ZiposUri *); int __zipos_scan(struct Zipos *, struct ZiposUri *);
ssize_t __zipos_parseuri(const char *, struct ZiposUri *); ssize_t __zipos_parseuri(const char *, struct ZiposUri *);
uint64_t __zipos_inode(struct Zipos *, int64_t, const void *, size_t); uint64_t __zipos_inode(struct Zipos *, int64_t, const void *, size_t);
int __zipos_open(struct ZiposUri *, int); int __zipos_open(struct ZiposUri *, int);
int __zipos_access(struct ZiposUri *, int); int __zipos_access(struct ZiposUri *, int);
int __zipos_stat(struct ZiposUri *, struct stat *); int __zipos_stat(struct ZiposUri *, struct stat *);
int __zipos_fstat(struct ZiposHandle *, struct stat *); int __zipos_fstat(struct ZiposHandle *, struct stat *);
int __zipos_stat_impl(struct Zipos *, size_t, struct stat *); int __zipos_stat_impl(struct Zipos *, int, struct stat *);
ssize_t __zipos_read(struct ZiposHandle *, const struct iovec *, size_t, ssize_t __zipos_read(struct ZiposHandle *, const struct iovec *, size_t,
ssize_t); ssize_t);
int64_t __zipos_seek(struct ZiposHandle *, int64_t, unsigned); int64_t __zipos_seek(struct ZiposHandle *, int64_t, unsigned);
@ -60,4 +67,4 @@ void *__zipos_mmap(void *, uint64_t, int32_t, int32_t, struct ZiposHandle *,
COSMOPOLITAN_C_END_ COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_ZIPOS_ZIPOS_H_ */ #endif /* COSMOPOLITAN_ZIPOS_H_ */

View file

@ -77,8 +77,8 @@ struct dirstream {
struct { struct {
struct Zipos *zipos; struct Zipos *zipos;
uint64_t inode; uint64_t inode;
uint64_t offset; int offset;
uint64_t records; int records;
struct ZiposUri prefix; struct ZiposUri prefix;
struct critbit0 found; struct critbit0 found;
} zip; } zip;
@ -298,7 +298,7 @@ DIR *fdopendir(int fd) {
// ensure open /zip/... file is a directory // ensure open /zip/... file is a directory
struct ZiposHandle *h = (struct ZiposHandle *)(intptr_t)g_fds.p[fd].handle; struct ZiposHandle *h = (struct ZiposHandle *)(intptr_t)g_fds.p[fd].handle;
if (h->cfile != ZIPOS_SYNTHETIC_DIRECTORY && if (h->cfile != ZIPOS_SYNTHETIC_DIRECTORY &&
!S_ISDIR(GetZipCfileMode(h->zipos->map + h->cfile))) { !S_ISDIR(GetZipCfileMode(h->zipos->cdir + h->cfile))) {
free(dir); free(dir);
enotdir(); enotdir();
return 0; return 0;
@ -308,8 +308,8 @@ DIR *fdopendir(int fd) {
size_t len; size_t len;
const char *name; const char *name;
if (h->cfile != ZIPOS_SYNTHETIC_DIRECTORY) { if (h->cfile != ZIPOS_SYNTHETIC_DIRECTORY) {
len = ZIP_CFILE_NAMESIZE(h->zipos->map + h->cfile); len = ZIP_CFILE_NAMESIZE(h->zipos->cdir + h->cfile);
name = ZIP_CFILE_NAME(h->zipos->map + h->cfile); name = ZIP_CFILE_NAME(h->zipos->cdir + h->cfile);
} else { } else {
len = h->size; len = h->size;
name = (const char *)h->data; name = (const char *)h->data;
@ -328,8 +328,8 @@ DIR *fdopendir(int fd) {
// setup state values for directory iterator // setup state values for directory iterator
dir->zip.zipos = h->zipos; dir->zip.zipos = h->zipos;
dir->zip.offset = GetZipCdirOffset(h->zipos->cdir); dir->zip.offset = 0;
dir->zip.records = GetZipCdirRecords(h->zipos->cdir); dir->zip.records = h->zipos->cnt;
dir->zip.inode = __zipos_inode(h->zipos, h->cfile, dir->zip.prefix.path, dir->zip.inode = __zipos_inode(h->zipos, h->cfile, dir->zip.prefix.path,
dir->zip.prefix.len); dir->zip.prefix.len);
@ -397,8 +397,8 @@ static struct dirent *readdir_zipos(DIR *dir) {
ent->d_ino = __zipos_inode( ent->d_ino = __zipos_inode(
dir->zip.zipos, __zipos_scan(dir->zip.zipos, &p), p.path, p.len); dir->zip.zipos, __zipos_scan(dir->zip.zipos, &p), p.path, p.len);
} else { } else {
const char *s = ZIP_CFILE_NAME(dir->zip.zipos->map + dir->zip.offset); const char *s = ZIP_CFILE_NAME(dir->zip.zipos->cdir + dir->zip.offset);
size_t n = ZIP_CFILE_NAMESIZE(dir->zip.zipos->map + dir->zip.offset); size_t n = ZIP_CFILE_NAMESIZE(dir->zip.zipos->cdir + dir->zip.offset);
if (n > dir->zip.prefix.len && if (n > dir->zip.prefix.len &&
!memcmp(dir->zip.prefix.path, s, dir->zip.prefix.len)) { !memcmp(dir->zip.prefix.path, s, dir->zip.prefix.len)) {
s += dir->zip.prefix.len; s += dir->zip.prefix.len;
@ -408,7 +408,7 @@ static struct dirent *readdir_zipos(DIR *dir) {
if (p) { if (p) {
n = p - s; n = p - s;
d_type = DT_DIR; d_type = DT_DIR;
} else if (S_ISDIR(GetZipCfileMode(dir->zip.zipos->map + } else if (S_ISDIR(GetZipCfileMode(dir->zip.zipos->cdir +
dir->zip.offset))) { dir->zip.offset))) {
d_type = DT_DIR; d_type = DT_DIR;
} else { } else {
@ -425,7 +425,7 @@ static struct dirent *readdir_zipos(DIR *dir) {
} }
} }
dir->zip.offset += dir->zip.offset +=
ZIP_CFILE_HDRSIZE(dir->zip.zipos->map + dir->zip.offset); ZIP_CFILE_HDRSIZE(dir->zip.zipos->cdir + dir->zip.offset);
} }
dir->tell++; dir->tell++;
} }
@ -611,7 +611,7 @@ void rewinddir(DIR *dir) {
if (dir->iszip) { if (dir->iszip) {
critbit0_clear(&dir->zip.found); critbit0_clear(&dir->zip.found);
dir->tell = 0; dir->tell = 0;
dir->zip.offset = GetZipCdirOffset(dir->zip.zipos->cdir); dir->zip.offset = 0;
} else if (!IsWindows()) { } else if (!IsWindows()) {
if (!lseek(dir->fd, 0, SEEK_SET)) { if (!lseek(dir->fd, 0, SEEK_SET)) {
dir->buf_pos = dir->buf_end = 0; dir->buf_pos = dir->buf_end = 0;
@ -637,7 +637,7 @@ void seekdir(DIR *dir, long tell) {
if (dir->iszip) { if (dir->iszip) {
critbit0_clear(&dir->zip.found); critbit0_clear(&dir->zip.found);
dir->tell = 0; dir->tell = 0;
dir->zip.offset = GetZipCdirOffset(dir->zip.zipos->cdir); dir->zip.offset = 0;
while (dir->tell < tell) { while (dir->tell < tell) {
if (!readdir_zipos(dir)) { if (!readdir_zipos(dir)) {
break; break;

View file

@ -36,6 +36,8 @@
#define kZipErrorRaceCondition _ZE(-12) #define kZipErrorRaceCondition _ZE(-12)
#define kZipErrorMapFailed _ZE(-13) #define kZipErrorMapFailed _ZE(-13)
#define kZipErrorOpenFailed _ZE(-14) #define kZipErrorOpenFailed _ZE(-14)
#define kZipErrorReadFailed _ZE(-15)
#define kZipErrorZipCorrupt _ZE(-16)
#define kZipCosmopolitanVersion kZipEra2001 #define kZipCosmopolitanVersion kZipEra2001
@ -70,6 +72,8 @@
#define kZipCompressionNone 0 #define kZipCompressionNone 0
#define kZipCompressionDeflate 8 #define kZipCompressionDeflate 8
#define kZipLookbehindBytes 65536
#define kZipCdirHdrMagic ZM_(0x06054b50) /* PK♣♠ "PK\5\6" */ #define kZipCdirHdrMagic ZM_(0x06054b50) /* PK♣♠ "PK\5\6" */
#define kZipCdirHdrMagicTodo ZM_(0x19184b50) /* PK♣♠ "PK\30\31" */ #define kZipCdirHdrMagicTodo ZM_(0x19184b50) /* PK♣♠ "PK\30\31" */
#define kZipCdirHdrMinSize 22 #define kZipCdirHdrMinSize 22

View file

@ -139,9 +139,9 @@ TEST(unveil, canBeUsedAgainAfterVfork) {
TEST(unveil, rwc_createExecutableFile_isAllowedButCantBeRun) { TEST(unveil, rwc_createExecutableFile_isAllowedButCantBeRun) {
SPAWN(fork); SPAWN(fork);
ASSERT_SYS(0, 0, mkdir("folder", 0755)); ASSERT_SYS(0, 0, mkdir("folder", 0755));
testlib_extract("/zip/life.elf", "folder/life.elf", 0755);
ASSERT_SYS(0, 0, unveil("folder", "rwc")); ASSERT_SYS(0, 0, unveil("folder", "rwc"));
ASSERT_SYS(0, 0, unveil(0, 0)); ASSERT_SYS(0, 0, unveil(0, 0));
testlib_extract("/zip/life.elf", "folder/life.elf", 0755);
SPAWN(fork); SPAWN(fork);
ASSERT_SYS(0, 0, stat("folder/life.elf", &st)); ASSERT_SYS(0, 0, stat("folder/life.elf", &st));
ASSERT_SYS(EACCES, -1, execl("folder/life.elf", "folder/life.elf", 0)); ASSERT_SYS(EACCES, -1, execl("folder/life.elf", "folder/life.elf", 0));
@ -152,9 +152,9 @@ TEST(unveil, rwc_createExecutableFile_isAllowedButCantBeRun) {
TEST(unveil, rwcx_createExecutableFile_canAlsoBeRun) { TEST(unveil, rwcx_createExecutableFile_canAlsoBeRun) {
SPAWN(fork); SPAWN(fork);
ASSERT_SYS(0, 0, mkdir("folder", 0755)); ASSERT_SYS(0, 0, mkdir("folder", 0755));
testlib_extract("/zip/life.elf", "folder/life.elf", 0755);
ASSERT_SYS(0, 0, unveil("folder", "rwcx")); ASSERT_SYS(0, 0, unveil("folder", "rwcx"));
ASSERT_SYS(0, 0, unveil(0, 0)); ASSERT_SYS(0, 0, unveil(0, 0));
testlib_extract("/zip/life.elf", "folder/life.elf", 0755);
SPAWN(fork); SPAWN(fork);
ASSERT_SYS(0, 0, stat("folder/life.elf", &st)); ASSERT_SYS(0, 0, stat("folder/life.elf", &st));
execl("folder/life.elf", "folder/life.elf", 0); execl("folder/life.elf", "folder/life.elf", 0);

View file

@ -52,7 +52,7 @@ void *Worker(void *arg) {
} }
TEST(zipos, test) { TEST(zipos, test) {
int i, n = 16; int i, n = 20;
pthread_t *t = gc(malloc(sizeof(pthread_t) * n)); pthread_t *t = gc(malloc(sizeof(pthread_t) * n));
for (i = 0; i < n; ++i) { for (i = 0; i < n; ++i) {
ASSERT_SYS(0, 0, pthread_create(t + i, 0, Worker, 0)); ASSERT_SYS(0, 0, pthread_create(t + i, 0, Worker, 0));
@ -86,6 +86,14 @@ TEST(zipos, readPastEof) {
EXPECT_SYS(0, 0, close(3)); EXPECT_SYS(0, 0, close(3));
} }
TEST(zipos, simple) {
char buf[31] = {0};
ASSERT_SYS(0, 3, open("/zip/libc/testlib/hyperion.txt", O_RDONLY));
ASSERT_SYS(0, 30, read(3, buf, 30));
ASSERT_STREQ("The fall of Hyperion - a Dream", buf);
ASSERT_SYS(0, 0, close(3));
}
TEST(zipos_O_DIRECTORY, blocksOpeningOfNormalFiles) { TEST(zipos_O_DIRECTORY, blocksOpeningOfNormalFiles) {
ASSERT_SYS(ENOTDIR, -1, ASSERT_SYS(ENOTDIR, -1,
open("/zip/libc/testlib/hyperion.txt", O_RDONLY | O_DIRECTORY)); open("/zip/libc/testlib/hyperion.txt", O_RDONLY | O_DIRECTORY));