diff --git a/Makefile b/Makefile index e12ecb17b..53ec70f42 100644 --- a/Makefile +++ b/Makefile @@ -97,21 +97,23 @@ endif endif .PLEDGE = stdio rpath wpath cpath fattr proc -.UNVEIL = \ - libc/integral \ - libc/stdbool.h \ - libc/disclaimer.inc \ - rwc:/dev/shm \ - rx:build/bootstrap \ - rx:o/third_party/gcc \ - r:build/portcosmo.h \ - /proc/stat \ - rw:/dev/null \ - rw:/dev/full \ - w:o/stack.log \ - /etc/hosts \ - ~/.runit.psk \ - /proc/self/status \ +.UNVEIL = \ + libc/integral \ + libc/stdbool.h \ + libc/disclaimer.inc \ + rwc:/dev/shm \ + rx:build/bootstrap \ + rx:o/third_party/gcc \ + r:build/portcosmo.h \ + /proc/stat \ + rw:/dev/null \ + rw:/dev/full \ + w:o/stack.log \ + /etc/hosts \ + ~/.runit.psk \ + /proc/self/status \ + rx:/usr/bin/qemu-aarch64 \ + rx:o/third_party/qemu/qemu-aarch64 \ /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor PKGS = diff --git a/README.md b/README.md index 95a4dc16e..972660876 100644 --- a/README.md +++ b/README.md @@ -128,25 +128,7 @@ cosmocc -Os -o hello2.com hello2.c ## ARM Cosmo supports cross-compiling binaries for machines with ARM -microprocessors. There are two options available for doing this. - -The first option is to embed the [blink virtual -machine](https://github.com/jart/blink) by adding the following to the -top of your main.c file: - -```c -__static_yoink("blink_linux_aarch64"); // for raspberry pi -__static_yoink("blink_xnu_aarch64"); // is apple silicon -``` - -The benefit is you'll have single file executables that'll run on both -x86_64 and arm64 platforms. The tradeoff is Blink's JIT is slower than -running natively, but tends to go fast enough, unless you're doing -scientific computing (e.g. running LLMs with -`o//third_party/ggml/llama.com`). - -Therefore, the second option is to cross compile aarch64 executables, -by using build modes like the following: +microprocessors. For example: ```sh make -j8 m=aarch64 o/aarch64/third_party/ggml/llama.com @@ -175,7 +157,196 @@ You can run your ELF AARCH64 executable on Apple Silicon as follows: ape ./llama.com ``` -## Source Builds +If you want to run the `MODE=aarch64` unit tests, you need to have +qemu-aarch64 installed as a binfmt_misc interpreter. It needs to be a +static binary if you want it to work with Landlock Make's security. You +can use the build included in our `third_party/qemu/` folder. + +``` +doas cp o/third_party/qemu/qemu-aarch64 /usr/bin/qemu-aarch64 +doas sh -c "echo ':qemu-aarch64:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/qemu-aarch64:CF' > /proc/sys/fs/binfmt_misc/register" +make -j8 m=aarch64 +``` + +Please note that the qemu-aarch64 binfmt_misc interpreter installation +process is *essential* for being able to use the `aarch64-unknown-cosmo` +toolchain to build fat APE binaries on your x86-64 machine. + +## AMD64 + ARM64 fat APE binaries + +If you've setup the qemu binfmt_misc interpreter, then you can can use +cosmo's toolchains to build fat ape binaries. It works by compiling your +program twice, so you can have a native build for both architectures in +the same file. The two programs are merged together by apelink.com which +also embeds multiple copies of APE loader and multiple symbols tables. + +The easiest way to build fat APE is using `fatcosmocc`. This compiler +works by creating a concomitant `.aarch64/foo.o` for every `foo.o` you +compile. The only exception is the C preprocessor mode, which actually +runs x86-64 GCC except with macros like `__x86_64__` undefined. + +This toolchain works great for C projects that are written in a portable +way and don't produce architecturue-specific artifacts. One example of a +large project that can be easily built is GNU coreutils. + +```sh +cd coreutils +fatcosmocc --update ||exit +./configure CC=fatcosmocc \ + AR=fatcosmoar \ + INSTALL=$(command -v fatcosmoinstall) \ + --prefix=/opt/cosmos \ + --disable-nls \ + --disable-dependency-tracking \ + --disable-silent-rules +make -j8 +``` + +You'll then have a bunch of files like `src/ls` which are fat ape +binaries. If you want to run them on Windows, then you simply need to +rename the file so that it has the `.com` suffix. Better yet, consider +making that a symlink (a.k.a. reparse point). The biggest gotcha with +`fatcosmocc` though is ensuring builds don't strip binaries. For +example, Linux's `install -s` command actually understands Windows' +Portable Executable format well enough to remove the MS-DOS stub, which +is where the APE shell script is stored. You need to ensure that +`fatcosmoinstall` is used instead. Especially if your project needs to +install the libraries built by `fatacosmoar` into `/opt/cosmos`. + +## Advanced Fat APE Builds + +Once you get seriously involved in creating fat APE builds of software +you're going to eventually outgrow `fatcosmocc`. One example is Emacs +which is trickier to build, because it produces architecture-specific +files, and it also depends on shared files, e.g. zoneinfo. Since we like +having everything in a neat little single-file executable container that +doesn't need an "installation wizard", this tutorial will explain how we +manage to accomplish that. + +What you're going to do is, instead of using `fatcosmocc`, you're going +to use both the `x86_64-unknown-cosmo-cc` and `aarch64-unknown-cosmo-cc` +toolchains independently, and then run `apelink` and `zip` to manually +build the final files. But there's a few tricks to learn first. + +The first trick is to create a symlink on your system called `/zip`. +Cosmopolitan Libc normally uses that as a synthetic folder that lets you +access the assets in your zip executable. But since that's a read-only +file system, your build system should use the normal one. + +```sh +doas ln -sf /opt/cosmos /zip +``` + +Now create a file named `rebuild-fat.sh` which runs the build twice: + +```sh +#!/bin/sh +set -ex +export MODE=aarch64 +export COSMOS=/opt/cosmos/aarch64 +rebuild-cosmos.sh aarch64 +export MODE= +export COSMOS=/opt/cosmos/x86_64 +rebuild-cosmos.sh x86_64 +wall.com 'finished building' +``` + +Then create a second file `rebuild-cosmos.sh` which runs your build: + +```sh +#!/bin/bash +set -ex + +ARCH=${1:-x86_64} +export COSMO=${COSMO:-/opt/cosmo} +export COSMOS=${COSMOS:-/opt/cosmos/$ARCH} +export AS=$(command -v $ARCH-unknown-cosmo-as) || exit +export CC=$(command -v $ARCH-unknown-cosmo-cc) || exit +export CXX=$(command -v $ARCH-unknown-cosmo-c++) || exit +export AR=$(command -v $ARCH-unknown-cosmo-ar) || exit +export STRIP=$(command -v $ARCH-unknown-cosmo-strip) || exit +export INSTALL=$(command -v $ARCH-unknown-cosmo-install) || exit +export OBJCOPY=$(command -v $ARCH-unknown-cosmo-objcopy) || exit +export OBJDUMP=$(command -v $ARCH-unknown-cosmo-objdump) || exit +export ADDR2LINE=$(command -v $ARCH-unknown-cosmo-addr2line) || exit + +$CC --update + +export COSMOPOLITAN_DISABLE_ZIPOS=1 + +cd ~/vendor/zlib +./configure --prefix=$COSMOS --static +make clean +make -j +make install + +cd ~/vendor/ncurses-6.4 +./configure --prefix=$COSMOS --sysconfdir=/zip --datarootdir=/zip/share --exec-prefix=/zip/$ARCH --disable-shared +make clean +make -j +make install + +cd ~/vendor/readline-8.2 +./configure --prefix=$COSMOS --sysconfdir=/zip --datarootdir=/zip/share --exec-prefix=/zip/$ARCH --disable-shared +make uninstall || true +make clean +make -j +make install + +# NOTES: +# 1. You'll need to patch enum { FOO = x } that fails to build into a #define FOO +# 2. You'll need to patch configure.ac so it DOES NOT define USABLE_FIONREAD to 1 +# 2. You'll need to patch configure.ac so it DOES NOT define INTERRUPT_INPUT to 1 +cd ~/vendor/emacs-28.2 +./configure --prefix=$COSMOS --sysconfdir=/zip --datarootdir=/zip/share --exec-prefix=/zip/$ARCH \ + --without-x --with-threads --without-gnutls --disable-silent-rules --with-file-notification=no +make uninstall || true +make clean +make -j +make install +``` + +Once you've completed this build process, you'll have the ELF files +`/opt/cosmos/x86_64/bin/emacs` and `/opt/cosmos/aarch64/bin/emacs`. Your +next move is to combine them into a single pristine `emacs.com` file. + +```sh +cd /zip +COSMO=${COSMO:-/opt/cosmo} +mkdir -p /opt/cosmos/bin +apelink \ + -o /opt/cosmos/bin/emacs.com \ + -l "$COSMO/o//ape/ape.elf" \ + -l "$COSMO/o/aarch64/ape/ape.elf" \ + -M "$COSMO/ape/ape-m1.c" \ + /opt/cosmos/x86_64/bin/emacs \ + /opt/cosmos/aarch64/bin/emacs +cd /zip +zip -r /opt/cosmos/bin/emacs.com \ + aarch64/libexec \ + x86_64/libexec \ + share/terminfo \ + $(find share/emacs -type f | + grep -v '\.el.gz$' | + grep -v refcards | + grep -v images) +``` + +You can now scp your `emacs.com` build to seven operating systems for +two distinct kinds of microprocessors without any dependencies. All the +LISP, zoneinfo, and termcap files it needs are stored inside the ZIP +structure of the binary, which has performance that's equivalent to the +Linux filesystem (even though it decompresses artifacts on the fly!) For +this reason, you might actually find that fat APE Emacs goes faster if +you're using an operating system like Windows where files are go slow. + +If you like to use Vim instead of Emacs, then you can build that too. +However Vim's build system makes it a bit harder, since it's configured +to always strip binaries. The `apelink` program needs the symbol tables +to still be there when it creates the fat version. Otherwise tools like +`--ftrace` won't work. + +## Monolithic Source Builds Cosmopolitan can be compiled from source on any Linux distro. First, you need to download or clone the repository. diff --git a/ape/ape-m1.c b/ape/ape-m1.c index 337959fc5..3a09c80e2 100644 --- a/ape/ape-m1.c +++ b/ape/ape-m1.c @@ -151,6 +151,7 @@ union ElfPhdrBuf { }; struct PathSearcher { + int literally; unsigned long namelen; const char *name; const char *syspath; @@ -377,10 +378,27 @@ static char SearchPath(struct PathSearcher *ps, const char *suffix) { } static char FindCommand(struct PathSearcher *ps, const char *suffix) { + ps->path[0] = 0; + + /* paths are always 100% taken literally when a slash exists + $ ape foo/bar.com arg1 arg2 */ if (MemChr(ps->name, '/', ps->namelen)) { - ps->path[0] = 0; return AccessCommand(ps, suffix, 0); } + + /* we don't run files in the current directory + $ ape foo.com arg1 arg2 + unless $PATH has an empty string entry, e.g. + $ expert PATH=":/bin" + $ ape foo.com arg1 arg2 + however we will execute this + $ ape - foo.com foo.com arg1 arg2 + because cosmo's execve needs it */ + if (ps->literally && AccessCommand(ps, suffix, 0)) { + return 1; + } + + /* otherwise search for name on $PATH */ return SearchPath(ps, suffix); } @@ -825,7 +843,7 @@ int main(int argc, char **argv, char **envp) { auxv = (long *)(envp + i + 1); /* interpret command line arguments */ - if (argc >= 3 && !StrCmp(argv[1], "-")) { + if ((M->ps.literally = argc >= 3 && !StrCmp(argv[1], "-"))) { /* if the first argument is a hyphen then we give the user the power to change argv[0] or omit it entirely. most operating systems don't permit the omission of argv[0] but we do, b/c @@ -836,7 +854,7 @@ int main(int argc, char **argv, char **envp) { } else if (argc < 2) { Emit("usage: ape PROG [ARGV1,ARGV2,...]\n" " ape - PROG [ARGV0,ARGV1,...]\n" - "actually portable executable loader silicon 1.7\n" + "actually portable executable loader silicon 1.8\n" "copyright 2023 justine alexandra roberts tunney\n" "https://justine.lol/ape.html\n"); _exit(1); @@ -881,8 +899,8 @@ int main(int argc, char **argv, char **envp) { pe = ebuf->buf + rc; /* resolve argv[0] to reflect path search */ - if ((argc > 0 && *prog != '/' && *exe == '/' && !StrCmp(prog, argv[0])) || - !StrCmp(BaseName(prog), argv[0])) { + if (argc > 0 && ((*prog != '/' && *exe == '/' && !StrCmp(prog, argv[0])) || + !StrCmp(BaseName(prog), argv[0]))) { argv[0] = exe; } diff --git a/ape/loader.c b/ape/loader.c index 0cf10c7f4..d848e1b31 100644 --- a/ape/loader.c +++ b/ape/loader.c @@ -208,6 +208,7 @@ union ElfPhdrBuf { struct PathSearcher { int os; + int literally; const char *name; const char *syspath; unsigned long namelen; @@ -588,10 +589,27 @@ static char SearchPath(struct PathSearcher *ps, const char *suffix) { } static char FindCommand(struct PathSearcher *ps, const char *suffix) { + ps->path[0] = 0; + + /* paths are always 100% taken literally when a slash exists + $ ape foo/bar.com arg1 arg2 */ if (MemChr(ps->name, '/', ps->namelen)) { - ps->path[0] = 0; return AccessCommand(ps, suffix, 0); } + + /* we don't run files in the current directory + $ ape foo.com arg1 arg2 + unless $PATH has an empty string entry, e.g. + $ expert PATH=":/bin" + $ ape foo.com arg1 arg2 + however we will execute this + $ ape - foo.com foo.com arg1 arg2 + because cosmo's execve needs it */ + if (ps->literally && AccessCommand(ps, suffix, 0)) { + return 1; + } + + /* otherwise search for name on $PATH */ return SearchPath(ps, suffix); } @@ -915,6 +933,7 @@ EXTERN_C __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, char dl) { int rc, n; unsigned i; + char literally; const char *ape; int c, fd, os, argc; struct ApeLoader *M; @@ -989,7 +1008,7 @@ EXTERN_C __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, } /* we can load via shell, shebang, or binfmt_misc */ - if (argc >= 3 && !StrCmp(argv[1], "-")) { + if ((literally = argc >= 3 && !StrCmp(argv[1], "-"))) { /* if the first argument is a hyphen then we give the user the power to change argv[0] or omit it entirely. most operating systems don't permit the omission of argv[0] but we do, b/c @@ -1009,6 +1028,7 @@ EXTERN_C __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, /* allocate loader memory in program's arg block */ n = sizeof(struct ApeLoader); M = (struct ApeLoader *)__builtin_alloca(n); + M->ps.literally = literally; /* create new bottom of stack for spawned program system v abi aligns this on a 16-byte boundary @@ -1045,8 +1065,8 @@ EXTERN_C __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, pe = ebuf->buf + rc; /* change argv[0] to resolved path if it's ambiguous */ - if ((argc > 0 && *prog != '/' && *exe == '/' && !StrCmp(prog, argv[0])) || - !StrCmp(BaseName(prog), argv[0])) { + if (argc > 0 && ((*prog != '/' && *exe == '/' && !StrCmp(prog, argv[0])) || + !StrCmp(BaseName(prog), argv[0]))) { argv[0] = exe; } diff --git a/bin/ape-uninstall b/bin/ape-uninstall index f18c38473..0079c6c9c 100755 --- a/bin/ape-uninstall +++ b/bin/ape-uninstall @@ -49,6 +49,7 @@ for x in .ape \ .ape-1.5 \ .ape-1.6 \ .ape-1.7 \ + .ape-1.8 \ .ape-blink-0.9.2 \ .ape-blink-1.0.0; do rm -f \ diff --git a/libc/calls/execve-nt.greg.c b/libc/calls/execve-nt.greg.c index 95bd1bfae..e7535b32b 100644 --- a/libc/calls/execve-nt.greg.c +++ b/libc/calls/execve-nt.greg.c @@ -59,6 +59,7 @@ #include "libc/sysv/errfuns.h" #include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" +#ifdef __x86_64__ #define keywords textwindows dontasan dontubsan dontinstrument @@ -275,3 +276,5 @@ static keywords void sys_execve_nt_relay(intptr_t h, long b, long c, long d) { __imp_ExitProcess(dwExitCode); __builtin_unreachable(); } + +#endif /* __x86_64__ */ diff --git a/libc/calls/execve-sysv.c b/libc/calls/execve-sysv.c index 347303bcb..a53dfab96 100644 --- a/libc/calls/execve-sysv.c +++ b/libc/calls/execve-sysv.c @@ -17,15 +17,21 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "ape/ape.h" +#include "libc/atomic.h" #include "libc/calls/blockcancel.internal.h" #include "libc/calls/blocksigs.internal.h" #include "libc/calls/calls.h" -#include "libc/calls/execve-sysv.internal.h" +#include "libc/calls/cp.internal.h" +#include "libc/calls/execve.internal.h" #include "libc/calls/syscall-sysv.internal.h" +#include "libc/cosmo.h" #include "libc/dce.h" #include "libc/errno.h" +#include "libc/fmt/magnumstrs.internal.h" #include "libc/intrin/bits.h" +#include "libc/intrin/describeflags.internal.h" #include "libc/intrin/safemacros.internal.h" +#include "libc/intrin/strace.internal.h" #include "libc/limits.h" #include "libc/mem/alloca.h" #include "libc/paths.h" @@ -36,77 +42,100 @@ #include "libc/sysv/consts/ok.h" #include "libc/sysv/errfuns.h" -static bool CanExecute(const char *path) { - return !sys_faccessat(AT_FDCWD, path, X_OK, 0); -} +#define ELIBBAD_LINUX 80 +#define EBADEXEC_XNU 85 +#define EBADARCH_XNU 86 -bool IsAPEMagic(char buf[8]) { - return READ64LE(buf) == READ64LE("MZqFpD='") || - READ64LE(buf) == READ64LE("JTqFpD='"); -} +static struct { + atomic_uint once; + const char *home; + const char *tmpdir; +} g_execve; -static bool IsApeBinary(const char *path) { - int fd; - char buf[8]; - bool res = false; - // TODO(jart): Should we block signals too? - BLOCK_CANCELLATIONS; - if ((fd = sys_openat(AT_FDCWD, path, O_RDONLY, 0)) != -1) { - res = sys_read(fd, buf, 8) == 8 && IsAPEMagic(buf); - sys_close(fd); +static bool IsApeFile(const char *path) { + if (!endswith(path, ".com")) { + return true; + } else { + bool res = false; + BLOCK_CANCELLATIONS; + BEGIN_CANCELLATION_POINT; + int fd; + char buf[8]; + int flags = O_RDONLY | O_NOCTTY | O_NONBLOCK | O_CLOEXEC; + if ((fd = sys_openat(AT_FDCWD, path, flags, 0)) != -1) { + res = sys_pread(fd, buf, 8, 0, 0) == 8 && IsApeLoadable(buf); + sys_close(fd); + } + END_CANCELLATION_POINT; + ALLOW_CANCELLATIONS; + return res; } - ALLOW_CANCELLATIONS; - return res; } static const char *Join(const char *a, const char *b, char buf[PATH_MAX]) { size_t n, m; - n = strlen(a); - m = strlen(b); - if (n + 1 + m + 1 < PATH_MAX) { - stpcpy(stpcpy(stpcpy(buf, a), "/"), b); - return buf; - } else { - return ""; + if (a && *a) { + n = strlen(a); + m = strlen(b); + if (n + m + 1 < PATH_MAX) { + stpcpy(stpcpy(buf, a), b); + return buf; + } + } + return 0; +} + +static void RetryExecve(const char *prog, char **argv, char *const *envp) { + if ((argv[0] = (char *)prog)) { + STRACE("execve(%#s, %s) due to %s", prog, DescribeStringList(argv), + _strerrno(errno)); + __sys_execve(prog, argv, envp); } } -int sys_execve(const char *prog, char *const argv[], char *const envp[]) { - size_t i; - int e, rc; - char *buf; - char **shargs; - const char *ape; - e = errno; - __sys_execve(prog, argv, envp); - if (errno == ENOEXEC) { - for (i = 0; argv[i];) ++i; - buf = alloca(PATH_MAX); - shargs = alloca((i + 4) * sizeof(char *)); - if (IsApeBinary(prog) && - (CanExecute((ape = "/usr/bin/ape")) || - CanExecute((ape = Join(firstnonnull(getenv("TMPDIR"), - firstnonnull(getenv("HOME"), ".")), - ".ape-" APE_VERSION_STR, buf))) || - CanExecute((ape = Join(firstnonnull(getenv("HOME"), "."), - ".ape-" APE_VERSION_STR, buf))))) { - shargs[0] = (char *)ape; - shargs[1] = (char *)"-"; - shargs[2] = (char *)prog; - memcpy(shargs + 3, argv, (i + 1) * sizeof(char *)); - errno = e; - rc = __sys_execve(shargs[0], shargs, envp); - } else if (CanExecute(prog)) { - shargs[0] = _PATH_BSHELL; - shargs[1] = (char *)prog; - memcpy(shargs + 2, argv + 1, i * sizeof(char *)); - errno = e; - rc = __sys_execve(shargs[0], shargs, envp); - } else { - rc = enoexec(); - } - } else { - rc = -1; - } - return rc; +static void SetupExecve(void) { + g_execve.home = getenv("HOME"); + g_execve.tmpdir = getenv("TMPDIR"); +} + +__attribute__((__constructor__)) static void InitExecve(void) { + cosmo_once(&g_execve.once, SetupExecve); +} + +int sys_execve(const char *prog, char *const argv[], char *const envp[]) { + + // try kernel + // this also checks execute bit + __sys_execve(prog, argv, envp); + if (!(errno == ENOEXEC || (IsLinux() && errno == ELIBBAD_LINUX))) { + return -1; + } + + // allocate memory + int argc; + for (argc = 0; argv[argc];) ++argc; + char **shargs = alloca((argc + 4) * sizeof(char *)); + + // try ape loader + if (IsApeFile(prog)) { + shargs[1] = (char *)"-"; + shargs[2] = (char *)prog; + memcpy(shargs + 3, argv, (argc + 1) * sizeof(char *)); + RetryExecve("/usr/bin/ape", shargs, envp); + char *buf = alloca(PATH_MAX); + const char *name = "/.ape-" APE_VERSION_STR; + InitExecve(); + RetryExecve(Join(g_execve.tmpdir, name, buf), shargs, envp); + RetryExecve(Join(g_execve.home, name, buf), shargs, envp); + RetryExecve(Join(".", name, buf), shargs, envp); + } + + // try bourne shell + shargs[0] = _PATH_BSHELL; + shargs[1] = (char *)prog; + memcpy(shargs + 2, argv + 1, argc * sizeof(char *)); + RetryExecve(shargs[0], shargs, envp); + + enoexec(); + return -1; } diff --git a/libc/calls/execve.c b/libc/calls/execve.c index 46f3c2fdd..3f2a53ae2 100644 --- a/libc/calls/execve.c +++ b/libc/calls/execve.c @@ -64,7 +64,7 @@ int execve(const char *prog, char *const argv[], char *const envp[]) { !__asan_is_valid_strlist(envp)))) { rc = efault(); } else { - STRACE("execve(%#s, %s, %s) → ...", prog, DescribeStringList(argv), + STRACE("execve(%#s, %s, %s)", prog, DescribeStringList(argv), DescribeStringList(envp)); rc = 0; if (IsLinux() && __execpromises && _weaken(sys_pledge_linux)) { diff --git a/libc/calls/execve-sysv.internal.h b/libc/calls/execve.internal.h similarity index 76% rename from libc/calls/execve-sysv.internal.h rename to libc/calls/execve.internal.h index 50c2d95a0..bba0081ae 100644 --- a/libc/calls/execve-sysv.internal.h +++ b/libc/calls/execve.internal.h @@ -3,7 +3,9 @@ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ -bool IsAPEMagic(char[8]); +void __execve_lock(void); +void __execve_unlock(void); +bool IsApeLoadable(char[8]); COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/calls/fexecve.c b/libc/calls/fexecve.c index 085a96f8e..d80aba034 100644 --- a/libc/calls/fexecve.c +++ b/libc/calls/fexecve.c @@ -21,7 +21,7 @@ #include "libc/calls/blocksigs.internal.h" #include "libc/calls/calls.h" #include "libc/calls/cp.internal.h" -#include "libc/calls/execve-sysv.internal.h" +#include "libc/calls/execve.internal.h" #include "libc/calls/internal.h" #include "libc/calls/struct/stat.internal.h" #include "libc/calls/syscall-sysv.internal.h" @@ -30,6 +30,7 @@ #include "libc/fmt/itoa.h" #include "libc/intrin/asan.internal.h" #include "libc/intrin/describeflags.internal.h" +#include "libc/intrin/kprintf.h" #include "libc/intrin/safemacros.internal.h" #include "libc/intrin/strace.internal.h" #include "libc/intrin/weaken.h" @@ -46,7 +47,7 @@ static bool IsAPEFd(const int fd) { char buf[8]; - return (sys_pread(fd, buf, 8, 0, 0) == 8) && IsAPEMagic(buf); + return (sys_pread(fd, buf, 8, 0, 0) == 8) && IsApeLoadable(buf); } static int fexecve_impl(const int fd, char *const argv[], char *const envp[]) { @@ -141,7 +142,7 @@ static int fd_to_mem_fd(const int infd, char *path) { ssize_t readRc; readRc = pread(infd, space, st.st_size, 0); bool success = readRc != -1; - if (success && (st.st_size > 8) && IsAPEMagic(space)) { + if (success && (st.st_size > 8) && IsApeLoadable(space)) { int flags = fcntl(fd, F_GETFD); if ((success = (flags != -1) && (fcntl(fd, F_SETFD, flags & (~FD_CLOEXEC)) != -1) && diff --git a/libc/calls/getprogramexecutablename.greg.c b/libc/calls/getprogramexecutablename.greg.c index 563490241..e82faf005 100644 --- a/libc/calls/getprogramexecutablename.greg.c +++ b/libc/calls/getprogramexecutablename.greg.c @@ -93,9 +93,9 @@ static inline void GetProgramExecutableNameImpl(char *p, char *e) { // if argv[0] exists then turn it into an absolute path. we also try // adding a .com suffix since the ape auto-appends it when resolving - if (((q = __argv[0]) && !sys_faccessat(AT_FDCWD, q, F_OK, 0)) || - ((q = StrCat(u.path, __argv[0], ".com")) && - !sys_faccessat(AT_FDCWD, q, F_OK, 0))) { + if ((q = __argv[0]) && ((!sys_faccessat(AT_FDCWD, q, F_OK, 0)) || + ((q = StrCat(u.path, __argv[0], ".com")) && + !sys_faccessat(AT_FDCWD, q, F_OK, 0)))) { if (*q != '/') { if (q[0] == '.' && q[1] == '/') { q += 2; diff --git a/libc/calls/isapemagic.c b/libc/calls/isapemagic.c new file mode 100644 index 000000000..a01946e8a --- /dev/null +++ b/libc/calls/isapemagic.c @@ -0,0 +1,29 @@ +/*-*- 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/intrin/bits.h" + +/** + * Returns true if executable image is supported by APE Loader. + */ +bool IsApeLoadable(char buf[8]) { + return READ32LE(buf) == READ32LE("\177ELF") || + READ64LE(buf) == READ64LE("MZqFpD='") || + READ64LE(buf) == READ64LE("JTqFpD='"); +} diff --git a/libc/calls/ntspawn.c b/libc/calls/ntspawn.c index 6c4390a53..3240d9140 100644 --- a/libc/calls/ntspawn.c +++ b/libc/calls/ntspawn.c @@ -34,6 +34,7 @@ #include "libc/nt/struct/securityattributes.h" #include "libc/nt/struct/startupinfo.h" #include "libc/sysv/errfuns.h" +#ifdef __x86_64__ struct SpawnBlock { union { @@ -108,3 +109,5 @@ textwindows int ntspawn( if (handle) CloseHandle(handle); return __fix_enotdir(rc, prog16); } + +#endif /* __x86_64__ */ diff --git a/libc/calls/tkill.c b/libc/calls/tkill.c index 4795a5f3a..b66a605ab 100644 --- a/libc/calls/tkill.c +++ b/libc/calls/tkill.c @@ -32,6 +32,7 @@ #include "libc/nt/runtime.h" #include "libc/nt/struct/context.h" #include "libc/nt/thread.h" +#include "libc/runtime/syslib.internal.h" #include "libc/sysv/consts/sicode.h" #include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" @@ -97,10 +98,17 @@ static dontinline textwindows int __tkill_nt(int tid, int sig, } } +static int __tkill_m1(int tid, int sig, struct CosmoTib *tib) { + struct PosixThread *pt = (struct PosixThread *)__get_tls()->tib_pthread; + return __syslib->pthread_kill(pt->next, sig); +} + // OpenBSD has an optional `tib` parameter for extra safety. int __tkill(int tid, int sig, void *tib) { int rc; - if (IsLinux() || IsXnu() || IsFreebsd() || IsOpenbsd() || IsNetbsd()) { + if (IsXnuSilicon()) { + return __tkill_m1(tid, sig, tib); + } else if (IsLinux() || IsXnu() || IsFreebsd() || IsOpenbsd() || IsNetbsd()) { rc = sys_tkill(tid, sig, tib); } else if (IsWindows()) { rc = __tkill_nt(tid, sig, tib); diff --git a/libc/intrin/asan.c b/libc/intrin/asan.c index c397612d8..5b2e6315f 100644 --- a/libc/intrin/asan.c +++ b/libc/intrin/asan.c @@ -406,11 +406,9 @@ static bool __asan_is_mapped(int x) { // xxx: we can't lock because no reentrant locks yet int i; bool res; - struct MemoryIntervals *m; __mmi_lock(); - m = _weaken(_mmi); - i = __find_memory(m, x); - res = i < m->i && x >= m->p[i].x; + i = __find_memory(&_mmi, x); + res = i < _mmi.i && x >= _mmi.p[i].x; __mmi_unlock(); return res; } @@ -902,7 +900,7 @@ static __wur __asan_die_f *__asan_report(const void *addr, int size, p = __asan_format_section(p, _etext, _edata, ".data", addr); p = __asan_format_section(p, _end, _edata, ".bss", addr); __mmi_lock(); - for (m = _weaken(_mmi), i = 0; i < m->i; ++i) { + for (m = &_mmi, i = 0; i < m->i; ++i) { x = m->p[i].x; y = m->p[i].y; p = __asan_format_interval(p, x << 16, (y << 16) + (FRAMESIZE - 1)); @@ -1396,7 +1394,7 @@ void __asan_map_shadow(uintptr_t p, size_t n) { kprintf("error: %p size %'zu overlaps shadow space\n", p, n); _Exit(1); } - m = _weaken(_mmi); + m = &_mmi; a = (0x7fff8000 + (p >> 3)) >> 16; b = (0x7fff8000 + (p >> 3) + (n >> 3) + 0xffff) >> 16; for (; a <= b; a += i) { @@ -1413,12 +1411,11 @@ void __asan_map_shadow(uintptr_t p, size_t n) { addr = (void *)ADDR_32_TO_48(a); prot = PROT_READ | PROT_WRITE; flag = MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS; - sm = _weaken(sys_mmap)(addr, size, prot, flag, -1, 0); + sm = sys_mmap(addr, size, prot, flag, -1, 0); if (sm.addr == MAP_FAILED || - _weaken(__track_memory)(m, a, a + i - 1, sm.maphandle, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, false, - false, 0, size) == -1) { + __track_memory(m, a, a + i - 1, sm.maphandle, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, false, false, 0, + size) == -1) { kprintf("error: could not map asan shadow memory\n"); __asan_die()(); __asan_unreachable(); @@ -1539,9 +1536,6 @@ void __asan_init(int argc, char **argv, char **envp, unsigned long *auxv) { __write_str("error: asan binaries require windows10\r\n"); _Exit(0); /* So `make MODE=dbg test` passes w/ Windows7 */ } - REQUIRE(_mmi); - REQUIRE(sys_mmap); - REQUIRE(__track_memory); if (_weaken(hook_malloc) || _weaken(hook_calloc) || _weaken(hook_realloc) || _weaken(hook_realloc_in_place) || _weaken(hook_free) || _weaken(hook_malloc_usable_size)) { diff --git a/libc/intrin/describeflags.c b/libc/intrin/describeflags.c index 2c1d22dad..301d012e2 100644 --- a/libc/intrin/describeflags.c +++ b/libc/intrin/describeflags.c @@ -18,7 +18,6 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/intrin/describeflags.internal.h" -// TODO(jart): Fork this function into ASAN and non-ASAN versions. const char *DescribeFlags(char *p, size_t n, const struct DescribeFlags *d, size_t m, const char *prefix, unsigned x) { bool t; diff --git a/libc/intrin/findmemoryinterval.c b/libc/intrin/findmemoryinterval.c index 6cda0f792..60e089bae 100644 --- a/libc/intrin/findmemoryinterval.c +++ b/libc/intrin/findmemoryinterval.c @@ -16,7 +16,6 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/assert.h" #include "libc/runtime/memtrack.internal.h" dontasan unsigned __find_memory(const struct MemoryIntervals *mm, int x) { @@ -31,6 +30,5 @@ dontasan unsigned __find_memory(const struct MemoryIntervals *mm, int x) { r = m; } } - unassert(l == mm->i || x <= mm->p[l].y); return l; } diff --git a/libc/runtime/clone.c b/libc/runtime/clone.c index 9d011753f..3688adc8d 100644 --- a/libc/runtime/clone.c +++ b/libc/runtime/clone.c @@ -487,6 +487,10 @@ static errno_t CloneSilicon(int (*fn)(void *, int), char *stk, size_t stksz, if (!(res = __syslib->pthread_create(&th, 0, SiliconThreadMain, wt)) && (flags & CLONE_PARENT_SETTID)) { *ptid = tid; + if (flags & CLONE_SETTLS) { + struct CosmoTib *tib = tls; + tib[-1].tib_pthread = th; + } } return res; } diff --git a/libc/runtime/syslib.internal.h b/libc/runtime/syslib.internal.h index 03b37438a..27f982385 100644 --- a/libc/runtime/syslib.internal.h +++ b/libc/runtime/syslib.internal.h @@ -16,7 +16,7 @@ COSMOPOLITAN_C_START_ */ #define SYSLIB_MAGIC ('s' | 'l' << 8 | 'i' << 16 | 'b' << 24) -#define SYSLIB_VERSION 1 +#define SYSLIB_VERSION 2 typedef uint64_t dispatch_time_t; typedef uint64_t dispatch_semaphore_t; @@ -42,6 +42,8 @@ struct Syslib { long (*dispatch_semaphore_signal)(dispatch_semaphore_t); long (*dispatch_semaphore_wait)(dispatch_semaphore_t, dispatch_time_t); dispatch_time_t (*dispatch_walltime)(const struct timespec *, int64_t); + /* v2 (2023-09-10) */ + long (*pthread_self)(void); }; extern struct Syslib *__syslib; diff --git a/libc/sysv/dos2errno.sh b/libc/sysv/dos2errno.sh index c12e8995d..76f43441a 100755 --- a/libc/sysv/dos2errno.sh +++ b/libc/sysv/dos2errno.sh @@ -89,6 +89,8 @@ dos kNtErrorUnexpNetErr ECONNABORTED dos kNtErrorWorkingSetQuota ENOMEM dos kNtErrorWriteProtect EACCES dos kNtErrorWrongDisk EACCES +dos kNtErrorExeMarkedInvalid ENOEXEC +dos kNtErrorExeMachineTypeMismatch ENOEXEC dos WSAEACCES EACCES dos WSAEDISCON EPIPE dos WSAEFAULT EFAULT diff --git a/libc/sysv/dos2errno/ENOEXEC.S b/libc/sysv/dos2errno/ENOEXEC.S new file mode 100644 index 000000000..8d1ebe73f --- /dev/null +++ b/libc/sysv/dos2errno/ENOEXEC.S @@ -0,0 +1,15 @@ +// generated by libc/sysv/dos2errno.sh +#include "libc/nt/errors.h" +#ifndef __x86_64__ + .end +#endif + .macro .e doscode systemv + .short \doscode + .long \systemv + .endm + .section .sort.rodata.dos2errno.2,"a",@progbits + .globl kDos2Errno.ENOEXEC + .type kDos2Errno.ENOEXEC,@object +kDos2Errno.ENOEXEC: + .e kNtErrorExeMarkedInvalid,ENOEXEC + .e kNtErrorExeMachineTypeMismatch,ENOEXEC diff --git a/libc/testlib/extract.c b/libc/testlib/extract.c index 4d6bcf194..eac75e905 100644 --- a/libc/testlib/extract.c +++ b/libc/testlib/extract.c @@ -17,6 +17,8 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/runtime/runtime.h" +#include "libc/stdio/stdio.h" #include "libc/sysv/consts/o.h" #include "libc/testlib/testlib.h" @@ -31,9 +33,24 @@ __static_yoink("zipos"); */ void testlib_extract(const char *zip, const char *to, int mode) { int fdin, fdout; - ASSERT_NE(-1, (fdin = open(zip, O_RDONLY))); - ASSERT_NE(-1, (fdout = creat(to, mode))); - ASSERT_NE(-1, copyfd(fdin, fdout, -1)); - ASSERT_NE(-1, close(fdout)); - ASSERT_NE(-1, close(fdin)); + if ((fdin = open(zip, O_RDONLY)) == -1) { + perror(zip); + exit(1); + } + if ((fdout = creat(to, mode)) == -1) { + perror(to); + exit(1); + } + if (copyfd(fdin, fdout, -1) == -1) { + perror(zip); + exit(1); + } + if (close(fdout)) { + perror(to); + exit(1); + } + if (close(fdin)) { + perror(zip); + exit(1); + } } diff --git a/libc/thread/posixthread.internal.h b/libc/thread/posixthread.internal.h index 6e6dec6a4..418887978 100644 --- a/libc/thread/posixthread.internal.h +++ b/libc/thread/posixthread.internal.h @@ -79,6 +79,7 @@ struct PosixThread { char *tls; // bottom of tls allocation struct CosmoTib *tib; // middle of tls allocation struct Dll list; // list of threads + pthread_t next; // for xnu silicon jmp_buf exiter; // for pthread_exit pthread_attr_t attr; struct _pthread_cleanup_buffer *cleanup; diff --git a/libc/thread/pthread_create.c b/libc/thread/pthread_create.c index fb58ded0a..9f9f3ba84 100644 --- a/libc/thread/pthread_create.c +++ b/libc/thread/pthread_create.c @@ -78,6 +78,7 @@ static int PosixThread(void *arg, int tid) { } // set long jump handler so pthread_exit can bring control back here if (!setjmp(pt->exiter)) { + pt->next = __get_tls()->tib_pthread; __get_tls()->tib_pthread = (pthread_t)pt; unassert(!sigprocmask(SIG_SETMASK, (sigset_t *)pt->attr.__sigmask, 0)); rc = pt->start(pt->arg); diff --git a/test/libc/calls/execve_test.c b/test/libc/calls/execve_test.c index f972b6cda..fe0a48246 100644 --- a/test/libc/calls/execve_test.c +++ b/test/libc/calls/execve_test.c @@ -31,8 +31,6 @@ #include "libc/testlib/subprocess.h" #include "libc/testlib/testlib.h" -#ifdef __x86_64__ - #define N 16 char *GenBuf(char buf[8], int x) { @@ -69,6 +67,7 @@ TEST(execve, testArgPassing) { } TEST(execve, ziposELF) { + if (1) return; // TODO: rewrite if (IsFreebsd()) return; // TODO: fixme on freebsd if (IsLinux() && !__is_linux_2_6_23()) return; // TODO: fixme on old linux if (!IsLinux() && !IsFreebsd()) { @@ -83,6 +82,7 @@ TEST(execve, ziposELF) { } TEST(execve, ziposAPE) { + if (1) return; // TODO: rewrite if (IsFreebsd()) return; // TODO: fixme on freebsd if (IsLinux() && !__is_linux_2_6_23()) return; // TODO: fixme on old linux if (!IsLinux() && !IsFreebsd()) { @@ -150,5 +150,3 @@ BENCH(execve, bench) { EZBENCH2("execve", donothing, ExecveTinyElf(path)); unlink(path); } - -#endif diff --git a/test/libc/calls/ioctl_test.c b/test/libc/calls/ioctl_test.c index 1b4cfe4ca..bd2f35749 100644 --- a/test/libc/calls/ioctl_test.c +++ b/test/libc/calls/ioctl_test.c @@ -72,7 +72,6 @@ TEST(siocgifconf, test) { ASSERT_NE(-1, close(socketfd)); } -#ifdef __x86_64__ TEST(siocgifconf, mkntenvblock_systemroot) { if (__argc != 1) return; SPAWN(fork); @@ -81,7 +80,6 @@ TEST(siocgifconf, mkntenvblock_systemroot) { abort(); EXITS(0); } -#endif TEST(fionread, pipe) { int pfds[2]; diff --git a/test/libc/calls/sched_getaffinity_test.c b/test/libc/calls/sched_getaffinity_test.c index 1c20849ea..cdd43d0c4 100644 --- a/test/libc/calls/sched_getaffinity_test.c +++ b/test/libc/calls/sched_getaffinity_test.c @@ -89,7 +89,6 @@ __attribute__((__constructor__)) static void init(void) { } } -#ifdef __x86_64__ TEST(sched_setaffinity, isInheritedAcrossExecve) { cpu_set_t x; CPU_ZERO(&x); @@ -104,7 +103,6 @@ TEST(sched_setaffinity, isInheritedAcrossExecve) { EXPECT_TRUE(WIFEXITED(ws)); EXPECT_EQ(42, WEXITSTATUS(ws)); } -#endif /* __x86_64__ */ TEST(sched_getaffinity, getpid) { cpu_set_t x, y; diff --git a/test/libc/mem/test.mk b/test/libc/mem/test.mk index b0bcef5b1..07e5a3e58 100644 --- a/test/libc/mem/test.mk +++ b/test/libc/mem/test.mk @@ -90,7 +90,7 @@ o/$(MODE)/test/libc/mem/prog/life.elf: \ o/$(MODE)/test/libc/mem/prog/life.elf @$(COMPILE) -wAASSIMILATE -T$@ \ $(VM) \ - o/$(MODE)/tool/build/assimilate.com -cf \ + o/$(MODE)/tool/build/assimilate.com -bcef \ o/$(MODE)/test/libc/mem/prog/life.elf o/$(MODE)/test/libc/mem/prog/life.elf.zip.o: private \ @@ -99,6 +99,12 @@ o/$(MODE)/test/libc/mem/prog/life.elf.zip.o: private \ ################################################################################ +o/$(MODE)/test/libc/mem/prog/life.com.zip.o: private \ + ZIPOBJ_FLAGS += \ + -B + +################################################################################ + o/$(MODE)/test/libc/mem/prog/sock.com.dbg: \ $(LIBC_RUNTIME) \ $(LIBC_SOCK) \ @@ -117,7 +123,7 @@ o/$(MODE)/test/libc/mem/prog/sock.elf: \ o/$(MODE)/test/libc/mem/prog/sock.elf @$(COMPILE) -wAASSIMILATE -T$@ \ $(VM) \ - o/$(MODE)/tool/build/assimilate.com -cf \ + o/$(MODE)/tool/build/assimilate.com -cef \ o/$(MODE)/test/libc/mem/prog/sock.elf o/$(MODE)/test/libc/mem/prog/sock.elf.zip.o: private \ diff --git a/test/libc/stdio/posix_spawn_test.c b/test/libc/stdio/posix_spawn_test.c index ec9c03394..11e730924 100644 --- a/test/libc/stdio/posix_spawn_test.c +++ b/test/libc/stdio/posix_spawn_test.c @@ -17,14 +17,20 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/stdio/posix_spawn.h" +#include "libc/assert.h" #include "libc/atomic.h" #include "libc/calls/calls.h" #include "libc/calls/state.internal.h" +#include "libc/calls/struct/rusage.h" #include "libc/calls/struct/sigaction.h" +#include "libc/calls/struct/stat.h" #include "libc/dce.h" #include "libc/fmt/conv.h" +#include "libc/intrin/kprintf.h" #include "libc/intrin/safemacros.internal.h" +#include "libc/limits.h" #include "libc/mem/gc.h" +#include "libc/mem/gc.internal.h" #include "libc/mem/mem.h" #include "libc/runtime/internal.h" #include "libc/runtime/memtrack.internal.h" @@ -33,15 +39,52 @@ #include "libc/str/str.h" #include "libc/sysv/consts/auxv.h" #include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/rusage.h" #include "libc/sysv/consts/sig.h" #include "libc/testlib/ezbench.h" #include "libc/testlib/testlib.h" #include "libc/thread/thread.h" #include "third_party/nsync/mu.h" -#ifdef __x86_64__ + +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 ☺    - 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*_joutput) { SendOutputFragmentMessage(kRunitStderr, client->output, appendz(client->output).i); @@ -570,7 +570,7 @@ RetryOnEtxtbsyRaceCondition: WARNF("killing %d %s and hanging up %d due to interrupt", client->fd, exename, client->pid); HangupClientAndTerminateJob: - SendProgramOutut(client); + SendProgramOutput(client); mbedtls_ssl_close_notify(&ezssl); TerminateJob: PrintProgramOutput(client); @@ -687,7 +687,7 @@ WaitAgain: AppendResourceReport(&client->output, &rusage, "\n"); PrintProgramOutput(client); } - SendProgramOutut(client); + SendProgramOutput(client); SendExitMessage(exitcode); mbedtls_ssl_close_notify(&ezssl); if (etxtbsy_tries) {