| git.druid.rocks | index | druid520 | mp | src/ | runsh.c |
src/runsh.c
#define _POSIX_C_SOURCE 200809L
#define _NETBSD_SOURCE 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include "mpx.h"
void
cmd_runsh(int argc, char** argv)
{
pid_t pid;
int status;
const char* shell;
static char buf[MAXFILE];
static char expbuf[MAXFILE];
static char secbuf[MAXFILE];
/* static, not stack: MAXKV * (MAXKEY+MAXVAL) is ~139KB -- the only
* frame in mpx that would otherwise breach the build's
* -Wstack-usage/-Wframe-larger-than limits. cmd_runsh runs at most
* once per process (mpx.c main dispatches straight into it) and
* parseconf fully initializes every entry it returns, so static
* storage is safe. */
static struct kv kv[MAXKV];
long n;
long expn;
long secn;
long nkv;
const char* pkgname;
const char* target;
const char* cc;
const char* ar;
const char* ranlib;
const char* strp;
const char* host;
const char* sysroot;
char sysrootflag[MAXVAL] = {0};
const char* arch;
const char* microarch;
char archflag[MAXVAL] = {0};
const char* prefix;
char prefixflag[MAXVAL] = {0};
const char* depprefixes;
const char* jobs;
long njobs;
char jobsflag[MAXVAL];
char njobsbuf[32];
const char* sandbox;
if(argc < 4)
{
badusage(argv[0], "runsh <dir> <script> [NAME=VAL ...]");
}
shell = "/bin/sh";
target = "native";
cc = NULL;
ar = NULL;
ranlib = NULL;
strp = NULL;
host = NULL;
sysroot = NULL;
arch = NULL;
microarch = NULL;
jobs = NULL;
sandbox = NULL;
pkgname = argval(argc, argv, "MP_PKG");
{
/* MP_MPXCONF (set by mplib::hooks::port_env_args only while a
* with_sysroot_scope callback is active) lets a sysroot have its
* own TARGET_CC/JOBS/MICROARCH/SANDBOX overrides distinct from
* the default root's -- every other WD-derived concept (db,
* manifest, hooks dir) already gets this same per-sysroot
* isolation; this file was the one config source still silently
* shared by every sysroot and the default root alike. absent
* (the default-root case, unchanged from before) or naming a
* file that doesn't exist yet (a sysroot with no override of its
* own), falls back to the single global /etc/mpx.conf exactly
* as always. */
const char* mpxconf = argval(argc, argv, "MP_MPXCONF");
if(mpxconf == NULL || mpxconf[0] == '\0')
{
mpxconf = "/etc/mpx.conf";
}
n = readfile(mpxconf, buf, (long)sizeof(buf) - 1);
if(n == -1 && strcmp(mpxconf, "/etc/mpx.conf") != 0)
{
n = readfile("/etc/mpx.conf", buf, (long)sizeof(buf) - 1);
}
}
if(n == -2)
{
/* unlike a missing mpx.conf (n == -1, silently optional --
* plenty of setups have no per-package overrides at all), a
* truncated read must not be silently treated the same way: it
* would silently drop whatever TARGET_/JOBS/sandbox overrides
* fall past the cut, for every package whose section comes
* after that point, with zero diagnostic. */
die("mpx.conf is too large to read (increase MAXFILE)");
}
if(n >= 0)
{
/* IMPORT=<path1> <path2> ... (see conf.c's expand_imports): lets
* a large, complex mpx.conf be split across multiple files
* instead of growing one file without bound. a no-op (buf used
* as-is) when there's nothing to import. */
expn = expand_imports(buf, n, expbuf, sizeof(expbuf));
if(expn > 0)
{
memcpy(buf, expbuf, (size_t)expn);
n = expn;
}
}
if(n >= 0)
{
/* a per-package "<name>:" section in mpx.conf overrides any of
* these same keys globally, exactly like mp.conf's per-package
* sections -- extractsection folds pkgname's section (if any)
* and the global lines into one buffer, section first so
* kvget's first-match-wins picks the override. */
secn = extractsection(buf, n, pkgname, secbuf, (long)sizeof(secbuf));
nkv = parseconf(secbuf, secn, kv, MAXKV);
if(kvget(kv, nkv, "SHELL") != NULL && kvget(kv, nkv, "SHELL")[0] != '\0'
&& strlen(kvget(kv, nkv, "SHELL")) < (size_t)MAXPATH)
{
shell = kvget(kv, nkv, "SHELL");
}
if(kvget(kv, nkv, "TARGET") != NULL && kvget(kv, nkv, "TARGET")[0] != '\0')
{
target = kvget(kv, nkv, "TARGET");
}
cc = kvget(kv, nkv, "TARGET_CC");
ar = kvget(kv, nkv, "TARGET_AR");
ranlib = kvget(kv, nkv, "TARGET_RANLIB");
strp = kvget(kv, nkv, "TARGET_STRIP");
host = kvget(kv, nkv, "TARGET_HOST");
sysroot = kvget(kv, nkv, "TARGET_SYSROOT");
arch = kvget(kv, nkv, "ARCH");
microarch = kvget(kv, nkv, "MICROARCH");
jobs = kvget(kv, nkv, "JOBS");
sandbox = kvget(kv, nkv, "SANDBOX");
}
/* a MP_SANDBOX=yes/no trailing arg (set by mp itself for the fetch
* phase and for legacy ports, which need real network) overrides
* whatever mpx.conf says -- mpx has no other way to know those. */
if(argval(argc, argv, "MP_SANDBOX") != NULL)
{
sandbox = argval(argc, argv, "MP_SANDBOX");
}
/* only ever read inside "#ifdef __linux__" below (sandboxing is a
* no-op on other platforms); this keeps a non-Linux build warning
* free without scattering more ifdefs through the config reads. */
(void)sandbox;
njobs = 0;
if(jobs != NULL && jobs[0] != '\0')
{
njobs = atol(jobs);
}
#ifdef _SC_NPROCESSORS_ONLN
if(njobs <= 0)
{
njobs = (long)sysconf(_SC_NPROCESSORS_ONLN);
}
#endif
if(njobs <= 0)
{
njobs = 1;
}
pid = fork();
if(pid < 0)
{
die("fork failed");
}
if(pid == 0)
{
if(chdir(argv[2]) != 0)
{
_exit(1);
}
setenv("MP_TARGET", target, 1);
setxenv("CC", cc);
setxenv("AR", ar);
setxenv("RANLIB", ranlib);
setxenv("STRIP", strp);
snprintf(njobsbuf, sizeof(njobsbuf), "%ld", njobs);
setenv("MP_JOBS", njobsbuf, 1);
snprintf(jobsflag, sizeof(jobsflag), "-j%ld", njobs);
setflags("MAKEFLAGS", jobsflag);
setxenv("MP_HOST", host);
if(sysroot != NULL && sysroot[0] != '\0')
{
size_t off = 0;
if(bufcat(sysrootflag, sizeof(sysrootflag), &off, "--sysroot=") == 0
|| bufcat(sysrootflag, sizeof(sysrootflag), &off, sysroot) == 0)
{
_exit(1);
}
setenv("MP_SYSROOT", sysroot, 1);
setflags("CFLAGS", sysrootflag);
/* CPPFLAGS too, not just CFLAGS: autoconf's own AC_PROG_CPP
* sanity check (and anything else invoking $CPP/$CC -E
* directly) reads CPPFLAGS specifically, never CFLAGS - a
* --sysroot affects header search exactly the same way a
* plain -I does, so it needs to reach that probe too. */
setflags("CPPFLAGS", sysrootflag);
setflags("LDFLAGS", sysrootflag);
/* qemu-user's own documented env var for "where to find the
* target's dynamic linker/shared libs" (identical to its -L
* flag) -- lets a check: phase's cross-built test binaries,
* if the host has qemu-user-binfmt registered for the target
* architecture (a host prerequisite mpx does not manage
* itself, the same way a host-provided git/shell already
* are), run transparently under emulation and still find
* their real target libraries instead of the host's. a
* no-op for anything not actually routed through qemu. */
setenv("QEMU_LD_PREFIX", sysroot, 1);
}
setxenv("MP_ARCH", arch);
if(microarch != NULL && microarch[0] != '\0')
{
size_t off = 0;
if(bufcat(archflag, sizeof(archflag), &off, "-march=") == 0
|| bufcat(archflag, sizeof(archflag), &off, microarch) == 0)
{
_exit(1);
}
setenv("MP_MICROARCH", microarch, 1);
setflags("CFLAGS", archflag);
}
/* every mp-managed dependency lands under MP_PREFIX (or, for a
* slotted build, MP_PREFIX_BASE -- the pre-slot prefix other
* packages still install flatly into), which a build's own
* ./configure has no way to know to search: it only receives
* --prefix, telling it where to *install*, not where to find
* already-installed headers/libs. without this, a build can
* silently fail to detect a real, already-installed dependency
* and either error out or silently degrade. */
prefix = argval(argc, argv, "MP_PREFIX_BASE");
if(prefix == NULL)
{
prefix = argval(argc, argv, "MP_PREFIX");
}
if(prefix == NULL)
{
prefix = getenv("MP_PREFIX");
}
if(prefix != NULL && prefix[0] != '\0')
{
/* an rpath here matters whenever MP_PREFIX isn't already on
* the dynamic linker's default search path -- true for the
* common case (a real /usr/local-ish prefix, usually already
* configured system-wide), but NOT for a sysroot's own
* MP_PREFIX (e.g. /var/mp-sysroots/name), which is exactly
* the kind of non-standard path two unslotted packages
* within the SAME sysroot need to find each other's shared
* libs at runtime without it. matches the rpath the
* MP_DEP_PREFIXES loop below already adds per slotted
* dependency -- this is the same thing for the base prefix
* itself. */
size_t off = 0;
if(bufcat(prefixflag, sizeof(prefixflag), &off, "-I") == 0
|| bufcat(prefixflag, sizeof(prefixflag), &off, prefix) == 0
|| bufcat(prefixflag, sizeof(prefixflag), &off, "/include") == 0)
{
_exit(1);
}
setflags("CFLAGS", prefixflag);
/* CPPFLAGS too - see the --sysroot comment above for why:
* autoconf's AC_PROG_CPP (and anything else compiling a
* probe with a bare "$CC -E") reads CPPFLAGS, not CFLAGS,
* so a build whose ./configure never finds MP_PREFIX's own
* headers without this -I explicitly present here too. */
setflags("CPPFLAGS", prefixflag);
off = 0;
if(bufcat(prefixflag, sizeof(prefixflag), &off, "-L") == 0
|| bufcat(prefixflag, sizeof(prefixflag), &off, prefix) == 0
|| bufcat(prefixflag, sizeof(prefixflag), &off, "/lib -Wl,-rpath,") == 0
|| bufcat(prefixflag, sizeof(prefixflag), &off, prefix) == 0
|| bufcat(prefixflag, sizeof(prefixflag), &off, "/lib") == 0)
{
_exit(1);
}
setflags("LDFLAGS", prefixflag);
}
/* buildlink-style wiring: one -I/-L/rpath/PKG_CONFIG_PATH entry
* per colon-separated slotted-dependency prefix mp resolved for
* this package (mplib::resolve::dep_slot_prefixes), so a build
* links against exactly the dependency variant it declared
* instead of whatever else sits on the default search path. */
depprefixes = argval(argc, argv, "MP_DEP_PREFIXES");
if(depprefixes != NULL && depprefixes[0] != '\0')
{
char depbuf[MAXVAL];
char depflag[MAXVAL] = {0};
char* save;
char* tok;
{
size_t dn = strlen(depprefixes);
if(dn > sizeof(depbuf) - 1)
{
die("MP_DEP_PREFIXES too long");
}
memcpy(depbuf, depprefixes, dn);
depbuf[dn] = '\0';
}
for(tok = strtok_r(depbuf, ":", &save); tok != NULL; tok = strtok_r(NULL, ":", &save))
{
/* atomic skip, pre-checked like before the format-free
* rewrite: a token that can't feed all three flags is
* dropped wholesale, never partially wired (the old
* heuristic bound; the bufcat checks below then provably
* succeed). */
if(tok[0] == '\0' || strlen(tok) > ((size_t)MAXVAL - 32) / 2)
{
continue;
}
{
size_t off = 0;
if(bufcat(depflag, sizeof(depflag), &off, "-I") == 0
|| bufcat(depflag, sizeof(depflag), &off, tok) == 0
|| bufcat(depflag, sizeof(depflag), &off, "/include") == 0)
{
continue;
}
setflags("CFLAGS", depflag);
/* CPPFLAGS too - same AC_PROG_CPP reasoning as the
* two setflags("CPPFLAGS", ...) calls above. */
setflags("CPPFLAGS", depflag);
off = 0;
if(bufcat(depflag, sizeof(depflag), &off, "-L") == 0
|| bufcat(depflag, sizeof(depflag), &off, tok) == 0
|| bufcat(depflag, sizeof(depflag), &off, "/lib -Wl,-rpath,") == 0
|| bufcat(depflag, sizeof(depflag), &off, tok) == 0
|| bufcat(depflag, sizeof(depflag), &off, "/lib") == 0)
{
continue;
}
setflags("LDFLAGS", depflag);
off = 0;
if(bufcat(depflag, sizeof(depflag), &off, tok) == 0
|| bufcat(depflag, sizeof(depflag), &off, "/lib/pkgconfig:") == 0
|| bufcat(depflag, sizeof(depflag), &off, tok) == 0
|| bufcat(depflag, sizeof(depflag), &off, "/share/pkgconfig") == 0)
{
continue;
}
setpath("PKG_CONFIG_PATH", depflag);
}
}
}
{
int ei;
for(ei = 4; ei < argc; ei = ei + 1)
{
char* eq;
eq = strchr(argv[ei], '=');
if(eq == NULL)
{
continue;
}
*eq = '\0';
if(argv[ei][0] != '\0')
{
setenv(argv[ei], eq + 1, 1);
}
*eq = '=';
}
}
#ifdef __linux__
if(sandbox == NULL || !(sandbox[0] == 'n' || sandbox[0] == 'N' || sandbox[0] == '0'))
{
setup_sandbox(argv[2], prefix);
}
#endif
/* /etc/mpx.env, if present, is sourced into this wrapper shell
* before the real phase script runs -- an arbitrary shell
* script (unlike mpx.conf's fixed key=value grammar), so it
* can export any env var or run any setup a build needs, not
* just the specific TARGET_/JOBS/ARCH/SANDBOX keys mpx.conf
* itself understands. exec (not a second fork) then replaces
* this wrapper with the exact same "$SHELL -e <script>"
* invocation as before mpx.env existed, keeping every
* exported var from it (exec preserves the environment) while
* leaving argv[0]/exit-status semantics for the phase script
* itself completely unchanged. a failing command inside
* mpx.env aborts here (this wrapper also runs under -e), same
* "fail loud, not silent" rule as everything else in mpx --
* absent entirely (the common case) is silently a no-op. */
execl(shell, shell, "-e", "-c",
"[ -f /etc/mpx.env ] && . /etc/mpx.env; exec \"$1\" -e \"$2\"",
shell, shell, argv[3], (char*)NULL);
_exit(1);
}
if(waitpid(pid, &status, 0) < 0)
{
die("waitpid failed");
}
if(!WIFEXITED(status) || WEXITSTATUS(status) != 0)
{
exit(1);
}
}