Add posix_spawn (#114)

This commit is contained in:
Justine Tunney 2021-03-07 20:14:07 -08:00
parent 5f088cec23
commit 5ce83b08c8
10 changed files with 521 additions and 22 deletions

View file

@ -41,40 +41,36 @@ static textwindows noinline uint32_t GetUserNameHash(void) {
return KnuthMultiplicativeHash32(buf, size >> 1);
}
static uint32_t getuidgid(int at, uint32_t impl(void)) {
/**
* Returns real user id of process.
*
* This never fails. On Windows, which doesn't really have this concept,
* we return a deterministic value that's likely to work.
*
* @asyncsignalsafe
* @vforksafe
*/
uint32_t getuid(void) {
if (!IsWindows()) {
if (at != -1) {
return getauxval(at);
} else {
return impl();
}
return sys_getuid();
} else {
return GetUserNameHash();
}
}
/**
* Returns real user id of process.
*
* This never fails. On Windows, which doesn't really have this concept,
* we return a deterministic value that's likely to work. On Linux, this
* is fast.
*
* @asyncsignalsafe
*/
uint32_t getuid(void) {
return getuidgid(AT_UID, sys_getuid);
}
/**
* Returns real group id of process.
*
* This never fails. On Windows, which doesn't really have this concept,
* we return a deterministic value that's likely to work. On Linux, this
* is fast.
* we return a deterministic value that's likely to work.
*
* @asyncsignalsafe
* @vforksafe
*/
uint32_t getgid(void) {
return getuidgid(AT_GID, sys_getgid);
if (!IsWindows()) {
return sys_getgid();
} else {
return GetUserNameHash();
}
}

View file

@ -24,6 +24,7 @@
*
* @return 0 on success, or -1 w/ errno
* @asyncsignalsafe
* @vforksafe
*/
int sigfillset(sigset_t *set) {
memset(set->__bits, -1, sizeof(set->__bits));

View file

@ -24,6 +24,7 @@
*
* @return true, false, or -1 w/ errno
* @asyncsignalsafe
* @vforksafe
*/
int sigismember(const sigset_t *set, int sig) {
unsigned i = sig - 1;

117
libc/stdio/spawn.c Normal file
View file

@ -0,0 +1,117 @@
/*-*- 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 2021 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/scheduler.h"
#include "libc/calls/sigbits.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/errno.h"
#include "libc/fmt/fmt.h"
#include "libc/stdio/spawn.h"
#include "libc/stdio/spawna.internal.h"
#include "libc/str/str.h"
/**
* Spawns process the POSIX way.
*
* @param pid is non-NULL and will be set to child pid in parent
* @param path of executable that is not PATH searched
* @return 0 on success or error number on failure
*/
int posix_spawn(int *pid, const char *path,
const posix_spawn_file_actions_t *file_actions,
const posix_spawnattr_t *attrp, char *const argv[],
char *const envp[]) {
unsigned mode;
sigset_t allsigs;
struct sigaction dfl;
char *p, *q, opath[PATH_MAX];
int s, fd, newfd, oflag, tempfd;
if (!(*pid = vfork())) {
if (attrp) {
if (attrp->posix_attr_flags & POSIX_SPAWN_SETPGROUP) {
if (setpgid(0, attrp->posix_attr_pgroup)) _exit(127);
}
if (attrp->posix_attr_flags & POSIX_SPAWN_SETSIGMASK) {
sigprocmask(SIG_SETMASK, &attrp->posix_attr_sigmask, NULL);
}
if (attrp->posix_attr_flags & POSIX_SPAWN_RESETIDS) {
setuid(getuid());
setgid(getgid());
}
if (attrp->posix_attr_flags & POSIX_SPAWN_SETSIGDEF) {
dfl.sa_handler = SIG_DFL;
dfl.sa_flags = 0;
sigfillset(&allsigs);
for (s = 0; sigismember(&allsigs, s); s++) {
if (sigismember(&attrp->posix_attr_sigdefault, s)) {
if (sigaction(s, &dfl, NULL) == -1) _exit(127);
}
}
}
}
if (file_actions) {
p = *file_actions;
while (*p != '\0') {
if (!strncmp(p, "close(", 6)) {
if (sscanf(p + 6, "%d)", &fd) != 1) _exit(127);
if (close(fd) == -1) _exit(127);
} else if (!strncmp(p, "dup2(", 5)) {
if (sscanf(p + 5, "%d,%d)", &fd, &newfd) != 2) _exit(127);
if (dup2(fd, newfd) == -1) _exit(127);
} else if (!strncmp(p, "open(", 5)) {
if (sscanf(p + 5, "%d,", &fd) != 1) _exit(127);
p = strchr(p, ',') + 1;
q = strchr(p, '*');
if (!q || q - p + 1 > PATH_MAX) _exit(127);
strncpy(opath, p, q - p);
opath[q - p] = '\0';
if (sscanf(q + 1, "%o,%o)", &oflag, &mode) != 2) _exit(127);
if (close(fd) == -1 && errno != EBADF) _exit(127);
tempfd = open(opath, oflag, mode);
if (tempfd == -1) _exit(127);
if (tempfd != fd) {
if (dup2(tempfd, fd) == -1) _exit(127);
if (close(tempfd) == -1) _exit(127);
}
} else {
_exit(127);
}
p = strchr(p, ')') + 1;
}
}
if (attrp) {
if (attrp->posix_attr_flags & POSIX_SPAWN_SETSCHEDULER) {
if (sched_setscheduler(0, attrp->posix_attr_schedpolicy,
&attrp->posix_attr_schedparam) == -1) {
_exit(127);
}
}
if (attrp->posix_attr_flags & POSIX_SPAWN_SETSCHEDPARAM) {
if (sched_setparam(0, &attrp->posix_attr_schedparam) == -1) {
_exit(127);
}
}
}
execve(path, argv, envp);
_exit(127);
} else {
if (*pid == -1) return errno;
return 0;
}
}

48
libc/stdio/spawn.h Normal file
View file

@ -0,0 +1,48 @@
#ifndef COSMOPOLITAN_LIBC_STDIO_SPAWN_H_
#define COSMOPOLITAN_LIBC_STDIO_SPAWN_H_
#include "libc/calls/struct/sched_param.h"
#include "libc/calls/struct/sigset.h"
#define POSIX_SPAWN_RESETIDS 0x01
#define POSIX_SPAWN_SETPGROUP 0x02
#define POSIX_SPAWN_SETSIGDEF 0x04
#define POSIX_SPAWN_SETSIGMASK 0x08
#define POSIX_SPAWN_SETSCHEDPARAM 0x10
#define POSIX_SPAWN_SETSCHEDULER 0x20
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
typedef char *posix_spawn_file_actions_t;
typedef struct _posix_spawnattr posix_spawnattr_t;
int posix_spawn_file_actions_init(posix_spawn_file_actions_t *);
int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *);
int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *, int);
int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t *, int, int);
int posix_spawn_file_actions_addopen(posix_spawn_file_actions_t *, int,
const char *, int, unsigned);
int posix_spawnattr_init(posix_spawnattr_t *);
int posix_spawnattr_destroy(posix_spawnattr_t *);
int posix_spawnattr_getflags(const posix_spawnattr_t *, short *);
int posix_spawnattr_setflags(posix_spawnattr_t *, short);
int posix_spawnattr_getpgroup(const posix_spawnattr_t *, int *);
int posix_spawnattr_setpgroup(posix_spawnattr_t *, int);
int posix_spawnattr_getschedpolicy(const posix_spawnattr_t *, int *);
int posix_spawnattr_setschedpolicy(posix_spawnattr_t *, int);
int posix_spawnattr_getschedparam(const posix_spawnattr_t *,
struct sched_param *);
int posix_spawnattr_setschedparam(posix_spawnattr_t *,
const struct sched_param *);
int posix_spawnattr_getsigmask(const posix_spawnattr_t *, sigset_t *);
int posix_spawnattr_setsigmask(posix_spawnattr_t *, const sigset_t *);
int posix_spawnattr_getdefault(const posix_spawnattr_t *, sigset_t *);
int posix_spawnattr_setsigdefault(posix_spawnattr_t *, const sigset_t *);
int posix_spawn(int *, const char *, const posix_spawn_file_actions_t *,
const posix_spawnattr_t *, char *const[], char *const[]);
int posix_spawnp(int *, const char *, const posix_spawn_file_actions_t *,
const posix_spawnattr_t *, char *const[], char *const[]);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_STDIO_SPAWN_H_ */

107
libc/stdio/spawna.c Normal file
View file

@ -0,0 +1,107 @@
/*-*- 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 2021 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/scheduler.h"
#include "libc/calls/sigbits.h"
#include "libc/stdio/spawn.h"
#include "libc/stdio/spawna.internal.h"
/**
* Initialize object with default values.
*/
int posix_spawnattr_init(posix_spawnattr_t *attr) {
attr->posix_attr_flags = 0;
attr->posix_attr_pgroup = 0;
sigprocmask(0, NULL, &attr->posix_attr_sigmask);
sigemptyset(&attr->posix_attr_sigdefault);
attr->posix_attr_schedpolicy = sched_getscheduler(0);
sched_getparam(0, &attr->posix_attr_schedparam);
return 0;
}
int posix_spawnattr_destroy(posix_spawnattr_t *attr) {
return 0;
}
int posix_spawnattr_getflags(const posix_spawnattr_t *attr, short *flags) {
*flags = attr->posix_attr_flags;
return 0;
}
int posix_spawnattr_setflags(posix_spawnattr_t *attr, short flags) {
attr->posix_attr_flags = flags;
return 0;
}
int posix_spawnattr_getpgroup(const posix_spawnattr_t *attr, int *pgroup) {
*pgroup = attr->posix_attr_pgroup;
return 0;
}
int posix_spawnattr_setpgroup(posix_spawnattr_t *attr, int pgroup) {
attr->posix_attr_pgroup = pgroup;
return 0;
}
int posix_spawnattr_getschedpolicy(const posix_spawnattr_t *attr,
int *schedpolicy) {
*schedpolicy = attr->posix_attr_schedpolicy;
return 0;
}
int posix_spawnattr_setschedpolicy(posix_spawnattr_t *attr, int schedpolicy) {
attr->posix_attr_schedpolicy = schedpolicy;
return 0;
}
int posix_spawnattr_getschedparam(const posix_spawnattr_t *attr,
struct sched_param *schedparam) {
*schedparam = attr->posix_attr_schedparam;
return 0;
}
int posix_spawnattr_setschedparam(posix_spawnattr_t *attr,
const struct sched_param *schedparam) {
attr->posix_attr_schedparam = *schedparam;
return 0;
}
int posix_spawnattr_getsigmask(const posix_spawnattr_t *attr,
sigset_t *sigmask) {
*sigmask = attr->posix_attr_sigmask;
return 0;
}
int posix_spawnattr_setsigmask(posix_spawnattr_t *attr,
const sigset_t *sigmask) {
attr->posix_attr_sigmask = *sigmask;
return 0;
}
int posix_spawnattr_getsigdefault(const posix_spawnattr_t *attr,
sigset_t *sigdefault) {
*sigdefault = attr->posix_attr_sigdefault;
return 0;
}
int posix_spawnattr_setsigdefault(posix_spawnattr_t *attr,
const sigset_t *sigdefault) {
attr->posix_attr_sigdefault = *sigdefault;
return 0;
}

View file

@ -0,0 +1,19 @@
#ifndef COSMOPOLITAN_LIBC_STDIO_SPAWNA_INTERNAL_H_
#define COSMOPOLITAN_LIBC_STDIO_SPAWNA_INTERNAL_H_
#include "libc/calls/struct/sched_param.h"
#include "libc/calls/struct/sigset.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
struct _posix_spawnattr {
int16_t posix_attr_flags;
int32_t posix_attr_pgroup;
sigset_t posix_attr_sigmask;
sigset_t posix_attr_sigdefault;
int32_t posix_attr_schedpolicy;
struct sched_param posix_attr_schedparam;
};
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_STDIO_SPAWNA_INTERNAL_H_ */

85
libc/stdio/spawnf.c Normal file
View file

@ -0,0 +1,85 @@
/*-*- 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 2021 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/errno.h"
#include "libc/fmt/fmt.h"
#include "libc/mem/mem.h"
#include "libc/stdio/spawn.h"
#include "libc/str/str.h"
/**
* Creates object with no actions.
*/
int posix_spawn_file_actions_init(posix_spawn_file_actions_t *file_actions) {
*file_actions = malloc(sizeof(char));
if (!*file_actions) return ENOMEM;
strcpy(*file_actions, "");
return 0;
}
/**
* Frees object storage and make invalid.
*/
int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *file_actions) {
free(*file_actions);
*file_actions = NULL;
return 0;
}
/**
* Adds new action string to object.
*/
static int add_to_file_actions(posix_spawn_file_actions_t *file_actions,
char *new_action) {
*file_actions =
realloc(*file_actions, strlen(*file_actions) + strlen(new_action) + 1);
if (!*file_actions) return ENOMEM;
strcat(*file_actions, new_action);
return 0;
}
/**
* Add a close action to object.
*/
int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *file_actions,
int fildes) {
char temp[100];
sprintf(temp, "close(%d)", fildes);
return add_to_file_actions(file_actions, temp);
}
/**
* Add a dup2 action to object.
*/
int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t *file_actions,
int fildes, int newfildes) {
char temp[100];
sprintf(temp, "dup2(%d,%d)", fildes, newfildes);
return add_to_file_actions(file_actions, temp);
}
/**
* Add an open action to object.
*/
int posix_spawn_file_actions_addopen(posix_spawn_file_actions_t *file_actions,
int fildes, const char *path, int oflag,
unsigned mode) {
char temp[100];
sprintf(temp, "open(%d,%s*%o,%o)", fildes, path, oflag, mode);
return add_to_file_actions(file_actions, temp);
}

