VirtualBox

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

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

kmkbultin: environment fixes and stats.

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