diff --git a/libc/runtime/mmap.c b/libc/runtime/mmap.c index 946169be9..bad8023ee 100644 --- a/libc/runtime/mmap.c +++ b/libc/runtime/mmap.c @@ -50,6 +50,7 @@ #include "libc/sysv/consts/prot.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 @@ -485,6 +486,11 @@ static noasan inline void *Mmap(void *addr, size_t size, int prot, int flags, void *mmap(void *addr, size_t size, int prot, int flags, int fd, int64_t off) { void *res; size_t toto; + if (__isfdkind(fd, kFdZip)) { + return _weaken(__zipos_mmap)( + addr, size, prot, flags, + (struct ZiposHandle *)(intptr_t)g_fds.p[fd].handle, off); + } #if defined(SYSDEBUG) && (_KERNTRACE || _NTTRACE) if (IsWindows()) { STRACE("mmap(%p, %'zu, %s, %s, %d, %'ld) → ...", addr, size, diff --git a/libc/zipos/mmap.c b/libc/zipos/mmap.c new file mode 100644 index 000000000..65255d57b --- /dev/null +++ b/libc/zipos/mmap.c @@ -0,0 +1,76 @@ +/*-*- 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/calls.h" +#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/sysv/consts/map.h" +#include "libc/sysv/consts/prot.h" +#include "libc/sysv/errfuns.h" +#include "libc/zipos/zipos.internal.h" + +#define IP(X) (intptr_t)(X) +#define VIP(X) (void *)IP(X) + +/** + * Map zipos file into memory. See mmap. + * + * @param addr should be 0 or a compatible address + * @param size must be >0 and will be rounded up to FRAMESIZE + * automatically. + * @param prot can have PROT_READ/PROT_WRITE/PROT_EXEC/PROT_NONE/etc. + * @param flags cannot have `MAP_SHARED` or `MAP_ANONYMOUS`, there is + * no actual file backing for zipos files. `MAP_SHARED` could be + * simulated for non-writable mappings, but that would require + * tracking zipos mappings to prevent making it PROT_WRITE. + * @param h is a zip store object + * @param off specifies absolute byte index of h's file for mapping, + * it does not need to be 64kb aligned. + * @return virtual base address of new mapping, or MAP_FAILED w/ errno + */ +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"); + return VIP(einval()); + } + + const int tempProt = !IsXnu() ? prot | PROT_WRITE : PROT_WRITE; + void *outAddr = + mmap(addr, size, tempProt, (flags & (~MAP_FILE)) | MAP_ANONYMOUS, -1, 0); + if (outAddr == MAP_FAILED) { + return MAP_FAILED; + } + const int64_t beforeOffset = __zipos_lseek(h, 0, SEEK_CUR); + if ((beforeOffset == -1) || + (__zipos_read(h, &(struct iovec){outAddr, size}, 1, off) == -1) || + (__zipos_lseek(h, beforeOffset, SEEK_SET) == -1) || + ((prot != tempProt) && (mprotect(outAddr, size, prot) == -1))) { + const int e = errno; + munmap(outAddr, size); + errno = e; + return MAP_FAILED; + } + return outAddr; +} diff --git a/libc/zipos/open.c b/libc/zipos/open.c index f4bf473af..930e5d285 100644 --- a/libc/zipos/open.c +++ b/libc/zipos/open.c @@ -48,7 +48,7 @@ static char *mapend; static size_t maptotal; -static void *__zipos_mmap(size_t mapsize) { +static void *__zipos_mmap_space(size_t mapsize) { char *start; size_t offset; _unassert(mapsize); @@ -78,7 +78,7 @@ StartOver: ph = &h->next; } if (!h) { - h = __zipos_mmap(mapsize); + h = __zipos_mmap_space(mapsize); } __zipos_unlock(); if (IsAsan()) { diff --git a/libc/zipos/zipos.S b/libc/zipos/zipos.S index 71cb51978..e0ef33ff3 100644 --- a/libc/zipos/zipos.S +++ b/libc/zipos/zipos.S @@ -34,6 +34,7 @@ .yoink __zipos_read .yoink __zipos_stat .yoink __zipos_notat + .yoink __zipos_mmap // TODO(jart): why does corruption happen when zip has no assets? .yoink .cosmo diff --git a/libc/zipos/zipos.internal.h b/libc/zipos/zipos.internal.h index ccc2aeaf8..d6565e0d5 100644 --- a/libc/zipos/zipos.internal.h +++ b/libc/zipos/zipos.internal.h @@ -48,6 +48,8 @@ ssize_t __zipos_write(struct ZiposHandle *, const struct iovec *, size_t, int64_t __zipos_lseek(struct ZiposHandle *, int64_t, unsigned) _Hide; int __zipos_fcntl(int, int, uintptr_t) _Hide; int __zipos_notat(int, const char *) _Hide; +void *__zipos_mmap(void *, uint64_t, int32_t, int32_t, struct ZiposHandle *, + int64_t) _Hide; #ifdef _NOPL0 #define __zipos_lock() _NOPL0("__threadcalls", __zipos_lock) diff --git a/test/libc/runtime/mmap_test.c b/test/libc/runtime/mmap_test.c index ee4a6371f..746a273fc 100644 --- a/test/libc/runtime/mmap_test.c +++ b/test/libc/runtime/mmap_test.c @@ -46,6 +46,8 @@ #include "libc/x/xspawn.h" #include "third_party/xed/x86.h" +STATIC_YOINK("zip_uri_support"); + char testlib_enable_tmp_setup_teardown; void SetUpOnce(void) { @@ -219,6 +221,86 @@ TEST(isheap, mallocOffset) { ASSERT_TRUE(_isheap(p + 100000)); } +static const char *ziposLifePath = "/zip/life.elf"; +TEST(mmap, ziposCannotBeAnonymous) { + int fd; + void *p; + ASSERT_NE(-1, (fd = open(ziposLifePath, O_RDONLY), "%s", ziposLifePath)); + EXPECT_SYS(EINVAL, MAP_FAILED, + (p = mmap(NULL, 0x00010000, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, + fd, 0))); + close(fd); +} + +TEST(mmap, ziposCannotBeShared) { + int fd; + void *p; + ASSERT_NE(-1, (fd = open(ziposLifePath, O_RDONLY), "%s", ziposLifePath)); + EXPECT_SYS(EINVAL, MAP_FAILED, + (p = mmap(NULL, 0x00010000, PROT_READ, MAP_SHARED, fd, 0))); + close(fd); +} + +//////////////////////////////////////////////////////////////////////////////// +// zipos NON-SHARED READ-ONLY FILE MEMORY + +TEST(mmap, ziposCow) { + int fd; + void *p; + ASSERT_NE(-1, (fd = open(ziposLifePath, O_RDONLY), "%s", ziposLifePath)); + EXPECT_NE(MAP_FAILED, + (p = mmap(NULL, 0x00010000, PROT_READ, MAP_PRIVATE, fd, 0))); + EXPECT_STREQN("ELF", ((const char *)p) + 1, 3); + EXPECT_NE(-1, munmap(p, 0x00010000)); + EXPECT_NE(-1, close(fd)); +} + +//////////////////////////////////////////////////////////////////////////////// +// zipos NON-SHARED READ-ONLY FILE MEMORY BETWEEN PROCESSES + +TEST(mmap, ziposCowFileMapReadonlyFork) { + int fd, ws; + void *p; + ASSERT_NE(-1, (fd = open(ziposLifePath, O_RDONLY), "%s", ziposLifePath)); + EXPECT_NE(MAP_FAILED, (p = mmap(NULL, 4, PROT_READ, MAP_PRIVATE, fd, 0))); + EXPECT_STREQN("ELF", ((const char *)p) + 1, 3); + ASSERT_NE(-1, (ws = xspawn(0))); + if (ws == -2) { + ASSERT_STREQN("ELF", ((const char *)p) + 1, 3); + _exit(0); + } + EXPECT_EQ(0, ws); + EXPECT_STREQN("ELF", ((const char *)p) + 1, 3); + EXPECT_NE(-1, munmap(p, 6)); + EXPECT_NE(-1, close(fd)); +} + +//////////////////////////////////////////////////////////////////////////////// +// zipos NON-SHARED READ/WRITE FILE MEMORY BETWEEN PROCESSES + +TEST(mmap, ziposCowFileMapFork) { + int fd, ws; + void *p; + char lol[4]; + ASSERT_NE(-1, (fd = open(ziposLifePath, O_RDONLY), "%s", ziposLifePath)); + EXPECT_NE(MAP_FAILED, + (p = mmap(NULL, 6, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0))); + memcpy(p, "parnt", 6); + ASSERT_NE(-1, (ws = xspawn(0))); + if (ws == -2) { + ASSERT_STREQN("parnt", p, 5); + strcpy(p, "child"); + ASSERT_STREQN("child", p, 5); + _exit(0); + } + EXPECT_EQ(0, ws); + EXPECT_STREQN("parnt", p, 5); // child changing memory did not change parent + EXPECT_EQ(4, pread(fd, lol, 4, 0)); + EXPECT_STREQN("ELF", &lol[1], 3); // changing memory did not change file + EXPECT_NE(-1, munmap(p, 6)); + EXPECT_NE(-1, close(fd)); +} + //////////////////////////////////////////////////////////////////////////////// // NON-SHARED READ-ONLY FILE MEMORY @@ -231,8 +313,7 @@ TEST(mmap, cow) { path); EXPECT_EQ(5, write(fd, "hello", 5)); EXPECT_NE(-1, fdatasync(fd)); - EXPECT_NE(MAP_FAILED, - (p = mmap(NULL, 5, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0))); + EXPECT_NE(MAP_FAILED, (p = mmap(NULL, 5, PROT_READ, MAP_PRIVATE, fd, 0))); EXPECT_STREQN("hello", p, 5); EXPECT_NE(-1, munmap(p, 5)); EXPECT_NE(-1, close(fd)); @@ -258,6 +339,7 @@ TEST(mmap, cowFileMapReadonlyFork) { ASSERT_STREQN("hello", p, 5); _exit(0); } + EXPECT_EQ(0, ws); EXPECT_STREQN("hello", p, 5); EXPECT_NE(-1, munmap(p, 6)); EXPECT_NE(-1, close(fd)); @@ -285,6 +367,7 @@ TEST(mmap, cowFileMapFork) { ASSERT_STREQN("child", p, 5); _exit(0); } + EXPECT_EQ(0, ws); EXPECT_STREQN("parnt", p, 5); // child changing memory did not change parent EXPECT_EQ(6, pread(fd, lol, 6, 0)); EXPECT_STREQN("parnt", lol, 5); // changing memory did not change file @@ -310,6 +393,7 @@ TEST(mmap, sharedAnonMapFork) { ASSERT_STREQN("child", p, 5); _exit(0); } + EXPECT_EQ(0, ws); EXPECT_STREQN("child", p, 5); // boom EXPECT_NE(-1, munmap(p, 5)); } @@ -336,6 +420,7 @@ TEST(mmap, sharedFileMapFork) { ASSERT_NE(-1, msync(p, 6, MS_SYNC | MS_INVALIDATE)); _exit(0); } + EXPECT_EQ(0, ws); EXPECT_STREQN("child", p, 5); // child changing memory changed parent memory // XXX: RHEL5 has a weird issue where if we read the file into its own // shared memory then corruption occurs! diff --git a/test/libc/runtime/test.mk b/test/libc/runtime/test.mk index e1678f71f..f42075df3 100644 --- a/test/libc/runtime/test.mk +++ b/test/libc/runtime/test.mk @@ -51,6 +51,7 @@ o/$(MODE)/test/libc/runtime/runtime.pkg: \ o/$(MODE)/test/libc/runtime/%.com.dbg: \ $(TEST_LIBC_RUNTIME_DEPS) \ + o/$(MODE)/test/libc/mem/prog/life.elf.zip.o \ o/$(MODE)/test/libc/runtime/%.o \ o/$(MODE)/test/libc/runtime/runtime.pkg \ $(LIBC_TESTMAIN) \