37
libc/stdio/spawnp.c Normal file
View file

@ -0,0 +1,37 @@
/*-*- 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 2021 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/errno.h"
#include "libc/stdio/spawn.h"
/**
* Spawns process the POSIX way w/ PATH search.
*
* @param pid is non-NULL and will be set to child pid in parent
* @param path of executable is PATH searched unless it contains a slash
* @return 0 on success or error number on failure
*/
int posix_spawnp(int *pid, const char *path,
const posix_spawn_file_actions_t *file_actions,
const posix_spawnattr_t *attrp, char *const argv[],
char *const envp[]) {
char pathbuf[PATH_MAX];
if (!(path = commandv(path, pathbuf))) return errno;
return posix_spawn(pid, path, file_actions, attrp, argv, envp);
}

View file

@ -0,0 +1,88 @@
/*-*- 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 2021 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/bits/safemacros.internal.h"
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/fmt/conv.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/spawn.h"
#include "libc/stdio/stdio.h"
#include "libc/sysv/consts/auxv.h"
#include "libc/sysv/consts/o.h"
#include "libc/testlib/ezbench.h"
#include "libc/testlib/testlib.h"
TEST(spawn, test) {
int rc, ws, pid;
char *prog = (char *)getauxval(AT_EXECFN);
char *args[] = {program_invocation_name, NULL};
char *envs[] = {"THE_DOGE=42", NULL};
if (atoi(nulltoempty(getenv("THE_DOGE"))) == 42) exit(42);
EXPECT_EQ(0, posix_spawn(&pid, prog, NULL, NULL, args, envs));
EXPECT_NE(-1, waitpid(pid, &ws, 0));
EXPECT_TRUE(WIFEXITED(ws));
EXPECT_EQ(42, WEXITSTATUS(ws));
}
/*
* BEST LINUX FORK+EXEC+EXIT+WAIT PERFORMANCE
* The fastest it can go with fork() is 40µs
* The fastest it can go with vfork() is 26µs
*/
void BenchmarkProcessLifecycle(void) {
int rc, ws, pid;
char *prog = "/tmp/tiny64";
char *args[] = {"tiny64", NULL};
char *envs[] = {NULL};
ASSERT_EQ(0, posix_spawn(&pid, prog, NULL, NULL, args, envs));
ASSERT_NE(-1, waitpid(pid, &ws, 0));
ASSERT_TRUE(WIFEXITED(ws));
ASSERT_EQ(42, WEXITSTATUS(ws));
}
const char kTinyLinuxExit[128] = {
0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, // ⌂ELF☻☺☺ 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //         
0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, // ☻ > ☺   
0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // x @     
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // @       
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //         
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, //     @ 8 
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ☺       
0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, // ☺   ♣   
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //         
0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, //   @     
0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, //   @     
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Ç       
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Ç       
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  ►      
0x6a, 0x2a, 0x5f, 0x6a, 0x3c, 0x58, 0x0f, 0x05, // j*_j<X☼♣
};
BENCH(spawn, bench) {
int fd;
if (IsLinux()) {
fd = open("/tmp/tiny64", O_CREAT | O_TRUNC | O_WRONLY, 0755);
write(fd, kTinyLinuxExit, 128);
close(fd);
EZBENCH2("spawn", donothing, BenchmarkProcessLifecycle());
unlink("/tmp/tiny64");
}
}