diff options
Diffstat (limited to 'src/kmk/kmkbuiltin/redirect.c')
-rw-r--r-- | src/kmk/kmkbuiltin/redirect.c | 2066 |
1 files changed, 2066 insertions, 0 deletions
diff --git a/src/kmk/kmkbuiltin/redirect.c b/src/kmk/kmkbuiltin/redirect.c new file mode 100644 index 0000000..f0d97d2 --- /dev/null +++ b/src/kmk/kmkbuiltin/redirect.c @@ -0,0 +1,2066 @@ +/* $Id: redirect.c 3564 2022-03-08 11:12:18Z bird $ */ +/** @file + * kmk_redirect - Do simple program <-> file redirection (++). + */ + +/* + * Copyright (c) 2007-2016 knut st. osmundsen <bird-kBuild-spamx@anduin.net> + * + * This file is part of kBuild. + * + * kBuild is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * kBuild is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with kBuild. If not, see <http://www.gnu.org/licenses/> + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#if defined(__APPLE__) +/*# define _POSIX_C_SOURCE 1 / * 10.4 sdk and unsetenv * / - breaks O_CLOEXEC on 10.8 */ +#endif +#include "makeint.h" +#include <assert.h> +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#if defined(KBUILD_OS_WINDOWS) || defined(KBUILD_OS_OS2) +# include <process.h> +#endif +#ifdef KBUILD_OS_WINDOWS +# include <Windows.h> +#endif +#if defined(_MSC_VER) +# include <ctype.h> +# include <io.h> +# include "quote_argv.h" +#else +# ifdef __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ +# if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050 +# define USE_POSIX_SPAWN +# endif +# elif !defined(KBUILD_OS_WINDOWS) && !defined(KBUILD_OS_OS2) +# define USE_POSIX_SPAWN +# endif +# include <unistd.h> +# ifdef USE_POSIX_SPAWN +# include <spawn.h> +# endif +# include <sys/wait.h> +#endif + +#include <k/kDefs.h> +#include <k/kTypes.h> +#include "err.h" +#include "kbuild_version.h" +#ifdef KBUILD_OS_WINDOWS +# include "nt/nt_child_inject_standard_handles.h" +#endif +#if defined(__gnu_hurd__) && !defined(KMK_BUILTIN_STANDALONE) /* need constant */ +# undef GET_PATH_MAX +# undef PATH_MAX +# define GET_PATH_MAX PATH_MAX +#endif +#include "kmkbuiltin.h" +#ifdef KMK +# ifdef KBUILD_OS_WINDOWS +# ifndef CONFIG_NEW_WIN_CHILDREN +# include "sub_proc.h" +# else +# include "../w32/winchildren.h" +# endif +# include "pathstuff.h" +# endif +#endif + +#ifdef __OS2__ +# define INCL_BASE +# include <os2.h> +# ifndef LIBPATHSTRICT +# define LIBPATHSTRICT 3 +# endif +#endif + +#ifndef KMK_BUILTIN_STANDALONE +extern void kmk_cache_exec_image_a(const char *); /* imagecache.c */ +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/* String + strlen tuple. */ +#define TUPLE(a_sz) a_sz, sizeof(a_sz) - 1 + +/** Only standard handles on windows. */ +#ifdef KBUILD_OS_WINDOWS +# define ONLY_TARGET_STANDARD_HANDLES +#endif + + +static int kmk_redirect_usage(PKMKBUILTINCTX pCtx, int fIsErr) +{ + /* 0 1 2 3 4 5 6 7 8 */ + /* 012345678901234567890123456789012345678901234567890123456789012345678901234567890 */ + kmk_builtin_ctx_printf(pCtx, fIsErr, + "Usage: %s [-[rwa+tb]<fd> <file>] [-d<fd>=<src-fd>] [-c<fd>] [--stdin-pipe]\n" + " [-Z] [-E <var=val>] [-A <var=val>] [-P <var=val>] [-D <var>]\n" + " [-C <dir>] [--wcc-brain-damage] [-v] -- <program> [args]\n" + " or: %s --help\n" + " or: %s --version\n" + "\n" + "Options:\n" + "-[rwa+tb]<fd> <file>\n" + " The rwa+tb is like for fopen, if not specified it defaults to w+.\n" + " The <fd> is either a number or an alias for the standard handles:\n" + " i = stdin\n" + " o = stdout\n" + " e = stderr\n" + "-d\n" + " The -d switch duplicate the right hand file descriptor (src-fd) to the left\n" + " hand side one (fd). The latter is limited to standard handles on windows.\n" + "-c <fd>, --close <fd>\n" + " The -c switch will close the specified file descriptor. Limited to standard\n" + " handles on windows.\n" + "--stdin-pipe\n" + " The --stdin-pipe switch will replace stdin with the read end of an\n" + " anonymous pipe. This is for tricking things like rsh.exe that blocks\n" + " reading on stdin.\n" + "-Z, --zap-env, --ignore-environment\n" + " The -Z switch zaps the environment.\n" + "-E <var=val>, --set <var=val>, --env <var=val>\n" + " The -E (--set, --env) switch is for making changes to the environment\n" + " in an putenv fashion.\n" + "-A <var=val>, --append <var=val>\n" + " The -A switch appends to an environment variable in a putenv fashion.\n" + "-D <var=val>, --prepend <var=val>\n" + " The -D switch prepends to an environment variable in a putenv fashion.\n" + "-U <var>, --unset <var>\n" + " The -U switch deletes an environment variable.\n" + /* 0 1 2 3 4 5 6 7 8 */ + /* 012345678901234567890123456789012345678901234567890123456789012345678901234567890 */ + "-C <dir>, --chdir <dir>\n" + " The -C switch is for changing the current directory. Please specify an\n" + " absolute program path as it's platform dependent whether this takes\n" + " effect before or after the executable is located.\n" + "--wcc-brain-damage, --watcom-brain-damage\n" + " The --wcc-brain-damage switch is to work around wcc and wcc386\n" + " (Open Watcom) not following normal quoting conventions on Windows and OS/2.\n" + "-v, --verbose\n" + " The -v switch is for making the thing more verbose.\n" + "\n" + "On OS/2 the kernel variables BEGINLIBPATH, ENDLIBPATH and LIBPATHSTRICT can be\n" + "accessed as-if they were regular enviornment variables.\n" + "\n" + "This command was originally just a quick hack to avoid invoking the shell\n" + "on Windows (cygwin) where forking is very expensive and has exhibited\n" + "stability issues on SMP machines. It has since grown into something like\n" + "/usr/bin/env on steroids.\n" + /* 0 1 2 3 4 5 6 7 8 */ + /* 012345678901234567890123456789012345678901234567890123456789012345678901234567890 */ + , + pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName); + return 2; +} + + +/** + * Decoded file descriptor operations. + */ +typedef struct REDIRECTORDERS +{ + enum { + kRedirectOrder_Invalid = 0, + kRedirectOrder_Close, + kRedirectOrder_Open, + kRedirectOrder_Dup + } enmOrder; + /** The target file handle. */ + int fdTarget; + /** The source file name, -1 on close only. + * This is an opened file if pszFilename is set. */ + int fdSource; + /** Whether to remove the file on failure cleanup. */ + int fRemoveOnFailure; + /** The open flags (for O_TEXT/O_BINARY) on windows. */ + int fOpen; + /** The filename - NULL if close only. */ + const char *pszFilename; + /** The other pipe end, needs closing in cleanup. */ + int fdOtherPipeEnd; +#ifndef USE_POSIX_SPAWN + /** Saved file descriptor. */ + int fdSaved; + /** Saved flags. */ + int fSaved; +#endif +} REDIRECTORDERS; + + +static KBOOL kRedirectHasConflict(int fd, unsigned cOrders, REDIRECTORDERS *paOrders) +{ +#ifdef ONLY_TARGET_STANDARD_HANDLES + return fd < 3; +#else + while (cOrders-- > 0) + if (paOrders[cOrders].fdTarget == fd) + return K_TRUE; + return K_FALSE; +#endif +} + + +/** + * Creates a pair of pipe descriptors that does not conflict with any previous + * orders. + * + * The pipe is open with both descriptors being inherited by the child as it's + * supposed to be a dummy pipe for stdin that won't break. + * + * @returns 0 on success, exit code on failure (error message displayed). + * @param pCtx The command execution context. + * @param paFds Where to return the pipe descriptors + * @param cOrders The number of orders. + * @param paOrders The order array. + * @param fdTarget The target descriptor (0). + */ +static int kRedirectCreateStdInPipeWithoutConflict(PKMKBUILTINCTX pCtx, int paFds[2], + unsigned cOrders, REDIRECTORDERS *paOrders, int fdTarget) +{ + struct + { + int aFds[2]; + } aTries[32]; + unsigned cTries = 0; + + while (cTries < K_ELEMENTS(aTries)) + { +#ifdef _MSC_VER + int rc = _pipe(aTries[cTries].aFds, 0, _O_BINARY); +#else + int rc = pipe(aTries[cTries].aFds); +#endif + if (rc >= 0) + { + if ( !kRedirectHasConflict(aTries[cTries].aFds[0], cOrders, paOrders) + && !kRedirectHasConflict(aTries[cTries].aFds[1], cOrders, paOrders) +#ifndef _MSC_VER + && aTries[cTries].aFds[0] != fdTarget + && aTries[cTries].aFds[1] != fdTarget +#endif + ) + { + paFds[0] = aTries[cTries].aFds[0]; + paFds[1] = aTries[cTries].aFds[1]; + + while (cTries-- > 0) + { + close(aTries[cTries].aFds[0]); + close(aTries[cTries].aFds[1]); + } + return 0; + } + } + else + { + err(pCtx, -1, "failed to create stdin pipe (try #%u)", cTries + 1); + break; + } + cTries++; + } + if (cTries >= K_ELEMENTS(aTries)) + errx(pCtx, -1, "failed to find a conflict free pair of pipe descriptor for stdin!"); + + /* cleanup */ + while (cTries-- > 0) + { + close(aTries[cTries].aFds[0]); + close(aTries[cTries].aFds[1]); + } + return 1; +} + + +/** + * Creates a file descriptor for @a pszFilename that does not conflict with any + * previous orders. + * + * We need to be careful that there isn't a close or dup targetting the + * temporary file descriptor we return. Also, we need to take care with the + * descriptor's inheritability. It should only be inheritable if the returned + * descriptor matches the target descriptor (@a fdTarget). + * + * @returns File descriptor on success, -1 & err/errx on failure. + * + * The returned file descriptor is not inherited (i.e. close-on-exec), + * unless it matches @a fdTarget + * + * @param pCtx The command execution context. + * @param pszFilename The filename to open. + * @param fOpen The open flags. + * @param fMode The file creation mode (if applicable). + * @param cOrders The number of orders. + * @param paOrders The order array. + * @param fRemoveOnFailure Whether to remove the file on failure. + * @param fdTarget The target descriptor. + */ +static int kRedirectOpenWithoutConflict(PKMKBUILTINCTX pCtx, const char *pszFilename, int fOpen, mode_t fMode, + unsigned cOrders, REDIRECTORDERS *paOrders, int fRemoveOnFailure, int fdTarget) +{ +#ifdef _O_NOINHERIT + int const fNoInherit = _O_NOINHERIT; +#elif defined(O_NOINHERIT) + int const fNoInherit = O_NOINHERIT; +#elif defined(O_CLOEXEC) + int const fNoInherit = O_CLOEXEC; +#else + int const fNoInherit = 0; +# define USE_FD_CLOEXEC +#endif + int aFdTries[32]; + unsigned cTries; + int fdOpened; + +#ifdef KBUILD_OS_WINDOWS + if (strcmp(pszFilename, "/dev/null") == 0) + pszFilename = "nul"; +#endif + + /* Open it first. */ + fdOpened = open(pszFilename, fOpen | fNoInherit, fMode); + if (fdOpened < 0) + return err(pCtx, -1, "open(%s,%#x,) failed", pszFilename, fOpen); + + /* Check for conflicts. */ + if (!kRedirectHasConflict(fdOpened, cOrders, paOrders)) + { +#ifndef KBUILD_OS_WINDOWS + if (fdOpened != fdTarget) + return fdOpened; +# ifndef USE_FD_CLOEXEC + if (fcntl(fdOpened, F_SETFD, 0) != -1) +# endif +#endif + return fdOpened; + } + + /* + * Do conflict resolving. + */ + cTries = 1; + aFdTries[cTries++] = fdOpened; + while (cTries < K_ELEMENTS(aFdTries)) + { + fdOpened = open(pszFilename, fOpen | fNoInherit, fMode); + if (fdOpened >= 0) + { + if (!kRedirectHasConflict(fdOpened, cOrders, paOrders)) + { +#ifndef KBUILD_OS_WINDOWS +# ifdef USE_FD_CLOEXEC + if ( fdOpened == fdTarget + || fcntl(fdOpened, F_SETFD, FD_CLOEXEC) != -1) +# else + if ( fdOpened != fdTarget + || fcntl(fdOpened, F_SETFD, 0) != -1) +# endif +#endif + { + while (cTries-- > 0) + close(aFdTries[cTries]); + return fdOpened; + } + } + + } + else + { + err(pCtx, -1, "open(%s,%#x,) #%u failed", pszFilename, cTries + 1, fOpen); + break; + } + aFdTries[cTries++] = fdOpened; + } + + /* + * Give up. + */ + if (fdOpened >= 0) + errx(pCtx, -1, "failed to find a conflict free file descriptor for '%s'!", pszFilename); + + while (cTries-- > 0) + close(aFdTries[cTries]); + return -1; +} + + +/** + * Cleans up the file operation orders. + * + * This does not restore stuff, just closes handles we've opened for the child. + * + * @param cOrders Number of file operation orders. + * @param paOrders The file operation orders. + * @param fFailed Set if it's a failure. + */ +static void kRedirectCleanupFdOrders(unsigned cOrders, REDIRECTORDERS *paOrders, KBOOL fFailure) +{ + unsigned i = cOrders; + while (i-- > 0) + { + if ( paOrders[i].enmOrder == kRedirectOrder_Open + && paOrders[i].fdSource != -1) + { + close(paOrders[i].fdSource); + paOrders[i].fdSource = -1; + + if (paOrders[i].fdOtherPipeEnd >= 0) + { + close(paOrders[i].fdOtherPipeEnd); + paOrders[i].fdOtherPipeEnd = -1; + } + + if ( fFailure + && paOrders[i].fRemoveOnFailure + && paOrders[i].pszFilename) + remove(paOrders[i].pszFilename); + } + } +} + +#if !defined(USE_POSIX_SPAWN) && !defined(KBUILD_OS_WINDOWS) + +/** + * Wrapper that chooses between fprintf and kmk_builtin_ctx_printf to get + * an error message to the user. + * + * @param pCtx The command execution context. + * @param pWorkingStdErr Work stderr. + * @param pszFormat The message format string. + * @param ... Format arguments. + */ +static void safe_err_printf(PKMKBUILTINCTX pCtx, FILE *pWorkingStdErr, const char *pszFormat, ...) +{ + char szMsg[4096]; + size_t cchMsg; + va_list va; + + va_start(va, pszFormat); + vsnprintf(szMsg, sizeof(szMsg) - 1, pszFormat, va); + va_end(va); + szMsg[sizeof(szMsg) - 1] = '\0'; + cchMsg = strlen(szMsg); + +#ifdef KMK_BUILTIN_STANDALONE + (void)pCtx; +#else + if (pCtx->pOut && pCtx->pOut->syncout) + output_write_text(pCtx->pOut, 1, szMsg, cchMsg); + else +#endif + fwrite(szMsg, cchMsg, 1, pWorkingStdErr); +} + + +/** + * Saves a file handle to one which isn't inherited and isn't affected by the + * file orders. + * + * @returns 0 on success, non-zero exit code on failure. + * @param pCtx The command execution context. + * @param pToSave Pointer to the file order to save the target + * descriptor of. + * @param cOrders Number of file orders. + * @param paOrders The array of file orders. + * @param ppWorkingStdErr Pointer to a pointer to a working stderr. This will + * get replaced if we're saving stderr, so that we'll + * keep having a working one to report failures to. + */ +static int kRedirectSaveHandle(PKMKBUILTINCTX pCtx, REDIRECTORDERS *pToSave, unsigned cOrders, + REDIRECTORDERS *paOrders, FILE **ppWorkingStdErr) +{ + int fdToSave = pToSave->fdTarget; + int rcRet = 10; + + /* + * First, check if there's actually handle here that needs saving. + */ + pToSave->fSaved = fcntl(pToSave->fdTarget, F_GETFD, 0); + if (pToSave->fSaved != -1) + { + /* + * Try up to 32 times to get a duplicate descriptor that doesn't conflict. + */ + int aFdTries[32]; + int cTries = 0; + do + { + /* Duplicate the handle (windows makes this complicated). */ + int fdDup; + fdDup = dup(fdToSave); + if (fdDup == -1) + { + safe_err_printf(pCtx, *ppWorkingStdErr, "%s: dup(%#x) failed: %u\n", pCtx->pszProgName, fdToSave, strerror(errno)); + break; + } + /* Is the duplicate usable? */ + if (!kRedirectHasConflict(fdDup, cOrders, paOrders)) + { + pToSave->fdSaved = fdDup; + if ( *ppWorkingStdErr == stderr + && fdToSave == fileno(*ppWorkingStdErr)) + { + *ppWorkingStdErr = fdopen(fdDup, "wt"); + if (*ppWorkingStdErr == NULL) + { + safe_err_printf(pCtx, stderr, "%s: fdopen(%d,\"wt\") failed: %s\n", pCtx->pszProgName, fdDup, strerror(errno)); + *ppWorkingStdErr = stderr; + close(fdDup); + break; + } + } + rcRet = 0; + break; + } + + /* Not usuable, stash it and try again. */ + aFdTries[cTries++] = fdDup; + } while (cTries < K_ELEMENTS(aFdTries)); + + /* + * Clean up unused duplicates. + */ + while (cTries-- > 0) + close(aFdTries[cTries]); + } + else + { + /* + * Nothing to save. + */ + pToSave->fdSaved = -1; + rcRet = 0; + } + return rcRet; +} + + +/** + * Restores the target file descriptors affected by the file operation orders. + * + * @param pCtx The command execution context. + * @param cOrders Number of file operation orders. + * @param paOrders The file operation orders. + * @param ppWorkingStdErr Pointer to a pointer to the working stderr. If this + * is one of the saved file descriptors, we'll restore + * it to stderr. + */ +static void kRedirectRestoreFdOrders(PKMKBUILTINCTX pCtx, unsigned cOrders, REDIRECTORDERS *paOrders, FILE **ppWorkingStdErr) +{ + int iSavedErrno = errno; + unsigned i = cOrders; + while (i-- > 0) + { + if (paOrders[i].fdSaved != -1) + { + KBOOL fRestoreStdErr = *ppWorkingStdErr != stderr + && paOrders[i].fdSaved == fileno(*ppWorkingStdErr); + if (dup2(paOrders[i].fdSaved, paOrders[i].fdTarget) != -1) + { + close(paOrders[i].fdSaved); + paOrders[i].fdSaved = -1; + + if (fRestoreStdErr) + { + *ppWorkingStdErr = stderr; + assert(fileno(stderr) == paOrders[i].fdTarget); + } + } + else + safe_err_printf(pCtx, *ppWorkingStdErr, "%s: dup2(%d,%d) failed: %s\n", + pCtx->pszProgName, paOrders[i].fdSaved, paOrders[i].fdTarget, strerror(errno)); + } + + if (paOrders[i].fSaved != -1) + { + if (fcntl(paOrders[i].fdTarget, F_SETFD, paOrders[i].fSaved & FD_CLOEXEC) != -1) + paOrders[i].fSaved = -1; + else + safe_err_printf(pCtx, *ppWorkingStdErr, "%s: fcntl(%d,F_SETFD,%s) failed: %s\n", + pCtx->pszProgName, paOrders[i].fdTarget, paOrders[i].fSaved & FD_CLOEXEC ? "FD_CLOEXEC" : "0", + strerror(errno)); + } + } + errno = iSavedErrno; +} + + +/** + * Executes the file operation orders. + * + * @returns 0 on success, exit code on failure. + * @param pCtx The command execution context. + * @param cOrders Number of file operation orders. + * @param paOrders File operation orders to execute. + * @param ppWorkingStdErr Where to return a working stderr (mainly for + * kRedirectRestoreFdOrders). + */ +static int kRedirectExecFdOrders(PKMKBUILTINCTX pCtx, unsigned cOrders, REDIRECTORDERS *paOrders, FILE **ppWorkingStdErr) +{ + unsigned i; + + *ppWorkingStdErr = stderr; + for (i = 0; i < cOrders; i++) + { + int rcExit = 10; + switch (paOrders[i].enmOrder) + { + case kRedirectOrder_Close: + { + /* If the handle isn't used by any of the following operation, + just mark it as non-inheritable if necessary. */ + int const fdTarget = paOrders[i].fdTarget; + unsigned j; + for (j = i + 1; j < cOrders; j++) + if (paOrders[j].fdTarget == fdTarget) + break; + if (j >= cOrders) + { + paOrders[j].fSaved = fcntl(fdTarget, F_GETFD, 0); + if (paOrders[j].fSaved != -1) + { + if (paOrders[j].fSaved & FD_CLOEXEC) + rcExit = 0; + else if ( fcntl(fdTarget, F_SETFD, FD_CLOEXEC) != -1 + || errno == EBADF) + rcExit = 0; + else + safe_err_printf(pCtx, *ppWorkingStdErr, "%s: fcntl(%d,F_SETFD,FD_CLOEXEC) failed: %s\n", + pCtx->pszProgName, fdTarget, strerror(errno)); + } + else if (errno == EBADF) + rcExit = 0; + else + safe_err_printf(pCtx, *ppWorkingStdErr, "%s: fcntl(%d,F_GETFD,0) failed: %s\n", + pCtx->pszProgName, fdTarget, strerror(errno)); + } + else + rcExit = kRedirectSaveHandle(pCtx, &paOrders[i], cOrders, paOrders, ppWorkingStdErr); + break; + } + + case kRedirectOrder_Dup: + case kRedirectOrder_Open: + rcExit = kRedirectSaveHandle(pCtx, &paOrders[i], cOrders, paOrders, ppWorkingStdErr); + if (rcExit == 0) + { + if (dup2(paOrders[i].fdSource, paOrders[i].fdTarget) != -1) + rcExit = 0; + else + { + if (paOrders[i].enmOrder == kRedirectOrder_Open) + safe_err_printf(pCtx, *ppWorkingStdErr, "%s: dup2(%d [%s],%d) failed: %s\n", pCtx->pszProgName, + paOrders[i].fdSource, paOrders[i].pszFilename, paOrders[i].fdTarget, strerror(errno)); + else + safe_err_printf(pCtx, *ppWorkingStdErr, "%s: dup2(%d,%d) failed: %s\n", + pCtx->pszProgName, paOrders[i].fdSource, paOrders[i].fdTarget, strerror(errno)); + rcExit = 10; + } + } + break; + + default: + safe_err_printf(pCtx, *ppWorkingStdErr, "%s: error! invalid enmOrder=%d\n", pCtx->pszProgName, paOrders[i].enmOrder); + rcExit = 99; + break; + } + + if (rcExit != 0) + { + kRedirectRestoreFdOrders(pCtx, i, paOrders, ppWorkingStdErr); + return rcExit; + } + } + + return 0; +} + +#endif /* !USE_POSIX_SPAWN */ +#ifdef KBUILD_OS_WINDOWS + +/** + * Registers the child process with a care provider or waits on it to complete. + * + * @returns 0 or non-zero success indicator or child exit code, depending on + * the value pfIsChildExitCode points to. + * @param pCtx The command execution context. + * @param hProcess The child process handle. + * @param cVerbosity The verbosity level. + * @param pPidSpawned Where to return the PID of the spawned child + * when we're inside KMK and we're return without + * waiting. + * @param pfIsChildExitCode Where to indicate whether the return exit code + * is from the child or from our setup efforts. + */ +static int kRedirectPostnatalCareOnWindows(PKMKBUILTINCTX pCtx, HANDLE hProcess, unsigned cVerbosity, + pid_t *pPidSpawned, KBOOL *pfIsChildExitCode) +{ + int rcExit; + DWORD dwTmp; + +# ifndef KMK_BUILTIN_STANDALONE + /* + * Try register the child with a childcare provider, i.e. winchildren.c + * or sub_proc.c. + */ +# ifndef CONFIG_NEW_WIN_CHILDREN + if (process_kmk_register_redirect(hProcess, pPidSpawned) == 0) +# else + if ( pPidSpawned + && MkWinChildCreateRedirect((intptr_t)hProcess, pPidSpawned) == 0) +# endif + { + if (cVerbosity > 0) + warnx(pCtx, "debug: spawned %d", *pPidSpawned); + *pfIsChildExitCode = K_FALSE; + return 0; + } +# ifndef CONFIG_NEW_WIN_CHILDREN + warn(pCtx, "sub_proc is out of slots, waiting for child..."); +# else + if (pPidSpawned) + warn(pCtx, "MkWinChildCreateRedirect failed..."); +# endif +# endif + + /* + * Either the provider is outbooked or we're not in a context (like + * standalone) where we get help with waiting and must to it ourselves + */ + dwTmp = WaitForSingleObject(hProcess, INFINITE); + if (dwTmp != WAIT_OBJECT_0) + warnx(pCtx, "WaitForSingleObject failed: %#x\n", dwTmp); + + if (GetExitCodeProcess(hProcess, &dwTmp)) + rcExit = (int)dwTmp; + else + { + warnx(pCtx, "GetExitCodeProcess failed: %u\n", GetLastError()); + TerminateProcess(hProcess, 127); + rcExit = 127; + } + + CloseHandle(hProcess); + *pfIsChildExitCode = K_TRUE; + return rcExit; +} + + +/** + * Tries to locate the executable image. + * + * This isn't quite perfect yet... + * + * @returns pszExecutable or pszBuf with valid string. + * @param pszExecutable The specified executable. + * @param pszBuf Buffer to return a modified path in. + * @param cbBuf Size of return buffer. + * @param pszPath The search path. + */ +static const char *kRedirectCreateProcessWindowsFindImage(const char *pszExecutable, char *pszBuf, size_t cbBuf, + const char *pszPath) +{ + /* + * Analyze the name. + */ + size_t const cchExecutable = strlen(pszExecutable); + BOOL fHavePath = FALSE; + BOOL fHaveSuffix = FALSE; + size_t off = cchExecutable; + while (off > 0) + { + char ch = pszExecutable[--off]; + if (ch == '.') + { + fHaveSuffix = TRUE; + break; + } + if (ch == '\\' || ch == '/' || ch == ':') + { + fHavePath = TRUE; + break; + } + } + if (!fHavePath) + while (off > 0) + { + char ch = pszExecutable[--off]; + if (ch == '\\' || ch == '/' || ch == ':') + { + fHavePath = TRUE; + break; + } + } + /* + * If no path, search the path value. + */ + if (!fHavePath) + { + char *pszFilename; + DWORD cchFound = SearchPathA(pszPath, pszExecutable, fHaveSuffix ? NULL : ".exe", cbBuf, pszBuf, &pszFilename); + if (cchFound) + return pszBuf; + } + + /* + * If no suffix, try add .exe. + */ + if ( !fHaveSuffix + && GetFileAttributesA(pszExecutable) == INVALID_FILE_ATTRIBUTES + && cchExecutable + 4 < cbBuf) + { + memcpy(pszBuf, pszExecutable, cchExecutable); + memcpy(&pszBuf[cchExecutable], ".exe", 5); + if (GetFileAttributesA(pszBuf) != INVALID_FILE_ATTRIBUTES) + return pszBuf; + } + + return pszExecutable; +} + + +/** + * Turns the orders into input for nt_child_inject_standard_handles and + * winchildren.c + * + * @returns 0 on success, non-zero on failure. + * @param pCtx The command execution context. + * @param cOrders Number of file operation orders. + * @param paOrders The file operation orders. + * @param pafReplace Replace (TRUE) or leave alone (FALSE) indicator + * for each of the starndard handles. + * @param pahChild Array of standard handles for injecting into the + * child. Parallel to pafReplace. + */ +static int kRedirectOrderToWindowsHandles(PKMKBUILTINCTX pCtx, unsigned cOrders, REDIRECTORDERS *paOrders, + BOOL pafReplace[3], HANDLE pahChild[3]) +{ + int i; + for (i = 0; i < (int)cOrders; i++) + { + int fdTarget = paOrders[i].fdTarget; + assert(fdTarget >= 0 && fdTarget < 3); + switch (paOrders[i].enmOrder) + { + case kRedirectOrder_Open: + if ( (paOrders[i].fOpen & O_APPEND) + && lseek(paOrders[i].fdSource, 0, SEEK_END) < 0) + return err(pCtx, 10, "lseek-to-end failed on %d (for %d)", paOrders[i].fdSource, fdTarget); + /* fall thru */ + case kRedirectOrder_Dup: + pahChild[fdTarget] = (HANDLE)_get_osfhandle(paOrders[i].fdSource); + if (pahChild[fdTarget] == NULL || pahChild[fdTarget] == INVALID_HANDLE_VALUE) + return err(pCtx, 10, "_get_osfhandle failed on %d (for %d)", paOrders[i].fdSource, fdTarget); + break; + + case kRedirectOrder_Close: + pahChild[fdTarget] = NULL; + break; + + default: + assert(0); + } + pafReplace[fdTarget] = TRUE; + } + return 0; +} + + +/** + * Alternative approach on windows that use CreateProcess and doesn't require + * any serialization wrt handles and CWD. + * + * @returns 0 on success, non-zero on failure to create. + * @param pCtx The command execution context. + * @param pszExecutable The child process executable. + * @param cArgs Number of arguments. + * @param papszArgs The child argument vector. + * @param papszEnvVars The child environment vector. + * @param pszCwd The current working directory of the child. + * @param cOrders Number of file operation orders. + * @param paOrders The file operation orders. + * @param phProcess Where to return process handle. + */ +static int kRedirectCreateProcessWindows(PKMKBUILTINCTX pCtx, const char *pszExecutable, int cArgs, char **papszArgs, + char **papszEnvVars, const char *pszCwd, unsigned cOrders, + REDIRECTORDERS *paOrders, HANDLE *phProcess) +{ + size_t cbArgs; + char *pszCmdLine; + size_t cbEnv; + char *pszzEnv; + char *pch; + int i; + int rc; + + /* + * Start by making the the command line. We just need to put spaces + * between the arguments since quote_argv don't the quoting already. + */ + cbArgs = 0; + for (i = 0; i < cArgs; i++) + cbArgs += strlen(papszArgs[i]) + 1; + pszCmdLine = pch = (char *)malloc(cbArgs); + if (!pszCmdLine) + return errx(pCtx, 9, "out of memory!"); + for (i = 0; i < cArgs; i++) + { + size_t cch; + if (i != 0) + *pch++ = ' '; + cch = strlen(papszArgs[i]); + memcpy(pch, papszArgs[i], cch); + pch += cch; + } + *pch++ = '\0'; + assert(pch - pszCmdLine == cbArgs); + + /* + * The environment vector is also simple. + */ + cbEnv = 0; + for (i = 0; papszEnvVars[i]; i++) + cbEnv += strlen(papszEnvVars[i]) + 1; + cbEnv++; + pszzEnv = pch = (char *)malloc(cbEnv); + if (pszzEnv) + { + char szAbsExe[1024]; + const char *pszPathVal = NULL; + STARTUPINFOA StartupInfo; + PROCESS_INFORMATION ProcInfo = { NULL, NULL, 0, 0 }; + + for (i = 0; papszEnvVars[i]; i++) + { + size_t cbSrc = strlen(papszEnvVars[i]) + 1; + memcpy(pch, papszEnvVars[i], cbSrc); + if ( !pszPathVal + && cbSrc >= 5 + && pch[4] == '=' + && (pch[0] == 'P' || pch[0] == 'p') + && (pch[1] == 'A' || pch[1] == 'a') + && (pch[2] == 'T' || pch[2] == 't') + && (pch[3] == 'H' || pch[3] == 'h')) + pszPathVal = &pch[5]; + pch += cbSrc; + } + *pch++ = '\0'; + assert(pch - pszzEnv == cbEnv); + + /* + * Locate the executable. + */ + pszExecutable = kRedirectCreateProcessWindowsFindImage(pszExecutable, szAbsExe, sizeof(szAbsExe), pszPathVal); + + /* + * Do basic startup info preparation. + */ + memset(&StartupInfo, 0, sizeof(StartupInfo)); + StartupInfo.cb = sizeof(StartupInfo); + GetStartupInfoA(&StartupInfo); + StartupInfo.lpReserved2 = 0; /* No CRT file handle + descriptor info possible, sorry. */ + StartupInfo.cbReserved2 = 0; + StartupInfo.dwFlags &= ~STARTF_USESTDHANDLES; + + /* + * If there are no redirection orders, we're good. + */ + if (!cOrders) + { + if (CreateProcessA(pszExecutable, pszCmdLine, NULL /*pProcAttrs*/, NULL /*pThreadAttrs*/, + FALSE /*fInheritHandles*/, 0 /*fFlags*/, pszzEnv, pszCwd, &StartupInfo, &ProcInfo)) + { + CloseHandle(ProcInfo.hThread); + *phProcess = ProcInfo.hProcess; +# ifndef KMK_BUILTIN_STANDALONE + kmk_cache_exec_image_a(pszExecutable); +# endif + rc = 0; + } + else + rc = errx(pCtx, 10, "CreateProcessA(%s) failed: %u", pszExecutable, GetLastError()); + } + else + { + /* + * Execute the orders, ending up with three handles we need to + * implant into the guest process. + * + * This isn't 100% perfect wrt O_APPEND, but it'll have to do for now. + */ + BOOL afReplace[3] = { FALSE, FALSE, FALSE }; + HANDLE ahChild[3] = { NULL, NULL, NULL }; + rc = kRedirectOrderToWindowsHandles(pCtx, cOrders, paOrders, afReplace, ahChild); + if (rc == 0) + { + /* + * Start the process in suspended animation so we can inject handles. + */ + if (CreateProcessA(pszExecutable, pszCmdLine, NULL /*pProcAttrs*/, NULL /*pThreadAttrs*/, + FALSE /*fInheritHandles*/, CREATE_SUSPENDED, pszzEnv, pszCwd, &StartupInfo, &ProcInfo)) + { + unsigned i; + + /* Inject the handles and try make it start executing. */ + char szErrMsg[128]; + rc = nt_child_inject_standard_handles(ProcInfo.hProcess, afReplace, ahChild, szErrMsg, sizeof(szErrMsg)); + if (rc) + rc = errx(pCtx, 10, "%s", szErrMsg); + else if (!ResumeThread(ProcInfo.hThread)) + rc = errx(pCtx, 10, "ResumeThread failed: %u", GetLastError()); + + /* Duplicate the write end of any stdin pipe handles into the child. */ + for (i = 0; i < cOrders; i++) + if (paOrders[i].fdOtherPipeEnd >= 0) + { + HANDLE hIgnored = INVALID_HANDLE_VALUE; + HANDLE hPipeW = (HANDLE)_get_osfhandle(paOrders[i].fdOtherPipeEnd); + if (!DuplicateHandle(GetCurrentProcess(), hPipeW, ProcInfo.hProcess, &hIgnored, 0 /*fDesiredAccess*/, + TRUE /*fInheritable*/, DUPLICATE_SAME_ACCESS)) + rc = errx(pCtx, 10, "DuplicateHandle failed on other stdin pipe end %d/%p: %u", + paOrders[i].fdOtherPipeEnd, hPipeW, GetLastError()); + } + + /* Kill it if any of that fails. */ + if (rc != 0) + TerminateProcess(ProcInfo.hProcess, rc); + + CloseHandle(ProcInfo.hThread); + *phProcess = ProcInfo.hProcess; +# ifndef KMK_BUILTIN_STANDALONE + kmk_cache_exec_image_a(pszExecutable); +# endif + rc = 0; + } + else + rc = errx(pCtx, 10, "CreateProcessA(%s) failed: %u", pszExecutable, GetLastError()); + } + } + free(pszzEnv); + } + else + rc = errx(pCtx, 9, "out of memory!"); + free(pszCmdLine); + return rc; +} + +# if !defined(KMK_BUILTIN_STANDALONE) && defined(CONFIG_NEW_WIN_CHILDREN) +/** + * Pass the problem on to winchildren.c when we're on one of its workers. + * + * @returns 0 on success, non-zero on failure to create. + * @param pCtx The command execution context. + * @param pszExecutable The child process executable. + * @param cArgs Number of arguments. + * @param papszArgs The child argument vector. + * @param papszEnvVars The child environment vector. + * @param pszCwd The current working directory of the child. + * @param cOrders Number of file operation orders. + * @param paOrders The file operation orders. + * @param phProcess Where to return process handle. + * @param pfIsChildExitCode Where to indicate whether the return exit code + * is from the child or from our setup efforts. + */ +static int kRedirectExecProcessWithinOnWorker(PKMKBUILTINCTX pCtx, const char *pszExecutable, int cArgs, char **papszArgs, + char **papszEnvVars, const char *pszCwd, unsigned cOrders, + REDIRECTORDERS *paOrders, KBOOL *pfIsChildExitCode) +{ + BOOL afReplace[3] = { FALSE, FALSE, FALSE }; + HANDLE ahChild[3] = { NULL, NULL, NULL }; + int rc = kRedirectOrderToWindowsHandles(pCtx, cOrders, paOrders, afReplace, ahChild); + if (rc == 0) + { + rc = MkWinChildBuiltInExecChild(pCtx->pvWorker, pszExecutable, papszArgs, TRUE /*fQuotedArgv*/, + papszEnvVars, pszCwd, afReplace, ahChild); + *pfIsChildExitCode = K_TRUE; + } + return rc; +} +# endif /* !KMK_BUILTIN_STANDALONE */ + +#endif /* KBUILD_OS_WINDOWS */ + +/** + * Does the child spawning . + * + * @returns Exit code. + * @param pCtx The command execution context. + * @param pszExecutable The child process executable. + * @param cArgs Number of arguments. + * @param papszArgs The child argument vector. + * @param fWatcomBrainDamage Whether MSC need to do quoting according to + * weird Watcom WCC rules. + * @param papszEnvVars The child environment vector. + * @param pszCwd The current working directory of the child. + * @param pszSavedCwd The saved current working directory. This is + * NULL if the CWD doesn't need changing. + * @param cOrders Number of file operation orders. + * @param paOrders The file operation orders. + * @param pFileActions The posix_spawn file actions. + * @param cVerbosity The verbosity level. + * @param pPidSpawned Where to return the PID of the spawned child + * when we're inside KMK and we're return without + * waiting. + * @param pfIsChildExitCode Where to indicate whether the return exit code + * is from the child or from our setup efforts. + */ +static int kRedirectDoSpawn(PKMKBUILTINCTX pCtx, const char *pszExecutable, int cArgs, char **papszArgs, int fWatcomBrainDamage, + char **papszEnvVars, const char *pszCwd, const char *pszSavedCwd, + unsigned cOrders, REDIRECTORDERS *paOrders, +#ifdef USE_POSIX_SPAWN + posix_spawn_file_actions_t *pFileActions, +#endif + unsigned cVerbosity, +#ifdef KMK + pid_t *pPidSpawned, +#endif + KBOOL *pfIsChildExitCode) +{ + int rcExit = 0; + int i; +#ifdef _MSC_VER + char **papszArgsOriginal = papszArgs; +#endif + *pfIsChildExitCode = K_FALSE; + +#ifdef _MSC_VER + /* + * Do MSC parameter quoting. + */ + papszArgs = malloc((cArgs + 1) * sizeof(papszArgs[0])); + if (papszArgs) + memcpy(papszArgs, papszArgsOriginal, (cArgs + 1) * sizeof(papszArgs[0])); + else + return errx(pCtx, 9, "out of memory!"); + + rcExit = quote_argv(cArgs, papszArgs, fWatcomBrainDamage, 0 /*fFreeOrLeak*/); + if (rcExit == 0) +#endif + { + /* + * Display what we're about to execute if we're in verbose mode. + */ + if (cVerbosity > 0) + { + for (i = 0; i < cArgs; i++) + warnx(pCtx, "debug: argv[%i]=%s<eos>", i, papszArgs[i]); + for (i = 0; i < (int)cOrders; i++) + switch (paOrders[i].enmOrder) + { + case kRedirectOrder_Close: + warnx(pCtx, "debug: close %d\n", paOrders[i].fdTarget); + break; + case kRedirectOrder_Dup: + warnx(pCtx, "debug: dup %d to %d\n", paOrders[i].fdSource, paOrders[i].fdTarget); + break; + case kRedirectOrder_Open: + warnx(pCtx, "debug: open '%s' (%#x) as [%d ->] %d\n", + paOrders[i].pszFilename, paOrders[i].fOpen, paOrders[i].fdSource, paOrders[i].fdTarget); + break; + default: + warnx(pCtx, "error! invalid enmOrder=%d", paOrders[i].enmOrder); + assert(0); + break; + } + if (pszSavedCwd) + warnx(pCtx, "debug: chdir %s\n", pszCwd); + } + +#ifndef KBUILD_OS_WINDOWS + /* + * Change working directory if so requested. + */ + if (pszSavedCwd) + { + if (chdir(pszCwd) < 0) + rcExit = errx(pCtx, 10, "Failed to change directory to '%s'", pszCwd); + } +#endif /* KBUILD_OS_WINDOWS */ + if (rcExit == 0) + { +# if !defined(USE_POSIX_SPAWN) && !defined(KBUILD_OS_WINDOWS) + /* + * Execute the file orders. + */ + FILE *pWorkingStdErr = NULL; + rcExit = kRedirectExecFdOrders(pCtx, cOrders, paOrders, &pWorkingStdErr); + if (rcExit == 0) +# endif + { +# ifdef KMK + /* + * We're spawning from within kmk. + */ +# ifdef KBUILD_OS_WINDOWS + /* Windows is slightly complicated due to handles and winchildren.c. */ + if (pPidSpawned) + *pPidSpawned = 0; +# ifdef CONFIG_NEW_WIN_CHILDREN + if (pCtx->pvWorker && !pPidSpawned) + rcExit = kRedirectExecProcessWithinOnWorker(pCtx, pszExecutable, cArgs, papszArgs, papszEnvVars, + pszSavedCwd ? pszCwd : NULL, cOrders, paOrders, + pfIsChildExitCode); + else +# endif + { + HANDLE hProcess = INVALID_HANDLE_VALUE; + rcExit = kRedirectCreateProcessWindows(pCtx, pszExecutable, cArgs, papszArgs, papszEnvVars, + pszSavedCwd ? pszCwd : NULL, cOrders, paOrders, &hProcess); + if (rcExit == 0) + rcExit = kRedirectPostnatalCareOnWindows(pCtx, hProcess, cVerbosity, pPidSpawned, pfIsChildExitCode); + } + +# elif defined(KBUILD_OS_OS2) + *pPidSpawned = _spawnvpe(P_NOWAIT, pszExecutable, papszArgs, papszEnvVars); + kRedirectRestoreFdOrders(pCtx, cOrders, paOrders, &pWorkingStdErr); + if (*pPidSpawned != -1) + { + if (cVerbosity > 0) + warnx(pCtx, "debug: spawned %d", *pPidSpawned); + } + else + { + rcExit = err(pCtx, 10, "_spawnvpe(%s) failed", pszExecutable); + *pPidSpawned = 0; + } +# else + rcExit = posix_spawnp(pPidSpawned, pszExecutable, pFileActions, NULL /*pAttr*/, papszArgs, papszEnvVars); + if (rcExit == 0) + { + if (cVerbosity > 0) + warnx(pCtx, "debug: spawned %d", *pPidSpawned); + } + else + { + rcExit = errx(pCtx, 10, "posix_spawnp(%s) failed: %s", pszExecutable, strerror(rcExit)); + *pPidSpawned = 0; + } +# endif + +#else /* !KMK */ + /* + * Spawning from inside the kmk_redirect executable. + */ +# ifdef KBUILD_OS_WINDOWS + HANDLE hProcess = INVALID_HANDLE_VALUE; + rcExit = kRedirectCreateProcessWindows(pCtx, pszExecutable, cArgs, papszArgs, papszEnvVars, + pszSavedCwd ? pszCwd : NULL, cOrders, paOrders, &hProcess); + if (rcExit == 0) + { + DWORD dwWait; + do + dwWait = WaitForSingleObject(hProcess, INFINITE); + while (dwWait == WAIT_IO_COMPLETION || dwWait == WAIT_TIMEOUT); + + dwWait = 11; + if (GetExitCodeProcess(hProcess, &dwWait)) + { + *pfIsChildExitCode = K_TRUE; + rcExit = dwWait; + } + else + rcExit = errx(pCtx, 11, "GetExitCodeProcess(%s) failed: %u", pszExecutable, GetLastError()); + } + +#elif defined(KBUILD_OS_OS2) + errno = 0; + rcExit = (int)_spawnvpe(P_WAIT, pszExecutable, papszArgs, papszEnvVars); + kRedirectRestoreFdOrders(pCtx, cOrders, paOrders, &pWorkingStdErr); + if (rcExit != -1 || errno == 0) + { + *pfIsChildExitCode = K_TRUE; + if (cVerbosity > 0) + warnx(pCtx, "debug: exit code: %d", rcExit); + } + else + rcExit = err(pCtx, 10, "_spawnvpe(%s) failed", pszExecutable); + +# else + pid_t pidChild = 0; + rcExit = posix_spawnp(&pidChild, pszExecutable, pFileActions, NULL /*pAttr*/, papszArgs, papszEnvVars); + if (rcExit == 0) + { + *pfIsChildExitCode = K_TRUE; + if (cVerbosity > 0) + warnx(pCtx, "debug: spawned %d", pidChild); + + /* Wait for the child. */ + for (;;) + { + int rcExitRaw = 1; + pid_t pid = waitpid(pidChild, &rcExitRaw, 0 /*block*/); + if (pid == pidChild) + { + rcExit = WIFEXITED(rcExitRaw) ? WEXITSTATUS(rcExitRaw) : 63; + if (cVerbosity > 0) + warnx(pCtx, "debug: %d exit code: %d (%d)", pidChild, rcExit, rcExitRaw); + break; + } + if ( errno != EINTR +# ifdef ERESTART + && errno != ERESTART +# endif + ) + { + rcExit = err(pCtx, 11, "waitpid failed"); + kill(pidChild, SIGKILL); + break; + } + } + } + else + rcExit = errx(pCtx, 10, "posix_spawnp(%s) failed: %s", pszExecutable, strerror(rcExit)); +# endif +#endif /* !KMK */ + } + } + +#ifndef KBUILD_OS_WINDOWS + /* + * Restore the current directory. + */ + if (pszSavedCwd) + { + if (chdir(pszSavedCwd) < 0) + warn(pCtx, "Failed to restore directory to '%s'", pszSavedCwd); + } +#endif + } +#ifdef _MSC_VER + else + rcExit = errx(pCtx, 9, "quite_argv failed: %u", rcExit); + + /* Restore the original argv strings, freeing the quote_argv replacements. */ + i = cArgs; + while (i-- > 0) + if (papszArgs[i] != papszArgsOriginal[i]) + free(papszArgs[i]); + free(papszArgs); +#endif + return rcExit; +} + + +/** + * The function that does almost everything here... ugly. + */ +int kmk_builtin_redirect(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx, struct child *pChild, pid_t *pPidSpawned) +{ + int rcExit = 0; + KBOOL fChildExitCode = K_FALSE; +#ifdef USE_POSIX_SPAWN + posix_spawn_file_actions_t FileActions; +#endif + unsigned cOrders = 0; + REDIRECTORDERS aOrders[32]; + + int iArg; + const char *pszExecutable = NULL; + char **papszEnvVars = NULL; + unsigned cAllocatedEnvVars; + unsigned cEnvVars; + int fWatcomBrainDamage = 0; + int cVerbosity = 0; + char *pszSavedCwd = NULL; + size_t const cbCwdBuf = GET_PATH_MAX; + PATH_VAR(szCwd); +#ifdef KBUILD_OS_OS2 + ULONG ulLibPath; + char *apszSavedLibPaths[LIBPATHSTRICT + 1] = { NULL, NULL, NULL, NULL }; +#endif + + + if (argc <= 1) + return kmk_redirect_usage(pCtx, 1); + + /* + * Create default program environment. + */ +#if defined(KMK) && defined(KBUILD_OS_WINDOWS) + if (getcwd_fs(szCwd, cbCwdBuf) != NULL) +#else + if (getcwd(szCwd, cbCwdBuf) != NULL) +#endif + { /* likely */ } + else + return err(pCtx, 9, "getcwd failed"); + + /* We start out with a read-only enviornment from kmk or the crt, and will + duplicate it if we make changes to it. */ + cAllocatedEnvVars = 0; + papszEnvVars = envp; + cEnvVars = 0; + while (papszEnvVars[cEnvVars] != NULL) + cEnvVars++; + +#ifdef USE_POSIX_SPAWN + /* + * Init posix attributes with stdout/err redirections according to pCtx. + */ + rcExit = posix_spawn_file_actions_init(&FileActions); + if (rcExit != 0) + rcExit = errx(pCtx, 9, "posix_spawn_file_actions_init failed: %s", strerror(rcExit)); +# if !defined(KMK_BUILTIN_STANDALONE) && !defined(CONFIG_WITH_OUTPUT_IN_MEMORY) + if (pCtx->pOut && rcExit == 0) + { + if (pCtx->pOut->out >= 0) + { + rcExit = posix_spawn_file_actions_adddup2(&FileActions, pCtx->pOut->out, 1); + if (rcExit != 0) + rcExit = errx(pCtx, 2, "posix_spawn_file_actions_addclose(%d, 1) failed: %s", pCtx->pOut->out, strerror(rcExit)); + } + if (pCtx->pOut->err >= 0 && rcExit == 0) + { + rcExit = posix_spawn_file_actions_adddup2(&FileActions, pCtx->pOut->err, 2); + if (rcExit != 0) + rcExit = errx(pCtx, 2, "posix_spawn_file_actions_addclose(%d, 1) failed: %s", pCtx->pOut->err, strerror(rcExit)); + } + } +# endif +#endif + + /* + * Parse arguments. + */ + for (iArg = 1; rcExit == 0 && iArg < argc; iArg++) + { + char *pszArg = argv[iArg]; + if (*pszArg == '-') + { + int fd; + char chOpt; + const char *pszValue; + + chOpt = *++pszArg; + pszArg++; + if (chOpt == '-') + { + /* '--' indicates where the bits to execute start. Check if we're + relaunching ourselves here and just continue parsing if we are. */ + if (*pszArg == '\0') + { + iArg++; + if ( iArg >= argc + || ( strcmp(argv[iArg], "kmk_builtin_redirect") != 0 + && strcmp(argv[iArg], argv[0]) != 0)) + break; + continue; + } + + if ( strcmp(pszArg, "wcc-brain-damage") == 0 + || strcmp(pszArg, "watcom-brain-damage") == 0) + { + fWatcomBrainDamage = 1; + continue; + } + + /* convert to short. */ + if (strcmp(pszArg, "help") == 0) + chOpt = 'h'; + else if (strcmp(pszArg, "version") == 0) + chOpt = 'V'; + else if ( strcmp(pszArg, "set") == 0 + || strcmp(pszArg, "env") == 0) + chOpt = 'E'; + else if (strcmp(pszArg, "append") == 0) + chOpt = 'A'; + else if (strcmp(pszArg, "prepend") == 0) + chOpt = 'D'; + else if (strcmp(pszArg, "unset") == 0) + chOpt = 'U'; + else if ( strcmp(pszArg, "zap-env") == 0 + || strcmp(pszArg, "ignore-environment") == 0 /* GNU env compatibility. */ ) + chOpt = 'Z'; + else if (strcmp(pszArg, "chdir") == 0) + chOpt = 'C'; + else if (strcmp(pszArg, "close") == 0) + chOpt = 'c'; + else if (strcmp(pszArg, "verbose") == 0) + chOpt = 'v'; + else if (strcmp(pszArg, "stdin-pipe") == 0) + chOpt = 'I'; + else + { + errx(pCtx, 2, "Unknown option: '%s'", pszArg - 2); + rcExit = kmk_redirect_usage(pCtx, 1); + break; + } + pszArg = ""; + } + + /* + * Deal with the obligatory help and version switches first to get them out of the way. + */ + if (chOpt == 'h') + { + kmk_redirect_usage(pCtx, 0); + rcExit = -1; + break; + } + if (chOpt == 'V') + { + kbuild_version(argv[0]); + rcExit = -1; + break; + } + + /* + * Get option value first, if the option takes one. + */ + if ( chOpt == 'E' + || chOpt == 'A' + || chOpt == 'D' + || chOpt == 'U' + || chOpt == 'C' + || chOpt == 'c' + || chOpt == 'd' + || chOpt == 'e') + { + if (*pszArg != '\0') + pszValue = pszArg + (*pszArg == ':' || *pszArg == '='); + else if (++iArg < argc) + pszValue = argv[iArg]; + else + { + errx(pCtx, 2, "syntax error: Option -%c requires a value!", chOpt); + rcExit = kmk_redirect_usage(pCtx, 1); + break; + } + } + else + pszValue = NULL; + + /* + * Environment switch? + */ + if (chOpt == 'E') + { + const char *pchEqual = strchr(pszValue, '='); +#ifdef KBUILD_OS_OS2 + if ( strncmp(pszValue, TUPLE("BEGINLIBPATH=")) == 0 + || strncmp(pszValue, TUPLE("ENDLIBPATH=")) == 0 + || strncmp(pszValue, TUPLE("LIBPATHSTRICT=")) == 0) + { + ULONG ulVar = *pszValue == 'B' ? BEGIN_LIBPATH + : *pszValue == 'E' ? END_LIBPATH + : LIBPATHSTRICT; + APIRET rc; + if (apszSavedLibPaths[ulVar] == NULL) + { + /* The max length is supposed to be 1024 bytes. */ + apszSavedLibPaths[ulVar] = calloc(1024, 2); + if (apszSavedLibPaths[ulVar]) + { + rc = DosQueryExtLIBPATH(apszSavedLibPaths[ulVar], ulVar); + if (rc) + { + rcExit = errx(pCtx, 9, "DosQueryExtLIBPATH(,%u) failed: %lu", ulVar, rc); + free(apszSavedLibPaths[ulVar]); + apszSavedLibPaths[ulVar] = NULL; + } + } + else + rcExit = errx(pCtx, 9, "out of memory!"); + } + if (rcExit == 0) + { + rc = DosSetExtLIBPATH(pchEqual + 1, ulVar); + if (rc) + rcExit = errx(pCtx, 9, "error: DosSetExtLibPath(\"%s\", %.*s (%lu)): %lu", + pchEqual, pchEqual - pszValue, pchEqual + 1, ulVar, rc); + } + continue; + } +#endif /* KBUILD_OS_OS2 */ + + /* We differ from kSubmit here and use putenv sematics. */ + if (pchEqual) + { + if (pchEqual[1] != '\0') + rcExit = kBuiltinOptEnvSet(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue); + else + { + char *pszCopy = strdup(pszValue); + if (pszCopy) + { + pszCopy[pchEqual - pszValue] = '\0'; + rcExit = kBuiltinOptEnvUnset(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszCopy); + free(pszCopy); + } + else + rcExit = errx(pCtx, 1, "out of memory!"); + } + continue; + } + /* Simple unset. */ + chOpt = 'U'; + } + + /* + * Append or prepend value to and environment variable. + */ + if (chOpt == 'A' || chOpt == 'D') + { +#ifdef KBUILD_OS_OS2 + if ( strcmp(pszValue, "BEGINLIBPATH") == 0 + || strcmp(pszValue, "ENDLIBPATH") == 0 + || strcmp(pszValue, "LIBPATHSTRICT") == 0) + rcExit = errx(pCtx, 2, "error: '%s' cannot currently be appended or prepended to. Please use -E/--set for now.", pszValue); + else +#endif + if (chOpt == 'A') + rcExit = kBuiltinOptEnvAppend(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue); + else + rcExit = kBuiltinOptEnvPrepend(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue); + continue; + } + + /* + * Unset environment variable. + */ + if (chOpt == 'U') + { +#ifdef KBUILD_OS_OS2 + if ( strcmp(pszValue, "BEGINLIBPATH") == 0 + || strcmp(pszValue, "ENDLIBPATH") == 0 + || strcmp(pszValue, "LIBPATHSTRICT") == 0) + rcExit = errx(pCtx, 2, "error: '%s' cannot be unset, only set to an empty value using -E/--set.", pszValue); + else +#endif + rcExit = kBuiltinOptEnvUnset(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue); + continue; + } + + /* + * Zap environment switch? + */ + if (chOpt == 'Z') /* (no -i option here, as it's reserved for stdin) */ + { + rcExit = kBuiltinOptEnvZap(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity); + continue; + } + + /* + * Change directory switch? + */ + if (chOpt == 'C') + { + if (pszSavedCwd == NULL) + pszSavedCwd = strdup(szCwd); + if (pszSavedCwd) + rcExit = kBuiltinOptChDir(pCtx, szCwd, cbCwdBuf, pszValue); + else + rcExit = err(pCtx, 9, "out of memory!"); + continue; + } + + + /* + * Verbose operation switch? + */ + if (chOpt == 'v') + { + cVerbosity++; + continue; + } + + /* + * Executable image other than the first argument following '--'. + */ + if (chOpt == 'e') + { + pszExecutable = pszValue; + continue; + } + + /* + * Okay, it is some file descriptor operation. Make sure we've got room for it. + */ + if (cOrders + 1 < K_ELEMENTS(aOrders)) + { + aOrders[cOrders].fdTarget = -1; + aOrders[cOrders].fdSource = -1; + aOrders[cOrders].fOpen = 0; + aOrders[cOrders].fRemoveOnFailure = 0; + aOrders[cOrders].pszFilename = NULL; + aOrders[cOrders].fdOtherPipeEnd = -1; +#ifndef USE_POSIX_SPAWN + aOrders[cOrders].fdSaved = -1; +#endif + } + else + { + rcExit = errx(pCtx, 2, "error: too many file actions (max: %d)", K_ELEMENTS(aOrders)); + break; + } + + if (chOpt == 'c') + { + /* + * Close the specified file descriptor (no stderr/out/in aliases). + */ + char *pszTmp; + fd = (int)strtol(pszValue, &pszTmp, 0); + if (pszTmp == pszValue || *pszTmp != '\0') + rcExit = errx(pCtx, 2, "error: failed to convert '%s' to a number", pszValue); + else if (fd < 0) + rcExit = errx(pCtx, 2, "error: negative fd %d (%s)", fd, pszValue); +#ifdef ONLY_TARGET_STANDARD_HANDLES + else if (fd > 2) + rcExit = errx(pCtx, 2, "error: %d is not a standard descriptor number", fd); +#endif + else + { + aOrders[cOrders].enmOrder = kRedirectOrder_Close; + aOrders[cOrders].fdTarget = fd; + cOrders++; +#ifdef USE_POSIX_SPAWN + rcExit = posix_spawn_file_actions_addclose(&FileActions, fd); + if (rcExit != 0) + rcExit = errx(pCtx, 2, "posix_spawn_file_actions_addclose(%d) failed: %s", fd, strerror(rcExit)); +#endif + } + } + else if (chOpt == 'd') + { + /* + * Duplicate file handle. Value is fdTarget=fdSource + */ + char *pszEqual; + fd = (int)strtol(pszValue, &pszEqual, 0); + if (pszEqual == pszValue) + rcExit = errx(pCtx, 2, "error: failed to convert target descriptor of '-d %s' to a number", pszValue); + else if (fd < 0) + rcExit = errx(pCtx, 2, "error: negative target descriptor %d ('-d %s')", fd, pszValue); +#ifdef ONLY_TARGET_STANDARD_HANDLES + else if (fd > 2) + rcExit = errx(pCtx, 2, "error: target %d is not a standard descriptor number", fd); +#endif + else if (*pszEqual != '=') + rcExit = errx(pCtx, 2, "syntax error: expected '=' to follow target descriptor: '-d %s'", pszValue); + else + { + char *pszEnd; + int fdSource = (int)strtol(++pszEqual, &pszEnd, 0); + if (pszEnd == pszEqual || *pszEnd != '\0') + rcExit = errx(pCtx, 2, "error: failed to convert source descriptor of '-d %s' to a number", pszValue); + else if (fdSource < 0) + rcExit = errx(pCtx, 2, "error: negative source descriptor %d ('-d %s')", fdSource, pszValue); + else + { + aOrders[cOrders].enmOrder = kRedirectOrder_Dup; + aOrders[cOrders].fdTarget = fd; + aOrders[cOrders].fdSource = fdSource; + cOrders++; +#ifdef USE_POSIX_SPAWN + rcExit = posix_spawn_file_actions_adddup2(&FileActions, fdSource, fd); + if (rcExit != 0) + rcExit = errx(pCtx, 2, "posix_spawn_file_actions_adddup2(%d) failed: %s", + fdSource, fd, strerror(rcExit)); +#endif + } + } + } + else if (chOpt == 'I') + { + /* + * Replace stdin with the read end of an anonymous pipe. + */ + int aFds[2] = { -1, -1 }; + rcExit = kRedirectCreateStdInPipeWithoutConflict(pCtx, aFds, cOrders, aOrders, 0 /*fdTarget*/); + if (rcExit == 0) + { + aOrders[cOrders].enmOrder = kRedirectOrder_Dup; + aOrders[cOrders].fdTarget = 0; + aOrders[cOrders].fdSource = aFds[0]; + aOrders[cOrders].fdOtherPipeEnd = aFds[1]; + cOrders++; +#ifdef USE_POSIX_SPAWN + rcExit = posix_spawn_file_actions_adddup2(&FileActions, aFds[0], 0); + if (rcExit != 0) + rcExit = errx(pCtx, 2, "posix_spawn_file_actions_adddup2(0) failed: %s", strerror(rcExit)); +#endif + } + } + else + { + /* + * Open file as a given file descriptor. + */ + int fdOpened; + int fOpen; + + /* mode */ + switch (chOpt) + { + case 'r': + chOpt = *pszArg++; + if (chOpt == '+') + { + fOpen = O_RDWR; + chOpt = *pszArg++; + } + else + fOpen = O_RDONLY; + break; + + case 'w': + chOpt = *pszArg++; + if (chOpt == '+') + { + fOpen = O_RDWR | O_CREAT | O_TRUNC; + chOpt = *pszArg++; + } + else + fOpen = O_WRONLY | O_CREAT | O_TRUNC; + aOrders[cOrders].fRemoveOnFailure = 1; + break; + + case 'a': + chOpt = *pszArg++; + if (chOpt == '+') + { + fOpen = O_RDWR | O_CREAT | O_APPEND; + chOpt = *pszArg++; + } + else + fOpen = O_WRONLY | O_CREAT | O_APPEND; + break; + + case 'i': /* make sure stdin is read-only. */ + fOpen = O_RDONLY; + break; + + case '+': + rcExit = errx(pCtx, 2, "syntax error: Unexpected '+' in '%s'", argv[iArg]); + continue; + + default: + fOpen = O_RDWR | O_CREAT | O_TRUNC; + aOrders[cOrders].fRemoveOnFailure = 1; + break; + } + + /* binary / text modifiers */ + switch (chOpt) + { + case 'b': + chOpt = *pszArg++; + default: +#ifdef O_BINARY + fOpen |= O_BINARY; +#elif defined(_O_BINARY) + fOpen |= _O_BINARY; +#endif + break; + + case 't': +#ifdef O_TEXT + fOpen |= O_TEXT; +#elif defined(_O_TEXT) + fOpen |= _O_TEXT; +#endif + chOpt = *pszArg++; + break; + + } + + /* convert to file descriptor number */ + switch (chOpt) + { + case 'i': + fd = 0; + break; + + case 'o': + fd = 1; + break; + + case 'e': + fd = 2; + break; + + case '0': + if (*pszArg == '\0') + { + fd = 0; + break; + } + /* fall thru */ + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + pszValue = pszArg - 1; + fd = (int)strtol(pszValue, &pszArg, 0); + if (pszArg == pszValue) + rcExit = errx(pCtx, 2, "error: failed to convert '%s' to a number", argv[iArg]); + else if (fd < 0) + rcExit = errx(pCtx, 2, "error: negative fd %d (%s)", fd, argv[iArg]); +#ifdef ONLY_TARGET_STANDARD_HANDLES + else if (fd > 2) + rcExit = errx(pCtx, 2, "error: %d is not a standard descriptor number", fd); +#endif + else + break; + continue; + + /* + * Invalid argument. + */ + default: + rcExit = errx(pCtx, 2, "error: failed to convert '%s' ('%s') to a file descriptor", pszArg, argv[iArg]); + continue; + } + + /* + * Check for the filename. + */ + if (*pszArg != '\0') + { + if (*pszArg != ':' && *pszArg != '=') + { + rcExit = errx(pCtx, 2, "syntax error: characters following the file descriptor: '%s' ('%s')", + pszArg, argv[iArg]); + break; + } + pszArg++; + } + else if (++iArg < argc) + pszArg = argv[iArg]; + else + { + rcExit = errx(pCtx, 2, "syntax error: missing filename argument."); + break; + } + + /* + * Open the file. We could've used posix_spawn_file_actions_addopen here, + * but that means complicated error reporting. So, since we need to do + * this for windows anyway, just do it the same way everywhere. + */ + fdOpened = kRedirectOpenWithoutConflict(pCtx, pszArg, fOpen, 0666, cOrders, aOrders, + aOrders[cOrders].fRemoveOnFailure, fd); + if (fdOpened >= 0) + { + aOrders[cOrders].enmOrder = kRedirectOrder_Open; + aOrders[cOrders].fdTarget = fd; + aOrders[cOrders].fdSource = fdOpened; + aOrders[cOrders].fOpen = fOpen; + aOrders[cOrders].pszFilename = pszArg; + cOrders++; + +#ifdef USE_POSIX_SPAWN + if (fdOpened != fd) + { + rcExit = posix_spawn_file_actions_adddup2(&FileActions, fdOpened, fd); + if (rcExit != 0) + rcExit = err(pCtx, 9, "posix_spawn_file_actions_adddup2(,%d [%s], %d) failed: %s", + fdOpened, fd, pszArg, strerror(rcExit)); + } +#endif + } + else + rcExit = 9; + } + } + else + { + errx(pCtx, 2, "syntax error: Invalid argument '%s'.", argv[iArg]); + rcExit = kmk_redirect_usage(pCtx, 1); + } + } + if (!pszExecutable) + pszExecutable = argv[iArg]; + + /* + * Make sure there's something to execute. + */ + if (rcExit == 0 && iArg < argc) + { + /* + * Do the spawning in a separate function (main is far to large as it is by now). + */ + rcExit = kRedirectDoSpawn(pCtx, pszExecutable, argc - iArg, &argv[iArg], fWatcomBrainDamage, + papszEnvVars, + szCwd, pszSavedCwd, +#ifdef USE_POSIX_SPAWN + cOrders, aOrders, &FileActions, cVerbosity, +#else + cOrders, aOrders, cVerbosity, +#endif +#ifdef KMK + pPidSpawned, +#endif + &fChildExitCode); + } + else if (rcExit == 0) + { + errx(pCtx, 2, "syntax error: nothing to execute!"); + rcExit = kmk_redirect_usage(pCtx, 1); + } + /* Help and version sets rcExit to -1. Change it to zero. */ + else if (rcExit == -1) + rcExit = 0; + + /* + * Cleanup. + */ + kBuiltinOptEnvCleanup(&papszEnvVars, cEnvVars, &cAllocatedEnvVars); + if (pszSavedCwd) + free(pszSavedCwd); + kRedirectCleanupFdOrders(cOrders, aOrders, rcExit != 0 && !fChildExitCode); +#ifdef USE_POSIX_SPAWN + posix_spawn_file_actions_destroy(&FileActions); +#endif +#ifdef KBUILD_OS_OS2 + for (ulLibPath = 0; ulLibPath < K_ELEMENTS(apszSavedLibPaths); ulLibPath++) + if (apszSavedLibPaths[ulLibPath] != NULL) + { + APIRET rc = DosSetExtLIBPATH(apszSavedLibPaths[ulLibPath], ulLibPath); + if (rc != 0) + warnx(pCtx, "DosSetExtLIBPATH('%s',%u) failed with %u when restoring the original values!", + apszSavedLibPaths[ulLibPath], ulLibPath, rc); + free(apszSavedLibPaths[ulLibPath]); + } +#endif + + return rcExit; +} + +#ifdef KMK_BUILTIN_STANDALONE +int main(int argc, char **argv, char **envp) +{ + KMKBUILTINCTX Ctx = { "kmk_redirect", NULL }; + return kmk_builtin_redirect(argc, argv, envp, &Ctx, NULL, NULL); +} +#endif + + |