VirtualBox

source: kBuild/trunk/src/kmk/kmkbuiltin/redirect.c@ 3199

Last change on this file since 3199 was 3195, checked in by bird, 7 years ago

kmk/win: Catch output from processes spawned by kmk_redirect. Made imagecase threadsafe and made winchildren use it.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 68.0 KB
Line 
1/* $Id: redirect.c 3195 2018-03-27 18:09:23Z bird $ */
2/** @file
3 * kmk_redirect - Do simple program <-> file redirection (++).
4 */
5
6/*
7 * Copyright (c) 2007-2016 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
8 *
9 * This file is part of kBuild.
10 *
11 * kBuild is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * kBuild is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with kBuild. If not, see <http://www.gnu.org/licenses/>
23 *
24 */
25
26/*******************************************************************************
27* Header Files *
28*******************************************************************************/
29#if defined(__APPLE__)
30/*# define _POSIX_C_SOURCE 1 / * 10.4 sdk and unsetenv * / - breaks O_CLOEXEC on 10.8 */
31#endif
32#include "makeint.h"
33#include <assert.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <errno.h>
38#include <fcntl.h>
39#if defined(KBUILD_OS_WINDOWS) || defined(KBUILD_OS_OS2)
40# include <process.h>
41#endif
42#ifdef KBUILD_OS_WINDOWS
43# include <Windows.h>
44#endif
45#if defined(_MSC_VER)
46# include <ctype.h>
47# include <io.h>
48# include "quote_argv.h"
49#else
50# ifdef __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
51# if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050
52# define USE_POSIX_SPAWN
53# endif
54# elif !defined(KBUILD_OS_WINDOWS) && !defined(KBUILD_OS_OS2)
55# define USE_POSIX_SPAWN
56# endif
57# include <unistd.h>
58# ifdef USE_POSIX_SPAWN
59# include <spawn.h>
60# endif
61# include <sys/wait.h>
62#endif
63
64#include <k/kDefs.h>
65#include <k/kTypes.h>
66#include "err.h"
67#include "kbuild_version.h"
68#ifdef KBUILD_OS_WINDOWS
69# include "nt/nt_child_inject_standard_handles.h"
70#endif
71#if defined(__gnu_hurd__) && !defined(KMK_BUILTIN_STANDALONE) /* need constant */
72# undef GET_PATH_MAX
73# undef PATH_MAX
74# define GET_PATH_MAX PATH_MAX
75#endif
76#include "kmkbuiltin.h"
77#ifdef KMK
78# ifdef KBUILD_OS_WINDOWS
79# ifndef CONFIG_NEW_WIN_CHILDREN
80# include "sub_proc.h"
81# else
82# include "../w32/winchildren.h"
83# endif
84# include "pathstuff.h"
85# endif
86#endif
87
88#ifdef __OS2__
89# define INCL_BASE
90# include <os2.h>
91# ifndef LIBPATHSTRICT
92# define LIBPATHSTRICT 3
93# endif
94#endif
95
96#ifndef KMK_BUILTIN_STANDALONE
97extern void kmk_cache_exec_image_a(const char *); /* imagecache.c */
98#endif
99
100
101/*********************************************************************************************************************************
102* Defined Constants And Macros *
103*********************************************************************************************************************************/
104/* String + strlen tuple. */
105#define TUPLE(a_sz) a_sz, sizeof(a_sz) - 1
106
107/** Only standard handles on windows. */
108#ifdef KBUILD_OS_WINDOWS
109# define ONLY_TARGET_STANDARD_HANDLES
110#endif
111
112
113static int kmk_redirect_usage(PKMKBUILTINCTX pCtx, int fIsErr)
114{
115 kmk_builtin_ctx_printf(pCtx, fIsErr,
116 "usage: %s [-[rwa+tb]<fd> <file>] [-d<fd>=<src-fd>] [-c<fd>]\n"
117 " [-Z] [-E <var=val>] [-C <dir>] [--wcc-brain-damage]\n"
118 " [-v] -- <program> [args]\n"
119 " or: %s --help\n"
120 " or: %s --version\n"
121 "\n"
122 "The rwa+tb is like for fopen, if not specified it defaults to w+.\n"
123 "The <fd> is either a number or an alias for the standard handles:\n"
124 " i = stdin\n"
125 " o = stdout\n"
126 " e = stderr\n"
127 "\n"
128 "The -d switch duplicate the right hand file descriptor (src-fd) to the left\n"
129 "hand side one (fd). The latter is limited to standard handles on windows.\n"
130 "\n"
131 "The -c switch will close the specified file descriptor. Limited to standard\n"
132 "handles on windows.\n"
133 "\n"
134 "The -Z switch zaps the environment.\n"
135 "\n"
136 "The -E switch is for making changes to the environment in a putenv\n"
137 "fashion.\n"
138 "\n"
139 "The -C switch is for changing the current directory. Please specify an\n"
140 "absolute program path as it's platform dependent whether this takes effect\n"
141 "before or after the executable is located.\n"
142 "\n"
143 "The --wcc-brain-damage switch is to work around wcc and wcc386 (Open Watcom)\n"
144 "not following normal quoting conventions on Windows, OS/2, and DOS.\n"
145 "\n"
146 "The -v switch is for making the thing more verbose.\n"
147 "\n"
148 "This command was originally just a quick hack to avoid invoking the shell\n"
149 "on Windows (cygwin) where forking is very expensive and has exhibited\n"
150 "stability issues on SMP machines. It has since grown into something like\n"
151 "/usr/bin/env on steroids.\n"
152 ,
153 pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
154 return 2;
155}
156
157
158/**
159 * Decoded file descriptor operations.
160 */
161typedef struct REDIRECTORDERS
162{
163 enum {
164 kRedirectOrder_Invalid = 0,
165 kRedirectOrder_Close,
166 kRedirectOrder_Open,
167 kRedirectOrder_Dup
168 } enmOrder;
169 /** The target file handle. */
170 int fdTarget;
171 /** The source file name, -1 on close only.
172 * This is an opened file if pszFilename is set. */
173 int fdSource;
174 /** Whether to remove the file on failure cleanup. */
175 int fRemoveOnFailure;
176 /** The open flags (for O_TEXT/O_BINARY) on windows. */
177 int fOpen;
178 /** The filename - NULL if close only. */
179 const char *pszFilename;
180#ifndef USE_POSIX_SPAWN
181 /** Saved file descriptor. */
182 int fdSaved;
183 /** Saved flags. */
184 int fSaved;
185#endif
186} REDIRECTORDERS;
187
188
189static KBOOL kRedirectHasConflict(int fd, unsigned cOrders, REDIRECTORDERS *paOrders)
190{
191#ifdef ONLY_TARGET_STANDARD_HANDLES
192 return fd < 3;
193#else
194 while (cOrders-- > 0)
195 if (paOrders[cOrders].fdTarget == fd)
196 return K_TRUE;
197 return K_FALSE;
198#endif
199}
200
201
202/**
203 * Creates a file descriptor for @a pszFilename that does not conflict with any
204 * previous orders.
205 *
206 * We need to be careful that there isn't a close or dup targetting the
207 * temporary file descriptor we return. Also, we need to take care with the
208 * descriptor's inheritability. It should only be inheritable if the returned
209 * descriptor matches the target descriptor (@a fdTarget).
210 *
211 * @returns File descriptor on success, -1 & err/errx on failure.
212 *
213 * The returned file descriptor is not inherited (i.e. close-on-exec),
214 * unless it matches @a fdTarget
215 *
216 * @param pCtx The command execution context.
217 * @param pszFilename The filename to open.
218 * @param fOpen The open flags.
219 * @param fMode The file creation mode (if applicable).
220 * @param cOrders The number of orders.
221 * @param paOrders The order array.
222 * @param fRemoveOnFailure Whether to remove the file on failure.
223 * @param fdTarget The target descriptor.
224 */
225static int kRedirectOpenWithoutConflict(PKMKBUILTINCTX pCtx, const char *pszFilename, int fOpen, mode_t fMode,
226 unsigned cOrders, REDIRECTORDERS *paOrders, int fRemoveOnFailure, int fdTarget)
227{
228#ifdef _O_NOINHERIT
229 int const fNoInherit = _O_NOINHERIT;
230#elif defined(O_NOINHERIT)
231 int const fNoInherit = O_NOINHERIT;
232#elif defined(O_CLOEXEC)
233 int const fNoInherit = O_CLOEXEC;
234#else
235 int const fNoInherit = 0;
236# define USE_FD_CLOEXEC
237#endif
238 int aFdTries[32];
239 unsigned cTries;
240 int fdOpened;
241
242#ifdef KBUILD_OS_WINDOWS
243 if (strcmp(pszFilename, "/dev/null") == 0)
244 pszFilename = "nul";
245#endif
246
247 /* Open it first. */
248 fdOpened = open(pszFilename, fOpen | fNoInherit, fMode);
249 if (fdOpened < 0)
250 return err(pCtx, -1, "open(%s,%#x,) failed", pszFilename, fOpen);
251
252 /* Check for conflicts. */
253 if (!kRedirectHasConflict(fdOpened, cOrders, paOrders))
254 {
255#ifndef KBUILD_OS_WINDOWS
256 if (fdOpened != fdTarget)
257 return fdOpened;
258# ifndef USE_FD_CLOEXEC
259 if (fcntl(fdOpened, F_SETFD, 0) != -1)
260# endif
261#endif
262 return fdOpened;
263 }
264
265 /*
266 * Do conflict resolving.
267 */
268 cTries = 1;
269 aFdTries[cTries++] = fdOpened;
270 while (cTries < K_ELEMENTS(aFdTries))
271 {
272 fdOpened = open(pszFilename, fOpen | fNoInherit, fMode);
273 if (fdOpened >= 0)
274 {
275 if (!kRedirectHasConflict(fdOpened, cOrders, paOrders))
276 {
277#ifndef KBUILD_OS_WINDOWS
278# ifdef USE_FD_CLOEXEC
279 if ( fdOpened == fdTarget
280 || fcntl(fdOpened, F_SETFD, FD_CLOEXEC) != -1)
281# else
282 if ( fdOpened != fdTarget
283 || fcntl(fdOpened, F_SETFD, 0) != -1)
284# endif
285#endif
286 {
287 while (cTries-- > 0)
288 close(aFdTries[cTries]);
289 return fdOpened;
290 }
291 }
292
293 }
294 else
295 {
296 err(pCtx, -1, "open(%s,%#x,) #%u failed", pszFilename, cTries + 1, fOpen);
297 break;
298 }
299 aFdTries[cTries++] = fdOpened;
300 }
301
302 /*
303 * Give up.
304 */
305 if (fdOpened >= 0)
306 errx(pCtx, -1, "failed to find a conflict free file descriptor for '%s'!", pszFilename);
307
308 while (cTries-- > 0)
309 close(aFdTries[cTries]);
310 return -1;
311}
312
313
314/**
315 * Cleans up the file operation orders.
316 *
317 * This does not restore stuff, just closes handles we've opened for the child.
318 *
319 * @param cOrders Number of file operation orders.
320 * @param paOrders The file operation orders.
321 * @param fFailed Set if it's a failure.
322 */
323static void kRedirectCleanupFdOrders(unsigned cOrders, REDIRECTORDERS *paOrders, KBOOL fFailure)
324{
325 unsigned i = cOrders;
326 while (i-- > 0)
327 {
328 if ( paOrders[i].enmOrder == kRedirectOrder_Open
329 && paOrders[i].fdSource != -1)
330 {
331 close(paOrders[i].fdSource);
332 paOrders[i].fdSource = -1;
333 if ( fFailure
334 && paOrders[i].fRemoveOnFailure
335 && paOrders[i].pszFilename)
336 remove(paOrders[i].pszFilename);
337 }
338 }
339}
340
341
342/**
343 * Wrapper that chooses between fprintf and kmk_builtin_ctx_printf to get
344 * an error message to the user.
345 *
346 * @param pCtx The command execution context.
347 * @param pWorkingStdErr Work stderr.
348 * @param pszFormat The message format string.
349 * @param ... Format arguments.
350 */
351static void safe_err_printf(PKMKBUILTINCTX pCtx, FILE *pWorkingStdErr, const char *pszFormat, ...)
352{
353 char szMsg[4096];
354 size_t cchMsg;
355 va_list va;
356
357 va_start(va, pszFormat);
358 vsnprintf(szMsg, sizeof(szMsg) - 1, pszFormat, va);
359 va_end(va);
360 szMsg[sizeof(szMsg) - 1] = '\0';
361 cchMsg = strlen(szMsg);
362
363#ifdef KMK_BUILTIN_STANDALONE
364 (void)pCtx;
365#else
366 if (pCtx->pOut && pCtx->pOut->syncout)
367 output_write_text(pCtx->pOut, 1, szMsg, cchMsg);
368 else
369#endif
370 fwrite(szMsg, cchMsg, 1, pWorkingStdErr);
371}
372
373#if !defined(USE_POSIX_SPAWN) && !defined(KBUILD_OS_WINDOWS)
374
375/**
376 * Saves a file handle to one which isn't inherited and isn't affected by the
377 * file orders.
378 *
379 * @returns 0 on success, non-zero exit code on failure.
380 * @param pCtx The command execution context.
381 * @param pToSave Pointer to the file order to save the target
382 * descriptor of.
383 * @param cOrders Number of file orders.
384 * @param paOrders The array of file orders.
385 * @param ppWorkingStdErr Pointer to a pointer to a working stderr. This will
386 * get replaced if we're saving stderr, so that we'll
387 * keep having a working one to report failures to.
388 */
389static int kRedirectSaveHandle(PKMKBUILTINCTX pCtx, REDIRECTORDERS *pToSave, unsigned cOrders,
390 REDIRECTORDERS *paOrders, FILE **ppWorkingStdErr)
391{
392 int fdToSave = pToSave->fdTarget;
393 int rcRet = 10;
394
395 /*
396 * First, check if there's actually handle here that needs saving.
397 */
398 pToSave->fSaved = fcntl(pToSave->fdTarget, F_GETFD, 0);
399 if (pToSave->fSaved != -1)
400 {
401 /*
402 * Try up to 32 times to get a duplicate descriptor that doesn't conflict.
403 */
404 int aFdTries[32];
405 int cTries = 0;
406 do
407 {
408 /* Duplicate the handle (windows makes this complicated). */
409 int fdDup;
410 fdDup = dup(fdToSave);
411 if (fdDup == -1)
412 {
413 safe_err_printf(pCtx, *ppWorkingStdErr, "%s: dup(%#x) failed: %u\n", pCtx->pszProgName, fdToSave, strerror(errno));
414 break;
415 }
416 /* Is the duplicate usable? */
417 if (!kRedirectHasConflict(fdDup, cOrders, paOrders))
418 {
419 pToSave->fdSaved = fdDup;
420 if ( *ppWorkingStdErr == stderr
421 && fdToSave == fileno(*ppWorkingStdErr))
422 {
423 *ppWorkingStdErr = fdopen(fdDup, "wt");
424 if (*ppWorkingStdErr == NULL)
425 {
426 safe_err_printf(pCtx, stderr, "%s: fdopen(%d,\"wt\") failed: %s\n", pCtx->pszProgName, fdDup, strerror(errno));
427 *ppWorkingStdErr = stderr;
428 close(fdDup);
429 break;
430 }
431 }
432 rcRet = 0;
433 break;
434 }
435
436 /* Not usuable, stash it and try again. */
437 aFdTries[cTries++] = fdDup;
438 } while (cTries < K_ELEMENTS(aFdTries));
439
440 /*
441 * Clean up unused duplicates.
442 */
443 while (cTries-- > 0)
444 close(aFdTries[cTries]);
445 }
446 else
447 {
448 /*
449 * Nothing to save.
450 */
451 pToSave->fdSaved = -1;
452 rcRet = 0;
453 }
454 return rcRet;
455}
456
457
458/**
459 * Restores the target file descriptors affected by the file operation orders.
460 *
461 * @param pCtx The command execution context.
462 * @param cOrders Number of file operation orders.
463 * @param paOrders The file operation orders.
464 * @param ppWorkingStdErr Pointer to a pointer to the working stderr. If this
465 * is one of the saved file descriptors, we'll restore
466 * it to stderr.
467 */
468static void kRedirectRestoreFdOrders(PKMKBUILTINCTX pCtx, unsigned cOrders, REDIRECTORDERS *paOrders, FILE **ppWorkingStdErr)
469{
470 int iSavedErrno = errno;
471 unsigned i = cOrders;
472 while (i-- > 0)
473 {
474 if (paOrders[i].fdSaved != -1)
475 {
476 KBOOL fRestoreStdErr = *ppWorkingStdErr != stderr
477 && paOrders[i].fdSaved == fileno(*ppWorkingStdErr);
478 if (dup2(paOrders[i].fdSaved, paOrders[i].fdTarget) != -1)
479 {
480 close(paOrders[i].fdSaved);
481 paOrders[i].fdSaved = -1;
482
483 if (fRestoreStdErr)
484 {
485 *ppWorkingStdErr = stderr;
486 assert(fileno(stderr) == paOrders[i].fdTarget);
487 }
488 }
489 else
490 safe_err_printf(pCtx, *ppWorkingStdErr, "%s: dup2(%d,%d) failed: %s\n",
491 pCtx->pszProgName, paOrders[i].fdSaved, paOrders[i].fdTarget, strerror(errno));
492 }
493
494 if (paOrders[i].fSaved != -1)
495 {
496 if (fcntl(paOrders[i].fdTarget, F_SETFD, paOrders[i].fSaved & FD_CLOEXEC) != -1)
497 paOrders[i].fSaved = -1;
498 else
499 safe_err_printf(pCtx, *ppWorkingStdErr, "%s: fcntl(%d,F_SETFD,%s) failed: %s\n",
500 pCtx->pszProgName, paOrders[i].fdTarget, paOrders[i].fSaved & FD_CLOEXEC ? "FD_CLOEXEC" : "0",
501 strerror(errno));
502 }
503 }
504 errno = iSavedErrno;
505}
506
507
508/**
509 * Executes the file operation orders.
510 *
511 * @returns 0 on success, exit code on failure.
512 * @param pCtx The command execution context.
513 * @param cOrders Number of file operation orders.
514 * @param paOrders File operation orders to execute.
515 * @param ppWorkingStdErr Where to return a working stderr (mainly for
516 * kRedirectRestoreFdOrders).
517 */
518static int kRedirectExecFdOrders(PKMKBUILTINCTX pCtx, unsigned cOrders, REDIRECTORDERS *paOrders, FILE **ppWorkingStdErr)
519{
520 unsigned i;
521
522 *ppWorkingStdErr = stderr;
523 for (i = 0; i < cOrders; i++)
524 {
525 int rcExit = 10;
526 switch (paOrders[i].enmOrder)
527 {
528 case kRedirectOrder_Close:
529 {
530 /* If the handle isn't used by any of the following operation,
531 just mark it as non-inheritable if necessary. */
532 int const fdTarget = paOrders[i].fdTarget;
533 unsigned j;
534 for (j = i + 1; j < cOrders; j++)
535 if (paOrders[j].fdTarget == fdTarget)
536 break;
537 if (j >= cOrders)
538 {
539 paOrders[j].fSaved = fcntl(fdTarget, F_GETFD, 0);
540 if (paOrders[j].fSaved != -1)
541 {
542 if (paOrders[j].fSaved & FD_CLOEXEC)
543 rcExit = 0;
544 else if ( fcntl(fdTarget, F_SETFD, FD_CLOEXEC) != -1
545 || errno == EBADF)
546 rcExit = 0;
547 else
548 safe_err_printf(pCtx, *ppWorkingStdErr, "%s: fcntl(%d,F_SETFD,FD_CLOEXEC) failed: %s\n",
549 pCtx->pszProgName, fdTarget, strerror(errno));
550 }
551 else if (errno == EBADF)
552 rcExit = 0;
553 else
554 safe_err_printf(pCtx, *ppWorkingStdErr, "%s: fcntl(%d,F_GETFD,0) failed: %s\n",
555 pCtx->pszProgName, fdTarget, strerror(errno));
556 }
557 else
558 rcExit = kRedirectSaveHandle(pCtx, &paOrders[i], cOrders, paOrders, ppWorkingStdErr);
559 break;
560 }
561
562 case kRedirectOrder_Dup:
563 case kRedirectOrder_Open:
564 rcExit = kRedirectSaveHandle(pCtx, &paOrders[i], cOrders, paOrders, ppWorkingStdErr);
565 if (rcExit == 0)
566 {
567 if (dup2(paOrders[i].fdSource, paOrders[i].fdTarget) != -1)
568 rcExit = 0;
569 else
570 {
571 if (paOrders[i].enmOrder == kRedirectOrder_Open)
572 safe_err_printf(pCtx, *ppWorkingStdErr, "%s: dup2(%d [%s],%d) failed: %s\n", pCtx->pszProgName,
573 paOrders[i].fdSource, paOrders[i].pszFilename, paOrders[i].fdTarget, strerror(errno));
574 else
575 safe_err_printf(pCtx, *ppWorkingStdErr, "%s: dup2(%d,%d) failed: %s\n",
576 pCtx->pszProgName, paOrders[i].fdSource, paOrders[i].fdTarget, strerror(errno));
577 rcExit = 10;
578 }
579 }
580 break;
581
582 default:
583 safe_err_printf(pCtx, *ppWorkingStdErr, "%s: error! invalid enmOrder=%d\n", pCtx->pszProgName, paOrders[i].enmOrder);
584 rcExit = 99;
585 break;
586 }
587
588 if (rcExit != 0)
589 {
590 kRedirectRestoreFdOrders(pCtx, i, paOrders, ppWorkingStdErr);
591 return rcExit;
592 }
593 }
594
595 return 0;
596}
597
598#endif /* !USE_POSIX_SPAWN */
599#ifdef KBUILD_OS_WINDOWS
600
601/**
602 * Registers the child process with a care provider or waits on it to complete.
603 *
604 * @returns 0 or non-zero success indicator or child exit code, depending on
605 * the value pfIsChildExitCode points to.
606 * @param pCtx The command execution context.
607 * @param hProcess The child process handle.
608 * @param cVerbosity The verbosity level.
609 * @param pPidSpawned Where to return the PID of the spawned child
610 * when we're inside KMK and we're return without
611 * waiting.
612 * @param pfIsChildExitCode Where to indicate whether the return exit code
613 * is from the child or from our setup efforts.
614 */
615static int kRedirectPostnatalCareOnWindows(PKMKBUILTINCTX pCtx, HANDLE hProcess, unsigned cVerbosity,
616 pid_t *pPidSpawned, KBOOL *pfIsChildExitCode)
617{
618 int rcExit;
619 DWORD dwTmp;
620
621# ifndef KMK_BUILTIN_STANDALONE
622 /*
623 * Try register the child with a childcare provider, i.e. winchildren.c
624 * or sub_proc.c.
625 */
626# ifndef CONFIG_NEW_WIN_CHILDREN
627 if (process_kmk_register_redirect(hProcess, pPidSpawned) == 0)
628# else
629 if ( pPidSpawned
630 && MkWinChildCreateRedirect((intptr_t)hProcess, pPidSpawned) == 0)
631# endif
632 {
633 if (cVerbosity > 0)
634 warnx(pCtx, "debug: spawned %d", *pPidSpawned);
635 *pfIsChildExitCode = K_FALSE;
636 return 0;
637 }
638# ifndef CONFIG_NEW_WIN_CHILDREN
639 warn(pCtx, "sub_proc is out of slots, waiting for child...");
640# else
641 if (pPidSpawned)
642 warn(pCtx, "MkWinChildCreateRedirect failed...");
643# endif
644# endif
645
646 /*
647 * Either the provider is outbooked or we're not in a context (like
648 * standalone) where we get help with waiting and must to it ourselves
649 */
650 dwTmp = WaitForSingleObject(hProcess, INFINITE);
651 if (dwTmp != WAIT_OBJECT_0)
652 warnx(pCtx, "WaitForSingleObject failed: %#x\n", dwTmp);
653
654 if (GetExitCodeProcess(hProcess, &dwTmp))
655 rcExit = (int)dwTmp;
656 else
657 {
658 warnx(pCtx, "GetExitCodeProcess failed: %u\n", GetLastError());
659 TerminateProcess(hProcess, 127);
660 rcExit = 127;
661 }
662
663 CloseHandle(hProcess);
664 *pfIsChildExitCode = K_TRUE;
665 return rcExit;
666}
667
668
669/**
670 * Tries to locate the executable image.
671 *
672 * This isn't quite perfect yet...
673 *
674 * @returns pszExecutable or pszBuf with valid string.
675 * @param pszExecutable The specified executable.
676 * @param pszBuf Buffer to return a modified path in.
677 * @param cbBuf Size of return buffer.
678 * @param pszPath The search path.
679 */
680static const char *kRedirectCreateProcessWindowsFindImage(const char *pszExecutable, char *pszBuf, size_t cbBuf,
681 const char *pszPath)
682{
683 /*
684 * Analyze the name.
685 */
686 size_t const cchExecutable = strlen(pszExecutable);
687 BOOL fHavePath = FALSE;
688 BOOL fHaveSuffix = FALSE;
689 size_t off = cchExecutable;
690 while (off > 0)
691 {
692 char ch = pszExecutable[--off];
693 if (ch == '.')
694 {
695 fHaveSuffix = TRUE;
696 break;
697 }
698 if (ch == '\\' || ch == '/' || ch == ':')
699 {
700 fHavePath = TRUE;
701 break;
702 }
703 }
704 if (!fHavePath)
705 while (off > 0)
706 {
707 char ch = pszExecutable[--off];
708 if (ch == '\\' || ch == '/' || ch == ':')
709 {
710 fHavePath = TRUE;
711 break;
712 }
713 }
714 /*
715 * If no path, search the path value.
716 */
717 if (!fHavePath)
718 {
719 char *pszFilename;
720 DWORD cchFound = SearchPathA(pszPath, pszExecutable, fHaveSuffix ? NULL : ".exe", cbBuf, pszBuf, &pszFilename);
721 if (cchFound)
722 return pszBuf;
723 }
724
725 /*
726 * If no suffix, try add .exe.
727 */
728 if ( !fHaveSuffix
729 && GetFileAttributesA(pszExecutable) == INVALID_FILE_ATTRIBUTES
730 && cchExecutable + 4 < cbBuf)
731 {
732 memcpy(pszBuf, pszExecutable, cchExecutable);
733 memcpy(&pszBuf[cchExecutable], ".exe", 5);
734 if (GetFileAttributesA(pszBuf) != INVALID_FILE_ATTRIBUTES)
735 return pszBuf;
736 }
737
738 return pszExecutable;
739}
740
741
742/**
743 * Turns the orders into input for nt_child_inject_standard_handles and
744 * winchildren.c
745 *
746 * @returns 0 on success, non-zero on failure.
747 * @param pCtx The command execution context.
748 * @param cOrders Number of file operation orders.
749 * @param paOrders The file operation orders.
750 * @param pafReplace Replace (TRUE) or leave alone (FALSE) indicator
751 * for each of the starndard handles.
752 * @param pahChild Array of standard handles for injecting into the
753 * child. Parallel to pafReplace.
754 */
755static int kRedirectOrderToWindowsHandles(PKMKBUILTINCTX pCtx, unsigned cOrders, REDIRECTORDERS *paOrders,
756 BOOL pafReplace[3], HANDLE pahChild[3])
757{
758 int i;
759 for (i = 0; i < (int)cOrders; i++)
760 {
761 int fdTarget = paOrders[i].fdTarget;
762 assert(fdTarget >= 0 && fdTarget < 3);
763 switch (paOrders[i].enmOrder)
764 {
765 case kRedirectOrder_Open:
766 if ( (paOrders[i].fOpen & O_APPEND)
767 && lseek(paOrders[i].fdSource, 0, SEEK_END) < 0)
768 return err(pCtx, 10, "lseek-to-end failed on %d (for %d)", paOrders[i].fdSource, fdTarget);
769 /* fall thru */
770 case kRedirectOrder_Dup:
771 pahChild[fdTarget] = (HANDLE)_get_osfhandle(paOrders[i].fdSource);
772 if (pahChild[fdTarget] == NULL || pahChild[fdTarget] == INVALID_HANDLE_VALUE)
773 return err(pCtx, 10, "_get_osfhandle failed on %d (for %d)", paOrders[i].fdSource, fdTarget);
774 break;
775
776 case kRedirectOrder_Close:
777 pahChild[fdTarget] = NULL;
778 break;
779
780 default:
781 assert(0);
782 }
783 pafReplace[fdTarget] = TRUE;
784 }
785 return 0;
786}
787
788
789/**
790 * Alternative approach on windows that use CreateProcess and doesn't require
791 * any serialization wrt handles and CWD.
792 *
793 * @returns 0 on success, non-zero on failure to create.
794 * @param pCtx The command execution context.
795 * @param pszExecutable The child process executable.
796 * @param cArgs Number of arguments.
797 * @param papszArgs The child argument vector.
798 * @param papszEnvVars The child environment vector.
799 * @param pszCwd The current working directory of the child.
800 * @param cOrders Number of file operation orders.
801 * @param paOrders The file operation orders.
802 * @param phProcess Where to return process handle.
803 */
804static int kRedirectCreateProcessWindows(PKMKBUILTINCTX pCtx, const char *pszExecutable, int cArgs, char **papszArgs,
805 char **papszEnvVars, const char *pszCwd, unsigned cOrders,
806 REDIRECTORDERS *paOrders, HANDLE *phProcess)
807{
808 size_t cbArgs;
809 char *pszCmdLine;
810 size_t cbEnv;
811 char *pszzEnv;
812 char *pch;
813 int i;
814 int rc;
815
816 /*
817 * Start by making the the command line. We just need to put spaces
818 * between the arguments since quote_argv don't the quoting already.
819 */
820 cbArgs = 0;
821 for (i = 0; i < cArgs; i++)
822 cbArgs += strlen(papszArgs[i]) + 1;
823 pszCmdLine = pch = (char *)malloc(cbArgs);
824 if (!pszCmdLine)
825 return errx(pCtx, 9, "out of memory!");
826 for (i = 0; i < cArgs; i++)
827 {
828 size_t cch;
829 if (i != 0)
830 *pch++ = ' ';
831 cch = strlen(papszArgs[i]);
832 memcpy(pch, papszArgs[i], cch);
833 pch += cch;
834 }
835 *pch++ = '\0';
836 assert(pch - pszCmdLine == cbArgs);
837
838 /*
839 * The environment vector is also simple.
840 */
841 cbEnv = 0;
842 for (i = 0; papszEnvVars[i]; i++)
843 cbEnv += strlen(papszEnvVars[i]) + 1;
844 cbEnv++;
845 pszzEnv = pch = (char *)malloc(cbEnv);
846 if (pszzEnv)
847 {
848 char szAbsExe[1024];
849 const char *pszPathVal = NULL;
850 STARTUPINFOA StartupInfo;
851 PROCESS_INFORMATION ProcInfo = { NULL, NULL, 0, 0 };
852
853 for (i = 0; papszEnvVars[i]; i++)
854 {
855 size_t cbSrc = strlen(papszEnvVars[i]) + 1;
856 memcpy(pch, papszEnvVars[i], cbSrc);
857 if ( !pszPathVal
858 && cbSrc >= 5
859 && pch[4] == '='
860 && (pch[0] == 'P' || pch[0] == 'p')
861 && (pch[1] == 'A' || pch[1] == 'a')
862 && (pch[2] == 'T' || pch[2] == 't')
863 && (pch[3] == 'H' || pch[3] == 'h'))
864 pszPathVal = &pch[5];
865 pch += cbSrc;
866 }
867 *pch++ = '\0';
868 assert(pch - pszzEnv == cbEnv);
869
870 /*
871 * Locate the executable.
872 */
873 pszExecutable = kRedirectCreateProcessWindowsFindImage(pszExecutable, szAbsExe, sizeof(szAbsExe), pszPathVal);
874
875 /*
876 * Do basic startup info preparation.
877 */
878 memset(&StartupInfo, 0, sizeof(StartupInfo));
879 StartupInfo.cb = sizeof(StartupInfo);
880 GetStartupInfoA(&StartupInfo);
881 StartupInfo.lpReserved2 = 0; /* No CRT file handle + descriptor info possible, sorry. */
882 StartupInfo.cbReserved2 = 0;
883 StartupInfo.dwFlags &= ~STARTF_USESTDHANDLES;
884
885 /*
886 * If there are no redirection orders, we're good.
887 */
888 if (!cOrders)
889 {
890 if (CreateProcessA(pszExecutable, pszCmdLine, NULL /*pProcAttrs*/, NULL /*pThreadAttrs*/,
891 FALSE /*fInheritHandles*/, 0 /*fFlags*/, pszzEnv, pszCwd, &StartupInfo, &ProcInfo))
892 {
893 CloseHandle(ProcInfo.hThread);
894 *phProcess = ProcInfo.hProcess;
895# ifndef KMK_BUILTIN_STANDALONE
896 kmk_cache_exec_image_a(pszExecutable);
897# endif
898 rc = 0;
899 }
900 else
901 rc = errx(pCtx, 10, "CreateProcessA(%s) failed: %u", pszExecutable, GetLastError());
902 }
903 else
904 {
905 /*
906 * Execute the orders, ending up with three handles we need to
907 * implant into the guest process.
908 *
909 * This isn't 100% perfect wrt O_APPEND, but it'll have to do for now.
910 */
911 BOOL afReplace[3] = { FALSE, FALSE, FALSE };
912 HANDLE ahChild[3] = { NULL, NULL, NULL };
913 rc = kRedirectOrderToWindowsHandles(pCtx, cOrders, paOrders, afReplace, ahChild);
914 if (rc == 0)
915 {
916 /*
917 * Start the process in suspended animation so we can inject handles.
918 */
919 if (CreateProcessA(pszExecutable, pszCmdLine, NULL /*pProcAttrs*/, NULL /*pThreadAttrs*/,
920 FALSE /*fInheritHandles*/, CREATE_SUSPENDED, pszzEnv, pszCwd, &StartupInfo, &ProcInfo))
921 {
922 /* Inject the handles and try make it start executing. */
923 char szErrMsg[128];
924 rc = nt_child_inject_standard_handles(ProcInfo.hProcess, afReplace, ahChild, szErrMsg, sizeof(szErrMsg));
925 if (rc)
926 rc = errx(pCtx, 10, "%s", szErrMsg);
927 else if (!ResumeThread(ProcInfo.hThread))
928 rc = errx(pCtx, 10, "ResumeThread failed: %u", GetLastError());
929
930 /* Kill it if any of that fails. */
931 if (rc != 0)
932 TerminateProcess(ProcInfo.hProcess, rc);
933
934 CloseHandle(ProcInfo.hThread);
935 *phProcess = ProcInfo.hProcess;
936# ifndef KMK_BUILTIN_STANDALONE
937 kmk_cache_exec_image_a(pszExecutable);
938# endif
939 rc = 0;
940 }
941 else
942 rc = errx(pCtx, 10, "CreateProcessA(%s) failed: %u", pszExecutable, GetLastError());
943 }
944 }
945 free(pszzEnv);
946 }
947 else
948 rc = errx(pCtx, 9, "out of memory!");
949 free(pszCmdLine);
950 return rc;
951}
952
953# if !defined(KMK_BUILTIN_STANDALONE) && defined(CONFIG_NEW_WIN_CHILDREN)
954/**
955 * Pass the problem on to winchildren.c when we're on one of its workers.
956 *
957 * @returns 0 on success, non-zero on failure to create.
958 * @param pCtx The command execution context.
959 * @param pszExecutable The child process executable.
960 * @param cArgs Number of arguments.
961 * @param papszArgs The child argument vector.
962 * @param papszEnvVars The child environment vector.
963 * @param pszCwd The current working directory of the child.
964 * @param cOrders Number of file operation orders.
965 * @param paOrders The file operation orders.
966 * @param phProcess Where to return process handle.
967 * @param pfIsChildExitCode Where to indicate whether the return exit code
968 * is from the child or from our setup efforts.
969 */
970static int kRedirectExecProcessWithinOnWorker(PKMKBUILTINCTX pCtx, const char *pszExecutable, int cArgs, char **papszArgs,
971 char **papszEnvVars, const char *pszCwd, unsigned cOrders,
972 REDIRECTORDERS *paOrders, KBOOL *pfIsChildExitCode)
973{
974 BOOL afReplace[3] = { FALSE, FALSE, FALSE };
975 HANDLE ahChild[3] = { NULL, NULL, NULL };
976 int rc = kRedirectOrderToWindowsHandles(pCtx, cOrders, paOrders, afReplace, ahChild);
977 if (rc == 0)
978 {
979 rc = MkWinChildBuiltInExecChild(pCtx->pvWorker, pszExecutable, papszArgs, TRUE /*fQuotedArgv*/,
980 papszEnvVars, pszCwd, afReplace, ahChild);
981 *pfIsChildExitCode = K_TRUE;
982 }
983 return rc;
984}
985# endif /* !KMK_BUILTIN_STANDALONE */
986
987#endif /* KBUILD_OS_WINDOWS */
988
989/**
990 * Does the child spawning .
991 *
992 * @returns Exit code.
993 * @param pCtx The command execution context.
994 * @param pszExecutable The child process executable.
995 * @param cArgs Number of arguments.
996 * @param papszArgs The child argument vector.
997 * @param fWatcomBrainDamage Whether MSC need to do quoting according to
998 * weird Watcom WCC rules.
999 * @param papszEnvVars The child environment vector.
1000 * @param pszCwd The current working directory of the child.
1001 * @param pszSavedCwd The saved current working directory. This is
1002 * NULL if the CWD doesn't need changing.
1003 * @param cOrders Number of file operation orders.
1004 * @param paOrders The file operation orders.
1005 * @param pFileActions The posix_spawn file actions.
1006 * @param cVerbosity The verbosity level.
1007 * @param pPidSpawned Where to return the PID of the spawned child
1008 * when we're inside KMK and we're return without
1009 * waiting.
1010 * @param pfIsChildExitCode Where to indicate whether the return exit code
1011 * is from the child or from our setup efforts.
1012 */
1013static int kRedirectDoSpawn(PKMKBUILTINCTX pCtx, const char *pszExecutable, int cArgs, char **papszArgs, int fWatcomBrainDamage,
1014 char **papszEnvVars, const char *pszCwd, const char *pszSavedCwd,
1015 unsigned cOrders, REDIRECTORDERS *paOrders,
1016#ifdef USE_POSIX_SPAWN
1017 posix_spawn_file_actions_t *pFileActions,
1018#endif
1019 unsigned cVerbosity,
1020#ifdef KMK
1021 pid_t *pPidSpawned,
1022#endif
1023 KBOOL *pfIsChildExitCode)
1024{
1025 int rcExit = 0;
1026 int i;
1027#ifdef _MSC_VER
1028 char **papszArgsOriginal = papszArgs;
1029#endif
1030 *pfIsChildExitCode = K_FALSE;
1031
1032#ifdef _MSC_VER
1033 /*
1034 * Do MSC parameter quoting.
1035 */
1036 papszArgs = malloc((cArgs + 1) * sizeof(papszArgs[0]));
1037 if (papszArgs)
1038 memcpy(papszArgs, papszArgsOriginal, (cArgs + 1) * sizeof(papszArgs[0]));
1039 else
1040 return errx(pCtx, 9, "out of memory!");
1041
1042 rcExit = quote_argv(cArgs, papszArgs, fWatcomBrainDamage, 0 /*fFreeOrLeak*/);
1043 if (rcExit == 0)
1044#endif
1045 {
1046 /*
1047 * Display what we're about to execute if we're in verbose mode.
1048 */
1049 if (cVerbosity > 0)
1050 {
1051 for (i = 0; i < cArgs; i++)
1052 warnx(pCtx, "debug: argv[%i]=%s<eos>", i, papszArgs[i]);
1053 for (i = 0; i < (int)cOrders; i++)
1054 switch (paOrders[i].enmOrder)
1055 {
1056 case kRedirectOrder_Close:
1057 warnx(pCtx, "debug: close %d\n", paOrders[i].fdTarget);
1058 break;
1059 case kRedirectOrder_Dup:
1060 warnx(pCtx, "debug: dup %d to %d\n", paOrders[i].fdSource, paOrders[i].fdTarget);
1061 break;
1062 case kRedirectOrder_Open:
1063 warnx(pCtx, "debug: open '%s' (%#x) as [%d ->] %d\n",
1064 paOrders[i].pszFilename, paOrders[i].fOpen, paOrders[i].fdSource, paOrders[i].fdTarget);
1065 break;
1066 default:
1067 warnx(pCtx, "error! invalid enmOrder=%d", paOrders[i].enmOrder);
1068 assert(0);
1069 break;
1070 }
1071 if (pszSavedCwd)
1072 warnx(pCtx, "debug: chdir %s\n", pszCwd);
1073 }
1074
1075#ifndef KBUILD_OS_WINDOWS
1076 /*
1077 * Change working directory if so requested.
1078 */
1079 if (pszSavedCwd)
1080 {
1081 if (chdir(pszCwd) < 0)
1082 rcExit = errx(pCtx, 10, "Failed to change directory to '%s'", pszCwd);
1083 }
1084#endif /* KBUILD_OS_WINDOWS */
1085 if (rcExit == 0)
1086 {
1087# if !defined(USE_POSIX_SPAWN) && !defined(KBUILD_OS_WINDOWS)
1088 /*
1089 * Execute the file orders.
1090 */
1091 FILE *pWorkingStdErr = NULL;
1092 rcExit = kRedirectExecFdOrders(pCtx, cOrders, paOrders, &pWorkingStdErr);
1093 if (rcExit == 0)
1094# endif
1095 {
1096# ifdef KMK
1097 /*
1098 * We're spawning from within kmk.
1099 */
1100# ifdef KBUILD_OS_WINDOWS
1101 /* Windows is slightly complicated due to handles and winchildren.c. */
1102 if (pPidSpawned)
1103 *pPidSpawned = 0;
1104# ifdef CONFIG_NEW_WIN_CHILDREN
1105 if (pCtx->pvWorker && !pPidSpawned)
1106 rcExit = kRedirectExecProcessWithinOnWorker(pCtx, pszExecutable, cArgs, papszArgs, papszEnvVars,
1107 pszSavedCwd ? pszCwd : NULL, cOrders, paOrders,
1108 pfIsChildExitCode);
1109 else
1110# endif
1111 {
1112 HANDLE hProcess = INVALID_HANDLE_VALUE;
1113 rcExit = kRedirectCreateProcessWindows(pCtx, pszExecutable, cArgs, papszArgs, papszEnvVars,
1114 pszSavedCwd ? pszCwd : NULL, cOrders, paOrders, &hProcess);
1115 if (rcExit == 0)
1116 rcExit = kRedirectPostnatalCareOnWindows(pCtx, hProcess, cVerbosity, pPidSpawned, pfIsChildExitCode);
1117 }
1118
1119# elif defined(KBUILD_OS_OS2)
1120 *pPidSpawned = _spawnvpe(P_NOWAIT, pszExecutable, papszArgs, papszEnvVars);
1121 kRedirectRestoreFdOrders(pCtx, cOrders, paOrders, &pWorkingStdErr);
1122 if (*pPidSpawned != -1)
1123 {
1124 if (cVerbosity > 0)
1125 warnx(pCtx, "debug: spawned %d", *pPidSpawned);
1126 }
1127 else
1128 {
1129 rcExit = err(pCtx, 10, "_spawnvpe(%s) failed", pszExecutable);
1130 *pPidSpawned = 0;
1131 }
1132# else
1133 rcExit = posix_spawnp(pPidSpawned, pszExecutable, pFileActions, NULL /*pAttr*/, papszArgs, papszEnvVars);
1134 if (rcExit == 0)
1135 {
1136 if (cVerbosity > 0)
1137 warnx(pCtx, "debug: spawned %d", *pPidSpawned);
1138 }
1139 else
1140 {
1141 rcExit = errx(pCtx, 10, "posix_spawnp(%s) failed: %s", pszExecutable, strerror(rcExit));
1142 *pPidSpawned = 0;
1143 }
1144# endif
1145
1146#else /* !KMK */
1147 /*
1148 * Spawning from inside the kmk_redirect executable.
1149 */
1150# ifdef KBUILD_OS_WINDOWS
1151 HANDLE hProcess = INVALID_HANDLE_VALUE;
1152 rcExit = kRedirectCreateProcessWindows(pCtx, pszExecutable, cArgs, papszArgs, papszEnvVars,
1153 pszSavedCwd ? pszCwd : NULL, cOrders, paOrders, &hProcess);
1154 if (rcExit == 0)
1155 {
1156 DWORD dwWait;
1157 do
1158 dwWait = WaitForSingleObject(hProcess, INFINITE);
1159 while (dwWait == WAIT_IO_COMPLETION || dwWait == WAIT_TIMEOUT);
1160
1161 dwWait = 11;
1162 if (GetExitCodeProcess(hProcess, &dwWait))
1163 {
1164 *pfIsChildExitCode = K_TRUE;
1165 rcExit = dwWait;
1166 }
1167 else
1168 rcExit = errx(pCtx, 11, "GetExitCodeProcess(%s) failed: %u", pszExecutable, GetLastError());
1169 }
1170
1171#elif defined(KBUILD_OS_OS2)
1172 errno = 0;
1173 rcExit = (int)_spawnvpe(P_WAIT, pszExecutable, papszArgs, papszEnvVars);
1174 kRedirectRestoreFdOrders(pCtx, cOrders, paOrders, &pWorkingStdErr);
1175 if (rcExit != -1 || errno == 0)
1176 {
1177 *pfIsChildExitCode = K_TRUE;
1178 if (cVerbosity > 0)
1179 warnx(pCtx, "debug: exit code: %d", rcExit);
1180 }
1181 else
1182 rcExit = err(pCtx, 10, "_spawnvpe(%s) failed", pszExecutable);
1183
1184# else
1185 pid_t pidChild = 0;
1186 rcExit = posix_spawnp(&pidChild, pszExecutable, pFileActions, NULL /*pAttr*/, papszArgs, papszEnvVars);
1187 if (rcExit == 0)
1188 {
1189 *pfIsChildExitCode = K_TRUE;
1190 if (cVerbosity > 0)
1191 warnx(pCtx, "debug: spawned %d", pidChild);
1192
1193 /* Wait for the child. */
1194 for (;;)
1195 {
1196 pid_t pid = waitpid(pidChild, &rcExit, 0 /*block*/);
1197 if (pid == pidChild)
1198 {
1199 if (cVerbosity > 0)
1200 warnx(pCtx, "debug: %d exit code: %d", pidChild, rcExit);
1201 break;
1202 }
1203 if ( errno != EINTR
1204# ifdef ERESTART
1205 && errno != ERESTART
1206# endif
1207 )
1208 {
1209 rcExit = err(pCtx, 11, "waitpid failed");
1210 kill(pidChild, SIGKILL);
1211 break;
1212 }
1213 }
1214 }
1215 else
1216 rcExit = errx(pCtx, 10, "posix_spawnp(%s) failed: %s", pszExecutable, strerror(rcExit));
1217# endif
1218#endif /* !KMK */
1219 }
1220 }
1221
1222#ifndef KBUILD_OS_WINDOWS
1223 /*
1224 * Restore the current directory.
1225 */
1226 if (pszSavedCwd)
1227 {
1228 if (chdir(pszSavedCwd) < 0)
1229 warn(pCtx, "Failed to restore directory to '%s'", pszSavedCwd);
1230 }
1231#endif
1232 }
1233#ifdef _MSC_VER
1234 else
1235 rcExit = errx(pCtx, 9, "quite_argv failed: %u", rcExit);
1236
1237 /* Restore the original argv strings, freeing the quote_argv replacements. */
1238 i = cArgs;
1239 while (i-- > 0)
1240 if (papszArgs[i] != papszArgsOriginal[i])
1241 free(papszArgs[i]);
1242 free(papszArgs);
1243#endif
1244 return rcExit;
1245}
1246
1247
1248/**
1249 * The function that does almost everything here... ugly.
1250 */
1251int kmk_builtin_redirect(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx, struct child *pChild, pid_t *pPidSpawned)
1252{
1253 int rcExit = 0;
1254 KBOOL fChildExitCode = K_FALSE;
1255#ifdef USE_POSIX_SPAWN
1256 posix_spawn_file_actions_t FileActions;
1257#endif
1258 unsigned cOrders = 0;
1259 REDIRECTORDERS aOrders[32];
1260
1261 int iArg;
1262 const char *pszExecutable = NULL;
1263 char **papszEnvVars = NULL;
1264 unsigned cAllocatedEnvVars;
1265 unsigned cEnvVars;
1266 int fWatcomBrainDamage = 0;
1267 int cVerbosity = 0;
1268 char *pszSavedCwd = NULL;
1269 size_t const cbCwdBuf = GET_PATH_MAX;
1270 PATH_VAR(szCwd);
1271#ifdef KBUILD_OS_OS2
1272 ULONG ulLibPath;
1273 char *apszSavedLibPaths[LIBPATHSTRICT + 1] = { NULL, NULL, NULL, NULL };
1274#endif
1275
1276
1277 if (argc <= 1)
1278 return kmk_redirect_usage(pCtx, 1);
1279
1280 /*
1281 * Create default program environment.
1282 */
1283#if defined(KMK) && defined(KBUILD_OS_WINDOWS)
1284 if (getcwd_fs(szCwd, cbCwdBuf) != NULL)
1285#else
1286 if (getcwd(szCwd, cbCwdBuf) != NULL)
1287#endif
1288 { /* likely */ }
1289 else
1290 return err(pCtx, 9, "getcwd failed");
1291
1292 /* We start out with a read-only enviornment from kmk or the crt, and will
1293 duplicate it if we make changes to it. */
1294 cAllocatedEnvVars = 0;
1295 papszEnvVars = envp;
1296 cEnvVars = 0;
1297 while (papszEnvVars[cEnvVars] != NULL)
1298 cEnvVars++;
1299
1300#ifdef USE_POSIX_SPAWN
1301 /*
1302 * Init posix attributes.
1303 */
1304 rcExit = posix_spawn_file_actions_init(&FileActions);
1305 if (rcExit != 0)
1306 rcExit = errx(pCtx, 9, "posix_spawn_file_actions_init failed: %s", strerror(rcExit));
1307#endif
1308
1309 /*
1310 * Parse arguments.
1311 */
1312 for (iArg = 1; rcExit == 0 && iArg < argc; iArg++)
1313 {
1314 char *pszArg = argv[iArg];
1315 if (*pszArg == '-')
1316 {
1317 int fd;
1318 char chOpt;
1319 const char *pszValue;
1320
1321 chOpt = *++pszArg;
1322 pszArg++;
1323 if (chOpt == '-')
1324 {
1325 /* '--' indicates where the bits to execute start. Check if we're
1326 relaunching ourselves here and just continue parsing if we are. */
1327 if (*pszArg == '\0')
1328 {
1329 iArg++;
1330 if ( iArg >= argc
1331 || ( strcmp(argv[iArg], "kmk_builtin_redirect") != 0
1332 && strcmp(argv[iArg], argv[0]) != 0))
1333 break;
1334 continue;
1335 }
1336
1337 if ( strcmp(pszArg, "wcc-brain-damage") == 0
1338 || strcmp(pszArg, "watcom-brain-damage") == 0)
1339 {
1340 fWatcomBrainDamage = 1;
1341 continue;
1342 }
1343
1344 /* convert to short. */
1345 if (strcmp(pszArg, "help") == 0)
1346 chOpt = 'h';
1347 else if (strcmp(pszArg, "version") == 0)
1348 chOpt = 'V';
1349 else if ( strcmp(pszArg, "set") == 0
1350 || strcmp(pszArg, "env") == 0)
1351 chOpt = 'E';
1352 else if (strcmp(pszArg, "append") == 0)
1353 chOpt = 'A';
1354 else if (strcmp(pszArg, "prepend") == 0)
1355 chOpt = 'D';
1356 else if (strcmp(pszArg, "unset") == 0)
1357 chOpt = 'U';
1358 else if ( strcmp(pszArg, "zap-env") == 0
1359 || strcmp(pszArg, "ignore-environment") == 0 /* GNU env compatibility. */ )
1360 chOpt = 'Z';
1361 else if (strcmp(pszArg, "chdir") == 0)
1362 chOpt = 'C';
1363 else if (strcmp(pszArg, "close") == 0)
1364 chOpt = 'c';
1365 else if (strcmp(pszArg, "verbose") == 0)
1366 chOpt = 'v';
1367 else
1368 {
1369 errx(pCtx, 2, "Unknown option: '%s'", pszArg - 2);
1370 rcExit = kmk_redirect_usage(pCtx, 1);
1371 break;
1372 }
1373 pszArg = "";
1374 }
1375
1376 /*
1377 * Deal with the obligatory help and version switches first to get them out of the way.
1378 */
1379 if (chOpt == 'h')
1380 {
1381 kmk_redirect_usage(pCtx, 0);
1382 rcExit = -1;
1383 break;
1384 }
1385 if (chOpt == 'V')
1386 {
1387 kbuild_version(argv[0]);
1388 rcExit = -1;
1389 break;
1390 }
1391
1392 /*
1393 * Get option value first, if the option takes one.
1394 */
1395 if ( chOpt == 'E'
1396 || chOpt == 'A'
1397 || chOpt == 'D'
1398 || chOpt == 'U'
1399 || chOpt == 'C'
1400 || chOpt == 'c'
1401 || chOpt == 'd'
1402 || chOpt == 'e')
1403 {
1404 if (*pszArg != '\0')
1405 pszValue = pszArg + (*pszArg == ':' || *pszArg == '=');
1406 else if (++iArg < argc)
1407 pszValue = argv[iArg];
1408 else
1409 {
1410 errx(pCtx, 2, "syntax error: Option -%c requires a value!", chOpt);
1411 rcExit = kmk_redirect_usage(pCtx, 1);
1412 break;
1413 }
1414 }
1415 else
1416 pszValue = NULL;
1417
1418 /*
1419 * Environment switch?
1420 */
1421 if (chOpt == 'E')
1422 {
1423 const char *pchEqual = strchr(pszValue, '=');
1424#ifdef KBUILD_OS_OS2
1425 if ( strncmp(pszValue, TUPLE("BEGINLIBPATH=")) == 0
1426 || strncmp(pszValue, TUPLE("ENDLIBPATH=")) == 0
1427 || strncmp(pszValue, TUPLE("LIBPATHSTRICT=")) == 0)
1428 {
1429 ULONG ulVar = *pszValue == 'B' ? BEGIN_LIBPATH
1430 : *pszValue == 'E' ? END_LIBPATH
1431 : LIBPATHSTRICT;
1432 APIRET rc;
1433 if (apszSavedLibPaths[ulVar] == NULL)
1434 {
1435 /* The max length is supposed to be 1024 bytes. */
1436 apszSavedLibPaths[ulVar] = calloc(1024, 2);
1437 if (apszSavedLibPaths[ulVar])
1438 {
1439 rc = DosQueryExtLIBPATH(apszSavedLibPaths[ulVar], ulVar);
1440 if (rc)
1441 {
1442 rcExit = errx(pCtx, 9, "DosQueryExtLIBPATH(,%u) failed: %lu", ulVar, rc);
1443 free(apszSavedLibPaths[ulVar]);
1444 apszSavedLibPaths[ulVar] = NULL;
1445 }
1446 }
1447 else
1448 rcExit = errx(pCtx, 9, "out of memory!");
1449 }
1450 if (rcExit == 0)
1451 {
1452 rc = DosSetExtLIBPATH(pchEqual + 1, ulVar);
1453 if (rc)
1454 rcExit = errx(pCtx, 9, "error: DosSetExtLibPath(\"%s\", %.*s (%lu)): %lu",
1455 pchEqual, pchEqual - pszValue, pchEqual + 1, ulVar, rc);
1456 }
1457 continue;
1458 }
1459#endif /* KBUILD_OS_OS2 */
1460
1461 /* We differ from kSubmit here and use putenv sematics. */
1462 if (pchEqual)
1463 {
1464 if (pchEqual[1] != '\0')
1465 rcExit = kBuiltinOptEnvSet(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
1466 else
1467 {
1468 char *pszCopy = strdup(pszValue);
1469 if (pszCopy)
1470 {
1471 pszCopy[pchEqual - pszValue] = '\0';
1472 rcExit = kBuiltinOptEnvUnset(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszCopy);
1473 free(pszCopy);
1474 }
1475 else
1476 rcExit = errx(pCtx, 1, "out of memory!");
1477 }
1478 continue;
1479 }
1480 /* Simple unset. */
1481 chOpt = 'U';
1482 }
1483
1484 /*
1485 * Append or prepend value to and environment variable.
1486 */
1487 if (chOpt == 'A' || chOpt == 'D')
1488 {
1489#ifdef KBUILD_OS_OS2
1490 if ( strcmp(pszValue, "BEGINLIBPATH") == 0
1491 || strcmp(pszValue, "ENDLIBPATH") == 0
1492 || strcmp(pszValue, "LIBPATHSTRICT") == 0)
1493 rcExit = errx(pCtx, 2, "error: '%s' cannot currently be appended or prepended to. Please use -E/--set for now.", pszValue);
1494 else
1495#endif
1496 if (chOpt == 'A')
1497 rcExit = kBuiltinOptEnvAppend(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
1498 else
1499 rcExit = kBuiltinOptEnvPrepend(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
1500 continue;
1501 }
1502
1503 /*
1504 * Unset environment variable.
1505 */
1506 if (chOpt == 'U')
1507 {
1508#ifdef KBUILD_OS_OS2
1509 if ( strcmp(pszValue, "BEGINLIBPATH") == 0
1510 || strcmp(pszValue, "ENDLIBPATH") == 0
1511 || strcmp(pszValue, "LIBPATHSTRICT") == 0)
1512 rcExit = errx(pCtx, 2, "error: '%s' cannot be unset, only set to an empty value using -E/--set.", pszValue);
1513 else
1514#endif
1515 rcExit = kBuiltinOptEnvUnset(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
1516 continue;
1517 }
1518
1519 /*
1520 * Zap environment switch?
1521 */
1522 if ( chOpt == 'Z'
1523 || chOpt == 'i' /* GNU env compatibility. */ )
1524 {
1525 rcExit = kBuiltinOptEnvZap(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity);
1526 continue;
1527 }
1528
1529 /*
1530 * Change directory switch?
1531 */
1532 if (chOpt == 'C')
1533 {
1534 if (pszSavedCwd == NULL)
1535 pszSavedCwd = strdup(szCwd);
1536 if (pszSavedCwd)
1537 rcExit = kBuiltinOptChDir(pCtx, szCwd, cbCwdBuf, pszValue);
1538 else
1539 rcExit = err(pCtx, 9, "out of memory!");
1540 continue;
1541 }
1542
1543
1544 /*
1545 * Verbose operation switch?
1546 */
1547 if (chOpt == 'v')
1548 {
1549 cVerbosity++;
1550 continue;
1551 }
1552
1553 /*
1554 * Executable image other than the first argument following '--'.
1555 */
1556 if (chOpt == 'e')
1557 {
1558 pszExecutable = pszValue;
1559 continue;
1560 }
1561
1562 /*
1563 * Okay, it is some file descriptor opearation. Make sure we've got room for it.
1564 */
1565 if (cOrders + 1 < K_ELEMENTS(aOrders))
1566 {
1567 aOrders[cOrders].fdTarget = -1;
1568 aOrders[cOrders].fdSource = -1;
1569 aOrders[cOrders].fOpen = 0;
1570 aOrders[cOrders].fRemoveOnFailure = 0;
1571 aOrders[cOrders].pszFilename = NULL;
1572#ifndef USE_POSIX_SPAWN
1573 aOrders[cOrders].fdSaved = -1;
1574#endif
1575 }
1576 else
1577 {
1578 rcExit = errx(pCtx, 2, "error: too many file actions (max: %d)", K_ELEMENTS(aOrders));
1579 break;
1580 }
1581
1582 if (chOpt == 'c')
1583 {
1584 /*
1585 * Close the specified file descriptor (no stderr/out/in aliases).
1586 */
1587 char *pszTmp;
1588 fd = (int)strtol(pszValue, &pszTmp, 0);
1589 if (pszTmp == pszValue || *pszTmp != '\0')
1590 rcExit = errx(pCtx, 2, "error: failed to convert '%s' to a number", pszValue);
1591 else if (fd < 0)
1592 rcExit = errx(pCtx, 2, "error: negative fd %d (%s)", fd, pszValue);
1593#ifdef ONLY_TARGET_STANDARD_HANDLES
1594 else if (fd > 2)
1595 rcExit = errx(pCtx, 2, "error: %d is not a standard descriptor number", fd);
1596#endif
1597 else
1598 {
1599 aOrders[cOrders].enmOrder = kRedirectOrder_Close;
1600 aOrders[cOrders].fdTarget = fd;
1601 cOrders++;
1602#ifdef USE_POSIX_SPAWN
1603 rcExit = posix_spawn_file_actions_addclose(&FileActions, fd);
1604 if (rcExit != 0)
1605 rcExit = errx(pCtx, 2, "posix_spawn_file_actions_addclose(%d) failed: %s", fd, strerror(rcExit));
1606#endif
1607 }
1608 }
1609 else if (chOpt == 'd')
1610 {
1611 /*
1612 * Duplicate file handle. Value is fdTarget=fdSource
1613 */
1614 char *pszEqual;
1615 fd = (int)strtol(pszValue, &pszEqual, 0);
1616 if (pszEqual == pszValue)
1617 rcExit = errx(pCtx, 2, "error: failed to convert target descriptor of '-d %s' to a number", pszValue);
1618 else if (fd < 0)
1619 rcExit = errx(pCtx, 2, "error: negative target descriptor %d ('-d %s')", fd, pszValue);
1620#ifdef ONLY_TARGET_STANDARD_HANDLES
1621 else if (fd > 2)
1622 rcExit = errx(pCtx, 2, "error: target %d is not a standard descriptor number", fd);
1623#endif
1624 else if (*pszEqual != '=')
1625 rcExit = errx(pCtx, 2, "syntax error: expected '=' to follow target descriptor: '-d %s'", pszValue);
1626 else
1627 {
1628 char *pszEnd;
1629 int fdSource = (int)strtol(++pszEqual, &pszEnd, 0);
1630 if (pszEnd == pszEqual || *pszEnd != '\0')
1631 rcExit = errx(pCtx, 2, "error: failed to convert source descriptor of '-d %s' to a number", pszValue);
1632 else if (fdSource < 0)
1633 rcExit = errx(pCtx, 2, "error: negative source descriptor %d ('-d %s')", fdSource, pszValue);
1634 else
1635 {
1636 aOrders[cOrders].enmOrder = kRedirectOrder_Dup;
1637 aOrders[cOrders].fdTarget = fd;
1638 aOrders[cOrders].fdSource = fdSource;
1639 cOrders++;
1640#ifdef USE_POSIX_SPAWN
1641 rcExit = posix_spawn_file_actions_adddup2(&FileActions, fdSource, fd);
1642 if (rcExit != 0)
1643 rcExit = errx(pCtx, 2, "posix_spawn_file_actions_addclose(%d) failed: %s", fd, strerror(rcExit));
1644#endif
1645 }
1646 }
1647 }
1648 else
1649 {
1650 /*
1651 * Open file as a given file descriptor.
1652 */
1653 int fdOpened;
1654 int fOpen;
1655
1656 /* mode */
1657 switch (chOpt)
1658 {
1659 case 'r':
1660 chOpt = *pszArg++;
1661 if (chOpt == '+')
1662 {
1663 fOpen = O_RDWR;
1664 chOpt = *pszArg++;
1665 }
1666 else
1667 fOpen = O_RDONLY;
1668 break;
1669
1670 case 'w':
1671 chOpt = *pszArg++;
1672 if (chOpt == '+')
1673 {
1674 fOpen = O_RDWR | O_CREAT | O_TRUNC;
1675 chOpt = *pszArg++;
1676 }
1677 else
1678 fOpen = O_WRONLY | O_CREAT | O_TRUNC;
1679 aOrders[cOrders].fRemoveOnFailure = 1;
1680 break;
1681
1682 case 'a':
1683 chOpt = *pszArg++;
1684 if (chOpt == '+')
1685 {
1686 fOpen = O_RDWR | O_CREAT | O_APPEND;
1687 chOpt = *pszArg++;
1688 }
1689 else
1690 fOpen = O_WRONLY | O_CREAT | O_APPEND;
1691 break;
1692
1693 case 'i': /* make sure stdin is read-only. */
1694 fOpen = O_RDONLY;
1695 break;
1696
1697 case '+':
1698 rcExit = errx(pCtx, 2, "syntax error: Unexpected '+' in '%s'", argv[iArg]);
1699 continue;
1700
1701 default:
1702 fOpen = O_RDWR | O_CREAT | O_TRUNC;
1703 aOrders[cOrders].fRemoveOnFailure = 1;
1704 break;
1705 }
1706
1707 /* binary / text modifiers */
1708 switch (chOpt)
1709 {
1710 case 'b':
1711 chOpt = *pszArg++;
1712 default:
1713#ifdef O_BINARY
1714 fOpen |= O_BINARY;
1715#elif defined(_O_BINARY)
1716 fOpen |= _O_BINARY;
1717#endif
1718 break;
1719
1720 case 't':
1721#ifdef O_TEXT
1722 fOpen |= O_TEXT;
1723#elif defined(_O_TEXT)
1724 fOpen |= _O_TEXT;
1725#endif
1726 chOpt = *pszArg++;
1727 break;
1728
1729 }
1730
1731 /* convert to file descriptor number */
1732 switch (chOpt)
1733 {
1734 case 'i':
1735 fd = 0;
1736 break;
1737
1738 case 'o':
1739 fd = 1;
1740 break;
1741
1742 case 'e':
1743 fd = 2;
1744 break;
1745
1746 case '0':
1747 if (*pszArg == '\0')
1748 {
1749 fd = 0;
1750 break;
1751 }
1752 /* fall thru */
1753 case '1':
1754 case '2':
1755 case '3':
1756 case '4':
1757 case '5':
1758 case '6':
1759 case '7':
1760 case '8':
1761 case '9':
1762 pszValue = pszArg - 1;
1763 fd = (int)strtol(pszValue, &pszArg, 0);
1764 if (pszArg == pszValue)
1765 rcExit = errx(pCtx, 2, "error: failed to convert '%s' to a number", argv[iArg]);
1766 else if (fd < 0)
1767 rcExit = errx(pCtx, 2, "error: negative fd %d (%s)", fd, argv[iArg]);
1768#ifdef ONLY_TARGET_STANDARD_HANDLES
1769 else if (fd > 2)
1770 rcExit = errx(pCtx, 2, "error: %d is not a standard descriptor number", fd);
1771#endif
1772 else
1773 break;
1774 continue;
1775
1776 /*
1777 * Invalid argument.
1778 */
1779 default:
1780 rcExit = errx(pCtx, 2, "error: failed to convert '%s' ('%s') to a file descriptor", pszArg, argv[iArg]);
1781 continue;
1782 }
1783
1784 /*
1785 * Check for the filename.
1786 */
1787 if (*pszArg != '\0')
1788 {
1789 if (*pszArg != ':' && *pszArg != '=')
1790 {
1791 rcExit = errx(pCtx, 2, "syntax error: characters following the file descriptor: '%s' ('%s')",
1792 pszArg, argv[iArg]);
1793 break;
1794 }
1795 pszArg++;
1796 }
1797 else if (++iArg < argc)
1798 pszArg = argv[iArg];
1799 else
1800 {
1801 rcExit = errx(pCtx, 2, "syntax error: missing filename argument.");
1802 break;
1803 }
1804
1805 /*
1806 * Open the file. We could've used posix_spawn_file_actions_addopen here,
1807 * but that means complicated error reporting. So, since we need to do
1808 * this for windows anyway, just do it the same way everywhere.
1809 */
1810 fdOpened = kRedirectOpenWithoutConflict(pCtx, pszArg, fOpen, 0666, cOrders, aOrders,
1811 aOrders[cOrders].fRemoveOnFailure, fd);
1812 if (fdOpened >= 0)
1813 {
1814 aOrders[cOrders].enmOrder = kRedirectOrder_Open;
1815 aOrders[cOrders].fdTarget = fd;
1816 aOrders[cOrders].fdSource = fdOpened;
1817 aOrders[cOrders].fOpen = fOpen;
1818 aOrders[cOrders].pszFilename = pszArg;
1819 cOrders++;
1820
1821#ifdef USE_POSIX_SPAWN
1822 if (fdOpened != fd)
1823 {
1824 rcExit = posix_spawn_file_actions_adddup2(&FileActions, fdOpened, fd);
1825 if (rcExit != 0)
1826 rcExit = err(pCtx, 9, "posix_spawn_file_actions_adddup2(,%d [%s], %d) failed: %s",
1827 fdOpened, fd, pszArg, strerror(rcExit));
1828 }
1829#endif
1830 }
1831 else
1832 rcExit = 9;
1833 }
1834 }
1835 else
1836 {
1837 errx(pCtx, 2, "syntax error: Invalid argument '%s'.", argv[iArg]);
1838 rcExit = kmk_redirect_usage(pCtx, 1);
1839 }
1840 }
1841 if (!pszExecutable)
1842 pszExecutable = argv[iArg];
1843
1844 /*
1845 * Make sure there's something to execute.
1846 */
1847 if (rcExit == 0 && iArg < argc)
1848 {
1849 /*
1850 * Do the spawning in a separate function (main is far to large as it is by now).
1851 */
1852 rcExit = kRedirectDoSpawn(pCtx, pszExecutable, argc - iArg, &argv[iArg], fWatcomBrainDamage,
1853 papszEnvVars,
1854 szCwd, pszSavedCwd,
1855#ifdef USE_POSIX_SPAWN
1856 cOrders, aOrders, &FileActions, cVerbosity,
1857#else
1858 cOrders, aOrders, cVerbosity,
1859#endif
1860#ifdef KMK
1861 pPidSpawned,
1862#endif
1863 &fChildExitCode);
1864 }
1865 else if (rcExit == 0)
1866 {
1867 errx(pCtx, 2, "syntax error: nothing to execute!");
1868 rcExit = kmk_redirect_usage(pCtx, 1);
1869 }
1870 /* Help and version sets rcExit to -1. Change it to zero. */
1871 else if (rcExit == -1)
1872 rcExit = 0;
1873
1874 /*
1875 * Cleanup.
1876 */
1877 kBuiltinOptEnvCleanup(&papszEnvVars, cEnvVars, &cAllocatedEnvVars);
1878 if (pszSavedCwd)
1879 free(pszSavedCwd);
1880 kRedirectCleanupFdOrders(cOrders, aOrders, rcExit != 0 && !fChildExitCode);
1881#ifdef USE_POSIX_SPAWN
1882 posix_spawn_file_actions_destroy(&FileActions);
1883#endif
1884#ifdef KBUILD_OS_OS2
1885 for (ulLibPath = 0; ulLibPath < K_ELEMENTS(apszSavedLibPaths); ulLibPath++)
1886 if (apszSavedLibPaths[ulLibPath] != NULL)
1887 {
1888 APIRET rc = DosSetExtLIBPATH(apszSavedLibPaths[ulLibPath], ulLibPath);
1889 if (rc != 0)
1890 warnx(pCtx, "DosSetExtLIBPATH('%s',%u) failed with %u when restoring the original values!",
1891 apszSavedLibPaths[ulLibPath], ulLibPath, rc);
1892 free(apszSavedLibPaths[ulLibPath]);
1893 }
1894#endif
1895
1896 return rcExit;
1897}
1898
1899#ifdef KMK_BUILTIN_STANDALONE
1900int main(int argc, char **argv, char **envp)
1901{
1902 KMKBUILTINCTX Ctx = { "kmk_redirect", NULL };
1903 return kmk_builtin_redirect(argc, argv, envp, &Ctx, NULL, NULL);
1904}
1905#endif
1906
1907
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette