mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-03-05 08:26:29 +00:00
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.
250 lines
8.5 KiB
C
250 lines
8.5 KiB
C
/*-*- 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 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 │
|
||
│ 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/blocksigs.internal.h"
|
||
#include "libc/calls/calls.h"
|
||
#include "libc/calls/internal.h"
|
||
#include "libc/calls/struct/fd.internal.h"
|
||
#include "libc/errno.h"
|
||
#include "libc/intrin/asan.internal.h"
|
||
#include "libc/intrin/asancodes.h"
|
||
#include "libc/intrin/atomic.h"
|
||
#include "libc/intrin/cmpxchg.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/str/str.h"
|
||
#include "libc/sysv/consts/at.h"
|
||
#include "libc/sysv/consts/map.h"
|
||
#include "libc/sysv/consts/o.h"
|
||
#include "libc/sysv/consts/s.h"
|
||
#include "libc/sysv/errfuns.h"
|
||
#include "libc/thread/thread.h"
|
||
#include "libc/zip.internal.h"
|
||
#include "third_party/zlib/zconf.h"
|
||
#include "third_party/zlib/zlib.h"
|
||
|
||
static bool __zipos_method_is_supported(int method) {
|
||
switch (method) {
|
||
case kZipCompressionNone:
|
||
case kZipCompressionDeflate:
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
static void *__zipos_mmap_space(struct Zipos *zipos, size_t mapsize) {
|
||
char *start;
|
||
size_t offset;
|
||
offset = zipos->maptotal;
|
||
zipos->maptotal += mapsize;
|
||
start = (char *)kMemtrackZiposStart;
|
||
if (!zipos->mapend) zipos->mapend = start;
|
||
zipos->mapend = _extend(start, zipos->maptotal, zipos->mapend, MAP_PRIVATE,
|
||
kMemtrackZiposStart + kMemtrackZiposSize);
|
||
return start + offset;
|
||
}
|
||
|
||
void __zipos_free(struct ZiposHandle *h) {
|
||
if (!h) return;
|
||
if (IsAsan()) {
|
||
__asan_poison((char *)h + sizeof(struct ZiposHandle),
|
||
h->mapsize - sizeof(struct ZiposHandle), kAsanHeapFree);
|
||
}
|
||
__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;
|
||
__zipos_lock();
|
||
mapsize = sizeof(struct ZiposHandle) + size;
|
||
mapsize = ROUNDUP(mapsize, 4096);
|
||
StartOver:
|
||
ph = &zipos->freelist;
|
||
while ((h = *ph)) {
|
||
if (h->mapsize >= mapsize) {
|
||
if (!_cmpxchg(ph, h, h->next)) goto StartOver;
|
||
h->next = 0;
|
||
break;
|
||
}
|
||
ph = &h->next;
|
||
}
|
||
if (!h) {
|
||
h = __zipos_mmap_space(zipos, mapsize);
|
||
}
|
||
__zipos_unlock();
|
||
if (IsAsan()) {
|
||
__asan_unpoison((char *)h, sizeof(struct ZiposHandle) + size);
|
||
__asan_poison((char *)h + sizeof(struct ZiposHandle) + size,
|
||
mapsize - (sizeof(struct ZiposHandle) + size),
|
||
kAsanHeapOverrun);
|
||
}
|
||
if (h) {
|
||
h->size = size;
|
||
h->zipos = zipos;
|
||
h->mapsize = mapsize;
|
||
}
|
||
return h;
|
||
}
|
||
|
||
static int __zipos_load_fd(struct Zipos *zipos, int cf, int fd,
|
||
struct ZiposUri *name,
|
||
struct ZiposHandle **out_handle) {
|
||
uint64_t size;
|
||
struct ZiposHandle *h;
|
||
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;
|
||
} else {
|
||
uint8_t lfile[kZipLfileHdrMinSize];
|
||
uint64_t lf = GetZipCfileOffset(zipos->cdir + cf);
|
||
int compressed = ZIP_CFILE_COMPRESSIONMETHOD(zipos->cdir + cf);
|
||
if (!__zipos_method_is_supported(compressed) ||
|
||
pread(fd, lfile, kZipLfileHdrMinSize, lf) != kZipLfileHdrMinSize ||
|
||
ZIP_LFILE_MAGIC(lfile) != kZipLfileHdrMagic) {
|
||
return eio(); // this corruption
|
||
}
|
||
size = GetZipCfileUncompressedSize(zipos->cdir + cf);
|
||
if (!(h = __zipos_alloc(zipos, size))) return -1;
|
||
uint64_t off = lf + ZIP_LFILE_HDRSIZE(lfile);
|
||
if (!compressed) {
|
||
if (pread(fd, h->data, size, off) != size) {
|
||
__zipos_free(h);
|
||
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->cfile = cf;
|
||
h->size = size;
|
||
*out_handle = h;
|
||
return 0;
|
||
}
|
||
|
||
static int __zipos_load(struct Zipos *zipos, int cf, int flags,
|
||
struct ZiposUri *name) {
|
||
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);
|
||
}
|
||
// turn it from a file fd to a zip fd
|
||
// layer the handle on windows so it can be restored on close
|
||
h->handle = g_fds.p[fd].handle;
|
||
g_fds.p[fd].kind = kFdZip;
|
||
g_fds.p[fd].handle = (intptr_t)h;
|
||
g_fds.p[fd].flags &= ~O_CLOEXEC;
|
||
g_fds.p[fd].flags |= flags & O_CLOEXEC;
|
||
return fd;
|
||
} else {
|
||
close(fd);
|
||
return -1;
|
||
}
|
||
} else {
|
||
return -1;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Loads compressed file from αcτµαlly pδrταblε εxεcµταblε object store.
|
||
*
|
||
* @param uri is obtained via __zipos_parseuri()
|
||
* @asyncsignalsafe
|
||
*/
|
||
int __zipos_open(struct ZiposUri *name, int flags) {
|
||
|
||
// check if this thread is cancelled
|
||
int rc;
|
||
if (_weaken(pthread_testcancel_np) &&
|
||
(rc = _weaken(pthread_testcancel_np)())) {
|
||
errno = rc;
|
||
return -1;
|
||
}
|
||
|
||
// validate api usage
|
||
if ((flags & O_CREAT) || //
|
||
(flags & O_TRUNC) || //
|
||
(flags & O_ACCMODE) != O_RDONLY) {
|
||
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
|
||
struct Zipos *zipos;
|
||
if (!(zipos = __zipos_get())) {
|
||
return enoexec();
|
||
}
|
||
|
||
// most open() calls are due to languages path searching assets. the
|
||
// majority of these calls will return ENOENT or ENOTDIR. we need to
|
||
// perform two extremely costly sigprocmask() calls below. thanks to
|
||
// zipos being a read-only filesystem, we can avoid it in many cases
|
||
int cf;
|
||
if ((cf = __zipos_find(zipos, name)) == -1) {
|
||
return -1;
|
||
}
|
||
if (cf != ZIPOS_SYNTHETIC_DIRECTORY) {
|
||
int mode = GetZipCfileMode(zipos->cdir + cf);
|
||
if ((flags & O_DIRECTORY) && !S_ISDIR(mode)) {
|
||
return enotdir();
|
||
}
|
||
if (!(mode & 0444)) {
|
||
return eacces();
|
||
}
|
||
}
|
||
|
||
// now do the heavy lifting
|
||
BLOCK_SIGNALS;
|
||
rc = __zipos_load(zipos, cf, flags, name);
|
||
ALLOW_SIGNALS;
|
||
return rc;
|
||
}
|