VirtualBox

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

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

kmkbuiltin: funnel output thru output.c (usually via err.c).

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

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