Implement zipos mmap (#725)

This commit is contained in:
Gavin Hayes 2023-02-02 05:28:50 -05:00 committed by GitHub
parent 6d36584ff2
commit fd0da9c0df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 175 additions and 4 deletions

View file

@ -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,

76
libc/zipos/mmap.c Normal file
View file

@ -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;
}

View file

@ -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()) {

View file

@ -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

View file

@ -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)

View file

@ -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!

View file

@ -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) \