diff options
Diffstat (limited to 'src/kmk/w32')
-rw-r--r-- | src/kmk/w32/Makefile.am | 26 | ||||
-rw-r--r-- | src/kmk/w32/Makefile.kup | 0 | ||||
-rw-r--r-- | src/kmk/w32/compat/Makefile.kup | 0 | ||||
-rw-r--r-- | src/kmk/w32/compat/dirent.c | 212 | ||||
-rw-r--r-- | src/kmk/w32/compat/posixfcn.c | 516 | ||||
-rw-r--r-- | src/kmk/w32/imagecache.c | 219 | ||||
-rw-r--r-- | src/kmk/w32/include/dirent.h | 66 | ||||
-rw-r--r-- | src/kmk/w32/include/dlfcn.h | 29 | ||||
-rw-r--r-- | src/kmk/w32/include/pathstuff.h | 30 | ||||
-rw-r--r-- | src/kmk/w32/include/sub_proc.h | 72 | ||||
-rw-r--r-- | src/kmk/w32/include/w32err.h | 26 | ||||
-rw-r--r-- | src/kmk/w32/pathstuff.c | 321 | ||||
-rw-r--r-- | src/kmk/w32/subproc/Makefile.kup | 0 | ||||
-rw-r--r-- | src/kmk/w32/subproc/NMakefile | 60 | ||||
-rw-r--r-- | src/kmk/w32/subproc/misc.c | 83 | ||||
-rw-r--r-- | src/kmk/w32/subproc/proc.h | 29 | ||||
-rw-r--r-- | src/kmk/w32/subproc/sub_proc.c | 1714 | ||||
-rw-r--r-- | src/kmk/w32/subproc/w32err.c | 85 | ||||
-rw-r--r-- | src/kmk/w32/tstFileInfo.c | 151 | ||||
-rw-r--r-- | src/kmk/w32/w32os.c | 220 | ||||
-rw-r--r-- | src/kmk/w32/winchildren.c | 3729 | ||||
-rw-r--r-- | src/kmk/w32/winchildren.h | 115 |
22 files changed, 7703 insertions, 0 deletions
diff --git a/src/kmk/w32/Makefile.am b/src/kmk/w32/Makefile.am new file mode 100644 index 0000000..5527f77 --- /dev/null +++ b/src/kmk/w32/Makefile.am @@ -0,0 +1,26 @@ +# Makefile.am to create libw32.a for mingw32 host. +# Copyright (C) 1997-2016 Free Software Foundation, Inc. +# This file is part of GNU Make. +# +# GNU Make is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see <http://www.gnu.org/licenses/>. + +AUTOMAKE_OPTIONS = subdir-objects + +noinst_LIBRARIES = libw32.a + +libw32_a_SOURCES = subproc/misc.c subproc/sub_proc.c subproc/w32err.c \ + compat/posixfcn.c pathstuff.c w32os.c + +libw32_a_CPPFLAGS = -I$(srcdir)/include -I$(srcdir)/subproc -I$(top_srcdir) \ + -I$(top_srcdir)/glob diff --git a/src/kmk/w32/Makefile.kup b/src/kmk/w32/Makefile.kup new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/kmk/w32/Makefile.kup diff --git a/src/kmk/w32/compat/Makefile.kup b/src/kmk/w32/compat/Makefile.kup new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/kmk/w32/compat/Makefile.kup diff --git a/src/kmk/w32/compat/dirent.c b/src/kmk/w32/compat/dirent.c new file mode 100644 index 0000000..9f992ec --- /dev/null +++ b/src/kmk/w32/compat/dirent.c @@ -0,0 +1,212 @@ +/* Directory entry code for Window platforms. +Copyright (C) 1996-2016 Free Software Foundation, Inc. +This file is part of GNU Make. + +GNU Make is free software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. */ + + +#include <config.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#ifdef KMK_PRF +# include <stdio.h> +#endif +#include "dirent.h" + + +DIR* +opendir(const char* pDirName) +{ + struct stat sb; + DIR* pDir; + char* pEndDirName; + int nBufferLen; + + /* sanity checks */ + if (!pDirName) { + errno = EINVAL; + return NULL; + } + if (stat(pDirName, &sb) != 0) { + errno = ENOENT; + return NULL; + } + if ((sb.st_mode & S_IFMT) != S_IFDIR) { + errno = ENOTDIR; + return NULL; + } + + /* allocate a DIR structure to return */ + pDir = (DIR *) malloc(sizeof (DIR)); + + if (!pDir) + return NULL; + + /* input directory name length */ + nBufferLen = strlen(pDirName); + + /* copy input directory name to DIR buffer */ + strcpy(pDir->dir_pDirectoryName, pDirName); + + /* point to end of the copied directory name */ + pEndDirName = &pDir->dir_pDirectoryName[nBufferLen - 1]; + + /* if directory name did not end in '/' or '\', add '/' */ + if ((*pEndDirName != '/') && (*pEndDirName != '\\')) { + pEndDirName++; + *pEndDirName = '/'; + } + + /* now append the wildcard character to the buffer */ + pEndDirName++; + *pEndDirName = '*'; + pEndDirName++; + *pEndDirName = '\0'; + + /* other values defaulted */ + pDir->dir_nNumFiles = 0; + pDir->dir_hDirHandle = INVALID_HANDLE_VALUE; + pDir->dir_ulCookie = __DIRENT_COOKIE; + +#ifdef KMK_PRF + fprintf(stderr, "opendir(%s) -> %p\n", pDirName, pDir); +#endif + return pDir; +} + +void +closedir(DIR *pDir) +{ + /* got a valid pointer? */ + if (!pDir) { + errno = EINVAL; + return; + } + + /* sanity check that this is a DIR pointer */ + if (pDir->dir_ulCookie != __DIRENT_COOKIE) { + errno = EINVAL; + return; + } + + /* close the WINDOWS32 directory handle */ + if (pDir->dir_hDirHandle != INVALID_HANDLE_VALUE) + FindClose(pDir->dir_hDirHandle); + + free(pDir); + + return; +} + +struct dirent * +readdir(DIR* pDir) +{ + WIN32_FIND_DATA wfdFindData; + + if (!pDir) { + errno = EINVAL; + return NULL; + } + + /* sanity check that this is a DIR pointer */ + if (pDir->dir_ulCookie != __DIRENT_COOKIE) { + errno = EINVAL; + return NULL; + } + + if (pDir->dir_nNumFiles == 0) { + pDir->dir_hDirHandle = FindFirstFile(pDir->dir_pDirectoryName, &wfdFindData); + if (pDir->dir_hDirHandle == INVALID_HANDLE_VALUE) + return NULL; + } else if (!FindNextFile(pDir->dir_hDirHandle, &wfdFindData)) + return NULL; + + /* bump count for next call to readdir() or telldir() */ + pDir->dir_nNumFiles++; + + /* fill in struct dirent values */ + pDir->dir_sdReturn.d_ino = (ino_t)-1; + strcpy(pDir->dir_sdReturn.d_name, wfdFindData.cFileName); + + return &pDir->dir_sdReturn; +} + +void +rewinddir(DIR* pDir) +{ + if (!pDir) { + errno = EINVAL; + return; + } + + /* sanity check that this is a DIR pointer */ + if (pDir->dir_ulCookie != __DIRENT_COOKIE) { + errno = EINVAL; + return; + } + + /* close the WINDOWS32 directory handle */ + if (pDir->dir_hDirHandle != INVALID_HANDLE_VALUE) + if (!FindClose(pDir->dir_hDirHandle)) + errno = EBADF; + + /* reset members which control readdir() */ + pDir->dir_hDirHandle = INVALID_HANDLE_VALUE; + pDir->dir_nNumFiles = 0; + + return; +} + +int +telldir(DIR* pDir) +{ + if (!pDir) { + errno = EINVAL; + return -1; + } + + /* sanity check that this is a DIR pointer */ + if (pDir->dir_ulCookie != __DIRENT_COOKIE) { + errno = EINVAL; + return -1; + } + + /* return number of times readdir() called */ + return pDir->dir_nNumFiles; +} + +void +seekdir(DIR* pDir, long nPosition) +{ + if (!pDir) + return; + + /* sanity check that this is a DIR pointer */ + if (pDir->dir_ulCookie != __DIRENT_COOKIE) + return; + + /* go back to beginning of directory */ + rewinddir(pDir); + + /* loop until we have found position we care about */ + for (--nPosition; nPosition && readdir(pDir); nPosition--); + + /* flag invalid nPosition value */ + if (nPosition) + errno = EINVAL; + + return; +} diff --git a/src/kmk/w32/compat/posixfcn.c b/src/kmk/w32/compat/posixfcn.c new file mode 100644 index 0000000..a537ca2 --- /dev/null +++ b/src/kmk/w32/compat/posixfcn.c @@ -0,0 +1,516 @@ +/* Replacements for Posix functions and Posix functionality for MS-Windows. + +Copyright (C) 2013-2016 Free Software Foundation, Inc. +This file is part of GNU Make. + +GNU Make is free software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include <string.h> +#include <io.h> +#include <stdarg.h> +#include <errno.h> +#include <windows.h> + +#include "dlfcn.h" + +#include "makeint.h" +#include "job.h" + +#ifndef NO_OUTPUT_SYNC +/* Support for OUTPUT_SYNC and related functionality. */ + +/* Emulation of fcntl that supports only F_GETFD and F_SETLKW. */ +int +fcntl (intptr_t fd, int cmd, ...) +{ + va_list ap; + + va_start (ap, cmd); + + switch (cmd) + { + case F_GETFD: + va_end (ap); + /* Could have used GetHandleInformation, but that isn't + supported on Windows 9X. */ + if (_get_osfhandle (fd) == -1) + return -1; + return 0; + case F_SETLKW: + { + void *buf = va_arg (ap, void *); + struct flock *fl = (struct flock *)buf; + HANDLE hmutex = (HANDLE)fd; + static struct flock last_fl; + short last_type = last_fl.l_type; + + va_end (ap); + + if (hmutex == INVALID_HANDLE_VALUE || !hmutex) + return -1; + + last_fl = *fl; + + switch (fl->l_type) + { + + case F_WRLCK: + { + DWORD result; + + if (last_type == F_WRLCK) + { + /* Don't call WaitForSingleObject if we already + own the mutex, because doing so will require + us to call ReleaseMutex an equal number of + times, before the mutex is actually + released. */ + return 0; + } + + result = WaitForSingleObject (hmutex, INFINITE); + switch (result) + { + case WAIT_OBJECT_0: + /* We don't care if the mutex owner crashed or + exited. */ + case WAIT_ABANDONED: + return 0; + case WAIT_FAILED: + case WAIT_TIMEOUT: /* cannot happen, really */ + { + DWORD err = GetLastError (); + + /* Invalidate the last command. */ + memset (&last_fl, 0, sizeof (last_fl)); + + switch (err) + { + case ERROR_INVALID_HANDLE: + case ERROR_INVALID_FUNCTION: + errno = EINVAL; + return -1; + default: + errno = EDEADLOCK; + return -1; + } + } + } + } + case F_UNLCK: + { + /* FIXME: Perhaps we should call ReleaseMutex + repatedly until it errors out, to make sure the + mutext is released even if we somehow managed to + to take ownership multiple times? */ + BOOL status = ReleaseMutex (hmutex); + + if (status) + return 0; + else + { + DWORD err = GetLastError (); + + if (err == ERROR_NOT_OWNER) + errno = EPERM; + else + { + memset (&last_fl, 0, sizeof (last_fl)); + errno = EINVAL; + } + return -1; + } + } + default: + errno = ENOSYS; + return -1; + } + } + default: + errno = ENOSYS; + va_end (ap); + return -1; + } +} + +static intptr_t mutex_handle = -1; + +/* Record in a static variable the mutex handle we were requested to + use. That nameless mutex was created by the top-level Make, and + its handle was passed to us via inheritance. The value of that + handle is passed via the command-line arguments, so that we know + which handle to use. */ +void +record_sync_mutex (const char *str) +{ +#ifdef CONFIG_NEW_WIN_CHILDREN + HANDLE hmtx = OpenMutexA(SYNCHRONIZE, FALSE /*fInheritable*/, str); + if (hmtx) + mutex_handle = (intptr_t)hmtx; + else + { + mutex_handle = -1; + errno = ENOENT; + } +#else + char *endp; + intptr_t hmutex = strtol (str, &endp, 16); + + if (*endp == '\0') + mutex_handle = hmutex; + else + { + mutex_handle = -1; + errno = EINVAL; + } +#endif +} + +/* Create a new mutex or reuse one created by our parent. */ +intptr_t +#ifdef CONFIG_NEW_WIN_CHILDREN +create_mutex (char *mtxname, size_t size) +#else +create_mutex (void) +#endif +{ +#ifndef CONFIG_NEW_WIN_CHILDREN + SECURITY_ATTRIBUTES secattr; +#endif + intptr_t hmutex = -1; + + /* If we have a mutex handle passed from the parent Make, just use + that. */ + if (mutex_handle > 0) + { +#ifdef CONFIG_NEW_WIN_CHILDREN + mtxname[0] = '\0'; +#endif + return mutex_handle; + } + +#ifdef CONFIG_NEW_WIN_CHILDREN + /* We're the top-level Make. Child Make processes will open our mutex, since + children does not inherit any handles other than the three standard ones. */ + snprintf(mtxname, size, "Make-output-%u-%u-%u", GetCurrentProcessId(), + GetCurrentThreadId(), GetTickCount()); + hmutex = (intptr_t)CreateMutexA (NULL, FALSE /*Locked*/, mtxname); +#else + /* We are the top-level Make, and we want the handle to be inherited + by our child processes. */ + secattr.nLength = sizeof (secattr); + secattr.lpSecurityDescriptor = NULL; /* use default security descriptor */ + secattr.bInheritHandle = TRUE; + + hmutex = (intptr_t)CreateMutex (&secattr, FALSE, NULL); +#endif + if (!hmutex) + { + DWORD err = GetLastError (); + + fprintf (stderr, "CreateMutex: error %lu\n", err); + errno = ENOLCK; + hmutex = -1; + } + + mutex_handle = hmutex; + return hmutex; +} + +/* Return non-zero if F1 and F2 are 2 streams representing the same + file or pipe or device. */ +int +same_stream (FILE *f1, FILE *f2) +{ + HANDLE fh1 = (HANDLE)_get_osfhandle (fileno (f1)); + HANDLE fh2 = (HANDLE)_get_osfhandle (fileno (f2)); + + /* Invalid file descriptors get treated as different streams. */ + if (fh1 && fh1 != INVALID_HANDLE_VALUE + && fh2 && fh2 != INVALID_HANDLE_VALUE) + { + if (fh1 == fh2) + return 1; + else + { + DWORD ftyp1 = GetFileType (fh1), ftyp2 = GetFileType (fh2); + + if (ftyp1 != ftyp2 + || ftyp1 == FILE_TYPE_UNKNOWN || ftyp2 == FILE_TYPE_UNKNOWN) + return 0; + else if (ftyp1 == FILE_TYPE_CHAR) + { + /* For character devices, check if they both refer to a + console. This loses if both handles refer to the + null device (FIXME!), but in that case we don't care + in the context of Make. */ + DWORD conmode1, conmode2; + + /* Each process on Windows can have at most 1 console, + so if both handles are for the console device, they + are the same. We also compare the console mode to + distinguish between stdin and stdout/stderr. */ + if (GetConsoleMode (fh1, &conmode1) + && GetConsoleMode (fh2, &conmode2) + && conmode1 == conmode2) + return 1; + } + else + { + /* For disk files and pipes, compare their unique + attributes. */ + BY_HANDLE_FILE_INFORMATION bhfi1, bhfi2; + + /* Pipes get zero in the volume serial number, but do + appear to have meaningful information in file index + attributes. We test file attributes as well, for a + good measure. */ + if (GetFileInformationByHandle (fh1, &bhfi1) + && GetFileInformationByHandle (fh2, &bhfi2)) + return (bhfi1.dwVolumeSerialNumber == bhfi2.dwVolumeSerialNumber + && bhfi1.nFileIndexLow == bhfi2.nFileIndexLow + && bhfi1.nFileIndexHigh == bhfi2.nFileIndexHigh + && bhfi1.dwFileAttributes == bhfi2.dwFileAttributes); + } + } + } + return 0; +} + +/* A replacement for tmpfile, since the MSVCRT implementation creates + the file in the root directory of the current drive, which might + not be writable by our user. Most of the code borrowed from + create_batch_file, see job.c. */ +FILE * +tmpfile (void) +{ + char temp_path[MAXPATHLEN]; + unsigned path_size = GetTempPath (sizeof temp_path, temp_path); + int path_is_dot = 0; + /* The following variable is static so we won't try to reuse a name + that was generated a little while ago, because that file might + not be on disk yet, since we use FILE_ATTRIBUTE_TEMPORARY below, + which tells the OS it doesn't need to flush the cache to disk. + If the file is not yet on disk, we might think the name is + available, while it really isn't. This happens in parallel + builds, where Make doesn't wait for one job to finish before it + launches the next one. */ + static unsigned uniq = 0; + static int second_loop = 0; + const char base[] = "gmake_tmpf"; + const unsigned sizemax = sizeof base - 1 + 4 + 10 + 10; + unsigned pid = GetCurrentProcessId (); + + if (path_size == 0) + { + path_size = GetCurrentDirectory (sizeof temp_path, temp_path); + path_is_dot = 1; + } + + ++uniq; + if (uniq >= 0x10000 && !second_loop) + { + /* If we already had 64K batch files in this + process, make a second loop through the numbers, + looking for free slots, i.e. files that were + deleted in the meantime. */ + second_loop = 1; + uniq = 1; + } + while (path_size > 0 && + path_size + sizemax < sizeof temp_path && + !(uniq >= 0x10000 && second_loop)) + { + HANDLE h; + + sprintf (temp_path + path_size, + "%s%s%u-%x.tmp", + temp_path[path_size - 1] == '\\' ? "" : "\\", + base, pid, uniq); + h = CreateFile (temp_path, /* file name */ + GENERIC_READ | GENERIC_WRITE | DELETE, /* desired access */ + FILE_SHARE_READ | FILE_SHARE_WRITE, /* share mode */ + NULL, /* default security attributes */ + CREATE_NEW, /* creation disposition */ + FILE_ATTRIBUTE_NORMAL | /* flags and attributes */ + FILE_ATTRIBUTE_TEMPORARY | + FILE_FLAG_DELETE_ON_CLOSE, + NULL); /* no template file */ + + if (h == INVALID_HANDLE_VALUE) + { + const DWORD er = GetLastError (); + + if (er == ERROR_FILE_EXISTS || er == ERROR_ALREADY_EXISTS) + { + ++uniq; + if (uniq == 0x10000 && !second_loop) + { + second_loop = 1; + uniq = 1; + } + } + + /* The temporary path is not guaranteed to exist, or might + not be writable by user. Use the current directory as + fallback. */ + else if (path_is_dot == 0) + { + path_size = GetCurrentDirectory (sizeof temp_path, temp_path); + path_is_dot = 1; + } + + else + { + errno = EACCES; + break; + } + } + else + { + int fd = _open_osfhandle ((intptr_t)h, 0); + + return _fdopen (fd, "w+b"); + } + } + + if (uniq >= 0x10000) + errno = EEXIST; + return NULL; +} + +#endif /* !NO_OUTPUT_SYNC */ + +#if MAKE_LOAD + +/* Support for dynamic loading of objects. */ + + +static DWORD last_err; + +void * +dlopen (const char *file, int mode) +{ + char dllfn[MAX_PATH], *p; + HANDLE dllhandle; + + if ((mode & ~(RTLD_LAZY | RTLD_NOW | RTLD_GLOBAL)) != 0) + { + errno = EINVAL; + last_err = ERROR_INVALID_PARAMETER; + return NULL; + } + + if (!file) + dllhandle = GetModuleHandle (NULL); + else + { + /* MSDN says to be sure to use backslashes in the DLL file name. */ + strcpy (dllfn, file); + for (p = dllfn; *p; p++) + if (*p == '/') + *p = '\\'; + + dllhandle = LoadLibrary (dllfn); + } + if (!dllhandle) + last_err = GetLastError (); + + return dllhandle; +} + +char * +dlerror (void) +{ + static char errbuf[1024]; + DWORD ret; + + if (!last_err) + return NULL; + + ret = FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, last_err, 0, errbuf, sizeof (errbuf), NULL); + while (ret > 0 && (errbuf[ret - 1] == '\n' || errbuf[ret - 1] == '\r')) + --ret; + + errbuf[ret] = '\0'; + if (!ret) + sprintf (errbuf, "Error code %lu", last_err); + + last_err = 0; + return errbuf; +} + +void * +dlsym (void *handle, const char *name) +{ + FARPROC addr = NULL; + + if (!handle || handle == INVALID_HANDLE_VALUE) + { + last_err = ERROR_INVALID_PARAMETER; + return NULL; + } + + addr = GetProcAddress (handle, name); + if (!addr) + last_err = GetLastError (); + + return (void *)addr; +} + +int +dlclose (void *handle) +{ + if (!handle || handle == INVALID_HANDLE_VALUE) + return -1; + if (!FreeLibrary (handle)) + return -1; + + return 0; +} + + +#endif /* MAKE_LOAD */ + + +/* MS runtime's isatty returns non-zero for any character device, + including the null device, which is not what we want. */ +int +isatty (int fd) +{ + HANDLE fh = (HANDLE) _get_osfhandle (fd); + DWORD con_mode; + + if (fh == INVALID_HANDLE_VALUE) + { + errno = EBADF; + return 0; + } + if (GetConsoleMode (fh, &con_mode)) + return 1; + + errno = ENOTTY; + return 0; +} + +char * +ttyname (int fd) +{ + /* This "knows" that Make only asks about stdout and stderr. A more + sophisticated implementation should test whether FD is open for + input or output. We can do that by looking at the mode returned + by GetConsoleMode. */ + return "CONOUT$"; +} diff --git a/src/kmk/w32/imagecache.c b/src/kmk/w32/imagecache.c new file mode 100644 index 0000000..2b26dac --- /dev/null +++ b/src/kmk/w32/imagecache.c @@ -0,0 +1,219 @@ +/* $Id: imagecache.c 3195 2018-03-27 18:09:23Z bird $ */ +/** @file + * kBuild specific executable image cache for Windows. + */ + +/* + * Copyright (c) 2012 knut st. osmundsen <bird-kBuild-spamx@anduin.net> + * + * This file is part of kBuild. + * + * kBuild is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * kBuild is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with kBuild. If not, see <http://www.gnu.org/licenses/> + * + */ + +/* No GNU coding style here! */ + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "makeint.h" + +#include <Windows.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct EXECCACHEENTRY +{ + /** The name hash value. */ + unsigned uHash; + /** The name length. */ + unsigned cwcName; + /** Pointer to the next name with the same hash. */ + struct EXECCACHEENTRY *pNext; + /** When it was last referenced. */ + unsigned uLastRef; + /** The module handle, LOAD_LIBRARY_AS_DATAFILE. */ + HMODULE hmod1; + /** The module handle, DONT_RESOLVE_DLL_REFERENCES. */ + HMODULE hmod2; + /** The executable path. */ + wchar_t wszName[1]; +} EXECCACHEENTRY; +typedef EXECCACHEENTRY *PEXECCACHEENTRY; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Critical section serializing all access. */ +static CRITICAL_SECTION g_CritSect; +/** Set if initialized. */ +static int volatile g_fInitialized = 0; +/** The number of cached images. */ +static unsigned g_cCached; +/** Used noting when entries was last used. + * Increased on each kmk_cache_exec_image call. */ +static unsigned g_uNow; + +/** The size of the hash table. */ +#define EXECCACHE_HASHTAB_SIZE 128 +/** The hash table. */ +static PEXECCACHEENTRY g_apHashTab[EXECCACHE_HASHTAB_SIZE]; + + +/** A sleepy approach to do-once. */ +static void kmk_cache_lazy_init(void) +{ + if (_InterlockedCompareExchange(&g_fInitialized, -1, 0) == 0) + { + InitializeCriticalSection(&g_CritSect); + _InterlockedExchange(&g_fInitialized, 1); + } + else + while (g_fInitialized != 1) + Sleep(1); +} + + +/* sdbm: + This algorithm was created for sdbm (a public-domain reimplementation of + ndbm) database library. it was found to do well in scrambling bits, + causing better distribution of the keys and fewer splits. it also happens + to be a good general hashing function with good distribution. the actual + function is hash(i) = hash(i - 1) * 65599 + str[i]; what is included below + is the faster version used in gawk. [there is even a faster, duff-device + version] the magic constant 65599 was picked out of thin air while + experimenting with different constants, and turns out to be a prime. + this is one of the algorithms used in berkeley db (see sleepycat) and + elsewhere. */ + +static unsigned execcache_calc_hash(const wchar_t *pwsz, size_t *pcch) +{ + wchar_t const * const pwszStart = pwsz; + unsigned hash = 0; + int ch; + + while ((ch = *pwsz++) != L'\0') + hash = ch + (hash << 6) + (hash << 16) - hash; + + *pcch = (size_t)(pwsz - pwszStart - 1); + return hash; +} + +/** + * Caches two memory mappings of the specified image so that it isn't flushed + * from the kernel's cache mananger. + * + * Not sure exactly how much this actually helps, but whatever... + * + * @param pwszExec The executable. + */ +extern void kmk_cache_exec_image_w(const wchar_t *pwszExec) +{ + /* + * Prepare name lookup and to lazy init. + */ + size_t cwcName; + const unsigned uHash = execcache_calc_hash(pwszExec, &cwcName); + PEXECCACHEENTRY *ppCur = &g_apHashTab[uHash % EXECCACHE_HASHTAB_SIZE]; + PEXECCACHEENTRY pCur; + + if (g_fInitialized != 1) + kmk_cache_lazy_init(); + + /* + * Do the lookup. + */ + EnterCriticalSection(&g_CritSect); + pCur = *ppCur; + while (pCur) + { + if ( pCur->uHash == uHash + && pCur->cwcName == cwcName + && !memcmp(pCur->wszName, pwszExec, cwcName * sizeof(wchar_t))) + { + pCur->uLastRef = ++g_uNow; + LeaveCriticalSection(&g_CritSect); + return; + } + ppCur = &pCur->pNext; + pCur = pCur->pNext; + } + LeaveCriticalSection(&g_CritSect); + + /* + * Not found, create a new entry. + */ + pCur = xmalloc(sizeof(*pCur) + cwcName * sizeof(wchar_t)); + pCur->uHash = uHash; + pCur->cwcName = (unsigned)cwcName; + pCur->pNext = NULL; + pCur->uLastRef = ++g_uNow; + memcpy(pCur->wszName, pwszExec, (cwcName + 1) * sizeof(wchar_t)); + pCur->hmod1 = LoadLibraryExW(pwszExec, NULL, LOAD_LIBRARY_AS_DATAFILE); + if (pCur->hmod1 != NULL) + pCur->hmod2 = LoadLibraryExW(pwszExec, NULL, DONT_RESOLVE_DLL_REFERENCES); + else + pCur->hmod2 = NULL; + + /* + * Insert it. + * Take into account that we might've been racing other threads, + * fortunately we don't evict anything from the cache. + */ + EnterCriticalSection(&g_CritSect); + if (*ppCur != NULL) + { + /* Find new end of chain and check for duplicate. */ + PEXECCACHEENTRY pCur2 = *ppCur; + while (pCur2) + { + if ( pCur->uHash == uHash + && pCur->cwcName == cwcName + && !memcmp(pCur->wszName, pwszExec, cwcName * sizeof(wchar_t))) + break; + ppCur = &pCur->pNext; + pCur = pCur->pNext; + } + + } + if (*ppCur == NULL) + { + *ppCur = pCur; + g_cCached++; + LeaveCriticalSection(&g_CritSect); + } + else + { + LeaveCriticalSection(&g_CritSect); + + if (pCur->hmod1 != NULL) + FreeLibrary(pCur->hmod1); + if (pCur->hmod2 != NULL) + FreeLibrary(pCur->hmod2); + free(pCur); + } +} + +extern void kmk_cache_exec_image_a(const char *pszExec) +{ + wchar_t wszExec[260]; + int cwc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszExec, strlen(pszExec) + 1, wszExec, 260); + if (cwc > 0) + kmk_cache_exec_image_w(wszExec); +} + diff --git a/src/kmk/w32/include/dirent.h b/src/kmk/w32/include/dirent.h new file mode 100644 index 0000000..c349e85 --- /dev/null +++ b/src/kmk/w32/include/dirent.h @@ -0,0 +1,66 @@ +/* Windows version of dirent.h +Copyright (C) 1996-2016 Free Software Foundation, Inc. +This file is part of GNU Make. + +GNU Make is free software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef _DIRENT_H +#define _DIRENT_H + +#ifdef KMK +# include <windows.h> +# include "nt/ntdir.h" + +#else /* !KMK */ + +#ifdef __MINGW32__ +# include <windows.h> +# include_next <dirent.h> +#else + +#include <stdlib.h> +#include <windows.h> +#include <limits.h> +#include <sys/types.h> + +#ifndef NAME_MAX +#define NAME_MAX 255 +#endif + +#define __DIRENT_COOKIE 0xfefeabab + + +struct dirent +{ + ino_t d_ino; /* unused - no equivalent on WINDOWS32 */ + char d_name[NAME_MAX+1]; +}; + +typedef struct dir_struct { + ULONG dir_ulCookie; + HANDLE dir_hDirHandle; + DWORD dir_nNumFiles; + char dir_pDirectoryName[NAME_MAX+1]; + struct dirent dir_sdReturn; +} DIR; + +DIR *opendir(const char *); +struct dirent *readdir(DIR *); +void rewinddir(DIR *); +void closedir(DIR *); +int telldir(DIR *); +void seekdir(DIR *, long); + +#endif /* !__MINGW32__ */ +#endif /* !KMK */ +#endif diff --git a/src/kmk/w32/include/dlfcn.h b/src/kmk/w32/include/dlfcn.h new file mode 100644 index 0000000..5a2ae28 --- /dev/null +++ b/src/kmk/w32/include/dlfcn.h @@ -0,0 +1,29 @@ +/* dlfcn.h replacement for MS-Windows build. +Copyright (C) 2013-2016 Free Software Foundation, Inc. +This file is part of GNU Make. + +GNU Make is free software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef DLFCN_H +#define DLFCN_H + +#define RTLD_LAZY 1 +#define RTLD_NOW 2 +#define RTLD_GLOBAL 4 + +extern void *dlopen (const char *, int); +extern void *dlsym (void *, const char *); +extern char *dlerror (void); +extern int dlclose (void *); + +#endif /* DLFCN_H */ diff --git a/src/kmk/w32/include/pathstuff.h b/src/kmk/w32/include/pathstuff.h new file mode 100644 index 0000000..3f839ec --- /dev/null +++ b/src/kmk/w32/include/pathstuff.h @@ -0,0 +1,30 @@ +/* Definitions for Windows path manipulation. +Copyright (C) 1996-2016 Free Software Foundation, Inc. +This file is part of GNU Make. + +GNU Make is free software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef _PATHSTUFF_H +#define _PATHSTUFF_H + +char *convert_Path_to_windows32(char *Path, char to_delim); +char *convert_vpath_to_windows32(char *Path, char to_delim); +#if 1 +char *unix_slashes(char *filename); /* bird */ +char *unix_slashes_resolved(const char *src, char *dst, unsigned len); /* bird */ +#else +char *w32ify(const char *filename, int resolve); +#endif +char *getcwd_fs(char *buf, int len); + +#endif diff --git a/src/kmk/w32/include/sub_proc.h b/src/kmk/w32/include/sub_proc.h new file mode 100644 index 0000000..51e3830 --- /dev/null +++ b/src/kmk/w32/include/sub_proc.h @@ -0,0 +1,72 @@ +/* Definitions for Windows process invocation. +Copyright (C) 1996-2016 Free Software Foundation, Inc. +This file is part of GNU Make. + +GNU Make is free software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef SUB_PROC_H +#define SUB_PROC_H +#ifdef CONFIG_NEW_WIN_CHILDREN +# error "Just checking..." +#endif + +/* + * Component Name: + * + * $Date$ + * + * $Source$ + * + * $Id$ + */ + +#define EXTERN_DECL(entry, args) extern entry args +#define VOID_DECL void + +EXTERN_DECL(HANDLE process_init, (VOID_DECL)); +EXTERN_DECL(HANDLE process_init_fd, (HANDLE stdinh, HANDLE stdouth, + HANDLE stderrh)); +EXTERN_DECL(long process_begin, (HANDLE proc, char **argv, char **envp, + char *exec_path, char *as_user)); +EXTERN_DECL(long process_pipe_io, (HANDLE proc, char *stdin_data, + int stdin_data_len)); +#ifndef KMK /* unused */ +EXTERN_DECL(long process_file_io, (HANDLE proc)); +#endif +EXTERN_DECL(void process_cleanup, (HANDLE proc)); +EXTERN_DECL(HANDLE process_wait_for_any, (int block, DWORD* pdwWaitStatus)); +EXTERN_DECL(void process_register, (HANDLE proc)); +EXTERN_DECL(HANDLE process_easy, (char** argv, char** env, + int outfd, int errfd)); +EXTERN_DECL(BOOL process_kill, (HANDLE proc, int signal)); +EXTERN_DECL(int process_used_slots, (VOID_DECL)); +EXTERN_DECL(DWORD process_set_handles, (HANDLE *handles)); + +#ifdef KMK +EXTERN_DECL(int process_kmk_register_submit, (HANDLE hEvent, intptr_t clue, pid_t *pPid)); +EXTERN_DECL(int process_kmk_register_redirect, (HANDLE hProcess, pid_t *pPid)); +#endif + +/* support routines */ +EXTERN_DECL(long process_errno, (HANDLE proc)); +EXTERN_DECL(long process_last_err, (HANDLE proc)); +EXTERN_DECL(long process_exit_code, (HANDLE proc)); +EXTERN_DECL(long process_signal, (HANDLE proc)); +EXTERN_DECL(char * process_outbuf, (HANDLE proc)); +EXTERN_DECL(char * process_errbuf, (HANDLE proc)); +EXTERN_DECL(int process_outcnt, (HANDLE proc)); +EXTERN_DECL(int process_errcnt, (HANDLE proc)); +EXTERN_DECL(void process_pipes, (HANDLE proc, int pipes[3])); +EXTERN_DECL(void process_noinherit, (int fildes)); + +#endif diff --git a/src/kmk/w32/include/w32err.h b/src/kmk/w32/include/w32err.h new file mode 100644 index 0000000..b4292f2 --- /dev/null +++ b/src/kmk/w32/include/w32err.h @@ -0,0 +1,26 @@ +/* Definitions for Windows error handling. +Copyright (C) 1996-2016 Free Software Foundation, Inc. +This file is part of GNU Make. + +GNU Make is free software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef _W32ERR_H_ +#define _W32ERR_H_ + +#ifndef EXTERN_DECL +#define EXTERN_DECL(entry, args) entry args +#endif + +EXTERN_DECL(const char * map_windows32_error_to_string, (DWORD error)); + +#endif /* !_W32ERR_H */ diff --git a/src/kmk/w32/pathstuff.c b/src/kmk/w32/pathstuff.c new file mode 100644 index 0000000..f80cbf1 --- /dev/null +++ b/src/kmk/w32/pathstuff.c @@ -0,0 +1,321 @@ +/* Path conversion for Windows pathnames. +Copyright (C) 1996-2016 Free Software Foundation, Inc. +This file is part of GNU Make. + +GNU Make is free software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "makeint.h" +#include <string.h> +#include <stdlib.h> +#include "pathstuff.h" +#if 1 /* bird */ +# include "nt_fullpath.h" +# include <assert.h> +#endif + +/* + * Convert delimiter separated vpath to Canonical format. + */ +char * +convert_vpath_to_windows32(char *Path, char to_delim) +{ + char *etok; /* token separator for old Path */ + + /* + * Convert all spaces to delimiters. Note that pathnames which + * contain blanks get trounced here. Use 8.3 format as a workaround. + */ + for (etok = Path; etok && *etok; etok++) + if (ISBLANK ((unsigned char) *etok)) + *etok = to_delim; + + return (convert_Path_to_windows32(Path, to_delim)); +} + +/* + * Convert delimiter separated path to Canonical format. + */ +char * +convert_Path_to_windows32(char *Path, char to_delim) +{ + char *etok; /* token separator for old Path */ + char *p; /* points to element of old Path */ + + /* is this a multi-element Path ? */ + /* FIXME: Perhaps use ":;\"" in strpbrk to convert all quotes to + delimiters as well, as a way to handle quoted directories in + PATH? */ + for (p = Path, etok = strpbrk(p, ":;"); + etok; + etok = strpbrk(p, ":;")) + if ((etok - p) == 1) { + if (*(etok - 1) == ';' || + *(etok - 1) == ':') { + etok[-1] = to_delim; + etok[0] = to_delim; + p = ++etok; + continue; /* ignore empty bucket */ + } else if (!isalpha ((unsigned char) *p)) { + /* found one to count, handle things like '.' */ + *etok = to_delim; + p = ++etok; + } else if ((*etok == ':') && (etok = strpbrk(etok+1, ":;"))) { + /* found one to count, handle drive letter */ + *etok = to_delim; + p = ++etok; + } else + /* all finished, force abort */ + p += strlen(p); + } else if (*p == '"') { /* a quoted directory */ + for (p++; *p && *p != '"'; p++) /* skip quoted part */ + ; + etok = strpbrk(p, ":;"); /* find next delimiter */ + if (etok) { + *etok = to_delim; + p = ++etok; + } else + p += strlen(p); + } else { + /* found another one, no drive letter */ + *etok = to_delim; + p = ++etok; + } + + return Path; +} + +/* + * Convert to forward slashes directly (w32ify(filename, 0)). + */ +char *unix_slashes(char *filename) /* bird */ +{ + char *slash = filename ; + while ((slash = strchr(slash, '\\')) != NULL) + *slash++ = '/'; + return filename; +} + +/* + * Resolve and convert to forward slashes directly (w32ify(filename, 1)). + * Returns if out of buffer space. + */ +char *unix_slashes_resolved(const char *src, char *dst, unsigned len) +{ + assert(len >= FILENAME_MAX); + *dst = '\0'; /** @todo nt_fullpath_cached needs to return some indication of overflow. */ +#if 1 + nt_fullpath_cached(src, dst, len); +#else + _fullpath(dst, src, len); +#endif + + return unix_slashes(dst); +} + +#if 0 /* bird: replaced by unix_slashes and unix_slahes_resolved. */ +/* + * Convert to forward slashes. Resolve to full pathname optionally + */ +char * +w32ify(const char *filename, int resolve) +{ + static char w32_path[FILENAME_MAX]; +#if 1 /* bird */ + + if (resolve) { + nt_fullpath_cached(filename, w32_path, sizeof(w32_path)); + } else { + w32_path[0] = '\0'; + strncat(w32_path, filename, sizeof(w32_path)); + } + return unix_slashes(w32_path); + +#else /* !bird */ + char *p; + + if (resolve) { + _fullpath(w32_path, filename, sizeof (w32_path)); + } else + strncpy(w32_path, filename, sizeof (w32_path)); + + for (p = w32_path; p && *p; p++) + if (*p == '\\') + *p = '/'; + + return w32_path; +#endif /* !bird */ +} +#endif + +char * +getcwd_fs(char* buf, int len) +{ + char *p = getcwd(buf, len); + + if (p) { +#if 1 + p = unix_slashes(p); +#else + char *q = w32ify(buf, 0); +#if 1 /* bird - UPSTREAM? */ + buf[0] = '\0'; + strncat(buf, q, len); +#else /* !bird */ + strncpy(buf, q, len); +#endif +#endif + } + + return p; +} + +#ifdef unused +/* + * Convert delimiter separated pathnames (e.g. PATH) or single file pathname + * (e.g. c:/foo, c:\bar) to NutC format. If we are handed a string that + * _NutPathToNutc() fails to convert, just return the path we were handed + * and assume the caller will know what to do with it (It was probably + * a mistake to try and convert it anyway due to some of the bizarre things + * that might look like pathnames in makefiles). + */ +char * +convert_path_to_nutc(char *path) +{ + int count; /* count of path elements */ + char *nutc_path; /* new NutC path */ + int nutc_path_len; /* length of buffer to allocate for new path */ + char *pathp; /* pointer to nutc_path used to build it */ + char *etok; /* token separator for old path */ + char *p; /* points to element of old path */ + char sep; /* what flavor of separator used in old path */ + char *rval; + + /* is this a multi-element path ? */ + for (p = path, etok = strpbrk(p, ":;"), count = 0; + etok; + etok = strpbrk(p, ":;")) + if ((etok - p) == 1) { + if (*(etok - 1) == ';' || + *(etok - 1) == ':') { + p = ++etok; + continue; /* ignore empty bucket */ + } else if (etok = strpbrk(etok+1, ":;")) + /* found one to count, handle drive letter */ + p = ++etok, count++; + else + /* all finished, force abort */ + p += strlen(p); + } else + /* found another one, no drive letter */ + p = ++etok, count++; + + if (count) { + count++; /* x1;x2;x3 <- need to count x3 */ + + /* + * Hazard a guess on how big the buffer needs to be. + * We have to convert things like c:/foo to /c=/foo. + */ + nutc_path_len = strlen(path) + (count*2) + 1; + nutc_path = xmalloc(nutc_path_len); + pathp = nutc_path; + *pathp = '\0'; + + /* + * Loop through PATH and convert one elemnt of the path at at + * a time. Single file pathnames will fail this and fall + * to the logic below loop. + */ + for (p = path, etok = strpbrk(p, ":;"); + etok; + etok = strpbrk(p, ":;")) { + + /* don't trip up on device specifiers or empty path slots */ + if ((etok - p) == 1) + if (*(etok - 1) == ';' || + *(etok - 1) == ':') { + p = ++etok; + continue; + } else if ((etok = strpbrk(etok+1, ":;")) == NULL) + break; /* thing found was a WINDOWS32 pathname */ + + /* save separator */ + sep = *etok; + + /* terminate the current path element -- temporarily */ + *etok = '\0'; + +#ifdef __NUTC__ + /* convert to NutC format */ + if (_NutPathToNutc(p, pathp, 0) == FALSE) { + free(nutc_path); + rval = savestring(path, strlen(path)); + return rval; + } +#else + *pathp++ = '/'; + *pathp++ = p[0]; + *pathp++ = '='; + *pathp++ = '/'; + strcpy(pathp, &p[2]); +#endif + + pathp += strlen(pathp); + *pathp++ = ':'; /* use Unix style path separtor for new path */ + *pathp = '\0'; /* make sure we are null terminaed */ + + /* restore path separator */ + *etok = sep; + + /* point p to first char of next path element */ + p = ++etok; + + } + } else { + nutc_path_len = strlen(path) + 3; + nutc_path = xmalloc(nutc_path_len); + pathp = nutc_path; + *pathp = '\0'; + p = path; + } + + /* + * OK, here we handle the last element in PATH (e.g. c of a;b;c) + * or the path was a single filename and will be converted + * here. Note, testing p here assures that we don't trip up + * on paths like a;b; which have trailing delimiter followed by + * nothing. + */ + if (*p != '\0') { +#ifdef __NUTC__ + if (_NutPathToNutc(p, pathp, 0) == FALSE) { + free(nutc_path); + rval = savestring(path, strlen(path)); + return rval; + } +#else + *pathp++ = '/'; + *pathp++ = p[0]; + *pathp++ = '='; + *pathp++ = '/'; + strcpy(pathp, &p[2]); +#endif + } else + *(pathp-1) = '\0'; /* we're already done, don't leave trailing : */ + + rval = savestring(nutc_path, strlen(nutc_path)); + free(nutc_path); + return rval; +} + +#endif diff --git a/src/kmk/w32/subproc/Makefile.kup b/src/kmk/w32/subproc/Makefile.kup new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/kmk/w32/subproc/Makefile.kup diff --git a/src/kmk/w32/subproc/NMakefile b/src/kmk/w32/subproc/NMakefile new file mode 100644 index 0000000..325e55c --- /dev/null +++ b/src/kmk/w32/subproc/NMakefile @@ -0,0 +1,60 @@ +# NOTE: If you have no 'make' program at all to process this makefile, run +# 'build.bat' instead. +# +# Copyright (C) 1996-2016 Free Software Foundation, Inc. +# This file is part of GNU Make. +# +# GNU Make is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see <http://www.gnu.org/licenses/>. + +# +# NMakefile for GNU Make (subproc library) +# +LIB = lib +CC = cl +MAKE = nmake + +OUTDIR=. +MAKEFILE=NMakefile + +CFLAGS_any = /nologo /MT /W4 /GX /Z7 /YX /D WIN32 /D WINDOWS32 /D _WINDOWS -I. -I../include -I../../ +CFLAGS_debug = $(CFLAGS_any) /Od /D _DEBUG /FR.\WinDebug\ /Fp.\WinDebug\subproc.pch /Fo.\WinDebug/ +CFLAGS_release = $(CFLAGS_any) /O2 /FR.\WinRel\ /Fp.\WinRel\subproc.pch /Fo.\WinRel/ + +all: Release Debug + +Release: + $(MAKE) /f $(MAKEFILE) OUTDIR=WinRel CFLAGS="$(CFLAGS_release)" WinRel/subproc.lib +Debug: + $(MAKE) /f $(MAKEFILE) OUTDIR=WinDebug CFLAGS="$(CFLAGS_debug)" WinDebug/subproc.lib + +clean: + rmdir /s /q WinRel WinDebug + erase *.pdb + +$(OUTDIR): + if not exist .\$@\nul mkdir .\$@ + +OBJS = $(OUTDIR)/misc.obj $(OUTDIR)/w32err.obj $(OUTDIR)/sub_proc.obj + +$(OUTDIR)/subproc.lib: $(OUTDIR) $(OBJS) + $(LIB) -out:$@ @<< + $(OBJS) +<< + +.c{$(OUTDIR)}.obj: + $(CC) $(CFLAGS) /c $< + +$(OUTDIR)/misc.obj: misc.c proc.h +$(OUTDIR)/sub_proc.obj: sub_proc.c ../include/sub_proc.h ../include/w32err.h proc.h +$(OUTDIR)/w32err.obj: w32err.c ../include/w32err.h diff --git a/src/kmk/w32/subproc/misc.c b/src/kmk/w32/subproc/misc.c new file mode 100644 index 0000000..8b17413 --- /dev/null +++ b/src/kmk/w32/subproc/misc.c @@ -0,0 +1,83 @@ +/* Process handling for Windows +Copyright (C) 1996-2016 Free Software Foundation, Inc. +This file is part of GNU Make. + +GNU Make is free software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include <config.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <windows.h> +#include "proc.h" + + +/* + * Description: Convert a NULL string terminated UNIX environment block to + * an environment block suitable for a windows32 system call + * + * Returns: TRUE= success, FALSE=fail + * + * Notes/Dependencies: the environment block is sorted in case-insensitive + * order, is double-null terminated, and is a char *, not a char ** + */ +int _cdecl compare(const void *a1, const void *a2) +{ + return _stricoll(*((char**)a1),*((char**)a2)); +} +bool_t +arr2envblk(char **arr, char **envblk_out, int *envsize_needed) +{ + char **tmp; + int size_needed; + int arrcnt; + char *ptr; + + arrcnt = 0; + while (arr[arrcnt]) { + arrcnt++; + } + + tmp = (char**) calloc(arrcnt + 1, sizeof(char *)); + if (!tmp) { + return FALSE; + } + + arrcnt = 0; + size_needed = *envsize_needed = 0; + while (arr[arrcnt]) { + tmp[arrcnt] = arr[arrcnt]; + size_needed += strlen(arr[arrcnt]) + 1; + arrcnt++; + } + size_needed++; + *envsize_needed = size_needed; + + qsort((void *) tmp, (size_t) arrcnt, sizeof (char*), compare); + + ptr = *envblk_out = calloc(size_needed, 1); + if (!ptr) { + free(tmp); + return FALSE; + } + + arrcnt = 0; + while (tmp[arrcnt]) { + strcpy(ptr, tmp[arrcnt]); + ptr += strlen(tmp[arrcnt]) + 1; + arrcnt++; + } + + free(tmp); + return TRUE; +} diff --git a/src/kmk/w32/subproc/proc.h b/src/kmk/w32/subproc/proc.h new file mode 100644 index 0000000..7ccb5ea --- /dev/null +++ b/src/kmk/w32/subproc/proc.h @@ -0,0 +1,29 @@ +/* Definitions for Windows +Copyright (C) 1996-2016 Free Software Foundation, Inc. +This file is part of GNU Make. + +GNU Make is free software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef _PROC_H +#define _PROC_H + +typedef int bool_t; + +#define E_SCALL 101 +#define E_IO 102 +#define E_NO_MEM 103 +#define E_FORK 104 + +extern bool_t arr2envblk(char **arr, char **envblk_out, int *envsize_needed); + +#endif diff --git a/src/kmk/w32/subproc/sub_proc.c b/src/kmk/w32/subproc/sub_proc.c new file mode 100644 index 0000000..6ccfa47 --- /dev/null +++ b/src/kmk/w32/subproc/sub_proc.c @@ -0,0 +1,1714 @@ +/* Process handling for Windows. +Copyright (C) 1996-2016 Free Software Foundation, Inc. +This file is part of GNU Make. + +GNU Make is free software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include <config.h> +#include <stdlib.h> +#include <stdio.h> +#include <io.h> /* for _get_osfhandle */ +#ifdef _MSC_VER +# include <stddef.h> /* for intptr_t */ +#else +# include <stdint.h> +#endif +#include <string.h> +#include <process.h> /* for msvc _beginthreadex, _endthreadex */ +#include <signal.h> +#include <windows.h> + +#include "makeint.h" +#include "filedef.h" +#include "variable.h" +#include "sub_proc.h" +#include "proc.h" +#include "w32err.h" +#include "debug.h" + +#ifdef KMK +# include <assert.h> +# include "kmkbuiltin.h" +extern void kmk_cache_exec_image_a(const char *); /* imagecache.c */ +#endif + +static char *make_command_line(char *shell_name, char *exec_path, char **argv); + +typedef struct sub_process_t { +#ifdef KMK + enum { kRegular = 0, kSubmit, kSubProcFreed } enmType; + intptr_t clue; +#endif + intptr_t sv_stdin[2]; + intptr_t sv_stdout[2]; + intptr_t sv_stderr[2]; + int using_pipes; + char *inp; + DWORD incnt; + char * volatile outp; + volatile DWORD outcnt; + char * volatile errp; + volatile DWORD errcnt; + pid_t pid; + int exit_code; + int signal; + long last_err; + long lerrno; +} sub_process; + +static long process_file_io_private(sub_process *pproc, BOOL fNeedToWait); /* bird */ + +/* keep track of children so we can implement a waitpid-like routine */ +static sub_process *proc_array[MAXIMUM_WAIT_OBJECTS]; +static int proc_index = 0; +static int fake_exits_pending = 0; + + +/* + * Fill a HANDLE list with handles to wait for. + */ +DWORD +process_set_handles(HANDLE *handles) +{ + DWORD count = 0; + int i; + + /* Build array of handles to wait for */ + for (i = 0; i < proc_index; i++) { + /* Don't wait on child processes that have already finished */ + if (fake_exits_pending && proc_array[i]->exit_code) + continue; + + handles[count++] = (HANDLE) proc_array[i]->pid; + } + + return count; +} + +#ifndef KMK /* Inefficient! */ +/* + * When a process has been waited for, adjust the wait state + * array so that we don't wait for it again + */ +static void +process_adjust_wait_state(sub_process* pproc) +{ + int i; + + if (!proc_index) + return; + + for (i = 0; i < proc_index; i++) + if (proc_array[i]->pid == pproc->pid) + break; + + if (i < proc_index) { + proc_index--; + if (i != proc_index) + memmove(&proc_array[i], &proc_array[i+1], + (proc_index-i) * sizeof(sub_process*)); + proc_array[proc_index] = NULL; + } +} + +#endif /* !KMK */ + +/* + * Waits for any of the registered child processes to finish. + */ +static sub_process * +process_wait_for_any_private(int block, DWORD* pdwWaitStatus) +{ + HANDLE handles[MAXIMUM_WAIT_OBJECTS]; + DWORD retval, which; + int i; + + if (!proc_index) + return NULL; + + /* build array of handles to wait for */ + for (i = 0; i < proc_index; i++) { + handles[i] = (HANDLE) proc_array[i]->pid; + + if (fake_exits_pending && proc_array[i]->exit_code) + break; + } + + /* wait for someone to exit */ + if (!fake_exits_pending) { +#ifdef KMK +l_wait_again: +#endif + retval = WaitForMultipleObjects(proc_index, handles, FALSE, (block ? INFINITE : 0)); + which = retval - WAIT_OBJECT_0; + } else { + fake_exits_pending--; + retval = !WAIT_FAILED; + which = i; + } + + /* If the pointer is not NULL, set the wait status result variable. */ + if (pdwWaitStatus) + *pdwWaitStatus = retval; + + /* return pointer to process */ + if ((retval == WAIT_TIMEOUT) || (retval == WAIT_FAILED)) { + return NULL; + } + else { + sub_process* pproc = proc_array[which]; +#ifdef KMK + if (pproc->enmType == kSubmit) { + /* Try get the result from kSubmit.c. This may not succeed if the whole + result hasn't arrived yet, in which we just restart the wait. */ + if (kSubmitSubProcGetResult(pproc->clue, &pproc->exit_code, &pproc->signal) != 0) { + goto l_wait_again; + } + } +#endif +#ifndef KMK /* Inefficient! */ + process_adjust_wait_state(pproc); +#else + proc_index--; + if ((int)which < proc_index) + proc_array[which] = proc_array[proc_index]; + proc_array[proc_index] = NULL; +#endif + return pproc; + } +} + +/* + * Terminate a process. + */ +BOOL + process_kill(HANDLE proc, int signal) +{ + sub_process* pproc = (sub_process*) proc; +#ifdef KMK + if (pproc->enmType == kRegular) { +#endif + pproc->signal = signal; + return (TerminateProcess((HANDLE) pproc->pid, signal)); +#ifdef KMK + } else if (pproc->enmType == kSubmit) + return kSubmitSubProcKill(pproc->clue, signal) == 0; + assert(0); + return FALSE; +#endif +} + +/* + * Use this function to register processes you wish to wait for by + * calling process_file_io(NULL) or process_wait_any(). This must be done + * because it is possible for callers of this library to reuse the same + * handle for multiple processes launches :-( + */ +void +process_register(HANDLE proc) +{ +#ifdef KMK + assert(((sub_process *)proc)->enmType == kRegular); +#endif + if (proc_index < MAXIMUM_WAIT_OBJECTS) + proc_array[proc_index++] = (sub_process *) proc; +} + +#ifdef KMK + +/** + * Interface used by kmkbuiltin/kSubmit.c to register stuff going down in a + * worker process. + * + * @returns 0 on success, -1 if there are too many sub-processes already. + * @param hEvent The event semaphore to wait on. + * @param clue The clue to base. + * @param pPid Where to return the pid that job.c expects. + */ +int +process_kmk_register_submit(HANDLE hEvent, intptr_t clue, pid_t *pPid) +{ + if (proc_index < MAXIMUM_WAIT_OBJECTS) { + sub_process *pSubProc = (sub_process *)xcalloc(sizeof(*pSubProc)); + pSubProc->enmType = kSubmit; + pSubProc->clue = clue; + pSubProc->pid = (intptr_t)hEvent; + + proc_array[proc_index++] = pSubProc; + *pPid = (intptr_t)pSubProc; + return 0; + } + return -1; +} + +/** + * Interface used by kmkbuiltin/kRedirect.c to register a spawned process. + * + * @returns 0 on success, -1 if there are too many sub-processes already. + * @param hProcess The process handle. + * @param pPid Where to return the pid that job.c expects. + */ +int +process_kmk_register_redirect(HANDLE hProcess, pid_t *pPid) +{ + if (proc_index < MAXIMUM_WAIT_OBJECTS) { + sub_process *pSubProc = (sub_process *)xcalloc(sizeof(*pSubProc)); + pSubProc->enmType = kRegular; + pSubProc->pid = (intptr_t)hProcess; + + proc_array[proc_index++] = pSubProc; + *pPid = (intptr_t)pSubProc; + return 0; + } + return -1; +} + +#endif /* KMK */ + +/* + * Return the number of processes that we are still waiting for. + */ +int +process_used_slots(void) +{ + return proc_index; +} + +/* + * Public function which works kind of like waitpid(). Wait for any + * of the children to die and return results. To call this function, + * you must do 1 of things: + * + * x = process_easy(...); + * + * or + * + * x = process_init_fd(); + * process_register(x); + * + * or + * + * x = process_init(); + * process_register(x); + * + * You must NOT then call process_pipe_io() because this function is + * not capable of handling automatic notification of any child + * death. + */ + +HANDLE +process_wait_for_any(int block, DWORD* pdwWaitStatus) +{ + sub_process* pproc = process_wait_for_any_private(block, pdwWaitStatus); + + if (!pproc) + return NULL; + else { + /* + * Ouch! can't tell caller if this fails directly. Caller + * will have to use process_last_err() + */ +#ifdef KMK + /* Invalidate negative directory cache entries now that a + job has completed and possibly created new files that + was missing earlier. */ + dir_cache_invalid_after_job (); + + if (pproc->enmType == kRegular) { + (void)process_file_io_private(pproc, FALSE); + } +#else + (void) process_file_io(pproc); +#endif + return ((HANDLE) pproc); + } +} + +long +process_signal(HANDLE proc) +{ + if (proc == INVALID_HANDLE_VALUE) return 0; + return (((sub_process *)proc)->signal); +} + +long +process_last_err(HANDLE proc) +{ + if (proc == INVALID_HANDLE_VALUE) return ERROR_INVALID_HANDLE; + return (((sub_process *)proc)->last_err); +} + +long +process_exit_code(HANDLE proc) +{ + if (proc == INVALID_HANDLE_VALUE) return EXIT_FAILURE; + return (((sub_process *)proc)->exit_code); +} + +void +process_noinherit(int fd) +{ + HANDLE fh = (HANDLE)_get_osfhandle(fd); + + if (fh && fh != INVALID_HANDLE_VALUE) + SetHandleInformation(fh, HANDLE_FLAG_INHERIT, 0); +} + +/* +2006-02: +All the following functions are currently unused. +All of them would crash gmake if called with argument INVALID_HANDLE_VALUE. +Hence whoever wants to use one of this functions must invent and implement +a reasonable error handling for this function. + +char * +process_outbuf(HANDLE proc) +{ + return (((sub_process *)proc)->outp); +} + +char * +process_errbuf(HANDLE proc) +{ + return (((sub_process *)proc)->errp); +} + +int +process_outcnt(HANDLE proc) +{ + return (((sub_process *)proc)->outcnt); +} + +int +process_errcnt(HANDLE proc) +{ + return (((sub_process *)proc)->errcnt); +} + +void +process_pipes(HANDLE proc, int pipes[3]) +{ + pipes[0] = ((sub_process *)proc)->sv_stdin[0]; + pipes[1] = ((sub_process *)proc)->sv_stdout[0]; + pipes[2] = ((sub_process *)proc)->sv_stderr[0]; + return; +} +*/ + + HANDLE +process_init() +{ + sub_process *pproc; + /* + * open file descriptors for attaching stdin/stdout/sterr + */ + HANDLE stdin_pipes[2]; + HANDLE stdout_pipes[2]; + HANDLE stderr_pipes[2]; + SECURITY_ATTRIBUTES inherit; + BYTE sd[SECURITY_DESCRIPTOR_MIN_LENGTH]; + + pproc = malloc(sizeof(*pproc)); + memset(pproc, 0, sizeof(*pproc)); + + /* We can't use NULL for lpSecurityDescriptor because that + uses the default security descriptor of the calling process. + Instead we use a security descriptor with no DACL. This + allows nonrestricted access to the associated objects. */ + + if (!InitializeSecurityDescriptor((PSECURITY_DESCRIPTOR)(&sd), + SECURITY_DESCRIPTOR_REVISION)) { + pproc->last_err = GetLastError(); + pproc->lerrno = E_SCALL; + return((HANDLE)pproc); + } + + inherit.nLength = sizeof(inherit); + inherit.lpSecurityDescriptor = (PSECURITY_DESCRIPTOR)(&sd); + inherit.bInheritHandle = TRUE; + + // By convention, parent gets pipe[0], and child gets pipe[1] + // This means the READ side of stdin pipe goes into pipe[1] + // and the WRITE side of the stdout and stderr pipes go into pipe[1] + if (CreatePipe( &stdin_pipes[1], &stdin_pipes[0], &inherit, 0) == FALSE || + CreatePipe( &stdout_pipes[0], &stdout_pipes[1], &inherit, 0) == FALSE || + CreatePipe( &stderr_pipes[0], &stderr_pipes[1], &inherit, 0) == FALSE) { + + pproc->last_err = GetLastError(); + pproc->lerrno = E_SCALL; + return((HANDLE)pproc); + } + + // + // Mark the parent sides of the pipes as non-inheritable + // + if (SetHandleInformation(stdin_pipes[0], + HANDLE_FLAG_INHERIT, 0) == FALSE || + SetHandleInformation(stdout_pipes[0], + HANDLE_FLAG_INHERIT, 0) == FALSE || + SetHandleInformation(stderr_pipes[0], + HANDLE_FLAG_INHERIT, 0) == FALSE) { + + pproc->last_err = GetLastError(); + pproc->lerrno = E_SCALL; + return((HANDLE)pproc); + } + pproc->sv_stdin[0] = (intptr_t) stdin_pipes[0]; + pproc->sv_stdin[1] = (intptr_t) stdin_pipes[1]; + pproc->sv_stdout[0] = (intptr_t) stdout_pipes[0]; + pproc->sv_stdout[1] = (intptr_t) stdout_pipes[1]; + pproc->sv_stderr[0] = (intptr_t) stderr_pipes[0]; + pproc->sv_stderr[1] = (intptr_t) stderr_pipes[1]; + + pproc->using_pipes = 1; + + pproc->lerrno = 0; + + return((HANDLE)pproc); +} + + + HANDLE +process_init_fd(HANDLE stdinh, HANDLE stdouth, HANDLE stderrh) +{ + sub_process *pproc; + + pproc = malloc(sizeof(*pproc)); + if (pproc) { + memset(pproc, 0, sizeof(*pproc)); + + /* + * Just pass the provided file handles to the 'child + * side' of the pipe, bypassing pipes altogether. + */ + pproc->sv_stdin[1] = (intptr_t) stdinh; + pproc->sv_stdout[1] = (intptr_t) stdouth; + pproc->sv_stderr[1] = (intptr_t) stderrh; + + pproc->last_err = pproc->lerrno = 0; + } + + return((HANDLE)pproc); +} + + +static HANDLE +find_file(const char *exec_path, const char *path_var, + char *full_fname, DWORD full_len) +{ + HANDLE exec_handle; + char *fname; + char *ext; + DWORD req_len; + int i; + static const char *extensions[] = + /* Should .com come before no-extension case? */ + { ".exe", ".cmd", ".bat", "", ".com", NULL }; + + fname = xmalloc(strlen(exec_path) + 5); + strcpy(fname, exec_path); + ext = fname + strlen(fname); + + for (i = 0; extensions[i]; i++) { + strcpy(ext, extensions[i]); + if (((req_len = SearchPath (path_var, fname, NULL, full_len, + full_fname, NULL)) > 0 + /* For compatibility with previous code, which + used OpenFile, and with Windows operation in + general, also look in various default + locations, such as Windows directory and + Windows System directory. Warning: this also + searches PATH in the Make's environment, which + might not be what the Makefile wants, but it + seems to be OK as a fallback, after the + previous SearchPath failed to find on child's + PATH. */ + || (req_len = SearchPath (NULL, fname, NULL, full_len, + full_fname, NULL)) > 0) + && req_len <= full_len + && (exec_handle = + CreateFile(full_fname, + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL)) != INVALID_HANDLE_VALUE) { + free(fname); + return(exec_handle); + } + } + + free(fname); + return INVALID_HANDLE_VALUE; +} + +/* + * Return non-zero of FNAME specifies a batch file and its name + * includes embedded whitespace. + */ + +static int +batch_file_with_spaces(const char *fname) +{ + size_t fnlen = strlen(fname); + + return (fnlen > 4 + && (_strnicmp(fname + fnlen - 4, ".bat", 4) == 0 + || _strnicmp(fname + fnlen - 4, ".cmd", 4) == 0) + /* The set of characters in the 2nd arg to strpbrk + should be the same one used by make_command_line + below to decide whether an argv[] element needs + quoting. */ + && strpbrk(fname, " \t") != NULL); +} + + +/* + * Description: Create the child process to be helped + * + * Returns: success <=> 0 + * + * Notes/Dependencies: + */ +long +process_begin( + HANDLE proc, + char **argv, + char **envp, + char *exec_path, + char *as_user) +{ + sub_process *pproc = (sub_process *)proc; + char *shell_name = 0; + int file_not_found=0; + HANDLE exec_handle; + char exec_fname[MAX_PATH]; + const char *path_var = NULL; + char **ep; + char buf[MAX_PATH]; + DWORD bytes_returned; + DWORD flags; + char *command_line; + STARTUPINFO startInfo; + PROCESS_INFORMATION procInfo; + char *envblk=NULL; + int envsize_needed = 0; + int pass_null_exec_path = 0; +#ifdef KMK + size_t exec_path_len; + extern int process_priority; + + assert (pproc->enmType == kRegular); +#endif + + /* + * Shell script detection... if the exec_path starts with #! then + * we want to exec shell-script-name exec-path, not just exec-path + * NT doesn't recognize #!/bin/sh or #!/etc/Tivoli/bin/perl. We do not + * hard-code the path to the shell or perl or whatever: Instead, we + * assume it's in the path somewhere (generally, the NT tools + * bin directory) + */ + +#ifdef KMK + /* kmk performance: Don't bother looking for shell scripts in .exe files. */ + exec_path_len = strlen(exec_path); + if (exec_path_len > 4 + && exec_path[exec_path_len - 4] == '.' + && !stricmp(exec_path + exec_path_len - 3, "exe")) { + exec_handle = INVALID_HANDLE_VALUE; + exec_fname[0] = '\0'; + } + else { +#endif /* KMK */ + + /* Use the Makefile's value of PATH to look for the program to + execute, because it could be different from Make's PATH + (e.g., if the target sets its own value. */ + if (envp) + for (ep = envp; *ep; ep++) { + if (strncmp (*ep, "PATH=", 5) == 0 + || strncmp (*ep, "Path=", 5) == 0) { + path_var = *ep + 5; + break; + } + } + exec_handle = find_file(exec_path, path_var, + exec_fname, sizeof(exec_fname)); +#ifdef KMK + } +#endif + + /* + * If we couldn't open the file, just assume that Windows will be + * somehow able to find and execute it. If the first character + * of the command is '/', assume they set SHELL to a Unixy shell + * that have some magic mounts known only to it, and run the whole + * command via $SHELL -c "COMMAND" instead. + */ + if (exec_handle == INVALID_HANDLE_VALUE) { + if (exec_path[0] == '/') { + char *new_argv0; + char **argvi = argv; + int arglen = 0; + + strcpy(buf, variable_expand ("$(SHELL)")); + shell_name = &buf[0]; + strcpy(exec_fname, "-c"); + /* Construct a single command string in argv[0]. */ + while (*argvi) { + arglen += strlen(*argvi) + 1; + argvi++; + } + new_argv0 = xmalloc(arglen + 1); + new_argv0[0] = '\0'; + for (argvi = argv; *argvi; argvi++) { + strcat(new_argv0, *argvi); + strcat(new_argv0, " "); + } + /* Remove the extra blank at the end. */ + new_argv0[arglen-1] = '\0'; + free(argv[0]); + argv[0] = new_argv0; + argv[1] = NULL; + } + else + file_not_found++; + } + else { + /* Attempt to read the first line of the file */ + if (ReadFile( exec_handle, + buf, sizeof(buf) - 1, /* leave room for trailing NULL */ + &bytes_returned, 0) == FALSE || bytes_returned < 2) { + + pproc->last_err = GetLastError(); + pproc->lerrno = E_IO; + CloseHandle(exec_handle); + return(-1); + } + if (buf[0] == '#' && buf[1] == '!') { + /* + * This is a shell script... Change the command line from + * exec_path args to shell_name exec_path args + */ + char *p; + + /* Make sure buf is NULL terminated */ + buf[bytes_returned] = 0; + /* + * Depending on the file system type, etc. the first line + * of the shell script may end with newline or newline-carriage-return + * Whatever it ends with, cut it off. + */ + p= strchr(buf, '\n'); + if (p) + *p = 0; + p = strchr(buf, '\r'); + if (p) + *p = 0; + + /* + * Find base name of shell + */ + shell_name = strrchr( buf, '/'); + if (shell_name) { + shell_name++; + } else { + shell_name = &buf[2];/* skipping "#!" */ + } + + } + CloseHandle(exec_handle); + } + + flags = 0; + + if (file_not_found) + command_line = make_command_line( shell_name, exec_path, argv); + else { + /* If exec_fname includes whitespace, CreateProcess + behaves erratically and unreliably, and often fails + if argv[0] also includes whitespace (and thus will + be quoted by make_command_line below). So in that + case, we don't pass exec_fname as the 1st arg to + CreateProcess, but instead replace argv[0] with + exec_fname (to keep its leading directories and + extension as found by find_file), and pass NULL to + CreateProcess as its 1st arg. This works around + the bugs in CreateProcess, which are probably + caused by its passing the command to cmd.exe with + some incorrect quoting. */ + if (!shell_name + && batch_file_with_spaces(exec_fname) + && _stricmp(exec_path, argv[0]) == 0) { + char *new_argv, *p; + char **argvi; + int arglen, i; + pass_null_exec_path = 1; + /* Rewrite argv[] replacing argv[0] with exec_fname. */ + for (argvi = argv + 1, arglen = strlen(exec_fname) + 1; + *argvi; + argvi++) { + arglen += strlen(*argvi) + 1; + } + new_argv = xmalloc(arglen); + p = strcpy(new_argv, exec_fname) + strlen(exec_fname) + 1; + for (argvi = argv + 1, i = 1; *argvi; argvi++, i++) { + strcpy(p, *argvi); + argv[i] = p; + p += strlen(*argvi) + 1; + } + argv[i] = NULL; + free (argv[0]); + argv[0] = new_argv; + } + command_line = make_command_line( shell_name, exec_fname, argv); + } + + if ( command_line == NULL ) { + pproc->last_err = 0; + pproc->lerrno = E_NO_MEM; + return(-1); + } + + if (envp) { + if (arr2envblk(envp, &envblk, &envsize_needed) == FALSE) { + pproc->lerrno = E_NO_MEM; + free( command_line ); + if ((pproc->last_err == ERROR_INVALID_PARAMETER + || pproc->last_err == ERROR_MORE_DATA) + && envsize_needed > 32*1024) { + fprintf (stderr, "CreateProcess failed, probably because environment is too large (%d bytes).\n", + envsize_needed); + } + pproc->last_err = 0; + return(-1); + } + } + + if (shell_name || file_not_found || pass_null_exec_path) { + exec_path = 0; /* Search for the program in %Path% */ + } else { + exec_path = exec_fname; + } + + /* + * Set up inherited stdin, stdout, stderr for child + */ + memset(&startInfo, '\0', sizeof(startInfo)); + GetStartupInfo(&startInfo); +#ifndef KMK + startInfo.dwFlags = STARTF_USESTDHANDLES; +#endif + startInfo.lpReserved = 0; + startInfo.cbReserved2 = 0; + startInfo.lpReserved2 = 0; +#ifndef KMK + startInfo.hStdInput = (HANDLE)pproc->sv_stdin[1]; + startInfo.hStdOutput = (HANDLE)pproc->sv_stdout[1]; + startInfo.hStdError = (HANDLE)pproc->sv_stderr[1]; +#else + if ( ((HANDLE)pproc->sv_stdin[1] != INVALID_HANDLE_VALUE && pproc->sv_stdin[1]) + || ((HANDLE)pproc->sv_stdout[1] != INVALID_HANDLE_VALUE && pproc->sv_stdout[1]) + || ((HANDLE)pproc->sv_stderr[1] != INVALID_HANDLE_VALUE && pproc->sv_stderr[1]) ) { + startInfo.dwFlags = STARTF_USESTDHANDLES; + startInfo.hStdInput = (HANDLE)pproc->sv_stdin[1]; + startInfo.hStdOutput = (HANDLE)pproc->sv_stdout[1]; + startInfo.hStdError = (HANDLE)pproc->sv_stderr[1]; + } else { + startInfo.dwFlags = 0; + startInfo.hStdInput = 0; + startInfo.hStdOutput = 0; + startInfo.hStdError = 0; + } +#endif + + if (as_user) { + free(envblk); + return -1; + } else { + DB (DB_JOBS, ("CreateProcess(%s,%s,...)\n", + exec_path ? exec_path : "NULL", + command_line ? command_line : "NULL")); +#ifdef KMK + if (exec_fname[0]) + kmk_cache_exec_image_a(exec_fname); + else if (exec_path) + kmk_cache_exec_image_a(exec_path); + else if (argv[0]) + kmk_cache_exec_image_a(argv[0]); + + switch (process_priority) { + case 1: flags |= CREATE_SUSPENDED | IDLE_PRIORITY_CLASS; break; + case 2: flags |= CREATE_SUSPENDED | BELOW_NORMAL_PRIORITY_CLASS; break; + case 3: flags |= CREATE_SUSPENDED | NORMAL_PRIORITY_CLASS; break; + case 4: flags |= CREATE_SUSPENDED | HIGH_PRIORITY_CLASS; break; + case 5: flags |= CREATE_SUSPENDED | REALTIME_PRIORITY_CLASS; break; + } +#endif + if (CreateProcess( + exec_path, + command_line, + NULL, + 0, /* default security attributes for thread */ + TRUE, /* inherit handles (e.g. helper pipes, oserv socket) */ + flags, + envblk, + 0, /* default starting directory */ + &startInfo, + &procInfo) == FALSE) { + + pproc->last_err = GetLastError(); + pproc->lerrno = E_FORK; + fprintf(stderr, "process_begin: CreateProcess(%s, %s, ...) failed.\n", + exec_path ? exec_path : "NULL", command_line); + free(envblk); + free( command_line ); + return(-1); + } +#ifdef KMK + switch (process_priority) { + case 1: SetThreadPriority(procInfo.hThread, THREAD_PRIORITY_IDLE); break; + case 2: SetThreadPriority(procInfo.hThread, THREAD_PRIORITY_BELOW_NORMAL); break; + case 3: SetThreadPriority(procInfo.hThread, THREAD_PRIORITY_NORMAL); break; + case 4: SetThreadPriority(procInfo.hThread, THREAD_PRIORITY_HIGHEST); break; + case 5: SetThreadPriority(procInfo.hThread, THREAD_PRIORITY_TIME_CRITICAL); break; + } + ResumeThread(procInfo.hThread); +#endif + } + + pproc->pid = (pid_t)procInfo.hProcess; + /* Close the thread handle -- we'll just watch the process */ + CloseHandle(procInfo.hThread); + + /* Close the halves of the pipes we don't need */ + if ((HANDLE)pproc->sv_stdin[1] != INVALID_HANDLE_VALUE && pproc->sv_stdin[1]) + CloseHandle((HANDLE)pproc->sv_stdin[1]); + if ((HANDLE)pproc->sv_stdout[1] != INVALID_HANDLE_VALUE && pproc->sv_stdout[1]) + CloseHandle((HANDLE)pproc->sv_stdout[1]); + if ((HANDLE)pproc->sv_stderr[1] != INVALID_HANDLE_VALUE && pproc->sv_stderr[1]) + CloseHandle((HANDLE)pproc->sv_stderr[1]); + pproc->sv_stdin[1] = 0; + pproc->sv_stdout[1] = 0; + pproc->sv_stderr[1] = 0; + + free( command_line ); + free(envblk); + pproc->lerrno=0; + return 0; +} + + + +#if 0 /* unused */ +static DWORD +proc_stdin_thread(sub_process *pproc) +{ + DWORD in_done; + for (;;) { + if (WriteFile( (HANDLE) pproc->sv_stdin[0], pproc->inp, pproc->incnt, + &in_done, NULL) == FALSE) + _endthreadex(0); + // This if should never be true for anonymous pipes, but gives + // us a chance to change I/O mechanisms later + if (in_done < pproc->incnt) { + pproc->incnt -= in_done; + pproc->inp += in_done; + } else { + _endthreadex(0); + } + } + return 0; // for compiler warnings only.. not reached +} + +static DWORD +proc_stdout_thread(sub_process *pproc) +{ + DWORD bufsize = 1024; + char c; + DWORD nread; + pproc->outp = malloc(bufsize); + if (pproc->outp == NULL) + _endthreadex(0); + pproc->outcnt = 0; + + for (;;) { + if (ReadFile( (HANDLE)pproc->sv_stdout[0], &c, 1, &nread, NULL) + == FALSE) { +/* map_windows32_error_to_string(GetLastError());*/ + _endthreadex(0); + } + if (nread == 0) + _endthreadex(0); + if (pproc->outcnt + nread > bufsize) { + bufsize += nread + 512; + pproc->outp = realloc(pproc->outp, bufsize); + if (pproc->outp == NULL) { + pproc->outcnt = 0; + _endthreadex(0); + } + } + pproc->outp[pproc->outcnt++] = c; + } + return 0; +} + +static DWORD +proc_stderr_thread(sub_process *pproc) +{ + DWORD bufsize = 1024; + char c; + DWORD nread; + pproc->errp = malloc(bufsize); + if (pproc->errp == NULL) + _endthreadex(0); + pproc->errcnt = 0; + + for (;;) { + if (ReadFile( (HANDLE)pproc->sv_stderr[0], &c, 1, &nread, NULL) == FALSE) { + map_windows32_error_to_string(GetLastError()); + _endthreadex(0); + } + if (nread == 0) + _endthreadex(0); + if (pproc->errcnt + nread > bufsize) { + bufsize += nread + 512; + pproc->errp = realloc(pproc->errp, bufsize); + if (pproc->errp == NULL) { + pproc->errcnt = 0; + _endthreadex(0); + } + } + pproc->errp[pproc->errcnt++] = c; + } + return 0; +} + + +/* + * Purpose: collects output from child process and returns results + * + * Description: + * + * Returns: + * + * Notes/Dependencies: + */ + long +process_pipe_io( + HANDLE proc, + char *stdin_data, + int stdin_data_len) +{ + sub_process *pproc = (sub_process *)proc; + bool_t stdin_eof = FALSE, stdout_eof = FALSE, stderr_eof = FALSE; + HANDLE childhand = (HANDLE) pproc->pid; + HANDLE tStdin = NULL, tStdout = NULL, tStderr = NULL; + unsigned int dwStdin, dwStdout, dwStderr; + HANDLE wait_list[4]; + DWORD wait_count; + DWORD wait_return; + HANDLE ready_hand; + bool_t child_dead = FALSE; + BOOL GetExitCodeResult; +#ifdef KMK + assert (pproc->enmType == kRegular); +#endif + + /* + * Create stdin thread, if needed + */ + pproc->inp = stdin_data; + pproc->incnt = stdin_data_len; + if (!pproc->inp) { + stdin_eof = TRUE; + CloseHandle((HANDLE)pproc->sv_stdin[0]); + pproc->sv_stdin[0] = 0; + } else { + tStdin = (HANDLE) _beginthreadex( 0, 1024, + (unsigned (__stdcall *) (void *))proc_stdin_thread, + pproc, 0, &dwStdin); + if (tStdin == 0) { + pproc->last_err = GetLastError(); + pproc->lerrno = E_SCALL; + goto done; + } + } + + /* + * Assume child will produce stdout and stderr + */ + tStdout = (HANDLE) _beginthreadex( 0, 1024, + (unsigned (__stdcall *) (void *))proc_stdout_thread, pproc, 0, + &dwStdout); + tStderr = (HANDLE) _beginthreadex( 0, 1024, + (unsigned (__stdcall *) (void *))proc_stderr_thread, pproc, 0, + &dwStderr); + + if (tStdout == 0 || tStderr == 0) { + + pproc->last_err = GetLastError(); + pproc->lerrno = E_SCALL; + goto done; + } + + + /* + * Wait for all I/O to finish and for the child process to exit + */ + + while (!stdin_eof || !stdout_eof || !stderr_eof || !child_dead) { + wait_count = 0; + if (!stdin_eof) { + wait_list[wait_count++] = tStdin; + } + if (!stdout_eof) { + wait_list[wait_count++] = tStdout; + } + if (!stderr_eof) { + wait_list[wait_count++] = tStderr; + } + if (!child_dead) { + wait_list[wait_count++] = childhand; + } + + wait_return = WaitForMultipleObjects(wait_count, wait_list, + FALSE, /* don't wait for all: one ready will do */ + child_dead? 1000 :INFINITE); /* after the child dies, subthreads have + one second to collect all remaining output */ + + if (wait_return == WAIT_FAILED) { +/* map_windows32_error_to_string(GetLastError());*/ + pproc->last_err = GetLastError(); + pproc->lerrno = E_SCALL; + goto done; + } + + ready_hand = wait_list[wait_return - WAIT_OBJECT_0]; + + if (ready_hand == tStdin) { + CloseHandle((HANDLE)pproc->sv_stdin[0]); + pproc->sv_stdin[0] = 0; + CloseHandle(tStdin); + tStdin = 0; + stdin_eof = TRUE; + + } else if (ready_hand == tStdout) { + + CloseHandle((HANDLE)pproc->sv_stdout[0]); + pproc->sv_stdout[0] = 0; + CloseHandle(tStdout); + tStdout = 0; + stdout_eof = TRUE; + + } else if (ready_hand == tStderr) { + + CloseHandle((HANDLE)pproc->sv_stderr[0]); + pproc->sv_stderr[0] = 0; + CloseHandle(tStderr); + tStderr = 0; + stderr_eof = TRUE; + + } else if (ready_hand == childhand) { + + DWORD ierr; + GetExitCodeResult = GetExitCodeProcess(childhand, &ierr); + if (ierr == CONTROL_C_EXIT) { + pproc->signal = SIGINT; + } else { + pproc->exit_code = ierr; + } + if (GetExitCodeResult == FALSE) { + pproc->last_err = GetLastError(); + pproc->lerrno = E_SCALL; + goto done; + } + child_dead = TRUE; + + } else { + + /* ?? Got back a handle we didn't query ?? */ + pproc->last_err = 0; + pproc->lerrno = E_FAIL; + goto done; + } + } + + done: + if (tStdin != 0) + CloseHandle(tStdin); + if (tStdout != 0) + CloseHandle(tStdout); + if (tStderr != 0) + CloseHandle(tStderr); + + if (pproc->lerrno) + return(-1); + else + return(0); + +} +#endif /* unused */ + +#ifndef KMK /* unused */ +/* + * Purpose: collects output from child process and returns results + * + * Description: + * + * Returns: + * + * Notes/Dependencies: + */ + long +process_file_io( + HANDLE proc) +{ + sub_process *pproc; + + if (proc == NULL) + pproc = process_wait_for_any_private(1, 0); + else + pproc = (sub_process *)proc; + + /* some sort of internal error */ + if (!pproc) + return -1; + + return process_file_io_private(proc, TRUE); +} +#endif /* !KMK - unused */ + +/* private function, avoid some kernel calls. (bird) */ +static long +process_file_io_private( + sub_process *pproc, + BOOL fNeedToWait) +{ + HANDLE childhand; + DWORD wait_return; + BOOL GetExitCodeResult; + DWORD ierr; + + childhand = (HANDLE) pproc->pid; + + /* + * This function is poorly named, and could also be used just to wait + * for child death if you're doing your own pipe I/O. If that is + * the case, close the pipe handles here. + */ + if (pproc->sv_stdin[0]) { + CloseHandle((HANDLE)pproc->sv_stdin[0]); + pproc->sv_stdin[0] = 0; + } + if (pproc->sv_stdout[0]) { + CloseHandle((HANDLE)pproc->sv_stdout[0]); + pproc->sv_stdout[0] = 0; + } + if (pproc->sv_stderr[0]) { + CloseHandle((HANDLE)pproc->sv_stderr[0]); + pproc->sv_stderr[0] = 0; + } + +#ifdef KMK + if (childhand == NULL || childhand == INVALID_HANDLE_VALUE) { + goto done2; + } +#endif + + /* + * Wait for the child process to exit + */ + + if (fNeedToWait) { /* bird */ + wait_return = WaitForSingleObject(childhand, INFINITE); + + if (wait_return != WAIT_OBJECT_0) { +/* map_windows32_error_to_string(GetLastError());*/ + pproc->last_err = GetLastError(); + pproc->lerrno = E_SCALL; + goto done2; + } + } /* bird */ + + GetExitCodeResult = GetExitCodeProcess(childhand, &ierr); + if (ierr == CONTROL_C_EXIT) { + pproc->signal = SIGINT; + } else { + pproc->exit_code = ierr; + } + if (GetExitCodeResult == FALSE) { + pproc->last_err = GetLastError(); + pproc->lerrno = E_SCALL; + } + +done2: + if (pproc->lerrno) + return(-1); + else + return(0); + +} + +/* + * Description: Clean up any leftover handles, etc. It is up to the + * caller to manage and free the input, output, and stderr buffers. + */ + void +process_cleanup( + HANDLE proc) +{ + sub_process *pproc = (sub_process *)proc; + int i; + +#ifdef KMK + if (pproc->enmType == kRegular) { +#endif + if (pproc->using_pipes) { + for (i= 0; i <= 1; i++) { + if ((HANDLE)pproc->sv_stdin[i] + && (HANDLE)pproc->sv_stdin[i] != INVALID_HANDLE_VALUE) + CloseHandle((HANDLE)pproc->sv_stdin[i]); + if ((HANDLE)pproc->sv_stdout[i] + && (HANDLE)pproc->sv_stdout[i] != INVALID_HANDLE_VALUE) + CloseHandle((HANDLE)pproc->sv_stdout[i]); + if ((HANDLE)pproc->sv_stderr[i] + && (HANDLE)pproc->sv_stderr[i] != INVALID_HANDLE_VALUE) + CloseHandle((HANDLE)pproc->sv_stderr[i]); + } + } + if ((HANDLE)pproc->pid) + CloseHandle((HANDLE)pproc->pid); +#ifdef KMK + } else if (pproc->enmType == kSubmit) { + kSubmitSubProcCleanup(pproc->clue); + } else { + assert(0); + return; + } + pproc->enmType = kSubProcFreed; +#endif + + free(pproc); +} + + +/* + * Description: + * Create a command line buffer to pass to CreateProcess + * + * Returns: the buffer or NULL for failure + * Shell case: sh_name a:/full/path/to/script argv[1] argv[2] ... + * Otherwise: argv[0] argv[1] argv[2] ... + * + * Notes/Dependencies: + * CreateProcess does not take an argv, so this command creates a + * command line for the executable. + */ + +static char * +make_command_line( char *shell_name, char *full_exec_path, char **argv) +{ + int argc = 0; + char** argvi; + int* enclose_in_quotes = NULL; + int* enclose_in_quotes_i; + unsigned int bytes_required = 0; + char* command_line; + char* command_line_i; + int cygwin_mode = 0; /* HAVE_CYGWIN_SHELL */ + int have_sh = 0; /* HAVE_CYGWIN_SHELL */ +#undef HAVE_CYGWIN_SHELL /* bird: paranoia */ +#ifdef HAVE_CYGWIN_SHELL + have_sh = (shell_name != NULL || strstr(full_exec_path, "sh.exe")); + cygwin_mode = 1; +#endif + + if (shell_name && full_exec_path) { + bytes_required + = strlen(shell_name) + 1 + strlen(full_exec_path); + /* + * Skip argv[0] if any, when shell_name is given. + * The special case of "-c" in full_exec_path means + * argv[0] is not the shell name, but the command string + * to pass to the shell. + */ + if (*argv && strcmp(full_exec_path, "-c")) argv++; + /* + * Add one for the intervening space. + */ + if (*argv) bytes_required++; + } + + argvi = argv; + while (*(argvi++)) argc++; + + if (argc) { + enclose_in_quotes = (int*) calloc(1, argc * sizeof(int)); + + if (!enclose_in_quotes) { + return NULL; + } + } + + /* We have to make one pass through each argv[i] to see if we need + * to enclose it in ", so we might as well figure out how much + * memory we'll need on the same pass. + */ + + argvi = argv; + enclose_in_quotes_i = enclose_in_quotes; + while(*argvi) { + char* p = *argvi; + unsigned int backslash_count = 0; + + /* + * We have to enclose empty arguments in ". + */ + if (!(*p)) *enclose_in_quotes_i = 1; + + while(*p) { + switch (*p) { + case '\"': + /* + * We have to insert a backslash for each " + * and each \ that precedes the ". + */ + bytes_required += (backslash_count + 1); + backslash_count = 0; + break; + +#if !defined(HAVE_MKS_SHELL) && !defined(HAVE_CYGWIN_SHELL) + case '\\': + backslash_count++; + break; +#endif + /* + * At one time we set *enclose_in_quotes_i for '*' or '?' to suppress + * wildcard expansion in programs linked with MSVC's SETARGV.OBJ so + * that argv in always equals argv out. This was removed. Say you have + * such a program named glob.exe. You enter + * glob '*' + * at the sh command prompt. Obviously the intent is to make glob do the + * wildcarding instead of sh. If we set *enclose_in_quotes_i for '*' or '?', + * then the command line that glob would see would be + * glob "*" + * and the _setargv in SETARGV.OBJ would _not_ expand the *. + */ + case ' ': + case '\t': + *enclose_in_quotes_i = 1; + /* fall through */ + + default: + backslash_count = 0; + break; + } + + /* + * Add one for each character in argv[i]. + */ + bytes_required++; + + p++; + } + + if (*enclose_in_quotes_i) { + /* + * Add one for each enclosing ", + * and one for each \ that precedes the + * closing ". + */ + bytes_required += (backslash_count + 2); + } + + /* + * Add one for the intervening space. + */ + if (*(++argvi)) bytes_required++; + enclose_in_quotes_i++; + } + + /* + * Add one for the terminating NULL. + */ + bytes_required++; +#ifdef KMK /* for the space before the final " in case we need it. */ + bytes_required++; +#endif + + command_line = (char*) malloc(bytes_required); + + if (!command_line) { + free(enclose_in_quotes); + return NULL; + } + + command_line_i = command_line; + + if (shell_name && full_exec_path) { + while(*shell_name) { + *(command_line_i++) = *(shell_name++); + } + + *(command_line_i++) = ' '; + + while(*full_exec_path) { + *(command_line_i++) = *(full_exec_path++); + } + + if (*argv) { + *(command_line_i++) = ' '; + } + } + + argvi = argv; + enclose_in_quotes_i = enclose_in_quotes; + + while(*argvi) { + char* p = *argvi; + unsigned int backslash_count = 0; + + if (*enclose_in_quotes_i) { + *(command_line_i++) = '\"'; + } + + while(*p) { + if (*p == '\"') { + if (cygwin_mode && have_sh) { /* HAVE_CYGWIN_SHELL */ + /* instead of a \", cygwin likes "" */ + *(command_line_i++) = '\"'; + } else { + + /* + * We have to insert a backslash for the " + * and each \ that precedes the ". + */ + backslash_count++; + + while(backslash_count) { + *(command_line_i++) = '\\'; + backslash_count--; + }; + } +#if !defined(HAVE_MKS_SHELL) && !defined(HAVE_CYGWIN_SHELL) + } else if (*p == '\\') { + backslash_count++; + } else { + backslash_count = 0; +#endif + } + + /* + * Copy the character. + */ + *(command_line_i++) = *(p++); + } + + if (*enclose_in_quotes_i) { +#if !defined(HAVE_MKS_SHELL) && !defined(HAVE_CYGWIN_SHELL) + /* + * Add one \ for each \ that precedes the + * closing ". + */ + while(backslash_count--) { + *(command_line_i++) = '\\'; + }; +#endif +#ifdef KMK + /* + * ash it put off by echo "hello world" ending up as: + * G:/.../kmk_ash.exe -c "echo ""hello world""" + * It wants a space before the last '"'. + * (The 'test_shell' goals in Makefile.kmk tests this problem.) + */ + if (command_line_i[-1] == '\"' /* && cygwin_mode && have_sh*/ && !argvi[1]) { + *(command_line_i++) = ' '; + } +#endif + + *(command_line_i++) = '\"'; + } + + /* + * Append an intervening space. + */ + if (*(++argvi)) { + *(command_line_i++) = ' '; + } + + enclose_in_quotes_i++; + } + + /* + * Append the terminating NULL. + */ + *command_line_i = '\0'; + + free(enclose_in_quotes); + return command_line; +} + +/* + * Description: Given an argv and optional envp, launch the process + * using the default stdin, stdout, and stderr handles. + * Also, register process so that process_wait_for_any_private() + * can be used via process_file_io(NULL) or + * process_wait_for_any(). + * + * Returns: + * + * Notes/Dependencies: + */ +HANDLE +process_easy( + char **argv, + char **envp, + int outfd, + int errfd) +{ + HANDLE hIn = INVALID_HANDLE_VALUE; + HANDLE hOut = INVALID_HANDLE_VALUE; + HANDLE hErr = INVALID_HANDLE_VALUE; + HANDLE hProcess, tmpIn, tmpOut, tmpErr; + DWORD e; + + if (proc_index >= MAXIMUM_WAIT_OBJECTS) { + DB (DB_JOBS, ("process_easy: All process slots used up\n")); + return INVALID_HANDLE_VALUE; + } +#ifdef KMK /* We can effort here by lettering CreateProcess/kernel do the standard handle duplication. */ + if (outfd != -1 || errfd != -1) { +#endif + /* Standard handles returned by GetStdHandle can be NULL or + INVALID_HANDLE_VALUE if the parent process closed them. If that + happens, we open the null device and pass its handle to + CreateProcess as the corresponding handle to inherit. */ + tmpIn = GetStdHandle(STD_INPUT_HANDLE); + if (DuplicateHandle(GetCurrentProcess(), + tmpIn, + GetCurrentProcess(), + &hIn, + 0, + TRUE, + DUPLICATE_SAME_ACCESS) == FALSE) { + if ((e = GetLastError()) == ERROR_INVALID_HANDLE) { + tmpIn = CreateFile("NUL", GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (tmpIn != INVALID_HANDLE_VALUE + && DuplicateHandle(GetCurrentProcess(), + tmpIn, + GetCurrentProcess(), + &hIn, + 0, + TRUE, + DUPLICATE_SAME_ACCESS) == FALSE) + CloseHandle(tmpIn); + } + if (hIn == INVALID_HANDLE_VALUE) { + fprintf(stderr, "process_easy: DuplicateHandle(In) failed (e=%ld)\n", e); + return INVALID_HANDLE_VALUE; + } + } + if (outfd >= 0) + tmpOut = (HANDLE)_get_osfhandle (outfd); + else + tmpOut = GetStdHandle (STD_OUTPUT_HANDLE); + if (DuplicateHandle(GetCurrentProcess(), + tmpOut, + GetCurrentProcess(), + &hOut, + 0, + TRUE, + DUPLICATE_SAME_ACCESS) == FALSE) { + if ((e = GetLastError()) == ERROR_INVALID_HANDLE) { + tmpOut = CreateFile("NUL", GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (tmpOut != INVALID_HANDLE_VALUE + && DuplicateHandle(GetCurrentProcess(), + tmpOut, + GetCurrentProcess(), + &hOut, + 0, + TRUE, + DUPLICATE_SAME_ACCESS) == FALSE) + CloseHandle(tmpOut); + } + if (hOut == INVALID_HANDLE_VALUE) { + fprintf(stderr, "process_easy: DuplicateHandle(Out) failed (e=%ld)\n", e); + return INVALID_HANDLE_VALUE; + } + } + if (errfd >= 0) + tmpErr = (HANDLE)_get_osfhandle (errfd); + else + tmpErr = GetStdHandle(STD_ERROR_HANDLE); + if (DuplicateHandle(GetCurrentProcess(), + tmpErr, + GetCurrentProcess(), + &hErr, + 0, + TRUE, + DUPLICATE_SAME_ACCESS) == FALSE) { + if ((e = GetLastError()) == ERROR_INVALID_HANDLE) { + tmpErr = CreateFile("NUL", GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (tmpErr != INVALID_HANDLE_VALUE + && DuplicateHandle(GetCurrentProcess(), + tmpErr, + GetCurrentProcess(), + &hErr, + 0, + TRUE, + DUPLICATE_SAME_ACCESS) == FALSE) + CloseHandle(tmpErr); + } + if (hErr == INVALID_HANDLE_VALUE) { + fprintf(stderr, "process_easy: DuplicateHandle(Err) failed (e=%ld)\n", e); + return INVALID_HANDLE_VALUE; + } + } +#ifdef KMK /* saving effort */ + } +#endif + + hProcess = process_init_fd(hIn, hOut, hErr); + + if (process_begin(hProcess, argv, envp, argv[0], NULL)) { + fake_exits_pending++; + /* process_begin() failed: make a note of that. */ + if (!((sub_process*) hProcess)->last_err) + ((sub_process*) hProcess)->last_err = -1; +#ifdef KMK + if (!((sub_process*) hProcess)->exit_code) +#endif + ((sub_process*) hProcess)->exit_code = process_last_err(hProcess); + + /* close up unused handles */ + if (hIn != INVALID_HANDLE_VALUE) + CloseHandle(hIn); + if (hOut != INVALID_HANDLE_VALUE) + CloseHandle(hOut); + if (hErr != INVALID_HANDLE_VALUE) + CloseHandle(hErr); + } + + process_register(hProcess); + + return hProcess; +} diff --git a/src/kmk/w32/subproc/w32err.c b/src/kmk/w32/subproc/w32err.c new file mode 100644 index 0000000..14eebed --- /dev/null +++ b/src/kmk/w32/subproc/w32err.c @@ -0,0 +1,85 @@ +/* Error handling for Windows +Copyright (C) 1996-2016 Free Software Foundation, Inc. +This file is part of GNU Make. + +GNU Make is free software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include <stdlib.h> +#include <windows.h> +#include "makeint.h" +#include "w32err.h" + +/* + * Description: the windows32 version of perror() + * + * Returns: a pointer to a static error + * + * Notes/Dependencies: I got this from + * comp.os.ms-windows.programmer.win32 + */ +const char * +map_windows32_error_to_string (DWORD ercode) { +/* + * We used to have an MSVC-specific '__declspec (thread)' qualifier + * here, with the following comment: + * + * __declspec (thread) necessary if you will use multiple threads on MSVC + * + * However, Make was never multithreaded on Windows (except when + * Ctrl-C is hit, in which case the main thread is stopped + * immediately, so it doesn't matter in this context). The functions + * on sub_proc.c that started and stopped additional threads were + * never used, and are now #ifdef'ed away. Until we need more than + * one thread, we have no problems with the following buffer being + * static. (If and when we do need it to be in thread-local storage, + * the corresponding GCC qualifier is '__thread'.) + */ + static char szMessageBuffer[128]; + /* Fill message buffer with a default message in + * case FormatMessage fails + */ + wsprintf (szMessageBuffer, "Error %ld\n", ercode); + + /* + * Special code for winsock error handling. + */ + if (ercode > WSABASEERR) { +#if 0 + HMODULE hModule = GetModuleHandle("wsock32"); + if (hModule != NULL) { + FormatMessage(FORMAT_MESSAGE_FROM_HMODULE, + hModule, + ercode, + LANG_NEUTRAL, + szMessageBuffer, + sizeof(szMessageBuffer), + NULL); + FreeLibrary(hModule); + } +#else + O (fatal, NILF, szMessageBuffer); +#endif + } else { + /* + * Default system message handling + */ + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + ercode, + LANG_NEUTRAL, + szMessageBuffer, + sizeof(szMessageBuffer), + NULL); + } + return szMessageBuffer; +} diff --git a/src/kmk/w32/tstFileInfo.c b/src/kmk/w32/tstFileInfo.c new file mode 100644 index 0000000..94d47bb --- /dev/null +++ b/src/kmk/w32/tstFileInfo.c @@ -0,0 +1,151 @@ +/* $Id: $ */ +/** @file + * Test program for some NtQueryInformationFile functionality. + */ + +/* + * Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net> + * + * This file is part of kBuild. + * + * kBuild is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * kBuild is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with kBuild. If not, see <http://www.gnu.org/licenses/> + * + */ + + + +#include <stdio.h> +#include <Windows.h> + +typedef enum _FILE_INFORMATION_CLASS { + FileDirectoryInformation = 1, + FileFullDirectoryInformation, // 2 + FileBothDirectoryInformation, // 3 + FileBasicInformation, // 4 wdm + FileStandardInformation, // 5 wdm + FileInternalInformation, // 6 + FileEaInformation, // 7 + FileAccessInformation, // 8 + FileNameInformation, // 9 + FileRenameInformation, // 10 + FileLinkInformation, // 11 + FileNamesInformation, // 12 + FileDispositionInformation, // 13 + FilePositionInformation, // 14 wdm + FileFullEaInformation, // 15 + FileModeInformation, // 16 + FileAlignmentInformation, // 17 + FileAllInformation, // 18 + FileAllocationInformation, // 19 + FileEndOfFileInformation, // 20 wdm + FileAlternateNameInformation, // 21 + FileStreamInformation, // 22 + FilePipeInformation, // 23 + FilePipeLocalInformation, // 24 + FilePipeRemoteInformation, // 25 + FileMailslotQueryInformation, // 26 + FileMailslotSetInformation, // 27 + FileCompressionInformation, // 28 + FileObjectIdInformation, // 29 + FileCompletionInformation, // 30 + FileMoveClusterInformation, // 31 + FileQuotaInformation, // 32 + FileReparsePointInformation, // 33 + FileNetworkOpenInformation, // 34 + FileAttributeTagInformation, // 35 + FileTrackingInformation, // 36 + FileIdBothDirectoryInformation, // 37 + FileIdFullDirectoryInformation, // 38 + FileMaximumInformation +} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS; + +typedef struct _FILE_NAME_INFORMATION +{ + ULONG FileNameLength; + WCHAR FileName[1]; +} FILE_NAME_INFORMATION, *PFILE_NAME_INFORMATION; + +typedef LONG NTSTATUS; + +typedef struct _IO_STATUS_BLOCK +{ + union + { + NTSTATUS Status; + PVOID Pointer; + }; + ULONG_PTR Information; +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; + +NTSYSAPI +NTSTATUS +NTAPI +NtQueryInformationFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation, + ULONG Length, FILE_INFORMATION_CLASS FileInformationClass); + + + +int main(int argc, char **argv) +{ + int rc = 0; + int i; + + NTSTATUS (NTAPI *pfnNtQueryInformationFile)(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation, + ULONG Length, FILE_INFORMATION_CLASS FileInformationClass); + + pfnNtQueryInformationFile = GetProcAddress(LoadLibrary("ntdll.dll"), "NtQueryInformationFile"); + + for (i = 1; i < argc; i++) + { + HANDLE hFile = CreateFile(argv[i], + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (hFile) + { + long rcNt; + char abBuf[4096]; + IO_STATUS_BLOCK Ios; + + memset(abBuf, 0, sizeof(abBuf)); + memset(&Ios, 0, sizeof(Ios)); + rcNt = pfnNtQueryInformationFile(hFile, &Ios, abBuf, sizeof(abBuf), FileNameInformation); + if (rcNt >= 0) + { + PFILE_NAME_INFORMATION pFileNameInfo = (PFILE_NAME_INFORMATION)abBuf; + printf("#%d: %s - rcNt=%#x - FileNameInformation:\n" + " FileName: %ls\n" + " FileNameLength: %lu\n", + i, argv[i], rcNt, + pFileNameInfo->FileName, + pFileNameInfo->FileNameLength + ); + } + else + printf("#%d: %s - rcNt=%#x - FileNameInformation!\n", i, argv[i], rcNt); + + CloseHandle(hFile); + } + else + { + printf("#%d: %s - open failed, last error %d\n", i, argv[i], GetLastError()); + rc = 1; + } + } + return rc; +} + diff --git a/src/kmk/w32/w32os.c b/src/kmk/w32/w32os.c new file mode 100644 index 0000000..fd30661 --- /dev/null +++ b/src/kmk/w32/w32os.c @@ -0,0 +1,220 @@ +/* Windows32-based operating system interface for GNU Make. +Copyright (C) 2016 Free Software Foundation, Inc. +This file is part of GNU Make. + +GNU Make is free software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "makeint.h" + +#include <stdio.h> +#include <string.h> + +#include <windows.h> +#include <process.h> +#include <io.h> +#include "pathstuff.h" +#ifndef CONFIG_NEW_WIN_CHILDREN +# include "sub_proc.h" +#else +# include "winchildren.h" +#endif +#include "w32err.h" +#include "os.h" +#include "debug.h" + +/* This section provides OS-specific functions to support the jobserver. */ + +static char jobserver_semaphore_name[MAX_PATH + 1]; +static HANDLE jobserver_semaphore = NULL; + +unsigned int +jobserver_setup (int slots) +{ +#ifndef CONFIG_NEW_WIN_CHILDREN + /* sub_proc.c cannot wait for more than MAXIMUM_WAIT_OBJECTS objects + * and one of them is the job-server semaphore object. Limit the + * number of available job slots to (MAXIMUM_WAIT_OBJECTS - 1). */ + + if (slots >= MAXIMUM_WAIT_OBJECTS) + { + slots = MAXIMUM_WAIT_OBJECTS - 1; + DB (DB_JOBS, (_("Jobserver slots limited to %d\n"), slots)); + } +#endif + + sprintf (jobserver_semaphore_name, "gmake_semaphore_%d", _getpid ()); + + jobserver_semaphore = CreateSemaphore ( + NULL, /* Use default security descriptor */ + slots, /* Initial count */ + slots, /* Maximum count */ + jobserver_semaphore_name); /* Semaphore name */ + + if (jobserver_semaphore == NULL) + { + DWORD err = GetLastError (); + const char *estr = map_windows32_error_to_string (err); + ONS (fatal, NILF, + _("creating jobserver semaphore: (Error %ld: %s)"), err, estr); + } + + return 1; +} + +unsigned int +jobserver_parse_auth (const char *auth) +{ + jobserver_semaphore = OpenSemaphore ( + SEMAPHORE_ALL_ACCESS, /* Semaphore access setting */ + FALSE, /* Child processes DON'T inherit */ + auth); /* Semaphore name */ + + if (jobserver_semaphore == NULL) + { + DWORD err = GetLastError (); + const char *estr = map_windows32_error_to_string (err); + fatal (NILF, strlen (auth) + INTSTR_LENGTH + strlen (estr), + _("internal error: unable to open jobserver semaphore '%s': (Error %ld: %s)"), + auth, err, estr); + } + DB (DB_JOBS, (_("Jobserver client (semaphore %s)\n"), auth)); + + return 1; +} + +char * +jobserver_get_auth () +{ + return xstrdup (jobserver_semaphore_name); +} + +unsigned int +jobserver_enabled () +{ + return jobserver_semaphore != NULL; +} + +/* Close jobserver semaphore */ +void +jobserver_clear () +{ + if (jobserver_semaphore != NULL) + { + CloseHandle (jobserver_semaphore); + jobserver_semaphore = NULL; + } +} + +void +jobserver_release (int is_fatal) +{ + if (! ReleaseSemaphore ( + jobserver_semaphore, /* handle to semaphore */ + 1, /* increase count by one */ + NULL)) /* not interested in previous count */ + { + if (is_fatal) + { + DWORD err = GetLastError (); + const char *estr = map_windows32_error_to_string (err); + ONS (fatal, NILF, + _("release jobserver semaphore: (Error %ld: %s)"), err, estr); + } + perror_with_name ("release_jobserver_semaphore", ""); + } +} + +unsigned int +jobserver_acquire_all () +{ + unsigned int tokens = 0; + while (1) + { + DWORD dwEvent = WaitForSingleObject ( + jobserver_semaphore, /* Handle to semaphore */ + 0); /* DON'T wait on semaphore */ + + if (dwEvent != WAIT_OBJECT_0) + return tokens; + + ++tokens; + } +} + +void +jobserver_signal () +{ +} + +void jobserver_pre_child (int recursive) +{ +} + +void jobserver_post_child (int recursive) +{ +} + +void +jobserver_pre_acquire () +{ +} + +/* Returns 1 if we got a token, or 0 if a child has completed. + The Windows implementation doesn't support load detection. */ +unsigned int +jobserver_acquire (int timeout) +{ +#ifndef CONFIG_NEW_WIN_CHILDREN + HANDLE handles[MAXIMUM_WAIT_OBJECTS + 1]; /* bird: + 1 to prevent trashing the stack. */ +#else + HANDLE handles[2]; +#endif + DWORD dwHandleCount; + DWORD dwEvent; + + /* Add jobserver semaphore to first slot. */ + handles[0] = jobserver_semaphore; + +#ifndef CONFIG_NEW_WIN_CHILDREN + /* Build array of handles to wait for. */ + dwHandleCount = 1 + process_set_handles (&handles[1]); + dwEvent = WaitForMultipleObjects ( + dwHandleCount, /* number of objects in array */ + handles, /* array of objects */ + FALSE, /* wait for any object */ + INFINITE); /* wait until object is signalled */ +#else + /* Add the completed children event as the 2nd one. */ + handles[1] = (HANDLE)MkWinChildGetCompleteEventHandle (); + if (handles[1] == NULL) + return 0; + dwHandleCount = 2; + dwEvent = WaitForMultipleObjectsEx (dwHandleCount, + handles, + FALSE /*bWaitAll*/, + 256, /* INFINITE - paranoia, only wait 256 ms before checking again. */ + TRUE /*bAlertable*/); +#endif + + if (dwEvent == WAIT_FAILED) + { + DWORD err = GetLastError (); + const char *estr = map_windows32_error_to_string (err); + ONS (fatal, NILF, + _("semaphore or child process wait: (Error %ld: %s)"), + err, estr); + } + + /* WAIT_OBJECT_0 indicates that the semaphore was signalled. */ + return dwEvent == WAIT_OBJECT_0; +} diff --git a/src/kmk/w32/winchildren.c b/src/kmk/w32/winchildren.c new file mode 100644 index 0000000..635fe99 --- /dev/null +++ b/src/kmk/w32/winchildren.c @@ -0,0 +1,3729 @@ +/* $Id: winchildren.c 3359 2020-06-05 16:17:17Z bird $ */ +/** @file + * Child process creation and management for kmk. + */ + +/* + * Copyright (c) 2018 knut st. osmundsen <bird-kBuild-spamx@anduin.net> + * + * This file is part of kBuild. + * + * kBuild is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * kBuild is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with kBuild. If not, see <http://www.gnu.org/licenses/> + * + */ + +/* No GNU coding style here atm, convert if upstreamed. */ + +/** @page pg_win_children Windows child process creation and managment + * + * This new implementation aims at addressing the following: + * + * 1. Speed up process creation by doing the expensive CreateProcess call + * in a worker thread. + * + * 2. No 64 process limit imposed by WaitForMultipleObjects. + * + * 3. Better distribute jobs among processor groups. + * + * 4. Offloading more expensive kmkbuiltin operations to worker threads, + * making the main thread focus on managing child processes. + * + * 5. Output synchronization using reusable pipes. + * + * + * To be quite honest, the first item (CreateProcess expense) didn't occur to me + * at first and was more of a sideeffect discovered along the way. A test + * rebuilding IPRT went from 4m52s to 3m19s on a 8 thread system. + * + * The 2nd and 3rd goals are related to newer build servers that have lots of + * CPU threads and various Windows NT (aka NT OS/2 at the time) design choices + * made in the late 1980ies. + * + * WaitForMultipleObjects does not support waiting for more than 64 objects, + * unlike poll and select. This is just something everyone ends up having to + * work around in the end. + * + * Affinity masks are uintptr_t sized, so 64-bit hosts can only manage 64 + * processors and 32-bit only 32. Workaround was introduced with Windows 7 + * (IIRC) and is called processor groups. The CPU threads are grouped into 1 or + * more groups of up to 64 processors. Processes are generally scheduled to a + * signle processor group at first, but threads may be changed to be scheduled + * on different groups. This code will try distribute children evenly among the + * processor groups, using a very simple algorithm (see details in code). + * + * + * @section sec_win_children_av Remarks on Microsoft Defender and other AV + * + * Part of the motivation for writing this code was horrible CPU utilization on + * a brand new AMD Threadripper 1950X system with lots of memory and SSDs, + * running 64-bit Windows 10 build 16299. + * + * Turns out Microsoft defender adds some overhead to CreateProcess + * and other stuff: + * - Old make with CreateProcess on main thread: + * - With runtime defender enabled: 14 min 6 seconds + * - With runtime defender disabled: 4 min 49 seconds + * - New make with CreateProcess on worker thread (this code): + * - With runtime defender enabled: 6 min 29 seconds + * - With runtime defender disabled: 4 min 36 seconds + * - With runtime defender disabled out dir only: 5 min 59 seconds + * + * See also kWorker / kSubmit for more bickering about AV & disk encryption. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <Windows.h> +#include <Winternl.h> + +#include "../makeint.h" +#include "../job.h" +#include "../filedef.h" +#include "../debug.h" +#include "../kmkbuiltin.h" +#include "winchildren.h" + +#include <assert.h> +#include <process.h> +#include <intrin.h> + +#include "nt/nt_child_inject_standard_handles.h" +#include "console.h" + +#ifndef KMK_BUILTIN_STANDALONE +extern void kmk_cache_exec_image_w(const wchar_t *); /* imagecache.c */ +#endif + +/* Option values from main.c: */ +extern const char *win_job_object_mode; +extern const char *win_job_object_name; +extern int win_job_object_no_kill; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define MKWINCHILD_MAX_PATH 1024 + +#define MKWINCHILD_DO_SET_PROCESSOR_GROUP + +/** Checks the UTF-16 environment variable pointed to is the PATH. */ +#define IS_PATH_ENV_VAR(a_cwcVar, a_pwszVar) \ + ( (a_cwcVar) >= 5 \ + && (a_pwszVar)[4] == L'=' \ + && ((a_pwszVar)[0] == L'P' || (a_pwszVar)[0] == L'p') \ + && ((a_pwszVar)[1] == L'A' || (a_pwszVar)[1] == L'a') \ + && ((a_pwszVar)[2] == L'T' || (a_pwszVar)[2] == L't') \ + && ((a_pwszVar)[3] == L'H' || (a_pwszVar)[3] == L'h') ) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to a childcare worker thread. */ +typedef struct WINCHILDCAREWORKER *PWINCHILDCAREWORKER; +/** Pointer to a windows child process. */ +typedef struct WINCHILD *PWINCHILD; + + +/** + * Child process type. + */ +typedef enum WINCHILDTYPE +{ + WINCHILDTYPE_INVALID = 0, + /** Normal child process. */ + WINCHILDTYPE_PROCESS, +#ifdef KMK + /** kmkbuiltin command. */ + WINCHILDTYPE_BUILT_IN, + /** kmkbuiltin_append result write out. */ + WINCHILDTYPE_APPEND, + /** kSubmit job. */ + WINCHILDTYPE_SUBMIT, + /** kmk_redirect job. */ + WINCHILDTYPE_REDIRECT, +#endif + /** End of valid child types. */ + WINCHILDTYPE_END +} WINCHILDTYPE; + + +/** + * Windows child process. + */ +typedef struct WINCHILD +{ + /** Magic / eyecatcher (WINCHILD_MAGIC). */ + ULONG uMagic; + /** Child type. */ + WINCHILDTYPE enmType; + /** Pointer to the next child process. */ + PWINCHILD pNext; + /** The pid for this child. */ + pid_t pid; + /** The make child structure associated with this child. */ + struct child *pMkChild; + + /** The process exit code. */ + int iExitCode; + /** Kill signal, in case we or someone else killed it. */ + int iSignal; + /** Set if core was dumped. */ + int fCoreDumped; + /** Set if the a child process is a candidate for cl.exe where we supress + * annoying source name output. */ + BOOL fProbableClExe; + /** The worker executing this child. */ + PWINCHILDCAREWORKER pWorker; + + /** Type specific data. */ + union + { + /** Data for WINCHILDTYPE_PROCESS. */ + struct + { + /** Argument vector (single allocation, strings following array). */ + char **papszArgs; + /** Length of the argument strings. */ + size_t cbArgsStrings; + /** Environment vector. Only a copy if fEnvIsCopy is set. */ + char **papszEnv; + /** If we made a copy of the environment, this is the size of the + * strings and terminator string (not in array). This is done to + * speed up conversion, since MultiByteToWideChar can handle '\0'. */ + size_t cbEnvStrings; + /** The make shell to use (copy). */ + char *pszShell; + /** Handle to use for standard out. */ + HANDLE hStdOut; + /** Handle to use for standard out. */ + HANDLE hStdErr; + /** Whether to close hStdOut after creating the process. */ + BOOL fCloseStdOut; + /** Whether to close hStdErr after creating the process. */ + BOOL fCloseStdErr; + /** Whether to catch output from the process. */ + BOOL fCatchOutput; + + /** Child process handle. */ + HANDLE hProcess; + } Process; + + /** Data for WINCHILDTYPE_BUILT_IN. */ + struct + { + /** The built-in command. */ + PCKMKBUILTINENTRY pBuiltIn; + /** Number of arguments. */ + int cArgs; + /** Argument vector (single allocation, strings following array). */ + char **papszArgs; + /** Environment vector. Only a copy if fEnvIsCopy is set. */ + char **papszEnv; + } BuiltIn; + + /** Data for WINCHILDTYPE_APPEND. */ + struct + { + /** The filename. */ + char *pszFilename; + /** How much to append. */ + size_t cbAppend; + /** What to append. */ + char *pszAppend; + /** Whether to truncate the file. */ + int fTruncate; + } Append; + + /** Data for WINCHILDTYPE_SUBMIT. */ + struct + { + /** The event we're to wait on (hooked up to a pipe) */ + HANDLE hEvent; + /** Parameter for the cleanup callback. */ + void *pvSubmitWorker; + /** Standard output catching pipe. Optional. */ + PWINCCWPIPE pStdOut; + /** Standard error catching pipe. Optional. */ + PWINCCWPIPE pStdErr; + } Submit; + + /** Data for WINCHILDTYPE_REDIRECT. */ + struct + { + /** Child process handle. */ + HANDLE hProcess; + } Redirect; + } u; + +} WINCHILD; +/** WINCHILD::uMagic value. */ +#define WINCHILD_MAGIC 0xbabebabeU + + +/** + * Data for a windows childcare worker thread. + * + * We use one worker thread per child, reusing the threads when possible. + * + * This setup helps avoid the 64-bit handle with the WaitForMultipleObject API. + * + * It also helps using all CPUs on systems with more than one CPU group + * (typically systems with more than 64 CPU threads or/and multiple sockets, or + * special configs). + * + * This helps facilitates using pipes for collecting output child rather + * than temporary files. Pipes doesn't involve NTFS and can easily be reused. + * + * Finally, kBuild specific, this allows running kmkbuiltin_xxxx commands in + * threads. + */ +typedef struct WINCHILDCAREWORKER +{ + /** Magic / eyecatcher (WINCHILDCAREWORKER_MAGIC). */ + ULONG uMagic; + /** The worker index. */ + unsigned int idxWorker; + /** The processor group for this worker. */ + unsigned int iProcessorGroup; + /** The thread ID. */ + unsigned int tid; + /** The thread handle. */ + HANDLE hThread; + /** The event the thread is idling on. */ + HANDLE hEvtIdle; + /** The pipe catching standard output from a child. */ + PWINCCWPIPE pStdOut; + /** The pipe catching standard error from a child. */ + PWINCCWPIPE pStdErr; + + /** Pointer to the current child. */ + PWINCHILD volatile pCurChild; + /** List of children pending execution on this worker. + * This is updated atomitically just like g_pTailCompletedChildren. */ + PWINCHILD volatile pTailTodoChildren; + /** TRUE if idle, FALSE if not. */ + long volatile fIdle; +} WINCHILDCAREWORKER; +/** WINCHILD::uMagic value. */ +#define WINCHILDCAREWORKER_MAGIC 0xdad0dad0U + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Whether it's initialized or not. */ +static BOOL g_fInitialized = FALSE; +/** Set when we're shutting down everything. */ +static BOOL volatile g_fShutdown = FALSE; +/** Event used to wait for children. */ +static HANDLE g_hEvtWaitChildren = INVALID_HANDLE_VALUE; +/** Number of childcare workers currently in g_papChildCareworkers. */ +static unsigned g_cChildCareworkers = 0; +/** Maximum number of childcare workers in g_papChildCareworkers. */ +static unsigned g_cChildCareworkersMax = 0; +/** Pointer to childcare workers. */ +static PWINCHILDCAREWORKER *g_papChildCareworkers = NULL; +/** The processor group allocator state. */ +static MKWINCHILDCPUGROUPALLOCSTATE g_ProcessorGroupAllocator; +/** Number of processor groups in the system. */ +static unsigned g_cProcessorGroups = 1; +/** Array detailing how many active processors there are in each group. */ +static unsigned const *g_pacProcessorsInGroup = &g_cProcessorGroups; +/** Kernel32!GetActiveProcessorGroupCount */ +static WORD (WINAPI *g_pfnGetActiveProcessorGroupCount)(VOID); +/** Kernel32!GetActiveProcessorCount */ +static DWORD (WINAPI *g_pfnGetActiveProcessorCount)(WORD); +/** Kernel32!SetThreadGroupAffinity */ +static BOOL (WINAPI *g_pfnSetThreadGroupAffinity)(HANDLE, CONST GROUP_AFFINITY *, GROUP_AFFINITY *); +/** NTDLL!NtQueryInformationProcess */ +static NTSTATUS (NTAPI *g_pfnNtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG); +/** Set if the windows host is 64-bit. */ +static BOOL g_f64BitHost = (K_ARCH_BITS == 64); +/** Windows version info. + * @note Putting this before the volatile stuff, hoping to keep it in a + * different cache line than the static bits above. */ +static OSVERSIONINFOA g_VersionInfo = { sizeof(g_VersionInfo), 4, 0, 1381, VER_PLATFORM_WIN32_NT, {0} }; + +/** Children that has been completed. + * This is updated atomically, pushing completed children in LIFO fashion + * (thus 'tail'), then hitting g_hEvtWaitChildren if head. */ +static PWINCHILD volatile g_pTailCompletedChildren = NULL; + +/** Number of idle pending children. + * This is updated before g_hEvtWaitChildren is signalled. */ +static unsigned volatile g_cPendingChildren = 0; + +/** Number of idle childcare worker threads. */ +static unsigned volatile g_cIdleChildcareWorkers = 0; +/** Index of the last idle child careworker (just a hint). */ +static unsigned volatile g_idxLastChildcareWorker = 0; + +#ifdef WITH_RW_LOCK +/** RW lock for serializing kmkbuiltin_redirect and CreateProcess. */ +static SRWLOCK g_RWLock; +#endif + +/** The job object for this make instance, if we created/opened one. */ +static HANDLE g_hJob = NULL; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void mkWinChildInitJobObjectAssociation(void); + +#if K_ARCH_BITS == 32 && !defined(_InterlockedCompareExchangePointer) +/** _InterlockedCompareExchangePointer is missing? (VS2010) */ +K_INLINE void *_InterlockedCompareExchangePointer(void * volatile *ppvDst, void *pvNew, void *pvOld) +{ + return (void *)_InterlockedCompareExchange((long volatile *)ppvDst, (intptr_t)pvNew, (intptr_t)pvOld); +} +#endif + + +/** + * Initializes the windows child module. + * + * @param cJobSlots The number of job slots. + */ +void MkWinChildInit(unsigned int cJobSlots) +{ + HMODULE hmod; + + /* + * Figure out how many childcare workers first. + */ + static unsigned int const s_cMaxWorkers = 4096; + unsigned cWorkers; + if (cJobSlots >= 1 && cJobSlots < s_cMaxWorkers) + cWorkers = cJobSlots; + else + cWorkers = s_cMaxWorkers; + + /* + * Allocate the array and the child completed event object. + */ + g_papChildCareworkers = (PWINCHILDCAREWORKER *)xcalloc(cWorkers * sizeof(g_papChildCareworkers[0])); + g_cChildCareworkersMax = cWorkers; + + g_hEvtWaitChildren = CreateEvent(NULL, FALSE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pszName*/); + if (!g_hEvtWaitChildren) + fatal(NILF, INTSTR_LENGTH, _("MkWinChildInit: CreateEvent failed: %u"), GetLastError()); + + /* + * NTDLL imports that we need. + */ + hmod = GetModuleHandleA("NTDLL.DLL"); + *(FARPROC *)&g_pfnNtQueryInformationProcess = GetProcAddress(hmod, "NtQueryInformationProcess"); + if (!g_pfnNtQueryInformationProcess) + fatal(NILF, 0, _("MkWinChildInit: NtQueryInformationProcess not found")); + +#if K_ARCH_BITS == 32 + /* + * Initialize g_f64BitHost. + */ + if (!IsWow64Process(GetCurrentProcess(), &g_f64BitHost)) + fatal(NILF, INTSTR_LENGTH, _("MkWinChildInit: IsWow64Process failed: %u"), GetLastError()); +#elif K_ARCH_BITS == 64 + assert(g_f64BitHost); +#else +# error "K_ARCH_BITS is bad/missing" +#endif + + /* + * Figure out how many processor groups there are. + * For that we need to first figure the windows version. + */ + if (!GetVersionExA(&g_VersionInfo)) + { + DWORD uRawVer = GetVersion(); + g_VersionInfo.dwMajorVersion = uRawVer & 0xff; + g_VersionInfo.dwMinorVersion = (uRawVer >> 8) & 0xff; + g_VersionInfo.dwBuildNumber = (uRawVer >> 16) & 0x7fff; + } + if (g_VersionInfo.dwMajorVersion >= 6) + { + hmod = GetModuleHandleA("KERNEL32.DLL"); + *(FARPROC *)&g_pfnGetActiveProcessorGroupCount = GetProcAddress(hmod, "GetActiveProcessorGroupCount"); + *(FARPROC *)&g_pfnGetActiveProcessorCount = GetProcAddress(hmod, "GetActiveProcessorCount"); + *(FARPROC *)&g_pfnSetThreadGroupAffinity = GetProcAddress(hmod, "SetThreadGroupAffinity"); + if ( g_pfnSetThreadGroupAffinity + && g_pfnGetActiveProcessorCount + && g_pfnGetActiveProcessorGroupCount) + { + unsigned int *pacProcessorsInGroup; + unsigned iGroup; + g_cProcessorGroups = g_pfnGetActiveProcessorGroupCount(); + if (g_cProcessorGroups == 0) + g_cProcessorGroups = 1; + + pacProcessorsInGroup = (unsigned int *)xmalloc(sizeof(g_pacProcessorsInGroup[0]) * g_cProcessorGroups); + g_pacProcessorsInGroup = pacProcessorsInGroup; + for (iGroup = 0; iGroup < g_cProcessorGroups; iGroup++) + pacProcessorsInGroup[iGroup] = g_pfnGetActiveProcessorCount(iGroup); + + MkWinChildInitCpuGroupAllocator(&g_ProcessorGroupAllocator); + } + else + { + g_pfnSetThreadGroupAffinity = NULL; + g_pfnGetActiveProcessorCount = NULL; + g_pfnGetActiveProcessorGroupCount = NULL; + } + } + +#ifdef WITH_RW_LOCK + /* + * For serializing with standard file handle manipulation (kmkbuiltin_redirect). + */ + InitializeSRWLock(&g_RWLock); +#endif + + /* + * Associate with a job object. + */ + mkWinChildInitJobObjectAssociation(); + + /* + * This is dead code that was thought to fix a problem observed doing + * `tcc.exe /c "kmk |& tee bld.log"` and leading to a crash in cl.exe + * when spawned with fInheritHandles = FALSE, see hStdErr=NULL in the + * child. However, it turns out this was probably caused by not clearing + * the CRT file descriptor and handle table in the startup info. + * Leaving the code here in case it comes in handy after all. + */ +#if 0 + { + struct + { + DWORD uStdHandle; + HANDLE hHandle; + } aHandles[3] = { { STD_INPUT_HANDLE, NULL }, { STD_OUTPUT_HANDLE, NULL }, { STD_ERROR_HANDLE, NULL } }; + int i; + + for (i = 0; i < 3; i++) + aHandles[i].hHandle = GetStdHandle(aHandles[i].uStdHandle); + + for (i = 0; i < 3; i++) + if ( aHandles[i].hHandle == NULL + || aHandles[i].hHandle == INVALID_HANDLE_VALUE) + { + int fd = open("nul", _O_RDWR); + if (fd >= 0) + { + if (_dup2(fd, i) >= 0) + { + assert((HANDLE)_get_osfhandle(i) != aHandles[i].hHandle); + assert((HANDLE)_get_osfhandle(i) == GetStdHandle(aHandles[i].uStdHandle)); + } + else + ONNNS(fatal, NILF, "_dup2(%d('nul'), %d) failed: %u (%s)", fd, i, errno, strerror(errno)); + if (fd != i) + close(fd); + } + else + ONNS(fatal, NILF, "open(nul,RW) failed: %u (%s)", i, errno, strerror(errno)); + } + else + { + int j; + for (j = i + 1; j < 3; j++) + if (aHandles[j].hHandle == aHandles[i].hHandle) + { + int fd = _dup(j); + if (fd >= 0) + { + if (_dup2(fd, j) >= 0) + { + aHandles[j].hHandle = (HANDLE)_get_osfhandle(j); + assert(aHandles[j].hHandle != aHandles[i].hHandle); + assert(aHandles[j].hHandle == GetStdHandle(aHandles[j].uStdHandle)); + } + else + ONNNS(fatal, NILF, "_dup2(%d, %d) failed: %u (%s)", fd, j, errno, strerror(errno)); + if (fd != j) + close(fd); + } + else + ONNS(fatal, NILF, "_dup(%d) failed: %u (%s)", j, errno, strerror(errno)); + } + } + } +#endif +} + +/** + * Create or open a job object for this make instance and its children. + * + * Depending on the --job-object=mode value, we typically create/open a job + * object here if we're the root make instance. The job object is then + * typically configured to kill all remaining processes when the root make + * terminates, so that there aren't any stuck processes around messing up + * subsequent builds. This is very handy on build servers. + * + * If we're it no-kill mode, the job object is pretty pointless for manual + * cleanup as the job object becomes invisible (or something) when the last + * handle to it closes, i.e. g_hJob. On windows 8 and later it looks + * like any orphaned children are immediately assigned to the parent job + * object. Too bad for kmk_kill and such. + * + * win_job_object_mode values: login, root, each, none + */ +static void mkWinChildInitJobObjectAssociation(void) +{ + BOOL fCreate = TRUE; + char szJobName[128]; + const char *pszJobName = win_job_object_name; + + /* Skip if disabled. */ + if (strcmp(win_job_object_mode, "none") == 0) + return; + + /* Skip if not root make instance, unless we're having one job object + per make instance. */ + if ( makelevel != 0 + && strcmp(win_job_object_mode, "each") != 0) + return; + + /* Format the the default job object name if --job-object-name + wasn't given. */ + if (!pszJobName || *pszJobName == '\0') + { + pszJobName = szJobName; + if (strcmp(win_job_object_mode, "login") == 0) + { + /* Use the AuthenticationId like mspdbsrv.exe does. */ + HANDLE hToken; + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) + { + TOKEN_STATISTICS TokenStats; + DWORD cbRet = 0; + memset(&TokenStats, 0, sizeof(TokenStats)); + if (GetTokenInformation(hToken, TokenStatistics, &TokenStats, sizeof(TokenStats), &cbRet)) + snprintf(szJobName, sizeof(szJobName), "kmk-job-obj-login-%08x.%08x", + (unsigned)TokenStats.AuthenticationId.HighPart, (unsigned)TokenStats.AuthenticationId.LowPart); + else + { + ONN(message, 0, _("GetTokenInformation failed: %u (cbRet=%u)"), GetLastError(), cbRet); + return; + } + CloseHandle(hToken); + } + else + { + ON(message, 0, _("OpenProcessToken failed: %u"), GetLastError()); + return; + } + } + else + { + SYSTEMTIME Now = {0}; + GetSystemTime(&Now); + snprintf(szJobName, sizeof(szJobName), "kmk-job-obj-%04u-%02u-%02uT%02u-%02u-%02uZ%u", + Now.wYear, Now.wMonth, Now.wDay, Now.wHour, Now.wMinute, Now.wSecond, getpid()); + } + } + + /* In login mode and when given a job object name, we try open it first. */ + if ( win_job_object_name + || strcmp(win_job_object_mode, "login") == 0) + { + g_hJob = OpenJobObjectA(JOB_OBJECT_ASSIGN_PROCESS, win_job_object_no_kill /*bInheritHandle*/, pszJobName); + if (g_hJob) + fCreate = FALSE; + else + { + DWORD dwErr = GetLastError(); + if (dwErr != ERROR_PATH_NOT_FOUND && dwErr != ERROR_FILE_NOT_FOUND) + { + OSN(message, 0, _("OpenJobObjectA(,,%s) failed: %u"), pszJobName, GetLastError()); + return; + } + } + } + + if (fCreate) + { + SECURITY_ATTRIBUTES SecAttr = { sizeof(SecAttr), NULL, TRUE /*bInheritHandle*/ }; + g_hJob = CreateJobObjectA(win_job_object_no_kill ? &SecAttr : NULL, pszJobName); + if (g_hJob) + { + /* We need to set the BREAKAWAY_OK flag, as we don't want make CreateProcess + fail if someone tries to break way. Also set KILL_ON_JOB_CLOSE unless + --job-object-no-kill is given. */ + JOBOBJECT_EXTENDED_LIMIT_INFORMATION Info; + DWORD cbActual = 0; + memset(&Info, 0, sizeof(Info)); + if (QueryInformationJobObject(g_hJob, JobObjectExtendedLimitInformation, &Info, sizeof(Info), &cbActual)) + { + Info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_BREAKAWAY_OK; + if (!win_job_object_no_kill) + Info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + else + Info.BasicLimitInformation.LimitFlags &= ~JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + if (!SetInformationJobObject(g_hJob, JobObjectExtendedLimitInformation, &Info, sizeof(Info))) + OSSN(message, 0, _("SetInformationJobObject(%s,JobObjectExtendedLimitInformation,{%s},) failed: %u"), + pszJobName, win_job_object_mode, GetLastError()); + } + else + OSN(message, 0, _("QueryInformationJobObject(%s,JobObjectExtendedLimitInformation,,,) failed: %u"), + pszJobName, GetLastError()); + } + else + { + OSN(message, 0, _("CreateJobObjectA(NULL,%s) failed: %u"), pszJobName, GetLastError()); + return; + } + } + + /* Make it our job object. */ + if (!(AssignProcessToJobObject(g_hJob, GetCurrentProcess()))) + OSN(message, 0, _("AssignProcessToJobObject(%s, me) failed: %u"), pszJobName, GetLastError()); +} + +/** + * Used by mkWinChildcareWorkerThread() and MkWinChildWait() to get the head + * child from a lifo (g_pTailCompletedChildren, pTailTodoChildren). + * + * @returns Head child. + * @param ppTail Pointer to the child variable. + * @param pChild Tail child. + */ +static PWINCHILD mkWinChildDequeFromLifo(PWINCHILD volatile *ppTail, PWINCHILD pChild) +{ + if (pChild->pNext) + { + PWINCHILD pPrev; + do + { + pPrev = pChild; + pChild = pChild->pNext; + } while (pChild->pNext); + pPrev->pNext = NULL; + } + else + { + PWINCHILD const pWantedChild = pChild; + pChild = _InterlockedCompareExchangePointer(ppTail, NULL, pWantedChild); + if (pChild != pWantedChild) + { + PWINCHILD pPrev; + do + { + pPrev = pChild; + pChild = pChild->pNext; + } while (pChild->pNext); + pPrev->pNext = NULL; + assert(pChild == pWantedChild); + } + } + return pChild; +} + +/** + * Output error message while running on a worker thread. + * + * @returns -1 + * @param pWorker The calling worker. Mainly for getting the + * current child and its stderr output unit. Pass + * NULL if the output should go thru the child + * stderr buffering. + * @param iType The error type: + * - 0: more of a info directly to stdout, + * - 1: child related error, + * - 2: child related error for immedate release. + * @param pszFormat The message format string. + * @param ... Argument for the message. + */ +static int MkWinChildError(PWINCHILDCAREWORKER pWorker, int iType, const char *pszFormat, ...) +{ + /* + * Format the message into stack buffer. + */ + char szMsg[4096]; + int cchMsg; + int cchPrefix; + va_list va; + + /* Compose the prefix, being paranoid about it not exceeding the buffer in any way. */ + const char *pszInfix = iType == 0 ? "info: " : "error: "; + const char *pszProgram = program; + if (strlen(pszProgram) > 80) + { +#ifdef KMK + pszProgram = "kmk"; +#else + pszProgram = "gnumake"; +#endif + } + if (makelevel == 0) + cchPrefix = snprintf(szMsg, sizeof(szMsg) / 2, "%s: %s", pszProgram, pszInfix); + else + cchPrefix = snprintf(szMsg, sizeof(szMsg) / 2, "%s[%u]: %s", pszProgram, makelevel, pszInfix); + assert(cchPrefix < sizeof(szMsg) / 2 && cchPrefix > 0); + + /* Format the user specified message. */ + va_start(va, pszFormat); + cchMsg = vsnprintf(&szMsg[cchPrefix], sizeof(szMsg) - 2 - cchPrefix, pszFormat, va); + va_end(va); + szMsg[sizeof(szMsg) - 2] = '\0'; + cchMsg = strlen(szMsg); + + /* Make sure there's a newline at the end of it (we reserved space for that). */ + if (cchMsg <= 0 || szMsg[cchMsg - 1] != '\n') + { + szMsg[cchMsg++] = '\n'; + szMsg[cchMsg] = '\0'; + } + +#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY + /* + * Try use the stderr of the current child of the worker. + */ + if ( iType != 0 + && iType != 3 + && pWorker) + { + PWINCHILD pChild = pWorker->pCurChild; + if (pChild) + { + struct child *pMkChild = pChild->pMkChild; + if (pMkChild) + { + output_write_text(&pMkChild->output, 1 /*is_err*/, szMsg, cchMsg); + return -1; + } + } + } +#endif + + /* + * Fallback to writing directly to stderr. + */ + maybe_con_fwrite(szMsg, cchMsg, 1, iType == 0 ? stdout : stderr); + return -1; +} + +/** + * Duplicates the given UTF-16 string. + * + * @returns 0 + * @param pwszSrc The UTF-16 string to duplicate. + * @param cwcSrc Length, may include the terminator. + * @param ppwszDst Where to return the duplicate. + */ +static int mkWinChildDuplicateUtf16String(const WCHAR *pwszSrc, size_t cwcSrc, WCHAR **ppwszDst) +{ + size_t cb = sizeof(WCHAR) * cwcSrc; + if (cwcSrc > 0 && pwszSrc[cwcSrc - 1] == L'\0') + *ppwszDst = (WCHAR *)memcpy(xmalloc(cb), pwszSrc, cb); + else + { + WCHAR *pwszDst = (WCHAR *)xmalloc(cb + sizeof(WCHAR)); + memcpy(pwszDst, pwszSrc, cb); + pwszDst[cwcSrc] = L'\0'; + *ppwszDst = pwszDst; + } + return 0; +} + + +/** + * Used to flush data we're read but not yet written at the termination of a + * process. + * + * @param pChild The child. + * @param pPipe The pipe. + */ +static void mkWinChildcareWorkerFlushUnwritten(PWINCHILD pChild, PWINCCWPIPE pPipe) +{ + DWORD cbUnwritten = pPipe->offPendingRead - pPipe->cbWritten; + assert(pPipe->cbWritten <= pPipe->cbBuffer - 16); + assert(pPipe->offPendingRead <= pPipe->cbBuffer - 16); + if (cbUnwritten) + { +#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY + if (pChild && pChild->pMkChild) + { + output_write_bin(&pChild->pMkChild->output, pPipe->iWhich == 2, &pPipe->pbBuffer[pPipe->cbWritten], cbUnwritten); + pPipe->cbWritten += cbUnwritten; + } + else +#endif + { + DWORD cbWritten = 0; + if (WriteFile(GetStdHandle(pPipe->iWhich == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE), + &pPipe->pbBuffer[pPipe->cbWritten], cbUnwritten, &cbWritten, NULL)) + pPipe->cbWritten += cbWritten <= cbUnwritten ? cbWritten : cbUnwritten; /* paranoia */ + } + pPipe->fHaveWrittenOut = TRUE; + } +} + +/** + * This logic mirrors kwSandboxConsoleFlushAll. + * + * @returns TRUE if it looks like a CL.EXE source line, otherwise FALSE. + * @param pPipe The pipe. + * @param offStart The start of the output in the pipe buffer. + * @param offEnd The end of the output in the pipe buffer. + */ +static BOOL mkWinChildcareWorkerIsClExeSourceLine(PWINCCWPIPE pPipe, DWORD offStart, DWORD offEnd) +{ + if (offEnd < offStart + 2) + return FALSE; + if (offEnd - offStart > 80) + return FALSE; + + if ( pPipe->pbBuffer[offEnd - 2] != '\r' + || pPipe->pbBuffer[offEnd - 1] != '\n') + return FALSE; + + offEnd -= 2; + while (offEnd-- > offStart) + { + char ch = pPipe->pbBuffer[offEnd]; + if (isalnum(ch) || ch == '.' || ch == ' ' || ch == '_' || ch == '-') + { /* likely */ } + else + return FALSE; + } + + return TRUE; +} + +/** + * Adds output to the given standard output for the child. + * + * There is no pending read when this function is called, so we're free to + * reshuffle the buffer if desirable. + * + * @param pChild The child. Optional (kSubmit). + * @param iWhich Which standard descriptor number. + * @param cbNewData How much more output was caught. + */ +static void mkWinChildcareWorkerCaughtMoreOutput(PWINCHILD pChild, PWINCCWPIPE pPipe, DWORD cbNewData) +{ + DWORD offStart = pPipe->cbWritten; + assert(offStart <= pPipe->offPendingRead); + assert(offStart <= pPipe->cbBuffer - 16); + assert(pPipe->offPendingRead <= pPipe->cbBuffer - 16); + if (cbNewData > 0) + { + DWORD offRest; + + /* Move offPendingRead ahead by cbRead. */ + pPipe->offPendingRead += cbNewData; + assert(pPipe->offPendingRead <= pPipe->cbBuffer); + if (pPipe->offPendingRead > pPipe->cbBuffer) + pPipe->offPendingRead = pPipe->cbBuffer; + + /* Locate the last newline in the buffer. */ + offRest = pPipe->offPendingRead; + while (offRest > offStart && pPipe->pbBuffer[offRest - 1] != '\n') + offRest--; + + /* If none were found and we've less than 16 bytes left in the buffer, try + find a word boundrary to flush on instead. */ + if ( offRest <= offStart + && pPipe->cbBuffer - pPipe->offPendingRead + offStart < 16) + { + offRest = pPipe->offPendingRead; + while ( offRest > offStart + && isalnum(pPipe->pbBuffer[offRest - 1])) + offRest--; + if (offRest == offStart) + offRest = pPipe->offPendingRead; + } + /* If this is a potential CL.EXE process, we will keep the source + filename unflushed and maybe discard it at the end. */ + else if ( pChild + && pChild->fProbableClExe + && pPipe->iWhich == 1 + && offRest == pPipe->offPendingRead + && mkWinChildcareWorkerIsClExeSourceLine(pPipe, offStart, offRest)) + offRest = offStart; + + if (offRest > offStart) + { + /* Write out offStart..offRest. */ + DWORD cbToWrite = offRest - offStart; +#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY + if (pChild && pChild->pMkChild) + { + output_write_bin(&pChild->pMkChild->output, pPipe->iWhich == 2, &pPipe->pbBuffer[offStart], cbToWrite); + offStart += cbToWrite; + pPipe->cbWritten = offStart; + } + else +#endif + { + DWORD cbWritten = 0; + if (WriteFile(GetStdHandle(pPipe->iWhich == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE), + &pPipe->pbBuffer[offStart], cbToWrite, &cbWritten, NULL)) + { + offStart += cbWritten <= cbToWrite ? cbWritten : cbToWrite; /* paranoia */ + pPipe->cbWritten = offStart; + } + } + pPipe->fHaveWrittenOut = TRUE; + } + } + + /* Shuffle the data to the front of the buffer. */ + if (offStart > 0) + { + DWORD cbUnwritten = pPipe->offPendingRead - offStart; + if (cbUnwritten > 0) + memmove(pPipe->pbBuffer, &pPipe->pbBuffer[offStart], cbUnwritten); + pPipe->offPendingRead -= pPipe->cbWritten; + pPipe->cbWritten = 0; + } +} + +/** + * Catches output from the given pipe. + * + * @param pChild The child. Optional (kSubmit). + * @param pPipe The pipe. + * @param fDraining Set if we're draining the pipe after the process + * terminated. + */ +static void mkWinChildcareWorkerCatchOutput(PWINCHILD pChild, PWINCCWPIPE pPipe, BOOL fDraining) +{ + /* + * Deal with already pending read. + */ + if (pPipe->fReadPending) + { + DWORD cbRead = 0; + if (GetOverlappedResult(pPipe->hPipeMine, &pPipe->Overlapped, &cbRead, !fDraining)) + { + mkWinChildcareWorkerCaughtMoreOutput(pChild, pPipe, cbRead); + pPipe->fReadPending = FALSE; + } + else if (fDraining && GetLastError() == ERROR_IO_INCOMPLETE) + return; + else + { + MkWinChildError(pChild ? pChild->pWorker : NULL, 2, "GetOverlappedResult failed: %u\n", GetLastError()); + pPipe->fReadPending = FALSE; + if (fDraining) + return; + } + } + + /* + * Read data till one becomes pending. + */ + for (;;) + { + DWORD cbRead; + + memset(&pPipe->Overlapped, 0, sizeof(pPipe->Overlapped)); + pPipe->Overlapped.hEvent = pPipe->hEvent; + ResetEvent(pPipe->hEvent); + + assert(pPipe->offPendingRead < pPipe->cbBuffer); + SetLastError(0); + cbRead = 0; + if (!ReadFile(pPipe->hPipeMine, &pPipe->pbBuffer[pPipe->offPendingRead], + pPipe->cbBuffer - pPipe->offPendingRead, &cbRead, &pPipe->Overlapped)) + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_IO_PENDING) + pPipe->fReadPending = TRUE; + else + MkWinChildError(pChild ? pChild->pWorker : NULL, 2, + "ReadFile failed on standard %s: %u\n", + pPipe->iWhich == 1 ? "output" : "error", GetLastError()); + return; + } + + mkWinChildcareWorkerCaughtMoreOutput(pChild, pPipe, cbRead); + } +} + +/** + * Makes sure the output pipes are drained and pushed to output. + * + * @param pChild The child. Optional (kSubmit). + * @param pStdOut The standard output pipe structure. + * @param pStdErr The standard error pipe structure. + */ +void MkWinChildcareWorkerDrainPipes(PWINCHILD pChild, PWINCCWPIPE pStdOut, PWINCCWPIPE pStdErr) +{ + mkWinChildcareWorkerCatchOutput(pChild, pStdOut, TRUE /*fDraining*/); + mkWinChildcareWorkerCatchOutput(pChild, pStdErr, TRUE /*fDraining*/); + + /* Drop lone 'source.c' line from CL.exe, but only if no other output at all. */ + if ( pChild + && pChild->fProbableClExe + && !pStdOut->fHaveWrittenOut + && !pStdErr->fHaveWrittenOut + && pStdErr->cbWritten == pStdErr->offPendingRead + && pStdOut->cbWritten < pStdOut->offPendingRead + && mkWinChildcareWorkerIsClExeSourceLine(pStdOut, pStdOut->cbWritten, pStdOut->offPendingRead)) + { + if (!pStdOut->fReadPending) + pStdOut->cbWritten = pStdOut->offPendingRead = 0; + else + pStdOut->cbWritten = pStdOut->offPendingRead; + } + else + { + mkWinChildcareWorkerFlushUnwritten(pChild, pStdOut); + mkWinChildcareWorkerFlushUnwritten(pChild, pStdErr); + } +} + +/** + * Commmon worker for waiting on a child process and retrieving the exit code. + * + * @returns Child exit code. + * @param pWorker The worker. + * @param pChild The child. + * @param hProcess The process handle. + * @param pwszJob The job name. + * @param fCatchOutput Set if we need to work the output pipes + * associated with the worker. + */ +static int mkWinChildcareWorkerWaitForProcess(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild, HANDLE hProcess, + WCHAR const *pwszJob, BOOL fCatchOutput) +{ + DWORD const msStart = GetTickCount(); + DWORD msNextMsg = msStart + 15000; + + /* Reset the written indicators on the pipes before we start loop. */ + pWorker->pStdOut->fHaveWrittenOut = FALSE; + pWorker->pStdErr->fHaveWrittenOut = FALSE; + + for (;;) + { + /* + * Do the waiting and output catching. + */ + DWORD dwStatus; + if (!fCatchOutput) + dwStatus = WaitForSingleObject(hProcess, 15001 /*ms*/); + else + { + HANDLE ahHandles[3] = { hProcess, pWorker->pStdOut->hEvent, pWorker->pStdErr->hEvent }; + dwStatus = WaitForMultipleObjects(3, ahHandles, FALSE /*fWaitAll*/, 1000 /*ms*/); + if (dwStatus == WAIT_OBJECT_0 + 1) + mkWinChildcareWorkerCatchOutput(pChild, pWorker->pStdOut, FALSE /*fDraining*/); + else if (dwStatus == WAIT_OBJECT_0 + 2) + mkWinChildcareWorkerCatchOutput(pChild, pWorker->pStdErr, FALSE /*fDraining*/); + } + assert(dwStatus != WAIT_FAILED); + + /* + * Get the exit code and return if the process was signalled as done. + */ + if (dwStatus == WAIT_OBJECT_0) + { + DWORD dwExitCode = -42; + if (GetExitCodeProcess(hProcess, &dwExitCode)) + { + pChild->iExitCode = (int)dwExitCode; + if (fCatchOutput) + MkWinChildcareWorkerDrainPipes(pChild, pWorker->pStdOut, pWorker->pStdErr); + return dwExitCode; + } + } + /* + * Loop again if just a timeout or pending output? + * Put out a message every 15 or 30 seconds if the job takes a while. + */ + else if ( dwStatus == WAIT_TIMEOUT + || dwStatus == WAIT_OBJECT_0 + 1 + || dwStatus == WAIT_OBJECT_0 + 2 + || dwStatus == WAIT_IO_COMPLETION) + { + DWORD msNow = GetTickCount(); + if (msNow >= msNextMsg) + { + if ( !pChild->pMkChild + || !pChild->pMkChild->recursive) /* ignore make recursions */ + { + if ( !pChild->pMkChild + || !pChild->pMkChild->file + || !pChild->pMkChild->file->name) + MkWinChildError(NULL, 0, "Pid %u ('%ls') still running after %u seconds\n", + GetProcessId(hProcess), pwszJob, (msNow - msStart) / 1000); + else + MkWinChildError(NULL, 0, "Target '%s' (pid %u) still running after %u seconds\n", + pChild->pMkChild->file->name, GetProcessId(hProcess), (msNow - msStart) / 1000); + } + + /* After 15s, 30s, 60s, 120s, 180s, ... */ + if (msNextMsg == msStart + 15000) + msNextMsg += 15000; + else + msNextMsg += 30000; + } + continue; + } + + /* Something failed. */ + pChild->iExitCode = GetLastError(); + if (pChild->iExitCode == 0) + pChild->iExitCode = -4242; + return pChild->iExitCode; + } +} + + +/** + * Closes standard handles that need closing before destruction. + * + * @param pChild The child (WINCHILDTYPE_PROCESS). + */ +static void mkWinChildcareWorkerCloseStandardHandles(PWINCHILD pChild) +{ + if ( pChild->u.Process.fCloseStdOut + && pChild->u.Process.hStdOut != INVALID_HANDLE_VALUE) + { + CloseHandle(pChild->u.Process.hStdOut); + pChild->u.Process.hStdOut = INVALID_HANDLE_VALUE; + pChild->u.Process.fCloseStdOut = FALSE; + } + if ( pChild->u.Process.fCloseStdErr + && pChild->u.Process.hStdErr != INVALID_HANDLE_VALUE) + { + CloseHandle(pChild->u.Process.hStdErr); + pChild->u.Process.hStdErr = INVALID_HANDLE_VALUE; + pChild->u.Process.fCloseStdErr = FALSE; + } +} + + +/** + * Does the actual process creation. + * + * @returns 0 if there is anything to wait on, otherwise non-zero windows error. + * @param pWorker The childcare worker. + * @param pChild The child. + * @param pwszImageName The image path. + * @param pwszCommandLine The command line. + * @param pwszzEnvironment The enviornment block. + */ +static int mkWinChildcareWorkerCreateProcess(PWINCHILDCAREWORKER pWorker, WCHAR const *pwszImageName, + WCHAR const *pwszCommandLine, WCHAR const *pwszzEnvironment, WCHAR const *pwszCwd, + BOOL pafReplace[3], HANDLE pahChild[3], BOOL fCatchOutput, HANDLE *phProcess) +{ + PROCESS_INFORMATION ProcInfo; + STARTUPINFOW StartupInfo; + DWORD fFlags = CREATE_UNICODE_ENVIRONMENT; + BOOL const fHaveHandles = pafReplace[0] | pafReplace[1] | pafReplace[2]; + BOOL fRet; + DWORD dwErr; +#ifdef KMK + extern int process_priority; +#endif + + /* + * Populate startup info. + * + * Turns out we can get away without passing TRUE for the inherit handles + * parameter to CreateProcess when we're not using STARTF_USESTDHANDLES. + * At least on NT, which is all worth caring about at this point + context IMO. + * + * Not inherting the handles is a good thing because it means we won't + * accidentally end up with a pipe handle or such intended for a different + * child process, potentially causing the EOF/HUP event to be delayed. + * + * Since the present handle inhertiance requirements only involves standard + * output and error, we'll never set the inherit handles flag and instead + * do manual handle duplication and planting. + */ + memset(&StartupInfo, 0, sizeof(StartupInfo)); + StartupInfo.cb = sizeof(StartupInfo); + GetStartupInfoW(&StartupInfo); + StartupInfo.lpReserved2 = 0; /* No CRT file handle + descriptor info possible, sorry. */ + StartupInfo.cbReserved2 = 0; + if ( !fHaveHandles + && !fCatchOutput) + StartupInfo.dwFlags &= ~STARTF_USESTDHANDLES; + else + { + fFlags |= CREATE_SUSPENDED; + StartupInfo.dwFlags &= ~STARTF_USESTDHANDLES; + } + + /* + * Flags. + */ +#ifdef KMK + switch (process_priority) + { + case 1: fFlags |= CREATE_SUSPENDED | IDLE_PRIORITY_CLASS; break; + case 2: fFlags |= CREATE_SUSPENDED | BELOW_NORMAL_PRIORITY_CLASS; break; + case 3: fFlags |= CREATE_SUSPENDED | NORMAL_PRIORITY_CLASS; break; + case 4: fFlags |= CREATE_SUSPENDED | HIGH_PRIORITY_CLASS; break; + case 5: fFlags |= CREATE_SUSPENDED | REALTIME_PRIORITY_CLASS; break; + } +#endif + if (g_cProcessorGroups > 1) + fFlags |= CREATE_SUSPENDED; + + /* + * Try create the process. + */ + DB(DB_JOBS, ("CreateProcessW(%ls, %ls,,, TRUE, %#x...)\n", pwszImageName, pwszCommandLine, fFlags)); + memset(&ProcInfo, 0, sizeof(ProcInfo)); +#ifdef WITH_RW_LOCK + AcquireSRWLockShared(&g_RWLock); +#endif + + fRet = CreateProcessW((WCHAR *)pwszImageName, (WCHAR *)pwszCommandLine, NULL /*pProcSecAttr*/, NULL /*pThreadSecAttr*/, + FALSE /*fInheritHandles*/, fFlags, (WCHAR *)pwszzEnvironment, pwszCwd, &StartupInfo, &ProcInfo); + dwErr = GetLastError(); + +#ifdef WITH_RW_LOCK + ReleaseSRWLockShared(&g_RWLock); +#endif + if (fRet) + *phProcess = ProcInfo.hProcess; + else + { + MkWinChildError(pWorker, 1, "CreateProcess(%ls) failed: %u\n", pwszImageName, dwErr); + return (int)dwErr; + } + + /* + * If the child is suspended, we've got some adjustment work to be done. + */ + dwErr = ERROR_SUCCESS; + if (fFlags & CREATE_SUSPENDED) + { + /* + * First do handle inhertiance as that's the most complicated. + */ + if (fHaveHandles || fCatchOutput) + { + char szErrMsg[128]; + if (fCatchOutput) + { + if (!pafReplace[1]) + { + pafReplace[1] = TRUE; + pahChild[1] = pWorker->pStdOut->hPipeChild; + } + if (!pafReplace[2]) + { + pafReplace[2] = TRUE; + pahChild[2] = pWorker->pStdErr->hPipeChild; + } + } + dwErr = nt_child_inject_standard_handles(ProcInfo.hProcess, pafReplace, pahChild, szErrMsg, sizeof(szErrMsg)); + if (dwErr != 0) + MkWinChildError(pWorker, 1, "%s\n", szErrMsg); + } + + /* + * Assign processor group (ignore failure). + */ +#ifdef MKWINCHILD_DO_SET_PROCESSOR_GROUP + if (g_cProcessorGroups > 1) + { + GROUP_AFFINITY Affinity = { 0 /* == all active apparently */, pWorker->iProcessorGroup, { 0, 0, 0 } }; + fRet = g_pfnSetThreadGroupAffinity(ProcInfo.hThread, &Affinity, NULL); + assert(fRet); + } +#endif + +#ifdef KMK + /* + * Set priority (ignore failure). + */ + switch (process_priority) + { + case 1: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_IDLE); break; + case 2: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_BELOW_NORMAL); break; + case 3: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_NORMAL); break; + case 4: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_HIGHEST); break; + case 5: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_TIME_CRITICAL); break; + default: fRet = TRUE; + } + assert(fRet); +#endif + + /* + * Inject the job object if we're in a non-killing mode, to postpone + * the closing of the job object and maybe make it more useful. + */ + if (win_job_object_no_kill && g_hJob) + { + HANDLE hWhatever = INVALID_HANDLE_VALUE; + DuplicateHandle(GetCurrentProcess(), g_hJob, ProcInfo.hProcess, &hWhatever, GENERIC_ALL, + TRUE /*bInheritHandle*/, DUPLICATE_SAME_ACCESS); + } + + /* + * Resume the thread if the adjustments succeeded, otherwise kill it. + */ + if (dwErr == ERROR_SUCCESS) + { + fRet = ResumeThread(ProcInfo.hThread); + assert(fRet); + if (!fRet) + { + dwErr = GetLastError(); + MkWinChildError(pWorker, 1, "ResumeThread failed on child process: %u\n", dwErr); + } + } + if (dwErr != ERROR_SUCCESS) + TerminateProcess(ProcInfo.hProcess, dwErr); + } + + /* + * Close unnecessary handles and cache the image. + */ + CloseHandle(ProcInfo.hThread); + kmk_cache_exec_image_w(pwszImageName); + return 0; +} + +/** + * Converts a argument vector that has already been quoted correctly. + * + * The argument vector is typically the result of quote_argv(). + * + * @returns 0 on success, non-zero on failure. + * @param pWorker The childcare worker. + * @param papszArgs The argument vector to convert. + * @param ppwszCommandLine Where to return the command line. + */ +static int mkWinChildcareWorkerConvertQuotedArgvToCommandline(PWINCHILDCAREWORKER pWorker, char **papszArgs, + WCHAR **ppwszCommandLine) +{ + WCHAR *pwszCmdLine; + WCHAR *pwszDst; + + /* + * Calc length the converted length. + */ + unsigned cwcNeeded = 1; + unsigned i = 0; + const char *pszSrc; + while ((pszSrc = papszArgs[i]) != NULL) + { + int cwcThis = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, -1, NULL, 0); + if (cwcThis > 0 || *pszSrc == '\0') + cwcNeeded += cwcThis + 1; + else + { + DWORD dwErr = GetLastError(); + MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert argv[%u] (%s): %u\n"), i, pszSrc, dwErr); + return dwErr; + } + i++; + } + + /* + * Allocate and do the conversion. + */ + pwszCmdLine = pwszDst = (WCHAR *)xmalloc(cwcNeeded * sizeof(WCHAR)); + i = 0; + while ((pszSrc = papszArgs[i]) != NULL) + { + int cwcThis; + if (i > 0) + { + *pwszDst++ = ' '; + cwcNeeded--; + } + + cwcThis = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, -1, pwszDst, cwcNeeded); + if (!cwcThis && *pszSrc != '\0') + { + DWORD dwErr = GetLastError(); + MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert argv[%u] (%s): %u\n"), i, pszSrc, dwErr); + free(pwszCmdLine); + return dwErr; + } + if (cwcThis > 0 && pwszDst[cwcThis - 1] == '\0') + cwcThis--; + pwszDst += cwcThis; + cwcNeeded -= cwcThis; + i++; + } + *pwszDst++ = '\0'; + + *ppwszCommandLine = pwszCmdLine; + return 0; +} + + +#define MKWCCWCMD_F_CYGWIN_SHELL 1 +#define MKWCCWCMD_F_MKS_SHELL 2 +#define MKWCCWCMD_F_HAVE_SH 4 +#define MKWCCWCMD_F_HAVE_KASH_C 8 /**< kmk_ash -c "..." */ + +/* + * @param pWorker The childcare worker if on one, otherwise NULL. + */ +static int mkWinChildcareWorkerConvertCommandline(PWINCHILDCAREWORKER pWorker, char **papszArgs, unsigned fFlags, + WCHAR **ppwszCommandLine) +{ + struct ARGINFO + { + size_t cchSrc; + size_t cwcDst; /**< converted size w/o terminator. */ + size_t cwcDstExtra : 24; /**< Only set with fSlowly. */ + size_t fSlowly : 1; + size_t fQuoteIt : 1; + size_t fEndSlashes : 1; /**< if escapes needed for trailing backslashes. */ + size_t fExtraSpace : 1; /**< if kash -c "" needs an extra space before the quote. */ + } *paArgInfo; + size_t cArgs; + size_t i; + size_t cwcNeeded; + WCHAR *pwszDst; + WCHAR *pwszCmdLine; + + /* + * Count them first so we can allocate an info array of the stack. + */ + cArgs = 0; + while (papszArgs[cArgs] != NULL) + cArgs++; + paArgInfo = (struct ARGINFO *)alloca(sizeof(paArgInfo[0]) * cArgs); + + /* + * Preprocess them and calculate the exact command line length. + */ + cwcNeeded = 1; + for (i = 0; i < cArgs; i++) + { + char *pszSrc = papszArgs[i]; + size_t cchSrc = strlen(pszSrc); + paArgInfo[i].cchSrc = cchSrc; + if (cchSrc == 0) + { + /* empty needs quoting. */ + paArgInfo[i].cwcDst = 2; + paArgInfo[i].cwcDstExtra = 0; + paArgInfo[i].fSlowly = 0; + paArgInfo[i].fQuoteIt = 1; + paArgInfo[i].fExtraSpace = 0; + paArgInfo[i].fEndSlashes = 0; + } + else + { + const char *pszSpace = memchr(pszSrc, ' ', cchSrc); + const char *pszTab = memchr(pszSrc, '\t', cchSrc); + const char *pszDQuote = memchr(pszSrc, '"', cchSrc); + const char *pszEscape = memchr(pszSrc, '\\', cchSrc); + int cwcDst = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cchSrc + 1, NULL, 0); + if (cwcDst >= 0) + --cwcDst; + else + { + DWORD dwErr = GetLastError(); + MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert argv[%u] (%s): %u\n"), i, pszSrc, dwErr); + return dwErr; + } +#if 0 + if (!pszSpace && !pszTab && !pszDQuote && !pszEscape) + { + /* no special handling needed. */ + paArgInfo[i].cwcDst = cwcDst; + paArgInfo[i].cwcDstExtra = 0; + paArgInfo[i].fSlowly = 0; + paArgInfo[i].fQuoteIt = 0; + paArgInfo[i].fExtraSpace = 0; + paArgInfo[i].fEndSlashes = 0; + } + else if (!pszDQuote && !pszEscape) + { + /* Just double quote it. */ + paArgInfo[i].cwcDst = cwcDst + 2; + paArgInfo[i].cwcDstExtra = 0; + paArgInfo[i].fSlowly = 0; + paArgInfo[i].fQuoteIt = 1; + paArgInfo[i].fExtraSpace = 0; + paArgInfo[i].fEndSlashes = 0; + } + else +#endif + { + /* Complicated, need to scan the string to figure out what to do. */ + size_t cwcDstExtra; + int cBackslashes; + char ch; + + paArgInfo[i].fQuoteIt = 0; + paArgInfo[i].fSlowly = 1; + paArgInfo[i].fExtraSpace = 0; + paArgInfo[i].fEndSlashes = 0; + + cwcDstExtra = 0; + cBackslashes = 0; + while ((ch = *pszSrc++) != '\0') + { + switch (ch) + { + default: + cBackslashes = 0; + break; + + case '\\': + cBackslashes++; + break; + + case '"': + if (fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_MKS_SHELL)) + cwcDstExtra += 1; /* just an extra '"' */ + else + cwcDstExtra += 1 + cBackslashes; /* extra '\\' for the '"' and for each preceeding slash. */ + cBackslashes = 0; + break; + + case ' ': + case '\t': + if (!paArgInfo[i].fQuoteIt) + { + paArgInfo[i].fQuoteIt = 1; + cwcDstExtra += 2; + } + cBackslashes = 0; + break; + } + } + + /* If we're quoting the argument and it ends with trailing '\\', it/they must be escaped. */ + if ( cBackslashes > 0 + && paArgInfo[i].fQuoteIt + && !(fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_MKS_SHELL))) + { + cwcDstExtra += cBackslashes; + paArgInfo[i].fEndSlashes = 1; + } + + paArgInfo[i].cwcDst = cwcDst + cwcDstExtra; + paArgInfo[i].cwcDstExtra = cwcDstExtra; + } + } + + if ( (fFlags & MKWCCWCMD_F_HAVE_KASH_C) + && paArgInfo[i].fQuoteIt) + { + paArgInfo[i].fExtraSpace = 1; + paArgInfo[i].cwcDst++; + paArgInfo[i].cwcDstExtra++; + } + + cwcNeeded += (i != 0) + paArgInfo[i].cwcDst; + } + + /* + * Allocate the result buffer and do the actual conversion. + */ + pwszDst = pwszCmdLine = (WCHAR *)xmalloc(sizeof(WCHAR) * cwcNeeded); + for (i = 0; i < cArgs; i++) + { + char *pszSrc = papszArgs[i]; + size_t cwcDst = paArgInfo[i].cwcDst; + + if (i != 0) + *pwszDst++ = L' '; + + if (paArgInfo[i].fQuoteIt) + { + *pwszDst++ = L'"'; + cwcDst -= 2; + } + + if (!paArgInfo[i].fSlowly) + { + int cwcDst2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, paArgInfo[i].cchSrc, pwszDst, cwcDst + 1); + assert(cwcDst2 >= 0); + pwszDst += cwcDst; + } + else + { + /* Do the conversion into the end of the output buffer, then move + it up to where it should be char by char. */ + int cBackslashes; + size_t cwcLeft = paArgInfo[i].cwcDst - paArgInfo[i].cwcDstExtra; + WCHAR volatile *pwchSlowSrc = pwszDst + paArgInfo[i].cwcDstExtra; + WCHAR volatile *pwchSlowDst = pwszDst; + int cwcDst2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, paArgInfo[i].cchSrc, + (WCHAR *)pwchSlowSrc, cwcLeft + 1); + assert(cwcDst2 >= 0); + + cBackslashes = 0; + while (cwcLeft-- > 0) + { + WCHAR wcSrc = *pwchSlowSrc++; + if (wcSrc != L'\\' && wcSrc != L'"') + cBackslashes = 0; + else if (wcSrc == L'\\') + cBackslashes++; + else if ( (fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_HAVE_SH)) + == (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_HAVE_SH)) + { + *pwchSlowDst++ = L'"'; /* cygwin: '"' instead of '\\', no escaped slashes. */ + cBackslashes = 0; + } + else + { + if (!(fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_MKS_SHELL))) + cBackslashes += 1; /* one extra escape the '"' and one for each preceeding slash. */ + while (cBackslashes > 0) + { + *pwchSlowDst++ = L'\\'; + cBackslashes--; + } + } + *pwchSlowDst++ = wcSrc; + assert((uintptr_t)pwchSlowDst <= (uintptr_t)pwchSlowSrc); + } + + if (paArgInfo[i].fEndSlashes) + while (cBackslashes-- > 0) + *pwchSlowDst++ = L'\\'; + + pwszDst += cwcDst; + assert(pwszDst == (WCHAR *)pwchSlowDst); + } + + if (paArgInfo[i].fExtraSpace) + *pwszDst++ = L' '; + if (paArgInfo[i].fQuoteIt) + *pwszDst++ = L'"'; + } + *pwszDst = L'\0'; + *ppwszCommandLine = pwszCmdLine; + return 0; +} + +static int mkWinChildcareWorkerConvertCommandlineWithShell(PWINCHILDCAREWORKER pWorker, const WCHAR *pwszShell, char **papszArgs, + WCHAR **ppwszCommandLine) +{ + MkWinChildError(pWorker, 1, "%s: not found!\n", papszArgs[0]); +//__debugbreak(); + return ERROR_FILE_NOT_FOUND; +} + +/** + * Searches the environment block for the PATH variable. + * + * @returns Pointer to the path in the block or "." in pwszPathFallback. + * @param pwszzEnv The UTF-16 environment block to search. + * @param pwszPathFallback Fallback. + */ +static const WCHAR *mkWinChildcareWorkerFindPathValue(const WCHAR *pwszzEnv, WCHAR pwszPathFallback[4]) +{ + while (*pwszzEnv) + { + size_t cwcVar = wcslen(pwszzEnv); + if (!IS_PATH_ENV_VAR(cwcVar, pwszzEnv)) + pwszzEnv += cwcVar + 1; + else if (cwcVar > 5) + return &pwszzEnv[5]; + else + break; + } + pwszPathFallback[0] = L'.'; + pwszPathFallback[1] = L'\0'; + return pwszPathFallback; +} + +/** + * Checks if we need to had this executable file to the shell. + * + * @returns TRUE if it's shell fooder, FALSE if we think windows can handle it. + * @param hFile Handle to the file in question + */ +static BOOL mkWinChildcareWorkerCheckIfNeedShell(HANDLE hFile) +{ + /* + * Read the first 512 bytes and check for an executable image header. + */ + union + { + DWORD dwSignature; + WORD wSignature; + BYTE ab[128]; + } uBuf; + DWORD cbRead; + uBuf.dwSignature = 0; + if ( ReadFile(hFile, &uBuf, sizeof(uBuf), &cbRead, NULL /*pOverlapped*/) + && cbRead == sizeof(uBuf)) + { + if (uBuf.wSignature == IMAGE_DOS_SIGNATURE) + return FALSE; + if (uBuf.dwSignature == IMAGE_NT_SIGNATURE) + return FALSE; + if ( uBuf.wSignature == IMAGE_OS2_SIGNATURE /* NE */ + || uBuf.wSignature == 0x5d4c /* LX */ + || uBuf.wSignature == IMAGE_OS2_SIGNATURE_LE /* LE */) + return FALSE; + } + return TRUE; +} + +/** + * Checks if the image path looks like microsoft CL.exe. + * + * @returns TRUE / FALSE. + * @param pwszImagePath The executable image path to evalutate. + * @param cwcImagePath The length of the image path. + */ +static BOOL mkWinChildIsProbableClExe(WCHAR const *pwszImagePath, size_t cwcImagePath) +{ + assert(pwszImagePath[cwcImagePath] == '\0'); + return cwcImagePath > 7 + && (pwszImagePath[cwcImagePath - 7] == L'/' || pwszImagePath[cwcImagePath - 7] == L'\\') + && (pwszImagePath[cwcImagePath - 6] == L'c' || pwszImagePath[cwcImagePath - 6] == L'C') + && (pwszImagePath[cwcImagePath - 5] == L'l' || pwszImagePath[cwcImagePath - 5] == L'L') + && pwszImagePath[cwcImagePath - 4] == L'.' + && (pwszImagePath[cwcImagePath - 3] == L'e' || pwszImagePath[cwcImagePath - 3] == L'E') + && (pwszImagePath[cwcImagePath - 2] == L'x' || pwszImagePath[cwcImagePath - 2] == L'X') + && (pwszImagePath[cwcImagePath - 1] == L'e' || pwszImagePath[cwcImagePath - 1] == L'E'); +} + +/** + * Temporary workaround for seemingly buggy kFsCache.c / dir-nt-bird.c. + * + * Something is not invalidated / updated correctly! + */ +static BOOL mkWinChildcareWorkerIsRegularFileW(PWINCHILDCAREWORKER pWorker, wchar_t const *pwszPath) +{ + BOOL fRet = FALSE; +#ifdef KMK + if (utf16_regular_file_p(pwszPath)) + fRet = TRUE; + else +#endif + { + /* Don't believe the cache. */ + DWORD dwAttr = GetFileAttributesW(pwszPath); + if (dwAttr != INVALID_FILE_ATTRIBUTES) + { + if (!(dwAttr & FILE_ATTRIBUTE_DIRECTORY)) + { +#ifdef KMK + extern void dir_cache_invalid_volatile(void); + dir_cache_invalid_volatile(); + if (utf16_regular_file_p(pwszPath)) + MkWinChildError(pWorker, 1, "kFsCache was out of sync! pwszPath=%S\n", pwszPath); + else + { + dir_cache_invalid_all(); + if (utf16_regular_file_p(pwszPath)) + MkWinChildError(pWorker, 1, "kFsCache was really out of sync! pwszPath=%S\n", pwszPath); + else + MkWinChildError(pWorker, 1, "kFsCache is really out of sync!! pwszPath=%S\n", pwszPath); + } +#endif + fRet = TRUE; + } + } + } + return fRet; +} + + +/** + * Tries to locate the image file, searching the path and maybe falling back on + * the shell in case it knows more (think cygwin with its own view of the file + * system). + * + * This will also check for shell script, falling back on the shell too to + * handle those. + * + * @returns 0 on success, windows error code on failure. + * @param pWorker The childcare worker. + * @param pszArg0 The first argument. + * @param pwszSearchPath In case mkWinChildcareWorkerConvertEnvironment + * had a chance of locating the search path already. + * @param pwszzEnv The environment block, in case we need to look for + * the path. + * @param pszShell The shell. + * @param ppwszImagePath Where to return the pointer to the image path. This + * could be the shell. + * @param pfNeedShell Where to return shell vs direct execution indicator. + * @param pfProbableClExe Where to return an indicator of probably CL.EXE. + */ +static int mkWinChildcareWorkerFindImage(PWINCHILDCAREWORKER pWorker, char const *pszArg0, WCHAR *pwszSearchPath, + WCHAR const *pwszzEnv, const char *pszShell, + WCHAR **ppwszImagePath, BOOL *pfNeedShell, BOOL *pfProbableClExe) +{ + /** @todo Slap a cache on this code. We usually end up executing the same + * stuff over and over again (e.g. compilers, linkers, etc). + * Hitting the file system is slow on windows. */ + + /* + * Convert pszArg0 to unicode so we can work directly on that. + */ + WCHAR wszArg0[MKWINCHILD_MAX_PATH + 4]; /* +4 for painless '.exe' appending */ + DWORD dwErr; + size_t cbArg0 = strlen(pszArg0) + 1; + int const cwcArg0 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszArg0, cbArg0, wszArg0, MKWINCHILD_MAX_PATH); + if (cwcArg0 > 0) + { + HANDLE hFile = INVALID_HANDLE_VALUE; + WCHAR wszPathBuf[MKWINCHILD_MAX_PATH + 4]; /* +4 for painless '.exe' appending */ + int cwc; + + /* + * If there isn't an .exe suffix, we may have to add one. + * Also we ASSUME that .exe suffixes means no hash bang detection needed. + */ + int const fHasExeSuffix = cwcArg0 > CSTRLEN(".exe") + && wszArg0[cwcArg0 - 4] == '.' + && (wszArg0[cwcArg0 - 3] == L'e' || wszArg0[cwcArg0 - 3] == L'E') + && (wszArg0[cwcArg0 - 2] == L'x' || wszArg0[cwcArg0 - 2] == L'X') + && (wszArg0[cwcArg0 - 1] == L'e' || wszArg0[cwcArg0 - 1] == L'E'); + + /* + * If there isn't any path specified, we need to search the PATH env.var. + */ + int const fHasPath = wszArg0[1] == L':' + || wszArg0[0] == L'\\' + || wszArg0[0] == L'/' + || wmemchr(wszArg0, L'/', cwcArg0) + || wmemchr(wszArg0, L'\\', cwcArg0); + + /* Before we do anything, flip UNIX slashes to DOS ones. */ + WCHAR *pwc = wszArg0; + while ((pwc = wcschr(pwc, L'/')) != NULL) + *pwc++ = L'\\'; + + /* Don't need to set these all the time... */ + *pfNeedShell = FALSE; + *pfProbableClExe = FALSE; + + /* + * If any kind of path is specified in arg0, we will not search the + * PATH env.var and can limit ourselves to maybe slapping a .exe on to it. + */ + if (fHasPath) + { + /* + * If relative to a CWD, turn it into an absolute one. + */ + unsigned cwcPath = cwcArg0; + WCHAR *pwszPath = wszArg0; + if ( *pwszPath != L'\\' + && (pwszPath[1] != ':' || pwszPath[2] != L'\\') ) + { + DWORD cwcAbsPath = GetFullPathNameW(wszArg0, MKWINCHILD_MAX_PATH, wszPathBuf, NULL); + if (cwcAbsPath > 0) + { + cwcPath = cwcAbsPath + 1; /* include terminator, like MultiByteToWideChar does. */ + pwszPath = wszPathBuf; + } + } + + /* + * Check with .exe suffix first. + * We don't open .exe files and look for hash bang stuff, we just + * assume they are executable images that CreateProcess can deal with. + */ + if (!fHasExeSuffix) + { + pwszPath[cwcPath - 1] = L'.'; + pwszPath[cwcPath ] = L'e'; + pwszPath[cwcPath + 1] = L'x'; + pwszPath[cwcPath + 2] = L'e'; + pwszPath[cwcPath + 3] = L'\0'; + } + + if (mkWinChildcareWorkerIsRegularFileW(pWorker, pwszPath)) + { + *pfProbableClExe = mkWinChildIsProbableClExe(pwszPath, cwcPath + 4 - 1); + return mkWinChildDuplicateUtf16String(pwszPath, cwcPath + 4, ppwszImagePath); + } + + /* + * If no suffix was specified, try without .exe too, but now we need + * to see if it's for the shell or CreateProcess. + */ + if (!fHasExeSuffix) + { + pwszPath[cwcPath - 1] = L'\0'; +#ifdef KMK + if (mkWinChildcareWorkerIsRegularFileW(pWorker, pwszPath)) +#endif + { + hFile = CreateFileW(pwszPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE, + NULL /*pSecAttr*/, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile != INVALID_HANDLE_VALUE) + { + *pfNeedShell = mkWinChildcareWorkerCheckIfNeedShell(hFile); + CloseHandle(hFile); + if (!*pfNeedShell) + { + *pfProbableClExe = mkWinChildIsProbableClExe(pwszPath, cwcPath - 1); + return mkWinChildDuplicateUtf16String(pwszPath, cwcPath, ppwszImagePath); + } + } + } + } + } + /* + * No path, need to search the PATH env.var. for the executable, maybe + * adding an .exe suffix while do so if that is missing. + */ + else + { + BOOL fSearchedCwd = FALSE; + WCHAR wszPathFallback[4]; + if (!pwszSearchPath) + pwszSearchPath = (WCHAR *)mkWinChildcareWorkerFindPathValue(pwszzEnv, wszPathFallback); + + for (;;) + { + size_t cwcCombined; + + /* + * Find the end of the current PATH component. + */ + size_t cwcSkip; + WCHAR wcEnd; + size_t cwcComponent = 0; + WCHAR wc; + while ((wc = pwszSearchPath[cwcComponent]) != L'\0') + { + if (wc != ';' && wc != ':') + { /* likely */ } + else if (wc == ';') + break; + else if (cwcComponent != (pwszSearchPath[cwcComponent] != L'"' ? 1 : 2)) + break; + cwcComponent++; + } + wcEnd = wc; + + /* Trim leading spaces and double quotes. */ + while ( cwcComponent > 0 + && ((wc = *pwszSearchPath) == L'"' || wc == L' ' || wc == L'\t')) + { + pwszSearchPath++; + cwcComponent--; + } + cwcSkip = cwcComponent; + + /* Trim trailing spaces & double quotes. */ + while ( cwcComponent > 0 + && ((wc = pwszSearchPath[cwcComponent - 1]) == L'"' || wc == L' ' || wc == L'\t')) + cwcComponent--; + + /* + * Skip empty components. Join the component and the filename, making sure to + * resolve any CWD relative stuff first. + */ + cwcCombined = cwcComponent + 1 + cwcArg0; + if (cwcComponent > 0 && cwcCombined <= MKWINCHILD_MAX_PATH) + { + /* Copy the component into wszPathBuf, maybe abspath'ing it. */ + DWORD cwcAbsPath = 0; + if ( *pwszSearchPath != L'\\' + && (pwszSearchPath[1] != ':' || pwszSearchPath[2] != L'\\') ) + { + /* To save an extra buffer + copying, we'll temporarily modify the PATH + value in our converted UTF-16 environment block. */ + WCHAR const wcSaved = pwszSearchPath[cwcComponent]; + pwszSearchPath[cwcComponent] = L'\0'; + cwcAbsPath = GetFullPathNameW(pwszSearchPath, MKWINCHILD_MAX_PATH, wszPathBuf, NULL); + pwszSearchPath[cwcComponent] = wcSaved; + if (cwcAbsPath > 0 && cwcAbsPath + 1 + cwcArg0 <= MKWINCHILD_MAX_PATH) + cwcCombined = cwcAbsPath + 1 + cwcArg0; + else + cwcAbsPath = 0; + } + if (cwcAbsPath == 0) + { + memcpy(wszPathBuf, pwszSearchPath, cwcComponent * sizeof(WCHAR)); + cwcAbsPath = cwcComponent; + } + + /* Append the filename. */ + if ((wc = wszPathBuf[cwcAbsPath - 1]) == L'\\' || wc == L'/' || wc == L':') + { + memcpy(&wszPathBuf[cwcAbsPath], wszArg0, cwcArg0 * sizeof(WCHAR)); + cwcCombined--; + } + else + { + wszPathBuf[cwcAbsPath] = L'\\'; + memcpy(&wszPathBuf[cwcAbsPath + 1], wszArg0, cwcArg0 * sizeof(WCHAR)); + } + assert(wszPathBuf[cwcCombined - 1] == L'\0'); + + /* DOS slash conversion */ + pwc = wszPathBuf; + while ((pwc = wcschr(pwc, L'/')) != NULL) + *pwc++ = L'\\'; + + /* + * Search with exe suffix first. + */ + if (!fHasExeSuffix) + { + wszPathBuf[cwcCombined - 1] = L'.'; + wszPathBuf[cwcCombined ] = L'e'; + wszPathBuf[cwcCombined + 1] = L'x'; + wszPathBuf[cwcCombined + 2] = L'e'; + wszPathBuf[cwcCombined + 3] = L'\0'; + } + if (mkWinChildcareWorkerIsRegularFileW(pWorker, wszPathBuf)) + { + *pfProbableClExe = mkWinChildIsProbableClExe(wszPathBuf, cwcCombined + (fHasExeSuffix ? 0 : 4) - 1); + return mkWinChildDuplicateUtf16String(wszPathBuf, cwcCombined + (fHasExeSuffix ? 0 : 4), ppwszImagePath); + } + if (!fHasExeSuffix) + { + wszPathBuf[cwcCombined - 1] = L'\0'; +#ifdef KMK + if (mkWinChildcareWorkerIsRegularFileW(pWorker, wszPathBuf)) +#endif + { + /* + * Check if the file exists w/o the added '.exe' suffix. If it does, + * we need to check if we can pass it to CreateProcess or need the shell. + */ + hFile = CreateFileW(wszPathBuf, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE, + NULL /*pSecAttr*/, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile != INVALID_HANDLE_VALUE) + { + *pfNeedShell = mkWinChildcareWorkerCheckIfNeedShell(hFile); + CloseHandle(hFile); + if (!*pfNeedShell) + { + *pfProbableClExe = mkWinChildIsProbableClExe(wszPathBuf, cwcCombined - 1); + return mkWinChildDuplicateUtf16String(wszPathBuf, cwcCombined, ppwszImagePath); + } + break; + } + } + } + } + + /* + * Advance to the next component. + */ + if (wcEnd != '\0') + pwszSearchPath += cwcSkip + 1; + else if (fSearchedCwd) + break; + else + { + fSearchedCwd = TRUE; + wszPathFallback[0] = L'.'; + wszPathFallback[1] = L'\0'; + pwszSearchPath = wszPathFallback; + } + } + } + + /* + * We need the shell. It will take care of finding/reporting missing + * image files and such. + */ + *pfNeedShell = TRUE; + if (pszShell) + { + cwc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszShell, strlen(pszShell) + 1, wszPathBuf, MKWINCHILD_MAX_PATH); + if (cwc > 0) + return mkWinChildDuplicateUtf16String(wszPathBuf, cwc, ppwszImagePath); + dwErr = GetLastError(); + MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert shell (%s): %u\n"), pszShell, dwErr); + } + else + { + MkWinChildError(pWorker, 1, "%s: not found!\n", pszArg0); + dwErr = ERROR_FILE_NOT_FOUND; + } + } + else + { + dwErr = GetLastError(); + MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert argv[0] (%s): %u\n"), pszArg0, dwErr); + } + return dwErr == ERROR_INSUFFICIENT_BUFFER ? ERROR_FILENAME_EXCED_RANGE : dwErr; +} + +/** + * Creates the environment block. + * + * @returns 0 on success, windows error code on failure. + * @param pWorker The childcare worker if on one, otherwise NULL. + * @param papszEnv The environment vector to convert. + * @param cbEnvStrings The size of the environment strings, iff they are + * sequential in a block. Otherwise, zero. + * @param ppwszEnv Where to return the pointer to the environment + * block. + * @param ppwszSearchPath Where to return the pointer to the path value + * within the environment block. This will not be set + * if cbEnvStrings is non-zero, more efficient to let + * mkWinChildcareWorkerFindImage() search when needed. + */ +static int mkWinChildcareWorkerConvertEnvironment(PWINCHILDCAREWORKER pWorker, char **papszEnv, size_t cbEnvStrings, + WCHAR **ppwszEnv, WCHAR const **ppwszSearchPath) +{ + DWORD dwErr; + int cwcRc; + int cwcDst; + WCHAR *pwszzDst; + + *ppwszSearchPath = NULL; + + /* + * We've got a little optimization here with help from mkWinChildCopyStringArray. + */ + if (cbEnvStrings) + { + cwcDst = cbEnvStrings + 32; + pwszzDst = (WCHAR *)xmalloc(cwcDst * sizeof(WCHAR)); + cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, pwszzDst, cwcDst); + if (cwcRc != 0) + { + *ppwszEnv = pwszzDst; + return 0; + } + + /* Resize the allocation and try again. */ + dwErr = GetLastError(); + if (dwErr == ERROR_INSUFFICIENT_BUFFER) + { + cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, NULL, 0); + if (cwcRc > 0) + cwcDst = cwcRc + 32; + else + cwcDst *= 2; + pwszzDst = (WCHAR *)xrealloc(pwszzDst, cwcDst); + cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, pwszzDst, cwcDst); + if (cwcRc != 0) + { + *ppwszEnv = pwszzDst; + return 0; + } + dwErr = GetLastError(); + } + MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert environment block: %u\n"), dwErr); + } + /* + * Need to convert it string by string. + */ + else + { + size_t offPathValue = ~(size_t)0; + size_t offDst; + + /* + * Estimate the size first. + */ + size_t cEnvVars; + size_t cwcDst = 32; + size_t iVar = 0; + const char *pszSrc; + while ((pszSrc = papszEnv[iVar]) != NULL) + { + cwcDst += strlen(pszSrc) + 1; + iVar++; + } + cEnvVars = iVar; + + /* Allocate estimated WCHARs and convert the variables one by one, reallocating + the block as needed. */ + pwszzDst = (WCHAR *)xmalloc(cwcDst * sizeof(WCHAR)); + cwcDst--; /* save one wchar for the terminating empty string. */ + offDst = 0; + for (iVar = 0; iVar < cEnvVars; iVar++) + { + size_t cwcLeft = cwcDst - offDst; + size_t const cbSrc = strlen(pszSrc = papszEnv[iVar]) + 1; + assert(cwcDst >= offDst); + + + cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, &pwszzDst[offDst], cwcLeft); + if (cwcRc > 0) + { /* likely */ } + else + { + dwErr = GetLastError(); + if (dwErr == ERROR_INSUFFICIENT_BUFFER) + { + /* Need more space. So, calc exacly how much and resize the block accordingly. */ + size_t cbSrc2 = cbSrc; + size_t iVar2 = iVar; + cwcLeft = 1; + for (;;) + { + size_t cwcRc2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, NULL, 0); + if (cwcRc2 > 0) + cwcLeft += cwcRc2; + else + cwcLeft += cbSrc * 4; + + /* advance */ + iVar2++; + if (iVar2 >= cEnvVars) + break; + pszSrc = papszEnv[iVar2]; + cbSrc2 = strlen(pszSrc) + 1; + } + pszSrc = papszEnv[iVar]; + + /* Grow the allocation and repeat the conversion. */ + if (offDst + cwcLeft > cwcDst + 1) + { + cwcDst = offDst + cwcLeft; + pwszzDst = (WCHAR *)xrealloc(pwszzDst, cwcDst * sizeof(WCHAR)); + cwcDst--; /* save one wchar for the terminating empty string. */ + cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, &pwszzDst[offDst], cwcLeft - 1); + if (cwcRc <= 0) + dwErr = GetLastError(); + } + } + if (cwcRc <= 0) + { + MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert environment string #%u (%s): %u\n"), + iVar, pszSrc, dwErr); + free(pwszzDst); + return dwErr; + } + } + + /* Look for the PATH. */ + if ( offPathValue == ~(size_t)0 + && IS_PATH_ENV_VAR(cwcRc, &pwszzDst[offDst]) ) + offPathValue = offDst + 4 + 1; + + /* Advance. */ + offDst += cwcRc; + } + pwszzDst[offDst++] = '\0'; + + if (offPathValue != ~(size_t)0) + *ppwszSearchPath = &pwszzDst[offPathValue]; + *ppwszEnv = pwszzDst; + return 0; + } + free(pwszzDst); + return dwErr; +} + +/** + * Childcare worker: handle regular process. + * + * @param pWorker The worker. + * @param pChild The kSubmit child. + */ +static void mkWinChildcareWorkerThreadHandleProcess(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild) +{ + WCHAR *pwszSearchPath = NULL; + WCHAR *pwszzEnvironment = NULL; + WCHAR *pwszCommandLine = NULL; + WCHAR *pwszImageName = NULL; + BOOL fNeedShell = FALSE; + BOOL fProbableClExe = FALSE; + int rc; + + /* + * First we convert the environment so we get the PATH we need to + * search for the executable. + */ + rc = mkWinChildcareWorkerConvertEnvironment(pWorker, pChild->u.Process.papszEnv ? pChild->u.Process.papszEnv : environ, + pChild->u.Process.cbEnvStrings, + &pwszzEnvironment, &pwszSearchPath); + /* + * Find the executable and maybe checking if it's a shell script, then + * convert it to a command line. + */ + if (rc == 0) + rc = mkWinChildcareWorkerFindImage(pWorker, pChild->u.Process.papszArgs[0], pwszSearchPath, pwszzEnvironment, + pChild->u.Process.pszShell, &pwszImageName, &fNeedShell, &pChild->fProbableClExe); + if (rc == 0) + { + if (!fNeedShell) + rc = mkWinChildcareWorkerConvertCommandline(pWorker, pChild->u.Process.papszArgs, 0 /*fFlags*/, &pwszCommandLine); + else + rc = mkWinChildcareWorkerConvertCommandlineWithShell(pWorker, pwszImageName, pChild->u.Process.papszArgs, + &pwszCommandLine); + + /* + * Create the child process. + */ + if (rc == 0) + { + BOOL afReplace[3] = { FALSE, pChild->u.Process.hStdOut != INVALID_HANDLE_VALUE, pChild->u.Process.hStdErr != INVALID_HANDLE_VALUE }; + HANDLE ahChild[3] = { INVALID_HANDLE_VALUE, pChild->u.Process.hStdOut, pChild->u.Process.hStdErr }; + rc = mkWinChildcareWorkerCreateProcess(pWorker, pwszImageName, pwszCommandLine, pwszzEnvironment, + NULL /*pwszCwd*/, afReplace, ahChild, pChild->u.Process.fCatchOutput, + &pChild->u.Process.hProcess); + mkWinChildcareWorkerCloseStandardHandles(pChild); + if (rc == 0) + { + /* + * Wait for the child to complete. + */ + mkWinChildcareWorkerWaitForProcess(pWorker, pChild, pChild->u.Process.hProcess, pwszImageName, + pChild->u.Process.fCatchOutput); + } + else + pChild->iExitCode = rc; + } + else + pChild->iExitCode = rc; + } + else + pChild->iExitCode = rc; + free(pwszCommandLine); + free(pwszImageName); + free(pwszzEnvironment); + + /* In case we failed, we must make sure the child end of pipes + used by $(shell no_such_command.exe) are closed, otherwise + the main thread will be stuck reading the parent end. */ + mkWinChildcareWorkerCloseStandardHandles(pChild); +} + +#ifdef KMK + +/** + * Childcare worker: handle builtin command. + * + * @param pWorker The worker. + * @param pChild The kSubmit child. + */ +static void mkWinChildcareWorkerThreadHandleBuiltIn(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild) +{ + PCKMKBUILTINENTRY pBuiltIn = pChild->u.BuiltIn.pBuiltIn; + KMKBUILTINCTX Ctx = + { + pBuiltIn->uName.s.sz, + pChild->pMkChild ? &pChild->pMkChild->output : NULL, + pWorker, + }; + if (pBuiltIn->uFnSignature == FN_SIG_MAIN) + pChild->iExitCode = pBuiltIn->u.pfnMain(pChild->u.BuiltIn.cArgs, pChild->u.BuiltIn.papszArgs, + pChild->u.BuiltIn.papszEnv, &Ctx); + else if (pBuiltIn->uFnSignature == FN_SIG_MAIN_SPAWNS) + pChild->iExitCode = pBuiltIn->u.pfnMainSpawns(pChild->u.BuiltIn.cArgs, pChild->u.BuiltIn.papszArgs, + pChild->u.BuiltIn.papszEnv, &Ctx, pChild->pMkChild, NULL /*pPid*/); + else + { + assert(0); + pChild->iExitCode = 98; + } +} + +/** + * Childcare worker: handle append write-out. + * + * @param pWorker The worker. + * @param pChild The kSubmit child. + */ +static void mkWinChildcareWorkerThreadHandleAppend(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild) +{ + int fd = open(pChild->u.Append.pszFilename, + pChild->u.Append.fTruncate + ? O_WRONLY | O_TRUNC | O_CREAT | _O_NOINHERIT | _O_BINARY + : O_WRONLY | O_APPEND | O_CREAT | _O_NOINHERIT | _O_BINARY, + 0666); + if (fd >= 0) + { + ssize_t cbWritten = write(fd, pChild->u.Append.pszAppend, pChild->u.Append.cbAppend); + if (cbWritten == (ssize_t)pChild->u.Append.cbAppend) + { + if (close(fd) >= 0) + { + pChild->iExitCode = 0; + return; + } + MkWinChildError(pWorker, 1, "kmk_builtin_append: close failed on '%s': %u (%s)\n", + pChild->u.Append.pszFilename, errno, strerror(errno)); + } + else + MkWinChildError(pWorker, 1, "kmk_builtin_append: error writing %lu bytes to on '%s': %u (%s)\n", + pChild->u.Append.cbAppend, pChild->u.Append.pszFilename, errno, strerror(errno)); + close(fd); + } + else + MkWinChildError(pWorker, 1, "kmk_builtin_append: error opening '%s': %u (%s)\n", + pChild->u.Append.pszFilename, errno, strerror(errno)); + pChild->iExitCode = 1; +} + +/** + * Childcare worker: handle kSubmit job. + * + * @param pWorker The worker. + * @param pChild The kSubmit child. + */ +static void mkWinChildcareWorkerThreadHandleSubmit(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild) +{ + void *pvSubmitWorker = pChild->u.Submit.pvSubmitWorker; + + /* + * Prep the wait handles. + */ + HANDLE ahHandles[3] = { pChild->u.Submit.hEvent, NULL, NULL }; + DWORD cHandles = 1; + if (pChild->u.Submit.pStdOut) + { + assert(pChild->u.Submit.pStdErr); + pChild->u.Submit.pStdOut->fHaveWrittenOut = FALSE; + ahHandles[cHandles++] = pChild->u.Submit.pStdOut->hEvent; + pChild->u.Submit.pStdErr->fHaveWrittenOut = FALSE; + ahHandles[cHandles++] = pChild->u.Submit.pStdErr->hEvent; + } + + /* + * Wait loop. + */ + for (;;) + { + int iExitCode = -42; + int iSignal = -1; + DWORD dwStatus; + if (cHandles == 1) + dwStatus = WaitForSingleObject(ahHandles[0], INFINITE); + else + { + dwStatus = WaitForMultipleObjects(cHandles, ahHandles, FALSE /*fWaitAll*/, INFINITE); + assert(dwStatus != WAIT_FAILED); + if (dwStatus == WAIT_OBJECT_0 + 1) + mkWinChildcareWorkerCatchOutput(pChild, pChild->u.Submit.pStdOut, FALSE /*fDraining*/); + else if (dwStatus == WAIT_OBJECT_0 + 2) + mkWinChildcareWorkerCatchOutput(pChild, pChild->u.Submit.pStdErr, FALSE /*fDraining*/); + } + if (kSubmitSubProcGetResult((intptr_t)pvSubmitWorker, dwStatus == WAIT_OBJECT_0 /*fBlock*/, &iExitCode, &iSignal) == 0) + { + if (pChild->u.Submit.pStdOut) + MkWinChildcareWorkerDrainPipes(pChild, pChild->u.Submit.pStdOut, pChild->u.Submit.pStdErr); + + pChild->iExitCode = iExitCode; + pChild->iSignal = iSignal; + /* Cleanup must be done on the main thread. */ + return; + } + + if (pChild->iSignal != 0) + kSubmitSubProcKill((intptr_t)pvSubmitWorker, pChild->iSignal); + } +} + +/** + * Childcare worker: handle kmk_redirect process. + * + * @param pWorker The worker. + * @param pChild The redirect child. + */ +static void mkWinChildcareWorkerThreadHandleRedirect(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild) +{ + mkWinChildcareWorkerWaitForProcess(pWorker, pChild, pChild->u.Redirect.hProcess, L"kmk_redirect", FALSE /*fCatchOutput*/); +} + +#endif /* KMK */ + +/** + * Childcare worker thread. + * + * @returns 0 + * @param pvUser The worker instance. + */ +static unsigned int __stdcall mkWinChildcareWorkerThread(void *pvUser) +{ + PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)pvUser; + assert(pWorker->uMagic == WINCHILDCAREWORKER_MAGIC); + +#ifdef MKWINCHILD_DO_SET_PROCESSOR_GROUP + /* + * Adjust process group if necessary. + * + * Note! It seems that setting the mask to zero means that we select all + * active processors. Couldn't find any alternative API for getting + * the correct active processor mask. + */ + if (g_cProcessorGroups > 1) + { + GROUP_AFFINITY Affinity = { 0 /* == all active apparently */, pWorker->iProcessorGroup, { 0, 0, 0 } }; + BOOL fRet = g_pfnSetThreadGroupAffinity(GetCurrentThread(), &Affinity, NULL); + assert(fRet); (void)fRet; +# ifndef NDEBUG + { + GROUP_AFFINITY ActualAffinity = { 0xbeefbeefU, 0xbeef, { 0xbeef, 0xbeef, 0xbeef } }; + fRet = GetThreadGroupAffinity(GetCurrentThread(), &ActualAffinity); + assert(fRet); (void)fRet; + assert(ActualAffinity.Group == pWorker->iProcessorGroup); + } +# endif + } +#endif + + /* + * Work loop. + */ + while (!g_fShutdown) + { + /* + * Try go idle. + */ + PWINCHILD pChild = pWorker->pTailTodoChildren; + if (!pChild) + { + _InterlockedExchange(&pWorker->fIdle, TRUE); + pChild = pWorker->pTailTodoChildren; + if (!pChild) + { + DWORD dwStatus; + + _InterlockedIncrement((long *)&g_cIdleChildcareWorkers); + _InterlockedExchange((long *)&g_idxLastChildcareWorker, pWorker->idxWorker); + dwStatus = WaitForSingleObject(pWorker->hEvtIdle, INFINITE); + _InterlockedExchange(&pWorker->fIdle, FALSE); + _InterlockedDecrement((long *)&g_cIdleChildcareWorkers); + + assert(dwStatus != WAIT_FAILED); + if (dwStatus == WAIT_FAILED) + Sleep(20); + + pChild = pWorker->pTailTodoChildren; + } + else + _InterlockedExchange(&pWorker->fIdle, FALSE); + } + if (pChild) + { + /* + * We got work to do. First job is to deque the job. + */ + pChild = mkWinChildDequeFromLifo(&pWorker->pTailTodoChildren, pChild); + assert(pChild); + if (pChild) + { + PWINCHILD pTailExpect; + + pChild->pWorker = pWorker; + pWorker->pCurChild = pChild; + switch (pChild->enmType) + { + case WINCHILDTYPE_PROCESS: + mkWinChildcareWorkerThreadHandleProcess(pWorker, pChild); + break; +#ifdef KMK + case WINCHILDTYPE_BUILT_IN: + mkWinChildcareWorkerThreadHandleBuiltIn(pWorker, pChild); + break; + case WINCHILDTYPE_APPEND: + mkWinChildcareWorkerThreadHandleAppend(pWorker, pChild); + break; + case WINCHILDTYPE_SUBMIT: + mkWinChildcareWorkerThreadHandleSubmit(pWorker, pChild); + break; + case WINCHILDTYPE_REDIRECT: + mkWinChildcareWorkerThreadHandleRedirect(pWorker, pChild); + break; +#endif + default: + assert(0); + } + pWorker->pCurChild = NULL; + pChild->pWorker = NULL; + + /* + * Move the child to the completed list. + */ + pTailExpect = NULL; + for (;;) + { + PWINCHILD pTailActual; + pChild->pNext = pTailExpect; + pTailActual = _InterlockedCompareExchangePointer(&g_pTailCompletedChildren, pChild, pTailExpect); + if (pTailActual != pTailExpect) + pTailExpect = pTailActual; + else + { + _InterlockedDecrement(&g_cPendingChildren); + if (pTailExpect) + break; + if (SetEvent(g_hEvtWaitChildren)) + break; + MkWinChildError(pWorker, 1, "SetEvent(g_hEvtWaitChildren=%p) failed: %u\n", + g_hEvtWaitChildren, GetLastError()); + break; + } + } + } + } + } + + _endthreadex(0); + return 0; +} + +/** + * Creates a pipe for catching child output. + * + * This is a custom CreatePipe implementation that allows for overlapped I/O on + * our end of the pipe. Silly that they don't offer an API that does this. + * + * @returns The pipe that was created. NULL on failure. + * @param pPipe The structure for the pipe. + * @param iWhich Which standard descriptor this is a pipe for. + * @param idxWorker The worker index. + */ +PWINCCWPIPE MkWinChildcareCreateWorkerPipe(unsigned iWhich, unsigned int idxWorker) +{ + /* + * We try generate a reasonably unique name from the get go, so this retry + * loop shouldn't really ever be needed. But you never know. + */ + static unsigned s_iSeqNo = 0; + DWORD const cMaxInstances = 1; + DWORD const cbPipe = 4096; + DWORD const cMsTimeout = 0; + unsigned cTries = 256; + while (cTries-- > 0) + { + /* Create the pipe (our end). */ + HANDLE hPipeRead; + DWORD fOpenMode = PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE; + DWORD fPipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS; + WCHAR wszName[MAX_PATH]; + s_iSeqNo++; + _snwprintf(wszName, MAX_PATH, L"\\\\.\\pipe\\kmk-winchildren-%u-%u-%u-%s-%u-%u", + GetCurrentProcessId(), GetCurrentThreadId(), idxWorker, iWhich == 1 ? L"out" : L"err", s_iSeqNo, GetTickCount()); + hPipeRead = CreateNamedPipeW(wszName, fOpenMode, fPipeMode, cMaxInstances, cbPipe, cbPipe, cMsTimeout, NULL /*pSecAttr*/); + if (hPipeRead == INVALID_HANDLE_VALUE && GetLastError() == ERROR_INVALID_PARAMETER) + { + fOpenMode &= ~FILE_FLAG_FIRST_PIPE_INSTANCE; + fPipeMode &= ~PIPE_REJECT_REMOTE_CLIENTS; + hPipeRead = CreateNamedPipeW(wszName, fOpenMode, fPipeMode, cMaxInstances, cbPipe, cbPipe, cMsTimeout, NULL /*pSecAttr*/); + } + if (hPipeRead != INVALID_HANDLE_VALUE) + { + /* Connect the other end. */ + HANDLE hPipeWrite = CreateFileW(wszName, GENERIC_WRITE | FILE_READ_ATTRIBUTES, 0 /*fShareMode*/, NULL /*pSecAttr*/, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL /*hTemplateFile*/); + if (hPipeWrite != INVALID_HANDLE_VALUE) + { + /* + * Create the event object and we're done. + * + * It starts in signalled stated so we don't need special code + * for handing when we start waiting. + */ + HANDLE hEvent = CreateEventW(NULL /*pSecAttr*/, TRUE /*fManualReset*/, TRUE /*fInitialState*/, NULL /*pwszName*/); + if (hEvent != NULL) + { + PWINCCWPIPE pPipe = (PWINCCWPIPE)xcalloc(sizeof(*pPipe)); + pPipe->hPipeMine = hPipeRead; + pPipe->hPipeChild = hPipeWrite; + pPipe->hEvent = hEvent; + pPipe->iWhich = iWhich; + pPipe->fReadPending = FALSE; + pPipe->cbBuffer = cbPipe; + pPipe->pbBuffer = xcalloc(cbPipe); + return pPipe; + } + + CloseHandle(hPipeWrite); + CloseHandle(hPipeRead); + return NULL; + } + CloseHandle(hPipeRead); + } + } + return NULL; +} + +/** + * Destroys a childcare worker pipe. + * + * @param pPipe The pipe. + */ +void MkWinChildcareDeleteWorkerPipe(PWINCCWPIPE pPipe) +{ + if (pPipe->hPipeChild != NULL) + { + CloseHandle(pPipe->hPipeChild); + pPipe->hPipeChild = NULL; + } + + if (pPipe->hPipeMine != NULL) + { + if (pPipe->fReadPending) + if (!CancelIo(pPipe->hPipeMine)) + WaitForSingleObject(pPipe->hEvent, INFINITE); + CloseHandle(pPipe->hPipeMine); + pPipe->hPipeMine = NULL; + } + + if (pPipe->hEvent != NULL) + { + CloseHandle(pPipe->hEvent); + pPipe->hEvent = NULL; + } + + if (pPipe->pbBuffer) + { + free(pPipe->pbBuffer); + pPipe->pbBuffer = NULL; + } +} + +/** + * Initializes the processor group allocator. + * + * @param pState The allocator to initialize. + */ +void MkWinChildInitCpuGroupAllocator(PMKWINCHILDCPUGROUPALLOCSTATE pState) +{ + /* We shift the starting group with the make nesting level as part of + our very simple distribution strategy. */ + pState->idxGroup = makelevel; + pState->idxProcessorInGroup = 0; +} + +/** + * Allocate CPU group for the next child process. + * + * @returns CPU group. + * @param pState The allocator state. Must be initialized by + * MkWinChildInitCpuGroupAllocator(). + */ +unsigned int MkWinChildAllocateCpuGroup(PMKWINCHILDCPUGROUPALLOCSTATE pState) +{ + unsigned int iGroup = 0; + if (g_cProcessorGroups > 1) + { + unsigned int cMaxInGroup; + unsigned int cInGroup; + + iGroup = pState->idxGroup % g_cProcessorGroups; + + /* Advance. We employ a very simple strategy that does 50% in + each group for each group cycle. Odd processor counts are + caught in odd group cycles. The init function selects the + starting group based on make nesting level to avoid stressing + out the first group. */ + cInGroup = ++pState->idxProcessorInGroup; + cMaxInGroup = g_pacProcessorsInGroup[iGroup]; + if ( !(cMaxInGroup & 1) + || !((pState->idxGroup / g_cProcessorGroups) & 1)) + cMaxInGroup /= 2; + else + cMaxInGroup = cMaxInGroup / 2 + 1; + if (cInGroup >= cMaxInGroup) + { + pState->idxProcessorInGroup = 0; + pState->idxGroup++; + } + } + return iGroup; +} + +/** + * Creates another childcare worker. + * + * @returns The new worker, if we succeeded. + */ +static PWINCHILDCAREWORKER mkWinChildcareCreateWorker(void) +{ + PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)xcalloc(sizeof(*pWorker)); + pWorker->uMagic = WINCHILDCAREWORKER_MAGIC; + pWorker->idxWorker = g_cChildCareworkers; + pWorker->hEvtIdle = CreateEventW(NULL, FALSE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pwszName*/); + if (pWorker->hEvtIdle) + { + pWorker->pStdOut = MkWinChildcareCreateWorkerPipe(1, pWorker->idxWorker); + if (pWorker->pStdOut) + { + pWorker->pStdErr = MkWinChildcareCreateWorkerPipe(2, pWorker->idxWorker); + if (pWorker->pStdErr) + { + /* Before we start the thread, assign it to a processor group. */ + pWorker->iProcessorGroup = MkWinChildAllocateCpuGroup(&g_ProcessorGroupAllocator); + + /* Try start the thread. */ + pWorker->hThread = (HANDLE)_beginthreadex(NULL, 0 /*cbStack*/, mkWinChildcareWorkerThread, pWorker, + 0, &pWorker->tid); + if (pWorker->hThread != NULL) + { + pWorker->idxWorker = g_cChildCareworkers++; /* paranoia */ + g_papChildCareworkers[pWorker->idxWorker] = pWorker; + return pWorker; + } + + /* Bail out! */ + ONS (error, NILF, "_beginthreadex failed: %u (%s)\n", errno, strerror(errno)); + MkWinChildcareDeleteWorkerPipe(pWorker->pStdErr); + } + else + ON (error, NILF, "Failed to create stderr pipe: %u\n", GetLastError()); + MkWinChildcareDeleteWorkerPipe(pWorker->pStdOut); + } + else + ON (error, NILF, "Failed to create stdout pipe: %u\n", GetLastError()); + CloseHandle(pWorker->hEvtIdle); + } + else + ON (error, NILF, "CreateEvent failed: %u\n", GetLastError()); + pWorker->uMagic = ~WINCHILDCAREWORKER_MAGIC; + free(pWorker); + return NULL; +} + +/** + * Helper for copying argument and environment vectors. + * + * @returns Single alloc block copy. + * @param papszSrc The source vector. + * @param pcbStrings Where to return the size of the strings & terminator. + */ +static char **mkWinChildCopyStringArray(char **papszSrc, size_t *pcbStrings) +{ + const char *psz; + char **papszDstArray; + char *pszDstStr; + size_t i; + + /* Calc sizes first. */ + size_t cbStrings = 1; /* (one extra for terminator string) */ + size_t cStrings = 0; + while ((psz = papszSrc[cStrings]) != NULL) + { + cbStrings += strlen(psz) + 1; + cStrings++; + } + *pcbStrings = cbStrings; + + /* Allocate destination. */ + papszDstArray = (char **)xmalloc(cbStrings + (cStrings + 1) * sizeof(papszDstArray[0])); + pszDstStr = (char *)&papszDstArray[cStrings + 1]; + + /* Copy it. */ + for (i = 0; i < cStrings; i++) + { + const char *pszSource = papszSrc[i]; + size_t cchString = strlen(pszSource); + papszDstArray[i] = pszDstStr; + memcpy(pszDstStr, pszSource, cchString); + pszDstStr += cchString; + *pszDstStr++ = '\0'; + } + *pszDstStr = '\0'; + assert(&pszDstStr[1] - papszDstArray[0] == cbStrings); + papszDstArray[i] = NULL; + return papszDstArray; +} + +/** + * Allocate and init a WINCHILD. + * + * @returns The new windows child structure. + * @param enmType The child type. + */ +static PWINCHILD mkWinChildNew(WINCHILDTYPE enmType) +{ + PWINCHILD pChild = xcalloc(sizeof(*pChild)); + pChild->enmType = enmType; + pChild->fCoreDumped = 0; + pChild->iSignal = 0; + pChild->iExitCode = 222222; + pChild->uMagic = WINCHILD_MAGIC; + pChild->pid = (intptr_t)pChild; + return pChild; +} + +/** + * Destructor for WINCHILD. + * + * @param pChild The child structure to destroy. + */ +static void mkWinChildDelete(PWINCHILD pChild) +{ + assert(pChild->uMagic == WINCHILD_MAGIC); + pChild->uMagic = ~WINCHILD_MAGIC; + + switch (pChild->enmType) + { + case WINCHILDTYPE_PROCESS: + { + if (pChild->u.Process.papszArgs) + { + free(pChild->u.Process.papszArgs); + pChild->u.Process.papszArgs = NULL; + } + if (pChild->u.Process.cbEnvStrings && pChild->u.Process.papszEnv) + { + free(pChild->u.Process.papszEnv); + pChild->u.Process.papszEnv = NULL; + } + if (pChild->u.Process.pszShell) + { + free(pChild->u.Process.pszShell); + pChild->u.Process.pszShell = NULL; + } + if (pChild->u.Process.hProcess) + { + CloseHandle(pChild->u.Process.hProcess); + pChild->u.Process.hProcess = NULL; + } + mkWinChildcareWorkerCloseStandardHandles(pChild); + break; + } + +#ifdef KMK + case WINCHILDTYPE_BUILT_IN: + if (pChild->u.BuiltIn.papszArgs) + { + free(pChild->u.BuiltIn.papszArgs); + pChild->u.BuiltIn.papszArgs = NULL; + } + if (pChild->u.BuiltIn.papszEnv) + { + free(pChild->u.BuiltIn.papszEnv); + pChild->u.BuiltIn.papszEnv = NULL; + } + break; + + case WINCHILDTYPE_APPEND: + if (pChild->u.Append.pszFilename) + { + free(pChild->u.Append.pszFilename); + pChild->u.Append.pszFilename = NULL; + } + if (pChild->u.Append.pszAppend) + { + free(pChild->u.Append.pszAppend); + pChild->u.Append.pszAppend = NULL; + } + break; + + case WINCHILDTYPE_SUBMIT: + if (pChild->u.Submit.pvSubmitWorker) + { + kSubmitSubProcCleanup((intptr_t)pChild->u.Submit.pvSubmitWorker); + pChild->u.Submit.pvSubmitWorker = NULL; + } + break; + + case WINCHILDTYPE_REDIRECT: + if (pChild->u.Redirect.hProcess) + { + CloseHandle(pChild->u.Redirect.hProcess); + pChild->u.Redirect.hProcess = NULL; + } + break; +#endif /* KMK */ + + default: + assert(0); + } + + free(pChild); +} + +/** + * Queues the child with a worker, creating new workers if necessary. + * + * @returns 0 on success, windows error code on failure (child destroyed). + * @param pChild The child. + * @param pPid Where to return the PID (optional). + */ +static int mkWinChildPushToCareWorker(PWINCHILD pChild, pid_t *pPid) +{ + PWINCHILDCAREWORKER pWorker = NULL; + PWINCHILD pOldChild; + PWINCHILD pCurChild; + + /* + * There are usually idle workers around, except for at the start. + */ + if (g_cIdleChildcareWorkers > 0) + { + /* + * Try the idle hint first and move forward from it. + */ + unsigned int const cWorkers = g_cChildCareworkers; + unsigned int iHint = g_idxLastChildcareWorker; + unsigned int i; + for (i = iHint; i < cWorkers; i++) + { + PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i]; + if (pPossibleWorker->fIdle) + { + pWorker = pPossibleWorker; + break; + } + } + if (!pWorker) + { + /* Scan from the start. */ + if (iHint > cWorkers) + iHint = cWorkers; + for (i = 0; i < iHint; i++) + { + PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i]; + if (pPossibleWorker->fIdle) + { + pWorker = pPossibleWorker; + break; + } + } + } + } + if (!pWorker) + { + /* + * Try create more workers if we haven't reached the max yet. + */ + if (g_cChildCareworkers < g_cChildCareworkersMax) + pWorker = mkWinChildcareCreateWorker(); + + /* + * Queue it with an existing worker. Look for one without anthing extra scheduled. + */ + if (!pWorker) + { + unsigned int i = g_cChildCareworkers; + if (i == 0) + fatal(NILF, 0, _("Failed to create worker threads for managing child processes!\n")); + pWorker = g_papChildCareworkers[--i]; + if (pWorker->pTailTodoChildren) + while (i-- > 0) + { + PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i]; + if (!pPossibleWorker->pTailTodoChildren) + { + pWorker = pPossibleWorker; + break; + } + } + } + } + + /* + * Do the queueing. + */ + pOldChild = NULL; + for (;;) + { + pChild->pNext = pOldChild; + pCurChild = _InterlockedCompareExchangePointer((void **)&pWorker->pTailTodoChildren, pChild, pOldChild); + if (pCurChild == pOldChild) + { + DWORD volatile dwErr; + _InterlockedIncrement(&g_cPendingChildren); + if ( !pWorker->fIdle + || SetEvent(pWorker->hEvtIdle)) + { + *pPid = pChild->pid; + return 0; + } + + _InterlockedDecrement(&g_cPendingChildren); + dwErr = GetLastError(); + assert(0); + mkWinChildDelete(pChild); + return dwErr ? dwErr : -20; + } + pOldChild = pCurChild; + } +} + +/** + * Creates a regular child process (job.c). + * + * Will copy the information and push it to a childcare thread that does the + * actual process creation. + * + * @returns 0 on success, windows status code on failure. + * @param papszArgs The arguments. + * @param papszEnv The environment (optional). + * @param pszShell The SHELL variable value (optional). + * @param pMkChild The make child structure (optional). + * @param pPid Where to return the pid. + */ +int MkWinChildCreate(char **papszArgs, char **papszEnv, const char *pszShell, struct child *pMkChild, pid_t *pPid) +{ + PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_PROCESS); + pChild->pMkChild = pMkChild; + + pChild->u.Process.papszArgs = mkWinChildCopyStringArray(papszArgs, &pChild->u.Process.cbArgsStrings); + if ( !papszEnv + || !pMkChild + || pMkChild->environment == papszEnv) + { + pChild->u.Process.cbEnvStrings = 0; + pChild->u.Process.papszEnv = papszEnv; + } + else + pChild->u.Process.papszEnv = mkWinChildCopyStringArray(papszEnv, &pChild->u.Process.cbEnvStrings); + if (pszShell) + pChild->u.Process.pszShell = xstrdup(pszShell); + pChild->u.Process.hStdOut = INVALID_HANDLE_VALUE; + pChild->u.Process.hStdErr = INVALID_HANDLE_VALUE; + + /* We always catch the output in order to prevent character soups courtesy + of the microsoft CRT and/or linkers writing character by character to the + console. Always try write whole lines, even when --output-sync is none. */ + pChild->u.Process.fCatchOutput = TRUE; + + return mkWinChildPushToCareWorker(pChild, pPid); +} + +/** + * Creates a chile process with a pipe hooked up to stdout. + * + * @returns 0 on success, non-zero on failure. + * @param papszArgs The argument vector. + * @param papszEnv The environment vector (optional). + * @param fdErr File descriptor to hook up to stderr. + * @param pPid Where to return the pid. + * @param pfdReadPipe Where to return the read end of the pipe. + */ +int MkWinChildCreateWithStdOutPipe(char **papszArgs, char **papszEnv, int fdErr, pid_t *pPid, int *pfdReadPipe) +{ + /* + * Create the pipe. + */ + HANDLE hReadPipe; + HANDLE hWritePipe; + if (CreatePipe(&hReadPipe, &hWritePipe, NULL, 0 /* default size */)) + { + //if (SetHandleInformation(hWritePipe, HANDLE_FLAG_INHERIT /* clear */ , HANDLE_FLAG_INHERIT /*set*/)) + { + int fdReadPipe = _open_osfhandle((intptr_t)hReadPipe, O_RDONLY); + if (fdReadPipe >= 0) + { + PWINCHILD pChild; + int rc; + + /* + * Get a handle for fdErr. Ignore failure. + */ + HANDLE hStdErr = INVALID_HANDLE_VALUE; + if (fdErr >= 0) + { + HANDLE hNative = (HANDLE)_get_osfhandle(fdErr); + if (!DuplicateHandle(GetCurrentProcess(), hNative, GetCurrentProcess(), + &hStdErr, 0 /*DesiredAccess*/, TRUE /*fInherit*/, DUPLICATE_SAME_ACCESS)) + { + ONN(error, NILF, _("DuplicateHandle failed on stderr descriptor (%u): %u\n"), fdErr, GetLastError()); + hStdErr = INVALID_HANDLE_VALUE; + } + } + + /* + * Push it off to the worker thread. + */ + pChild = mkWinChildNew(WINCHILDTYPE_PROCESS); + pChild->u.Process.papszArgs = mkWinChildCopyStringArray(papszArgs, &pChild->u.Process.cbArgsStrings); + pChild->u.Process.papszEnv = mkWinChildCopyStringArray(papszEnv ? papszEnv : environ, + &pChild->u.Process.cbEnvStrings); + //if (pszShell) + // pChild->u.Process.pszShell = xstrdup(pszShell); + pChild->u.Process.hStdOut = hWritePipe; + pChild->u.Process.hStdErr = hStdErr; + pChild->u.Process.fCloseStdErr = TRUE; + pChild->u.Process.fCloseStdOut = TRUE; + + rc = mkWinChildPushToCareWorker(pChild, pPid); + if (rc == 0) + *pfdReadPipe = fdReadPipe; + else + { + ON(error, NILF, _("mkWinChildPushToCareWorker failed on pipe: %d\n"), rc); + close(fdReadPipe); + *pfdReadPipe = -1; + *pPid = -1; + } + return rc; + } + + ON(error, NILF, _("_open_osfhandle failed on pipe: %u\n"), errno); + } + //else + // ON(error, NILF, _("SetHandleInformation failed on pipe: %u\n"), GetLastError()); + if (hReadPipe != INVALID_HANDLE_VALUE) + CloseHandle(hReadPipe); + CloseHandle(hWritePipe); + } + else + ON(error, NILF, _("CreatePipe failed: %u\n"), GetLastError()); + *pfdReadPipe = -1; + *pPid = -1; + return -1; +} + +#ifdef KMK + +/** + * Interface used by kmkbuiltin.c for executing builtin commands on threads. + * + * @returns 0 on success, windows status code on failure. + * @param pBuiltIn The kmk built-in command entry. + * @param cArgs The number of arguments in papszArgs. + * @param papszArgs The argument vector. + * @param papszEnv The environment vector, optional. + * @param pMkChild The make child structure. + * @param pPid Where to return the pid. + */ +int MkWinChildCreateBuiltIn(PCKMKBUILTINENTRY pBuiltIn, int cArgs, char **papszArgs, char **papszEnv, + struct child *pMkChild, pid_t *pPid) +{ + size_t cbIgnored; + PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_BUILT_IN); + pChild->pMkChild = pMkChild; + pChild->u.BuiltIn.pBuiltIn = pBuiltIn; + pChild->u.BuiltIn.cArgs = cArgs; + pChild->u.BuiltIn.papszArgs = mkWinChildCopyStringArray(papszArgs, &cbIgnored); + pChild->u.BuiltIn.papszEnv = papszEnv ? mkWinChildCopyStringArray(papszEnv, &cbIgnored) : NULL; + return mkWinChildPushToCareWorker(pChild, pPid); +} + +/** + * Interface used by append.c for do the slow file system part. + * + * This will append the given buffer to the specified file and free the buffer. + * + * @returns 0 on success, windows status code on failure. + * + * @param pszFilename The name of the file to append to. + * @param ppszAppend What to append. The pointer pointed to is set to + * NULL once we've taken ownership of the buffer and + * promise to free it. + * @param cbAppend How much to append. + * @param fTruncate Whether to truncate the file before appending to it. + * @param pMkChild The make child structure. + * @param pPid Where to return the pid. + */ +int MkWinChildCreateAppend(const char *pszFilename, char **ppszAppend, size_t cbAppend, int fTruncate, + struct child *pMkChild, pid_t *pPid) +{ + size_t cbFilename = strlen(pszFilename) + 1; + PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_APPEND); + pChild->pMkChild = pMkChild; + pChild->u.Append.fTruncate = fTruncate; + pChild->u.Append.pszAppend = *ppszAppend; + pChild->u.Append.cbAppend = cbAppend; + pChild->u.Append.pszFilename = (char *)memcpy(xmalloc(cbFilename), pszFilename, cbFilename); + *ppszAppend = NULL; + return mkWinChildPushToCareWorker(pChild, pPid); +} + +/** + * Interface used by kSubmit.c for registering stuff to wait on. + * + * @returns 0 on success, windows status code on failure. + * @param hEvent The event object handle to wait on. + * @param pvSubmitWorker The argument to pass back to kSubmit to clean up. + * @param pStdOut Standard output pipe for the worker. Optional. + * @param pStdErr Standard error pipe for the worker. Optional. + * @param pMkChild The make child structure. + * @param pPid Where to return the pid. + */ +int MkWinChildCreateSubmit(intptr_t hEvent, void *pvSubmitWorker, PWINCCWPIPE pStdOut, PWINCCWPIPE pStdErr, + struct child *pMkChild, pid_t *pPid) +{ + PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_SUBMIT); + pChild->pMkChild = pMkChild; + pChild->u.Submit.hEvent = (HANDLE)hEvent; + pChild->u.Submit.pvSubmitWorker = pvSubmitWorker; + pChild->u.Submit.pStdOut = pStdOut; + pChild->u.Submit.pStdErr = pStdErr; + return mkWinChildPushToCareWorker(pChild, pPid); +} + +/** + * Interface used by redirect.c for registering stuff to wait on. + * + * @returns 0 on success, windows status code on failure. + * @param hProcess The process object to wait on. + * @param pPid Where to return the pid. + */ +int MkWinChildCreateRedirect(intptr_t hProcess, pid_t *pPid) +{ + PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_REDIRECT); + pChild->u.Redirect.hProcess = (HANDLE)hProcess; + return mkWinChildPushToCareWorker(pChild, pPid); +} + + +/** + * New interface used by redirect.c for spawning and waitin on a child. + * + * This interface is only used when kmk_builtin_redirect is already running on + * a worker thread. + * + * @returns exit status. + * @param pvWorker The worker instance. + * @param pszExecutable The executable image to run. + * @param papszArgs Argument vector. + * @param fQuotedArgv Whether the argument vector is already quoted and + * just need some space to be turned into a command + * line. + * @param papszEnvVars Environment vector. + * @param pszCwd The working directory of the child. Optional. + * @param pafReplace Which standard handles to replace. Maybe modified! + * @param pahReplace The replacement handles. Maybe modified! + * + */ +int MkWinChildBuiltInExecChild(void *pvWorker, const char *pszExecutable, char **papszArgs, BOOL fQuotedArgv, + char **papszEnvVars, const char *pszCwd, BOOL pafReplace[3], HANDLE pahReplace[3]) +{ + PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)pvWorker; + WCHAR *pwszSearchPath = NULL; + WCHAR *pwszzEnvironment = NULL; + WCHAR *pwszCommandLine = NULL; + WCHAR *pwszImageName = NULL; + WCHAR *pwszCwd = NULL; + BOOL fNeedShell = FALSE; + PWINCHILD pChild; + int rc; + assert(pWorker->uMagic == WINCHILDCAREWORKER_MAGIC); + pChild = pWorker->pCurChild; + assert(pChild != NULL && pChild->uMagic == WINCHILD_MAGIC); + + /* + * Convert the CWD first since it's optional and we don't need to clean + * up anything here if it fails. + */ + if (pszCwd) + { + size_t cchCwd = strlen(pszCwd); + int cwcCwd = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszCwd, cchCwd + 1, NULL, 0); + pwszCwd = xmalloc((cwcCwd + 1) * sizeof(WCHAR)); /* (+1 in case cwcCwd is 0) */ + cwcCwd = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszCwd, cchCwd + 1, pwszCwd, cwcCwd + 1); + if (!cwcCwd) + { + rc = GetLastError(); + MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert CWD (%s): %u\n"), pszCwd, (unsigned)rc); + return rc; + } + } + + /* + * Before we search for the image, we convert the environment so we don't + * have to traverse it twice to find the PATH. + */ + rc = mkWinChildcareWorkerConvertEnvironment(pWorker, papszEnvVars ? papszEnvVars : environ, 0/*cbEnvStrings*/, + &pwszzEnvironment, &pwszSearchPath); + /* + * Find the executable and maybe checking if it's a shell script, then + * convert it to a command line. + */ + if (rc == 0) + rc = mkWinChildcareWorkerFindImage(pWorker, pszExecutable, pwszSearchPath, pwszzEnvironment, NULL /*pszShell*/, + &pwszImageName, &fNeedShell, &pChild->fProbableClExe); + if (rc == 0) + { + assert(!fNeedShell); + if (!fQuotedArgv) + rc = mkWinChildcareWorkerConvertCommandline(pWorker, papszArgs, 0 /*fFlags*/, &pwszCommandLine); + else + rc = mkWinChildcareWorkerConvertQuotedArgvToCommandline(pWorker, papszArgs, &pwszCommandLine); + + /* + * Create the child process. + */ + if (rc == 0) + { + HANDLE hProcess; + rc = mkWinChildcareWorkerCreateProcess(pWorker, pwszImageName, pwszCommandLine, pwszzEnvironment, + pwszCwd, pafReplace, pahReplace, TRUE /*fCatchOutput*/, &hProcess); + if (rc == 0) + { + /* + * Wait for the child to complete. + */ + rc = mkWinChildcareWorkerWaitForProcess(pWorker, pChild, hProcess, pwszImageName, TRUE /*fCatchOutput*/); + CloseHandle(hProcess); + } + } + } + + free(pwszCwd); + free(pwszCommandLine); + free(pwszImageName); + free(pwszzEnvironment); + + return rc; +} + +#endif /* CONFIG_NEW_WIN_CHILDREN */ + +/** + * Interface used to kill process when processing Ctrl-C and fatal errors. + * + * @returns 0 on success, -1 & errno on error. + * @param pid The process to kill (PWINCHILD). + * @param iSignal What to kill it with. + * @param pMkChild The make child structure for validation. + */ +int MkWinChildKill(pid_t pid, int iSignal, struct child *pMkChild) +{ + PWINCHILD pChild = (PWINCHILD)pid; + if (pChild) + { + assert(pChild->uMagic == WINCHILD_MAGIC); + if (pChild->uMagic == WINCHILD_MAGIC) + { + switch (pChild->enmType) + { + case WINCHILDTYPE_PROCESS: + assert(pChild->pMkChild == pMkChild); + TerminateProcess(pChild->u.Process.hProcess, DBG_TERMINATE_PROCESS); + pChild->iSignal = iSignal; + break; +#ifdef KMK + case WINCHILDTYPE_SUBMIT: + { + pChild->iSignal = iSignal; + SetEvent(pChild->u.Submit.hEvent); + break; + } + + case WINCHILDTYPE_REDIRECT: + TerminateProcess(pChild->u.Redirect.hProcess, DBG_TERMINATE_PROCESS); + pChild->iSignal = iSignal; + break; + + case WINCHILDTYPE_BUILT_IN: + break; + +#endif /* KMK */ + default: + assert(0); + } + } + } + return -1; +} + +/** + * Wait for a child process to complete + * + * @returns 0 on success, windows error code on failure. + * @param fBlock Whether to block. + * @param pPid Where to return the pid if a child process + * completed. This is set to zero if none. + * @param piExitCode Where to return the exit code. + * @param piSignal Where to return the exit signal number. + * @param pfCoreDumped Where to return the core dumped indicator. + * @param ppMkChild Where to return the associated struct child pointer. + */ +int MkWinChildWait(int fBlock, pid_t *pPid, int *piExitCode, int *piSignal, int *pfCoreDumped, struct child **ppMkChild) +{ + PWINCHILD pChild; + + *pPid = 0; + *piExitCode = -222222; + *pfCoreDumped = 0; + *ppMkChild = NULL; + + /* + * Wait if necessary. + */ + if (fBlock && !g_pTailCompletedChildren && g_cPendingChildren > 0) + { + DWORD dwStatus = WaitForSingleObject(g_hEvtWaitChildren, INFINITE); + if (dwStatus == WAIT_FAILED) + return (int)GetLastError(); + } + + /* + * Try unlink the last child in the LIFO. + */ + pChild = g_pTailCompletedChildren; + if (!pChild) + return 0; + pChild = mkWinChildDequeFromLifo(&g_pTailCompletedChildren, pChild); + assert(pChild); + + /* + * Set return values and ditch the child structure. + */ + *pPid = pChild->pid; + *piExitCode = pChild->iExitCode; + *pfCoreDumped = pChild->fCoreDumped; + *ppMkChild = pChild->pMkChild; + switch (pChild->enmType) + { + case WINCHILDTYPE_PROCESS: + break; +#ifdef KMK + case WINCHILDTYPE_BUILT_IN: + case WINCHILDTYPE_APPEND: + case WINCHILDTYPE_SUBMIT: + case WINCHILDTYPE_REDIRECT: + break; +#endif /* KMK */ + default: + assert(0); + } + mkWinChildDelete(pChild); + +#ifdef KMK + /* Flush the volatile directory cache. */ + dir_cache_invalid_after_job(); +#endif + return 0; +} + +/** + * Get the child completed event handle. + * + * Needed when w32os.c is waiting for a job token to become available, given + * that completed children is the typical source of these tokens (esp. for kmk). + * + * @returns Zero if no active children, event handle if waiting is required. + */ +intptr_t MkWinChildGetCompleteEventHandle(void) +{ + /* We don't return the handle if we've got completed children. This + is a safe guard against being called twice in a row without any + MkWinChildWait call inbetween. */ + if (!g_pTailCompletedChildren) + return (intptr_t)g_hEvtWaitChildren; + return 0; +} + +/** + * Emulate execv() for restarting kmk after one or more makefiles has been made. + * + * Does not return. + * + * @param papszArgs The arguments. + * @param papszEnv The environment. + */ +void MkWinChildReExecMake(char **papszArgs, char **papszEnv) +{ + PROCESS_INFORMATION ProcInfo; + STARTUPINFOW StartupInfo; + WCHAR *pwszCommandLine; + WCHAR *pwszzEnvironment; + WCHAR *pwszPathIgnored; + int rc; + + /* + * Get the executable name. + */ + WCHAR wszImageName[MKWINCHILD_MAX_PATH]; + DWORD cwcImageName = GetModuleFileNameW(GetModuleHandle(NULL), wszImageName, MKWINCHILD_MAX_PATH); + if (cwcImageName == 0) + ON(fatal, NILF, _("MkWinChildReExecMake: GetModuleFileName failed: %u\n"), GetLastError()); + + /* + * Create the command line and environment. + */ + rc = mkWinChildcareWorkerConvertCommandline(NULL, papszArgs, 0 /*fFlags*/, &pwszCommandLine); + if (rc != 0) + ON(fatal, NILF, _("MkWinChildReExecMake: mkWinChildcareWorkerConvertCommandline failed: %u\n"), rc); + + rc = mkWinChildcareWorkerConvertEnvironment(NULL, papszEnv ? papszEnv : environ, 0 /*cbEnvStrings*/, + &pwszzEnvironment, &pwszPathIgnored); + if (rc != 0) + ON(fatal, NILF, _("MkWinChildReExecMake: mkWinChildcareWorkerConvertEnvironment failed: %u\n"), rc); + +#ifdef KMK + /* + * Flush the file system cache to avoid messing up tools fetching + * going on in the "exec'ed" make by keeping directories open. + */ + dir_cache_invalid_all_and_close_dirs(1); +#endif + + /* + * Fill out the startup info and try create the process. + */ + memset(&ProcInfo, 0, sizeof(ProcInfo)); + memset(&StartupInfo, 0, sizeof(StartupInfo)); + StartupInfo.cb = sizeof(StartupInfo); + GetStartupInfoW(&StartupInfo); + if (!CreateProcessW(wszImageName, pwszCommandLine, NULL /*pProcSecAttr*/, NULL /*pThreadSecAttr*/, + TRUE /*fInheritHandles*/, CREATE_UNICODE_ENVIRONMENT, pwszzEnvironment, NULL /*pwsz*/, + &StartupInfo, &ProcInfo)) + ON(fatal, NILF, _("MkWinChildReExecMake: CreateProcessW failed: %u\n"), GetLastError()); + CloseHandle(ProcInfo.hThread); + + /* + * Wait for it to complete and forward the status code to our parent. + */ + for (;;) + { + DWORD dwExitCode = -2222; + DWORD dwStatus = WaitForSingleObject(ProcInfo.hProcess, INFINITE); + if ( dwStatus == WAIT_IO_COMPLETION + || dwStatus == WAIT_TIMEOUT /* whatever */) + continue; /* however unlikely, these aren't fatal. */ + + /* Get the status code and terminate. */ + if (dwStatus == WAIT_OBJECT_0) + { + if (!GetExitCodeProcess(ProcInfo.hProcess, &dwExitCode)) + { + ON(fatal, NILF, _("MkWinChildReExecMake: GetExitCodeProcess failed: %u\n"), GetLastError()); + dwExitCode = -2222; + } + } + else if (dwStatus) + dwExitCode = dwStatus; + + CloseHandle(ProcInfo.hProcess); + for (;;) + exit(dwExitCode); + } +} + +#ifdef WITH_RW_LOCK +/** Serialization with kmkbuiltin_redirect. */ +void MkWinChildExclusiveAcquire(void) +{ + AcquireSRWLockExclusive(&g_RWLock); +} + +/** Serialization with kmkbuiltin_redirect. */ +void MkWinChildExclusiveRelease(void) +{ + ReleaseSRWLockExclusive(&g_RWLock); +} +#endif /* WITH_RW_LOCK */ + +/** + * Implementation of the CLOSE_ON_EXEC macro. + * + * @returns errno value. + * @param fd The file descriptor to hide from children. + */ +int MkWinChildUnrelatedCloseOnExec(int fd) +{ + if (fd >= 0) + { + HANDLE hNative = (HANDLE)_get_osfhandle(fd); + if (hNative != INVALID_HANDLE_VALUE && hNative != NULL) + { + if (SetHandleInformation(hNative, HANDLE_FLAG_INHERIT /*clear*/ , 0 /*set*/)) + return 0; + } + return errno; + } + return EINVAL; +} + diff --git a/src/kmk/w32/winchildren.h b/src/kmk/w32/winchildren.h new file mode 100644 index 0000000..e70bd1e --- /dev/null +++ b/src/kmk/w32/winchildren.h @@ -0,0 +1,115 @@ +/* $Id: winchildren.h 3313 2020-03-16 02:31:38Z bird $ */ +/** @file + * Child process creation and management for kmk. + */ + +/* + * Copyright (c) 2018 knut st. osmundsen <bird-kBuild-spamx@anduin.net> + * + * This file is part of kBuild. + * + * kBuild is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * kBuild is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with kBuild. If not, see <http://www.gnu.org/licenses/> + * + */ + +#ifndef INCLUDED_WINCHILDREN_H +#define INCLUDED_WINCHILDREN_H + +/** Child processor group allocator state. */ +typedef struct MKWINCHILDCPUGROUPALLOCSTATE +{ + /** The group index for the worker allocator. + * This is ever increasing and must be modded by g_cProcessorGroups. */ + unsigned int idxGroup; + /** The processor in group index for the worker allocator. */ + unsigned int idxProcessorInGroup; +} MKWINCHILDCPUGROUPALLOCSTATE; +/** Pointer to a CPU group allocator state. */ +typedef MKWINCHILDCPUGROUPALLOCSTATE *PMKWINCHILDCPUGROUPALLOCSTATE; + +#ifdef DECLARE_HANDLE +/** + * A childcare worker pipe. + */ +typedef struct WINCCWPIPE +{ + /** My end of the pipe. */ + HANDLE hPipeMine; + /** The child end of the pipe. */ + HANDLE hPipeChild; + /** The event for asynchronous reading. */ + HANDLE hEvent; + /** Which pipe this is (1 == stdout, 2 == stderr). */ + unsigned char iWhich; + /** Set if we've got a read pending already. */ + BOOL fReadPending; + /** Indicator that we've written out something. This is cleared before + * we start catching output from a new child and use in the CL.exe + * supression heuristics. */ + BOOL fHaveWrittenOut; + /** Number of bytes at the start of the buffer that we've already + * written out. We try write out whole lines. */ + DWORD cbWritten; + /** The buffer offset of the read currently pending. */ + DWORD offPendingRead; + /** Read buffer size. */ + DWORD cbBuffer; + /** The read buffer allocation. */ + unsigned char *pbBuffer; + /** Overlapped I/O structure. */ + OVERLAPPED Overlapped; +} WINCCWPIPE; +#endif + +typedef struct WINCCWPIPE *PWINCCWPIPE; + +void MkWinChildInit(unsigned int cJobSlot); +void MkWinChildReExecMake(char **papszArgs, char **papszEnv); +intptr_t MkWinChildGetCompleteEventHandle(void); +int MkWinChildCreate(char **papszArgs, char **papszEnv, const char *pszShell, struct child *pMkChild, pid_t *pPid); +int MkWinChildCreateWithStdOutPipe(char **papszArgs, char **papszEnv, int fdErr, pid_t *pPid, int *pfdReadPipe); +void MkWinChildInitCpuGroupAllocator(PMKWINCHILDCPUGROUPALLOCSTATE pState); +unsigned int MkWinChildAllocateCpuGroup(PMKWINCHILDCPUGROUPALLOCSTATE pState); + +#ifdef KMK +struct KMKBUILTINENTRY; +int MkWinChildCreateBuiltIn(struct KMKBUILTINENTRY const *pBuiltIn, int cArgs, char **papszArgs, + char **papszEnv, struct child *pMkChild, pid_t *pPid); +int MkWinChildCreateAppend(const char *pszFilename, char **ppszAppend, size_t cbAppend, int fTruncate, + struct child *pMkChild, pid_t *pPid); + +int MkWinChildCreateSubmit(intptr_t hEvent, void *pvSubmitWorker, PWINCCWPIPE pStdOut, PWINCCWPIPE pStdErr, + struct child *pMkChild, pid_t *pPid); +PWINCCWPIPE MkWinChildcareCreateWorkerPipe(unsigned iWhich, unsigned int idxWorker); +void MkWinChildcareWorkerDrainPipes(struct WINCHILD *pChild, PWINCCWPIPE pStdOut, PWINCCWPIPE pStdErr); +void MkWinChildcareDeleteWorkerPipe(PWINCCWPIPE pPipe); + +int MkWinChildCreateRedirect(intptr_t hProcess, pid_t *pPid); +# ifdef DECLARE_HANDLE +int MkWinChildBuiltInExecChild(void *pvWorker, const char *pszExecutable, char **papszArgs, BOOL fQuotedArgv, + char **papszEnvVars, const char *pszCwd, BOOL pafReplace[3], HANDLE pahReplace[3]); +# endif +#endif /* KMK */ +int MkWinChildKill(pid_t pid, int iSignal, struct child *pMkChild); +int MkWinChildWait(int fBlock, pid_t *pPid, int *piExitCode, int *piSignal, int *pfCoreDumped, struct child **ppMkChild); +void MkWinChildExclusiveAcquire(void); +void MkWinChildExclusiveRelease(void); + +#undef CLOSE_ON_EXEC +#define CLOSE_ON_EXEC(a_fd) MkWinChildUnrelatedCloseOnExec(a_fd) +int MkWinChildUnrelatedCloseOnExec(int fd); + + +#endif + |