summaryrefslogtreecommitdiffstats
path: root/src/kmk/w32
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:21:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:21:29 +0000
commit29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc (patch)
tree63ef546b10a81d461e5cf5ed9e98a68cd7dee1aa /src/kmk/w32
parentInitial commit. (diff)
downloadkbuild-29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc.tar.xz
kbuild-29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc.zip
Adding upstream version 1:0.1.9998svn3589+dfsg.upstream/1%0.1.9998svn3589+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/kmk/w32')
-rw-r--r--src/kmk/w32/Makefile.am26
-rw-r--r--src/kmk/w32/Makefile.kup0
-rw-r--r--src/kmk/w32/compat/Makefile.kup0
-rw-r--r--src/kmk/w32/compat/dirent.c212
-rw-r--r--src/kmk/w32/compat/posixfcn.c516
-rw-r--r--src/kmk/w32/imagecache.c219
-rw-r--r--src/kmk/w32/include/dirent.h66
-rw-r--r--src/kmk/w32/include/dlfcn.h29
-rw-r--r--src/kmk/w32/include/pathstuff.h30
-rw-r--r--src/kmk/w32/include/sub_proc.h72
-rw-r--r--src/kmk/w32/include/w32err.h26
-rw-r--r--src/kmk/w32/pathstuff.c321
-rw-r--r--src/kmk/w32/subproc/Makefile.kup0
-rw-r--r--src/kmk/w32/subproc/NMakefile60
-rw-r--r--src/kmk/w32/subproc/misc.c83
-rw-r--r--src/kmk/w32/subproc/proc.h29
-rw-r--r--src/kmk/w32/subproc/sub_proc.c1714
-rw-r--r--src/kmk/w32/subproc/w32err.c85
-rw-r--r--src/kmk/w32/tstFileInfo.c151
-rw-r--r--src/kmk/w32/w32os.c220
-rw-r--r--src/kmk/w32/winchildren.c3729
-rw-r--r--src/kmk/w32/winchildren.h115
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
+