Changeset 3161 for trunk/src/kmk/kmkbuiltin
- Timestamp:
- Mar 19, 2018, 11:40:35 PM (7 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/kmk/kmkbuiltin/redirect.c
r3159 r3161 39 39 #if defined(KBUILD_OS_WINDOWS) || defined(KBUILD_OS_OS2) 40 40 # include <process.h> 41 #endif 42 #if defined(KBUILD_OS_WINDOWS) || defined(KBUILD_OS_OS2) 43 # include <Windows.h> 44 # include <Winternl.h> 41 45 #endif 42 46 #if defined(_MSC_VER) … … 91 95 92 96 97 /********************************************************************************************************************************* 98 * Defined Constants And Macros * 99 *********************************************************************************************************************************/ 93 100 /* String + strlen tuple. */ 94 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 95 108 96 109 … … 156 169 "\n" 157 170 "The -d switch duplicate the right hand file descriptor (src-fd) to the left\n" 158 "hand side one (fd). \n"171 "hand side one (fd). The latter is limited to standard handles on windows.\n" 159 172 "\n" 160 "The -c switch will close the specified file descriptor.\n" 173 "The -c switch will close the specified file descriptor. Limited to standard\n" 174 "handles on windows.\n" 161 175 "\n" 162 176 "The -Z switch zaps the environment.\n" … … 165 179 "fashion.\n" 166 180 "\n" 167 "The -C switch is for changing the current directory. This takes immediate\n" 168 "effect, so be careful where you put it.\n" 181 "The -C switch is for changing the current directory. Please specify an\n" 182 "absolute program path as it's platform dependent whether this takes effect\n" 183 "before or after the executable is located.\n" 169 184 "\n" 170 185 "The --wcc-brain-damage switch is to work around wcc and wcc386 (Open Watcom)\n" … … 439 454 if (fdOpened >= 0) 440 455 { 456 #ifndef KBUILD_OS_WINDOWS 441 457 if ( !kRedirectHasConflict(fdOpened, cOrders, paOrders) 442 # ifdef _MSC_VER458 # ifdef _MSC_VER 443 459 && fdOpened != fdTarget 444 # endif460 # endif 445 461 ) 462 #endif 446 463 { 447 464 #ifndef _MSC_VER … … 510 527 511 528 512 #if ndef USE_POSIX_SPAWN529 #if !defined(USE_POSIX_SPAWN) && !defined(KBUILD_OS_WINDOWS) 513 530 514 531 /** … … 778 795 #endif /* !USE_POSIX_SPAWN */ 779 796 797 #ifdef KBUILD_OS_WINDOWS 798 799 /** 800 * Tries to locate the executable image. 801 * 802 * This isn't quite perfect yet... 803 * 804 * @returns pszExecutable or pszBuf with valid string. 805 * @param pszExecutable The specified executable. 806 * @param pszBuf Buffer to return a modified path in. 807 * @param cbBuf Size of return buffer. 808 * @param pszPath The search path. 809 */ 810 static const char *kRedirectCreateProcessWindowsFindImage(const char *pszExecutable, char *pszBuf, size_t cbBuf, 811 const char *pszPath) 812 { 813 /* 814 * Analyze the name. 815 */ 816 size_t const cchExecutable = strlen(pszExecutable); 817 BOOL fHavePath = FALSE; 818 BOOL fHaveSuffix = FALSE; 819 size_t off = cchExecutable; 820 while (off > 0) 821 { 822 char ch = pszExecutable[--off]; 823 if (ch == '.') 824 { 825 fHaveSuffix = TRUE; 826 break; 827 } 828 if (ch == '\\' || ch == '/' || ch == ':') 829 { 830 fHavePath = TRUE; 831 break; 832 } 833 } 834 if (!fHavePath) 835 while (off > 0) 836 { 837 char ch = pszExecutable[--off]; 838 if (ch == '\\' || ch == '/' || ch == ':') 839 { 840 fHavePath = TRUE; 841 break; 842 } 843 } 844 /* 845 * If no path, search the path value. 846 */ 847 if (!fHavePath) 848 { 849 char *pszFilename; 850 DWORD cchFound = SearchPathA(pszPath, pszExecutable, fHaveSuffix ? NULL : ".exe", cbBuf, pszBuf, &pszFilename); 851 if (cchFound) 852 return pszBuf; 853 } 854 855 /* 856 * If no suffix, try add .exe. 857 */ 858 if ( !fHaveSuffix 859 && GetFileAttributesA(pszExecutable) == INVALID_FILE_ATTRIBUTES 860 && cchExecutable + 4 < cbBuf) 861 { 862 memcpy(pszBuf, pszExecutable, cchExecutable); 863 memcpy(&pszBuf[cchExecutable], ".exe", 5); 864 if (GetFileAttributesA(pszBuf) != INVALID_FILE_ATTRIBUTES) 865 return pszBuf; 866 } 867 868 return pszExecutable; 869 } 870 871 /** 872 * Alternative approach on windows that use CreateProcess and doesn't require 873 * any serialization wrt handles and CWD. 874 * 875 * @returns 0 on success, non-zero on failure to create. 876 * @param pszExecutable The child process executable. 877 * @param cArgs Number of arguments. 878 * @param papszArgs The child argument vector. 879 * @param papszEnvVars The child environment vector. 880 * @param pszCwd The current working directory of the child. 881 * @param cOrders Number of file operation orders. 882 * @param paOrders The file operation orders. 883 * @param phProcess Where to return process handle. 884 */ 885 static kRedirectCreateProcessWindows(const char *pszExecutable, int cArgs, char **papszArgs, char **papszEnvVars, 886 const char *pszCwd, unsigned cOrders, REDIRECTORDERS *paOrders, HANDLE *phProcess) 887 { 888 static NTSTATUS (NTAPI *s_pfnNtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG) = NULL; 889 size_t cbArgs; 890 char *pszCmdLine; 891 size_t cbEnv; 892 char *pszzEnv; 893 char *pch; 894 int i; 895 int rc; 896 897 /* 898 * Determin host bitness and APIs while we can still easily return. 899 */ 900 #if K_ARCH_BITS == 32 901 BOOL f64BitHost = TRUE; 902 if (!IsWow64Process(GetCurrentProcess(), &f64BitHost)) 903 return errx(9, "IsWow64Process failed: %u", GetLastError()); 904 #elif K_ARCH_BITS == 64 905 BOOL const f64BitHost = TRUE; 906 #else 907 # error "K_ARCH_BITS is bad/missing" 908 #endif 909 if (cOrders > 0 && !s_pfnNtQueryInformationProcess) 910 { 911 *(FARPROC *)&s_pfnNtQueryInformationProcess = GetProcAddress(GetModuleHandleA("NTDLL.DLL"), "NtQueryInformationProcess"); 912 if (!s_pfnNtQueryInformationProcess) 913 return errx(9, "NtQueryInformationProcess not found!"); 914 } 915 916 /* 917 * Start by making the the command line. We just need to put spaces 918 * between the arguments since quote_argv don't the quoting already. 919 */ 920 cbArgs = 0; 921 for (i = 0; i < cArgs; i++) 922 cbArgs += strlen(papszArgs[i]) + 1; 923 pszCmdLine = pch = (char *)malloc(cbArgs); 924 if (!pszCmdLine) 925 return errx(9, "out of memory!"); 926 for (i = 0; i < cArgs; i++) 927 { 928 size_t cch; 929 if (i != 0) 930 *pch++ = ' '; 931 cch = strlen(papszArgs[i]); 932 memcpy(pch, papszArgs[i], cch); 933 pch += cch; 934 } 935 *pch++ = '\0'; 936 assert(pch - pszCmdLine == cbArgs); 937 938 /* 939 * The environment vector is also simple. 940 */ 941 cbEnv = 0; 942 for (i = 0; papszEnvVars[i]; i++) 943 cbEnv += strlen(papszEnvVars[i]) + 1; 944 cbEnv++; 945 pszzEnv = pch = (char *)malloc(cbEnv); 946 if (pszzEnv) 947 { 948 char szAbsExe[1024]; 949 const char *pszPathVal = NULL; 950 STARTUPINFOA StartupInfo; 951 PROCESS_INFORMATION ProcInfo = { NULL, NULL, 0, 0 }; 952 953 for (i = 0; papszEnvVars[i]; i++) 954 { 955 size_t cbSrc = strlen(papszEnvVars[i]) + 1; 956 memcpy(pch, papszEnvVars[i], cbSrc); 957 if ( !pszPathVal 958 && cbSrc >= 5 959 && pch[4] == '=' 960 && (pch[0] == 'P' || pch[0] == 'p') 961 && (pch[1] == 'A' || pch[1] == 'a') 962 && (pch[2] == 'T' || pch[2] == 't') 963 && (pch[3] == 'H' || pch[3] == 'h')) 964 pszPathVal = &pch[5]; 965 pch += cbSrc; 966 } 967 *pch++ = '\0'; 968 assert(pch - pszzEnv == cbEnv); 969 970 /* 971 * Locate the executable. 972 */ 973 pszExecutable = kRedirectCreateProcessWindowsFindImage(pszExecutable, szAbsExe, sizeof(szAbsExe), pszPathVal); 974 975 /* 976 * Do basic startup info preparation. 977 */ 978 memset(&StartupInfo, 0, sizeof(StartupInfo)); 979 StartupInfo.cb = sizeof(StartupInfo); 980 GetStartupInfoA(&StartupInfo); 981 StartupInfo.dwFlags &= ~STARTF_USESTDHANDLES; 982 983 /* 984 * If there are no redirection orders, we're good. 985 */ 986 if (!cOrders) 987 { 988 if (CreateProcessA(pszExecutable, pszCmdLine, NULL /*pProcAttrs*/, NULL /*pThreadAttrs*/, 989 FALSE /*fInheritHandles*/, 0 /*fFlags*/, pszzEnv, pszCwd, &StartupInfo, &ProcInfo)) 990 { 991 CloseHandle(ProcInfo.hThread); 992 *phProcess = ProcInfo.hProcess; 993 rc = 0; 994 } 995 else 996 rc = errx(10, "CreateProcessA(%s) failed: %u", pszExecutable, GetLastError()); 997 } 998 else 999 { 1000 /* 1001 * Execute the orders, ending up with three handles we need to 1002 * implant into the guest process. 1003 * 1004 * This isn't 100% perfect wrt O_APPEND, but it'll have to do for now. 1005 */ 1006 BOOL afReplace[3] = { FALSE, FALSE, FALSE }; 1007 HANDLE ahChild[3] = { NULL, NULL, NULL }; 1008 rc = 0; 1009 for (i = 0; i < (int)cOrders; i++) 1010 { 1011 int fdTarget = paOrders[i].fdTarget; 1012 assert(fdTarget >= 0 && fdTarget < 3); 1013 switch (paOrders[i].enmOrder) 1014 { 1015 case kRedirectOrder_Open: 1016 if ( (paOrders[i].fOpen & O_APPEND) 1017 && lseek(paOrders[i].fdSource, 0, SEEK_END) < 0) 1018 rc = err(10, "lseek-to-end failed on %d (for %d)", paOrders[i].fdSource, fdTarget); 1019 case kRedirectOrder_Dup: 1020 ahChild[fdTarget] = (HANDLE)_get_osfhandle(paOrders[i].fdSource); 1021 if (ahChild[fdTarget] == NULL || ahChild[fdTarget] == INVALID_HANDLE_VALUE) 1022 rc = err(10, "_get_osfhandle failed on %d (for %d)", paOrders[i].fdSource, fdTarget); 1023 break; 1024 case kRedirectOrder_Close: 1025 ahChild[fdTarget] = NULL; 1026 break; 1027 default: 1028 assert(0); 1029 } 1030 afReplace[fdTarget] = TRUE; 1031 } 1032 if (rc == 0) 1033 { 1034 /* 1035 * Start the process in suspended animation so we can inject handles. 1036 * 1037 * We clear the reserved 2 pointer + size to avoid passing the wrong 1038 * filehandle info to the child. We may later want to generate this. 1039 */ 1040 StartupInfo.cbReserved2 = 0; 1041 StartupInfo.lpReserved2 = 0; 1042 1043 if (CreateProcessA(pszExecutable, pszCmdLine, NULL /*pProcAttrs*/, NULL /*pThreadAttrs*/, 1044 FALSE /*fInheritHandles*/, CREATE_SUSPENDED, pszzEnv, pszCwd, &StartupInfo, &ProcInfo)) 1045 { 1046 /* 1047 * Figure out where we need to write the handles. 1048 */ 1049 ULONG cbActual1 = 0; 1050 PROCESS_BASIC_INFORMATION BasicInfo = { 0, 0, }; 1051 NTSTATUS rcNt = s_pfnNtQueryInformationProcess(ProcInfo.hProcess, ProcessBasicInformation, 1052 &BasicInfo, sizeof(BasicInfo), &cbActual1); 1053 if (NT_SUCCESS(rcNt)) 1054 { 1055 BOOL const f32BitPeb = !f64BitHost; 1056 ULONG const cbChildPtr = f32BitPeb ? 4 : 8; 1057 PVOID pvSrcInPeb = (char *)BasicInfo.PebBaseAddress + (f32BitPeb ? 0x10 : 0x20); 1058 char * pbDst = 0; 1059 SIZE_T cbActual2 = 0; 1060 if (ReadProcessMemory(ProcInfo.hProcess, pvSrcInPeb, &pbDst, cbChildPtr, &cbActual2)) 1061 { 1062 union 1063 { 1064 KU32 au32[3]; 1065 KU64 au64[3]; 1066 } uBuf; 1067 memset(&uBuf, 0, sizeof(uBuf)); 1068 pbDst += f32BitPeb ? 0x18 : 0x20; 1069 if ( (afReplace[0] && afReplace[1] && afReplace[2]) 1070 || ReadProcessMemory(ProcInfo.hProcess, pbDst, &uBuf, cbChildPtr * 3, &cbActual2)) 1071 { 1072 for (i = 0; i < 3; i++) 1073 if (afReplace[i]) 1074 { 1075 HANDLE hInChild = INVALID_HANDLE_VALUE; 1076 if ( ahChild[i] == NULL /* just closed*/ 1077 || DuplicateHandle(GetCurrentProcess(), ahChild[i], ProcInfo.hProcess, &hInChild, 1078 0, TRUE /*fInheriable*/, DUPLICATE_SAME_ACCESS)) 1079 { 1080 if (f32BitPeb) 1081 uBuf.au32[i] = (KU32)(uintptr_t)hInChild; 1082 else 1083 uBuf.au64[i] = (uintptr_t)hInChild; 1084 } 1085 else 1086 rc = errx(10, "Error duplicating %p into the child: %u", ahChild[i], GetLastError()); 1087 } 1088 if ( rc == 0 1089 && !WriteProcessMemory(ProcInfo.hProcess, pbDst, &uBuf, cbChildPtr * 3, &cbActual2)) 1090 rc = errx(10, "Error writing standard handles at %p LB %u in the child: %u", 1091 pbDst, cbChildPtr * 3, GetLastError()); 1092 } 1093 else 1094 rc = errx(10, "Error reading %p LB %u from the child: %u", 1095 pbDst, cbChildPtr * 3, GetLastError()); 1096 } 1097 else 1098 rc = errx(10, "Error reading %p LB %u from the child: %u", pvSrcInPeb, cbChildPtr); 1099 } 1100 else 1101 rc = errx(10, "NtQueryInformationProcess failed: %#x", rcNt); 1102 1103 /* Resume the bugger... */ 1104 if ( rc == 0 1105 && !ResumeThread(ProcInfo.hThread)) 1106 rc = errx(10, "ResumeThread failed: %u", GetLastError()); 1107 1108 /* .. or kill it. */ 1109 if (rc != 0) 1110 TerminateProcess(ProcInfo.hProcess, rc); 1111 1112 CloseHandle(ProcInfo.hThread); 1113 *phProcess = ProcInfo.hProcess; 1114 rc = 0; 1115 } 1116 else 1117 rc = errx(10, "CreateProcessA(%s) failed: %u", pszExecutable, GetLastError()); 1118 } 1119 } 1120 free(pszzEnv); 1121 } 1122 else 1123 rc = errx(9, "out of memory!"); 1124 free(pszCmdLine); 1125 return rc; 1126 } 1127 #endif /* KBUILD_OS_WINDOWS */ 780 1128 781 1129 /** … … 802 1150 * is from the child or from our setup efforts. 803 1151 */ 804 static int kRedirectDoSpawn(const char *pszExecutable, int cArgs, char **papszArgs, int fWatcomBrainDamage, char **papszEnvVars, 805 const char *pszCwd, const char *pszSavedCwd, unsigned cOrders, REDIRECTORDERS *paOrders, 1152 static int kRedirectDoSpawn(const char *pszExecutable, int cArgs, char **papszArgs, int fWatcomBrainDamage, 1153 char **papszEnvVars, const char *pszCwd, const char *pszSavedCwd, 1154 unsigned cOrders, REDIRECTORDERS *paOrders, 806 1155 #ifdef USE_POSIX_SPAWN 807 1156 posix_spawn_file_actions_t *pFileActions, … … 863 1212 } 864 1213 1214 #ifndef KBUILD_OS_WINDOWS 865 1215 /* 866 1216 * Change working directory if so requested. … … 871 1221 rcExit = errx(10, "Failed to change directory to '%s'", pszCwd); 872 1222 } 1223 #endif /* KBUILD_OS_WINDOWS */ 873 1224 if (rcExit == 0) 874 1225 { 875 # ifndef USE_POSIX_SPAWN1226 # if !defined(USE_POSIX_SPAWN) && !defined(KBUILD_OS_WINDOWS) 876 1227 /* 877 1228 * Execute the file orders. 878 1229 */ 879 1230 FILE *pWorkingStdErr = NULL; 880 # if defined(CONFIG_NEW_WIN_CHILDREN) && defined(KMK)1231 # if defined(CONFIG_NEW_WIN_CHILDREN) && defined(KMK) 881 1232 if (cOrders > 0) 882 1233 MkWinChildExclusiveAcquire(); 883 # endif1234 # endif 884 1235 rcExit = kRedirectExecFdOrders(cOrders, paOrders, &pWorkingStdErr); 885 1236 if (rcExit == 0) 886 # endif887 { 888 # ifdef KMK1237 # endif 1238 { 1239 # ifdef KMK 889 1240 /* 890 1241 * We're spawning from within kmk. 891 1242 */ 892 #if defined(KBUILD_OS_WINDOWS) 893 /* Windows is slightly complicated due to handles and sub_proc.c. */ 1243 # if defined(KBUILD_OS_WINDOWS) 1244 /* Windows is slightly complicated due to handles and winchildren.c. */ 1245 # if 1 1246 HANDLE hProcess = INVALID_HANDLE_VALUE; 1247 rcExit = kRedirectCreateProcessWindows(pszExecutable, cArgs, papszArgs, papszEnvVars, pszCwd, 1248 cOrders, paOrders, &hProcess); 1249 if (rcExit == 0) 1250 # else 894 1251 HANDLE hProcess = (HANDLE)_spawnvpe(_P_NOWAIT, pszExecutable, papszArgs, papszEnvVars); 895 1252 kRedirectRestoreFdOrders(cOrders, paOrders, &pWorkingStdErr); 896 # ifdef CONFIG_NEW_WIN_CHILDREN1253 # ifdef CONFIG_NEW_WIN_CHILDREN 897 1254 if (cOrders > 0) 898 1255 MkWinChildExclusiveRelease(); 1256 # endif 1257 if ((intptr_t)hProcess != -1) 899 1258 # endif 900 if ((intptr_t)hProcess != -1) 901 { 902 # ifndef CONFIG_NEW_WIN_CHILDREN 1259 { 1260 # ifndef CONFIG_NEW_WIN_CHILDREN 903 1261 if (process_kmk_register_redirect(hProcess, pPidSpawned) == 0) 904 # else1262 # else 905 1263 if (MkWinChildCreateRedirect((intptr_t)hProcess, pPidSpawned) == 0) 906 # endif1264 # endif 907 1265 { 908 1266 if (cVerbosity > 0) … … 912 1270 { 913 1271 DWORD dwTmp; 914 # ifndef CONFIG_NEW_WIN_CHILDREN1272 # ifndef CONFIG_NEW_WIN_CHILDREN 915 1273 warn("sub_proc is out of slots, waiting for child..."); 916 # else1274 # else 917 1275 warn("MkWinChildCreateRedirect failed..."); 918 # endif1276 # endif 919 1277 dwTmp = WaitForSingleObject(hProcess, INFINITE); 920 1278 if (dwTmp != WAIT_OBJECT_0) … … 935 1293 } 936 1294 } 1295 # if 0 937 1296 else 938 1297 rcExit = err(10, "_spawnvpe(%s) failed", pszExecutable); 1298 # endif 939 1299 940 1300 # elif defined(KBUILD_OS_OS2) … … 951 1311 *pPidSpawned = 0; 952 1312 } 953 # else1313 # else 954 1314 rcExit = posix_spawnp(pPidSpawned, pszExecutable, pFileActions, NULL /*pAttr*/, papszArgs, papszEnvVars); 955 1315 if (rcExit == 0) … … 963 1323 *pPidSpawned = 0; 964 1324 } 965 # endif1325 # endif 966 1326 967 1327 #else /* !KMK */ … … 969 1329 * Spawning from inside the kmk_redirect executable. 970 1330 */ 971 # if defined(KBUILD_OS_WINDOWS) || defined(KBUILD_OS_OS2) 1331 # ifdef KBUILD_OS_WINDOWS 1332 HANDLE hProcess = INVALID_HANDLE_VALUE; 1333 rcExit = kRedirectCreateProcessWindows(pszExecutable, cArgs, papszArgs, papszEnvVars, pszCwd, 1334 cOrders, paOrders, &hProcess); 1335 if (rcExit == 0) 1336 { 1337 DWORD dwWait; 1338 do 1339 dwWait = WaitForSingleObject(hProcess, INFINITE); 1340 while (dwWait == WAIT_IO_COMPLETION || dwWait == WAIT_TIMEOUT); 1341 1342 dwWait = 11; 1343 if (GetExitCodeProcess(hProcess, &dwWait)) 1344 rcExit = dwWait; 1345 else 1346 rcExit = errx(11, "GetExitCodeProcess(%s) failed: %u", pszExecutable, GetLastError()); 1347 } 1348 1349 #elif defined(KBUILD_OS_WINDOWS) || defined(KBUILD_OS_OS2) 972 1350 errno = 0; 973 1351 # if defined(KBUILD_OS_WINDOWS) … … 1023 1401 #endif /* !KMK */ 1024 1402 } 1025 #if defined(CONFIG_NEW_WIN_CHILDREN) && defined(KBUILD_OS_WINDOWS) && defined(KMK) 1403 #if defined(CONFIG_NEW_WIN_CHILDREN) && defined(KBUILD_OS_WINDOWS) && defined(KMK) && 0 1026 1404 else if (cOrders > 0) 1027 1405 MkWinChildExclusiveRelease(); … … 1441 1819 else if (fd < 0) 1442 1820 rcExit = errx(2, "error: negative fd %d (%s)", fd, pszValue); 1821 #ifdef ONLY_TARGET_STANDARD_HANDLES 1822 else if (fd > 2) 1823 rcExit = errx(2, "error: %d is not a standard descriptor number", fd); 1824 #endif 1443 1825 else 1444 1826 { … … 1464 1846 else if (fd < 0) 1465 1847 rcExit = errx(2, "error: negative target descriptor %d ('-d %s')", fd, pszValue); 1848 #ifdef ONLY_TARGET_STANDARD_HANDLES 1849 else if (fd > 2) 1850 rcExit = errx(2, "error: target %d is not a standard descriptor number", fd); 1851 #endif 1466 1852 else if (*pszEqual != '=') 1467 1853 rcExit = errx(2, "syntax error: expected '=' to follow target descriptor: '-d %s'", pszValue); … … 1608 1994 else if (fd < 0) 1609 1995 rcExit = errx(2, "error: negative fd %d (%s)", fd, argv[iArg]); 1996 #ifdef ONLY_TARGET_STANDARD_HANDLES 1997 else if (fd > 2) 1998 rcExit = errx(2, "error: %d is not a standard descriptor number", fd); 1999 #endif 1610 2000 else 1611 2001 break; … … 1688 2078 * Do the spawning in a separate function (main is far to large as it is by now). 1689 2079 */ 1690 rcExit = kRedirectDoSpawn(pszExecutable, argc - iArg, &argv[iArg], fWatcomBrainDamage, papszEnvVars, szCwd, pszSavedCwd, 2080 rcExit = kRedirectDoSpawn(pszExecutable, argc - iArg, &argv[iArg], fWatcomBrainDamage, 2081 papszEnvVars, 2082 szCwd, pszSavedCwd, 1691 2083 #ifdef USE_POSIX_SPAWN 1692 2084 cOrders, aOrders, &FileActions, cVerbosity,
Note:
See TracChangeset
for help on using the changeset viewer.