summaryrefslogtreecommitdiffstats
path: root/src/kmk/kmkbuiltin
diff options
context:
space:
mode:
Diffstat (limited to 'src/kmk/kmkbuiltin')
-rw-r--r--src/kmk/kmkbuiltin/Makefile.kup0
-rw-r--r--src/kmk/kmkbuiltin/append.c459
-rw-r--r--src/kmk/kmkbuiltin/cat.c416
-rw-r--r--src/kmk/kmkbuiltin/chmod.c288
-rw-r--r--src/kmk/kmkbuiltin/cmp.c153
-rw-r--r--src/kmk/kmkbuiltin/cmp_extern.h49
-rw-r--r--src/kmk/kmkbuiltin/cmp_util.c562
-rw-r--r--src/kmk/kmkbuiltin/common-env-and-cwd-opt.c516
-rw-r--r--src/kmk/kmkbuiltin/cp.c779
-rw-r--r--src/kmk/kmkbuiltin/cp_extern.h55
-rw-r--r--src/kmk/kmkbuiltin/cp_utils.c397
-rw-r--r--src/kmk/kmkbuiltin/darwin.c55
-rw-r--r--src/kmk/kmkbuiltin/echo.c125
-rw-r--r--src/kmk/kmkbuiltin/err.c340
-rw-r--r--src/kmk/kmkbuiltin/err.h38
-rw-r--r--src/kmk/kmkbuiltin/expr.c617
-rw-r--r--src/kmk/kmkbuiltin/fts.c1461
-rw-r--r--src/kmk/kmkbuiltin/ftsfake.h159
-rw-r--r--src/kmk/kmkbuiltin/getopt1_r.c186
-rw-r--r--src/kmk/kmkbuiltin/getopt_r.c1090
-rw-r--r--src/kmk/kmkbuiltin/getopt_r.h182
-rw-r--r--src/kmk/kmkbuiltin/haikufakes.c53
-rw-r--r--src/kmk/kmkbuiltin/haikufakes.h42
-rw-r--r--src/kmk/kmkbuiltin/install.c1248
-rw-r--r--src/kmk/kmkbuiltin/kDepIDB.c860
-rw-r--r--src/kmk/kmkbuiltin/kDepObj.c1250
-rw-r--r--src/kmk/kmkbuiltin/kSubmit.c2116
-rw-r--r--src/kmk/kmkbuiltin/kbuild_protection.c376
-rw-r--r--src/kmk/kmkbuiltin/kbuild_protection.h67
-rw-r--r--src/kmk/kmkbuiltin/kill.c653
-rw-r--r--src/kmk/kmkbuiltin/ln.c287
-rw-r--r--src/kmk/kmkbuiltin/md5sum.c874
-rw-r--r--src/kmk/kmkbuiltin/mkdir.c302
-rw-r--r--src/kmk/kmkbuiltin/mscfakes.c839
-rw-r--r--src/kmk/kmkbuiltin/mscfakes.h183
-rw-r--r--src/kmk/kmkbuiltin/mv.c529
-rw-r--r--src/kmk/kmkbuiltin/openbsd.c54
-rw-r--r--src/kmk/kmkbuiltin/osdep.c48
-rw-r--r--src/kmk/kmkbuiltin/printf.c954
-rw-r--r--src/kmk/kmkbuiltin/redirect.c2066
-rw-r--r--src/kmk/kmkbuiltin/rm.c844
-rw-r--r--src/kmk/kmkbuiltin/rmdir.c251
-rw-r--r--src/kmk/kmkbuiltin/setmode.c506
-rw-r--r--src/kmk/kmkbuiltin/sleep.c179
-rw-r--r--src/kmk/kmkbuiltin/solfakes.c99
-rw-r--r--src/kmk/kmkbuiltin/solfakes.h51
-rw-r--r--src/kmk/kmkbuiltin/strlcpy.c78
-rw-r--r--src/kmk/kmkbuiltin/strmode.c197
-rw-r--r--src/kmk/kmkbuiltin/test.c869
-rw-r--r--src/kmk/kmkbuiltin/touch.c952
50 files changed, 24754 insertions, 0 deletions
diff --git a/src/kmk/kmkbuiltin/Makefile.kup b/src/kmk/kmkbuiltin/Makefile.kup
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/kmk/kmkbuiltin/Makefile.kup
diff --git a/src/kmk/kmkbuiltin/append.c b/src/kmk/kmkbuiltin/append.c
new file mode 100644
index 0000000..c34847e
--- /dev/null
+++ b/src/kmk/kmkbuiltin/append.c
@@ -0,0 +1,459 @@
+/* $Id: append.c 3246 2018-12-25 21:02:04Z bird $ */
+/** @file
+ * kMk Builtin command - append text to file.
+ */
+
+/*
+ * Copyright (c) 2005-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/>
+ *
+ */
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifndef KMK_BUILTIN_STANDALONE
+# include "makeint.h"
+# include "filedef.h"
+# include "variable.h"
+#else
+# include "config.h"
+#endif
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#ifdef _MSC_VER
+# include <io.h>
+#endif
+#ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+#endif
+#if !defined(KMK_BUILTIN_STANDALONE) && defined(KBUILD_OS_WINDOWS) && defined(CONFIG_NEW_WIN_CHILDREN)
+# include "../w32/winchildren.h"
+#endif
+#include "err.h"
+#include "kmkbuiltin.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define STR_TUPLE(a_sz) a_sz, (sizeof(a_sz) - 1)
+
+/** No-inherit open flag. */
+#ifdef _O_NOINHERIT
+# define MY_O_NOINHERIT _O_NOINHERIT
+#elif defined(O_NOINHERIT)
+# define MY_O_NOINHERIT O_NOINHERIT
+#elif defined(O_CLOEXEC)
+# define MY_O_NOINHERIT O_CLOEXEC
+#else
+# define MY_O_NOINHERIT 0
+#endif
+
+/** Binary mode open flag. */
+#ifdef _O_BINARY
+# define MY_O_BINARY _O_BINARY
+#elif defined(O_BINARY)
+# define MY_O_BINARY O_BINARY
+#else
+# define MY_O_BINARY 0
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Append output buffer.
+ */
+typedef struct KMKBUILTINAPPENDBUF
+{
+ /** Buffer pointer. */
+ char *pszBuf;
+ /** The buffer allocation size. */
+ size_t cbBuf;
+ /** The current buffer offset. */
+ size_t offBuf;
+ /** Set if we ran out of memory. */
+ int fOutOfMemory;
+} KMKBUILTINAPPENDBUF;
+
+
+/**
+ * Appends a substring to the output buffer.
+ *
+ * @param pBuf The output buffer.
+ * @param pch The substring pointer.
+ * @param cch The substring length.
+ */
+static void write_to_buf(KMKBUILTINAPPENDBUF *pBuf, const char *pch, size_t cch)
+{
+ size_t const offCur = pBuf->offBuf;
+ size_t offNew = offCur + cch;
+
+ if (offNew >= pBuf->cbBuf)
+ {
+ size_t cbNew = offNew + 1 + 256;
+ void *pvNew;
+ cbNew = (cbNew + 511) & ~(size_t)511;
+ pvNew = realloc(pBuf->pszBuf, cbNew);
+ if (pvNew)
+ pBuf->pszBuf = (char *)pvNew;
+ else
+ {
+ free(pBuf->pszBuf);
+ pBuf->pszBuf = NULL;
+ pBuf->cbBuf = 0;
+ pBuf->offBuf = offNew;
+ pBuf->fOutOfMemory = 1;
+ return;
+ }
+ }
+
+ memcpy(&pBuf->pszBuf[offCur], pch, cch);
+ pBuf->pszBuf[offNew] = '\0';
+ pBuf->offBuf = offNew;
+}
+
+/**
+ * Adds a string to the output buffer.
+ *
+ * @param pBuf The output buffer.
+ * @param psz The string.
+ */
+static void string_to_buf(KMKBUILTINAPPENDBUF *pBuf, const char *psz)
+{
+ write_to_buf(pBuf, psz, strlen(psz));
+}
+
+
+/**
+ * Prints the usage and return 1.
+ */
+static int kmk_builtin_append_usage(const char *arg0, FILE *pf)
+{
+ fprintf(pf,
+ "usage: %s [-dcnNtv] file [string ...]\n"
+ " or: %s --version\n"
+ " or: %s --help\n"
+ "\n"
+ "Options:\n"
+ " -d Enclose the output in define ... endef, taking the name from\n"
+ " the first argument following the file name.\n"
+ " -c Output the command for specified target(s). [builtin only]\n"
+ " -i look for --insert-command=trg and --insert-variable=var. [builtin only]\n"
+ " -n Insert a newline between the strings.\n"
+ " -N Suppress the trailing newline.\n"
+ " -t Truncate the file instead of appending\n"
+ " -v Output the value(s) for specified variable(s). [builtin only]\n"
+ ,
+ arg0, arg0, arg0);
+ return 1;
+}
+
+/**
+ * Appends text to a textfile, creating the textfile if necessary.
+ */
+int kmk_builtin_append(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx, struct child *pChild, pid_t *pPidSpawned)
+{
+#if defined(KBUILD_OS_WINDOWS) || defined(KBUILD_OS_OS2)
+ static const char s_szNewLine[] = "\r\n";
+#else
+ static const char s_szNewLine[] = "\n";
+#endif
+ KMKBUILTINAPPENDBUF OutBuf = { NULL, 0, 0, 0 };
+ const char *pszFilename;
+ int rc = 88;
+ int i;
+ int fFirst;
+ int fNewline = 0;
+ int fNoTrailingNewline = 0;
+ int fTruncate = 0;
+ int fDefine = 0;
+ int fVariables = 0;
+ int fCommands = 0;
+#ifndef KMK_BUILTIN_STANDALONE
+ int fLookForInserts = 0;
+#else
+ (void)pChild; (void)pPidSpawned;
+#endif
+
+ /*
+ * Parse options.
+ */
+ i = 1;
+ while (i < argc
+ && argv[i][0] == '-'
+ && argv[i][1] != '\0' /* '-' is a file */
+ && strchr("-cdinNtv", argv[i][1]) /* valid option char */
+ )
+ {
+ char *psz = &argv[i][1];
+ if (*psz != '-')
+ {
+ do
+ {
+ switch (*psz)
+ {
+ case 'c':
+ if (fVariables)
+ {
+ errx(pCtx, 1, "Option '-c' clashes with '-v'.");
+ return kmk_builtin_append_usage(argv[0], stderr);
+ }
+#ifndef KMK_BUILTIN_STANDALONE
+ fCommands = 1;
+ break;
+#else
+ errx(pCtx, 1, "Option '-c' isn't supported in external mode.");
+ return kmk_builtin_append_usage(argv[0], stderr);
+#endif
+ case 'd':
+ if (fVariables)
+ {
+ errx(pCtx, 1, "Option '-d' must come before '-v'!");
+ return kmk_builtin_append_usage(argv[0], stderr);
+ }
+ fDefine = 1;
+ break;
+ case 'i':
+ if (fVariables || fCommands)
+ {
+ errx(pCtx, 1, fVariables ? "Option '-i' clashes with '-v'." : "Option '-i' clashes with '-c'.");
+ return kmk_builtin_append_usage(argv[0], stderr);
+ }
+#ifndef KMK_BUILTIN_STANDALONE
+ fLookForInserts = 1;
+ break;
+#else
+ errx(pCtx, 1, "Option '-C' isn't supported in external mode.");
+ return kmk_builtin_append_usage(argv[0], stderr);
+#endif
+ case 'n':
+ fNewline = 1;
+ break;
+ case 'N':
+ fNoTrailingNewline = 1;
+ break;
+ case 't':
+ fTruncate = 1;
+ break;
+ case 'v':
+ if (fCommands)
+ {
+ errx(pCtx, 1, "Option '-v' clashes with '-c'.");
+ return kmk_builtin_append_usage(argv[0], stderr);
+ }
+#ifndef KMK_BUILTIN_STANDALONE
+ fVariables = 1;
+ break;
+#else
+ errx(pCtx, 1, "Option '-v' isn't supported in external mode.");
+ return kmk_builtin_append_usage(argv[0], stderr);
+#endif
+ default:
+ errx(pCtx, 1, "Invalid option '%c'! (%s)", *psz, argv[i]);
+ return kmk_builtin_append_usage(argv[0], stderr);
+ }
+ } while (*++psz);
+ }
+ else if (!strcmp(psz, "-help"))
+ {
+ kmk_builtin_append_usage(argv[0], stdout);
+ return 0;
+ }
+ else if (!strcmp(psz, "-version"))
+ return kbuild_version(argv[0]);
+ else
+ break;
+ i++;
+ }
+
+ /*
+ * Take down the filename.
+ */
+ if (i + fDefine < argc)
+ pszFilename = argv[i++];
+ else
+ {
+ if (i <= argc)
+ errx(pCtx, 1, "missing filename!");
+ else
+ errx(pCtx, 1, "missing define name!");
+ return kmk_builtin_append_usage(argv[0], stderr);
+ }
+
+ /* Start of no-return zone! */
+
+ /*
+ * Start define?
+ */
+ if (fDefine)
+ {
+ write_to_buf(&OutBuf, STR_TUPLE("define "));
+ string_to_buf(&OutBuf, argv[i]);
+ write_to_buf(&OutBuf, STR_TUPLE(s_szNewLine));
+ i++;
+ }
+
+ /*
+ * Append the argument strings to the file
+ */
+ fFirst = 1;
+ for (; i < argc; i++)
+ {
+ const char *psz = argv[i];
+ size_t cch = strlen(psz);
+ if (!fFirst)
+ {
+ if (fNewline)
+ write_to_buf(&OutBuf, STR_TUPLE(s_szNewLine));
+ else
+ write_to_buf(&OutBuf, STR_TUPLE(" "));
+ }
+#ifndef KMK_BUILTIN_STANDALONE
+ if (fCommands)
+ {
+ char *pszOldBuf;
+ unsigned cchOldBuf;
+ char *pchEnd;
+
+ install_variable_buffer(&pszOldBuf, &cchOldBuf);
+
+ pchEnd = func_commands(variable_buffer, &argv[i], "commands");
+ write_to_buf(&OutBuf, variable_buffer, pchEnd - variable_buffer);
+
+ restore_variable_buffer(pszOldBuf, cchOldBuf);
+ }
+ else if (fVariables)
+ {
+ struct variable *pVar = lookup_variable(psz, cch);
+ if (!pVar)
+ continue;
+ if ( !pVar->recursive
+ || IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR(pVar))
+ write_to_buf(&OutBuf, pVar->value, pVar->value_length);
+ else
+ {
+ char *pszExpanded = allocated_variable_expand(pVar->value);
+ string_to_buf(&OutBuf, pszExpanded);
+ free(pszExpanded);
+ }
+ }
+ else if (fLookForInserts && strncmp(psz, "--insert-command=", 17) == 0)
+ {
+ char *pszOldBuf;
+ unsigned cchOldBuf;
+ char *pchEnd;
+
+ install_variable_buffer(&pszOldBuf, &cchOldBuf);
+
+ psz += 17;
+ pchEnd = func_commands(variable_buffer, (char **)&psz, "commands");
+ write_to_buf(&OutBuf, variable_buffer, pchEnd - variable_buffer);
+
+ restore_variable_buffer(pszOldBuf, cchOldBuf);
+ }
+ else if (fLookForInserts && strncmp(psz, "--insert-variable=", 18) == 0)
+ {
+ struct variable *pVar = lookup_variable(psz + 18, cch);
+ if (!pVar)
+ continue;
+ if ( !pVar->recursive
+ || IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR(pVar))
+ write_to_buf(&OutBuf, pVar->value, pVar->value_length);
+ else
+ {
+ char *pszExpanded = allocated_variable_expand(pVar->value);
+ string_to_buf(&OutBuf, pszExpanded);
+ free(pszExpanded);
+ }
+ }
+ else
+#endif
+ write_to_buf(&OutBuf, psz, cch);
+ fFirst = 0;
+ }
+
+ /*
+ * End the define?
+ */
+ if (fDefine)
+ {
+ if (fFirst)
+ write_to_buf(&OutBuf, STR_TUPLE(s_szNewLine));
+ write_to_buf(&OutBuf, STR_TUPLE("endef"));
+ }
+
+ /*
+ * Add final newline (unless supressed) and check for errors.
+ */
+ if (!fNoTrailingNewline)
+ write_to_buf(&OutBuf, STR_TUPLE(s_szNewLine));
+
+ /*
+ * Write the buffer (unless we ran out of heap already).
+ */
+#if !defined(KMK_BUILTIN_STANDALONE) && defined(KBUILD_OS_WINDOWS) && defined(CONFIG_NEW_WIN_CHILDREN)
+ if (!OutBuf.fOutOfMemory)
+ {
+ rc = MkWinChildCreateAppend(pszFilename, &OutBuf.pszBuf, OutBuf.offBuf, fTruncate, pChild, pPidSpawned);
+ if (rc != 0)
+ rc = errx(pCtx, rc, "MkWinChildCreateAppend failed: %u", rc);
+ if (OutBuf.pszBuf)
+ free(OutBuf.pszBuf);
+ }
+ else
+#endif
+ if (!OutBuf.fOutOfMemory)
+ {
+ int fd = open(pszFilename,
+ fTruncate ? O_WRONLY | O_TRUNC | O_CREAT | MY_O_NOINHERIT | MY_O_BINARY
+ : O_WRONLY | O_APPEND | O_CREAT | MY_O_NOINHERIT | MY_O_BINARY,
+ 0666);
+ if (fd >= 0)
+ {
+ ssize_t cbWritten = write(fd, OutBuf.pszBuf, OutBuf.offBuf);
+ if (cbWritten == (ssize_t)OutBuf.offBuf)
+ rc = 0;
+ else
+ rc = err(pCtx, 1, "error writing %lu bytes to '%s'", (unsigned long)OutBuf.offBuf, pszFilename);
+ if (close(fd) < 0)
+ rc = err(pCtx, 1, "error closing '%s'", pszFilename);
+ }
+ else
+ rc = err(pCtx, 1, "failed to open '%s'", pszFilename);
+ free(OutBuf.pszBuf);
+ }
+ else
+ rc = errx(pCtx, 1, "out of memory for output buffer! (%u needed)", OutBuf.offBuf + 1);
+ return rc;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_append", NULL };
+ return kmk_builtin_append(argc, argv, envp, &Ctx, NULL, NULL);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin/cat.c b/src/kmk/kmkbuiltin/cat.c
new file mode 100644
index 0000000..7b52d0f
--- /dev/null
+++ b/src/kmk/kmkbuiltin/cat.c
@@ -0,0 +1,416 @@
+/*-
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kevin Fall.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static char const copyright[] =
+"@(#) Copyright (c) 1989, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+#endif
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)cat.c 8.2 (Berkeley) 4/27/95";
+#endif
+#endif /* not lint */
+#if 0
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/bin/cat/cat.c,v 1.32 2005/01/10 08:39:20 imp Exp $");
+#endif
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define FAKES_NO_GETOPT_H /* bird */
+#define NO_UDOM_SUPPORT /* kmk */
+#include "config.h"
+#ifndef _MSC_VER
+# include <sys/param.h>
+#endif
+#include <sys/stat.h>
+#ifndef NO_UDOM_SUPPORT
+# include <sys/socket.h>
+# include <sys/un.h>
+# include <errno.h>
+#endif
+
+#include <ctype.h>
+#include "err.h"
+#include <fcntl.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stddef.h>
+#include "getopt_r.h"
+#ifdef __sun__
+# include "solfakes.h"
+#endif
+#ifdef _MSC_VER
+# include "mscfakes.h"
+#endif
+#include "kmkbuiltin.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct CATINSTANCE
+{
+ PKMKBUILTINCTX pCtx;
+ int bflag, eflag, nflag, sflag, tflag, vflag;
+ /*int rval;*/
+ const char *filename;
+ /* function level statics from raw_cat (needs freeing): */
+ size_t bsize;
+ char *buf;
+} CATINSTANCE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static struct option long_options[] =
+{
+ { "help", no_argument, 0, 261 },
+ { "version", no_argument, 0, 262 },
+ { 0, 0, 0, 0 },
+};
+
+
+static int usage(PKMKBUILTINCTX pCtx, int fIsErr);
+static int scanfiles(CATINSTANCE *pThis, char *argv[], int cooked);
+static int cook_cat(CATINSTANCE *pThis, FILE *);
+static int raw_cat(CATINSTANCE *pThis, int);
+
+#ifndef NO_UDOM_SUPPORT
+static int udom_open(PKMKBUILTINCTX pCtx, const char *path, int flags);
+#endif
+
+int
+kmk_builtin_cat(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ struct getopt_state_r gos;
+ CATINSTANCE This;
+ int ch, rc;
+
+ /* kmk: reinitialize globals */
+ This.pCtx = pCtx;
+ This.bflag = This.eflag = This.nflag = This.sflag = This.tflag = This.vflag = 0;
+ This.filename = NULL;
+ This.bsize = 0;
+ This.buf = 0;
+
+ getopt_initialize_r(&gos, argc, argv, "benstuv", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1)
+ switch (ch) {
+ case 'b':
+ This.bflag = This.nflag = 1; /* -b implies -n */
+ break;
+ case 'e':
+ This.eflag = This.vflag = 1; /* -e implies -v */
+ break;
+ case 'n':
+ This.nflag = 1;
+ break;
+ case 's':
+ This.sflag = 1;
+ break;
+ case 't':
+ This.tflag = This.vflag = 1; /* -t implies -v */
+ break;
+ case 'u':
+#ifdef KMK_BUILTIN_STANDALONE /* don't allow messing with stdout */
+ setbuf(stdout, NULL);
+#endif
+ break;
+ case 'v':
+ This.vflag = 1;
+ break;
+ case 261:
+ usage(pCtx, 0);
+ return 0;
+ case 262:
+ return kbuild_version(argv[0]);
+ default:
+ return usage(pCtx, 1);
+ }
+ argv += gos.optind;
+
+ if (This.bflag || This.eflag || This.nflag || This.sflag || This.tflag || This.vflag)
+ rc = scanfiles(&This, argv, 1);
+ else
+ rc = scanfiles(&This, argv, 0);
+ if (This.buf) {
+ free(This.buf);
+ This.buf = NULL;
+ }
+#ifdef KMK_BUILTIN_STANDALONE /* don't allow messing with stdout */
+ if (fclose(stdout))
+ return err(pCtx, 1, "stdout");
+#endif
+ return rc;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_cat", NULL };
+ setlocale(LC_CTYPE, "");
+ return kmk_builtin_cat(argc, argv, envp, &Ctx);
+}
+#endif
+
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s [-benstuv] [file ...]\n"
+ " or: %s --help\n"
+ " or: %s --version\n",
+ pCtx->pszProgName, pCtx->pszProgName,
+ pCtx->pszProgName);
+ return 1;
+}
+
+static int
+scanfiles(CATINSTANCE *pThis, char *argv[], int cooked)
+{
+ int i = 0;
+ char *path;
+ FILE *fp;
+ int rc2 = 0;
+ int rc = 0;
+
+ while ((path = argv[i]) != NULL || i == 0) {
+ int fd;
+
+ if (path == NULL || strcmp(path, "-") == 0) {
+ pThis->filename = "stdin";
+ fd = STDIN_FILENO;
+ } else {
+ pThis->filename = path;
+ fd = open(path, O_RDONLY | KMK_OPEN_NO_INHERIT);
+#ifndef NO_UDOM_SUPPORT
+ if (fd < 0 && errno == EOPNOTSUPP)
+ fd = udom_open(pThis, path, O_RDONLY);
+#endif
+ }
+ if (fd < 0) {
+ warn(pThis->pCtx, "%s", path);
+ rc2 = 1; /* non fatal */
+ } else if (cooked) {
+ if (fd == STDIN_FILENO)
+ rc = cook_cat(pThis, stdin);
+ else {
+ fp = fdopen(fd, "r");
+ rc = cook_cat(pThis, fp);
+ fclose(fp);
+ }
+ } else {
+ rc = raw_cat(pThis, fd);
+ if (fd != STDIN_FILENO)
+ close(fd);
+ }
+ if (rc || path == NULL)
+ break;
+ ++i;
+ }
+ return !rc ? rc2 : rc;
+}
+
+static int
+cat_putchar(PKMKBUILTINCTX pCtx, char ch)
+{
+#ifndef KMK_BUILTIN_STANDALONE
+ if (pCtx->pOut) {
+ output_write_text(pCtx->pOut, 0, &ch, 1);
+ return 0;
+ }
+#endif
+ return putchar(ch);
+}
+
+static int
+cook_cat(CATINSTANCE *pThis, FILE *fp)
+{
+ int ch, gobble, line, prev;
+ int rc = 0;
+
+ /* Reset EOF condition on stdin. */
+ if (fp == stdin && feof(stdin))
+ clearerr(stdin);
+
+ line = gobble = 0;
+ for (prev = '\n'; (ch = getc(fp)) != EOF; prev = ch) {
+ if (prev == '\n') {
+ if (pThis->sflag) {
+ if (ch == '\n') {
+ if (gobble)
+ continue;
+ gobble = 1;
+ } else
+ gobble = 0;
+ }
+ if (pThis->nflag && (!pThis->bflag || ch != '\n')) {
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%6d\t", ++line);
+ if (ferror(stdout))
+ break;
+ }
+ }
+ if (ch == '\n') {
+ if (pThis->eflag && cat_putchar(pThis->pCtx, '$') == EOF)
+ break;
+ } else if (ch == '\t') {
+ if (pThis->tflag) {
+ if (cat_putchar(pThis->pCtx, '^') == EOF || cat_putchar(pThis->pCtx, 'I') == EOF)
+ break;
+ continue;
+ }
+ } else if (pThis->vflag) {
+ if (!isascii(ch) && !isprint(ch)) {
+ if (cat_putchar(pThis->pCtx, 'M') == EOF || cat_putchar(pThis->pCtx, '-') == EOF)
+ break;
+ ch = toascii(ch);
+ }
+ if (iscntrl(ch)) {
+ if (cat_putchar(pThis->pCtx, '^') == EOF ||
+ cat_putchar(pThis->pCtx, ch == '\177' ? '?' :
+ ch | 0100) == EOF)
+ break;
+ continue;
+ }
+ }
+ if (cat_putchar(pThis->pCtx, ch) == EOF)
+ break;
+ }
+ if (ferror(fp)) {
+ warn(pThis->pCtx, "%s", pThis->filename);
+ rc = 1;
+ clearerr(fp);
+ }
+ if (ferror(stdout))
+ return err(pThis->pCtx, 1, "stdout");
+ return rc;
+}
+
+static int
+raw_cat(CATINSTANCE *pThis, int rfd)
+{
+ int off, wfd = fileno(stdout);
+ ssize_t nr, nw;
+
+ wfd = fileno(stdout);
+ if (pThis->buf == NULL) {
+ struct stat sbuf;
+ if (fstat(wfd, &sbuf))
+ return err(pThis->pCtx, 1, "%s", pThis->filename);
+#ifdef KBUILD_OS_WINDOWS
+ pThis->bsize = 16384;
+#else
+ pThis->bsize = MAX(sbuf.st_blksize, 1024);
+#endif
+ if ((pThis->buf = malloc(pThis->bsize)) == NULL)
+ return err(pThis->pCtx, 1, "buffer");
+ }
+ while ((nr = read(rfd, pThis->buf, pThis->bsize)) > 0)
+ for (off = 0; nr; nr -= nw, off += nw) {
+#ifndef KMK_BUILTIN_STANDALONE
+ if (pThis->pCtx->pOut)
+ nw = output_write_text(pThis->pCtx->pOut, 0, pThis->buf + off, nr);
+ else
+#endif
+ nw = write(wfd, pThis->buf + off, (size_t)nr);
+ if (nw < 0)
+ return err(pThis->pCtx, 1, "stdout");
+ }
+ if (nr < 0) {
+ warn(pThis->pCtx, "%s", pThis->filename);
+ return 1;
+ }
+ return 0;
+}
+
+#ifndef NO_UDOM_SUPPORT
+
+static int
+udom_open(CATINSTANCE *pThis, const char *path, int flags)
+{
+ struct sockaddr_un sou;
+ int fd;
+ unsigned int len;
+
+ bzero(&sou, sizeof(sou));
+
+ /*
+ * Construct the unix domain socket address and attempt to connect
+ */
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd >= 0) {
+ sou.sun_family = AF_UNIX;
+ if ((len = strlcpy(sou.sun_path, path,
+ sizeof(sou.sun_path))) >= sizeof(sou.sun_path)) {
+ errno = ENAMETOOLONG;
+ return (-1);
+ }
+ len = offsetof(struct sockaddr_un, sun_path[len+1]);
+
+ if (connect(fd, (void *)&sou, len) < 0) {
+ close(fd);
+ fd = -1;
+ }
+ }
+
+ /*
+ * handle the open flags by shutting down appropriate directions
+ */
+ if (fd >= 0) {
+ switch(flags & O_ACCMODE) {
+ case O_RDONLY:
+ if (shutdown(fd, SHUT_WR) == -1)
+ warn(pThis->pCtx, NULL);
+ break;
+ case O_WRONLY:
+ if (shutdown(fd, SHUT_RD) == -1)
+ warn(pThis->pCtx, NULL);
+ break;
+ default:
+ break;
+ }
+ }
+ return(fd);
+}
+
+#endif
+
diff --git a/src/kmk/kmkbuiltin/chmod.c b/src/kmk/kmkbuiltin/chmod.c
new file mode 100644
index 0000000..0061924
--- /dev/null
+++ b/src/kmk/kmkbuiltin/chmod.c
@@ -0,0 +1,288 @@
+/*-
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static char const copyright[] =
+"@(#) Copyright (c) 1989, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)chmod.c 8.8 (Berkeley) 4/1/94";
+#endif /* not lint */
+#endif
+/*#include <sys/cdefs.h> */
+/*__FBSDID("$FreeBSD: src/bin/chmod/chmod.c,v 1.33 2005/01/10 08:39:20 imp Exp $");*/
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define FAKES_NO_GETOPT_H /* bird */
+#include "config.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "err.h"
+#include <errno.h>
+#include "fts.h"
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef _MSC_VER
+# include <unistd.h>
+#else
+# include "mscfakes.h"
+#endif
+#ifdef __sun__
+# include "solfakes.h"
+#endif
+#ifdef __HAIKU__
+# include "haikufakes.h"
+#endif
+#include "getopt_r.h"
+#include "kmkbuiltin.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static struct option long_options[] =
+{
+ { "help", no_argument, 0, 261 },
+ { "version", no_argument, 0, 262 },
+ { 0, 0, 0, 0 },
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+extern void * bsd_setmode(const char *p);
+extern mode_t bsd_getmode(const void *bbox, mode_t omode);
+extern void bsd_strmode(mode_t mode, char *p);
+
+#if (defined(__APPLE__) && !defined(_DARWIN_FEATURE_UNIX_CONFORMANCE)) || defined(__OpenBSD__)
+extern int lchmod(const char *, mode_t);
+#endif
+
+static int usage(PKMKBUILTINCTX pCtx, int is_err);
+
+
+int
+kmk_builtin_chmod(int argc, char *argv[], char **envp, PKMKBUILTINCTX pCtx)
+{
+ struct getopt_state_r gos;
+ FTS *ftsp;
+ FTSENT *p;
+ mode_t *set;
+ int Hflag, Lflag, Rflag, ch, fflag, fts_options, hflag, rval;
+ int vflag;
+ char *mode;
+ mode_t newmode;
+ int (*change_mode)(const char *, mode_t);
+
+ set = NULL;
+ Hflag = Lflag = Rflag = fflag = hflag = vflag = 0;
+
+ getopt_initialize_r(&gos, argc, argv, "HLPRXfghorstuvwx", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1)
+ switch (ch) {
+ case 'H':
+ Hflag = 1;
+ Lflag = 0;
+ break;
+ case 'L':
+ Lflag = 1;
+ Hflag = 0;
+ break;
+ case 'P':
+ Hflag = Lflag = 0;
+ break;
+ case 'R':
+ Rflag = 1;
+ break;
+ case 'f':
+ fflag = 1;
+ break;
+ case 'h':
+ /*
+ * In System V (and probably POSIX.2) the -h option
+ * causes chmod to change the mode of the symbolic
+ * link. 4.4BSD's symbolic links didn't have modes,
+ * so it was an undocumented noop. In FreeBSD 3.0,
+ * lchmod(2) is introduced and this option does real
+ * work.
+ */
+ hflag = 1;
+ break;
+ /*
+ * XXX
+ * "-[rwx]" are valid mode commands. If they are the entire
+ * argument, getopt has moved past them, so decrement optind.
+ * Regardless, we're done argument processing.
+ */
+ case 'g': case 'o': case 'r': case 's':
+ case 't': case 'u': case 'w': case 'X': case 'x':
+ if (argv[gos.optind - 1][0] == '-' &&
+ argv[gos.optind - 1][1] == ch &&
+ argv[gos.optind - 1][2] == '\0')
+ --gos.optind;
+ goto done;
+ case 'v':
+ vflag++;
+ break;
+ case 261:
+ usage(pCtx, 0);
+ return 0;
+ case 262:
+ return kbuild_version(argv[0]);
+ case '?':
+ default:
+ return usage(pCtx, 1);
+ }
+done: argv += gos.optind;
+ argc -= gos.optind;
+
+ if (argc < 2)
+ return usage(pCtx, 1);
+
+ if (Rflag) {
+ fts_options = FTS_PHYSICAL;
+ if (hflag)
+ return errx(pCtx, 1,
+ "the -R and -h options may not be specified together.");
+ if (Hflag)
+ fts_options |= FTS_COMFOLLOW;
+ if (Lflag) {
+ fts_options &= ~FTS_PHYSICAL;
+ fts_options |= FTS_LOGICAL;
+ }
+ } else
+ fts_options = hflag ? FTS_PHYSICAL : FTS_LOGICAL;
+#ifndef KMK_BUILTIN_STANDALONE
+ fts_options |= FTS_NOCHDIR; /* Don't change the CWD while inside kmk. */
+#endif
+
+ if (hflag)
+ change_mode = lchmod;
+ else
+ change_mode = chmod;
+
+ mode = *argv;
+ if ((set = bsd_setmode(mode)) == NULL)
+ return errx(pCtx, 1, "invalid file mode: %s", mode);
+
+ if ((ftsp = fts_open(++argv, fts_options, 0)) == NULL)
+ return err(pCtx, 1, "fts_open");
+ for (rval = 0; (p = fts_read(ftsp)) != NULL;) {
+ switch (p->fts_info) {
+ case FTS_D: /* Change it at FTS_DP. */
+ if (!Rflag)
+ fts_set(ftsp, p, FTS_SKIP);
+ continue;
+ case FTS_DNR: /* Warn, chmod, continue. */
+ warnx(pCtx, "fts: %s: %s", p->fts_path, strerror(p->fts_errno));
+ rval = 1;
+ break;
+ case FTS_ERR: /* Warn, continue. */
+ case FTS_NS:
+ warnx(pCtx, "fts: %s: %s", p->fts_path, strerror(p->fts_errno));
+ rval = 1;
+ continue;
+ case FTS_SL: /* Ignore. */
+ case FTS_SLNONE:
+ /*
+ * The only symlinks that end up here are ones that
+ * don't point to anything and ones that we found
+ * doing a physical walk.
+ */
+ if (!hflag)
+ continue;
+ /* else */
+ /* FALLTHROUGH */
+ default:
+ break;
+ }
+ newmode = bsd_getmode(set, p->fts_statp->st_mode);
+ if ((newmode & ALLPERMS) == (p->fts_statp->st_mode & ALLPERMS))
+ continue;
+ if ((*change_mode)(p->fts_accpath, newmode) && !fflag) {
+ warn(pCtx, "%schmod: %s", hflag ? "l" : "", p->fts_path);
+ rval = 1;
+ } else {
+ if (vflag) {
+ kmk_builtin_ctx_printf(pCtx, 0, "%s", p->fts_path);
+
+ if (vflag > 1) {
+ char m1[12], m2[12];
+
+ bsd_strmode(p->fts_statp->st_mode, m1);
+ bsd_strmode((p->fts_statp->st_mode &
+ S_IFMT) | newmode, m2);
+
+ kmk_builtin_ctx_printf(pCtx, 0, ": 0%o [%s] -> 0%o [%s]",
+ (unsigned int)p->fts_statp->st_mode, m1,
+ (unsigned int)((p->fts_statp->st_mode & S_IFMT) | newmode), m2);
+ }
+ kmk_builtin_ctx_printf(pCtx, 0, "\n");
+ }
+
+ }
+ }
+ if (errno)
+ rval = err(pCtx, 1, "fts_read");
+ free(set);
+ fts_close(ftsp);
+ return rval;
+}
+
+int
+usage(PKMKBUILTINCTX pCtx, int is_err)
+{
+ kmk_builtin_ctx_printf(pCtx, is_err,
+ "usage: %s [-fhv] [-R [-H | -L | -P]] mode file ...\n"
+ " or: %s --version\n"
+ " or: %s --help\n",
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+
+ return 1;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+mode_t g_fUMask;
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_chmod", NULL };
+ umask(g_fUMask = umask(0077));
+ return kmk_builtin_chmod(argc, argv, envp, &Ctx);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin/cmp.c b/src/kmk/kmkbuiltin/cmp.c
new file mode 100644
index 0000000..238d044
--- /dev/null
+++ b/src/kmk/kmkbuiltin/cmp.c
@@ -0,0 +1,153 @@
+/* $NetBSD: cmp.c,v 1.15 2006/01/19 20:44:57 garbled Exp $ */
+
+/*
+ * Copyright (c) 1987, 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*__COPYRIGHT("@(#) Copyright (c) 1987, 1990, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n");
+static char sccsid[] = "@(#)cmp.c 8.3 (Berkeley) 4/2/94";
+__RCSID("$NetBSD: cmp.c,v 1.15 2006/01/19 20:44:57 garbled Exp $"); */
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define FAKES_NO_GETOPT_H /* bird */
+#ifdef _MSC_VER
+# define MSC_DO_64_BIT_IO /* for correct off_t */
+#endif
+#include "config.h"
+#include <sys/types.h>
+#include "err.h"
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+#ifndef _MSC_VER
+# include <unistd.h>
+#else
+# include "mscfakes.h"
+#endif
+#include "getopt_r.h"
+#include "kmkbuiltin.h"
+#include "cmp_extern.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static const struct option long_options[] =
+{
+ { "help", no_argument, 0, 261 },
+ { "version", no_argument, 0, 262 },
+ { 0, 0, 0, 0 },
+};
+
+
+static int usage(PKMKBUILTINCTX pCtx, int is_err);
+
+int
+kmk_builtin_cmp(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ struct getopt_state_r gos;
+ off_t skip1 = 0, skip2 = 0;
+ int lflag = 0, sflag = 0;
+ int ch;
+ char *file1, *file2;
+
+ getopt_initialize_r(&gos, argc, argv, "ls", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1)
+ {
+ switch (ch)
+ {
+ case 'l': /* print all differences */
+ lflag = 1;
+ break;
+ case 's': /* silent run */
+ sflag = 1;
+ break;
+ case 261:
+ usage(pCtx, 0);
+ return 0;
+ case 262:
+ return kbuild_version(argv[0]);
+ case '?':
+ default:
+ return usage(pCtx, 1);
+ }
+ }
+ argv += gos.optind;
+ argc -= gos.optind;
+
+ if (argc < 2 || argc > 4)
+ return usage(pCtx, 1);
+
+ file1 = argv[0];
+ file2 = argv[1];
+
+ if (argc > 2)
+ {
+ char *ep;
+
+ errno = 0;
+ skip1 = strtoll(argv[2], &ep, 0);
+ if (errno || ep == argv[2])
+ return errx(pCtx, ERR_EXIT, "strtoll(%s,,) failed", argv[2]);
+
+ if (argc == 4)
+ {
+ skip2 = strtoll(argv[3], &ep, 0);
+ if (errno || ep == argv[3])
+ return errx(pCtx, ERR_EXIT, "strtoll(%s,,) failed", argv[3]);
+ }
+ }
+
+ return cmp_file_and_file_ex(pCtx, file1, skip1, file2, skip2, sflag, lflag, 0);
+}
+
+static int
+usage(PKMKBUILTINCTX pCtx, int is_err)
+{
+ kmk_builtin_ctx_printf(pCtx, is_err,
+ "usage: %s [-l | -s] file1 file2 [skip1 [skip2]]\n"
+ " or: %s --help\n"
+ " or: %s --version\n",
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+ return ERR_EXIT;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_cmp", NULL };
+ setlocale(LC_ALL, "");
+ return kmk_builtin_cmp(argc, argv, envp, &Ctx);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin/cmp_extern.h b/src/kmk/kmkbuiltin/cmp_extern.h
new file mode 100644
index 0000000..94e69d3
--- /dev/null
+++ b/src/kmk/kmkbuiltin/cmp_extern.h
@@ -0,0 +1,49 @@
+/* $NetBSD: extern.h,v 1.7 2007/08/21 14:09:53 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.3 (Berkeley) 4/2/94
+ */
+
+#define OK_EXIT 0
+#define DIFF_EXIT 1
+#define ERR_EXIT 2 /* error exit code */
+
+int cmp_file_and_file(PKMKBUILTINCTX pCtx, const char *file1, const char *file2, int sflag, int lflag, int special);
+int cmp_file_and_file_ex(PKMKBUILTINCTX pCtx, const char *file1, off_t skip1,
+ const char *file2, off_t skip2, int sflag, int lflag, int special);
+int cmp_fd_and_file(PKMKBUILTINCTX pCtx, int fd1, const char *file1,
+ const char *file2, int sflag, int lflag, int special);
+int cmp_fd_and_file_ex(PKMKBUILTINCTX pCtx, int fd1, const char *file1, off_t skip1,
+ const char *file2, off_t skip2, int sflag, int lflag, int special);
+int cmp_fd_and_fd(PKMKBUILTINCTX pCtx, int fd1, const char *file1,
+ int fd2, const char *file2, int sflag, int lflag, int special);
+int cmp_fd_and_fd_ex(PKMKBUILTINCTX pCtx, int fd1, const char *file1, off_t skip1,
+ int fd2, const char *file2, off_t skip2, int sflag, int lflag, int special);
+
diff --git a/src/kmk/kmkbuiltin/cmp_util.c b/src/kmk/kmkbuiltin/cmp_util.c
new file mode 100644
index 0000000..0228f38
--- /dev/null
+++ b/src/kmk/kmkbuiltin/cmp_util.c
@@ -0,0 +1,562 @@
+/* $NetBSD: cmp.c,v 1.15 2006/01/19 20:44:57 garbled Exp $ */
+/* $NetBSD: misc.c,v 1.11 2007/08/22 16:59:19 christos Exp $ */
+/* $NetBSD: regular.c,v 1.20 2006/06/03 21:47:55 christos Exp $ */
+/* $NetBSD: special.c,v 1.12 2007/08/21 14:09:54 christos Exp $ */
+
+/*
+ * Copyright (c) 1987, 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*__COPYRIGHT("@(#) Copyright (c) 1987, 1990, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n");*/
+
+#ifdef _MSC_VER
+# define MSC_DO_64_BIT_IO
+#endif
+#include "config.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#if defined(__FreeBSD__) || defined(__NetBSD__) /** @todo more mmap capable OSes. */
+# define CMP_USE_MMAP
+# include <sys/param.h>
+# include <sys/mman.h>
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef _MSC_VER
+# include <unistd.h>
+# ifndef O_BINARY
+# define O_BINARY 0
+# endif
+#else
+# include "mscfakes.h"
+#endif
+#include "err.h"
+
+#include "cmp_extern.h"
+
+
+static int
+errmsg(PKMKBUILTINCTX pCtx, const char *file, off_t byte, off_t line, int lflag)
+{
+ if (lflag)
+#ifdef _MSC_VER
+ return err(pCtx, ERR_EXIT, "%s: char %I64d, line %lld", file, (__int64)byte, (long long)line);
+#else
+ return err(pCtx, ERR_EXIT, "%s: char %lld, line %lld", file, (long long)byte, (long long)line);
+#endif
+ return err(pCtx, ERR_EXIT, "%s", file);
+}
+
+
+static int
+eofmsg(PKMKBUILTINCTX pCtx, const char *file, off_t byte, off_t line, int sflag, int lflag)
+{
+ if (!sflag)
+ {
+ if (!lflag)
+ warnx(pCtx, "EOF on %s", file);
+ else
+ {
+#ifdef _MSC_VER
+ if (line > 0)
+ warnx(pCtx, "EOF on %s: char %I64d, line %I64d", file, (__int64)byte, (__int64)line);
+ else
+ warnx(pCtx, "EOF on %s: char %I64d", file, (__int64)byte);
+#else
+ if (line > 0)
+ warnx(pCtx, "EOF on %s: char %lld, line %lld", file, (long long)byte, (long long)line);
+ else
+ warnx(pCtx, "EOF on %s: char %lld", file, (long long)byte);
+#endif
+ }
+ }
+ return DIFF_EXIT;
+}
+
+
+static int
+diffmsg(PKMKBUILTINCTX pCtx, const char *file1, const char *file2, off_t byte, off_t line, int sflag)
+{
+ if (!sflag)
+#ifdef _MSC_VER
+ kmk_builtin_ctx_printf(pCtx, 0, "%s %s differ: char %I64d, line %I64d\n",
+ file1, file2, (__int64)byte, (__int64)line);
+#else
+ kmk_builtin_ctx_printf(pCtx, 0, "%s %s differ: char %lld, line %lld\n",
+ file1, file2, (long long)byte, (long long)line);
+#endif
+ return DIFF_EXIT;
+}
+
+
+/**
+ * Compares two files, where one or both are non-regular ones.
+ */
+static int
+c_special(PKMKBUILTINCTX pCtx, int fd1, const char *file1, off_t skip1,
+ int fd2, const char *file2, off_t skip2,
+ int lflag, int sflag)
+{
+ int fd1dup, fd2dup;
+ FILE *fp1;
+ int rc;
+
+ /* duplicate because fdopen+fclose will otherwise close the handle. */
+ fd1dup = dup(fd1);
+ if (fd1 < 0)
+ return err(pCtx, ERR_EXIT, "%s", file1);
+ fp1 = fdopen(fd1dup, "rb");
+ if (!fp1)
+ fp1 = fdopen(fd1dup, "r");
+ if (!fp1)
+ {
+ err(pCtx, ERR_EXIT, "%s", file1);
+ close(fd1dup);
+ return ERR_EXIT;
+ }
+
+ fd2dup = dup(fd2);
+ if (fd2dup >= 0)
+ {
+ FILE *fp2 = fdopen(fd2dup, "rb");
+ if (!fp2)
+ fp2 = fdopen(fd2dup, "r");
+ if (fp2)
+ {
+ off_t byte;
+ off_t line;
+ int ch1 = 0;
+ int ch2 = 0;
+
+ /* skipping ahead */
+ rc = OK_EXIT;
+ for (byte = line = 1; skip1--; byte++)
+ {
+ ch1 = getc(fp1);
+ if (ch1 == EOF)
+ break;
+ if (ch1 == '\n')
+ line++;
+ }
+ for (byte = line = 1; skip2--; byte++)
+ {
+ ch2 = getc(fp2);
+ if (ch2 == EOF)
+ break;
+ if (ch2 == '\n')
+ line++;
+ }
+ if (ch2 != EOF && ch1 != EOF)
+ {
+ /* compare byte by byte */
+ for (byte = line = 1;; ++byte)
+ {
+ ch1 = getc(fp1);
+ ch2 = getc(fp2);
+ if (ch1 == EOF || ch2 == EOF)
+ break;
+ if (ch1 != ch2)
+ {
+ if (!lflag)
+ {
+ rc = diffmsg(pCtx, file1, file2, byte, line, sflag);
+ break;
+ }
+ rc = DIFF_EXIT;
+#ifdef _MSC_VER
+ kmk_builtin_ctx_printf(pCtx, 0, "%6i64d %3o %3o\n", (__int64)byte, ch1, ch2);
+#else
+ kmk_builtin_ctx_printf(pCtx, 0, "%6lld %3o %3o\n", (long long)byte, ch1, ch2);
+#endif
+ }
+ if (ch1 == '\n')
+ ++line;
+ }
+ }
+
+ /* Check for errors and length differences (EOF). */
+ if (ferror(fp1) && rc != ERR_EXIT)
+ rc = errmsg(pCtx, file1, byte, line, lflag);
+ if (ferror(fp2) && rc != ERR_EXIT)
+ rc = errmsg(pCtx, file2, byte, line, lflag);
+ if (rc == OK_EXIT)
+ {
+ if (feof(fp1))
+ {
+ if (!feof(fp2))
+ rc = eofmsg(pCtx, file1, byte, line, sflag, lflag);
+ }
+ else if (feof(fp2))
+ rc = eofmsg(pCtx, file2, byte, line, sflag, lflag);
+ }
+
+ fclose(fp2);
+ }
+ else
+ {
+ rc = err(pCtx, ERR_EXIT, "%s", file2);
+ close(fd2dup);
+ }
+ }
+ else
+ rc = err(pCtx, ERR_EXIT, "%s", file2);
+
+ fclose(fp1);
+ return rc;
+}
+
+
+#ifdef CMP_USE_MMAP
+/**
+ * Compare two files using mmap.
+ */
+static int
+c_regular(PKMKBUILTINCTX pCtx, int fd1, const char *file1, off_t skip1, off_t len1,
+ int fd2, const char *file2, off_t skip2, off_t len2, int sflag, int lflag)
+{
+ unsigned char ch, *p1, *p2, *b1, *b2;
+ off_t byte, length, line;
+ int dfound;
+ size_t blk_sz, blk_cnt;
+
+ if (sflag && len1 != len2)
+ return DIFF_EXIT;
+
+ if (skip1 > len1)
+ return eofmsg(pCtx, file1, len1 + 1, 0, sflag, lflag);
+ len1 -= skip1;
+ if (skip2 > len2)
+ return eofmsg(pCtx, file2, len2 + 1, 0, sflag, lflag);
+ len2 -= skip2;
+
+ byte = line = 1;
+ dfound = 0;
+ length = len1 <= len2 ? len1 : len2;
+ for (blk_sz = 1024 * 1024; length != 0; length -= blk_sz)
+ {
+ if (blk_sz > length)
+ blk_sz = length;
+ b1 = p1 = mmap(NULL, blk_sz, PROT_READ, MAP_FILE | MAP_SHARED, fd1, skip1);
+ if (p1 == MAP_FAILED)
+ goto l_mmap_failed;
+
+ b2 = p2 = mmap(NULL, blk_sz, PROT_READ, MAP_FILE | MAP_SHARED, fd2, skip2);
+ if (p2 == MAP_FAILED)
+ {
+ munmap(p1, blk_sz);
+ goto l_mmap_failed;
+ }
+
+ blk_cnt = blk_sz;
+ for (; blk_cnt--; ++p1, ++p2, ++byte)
+ {
+ if ((ch = *p1) != *p2)
+ {
+ if (!lflag)
+ {
+ munmap(b1, blk_sz);
+ munmap(b2, blk_sz);
+ return diffmsg(pCtx, file1, file2, byte, line, sflag);
+ }
+ dfound = 1;
+#ifdef _MSC_VER
+ kmk_builtin_ctx_printf(pCtx, 0, "%6I64d %3o %3o\n", (__int64)byte, ch, *p2);
+#else
+ kmk_builtin_ctx_printf(pCtx, 0, "%6lld %3o %3o\n", (long long)byte, ch, *p2);
+#endif
+ }
+ if (ch == '\n')
+ ++line;
+ }
+ munmap(p1 - blk_sz, blk_sz);
+ munmap(p2 - blk_sz, blk_sz);
+ skip1 += blk_sz;
+ skip2 += blk_sz;
+ }
+
+ if (len1 != len2)
+ return eofmsg(pCtx, len1 > len2 ? file2 : file1, byte, line, sflag, lflag);
+ if (dfound)
+ return DIFF_EXIT;
+ return OK_EXIT;
+
+l_mmap_failed:
+ return c_special(pCtx, fd1, file1, skip1, fd2, file2, skip2, lflag, sflag);
+}
+
+#else /* non-mmap c_regular: */
+
+/**
+ * Compare two files without mmaping them.
+ */
+static int
+c_regular(PKMKBUILTINCTX pCtx, int fd1, const char *file1, off_t skip1, off_t len1,
+ int fd2, const char *file2, off_t skip2, off_t len2, int sflag, int lflag)
+{
+ unsigned char ch, *p1, *p2, *b1 = 0, *b2 = 0;
+ off_t byte, length, line, bytes_read;
+ int dfound;
+ size_t blk_sz, blk_cnt;
+
+ if (sflag && len1 != len2)
+ return DIFF_EXIT;
+
+ if (skip1 > len1)
+ return eofmsg(pCtx, file1, len1 + 1, 0, sflag, lflag);
+ len1 -= skip1;
+ if (skip2 > len2)
+ return eofmsg(pCtx, file2, len2 + 1, 0, sflag, lflag);
+ len2 -= skip2;
+
+ if (skip1 && lseek(fd1, skip1, SEEK_SET) < 0)
+ goto l_special;
+ if (skip2 && lseek(fd2, skip2, SEEK_SET) < 0)
+ {
+ if (skip1 && lseek(fd1, 0, SEEK_SET) < 0)
+ return err(pCtx, 1, "seek failed");
+ goto l_special;
+ }
+
+#define CMP_BUF_SIZE (128*1024)
+
+ b1 = malloc(CMP_BUF_SIZE);
+ b2 = malloc(CMP_BUF_SIZE);
+ if (!b1 || !b2)
+ goto l_malloc_failed;
+
+ byte = line = 1;
+ dfound = 0;
+ length = len1;
+ if (length > len2)
+ length = len2;
+ for (blk_sz = CMP_BUF_SIZE; length != 0; length -= blk_sz)
+ {
+ if ((off_t)blk_sz > length)
+ blk_sz = (size_t)length;
+
+ bytes_read = read(fd1, b1, blk_sz);
+ if (bytes_read != (off_t)blk_sz)
+ goto l_read_error;
+
+ bytes_read = read(fd2, b2, blk_sz);
+ if (bytes_read != (off_t)blk_sz)
+ goto l_read_error;
+
+ blk_cnt = blk_sz;
+ p1 = b1;
+ p2 = b2;
+ for (; blk_cnt--; ++p1, ++p2, ++byte)
+ {
+ if ((ch = *p1) != *p2)
+ {
+ if (!lflag)
+ {
+ free(b1);
+ free(b2);
+ return diffmsg(pCtx, file1, file2, byte, line, sflag);
+ }
+ dfound = 1;
+#ifdef _MSC_VER
+ kmk_builtin_ctx_printf(pCtx, 0, "%6I64d %3o %3o\n", (__int64)byte, ch, *p2);
+#else
+ kmk_builtin_ctx_printf(pCtx, 0, "%6lld %3o %3o\n", (long long)byte, ch, *p2);
+#endif
+ }
+ if (ch == '\n')
+ ++line;
+ }
+ skip1 += blk_sz;
+ skip2 += blk_sz;
+ }
+
+ if (len1 != len2)
+ return eofmsg(pCtx, len1 > len2 ? file2 : file1, byte, line, sflag, lflag);
+ if (dfound)
+ return DIFF_EXIT;
+ return OK_EXIT;
+
+l_read_error:
+ if ( lseek(fd1, 0, SEEK_SET) < 0
+ || lseek(fd2, 0, SEEK_SET) < 0)
+ {
+ err(pCtx, 1, "seek failed");
+ free(b1);
+ free(b2);
+ return 1;
+ }
+l_malloc_failed:
+ free(b1);
+ free(b2);
+l_special:
+ return c_special(pCtx, fd1, file1, skip1, fd2, file2, skip2, lflag, sflag);
+}
+#endif /* non-mmap c_regular */
+
+
+/**
+ * Compares two open files.
+ */
+int
+cmp_fd_and_fd_ex(PKMKBUILTINCTX pCtx, int fd1, const char *file1, off_t skip1,
+ int fd2, const char *file2, off_t skip2,
+ int sflag, int lflag, int special)
+{
+ struct stat st1, st2;
+ int rc;
+
+ if (fstat(fd1, &st1))
+ return err(pCtx, ERR_EXIT, "%s", file1);
+ if (fstat(fd2, &st2))
+ return err(pCtx, ERR_EXIT, "%s", file2);
+
+ if ( !S_ISREG(st1.st_mode)
+ || !S_ISREG(st2.st_mode)
+ || special)
+ rc = c_special(pCtx, fd1, file1, skip1,
+ fd2, file2, skip2, sflag, lflag);
+ else
+ rc = c_regular(pCtx, fd1, file1, skip1, st1.st_size,
+ fd2, file2, skip2, st2.st_size, sflag, lflag);
+ return rc;
+}
+
+
+/**
+ * Compares two open files.
+ */
+int
+cmp_fd_and_fd(PKMKBUILTINCTX pCtx, int fd1, const char *file1,
+ int fd2, const char *file2,
+ int sflag, int lflag, int special)
+{
+ return cmp_fd_and_fd_ex(pCtx, fd1, file1, 0, fd2, file2, 0, sflag, lflag, special);
+}
+
+
+/**
+ * Compares an open file with another that isn't open yet.
+ */
+int
+cmp_fd_and_file_ex(PKMKBUILTINCTX pCtx, int fd1, const char *file1, off_t skip1,
+ const char *file2, off_t skip2,
+ int sflag, int lflag, int special)
+{
+ int rc;
+ int fd2;
+
+ if (!strcmp(file2, "-"))
+ {
+ fd2 = 0 /* stdin */;
+ special = 1;
+ file2 = "stdin";
+ }
+ else
+ fd2 = open(file2, O_RDONLY | O_BINARY | KMK_OPEN_NO_INHERIT, 0);
+ if (fd2 >= 0)
+ {
+ rc = cmp_fd_and_fd_ex(pCtx, fd1, file1, skip1,
+ fd2, file2, skip2, sflag, lflag, special);
+ close(fd2);
+ }
+ else
+ {
+ if (!sflag)
+ warn(pCtx, "%s", file2);
+ rc = ERR_EXIT;
+ }
+ return rc;
+}
+
+
+/**
+ * Compares an open file with another that isn't open yet.
+ */
+int
+cmp_fd_and_file(PKMKBUILTINCTX pCtx, int fd1, const char *file1,
+ const char *file2,
+ int sflag, int lflag, int special)
+{
+ return cmp_fd_and_file_ex(pCtx, fd1, file1, 0,
+ file2, 0, sflag, lflag, special);
+}
+
+
+/**
+ * Opens and compare two files.
+ */
+int
+cmp_file_and_file_ex(PKMKBUILTINCTX pCtx, const char *file1, off_t skip1,
+ const char *file2, off_t skip2,
+ int sflag, int lflag, int special)
+{
+ int fd1;
+ int rc;
+
+ if (lflag && sflag)
+ return errx(pCtx, ERR_EXIT, "only one of -l and -s may be specified");
+
+ if (!strcmp(file1, "-"))
+ {
+ if (!strcmp(file2, "-"))
+ return errx(pCtx, ERR_EXIT, "standard input may only be specified once");
+ file1 = "stdin";
+ fd1 = 1;
+ special = 1;
+ }
+ else
+ fd1 = open(file1, O_RDONLY | O_BINARY | KMK_OPEN_NO_INHERIT, 0);
+ if (fd1 >= 0)
+ {
+ rc = cmp_fd_and_file_ex(pCtx, fd1, file1, skip1,
+ file2, skip2, sflag, lflag, special);
+ close(fd1);
+ }
+ else
+ {
+ if (!sflag)
+ warn(pCtx, "%s", file1);
+ rc = ERR_EXIT;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Opens and compare two files.
+ */
+int
+cmp_file_and_file(PKMKBUILTINCTX pCtx, const char *file1, const char *file2, int sflag, int lflag, int special)
+{
+ return cmp_file_and_file_ex(pCtx, file1, 0, file2, 0, sflag, lflag, special);
+}
+
diff --git a/src/kmk/kmkbuiltin/common-env-and-cwd-opt.c b/src/kmk/kmkbuiltin/common-env-and-cwd-opt.c
new file mode 100644
index 0000000..a7f6d58
--- /dev/null
+++ b/src/kmk/kmkbuiltin/common-env-and-cwd-opt.c
@@ -0,0 +1,516 @@
+/* $Id: common-env-and-cwd-opt.c 3332 2020-04-19 23:08:16Z bird $ */
+/** @file
+ * kMk Builtin command - Commmon environment and CWD option handling code.
+ */
+
+/*
+ * Copyright (c) 2007-2016 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/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "kmkbuiltin.h"
+#include "err.h"
+
+
+/** The environment variable compare function.
+ * We must use case insensitive compare on windows (Path vs PATH). */
+#ifdef KBUILD_OS_WINDOWS
+# define KSUBMIT_ENV_NCMP _strnicmp
+#else
+# define KSUBMIT_ENV_NCMP strncmp
+#endif
+
+
+/**
+ * Duplicates a read-only enviornment vector.
+ *
+ * @returns The duplicate enviornment.
+ * @param pCtx The built-in command context.
+ * @param papszEnv The read-only vector.
+ * @param cEnvVars The number of variables.
+ * @param pcAllocatedEnvVars The allocated papszEnv size. This is zero on
+ * input and non-zero on successful return.
+ * @param cVerbosity The verbosity level.
+ */
+static char **kBuiltinOptEnvDuplicate(PKMKBUILTINCTX pCtx, char **papszEnv, unsigned cEnvVars, unsigned *pcAllocatedEnvVars,
+ int cVerbosity)
+{
+ unsigned cAllocatedEnvVars = (cEnvVars + 2 + 0xf) & ~(unsigned)0xf;
+ char **papszEnvNew = malloc(cAllocatedEnvVars * sizeof(papszEnvNew[0]));
+ assert(*pcAllocatedEnvVars == 0);
+ if (papszEnvNew)
+ {
+ unsigned i;
+ for (i = 0; i < cEnvVars; i++)
+ {
+ papszEnvNew[i] = strdup(papszEnv[i]);
+ if (!papszEnvNew)
+ {
+ while (i-- > 0)
+ free(papszEnvNew[i]);
+ free(papszEnvNew);
+ errx(pCtx, 1, "out of memory for duplicating environment variables!", i);
+ return NULL;
+ }
+ }
+ papszEnvNew[i] = NULL;
+ *pcAllocatedEnvVars = cAllocatedEnvVars;
+ }
+ else
+ errx(pCtx, 1, "out of memory for duplicating environment vector!");
+ return papszEnvNew;
+}
+
+
+/**
+ * Common worker for kBuiltinOptEnvSet and kBuiltinOptEnvAppendPrepend that adds
+ * a new variable to the environment.
+ *
+ * @returns 0 on success, non-zero exit code on error.
+ * @param pCtx The built-in command context.
+ * @param papszEnv The environment vector.
+ * @param pcEnvVars Pointer to the variable holding the number of
+ * environment variables held by @a papszEnv.
+ * @param pcAllocatedEnvVars Pointer to the variable holding max size of the
+ * environment vector.
+ * @param cVerbosity The verbosity level.
+ * @param pszValue The var=value string to apply.
+ */
+static int kBuiltinOptEnvAddVar(PKMKBUILTINCTX pCtx, char ***ppapszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars,
+ int cVerbosity, const char *pszValue)
+{
+ /* Append new variable. We probably need to resize the vector. */
+ char **papszEnv = *ppapszEnv;
+ unsigned cEnvVars = *pcEnvVars;
+ if ((cEnvVars + 2) > *pcAllocatedEnvVars)
+ {
+ *pcAllocatedEnvVars = (cEnvVars + 2 + 0xf) & ~(unsigned)0xf;
+ papszEnv = (char **)realloc(papszEnv, *pcAllocatedEnvVars * sizeof(papszEnv[0]));
+ if (!papszEnv)
+ return errx(pCtx, 1, "out of memory growing environment vector!");
+ *ppapszEnv = papszEnv;
+ }
+ papszEnv[cEnvVars] = strdup(pszValue);
+ if (!papszEnv[cEnvVars])
+ return errx(pCtx, 1, "out of memory adding environment variable!");
+ papszEnv[++cEnvVars] = NULL;
+ *pcEnvVars = cEnvVars;
+ if (cVerbosity > 0)
+ warnx(pCtx, "added '%s'", papszEnv[cEnvVars - 1]);
+ return 0;
+}
+
+
+/**
+ * Common worker for kBuiltinOptEnvSet and kBuiltinOptEnvAppendPrepend that
+ * remove duplicates.
+ *
+ * @returns 0 on success, non-zero exit code on error.
+ * @param pCtx The built-in command context.
+ * @param papszEnv The environment vector.
+ * @param cEnvVars Number of environment variables.
+ * @param cVerbosity The verbosity level.
+ * @param pszValue The var=value string to apply.
+ * @param cchVar The length of the variable part of @a pszValue.
+ * @param iEnvVar Where to start searching after.
+ */
+static int kBuiltinOptEnvRemoveDuplicates(PKMKBUILTINCTX pCtx, char **papszEnv, unsigned cEnvVars, int cVerbosity,
+ const char *pszValue, size_t cchVar, unsigned iEnvVar)
+{
+ for (iEnvVar++; iEnvVar < cEnvVars; iEnvVar++)
+ if ( KSUBMIT_ENV_NCMP(papszEnv[iEnvVar], pszValue, cchVar) == 0
+ && papszEnv[iEnvVar][cchVar] == '=')
+ {
+ if (cVerbosity > 0)
+ warnx(pCtx, "removing duplicate '%s'", papszEnv[iEnvVar]);
+ free(papszEnv[iEnvVar]);
+ cEnvVars--;
+ if (iEnvVar != cEnvVars)
+ papszEnv[iEnvVar] = papszEnv[cEnvVars];
+ papszEnv[cEnvVars] = NULL;
+ iEnvVar--;
+ }
+ return 0;
+}
+
+
+/**
+ * Handles the --set var=value option.
+ *
+ * @returns 0 on success, non-zero exit code on error.
+ * @param pCtx The built-in command context.
+ * @param ppapszEnv The environment vector pointer.
+ * @param pcEnvVars Pointer to the variable holding the number of
+ * environment variables held by @a papszEnv.
+ * @param pcAllocatedEnvVars Pointer to the variable holding max size of the
+ * environment vector.
+ * @param cVerbosity The verbosity level.
+ * @param pszValue The var=value string to apply.
+ */
+int kBuiltinOptEnvSet(PKMKBUILTINCTX pCtx, char ***ppapszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars,
+ int cVerbosity, const char *pszValue)
+{
+ const char *pszEqual = strchr(pszValue, '=');
+ if (pszEqual)
+ {
+ char **papszEnv = *ppapszEnv;
+ unsigned iEnvVar;
+ unsigned cEnvVars = *pcEnvVars;
+ size_t const cchVar = pszEqual - pszValue;
+
+ if (!*pcAllocatedEnvVars)
+ {
+ papszEnv = kBuiltinOptEnvDuplicate(pCtx, papszEnv, cEnvVars, pcAllocatedEnvVars, cVerbosity);
+ if (!papszEnv)
+ return errx(pCtx, 1, "out of memory duplicating enviornment (setenv)!");
+ *ppapszEnv = papszEnv;
+ }
+
+ for (iEnvVar = 0; iEnvVar < cEnvVars; iEnvVar++)
+ {
+ char *pszCur = papszEnv[iEnvVar];
+ if ( KSUBMIT_ENV_NCMP(pszCur, pszValue, cchVar) == 0
+ && pszCur[cchVar] == '=')
+ {
+ if (cVerbosity > 0)
+ warnx(pCtx, "replacing '%s' with '%s'", papszEnv[iEnvVar], pszValue);
+ free(papszEnv[iEnvVar]);
+ papszEnv[iEnvVar] = strdup(pszValue);
+ if (!papszEnv[iEnvVar])
+ return errx(pCtx, 1, "out of memory for modified environment variable!");
+
+ return kBuiltinOptEnvRemoveDuplicates(pCtx, papszEnv, cEnvVars, cVerbosity, pszValue, cchVar, iEnvVar);
+ }
+ }
+ return kBuiltinOptEnvAddVar(pCtx, ppapszEnv, pcEnvVars, pcAllocatedEnvVars, cVerbosity, pszValue);
+ }
+ return errx(pCtx, 1, "Missing '=': -E %s", pszValue);
+}
+
+
+/**
+ * Common worker for kBuiltinOptEnvAppend and kBuiltinOptEnvPrepend.
+ *
+ * @returns 0 on success, non-zero exit code on error.
+ * @param pCtx The built-in command context.
+ * @param ppapszEnv The environment vector pointer.
+ * @param pcEnvVars Pointer to the variable holding the number of
+ * environment variables held by @a papszEnv.
+ * @param pcAllocatedEnvVars Pointer to the variable holding max size of the
+ * environment vector.
+ * @param cVerbosity The verbosity level.
+ * @param pszValue The var=value string to apply.
+ */
+static int kBuiltinOptEnvAppendPrepend(PKMKBUILTINCTX pCtx, char ***ppapszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars,
+ int cVerbosity, const char *pszValue, int fAppend)
+{
+ const char *pszEqual = strchr(pszValue, '=');
+ if (pszEqual)
+ {
+ char **papszEnv = *ppapszEnv;
+ unsigned iEnvVar;
+ unsigned cEnvVars = *pcEnvVars;
+ size_t const cchVar = pszEqual - pszValue;
+
+ if (!*pcAllocatedEnvVars)
+ {
+ papszEnv = kBuiltinOptEnvDuplicate(pCtx, papszEnv, cEnvVars, pcAllocatedEnvVars, cVerbosity);
+ if (!papszEnv)
+ return errx(pCtx, 1, "out of memory duplicating environment (append)!");
+ *ppapszEnv = papszEnv;
+ }
+
+ for (iEnvVar = 0; iEnvVar < cEnvVars; iEnvVar++)
+ {
+ char *pszCur = papszEnv[iEnvVar];
+ if ( KSUBMIT_ENV_NCMP(pszCur, pszValue, cchVar) == 0
+ && pszCur[cchVar] == '=')
+ {
+ size_t cchOldValue = strlen(pszCur) - cchVar - 1;
+ size_t cchNewValue = strlen(pszValue) - cchVar - 1;
+ char *pszNew = malloc(cchVar + 1 + cchOldValue + cchNewValue + 1);
+ if (!pszNew)
+ return errx(pCtx, 1, "out of memory appending to environment variable!");
+ if (fAppend)
+ {
+ memcpy(pszNew, pszCur, cchVar + 1 + cchOldValue);
+ memcpy(&pszNew[cchVar + 1 + cchOldValue], &pszValue[cchVar + 1], cchNewValue + 1);
+ }
+ else
+ {
+ memcpy(pszNew, pszCur, cchVar + 1); /* preserve variable name case */
+ memcpy(&pszNew[cchVar + 1], &pszValue[cchVar + 1], cchNewValue);
+ memcpy(&pszNew[cchVar + 1 + cchNewValue], &pszCur[cchVar + 1], cchOldValue + 1);
+ }
+
+ if (cVerbosity > 0)
+ warnx(pCtx, "replacing '%s' with '%s'", pszCur, pszNew);
+ free(pszCur);
+ papszEnv[iEnvVar] = pszNew;
+
+ return kBuiltinOptEnvRemoveDuplicates(pCtx, papszEnv, cEnvVars, cVerbosity, pszValue, cchVar, iEnvVar);
+ }
+ }
+ return kBuiltinOptEnvAddVar(pCtx, ppapszEnv, pcEnvVars, pcAllocatedEnvVars, cVerbosity, pszValue);
+ }
+ return errx(pCtx, 1, "Missing '=': -%c %s", fAppend ? 'A' : 'D', pszValue);
+}
+
+
+/**
+ * Handles the --append var=value option.
+ *
+ * @returns 0 on success, non-zero exit code on error.
+ * @param pCtx The built-in command context.
+ * @param ppapszEnv The environment vector pointer.
+ * @param pcEnvVars Pointer to the variable holding the number of
+ * environment variables held by @a papszEnv.
+ * @param pcAllocatedEnvVars Pointer to the variable holding max size of the
+ * environment vector.
+ * @param cVerbosity The verbosity level.
+ * @param pszValue The var=value string to apply.
+ */
+int kBuiltinOptEnvAppend(PKMKBUILTINCTX pCtx, char ***ppapszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars,
+ int cVerbosity, const char *pszValue)
+{
+ return kBuiltinOptEnvAppendPrepend(pCtx, ppapszEnv, pcEnvVars, pcAllocatedEnvVars, cVerbosity, pszValue, 1 /*fAppend*/);
+}
+
+
+/**
+ * Handles the --prepend var=value option.
+ *
+ * @returns 0 on success, non-zero exit code on error.
+ * @param pCtx The built-in command context.
+ * @param ppapszEnv The environment vector pointer.
+ * @param pcEnvVars Pointer to the variable holding the number of
+ * environment variables held by @a papszEnv.
+ * @param pcAllocatedEnvVars Pointer to the variable holding max size of the
+ * environment vector.
+ * @param cVerbosity The verbosity level.
+ * @param pszValue The var=value string to apply.
+ */
+int kBuiltinOptEnvPrepend(PKMKBUILTINCTX pCtx, char ***ppapszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars,
+ int cVerbosity, const char *pszValue)
+{
+ return kBuiltinOptEnvAppendPrepend(pCtx, ppapszEnv, pcEnvVars, pcAllocatedEnvVars, cVerbosity, pszValue, 0 /*fAppend*/);
+}
+
+
+/**
+ * Handles the --unset var option.
+ *
+ * @returns 0 on success, non-zero exit code on error.
+ * @param pCtx The built-in command context.
+ * @param ppapszEnv The environment vector pointer.
+ * @param pcEnvVars Pointer to the variable holding the number of
+ * environment variables held by @a papszEnv.
+ * @param pcAllocatedEnvVars Pointer to the size of the vector allocation.
+ * The size is zero when read-only (CRT, GNU make)
+ * environment.
+ * @param cVerbosity The verbosity level.
+ * @param pszVarToRemove The name of the variable to remove.
+ */
+int kBuiltinOptEnvUnset(PKMKBUILTINCTX pCtx, char ***ppapszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars,
+ int cVerbosity, const char *pszVarToRemove)
+{
+ if (strchr(pszVarToRemove, '=') == NULL)
+ {
+ char **papszEnv = *ppapszEnv;
+ unsigned cRemoved = 0;
+ size_t const cchVar = strlen(pszVarToRemove);
+ unsigned cEnvVars = *pcEnvVars;
+ unsigned iEnvVar;
+
+ for (iEnvVar = 0; iEnvVar < cEnvVars; iEnvVar++)
+ if ( KSUBMIT_ENV_NCMP(papszEnv[iEnvVar], pszVarToRemove, cchVar) == 0
+ && papszEnv[iEnvVar][cchVar] == '=')
+ {
+ if (cVerbosity > 0)
+ warnx(pCtx, !cRemoved ? "removing '%s'" : "removing duplicate '%s'", papszEnv[iEnvVar]);
+
+ if (!*pcAllocatedEnvVars)
+ {
+ papszEnv = kBuiltinOptEnvDuplicate(pCtx, papszEnv, cEnvVars, pcAllocatedEnvVars, cVerbosity);
+ if (!papszEnv)
+ return errx(pCtx, 1, "out of memory duplicating environment (unset)!");
+ *ppapszEnv = papszEnv;
+ }
+
+ free(papszEnv[iEnvVar]);
+ cEnvVars--;
+ if (iEnvVar != cEnvVars)
+ papszEnv[iEnvVar] = papszEnv[cEnvVars];
+ papszEnv[cEnvVars] = NULL;
+ cRemoved++;
+ iEnvVar--;
+ }
+ *pcEnvVars = cEnvVars;
+
+ if (cVerbosity > 0 && !cRemoved)
+ warnx(pCtx, "not found '%s'", pszVarToRemove);
+ }
+ else
+ return errx(pCtx, 1, "Found invalid variable name character '=' in: -U %s", pszVarToRemove);
+ return 0;
+}
+
+
+/**
+ * Handles the --zap-env & --ignore-environment options.
+ *
+ * @returns 0 on success, non-zero exit code on error.
+ * @param pCtx The built-in command context.
+ * @param ppapszEnv The environment vector pointer.
+ * @param pcEnvVars Pointer to the variable holding the number of
+ * environment variables held by @a papszEnv.
+ * @param pcAllocatedEnvVars Pointer to the size of the vector allocation.
+ * The size is zero when read-only (CRT, GNU make)
+ * environment.
+ * @param cVerbosity The verbosity level.
+ */
+int kBuiltinOptEnvZap(PKMKBUILTINCTX pCtx, char ***ppapszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars, int cVerbosity)
+{
+ if (*pcAllocatedEnvVars > 0)
+ {
+ char **papszEnv = *ppapszEnv;
+ unsigned i = *pcEnvVars;
+ while (i-- > 0)
+ {
+ free(papszEnv[i]);
+ papszEnv[i] = NULL;
+ }
+ }
+ else
+ {
+ char **papszEnv = calloc(4, sizeof(char *));
+ if (!papszEnv)
+ return err(pCtx, 1, "out of memory!");
+ *ppapszEnv = papszEnv;
+ *pcAllocatedEnvVars = 4;
+ }
+ *pcEnvVars = 0;
+ return 0;
+}
+
+
+/**
+ * Cleans up afterwards, if necessary.
+ *
+ * @param ppapszEnv The environment vector pointer.
+ * @param cEnvVars The number of variables in the vector.
+ * @param pcAllocatedEnvVars Pointer to the size of the vector allocation.
+ * The size is zero when read-only (CRT, GNU make)
+ * environment.
+ */
+void kBuiltinOptEnvCleanup(char ***ppapszEnv, unsigned cEnvVars, unsigned *pcAllocatedEnvVars)
+{
+ char **papszEnv = *ppapszEnv;
+ *ppapszEnv = NULL;
+ if (*pcAllocatedEnvVars > 0)
+ {
+ *pcAllocatedEnvVars = 0;
+ while (cEnvVars-- > 0)
+ {
+ free(papszEnv[cEnvVars]);
+ papszEnv[cEnvVars] = NULL;
+ }
+ free(papszEnv);
+ }
+}
+
+
+/**
+ * Handles the --chdir dir option.
+ *
+ * @returns 0 on success, non-zero exit code on error.
+ * @param pCtx The built-in command context.
+ * @param pszCwd The CWD buffer. Contains current CWD on input,
+ * modified by @a pszValue on output.
+ * @param cbCwdBuf The size of the CWD buffer.
+ * @param pszValue The --chdir value to apply.
+ */
+int kBuiltinOptChDir(PKMKBUILTINCTX pCtx, char *pszCwd, size_t cbCwdBuf, const char *pszValue)
+{
+ size_t cchNewCwd = strlen(pszValue);
+ size_t offDst;
+ if (cchNewCwd)
+ {
+#ifdef HAVE_DOS_PATHS
+ if (*pszValue == '/' || *pszValue == '\\')
+ {
+ if (pszValue[1] == '/' || pszValue[1] == '\\')
+ offDst = 0; /* UNC */
+ else if (pszCwd[1] == ':' && isalpha(pszCwd[0]))
+ offDst = 2; /* Take drive letter from CWD. */
+ else
+ return errx(pCtx, 1, "UNC relative CWD not implemented: cur='%s' new='%s'", pszCwd, pszValue);
+ }
+ else if ( pszValue[1] == ':'
+ && isalpha(pszValue[0]))
+ {
+ if (pszValue[2] == '/'|| pszValue[2] == '\\')
+ offDst = 0; /* DOS style absolute path. */
+ else if ( pszCwd[1] == ':'
+ && tolower(pszCwd[0]) == tolower(pszValue[0]) )
+ {
+ pszValue += 2; /* Same drive as CWD, append drive relative path from value. */
+ cchNewCwd -= 2;
+ offDst = strlen(pszCwd);
+ }
+ else
+ {
+ /* Get current CWD on the specified drive and append value. */
+ int iDrive = tolower(pszValue[0]) - 'a' + 1;
+ if (!_getdcwd(iDrive, pszCwd, cbCwdBuf))
+ return err(pCtx, 1, "_getdcwd(%d,,) failed", iDrive);
+ pszValue += 2;
+ cchNewCwd -= 2;
+ }
+ }
+#else
+ if (*pszValue == '/')
+ offDst = 0;
+#endif
+ else
+ offDst = strlen(pszCwd); /* Relative path, append to the existing CWD value. */
+
+ /* Do the copying. */
+#ifdef HAVE_DOS_PATHS
+ if (offDst > 0 && pszCwd[offDst - 1] != '/' && pszCwd[offDst - 1] != '\\')
+#else
+ if (offDst > 0 && pszCwd[offDst - 1] != '/')
+#endif
+ pszCwd[offDst++] = '/';
+ if (offDst + cchNewCwd >= cbCwdBuf)
+ return errx(pCtx, 1, "Too long CWD: %*.*s%s", offDst, offDst, pszCwd, pszValue);
+ memcpy(&pszCwd[offDst], pszValue, cchNewCwd + 1);
+ }
+ /* else: relative, no change - quitely ignore. */
+ return 0;
+}
+
diff --git a/src/kmk/kmkbuiltin/cp.c b/src/kmk/kmkbuiltin/cp.c
new file mode 100644
index 0000000..6719fc2
--- /dev/null
+++ b/src/kmk/kmkbuiltin/cp.c
@@ -0,0 +1,779 @@
+/*
+ * Copyright (c) 1988, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * David Hitz of Auspex Systems Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static char const copyright[] =
+"@(#) Copyright (c) 1988, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)cp.c 8.2 (Berkeley) 4/1/94";
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/bin/cp/cp.c,v 1.50 2004/04/06 20:06:44 markm Exp $");
+#endif
+
+/*
+ * Cp copies source files to target files.
+ *
+ * The global PATH_T structure "to" always contains the path to the
+ * current target file. Since fts(3) does not change directories,
+ * this path can be either absolute or dot-relative.
+ *
+ * The basic algorithm is to initialize "to" and use fts(3) to traverse
+ * the file hierarchy rooted in the argument list. A trivial case is the
+ * case of 'cp file1 file2'. The more interesting case is the case of
+ * 'cp file1 file2 ... fileN dir' where the hierarchy is traversed and the
+ * path (relative to the root of the traversal) is appended to dir (stored
+ * in "to") to form the final target path.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define FAKES_NO_GETOPT_H /* bird */
+#include "config.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include "err.h"
+#include <errno.h>
+#include "fts.h"
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "getopt_r.h"
+#include "k/kDefs.h"
+#ifdef _MSC_VER
+# include "mscfakes.h"
+#endif
+#include "cp_extern.h"
+#include "kmkbuiltin.h"
+#include "kbuild_protection.h"
+
+#if defined(_MSC_VER) || defined(__gnu_linux__) || defined(__linux__)
+extern size_t strlcpy(char *, const char *, size_t);
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifndef S_IFWHT
+#define S_IFWHT 0
+#define S_ISWHT(s) 0
+#define undelete(s) (-1)
+#endif
+
+#ifndef S_ISTXT
+#ifdef S_ISVTX
+#define S_ISTXT S_ISVTX
+#else
+#define S_ISTXT 0
+#endif
+#endif /* !S_ISTXT */
+
+#ifndef __unused
+# define __unused
+#endif
+
+#if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
+# define IS_SLASH(ch) ((ch) == '/' || (ch) == '\\')
+#else
+# define IS_SLASH(ch) ((ch) == '/')
+#endif
+
+#define STRIP_TRAILING_SLASH(p) { \
+ while ((p).p_end > (p).p_path + 1 && IS_SLASH((p).p_end[-1])) \
+ *--(p).p_end = 0; \
+}
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct CPINSTANCE
+{
+ CPUTILSINSTANCE Utils;
+ int Rflag, rflag;
+ int cp_ignore_non_existing, cp_changed_only;
+ KBUILDPROTECTION g_ProtData;
+} CPINSTANCE;
+
+/* have wrappers for globals in cp_extern! */
+
+
+enum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE };
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+enum cp_arg {
+ CP_OPT_HELP = 261,
+ CP_OPT_VERSION,
+ CP_OPT_IGNORE_NON_EXISTING,
+ CP_OPT_CHANGED,
+ CP_OPT_DISABLE_PROTECTION,
+ CP_OPT_ENABLE_PROTECTION,
+ CP_OPT_ENABLE_FULL_PROTECTION,
+ CP_OPT_DISABLE_FULL_PROTECTION,
+ CP_OPT_PROTECTION_DEPTH
+};
+
+static struct option long_options[] =
+{
+ { "help", no_argument, 0, CP_OPT_HELP },
+ { "version", no_argument, 0, CP_OPT_VERSION },
+ { "ignore-non-existing", no_argument, 0, CP_OPT_IGNORE_NON_EXISTING },
+ { "changed", no_argument, 0, CP_OPT_CHANGED },
+ { "disable-protection", no_argument, 0, CP_OPT_DISABLE_PROTECTION },
+ { "enable-protection", no_argument, 0, CP_OPT_ENABLE_PROTECTION },
+ { "enable-full-protection", no_argument, 0, CP_OPT_ENABLE_FULL_PROTECTION },
+ { "disable-full-protection", no_argument, 0, CP_OPT_DISABLE_FULL_PROTECTION },
+ { "protection-depth", required_argument, 0, CP_OPT_PROTECTION_DEPTH },
+ { 0, 0, 0, 0 },
+};
+
+static char emptystring[] = "";
+
+#if defined(SIGINFO) && defined(KMK_BUILTIN_STANDALONE)
+volatile sig_atomic_t g_cp_info;
+#endif
+
+extern mode_t g_fUMask;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int copy(CPINSTANCE *pThis, char * const *, enum op, int);
+#ifdef FTSCALL
+static int FTSCALL mastercmp(const FTSENT * const *, const FTSENT * const *);
+#else
+static int mastercmp(const FTSENT **, const FTSENT **);
+#endif
+#if defined(SIGINFO) && defined(KMK_BUILTIN_STANDALONE)
+static void siginfo(int __unused);
+#endif
+static int usage(PKMKBUILTINCTX, int);
+
+int
+kmk_builtin_cp(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ CPINSTANCE This;
+ struct getopt_state_r gos;
+ struct stat to_stat, tmp_stat;
+ enum op type;
+ int Hflag, Lflag, Pflag, ch, fts_options, r, have_trailing_slash, rc;
+ char *target;
+
+ /* init globals */
+ This.Utils.pCtx = pCtx;
+ This.Utils.to.p_end = This.Utils.to.p_path;
+ This.Utils.to.target_end = emptystring;
+ memset(This.Utils.to.p_path, 0, sizeof(This.Utils.to.p_path));
+ This.Utils.fflag = 0;
+ This.Utils.iflag = 0;
+ This.Utils.nflag = 0;
+ This.Utils.pflag = 0;
+ This.Utils.vflag = 0;
+ This.Rflag = 0;
+ This.rflag = 0;
+ This.cp_ignore_non_existing = This.cp_changed_only = 0;
+ kBuildProtectionInit(&This.g_ProtData, pCtx);
+
+ Hflag = Lflag = Pflag = 0;
+ getopt_initialize_r(&gos, argc, argv, "HLPRfinprv", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1)
+ switch (ch) {
+ case 'H':
+ Hflag = 1;
+ Lflag = Pflag = 0;
+ break;
+ case 'L':
+ Lflag = 1;
+ Hflag = Pflag = 0;
+ break;
+ case 'P':
+ Pflag = 1;
+ Hflag = Lflag = 0;
+ break;
+ case 'R':
+ This.Rflag = 1;
+ break;
+ case 'f':
+ This.Utils.fflag = 1;
+ This.Utils.iflag = This.Utils.nflag = 0;
+ break;
+ case 'i':
+ This.Utils.iflag = 1;
+ This.Utils.fflag = This.Utils.nflag = 0;
+ break;
+ case 'n':
+ This.Utils.nflag = 1;
+ This.Utils.fflag = This.Utils.iflag = 0;
+ break;
+ case 'p':
+ This.Utils.pflag = 1;
+ break;
+ case 'r':
+ This.rflag = 1;
+ break;
+ case 'v':
+ This.Utils.vflag = 1;
+ break;
+ case CP_OPT_HELP:
+ usage(pCtx, 0);
+ kBuildProtectionTerm(&This.g_ProtData);
+ return 0;
+ case CP_OPT_VERSION:
+ kBuildProtectionTerm(&This.g_ProtData);
+ return kbuild_version(argv[0]);
+ case CP_OPT_IGNORE_NON_EXISTING:
+ This.cp_ignore_non_existing = 1;
+ break;
+ case CP_OPT_CHANGED:
+ This.cp_changed_only = 1;
+ break;
+ case CP_OPT_DISABLE_PROTECTION:
+ kBuildProtectionDisable(&This.g_ProtData, KBUILDPROTECTIONTYPE_RECURSIVE);
+ break;
+ case CP_OPT_ENABLE_PROTECTION:
+ kBuildProtectionEnable(&This.g_ProtData, KBUILDPROTECTIONTYPE_RECURSIVE);
+ break;
+ case CP_OPT_ENABLE_FULL_PROTECTION:
+ kBuildProtectionEnable(&This.g_ProtData, KBUILDPROTECTIONTYPE_FULL);
+ break;
+ case CP_OPT_DISABLE_FULL_PROTECTION:
+ kBuildProtectionDisable(&This.g_ProtData, KBUILDPROTECTIONTYPE_FULL);
+ break;
+ case CP_OPT_PROTECTION_DEPTH:
+ if (kBuildProtectionSetDepth(&This.g_ProtData, gos.optarg)) {
+ kBuildProtectionTerm(&This.g_ProtData);
+ return 1;
+ }
+ break;
+ default:
+ kBuildProtectionTerm(&This.g_ProtData);
+ return usage(pCtx, 1);
+ }
+ argc -= gos.optind;
+ argv += gos.optind;
+
+ if (argc < 2) {
+ kBuildProtectionTerm(&This.g_ProtData);
+ return usage(pCtx, 1);
+ }
+
+ fts_options = FTS_NOCHDIR | FTS_PHYSICAL;
+ if (This.rflag) {
+ if (This.Rflag) {
+ kBuildProtectionTerm(&This.g_ProtData);
+ return errx(pCtx, 1,
+ "the -R and -r options may not be specified together.");
+ }
+ if (Hflag || Lflag || Pflag)
+ errx(pCtx, 1,
+ "the -H, -L, and -P options may not be specified with the -r option.");
+ fts_options &= ~FTS_PHYSICAL;
+ fts_options |= FTS_LOGICAL;
+ }
+ if (This.Rflag) {
+ if (Hflag)
+ fts_options |= FTS_COMFOLLOW;
+ if (Lflag) {
+ fts_options &= ~FTS_PHYSICAL;
+ fts_options |= FTS_LOGICAL;
+ }
+ } else {
+ fts_options &= ~FTS_PHYSICAL;
+ fts_options |= FTS_LOGICAL | FTS_COMFOLLOW;
+ }
+#if defined(SIGINFO) && defined(KMK_BUILTIN_STANDALONE)
+ (void)signal(SIGINFO, siginfo);
+#endif
+
+ /* Save the target base in "to". */
+ target = argv[--argc];
+ if (strlcpy(This.Utils.to.p_path, target, sizeof(This.Utils.to.p_path)) >= sizeof(This.Utils.to.p_path)) {
+ kBuildProtectionTerm(&This.g_ProtData);
+ return errx(pCtx, 1, "%s: name too long", target);
+ }
+ This.Utils.to.p_end = This.Utils.to.p_path + strlen(This.Utils.to.p_path);
+ if (This.Utils.to.p_path == This.Utils.to.p_end) {
+ *This.Utils.to.p_end++ = '.';
+ *This.Utils.to.p_end = 0;
+ }
+ have_trailing_slash = IS_SLASH(This.Utils.to.p_end[-1]);
+ if (have_trailing_slash)
+ STRIP_TRAILING_SLASH(This.Utils.to);
+ This.Utils.to.target_end = This.Utils.to.p_end;
+
+ /* Set end of argument list for fts(3). */
+ argv[argc] = NULL;
+
+ /*
+ * Cp has two distinct cases:
+ *
+ * cp [-R] source target
+ * cp [-R] source1 ... sourceN directory
+ *
+ * In both cases, source can be either a file or a directory.
+ *
+ * In (1), the target becomes a copy of the source. That is, if the
+ * source is a file, the target will be a file, and likewise for
+ * directories.
+ *
+ * In (2), the real target is not directory, but "directory/source".
+ */
+ r = stat(This.Utils.to.p_path, &to_stat);
+ if (r == -1 && errno != ENOENT) {
+ kBuildProtectionTerm(&This.g_ProtData);
+ return err(pCtx, 1, "stat: %s", This.Utils.to.p_path);
+ }
+ if (r == -1 || !S_ISDIR(to_stat.st_mode)) {
+ /*
+ * Case (1). Target is not a directory.
+ */
+ if (argc > 1) {
+ kBuildProtectionTerm(&This.g_ProtData);
+ return usage(pCtx, 1);
+ }
+ /*
+ * Need to detect the case:
+ * cp -R dir foo
+ * Where dir is a directory and foo does not exist, where
+ * we want pathname concatenations turned on but not for
+ * the initial mkdir().
+ */
+ if (r == -1) {
+ if (This.rflag || (This.Rflag && (Lflag || Hflag)))
+ stat(*argv, &tmp_stat);
+ else
+ lstat(*argv, &tmp_stat);
+
+ if (S_ISDIR(tmp_stat.st_mode) && (This.Rflag || This.rflag))
+ type = DIR_TO_DNE;
+ else
+ type = FILE_TO_FILE;
+ } else
+ type = FILE_TO_FILE;
+
+ if (have_trailing_slash && type == FILE_TO_FILE) {
+ kBuildProtectionTerm(&This.g_ProtData);
+ if (r == -1)
+ return errx(pCtx, 1, "directory %s does not exist",
+ This.Utils.to.p_path);
+ else
+ return errx(pCtx, 1, "%s is not a directory", This.Utils.to.p_path);
+ }
+ } else
+ /*
+ * Case (2). Target is a directory.
+ */
+ type = FILE_TO_DIR;
+
+ /* Finally, check that the "to" directory isn't protected. */
+ rc = 1;
+ if (!kBuildProtectionScanEnv(&This.g_ProtData, envp, "KMK_CP_")
+ && !kBuildProtectionEnforce(&This.g_ProtData,
+ This.Rflag || This.rflag
+ ? KBUILDPROTECTIONTYPE_RECURSIVE
+ : KBUILDPROTECTIONTYPE_FULL,
+ This.Utils.to.p_path)) {
+ rc = copy(&This, argv, type, fts_options);
+ }
+
+ kBuildProtectionTerm(&This.g_ProtData);
+ return rc;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+mode_t g_fUMask;
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_cp", NULL };
+ umask(g_fUMask = umask(0077));
+ return kmk_builtin_cp(argc, argv, envp, &Ctx);
+}
+#endif
+
+static int
+copy(CPINSTANCE *pThis, char * const *argv, enum op type, int fts_options)
+{
+ struct stat to_stat;
+ FTS *ftsp;
+ FTSENT *curr;
+ int base = 0, dne, badcp, rval;
+ size_t nlen;
+ char *p, *target_mid;
+ mode_t mask, mode;
+
+ /*
+ * Keep an inverted copy of the umask, for use in correcting
+ * permissions on created directories when not using -p.
+ */
+ mask = g_fUMask;
+ assert(mask == umask(mask));
+ mask = ~mask;
+
+ if ((ftsp = fts_open(argv, fts_options, mastercmp)) == NULL)
+ return err(pThis->Utils.pCtx, 1, "fts_open");
+ for (badcp = rval = 0; (curr = fts_read(ftsp)) != NULL; badcp = 0) {
+ int copied = 0;
+
+ switch (curr->fts_info) {
+ case FTS_NS:
+ if ( pThis->cp_ignore_non_existing
+ && curr->fts_errno == ENOENT) {
+ if (pThis->Utils.vflag) {
+ warnx(pThis->Utils.pCtx, "fts: %s: %s", curr->fts_path,
+ strerror(curr->fts_errno));
+ }
+ continue;
+ }
+ /* fall thru */
+ case FTS_DNR:
+ case FTS_ERR:
+ warnx(pThis->Utils.pCtx, "fts: %s: %s",
+ curr->fts_path, strerror(curr->fts_errno));
+ badcp = rval = 1;
+ continue;
+ case FTS_DC: /* Warn, continue. */
+ warnx(pThis->Utils.pCtx, "%s: directory causes a cycle", curr->fts_path);
+ badcp = rval = 1;
+ continue;
+ default:
+ ;
+ }
+
+ /*
+ * If we are in case (2) or (3) above, we need to append the
+ * source name to the target name.
+ */
+ if (type != FILE_TO_FILE) {
+ /*
+ * Need to remember the roots of traversals to create
+ * correct pathnames. If there's a directory being
+ * copied to a non-existent directory, e.g.
+ * cp -R a/dir noexist
+ * the resulting path name should be noexist/foo, not
+ * noexist/dir/foo (where foo is a file in dir), which
+ * is the case where the target exists.
+ *
+ * Also, check for "..". This is for correct path
+ * concatenation for paths ending in "..", e.g.
+ * cp -R .. /tmp
+ * Paths ending in ".." are changed to ".". This is
+ * tricky, but seems the easiest way to fix the problem.
+ *
+ * XXX
+ * Since the first level MUST be FTS_ROOTLEVEL, base
+ * is always initialized.
+ */
+ if (curr->fts_level == FTS_ROOTLEVEL) {
+ if (type != DIR_TO_DNE) {
+ p = strrchr(curr->fts_path, '/');
+#if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
+ if (strrchr(curr->fts_path, '\\') > p)
+ p = strrchr(curr->fts_path, '\\');
+#endif
+ base = (p == NULL) ? 0 :
+ (int)(p - curr->fts_path + 1);
+
+ if (!strcmp(&curr->fts_path[base],
+ ".."))
+ base += 1;
+ } else
+ base = curr->fts_pathlen;
+ }
+
+ p = &curr->fts_path[base];
+ nlen = curr->fts_pathlen - base;
+ target_mid = pThis->Utils.to.target_end;
+ if (!IS_SLASH(*p) && !IS_SLASH(target_mid[-1]))
+ *target_mid++ = '/';
+ *target_mid = 0;
+ if (target_mid - pThis->Utils.to.p_path + nlen >= PATH_MAX) {
+ warnx(pThis->Utils.pCtx, "%s%s: name too long (not copied)",
+ pThis->Utils.to.p_path, p);
+ badcp = rval = 1;
+ continue;
+ }
+ (void)strncat(target_mid, p, nlen);
+ pThis->Utils.to.p_end = target_mid + nlen;
+ *pThis->Utils.to.p_end = 0;
+ STRIP_TRAILING_SLASH(pThis->Utils.to);
+ }
+
+ if (curr->fts_info == FTS_DP) {
+ /*
+ * We are nearly finished with this directory. If we
+ * didn't actually copy it, or otherwise don't need to
+ * change its attributes, then we are done.
+ */
+ if (!curr->fts_number)
+ continue;
+ /*
+ * If -p is in effect, set all the attributes.
+ * Otherwise, set the correct permissions, limited
+ * by the umask. Optimise by avoiding a chmod()
+ * if possible (which is usually the case if we
+ * made the directory). Note that mkdir() does not
+ * honour setuid, setgid and sticky bits, but we
+ * normally want to preserve them on directories.
+ */
+ if (pThis->Utils.pflag) {
+ if (copy_file_attribs(&pThis->Utils, curr->fts_statp, -1))
+ rval = 1;
+ } else {
+ mode = curr->fts_statp->st_mode;
+ if ((mode & (S_ISUID | S_ISGID | S_ISTXT)) ||
+ ((mode | S_IRWXU) & mask) != (mode & mask))
+ if (chmod(pThis->Utils.to.p_path, mode & mask) != 0){
+ warn(pThis->Utils.pCtx, "chmod: %s", pThis->Utils.to.p_path);
+ rval = 1;
+ }
+ }
+ continue;
+ }
+
+ /* Not an error but need to remember it happened */
+ if (stat(pThis->Utils.to.p_path, &to_stat) == -1)
+ dne = 1;
+ else {
+ if (to_stat.st_dev == curr->fts_statp->st_dev &&
+ to_stat.st_dev != 0 &&
+ to_stat.st_ino == curr->fts_statp->st_ino &&
+ to_stat.st_ino != 0) {
+ warnx(pThis->Utils.pCtx, "%s and %s are identical (not copied).",
+ pThis->Utils.to.p_path, curr->fts_path);
+ badcp = rval = 1;
+ if (S_ISDIR(curr->fts_statp->st_mode))
+ (void)fts_set(ftsp, curr, FTS_SKIP);
+ continue;
+ }
+ if (!S_ISDIR(curr->fts_statp->st_mode) &&
+ S_ISDIR(to_stat.st_mode)) {
+ warnx(pThis->Utils.pCtx, "cannot overwrite directory %s with "
+ "non-directory %s",
+ pThis->Utils.to.p_path, curr->fts_path);
+ badcp = rval = 1;
+ continue;
+ }
+ dne = 0;
+ }
+
+ switch (curr->fts_statp->st_mode & S_IFMT) {
+#ifdef S_IFLNK
+ case S_IFLNK:
+ /* Catch special case of a non-dangling symlink */
+ if ((fts_options & FTS_LOGICAL) ||
+ ((fts_options & FTS_COMFOLLOW) &&
+ curr->fts_level == 0)) {
+ if (copy_file(&pThis->Utils, curr, dne, pThis->cp_changed_only, &copied))
+ badcp = rval = 1;
+ } else {
+ if (copy_link(&pThis->Utils, curr, !dne))
+ badcp = rval = 1;
+ }
+ break;
+#endif
+ case S_IFDIR:
+ if (!pThis->Rflag && !pThis->rflag) {
+ warnx(pThis->Utils.pCtx, "%s is a directory (not copied).",
+ curr->fts_path);
+ (void)fts_set(ftsp, curr, FTS_SKIP);
+ badcp = rval = 1;
+ break;
+ }
+ /*
+ * If the directory doesn't exist, create the new
+ * one with the from file mode plus owner RWX bits,
+ * modified by the umask. Trade-off between being
+ * able to write the directory (if from directory is
+ * 555) and not causing a permissions race. If the
+ * umask blocks owner writes, we fail..
+ */
+ if (dne) {
+ if (mkdir(pThis->Utils.to.p_path,
+ curr->fts_statp->st_mode | S_IRWXU) < 0)
+ return err(pThis->Utils.pCtx, 1, "mkdir: %s", pThis->Utils.to.p_path);
+ } else if (!S_ISDIR(to_stat.st_mode)) {
+ errno = ENOTDIR;
+ return err(pThis->Utils.pCtx, 1, "to-mode: %s", pThis->Utils.to.p_path);
+ }
+ /*
+ * Arrange to correct directory attributes later
+ * (in the post-order phase) if this is a new
+ * directory, or if the -p flag is in effect.
+ */
+ curr->fts_number = pThis->Utils.pflag || dne;
+ break;
+#ifdef S_IFBLK
+ case S_IFBLK:
+#endif
+ case S_IFCHR:
+ if (pThis->Rflag) {
+ if (copy_special(&pThis->Utils, curr->fts_statp, !dne))
+ badcp = rval = 1;
+ } else {
+ if (copy_file(&pThis->Utils, curr, dne, pThis->cp_changed_only, &copied))
+ badcp = rval = 1;
+ }
+ break;
+#ifdef S_IFIFO
+ case S_IFIFO:
+#endif
+ if (pThis->Rflag) {
+ if (copy_fifo(&pThis->Utils, curr->fts_statp, !dne))
+ badcp = rval = 1;
+ } else {
+ if (copy_file(&pThis->Utils, curr, dne, pThis->cp_changed_only, &copied))
+ badcp = rval = 1;
+ }
+ break;
+ default:
+ if (copy_file(&pThis->Utils, curr, dne, pThis->cp_changed_only, &copied))
+ badcp = rval = 1;
+ break;
+ }
+ if (pThis->Utils.vflag && !badcp)
+ kmk_builtin_ctx_printf(pThis->Utils.pCtx, 0, copied ? "%s -> %s\n" : "%s matches %s - not copied\n",
+ curr->fts_path, pThis->Utils.to.p_path);
+ }
+ if (errno)
+ return err(pThis->Utils.pCtx, 1, "fts_read");
+ return (rval);
+}
+
+/*
+ * mastercmp --
+ * The comparison function for the copy order. The order is to copy
+ * non-directory files before directory files. The reason for this
+ * is because files tend to be in the same cylinder group as their
+ * parent directory, whereas directories tend not to be. Copying the
+ * files first reduces seeking.
+ */
+#ifdef FTSCALL
+static int FTSCALL mastercmp(const FTSENT * const *a, const FTSENT * const *b)
+#else
+static int mastercmp(const FTSENT **a, const FTSENT **b)
+#endif
+{
+ int a_info, b_info;
+
+ a_info = (*a)->fts_info;
+ if (a_info == FTS_ERR || a_info == FTS_NS || a_info == FTS_DNR)
+ return (0);
+ b_info = (*b)->fts_info;
+ if (b_info == FTS_ERR || b_info == FTS_NS || b_info == FTS_DNR)
+ return (0);
+ if (a_info == FTS_D)
+ return (-1);
+ if (b_info == FTS_D)
+ return (1);
+ return (0);
+}
+
+#if defined(SIGINFO) && defined(KMK_BUILTIN_STANDALONE)
+static void
+siginfo(int sig __unused)
+{
+
+ g_cp_info = 1;
+}
+#endif
+
+
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+"usage: %s [options] src target\n"
+" or: %s [options] src1 ... srcN directory\n"
+" or: %s --help\n"
+" or: %s --version\n"
+"\n"
+"Options:\n"
+" -R Recursive copy.\n"
+" -H Follow symbolic links on the commandline. Only valid with -R.\n"
+" -L Follow all symbolic links. Only valid with -R.\n"
+" -P Do not follow symbolic links. Default. Only valid with -R\n"
+" -f Force. Overrides -i and -n.\n"
+" -i Iteractive. Overrides -n and -f.\n"
+" -n Don't overwrite any files. Overrides -i and -f.\n"
+" -v Verbose.\n"
+" --ignore-non-existing\n"
+" Don't fail if the specified source file doesn't exist.\n"
+" --changed\n"
+" Only copy if changed (i.e. compare first).\n"
+" --disable-protection\n"
+" Will disable the protection file protection applied with -R.\n"
+" --enable-protection\n"
+" Will enable the protection file protection applied with -R.\n"
+" --enable-full-protection\n"
+" Will enable the protection file protection for all operations.\n"
+" --disable-full-protection\n"
+" Will disable the protection file protection for all operations.\n"
+" --protection-depth\n"
+" Number or path indicating the file protection depth. Default: %d\n"
+"\n"
+"Environment:\n"
+" KMK_CP_DISABLE_PROTECTION\n"
+" Same as --disable-protection. Overrides command line.\n"
+" KMK_CP_ENABLE_PROTECTION\n"
+" Same as --enable-protection. Overrides everyone else.\n"
+" KMK_CP_ENABLE_FULL_PROTECTION\n"
+" Same as --enable-full-protection. Overrides everyone else.\n"
+" KMK_CP_DISABLE_FULL_PROTECTION\n"
+" Same as --disable-full-protection. Overrides command line.\n"
+" KMK_CP_PROTECTION_DEPTH\n"
+" Same as --protection-depth. Overrides command line.\n"
+"\n"
+"The file protection of the top %d layers of the file hierarchy is there\n"
+"to try prevent makefiles from doing bad things to your system. This\n"
+"protection is not bulletproof, but should help prevent you from shooting\n"
+"yourself in the foot.\n"
+ ,
+ pCtx->pszProgName, pCtx->pszProgName,
+ pCtx->pszProgName, pCtx->pszProgName,
+ kBuildProtectionDefaultDepth(), kBuildProtectionDefaultDepth());
+ return 1;
+}
diff --git a/src/kmk/kmkbuiltin/cp_extern.h b/src/kmk/kmkbuiltin/cp_extern.h
new file mode 100644
index 0000000..bcae631
--- /dev/null
+++ b/src/kmk/kmkbuiltin/cp_extern.h
@@ -0,0 +1,55 @@
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.2 (Berkeley) 4/1/94
+ * $FreeBSD: src/bin/cp/extern.h,v 1.19 2004/04/06 20:06:44 markm Exp $
+ */
+
+#include "kmkbuiltin.h" /* for PATH_MAX on GNU/hurd */
+
+typedef struct {
+ char *p_end; /* pointer to NULL at end of path */
+ char *target_end; /* pointer to end of target base */
+ char p_path[PATH_MAX]; /* pointer to the start of a path */
+} PATH_T;
+
+typedef struct CPUTILSINSTANCE {
+ PKMKBUILTINCTX pCtx;
+ /*extern*/ PATH_T to;
+ /*extern*/ int fflag, iflag, nflag, pflag, vflag;
+} CPUTILSINSTANCE;
+
+#if defined(SIGINFO) && defined(KMK_BUILTIN_STANDALONE)
+extern volatile sig_atomic_t g_cp_info;
+#endif
+
+int copy_fifo(CPUTILSINSTANCE *pThis, struct stat *, int);
+int copy_file(CPUTILSINSTANCE *pThis, const FTSENT *, int, int, int *);
+int copy_link(CPUTILSINSTANCE *pThis, const FTSENT *, int);
+int copy_special(CPUTILSINSTANCE *pThis, struct stat *, int);
+int copy_file_attribs(CPUTILSINSTANCE *pThis, struct stat *, int);
diff --git a/src/kmk/kmkbuiltin/cp_utils.c b/src/kmk/kmkbuiltin/cp_utils.c
new file mode 100644
index 0000000..e97e75d
--- /dev/null
+++ b/src/kmk/kmkbuiltin/cp_utils.c
@@ -0,0 +1,397 @@
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)utils.c 8.3 (Berkeley) 4/1/94";
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/bin/cp/utils.c,v 1.43 2004/04/06 20:06:44 markm Exp $");
+#endif
+#endif /* not lint */
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define MSC_DO_64_BIT_IO
+#include "config.h"
+#ifndef _MSC_VER
+# include <sys/param.h>
+#endif
+#include <sys/stat.h>
+#ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
+# include <sys/mman.h>
+#endif
+
+#include "err.h"
+#include <errno.h>
+#include <fcntl.h>
+#include "fts.h"
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#ifndef __HAIKU__
+# include <sysexits.h>
+#endif
+#include <unistd.h>
+#ifdef __sun__
+# include "solfakes.h"
+#endif
+#ifdef __HAIKU__
+# include "haikufakes.h"
+#endif
+#ifdef _MSC_VER
+# include "mscfakes.h"
+#else
+# include <sys/time.h>
+#endif
+#include "cp_extern.h"
+#include "cmp_extern.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define cp_pct(x,y) (int)(100.0 * (double)(x) / (double)(y))
+
+#ifndef MAXBSIZE
+# define MAXBSIZE 0x10000
+#endif
+#ifndef O_BINARY
+# define O_BINARY 0
+#endif
+
+#ifndef S_ISVTX
+# define S_ISVTX 0
+#endif
+
+
+int
+copy_file(CPUTILSINSTANCE *pThis, const FTSENT *entp, int dne, int changed_only, int *pcopied)
+{
+ /*static*/ char buf[MAXBSIZE];
+ struct stat *fs;
+ int ch, checkch, from_fd, rcount, rval, to_fd;
+ ssize_t wcount;
+ size_t wresid;
+ size_t wtotal;
+ char *bufp;
+#ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
+ char *p;
+#endif
+
+ *pcopied = 0;
+
+ if ((from_fd = open(entp->fts_path, O_RDONLY | O_BINARY | KMK_OPEN_NO_INHERIT, 0)) == -1) {
+ warn(pThis->pCtx, "open: %s", entp->fts_path);
+ return (1);
+ }
+
+ fs = entp->fts_statp;
+
+ /*
+ * If the file exists and we're interactive, verify with the user.
+ * If the file DNE, set the mode to be the from file, minus setuid
+ * bits, modified by the umask; arguably wrong, but it makes copying
+ * executables work right and it's been that way forever. (The
+ * other choice is 666 or'ed with the execute bits on the from file
+ * modified by the umask.)
+ */
+ if (!dne) {
+ /* compare the files first if requested */
+ if (changed_only) {
+ if (cmp_fd_and_file(pThis->pCtx, from_fd, entp->fts_path, pThis->to.p_path,
+ 1 /* silent */, 0 /* lflag */,
+ 0 /* special */) == OK_EXIT) {
+ close(from_fd);
+ return (0);
+ }
+ if (lseek(from_fd, 0, SEEK_SET) != 0) {
+ close(from_fd);
+ if ((from_fd = open(entp->fts_path, O_RDONLY | O_BINARY | KMK_OPEN_NO_INHERIT, 0)) == -1) {
+ warn(pThis->pCtx, "open: %s", entp->fts_path);
+ return (1);
+ }
+ }
+ }
+
+#define YESNO "(y/n [n]) "
+ if (pThis->nflag) {
+ if (pThis->vflag)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s not overwritten\n", pThis->to.p_path);
+ return (0);
+ } else if (pThis->iflag) {
+ (void)fprintf(stderr, "overwrite %s? %s",
+ pThis->to.p_path, YESNO);
+ checkch = ch = getchar();
+ while (ch != '\n' && ch != EOF)
+ ch = getchar();
+ if (checkch != 'y' && checkch != 'Y') {
+ (void)close(from_fd);
+ kmk_builtin_ctx_printf(pThis->pCtx, 1, "not overwritten\n");
+ return (1);
+ }
+ }
+
+ if (pThis->fflag) {
+ /* remove existing destination file name,
+ * create a new file */
+ (void)unlink(pThis->to.p_path);
+ to_fd = open(pThis->to.p_path, O_WRONLY | O_TRUNC | O_CREAT | O_BINARY | KMK_OPEN_NO_INHERIT,
+ fs->st_mode & ~(S_ISUID | S_ISGID));
+ } else
+ /* overwrite existing destination file name */
+ to_fd = open(pThis->to.p_path, O_WRONLY | O_TRUNC | O_BINARY | KMK_OPEN_NO_INHERIT, 0);
+ } else
+ to_fd = open(pThis->to.p_path, O_WRONLY | O_TRUNC | O_CREAT | O_BINARY | KMK_OPEN_NO_INHERIT,
+ fs->st_mode & ~(S_ISUID | S_ISGID));
+
+ if (to_fd == -1) {
+ warn(pThis->pCtx, "open: %s", pThis->to.p_path);
+ (void)close(from_fd);
+ return (1);
+ }
+
+ rval = 0;
+ *pcopied = 1;
+
+ /*
+ * Mmap and write if less than 8M (the limit is so we don't totally
+ * trash memory on big files. This is really a minor hack, but it
+ * wins some CPU back.
+ */
+#ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
+ if (S_ISREG(fs->st_mode) && fs->st_size > 0 &&
+ fs->st_size <= 8 * 1048576) {
+ if ((p = mmap(NULL, (size_t)fs->st_size, PROT_READ,
+ MAP_SHARED, from_fd, (off_t)0)) == MAP_FAILED) {
+ warn(pThis->pCtx, "mmap: %s", entp->fts_path);
+ rval = 1;
+ } else {
+ wtotal = 0;
+ for (bufp = p, wresid = fs->st_size; ;
+ bufp += wcount, wresid -= (size_t)wcount) {
+ wcount = write(to_fd, bufp, wresid);
+ wtotal += wcount;
+# if defined(SIGINFO) && defined(KMK_BUILTIN_STANDALONE)
+ if (g_cp_info) {
+ g_cp_info = 0;
+ kmk_builtin_ctx_printf(pThis->pCtx, 1,
+ "%s -> %s %3d%%\n",
+ entp->fts_path, pThis->to.p_path,
+ cp_pct(wtotal, fs->st_size));
+
+ }
+#endif
+ if (wcount >= (ssize_t)wresid || wcount <= 0)
+ break;
+ }
+ if (wcount != (ssize_t)wresid) {
+ warn(pThis->pCtx, "write[%zd != %zu]: %s", wcount, wresid, pThis->to.p_path);
+ rval = 1;
+ }
+ /* Some systems don't unmap on close(2). */
+ if (munmap(p, fs->st_size) < 0) {
+ warn(pThis->pCtx, "munmap: %s", entp->fts_path);
+ rval = 1;
+ }
+ }
+ } else
+#endif
+ {
+ wtotal = 0;
+ while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) {
+ for (bufp = buf, wresid = rcount; ;
+ bufp += wcount, wresid -= wcount) {
+ wcount = write(to_fd, bufp, wresid);
+ wtotal += wcount;
+#if defined(SIGINFO) && defined(KMK_BUILTIN_STANDALONE)
+ if (g_cp_info) {
+ g_cp_info = 0;
+ kmk_builtin_ctx_printf(pThis->pCtx, 1,
+ "%s -> %s %3d%%\n",
+ entp->fts_path, pThis->to.p_path,
+ cp_pct(wtotal, fs->st_size));
+
+ }
+#endif
+ if (wcount >= (ssize_t)wresid || wcount <= 0)
+ break;
+ }
+ if (wcount != (ssize_t)wresid) {
+ warn(pThis->pCtx, "write[%zd != %zu]: %s", wcount, wresid, pThis->to.p_path);
+ rval = 1;
+ break;
+ }
+ }
+ if (rcount < 0) {
+ warn(pThis->pCtx, "read: %s", entp->fts_path);
+ rval = 1;
+ }
+ }
+
+ /*
+ * Don't remove the target even after an error. The target might
+ * not be a regular file, or its attributes might be important,
+ * or its contents might be irreplaceable. It would only be safe
+ * to remove it if we created it and its length is 0.
+ */
+
+ if (pThis->pflag && copy_file_attribs(pThis, fs, to_fd))
+ rval = 1;
+ (void)close(from_fd);
+ if (close(to_fd)) {
+ warn(pThis->pCtx, "close: %s", pThis->to.p_path);
+ rval = 1;
+ }
+ return (rval);
+}
+
+int
+copy_link(CPUTILSINSTANCE *pThis, const FTSENT *p, int exists)
+{
+ int len;
+ char llink[PATH_MAX];
+
+ if ((len = readlink(p->fts_path, llink, sizeof(llink) - 1)) == -1) {
+ warn(pThis->pCtx, "readlink: %s", p->fts_path);
+ return (1);
+ }
+ llink[len] = '\0';
+ if (exists && unlink(pThis->to.p_path)) {
+ warn(pThis->pCtx, "unlink: %s", pThis->to.p_path);
+ return (1);
+ }
+ if (symlink(llink, pThis->to.p_path)) {
+ warn(pThis->pCtx, "symlink: %s", llink);
+ return (1);
+ }
+ return (pThis->pflag ? copy_file_attribs(pThis, p->fts_statp, -1) : 0);
+}
+
+int
+copy_fifo(CPUTILSINSTANCE *pThis, struct stat *from_stat, int exists)
+{
+ if (exists && unlink(pThis->to.p_path)) {
+ warn(pThis->pCtx, "unlink: %s", pThis->to.p_path);
+ return (1);
+ }
+ if (mkfifo(pThis->to.p_path, from_stat->st_mode)) {
+ warn(pThis->pCtx, "mkfifo: %s", pThis->to.p_path);
+ return (1);
+ }
+ return (pThis->pflag ? copy_file_attribs(pThis, from_stat, -1) : 0);
+}
+
+int
+copy_special(CPUTILSINSTANCE *pThis, struct stat *from_stat, int exists)
+{
+ if (exists && unlink(pThis->to.p_path)) {
+ warn(pThis->pCtx, "unlink: %s", pThis->to.p_path);
+ return (1);
+ }
+ if (mknod(pThis->to.p_path, from_stat->st_mode, from_stat->st_rdev)) {
+ warn(pThis->pCtx, "mknod: %s", pThis->to.p_path);
+ return (1);
+ }
+ return (pThis->pflag ? copy_file_attribs(pThis, from_stat, -1) : 0);
+}
+
+int
+copy_file_attribs(CPUTILSINSTANCE *pThis, struct stat *fs, int fd)
+{
+ /*static*/ struct timeval tv[2];
+ struct stat ts;
+ int rval, gotstat, islink, fdval;
+
+ rval = 0;
+ fdval = fd != -1;
+ islink = !fdval && S_ISLNK(fs->st_mode);
+ fs->st_mode &= S_ISUID | S_ISGID | S_ISVTX |
+ S_IRWXU | S_IRWXG | S_IRWXO;
+
+#ifdef HAVE_ST_TIMESPEC
+ TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
+ TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
+#else
+ tv[0].tv_sec = fs->st_atime;
+ tv[1].tv_sec = fs->st_mtime;
+ tv[0].tv_usec = tv[1].tv_usec = 0;
+#endif
+ if (islink ? lutimes(pThis->to.p_path, tv) : utimes(pThis->to.p_path, tv)) {
+ warn(pThis->pCtx, "%sutimes: %s", islink ? "l" : "", pThis->to.p_path);
+ rval = 1;
+ }
+ if (fdval ? fstat(fd, &ts) :
+ (islink ? lstat(pThis->to.p_path, &ts) : stat(pThis->to.p_path, &ts)))
+ gotstat = 0;
+ else {
+ gotstat = 1;
+ ts.st_mode &= S_ISUID | S_ISGID | S_ISVTX |
+ S_IRWXU | S_IRWXG | S_IRWXO;
+ }
+ /*
+ * Changing the ownership probably won't succeed, unless we're root
+ * or POSIX_CHOWN_RESTRICTED is not set. Set uid/gid before setting
+ * the mode; current BSD behavior is to remove all setuid bits on
+ * chown. If chown fails, lose setuid/setgid bits.
+ */
+ if (!gotstat || fs->st_uid != ts.st_uid || fs->st_gid != ts.st_gid)
+ if (fdval ? fchown(fd, fs->st_uid, fs->st_gid) :
+ (islink ? lchown(pThis->to.p_path, fs->st_uid, fs->st_gid) :
+ chown(pThis->to.p_path, fs->st_uid, fs->st_gid))) {
+ if (errno != EPERM) {
+ warn(pThis->pCtx, "chown: %s", pThis->to.p_path);
+ rval = 1;
+ }
+ fs->st_mode &= ~(S_ISUID | S_ISGID);
+ }
+
+ if (!gotstat || fs->st_mode != ts.st_mode)
+ if (fdval ? fchmod(fd, fs->st_mode) :
+ (islink ? lchmod(pThis->to.p_path, fs->st_mode) :
+ chmod(pThis->to.p_path, fs->st_mode))) {
+ warn(pThis->pCtx, "chmod: %s", pThis->to.p_path);
+ rval = 1;
+ }
+
+#ifdef HAVE_ST_FLAGS
+ if (!gotstat || fs->st_flags != ts.st_flags)
+ if (fdval ?
+ fchflags(fd, fs->st_flags) :
+ (islink ? (errno = ENOSYS) :
+ chflags(pThis->to.p_path, fs->st_flags))) {
+ warn(pThis->pCtx, "chflags: %s", pThis->to.p_path);
+ rval = 1;
+ }
+#endif
+
+ return (rval);
+}
+
diff --git a/src/kmk/kmkbuiltin/darwin.c b/src/kmk/kmkbuiltin/darwin.c
new file mode 100644
index 0000000..583d6e1
--- /dev/null
+++ b/src/kmk/kmkbuiltin/darwin.c
@@ -0,0 +1,55 @@
+/* $Id: darwin.c 2591 2012-06-17 20:45:31Z bird $ */
+/** @file
+ * Missing BSD functions on Darwin / Mac OS X.
+ */
+
+/*
+ * Copyright (c) 2006-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/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "config.h"
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+
+int lchmod(const char *path, mode_t mode)
+{
+ struct stat st;
+ if (lstat(path, &st))
+ return -1;
+ if (S_ISLNK(st.st_mode))
+ return 0; /* pretend success */
+ return chmod(path, mode);
+}
+
+
+int lutimes(const char *path, const struct timeval *tvs)
+{
+ struct stat st;
+ if (lstat(path, &st))
+ return -1;
+ if (S_ISLNK(st.st_mode))
+ return 0; /* pretend success */
+ return utimes(path, tvs);
+}
+
diff --git a/src/kmk/kmkbuiltin/echo.c b/src/kmk/kmkbuiltin/echo.c
new file mode 100644
index 0000000..11dc227
--- /dev/null
+++ b/src/kmk/kmkbuiltin/echo.c
@@ -0,0 +1,125 @@
+/* $Id: echo.c 3192 2018-03-26 20:25:56Z bird $ */
+/** @file
+ * kMk Builtin command - echo
+ */
+
+/*
+ * 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/>
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "config.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#ifdef _MSC_VER
+# include <io.h>
+#endif
+
+#include "kmkbuiltin.h"
+#include "err.h"
+
+
+int kmk_builtin_echo(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ int rcExit = 0;
+ int iFirst = 1;
+ int i;
+ char *pszBuf;
+ size_t cbBuf;
+
+ /*
+ * Check for the -n option.
+ */
+ int fNoNewLine = 0;
+ if ( argc > iFirst
+ && strcmp(argv[iFirst], "-n") == 0)
+ {
+ iFirst++;
+ fNoNewLine = 1;
+ }
+
+ /*
+ * Calc buffer size and allocate it.
+ */
+ cbBuf = 1 + 1;
+ for (i = 1; i < argc; i++)
+ cbBuf += (i > iFirst) + strlen(argv[i]);
+ pszBuf = (char *)malloc(cbBuf);
+ if (pszBuf)
+ {
+ /*
+ * Assembler the output into the buffer.
+ */
+ char *pszDst = pszBuf;
+ for (i = iFirst; i < argc; i++)
+ {
+ const char *pszArg = argv[i];
+ size_t cchArg = strlen(pszArg);
+
+ /* Check for "\c" in final argument (same as -n). */
+ if (i + 1 >= argc
+ && cchArg >= 2
+ && pszArg[cchArg - 2] == '\\'
+ && pszArg[cchArg - 1] == 'c')
+ {
+ fNoNewLine = 1;
+ cchArg -= 2;
+ }
+ if (i > iFirst)
+ *pszDst++ = ' ';
+ memcpy(pszDst, pszArg, cchArg);
+ pszDst += cchArg;
+ }
+ if (!fNoNewLine)
+ *pszDst++ = '\n';
+ *pszDst = '\0';
+
+ /*
+ * Push it out.
+ */
+#ifndef KMK_BUILTIN_STANDALONE
+ if (output_write_text(pCtx->pOut, 0, pszBuf, pszDst - pszBuf) == -1)
+ rcExit = err(pCtx, 1, "output_write_text");
+#else
+ if (write(STDOUT_FILENO, pszBuf, pszDst - pszBuf) == -1)
+ rcExit = err(pCtx, 1, "write");
+#endif
+ free(pszBuf);
+ }
+ else
+ rcExit = err(pCtx, 1, "malloc(%lu)", (unsigned long)cbBuf);
+ return rcExit;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_echo", NULL };
+ return kmk_builtin_echo(argc, argv, envp, &Ctx);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin/err.c b/src/kmk/kmkbuiltin/err.c
new file mode 100644
index 0000000..bbab335
--- /dev/null
+++ b/src/kmk/kmkbuiltin/err.c
@@ -0,0 +1,340 @@
+/* $Id: err.c 3237 2018-12-25 04:11:26Z bird $ */
+/** @file
+ * Override err.h so we get the program name right.
+ */
+
+/*
+ * Copyright (c) 2005-2016 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/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+# ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+# endif
+# ifdef HAVE_STDINT_H
+# include <stdint.h>
+# endif
+#else
+# include <stdlib.h>
+# define snprintf _snprintf
+#endif
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include "err.h"
+#if !defined(KMK_BUILTIN_STANDALONE) && !defined(KWORKER)
+# include "../output.h"
+#endif
+
+#ifdef KBUILD_OS_WINDOWS
+/* This is a trick to speed up console output on windows. */
+# include "console.h"
+# undef fwrite
+# define fwrite maybe_con_fwrite
+#endif
+
+int err(PKMKBUILTINCTX pCtx, int eval, const char *fmt, ...)
+{
+ /*
+ * We format into a buffer and pass that onto output.c or fwrite.
+ */
+ int error = errno;
+ char *pszToFree = NULL;
+ char szMsgStack[4096];
+ char *pszMsg = szMsgStack;
+ size_t cbMsg = sizeof(szMsgStack);
+ for (;;)
+ {
+ int cchMsg = snprintf(pszMsg, cbMsg, "%s: error: ", pCtx->pszProgName);
+ if (cchMsg < (int)cbMsg - 1 && cchMsg > 0)
+ {
+ int cchMsg2;
+ va_list va;
+ va_start(va, fmt);
+ cchMsg += cchMsg2 = vsnprintf(&pszMsg[cchMsg], cbMsg - cchMsg, fmt, va);
+ va_end(va);
+
+ if ( cchMsg < (int)cbMsg - 1
+ && cchMsg2 >= 0)
+ {
+ cchMsg += cchMsg2 = snprintf(&pszMsg[cchMsg], cbMsg - cchMsg, ": %s\n", strerror(error));
+ if ( cchMsg < (int)cbMsg - 1
+ && cchMsg2 >= 0)
+ {
+#if !defined(KMK_BUILTIN_STANDALONE) && !defined(KWORKER)
+ if (pCtx->pOut)
+ output_write_text(pCtx->pOut, 1 /*is_err*/, pszMsg, cchMsg);
+ else
+#endif
+ {
+ fflush(stdout);
+ fwrite(pszMsg, cchMsg, 1, stderr);
+ fflush(stderr); /* paranoia */
+ }
+ if (pszToFree)
+ free(pszToFree);
+ errno = error;
+ return eval;
+ }
+ }
+ }
+
+ /* double the buffer size and retry */
+ if (pszToFree)
+ free(pszToFree);
+ cbMsg *= 2;
+ pszToFree = malloc(cbMsg);
+ if (!pszToFree)
+ {
+ fprintf(stderr, "out of memory!\n");
+ errno = error;
+ return eval;
+ }
+ }
+}
+
+
+int errx(PKMKBUILTINCTX pCtx, int eval, const char *fmt, ...)
+{
+ /*
+ * We format into a buffer and pass that onto output.c or fwrite.
+ */
+ char *pszToFree = NULL;
+ char szMsgStack[4096];
+ char *pszMsg = szMsgStack;
+ size_t cbMsg = sizeof(szMsgStack);
+ for (;;)
+ {
+ int cchMsg = snprintf(pszMsg, cbMsg, "%s: error: ", pCtx->pszProgName);
+ if (cchMsg < (int)cbMsg - 1 && cchMsg > 0)
+ {
+ int cchMsg2;
+ va_list va;
+ va_start(va, fmt);
+ cchMsg += cchMsg2 = vsnprintf(&pszMsg[cchMsg], cbMsg - cchMsg, fmt, va);
+ va_end(va);
+
+ if ( cchMsg < (int)cbMsg - 2
+ && cchMsg2 >= 0)
+ {
+ /* ensure newline */
+ if (pszMsg[cchMsg - 1] != '\n')
+ {
+ pszMsg[cchMsg++] = '\n';
+ pszMsg[cchMsg] = '\0';
+ }
+
+#if !defined(KMK_BUILTIN_STANDALONE) && !defined(KWORKER)
+ if (pCtx->pOut)
+ output_write_text(pCtx->pOut, 1 /*is_err*/, pszMsg, cchMsg);
+ else
+#endif
+ {
+ fflush(stdout);
+ fwrite(pszMsg, cchMsg, 1, stderr);
+ fflush(stderr); /* paranoia */
+ }
+ if (pszToFree)
+ free(pszToFree);
+ return eval;
+ }
+ }
+
+ /* double the buffer size and retry */
+ if (pszToFree)
+ free(pszToFree);
+ cbMsg *= 2;
+ pszToFree = malloc(cbMsg);
+ if (!pszToFree)
+ {
+ fprintf(stderr, "out of memory!\n");
+ return eval;
+ }
+ }
+}
+
+void warn(PKMKBUILTINCTX pCtx, const char *fmt, ...)
+{
+ /*
+ * We format into a buffer and pass that onto output.c or fwrite.
+ */
+ int error = errno;
+ char *pszToFree = NULL;
+ char szMsgStack[4096];
+ char *pszMsg = szMsgStack;
+ size_t cbMsg = sizeof(szMsgStack);
+ for (;;)
+ {
+ int cchMsg = snprintf(pszMsg, cbMsg, "%s: ", pCtx->pszProgName);
+ if (cchMsg < (int)cbMsg - 1 && cchMsg > 0)
+ {
+ int cchMsg2;
+ va_list va;
+ va_start(va, fmt);
+ cchMsg += cchMsg2 = vsnprintf(&pszMsg[cchMsg], cbMsg - cchMsg, fmt, va);
+ va_end(va);
+
+ if ( cchMsg < (int)cbMsg - 1
+ && cchMsg2 >= 0)
+ {
+ cchMsg += cchMsg2 = snprintf(&pszMsg[cchMsg], cbMsg - cchMsg, ": %s\n", strerror(error));
+ if ( cchMsg < (int)cbMsg - 1
+ && cchMsg2 >= 0)
+ {
+#if !defined(KMK_BUILTIN_STANDALONE) && !defined(KWORKER)
+ if (pCtx->pOut)
+ output_write_text(pCtx->pOut, 1 /*is_err*/, pszMsg, cchMsg);
+ else
+#endif
+ {
+ fflush(stdout);
+ fwrite(pszMsg, cchMsg, 1, stderr);
+ fflush(stderr); /* paranoia */
+ }
+ if (pszToFree)
+ free(pszToFree);
+ errno = error;
+ return;
+ }
+ }
+ }
+
+ /* double the buffer size and retry */
+ if (pszToFree)
+ free(pszToFree);
+ cbMsg *= 2;
+ pszToFree = malloc(cbMsg);
+ if (!pszToFree)
+ {
+ fprintf(stderr, "out of memory!\n");
+ errno = error;
+ return;
+ }
+ }
+}
+
+void warnx(PKMKBUILTINCTX pCtx, const char *fmt, ...)
+{
+ /*
+ * We format into a buffer and pass that onto output.c or fwrite.
+ */
+ char *pszToFree = NULL;
+ char szMsgStack[4096];
+ char *pszMsg = szMsgStack;
+ size_t cbMsg = sizeof(szMsgStack);
+ for (;;)
+ {
+ int cchMsg = snprintf(pszMsg, cbMsg, "%s: ", pCtx->pszProgName);
+ if (cchMsg < (int)cbMsg - 1 && cchMsg > 0)
+ {
+ int cchMsg2;
+ va_list va;
+ va_start(va, fmt);
+ cchMsg += cchMsg2 = vsnprintf(&pszMsg[cchMsg], cbMsg - cchMsg, fmt, va);
+ va_end(va);
+
+ if ( cchMsg < (int)cbMsg - 2
+ && cchMsg2 >= 0)
+ {
+ /* ensure newline */
+ if (pszMsg[cchMsg - 1] != '\n')
+ {
+ pszMsg[cchMsg++] = '\n';
+ pszMsg[cchMsg] = '\0';
+ }
+
+#if !defined(KMK_BUILTIN_STANDALONE) && !defined(KWORKER)
+ if (pCtx->pOut)
+ output_write_text(pCtx->pOut, 1 /*is_err*/, pszMsg, cchMsg);
+ else
+#endif
+ {
+ fflush(stdout);
+ fwrite(pszMsg, cchMsg, 1, stderr);
+ fflush(stderr); /* paranoia */
+ }
+ if (pszToFree)
+ free(pszToFree);
+ return;
+ }
+ }
+
+ /* double the buffer size and retry */
+ if (pszToFree)
+ free(pszToFree);
+ cbMsg *= 2;
+ pszToFree = malloc(cbMsg);
+ if (!pszToFree)
+ {
+ fprintf(stderr, "out of memory!\n");
+ return;
+ }
+ }
+}
+
+void kmk_builtin_ctx_printf(PKMKBUILTINCTX pCtx, int fIsErr, const char *pszFormat, ...)
+{
+ /*
+ * We format into a buffer and pass that onto output.c or fwrite.
+ */
+ char *pszToFree = NULL;
+ char szMsgStack[4096];
+ char *pszMsg = szMsgStack;
+ size_t cbMsg = sizeof(szMsgStack);
+ for (;;)
+ {
+ int cchMsg;
+ va_list va;
+ va_start(va, pszFormat);
+ cchMsg = vsnprintf(pszMsg, cbMsg, pszFormat, va);
+ va_end(va);
+ if (cchMsg < (int)cbMsg - 1 && cchMsg > 0)
+ {
+#if !defined(KMK_BUILTIN_STANDALONE) && !defined(KWORKER)
+ if (pCtx->pOut)
+ output_write_text(pCtx->pOut, fIsErr, pszMsg, cchMsg);
+ else
+#endif
+ {
+ fwrite(pszMsg, cchMsg, 1, fIsErr ? stderr : stdout);
+ fflush(fIsErr ? stderr : stdout);
+ }
+ if (pszToFree)
+ free(pszToFree);
+ return;
+ }
+
+ /* double the buffer size and retry */
+ if (pszToFree)
+ free(pszToFree);
+ cbMsg *= 2;
+ pszToFree = malloc(cbMsg);
+ if (!pszToFree)
+ {
+ fprintf(stderr, "out of memory!\n");
+ return;
+ }
+ }
+}
+
diff --git a/src/kmk/kmkbuiltin/err.h b/src/kmk/kmkbuiltin/err.h
new file mode 100644
index 0000000..4150e42
--- /dev/null
+++ b/src/kmk/kmkbuiltin/err.h
@@ -0,0 +1,38 @@
+/* $Id: err.h 3192 2018-03-26 20:25:56Z bird $ */
+/** @file
+ * Override err.h stuff so we get the program names right.
+ */
+
+/*
+ * Copyright (c) 2005-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/>
+ *
+ */
+
+#ifndef ___err_h
+#define ___err_h
+
+#include "../kmkbuiltin.h"
+
+int err(PKMKBUILTINCTX pCtx, int eval, const char *fmt, ...);
+int errx(PKMKBUILTINCTX pCtx, int eval, const char *fmt, ...);
+void warn(PKMKBUILTINCTX pCtx, const char *fmt, ...);
+void warnx(PKMKBUILTINCTX pCtx, const char *fmt, ...);
+void kmk_builtin_ctx_printf(PKMKBUILTINCTX pCtx, int fIsErr, const char *pszFormat, ...);
+
+#endif
+
diff --git a/src/kmk/kmkbuiltin/expr.c b/src/kmk/kmkbuiltin/expr.c
new file mode 100644
index 0000000..350c2a3
--- /dev/null
+++ b/src/kmk/kmkbuiltin/expr.c
@@ -0,0 +1,617 @@
+/* $OpenBSD: expr.c,v 1.17 2006/06/21 18:28:24 deraadt Exp $ */
+/* $NetBSD: expr.c,v 1.3.6.1 1996/06/04 20:41:47 cgd Exp $ */
+
+/*
+ * Written by J.T. Conklin <jtc@netbsd.org>.
+ * Public domain.
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+#include <ctype.h>
+#ifdef KMK_WITH_REGEX
+#include <regex.h>
+#endif
+#include <setjmp.h>
+#include <assert.h>
+#ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+#endif
+#include "err.h"
+#include "kmkbuiltin.h"
+
+typedef struct EXPRINSTANCE *PEXPRINSTANCE;
+
+static struct val *make_int(PEXPRINSTANCE, int);
+static struct val *make_str(PEXPRINSTANCE, char *);
+static void free_value(PEXPRINSTANCE, struct val *);
+static int is_integer(struct val *, int *);
+static int to_integer(PEXPRINSTANCE, struct val *);
+static void to_string(PEXPRINSTANCE, struct val *);
+static int is_zero_or_null(PEXPRINSTANCE, struct val *);
+static void nexttoken(PEXPRINSTANCE, int);
+static struct val *eval6(PEXPRINSTANCE);
+static struct val *eval5(PEXPRINSTANCE);
+static struct val *eval4(PEXPRINSTANCE);
+static struct val *eval3(PEXPRINSTANCE);
+static struct val *eval2(PEXPRINSTANCE);
+static struct val *eval1(PEXPRINSTANCE);
+static struct val *eval0(PEXPRINSTANCE);
+
+enum token {
+ OR, AND, EQ, LT, GT, ADD, SUB, MUL, DIV, MOD, MATCH, RP, LP,
+ NE, LE, GE, OPERAND, EOI
+};
+
+struct val {
+ enum {
+ integer,
+ string
+ } type;
+
+ union {
+ char *s;
+ int i;
+ } u;
+};
+
+typedef struct EXPRINSTANCE {
+ PKMKBUILTINCTX pCtx;
+ enum token token;
+ struct val *tokval;
+ char **av;
+ jmp_buf g_expr_jmp;
+ void **recorded_allocations;
+ int num_recorded_allocations;
+} EXPRINSTANCE;
+
+
+static void expr_mem_record_alloc(PEXPRINSTANCE pThis, void *ptr)
+{
+ if (!(pThis->num_recorded_allocations & 31)) {
+ void *newtab = realloc(pThis->recorded_allocations, (pThis->num_recorded_allocations + 33) * sizeof(void *));
+ if (!newtab)
+ longjmp(pThis->g_expr_jmp, err(pThis->pCtx, 3, NULL));
+ pThis->recorded_allocations = (void **)newtab;
+ }
+ pThis->recorded_allocations[pThis->num_recorded_allocations++] = ptr;
+}
+
+
+static void expr_mem_record_free(PEXPRINSTANCE pThis, void *ptr)
+{
+ int i = pThis->num_recorded_allocations;
+ while (i-- > 0)
+ if (pThis->recorded_allocations[i] == ptr) {
+ pThis->num_recorded_allocations--;
+ pThis->recorded_allocations[i] = pThis->recorded_allocations[pThis->num_recorded_allocations];
+ return;
+ }
+ assert(i >= 0);
+}
+
+static void expr_mem_init(PEXPRINSTANCE pThis)
+{
+ pThis->num_recorded_allocations = 0;
+ pThis->recorded_allocations = NULL;
+}
+
+static void expr_mem_cleanup(PEXPRINSTANCE pThis)
+{
+ if (pThis->recorded_allocations) {
+ while (pThis->num_recorded_allocations-- > 0)
+ free(pThis->recorded_allocations[pThis->num_recorded_allocations]);
+ free(pThis->recorded_allocations);
+ pThis->recorded_allocations = NULL;
+ }
+}
+
+
+static struct val *
+make_int(PEXPRINSTANCE pThis, int i)
+{
+ struct val *vp;
+
+ vp = (struct val *) malloc(sizeof(*vp));
+ if (vp == NULL)
+ longjmp(pThis->g_expr_jmp, err(pThis->pCtx, 3, NULL));
+ expr_mem_record_alloc(pThis, vp);
+ vp->type = integer;
+ vp->u.i = i;
+ return vp;
+}
+
+
+static struct val *
+make_str(PEXPRINSTANCE pThis, char *s)
+{
+ struct val *vp;
+
+ vp = (struct val *) malloc(sizeof(*vp));
+ if (vp == NULL || ((vp->u.s = strdup(s)) == NULL))
+ longjmp(pThis->g_expr_jmp, err(pThis->pCtx, 3, NULL));
+ expr_mem_record_alloc(pThis, vp->u.s);
+ expr_mem_record_alloc(pThis, vp);
+ vp->type = string;
+ return vp;
+}
+
+
+static void
+free_value(PEXPRINSTANCE pThis, struct val *vp)
+{
+ if (vp->type == string) {
+ expr_mem_record_free(pThis, vp->u.s);
+ free(vp->u.s);
+ }
+ free(vp);
+ expr_mem_record_free(pThis, vp);
+}
+
+
+/* determine if vp is an integer; if so, return it's value in *r */
+static int
+is_integer(struct val *vp, int *r)
+{
+ char *s;
+ int neg;
+ int i;
+
+ if (vp->type == integer) {
+ *r = vp->u.i;
+ return 1;
+ }
+
+ /*
+ * POSIX.2 defines an "integer" as an optional unary minus
+ * followed by digits.
+ */
+ s = vp->u.s;
+ i = 0;
+
+ neg = (*s == '-');
+ if (neg)
+ s++;
+
+ while (*s) {
+ if (!isdigit(*s))
+ return 0;
+
+ i *= 10;
+ i += *s - '0';
+
+ s++;
+ }
+
+ if (neg)
+ i *= -1;
+
+ *r = i;
+ return 1;
+}
+
+
+/* coerce to vp to an integer */
+static int
+to_integer(PEXPRINSTANCE pThis, struct val *vp)
+{
+ int r;
+
+ if (vp->type == integer)
+ return 1;
+
+ if (is_integer(vp, &r)) {
+ expr_mem_record_free(pThis, vp->u.s);
+ free(vp->u.s);
+ vp->u.i = r;
+ vp->type = integer;
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/* coerce to vp to an string */
+static void
+to_string(PEXPRINSTANCE pThis, struct val *vp)
+{
+ char *tmp;
+
+ if (vp->type == string)
+ return;
+
+ if (asprintf(&tmp, "%d", vp->u.i) == -1)
+ longjmp(pThis->g_expr_jmp, err(pThis->pCtx, 3, NULL));
+ expr_mem_record_alloc(pThis, tmp);
+
+ vp->type = string;
+ vp->u.s = tmp;
+}
+
+static int
+is_zero_or_null(PEXPRINSTANCE pThis, struct val *vp)
+{
+ if (vp->type == integer) {
+ return (vp->u.i == 0);
+ } else {
+ return (*vp->u.s == 0 || (to_integer(pThis, vp) && vp->u.i == 0));
+ }
+ /* NOTREACHED */
+}
+
+static void
+nexttoken(PEXPRINSTANCE pThis, int pat)
+{
+ char *p;
+
+ if ((p = *pThis->av) == NULL) {
+ pThis->token = EOI;
+ return;
+ }
+ pThis->av++;
+
+
+ if (pat == 0 && p[0] != '\0') {
+ if (p[1] == '\0') {
+ const char *x = "|&=<>+-*/%:()";
+ char *i; /* index */
+
+ if ((i = strchr(x, *p)) != NULL) {
+ pThis->token = i - x;
+ return;
+ }
+ } else if (p[1] == '=' && p[2] == '\0') {
+ switch (*p) {
+ case '<':
+ pThis->token = LE;
+ return;
+ case '>':
+ pThis->token = GE;
+ return;
+ case '!':
+ pThis->token = NE;
+ return;
+ }
+ }
+ }
+ pThis->tokval = make_str(pThis, p);
+ pThis->token = OPERAND;
+ return;
+}
+
+#ifdef __GNUC__
+__attribute__((noreturn))
+#endif
+#ifdef _MSC_VER
+__declspec(noreturn)
+#endif
+static void
+error(PEXPRINSTANCE pThis)
+{
+ longjmp(pThis->g_expr_jmp, errx(pThis->pCtx, 2, "syntax error"));
+ /* NOTREACHED */
+}
+
+static struct val *
+eval6(PEXPRINSTANCE pThis)
+{
+ struct val *v;
+
+ if (pThis->token == OPERAND) {
+ nexttoken(pThis, 0);
+ return pThis->tokval;
+
+ } else if (pThis->token == RP) {
+ nexttoken(pThis, 0);
+ v = eval0(pThis);
+
+ if (pThis->token != LP) {
+ error(pThis);
+ /* NOTREACHED */
+ }
+ nexttoken(pThis, 0);
+ return v;
+ } else {
+ error(pThis);
+ }
+ /* NOTREACHED */
+}
+
+/* Parse and evaluate match (regex) expressions */
+static struct val *
+eval5(PEXPRINSTANCE pThis)
+{
+#ifdef KMK_WITH_REGEX
+ regex_t rp;
+ regmatch_t rm[2];
+ char errbuf[256];
+ int eval;
+ struct val *r;
+ struct val *v;
+#endif
+ struct val *l;
+
+ l = eval6(pThis);
+ while (pThis->token == MATCH) {
+#ifdef KMK_WITH_REGEX
+ nexttoken(pThis, 1);
+ r = eval6(pThis);
+
+ /* coerce to both arguments to strings */
+ to_string(pThis, l);
+ to_string(pThis, r);
+
+ /* compile regular expression */
+ if ((eval = regcomp(&rp, r->u.s, 0)) != 0) {
+ regerror(eval, &rp, errbuf, sizeof(errbuf));
+ longjmp(pThis->g_expr_jmp, errx(pThis->pCtx, 2, "%s", errbuf));
+ }
+
+ /* compare string against pattern -- remember that patterns
+ are anchored to the beginning of the line */
+ if (regexec(&rp, l->u.s, 2, rm, 0) == 0 && rm[0].rm_so == 0) {
+ if (rm[1].rm_so >= 0) {
+ *(l->u.s + rm[1].rm_eo) = '\0';
+ v = make_str(pThis, l->u.s + rm[1].rm_so);
+
+ } else {
+ v = make_int(pThis, (int)(rm[0].rm_eo - rm[0].rm_so));
+ }
+ } else {
+ if (rp.re_nsub == 0) {
+ v = make_int(pThis, 0);
+ } else {
+ v = make_str(pThis, "");
+ }
+ }
+
+ /* free arguments and pattern buffer */
+ free_value(pThis, l);
+ free_value(pThis, r);
+ regfree(&rp);
+
+ l = v;
+#else
+ longjmp(pThis->g_expr_jmp, errx(pThis->pCtx, 2, "regex not supported, sorry."));
+#endif
+ }
+
+ return l;
+}
+
+/* Parse and evaluate multiplication and division expressions */
+static struct val *
+eval4(PEXPRINSTANCE pThis)
+{
+ struct val *l, *r;
+ enum token op;
+
+ l = eval5(pThis);
+ while ((op = pThis->token) == MUL || op == DIV || op == MOD) {
+ nexttoken(pThis, 0);
+ r = eval5(pThis);
+
+ if (!to_integer(pThis, l) || !to_integer(pThis, r)) {
+ longjmp(pThis->g_expr_jmp, errx(pThis->pCtx, 2, "non-numeric argument"));
+ }
+
+ if (op == MUL) {
+ l->u.i *= r->u.i;
+ } else {
+ if (r->u.i == 0) {
+ longjmp(pThis->g_expr_jmp, errx(pThis->pCtx, 2, "division by zero"));
+ }
+ if (op == DIV) {
+ l->u.i /= r->u.i;
+ } else {
+ l->u.i %= r->u.i;
+ }
+ }
+
+ free_value(pThis, r);
+ }
+
+ return l;
+}
+
+/* Parse and evaluate addition and subtraction expressions */
+static struct val *
+eval3(PEXPRINSTANCE pThis)
+{
+ struct val *l, *r;
+ enum token op;
+
+ l = eval4(pThis);
+ while ((op = pThis->token) == ADD || op == SUB) {
+ nexttoken(pThis, 0);
+ r = eval4(pThis);
+
+ if (!to_integer(pThis, l) || !to_integer(pThis, r)) {
+ longjmp(pThis->g_expr_jmp, errx(pThis->pCtx, 2, "non-numeric argument"));
+ }
+
+ if (op == ADD) {
+ l->u.i += r->u.i;
+ } else {
+ l->u.i -= r->u.i;
+ }
+
+ free_value(pThis, r);
+ }
+
+ return l;
+}
+
+/* Parse and evaluate comparison expressions */
+static struct val *
+eval2(PEXPRINSTANCE pThis)
+{
+ struct val *l, *r;
+ enum token op;
+ int v = 0, li, ri;
+
+ l = eval3(pThis);
+ while ((op = pThis->token) == EQ || op == NE || op == LT || op == GT ||
+ op == LE || op == GE) {
+ nexttoken(pThis, 0);
+ r = eval3(pThis);
+
+ if (is_integer(l, &li) && is_integer(r, &ri)) {
+ switch (op) {
+ case GT:
+ v = (li > ri);
+ break;
+ case GE:
+ v = (li >= ri);
+ break;
+ case LT:
+ v = (li < ri);
+ break;
+ case LE:
+ v = (li <= ri);
+ break;
+ case EQ:
+ v = (li == ri);
+ break;
+ case NE:
+ v = (li != ri);
+ break;
+ default:
+ break;
+ }
+ } else {
+ to_string(pThis, l);
+ to_string(pThis, r);
+
+ switch (op) {
+ case GT:
+ v = (strcoll(l->u.s, r->u.s) > 0);
+ break;
+ case GE:
+ v = (strcoll(l->u.s, r->u.s) >= 0);
+ break;
+ case LT:
+ v = (strcoll(l->u.s, r->u.s) < 0);
+ break;
+ case LE:
+ v = (strcoll(l->u.s, r->u.s) <= 0);
+ break;
+ case EQ:
+ v = (strcoll(l->u.s, r->u.s) == 0);
+ break;
+ case NE:
+ v = (strcoll(l->u.s, r->u.s) != 0);
+ break;
+ default:
+ break;
+ }
+ }
+
+ free_value(pThis, l);
+ free_value(pThis, r);
+ l = make_int(pThis, v);
+ }
+
+ return l;
+}
+
+/* Parse and evaluate & expressions */
+static struct val *
+eval1(PEXPRINSTANCE pThis)
+{
+ struct val *l, *r;
+
+ l = eval2(pThis);
+ while (pThis->token == AND) {
+ nexttoken(pThis, 0);
+ r = eval2(pThis);
+
+ if (is_zero_or_null(pThis, l) || is_zero_or_null(pThis, r)) {
+ free_value(pThis, l);
+ free_value(pThis, r);
+ l = make_int(pThis, 0);
+ } else {
+ free_value(pThis, r);
+ }
+ }
+
+ return l;
+}
+
+/* Parse and evaluate | expressions */
+static struct val *
+eval0(PEXPRINSTANCE pThis)
+{
+ struct val *l, *r;
+
+ l = eval1(pThis);
+ while (pThis->token == OR) {
+ nexttoken(pThis, 0);
+ r = eval1(pThis);
+
+ if (is_zero_or_null(pThis, l)) {
+ free_value(pThis, l);
+ l = r;
+ } else {
+ free_value(pThis, r);
+ }
+ }
+
+ return l;
+}
+
+
+int
+kmk_builtin_expr(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ EXPRINSTANCE This;
+ struct val *vp;
+ int rval;
+
+ if (argc > 1 && !strcmp(argv[1], "--"))
+ argv++;
+
+ /* Init globals */
+ This.pCtx = pCtx;
+ This.token = 0;
+ This.tokval = 0;
+ This.av = argv + 1;
+ expr_mem_init(&This);
+
+ rval = setjmp(This.g_expr_jmp);
+ if (!rval) {
+ nexttoken(&This, 0);
+ vp = eval0(&This);
+
+ if (This.token != EOI) {
+ error(&This);
+ /* NOTREACHED */
+ }
+
+ if (vp->type == integer)
+ kmk_builtin_ctx_printf(pCtx, 0, "%d\n", vp->u.i);
+ else
+ kmk_builtin_ctx_printf(pCtx, 0, "%s\n", vp->u.s);
+
+ rval = is_zero_or_null(&This, vp);
+ }
+ /* else: longjmp */
+
+ /* cleanup */
+ expr_mem_cleanup(&This);
+ return rval;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_expr", NULL };
+ (void) setlocale(LC_ALL, "");
+ return kmk_builtin_expr(argc, argv, envp, &Ctx);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin/fts.c b/src/kmk/kmkbuiltin/fts.c
new file mode 100644
index 0000000..10098b9
--- /dev/null
+++ b/src/kmk/kmkbuiltin/fts.c
@@ -0,0 +1,1461 @@
+/* $NetBSD: __fts13.c,v 1.44 2005/01/19 00:59:48 mycroft Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef __sun__
+# define _POSIX_C_SOURCE 199506L /* for dirfd() */
+# define __EXTENSIONS__ 1 /* for u_short and friends */
+#endif
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+/*#include <sys/cdefs.h>*/
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)fts.c 8.6 (Berkeley) 8/14/94";
+#else
+__RCSID("$NetBSD: __fts13.c,v 1.44 2005/01/19 00:59:48 mycroft Exp $");
+#endif
+#endif /* LIBC_SCCS and not lint */
+
+#include "config.h"
+/*#include "namespace.h"*/
+#ifndef _MSC_VER
+#include <sys/param.h>
+#endif
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include "ftsfake.h"
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+#endif
+#include "kmkbuiltin.h" /* MAXPATHLEN for GNU/hurd */
+
+#ifdef __sun__
+# include "solfakes.h"
+# define dirfd(dir) ((dir)->d_fd)
+#endif
+#ifdef _MSC_VER
+# include "mscfakes.h"
+# define dirfd(dir) -1
+#endif
+
+#if ! HAVE_NBTOOL_CONFIG_H
+# if !defined(__sun__) && !defined(__gnu_linux__) && !defined(__HAIKU__)
+# define HAVE_STRUCT_DIRENT_D_NAMLEN 1
+# endif
+#endif
+
+#if 0
+#ifdef __weak_alias
+#ifdef __LIBC12_SOURCE__
+__weak_alias(fts_children,_fts_children)
+__weak_alias(fts_close,_fts_close)
+__weak_alias(fts_open,_fts_open)
+__weak_alias(fts_read,_fts_read)
+__weak_alias(fts_set,_fts_set)
+#endif /* __LIBC12_SOURCE__ */
+#endif /* __weak_alias */
+#endif
+
+#ifdef __LIBC12_SOURCE__
+#define STAT stat12
+#else
+#define STAT stat
+#endif
+
+#ifdef __LIBC12_SOURCE__
+__warn_references(fts_children,
+ "warning: reference to compatibility fts_children();"
+ " include <fts.h> for correct reference")
+__warn_references(fts_close,
+ "warning: reference to compatibility fts_close();"
+ " include <fts.h> for correct reference")
+__warn_references(fts_open,
+ "warning: reference to compatibility fts_open();"
+ " include <fts.h> for correct reference")
+__warn_references(fts_read,
+ "warning: reference to compatibility fts_read();"
+ " include <fts.h> for correct reference")
+__warn_references(fts_set,
+ "warning: reference to compatibility fts_set();"
+ " include <fts.h> for correct reference")
+#endif
+
+static FTSENT *fts_alloc(FTS *, const char *, size_t);
+static FTSENT *fts_build(FTS *, int);
+static void fts_lfree(FTSENT *);
+static void fts_load(FTS *, FTSENT *);
+static size_t fts_maxarglen(char * const *);
+static size_t fts_pow2(size_t);
+static int fts_palloc(FTS *, size_t);
+static void fts_padjust(FTS *, FTSENT *);
+static FTSENT *fts_sort(FTS *, FTSENT *, size_t);
+static u_short fts_stat(FTS *, FTSENT *, int);
+#ifdef _MSC_VER
+static u_short fts_stat_dirent(FTS *sp, FTSENT *p, int follow, struct dirent *pDirEnt);
+#endif
+static int fts_safe_changedir(const FTS *, const FTSENT *, int,
+ const char *);
+
+#ifdef _MSC_VER
+# undef HAVE_FCHDIR
+#endif
+
+#if defined(__EMX__) || defined(_MSC_VER)
+# define NEED_STRRSLASH
+# define IS_SLASH(ch) ( (ch) == '/' || (ch) == '\\' )
+#else
+# define HAVE_FCHDIR
+# define IS_SLASH(ch) ( (ch) == '/' )
+#endif
+
+#define ISDOT(a) (a[0] == '.' && (!a[1] || (a[1] == '.' && !a[2])))
+
+#define CLR(opt) (sp->fts_options &= ~(opt))
+#define ISSET(opt) (sp->fts_options & (opt))
+#define SET(opt) (sp->fts_options |= (opt))
+
+#define CHDIR(sp, path) (!ISSET(FTS_NOCHDIR) && chdir(path))
+#ifdef HAVE_FCHDIR
+#define FCHDIR(sp, fd) (!ISSET(FTS_NOCHDIR) && fchdir(fd))
+#else
+#define FCHDIR(sp, rdir) CHDIR(sp, rdir)
+#endif
+
+
+/* fts_build flags */
+#define BCHILD 1 /* fts_children */
+#define BNAMES 2 /* fts_children, names only */
+#define BREAD 3 /* fts_read */
+
+#ifndef DTF_HIDEW
+#undef FTS_WHITEOUT
+#endif
+
+#ifndef _DIAGASSERT
+#define _DIAGASSERT assert
+#endif
+
+
+FTS *
+fts_open(argv, options, compar)
+ char * const *argv;
+ int options;
+ int (*compar)(const FTSENT **, const FTSENT **);
+{
+ FTS *sp;
+ FTSENT *p, *root;
+ size_t nitems;
+ FTSENT *parent, *tmp = NULL; /* pacify gcc */
+ size_t len;
+
+ _DIAGASSERT(argv != NULL);
+
+ /* Options check. */
+ if (options & ~FTS_OPTIONMASK) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ /* Allocate/initialize the stream */
+ if ((sp = malloc((u_int)sizeof(FTS))) == NULL)
+ return (NULL);
+ memset(sp, 0, sizeof(FTS));
+ sp->fts_compar = compar;
+ sp->fts_options = options;
+
+ /* Logical walks turn on NOCHDIR; symbolic links are too hard. */
+ if (ISSET(FTS_LOGICAL))
+ SET(FTS_NOCHDIR);
+
+ /*
+ * Start out with 1K of path space, and enough, in any case,
+ * to hold the user's paths.
+ */
+ if (fts_palloc(sp, MAX(fts_maxarglen(argv), MAXPATHLEN)))
+ goto mem1;
+
+ /* Allocate/initialize root's parent. */
+ if ((parent = fts_alloc(sp, "", 0)) == NULL)
+ goto mem2;
+ parent->fts_level = FTS_ROOTPARENTLEVEL;
+
+ /* Allocate/initialize root(s). */
+ for (root = NULL, nitems = 0; *argv; ++argv, ++nitems) {
+ /* Don't allow zero-length paths. */
+ if ((len = strlen(*argv)) == 0) {
+ errno = ENOENT;
+ goto mem3;
+ }
+
+ if ((p = fts_alloc(sp, *argv, len)) == NULL)
+ goto mem3;
+ p->fts_level = FTS_ROOTLEVEL;
+ p->fts_parent = parent;
+ p->fts_accpath = p->fts_name;
+ p->fts_info = fts_stat(sp, p, ISSET(FTS_COMFOLLOW));
+
+ /* Command-line "." and ".." are real directories. */
+ if (p->fts_info == FTS_DOT)
+ p->fts_info = FTS_D;
+
+ /*
+ * If comparison routine supplied, traverse in sorted
+ * order; otherwise traverse in the order specified.
+ */
+ if (compar) {
+ p->fts_link = root;
+ root = p;
+ } else {
+ p->fts_link = NULL;
+ if (root == NULL)
+ tmp = root = p;
+ else {
+ tmp->fts_link = p;
+ tmp = p;
+ }
+ }
+ }
+ if (compar && nitems > 1)
+ root = fts_sort(sp, root, nitems);
+
+ /*
+ * Allocate a dummy pointer and make fts_read think that we've just
+ * finished the node before the root(s); set p->fts_info to FTS_INIT
+ * so that everything about the "current" node is ignored.
+ */
+ if ((sp->fts_cur = fts_alloc(sp, "", 0)) == NULL)
+ goto mem3;
+ sp->fts_cur->fts_link = root;
+ sp->fts_cur->fts_info = FTS_INIT;
+
+ /*
+ * If using chdir(2), grab a file descriptor pointing to dot to insure
+ * that we can get back here; this could be avoided for some paths,
+ * but almost certainly not worth the effort. Slashes, symbolic links,
+ * and ".." are all fairly nasty problems. Note, if we can't get the
+ * descriptor we run anyway, just more slowly.
+ */
+ if (!ISSET(FTS_NOCHDIR)) {
+#ifdef HAVE_FCHDIR
+ if ((sp->fts_rfd = open(".", O_RDONLY | KMK_OPEN_NO_INHERIT, 0)) == -1)
+ SET(FTS_NOCHDIR);
+ else if (fcntl(sp->fts_rfd, F_SETFD, FD_CLOEXEC) == -1) {
+ close(sp->fts_rfd);
+ SET(FTS_NOCHDIR);
+ }
+#else
+ if ((sp->fts_rdir = getcwd(NULL, 0)) != NULL)
+ SET(FTS_NOCHDIR);
+#endif
+ }
+
+ return (sp);
+
+mem3: fts_lfree(root);
+ free(parent);
+mem2: free(sp->fts_path);
+mem1: free(sp);
+ return (NULL);
+}
+
+#ifdef NEED_STRRSLASH
+static char *strrslash(register char *psz)
+{
+ register char ch;
+ char *pszLast = NULL;
+ for (; (ch = *psz); psz++)
+ switch (ch)
+ {
+ case '/':
+ case '\\':
+ case ':':
+ pszLast = psz;
+ break;
+ }
+ return pszLast;
+}
+#endif
+
+static void
+fts_load(sp, p)
+ FTS *sp;
+ FTSENT *p;
+{
+ size_t len;
+ char *cp;
+
+ _DIAGASSERT(sp != NULL);
+ _DIAGASSERT(p != NULL);
+
+ /*
+ * Load the stream structure for the next traversal. Since we don't
+ * actually enter the directory until after the preorder visit, set
+ * the fts_accpath field specially so the chdir gets done to the right
+ * place and the user can access the first node. From fts_open it's
+ * known that the path will fit.
+ */
+ len = p->fts_pathlen = p->fts_namelen;
+ memmove(sp->fts_path, p->fts_name, len + 1);
+#ifdef NEED_STRRSLASH
+ if ((cp = strrslash(p->fts_name)) && (cp != p->fts_name || cp[1])) {
+#else
+ if ((cp = strrchr(p->fts_name, '/')) && (cp != p->fts_name || cp[1])) {
+#endif
+ len = strlen(++cp);
+ memmove(p->fts_name, cp, len + 1);
+ p->fts_namelen = len;
+ }
+ p->fts_accpath = p->fts_path = sp->fts_path;
+ sp->fts_dev = p->fts_dev;
+}
+
+int
+fts_close(sp)
+ FTS *sp;
+{
+ FTSENT *freep, *p;
+ int saved_errno = 0;
+
+ _DIAGASSERT(sp != NULL);
+
+ /*
+ * This still works if we haven't read anything -- the dummy structure
+ * points to the root list, so we step through to the end of the root
+ * list which has a valid parent pointer.
+ */
+ if (sp->fts_cur) {
+#ifndef _MSC_VER
+ if (ISSET(FTS_SYMFOLLOW))
+ (void)close(sp->fts_cur->fts_symfd);
+#endif
+ for (p = sp->fts_cur; p->fts_level >= FTS_ROOTLEVEL;) {
+ freep = p;
+ p = p->fts_link ? p->fts_link : p->fts_parent;
+ free(freep);
+ }
+ free(p);
+ }
+
+ /* Free up child linked list, sort array, path buffer. */
+ if (sp->fts_child)
+ fts_lfree(sp->fts_child);
+ if (sp->fts_array)
+ free(sp->fts_array);
+ free(sp->fts_path);
+
+ /* Return to original directory, save errno if necessary. */
+ if (!ISSET(FTS_NOCHDIR)) {
+#ifdef HAVE_FCHDIR
+ if (fchdir(sp->fts_rfd))
+ saved_errno = errno;
+ (void)close(sp->fts_rfd);
+#else
+ if (chdir(sp->fts_rdir))
+ saved_errno = errno;
+ free(sp->fts_rdir);
+ sp->fts_rdir = NULL;
+#endif
+ }
+
+ /* Free up the stream pointer. */
+ free(sp);
+ /* ISSET() is illegal after this, since the macro touches sp */
+
+ /* Set errno and return. */
+ if (saved_errno) {
+ errno = saved_errno;
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Special case a root of "/" so that slashes aren't appended which would
+ * cause paths to be written as "//foo".
+ */
+#define NAPPEND(p) \
+ (p->fts_level == FTS_ROOTLEVEL && p->fts_pathlen == 1 && \
+ IS_SLASH(p->fts_path[0]) ? 0 : p->fts_pathlen)
+
+FTSENT *
+fts_read(sp)
+ FTS *sp;
+{
+ FTSENT *p, *tmp;
+ int instr;
+ char *t;
+ int saved_errno;
+
+ _DIAGASSERT(sp != NULL);
+
+ /* If finished or unrecoverable error, return NULL. */
+ if (sp->fts_cur == NULL || ISSET(FTS_STOP))
+ return (NULL);
+
+ /* Set current node pointer. */
+ p = sp->fts_cur;
+
+ /* Save and zero out user instructions. */
+ instr = p->fts_instr;
+ p->fts_instr = FTS_NOINSTR;
+
+ /* Any type of file may be re-visited; re-stat and re-turn. */
+ if (instr == FTS_AGAIN) {
+ p->fts_info = fts_stat(sp, p, 0);
+ return (p);
+ }
+
+ /*
+ * Following a symlink -- SLNONE test allows application to see
+ * SLNONE and recover. If indirecting through a symlink, have
+ * keep a pointer to current location. If unable to get that
+ * pointer, follow fails.
+ */
+ if (instr == FTS_FOLLOW &&
+ (p->fts_info == FTS_SL || p->fts_info == FTS_SLNONE)) {
+ p->fts_info = fts_stat(sp, p, 1);
+ if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) {
+#ifdef HAVE_FCHDIR
+ if ((p->fts_symfd = open(".", O_RDONLY | KMK_OPEN_NO_INHERIT, 0)) == -1) {
+ p->fts_errno = errno;
+ p->fts_info = FTS_ERR;
+ } else if (fcntl(p->fts_symfd, F_SETFD, FD_CLOEXEC) == -1) {
+ p->fts_errno = errno;
+ p->fts_info = FTS_ERR;
+ close(p->fts_symfd);
+ } else
+ p->fts_flags |= FTS_SYMFOLLOW;
+#endif
+ }
+ return (p);
+ }
+
+ /* Directory in pre-order. */
+ if (p->fts_info == FTS_D) {
+ /* If skipped or crossed mount point, do post-order visit. */
+ if (instr == FTS_SKIP ||
+ (ISSET(FTS_XDEV) && p->fts_dev != sp->fts_dev)) {
+#ifdef HAVE_FCHDIR
+ if (p->fts_flags & FTS_SYMFOLLOW)
+ (void)close(p->fts_symfd);
+#endif
+ if (sp->fts_child) {
+ fts_lfree(sp->fts_child);
+ sp->fts_child = NULL;
+ }
+ p->fts_info = FTS_DP;
+ return (p);
+ }
+
+ /* Rebuild if only read the names and now traversing. */
+ if (sp->fts_child && ISSET(FTS_NAMEONLY)) {
+ CLR(FTS_NAMEONLY);
+ fts_lfree(sp->fts_child);
+ sp->fts_child = NULL;
+ }
+
+ /*
+ * Cd to the subdirectory.
+ *
+ * If have already read and now fail to chdir, whack the list
+ * to make the names come out right, and set the parent errno
+ * so the application will eventually get an error condition.
+ * Set the FTS_DONTCHDIR flag so that when we logically change
+ * directories back to the parent we don't do a chdir.
+ *
+ * If haven't read do so. If the read fails, fts_build sets
+ * FTS_STOP or the fts_info field of the node.
+ */
+ if (sp->fts_child) {
+ if (fts_safe_changedir(sp, p, -1, p->fts_accpath)) {
+ p->fts_errno = errno;
+ p->fts_flags |= FTS_DONTCHDIR;
+ for (p = sp->fts_child; p; p = p->fts_link)
+ p->fts_accpath =
+ p->fts_parent->fts_accpath;
+ }
+ } else if ((sp->fts_child = fts_build(sp, BREAD)) == NULL) {
+ if (ISSET(FTS_STOP))
+ return (NULL);
+ return (p);
+ }
+ p = sp->fts_child;
+ sp->fts_child = NULL;
+ goto name;
+ }
+
+ /* Move to the next node on this level. */
+next: tmp = p;
+ if ((p = p->fts_link) != NULL) {
+ free(tmp);
+
+ /*
+ * If reached the top, return to the original directory, and
+ * load the paths for the next root.
+ */
+ if (p->fts_level == FTS_ROOTLEVEL) {
+#ifdef HAVE_FCHDIR
+ if (FCHDIR(sp, sp->fts_rfd)) {
+#else
+ if (CHDIR(sp, sp->fts_rdir)) {
+#endif
+ SET(FTS_STOP);
+ return (NULL);
+ }
+ fts_load(sp, p);
+ return (sp->fts_cur = p);
+ }
+
+ /*
+ * User may have called fts_set on the node. If skipped,
+ * ignore. If followed, get a file descriptor so we can
+ * get back if necessary.
+ */
+ if (p->fts_instr == FTS_SKIP)
+ goto next;
+ if (p->fts_instr == FTS_FOLLOW) {
+ p->fts_info = fts_stat(sp, p, 1);
+ if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) {
+#ifdef HAVE_FCHDIR
+ if ((p->fts_symfd =
+ open(".", O_RDONLY | KMK_OPEN_NO_INHERIT, 0)) == -1) {
+ p->fts_errno = errno;
+ p->fts_info = FTS_ERR;
+ } else if (fcntl(p->fts_symfd, F_SETFD, FD_CLOEXEC) == -1) {
+ p->fts_errno = errno;
+ p->fts_info = FTS_ERR;
+ close(p->fts_symfd);
+ } else
+ p->fts_flags |= FTS_SYMFOLLOW;
+#endif
+ }
+ p->fts_instr = FTS_NOINSTR;
+ }
+
+name: t = sp->fts_path + NAPPEND(p->fts_parent);
+ *t++ = '/';
+ memmove(t, p->fts_name, (size_t)(p->fts_namelen + 1));
+ return (sp->fts_cur = p);
+ }
+
+ /* Move up to the parent node. */
+ p = tmp->fts_parent;
+ free(tmp);
+
+ if (p->fts_level == FTS_ROOTPARENTLEVEL) {
+ /*
+ * Done; free everything up and set errno to 0 so the user
+ * can distinguish between error and EOF.
+ */
+ free(p);
+ errno = 0;
+ return (sp->fts_cur = NULL);
+ }
+
+ /* Nul terminate the pathname. */
+ sp->fts_path[p->fts_pathlen] = '\0';
+
+ /*
+ * Return to the parent directory. If at a root node or came through
+ * a symlink, go back through the file descriptor. Otherwise, cd up
+ * one directory.
+ */
+ if (p->fts_level == FTS_ROOTLEVEL) {
+#ifdef HAVE_FCHDIR
+ if (FCHDIR(sp, sp->fts_rfd)) {
+#else
+ if (CHDIR(sp, sp->fts_rdir)) {
+#endif
+ SET(FTS_STOP);
+ return (NULL);
+ }
+#ifdef HAVE_FCHDIR
+ } else if (p->fts_flags & FTS_SYMFOLLOW) {
+ if (FCHDIR(sp, p->fts_symfd)) {
+ saved_errno = errno;
+ (void)close(p->fts_symfd);
+ errno = saved_errno;
+ SET(FTS_STOP);
+ return (NULL);
+ }
+ (void)close(p->fts_symfd);
+#else
+ (void)saved_errno;
+#endif
+ } else if (!(p->fts_flags & FTS_DONTCHDIR) &&
+ fts_safe_changedir(sp, p->fts_parent, -1, "..")) {
+ SET(FTS_STOP);
+ return (NULL);
+ }
+ p->fts_info = p->fts_errno ? FTS_ERR : FTS_DP;
+ return (sp->fts_cur = p);
+}
+
+/*
+ * Fts_set takes the stream as an argument although it's not used in this
+ * implementation; it would be necessary if anyone wanted to add global
+ * semantics to fts using fts_set. An error return is allowed for similar
+ * reasons.
+ */
+/* ARGSUSED */
+int
+fts_set(sp, p, instr)
+ FTS *sp;
+ FTSENT *p;
+ int instr;
+{
+
+ _DIAGASSERT(sp != NULL);
+ _DIAGASSERT(p != NULL);
+
+ if (instr && instr != FTS_AGAIN && instr != FTS_FOLLOW &&
+ instr != FTS_NOINSTR && instr != FTS_SKIP) {
+ errno = EINVAL;
+ return (1);
+ }
+ p->fts_instr = instr;
+ return (0);
+}
+
+FTSENT *
+fts_children(sp, instr)
+ FTS *sp;
+ int instr;
+{
+ FTSENT *p;
+#ifdef HAVE_FCHDIR
+ int fd;
+#else
+ char *pszRoot;
+ int rc;
+#endif
+
+ _DIAGASSERT(sp != NULL);
+
+ if (instr && instr != FTS_NAMEONLY) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ /* Set current node pointer. */
+ p = sp->fts_cur;
+
+ /*
+ * Errno set to 0 so user can distinguish empty directory from
+ * an error.
+ */
+ errno = 0;
+
+ /* Fatal errors stop here. */
+ if (ISSET(FTS_STOP))
+ return (NULL);
+
+ /* Return logical hierarchy of user's arguments. */
+ if (p->fts_info == FTS_INIT)
+ return (p->fts_link);
+
+ /*
+ * If not a directory being visited in pre-order, stop here. Could
+ * allow FTS_DNR, assuming the user has fixed the problem, but the
+ * same effect is available with FTS_AGAIN.
+ */
+ if (p->fts_info != FTS_D /* && p->fts_info != FTS_DNR */)
+ return (NULL);
+
+ /* Free up any previous child list. */
+ if (sp->fts_child)
+ fts_lfree(sp->fts_child);
+
+ if (instr == FTS_NAMEONLY) {
+ SET(FTS_NAMEONLY);
+ instr = BNAMES;
+ } else
+ instr = BCHILD;
+
+ /*
+ * If using chdir on a relative path and called BEFORE fts_read does
+ * its chdir to the root of a traversal, we can lose -- we need to
+ * chdir into the subdirectory, and we don't know where the current
+ * directory is, so we can't get back so that the upcoming chdir by
+ * fts_read will work.
+ */
+ if (p->fts_level != FTS_ROOTLEVEL || IS_SLASH(p->fts_accpath[0]) ||
+ ISSET(FTS_NOCHDIR))
+ return (sp->fts_child = fts_build(sp, instr));
+
+#ifdef HAVE_FCHDIR
+ if ((fd = open(".", O_RDONLY | KMK_OPEN_NO_INHERIT, 0)) == -1)
+#else
+ if ((pszRoot = getcwd(NULL, 0)) == NULL)
+#endif
+ return (sp->fts_child = NULL);
+ sp->fts_child = fts_build(sp, instr);
+#ifdef HAVE_FCHDIR
+ if (fchdir(fd)) {
+ (void)close(fd);
+ return (NULL);
+ }
+ (void)close(fd);
+#else
+ rc = chdir(pszRoot);
+ free(pszRoot);
+ if (rc)
+ return (NULL);
+#endif
+
+ return (sp->fts_child);
+}
+
+/*
+ * This is the tricky part -- do not casually change *anything* in here. The
+ * idea is to build the linked list of entries that are used by fts_children
+ * and fts_read. There are lots of special cases.
+ *
+ * The real slowdown in walking the tree is the stat calls. If FTS_NOSTAT is
+ * set and it's a physical walk (so that symbolic links can't be directories),
+ * we can do things quickly. First, if it's a 4.4BSD file system, the type
+ * of the file is in the directory entry. Otherwise, we assume that the number
+ * of subdirectories in a node is equal to the number of links to the parent.
+ * The former skips all stat calls. The latter skips stat calls in any leaf
+ * directories and for any files after the subdirectories in the directory have
+ * been found, cutting the stat calls by about 2/3.
+ */
+static FTSENT *
+fts_build(sp, type)
+ FTS *sp;
+ int type;
+{
+ struct dirent *dp;
+ FTSENT *p, *head;
+ size_t nitems;
+ FTSENT *cur, *tail;
+ DIR *dirp;
+ int adjust, cderrno, descend, len, level, nlinks, saved_errno, nostat;
+ size_t maxlen;
+#ifdef FTS_WHITEOUT
+ int oflag;
+#endif
+ char *cp = NULL; /* pacify gcc */
+
+ _DIAGASSERT(sp != NULL);
+
+ /* Set current node pointer. */
+ cur = sp->fts_cur;
+
+ /*
+ * Open the directory for reading. If this fails, we're done.
+ * If being called from fts_read, set the fts_info field.
+ */
+#if defined(FTS_WHITEOUT) && !defined(__OS2__)
+ if (ISSET(FTS_WHITEOUT))
+ oflag = DTF_NODUP|DTF_REWIND;
+ else
+ oflag = DTF_HIDEW|DTF_NODUP|DTF_REWIND;
+#elif defined(_MSC_VER)
+# define __opendir2(path, flag) birdDirOpenExtraInfo(path)
+#else
+#define __opendir2(path, flag) opendir(path)
+#endif
+ if ((dirp = __opendir2(cur->fts_accpath, oflag)) == NULL) {
+ if (type == BREAD) {
+ cur->fts_info = FTS_DNR;
+ cur->fts_errno = errno;
+ }
+ return (NULL);
+ }
+
+ /*
+ * Nlinks is the number of possible entries of type directory in the
+ * directory if we're cheating on stat calls, 0 if we're not doing
+ * any stat calls at all, -1 if we're doing stats on everything.
+ */
+ if (type == BNAMES) {
+ nlinks = 0;
+ nostat = 1;
+ } else if (ISSET(FTS_NOSTAT) && ISSET(FTS_PHYSICAL)) {
+ nlinks = cur->fts_nlink - (ISSET(FTS_SEEDOT) ? 0 : 2);
+ nostat = 1;
+ } else {
+ nlinks = -1;
+ nostat = 0;
+ }
+
+#ifdef notdef
+ (void)printf("nlinks == %d (cur: %d)\n", nlinks, cur->fts_nlink);
+ (void)printf("NOSTAT %d PHYSICAL %d SEEDOT %d\n",
+ ISSET(FTS_NOSTAT), ISSET(FTS_PHYSICAL), ISSET(FTS_SEEDOT));
+#endif
+ /*
+ * If we're going to need to stat anything or we want to descend
+ * and stay in the directory, chdir. If this fails we keep going,
+ * but set a flag so we don't chdir after the post-order visit.
+ * We won't be able to stat anything, but we can still return the
+ * names themselves. Note, that since fts_read won't be able to
+ * chdir into the directory, it will have to return different path
+ * names than before, i.e. "a/b" instead of "b". Since the node
+ * has already been visited in pre-order, have to wait until the
+ * post-order visit to return the error. There is a special case
+ * here, if there was nothing to stat then it's not an error to
+ * not be able to stat. This is all fairly nasty. If a program
+ * needed sorted entries or stat information, they had better be
+ * checking FTS_NS on the returned nodes.
+ */
+ cderrno = 0;
+ if (nlinks || type == BREAD) {
+#ifdef HAVE_FCHDIR
+ if (fts_safe_changedir(sp, cur, dirfd(dirp), NULL)) {
+#else
+ if (fts_safe_changedir(sp, cur, dirfd(dirp), cur->fts_accpath)) {
+#endif
+ if (nlinks && type == BREAD)
+ cur->fts_errno = errno;
+ cur->fts_flags |= FTS_DONTCHDIR;
+ descend = 0;
+ cderrno = errno;
+ } else
+ descend = 1;
+ } else
+ descend = 0;
+
+ /*
+ * Figure out the max file name length that can be stored in the
+ * current path -- the inner loop allocates more path as necessary.
+ * We really wouldn't have to do the maxlen calculations here, we
+ * could do them in fts_read before returning the path, but it's a
+ * lot easier here since the length is part of the dirent structure.
+ *
+ * If not changing directories set a pointer so that can just append
+ * each new name into the path.
+ */
+ len = NAPPEND(cur);
+ if (ISSET(FTS_NOCHDIR)) {
+ cp = sp->fts_path + len;
+ *cp++ = '/';
+ }
+ len++;
+ maxlen = sp->fts_pathlen - len;
+
+ level = cur->fts_level + 1;
+
+ /* Read the directory, attaching each entry to the `link' pointer. */
+ adjust = 0;
+ for (head = tail = NULL, nitems = 0; (dp = readdir(dirp)) != NULL;) {
+ size_t dlen;
+
+ if (!ISSET(FTS_SEEDOT) && ISDOT(dp->d_name))
+ continue;
+
+#if HAVE_STRUCT_DIRENT_D_NAMLEN
+ dlen = dp->d_namlen;
+#else
+ dlen = strlen(dp->d_name);
+#endif
+ if ((p = fts_alloc(sp, dp->d_name, dlen)) == NULL)
+ goto mem1;
+ if (dlen >= maxlen) { /* include space for NUL */
+ if (fts_palloc(sp, len + dlen + 1)) {
+ /*
+ * No more memory for path or structures. Save
+ * errno, free up the current structure and the
+ * structures already allocated.
+ */
+mem1: saved_errno = errno;
+ if (p)
+ free(p);
+ fts_lfree(head);
+ (void)closedir(dirp);
+ errno = saved_errno;
+ cur->fts_info = FTS_ERR;
+ SET(FTS_STOP);
+ return (NULL);
+ }
+ adjust = 1;
+ if (ISSET(FTS_NOCHDIR))
+ cp = sp->fts_path + len;
+ maxlen = sp->fts_pathlen - len;
+ }
+
+ p->fts_pathlen = len + dlen;
+ p->fts_parent = sp->fts_cur;
+ p->fts_level = level;
+
+#ifdef FTS_WHITEOUT
+ if (dp->d_type == DT_WHT)
+ p->fts_flags |= FTS_ISW;
+#endif
+
+ if (cderrno) {
+ if (nlinks) {
+ p->fts_info = FTS_NS;
+ p->fts_errno = cderrno;
+ } else
+ p->fts_info = FTS_NSOK;
+ p->fts_accpath = cur->fts_accpath;
+ } else if (nlinks == 0
+#ifdef DT_DIR
+ || (nostat &&
+ dp->d_type != DT_DIR && dp->d_type != DT_UNKNOWN)
+#endif
+ ) {
+ p->fts_accpath =
+ ISSET(FTS_NOCHDIR) ? p->fts_path : p->fts_name;
+ p->fts_info = FTS_NSOK;
+ } else {
+ /* Build a file name for fts_stat to stat. */
+ if (ISSET(FTS_NOCHDIR)) {
+ p->fts_accpath = p->fts_path;
+ memmove(cp, p->fts_name,
+ (size_t)(p->fts_namelen + 1));
+ } else
+ p->fts_accpath = p->fts_name;
+
+ /* Stat it. */
+#ifdef _MSC_VER
+ p->fts_info = fts_stat_dirent(sp, p, 0, dp);
+#else
+ p->fts_info = fts_stat(sp, p, 0);
+#endif
+
+ /* Decrement link count if applicable. */
+ if (nlinks > 0 && (p->fts_info == FTS_D ||
+ p->fts_info == FTS_DC || p->fts_info == FTS_DOT))
+ --nlinks;
+ }
+
+ /* We walk in directory order so "ls -f" doesn't get upset. */
+ p->fts_link = NULL;
+ if (head == NULL)
+ head = tail = p;
+ else {
+ tail->fts_link = p;
+ tail = p;
+ }
+ ++nitems;
+ }
+ (void)closedir(dirp);
+
+ /*
+ * If had to realloc the path, adjust the addresses for the rest
+ * of the tree.
+ */
+ if (adjust)
+ fts_padjust(sp, head);
+
+ /*
+ * If not changing directories, reset the path back to original
+ * state.
+ */
+ if (ISSET(FTS_NOCHDIR)) {
+ if (cp - 1 > sp->fts_path)
+ --cp;
+ *cp = '\0';
+ }
+
+ /*
+ * If descended after called from fts_children or after called from
+ * fts_read and nothing found, get back. At the root level we use
+ * the saved fd; if one of fts_open()'s arguments is a relative path
+ * to an empty directory, we wind up here with no other way back. If
+ * can't get back, we're done.
+ */
+ if (descend && (type == BCHILD || !nitems) &&
+ (cur->fts_level == FTS_ROOTLEVEL ?
+#ifdef HAVE_FCHDIR
+ FCHDIR(sp, sp->fts_rfd) :
+#else
+ CHDIR(sp, sp->fts_rdir) :
+#endif
+ fts_safe_changedir(sp, cur->fts_parent, -1, ".."))) {
+ cur->fts_info = FTS_ERR;
+ SET(FTS_STOP);
+ return (NULL);
+ }
+
+ /* If didn't find anything, return NULL. */
+ if (!nitems) {
+ if (type == BREAD)
+ cur->fts_info = FTS_DP;
+ return (NULL);
+ }
+
+ /* Sort the entries. */
+ if (sp->fts_compar && nitems > 1)
+ head = fts_sort(sp, head, nitems);
+ return (head);
+}
+
+#ifdef _MSC_VER
+/** Special version of fts_stat that takes the information from the directory
+ * entry returned by readdir().
+ *
+ * Directory listing returns all the stat information on systems likes
+ * Windows and OS/2. */
+static u_short
+fts_stat_dirent(FTS *sp, FTSENT *p, int follow, struct dirent *pDirEnt)
+{
+ FTSENT *t;
+ dev_t dev;
+ ino_t ino;
+ struct STAT *sbp, sb;
+ int saved_errno;
+
+ _DIAGASSERT(sp != NULL);
+ _DIAGASSERT(p != NULL);
+
+ /* If user needs stat info, stat buffer already allocated. */
+ sbp = ISSET(FTS_NOSTAT) ? &sb : p->fts_statp;
+
+ /*
+ * Copy over the stat info from the direntry.
+ */
+ *sbp = pDirEnt->d_stat;
+
+ /*
+ * If doing a logical walk, or application requested FTS_FOLLOW, do
+ * a stat(2) on symlinks. If that fails, assume non-existent
+ * symlink and set the errno from the stat call.
+ */
+ if (S_ISLNK(sbp->st_mode) && (ISSET(FTS_LOGICAL) || follow)) {
+ if (stat(p->fts_accpath, sbp)) {
+ saved_errno = errno;
+ errno = 0;
+ return (FTS_SLNONE);
+ }
+ }
+
+ if (S_ISDIR(sbp->st_mode)) {
+ /*
+ * Set the device/inode. Used to find cycles and check for
+ * crossing mount points. Also remember the link count, used
+ * in fts_build to limit the number of stat calls. It is
+ * understood that these fields are only referenced if fts_info
+ * is set to FTS_D.
+ */
+ dev = p->fts_dev = sbp->st_dev;
+ ino = p->fts_ino = sbp->st_ino;
+ p->fts_nlink = sbp->st_nlink;
+
+ if (ISDOT(p->fts_name))
+ return (FTS_DOT);
+
+ /*
+ * Cycle detection is done by brute force when the directory
+ * is first encountered. If the tree gets deep enough or the
+ * number of symbolic links to directories is high enough,
+ * something faster might be worthwhile.
+ */
+
+#ifdef _MSC_VER
+ if (ino && dev) /** @todo ino emulation on windows... */
+#endif
+ for (t = p->fts_parent;
+ t->fts_level >= FTS_ROOTLEVEL; t = t->fts_parent)
+ if (ino == t->fts_ino && dev == t->fts_dev) {
+ p->fts_cycle = t;
+ return (FTS_DC);
+ }
+ return (FTS_D);
+ }
+ if (S_ISLNK(sbp->st_mode))
+ return (FTS_SL);
+ if (S_ISREG(sbp->st_mode))
+ return (FTS_F);
+ return (FTS_DEFAULT);
+}
+
+#endif /* fts_stat_dirent */
+
+static u_short
+fts_stat(sp, p, follow)
+ FTS *sp;
+ FTSENT *p;
+ int follow;
+{
+ FTSENT *t;
+ dev_t dev;
+ ino_t ino;
+ struct STAT *sbp, sb;
+ int saved_errno;
+
+ _DIAGASSERT(sp != NULL);
+ _DIAGASSERT(p != NULL);
+
+ /* If user needs stat info, stat buffer already allocated. */
+ sbp = ISSET(FTS_NOSTAT) ? &sb : p->fts_statp;
+
+#ifdef FTS_WHITEOUT
+ /* check for whiteout */
+ if (p->fts_flags & FTS_ISW) {
+ if (sbp != &sb) {
+ memset(sbp, '\0', sizeof (*sbp));
+ sbp->st_mode = S_IFWHT;
+ }
+ return (FTS_W);
+ }
+#endif
+
+ /*
+ * If doing a logical walk, or application requested FTS_FOLLOW, do
+ * a stat(2). If that fails, check for a non-existent symlink. If
+ * fail, set the errno from the stat call.
+ */
+ if (ISSET(FTS_LOGICAL) || follow) {
+ if (stat(p->fts_accpath, sbp)) {
+ saved_errno = errno;
+ if (!lstat(p->fts_accpath, sbp)) {
+ errno = 0;
+ return (FTS_SLNONE);
+ }
+ p->fts_errno = saved_errno;
+ goto err;
+ }
+ } else if (lstat(p->fts_accpath, sbp)) {
+ p->fts_errno = errno;
+err: memset(sbp, 0, sizeof(struct STAT));
+ return (FTS_NS);
+ }
+
+ if (S_ISDIR(sbp->st_mode)) {
+ /*
+ * Set the device/inode. Used to find cycles and check for
+ * crossing mount points. Also remember the link count, used
+ * in fts_build to limit the number of stat calls. It is
+ * understood that these fields are only referenced if fts_info
+ * is set to FTS_D.
+ */
+ dev = p->fts_dev = sbp->st_dev;
+ ino = p->fts_ino = sbp->st_ino;
+ p->fts_nlink = sbp->st_nlink;
+
+ if (ISDOT(p->fts_name))
+ return (FTS_DOT);
+
+ /*
+ * Cycle detection is done by brute force when the directory
+ * is first encountered. If the tree gets deep enough or the
+ * number of symbolic links to directories is high enough,
+ * something faster might be worthwhile.
+ */
+
+#ifdef _MSC_VER
+ if (ino && dev) /** @todo ino emulation on windows... */
+#endif
+ for (t = p->fts_parent;
+ t->fts_level >= FTS_ROOTLEVEL; t = t->fts_parent)
+ if (ino == t->fts_ino && dev == t->fts_dev) {
+ p->fts_cycle = t;
+ return (FTS_DC);
+ }
+ return (FTS_D);
+ }
+ if (S_ISLNK(sbp->st_mode))
+ return (FTS_SL);
+ if (S_ISREG(sbp->st_mode))
+ return (FTS_F);
+ return (FTS_DEFAULT);
+}
+
+static FTSENT *
+fts_sort(sp, head, nitems)
+ FTS *sp;
+ FTSENT *head;
+ size_t nitems;
+{
+ FTSENT **ap, *p;
+
+ _DIAGASSERT(sp != NULL);
+ _DIAGASSERT(head != NULL);
+
+ /*
+ * Construct an array of pointers to the structures and call qsort(3).
+ * Reassemble the array in the order returned by qsort. If unable to
+ * sort for memory reasons, return the directory entries in their
+ * current order. Allocate enough space for the current needs plus
+ * 40 so don't realloc one entry at a time.
+ */
+ if (nitems > sp->fts_nitems) {
+ FTSENT **new;
+
+ new = realloc(sp->fts_array, sizeof(FTSENT *) * (nitems + 40));
+ if (new == 0)
+ return (head);
+ sp->fts_array = new;
+ sp->fts_nitems = nitems + 40;
+ }
+ for (ap = sp->fts_array, p = head; p; p = p->fts_link)
+ *ap++ = p;
+ qsort((void *)sp->fts_array, nitems, sizeof(FTSENT *),
+ (int (*)(const void *, const void *))sp->fts_compar);
+ for (head = *(ap = sp->fts_array); --nitems; ++ap)
+ ap[0]->fts_link = ap[1];
+ ap[0]->fts_link = NULL;
+ return (head);
+}
+
+static FTSENT *
+fts_alloc(sp, name, namelen)
+ FTS *sp;
+ const char *name;
+ size_t namelen;
+{
+ FTSENT *p;
+ size_t len;
+
+ _DIAGASSERT(sp != NULL);
+ _DIAGASSERT(name != NULL);
+
+#if defined(ALIGNBYTES) && defined(ALIGN)
+ /*
+ * The file name is a variable length array and no stat structure is
+ * necessary if the user has set the nostat bit. Allocate the FTSENT
+ * structure, the file name and the stat structure in one chunk, but
+ * be careful that the stat structure is reasonably aligned. Since the
+ * fts_name field is declared to be of size 1, the fts_name pointer is
+ * namelen + 2 before the first possible address of the stat structure.
+ */
+ len = sizeof(FTSENT) + namelen;
+ if (!ISSET(FTS_NOSTAT))
+ len += sizeof(struct STAT) + ALIGNBYTES;
+ if ((p = malloc(len)) == NULL)
+ return (NULL);
+
+ if (!ISSET(FTS_NOSTAT))
+ p->fts_statp =
+ (struct STAT *)ALIGN((u_long)(p->fts_name + namelen + 2));
+#else
+ (void)len;
+ if ((p = malloc(sizeof(FTSENT) + namelen)) == NULL)
+ return (NULL);
+
+ if (!ISSET(FTS_NOSTAT))
+ if ((p->fts_statp = malloc(sizeof(struct STAT))) == NULL) {
+ free(p);
+ return (NULL);
+ }
+#endif
+
+ /* Copy the name plus the trailing NULL. */
+ memmove(p->fts_name, name, namelen + 1);
+
+ p->fts_namelen = namelen;
+ p->fts_path = sp->fts_path;
+ p->fts_errno = 0;
+ p->fts_flags = 0;
+ p->fts_instr = FTS_NOINSTR;
+ p->fts_number = 0;
+ p->fts_pointer = NULL;
+ return (p);
+}
+
+static void
+fts_lfree(head)
+ FTSENT *head;
+{
+ FTSENT *p;
+
+ /* XXX: head may be NULL ? */
+
+ /* Free a linked list of structures. */
+ while ((p = head) != NULL) {
+ head = head->fts_link;
+
+#if !defined(ALIGNBYTES) || !defined(ALIGN)
+ if (p->fts_statp)
+ free(p->fts_statp);
+#endif
+ free(p);
+ }
+}
+
+static size_t
+fts_pow2(x)
+ size_t x;
+{
+
+ x--;
+ x |= x>>1;
+ x |= x>>2;
+ x |= x>>4;
+ x |= x>>8;
+ x |= x>>16;
+#if LONG_BIT > 32
+ x |= x>>32;
+#endif
+#if LONG_BIT > 64
+ x |= x>>64;
+#endif
+ x++;
+ return (x);
+}
+
+/*
+ * Allow essentially unlimited paths; find, rm, ls should all work on any tree.
+ * Most systems will allow creation of paths much longer than MAXPATHLEN, even
+ * though the kernel won't resolve them. Round up the new size to a power of 2,
+ * so we don't realloc the path 2 bytes at a time.
+ */
+static int
+fts_palloc(sp, size)
+ FTS *sp;
+ size_t size;
+{
+ char *new;
+
+ _DIAGASSERT(sp != NULL);
+
+#if 1
+ /* Protect against fts_pathlen overflow. */
+ if (size > USHRT_MAX + 1) {
+ errno = ENOMEM;
+ return (1);
+ }
+#endif
+ size = fts_pow2(size);
+ new = realloc(sp->fts_path, size);
+ if (new == 0)
+ return (1);
+ sp->fts_path = new;
+ sp->fts_pathlen = size;
+ return (0);
+}
+
+/*
+ * When the path is realloc'd, have to fix all of the pointers in structures
+ * already returned.
+ */
+static void
+fts_padjust(sp, head)
+ FTS *sp;
+ FTSENT *head;
+{
+ FTSENT *p;
+ char *addr;
+
+ _DIAGASSERT(sp != NULL);
+
+#define ADJUST(p) do { \
+ if ((p)->fts_accpath != (p)->fts_name) \
+ (p)->fts_accpath = \
+ addr + ((p)->fts_accpath - (p)->fts_path); \
+ (p)->fts_path = addr; \
+} while (/*CONSTCOND*/0)
+
+ addr = sp->fts_path;
+
+ /* Adjust the current set of children. */
+ for (p = sp->fts_child; p; p = p->fts_link)
+ ADJUST(p);
+
+ /* Adjust the rest of the tree, including the current level. */
+ for (p = head; p->fts_level >= FTS_ROOTLEVEL;) {
+ ADJUST(p);
+ p = p->fts_link ? p->fts_link : p->fts_parent;
+ }
+}
+
+static size_t
+fts_maxarglen(argv)
+ char * const *argv;
+{
+ size_t len, max;
+
+ _DIAGASSERT(argv != NULL);
+
+ for (max = 0; *argv; ++argv)
+ if ((len = strlen(*argv)) > max)
+ max = len;
+ return (max + 1);
+}
+
+/*
+ * Change to dir specified by fd or p->fts_accpath without getting
+ * tricked by someone changing the world out from underneath us.
+ * Assumes p->fts_dev and p->fts_ino are filled in.
+ */
+static int
+fts_safe_changedir(sp, p, fd, path)
+ const FTS *sp;
+ const FTSENT *p;
+ int fd;
+ const char *path;
+{
+ int oldfd = fd, ret = -1;
+ struct STAT sb;
+
+ if (ISSET(FTS_NOCHDIR))
+ return 0;
+
+#ifdef HAVE_FCHDIR
+ if (oldfd < 0) {
+ if (!path) /* shuts up gcc nonull checks*/
+ return -1;
+ fd = open(path, O_RDONLY | KMK_OPEN_NO_INHERIT);
+ if (fd == -1)
+ return -1;
+ }
+
+ if (fstat(fd, &sb) == -1)
+ goto bail;
+#else
+ if (stat(path, &sb))
+ goto bail;
+#endif
+
+ if (sb.st_ino != p->fts_ino || sb.st_dev != p->fts_dev) {
+ errno = ENOENT;
+ goto bail;
+ }
+
+#ifdef HAVE_FCHDIR
+ ret = fchdir(fd);
+#else
+ ret = chdir(path);
+#endif
+
+bail:
+#ifdef HAVE_FCHDIR
+ if (oldfd < 0) {
+ int save_errno = errno;
+ (void)close(fd);
+ errno = save_errno;
+ }
+#endif
+ return ret;
+}
diff --git a/src/kmk/kmkbuiltin/ftsfake.h b/src/kmk/kmkbuiltin/ftsfake.h
new file mode 100644
index 0000000..2518d30
--- /dev/null
+++ b/src/kmk/kmkbuiltin/ftsfake.h
@@ -0,0 +1,159 @@
+/* $NetBSD: fts.h,v 1.12 2005/02/03 04:39:32 perry Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)fts.h 8.3 (Berkeley) 8/14/94
+ */
+
+#ifndef _FTS_H_
+#define _FTS_H_
+
+#ifdef _MSC_VER
+# include "kmkbuiltin/mscfakes.h"
+#endif
+
+typedef struct {
+ struct _ftsent *fts_cur; /* current node */
+ struct _ftsent *fts_child; /* linked list of children */
+ struct _ftsent **fts_array; /* sort array */
+ dev_t fts_dev; /* starting device # */
+ char *fts_path; /* path for this descent */
+#if defined(_MSC_VER) || defined(__OS2__)
+ char *fts_rdir; /* path of root */
+#else
+ int fts_rfd; /* fd for root */
+#endif
+ u_int fts_pathlen; /* sizeof(path) */
+ u_int fts_nitems; /* elements in the sort array */
+ int (*fts_compar) /* compare function */
+ (const struct _ftsent **, const struct _ftsent **);
+
+#define FTS_COMFOLLOW 0x001 /* follow command line symlinks */
+#define FTS_LOGICAL 0x002 /* logical walk */
+#define FTS_NOCHDIR 0x004 /* don't change directories */
+#define FTS_NOSTAT 0x008 /* don't get stat info */
+#define FTS_PHYSICAL 0x010 /* physical walk */
+#define FTS_SEEDOT 0x020 /* return dot and dot-dot */
+#define FTS_XDEV 0x040 /* don't cross devices */
+#ifndef _MSC_VER
+#define FTS_WHITEOUT 0x080 /* return whiteout information */
+#endif
+#define FTS_OPTIONMASK 0x0ff /* valid user option mask */
+
+#define FTS_NAMEONLY 0x100 /* (private) child names only */
+#define FTS_STOP 0x200 /* (private) unrecoverable error */
+ int fts_options; /* fts_open options, global flags */
+} FTS;
+
+typedef struct _ftsent {
+ struct _ftsent *fts_cycle; /* cycle node */
+ struct _ftsent *fts_parent; /* parent directory */
+ struct _ftsent *fts_link; /* next file in directory */
+ long fts_number; /* local numeric value */
+ void *fts_pointer; /* local address value */
+ char *fts_accpath; /* access path */
+ char *fts_path; /* root path */
+ int fts_errno; /* errno for this node */
+#ifndef _MSC_VER
+ int fts_symfd; /* fd for symlink */
+#endif
+ u_short fts_pathlen; /* strlen(fts_path) */
+ u_short fts_namelen; /* strlen(fts_name) */
+
+ ino_t fts_ino; /* inode */
+ dev_t fts_dev; /* device */
+#ifdef __LIBC12_SOURCE__
+ u_int16_t fts_nlink; /* link count */
+#else
+#ifndef _MSC_VER
+ nlink_t fts_nlink; /* link count */
+#else
+ int fts_nlink; /* link count */
+#endif
+#endif
+
+#define FTS_ROOTPARENTLEVEL -1
+#define FTS_ROOTLEVEL 0
+ short fts_level; /* depth (-1 to N) */
+
+#define FTS_D 1 /* preorder directory */
+#define FTS_DC 2 /* directory that causes cycles */
+#define FTS_DEFAULT 3 /* none of the above */
+#define FTS_DNR 4 /* unreadable directory */
+#define FTS_DOT 5 /* dot or dot-dot */
+#define FTS_DP 6 /* postorder directory */
+#define FTS_ERR 7 /* error; errno is set */
+#define FTS_F 8 /* regular file */
+#define FTS_INIT 9 /* initialized only */
+#define FTS_NS 10 /* stat(2) failed */
+#define FTS_NSOK 11 /* no stat(2) requested */
+#define FTS_SL 12 /* symbolic link */
+#define FTS_SLNONE 13 /* symbolic link without target */
+#ifndef _MSC_VER
+#define FTS_W 14 /* whiteout object */
+#endif
+ u_short fts_info; /* user flags for FTSENT structure */
+
+#define FTS_DONTCHDIR 0x01 /* don't chdir .. to the parent */
+#define FTS_SYMFOLLOW 0x02 /* followed a symlink to get here */
+#ifndef _MSC_VER
+#define FTS_ISW 0x04 /* this is a whiteout object */
+#endif
+ u_short fts_flags; /* private flags for FTSENT structure */
+
+#define FTS_AGAIN 1 /* read node again */
+#define FTS_FOLLOW 2 /* follow symbolic link */
+#define FTS_NOINSTR 3 /* no instructions */
+#define FTS_SKIP 4 /* discard node */
+ u_short fts_instr; /* fts_set() instructions */
+
+#ifdef __LIBC12_SOURCE__
+ struct stat12 *fts_statp; /* stat(2) information */
+#else
+ struct stat *fts_statp; /* stat(2) information */
+#endif
+ char fts_name[1]; /* file name */
+} FTSENT;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+FTSENT *fts_children(FTS *, int);
+int fts_close(FTS *);
+FTS *fts_open(char * const *, int,
+ int (*)(const FTSENT **, const FTSENT **));
+FTSENT *fts_read(FTS *);
+int fts_set(FTS *, FTSENT *, int);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !_FTS_H_ */
diff --git a/src/kmk/kmkbuiltin/getopt1_r.c b/src/kmk/kmkbuiltin/getopt1_r.c
new file mode 100644
index 0000000..29e4d21
--- /dev/null
+++ b/src/kmk/kmkbuiltin/getopt1_r.c
@@ -0,0 +1,186 @@
+/* Reentrant version of getopt_long and getopt_long_only.
+
+Based on ../getopt*.*:
+
+ getopt_long and getopt_long_only entry points for GNU getopt.
+Copyright (C) 1987-1994, 1996-2016 Free Software Foundation, Inc.
+
+NOTE: The canonical source of this file is maintained with the GNU C Library.
+Bugs can be reported to bug-glibc@gnu.org.
+
+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/>.
+
+Modifications:
+ Copyright (c) 2018 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+*/
+
+#define FAKES_NO_GETOPT_H /* bird */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "getopt_r.h"
+
+#if !defined __STDC__ || !__STDC__
+/* This is a separate conditional since some stdc systems
+ reject `defined (const)'. */
+#ifndef const
+#define const
+#endif
+#endif
+
+#include <stdio.h>
+
+#if 0
+/* Comment out all this code if we are using the GNU C Library, and are not
+ actually compiling the library itself. This code is part of the GNU C
+ Library, but also included in many other GNU distributions. Compiling
+ and linking in this code is a waste when using the GNU C library
+ (especially if it is a shared library). Rather than having every GNU
+ program understand `configure --with-gnu-libc' and omit the object files,
+ it is simpler to just do this in the source for each such file. */
+
+#define GETOPT_INTERFACE_VERSION 2
+#if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2
+#include <gnu-versions.h>
+#if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION
+#define ELIDE_CODE
+#endif
+#endif
+#endif
+
+#if 1 //ndef ELIDE_CODE
+
+
+/* This needs to come after some library #include
+ to get __GNU_LIBRARY__ defined. */
+#ifdef __GNU_LIBRARY__
+#include <stdlib.h>
+#endif
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+int
+getopt_long_r (struct getopt_state_r *gos, int *opt_index)
+{
+ return _getopt_internal_r (gos, gos->long_options, opt_index, 0);
+}
+
+/* Like getopt_long, but '-' as well as '--' can indicate a long option.
+ If an option that starts with '-' (not '--') doesn't match a long option,
+ but does match a short option, it is parsed as a short option
+ instead. */
+
+int
+getopt_long_only_r (struct getopt_state_r *gos, int *opt_index)
+{
+ return _getopt_internal_r (gos, gos->long_options, opt_index, 0);
+}
+
+
+#endif /* #if 1 */ /* Not ELIDE_CODE. */
+
+#ifdef TEST
+
+#include <stdio.h>
+
+int
+main (int argc, char **argv)
+{
+ int c;
+ int digit_optind = 0;
+
+ while (1)
+ {
+ int this_option_optind = optind ? optind : 1;
+ int option_index = 0;
+ struct getopt_state_r gos;
+ static struct option long_options[] =
+ {
+ {"add", 1, 0, 0},
+ {"append", 0, 0, 0},
+ {"delete", 1, 0, 0},
+ {"verbose", 0, 0, 0},
+ {"create", 0, 0, 0},
+ {"file", 1, 0, 0},
+ {0, 0, 0, 0}
+ };
+
+ getopt_initialize_r (&gos, argc, argv, "abc:d:0123456789", long_options, NULL, NULL);
+ c = getopt_long_r (&geo, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c)
+ {
+ case 0:
+ printf ("option %s", long_options[option_index].name);
+ if (geo.optarg)
+ printf (" with arg %s", geo.optarg);
+ printf ("\n");
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_optind != 0 && digit_optind != this_option_optind)
+ printf ("digits occur in two different argv-elements.\n");
+ digit_optind = this_option_optind;
+ printf ("option %c\n", c);
+ break;
+
+ case 'a':
+ printf ("option a\n");
+ break;
+
+ case 'b':
+ printf ("option b\n");
+ break;
+
+ case 'c':
+ printf ("option c with value '%s'\n", geo.optarg);
+ break;
+
+ case 'd':
+ printf ("option d with value '%s'\n", geo.optarg);
+ break;
+
+ case '?':
+ break;
+
+ default:
+ printf ("?? getopt returned character code 0%o ??\n", c);
+ }
+ }
+
+ if (geo.optind < argc)
+ {
+ printf ("non-option ARGV-elements: ");
+ while (optind < argc)
+ printf ("%s ", argv[geo.optind++]);
+ printf ("\n");
+ }
+
+ exit (0);
+}
+
+#endif /* TEST */
diff --git a/src/kmk/kmkbuiltin/getopt_r.c b/src/kmk/kmkbuiltin/getopt_r.c
new file mode 100644
index 0000000..c531ba5
--- /dev/null
+++ b/src/kmk/kmkbuiltin/getopt_r.c
@@ -0,0 +1,1090 @@
+/* Reentrant version of getopt.
+
+Based on ../getopt*.*:
+
+ Getopt for GNU.
+NOTE: getopt is now part of the C library, so if you don't know what
+"Keep this file name-space clean" means, talk to drepper@gnu.org
+before changing it!
+
+Copyright (C) 1987-2016 Free Software Foundation, Inc.
+
+NOTE: The canonical source of this file is maintained with the GNU C Library.
+Bugs can be reported to bug-glibc@gnu.org.
+
+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/>.
+
+Modifications:
+ Copyright (c) 2018 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+*/
+
+/* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>.
+ Ditto for AIX 3.2 and <stdlib.h>. */
+#ifndef _NO_PROTO
+# define _NO_PROTO
+#endif
+
+#define FAKES_NO_GETOPT_H /* bird */
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#if !defined __STDC__ || !__STDC__
+/* This is a separate conditional since some stdc systems
+ reject `defined (const)'. */
+# ifndef const
+# define const
+# endif
+#endif
+
+#include <stdio.h>
+
+#if 0
+/* Comment out all this code if we are using the GNU C Library, and are not
+ actually compiling the library itself. This code is part of the GNU C
+ Library, but also included in many other GNU distributions. Compiling
+ and linking in this code is a waste when using the GNU C library
+ (especially if it is a shared library). Rather than having every GNU
+ program understand `configure --with-gnu-libc' and omit the object files,
+ it is simpler to just do this in the source for each such file. */
+
+#define GETOPT_INTERFACE_VERSION 2
+#if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2
+# include <gnu-versions.h>
+# if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION
+# define ELIDE_CODE
+# endif
+#endif
+#endif
+
+#if 1 //ndef ELIDE_CODE
+
+
+/* This needs to come after some library #include
+ to get __GNU_LIBRARY__ defined. */
+#ifdef __GNU_LIBRARY__
+/* Don't include stdlib.h for non-GNU C libraries because some of them
+ contain conflicting prototypes for getopt. */
+# include <stdlib.h>
+# include <unistd.h>
+#endif /* GNU C library. */
+#include <stdlib.h> /* bird: we don't define getopt, so we're good. */
+
+#ifdef VMS
+# include <unixlib.h>
+# if HAVE_STRING_H - 0
+# include <string.h>
+# endif
+#endif
+
+/* This is for other GNU distributions with internationalized messages.
+ When compiling libc, the _ macro is predefined. */
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+
+/* This version of `getopt' appears to the caller like standard Unix 'getopt'
+ but it behaves differently for the user, since it allows the user
+ to intersperse the options with the other arguments.
+
+ As `getopt' works, it permutes the elements of ARGV so that,
+ when it is done, all the options precede everything else. Thus
+ all application programs are extended to handle flexible argument order.
+
+ Setting the environment variable POSIXLY_CORRECT disables permutation.
+ Then the behavior is completely standard.
+
+ GNU application programs can use a third alternative mode in which
+ they can distinguish the relative order of options and other arguments. */
+
+#include "getopt_r.h"
+#include "err.h"
+#include <assert.h>
+
+#if 0 /* Moved to state_getopt_r in getopt_r.h. */
+/* For communication from `getopt' to the caller.
+ When `getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+char *optarg = NULL;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `getopt'.
+
+ On entry to `getopt', zero means this is the first call; initialize.
+
+ When `getopt' returns -1, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+/* 1003.2 says this must be 1 before any call. */
+int optind = 1;
+
+/* Formerly, initialization of getopt depended on optind==0, which
+ causes problems with re-calling getopt as programs generally don't
+ know that. */
+
+int __getopt_initialized = 0;
+
+/* The next char to be scanned in the option-element
+ in which the last option character we returned was found.
+ This allows us to pick up the scan where we left off.
+
+ If this is zero, or a null string, it means resume the scan
+ by advancing to the next ARGV-element. */
+
+static char *nextchar;
+
+/* Callers store zero here to inhibit the error message
+ for unrecognized options. */
+
+int opterr = 1;
+
+/* Set to an option character which was unrecognized.
+ This must be initialized on some systems to avoid linking in the
+ system's own getopt implementation. */
+
+int optopt = '?';
+#endif /* Moved to state_getopt_r in getopt_r.h. */
+
+/* Describe how to deal with options that follow non-option ARGV-elements.
+
+ If the caller did not specify anything,
+ the default is REQUIRE_ORDER if the environment variable
+ POSIXLY_CORRECT is defined, PERMUTE otherwise.
+
+ REQUIRE_ORDER means don't recognize them as options;
+ stop option processing when the first non-option is seen.
+ This is what Unix does.
+ This mode of operation is selected by either setting the environment
+ variable POSIXLY_CORRECT, or using `+' as the first character
+ of the list of option characters.
+
+ PERMUTE is the default. We permute the contents of ARGV as we scan,
+ so that eventually all the non-options are at the end. This allows options
+ to be given in any order, even with programs that were not written to
+ expect this.
+
+ RETURN_IN_ORDER is an option available to programs that were written
+ to expect options and other ARGV-elements in any order and that care about
+ the ordering of the two. We describe each non-option ARGV-element
+ as if it were the argument of an option with character code 1.
+ Using `-' as the first character of the list of option characters
+ selects this mode of operation.
+
+ The special argument `--' forces an end of option-scanning regardless
+ of the value of `ordering'. In the case of RETURN_IN_ORDER, only
+ `--' can cause `getopt' to return -1 with `optind' != ARGC. */
+
+/*static*/ enum
+{
+ REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER
+} /*ordering*/;
+
+#if 0 /* Moved to state_getopt_r in getopt_r.h. */
+/* Value of POSIXLY_CORRECT environment variable. */
+static char *posixly_correct;
+#endif
+
+
+#if 1 //def __GNU_LIBRARY__
+/* We want to avoid inclusion of string.h with non-GNU libraries
+ because there are many ways it can cause trouble.
+ On some systems, it contains special magic macros that don't work
+ in GCC. */
+# include <string.h>
+//# define my_index strchr
+#else
+
+# if HAVE_STRING_H
+# include <string.h>
+# else
+# include <strings.h>
+# endif
+
+#if 0 //def
+/* Avoid depending on library functions or files
+ whose names are inconsistent. */
+#ifndef getenv
+extern char *getenv ();
+#endif
+#endif
+
+static char *
+my_index (const char *str, int chr)
+{
+ while (*str)
+ {
+ if (*str == chr)
+ return (char *) str;
+ str++;
+ }
+ return 0;
+}
+
+/* If using GCC, we can safely declare strlen this way.
+ If not using GCC, it is ok not to declare it. */
+#ifdef __GNUC__
+/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h.
+ That was relevant to code that was here before. */
+# if (!defined __STDC__ || !__STDC__) && !defined strlen
+/* gcc with -traditional declares the built-in strlen to return int,
+ and has done so at least since version 2.4.5. -- rms. */
+extern int strlen (const char *);
+# endif /* not __STDC__ */
+#endif /* __GNUC__ */
+
+#endif /* not __GNU_LIBRARY__ */
+
+/* Handle permutation of arguments. */
+
+#if 0 /* Moved to state_getopt_r in getopt_r.h. */
+/* Describe the part of ARGV that contains non-options that have
+ been skipped. `first_nonopt' is the index in ARGV of the first of them;
+ `last_nonopt' is the index after the last of them. */
+
+static int first_nonopt;
+static int last_nonopt;
+#endif
+
+#if 0 //def _LIBC
+/* Bash 2.0 gives us an environment variable containing flags
+ indicating ARGV elements that should not be considered arguments. */
+
+/* Defined in getopt_init.c */
+extern char *__getopt_nonoption_flags;
+
+static int nonoption_flags_max_len;
+static int nonoption_flags_len;
+
+static int original_argc;
+static char *const *original_argv;
+
+/* Make sure the environment variable bash 2.0 puts in the environment
+ is valid for the getopt call we must make sure that the ARGV passed
+ to getopt is that one passed to the process. */
+static void __attribute__ ((unused))
+store_args_and_env (int argc, char *const *argv)
+{
+ /* XXX This is no good solution. We should rather copy the args so
+ that we can compare them later. But we must not use malloc(3). */
+ original_argc = argc;
+ original_argv = argv;
+}
+# ifdef text_set_element
+text_set_element (__libc_subinit, store_args_and_env);
+# endif /* text_set_element */
+
+# define SWAP_FLAGS(ch1, ch2) \
+ if (nonoption_flags_len > 0) \
+ { \
+ char __tmp = __getopt_nonoption_flags[ch1]; \
+ __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \
+ __getopt_nonoption_flags[ch2] = __tmp; \
+ }
+#else /* !_LIBC */
+# define SWAP_FLAGS(ch1, ch2) do { } while (0)
+#endif /* _LIBC */
+
+/* Exchange two adjacent subsequences of ARGV.
+ One subsequence is elements [first_nonopt,last_nonopt)
+ which contains all the non-options that have been skipped so far.
+ The other is elements [last_nonopt,optind), which contains all
+ the options processed since those non-options were skipped.
+
+ `first_nonopt' and `last_nonopt' are relocated so that they describe
+ the new indices of the non-options in ARGV after they are moved. */
+
+static void
+exchange (struct getopt_state_r *gos, char **argv)
+{
+ int bottom = gos->first_nonopt;
+ int middle = gos->last_nonopt;
+ int top = gos->optind;
+ char *tem;
+
+ /* Exchange the shorter segment with the far end of the longer segment.
+ That puts the shorter segment into the right place.
+ It leaves the longer segment in the right place overall,
+ but it consists of two parts that need to be swapped next. */
+
+#if 0 //def _LIBC
+ /* First make sure the handling of the `__getopt_nonoption_flags'
+ string can work normally. Our top argument must be in the range
+ of the string. */
+ if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len)
+ {
+ /* We must extend the array. The user plays games with us and
+ presents new arguments. */
+ char *new_str = malloc (top + 1);
+ if (new_str == NULL)
+ nonoption_flags_len = nonoption_flags_max_len = 0;
+ else
+ {
+ memset (__mempcpy (new_str, __getopt_nonoption_flags,
+ nonoption_flags_max_len),
+ '\0', top + 1 - nonoption_flags_max_len);
+ nonoption_flags_max_len = top + 1;
+ __getopt_nonoption_flags = new_str;
+ }
+ }
+#endif
+
+ while (top > middle && middle > bottom)
+ {
+ if (top - middle > middle - bottom)
+ {
+ /* Bottom segment is the short one. */
+ int len = middle - bottom;
+ register int i;
+
+ /* Swap it with the top part of the top segment. */
+ for (i = 0; i < len; i++)
+ {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[top - (middle - bottom) + i];
+ argv[top - (middle - bottom) + i] = tem;
+ SWAP_FLAGS (bottom + i, top - (middle - bottom) + i);
+ }
+ /* Exclude the moved bottom segment from further swapping. */
+ top -= len;
+ }
+ else
+ {
+ /* Top segment is the short one. */
+ int len = top - middle;
+ register int i;
+
+ /* Swap it with the bottom part of the bottom segment. */
+ for (i = 0; i < len; i++)
+ {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[middle + i];
+ argv[middle + i] = tem;
+ SWAP_FLAGS (bottom + i, middle + i);
+ }
+ /* Exclude the moved top segment from further swapping. */
+ bottom += len;
+ }
+ }
+
+ /* Update records for the slots the non-options now occupy. */
+
+ gos->first_nonopt += (gos->optind - gos->last_nonopt);
+ gos->last_nonopt = gos->optind;
+}
+
+/* Initialize the internal data */
+
+void
+getopt_initialize_r (struct getopt_state_r *gos, int argc,
+ char * const *argv, const char *shortopts,
+ const struct option *long_options,
+ char **envp, struct KMKBUILTINCTX *pCtx)
+{
+ assert (shortopts != NULL);
+
+ /* General initialization. */
+ gos->optarg = NULL;
+ gos->optind = 1;
+ gos->__getopt_initialized = (void *)(uintptr_t)&exchange;
+ gos->opterr = 1;
+ gos->optopt = '?';
+ gos->argc = argc;
+ gos->argv = argv;
+ gos->optstring = shortopts;
+ gos->len_optstring = strlen (shortopts);
+ gos->long_options = long_options;
+ gos->pCtx = pCtx;
+
+ /* Start processing options with ARGV-element 1 (since ARGV-element 0
+ is the program name); the sequence of previously skipped
+ non-option ARGV-elements is empty. */
+
+ gos->first_nonopt = gos->last_nonopt = gos->optind;
+
+ gos->nextchar = NULL;
+
+ if (!envp)
+ gos->posixly_correct = getenv("POSIXLY_CORRECT");
+ else
+ {
+ const char *psz;
+ size_t i = 0;
+ gos->posixly_correct = NULL;
+ while ((psz = envp[i]) != NULL)
+ {
+ if ( psz[0] == 'P'
+ && strncmp (psz, "POSIXLY_CORRECT=", sizeof("POSIXLY_CORRECT=") - 1) == 0)
+ {
+ gos->posixly_correct = psz + sizeof("POSIXLY_CORRECT=") - 1;
+ break;
+ }
+ i++;
+ }
+ }
+
+ /* Determine how to handle the ordering of options and nonoptions. */
+
+ if (shortopts[0] == '-')
+ {
+ gos->ordering = RETURN_IN_ORDER;
+ gos->optstring++;
+ gos->len_optstring--;
+ }
+ else if (shortopts[0] == '+')
+ {
+ gos->ordering = REQUIRE_ORDER;
+ gos->optstring++;
+ gos->len_optstring--;
+ }
+ else if (gos->posixly_correct != NULL)
+ gos->ordering = REQUIRE_ORDER;
+ else
+ gos->ordering = PERMUTE;
+
+#if 0 //def _LIBC
+ if (posixly_correct == NULL
+ && argc == original_argc && argv == original_argv)
+ {
+ if (nonoption_flags_max_len == 0)
+ {
+ if (__getopt_nonoption_flags == NULL
+ || __getopt_nonoption_flags[0] == '\0')
+ nonoption_flags_max_len = -1;
+ else
+ {
+ const char *orig_str = __getopt_nonoption_flags;
+ int len = nonoption_flags_max_len = strlen (orig_str);
+ if (nonoption_flags_max_len < argc)
+ nonoption_flags_max_len = argc;
+ __getopt_nonoption_flags =
+ (char *) malloc (nonoption_flags_max_len);
+ if (__getopt_nonoption_flags == NULL)
+ nonoption_flags_max_len = -1;
+ else
+ memset (__mempcpy (__getopt_nonoption_flags, orig_str, len),
+ '\0', nonoption_flags_max_len - len);
+ }
+ }
+ nonoption_flags_len = nonoption_flags_max_len;
+ }
+ else
+ nonoption_flags_len = 0;
+#endif
+
+ //return optstring;
+}
+
+/* Scan elements of ARGV (whose length is ARGC) for option characters
+ given in OPTSTRING.
+
+ If an element of ARGV starts with '-', and is not exactly "-" or "--",
+ then it is an option element. The characters of this element
+ (aside from the initial '-') are option characters. If `getopt'
+ is called repeatedly, it returns successively each of the option characters
+ from each of the option elements.
+
+ If `getopt' finds another option character, it returns that character,
+ updating `optind' and `nextchar' so that the next call to `getopt' can
+ resume the scan with the following option character or ARGV-element.
+
+ If there are no more option characters, `getopt' returns -1.
+ Then `optind' is the index in ARGV of the first ARGV-element
+ that is not an option. (The ARGV-elements have been permuted
+ so that those that are not options now come last.)
+
+ OPTSTRING is a string containing the legitimate option characters.
+ If an option character is seen that is not listed in OPTSTRING,
+ return '?' after printing an error message. If you set `opterr' to
+ zero, the error message is suppressed but we still return '?'.
+
+ If a char in OPTSTRING is followed by a colon, that means it wants an arg,
+ so the following text in the same ARGV-element, or the text of the following
+ ARGV-element, is returned in `optarg'. Two colons mean an option that
+ wants an optional arg; if there is text in the current ARGV-element,
+ it is returned in `optarg', otherwise `optarg' is set to zero.
+
+ If OPTSTRING starts with `-' or `+', it requests different methods of
+ handling the non-option ARGV-elements.
+ See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
+
+ Long-named options begin with `--' instead of `-'.
+ Their names may be abbreviated as long as the abbreviation is unique
+ or is an exact match for some defined option. If they have an
+ argument, it follows the option name in the same ARGV-element, separated
+ from the option name by a `=', or else the in next ARGV-element.
+ When `getopt' finds a long-named option, it returns 0 if that option's
+ `flag' field is nonzero, the value of the option's `val' field
+ if the `flag' field is zero.
+
+ The elements of ARGV aren't really const, because we permute them.
+ But we pretend they're const in the prototype to be compatible
+ with other systems.
+
+ LONGOPTS is a vector of `struct option' terminated by an
+ element containing a name which is zero.
+
+ LONGIND returns the index in LONGOPT of the long-named option found.
+ It is only valid when a long-named option has been found by the most
+ recent call.
+
+ If LONG_ONLY is nonzero, '-' as well as '--' can introduce
+ long-named options. */
+
+int
+_getopt_internal_r (struct getopt_state_r *gos, const struct option *longopts,
+ int *longind, int long_only)
+{
+ assert (gos->__getopt_initialized == (void *)(uintptr_t)&exchange);
+ gos->optarg = NULL;
+
+#if 0 /* requires explicit call now */
+ if (gos->optind == 0 || !gos->__getopt_initialized)
+ {
+ if (gos->optind == 0)
+ gos->optind = 1; /* Don't scan ARGV[0], the program name. */
+ optstring = _getopt_initialize_r (gos, gos->argc, gos->argv, optstring);
+ gos->__getopt_initialized = 1;
+ }
+#else
+ assert (gos->__getopt_initialized == (void *)(uintptr_t)&exchange);
+#endif
+
+ /* Test whether ARGV[optind] points to a non-option argument.
+ Either it does not have option syntax, or there is an environment flag
+ from the shell indicating it is not an option. The later information
+ is only used when the used in the GNU libc. */
+#if 0 //def _LIBC
+# define NONOPTION_P (gos->argv[gos->optind][0] != '-' || gos->argv[gos->optind][1] == '\0' \
+ || (gos->optind < gos->nonoption_flags_len \
+ && gos->__getopt_nonoption_flags[gos->optind] == '1'))
+#else
+# define NONOPTION_P (gos->argv[gos->optind][0] != '-' || gos->argv[gos->optind][1] == '\0')
+#endif
+
+ if (gos->nextchar == NULL || *gos->nextchar == '\0')
+ {
+ /* Advance to the next ARGV-element. */
+
+ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been
+ moved back by the user (who may also have changed the arguments). */
+ if (gos->last_nonopt > gos->optind)
+ gos->last_nonopt = gos->optind;
+ if (gos->first_nonopt > gos->optind)
+ gos->first_nonopt = gos->optind;
+
+ if (gos->ordering == PERMUTE)
+ {
+ /* If we have just processed some options following some non-options,
+ exchange them so that the options come first. */
+
+ if (gos->first_nonopt != gos->last_nonopt && gos->last_nonopt != gos->optind)
+ exchange (gos, (char **) gos->argv);
+ else if (gos->last_nonopt != gos->optind)
+ gos->first_nonopt = gos->optind;
+
+ /* Skip any additional non-options
+ and extend the range of non-options previously skipped. */
+
+ while (gos->optind < gos->argc && NONOPTION_P)
+ gos->optind++;
+ gos->last_nonopt = gos->optind;
+ }
+
+ /* The special ARGV-element `--' means premature end of options.
+ Skip it like a null option,
+ then exchange with previous non-options as if it were an option,
+ then skip everything else like a non-option. */
+
+ if (gos->optind != gos->argc && !strcmp (gos->argv[gos->optind], "--"))
+ {
+ gos->optind++;
+
+ if (gos->first_nonopt != gos->last_nonopt && gos->last_nonopt != gos->optind)
+ exchange (gos, (char **) gos->argv);
+ else if (gos->first_nonopt == gos->last_nonopt)
+ gos->first_nonopt = gos->optind;
+ gos->last_nonopt = gos->argc;
+
+ gos->optind = gos->argc;
+ }
+
+ /* If we have done all the ARGV-elements, stop the scan
+ and back over any non-options that we skipped and permuted. */
+
+ if (gos->optind == gos->argc)
+ {
+ /* Set the next-arg-index to point at the non-options
+ that we previously skipped, so the caller will digest them. */
+ if (gos->first_nonopt != gos->last_nonopt)
+ gos->optind = gos->first_nonopt;
+ return -1;
+ }
+
+ /* If we have come to a non-option and did not permute it,
+ either stop the scan or describe it to the caller and pass it by. */
+
+ if (NONOPTION_P)
+ {
+ if (gos->ordering == REQUIRE_ORDER)
+ return -1;
+ gos->optarg = gos->argv[gos->optind++];
+ return 1;
+ }
+
+ /* We have found another option-ARGV-element.
+ Skip the initial punctuation. */
+
+ gos->nextchar = (gos->argv[gos->optind] + 1
+ + (longopts != NULL && gos->argv[gos->optind][1] == '-'));
+ }
+
+ /* Decode the current option-ARGV-element. */
+
+ /* Check whether the ARGV-element is a long option.
+
+ If long_only and the ARGV-element has the form "-f", where f is
+ a valid short option, don't consider it an abbreviated form of
+ a long option that starts with f. Otherwise there would be no
+ way to give the -f short option.
+
+ On the other hand, if there's a long option "fubar" and
+ the ARGV-element is "-fu", do consider that an abbreviation of
+ the long option, just like "--fu", and not "-f" with arg "u".
+
+ This distinction seems to be the most useful approach. */
+
+ if (longopts != NULL
+ && (gos->argv[gos->optind][1] == '-'
+ || (long_only
+ && ( gos->argv[gos->optind][2]
+ || !memchr (gos->optstring, gos->argv[gos->optind][1], gos->len_optstring) )
+ )
+ )
+ )
+ {
+ char *nameend;
+ const struct option *p;
+ const struct option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = -1;
+ int option_index;
+
+ for (nameend = gos->nextchar; *nameend && *nameend != '='; nameend++)
+ /* Do nothing. */ ;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (!strncmp (p->name, gos->nextchar, nameend - gos->nextchar))
+ {
+ if ((unsigned int) (nameend - gos->nextchar)
+ == (unsigned int) strlen (p->name))
+ {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ }
+ else if (pfound == NULL)
+ {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ }
+ else
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+
+ if (ambig && !exact)
+ {
+ if (gos->opterr)
+ errx (gos->pCtx, 2, _("%s: option '%s' is ambiguous"),
+ gos->argv[0], gos->argv[gos->optind]);
+ gos->nextchar += strlen (gos->nextchar);
+ gos->optind++;
+ gos->optopt = 0;
+ return '?';
+ }
+
+ if (pfound != NULL)
+ {
+ option_index = indfound;
+ gos->optind++;
+ if (*nameend)
+ {
+ /* Don't test has_arg with >, because some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ gos->optarg = nameend + 1;
+ else
+ {
+ if (gos->opterr)
+ { /* bird: disambiguate */
+ if (gos->argv[gos->optind - 1][1] == '-')
+ /* --option */
+ errx (gos->pCtx, 2,
+ _("%s: option '--%s' doesn't allow an argument\n"),
+ gos->argv[0], pfound->name);
+ else
+ /* +option or -option */
+ errx (gos->pCtx, 2,
+ _("%s: option '%c%s' doesn't allow an argument\n"),
+ gos->argv[0], gos->argv[gos->optind - 1][0], pfound->name);
+ }
+
+ gos->nextchar += strlen (gos->nextchar);
+
+ gos->optopt = pfound->val;
+ return '?';
+ }
+ }
+ else if (pfound->has_arg == 1)
+ {
+ if (gos->optind < gos->argc)
+ gos->optarg = gos->argv[gos->optind++];
+ else
+ {
+ if (gos->opterr)
+ errx (gos->pCtx, 2,
+ _("%s: option '%s' requires an argument\n"),
+ gos->argv[0], gos->argv[gos->optind - 1]);
+ gos->nextchar += strlen (gos->nextchar);
+ gos->optopt = pfound->val;
+ return gos->optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ gos->nextchar += strlen (gos->nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag)
+ {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+
+ /* Can't find it as a long option. If this is not getopt_long_only,
+ or the option starts with '--' or is not a valid short
+ option, then it's an error.
+ Otherwise interpret it as a short option. */
+ if (!long_only || gos->argv[gos->optind][1] == '-'
+ || memchr(gos->optstring, *gos->nextchar, gos->len_optstring) == NULL)
+ {
+ if (gos->opterr)
+ {
+ if (gos->argv[gos->optind][1] == '-')
+ /* --option */
+ errx (gos->pCtx, 2, _("%s: unrecognized option '--%s'\n"),
+ gos->argv[0], gos->nextchar);
+ else
+ /* +option or -option */
+ errx (gos->pCtx, 2, _("%s: unrecognized option '%c%s'\n"),
+ gos->argv[0], gos->argv[gos->optind][0], gos->nextchar);
+ }
+ gos->nextchar = (char *) "";
+ gos->optind++;
+ gos->optopt = 0;
+ return '?';
+ }
+ }
+
+ /* Look at and handle the next short option-character. */
+
+ {
+ char c = *gos->nextchar++;
+ char *temp = (char *)memchr (gos->optstring, c, gos->len_optstring);
+
+ /* Increment `optind' when we start to process its last character. */
+ if (*gos->nextchar == '\0')
+ ++gos->optind;
+
+ if (temp == NULL || c == ':')
+ {
+ if (gos->opterr)
+ {
+ if (gos->posixly_correct)
+ /* 1003.2 specifies the format of this message. */
+ errx (gos->pCtx, 2, _("%s: illegal option -- %c\n"),
+ gos->argv[0], c);
+ else
+ errx (gos->pCtx, 2, _("%s: invalid option -- %c\n"),
+ gos->argv[0], c);
+ }
+ gos->optopt = c;
+ return '?';
+ }
+ /* Convenience. Treat POSIX -W foo same as long option --foo */
+ if (temp[0] == 'W' && temp[1] == ';')
+ {
+ char *nameend;
+ const struct option *p;
+ const struct option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = 0;
+ int option_index;
+
+ /* This is an option that requires an argument. */
+ if (*gos->nextchar != '\0')
+ {
+ gos->optarg = gos->nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ gos->optind++;
+ }
+ else if (gos->optind == gos->argc)
+ {
+ if (gos->opterr)
+ {
+ /* 1003.2 specifies the format of this message. */
+ errx (gos->pCtx, 2, _("%s: option requires an argument -- %c\n"),
+ gos->argv[0], c);
+ }
+ gos->optopt = c;
+ if (gos->optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ return c;
+ }
+ else
+ /* We already incremented `optind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ gos->optarg = gos->argv[gos->optind++];
+
+ /* optarg is now the argument, see if it's in the
+ table of longopts. */
+
+ for (gos->nextchar = nameend = gos->optarg; *nameend && *nameend != '='; nameend++)
+ /* Do nothing. */ ;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (!strncmp (p->name, gos->nextchar, nameend - gos->nextchar))
+ {
+ if ((unsigned int) (nameend - gos->nextchar) == strlen (p->name))
+ {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ }
+ else if (pfound == NULL)
+ {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ }
+ else
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+ if (ambig && !exact)
+ {
+ if (gos->opterr)
+ errx (gos->pCtx, 2, _("%s: option '-W %s' is ambiguous\n"),
+ gos->argv[0], gos->argv[gos->optind]);
+ gos->nextchar += strlen (gos->nextchar);
+ gos->optind++;
+ return '?';
+ }
+ if (pfound != NULL)
+ {
+ option_index = indfound;
+ if (*nameend)
+ {
+ /* Don't test has_arg with >, because some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ gos->optarg = nameend + 1;
+ else
+ {
+ if (gos->opterr)
+ errx (gos->pCtx, 2,
+ _("%s: option '-W %s' doesn't allow an argument\n"),
+ gos->argv[0], pfound->name);
+
+ gos->nextchar += strlen (gos->nextchar);
+ return '?';
+ }
+ }
+ else if (pfound->has_arg == 1)
+ {
+ if (gos->optind < gos->argc)
+ gos->optarg = gos->argv[gos->optind++];
+ else
+ {
+ if (gos->opterr)
+ errx (gos->pCtx, 2,
+ _("%s: option '%s' requires an argument\n"),
+ gos->argv[0], gos->argv[gos->optind - 1]);
+ gos->nextchar += strlen (gos->nextchar);
+ return gos->optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ gos->nextchar += strlen (gos->nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag)
+ {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+ gos->nextchar = NULL;
+ return 'W'; /* Let the application handle it. */
+ }
+ if (temp[1] == ':')
+ {
+ if (temp[2] == ':')
+ {
+ /* This is an option that accepts an argument optionally. */
+ if (*gos->nextchar != '\0')
+ {
+ gos->optarg = gos->nextchar;
+ gos->optind++;
+ }
+ else
+ gos->optarg = NULL;
+ gos->nextchar = NULL;
+ }
+ else
+ {
+ /* This is an option that requires an argument. */
+ if (*gos->nextchar != '\0')
+ {
+ gos->optarg = gos->nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ gos->optind++;
+ }
+ else if (gos->optind == gos->argc)
+ {
+ if (gos->opterr)
+ {
+ /* 1003.2 specifies the format of this message. */
+ errx (gos->pCtx, 2,
+ _("%s: option requires an argument -- %c\n"),
+ gos->argv[0], c);
+ }
+ gos->optopt = c;
+ if (gos->optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ }
+ else
+ /* We already incremented `optind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ gos->optarg = gos->argv[gos->optind++];
+ gos->nextchar = NULL;
+ }
+ }
+ return c;
+ }
+}
+
+int
+getopt_r (struct getopt_state_r *gos)
+{
+ return _getopt_internal_r (gos, NULL, NULL, 0);
+}
+
+#endif /* #if 1 */ /* Not ELIDE_CODE. */
+
+#ifdef TEST
+
+/* Compile with -DTEST to make an executable for use in testing
+ the above definition of `getopt'. */
+
+int
+main (int argc, char **argv)
+{
+ int c;
+ int digit_optind = 0;
+ struct getopt_state_r = gos;
+
+ getopt_initialize_r (&gos, argc, argv, "abc:d:0123456789", NULL, NULL, NULL);
+
+ while (1)
+ {
+ int this_option_optind = gos.optind ? gos.optind : 1;
+
+ c = getopt_r (&gos);
+ if (c == -1)
+ break;
+
+ switch (c)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_optind != 0 && digit_optind != this_option_optind)
+ printf ("digits occur in two different argv-elements.\n");
+ digit_optind = this_option_optind;
+ printf ("option %c\n", c);
+ break;
+
+ case 'a':
+ printf ("option a\n");
+ break;
+
+ case 'b':
+ printf ("option b\n");
+ break;
+
+ case 'c':
+ printf ("option c with value '%s'\n", optarg);
+ break;
+
+ case '?':
+ break;
+
+ default:
+ printf ("?? getopt returned character code 0%o ??\n", c);
+ }
+ }
+
+ if (gos.optind < argc)
+ {
+ printf ("non-option ARGV-elements: ");
+ while (gos.optind < argc)
+ printf ("%s ", argv[gos.optind++]);
+ printf ("\n");
+ }
+
+ exit (0);
+}
+
+#endif /* TEST */
diff --git a/src/kmk/kmkbuiltin/getopt_r.h b/src/kmk/kmkbuiltin/getopt_r.h
new file mode 100644
index 0000000..b9f3c8b
--- /dev/null
+++ b/src/kmk/kmkbuiltin/getopt_r.h
@@ -0,0 +1,182 @@
+/* Reentrant version of getopt.
+
+Based on ../getopt*.*:
+
+ Declarations for getopt.
+Copyright (C) 1989-2016 Free Software Foundation, Inc.
+
+NOTE: The canonical source of this file is maintained with the GNU C Library.
+Bugs can be reported to bug-glibc@gnu.org.
+
+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/>.
+
+Modifications:
+ Copyright (c) 2018 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+*/
+
+/* Not quite safe to mix when converting code. */
+#ifdef _GETOPT_H
+# define _GETOPT_H "getopt.h was included already"
+# error "getopt.h was included already"
+#endif
+
+#ifndef INCLUDED_GETOPT_R_H
+#define INCLUDED_GETOPT_R_H 1
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct getopt_state_r
+{
+
+/* For communication from `getopt' to the caller.
+ When `getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+/*extern*/ char *optarg;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `getopt'.
+
+ On entry to `getopt', zero means this is the first call; initialize.
+
+ When `getopt' returns -1, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+/*extern*/ int optind;
+
+/* Callers store zero here to inhibit the error message `getopt' prints
+ for unrecognized options. */
+
+/*extern*/ int opterr;
+
+/* Set to an option character which was unrecognized. */
+
+/*extern*/ int optopt;
+
+
+/* Internal state: */
+
+/* The next char to be scanned in the option-element
+ in which the last option character we returned was found.
+ This allows us to pick up the scan where we left off.
+
+ If this is zero, or a null string, it means resume the scan
+ by advancing to the next ARGV-element. */
+
+/*static*/ char *nextchar;
+
+/* REQUIRE_ORDER, PERMUTE or RETURN_IN_ORDER, see getopt_r.c. */
+/*static*/ int ordering;
+
+/* Value of POSIXLY_CORRECT environment variable. */
+/*static*/ const char *posixly_correct; /* bird: added 'const' */
+
+/* Describe the part of ARGV that contains non-options that have
+ been skipped. `first_nonopt' is the index in ARGV of the first of them;
+ `last_nonopt' is the index after the last of them. */
+
+/*static*/ int first_nonopt;
+/*static*/ int last_nonopt;
+
+/* Mainly for asserting usage sanity. */
+/*static*/ void *__getopt_initialized;
+
+/* New internal state (to resubmitting same parameters in each call): */
+ /* new: the argument vector length. */
+ int argc;
+ /* new: the argument vector. */
+ char * const *argv;
+ /* new: the short option string (can be NULL/empty). */
+ const char *optstring;
+ /* new: the short option string length. */
+ size_t len_optstring;
+ /* new: the long options (can be NULL) */
+ const struct option *long_options;
+ /* Output context for err.h. */
+ struct KMKBUILTINCTX *pCtx;
+} getopt_state_r;
+
+
+#ifndef no_argument
+
+/* Describe the long-named options requested by the application.
+ The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector
+ of `struct option' terminated by an element containing a name which is
+ zero.
+
+ The field `has_arg' is:
+ no_argument (or 0) if the option does not take an argument,
+ required_argument (or 1) if the option requires an argument,
+ optional_argument (or 2) if the option takes an optional argument.
+
+ If the field `flag' is not NULL, it points to a variable that is set
+ to the value given in the field `val' when the option is found, but
+ left unchanged if the option is not found.
+
+ To have a long-named option do something other than set an `int' to
+ a compiled-in constant, such as set a value from `optarg', set the
+ option's `flag' field to zero and its `val' field to a nonzero
+ value (the equivalent single-letter option character, if there is
+ one). For long options that have a zero `flag' field, `getopt'
+ returns the contents of the `val' field. */
+
+struct option
+{
+#if defined (__STDC__) && __STDC__
+ const char *name;
+#else
+ char *name;
+#endif
+ /* has_arg can't be an enum because some compilers complain about
+ type mismatches in all the code that assumes it is an int. */
+ int has_arg;
+ int *flag;
+ int val;
+};
+
+/* Names for the values of the `has_arg' field of `struct option'. */
+
+#define no_argument 0
+#define required_argument 1
+#define optional_argument 2
+
+#endif /* Same as ../getopt.h. Fix later? */
+
+extern void getopt_initialize_r (struct getopt_state_r *gos, int argc,
+ char *const *argv, const char *shortopts,
+ const struct option *longopts,
+ char **envp, struct KMKBUILTINCTX *pCtx);
+extern int getopt_r (struct getopt_state_r *gos);
+extern int getopt_long_r (struct getopt_state_r *gos, int *longind);
+extern int getopt_long_only_r (struct getopt_state_r *gos, int *longind);
+
+/* Internal only. Users should not call this directly. */
+extern int _getopt_internal_r (struct getopt_state_r *gos,
+ const struct option *longopts,
+ int *longind, int long_only);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* getopt_r.h */
diff --git a/src/kmk/kmkbuiltin/haikufakes.c b/src/kmk/kmkbuiltin/haikufakes.c
new file mode 100644
index 0000000..15b4a3a
--- /dev/null
+++ b/src/kmk/kmkbuiltin/haikufakes.c
@@ -0,0 +1,53 @@
+/* $Id: haikufakes.c 2546 2011-10-01 19:49:54Z bird $ */
+/** @file
+ * Fake Unix/BSD stuff for Haiku.
+ */
+
+/*
+ * Copyright (c) 2005-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/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "config.h"
+#include <errno.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include "haikufakes.h"
+
+
+int haiku_lchmod(const char *pszPath, mode_t mode)
+{
+ /*
+ * Weed out symbolic links.
+ */
+ struct stat s;
+ if ( !lstat(pszPath, &s)
+ && S_ISLNK(s.st_mode))
+ {
+ errno = -ENOSYS;
+ return -1;
+ }
+
+ return chmod(pszPath, mode);
+}
+
diff --git a/src/kmk/kmkbuiltin/haikufakes.h b/src/kmk/kmkbuiltin/haikufakes.h
new file mode 100644
index 0000000..eff20bb
--- /dev/null
+++ b/src/kmk/kmkbuiltin/haikufakes.h
@@ -0,0 +1,42 @@
+/* $Id: haikufakes.h 2656 2012-09-10 20:39:16Z bird $ */
+/** @file
+ * Unix/BSD fakes for Haiku.
+ */
+
+/*
+ * Copyright (c) 2005-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/>
+ *
+ */
+
+#ifndef ___haikufakes_h
+#define ___haikufakes_h
+
+#define EX_OK 0
+#define EX_OSERR 1
+#define EX_NOUSER 1
+#define EX_USAGE 1
+#define EX_TEMPFAIL 1
+#define EX_SOFTWARE 1
+
+#define lutimes(path, tvs) utimes(path, tvs)
+#define lchmod haiku_lchmod
+
+extern int haiku_lchmod(const char *pszPath, mode_t mode);
+
+#endif
+
diff --git a/src/kmk/kmkbuiltin/install.c b/src/kmk/kmkbuiltin/install.c
new file mode 100644
index 0000000..a6d1ca8
--- /dev/null
+++ b/src/kmk/kmkbuiltin/install.c
@@ -0,0 +1,1248 @@
+/*
+ * Copyright (c) 1987, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0 /*ndef lint*/
+static const char copyright[] =
+"@(#) Copyright (c) 1987, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)xinstall.c 8.1 (Berkeley) 7/21/93";
+#endif /* not lint */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/xinstall/xinstall.c,v 1.66 2005/01/25 14:34:57 ssouhlal Exp $");
+#endif
+
+#define FAKES_NO_GETOPT_H
+#include "config.h"
+#ifndef _MSC_VER
+# include <sys/param.h>
+# if !defined(__HAIKU__) && !defined(__gnu_hurd__)
+# include <sys/mount.h>
+# endif
+# include <sys/wait.h>
+# include <sys/time.h>
+#endif /* !_MSC_VER */
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include "err.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef __HAIKU__
+# include <sysexits.h>
+#endif
+#ifdef __NetBSD__
+# include <util.h>
+# define strtofflags(a, b, c) string_to_flags(a, b, c)
+#endif
+#include <unistd.h>
+#if defined(__EMX__) || defined(_MSC_VER)
+# include <process.h>
+#endif
+#include "getopt_r.h"
+#ifdef __sun__
+# include "solfakes.h"
+#endif
+#ifdef _MSC_VER
+# include "mscfakes.h"
+#endif
+#ifdef __HAIKU__
+# include "haikufakes.h"
+#endif
+#include "kmkbuiltin.h"
+#include "k/kDefs.h" /* for K_OS */
+#include "dos2unix.h"
+
+
+extern void * bsd_setmode(const char *p);
+extern mode_t bsd_getmode(const void *bbox, mode_t omode);
+
+#ifndef MAXBSIZE
+# define MAXBSIZE 0x20000
+#endif
+
+#define MAX_CMP_SIZE (16 * 1024 * 1024)
+
+#define DIRECTORY 0x01 /* Tell install it's a directory. */
+#define SETFLAGS 0x02 /* Tell install to set flags. */
+#define NOCHANGEBITS (UF_IMMUTABLE | UF_APPEND | SF_IMMUTABLE | SF_APPEND)
+#define BACKUP_SUFFIX ".old"
+
+#ifndef O_BINARY
+# define O_BINARY 0
+#endif
+
+#ifndef EFTYPE
+# define EFTYPE EINVAL
+#endif
+
+#if defined(__WIN32__) || defined(__WIN64__) || defined(__OS2__)
+# define IS_SLASH(ch) ((ch) == '/' || (ch) == '\\')
+#else
+# define IS_SLASH(ch) ((ch) == '/')
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct INSTALLINSTANCE
+{
+ PKMKBUILTINCTX pCtx;
+
+ gid_t gid;
+ uid_t uid;
+ int dobackup, docompare, dodir, dopreserve, dostrip, nommap, safecopy, verbose, mode_given;
+ mode_t mode;
+ const char *suffix;
+ int ignore_perm_errors;
+ int hard_link_files_when_possible;
+ int verbose_hard_link_refusal;
+ int verbose_hard_link_mode_mismatch;
+ int dos2unix;
+} INSTALLINSTANCE;
+typedef INSTALLINSTANCE *PINSTALLINSTANCE;
+
+enum
+{
+ kInstOpt_Help = 261,
+ kInstOpt_Version,
+ kInstOpt_Verbose,
+ kInstOpt_Quiet,
+ kInstOpt_IgnorePermErrors,
+ kInstOpt_NoIgnorePermErrors,
+ kInstOpt_HardLinkFilesWhenPossible,
+ kInstOpt_NoHardLinkFilesWhenPossible,
+ kInstOpt_Dos2Unix,
+ kInstOpt_Unix2Dos,
+ kInstOpt_VerboseHardLinkRefusal,
+ kInstOpt_QuietHardLinkRefusal,
+ kInstOpt_VerboseHardLinkModeMismatch,
+ kInstOpt_QuietHardLinkModeMismatch
+};
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static struct option long_options[] =
+{
+ { "help", no_argument, 0, kInstOpt_Help },
+ { "version", no_argument, 0, kInstOpt_Version },
+ { "quiet", no_argument, 0, kInstOpt_Quiet },
+ { "ignore-perm-errors", no_argument, 0, kInstOpt_IgnorePermErrors },
+ { "no-ignore-perm-errors", no_argument, 0, kInstOpt_NoIgnorePermErrors },
+ { "hard-link-files-when-possible", no_argument, 0, kInstOpt_HardLinkFilesWhenPossible },
+ { "no-hard-link-files-when-possible", no_argument, 0, kInstOpt_NoHardLinkFilesWhenPossible },
+ { "verbose-hard-link-refusal", no_argument, 0, kInstOpt_VerboseHardLinkRefusal },
+ { "quiet-hard-link-refusal", no_argument, 0, kInstOpt_QuietHardLinkRefusal },
+ { "verbose-hard-link-mode-mismatch", no_argument, 0, kInstOpt_VerboseHardLinkModeMismatch },
+ { "quiet-hard-link-mode-mismatch", no_argument, 0, kInstOpt_QuietHardLinkModeMismatch },
+ { "dos2unix", no_argument, 0, kInstOpt_Dos2Unix },
+ { "unix2dos", no_argument, 0, kInstOpt_Unix2Dos },
+#if 1 /* GNU coreutils compatibility: */
+ { "compare", no_argument, 0, 'C' },
+ { "directory", no_argument, 0, 'd' },
+ { "group", required_argument, 0, 'g' },
+ { "mode", required_argument, 0, 'm' },
+ { "owner", required_argument, 0, 'o' },
+ { "strip", no_argument, 0, 's' },
+ { "suffix", required_argument, 0, 'S' },
+ { "verbose", no_argument, 0, 'v' },
+#endif
+ { 0, 0, 0, 0 },
+};
+
+
+static int copy(PINSTALLINSTANCE, int, const char *, int *, const char *);
+static int compare(int, size_t, int, size_t);
+static int create_newfile(PINSTALLINSTANCE, const char *, int, struct stat *);
+static int create_tempfile(const char *, char *, size_t);
+static int install(PINSTALLINSTANCE, const char *, const char *, u_long, u_int);
+static int install_dir(PINSTALLINSTANCE, char *);
+static u_long numeric_id(PINSTALLINSTANCE, const char *, const char *);
+static int strip(PINSTALLINSTANCE, const char *);
+static int usage(PKMKBUILTINCTX, int);
+static char *last_slash(const char *);
+static KBOOL needs_dos2unix_conversion(const char *pszFilename);
+static KBOOL needs_unix2dos_conversion(const char *pszFilename);
+
+int
+kmk_builtin_install(int argc, char *argv[], char ** envp, PKMKBUILTINCTX pCtx)
+{
+ INSTALLINSTANCE This;
+ struct getopt_state_r gos;
+ struct stat from_sb, to_sb;
+ mode_t *set;
+ u_long fset = 0;
+ int ch, no_target;
+ u_int iflags;
+ char *flags;
+ const char *group, *owner, *to_name;
+ (void)envp;
+
+ /* Initialize global instance data. */
+ This.pCtx = pCtx;
+ This.mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
+ This.suffix = BACKUP_SUFFIX;
+ This.gid = 0;
+ This.uid = 0;
+ This.dobackup = 0;
+ This.docompare = 0;
+ This.dodir = 0;
+ This.dopreserve = 0;
+ This.dostrip = 0;
+ This.nommap = 0;
+ This.safecopy = 0;
+ This.verbose = 0;
+ This.mode_given = 0;
+ This.ignore_perm_errors = geteuid() != 0;
+ This.hard_link_files_when_possible = 0;
+ This.verbose_hard_link_refusal = 0;
+ This.verbose_hard_link_mode_mismatch = 0;
+ This.dos2unix = 0;
+
+ iflags = 0;
+ group = owner = NULL;
+ getopt_initialize_r(&gos, argc, argv, "B:bCcdf:g:Mm:o:pSsv", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1)
+ switch(ch) {
+ case 'B':
+ This.suffix = gos.optarg;
+ /* FALLTHROUGH */
+ case 'b':
+ This.dobackup = 1;
+ break;
+ case 'C':
+ This.docompare = 1;
+ break;
+ case 'c':
+ /* For backwards compatibility. */
+ break;
+ case 'd':
+ This.dodir = 1;
+ break;
+ case 'f':
+#if defined(UF_IMMUTABLE) && K_OS != K_OS_GNU_KFBSD && K_OS != K_OS_GNU_HURD
+ flags = optarg;
+ if (strtofflags(&flags, &fset, NULL))
+ return errx(pCtx, EX_USAGE, "%s: invalid flag", flags);
+ iflags |= SETFLAGS;
+#else
+ (void)flags;
+#endif
+ break;
+ case 'g':
+ group = gos.optarg;
+ break;
+ case 'M':
+ This.nommap = 1;
+ break;
+ case 'm':
+ if (!(set = bsd_setmode(gos.optarg)))
+ return errx(pCtx, EX_USAGE, "invalid file mode: %s", gos.optarg);
+ This.mode = bsd_getmode(set, 0);
+ free(set);
+ This.mode_given = 1;
+ break;
+ case 'o':
+ owner = gos.optarg;
+ break;
+ case 'p':
+ This.docompare = This.dopreserve = 1;
+ break;
+ case 'S':
+ This.safecopy = 1;
+ This.verbose_hard_link_refusal = 0;
+ break;
+ case 's':
+ This.dostrip = 1;
+ This.verbose_hard_link_refusal = 0;
+ break;
+ case 'v':
+ case kInstOpt_Verbose:
+ This.verbose = 1;
+ break;
+ case kInstOpt_Quiet:
+ This.verbose = 0;
+ break;
+ case kInstOpt_Help:
+ usage(pCtx, 0);
+ return 0;
+ case kInstOpt_Version:
+ return kbuild_version(argv[0]);
+ case kInstOpt_IgnorePermErrors:
+ This.ignore_perm_errors = 1;
+ break;
+ case kInstOpt_NoIgnorePermErrors:
+ This.ignore_perm_errors = 0;
+ break;
+ case kInstOpt_HardLinkFilesWhenPossible:
+ This.hard_link_files_when_possible = 1;
+ break;
+ case kInstOpt_NoHardLinkFilesWhenPossible:
+ This.hard_link_files_when_possible = 0;
+ break;
+ case kInstOpt_VerboseHardLinkRefusal:
+ This.verbose_hard_link_refusal = 1;
+ break;
+ case kInstOpt_QuietHardLinkRefusal:
+ This.verbose_hard_link_refusal = 0;
+ break;
+ case kInstOpt_VerboseHardLinkModeMismatch:
+ This.verbose_hard_link_mode_mismatch = 1;
+ break;
+ case kInstOpt_QuietHardLinkModeMismatch:
+ This.verbose_hard_link_mode_mismatch = 0;
+ break;
+ case kInstOpt_Dos2Unix:
+ This.dos2unix = 1;
+ This.verbose_hard_link_refusal = 0;
+ break;
+ case kInstOpt_Unix2Dos:
+ This.dos2unix = -1;
+ This.verbose_hard_link_refusal = 0;
+ break;
+ case '?':
+ default:
+ return usage(pCtx, 1);
+ }
+ argc -= gos.optind;
+ argv += gos.optind;
+
+ /* some options make no sense when creating directories */
+ if (This.dostrip && This.dodir) {
+ warnx(pCtx, "-d and -s may not be specified together");
+ return usage(pCtx, 1);
+ }
+
+ /* must have at least two arguments, except when creating directories */
+ if (argc == 0 || (argc == 1 && !This.dodir))
+ return usage(pCtx, 1);
+
+ /* and unix2dos doesn't combine well with a couple of other options. */
+ if (This.dos2unix != 0) {
+ if (This.docompare) {
+ warnx(pCtx, "-C/-p and --dos2unix/unix2dos may not be specified together");
+ return usage(pCtx, 1);
+ }
+ if (This.dostrip) {
+ warnx(pCtx, "-s and --dos2unix/unix2dos may not be specified together");
+ return usage(pCtx, 1);
+ }
+ }
+
+ /* need to make a temp copy so we can compare stripped version */
+ if (This.docompare && This.dostrip)
+ This.safecopy = 1;
+
+ /* get group and owner id's */
+ if (group != NULL) {
+#ifndef _MSC_VER
+ struct group *gp;
+ if ((gp = getgrnam(group)) != NULL)
+ This.gid = gp->gr_gid;
+ else
+#endif
+ {
+ This.gid = (gid_t)numeric_id(&This, group, "group");
+ if (This.gid == (gid_t)-1)
+ return 1;
+ }
+ } else
+ This.gid = (gid_t)-1;
+
+ if (owner != NULL) {
+#ifndef _MSC_VER
+ struct passwd *pp;
+ if ((pp = getpwnam(owner)) != NULL)
+ This.uid = pp->pw_uid;
+ else
+#endif
+ {
+ This.uid = (uid_t)numeric_id(&This, owner, "user");
+ if (This.uid == (uid_t)-1)
+ return 1;
+ }
+ } else
+ This.uid = (uid_t)-1;
+
+ if (This.dodir) {
+ for (; *argv != NULL; ++argv) {
+ int rc = install_dir(&This, *argv);
+ if (rc)
+ return rc;
+ }
+ return EX_OK;
+ /* NOTREACHED */
+ }
+
+ no_target = stat(to_name = argv[argc - 1], &to_sb);
+ if (!no_target && S_ISDIR(to_sb.st_mode)) {
+ for (; *argv != to_name; ++argv) {
+ int rc = install(&This, *argv, to_name, fset, iflags | DIRECTORY);
+ if (rc)
+ return rc;
+ }
+ return EX_OK;
+ }
+
+ /* can't do file1 file2 directory/file */
+ if (argc != 2) {
+ warnx(pCtx, "wrong number or types of arguments");
+ return usage(pCtx, 1);
+ }
+
+ if (!no_target) {
+ if (stat(*argv, &from_sb))
+ return err(pCtx, EX_OSERR, "%s", *argv);
+ if (!S_ISREG(to_sb.st_mode)) {
+ errno = EFTYPE;
+ return err(pCtx, EX_OSERR, "%s", to_name);
+ }
+ if (to_sb.st_dev == from_sb.st_dev &&
+ to_sb.st_dev != 0 &&
+ to_sb.st_ino == from_sb.st_ino &&
+ to_sb.st_ino != 0 &&
+ !This.hard_link_files_when_possible)
+ return errx(pCtx, EX_USAGE,
+ "%s and %s are the same file", *argv, to_name);
+ }
+ return install(&This, *argv, to_name, fset, iflags);
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+mode_t g_fUMask;
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_install", NULL };
+ umask(g_fUMask = umask(0077));
+ return kmk_builtin_install(argc, argv, envp, &Ctx);
+}
+#endif
+
+static u_long
+numeric_id(PINSTALLINSTANCE pThis, const char *name, const char *type)
+{
+ u_long val;
+ char *ep;
+
+ /*
+ * XXX
+ * We know that uid_t's and gid_t's are unsigned longs.
+ */
+ errno = 0;
+ val = strtoul(name, &ep, 10);
+ if (errno)
+ return err(pThis->pCtx, -1, "%s", name);
+ if (*ep != '\0')
+ return errx(pThis->pCtx, -1, "unknown %s %s", type, name);
+ return (val);
+}
+
+/*
+ * install --
+ * build a path name and install the file
+ */
+static int
+install(PINSTALLINSTANCE pThis, const char *from_name, const char *to_name, u_long fset, u_int flags)
+{
+ struct stat from_sb, temp_sb, to_sb;
+ struct timeval tvb[2];
+ int devnull, files_match, from_fd, serrno, target;
+ int tempcopy, temp_fd, to_fd;
+ char backup[MAXPATHLEN], *p, pathbuf[MAXPATHLEN], tempfile[MAXPATHLEN];
+ int rc = EX_OK;
+
+ files_match = 0;
+ from_fd = -1;
+ to_fd = -1;
+ temp_fd = -1;
+
+ /* If try to install NULL file to a directory, fails. */
+ if (flags & DIRECTORY
+#if defined(__EMX__) || defined(_MSC_VER)
+ || ( stricmp(from_name, _PATH_DEVNULL)
+ && stricmp(from_name, "nul")
+# ifdef __EMX__
+ && stricmp(from_name, "/dev/nul")
+# endif
+ )
+#else
+ || strcmp(from_name, _PATH_DEVNULL)
+#endif
+ ) {
+ if (stat(from_name, &from_sb))
+ return err(pThis->pCtx, EX_OSERR, "%s", from_name);
+ if (!S_ISREG(from_sb.st_mode)) {
+ errno = EFTYPE;
+ return err(pThis->pCtx, EX_OSERR, "%s", from_name);
+ }
+ /* Build the target path. */
+ if (flags & DIRECTORY) {
+ (void)snprintf(pathbuf, sizeof(pathbuf), "%s/%s",
+ to_name,
+ (p = last_slash(from_name)) ? ++p : from_name);
+ to_name = pathbuf;
+ }
+ devnull = 0;
+ } else {
+ devnull = 1;
+ }
+
+ target = stat(to_name, &to_sb) == 0;
+
+ /* Only install to regular files. */
+ if (target && !S_ISREG(to_sb.st_mode)) {
+ errno = EFTYPE;
+ warn(pThis->pCtx, "%s", to_name);
+ return EX_OK;
+ }
+
+ /* Only copy safe if the target exists. */
+ tempcopy = pThis->safecopy && target;
+
+ /* Try hard linking if wanted and possible. */
+ if (pThis->hard_link_files_when_possible)
+ {
+#ifdef KBUILD_OS_OS2
+ const char *why_not = "not supported on OS/2";
+#else
+ const char *why_not = NULL;
+ if (devnull) {
+ why_not = "/dev/null";
+ } else if (pThis->dostrip) {
+ why_not = "strip (-s)";
+ } else if (pThis->docompare) {
+ why_not = "compare (-C)";
+ } else if (pThis->dobackup) {
+ why_not = "backup (-b/-B)";
+ } else if (pThis->safecopy) {
+ why_not = "safe copy (-S)";
+ } else if (lstat(from_name, &temp_sb)) {
+ why_not = "lstat on source failed";
+ } else if (S_ISLNK(temp_sb.st_mode)) {
+ why_not = "symlink";
+ } else if (!S_ISREG(temp_sb.st_mode)) {
+ why_not = "not regular file";
+# if defined(KBUILD_OS_WINDOWS) || defined(KBUILD_OS_OS2)
+ } else if ((pThis->mode & S_IWUSR) != (from_sb.st_mode & S_IWUSR)) {
+# else
+ } else if (pThis->mode != (from_sb.st_mode & ALLPERMS)) {
+# endif
+ if ( pThis->verbose_hard_link_mode_mismatch
+ || pThis->verbose_hard_link_refusal)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0,
+ "install: warning: Not hard linking, mode differs: 0%03o, desires 0%03o\n"
+ "install: src path '%s'\n"
+ "install: dst path '%s'\n",
+ (from_sb.st_mode & ALLPERMS), pThis->mode, from_name, to_name);
+ why_not = NULL;
+ } else if (pThis->uid != (uid_t)-1 && pThis->uid != from_sb.st_uid) {
+ why_not = "uid mismatch";
+ } else if (pThis->gid != (gid_t)-1 && pThis->gid != from_sb.st_gid) {
+ why_not = "gid mismatch";
+ } else if (pThis->dos2unix > 0 && needs_dos2unix_conversion(from_name)) {
+ why_not = "dos2unix";
+ } else if (pThis->dos2unix < 0 && needs_unix2dos_conversion(from_name)) {
+ why_not = "unix2dos";
+ } else {
+ int rcLink = link(from_name, to_name);
+ if (rcLink != 0 && errno == EEXIST) {
+ unlink(to_name);
+ rcLink = link(from_name, to_name);
+ }
+ if (rcLink == 0) {
+ if (pThis->verbose)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0,
+ "install: %s -> %s (hardlinked)\n", from_name, to_name);
+ goto l_done;
+ }
+ if (pThis->verbose)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: hard linking '%s' to '%s' failed: %s\n",
+ to_name, from_name, strerror(errno));
+ why_not = NULL;
+ }
+#endif
+ if (why_not && pThis->verbose_hard_link_refusal)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: not hard linking '%s' to '%s' because: %s\n",
+ to_name, from_name, why_not);
+
+ /* Can't hard link or we failed, continue as nothing happend. */
+ }
+
+ if (!devnull && (from_fd = open(from_name, O_RDONLY | O_BINARY | KMK_OPEN_NO_INHERIT, 0)) < 0)
+ return err(pThis->pCtx, EX_OSERR, "%s", from_name);
+
+ /* If we don't strip, we can compare first. */
+ if (pThis->docompare && !pThis->dostrip && target) {
+ if ((to_fd = open(to_name, O_RDONLY | O_BINARY | KMK_OPEN_NO_INHERIT, 0)) < 0) {
+ rc = err(pThis->pCtx, EX_OSERR, "%s", to_name);
+ goto l_done;
+ }
+ if (devnull)
+ files_match = to_sb.st_size == 0;
+ else
+ files_match = !compare(from_fd, (size_t)from_sb.st_size,
+ to_fd, (size_t)to_sb.st_size);
+
+ /* Close "to" file unless we match. */
+ if (!files_match) {
+ (void)close(to_fd);
+ to_fd = -1;
+ }
+ }
+
+ if (!files_match) {
+ if (tempcopy) {
+ to_fd = create_tempfile(to_name, tempfile,
+ sizeof(tempfile));
+ if (to_fd < 0) {
+ rc = err(pThis->pCtx, EX_OSERR, "%s", tempfile);
+ goto l_done;
+ }
+ } else {
+ if ((to_fd = create_newfile(pThis, to_name, target, &to_sb)) < 0) {
+ rc = err(pThis->pCtx, EX_OSERR, "%s", to_name);
+ goto l_done;
+ }
+ if (pThis->verbose)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: %s -> %s\n", from_name, to_name);
+ }
+ if (!devnull) {
+ rc = copy(pThis, from_fd, from_name, &to_fd, tempcopy ? tempfile : to_name);
+ if (rc)
+ goto l_done;
+ }
+ }
+
+ if (pThis->dostrip) {
+#if defined(__EMX__) || defined(_MSC_VER)
+ /* close before we strip. */
+ close(to_fd);
+ to_fd = -1;
+#endif
+ rc = strip(pThis, tempcopy ? tempfile : to_name);
+ if (rc)
+ goto l_done;
+
+ /*
+ * Re-open our fd on the target, in case we used a strip
+ * that does not work in-place -- like GNU binutils strip.
+ */
+#if !defined(__EMX__) && !defined(_MSC_VER)
+ close(to_fd);
+#endif
+ to_fd = open(tempcopy ? tempfile : to_name, O_RDONLY | O_BINARY | KMK_OPEN_NO_INHERIT, 0);
+ if (to_fd < 0) {
+ rc = err(pThis->pCtx, EX_OSERR, "stripping %s", to_name);
+ goto l_done;
+ }
+ }
+
+ /*
+ * Compare the stripped temp file with the target.
+ */
+ if (pThis->docompare && pThis->dostrip && target) {
+ temp_fd = to_fd;
+
+ /* Re-open to_fd using the real target name. */
+ if ((to_fd = open(to_name, O_RDONLY | O_BINARY | KMK_OPEN_NO_INHERIT, 0)) < 0) {
+ rc = err(pThis->pCtx, EX_OSERR, "%s", to_name);
+ goto l_done;
+ }
+
+ if (fstat(temp_fd, &temp_sb)) {
+ serrno = errno;
+ (void)unlink(tempfile);
+ errno = serrno;
+ rc = err(pThis->pCtx, EX_OSERR, "%s", tempfile);
+ goto l_done;
+ }
+
+ if (compare(temp_fd, (size_t)temp_sb.st_size,
+ to_fd, (size_t)to_sb.st_size) == 0) {
+ /*
+ * If target has more than one link we need to
+ * replace it in order to snap the extra links.
+ * Need to preserve target file times, though.
+ */
+#if !defined(_MSC_VER) && !defined(__EMX__)
+ if (to_sb.st_nlink != 1) {
+ tvb[0].tv_sec = to_sb.st_atime;
+ tvb[0].tv_usec = 0;
+ tvb[1].tv_sec = to_sb.st_mtime;
+ tvb[1].tv_usec = 0;
+ (void)utimes(tempfile, tvb);
+ } else
+#endif
+ {
+
+ files_match = 1;
+ (void)unlink(tempfile);
+ }
+ (void) close(temp_fd);
+ temp_fd = -1;
+ }
+ }
+
+ /*
+ * Move the new file into place if doing a safe copy
+ * and the files are different (or just not compared).
+ */
+ if (tempcopy && !files_match) {
+#ifdef UF_IMMUTABLE
+ /* Try to turn off the immutable bits. */
+ if (to_sb.st_flags & NOCHANGEBITS)
+ (void)chflags(to_name, to_sb.st_flags & ~NOCHANGEBITS);
+#endif
+ if (pThis->dobackup) {
+ if ( (size_t)snprintf(backup, MAXPATHLEN, "%s%s", to_name, pThis->suffix)
+ != strlen(to_name) + strlen(pThis->suffix)) {
+ unlink(tempfile);
+ rc = errx(pThis->pCtx, EX_OSERR, "%s: backup filename too long",
+ to_name);
+ goto l_done;
+ }
+ if (pThis->verbose)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: %s -> %s\n", to_name, backup);
+ if (rename(to_name, backup) < 0) {
+ serrno = errno;
+ unlink(tempfile);
+ errno = serrno;
+ rc = err(pThis->pCtx, EX_OSERR, "rename: %s to %s", to_name,
+ backup);
+ goto l_done;
+ }
+ }
+ if (pThis->verbose)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: %s -> %s\n", from_name, to_name);
+ if (rename(tempfile, to_name) < 0) {
+ serrno = errno;
+ unlink(tempfile);
+ errno = serrno;
+ rc = err(pThis->pCtx, EX_OSERR, "rename: %s to %s", tempfile, to_name);
+ goto l_done;
+ }
+
+ /* Re-open to_fd so we aren't hosed by the rename(2). */
+ (void) close(to_fd);
+ if ((to_fd = open(to_name, O_RDONLY | O_BINARY | KMK_OPEN_NO_INHERIT, 0)) < 0) {
+ rc = err(pThis->pCtx, EX_OSERR, "%s", to_name);
+ goto l_done;
+ }
+ }
+
+ /*
+ * Preserve the timestamp of the source file if necessary.
+ */
+ if (pThis->dopreserve && !files_match && !devnull) {
+ tvb[0].tv_sec = from_sb.st_atime;
+ tvb[0].tv_usec = 0;
+ tvb[1].tv_sec = from_sb.st_mtime;
+ tvb[1].tv_usec = 0;
+ (void)utimes(to_name, tvb);
+ }
+
+ if (fstat(to_fd, &to_sb) == -1) {
+ serrno = errno;
+ (void)unlink(to_name);
+ errno = serrno;
+ rc = err(pThis->pCtx, EX_OSERR, "%s", to_name);
+ goto l_done;
+ }
+
+ /*
+ * Set owner, group, mode for target; do the chown first,
+ * chown may lose the setuid bits.
+ */
+#ifdef UF_IMMUTABLE
+ if ((pThis->gid != (gid_t)-1 && pThis->gid != to_sb.st_gid) ||
+ (pThis->uid != (uid_t)-1 && pThis->uid != to_sb.st_uid) ||
+ (pThis->mode != (to_sb.st_mode & ALLPERMS))) {
+ /* Try to turn off the immutable bits. */
+ if (to_sb.st_flags & NOCHANGEBITS)
+ (void)fchflags(to_fd, to_sb.st_flags & ~NOCHANGEBITS);
+ }
+#endif
+
+ if ((pThis->gid != (gid_t)-1 && pThis->gid != to_sb.st_gid) ||
+ (pThis->uid != (uid_t)-1 && pThis->uid != to_sb.st_uid))
+ if (fchown(to_fd, pThis->uid, pThis->gid) == -1) {
+ if (errno == EPERM && pThis->ignore_perm_errors) {
+ warn(pThis->pCtx, "%s: ignoring chown uid=%d gid=%d failure",
+ to_name, (int)pThis->uid, (int)pThis->gid);
+ } else {
+ serrno = errno;
+ (void)unlink(to_name);
+ errno = serrno;
+ rc = err(pThis->pCtx, EX_OSERR,"%s: chown/chgrp", to_name);
+ goto l_done;
+ }
+ }
+
+ if (pThis->mode != (to_sb.st_mode & ALLPERMS))
+ if (fchmod(to_fd, pThis->mode)) {
+ serrno = errno;
+ if (serrno == EPERM && pThis->ignore_perm_errors) {
+ fchmod(to_fd, pThis->mode & (ALLPERMS & ~0007000));
+ errno = errno;
+ warn(pThis->pCtx, "%s: ignoring chmod 0%o failure", to_name, (int)(pThis->mode & ALLPERMS));
+ } else {
+ serrno = errno;
+ (void)unlink(to_name);
+ errno = serrno;
+ rc = err(pThis->pCtx, EX_OSERR, "%s: chmod", to_name);
+ goto l_done;
+ }
+ }
+
+ /*
+ * If provided a set of flags, set them, otherwise, preserve the
+ * flags, except for the dump flag.
+ * NFS does not support flags. Ignore EOPNOTSUPP flags if we're just
+ * trying to turn off UF_NODUMP. If we're trying to set real flags,
+ * then warn if the the fs doesn't support it, otherwise fail.
+ */
+#ifdef UF_IMMUTABLE
+ if ( !devnull
+ && (flags & SETFLAGS || (from_sb.st_flags & ~UF_NODUMP) != to_sb.st_flags)
+ && fchflags(to_fd, flags & SETFLAGS ? fset : from_sb.st_flags & ~UF_NODUMP)) {
+ if (flags & SETFLAGS) {
+ if (errno == EOPNOTSUPP)
+ warn(pThis->pCtx, "%s: chflags", to_name);
+ else {
+ serrno = errno;
+ (void)unlink(to_name);
+ errno = serrno;
+ rc = err(pThis->pCtx, EX_OSERR, "%s: chflags", to_name);
+ goto l_done;
+ }
+ }
+ }
+#endif
+
+l_done:
+ if (to_fd >= 0)
+ (void)close(to_fd);
+ if (temp_fd >= 0)
+ (void)close(temp_fd);
+ if (from_fd >= 0 && !devnull)
+ (void)close(from_fd);
+ return rc;
+}
+
+/*
+ * compare --
+ * compare two files; non-zero means files differ
+ */
+static int
+compare(int from_fd, size_t from_len, int to_fd, size_t to_len)
+{
+ char buf1[MAXBSIZE];
+ char buf2[MAXBSIZE];
+ int n1, n2;
+ int rv;
+
+ if (from_len != to_len)
+ return 1;
+
+ if (from_len <= MAX_CMP_SIZE) {
+ rv = 0;
+ lseek(from_fd, 0, SEEK_SET);
+ lseek(to_fd, 0, SEEK_SET);
+ while (rv == 0) {
+ n1 = read(from_fd, buf1, sizeof(buf1));
+ if (n1 == 0)
+ break; /* EOF */
+ else if (n1 > 0) {
+ n2 = read(to_fd, buf2, n1);
+ if (n2 == n1)
+ rv = memcmp(buf1, buf2, n1);
+ else
+ rv = 1; /* out of sync */
+ } else
+ rv = 1; /* read failure */
+ }
+ lseek(from_fd, 0, SEEK_SET);
+ lseek(to_fd, 0, SEEK_SET);
+ } else
+ rv = 1; /* don't bother in this case */
+
+ return rv;
+}
+
+/*
+ * create_tempfile --
+ * create a temporary file based on path and open it
+ */
+int
+create_tempfile(const char *path, char *temp, size_t tsize)
+{
+ static char s_szTemplate[] = "INS@XXXXXX";
+ const char *p = last_slash(path);
+ if (p) {
+ size_t cchDir = ++p - path;
+ if (cchDir + sizeof(s_szTemplate) <= tsize) {
+ memcpy(temp, path, cchDir);
+ memcpy(&temp[cchDir], s_szTemplate, sizeof(s_szTemplate));
+ } else
+ return EOVERFLOW;
+ } else if (tsize >= sizeof(s_szTemplate))
+ memcpy(temp, s_szTemplate, sizeof(s_szTemplate));
+ else
+ return EOVERFLOW;
+
+ return (mkstemp(temp));
+}
+
+/*
+ * create_newfile --
+ * create a new file, overwriting an existing one if necessary
+ */
+int
+create_newfile(PINSTALLINSTANCE pThis, const char *path, int target, struct stat *sbp)
+{
+ char backup[MAXPATHLEN];
+ int saved_errno = 0;
+ int newfd;
+
+ if (target) {
+ /*
+ * Unlink now... avoid ETXTBSY errors later. Try to turn
+ * off the append/immutable bits -- if we fail, go ahead,
+ * it might work.
+ */
+#ifdef UF_IMMUTABLE
+ if (sbp->st_flags & NOCHANGEBITS)
+ (void)chflags(path, sbp->st_flags & ~NOCHANGEBITS);
+#endif
+
+ if (pThis->dobackup) {
+ if ( (size_t)snprintf(backup, MAXPATHLEN, "%s%s", path, pThis->suffix)
+ != strlen(path) + strlen(pThis->suffix)) {
+ errx(pThis->pCtx, EX_OSERR, "%s: backup filename too long", path);
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ (void)snprintf(backup, MAXPATHLEN, "%s%s", path, pThis->suffix);
+ if (pThis->verbose)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: %s -> %s\n", path, backup);
+ if (rename(path, backup) < 0) {
+ err(pThis->pCtx, EX_OSERR, "rename: %s to %s", path, backup);
+ return -1;
+ }
+ } else
+ if (unlink(path) < 0)
+ saved_errno = errno;
+ }
+
+ newfd = open(path, O_CREAT | O_RDWR | O_TRUNC | O_BINARY | KMK_OPEN_NO_INHERIT, S_IRUSR | S_IWUSR);
+ if (newfd < 0 && saved_errno != 0)
+ errno = saved_errno;
+ return newfd;
+}
+
+/*
+ * Write error handler.
+ */
+static int write_error(PINSTALLINSTANCE pThis, int *ptr_to_fd, const char *to_name, int nw)
+{
+ int serrno = errno;
+ (void)close(*ptr_to_fd);
+ *ptr_to_fd = -1;
+ (void)unlink(to_name);
+ errno = nw > 0 ? EIO : serrno;
+ return err(pThis->pCtx, EX_OSERR, "%s", to_name);
+}
+
+/*
+ * Read error handler.
+ */
+static int read_error(PINSTALLINSTANCE pThis, const char *from_name, int *ptr_to_fd, const char *to_name)
+{
+ int serrno = errno;
+ (void)close(*ptr_to_fd);
+ *ptr_to_fd = -1;
+ (void)unlink(to_name);
+ errno = serrno;
+ return err(pThis->pCtx, EX_OSERR, "%s", from_name);
+}
+
+/*
+ * copy --
+ * copy from one file to another
+ */
+static int
+copy(PINSTALLINSTANCE pThis, int from_fd, const char *from_name, int *ptr_to_fd, const char *to_name)
+{
+ KBOOL fPendingCr = K_FALSE;
+ KSIZE cchDst;
+ int nr, nw;
+ char buf[MAXBSIZE];
+ int to_fd = *ptr_to_fd;
+
+ /* Rewind file descriptors. */
+ if (lseek(from_fd, (off_t)0, SEEK_SET) == (off_t)-1)
+ return err(pThis->pCtx, EX_OSERR, "lseek: %s", from_name);
+ if (lseek(to_fd, (off_t)0, SEEK_SET) == (off_t)-1)
+ return err(pThis->pCtx, EX_OSERR, "lseek: %s", to_name);
+
+ if (pThis->dos2unix == 0) {
+ /*
+ * Copy bytes, no conversion.
+ */
+ while ((nr = read(from_fd, buf, sizeof(buf))) > 0)
+ if ((nw = write(to_fd, buf, nr)) != nr)
+ return write_error(pThis, ptr_to_fd, to_name, nw);
+ } else if (pThis->dos2unix > 0) {
+ /*
+ * CRLF -> LF is a reduction, so we can work with full buffers.
+ */
+ while ((nr = read(from_fd, buf, sizeof(buf))) > 0) {
+ if ( fPendingCr
+ && buf[0] != '\n'
+ && (nw = write(to_fd, "\r", 1)) != 1)
+ return write_error(pThis, ptr_to_fd, to_name, nw);
+
+ fPendingCr = dos2unix_convert_to_unix(buf, nr, buf, &cchDst);
+
+ nw = write(to_fd, buf, cchDst);
+ if (nw != (int)cchDst)
+ return write_error(pThis, ptr_to_fd, to_name, nw);
+ }
+ } else {
+ /*
+ * LF -> CRLF is an expansion, so we work with half buffers, reading
+ * into the upper half of the buffer and expanding into the full buffer.
+ * The conversion will never expand to more than the double size.
+ *
+ * Note! We do not convert valid CRLF line endings. This gives us
+ * valid DOS text, but no round-trip conversion.
+ */
+ char * const pchSrc = &buf[sizeof(buf) / 2];
+ while ((nr = read(from_fd, pchSrc, sizeof(buf) / 2)) > 0) {
+ if ( fPendingCr
+ && pchSrc[0] != '\n'
+ && (nw = write(to_fd, "\r", 1))!= 1)
+ return write_error(pThis, ptr_to_fd, to_name, nw);
+
+ fPendingCr = dos2unix_convert_to_dos(pchSrc, nr, buf, &cchDst);
+
+ nw = write(to_fd, buf, cchDst);
+ if (nw != (int)cchDst)
+ return write_error(pThis, ptr_to_fd, to_name, nw);
+ }
+ }
+
+ /* Check for read error. */
+ if (nr != 0)
+ return read_error(pThis, from_name, ptr_to_fd, to_name);
+
+ /* When converting, we might have a pending final CR to write. */
+ if ( fPendingCr
+ && (nw = write(to_fd, "\r", 1))!= 1)
+ return write_error(pThis, ptr_to_fd, to_name, nw);
+
+ return EX_OK;
+}
+
+/*
+ * strip --
+ * use strip(1) to strip the target file
+ */
+static int
+strip(PINSTALLINSTANCE pThis, const char *to_name)
+{
+#if defined(__EMX__) || defined(_MSC_VER)
+ const char *stripbin = getenv("STRIPBIN");
+ if (stripbin == NULL)
+ stripbin = "strip";
+ (void)pThis;
+ return spawnlp(P_WAIT, stripbin, stripbin, to_name, NULL);
+#else
+ const char *stripbin;
+ int serrno, status;
+ pid_t pid;
+
+ pid = fork();
+ switch (pid) {
+ case -1:
+ serrno = errno;
+ (void)unlink(to_name);
+ errno = serrno;
+ return err(pThis->pCtx, EX_TEMPFAIL, "fork");
+ case 0:
+ stripbin = getenv("STRIPBIN");
+ if (stripbin == NULL)
+ stripbin = "strip";
+ execlp(stripbin, stripbin, to_name, (char *)NULL);
+ err(pThis->pCtx, EX_OSERR, "exec(%s)", stripbin);
+ exit(EX_OSERR);
+ default:
+ if (waitpid(pid, &status, 0) == -1 || status) {
+ serrno = errno;
+ (void)unlink(to_name);
+ errno = serrno;
+ return err(pThis->pCtx, EX_SOFTWARE, "waitpid");
+ /* NOTREACHED */
+ }
+ }
+ return 0;
+#endif
+}
+
+/*
+ * install_dir --
+ * build directory heirarchy
+ */
+static int
+install_dir(PINSTALLINSTANCE pThis, char *path)
+{
+ char *p;
+ struct stat sb;
+ int ch;
+
+ for (p = path;; ++p)
+ if ( !*p
+ || ( p != path
+ && IS_SLASH(*p)
+#if defined(_MSC_VER) /* stat("C:") fails (VC++ v10). Just skip it since it's unnecessary. */
+ && (p - path != 2 || p[-1] != ':')
+#endif
+ )) {
+ ch = *p;
+ *p = '\0';
+ if (stat(path, &sb)) {
+ if (errno != ENOENT || mkdir(path, 0755) < 0) {
+ return err(pThis->pCtx, EX_OSERR, "mkdir %s", path);
+ /* NOTREACHED */
+ } else if (pThis->verbose)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: mkdir %s\n", path);
+ } else if (!S_ISDIR(sb.st_mode))
+ return errx(pThis->pCtx, EX_OSERR, "%s exists but is not a directory", path);
+ if (!(*p = ch))
+ break;
+ }
+
+ if ((pThis->gid != (gid_t)-1 || pThis->uid != (uid_t)-1) && chown(path, pThis->uid, pThis->gid))
+ warn(pThis->pCtx, "chown %u:%u %s", pThis->uid, pThis->gid, path);
+ if (chmod(path, pThis->mode))
+ warn(pThis->pCtx, "chmod %o %s", pThis->mode, path);
+ return EX_OK;
+}
+
+/*
+ * usage --
+ * print a usage message and die
+ */
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+"usage: %s [-bCcpSsv] [--[no-]hard-link-files-when-possible]\n"
+" [--verbose-hard-link-refusal] [--verbose-hard-link-mode-mismatch]\n"
+" [--[no-]ignore-perm-errors] [-B suffix] [-f flags] [-g group]\n"
+" [-m mode] [-o owner] [--dos2unix|--unix2dos] file1 file2\n"
+" or: %s [-bCcpSsv] [--[no-]ignore-perm-errors] [-B suffix] [-f flags]\n"
+" [-g group] [-m mode] [-o owner] file1 ... fileN directory\n"
+" or: %s -d [-v] [-g group] [-m mode] [-o owner] directory ...\n"
+" or: %s --help\n"
+" or: %s --version\n",
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName,
+ pCtx->pszProgName, pCtx->pszProgName);
+ return EX_USAGE;
+}
+
+/* figures out where the last slash or colon is. */
+static char *
+last_slash(const char *path)
+{
+#if defined(__WIN32__) || defined(__WIN64__) || defined(__OS2__)
+ char *p = (char *)strrchr(path, '/');
+ if (p)
+ {
+ char *p2 = strrchr(p, '\\');
+ if (p2)
+ p = p2;
+ }
+ else
+ {
+ p = (char *)strrchr(path, '\\');
+ if (!p && isalpha(path[0]) && path[1] == ':')
+ p = (char *)&path[1];
+ }
+ return p;
+#else
+ return strrchr(path, '/');
+#endif
+}
+
+/**
+ * Checks if @a pszFilename actually needs dos2unix conversion.
+ *
+ * @returns boolean.
+ * @param pszFilename The name of the file to check.
+ */
+static KBOOL needs_dos2unix_conversion(const char *pszFilename)
+{
+ KU32 fStyle = 0;
+ int iErr = dos2unix_analyze_file(pszFilename, &fStyle, NULL, NULL);
+ return iErr != 0
+ || (fStyle & (DOS2UNIX_STYLE_MASK | DOS2UNIX_F_BINARY)) != DOS2UNIX_STYLE_UNIX;
+}
+
+/**
+ * Checks if @a pszFilename actually needs unix2dos conversion.
+ *
+ * @returns boolean.
+ * @param pszFilename The name of the file to check.
+ */
+static KBOOL needs_unix2dos_conversion(const char *pszFilename)
+{
+ KU32 fStyle = 0;
+ int iErr = dos2unix_analyze_file(pszFilename, &fStyle, NULL, NULL);
+ return iErr != 0
+ || (fStyle & (DOS2UNIX_STYLE_MASK | DOS2UNIX_F_BINARY)) != DOS2UNIX_STYLE_DOS;
+}
+
diff --git a/src/kmk/kmkbuiltin/kDepIDB.c b/src/kmk/kmkbuiltin/kDepIDB.c
new file mode 100644
index 0000000..24cadc1
--- /dev/null
+++ b/src/kmk/kmkbuiltin/kDepIDB.c
@@ -0,0 +1,860 @@
+/* $Id: kDepIDB.c 3315 2020-03-31 01:12:19Z bird $ */
+/** @file
+ * kDepIDB - Extract dependency information from a MS Visual C++ .idb file.
+ */
+
+/*
+ * 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/>
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+#endif
+#if !defined(_MSC_VER)
+# include <unistd.h>
+#else
+# include <io.h>
+#endif
+#include "k/kDefs.h"
+#include "k/kTypes.h"
+#include "kDep.h"
+#include "err.h"
+#include "kmkbuiltin.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*#define DEBUG*/
+#ifdef DEBUG
+# define dprintf(a) printf a
+# define dump(pb, cb, offBase) depHexDump(pb,cb,offBase)
+#else
+# define dprintf(a) do {} while (0)
+# define dump(pb, cb, offBase) do {} while (0)
+#endif
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct KDEPIDBGLOBALS
+{
+ PKMKBUILTINCTX pCtx;
+ DEPGLOBALS Core;
+} KDEPIDBGLOBALS;
+typedef KDEPIDBGLOBALS *PKDEPIDBGLOBALS;
+
+
+/**
+ * Scans a stream (chunk of data really) for dependencies.
+ *
+ * @returns 0 on success.
+ * @returns !0 on failure.
+ * @param pThis The kDepIDB instance.
+ * @param pbStream The stream bits.
+ * @param cbStream The size of the stream.
+ * @param pszPrefix The dependency prefix.
+ * @param cchPrefix The size of the prefix.
+ */
+static int ScanStream(PKDEPIDBGLOBALS pThis, KU8 *pbStream, size_t cbStream, const char *pszPrefix, size_t cchPrefix)
+{
+ const KU8 *pbCur = pbStream;
+ size_t cbLeft = cbStream;
+ register char chFirst = *pszPrefix;
+ while (cbLeft > cchPrefix + 2)
+ {
+ if ( *pbCur != chFirst
+ || memcmp(pbCur, pszPrefix, cchPrefix))
+ {
+ pbCur++;
+ cbLeft--;
+ }
+ else
+ {
+ size_t cchDep;
+ pbCur += cchPrefix;
+ cchDep = strlen((const char *)pbCur);
+ depAdd(&pThis->Core, (const char *) pbCur, cchDep);
+ dprintf(("%05x: '%s'\n", pbCur - pbStream, pbCur));
+
+ pbCur += cchDep;
+ cbLeft -= cchDep + cchPrefix;
+ }
+ }
+
+ return 0;
+}
+
+
+/*/////////////////////////////////////////////////////////////////////////////
+//
+//
+// P D B 7 . 0
+//
+//
+/////////////////////////////////////////////////////////////////////////////*/
+
+/** A PDB 7.0 Page number. */
+typedef KU32 PDB70PAGE;
+/** Pointer to a PDB 7.0 Page number. */
+typedef PDB70PAGE *PPDB70PAGE;
+
+/**
+ * A PDB 7.0 stream.
+ */
+typedef struct PDB70STREAM
+{
+ /** The size of the stream. */
+ KU32 cbStream;
+} PDB70STREAM, *PPDB70STREAM;
+
+
+/** The PDB 7.00 signature. */
+#define PDB_SIGNATURE_700 "Microsoft C/C++ MSF 7.00\r\n\x1A" "DS\0\0"
+/**
+ * The PDB 7.0 header.
+ */
+typedef struct PDB70HDR
+{
+ /** The signature string. */
+ KU8 szSignature[sizeof(PDB_SIGNATURE_700)];
+ /** The page size. */
+ KU32 cbPage;
+ /** The start page. */
+ PDB70PAGE iStartPage;
+ /** The number of pages in the file. */
+ PDB70PAGE cPages;
+ /** The root stream directory. */
+ KU32 cbRoot;
+ /** Unknown function, always 0. */
+ KU32 u32Reserved;
+ /** The page index of the root page table. */
+ PDB70PAGE iRootPages;
+} PDB70HDR, *PPDB70HDR;
+
+/**
+ * The PDB 7.0 root directory.
+ */
+typedef struct PDB70ROOT
+{
+ /** The number of streams */
+ KU32 cStreams;
+ /** Array of streams. */
+ PDB70STREAM aStreams[1];
+ /* KU32 aiPages[] */
+} PDB70ROOT, *PPDB70ROOT;
+
+/**
+ * The PDB 7.0 name stream (#1) header.
+ */
+typedef struct PDB70NAMES
+{
+ /** The structure version. */
+ KU32 Version;
+ /** Timestamp. */
+ KU32 TimeStamp;
+ /** Unknown. */
+ KU32 Unknown1;
+ /** GUID. */
+ KU32 u32Guid[4];
+ /** The size of the following name table. */
+ KU32 cbNames;
+ /** The name table. */
+ char szzNames[1];
+} PDB70NAMES, *PPDB70NAMES;
+
+/** The version / magic of the names structure. */
+#define PDB70NAMES_VERSION 20000404
+
+
+static int Pdb70ValidateHeader(PKDEPIDBGLOBALS pThis, PPDB70HDR pHdr, size_t cbFile)
+{
+ if (pHdr->cbPage * pHdr->cPages != cbFile)
+ return errx(pThis->pCtx, 1, "Bad PDB 2.0 header - cbPage * cPages != cbFile.");
+ if (pHdr->iStartPage >= pHdr->cPages && pHdr->iStartPage <= 0)
+ return errx(pThis->pCtx, 1, "Bad PDB 2.0 header - iStartPage=%u cPages=%u.",
+ pHdr->iStartPage, pHdr->cPages);
+ if (pHdr->iRootPages >= pHdr->cPages && pHdr->iRootPages <= 0)
+ return errx(pThis->pCtx, 1, "Bad PDB 2.0 header - iRootPages=%u cPage=%u.",
+ pHdr->iStartPage, pHdr->cPages);
+ return 0;
+}
+
+#ifdef DEBUG
+static size_t Pdb70Align(PPDB70HDR pHdr, size_t cb)
+{
+ if (cb == ~(KU32)0 || !cb)
+ return 0;
+ return ((cb + pHdr->cbPage - 1) / pHdr->cbPage) * pHdr->cbPage;
+}
+#endif /* DEBUG */
+
+static size_t Pdb70Pages(PPDB70HDR pHdr, size_t cb)
+{
+ if (cb == ~(KU32)0 || !cb)
+ return 0;
+ return (cb + pHdr->cbPage - 1) / pHdr->cbPage;
+}
+
+static void *Pdb70AllocAndRead(PKDEPIDBGLOBALS pThis, PPDB70HDR pHdr, size_t cb, PPDB70PAGE paiPageMap)
+{
+ const size_t cbPage = pHdr->cbPage;
+ size_t cPages = Pdb70Pages(pHdr, cb);
+ KU8 *pbBuf = malloc(cPages * cbPage + 1);
+ if (pbBuf)
+ {
+ size_t iPage = 0;
+ while (iPage < cPages)
+ {
+ size_t off = paiPageMap[iPage];
+ if (off < pHdr->cPages)
+ {
+ off *= cbPage;
+ memcpy(pbBuf + iPage * cbPage, (KU8 *)pHdr + off, cbPage);
+ dump(pbBuf + iPage * cbPage, iPage + 1 < cPages ? cbPage : cb % cbPage, off);
+ }
+ else
+ {
+ warnx(pThis->pCtx, "warning: Invalid page index %u (max %u)!\n", (unsigned)off, pHdr->cPages);
+ memset(pbBuf + iPage * cbPage, 0, cbPage);
+ }
+
+ iPage++;
+ }
+ pbBuf[cPages * cbPage] = '\0';
+ }
+ else
+ {
+ errx(pThis->pCtx, 1, "failed to allocate %lu bytes", (unsigned long)(cPages * cbPage + 1));
+ return NULL;
+ }
+ return pbBuf;
+}
+
+static PPDB70ROOT Pdb70AllocAndReadRoot(PKDEPIDBGLOBALS pThis, PPDB70HDR pHdr)
+{
+ /*
+ * The tricky bit here is to find the right length. Really?
+ * (Todo: Check if we can just use the stream #0 size..)
+ */
+ PPDB70PAGE piPageMap = (KU32 *)((KU8 *)pHdr + pHdr->iRootPages * pHdr->cbPage);
+ PPDB70ROOT pRoot = Pdb70AllocAndRead(pThis, pHdr, pHdr->cbRoot, piPageMap);
+ if (pRoot)
+ {
+#if 1
+ /* This stuff is probably unnecessary: */
+ /* size = stream header + array of stream. */
+ size_t cb = K_OFFSETOF(PDB70ROOT, aStreams[pRoot->cStreams]);
+ free(pRoot);
+ pRoot = Pdb70AllocAndRead(pThis, pHdr, cb, piPageMap);
+ if (pRoot)
+ {
+ /* size += page tables. */
+ unsigned iStream = pRoot->cStreams;
+ while (iStream-- > 0)
+ if (pRoot->aStreams[iStream].cbStream != ~(KU32)0)
+ cb += Pdb70Pages(pHdr, pRoot->aStreams[iStream].cbStream) * sizeof(PDB70PAGE);
+ free(pRoot);
+ pRoot = Pdb70AllocAndRead(pThis, pHdr, cb, piPageMap);
+ if (pRoot)
+ {
+ /* validate? */
+ return pRoot;
+ }
+ }
+#else
+ /* validate? */
+ return pRoot;
+#endif
+ }
+ return NULL;
+}
+
+static void *Pdb70AllocAndReadStream(PKDEPIDBGLOBALS pThis, PPDB70HDR pHdr, PPDB70ROOT pRoot, unsigned iStream, size_t *pcbStream)
+{
+ const size_t cbStream = pRoot->aStreams[iStream].cbStream;
+ PPDB70PAGE paiPageMap;
+ if ( iStream >= pRoot->cStreams
+ || cbStream == ~(KU32)0)
+ {
+ errx(pThis->pCtx, 1, "Invalid stream %d", iStream);
+ return NULL;
+ }
+
+ paiPageMap = (PPDB70PAGE)&pRoot->aStreams[pRoot->cStreams];
+ while (iStream-- > 0)
+ if (pRoot->aStreams[iStream].cbStream != ~(KU32)0)
+ paiPageMap += Pdb70Pages(pHdr, pRoot->aStreams[iStream].cbStream);
+
+ if (pcbStream)
+ *pcbStream = cbStream;
+ return Pdb70AllocAndRead(pThis, pHdr, cbStream, paiPageMap);
+}
+
+static int Pdb70Process(PKDEPIDBGLOBALS pThis, KU8 *pbFile, size_t cbFile)
+{
+ PPDB70HDR pHdr = (PPDB70HDR)pbFile;
+ PPDB70ROOT pRoot;
+ PPDB70NAMES pNames;
+ size_t cbStream = 0;
+ unsigned fDone = 0;
+ unsigned iStream;
+ int rc = 0;
+ dprintf(("pdb70\n"));
+
+ /*
+ * Validate the header and read the root stream.
+ */
+ if (Pdb70ValidateHeader(pThis, pHdr, cbFile))
+ return 1;
+ pRoot = Pdb70AllocAndReadRoot(pThis, pHdr);
+ if (!pRoot)
+ return 1;
+
+ /*
+ * The names we want are usually all found in the 'Names' stream, that is #1.
+ */
+ dprintf(("Reading the names stream....\n"));
+ pNames = Pdb70AllocAndReadStream(pThis, pHdr, pRoot, 1, &cbStream);
+ if (pNames)
+ {
+ dprintf(("Names: Version=%u cbNames=%u (%#x)\n", pNames->Version, pNames->cbNames, pNames->cbNames));
+ if ( pNames->Version == PDB70NAMES_VERSION
+ && pNames->cbNames > 32
+ && pNames->cbNames + K_OFFSETOF(PDB70NAMES, szzNames) <= pRoot->aStreams[1].cbStream)
+ {
+ /*
+ * Iterate the names and add the /mr/inversedeps/ ones to the dependency list.
+ */
+ const char *psz = &pNames->szzNames[0];
+ size_t cb = pNames->cbNames;
+ size_t off = 0;
+ dprintf(("0x0000 #0: %6d bytes [root / toc]\n", pRoot->aStreams[0].cbStream));
+ for (iStream = 1; cb > 0; iStream++)
+ {
+ int fAdded = 0;
+ size_t cch = strlen(psz);
+ if ( cch >= sizeof("/mr/inversedeps/")
+ && !memcmp(psz, "/mr/inversedeps/", sizeof("/mr/inversedeps/") - 1))
+ {
+ depAdd(&pThis->Core, psz + sizeof("/mr/inversedeps/") - 1, cch - (sizeof("/mr/inversedeps/") - 1));
+ fAdded = 1;
+ }
+ dprintf(("%#06x #%d: %6d bytes %s%s\n", off, iStream,
+ iStream < pRoot->cStreams ? pRoot->aStreams[iStream].cbStream : -1,
+ psz, fAdded ? " [dep]" : ""));
+ (void)fAdded;
+
+ /* next */
+ if (cch >= cb)
+ {
+ dprintf(("warning! cch=%d cb=%d\n", cch, cb));
+ cch = cb - 1;
+ }
+ cb -= cch + 1;
+ psz += cch + 1;
+ off += cch + 1;
+ }
+ rc = 0;
+ fDone = 1;
+ }
+ else
+ dprintf(("Unknown version or bad size: Version=%u cbNames=%d cbStream=%d\n",
+ pNames->Version, pNames->cbNames, cbStream));
+ free(pNames);
+ }
+
+ if (!fDone)
+ {
+ /*
+ * Iterate the streams in the root and scan their content for
+ * dependencies.
+ */
+ rc = 0;
+ for (iStream = 0; iStream < pRoot->cStreams && !rc; iStream++)
+ {
+ KU8 *pbStream;
+ if ( pRoot->aStreams[iStream].cbStream == ~(KU32)0
+ || !pRoot->aStreams[iStream].cbStream)
+ continue;
+ dprintf(("Stream #%d: %#x bytes (%#x aligned)\n", iStream, pRoot->aStreams[iStream].cbStream,
+ Pdb70Align(pHdr, pRoot->aStreams[iStream].cbStream)));
+ pbStream = (KU8 *)Pdb70AllocAndReadStream(pThis, pHdr, pRoot, iStream, &cbStream);
+ if (pbStream)
+ {
+ rc = ScanStream(pThis, pbStream, cbStream, "/mr/inversedeps/", sizeof("/mr/inversedeps/") - 1);
+ free(pbStream);
+ }
+ else
+ rc = 1;
+ }
+ }
+
+ free(pRoot);
+ return rc;
+}
+
+
+
+/*/////////////////////////////////////////////////////////////////////////////
+//
+//
+// P D B 2 . 0
+//
+//
+/////////////////////////////////////////////////////////////////////////////*/
+
+
+/** A PDB 2.0 Page number. */
+typedef KU16 PDB20PAGE;
+/** Pointer to a PDB 2.0 Page number. */
+typedef PDB20PAGE *PPDB20PAGE;
+
+/**
+ * A PDB 2.0 stream.
+ */
+typedef struct PDB20STREAM
+{
+ /** The size of the stream. */
+ KU32 cbStream;
+ /** Some unknown value. */
+ KU32 u32Unknown;
+} PDB20STREAM, *PPDB20STREAM;
+
+/** The PDB 2.00 signature. */
+#define PDB_SIGNATURE_200 "Microsoft C/C++ program database 2.00\r\n\x1A" "JG\0"
+/**
+ * The PDB 2.0 header.
+ */
+typedef struct PDB20HDR
+{
+ /** The signature string. */
+ KU8 szSignature[sizeof(PDB_SIGNATURE_200)];
+ /** The page size. */
+ KU32 cbPage;
+ /** The start page - whatever that is... */
+ PDB20PAGE iStartPage;
+ /** The number of pages in the file. */
+ PDB20PAGE cPages;
+ /** The root stream directory. */
+ PDB20STREAM RootStream;
+ /** The root page table. */
+ PDB20PAGE aiRootPageMap[1];
+} PDB20HDR, *PPDB20HDR;
+
+/**
+ * The PDB 2.0 root directory.
+ */
+typedef struct PDB20ROOT
+{
+ /** The number of streams */
+ KU16 cStreams;
+ /** Reserved or high part of cStreams. */
+ KU16 u16Reserved;
+ /** Array of streams. */
+ PDB20STREAM aStreams[1];
+} PDB20ROOT, *PPDB20ROOT;
+
+
+static int Pdb20ValidateHeader(PKDEPIDBGLOBALS pThis, PPDB20HDR pHdr, size_t cbFile)
+{
+ if (pHdr->cbPage * pHdr->cPages != cbFile)
+ return errx(pThis->pCtx, 1, "Bad PDB 2.0 header - cbPage * cPages != cbFile.");
+ if (pHdr->iStartPage >= pHdr->cPages && pHdr->iStartPage <= 0)
+ return errx(pThis->pCtx, 1, "Bad PDB 2.0 header - cbPage * cPages != cbFile.");
+ return 0;
+}
+
+static size_t Pdb20Pages(PPDB20HDR pHdr, size_t cb)
+{
+ if (cb == ~(KU32)0 || !cb)
+ return 0;
+ return (cb + pHdr->cbPage - 1) / pHdr->cbPage;
+}
+
+static void *Pdb20AllocAndRead(PKDEPIDBGLOBALS pThis, PPDB20HDR pHdr, size_t cb, PPDB20PAGE paiPageMap)
+{
+ size_t cPages = Pdb20Pages(pHdr, cb);
+ KU8 *pbBuf = malloc(cPages * pHdr->cbPage + 1);
+ if (pbBuf)
+ {
+ size_t iPage = 0;
+ while (iPage < cPages)
+ {
+ size_t off = paiPageMap[iPage];
+ off *= pHdr->cbPage;
+ memcpy(pbBuf + iPage * pHdr->cbPage, (KU8 *)pHdr + off, pHdr->cbPage);
+ iPage++;
+ }
+ pbBuf[cPages * pHdr->cbPage] = '\0';
+ }
+ else
+ errx(pThis->pCtx, 1, "failed to allocate %lu bytes", (unsigned long)(cPages * pHdr->cbPage + 1));
+ return pbBuf;
+}
+
+static PPDB20ROOT Pdb20AllocAndReadRoot(PKDEPIDBGLOBALS pThis, PPDB20HDR pHdr)
+{
+ /*
+ * The tricky bit here is to find the right length.
+ * (Todo: Check if we can just use the stream size..)
+ */
+ PPDB20ROOT pRoot = Pdb20AllocAndRead(pThis, pHdr, sizeof(*pRoot), &pHdr->aiRootPageMap[0]);
+ if (pRoot)
+ {
+ /* size = stream header + array of stream. */
+ size_t cb = K_OFFSETOF(PDB20ROOT, aStreams[pRoot->cStreams]);
+ free(pRoot);
+ pRoot = Pdb20AllocAndRead(pThis, pHdr, cb, &pHdr->aiRootPageMap[0]);
+ if (pRoot)
+ {
+ /* size += page tables. */
+ unsigned iStream = pRoot->cStreams;
+ while (iStream-- > 0)
+ if (pRoot->aStreams[iStream].cbStream != ~(KU32)0)
+ cb += Pdb20Pages(pHdr, pRoot->aStreams[iStream].cbStream) * sizeof(PDB20PAGE);
+ free(pRoot);
+ pRoot = Pdb20AllocAndRead(pThis, pHdr, cb, &pHdr->aiRootPageMap[0]);
+ if (pRoot)
+ {
+ /* validate? */
+ return pRoot;
+ }
+ }
+ }
+ return NULL;
+
+}
+
+static void *Pdb20AllocAndReadStream(PKDEPIDBGLOBALS pThis, PPDB20HDR pHdr, PPDB20ROOT pRoot, unsigned iStream, size_t *pcbStream)
+{
+ size_t cbStream = pRoot->aStreams[iStream].cbStream;
+ PPDB20PAGE paiPageMap;
+ if ( iStream >= pRoot->cStreams
+ || cbStream == ~(KU32)0)
+ {
+ errx(pThis->pCtx, 1, "Invalid stream %d", iStream);
+ return NULL;
+ }
+
+ paiPageMap = (PPDB20PAGE)&pRoot->aStreams[pRoot->cStreams];
+ while (iStream-- > 0)
+ if (pRoot->aStreams[iStream].cbStream != ~(KU32)0)
+ paiPageMap += Pdb20Pages(pHdr, pRoot->aStreams[iStream].cbStream);
+
+ if (pcbStream)
+ *pcbStream = cbStream;
+ return Pdb20AllocAndRead(pThis, pHdr, cbStream, paiPageMap);
+}
+
+static int Pdb20Process(PKDEPIDBGLOBALS pThis, KU8 *pbFile, size_t cbFile)
+{
+ PPDB20HDR pHdr = (PPDB20HDR)pbFile;
+ PPDB20ROOT pRoot;
+ unsigned iStream;
+ int rc = 0;
+
+ /*
+ * Validate the header and read the root stream.
+ */
+ if (Pdb20ValidateHeader(pThis, pHdr, cbFile))
+ return 1;
+ pRoot = Pdb20AllocAndReadRoot(pThis, pHdr);
+ if (!pRoot)
+ return 1;
+
+ /*
+ * Iterate the streams in the root and scan their content for
+ * dependencies.
+ */
+ rc = 0;
+ for (iStream = 0; iStream < pRoot->cStreams && !rc; iStream++)
+ {
+ KU8 *pbStream;
+ if (pRoot->aStreams[iStream].cbStream == ~(KU32)0)
+ continue;
+ pbStream = (KU8 *)Pdb20AllocAndReadStream(pThis, pHdr, pRoot, iStream, NULL);
+ if (pbStream)
+ {
+ rc = ScanStream(pThis, pbStream, pRoot->aStreams[iStream].cbStream, "/ipm/header/", sizeof("/ipm/header/") - 1);
+ free(pbStream);
+ }
+ else
+ rc = 1;
+ }
+
+ free(pRoot);
+ return rc;
+}
+
+
+/**
+ * Make an attempt at parsing a Visual C++ IDB file.
+ */
+static int ProcessIDB(PKDEPIDBGLOBALS pThis, FILE *pInput)
+{
+ size_t cbFile;
+ KU8 *pbFile;
+ void *pvOpaque;
+ int rc = 0;
+
+ /*
+ * Read the file into memory.
+ */
+ pbFile = (KU8 *)depReadFileIntoMemory(pInput, &cbFile, &pvOpaque);
+ if (!pbFile)
+ return 1;
+
+ /*
+ * Figure out which parser to use.
+ */
+ if (!memcmp(pbFile, PDB_SIGNATURE_700, sizeof(PDB_SIGNATURE_700)))
+ rc = Pdb70Process(pThis, pbFile, cbFile);
+ else if (!memcmp(pbFile, PDB_SIGNATURE_200, sizeof(PDB_SIGNATURE_200)))
+ rc = Pdb20Process(pThis, pbFile, cbFile);
+ else
+ rc = errx(pThis->pCtx, 1, "Doesn't recognize the header of the Visual C++ IDB file.");
+
+ depFreeFileMemory(pbFile, pvOpaque);
+ return rc;
+}
+
+
+static void kDepIDBUsage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s -o <output> -t <target> [-fqs] <vc idb-file>\n"
+ " or: %s --help\n"
+ " or: %s --version\n",
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+}
+
+
+int kmk_builtin_kDepIDB(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ int i;
+ KDEPIDBGLOBALS This;
+
+ /* Arguments. */
+ FILE *pOutput = NULL;
+ const char *pszOutput = NULL;
+ FILE *pInput = NULL;
+ const char *pszTarget = NULL;
+ int fStubs = 0;
+ int fFixCase = 0;
+ /* Argument parsing. */
+ int fInput = 0; /* set when we've found input argument. */
+ int fQuiet = 0;
+
+ /* Init the instance data. */
+ This.pCtx = pCtx;
+
+ /*
+ * Parse arguments.
+ */
+ if (argc <= 1)
+ {
+ kDepIDBUsage(pCtx, 0);
+ return 1;
+ }
+ for (i = 1; i < argc; i++)
+ {
+ if (argv[i][0] == '-')
+ {
+ const char *psz = &argv[i][1];
+ if (*psz == '-')
+ {
+ if (!strcmp(psz, "-quiet"))
+ psz = "q";
+ else if (!strcmp(psz, "-help"))
+ psz = "?";
+ else if (!strcmp(psz, "-version"))
+ psz = "V";
+ }
+
+ switch (*psz)
+ {
+ /*
+ * Output file.
+ */
+ case 'o':
+ {
+ pszOutput = &argv[i][2];
+ if (pOutput)
+ return errx(pCtx, 2, "only one output file!");
+ if (!*pszOutput)
+ {
+ if (++i >= argc)
+ return errx(pCtx, 2, "The '-o' argument is missing the filename.");
+ pszOutput = argv[i];
+ }
+ if (pszOutput[0] == '-' && !pszOutput[1])
+ pOutput = stdout;
+ else
+ pOutput = fopen(pszOutput, "w" KMK_FOPEN_NO_INHERIT_MODE);
+ if (!pOutput)
+ return err(pCtx, 1, "Failed to create output file '%s'", pszOutput);
+ break;
+ }
+
+ /*
+ * Target name.
+ */
+ case 't':
+ {
+ if (pszTarget)
+ return errx(pCtx, 2, "only one target!");
+ pszTarget = &argv[i][2];
+ if (!*pszTarget)
+ {
+ if (++i >= argc)
+ return errx(pCtx, 2, "The '-t' argument is missing the target name.");
+ pszTarget = argv[i];
+ }
+ break;
+ }
+
+ /*
+ * Fix case.
+ */
+ case 'f':
+ {
+ fFixCase = 1;
+ break;
+ }
+
+ /*
+ * Quiet.
+ */
+ case 'q':
+ {
+ fQuiet = 1;
+ break;
+ }
+
+ /*
+ * Generate stubs.
+ */
+ case 's':
+ {
+ fStubs = 1;
+ break;
+ }
+
+ /*
+ * The mandatory version & help.
+ */
+ case '?':
+ kDepIDBUsage(pCtx, 0);
+ return 0;
+ case 'V':
+ case 'v':
+ return kbuild_version(pCtx->pszProgName);
+
+ /*
+ * Invalid argument.
+ */
+ default:
+ errx(pCtx, 2, "Invalid argument '%s.'", argv[i]);
+ kDepIDBUsage(pCtx, 1);
+ return 2;
+ }
+ }
+ else
+ {
+ pInput = fopen(argv[i], "rb" KMK_FOPEN_NO_INHERIT_MODE);
+ if (!pInput)
+ return err(pCtx, 1, "Failed to open input file '%s'", argv[i]);
+ fInput = 1;
+ }
+
+ /*
+ * End of the line?
+ */
+ if (fInput)
+ {
+ if (++i < argc)
+ return errx(pCtx, 2, "No arguments shall follow the input spec.");
+ break;
+ }
+ }
+
+ /*
+ * Got all we require?
+ */
+ if (!pInput)
+ return errx(pCtx, 2, "No input!");
+ if (!pOutput)
+ return errx(pCtx, 2, "No output!");
+ if (!pszTarget)
+ return errx(pCtx, 2, "No target!");
+
+ /*
+ * Do the parsing.
+ */
+ depInit(&This.Core);
+ i = ProcessIDB(&This, pInput);
+ fclose(pInput);
+
+ /*
+ * Write the dependecy file.
+ */
+ if (!i)
+ {
+ depOptimize(&This.Core, fFixCase, fQuiet, NULL /*pszIgnoredExt*/);
+ depPrintTargetWithDeps(&This.Core, pOutput, pszTarget, 1 /*fEscapeTarget*/);
+ if (fStubs)
+ depPrintStubs(&This.Core, pOutput);
+ }
+
+ /*
+ * Close the output, delete output on failure.
+ */
+ if (!i && ferror(pOutput))
+ i = errx(pCtx, 1, "Error writing to '%s'.", pszOutput);
+ fclose(pOutput);
+ if (i)
+ {
+ if (unlink(pszOutput))
+ warnx(pCtx, "warning: failed to remove output file '%s' on failure.", pszOutput);
+ }
+
+ depCleanup(&This.Core);
+ return i;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_kDepIDB", NULL };
+ return kmk_builtin_kDepIDB(argc, argv, envp, &Ctx);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin/kDepObj.c b/src/kmk/kmkbuiltin/kDepObj.c
new file mode 100644
index 0000000..280f15f
--- /dev/null
+++ b/src/kmk/kmkbuiltin/kDepObj.c
@@ -0,0 +1,1250 @@
+/* $Id: kDepObj.c 3364 2020-06-08 19:29:42Z bird $ */
+/** @file
+ * kDepObj - Extract dependency information from an object file.
+ */
+
+/*
+ * 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/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#define MSCFAKES_NO_WINDOWS_H
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdarg.h>
+#if !defined(_MSC_VER)
+# include <unistd.h>
+#else
+# include <io.h>
+typedef intptr_t ssize_t;
+#endif
+#include "k/kDefs.h"
+#include "k/kTypes.h"
+#include "k/kLdrFmts/pe.h"
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include "kDep.h"
+#include "err.h"
+#include "kmkbuiltin.h"
+
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+#if 0
+# define dprintf(a) printf a
+# define dump(pb, cb, offBase) depHexDump(pb,cb,offBase)
+# define WITH_DPRINTF
+#else
+# define dprintf(a) do {} while (0)
+# define dump(pb, cb, offBase) do {} while (0)
+# undef WITH_DPRINTF
+#endif
+
+/** @name OMF defines
+ * @{ */
+#define KDEPOMF_THEADR 0x80
+#define KDEPOMF_LHEADR 0x82
+#define KDEPOMF_COMENT 0x88
+#define KDEPOMF_CMTCLS_DEPENDENCY 0xe9
+#define KDEPOMF_CMTCLS_DBGTYPE 0xa1
+#define KDEPOMF_LINNUM 0x94
+#define KDEPOMF_LINNUM32 0x95
+/** @} */
+
+
+/*******************************************************************************
+* Structures and Typedefs *
+*******************************************************************************/
+/** @name OMF Structures
+ * @{ */
+#pragma pack(1)
+/** OMF record header. */
+typedef struct KDEPOMFHDR
+{
+ /** The record type. */
+ KU8 bType;
+ /** The size of the record, excluding this header. */
+ KU16 cbRec;
+} KDEPOMFHDR;
+typedef KDEPOMFHDR *PKDEPOMFHDR;
+typedef const KDEPOMFHDR *PCKDEPOMFHDR;
+
+/** OMF string. */
+typedef struct KDEPOMFSTR
+{
+ KU8 cch;
+ char ach[1];
+} KDEPOMFSTR;
+typedef KDEPOMFSTR *PKDEPOMFSTR;
+typedef const KDEPOMFSTR *PCKDEPOMFSTR;
+
+/** THEADR/LHEADR. */
+typedef struct KDEPOMFTHEADR
+{
+ KDEPOMFHDR Hdr;
+ KDEPOMFSTR Name;
+} KDEPOMFTHEADR;
+typedef KDEPOMFTHEADR *PKDEPOMFTHEADR;
+typedef const KDEPOMFTHEADR *PCKDEPOMFTHEADR;
+
+/** Dependency File. */
+typedef struct KDEPOMFDEPFILE
+{
+ KDEPOMFHDR Hdr;
+ KU8 fType;
+ KU8 bClass;
+ KU16 wDosTime;
+ KU16 wDosDate;
+ KDEPOMFSTR Name;
+} KDEPOMFDEPFILE;
+typedef KDEPOMFDEPFILE *PKDEPOMFDEPFILE;
+typedef const KDEPOMFDEPFILE *PCKDEPOMFDEPFILE;
+
+#pragma pack()
+/** @} */
+
+
+/** @name COFF Structures
+ * @{ */
+#pragma pack(1)
+
+typedef struct KDEPCVSYMHDR
+{
+ /** The record size minus the size field. */
+ KU16 cb;
+ /** The record type. */
+ KU16 uType;
+} KDEPCVSYMHDR;
+typedef KDEPCVSYMHDR *PKDEPCVSYMHDR;
+typedef const KDEPCVSYMHDR *PCKDEPCVSYMHDR;
+
+/** @name Selection of KDEPCVSYMHDR::uType values.
+ * @{ */
+#define K_CV8_S_MSTOOL KU16_C(0x1116)
+/** @} */
+
+typedef struct KDEPCV8SYMHDR
+{
+ /** The record type. */
+ KU32 uType;
+ /** The record size minus the size field. */
+ KU32 cb;
+} KDEPCV8SYMHDR;
+typedef KDEPCV8SYMHDR *PKDEPCV8SYMHDR;
+typedef const KDEPCV8SYMHDR *PCKDEPCV8SYMHDR;
+
+/** @name Known KDEPCV8SYMHDR::uType Values.
+ * @{ */
+#define K_CV8_SYMBOL_INFO KU32_C(0x000000f1)
+#define K_CV8_LINE_NUMBERS KU32_C(0x000000f2)
+#define K_CV8_STRING_TABLE KU32_C(0x000000f3)
+#define K_CV8_SOURCE_FILES KU32_C(0x000000f4)
+#define K_CV8_COMDAT_XXXXX KU32_C(0x000000f5) /**< no idea about the format... */
+/** @} */
+
+#pragma pack()
+/** @} */
+
+/**
+ * Globals.
+ */
+typedef struct KDEPOBJGLOBALS
+{
+ /** The command execution context. */
+ PKMKBUILTINCTX pCtx;
+ /** Core instance. */
+ DEPGLOBALS Core;
+ /** The file. */
+ const char *pszFile;
+} KDEPOBJGLOBALS;
+/** Pointer to kDepObj globals. */
+typedef KDEPOBJGLOBALS *PKDEPOBJGLOBALS;
+
+
+
+/**
+ * Prints an error message.
+ *
+ * @returns rc.
+ * @param pThis kObjDep instance data.
+ * @param rc The return code, for making one line return statements.
+ * @param pszFormat The message format string.
+ * @param ... Format arguments.
+ * @todo Promote this to kDep.c.
+ */
+static int kDepErr(PKDEPOBJGLOBALS pThis, int rc, const char *pszFormat, ...)
+{
+ char szMsg[2048];
+ va_list va;
+ va_start(va, pszFormat);
+ vsnprintf(szMsg, sizeof(szMsg) - 1, pszFormat, va);
+ va_end(va);
+ szMsg[sizeof(szMsg) - 1] = '\0';
+
+ if (pThis->pszFile)
+ warnx(pThis->pCtx, "%s: error: %s", pThis->pszFile, szMsg);
+ else
+ errx(pThis->pCtx, rc, "%s", szMsg);
+ return rc;
+}
+
+
+/**
+ * Gets an index from the data.
+ *
+ * @returns The index, KU16_MAX on buffer underflow.
+ * @param puData The current data stream position (in/out).
+ * @param pcbLeft Number of bytes left (in/out).
+ */
+static KU16 kDepObjOMFGetIndex(KPCUINT *puData, KU16 *pcbLeft)
+{
+ KU16 u16;
+
+ if (*pcbLeft >= 1 && *pcbLeft != KU16_MAX)
+ {
+ *pcbLeft -= 1;
+ u16 = *puData->pb++;
+ if (u16 & KU16_C(0x80))
+ {
+ if (*pcbLeft >= 1)
+ {
+ *pcbLeft -= 1;
+ u16 = ((u16 & KU16_C(0x7f)) << 8) | *puData->pb++;
+ }
+ else
+ u16 = KU16_MAX;
+ }
+ }
+ else
+ u16 = KU16_MAX;
+ return u16;
+}
+
+
+/**
+ * Parses the OMF file.
+ *
+ * @returns 0 on success, 1 on failure, 2 if no dependencies was found.
+ * @param pThis The kDepObj instance data.
+ * @param pbFile The start of the file.
+ * @param cbFile The file size.
+ */
+int kDepObjOMFParse(PKDEPOBJGLOBALS pThis, const KU8 *pbFile, KSIZE cbFile)
+{
+ PCKDEPOMFHDR pHdr = (PCKDEPOMFHDR)pbFile;
+ KSIZE cbLeft = cbFile;
+ char uDbgType = 0; /* H or C */
+ KU8 uDbgVer = KU8_MAX;
+ KU32 iSrc = 0;
+ KU32 iMaybeSrc = 0;
+ KU8 uLinNumType = KU8_MAX;
+ KU16 cLinNums = 0;
+ KU32 cLinFiles = 0;
+ KU32 iLinFile = 0;
+
+ /*
+ * Iterate thru the records until we hit the end or an invalid one.
+ */
+ while ( cbLeft >= sizeof(*pHdr)
+ && cbLeft >= pHdr->cbRec + sizeof(*pHdr))
+ {
+ KPCUINT uData;
+ uData.pv = pHdr + 1;
+
+ /* process selected record types. */
+ dprintf(("%#07" KUPTR_PRI ": %#04x %#05x\n", (const KU8*)pHdr - pbFile, pHdr->bType, pHdr->cbRec));
+ switch (pHdr->bType)
+ {
+ /*
+ * The T/L Header contains the source name. When emitting CodeView 4
+ * and earlier (like masm and watcom does), all includes used by the
+ * line number tables have their own THEADR record.
+ */
+ case KDEPOMF_THEADR:
+ case KDEPOMF_LHEADR:
+ {
+ PCKDEPOMFTHEADR pTHeadr = (PCKDEPOMFTHEADR)pHdr;
+ if (1 + pTHeadr->Name.cch + 1 != pHdr->cbRec)
+ return kDepErr(pThis, 1, "%#07x - Bad %cHEADR record, length mismatch.",
+ (const KU8*)pHdr - pbFile, pHdr->bType == KDEPOMF_THEADR ? 'T' : 'L');
+ if ( ( pTHeadr->Name.cch > 2
+ && pTHeadr->Name.ach[pTHeadr->Name.cch - 2] == '.'
+ && ( pTHeadr->Name.ach[pTHeadr->Name.cch - 1] == 'o'
+ || pTHeadr->Name.ach[pTHeadr->Name.cch - 1] == 'O'))
+ || ( pTHeadr->Name.cch > 4
+ && pTHeadr->Name.ach[pTHeadr->Name.cch - 4] == '.'
+ && ( pTHeadr->Name.ach[pTHeadr->Name.cch - 3] == 'o'
+ || pTHeadr->Name.ach[pTHeadr->Name.cch - 3] == 'O')
+ && ( pTHeadr->Name.ach[pTHeadr->Name.cch - 2] == 'b'
+ || pTHeadr->Name.ach[pTHeadr->Name.cch - 2] == 'B')
+ && ( pTHeadr->Name.ach[pTHeadr->Name.cch - 1] == 'j'
+ || pTHeadr->Name.ach[pTHeadr->Name.cch - 1] == 'J'))
+ )
+ dprintf(("%cHEADR: %.*s [ignored]\n", pHdr->bType == KDEPOMF_THEADR ? 'T' : 'L', pTHeadr->Name.cch, pTHeadr->Name.ach));
+ else
+ {
+ dprintf(("%cHEADR: %.*s\n", pHdr->bType == KDEPOMF_THEADR ? 'T' : 'L', pTHeadr->Name.cch, pTHeadr->Name.ach));
+ depAdd(&pThis->Core, pTHeadr->Name.ach, pTHeadr->Name.cch);
+ iMaybeSrc++;
+ }
+ uLinNumType = KU8_MAX;
+ break;
+ }
+
+ case KDEPOMF_COMENT:
+ {
+ KU8 uClass;
+
+ if (pHdr->cbRec < 2 + 1)
+ return kDepErr(pThis, 1, "%#07x - Bad COMMENT record, too small.", (const KU8*)pHdr - pbFile);
+ if (uData.pb[0] & 0x3f)
+ return kDepErr(pThis, 1, "%#07x - Bad COMMENT record, reserved flags set.", (const KU8*)pHdr - pbFile);
+ uClass = uData.pb[1];
+ uData.pb += 2;
+ switch (uClass)
+ {
+ /*
+ * Borland dependency file comment (famously used by wmake and Watcom).
+ */
+ case KDEPOMF_CMTCLS_DEPENDENCY:
+ {
+ PCKDEPOMFDEPFILE pDep = (PCKDEPOMFDEPFILE)pHdr;
+ if (K_OFFSETOF(KDEPOMFDEPFILE, Name.ach[pDep->Name.cch]) + 1 != pHdr->cbRec + sizeof(*pHdr))
+ {
+ /* Empty record indicates the end of the dependency files,
+ no need to go on. */
+ if (pHdr->cbRec == 2 + 1)
+ return 0;
+ return kDepErr(pThis, 1, "%#07lx - Bad DEPENDENCY FILE record, length mismatch. (%u/%u)",
+ (long)((const KU8 *)pHdr - pbFile),
+ K_OFFSETOF(KDEPOMFDEPFILE, Name.ach[pDep->Name.cch]) + 1,
+ (unsigned)(pHdr->cbRec + sizeof(*pHdr)));
+ }
+ depAdd(&pThis->Core, pDep->Name.ach, pDep->Name.cch);
+ iSrc++;
+ break;
+ }
+
+ /*
+ * Pick up the debug type so we can parse the LINNUM records.
+ */
+ case KDEPOMF_CMTCLS_DBGTYPE:
+ if (pHdr->cbRec < 2 + 3 + 1)
+ break; /* ignore, Borland used this for something else apparently. */
+ if ( !(uData.pb[1] == 'C' && uData.pb[2] == 'V')
+ && !(uData.pb[1] == 'H' && uData.pb[2] == 'L'))
+ {
+ dprintf(("Unknown debug type: %c%c (%u)\n", uData.pb[1], uData.pb[2], uData.pb[0]));
+ break;
+ }
+ uDbgType = uData.pb[1];
+ uDbgVer = uData.pb[0];
+ dprintf(("Debug Type %s ver %u\n", uDbgType == 'H' ? "HLL" : "CodeView", uDbgVer));
+ break;
+
+ }
+ break; /* COMENT */
+ }
+
+ /*
+ * LINNUM + THEADR == sigar.
+ */
+ case KDEPOMF_LINNUM:
+ if (uDbgType == 'C')
+ iMaybeSrc |= KU32_C(0x80000000);
+ dprintf(("LINNUM:\n"));
+ break;
+
+ /*
+ * The HLL v4 and v6 file names table will include all files when present, which
+ * is perfect for generating dependencies.
+ */
+ case KDEPOMF_LINNUM32:
+ if ( uDbgType == 'H'
+ && uDbgVer >= 3
+ && uDbgVer <= 6)
+ {
+ /* skip two indexes (group & segment) */
+ KU16 cbRecLeft = pHdr->cbRec - 1;
+ KU16 uGrp = kDepObjOMFGetIndex(&uData, &cbRecLeft);
+ KU16 uSeg = kDepObjOMFGetIndex(&uData, &cbRecLeft);
+ if (uSeg == KU16_MAX)
+ return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record", (long)((const KU8 *)pHdr - pbFile));
+ K_NOREF(uGrp);
+
+ if (uLinNumType == KU8_MAX)
+ {
+#ifdef WITH_DPRINTF
+ static const char * const s_apsz[5] =
+ {
+ "source file", "listing file", "source & listing file", "file names table", "path table"
+ };
+#endif
+ KU16 uLine;
+ KU8 uReserved;
+ KU16 uSeg2;
+ KU32 cbLinNames;
+
+ if (cbRecLeft < 2+1+1+2+2+4)
+ return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, too short", (long)((const KU8 *)pHdr - pbFile));
+ cbRecLeft -= 2+1+1+2+2+4;
+ uLine = *uData.pu16++;
+ uLinNumType = *uData.pu8++;
+ uReserved = *uData.pu8++; K_NOREF(uReserved);
+ cLinNums = *uData.pu16++; K_NOREF(cLinNums);
+ uSeg2 = *uData.pu16++; K_NOREF(uSeg2);
+ cbLinNames = *uData.pu32++; K_NOREF(cbLinNames);
+
+ dprintf(("LINNUM32: uGrp=%#x uSeg=%#x uSeg2=%#x uLine=%#x (MBZ) uReserved=%#x\n",
+ uGrp, uSeg, uSeg2, uLine, uReserved));
+ dprintf(("LINNUM32: cLinNums=%#x (%u) cbLinNames=%#x (%u) uLinNumType=%#x (%s)\n",
+ cLinNums, cLinNums, cbLinNames, cbLinNames, uLinNumType,
+ uLinNumType < K_ELEMENTS(s_apsz) ? s_apsz[uLinNumType] : "??"));
+
+ if (uLine != 0)
+ return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, line %#x (MBZ)", (long)((const KU8 *)pHdr - pbFile), uLine);
+ cLinFiles = iLinFile = KU32_MAX;
+ if ( uLinNumType == 3 /* file names table */
+ || uLinNumType == 4 /* path table */)
+ cLinNums = 0; /* no line numbers */
+ else if (uLinNumType > 4)
+ return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, type %#x unknown", (long)((const KU8 *)pHdr - pbFile), uLinNumType);
+ }
+ else
+ dprintf(("LINNUM32: uGrp=%#x uSeg=%#x\n", uGrp, uSeg));
+
+
+ /* Skip file numbers (we parse them to follow the stream correctly). */
+ if (uLinNumType != 3 && uLinNumType != 4)
+ {
+ static const KU16 s_acbTypes[3] = { 2+2+4, 4+4+4, 2+2+4+4+4 };
+ KU16 cbEntry = s_acbTypes[uLinNumType];
+
+ while (cLinNums && cbRecLeft)
+ {
+ if (cbRecLeft < cbEntry)
+ return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, incomplete line entry", (long)((const KU8 *)pHdr - pbFile));
+
+ switch (uLinNumType)
+ {
+ case 0: /* source file */
+ dprintf((" Line %6" KU16_PRI " of file %2" KU16_PRI " at %#010" KX32_PRI "\n",
+ uData.pu16[0], uData.pu16[1], uData.pu32[1]));
+ break;
+ case 1: /* listing file */
+ dprintf((" Line %6" KU32_PRI ", statement %6" KU32_PRI " at %#010" KX32_PRI "\n",
+ uData.pu32[0], uData.pu32[1], uData.pu32[2]));
+ break;
+ case 2: /* source & listing file */
+ dprintf((" Line %6" KU16_PRI " of file %2" KU16_PRI ", listning line %6" KU32_PRI ", statement %6" KU32_PRI " at %#010" KX32_PRI "\n",
+ uData.pu16[0], uData.pu16[1], uData.pu32[1], uData.pu32[2], uData.pu32[3]));
+ break;
+ }
+ uData.pb += cbEntry;
+ cbRecLeft -= cbEntry;
+ cLinNums--;
+ }
+
+ /* If at end of the announced line number entiries, we may find a file names table
+ here (who is actually emitting this?). */
+ if (!cLinNums)
+ {
+ uLinNumType = cbRecLeft > 0 ? 3 : KU8_MAX;
+ dprintf(("End-of-line-numbers; uLinNumType=%u cbRecLeft=%#x\n", uLinNumType, cbRecLeft));
+ }
+ }
+
+ if (uLinNumType == 3 || uLinNumType == 4)
+ {
+ /* Read the file/path table header (first time only). */
+ if (cLinFiles == KU32_MAX && iLinFile == KU32_MAX)
+ {
+ KU32 iFirstCol;
+ KU32 cCols;
+
+ if (cbRecLeft < 4+4+4)
+ return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, incomplete file/path table header", (long)((const KU8 *)pHdr - pbFile));
+ cbRecLeft -= 4+4+4;
+
+ iFirstCol = *uData.pu32++; K_NOREF(iFirstCol);
+ cCols = *uData.pu32++; K_NOREF(cCols);
+ cLinFiles = *uData.pu32++;
+ dprintf(("%s table header: cLinFiles=%#" KX32_PRI " (%" KU32_PRI ") iFirstCol=%" KU32_PRI " cCols=%" KU32_PRI"\n",
+ uLinNumType == 3 ? "file names" : "path", cLinFiles, cLinFiles, iFirstCol, cCols));
+ if (cLinFiles == KU32_MAX)
+ return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, too many file/path table entries.", (long)((const KU8 *)pHdr - pbFile));
+ iLinFile = 0;
+ }
+
+ /* Parse the file names / path table. */
+ while (iLinFile < cLinFiles && cbRecLeft)
+ {
+ KU16 cbName = *uData.pb++;
+ if (cbRecLeft < 1 + cbName)
+ return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, file/path table entry too long.", (long)((const KU8 *)pHdr - pbFile));
+ iLinFile++;
+ dprintf(("#%" KU32_PRI": %.*s\n", iLinFile, cbName, uData.pch));
+ if (uLinNumType == 3)
+ {
+ depAdd(&pThis->Core, uData.pch, cbName);
+ iSrc++;
+ }
+ cbRecLeft -= 1 + cbName;
+ uData.pb += cbName;
+ }
+
+ /* The end? */
+ if (iLinFile == cLinFiles)
+ {
+ uLinNumType = KU8_MAX;
+ dprintf(("End-of-file/path-table; cbRecLeft=%#x\n", cbRecLeft));
+ }
+ }
+ }
+ else
+ dprintf(("LINNUM32: Unknown or unsupported format\n"));
+ break;
+
+ }
+
+ /* advance */
+ cbLeft -= pHdr->cbRec + sizeof(*pHdr);
+ pHdr = (PCKDEPOMFHDR)((const KU8 *)(pHdr + 1) + pHdr->cbRec);
+ }
+
+ if (cbLeft)
+ return kDepErr(pThis, 1, "%#07x - Unexpected EOF. cbLeft=%#x", (const KU8*)pHdr - pbFile, cbLeft);
+
+ if (iSrc == 0 && iMaybeSrc <= 1)
+ {
+ dprintf(("kDepObjOMFParse: No cylindrical smoking thing: iSrc=0 iMaybeSrc=%#" KX32_PRI"\n", iMaybeSrc));
+ return 2;
+ }
+ dprintf(("kDepObjOMFParse: iSrc=%" KU32_PRI " iMaybeSrc=%#" KX32_PRI "\n", iSrc, iMaybeSrc));
+ return 0;
+}
+
+
+/**
+ * Checks if this file is an OMF file or not.
+ *
+ * @returns K_TRUE if it's OMF, K_FALSE otherwise.
+ *
+ * @param pb The start of the file.
+ * @param cb The file size.
+ */
+KBOOL kDepObjOMFTest(const KU8 *pbFile, KSIZE cbFile)
+{
+ PCKDEPOMFTHEADR pHdr = (PCKDEPOMFTHEADR)pbFile;
+
+ if (cbFile <= sizeof(*pHdr))
+ return K_FALSE;
+ if ( pHdr->Hdr.bType != KDEPOMF_THEADR
+ && pHdr->Hdr.bType != KDEPOMF_LHEADR)
+ return K_FALSE;
+ if (pHdr->Hdr.cbRec + sizeof(pHdr->Hdr) >= cbFile)
+ return K_FALSE;
+ if (pHdr->Hdr.cbRec != 1 + pHdr->Name.cch + 1)
+ return K_FALSE;
+
+ return K_TRUE;
+}
+
+
+/**
+ * Parses a CodeView 8 symbol section.
+ *
+ * @returns 0 on success, 1 on failure, 2 if no dependencies was found.
+ * @param pThis The kDepObj instance data.
+ * @param pbSyms Pointer to the start of the symbol section.
+ * @param cbSyms Size of the symbol section.
+ */
+int kDepObjCOFFParseCV8SymbolSection(PKDEPOBJGLOBALS pThis, const KU8 *pbSyms, KU32 cbSyms)
+{
+ char const * pchStrTab = NULL;
+ KU32 cbStrTab = 0;
+ KPCUINT uSrcFiles = {0};
+ KU32 cbSrcFiles = 0;
+ KU32 off = 4;
+ KU32 iSrc = 0;
+
+ if (cbSyms < 16)
+ return 1;
+
+ /*
+ * The parsing loop.
+ */
+ while (off < cbSyms)
+ {
+ PCKDEPCV8SYMHDR pHdr = (PCKDEPCV8SYMHDR)(pbSyms + off);
+ KPCUINT uData;
+ KU32 cbData;
+ uData.pv = pHdr + 1;
+
+ if (off + sizeof(*pHdr) >= cbSyms)
+ {
+ kDepErr(pThis, 1, "CV symbol table entry at %08" KX32_PRI " is too long; cbSyms=%#" KX32_PRI "",
+ off, cbSyms);
+ return 1; /* FIXME */
+ }
+
+ cbData = pHdr->cb;
+ if (off + cbData + sizeof(*pHdr) > cbSyms)
+ {
+ kDepErr(pThis, 1, "CV symbol table entry at %08" KX32_PRI " is too long; cbData=%#" KX32_PRI " cbSyms=%#" KX32_PRI,
+ off, cbData, cbSyms);
+ return 1; /* FIXME */
+ }
+
+ /* If the size is 0, assume it covers the rest of the section. VC++ 2003 has
+ been observed doing thing. */
+ if (!cbData)
+ cbData = cbSyms - off;
+
+ switch (pHdr->uType)
+ {
+ case K_CV8_SYMBOL_INFO:
+ dprintf(("%06" KX32_PRI " %06" KX32_PRI ": Symbol Info\n", off, cbData));
+ /*dump(uData.pb, cbData, 0);*/
+ break;
+
+ case K_CV8_LINE_NUMBERS:
+ dprintf(("%06" KX32_PRI " %06" KX32_PRI ": Line numbers\n", off, cbData));
+ /*dump(uData.pb, cbData, 0);*/
+ break;
+
+ case K_CV8_STRING_TABLE:
+ dprintf(("%06" KX32_PRI " %06" KX32_PRI ": String table\n", off, cbData));
+ if (pchStrTab)
+ warnx(pThis->pCtx, "%s: warning: Found yet another string table!", pThis->pszFile);
+ pchStrTab = uData.pch;
+ cbStrTab = cbData;
+ /*dump(uData.pb, cbData, 0);*/
+ break;
+
+ case K_CV8_SOURCE_FILES:
+ dprintf(("%06" KX32_PRI " %06" KX32_PRI ": Source files\n", off, cbData));
+ if (uSrcFiles.pb)
+ warnx(pThis->pCtx, "%s: warning: Found yet another source files table!", pThis->pszFile);
+ uSrcFiles = uData;
+ cbSrcFiles = cbData;
+ /*dump(uData.pb, cbData, 0);*/
+ break;
+
+ case K_CV8_COMDAT_XXXXX:
+ dprintf(("%06" KX32_PRI " %06" KX32_PRI ": 0xf5 Unknown COMDAT stuff\n", off, cbData));
+ /*dump(uData.pb, cbData, 0);*/
+ break;
+
+ default:
+ dprintf(("%06" KX32_PRI " %06" KX32_PRI ": Unknown type %#" KX32_PRI "\n",
+ off, cbData, pHdr->uType));
+ dump(uData.pb, cbData, 0);
+ break;
+ }
+
+ /* next */
+ cbData = (cbData + 3) & ~KU32_C(3);
+ off += cbData + sizeof(*pHdr);
+ }
+
+ /*
+ * Did we find any source files and strings?
+ */
+ if (!pchStrTab || !uSrcFiles.pv)
+ {
+ dprintf(("kDepObjCOFFParseCV8SymbolSection: No cylindrical smoking thing: pchStrTab=%p uSrcFiles.pv=%p\n", pchStrTab, uSrcFiles.pv));
+ return 2;
+ }
+
+ /*
+ * Iterate the source file table.
+ */
+ off = 0;
+ while (off < cbSrcFiles)
+ {
+ KU32 offFile;
+ const char *pszFile;
+ KSIZE cchFile;
+ KU16 u16Type;
+ KPCUINT uSrc;
+ KU32 cbSrc;
+
+ /*
+ * Validate and parse the entry (variable length record are fun).
+ */
+ if (off + 8 > cbSrcFiles)
+ return kDepErr(pThis, 1, "CV source file entry at %08" KX32_PRI " is too long; cbSrcFiles=%#" KX32_PRI,
+ off, cbSrcFiles);
+ uSrc.pb = uSrcFiles.pb + off;
+ u16Type = uSrc.pu16[2];
+ switch (u16Type)
+ {
+ case 0x0110: cbSrc = 6 + 16 + 2; break; /* MD5 */
+ case 0x0214: cbSrc = 6 + 20 + 2; break; /* SHA1 */ /** @todo check this */
+ case 0x0320: cbSrc = 6 + 32 + 2; break; /* SHA-256 */
+ default: cbSrc = 6 + 0 + 2; break;
+ }
+ if (off + cbSrc > cbSrcFiles)
+ return kDepErr(pThis, 1, "CV source file entry at %08" KX32_PRI " is too long; cbSrc=%#" KX32_PRI " cbSrcFiles=%#" KX32_PRI,
+ off, cbSrc, cbSrcFiles);
+
+ offFile = *uSrc.pu32;
+ if (offFile > cbStrTab)
+ return kDepErr(pThis, 1, "CV source file entry at %08" KX32_PRI " is out side the string table; offFile=%#" KX32_PRI " cbStrTab=%#" KX32_PRI,
+ off, offFile, cbStrTab);
+ pszFile = pchStrTab + offFile;
+ cchFile = strlen(pszFile);
+ if (cchFile == 0)
+ return kDepErr(pThis, 1, "CV source file entry at %08" KX32_PRI " has an empty file name; offFile=%#x" KX32_PRI,
+ off, offFile);
+
+ /*
+ * Display the result and add it to the dependency database.
+ */
+ depAdd(&pThis->Core, pszFile, cchFile);
+#ifdef WITH_DPRINTF
+ dprintf(("#%03" KU32_PRI ": ", iSrc));
+ {
+ KU32 off = 6;
+ for (;off < cbSrc - 2; off++)
+ dprintf(("%02" KX8_PRI, uSrc.pb[off]));
+ if (cbSrc == 6)
+ dprintf(("type=%#06" KX16_PRI, u16Type));
+ dprintf((" '%s'\n", pszFile));
+ }
+#endif
+
+
+ /* next */
+ iSrc++;
+ off += cbSrc;
+ }
+
+ if (iSrc == 0)
+ {
+ dprintf(("kDepObjCOFFParseCV8SymbolSection: No cylindrical smoking thing: iSrc=0\n"));
+ return 2;
+ }
+ dprintf(("kDepObjCOFFParseCV8SymbolSection: iSrc=%" KU32_PRI "\n", iSrc));
+ return 0;
+}
+
+
+/**
+ * Parses the OMF file.
+ *
+ * @returns 0 on success, 1 on failure, 2 if no dependencies was found.
+ * @param pThis The kDepObj instance data.
+ * @param pbFile The start of the file.
+ * @param cbFile The file size.
+ */
+int kDepObjCOFFParse(PKDEPOBJGLOBALS pThis, const KU8 *pbFile, KSIZE cbFile)
+{
+ IMAGE_FILE_HEADER const *pFileHdr = (IMAGE_FILE_HEADER const *)pbFile;
+ ANON_OBJECT_HEADER_BIGOBJ const *pBigObjHdr = (ANON_OBJECT_HEADER_BIGOBJ const *)pbFile;
+ IMAGE_SECTION_HEADER const *paSHdrs;
+ KU32 cSHdrs;
+ unsigned iSHdr;
+ KPCUINT u;
+ KBOOL fDebugS = K_FALSE;
+ int rcRet = 2;
+ int rc;
+
+ if ( pBigObjHdr->Sig1 == 0
+ && pBigObjHdr->Sig2 == KU16_MAX)
+ {
+ paSHdrs = (IMAGE_SECTION_HEADER const *)(pBigObjHdr + 1);
+ cSHdrs = pBigObjHdr->NumberOfSections;
+ }
+ else
+ {
+ paSHdrs = (IMAGE_SECTION_HEADER const *)((KU8 const *)(pFileHdr + 1) + pFileHdr->SizeOfOptionalHeader);
+ cSHdrs = pFileHdr->NumberOfSections;
+ }
+
+
+ dprintf(("COFF file!\n"));
+
+ for (iSHdr = 0; iSHdr < cSHdrs; iSHdr++)
+ {
+ if ( !memcmp(paSHdrs[iSHdr].Name, ".debug$S", sizeof(".debug$S") - 1)
+ && paSHdrs[iSHdr].SizeOfRawData > 4)
+ {
+ u.pb = pbFile + paSHdrs[iSHdr].PointerToRawData;
+ dprintf(("CV symbol table: version=%x\n", *u.pu32));
+ if (*u.pu32 == 0x000000004)
+ rc = kDepObjCOFFParseCV8SymbolSection(pThis, u.pb, paSHdrs[iSHdr].SizeOfRawData);
+ else
+ rc = 2;
+ dprintf(("rc=%d\n", rc));
+ if (rcRet == 2)
+ rcRet = rc;
+ if (rcRet != 2)
+ return rc;
+ fDebugS = K_TRUE;
+ }
+ dprintf(("#%d: %.8s\n", iSHdr, paSHdrs[iSHdr].Name));
+ }
+
+ /* If we found no dependencies but did find a .debug$S section, check if
+ this is a case where the compile didn't emit any because there is no
+ code in this compilation unit. */
+ if (rcRet == 2)
+ {
+ if (fDebugS)
+ {
+ for (iSHdr = 0; iSHdr < cSHdrs; iSHdr++)
+ if (!memcmp(paSHdrs[iSHdr].Name, ".text", sizeof(".text") - 1))
+ return kDepErr(pThis, 2, "%s: no dependencies (has text).", pThis->pszFile);
+ warnx(pThis->pCtx, "%s: no dependencies, but also no text, so probably (mostly) harmless.", pThis->pszFile);
+ return 0;
+ }
+ kDepErr(pThis, 2, "%s: no dependencies.", pThis->pszFile);
+ }
+
+ return rcRet;
+}
+
+
+/**
+ * Checks if this file is a COFF file or not.
+ *
+ * @returns K_TRUE if it's COFF, K_FALSE otherwise.
+ *
+ * @param pThis The kDepObj instance data.
+ * @param pb The start of the file.
+ * @param cb The file size.
+ */
+KBOOL kDepObjCOFFTest(PKDEPOBJGLOBALS pThis, const KU8 *pbFile, KSIZE cbFile)
+{
+ IMAGE_FILE_HEADER const *pFileHdr = (IMAGE_FILE_HEADER const *)pbFile;
+ ANON_OBJECT_HEADER_BIGOBJ const *pBigObjHdr = (ANON_OBJECT_HEADER_BIGOBJ const *)pbFile;
+ IMAGE_SECTION_HEADER const *paSHdrs;
+ KU32 cSHdrs;
+ KU32 iSHdr;
+ KSIZE cbHdrs;
+
+ if (cbFile <= sizeof(*pFileHdr))
+ return K_FALSE;
+
+ /*
+ * Deal with -bigobj output first.
+ */
+ if ( pBigObjHdr->Sig1 == 0
+ && pBigObjHdr->Sig2 == KU16_MAX)
+ {
+ static const KU8 s_abClsId[16] = { ANON_OBJECT_HEADER_BIGOBJ_CLS_ID_BYTES };
+
+ paSHdrs = (IMAGE_SECTION_HEADER const *)(pBigObjHdr + 1);
+ cSHdrs = pBigObjHdr->NumberOfSections;
+ cbHdrs = sizeof(IMAGE_SECTION_HEADER) * cSHdrs;
+
+ if (cbFile <= sizeof(*pBigObjHdr))
+ return K_FALSE;
+
+ if (pBigObjHdr->Version != 2)
+ return K_FALSE;
+ if (memcmp(&pBigObjHdr->ClassID[0], s_abClsId, sizeof(pBigObjHdr->ClassID)) != 0)
+ return K_FALSE;
+
+ if ( pBigObjHdr->Machine != IMAGE_FILE_MACHINE_I386
+ && pBigObjHdr->Machine != IMAGE_FILE_MACHINE_AMD64
+ && pBigObjHdr->Machine != IMAGE_FILE_MACHINE_ARM
+ && pBigObjHdr->Machine != IMAGE_FILE_MACHINE_ARMNT
+ && pBigObjHdr->Machine != IMAGE_FILE_MACHINE_ARM64
+ && pBigObjHdr->Machine != IMAGE_FILE_MACHINE_EBC)
+ {
+ kDepErr(pThis, 1, "bigobj Machine not supported: %#x", pBigObjHdr->Machine);
+ return K_FALSE;
+ }
+ if (pBigObjHdr->Flags != 0)
+ {
+ kDepErr(pThis, 1, "bigobj Flags field is non-zero: %#x", pBigObjHdr->Flags);
+ return K_FALSE;
+ }
+ if (pBigObjHdr->SizeOfData != 0)
+ {
+ kDepErr(pThis, 1, "bigobj SizeOfData field is non-zero: %#x", pBigObjHdr->SizeOfData);
+ return K_FALSE;
+ }
+
+ if ( pBigObjHdr->PointerToSymbolTable != 0
+ && ( pBigObjHdr->PointerToSymbolTable < cbHdrs
+ || pBigObjHdr->PointerToSymbolTable > cbFile))
+ return K_FALSE;
+ if ( pBigObjHdr->PointerToSymbolTable == 0
+ && pBigObjHdr->NumberOfSymbols != 0)
+ return K_FALSE;
+ }
+ /*
+ * Look for normal COFF object.
+ */
+ else
+ {
+ paSHdrs = (IMAGE_SECTION_HEADER const *)((KU8 const *)(pFileHdr + 1) + pFileHdr->SizeOfOptionalHeader);
+ cSHdrs = pFileHdr->NumberOfSections;
+ cbHdrs = (const KU8 *)&paSHdrs[cSHdrs] - (const KU8 *)pbFile;
+
+ if ( pFileHdr->Machine != IMAGE_FILE_MACHINE_I386
+ && pFileHdr->Machine != IMAGE_FILE_MACHINE_AMD64
+ && pFileHdr->Machine != IMAGE_FILE_MACHINE_ARM
+ && pFileHdr->Machine != IMAGE_FILE_MACHINE_ARMNT
+ && pFileHdr->Machine != IMAGE_FILE_MACHINE_ARM64
+ && pFileHdr->Machine != IMAGE_FILE_MACHINE_EBC)
+ return K_FALSE;
+
+ if (pFileHdr->SizeOfOptionalHeader != 0)
+ return K_FALSE; /* COFF files doesn't have an optional header */
+
+ if ( pFileHdr->PointerToSymbolTable != 0
+ && ( pFileHdr->PointerToSymbolTable < cbHdrs
+ || pFileHdr->PointerToSymbolTable > cbFile))
+ return K_FALSE;
+ if ( pFileHdr->PointerToSymbolTable == 0
+ && pFileHdr->NumberOfSymbols != 0)
+ return K_FALSE;
+ if ( pFileHdr->Characteristics
+ & ( IMAGE_FILE_DLL
+ | IMAGE_FILE_SYSTEM
+ | IMAGE_FILE_UP_SYSTEM_ONLY
+ | IMAGE_FILE_NET_RUN_FROM_SWAP
+ | IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP
+ | IMAGE_FILE_EXECUTABLE_IMAGE
+ | IMAGE_FILE_RELOCS_STRIPPED))
+ return K_FALSE;
+ }
+ if ( cSHdrs <= 1
+ || cSHdrs > cbFile)
+ return K_FALSE;
+ if (cbHdrs >= cbFile)
+ return K_FALSE;
+
+ /*
+ * Check the section headers.
+ */
+ for (iSHdr = 0; iSHdr < cSHdrs; iSHdr++)
+ {
+ if ( paSHdrs[iSHdr].PointerToRawData != 0
+ && ( paSHdrs[iSHdr].PointerToRawData < cbHdrs
+ || paSHdrs[iSHdr].PointerToRawData >= cbFile
+ || paSHdrs[iSHdr].PointerToRawData + paSHdrs[iSHdr].SizeOfRawData > cbFile))
+ return K_FALSE;
+ if ( paSHdrs[iSHdr].PointerToRelocations != 0
+ && ( paSHdrs[iSHdr].PointerToRelocations < cbHdrs
+ || paSHdrs[iSHdr].PointerToRelocations >= cbFile
+ || paSHdrs[iSHdr].PointerToRelocations + paSHdrs[iSHdr].NumberOfRelocations * 10 > cbFile)) /* IMAGE_RELOCATION */
+ return K_FALSE;
+ if ( paSHdrs[iSHdr].PointerToLinenumbers != 0
+ && ( paSHdrs[iSHdr].PointerToLinenumbers < cbHdrs
+ || paSHdrs[iSHdr].PointerToLinenumbers >= cbFile
+ || paSHdrs[iSHdr].PointerToLinenumbers + paSHdrs[iSHdr].NumberOfLinenumbers * 6 > cbFile)) /* IMAGE_LINENUMBER */
+ return K_FALSE;
+ }
+
+ return K_TRUE;
+}
+
+
+/**
+ * Read the file into memory and parse it.
+ */
+static int kDepObjProcessFile(PKDEPOBJGLOBALS pThis, FILE *pInput)
+{
+ size_t cbFile;
+ KU8 *pbFile;
+ void *pvOpaque;
+ int rc = 0;
+
+ /*
+ * Read the file into memory.
+ */
+ pbFile = (KU8 *)depReadFileIntoMemory(pInput, &cbFile, &pvOpaque);
+ if (!pbFile)
+ return 1;
+
+ /*
+ * See if it's an OMF file, then process it.
+ */
+ if (kDepObjOMFTest(pbFile, cbFile))
+ rc = kDepObjOMFParse(pThis, pbFile, cbFile);
+ else if (kDepObjCOFFTest(pThis, pbFile, cbFile))
+ rc = kDepObjCOFFParse(pThis, pbFile, cbFile);
+ else
+ rc = kDepErr(pThis, 1, "Doesn't recognize the header of the OMF/COFF file.");
+
+ depFreeFileMemory(pbFile, pvOpaque);
+ return rc;
+}
+
+
+static void kDebObjUsage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s -o <output> -t <target> [-fqs] [-e <ignore-ext>] <OMF or COFF file>\n"
+ " or: %s --help\n"
+ " or: %s --version\n",
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+}
+
+
+int kmk_builtin_kDepObj(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ int i;
+ KDEPOBJGLOBALS This;
+
+ /* Arguments. */
+ FILE *pOutput = NULL;
+ const char *pszOutput = NULL;
+ FILE *pInput = NULL;
+ const char *pszTarget = NULL;
+ int fStubs = 0;
+ int fFixCase = 0;
+ const char *pszIgnoreExt = NULL;
+ /* Argument parsing. */
+ int fInput = 0; /* set when we've found input argument. */
+ int fQuiet = 0;
+
+ /* Init instance data. */
+ This.pCtx = pCtx;
+ This.pszFile = NULL;
+
+ /*
+ * Parse arguments.
+ */
+ if (argc <= 1)
+ {
+ kDebObjUsage(pCtx, 0);
+ return 1;
+ }
+ for (i = 1; i < argc; i++)
+ {
+ if (argv[i][0] == '-')
+ {
+ const char *pszValue;
+ const char *psz = &argv[i][1];
+ char chOpt;
+ chOpt = *psz++;
+ if (chOpt == '-')
+ {
+ /* Convert long to short option. */
+ if (!strcmp(psz, "quiet"))
+ chOpt = 'q';
+ else if (!strcmp(psz, "help"))
+ chOpt = '?';
+ else if (!strcmp(psz, "version"))
+ chOpt = 'V';
+ else
+ {
+ errx(pCtx, 2, "Invalid argument '%s'.", argv[i]);
+ kDebObjUsage(pCtx, 1);
+ return 2;
+ }
+ psz = "";
+ }
+
+ /*
+ * Requires value?
+ */
+ switch (chOpt)
+ {
+ case 'o':
+ case 't':
+ case 'e':
+ if (*psz)
+ pszValue = psz;
+ else if (++i < argc)
+ pszValue = argv[i];
+ else
+ return errx(pCtx, 2, "The '-%c' option takes a value.", chOpt);
+ break;
+
+ default:
+ pszValue = NULL;
+ break;
+ }
+
+
+ switch (chOpt)
+ {
+ /*
+ * Output file.
+ */
+ case 'o':
+ {
+ if (pOutput)
+ return errx(pCtx, 2, "only one output file!");
+ pszOutput = pszValue;
+ if (pszOutput[0] == '-' && !pszOutput[1])
+ pOutput = stdout;
+ else
+ pOutput = fopen(pszOutput, "w" KMK_FOPEN_NO_INHERIT_MODE);
+ if (!pOutput)
+ return err(pCtx, 1, "Failed to create output file '%s'", pszOutput);
+ break;
+ }
+
+ /*
+ * Target name.
+ */
+ case 't':
+ {
+ if (pszTarget)
+ return errx(pCtx, 2, "only one target!");
+ pszTarget = pszValue;
+ break;
+ }
+
+ /*
+ * Fix case.
+ */
+ case 'f':
+ {
+ fFixCase = 1;
+ break;
+ }
+
+ /*
+ * Quiet.
+ */
+ case 'q':
+ {
+ fQuiet = 1;
+ break;
+ }
+
+ /*
+ * Generate stubs.
+ */
+ case 's':
+ {
+ fStubs = 1;
+ break;
+ }
+
+ /*
+ * Extension to ignore.
+ */
+ case 'e':
+ {
+ if (pszIgnoreExt)
+ return errx(pCtx, 2, "The '-e' option can only be used once!");
+ pszIgnoreExt = pszValue;
+ break;
+ }
+
+ /*
+ * The mandatory version & help.
+ */
+ case '?':
+ kDebObjUsage(pCtx, 0);
+ return 0;
+ case 'V':
+ case 'v':
+ return kbuild_version(argv[0]);
+
+ /*
+ * Invalid argument.
+ */
+ default:
+ errx(pCtx, 2, "Invalid argument '%s'.", argv[i]);
+ kDebObjUsage(pCtx, 1);
+ return 2;
+ }
+ }
+ else
+ {
+ pInput = fopen(argv[i], "rb" KMK_FOPEN_NO_INHERIT_MODE);
+ if (!pInput)
+ return err(pCtx, 1, "Failed to open input file '%s'", argv[i]);
+ This.pszFile = argv[i];
+ fInput = 1;
+ }
+
+ /*
+ * End of the line?
+ */
+ if (fInput)
+ {
+ if (++i < argc)
+ return errx(pCtx, 2, "No arguments shall follow the input spec.");
+ break;
+ }
+ }
+
+ /*
+ * Got all we require?
+ */
+ if (!pInput)
+ return errx(pCtx, 2, "No input!");
+ if (!pOutput)
+ return errx(pCtx, 2, "No output!");
+ if (!pszTarget)
+ return errx(pCtx, 2, "No target!");
+
+ /*
+ * Do the parsing.
+ */
+ depInit(&This.Core);
+ i = kDepObjProcessFile(&This, pInput);
+ fclose(pInput);
+
+ /*
+ * Write the dependecy file.
+ */
+ if (!i)
+ {
+ depOptimize(&This.Core, fFixCase, fQuiet, pszIgnoreExt);
+ depPrintTargetWithDeps(&This.Core, pOutput, pszTarget, 1 /*fEscapeTarget*/);
+ if (fStubs)
+ depPrintStubs(&This.Core, pOutput);
+ }
+
+ /*
+ * Close the output, delete output on failure.
+ */
+ if (!i && ferror(pOutput))
+ i = errx(pCtx, 1, "Error writing to '%s'", pszOutput);
+ fclose(pOutput);
+ if (i)
+ {
+ if (unlink(pszOutput))
+ warn(pCtx, "warning: failed to remove output file '%s' on failure.", pszOutput);
+ }
+
+ depCleanup(&This.Core);
+ return i;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kDepObj", NULL };
+ return kmk_builtin_kDepObj(argc, argv, envp, &Ctx);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin/kSubmit.c b/src/kmk/kmkbuiltin/kSubmit.c
new file mode 100644
index 0000000..dffa198
--- /dev/null
+++ b/src/kmk/kmkbuiltin/kSubmit.c
@@ -0,0 +1,2116 @@
+/* $Id: kSubmit.c 3413 2020-08-20 08:20:15Z bird $ */
+/** @file
+ * kMk Builtin command - submit job to a kWorker.
+ */
+
+/*
+ * Copyright (c) 2007-2016 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/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#ifdef __APPLE__
+# define _POSIX_C_SOURCE 1 /* 10.4 sdk and unsetenv */
+#endif
+#include "makeint.h"
+#include "job.h"
+#include "variable.h"
+#include "pathstuff.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+#endif
+#if defined(_MSC_VER)
+# include <ctype.h>
+# include <io.h>
+# include <direct.h>
+# include <process.h>
+#else
+# include <unistd.h>
+#endif
+#ifdef KBUILD_OS_WINDOWS
+# ifndef CONFIG_NEW_WIN_CHILDREN
+# include "sub_proc.h"
+# else
+# include "../w32/winchildren.h"
+# endif
+# include "nt/nt_child_inject_standard_handles.h"
+#endif
+
+#include "kbuild.h"
+#include "kmkbuiltin.h"
+#include "err.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Hashes a pid. */
+#define KWORKER_PID_HASH(a_pid) ((size_t)(a_pid) % 61)
+
+#define TUPLE(a_sz) a_sz, sizeof(a_sz) - 1
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct WORKERINSTANCE *PWORKERINSTANCE;
+typedef struct WORKERINSTANCE
+{
+ /** Pointer to the next worker instance. */
+ PWORKERINSTANCE pNext;
+ /** Pointer to the previous worker instance. */
+ PWORKERINSTANCE pPrev;
+ /** Pointer to the next worker with the same pid hash slot. */
+ PWORKERINSTANCE pNextPidHash;
+ /** 32 or 64. */
+ unsigned cBits;
+ /** The process ID of the kWorker process. */
+ pid_t pid;
+ union
+ {
+ struct
+ {
+ /** The exit code. */
+ int32_t rcExit;
+ /** Set to 1 if the worker is exiting. */
+ uint8_t bWorkerExiting;
+ uint8_t abUnused[3];
+ } s;
+ uint8_t ab[8];
+ } Result;
+ /** Number of result bytes read alread. */
+ size_t cbResultRead;
+
+#ifdef KBUILD_OS_WINDOWS
+ /** The process handle. */
+ HANDLE hProcess;
+ /** The bi-directional pipe we use to talk to the kWorker process. */
+ HANDLE hPipe;
+ /** For overlapped read (have valid event semaphore). */
+ OVERLAPPED OverlappedRead;
+# ifdef CONFIG_NEW_WIN_CHILDREN
+ /** Standard output catcher (reused). */
+ PWINCCWPIPE pStdOut;
+ /** Standard error catcher (reused). */
+ PWINCCWPIPE pStdErr;
+# endif
+#else
+ /** The socket descriptor we use to talk to the kWorker process. */
+ int fdSocket;
+#endif
+
+ /** --debug-dump-history-on-failure. */
+ int fDebugDumpHistoryOnFailure;
+ /** Current history index (must mod with aHistory element count). */
+ unsigned iHistory;
+ /** History. */
+ struct
+ {
+ /** Pointer to the message, NULL if none. */
+ void *pvMsg;
+ /** The message size, zero if not present. */
+ size_t cbMsg;
+ } aHistory[4];
+
+ /** What it's busy with. NULL if idle. */
+ struct child *pBusyWith;
+} WORKERINSTANCE;
+
+
+typedef struct WORKERLIST
+{
+ /** The head of the list. NULL if empty. */
+ PWORKERINSTANCE pHead;
+ /** The tail of the list. NULL if empty. */
+ PWORKERINSTANCE pTail;
+ /** Number of list entries. */
+ size_t cEntries;
+} WORKERLIST;
+typedef WORKERLIST *PWORKERLIST;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** List of idle worker.*/
+static WORKERLIST g_IdleList;
+/** List of busy workers. */
+static WORKERLIST g_BusyList;
+/** PID hash table for the workers.
+ * @sa KWORKER_PID_HASH() */
+static PWORKERINSTANCE g_apPidHash[61];
+
+#ifdef KBUILD_OS_WINDOWS
+/** For naming the pipes.
+ * Also indicates how many worker instances we've spawned. */
+static unsigned g_uWorkerSeqNo = 0;
+#endif
+/** Set if we've registred the atexit handler already. */
+static int g_fAtExitRegistered = 0;
+
+/** @var g_cArchBits
+ * The bit count of the architecture this binary is compiled for. */
+/** @var g_szArch
+ * The name of the architecture this binary is compiled for. */
+/** @var g_cArchBits
+ * The bit count of the alternative architecture. */
+/** @var g_szAltArch
+ * The name of the alternative architecture. */
+#if defined(KBUILD_ARCH_AMD64)
+static unsigned g_cArchBits = 64;
+static char const g_szArch[] = "amd64";
+static unsigned g_cAltArchBits = 32;
+static char const g_szAltArch[] = "x86";
+#elif defined(KBUILD_ARCH_X86)
+static unsigned g_cArchBits = 32;
+static char const g_szArch[] = "x86";
+static unsigned g_cAltArchBits = 64;
+static char const g_szAltArch[] = "amd64";
+#else
+# error "Port me!"
+#endif
+
+#ifdef KBUILD_OS_WINDOWS
+/** The processor group allocator state. */
+static MKWINCHILDCPUGROUPALLOCSTATE g_SubmitProcessorGroupAllocator;
+# if K_ARCH_BITS == 64
+/** The processor group allocator state for 32-bit processes. */
+static MKWINCHILDCPUGROUPALLOCSTATE g_SubmitProcessorGroupAllocator32;
+# endif
+#endif
+
+#ifdef KBUILD_OS_WINDOWS
+/** Pointer to kernel32!SetThreadGroupAffinity. */
+static BOOL (WINAPI *g_pfnSetThreadGroupAffinity)(HANDLE, const GROUP_AFFINITY*, GROUP_AFFINITY *);
+#endif
+
+
+
+/**
+ * Unlinks a worker instance from a list.
+ *
+ * @param pList The list.
+ * @param pWorker The worker.
+ */
+static void kSubmitListUnlink(PWORKERLIST pList, PWORKERINSTANCE pWorker)
+{
+ PWORKERINSTANCE pNext = pWorker->pNext;
+ PWORKERINSTANCE pPrev = pWorker->pPrev;
+
+ if (pNext)
+ {
+ assert(pNext->pPrev == pWorker);
+ pNext->pPrev = pPrev;
+ }
+ else
+ {
+ assert(pList->pTail == pWorker);
+ pList->pTail = pPrev;
+ }
+
+ if (pPrev)
+ {
+ assert(pPrev->pNext == pWorker);
+ pPrev->pNext = pNext;
+ }
+ else
+ {
+ assert(pList->pHead == pWorker);
+ pList->pHead = pNext;
+ }
+
+ assert(!pList->pHead || pList->pHead->pPrev == NULL);
+ assert(!pList->pTail || pList->pTail->pNext == NULL);
+
+ assert(pList->cEntries > 0);
+ pList->cEntries--;
+
+ pWorker->pNext = NULL;
+ pWorker->pPrev = NULL;
+}
+
+
+/**
+ * Appends a worker instance to the tail of a list.
+ *
+ * @param pList The list.
+ * @param pWorker The worker.
+ */
+static void kSubmitListAppend(PWORKERLIST pList, PWORKERINSTANCE pWorker)
+{
+ PWORKERINSTANCE pTail = pList->pTail;
+
+ assert(pTail != pWorker);
+ assert(pList->pHead != pWorker);
+
+ pWorker->pNext = NULL;
+ pWorker->pPrev = pTail;
+ if (pTail != NULL)
+ {
+ assert(pTail->pNext == NULL);
+ pTail->pNext = pWorker;
+ }
+ else
+ {
+ assert(pList->pHead == NULL);
+ pList->pHead = pWorker;
+ }
+ pList->pTail = pWorker;
+
+ assert(pList->pHead->pPrev == NULL);
+ assert(pList->pTail->pNext == NULL);
+
+ pList->cEntries++;
+}
+
+
+/**
+ * Remove worker from the process ID hash table.
+ *
+ * @param pWorker The worker.
+ */
+static void kSubmitPidHashRemove(PWORKERINSTANCE pWorker)
+{
+ size_t idxHash = KWORKER_PID_HASH(pWorker->pid);
+ if (g_apPidHash[idxHash] == pWorker)
+ g_apPidHash[idxHash] = pWorker->pNext;
+ else
+ {
+ PWORKERINSTANCE pPrev = g_apPidHash[idxHash];
+ while (pPrev && pPrev->pNext != pWorker)
+ pPrev = pPrev->pNext;
+ assert(pPrev != NULL);
+ if (pPrev)
+ pPrev->pNext = pWorker->pNext;
+ }
+ pWorker->pid = -1;
+}
+
+
+/**
+ * Looks up a worker by its process ID.
+ *
+ * @returns Pointer to the worker instance if found. NULL if not.
+ * @param pid The process ID of the worker.
+ */
+static PWORKERINSTANCE kSubmitFindWorkerByPid(pid_t pid)
+{
+ PWORKERINSTANCE pWorker = g_apPidHash[KWORKER_PID_HASH(pid)];
+ while (pWorker && pWorker->pid != pid)
+ pWorker = pWorker->pNextPidHash;
+ return pWorker;
+}
+
+
+/**
+ * Calcs the path to the kWorker binary for the worker.
+ *
+ * @returns
+ * @param pCtx The command execution context.
+ * @param pWorker The worker (for its bitcount).
+ * @param pszExecutable The output buffer.
+ * @param cbExecutable The output buffer size.
+ */
+static int kSubmitCalcExecutablePath(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, char *pszExecutable, size_t cbExecutable)
+{
+#if defined(KBUILD_OS_WINDOWS) || defined(KBUILD_OS_OS2)
+ static const char s_szWorkerName[] = "kWorker.exe";
+#else
+ static const char s_szWorkerName[] = "kWorker";
+#endif
+ const char *pszBinPath = get_kbuild_bin_path();
+ size_t const cchBinPath = strlen(pszBinPath);
+ size_t cchExecutable;
+ if ( pWorker->cBits == g_cArchBits
+ ? cchBinPath + 1 + sizeof(s_szWorkerName) <= cbExecutable
+ : cchBinPath + 1 - sizeof(g_szArch) + sizeof(g_szAltArch) + sizeof(s_szWorkerName) <= cbExecutable )
+ {
+ memcpy(pszExecutable, pszBinPath, cchBinPath);
+ cchExecutable = cchBinPath;
+
+ /* Replace the arch bin directory extension with the alternative one if requested. */
+ if (pWorker->cBits != g_cArchBits)
+ {
+ if ( cchBinPath < sizeof(g_szArch)
+ || memcmp(&pszExecutable[cchBinPath - sizeof(g_szArch) + 1], g_szArch, sizeof(g_szArch) - 1) != 0)
+ return errx(pCtx, 1, "KBUILD_BIN_PATH does not end with main architecture (%s) as expected: %s",
+ pszBinPath, g_szArch);
+ cchExecutable -= sizeof(g_szArch) - 1;
+ memcpy(&pszExecutable[cchExecutable], g_szAltArch, sizeof(g_szAltArch) - 1);
+ cchExecutable += sizeof(g_szAltArch) - 1;
+ }
+
+ /* Append a slash and the worker name. */
+ pszExecutable[cchExecutable++] = '/';
+ memcpy(&pszExecutable[cchExecutable], s_szWorkerName, sizeof(s_szWorkerName));
+ return 0;
+ }
+ return errx(pCtx, 1, "KBUILD_BIN_PATH is too long");
+}
+
+
+#ifdef KBUILD_OS_WINDOWS
+/**
+ * Calcs the UTF-16 path to the kWorker binary for the worker.
+ *
+ * @returns
+ * @param pCtx The command execution context.
+ * @param pWorker The worker (for its bitcount).
+ * @param pwszExecutable The output buffer.
+ * @param cwcExecutable The output buffer size.
+ */
+static int kSubmitCalcExecutablePathW(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, wchar_t *pwszExecutable, size_t cwcExecutable)
+{
+ char szExecutable[MAX_PATH];
+ int rc = kSubmitCalcExecutablePath(pCtx, pWorker, szExecutable, sizeof(szExecutable));
+ if (rc == 0)
+ {
+ int cwc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, szExecutable, strlen(szExecutable) + 1,
+ pwszExecutable, cwcExecutable);
+ if (cwc > 0)
+ return 0;
+ return errx(pCtx, 1, "MultiByteToWideChar failed on '%s': %u", szExecutable, GetLastError());
+ }
+ return rc;
+}
+#endif
+
+
+/**
+ * Creates a new worker process.
+ *
+ * @returns 0 on success, non-zero value on failure.
+ * @param pCtx The command execution context.
+ * @param pWorker The worker structure. Caller does the linking
+ * (as we might be reusing an existing worker
+ * instance because a worker shut itself down due
+ * to high resource leak level).
+ * @param cVerbosity The verbosity level.
+ */
+static int kSubmitSpawnWorker(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, int cVerbosity)
+{
+ int rc;
+#ifdef KBUILD_OS_WINDOWS
+ wchar_t wszExecutable[MAX_PATH];
+#else
+ PATH_VAR(szExecutable);
+#endif
+
+ /*
+ * Get the output path so it can be passed on as a volatile.
+ */
+ const char *pszVarVolatile;
+ struct variable *pVarVolatile = lookup_variable(TUPLE("PATH_OUT"));
+ if (pVarVolatile)
+ pszVarVolatile = "PATH_OUT";
+ else
+ {
+ pVarVolatile = lookup_variable(TUPLE("PATH_OUT_BASE"));
+ if (pVarVolatile)
+ pszVarVolatile = "PATH_OUT_BASE";
+ else
+ warn(pCtx, "Neither PATH_OUT_BASE nor PATH_OUT was found.");
+ }
+ if (pVarVolatile && strchr(pVarVolatile->value, '"'))
+ return errx(pCtx, -1, "%s contains double quotes.", pszVarVolatile);
+ if (pVarVolatile && strlen(pVarVolatile->value) >= GET_PATH_MAX)
+ return errx(pCtx, -1, "%s is too long (max %u)", pszVarVolatile, GET_PATH_MAX);
+
+ /*
+ * Construct the executable path.
+ */
+#ifdef KBUILD_OS_WINDOWS
+ rc = kSubmitCalcExecutablePathW(pCtx, pWorker, wszExecutable, K_ELEMENTS(wszExecutable));
+#else
+ rc = kSubmitCalcExecutablePath(pCtx, pWorker, szExecutable, GET_PATH_MAX);
+#endif
+ if (rc == 0)
+ {
+#ifdef KBUILD_OS_WINDOWS
+ static DWORD s_fDenyRemoteClients = ~(DWORD)0;
+ wchar_t wszPipeName[128];
+ HANDLE hWorkerPipe;
+ int iProcessorGroup;
+
+# if K_ARCH_BITS == 64
+ /** @todo make it return -1 if not applicable (e.g only one group). */
+ if (pWorker->cBits != 32)
+ iProcessorGroup = MkWinChildAllocateCpuGroup(&g_SubmitProcessorGroupAllocator);
+ else
+ iProcessorGroup = MkWinChildAllocateCpuGroup(&g_SubmitProcessorGroupAllocator32);
+# else
+ iProcessorGroup = MkWinChildAllocateCpuGroup(&g_SubmitProcessorGroupAllocator);
+# endif
+
+ /*
+ * Create the bi-directional pipe with overlapping I/O enabled.
+ */
+ if (s_fDenyRemoteClients == ~(DWORD)0)
+ s_fDenyRemoteClients = GetVersion() >= 0x60000 ? PIPE_REJECT_REMOTE_CLIENTS : 0;
+ _snwprintf(wszPipeName, sizeof(wszPipeName), L"\\\\.\\pipe\\kmk-%u-kWorker-%u-%u",
+ GetCurrentProcessId(), g_uWorkerSeqNo++, GetTickCount());
+ hWorkerPipe = CreateNamedPipeW(wszPipeName,
+ PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE /* win2k sp2+ */,
+ PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | s_fDenyRemoteClients,
+ 1 /* cMaxInstances */,
+ 64 /*cbOutBuffer*/,
+ 65536 /*cbInBuffer*/,
+ 0 /*cMsDefaultTimeout -> 50ms*/,
+ NULL /* pSecAttr - no inherit */);
+ if (hWorkerPipe != INVALID_HANDLE_VALUE)
+ {
+ pWorker->hPipe = CreateFileW(wszPipeName,
+ GENERIC_READ | GENERIC_WRITE,
+ 0 /* dwShareMode - no sharing */,
+ NULL /*pSecAttr - no inherit */,
+ OPEN_EXISTING,
+ FILE_FLAG_OVERLAPPED,
+ NULL /*hTemplate*/);
+ if (pWorker->hPipe != INVALID_HANDLE_VALUE)
+ {
+ pWorker->OverlappedRead.hEvent = CreateEventW(NULL /*pSecAttrs - no inherit*/, TRUE /*bManualReset*/,
+ TRUE /*bInitialState*/, NULL /*pwszName*/);
+ if (pWorker->OverlappedRead.hEvent != NULL)
+ {
+ extern int process_priority; /* main.c */
+ wchar_t wszCommandLine[MAX_PATH * 3 + 32];
+ wchar_t *pwszDst = wszCommandLine;
+ size_t cwcDst = K_ELEMENTS(wszCommandLine);
+ int cwc;
+ DWORD fFlags;
+ STARTUPINFOW StartupInfo;
+ PROCESS_INFORMATION ProcInfo = { NULL, NULL, 0, 0 };
+
+ /*
+ * Compose the command line.
+ */
+ cwc = _snwprintf(pwszDst, cwcDst, L"\"%s\" ", wszExecutable);
+ assert(cwc > 0 && cwc < cwcDst);
+ pwszDst += cwc;
+ cwcDst -= cwc;
+ if (pVarVolatile && *pVarVolatile->value)
+ {
+ char chEnd = strchr(pVarVolatile->value, '\0')[-1];
+ if (chEnd == '\\')
+ cwc = _snwprintf(pwszDst, cwcDst, L" --volatile \"%S.\"", pVarVolatile->value);
+ else
+ cwc = _snwprintf(pwszDst, cwcDst, L" --volatile \"%S\"", pVarVolatile->value);
+ assert(cwc > 0 && cwc < cwcDst);
+ pwszDst += cwc;
+ cwcDst -= cwc;
+ }
+ if (iProcessorGroup >= 0)
+ {
+ cwc = _snwprintf(pwszDst, cwcDst, L" --group %d", iProcessorGroup);
+ assert(cwc > 0 && cwc < cwcDst);
+ pwszDst += cwc;
+ cwcDst -= cwc;
+ }
+ *pwszDst = '\0';
+
+ /*
+ * Fill in the startup information.
+ */
+ memset(&StartupInfo, 0, sizeof(StartupInfo));
+ StartupInfo.cb = sizeof(StartupInfo);
+ GetStartupInfoW(&StartupInfo);
+ StartupInfo.dwFlags &= ~STARTF_USESTDHANDLES;
+ StartupInfo.lpReserved2 = NULL;
+ StartupInfo.cbReserved2 = 0;
+
+ /*
+ * Flags and such.
+ */
+ fFlags = CREATE_SUSPENDED;
+ 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;
+ }
+
+ /*
+ * Create the worker process.
+ */
+ if (CreateProcessW(wszExecutable, wszCommandLine, NULL /*pProcSecAttr*/, NULL /*pThreadSecAttr*/,
+ FALSE /*fInheritHandles*/, fFlags, NULL /*pwszzEnvironment*/,
+ NULL /*pwszCwd*/, &StartupInfo, &ProcInfo))
+ {
+ char szErrMsg[256];
+ BOOL afReplace[3] = { TRUE, FALSE, FALSE };
+ HANDLE ahReplace[3] = { hWorkerPipe, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE };
+ if (pWorker->pStdOut)
+ {
+ afReplace[1] = TRUE;
+ afReplace[2] = TRUE;
+ ahReplace[1] = pWorker->pStdOut->hPipeChild;
+ ahReplace[2] = pWorker->pStdErr->hPipeChild;
+ }
+
+ rc = nt_child_inject_standard_handles(ProcInfo.hProcess, afReplace, ahReplace, szErrMsg, sizeof(szErrMsg));
+ if (rc == 0)
+ {
+ BOOL fRet;
+ 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;
+ }
+ if (!fRet)
+ warnx(pCtx, "warning: failed to set kWorker thread priority: %u\n", GetLastError());
+
+ if (iProcessorGroup >= 0 && g_pfnSetThreadGroupAffinity)
+ {
+ GROUP_AFFINITY OldAff = { 0, 0, 0, 0, 0 };
+ GROUP_AFFINITY NewAff = { 0 /* == all active apparently */, (WORD)iProcessorGroup, 0, 0, 0 };
+ if (!g_pfnSetThreadGroupAffinity(ProcInfo.hThread, &NewAff, &OldAff))
+ warnx(pCtx, "warning: Failed to set processor group to %d: %u\n",
+ iProcessorGroup, GetLastError());
+ }
+
+ /*
+ * Now, we just need to resume the thread.
+ */
+ if (ResumeThread(ProcInfo.hThread))
+ {
+ CloseHandle(hWorkerPipe);
+ CloseHandle(ProcInfo.hThread);
+ pWorker->pid = ProcInfo.dwProcessId;
+ pWorker->hProcess = ProcInfo.hProcess;
+ if (cVerbosity > 0)
+ warnx(pCtx, "created %d bit worker %d\n", pWorker->cBits, pWorker->pid);
+ return 0;
+ }
+
+ /*
+ * Failed, bail out.
+ */
+ rc = errx(pCtx, -3, "ResumeThread failed: %u", GetLastError());
+ }
+ else
+ rc = errx(pCtx, -3, "%s", szErrMsg);
+ TerminateProcess(ProcInfo.hProcess, 1234);
+ CloseHandle(ProcInfo.hThread);
+ CloseHandle(ProcInfo.hProcess);
+ }
+ else
+ rc = errx(pCtx, -2, "CreateProcessW failed: %u (exe=%S cmdline=%S)",
+ GetLastError(), wszExecutable, wszCommandLine);
+ CloseHandle(pWorker->OverlappedRead.hEvent);
+ pWorker->OverlappedRead.hEvent = INVALID_HANDLE_VALUE;
+ }
+ else
+ rc = errx(pCtx, -1, "CreateEventW failed: %u", GetLastError());
+ CloseHandle(pWorker->hPipe);
+ pWorker->hPipe = INVALID_HANDLE_VALUE;
+ }
+ else
+ rc = errx(pCtx, -1, "Opening named pipe failed: %u", GetLastError());
+ CloseHandle(hWorkerPipe);
+ }
+ else
+ rc = errx(pCtx, -1, "CreateNamedPipeW failed: %u", GetLastError());
+
+#else
+ /*
+ * Create a socket pair.
+ */
+ int aiPair[2] = { -1, -1 };
+ if (socketpair(AF_LOCAL, SOCK_STREAM, 0, aiPair) == 0)
+ {
+ pWorker->fdSocket = aiPair[1];
+
+ rc = -1;
+ }
+ else
+ rc = err(pCtx, -1, "socketpair");
+#endif
+ }
+ else
+ rc = errx(pCtx, -1, "KBUILD_BIN_PATH is too long");
+ return rc;
+}
+
+
+/**
+ * Selects an idle worker or spawns a new one.
+ *
+ * @returns Pointer to the selected worker instance. NULL on error.
+ * @param pCtx The command execution context.
+ * @param pWorker The idle worker instance to respawn.
+ * On failure this will be freed!
+ * @param cBitsWorker The worker bitness - 64 or 32.
+ */
+static int kSubmitRespawnWorker(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, int cVerbosity)
+{
+ /*
+ * Clean up after the old worker.
+ */
+#ifdef KBUILD_OS_WINDOWS
+ DWORD rcWait;
+
+ /* Close the pipe handle first, breaking the pipe in case it's not already
+ busted up. Close the event semaphore too before waiting for the process. */
+ if (pWorker->hPipe != INVALID_HANDLE_VALUE)
+ {
+ if (!CloseHandle(pWorker->hPipe))
+ warnx(pCtx, "CloseHandle(pWorker->hPipe): %u", GetLastError());
+ pWorker->hPipe = INVALID_HANDLE_VALUE;
+ }
+
+ if (!CloseHandle(pWorker->OverlappedRead.hEvent))
+ warnx(pCtx, "CloseHandle(pWorker->OverlappedRead.hEvent): %u", GetLastError());
+ pWorker->OverlappedRead.hEvent = INVALID_HANDLE_VALUE;
+
+ if (pWorker->pStdOut)
+ MkWinChildcareWorkerDrainPipes(NULL, pWorker->pStdOut, pWorker->pStdErr);
+
+ /* It's probably shutdown already, if not give it 10 milliseconds before
+ we terminate it forcefully. */
+ rcWait = WaitForSingleObject(pWorker->hProcess, 10);
+ if (rcWait != WAIT_OBJECT_0)
+ {
+ BOOL fRc = TerminateProcess(pWorker->hProcess, 127);
+
+ if (pWorker->pStdOut)
+ MkWinChildcareWorkerDrainPipes(NULL, pWorker->pStdOut, pWorker->pStdErr);
+
+ rcWait = WaitForSingleObject(pWorker->hProcess, 100);
+ if (rcWait != WAIT_OBJECT_0)
+ warnx(pCtx, "WaitForSingleObject returns %u (and TerminateProcess %d)", rcWait, fRc);
+ }
+
+ if (pWorker->pStdOut)
+ MkWinChildcareWorkerDrainPipes(NULL, pWorker->pStdOut, pWorker->pStdErr);
+
+ if (!CloseHandle(pWorker->hProcess))
+ warnx(pCtx, "CloseHandle(pWorker->hProcess): %u", GetLastError());
+ pWorker->hProcess = INVALID_HANDLE_VALUE;
+
+#else
+ pid_t pidWait;
+ int rc;
+
+ if (pWorker->fdSocket != -1)
+ {
+ if (close(pWorker->fdSocket) != 0)
+ warn(pCtx, "close(pWorker->fdSocket)");
+ pWorker->fdSocket = -1;
+ }
+
+ kill(pWorker->pid, SIGTERM);
+ pidWait = waitpid(pWorker->pid, &rc, 0);
+ if (pidWait != pWorker->pid)
+ warn(pCtx, "waitpid(pWorker->pid,,0)");
+#endif
+
+ /*
+ * Unlink it from the hash table.
+ */
+ kSubmitPidHashRemove(pWorker);
+
+ /*
+ * Respawn it.
+ */
+ if (kSubmitSpawnWorker(pCtx, pWorker, cVerbosity) == 0)
+ {
+ /*
+ * Insert it into the process ID hash table and idle list.
+ */
+ size_t idxHash = KWORKER_PID_HASH(pWorker->pid);
+ pWorker->pNextPidHash = g_apPidHash[idxHash];
+ g_apPidHash[idxHash] = pWorker;
+ return 0;
+ }
+
+ kSubmitListUnlink(&g_IdleList, pWorker);
+ free(pWorker);
+ return -1;
+}
+
+
+/**
+ * Selects an idle worker or spawns a new one.
+ *
+ * @returns Pointer to the selected worker instance. NULL on error.
+ * @param cBitsWorker The worker bitness - 64 or 32.
+ */
+static PWORKERINSTANCE kSubmitSelectWorkSpawnNewIfNecessary(PKMKBUILTINCTX pCtx, unsigned cBitsWorker, int cVerbosity)
+{
+ /*
+ * Lookup up an idle worker.
+ */
+ PWORKERINSTANCE pWorker = g_IdleList.pHead;
+ while (pWorker)
+ {
+ if (pWorker->cBits == cBitsWorker)
+ return pWorker;
+ pWorker = pWorker->pNext;
+ }
+
+ /*
+ * Create a new worker instance.
+ */
+ pWorker = (PWORKERINSTANCE)xcalloc(sizeof(*pWorker));
+ pWorker->cBits = cBitsWorker;
+#if defined(CONFIG_NEW_WIN_CHILDREN) && defined(KBUILD_OS_WINDOWS)
+ if (output_sync != OUTPUT_SYNC_NONE)
+ {
+ pWorker->pStdOut = MkWinChildcareCreateWorkerPipe(1, g_uWorkerSeqNo << 1);
+ pWorker->pStdErr = MkWinChildcareCreateWorkerPipe(2, g_uWorkerSeqNo << 1);
+ }
+ if ( output_sync == OUTPUT_SYNC_NONE
+ || ( pWorker->pStdOut != NULL
+ && pWorker->pStdErr != NULL))
+#endif
+ {
+ if (kSubmitSpawnWorker(pCtx, pWorker, cVerbosity) == 0)
+ {
+ /*
+ * Insert it into the process ID hash table and idle list.
+ */
+ size_t idxHash = KWORKER_PID_HASH(pWorker->pid);
+ pWorker->pNextPidHash = g_apPidHash[idxHash];
+ g_apPidHash[idxHash] = pWorker;
+
+ kSubmitListAppend(&g_IdleList, pWorker);
+ return pWorker;
+ }
+ }
+#if defined(CONFIG_NEW_WIN_CHILDREN) && defined(KBUILD_OS_WINDOWS)
+ if (pWorker->pStdErr)
+ MkWinChildcareDeleteWorkerPipe(pWorker->pStdErr);
+ if (pWorker->pStdOut)
+ MkWinChildcareDeleteWorkerPipe(pWorker->pStdOut);
+#endif
+
+ free(pWorker);
+ return NULL;
+}
+
+
+/**
+ * Composes a JOB mesage for a worker.
+ *
+ * @returns Pointer to the message.
+ * @param pszExecutable The executable to run.
+ * @param papszArgs The argument vector.
+ * @param papszEnvVars The environment vector.
+ * @param pszCwd The current directory.
+ * @param fWatcomBrainDamage The wcc/wcc386 workaround.
+ * @param fNoPchCaching Whether to disable precompiled header caching.
+ * @param pszSpecialEnv Environment variable (name=value) subject to
+ * special expansion in kWorker. NULL if none.
+ * @param papszPostCmdArgs The post command and it's arguments.
+ * @param cPostCmdArgs Number of post command argument, including the
+ * command. Zero if no post command scheduled.
+ * @param pcbMsg Where to return the message length.
+ */
+static void *kSubmitComposeJobMessage(const char *pszExecutable, char **papszArgs, char **papszEnvVars,
+ const char *pszCwd, int fWatcomBrainDamage, int fNoPchCaching, const char *pszSpecialEnv,
+ char **papszPostCmdArgs, uint32_t cPostCmdArgs, uint32_t *pcbMsg)
+{
+ size_t cbTmp;
+ size_t cbSpecialEnv;
+ uint32_t i;
+ uint32_t cbMsg;
+ uint32_t cArgs;
+ uint32_t cEnvVars;
+ uint8_t *pbMsg;
+ uint8_t *pbCursor;
+
+ /*
+ * Adjust input.
+ */
+ if (!pszExecutable)
+ pszExecutable = papszArgs[0];
+
+ /*
+ * Calculate the message length first.
+ */
+ cbMsg = sizeof(cbMsg);
+ cbMsg += sizeof("JOB");
+ cbMsg += strlen(pszExecutable) + 1;
+ cbMsg += strlen(pszCwd) + 1;
+
+ cbMsg += sizeof(cArgs);
+ for (i = 0; papszArgs[i] != NULL; i++)
+ cbMsg += 1 + strlen(papszArgs[i]) + 1;
+ cArgs = i;
+
+ cbMsg += sizeof(cArgs);
+ for (i = 0; papszEnvVars[i] != NULL; i++)
+ cbMsg += strlen(papszEnvVars[i]) + 1;
+ cEnvVars = i;
+
+ cbMsg += 1; /* fWatcomBrainDamage */
+ cbMsg += 1; /* fNoPchCaching */
+
+ cbSpecialEnv = pszSpecialEnv ? strchr(pszSpecialEnv, '=') - pszSpecialEnv : 0;
+ cbMsg += cbSpecialEnv + 1;
+
+ cbMsg += sizeof(cPostCmdArgs);
+ for (i = 0; i < cPostCmdArgs; i++)
+ cbMsg += strlen(papszPostCmdArgs[i]) + 1;
+
+ /*
+ * Compose the message.
+ */
+ pbMsg = pbCursor = xmalloc(cbMsg);
+
+ /* header */
+ memcpy(pbCursor, &cbMsg, sizeof(cbMsg));
+ pbCursor += sizeof(cbMsg);
+ memcpy(pbCursor, "JOB", sizeof("JOB"));
+ pbCursor += sizeof("JOB");
+
+ /* executable. */
+ cbTmp = strlen(pszExecutable) + 1;
+ memcpy(pbCursor, pszExecutable, cbTmp);
+ pbCursor += cbTmp;
+
+ /* cwd */
+ cbTmp = strlen(pszCwd) + 1;
+ memcpy(pbCursor, pszCwd, cbTmp);
+ pbCursor += cbTmp;
+
+ /* arguments */
+ memcpy(pbCursor, &cArgs, sizeof(cArgs));
+ pbCursor += sizeof(cArgs);
+ for (i = 0; papszArgs[i] != NULL; i++)
+ {
+ *pbCursor++ = 0; /* Argument expansion flags (MSC, EMX). */
+ cbTmp = strlen(papszArgs[i]) + 1;
+ memcpy(pbCursor, papszArgs[i], cbTmp);
+ pbCursor += cbTmp;
+ }
+ assert(i == cArgs);
+
+ /* environment */
+ memcpy(pbCursor, &cEnvVars, sizeof(cEnvVars));
+ pbCursor += sizeof(cEnvVars);
+ for (i = 0; papszEnvVars[i] != NULL; i++)
+ {
+ cbTmp = strlen(papszEnvVars[i]) + 1;
+ memcpy(pbCursor, papszEnvVars[i], cbTmp);
+ pbCursor += cbTmp;
+ }
+ assert(i == cEnvVars);
+
+ /* flags */
+ *pbCursor++ = fWatcomBrainDamage != 0;
+ *pbCursor++ = fNoPchCaching != 0;
+
+ /* Special environment variable name. */
+ memcpy(pbCursor, pszSpecialEnv, cbSpecialEnv);
+ pbCursor += cbSpecialEnv;
+ *pbCursor++ = '\0';
+
+ /* post command */
+ memcpy(pbCursor, &cPostCmdArgs, sizeof(cPostCmdArgs));
+ pbCursor += sizeof(cPostCmdArgs);
+ for (i = 0; i < cPostCmdArgs; i++)
+ {
+ cbTmp = strlen(papszPostCmdArgs[i]) + 1;
+ memcpy(pbCursor, papszPostCmdArgs[i], cbTmp);
+ pbCursor += cbTmp;
+ }
+ assert(i == cPostCmdArgs);
+
+ assert(pbCursor - pbMsg == (size_t)cbMsg);
+
+ /*
+ * Done.
+ */
+ *pcbMsg = cbMsg;
+ return pbMsg;
+}
+
+
+/**
+ * Sends the job message to the given worker, respawning the worker if
+ * necessary.
+ *
+ * @returns 0 on success, non-zero on failure.
+ *
+ * @param pCtx The command execution context.
+ * @param pWorker The work to send the request to. The worker is
+ * on the idle list.
+ * @param pvMsg The message to send.
+ * @param cbMsg The size of the message.
+ * @param fNoRespawning Set if
+ * @param cVerbosity The verbosity level.
+ */
+static int kSubmitSendJobMessage(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, void const *pvMsg, uint32_t cbMsg,
+ int fNoRespawning, int cVerbosity)
+{
+ int cRetries;
+
+ /*
+ * Respawn the worker if it stopped by itself and we closed the pipe already.
+ */
+#ifdef KBUILD_OS_WINDOWS
+ if (pWorker->hPipe == INVALID_HANDLE_VALUE)
+#else
+ if (pWorker->fdSocket == -1)
+#endif
+ {
+ if (!fNoRespawning)
+ {
+ if (cVerbosity > 0)
+ warnx(pCtx, "Respawning worker (#1)...\n");
+ if (kSubmitRespawnWorker(pCtx, pWorker, cVerbosity) != 0)
+ return 2;
+ }
+
+ }
+
+ /*
+ * Restart-on-broken-pipe loop. Necessary?
+ */
+ for (cRetries = !fNoRespawning ? 1 : 0; ; cRetries--)
+ {
+ /*
+ * Try write the message.
+ */
+ uint32_t cbLeft = cbMsg;
+ uint8_t const *pbLeft = (uint8_t const *)pvMsg;
+#ifdef KBUILD_OS_WINDOWS
+ DWORD dwErr;
+ DWORD cbWritten;
+ while (WriteFile(pWorker->hPipe, pbLeft, cbLeft, &cbWritten, NULL /*pOverlapped*/))
+ {
+ assert(cbWritten <= cbLeft);
+ cbLeft -= cbWritten;
+ if (!cbLeft)
+ return 0;
+
+ /* This scenario shouldn't really ever happen. But just in case... */
+ pbLeft += cbWritten;
+ }
+ dwErr = GetLastError();
+ if ( ( dwErr != ERROR_BROKEN_PIPE
+ && dwErr != ERROR_NO_DATA)
+ || cRetries <= 0)
+ return errx(pCtx, 1, "Error writing to worker: %u", dwErr);
+#else
+ ssize_t cbWritten
+ while ((cbWritten = write(pWorker->fdSocket, pbLeft, cbLeft)) >= 0)
+ {
+ assert(cbWritten <= cbLeft);
+ cbLeft -= cbWritten;
+ if (!cbLeft)
+ return 0;
+
+ pbLeft += cbWritten;
+ }
+ if ( ( errno != EPIPE
+ && errno != ENOTCONN
+ && errno != ECONNRESET))
+ || cRetries <= 0)
+ return err(pCtx, 1, "Error writing to worker");
+# error "later"
+#endif
+
+ /*
+ * Broken connection. Try respawn the worker.
+ */
+ if (cVerbosity > 0)
+ warnx(pCtx, "Respawning worker (#2)...\n");
+ if (kSubmitRespawnWorker(pCtx, pWorker, cVerbosity) != 0)
+ return 2;
+ }
+}
+
+
+/**
+ * Closes the connection on a worker that said it is going to exit now.
+ *
+ * This is a way of dealing with imperfect resource management in the worker, it
+ * will monitor it a little and trigger a respawn when it looks bad.
+ *
+ * This function just closes the pipe / socket connection to the worker. The
+ * kSubmitSendJobMessage function will see this a trigger a respawn the next
+ * time the worker is engaged. This will usually mean there's a little delay in
+ * which the process can terminate without us having to actively wait for it.
+ *
+ * @param pCtx The command execution context.
+ * @param pWorker The worker instance.
+ */
+static void kSubmitCloseConnectOnExitingWorker(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker)
+{
+#ifdef KBUILD_OS_WINDOWS
+ if (!CloseHandle(pWorker->hPipe))
+ warnx(pCtx, "CloseHandle(pWorker->hPipe): %u", GetLastError());
+ pWorker->hPipe = INVALID_HANDLE_VALUE;
+#else
+ if (close(pWorker->fdSocket) != 0)
+ warn(pCtx, "close(pWorker->fdSocket)");
+ pWorker->fdSocket = -1;
+#endif
+}
+
+
+#ifdef KBUILD_OS_WINDOWS
+
+/**
+ * Handles read failure.
+ *
+ * @returns Exit code.
+ * @param pCtx The command execution context.
+ * @param pWorker The worker instance.
+ * @param dwErr The error code.
+ * @param pszWhere Where it failed.
+ */
+static int kSubmitWinReadFailed(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, DWORD dwErr, const char *pszWhere)
+{
+ DWORD dwExitCode;
+
+ if (pWorker->cbResultRead == 0)
+ errx(pCtx, 1, "%s/ReadFile failed: %u", pszWhere, dwErr);
+ else
+ errx(pCtx, 1, "%s/ReadFile failed: %u (read %u bytes)", pszWhere, dwErr, pWorker->cbResultRead);
+ assert(dwErr != 0);
+
+ /* Complete the result. */
+ pWorker->Result.s.rcExit = 127;
+ pWorker->Result.s.bWorkerExiting = 1;
+ pWorker->cbResultRead = sizeof(pWorker->Result);
+
+ if (GetExitCodeProcess(pWorker->hProcess, &dwExitCode))
+ {
+ if (dwExitCode != 0)
+ pWorker->Result.s.rcExit = dwExitCode;
+ }
+
+ return dwErr != 0 ? (int)(dwErr & 0x7fffffff) : 0x7fffffff;
+
+}
+
+
+/**
+ * Used by
+ * @returns 0 if we got the whole result, -1 if I/O is pending, and windows last
+ * error on ReadFile failure.
+ * @param pCtx The command execution context.
+ * @param pWorker The worker instance.
+ */
+static int kSubmitReadMoreResultWin(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, const char *pszWhere)
+{
+ /*
+ * Set up the result read, telling the sub_proc.c unit about it.
+ */
+ while (pWorker->cbResultRead < sizeof(pWorker->Result))
+ {
+ DWORD cbRead = 0;
+
+ BOOL fRc = ResetEvent(pWorker->OverlappedRead.hEvent);
+ assert(fRc); (void)fRc;
+
+ pWorker->OverlappedRead.Offset = 0;
+ pWorker->OverlappedRead.OffsetHigh = 0;
+
+ if (!ReadFile(pWorker->hPipe, &pWorker->Result.ab[pWorker->cbResultRead],
+ sizeof(pWorker->Result) - pWorker->cbResultRead,
+ &cbRead,
+ &pWorker->OverlappedRead))
+ {
+ DWORD dwErr = GetLastError();
+ if (dwErr == ERROR_IO_PENDING)
+ return -1;
+ return kSubmitWinReadFailed(pCtx, pWorker, dwErr, pszWhere);
+ }
+
+ pWorker->cbResultRead += cbRead;
+ assert(pWorker->cbResultRead <= sizeof(pWorker->Result));
+ }
+ return 0;
+}
+
+#endif /* KBUILD_OS_WINDOWS */
+
+
+/**
+ * Adds the given message to the history.
+ *
+ * @returns Pointer to old message, or NULL if no old msg to free.
+ * @param pWorker The worker instance.
+ * @param pvMsg The message.
+ * @param cbMsg The message size.
+ */
+static void *kSubmitUpdateHistory(PWORKERINSTANCE pWorker, void *pvMsg, size_t cbMsg)
+{
+ unsigned iHistory = pWorker->iHistory % K_ELEMENTS(pWorker->aHistory);
+ void *pvRet;
+ pWorker->iHistory++;
+ pvRet = pWorker->aHistory[iHistory].pvMsg;
+ pWorker->aHistory[iHistory].pvMsg = pvMsg;
+ pWorker->aHistory[iHistory].cbMsg = cbMsg;
+ return pvRet;
+}
+
+typedef struct HISTORYDUMPBUF
+{
+ char *pszBuf;
+ size_t cbBuf;
+ size_t off;
+ PKMKBUILTINCTX pCtx;
+} HISTORYDUMPBUF;
+
+
+static void kSubmitDumpHistoryWrite(HISTORYDUMPBUF *pBuf, const char *pch, size_t cch)
+{
+ if (pBuf->off + cch >= pBuf->cbBuf)
+ {
+ size_t cbNew = pBuf->cbBuf ? pBuf->cbBuf * 2 : 65536;
+ while (pBuf->off + cch >= cbNew)
+ cbNew *= 2;
+ pBuf->pszBuf = (char *)xrealloc(pBuf->pszBuf, cbNew);
+ pBuf->cbBuf = cbNew;
+ }
+
+ memcpy(&pBuf->pszBuf[pBuf->off], pch, cch);
+ pBuf->off += cch;
+ pBuf->pszBuf[pBuf->off] = '\0';
+}
+
+static void kSubmitDumpHistoryPrintf(HISTORYDUMPBUF *pBuf, const char *pszFormat, ...)
+{
+ char szTmp[32];
+ va_list va;
+ va_start(va, pszFormat);
+ for (;;)
+ {
+ const char *pszPct = strchr(pszFormat, '%');
+ if (!pszPct)
+ {
+ kSubmitDumpHistoryWrite(pBuf, pszFormat, strlen(pszFormat));
+ return;
+ }
+ if (pszPct != pszFormat)
+ {
+ kSubmitDumpHistoryWrite(pBuf, pszFormat, pszPct - pszFormat);
+ pszFormat = pszPct;
+ }
+ pszFormat++;
+ switch (*pszFormat++)
+ {
+ case 's':
+ {
+ const char * const psz = va_arg(va, const char *);
+ size_t const cch = strlen(psz);
+ if (cch == 0 || memchr(psz, '\'', cch))
+ {
+ kSubmitDumpHistoryWrite(pBuf, TUPLE("\"")); /** @todo what if there are '"' in the string? */
+ kSubmitDumpHistoryWrite(pBuf, psz, cch);
+ kSubmitDumpHistoryWrite(pBuf, TUPLE("\""));
+ }
+ else if ( !memchr(psz, ' ', cch)
+ && !memchr(psz, '\\', cch)
+ && !memchr(psz, '\t', cch)
+ && !memchr(psz, '\n', cch)
+ && !memchr(psz, '\r', cch)
+ && !memchr(psz, '&', cch)
+ && !memchr(psz, ';', cch)
+ && !memchr(psz, '|', cch))
+ kSubmitDumpHistoryWrite(pBuf, psz, cch);
+ else
+ {
+ kSubmitDumpHistoryWrite(pBuf, TUPLE("'"));
+ kSubmitDumpHistoryWrite(pBuf, psz, strlen(psz));
+ kSubmitDumpHistoryWrite(pBuf, TUPLE("'"));
+ }
+ break;
+ }
+
+ case 'd':
+ {
+ int iValue = va_arg(va, int);
+ kSubmitDumpHistoryWrite(pBuf, szTmp, snprintf(szTmp, sizeof(szTmp), "%d", iValue));
+ break;
+ }
+
+ case 'u':
+ {
+ unsigned uValue = va_arg(va, unsigned);
+ kSubmitDumpHistoryWrite(pBuf, szTmp, snprintf(szTmp, sizeof(szTmp), "%u", uValue));
+ break;
+ }
+
+ case '%':
+ kSubmitDumpHistoryWrite(pBuf, "%s", 1);
+ break;
+
+ default:
+ assert(0);
+ }
+ }
+ va_end(va);
+}
+
+static void kSubmitDumpHistoryFlush(HISTORYDUMPBUF *pBuf)
+{
+ if (pBuf->off > 0)
+ output_write_text(pBuf->pCtx->pOut, 1, pBuf->pszBuf, pBuf->off);
+ pBuf->off = 0;
+}
+
+/**
+ * Dumps the history for this worker to stderr in the given context.
+ *
+ * @param pCtx The command execution context. (Typically not a
+ * real context.)
+ * @param pWorker The worker instance.
+ */
+static void kSubmitDumpHistory(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker)
+{
+ HISTORYDUMPBUF Buf = { NULL, 0, 0, pCtx };
+ int iHistory = pWorker->iHistory;
+ unsigned cDumped = 0;
+
+ while (cDumped < K_ELEMENTS(pWorker->aHistory) && iHistory > 0)
+ {
+ unsigned const idx = (unsigned)--iHistory % K_ELEMENTS(pWorker->aHistory);
+ const char *pszMsg = (const char *)pWorker->aHistory[idx].pvMsg;
+ ssize_t cbMsg = pWorker->aHistory[idx].cbMsg;
+ const char *pszExe;
+ const char *pszCwd;
+ uint32_t i;
+ uint32_t cArgs;
+ const char *pszArgs;
+ size_t cbArgs;
+ uint32_t cEnvVars;
+ const char *pszEnvVars;
+ size_t cbEnvVars;
+ const char *pszSpecialEnv;
+ char fNoPchCaching;
+ char fWatcomBrainDamage;
+ uint32_t cPostArgs;
+ const char *pszPostArgs;
+ size_t cbPostArgs;
+
+ cDumped++;
+ if (!pszMsg || !cbMsg)
+ break;
+
+#define SKIP_BYTES(a_cbSkip) do { pszMsg += (a_cbSkip); cbMsg -= (a_cbSkip); } while (0)
+#define SKIP_STR() do { size_t const cbToSkip = strlen(pszMsg) + 1; SKIP_BYTES(cbToSkip); } while (0)
+#define SKIP_STRING_ARRAY(a_cStrings, a_cbPreable) do { \
+ for (i = 0; i < (a_cStrings) && cbMsg > 0; i++) { \
+ size_t const cbToSkip = (a_cbPreable) + strlen(pszMsg + (a_cbPreable)) + 1; \
+ SKIP_BYTES(cbToSkip); \
+ } } while (0)
+
+ /* Decode it: */
+ SKIP_BYTES(sizeof(uint32_t) + sizeof("JOB"));
+ pszExe = pszMsg;
+ SKIP_STR();
+ pszCwd = pszMsg;
+ SKIP_STR();
+
+ cArgs = *(uint32_t *)pszMsg;
+ SKIP_BYTES(sizeof(uint32_t));
+ pszArgs = pszMsg;
+ SKIP_STRING_ARRAY(cArgs, 1 /*fbFlags*/);
+ cbArgs = pszMsg - pszArgs;
+
+ cEnvVars = *(uint32_t *)pszMsg;
+ SKIP_BYTES(sizeof(uint32_t));
+ pszEnvVars = pszMsg;
+ SKIP_STRING_ARRAY(cEnvVars, 0);
+ cbEnvVars = pszMsg - pszEnvVars;
+
+ fWatcomBrainDamage = pszMsg[0] != '\0';
+ fNoPchCaching = pszMsg[1] != '\0';
+ SKIP_BYTES(2);
+
+ pszSpecialEnv = pszMsg;
+ SKIP_STR();
+
+ cPostArgs = *(uint32_t *)pszMsg;
+ SKIP_BYTES(sizeof(uint32_t));
+ pszPostArgs = pszMsg;
+ SKIP_STRING_ARRAY(cPostArgs, 0);
+ cbPostArgs = pszMsg - pszPostArgs;
+
+ /* Produce parseable output: */
+ kSubmitDumpHistoryPrintf(&Buf, "kWorker %u/%u:\n\tkSubmit", (long)pWorker->pid, iHistory, pszExe);
+ if (fNoPchCaching)
+ kSubmitDumpHistoryWrite(&Buf, TUPLE(" --no-pch-caching"));
+ if (fWatcomBrainDamage)
+ kSubmitDumpHistoryWrite(&Buf, TUPLE(" --watcom-brain-damage"));
+ if (pszSpecialEnv)
+ kSubmitDumpHistoryPrintf(&Buf, " --special-env %s", pszSpecialEnv);
+ kSubmitDumpHistoryPrintf(&Buf, " --chdir %s \\\n", pszCwd);
+
+ pszMsg = pszEnvVars;
+ cbMsg = cbEnvVars;
+ for (i = 0; i < cEnvVars && cbMsg > 0; i++)
+ {
+ kSubmitDumpHistoryPrintf(&Buf, "\t--putenv %s \\\n", pszMsg);
+ SKIP_STR();
+ }
+
+ if (cPostArgs > 0)
+ {
+ kSubmitDumpHistoryWrite(&Buf, TUPLE("\t--post-cmd "));
+ pszMsg = pszPostArgs;
+ cbMsg = cbPostArgs;
+ for (i = 0; i < cPostArgs && cbMsg > 0; i++)
+ {
+ kSubmitDumpHistoryPrintf(&Buf, " %s", pszMsg);
+ SKIP_STR();
+ }
+ kSubmitDumpHistoryWrite(&Buf, TUPLE(" \\\n"));
+ }
+ kSubmitDumpHistoryWrite(&Buf, TUPLE("\t-- \\\n"));
+
+ pszMsg = pszArgs;
+ cbMsg = cbArgs;
+ for (i = 0; i < cArgs && cbMsg > 0; i++)
+ {
+ SKIP_BYTES(1);
+ kSubmitDumpHistoryPrintf(&Buf, i + 1 < cArgs ? "\t%s \\\n" : "\t%s\n", pszMsg);
+ SKIP_STR();
+ }
+
+#undef SKIP_BYTES
+#undef SKIP_STR
+#undef SKIP_STRING_ARRAY
+ }
+
+ kSubmitDumpHistoryFlush(&Buf);
+ free(Buf.pszBuf);
+}
+
+
+/**
+ * Marks the worker active.
+ *
+ * On windows this involves setting up the async result read and telling
+ * sub_proc.c about the process.
+ *
+ * @returns Exit code.
+ * @param pCtx The command execution context.
+ * @param pWorker The worker instance to mark as active.
+ * @param cVerbosity The verbosity level.
+ * @param pChild The kmk child to associate the job with.
+ * @param pPidSpawned If @a *pPidSpawned is non-zero if the child is
+ * running, otherwise the worker is already done
+ * and we've returned the exit code of the job.
+ */
+static int kSubmitMarkActive(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, int cVerbosity, struct child *pChild, pid_t *pPidSpawned)
+{
+#ifdef KBUILD_OS_WINDOWS
+ int rc;
+#endif
+
+ pWorker->cbResultRead = 0;
+
+#ifdef KBUILD_OS_WINDOWS
+ /*
+ * Setup the async result read on windows. If we're slow and the worker
+ * very fast, this may actually get the result immediately.
+ */
+l_again:
+ rc = kSubmitReadMoreResultWin(pCtx, pWorker, "kSubmitMarkActive");
+ if (rc == -1)
+ {
+# ifndef CONFIG_NEW_WIN_CHILDREN
+ if (process_kmk_register_submit(pWorker->OverlappedRead.hEvent, (intptr_t)pWorker, pPidSpawned) == 0)
+ { /* likely */ }
+ else
+ {
+ /* We need to do the waiting here because sub_proc.c has too much to do. */
+ warnx(pCtx, "Too many processes for sub_proc.c to handle!");
+ WaitForSingleObject(pWorker->OverlappedRead.hEvent, INFINITE);
+ goto l_again;
+ }
+# else
+ if (MkWinChildCreateSubmit((intptr_t)pWorker->OverlappedRead.hEvent, pWorker,
+ pWorker->pStdOut, pWorker->pStdErr, pChild, pPidSpawned) == 0)
+ { /* likely */ }
+ else
+ {
+ /* We need to do the waiting here because sub_proc.c has too much to do. */
+ warnx(pCtx, "MkWinChildCreateSubmit failed!");
+ WaitForSingleObject(pWorker->OverlappedRead.hEvent, INFINITE);
+ goto l_again;
+ }
+# endif
+ }
+ else
+ {
+ assert(rc == 0 || pWorker->Result.s.rcExit != 0);
+ if (pWorker->Result.s.bWorkerExiting)
+ kSubmitCloseConnectOnExitingWorker(pCtx, pWorker);
+ if (pWorker->Result.s.rcExit && 1)
+ kSubmitDumpHistory(pCtx, pWorker);
+ *pPidSpawned = 0;
+ return pWorker->Result.s.rcExit;
+ }
+#endif
+
+ /*
+ * Mark it busy and move it to the active instance.
+ */
+ pWorker->pBusyWith = pChild;
+#ifndef KBUILD_OS_WINDOWS
+ *pPidSpawned = pWorker->pid;
+#endif
+
+ kSubmitListUnlink(&g_IdleList, pWorker);
+ kSubmitListAppend(&g_BusyList, pWorker);
+ return 0;
+}
+
+
+#ifdef KBUILD_OS_WINDOWS
+
+/**
+ * Retrieve the worker child result.
+ *
+ * If incomplete, we restart the ReadFile operation like kSubmitMarkActive does.
+ *
+ * @returns 0 on success, -1 if ReadFile was restarted.
+ * @param pvUser The worker instance.
+ * @param fBlock if we're to block waiting for the result or not.
+ * @param prcExit Where to return the exit code.
+ * @param piSigNo Where to return the signal number.
+ */
+int kSubmitSubProcGetResult(intptr_t pvUser, int fBlock, int *prcExit, int *piSigNo)
+{
+ PWORKERINSTANCE pWorker = (PWORKERINSTANCE)pvUser;
+ KMKBUILTINCTX FakeCtx = { "kSubmit/GetResult", NULL };
+ PKMKBUILTINCTX pCtx = &FakeCtx;
+
+ /*
+ * Get the overlapped result. There should be one since we're here
+ * because of a satisfied WaitForMultipleObject.
+ */
+ DWORD cbRead = 0;
+ if (GetOverlappedResult(pWorker->hPipe, &pWorker->OverlappedRead, &cbRead, fBlock ? TRUE : FALSE))
+ {
+ pWorker->cbResultRead += cbRead;
+ assert(pWorker->cbResultRead <= sizeof(pWorker->Result));
+
+ /* More to be read? */
+ while (pWorker->cbResultRead < sizeof(pWorker->Result))
+ {
+ int rc = kSubmitReadMoreResultWin(pCtx, pWorker, "kSubmitSubProcGetResult/more");
+ if (rc == -1)
+ return -1;
+ assert(rc == 0 || pWorker->Result.s.rcExit != 0);
+ }
+ assert(pWorker->cbResultRead == sizeof(pWorker->Result));
+ }
+ else
+ {
+ DWORD dwErr = GetLastError();
+ if (dwErr == ERROR_IO_INCOMPLETE && !fBlock)
+ return -1;
+ kSubmitWinReadFailed(pCtx, pWorker, dwErr, "kSubmitSubProcGetResult/result");
+ }
+
+ /*
+ * Okay, we've got a result.
+ */
+ *prcExit = pWorker->Result.s.rcExit;
+ switch (pWorker->Result.s.rcExit)
+ {
+ default: *piSigNo = 0; break;
+ case CONTROL_C_EXIT: *piSigNo = SIGINT; break;
+ case STATUS_INTEGER_DIVIDE_BY_ZERO: *piSigNo = SIGFPE; break;
+ case STATUS_ACCESS_VIOLATION: *piSigNo = SIGSEGV; break;
+ case STATUS_PRIVILEGED_INSTRUCTION:
+ case STATUS_ILLEGAL_INSTRUCTION: *piSigNo = SIGILL; break;
+ }
+ if (pWorker->Result.s.rcExit && pWorker->fDebugDumpHistoryOnFailure)
+ kSubmitDumpHistory(pCtx, pWorker);
+ if (pWorker->Result.s.bWorkerExiting)
+ kSubmitCloseConnectOnExitingWorker(pCtx, pWorker);
+
+ return 0;
+}
+
+
+int kSubmitSubProcKill(intptr_t pvUser, int iSignal)
+{
+ return -1;
+}
+
+
+/**
+ * Called by process_cleanup when it's done with the worker.
+ *
+ * @param pvUser The worker instance.
+ */
+void kSubmitSubProcCleanup(intptr_t pvUser)
+{
+ PWORKERINSTANCE pWorker = (PWORKERINSTANCE)pvUser;
+ kSubmitListUnlink(&g_BusyList, pWorker);
+ kSubmitListAppend(&g_IdleList, pWorker);
+}
+
+#endif /* KBUILD_OS_WINDOWS */
+
+
+/**
+ * atexit callback that trigger worker termination.
+ */
+static void kSubmitAtExitCallback(void)
+{
+ PWORKERINSTANCE pWorker;
+ DWORD msStartTick;
+ DWORD cKillRaids = 0;
+ KMKBUILTINCTX FakeCtx = { "kSubmit/atexit", NULL };
+ PKMKBUILTINCTX pCtx = &FakeCtx;
+
+ /*
+ * Tell all the workers to exit by breaking the connection.
+ */
+ for (pWorker = g_IdleList.pHead; pWorker != NULL; pWorker = pWorker->pNext)
+ kSubmitCloseConnectOnExitingWorker(pCtx, pWorker);
+ for (pWorker = g_BusyList.pHead; pWorker != NULL; pWorker = pWorker->pNext)
+ kSubmitCloseConnectOnExitingWorker(pCtx, pWorker);
+
+ /*
+ * Wait a little while for them to stop.
+ */
+ Sleep(0);
+ msStartTick = GetTickCount();
+ for (;;)
+ {
+ /*
+ * Collect handles of running processes.
+ */
+ PWORKERINSTANCE apWorkers[MAXIMUM_WAIT_OBJECTS];
+ HANDLE ahHandles[MAXIMUM_WAIT_OBJECTS];
+ DWORD cHandles = 0;
+
+ for (pWorker = g_IdleList.pHead; pWorker != NULL; pWorker = pWorker->pNext)
+ if (pWorker->hProcess != INVALID_HANDLE_VALUE)
+ {
+ if (cHandles < MAXIMUM_WAIT_OBJECTS)
+ {
+ apWorkers[cHandles] = pWorker;
+ ahHandles[cHandles] = pWorker->hProcess;
+ }
+ cHandles++;
+ }
+ for (pWorker = g_BusyList.pHead; pWorker != NULL; pWorker = pWorker->pNext)
+ if (pWorker->hProcess != INVALID_HANDLE_VALUE)
+ {
+ if (cHandles < MAXIMUM_WAIT_OBJECTS)
+ {
+ apWorkers[cHandles] = pWorker;
+ ahHandles[cHandles] = pWorker->hProcess;
+ }
+ cHandles++;
+ }
+ if (cHandles == 0)
+ return;
+
+ /*
+ * Wait for the processes.
+ */
+ for (;;)
+ {
+ DWORD cMsElapsed = GetTickCount() - msStartTick;
+ DWORD dwWait = WaitForMultipleObjects(cHandles <= MAXIMUM_WAIT_OBJECTS ? cHandles : MAXIMUM_WAIT_OBJECTS,
+ ahHandles, FALSE /*bWaitAll*/,
+ cMsElapsed < 5000 ? 5000 - cMsElapsed + 16 : 16);
+ if ( dwWait >= WAIT_OBJECT_0
+ && dwWait <= WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS)
+ {
+ size_t idx = dwWait - WAIT_OBJECT_0;
+ CloseHandle(apWorkers[idx]->hProcess);
+ apWorkers[idx]->hProcess = INVALID_HANDLE_VALUE;
+
+ if (cHandles <= MAXIMUM_WAIT_OBJECTS)
+ {
+ /* Restart the wait with the worker removed, or quit if it was the last worker. */
+ cHandles--;
+ if (!cHandles)
+ return;
+ if (idx != cHandles)
+ {
+ apWorkers[idx] = apWorkers[cHandles];
+ ahHandles[idx] = ahHandles[cHandles];
+ }
+ continue;
+ }
+ /* else: Reconstruct the wait array so we get maximum coverage. */
+ }
+ else if (dwWait == WAIT_TIMEOUT)
+ {
+ /* Terminate the whole bunch. */
+ cKillRaids++;
+ if (cKillRaids == 1 && getenv("KMK_KSUBMIT_NO_KILL") == NULL)
+ {
+ warnx(pCtx, "Killing %u lingering worker processe(s)!\n", cHandles);
+ for (pWorker = g_IdleList.pHead; pWorker != NULL; pWorker = pWorker->pNext)
+ if (pWorker->hProcess != INVALID_HANDLE_VALUE)
+ TerminateProcess(pWorker->hProcess, WAIT_TIMEOUT);
+ for (pWorker = g_BusyList.pHead; pWorker != NULL; pWorker = pWorker->pNext)
+ if (pWorker->hProcess != INVALID_HANDLE_VALUE)
+ TerminateProcess(pWorker->hProcess, WAIT_TIMEOUT);
+ }
+ else
+ {
+ warnx(pCtx, "Giving up on the last %u worker processe(s). :-(\n", cHandles);
+ return;
+ }
+ }
+ else
+ {
+ /* Some kind of wait error. Could be a bad handle, check each and remove
+ bad ones as well as completed ones. */
+ size_t idx;
+ warnx(pCtx, "WaitForMultipleObjects unexpectedly returned %#u (err=%u)\n",
+ dwWait, GetLastError());
+ for (idx = 0; idx < cHandles; idx++)
+ {
+ dwWait = WaitForSingleObject(ahHandles[idx], 0 /*ms*/);
+ if (dwWait != WAIT_TIMEOUT)
+ {
+ CloseHandle(apWorkers[idx]->hProcess);
+ apWorkers[idx]->hProcess = INVALID_HANDLE_VALUE;
+ }
+ }
+ }
+ break;
+ } /* wait loop */
+ } /* outer wait loop */
+}
+
+
+static int kmk_builtin_kSubmit_usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s [-Z|--zap-env] [-E|--set <var=val>] [-U|--unset <var=val>]\n"
+ " [-A|--append <var=val>] [-D|--prepend <var=val>]\n"
+ " [-s|--special-env <var=val>] [-C|--chdir <dir>]\n"
+ " [--wcc-brain-damage] [--no-pch-caching]\n"
+ " [-3|--32-bit] [-6|--64-bit] [-v] [--debug-dump-history]\n"
+ " [-P|--post-cmd <cmd> [args]] -- <program> [args]\n"
+ " or: %s --help\n"
+ " or: %s --version\n"
+ "\n"
+ "Options:\n"
+ " -Z, --zap-env, -i, --ignore-environment\n"
+ " Zaps the environment. Position dependent.\n"
+ " -E, --set <var>=[value]\n"
+ " Sets an environment variable putenv fashion. Position dependent.\n"
+ " -U, --unset <var>\n"
+ " Removes an environment variable. Position dependent.\n"
+ " -A, --append <var>=<value>\n"
+ " Appends the given value to the environment variable.\n"
+ " -D,--prepend <var>=<value>\n"
+ " Prepends the given value to the environment variable.\n"
+ " -s,--special-env <var>=<value>\n"
+ " Same as --set, but flags the variable for further expansion\n"
+ " within kWorker. Replacements:\n"
+ " @@PROCESSOR_GROUP@@ - The processor group number.\n"
+ " @@AUTHENTICATION_ID@@ - The authentication ID from the process token.\n"
+ " @@PID@@ - The kWorker process ID.\n"
+ " @@@@ - Escaped \"@@\".\n"
+ " @@DEBUG_COUNTER@@ - An ever increasing counter (starts at zero).\n"
+ " -C, --chdir <dir>\n"
+ " Specifies the current directory for the program. Relative paths\n"
+ " are relative to the previous -C option. Default is getcwd value.\n"
+ " -3, --32-bit\n"
+ " Selects a 32-bit kWorker process. Default: kmk bit count\n"
+ " -6, --64-bit\n"
+ " Selects a 64-bit kWorker process. Default: kmk bit count\n"
+ " --wcc-brain-damage\n"
+ " Works around wcc and wcc386 (Open Watcom) not following normal\n"
+ " quoting conventions on Windows, OS/2, and DOS.\n"
+ " --no-pch-caching\n"
+ " Do not cache precompiled header files because they're being created.\n"
+ " -v,--verbose\n"
+ " More verbose execution.\n"
+ " --debug-dump-history\n"
+ " Dump the history as of the submitted command. Handy for debugging\n"
+ " trouble caused by a previous job.\n"
+ " --debug-dump-history-on-failure, --no-debug-dump-history-on-failure\n"
+ " Dump the history on failure. Can also be enabled by a non-empty\n"
+ " KMK_KSUBMIT_DUMP_HISTORY_ON_FAILURE variable (first invocation only).\n"
+ " -P|--post-cmd <cmd> ...\n"
+ " For running a built-in command on the output, specifying the command\n"
+ " and all it's parameters. Currently supported commands:\n"
+ " kDepObj\n"
+ " -V,--version\n"
+ " Show the version number.\n"
+ " -h,--help\n"
+ " Show this usage information.\n"
+ "\n"
+ ,
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+ return 2;
+}
+
+
+int kmk_builtin_kSubmit(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx, struct child *pChild, pid_t *pPidSpawned)
+{
+#ifdef KBUILD_OS_WINDOWS
+ static int s_fInitialized = 0;
+#endif
+ int rcExit = 0;
+ int iArg;
+ unsigned cAllocatedEnvVars;
+ unsigned cEnvVars;
+ char **papszEnvVars;
+ const char *pszExecutable = NULL;
+ const char *pszSpecialEnv = NULL;
+ int iPostCmd = argc;
+ int cPostCmdArgs = 0;
+ unsigned cBitsWorker = g_cArchBits;
+ int fWatcomBrainDamage = 0;
+ int fNoPchCaching = 0;
+ int fDebugDumpHistory = 0;
+ static int s_fDebugDumpHistoryOnFailure = -1;
+ int fDebugDumpHistoryOnFailure = s_fDebugDumpHistoryOnFailure;
+ int cVerbosity = 0;
+ size_t const cbCwdBuf = GET_PATH_MAX;
+ PATH_VAR(szCwd);
+
+#ifdef KBUILD_OS_WINDOWS
+ /*
+ * First time thru we must perform some initializations.
+ */
+ if (s_fInitialized)
+ { }
+ else
+ {
+ MkWinChildInitCpuGroupAllocator(&g_SubmitProcessorGroupAllocator);
+# if K_ARCH_BITS == 64
+ MkWinChildInitCpuGroupAllocator(&g_SubmitProcessorGroupAllocator32);
+# endif
+ *(FARPROC *)&g_pfnSetThreadGroupAffinity = GetProcAddress(GetModuleHandleW(L"KERNEL32.DLL"), "SetThreadGroupAffinity");
+ s_fInitialized = 1;
+ }
+#endif
+ if (fDebugDumpHistoryOnFailure != -1)
+ { /* likely */ }
+ else
+ {
+ struct variable *pVar = lookup_variable(TUPLE("KMK_KSUBMIT_DUMP_HISTORY_ON_FAILURE"));
+ fDebugDumpHistoryOnFailure = pVar && *pVar->value != '\0';
+ s_fDebugDumpHistoryOnFailure = fDebugDumpHistoryOnFailure;
+ }
+
+ /*
+ * Create default program environment.
+ *
+ * Note! We only clean up the environment on successful return, assuming
+ * make will stop after that.
+ */
+ if (getcwd_fs(szCwd, cbCwdBuf) != NULL)
+ { /* likely */ }
+ else
+ return err(pCtx, 1, "getcwd_fs failed\n");
+
+ /* The environment starts out in read-only mode and will be duplicated if modified. */
+ cAllocatedEnvVars = 0;
+ papszEnvVars = envp;
+ cEnvVars = 0;
+ while (papszEnvVars[cEnvVars] != NULL)
+ cEnvVars++;
+
+ /*
+ * Parse the command line.
+ */
+ for (iArg = 1; iArg < argc; iArg++)
+ {
+ const char *pszArg = argv[iArg];
+ if (*pszArg == '-')
+ {
+ char chOpt = *++pszArg;
+ pszArg++;
+ if (chOpt != '-')
+ {
+ if (chOpt != '\0')
+ { /* likely */ }
+ else
+ {
+ errx(pCtx, 1, "Incomplete option: '-'");
+ return kmk_builtin_kSubmit_usage(pCtx, 1);
+ }
+ }
+ else
+ {
+ /* '--' indicates where the bits to execute start. */
+ if (*pszArg == '\0')
+ {
+ iArg++;
+ break;
+ }
+
+ if ( strcmp(pszArg, "wcc-brain-damage") == 0
+ || strcmp(pszArg, "watcom-brain-damage") == 0)
+ {
+ fWatcomBrainDamage = 1;
+ continue;
+ }
+
+ if (strcmp(pszArg, "no-pch-caching") == 0)
+ {
+ fNoPchCaching = 1;
+ continue;
+ }
+
+ if (strcmp(pszArg, "debug-dump-history") == 0)
+ {
+ fDebugDumpHistory = 1;
+ continue;
+ }
+
+ if (strcmp(pszArg, "debug-dump-history-on-failure") == 0)
+ {
+ fDebugDumpHistoryOnFailure = 1;
+ continue;
+ }
+
+ if (strcmp(pszArg, "no-debug-dump-history-on-failure") == 0)
+ {
+ fDebugDumpHistoryOnFailure = 0;
+ continue;
+ }
+
+ /* convert to short. */
+ if (strcmp(pszArg, "help") == 0)
+ chOpt = 'h';
+ else if (strcmp(pszArg, "version") == 0)
+ chOpt = 'V';
+ else if (strcmp(pszArg, "set") == 0)
+ chOpt = 'E';
+ else if (strcmp(pszArg, "append") == 0)
+ chOpt = 'A';
+ else if (strcmp(pszArg, "prepend") == 0)
+ chOpt = 'D';
+ else if (strcmp(pszArg, "unset") == 0)
+ chOpt = 'U';
+ else if ( strcmp(pszArg, "zap-env") == 0
+ || strcmp(pszArg, "ignore-environment") == 0 /* GNU env compatibility. */ )
+ chOpt = 'Z';
+ else if (strcmp(pszArg, "chdir") == 0)
+ chOpt = 'C';
+ else if (strcmp(pszArg, "set-special") == 0)
+ chOpt = 's';
+ else if (strcmp(pszArg, "post-cmd") == 0)
+ chOpt = 'P';
+ else if (strcmp(pszArg, "32-bit") == 0)
+ chOpt = '3';
+ else if (strcmp(pszArg, "64-bit") == 0)
+ chOpt = '6';
+ else if (strcmp(pszArg, "verbose") == 0)
+ chOpt = 'v';
+ else if (strcmp(pszArg, "executable") == 0)
+ chOpt = 'e';
+ else
+ {
+ errx(pCtx, 2, "Unknown option: '%s'", pszArg - 2);
+ return kmk_builtin_kSubmit_usage(pCtx, 1);
+ }
+ pszArg = "";
+ }
+
+ do
+ {
+ /* Get option value first, if the option takes one. */
+ const char *pszValue = NULL;
+ switch (chOpt)
+ {
+ case 'A':
+ case 'C':
+ case 'E':
+ case 'U':
+ case 'D':
+ case 'e':
+ case 's':
+ if (*pszArg != '\0')
+ pszValue = pszArg + (*pszArg == ':' || *pszArg == '=');
+ else if (++iArg < argc)
+ pszValue = argv[iArg];
+ else
+ {
+ errx(pCtx, 1, "Option -%c requires a value!", chOpt);
+ return kmk_builtin_kSubmit_usage(pCtx, 1);
+ }
+ break;
+ }
+
+ switch (chOpt)
+ {
+ case 'Z':
+ case 'i': /* GNU env compatibility. */
+ rcExit = kBuiltinOptEnvZap(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity);
+ if (rcExit == 0)
+ break;
+ return rcExit;
+
+ case 'E':
+ rcExit = kBuiltinOptEnvSet(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
+ if (rcExit == 0)
+ break;
+ return rcExit;
+
+ case 'A':
+ rcExit = kBuiltinOptEnvAppend(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
+ if (rcExit == 0)
+ break;
+ return rcExit;
+
+ case 'D':
+ rcExit = kBuiltinOptEnvPrepend(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
+ if (rcExit == 0)
+ break;
+ return rcExit;
+
+ case 'U':
+ rcExit = kBuiltinOptEnvUnset(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
+ if (rcExit == 0)
+ break;
+ return rcExit;
+
+ case 'C':
+ rcExit = kBuiltinOptChDir(pCtx, szCwd, cbCwdBuf, pszValue);
+ if (rcExit == 0)
+ break;
+ return rcExit;
+
+ case 's':
+ if (pszSpecialEnv)
+ return errx(pCtx, 1, "The -s option can only be used once!");
+ pszSpecialEnv = pszValue;
+ rcExit = kBuiltinOptEnvSet(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
+ if (rcExit == 0)
+ break;
+ return rcExit;
+
+ case 'P':
+ if (cPostCmdArgs > 0)
+ return errx(pCtx, 1, "The -P option can only be used once!");
+ if (*pszArg != '\0')
+ return errx(pCtx, 1, "The cmd part of the -P needs to be a separate argument!");
+ iPostCmd = ++iArg;
+ if (iArg >= argc)
+ return errx(pCtx, 1, "The -P option requires a command following it!");
+ while (iArg < argc && strcmp(argv[iArg], "--") != 0)
+ iArg++;
+ cPostCmdArgs = iArg - iPostCmd;
+ iArg--;
+ break;
+
+ case '3':
+ cBitsWorker = 32;
+ break;
+
+ case '6':
+ cBitsWorker = 64;
+ break;
+
+ case 'e':
+ pszExecutable = pszValue;
+ break;
+
+ case 'v':
+ cVerbosity++;
+ break;
+
+ case 'h':
+ kmk_builtin_kSubmit_usage(pCtx, 0);
+ kBuiltinOptEnvCleanup(&papszEnvVars, cEnvVars, &cAllocatedEnvVars);
+ return 0;
+
+ case 'V':
+ kBuiltinOptEnvCleanup(&papszEnvVars, cEnvVars, &cAllocatedEnvVars);
+ return kbuild_version(argv[0]);
+ }
+ } while ((chOpt = *pszArg++) != '\0');
+ }
+ else
+ {
+ errx(pCtx, 1, "Unknown argument: '%s'", pszArg);
+ return kmk_builtin_kSubmit_usage(pCtx, 1);
+ }
+ }
+
+ /*
+ * Check that we've got something to execute.
+ */
+ if (iArg < argc)
+ {
+ uint32_t cbMsg;
+ void *pvMsg = kSubmitComposeJobMessage(pszExecutable, &argv[iArg], papszEnvVars, szCwd,
+ fWatcomBrainDamage, fNoPchCaching, pszSpecialEnv,
+ &argv[iPostCmd], cPostCmdArgs, &cbMsg);
+ PWORKERINSTANCE pWorker = kSubmitSelectWorkSpawnNewIfNecessary(pCtx, cBitsWorker, cVerbosity);
+ if (pWorker)
+ {
+ /* Before we send off the job, we should dump pending output, since
+ the kWorker process currently does not coordinate its output with
+ the output.c mechanics. */
+#ifdef CONFIG_NEW_WIN_CHILDREN
+ if (pCtx->pOut && !pWorker->pStdOut)
+#else
+ if (pCtx->pOut)
+#endif
+ output_dump(pCtx->pOut);
+ pWorker->fDebugDumpHistoryOnFailure = fDebugDumpHistoryOnFailure;
+ rcExit = kSubmitSendJobMessage(pCtx, pWorker, pvMsg, cbMsg, 0 /*fNoRespawning*/, cVerbosity);
+ if (rcExit == 0)
+ {
+ pvMsg = kSubmitUpdateHistory(pWorker, pvMsg, cbMsg);
+ if (fDebugDumpHistory)
+ kSubmitDumpHistory(pCtx, pWorker);
+ rcExit = kSubmitMarkActive(pCtx, pWorker, cVerbosity, pChild, pPidSpawned);
+ }
+
+ if (!g_fAtExitRegistered)
+ if (atexit(kSubmitAtExitCallback) == 0)
+ g_fAtExitRegistered = 1;
+ }
+ else
+ rcExit = 1;
+ free(pvMsg);
+ }
+ else
+ {
+ errx(pCtx, 1, "Nothing to executed!");
+ rcExit = kmk_builtin_kSubmit_usage(pCtx, 1);
+ }
+
+ kBuiltinOptEnvCleanup(&papszEnvVars, cEnvVars, &cAllocatedEnvVars);
+ return rcExit;
+}
+
diff --git a/src/kmk/kmkbuiltin/kbuild_protection.c b/src/kmk/kmkbuiltin/kbuild_protection.c
new file mode 100644
index 0000000..373fd88
--- /dev/null
+++ b/src/kmk/kmkbuiltin/kbuild_protection.c
@@ -0,0 +1,376 @@
+/* $Id: kbuild_protection.c 3192 2018-03-26 20:25:56Z bird $ */
+/** @file
+ * Simple File Protection.
+ */
+
+/*
+ * Copyright (c) 2008-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/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "config.h"
+#include <sys/types.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#if defined(_MSC_VER) || defined(__OS2__)
+# include <limits.h>
+# include <direct.h>
+#else
+# include <unistd.h>
+#endif
+#include "kbuild_protection.h"
+#include "err.h"
+
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+#define KBUILD_PROTECTION_MAGIC 0x00111100
+
+#if defined(__EMX__) || defined(_MSC_VER)
+# define IS_SLASH(ch) ( (ch) == '/' || (ch) == '\\' )
+# define DEFAULT_PROTECTION_DEPTH 1
+#else
+# define IS_SLASH(ch) ( (ch) == '/' )
+# define DEFAULT_PROTECTION_DEPTH 2
+#endif
+
+
+
+/**
+ * Counts the components in the specified sub path.
+ * This is a helper for count_path_components.
+ *
+ * etc = 1
+ * etc/ = 1
+ * etc/x11 = 2
+ * and so and and so forth.
+ */
+static int countSubPathComponents(const char *pszPath, int cDepth)
+{
+ for (;;)
+ {
+ const char *pszEnd;
+ size_t cch;
+
+ /* skip slashes. */
+ while (IS_SLASH(*pszPath))
+ pszPath++;
+ if (!*pszPath)
+ break;
+
+ /* find end of component. */
+ pszEnd = pszPath;
+ while (!IS_SLASH(*pszEnd) && *pszEnd)
+ pszEnd++;
+
+ /* count it, checking for '..' and '.'. */
+ cch = pszEnd - pszPath;
+ if (cch == 2 && pszPath[0] == '.' && pszPath[1] == '.')
+ {
+ if (cDepth > 0)
+ cDepth--;
+ }
+ else if (cch != 1 || pszPath[0] != '.')
+ cDepth++;
+
+ /* advance */
+ if (!*pszEnd)
+ break;
+ pszPath = pszEnd + 1;
+ }
+ return cDepth;
+}
+
+
+/**
+ * Parses the specified path counting the number of components
+ * relative to root.
+ *
+ * We don't check symbolic links and such, just some simple and cheap
+ * path parsing.
+ *
+ * @param pszPath The path to process.
+ *
+ * @returns 0 or higher on success.
+ * On failure an error is printed, eval is set and -1 is returned.
+ */
+static int countPathComponents(PCKBUILDPROTECTION pThis, const char *pszPath)
+{
+ int cComponents = 0;
+
+ /*
+ * Deal with root, UNC, drive letter.
+ */
+#if defined(_MSC_VER) || defined(__OS2__)
+ if (IS_SLASH(pszPath[0]) && IS_SLASH(pszPath[1]) && !IS_SLASH(pszPath[2]))
+ {
+ /* skip the root - UNC */
+ pszPath += 3;
+ while (!IS_SLASH(*pszPath) && *pszPath) /* server name */
+ pszPath++;
+ while (IS_SLASH(*pszPath))
+ pszPath++;
+ while (!IS_SLASH(*pszPath) && *pszPath) /* share name */
+ pszPath++;
+ while (IS_SLASH(*pszPath))
+ pszPath++;
+ }
+ else
+ {
+ unsigned uDriveLetter = (unsigned)toupper(pszPath[0]) - (unsigned)'A';
+ if (uDriveLetter <= (unsigned)('Z' - 'A') && pszPath[1] == ':')
+ uDriveLetter++; /* A == 1 */
+ else
+ uDriveLetter = 0; /* 0 == default */
+
+ if (!IS_SLASH(pszPath[uDriveLetter ? 2 : 0]))
+ {
+ /*
+ * Relative path, must count cwd depth first.
+ */
+#ifdef __OS2__ /** @todo remove when ticket 194 has been fixed */
+ char *pszCwd = _getdcwd(uDriveLetter, NULL, PATH_MAX);
+#else
+ char *pszCwd = _getdcwd(uDriveLetter, NULL, 0);
+#endif
+ char *pszTmp = pszCwd;
+ if (!pszTmp)
+ {
+ err(pThis->pCtx, 1, "_getdcwd");
+ return -1;
+ }
+
+ if (IS_SLASH(pszTmp[0]) && IS_SLASH(pszTmp[1]))
+ {
+ /* skip the root - UNC */
+ pszTmp += 2;
+ while (!IS_SLASH(*pszTmp) && *pszTmp) /* server name */
+ pszTmp++;
+ while (IS_SLASH(*pszTmp))
+ pszTmp++;
+ while (!IS_SLASH(*pszTmp) && *pszTmp) /* share name */
+ pszTmp++;
+ }
+ else
+ {
+ /* skip the drive letter and while we're at it, the root slash too. */
+ pszTmp += 1 + (pszTmp[1] == ':');
+ }
+ cComponents = countSubPathComponents(pszTmp, 0);
+ free(pszCwd);
+ }
+ else
+ {
+ /* skip the drive letter and while we're at it, the root slash too. */
+ pszPath += uDriveLetter ? 3 : 1;
+ }
+ }
+#else /* !WIN && !OS2 */
+ if (!IS_SLASH(pszPath[0]))
+ {
+ /*
+ * Relative path, must count cwd depth first.
+ */
+ char szCwd[4096];
+ if (!getcwd(szCwd, sizeof(szCwd)))
+ {
+ err(pThis->pCtx, 1, "getcwd");
+ return -1;
+ }
+ cComponents = countSubPathComponents(szCwd, 0);
+ }
+#endif /* !WIN && !OS2 */
+
+ /*
+ * We're now past any UNC or drive letter crap, possibly positioned
+ * at the root slash or at the start of a path component at the
+ * given depth. Count the remainder.
+ */
+ return countSubPathComponents(pszPath, cComponents);
+}
+
+
+/**
+ * Initializes the instance data.
+ *
+ * @param pThis Pointer to the instance data.
+ */
+void kBuildProtectionInit(PKBUILDPROTECTION pThis, PKMKBUILTINCTX pCtx)
+{
+ pThis->uMagic = KBUILD_PROTECTION_MAGIC;
+ pThis->pCtx = pCtx;
+ pThis->afTypes[KBUILDPROTECTIONTYPE_FULL] = 0;
+ pThis->afTypes[KBUILDPROTECTIONTYPE_RECURSIVE] = 1;
+ pThis->cProtectionDepth = DEFAULT_PROTECTION_DEPTH;
+}
+
+
+/**
+ * Destroys the instance data.
+ *
+ * @param pThis Pointer to the instance data.
+ */
+void kBuildProtectionTerm(PKBUILDPROTECTION pThis)
+{
+ pThis->uMagic = 0;
+}
+
+
+void kBuildProtectionEnable(PKBUILDPROTECTION pThis, KBUILDPROTECTIONTYPE enmType)
+{
+ assert(pThis->uMagic == KBUILD_PROTECTION_MAGIC);
+ assert(enmType < KBUILDPROTECTIONTYPE_MAX && enmType >= KBUILDPROTECTIONTYPE_FIRST);
+ pThis->afTypes[enmType] |= 1;
+}
+
+
+void kBuildProtectionDisable(PKBUILDPROTECTION pThis, KBUILDPROTECTIONTYPE enmType)
+{
+ assert(pThis->uMagic == KBUILD_PROTECTION_MAGIC);
+ assert(enmType < KBUILDPROTECTIONTYPE_MAX && enmType >= KBUILDPROTECTIONTYPE_FIRST);
+ pThis->afTypes[enmType] &= ~1U;
+}
+
+
+/**
+ * Sets the protection depth according to the option argument.
+ *
+ * @param pszValue The value.
+ *
+ * @returns 0 on success, -1 and errx on failure.
+ */
+int kBuildProtectionSetDepth(PKBUILDPROTECTION pThis, const char *pszValue)
+{
+ /* skip leading blanks, they don't count either way. */
+ while (isspace(*pszValue))
+ pszValue++;
+
+ /* number or path? */
+ if (!isdigit(*pszValue) || strpbrk(pszValue, ":/\\"))
+ pThis->cProtectionDepth = countPathComponents(pThis, pszValue);
+ else
+ {
+ char *pszMore = 0;
+ pThis->cProtectionDepth = strtol(pszValue, &pszMore, 0);
+ if (pThis->cProtectionDepth != 0 && pszMore)
+ {
+ /* trailing space is harmless. */
+ while (isspace(*pszMore))
+ pszMore++;
+ }
+ if (!pThis->cProtectionDepth || pszValue == pszMore || *pszMore)
+ return errx(pThis->pCtx, 1, "bogus protection depth: %s", pszValue);
+ }
+
+ if (pThis->cProtectionDepth < 1)
+ return errx(pThis->pCtx, 1, "bogus protection depth: %s", pszValue);
+ return 0;
+}
+
+
+/**
+ * Scans the environment for option overrides.
+ *
+ * @param pThis Pointer to the instance data.
+ * @param papszEnv The environment array.
+ * @param pszPrefix The variable prefix.
+ *
+ * @returns 0 on success, -1 and err*() on failure.
+ */
+int kBuildProtectionScanEnv(PKBUILDPROTECTION pThis, char **papszEnv, const char *pszPrefix)
+{
+ unsigned i;
+ const size_t cchPrefix = strlen(pszPrefix);
+
+ for (i = 0; papszEnv[i]; i++)
+ {
+ const char *pszVar = papszEnv[i];
+ if (!strncmp(pszVar, pszPrefix, cchPrefix))
+ {
+ pszVar += cchPrefix;
+ if (!strncmp(pszVar, "PROTECTION_DEPTH=", sizeof("PROTECTION_DEPTH=") - 1))
+ {
+ const char *pszVal = pszVar + sizeof("PROTECTION_DEPTH=") - 1;
+ if (kBuildProtectionSetDepth(pThis, pszVal))
+ return -1;
+ }
+ else if (!strncmp(pszVar, "DISABLE_PROTECTION=", sizeof("DISABLE_PROTECTION=") - 1))
+ pThis->afTypes[KBUILDPROTECTIONTYPE_RECURSIVE] &= ~1U;
+ else if (!strncmp(pszVar, "ENABLE_PROTECTION=", sizeof("ENABLE_PROTECTION=") - 1))
+ pThis->afTypes[KBUILDPROTECTIONTYPE_RECURSIVE] |= 3;
+ else if (!strncmp(pszVar, "DISABLE_FULL_PROTECTION=", sizeof("DISABLE_FULL_PROTECTION=") - 1))
+ pThis->afTypes[KBUILDPROTECTIONTYPE_FULL] &= ~1U;
+ else if (!strncmp(pszVar, "ENABLE_FULL_PROTECTION=", sizeof("ENABLE_FULL_PROTECTION=") - 1))
+ pThis->afTypes[KBUILDPROTECTIONTYPE_FULL] |= 3;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Protect the upper layers of the file system against accidental
+ * or malicious deletetion attempt from within a makefile.
+ *
+ * @param pszPath The path to check.
+ * @param required_depth The minimum number of components in the
+ * path counting from the root.
+ *
+ * @returns 0 on success.
+ * On failure an error is printed and -1 is returned.
+ */
+int kBuildProtectionEnforce(PCKBUILDPROTECTION pThis, KBUILDPROTECTIONTYPE enmType, const char *pszPath)
+{
+ assert(pThis->uMagic == KBUILD_PROTECTION_MAGIC);
+ assert(enmType < KBUILDPROTECTIONTYPE_MAX && enmType >= KBUILDPROTECTIONTYPE_FIRST);
+
+ if ( (pThis->afTypes[enmType] & 3)
+ || (pThis->afTypes[KBUILDPROTECTIONTYPE_FULL] & 3))
+ {
+ /*
+ * Count the path and compare it with the required depth.
+ */
+ int cComponents = countPathComponents(pThis, pszPath);
+ if (cComponents < 0)
+ return -1;
+ if ((unsigned int)cComponents <= pThis->cProtectionDepth)
+ {
+ errx(pThis->pCtx, 1, "%s: protected", pszPath);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Retrieve the default path protection depth.
+ *
+ * @returns the default value.
+ */
+int kBuildProtectionDefaultDepth(void)
+{
+ return DEFAULT_PROTECTION_DEPTH;
+}
+
diff --git a/src/kmk/kmkbuiltin/kbuild_protection.h b/src/kmk/kmkbuiltin/kbuild_protection.h
new file mode 100644
index 0000000..4e8aed1
--- /dev/null
+++ b/src/kmk/kmkbuiltin/kbuild_protection.h
@@ -0,0 +1,67 @@
+/* $Id: kbuild_protection.h 3192 2018-03-26 20:25:56Z bird $ */
+/** @file
+ * Simple File Protection.
+ */
+
+/*
+ * Copyright (c) 2008-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/>
+ *
+ */
+
+#ifndef ___kbuild_protection_h
+#define ___kbuild_protection_h
+
+
+/**
+ * The different protection types.
+ */
+typedef enum
+{
+ KBUILDPROTECTIONTYPE_FIRST = 0,
+ KBUILDPROTECTIONTYPE_RECURSIVE = KBUILDPROTECTIONTYPE_FIRST,
+ KBUILDPROTECTIONTYPE_FULL,
+ KBUILDPROTECTIONTYPE_MAX
+} KBUILDPROTECTIONTYPE;
+
+
+/**
+ * The instance data.
+ * Don't touch.
+ */
+typedef struct KBUILDPROTECTION
+{
+ unsigned int uMagic;
+ unsigned int cProtectionDepth;
+ struct KMKBUILTINCTX *pCtx;
+ unsigned char afTypes[KBUILDPROTECTIONTYPE_MAX];
+} KBUILDPROTECTION;
+typedef KBUILDPROTECTION *PKBUILDPROTECTION;
+typedef const KBUILDPROTECTION *PCKBUILDPROTECTION;
+
+
+void kBuildProtectionInit(PKBUILDPROTECTION pThis, struct KMKBUILTINCTX *pCtx);
+void kBuildProtectionTerm(PKBUILDPROTECTION pThis);
+int kBuildProtectionScanEnv(PKBUILDPROTECTION pThis, char **papszEnv, const char *pszPrefix);
+void kBuildProtectionEnable(PKBUILDPROTECTION pThis, KBUILDPROTECTIONTYPE enmType);
+void kBuildProtectionDisable(PKBUILDPROTECTION pThis, KBUILDPROTECTIONTYPE enmType);
+int kBuildProtectionSetDepth(PKBUILDPROTECTION pThis, const char *pszValue);
+int kBuildProtectionEnforce(PCKBUILDPROTECTION pThis, KBUILDPROTECTIONTYPE enmType, const char *pszPath);
+int kBuildProtectionDefaultDepth(void);
+
+#endif
+
diff --git a/src/kmk/kmkbuiltin/kill.c b/src/kmk/kmkbuiltin/kill.c
new file mode 100644
index 0000000..7ba6f17
--- /dev/null
+++ b/src/kmk/kmkbuiltin/kill.c
@@ -0,0 +1,653 @@
+/* $Id: kill.c 3352 2020-06-05 00:31:50Z bird $ */
+/** @file
+ * kmk kill - Process killer.
+ */
+
+/*
+ * Copyright (c) 2007-2020 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/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include <assert.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#ifdef WINDOWS32
+# include <process.h>
+# include <Windows.h>
+# include <tlhelp32.h>
+#endif
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include "err.h"
+#include "kmkbuiltin.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @name kmkKillMatchName flags
+ * @{ */
+#define KMKKILL_FN_EXE_SUFF 1
+#define KMKKILL_FN_WILDCARD 2
+#define KMKKILL_FN_WITH_PATH 4
+#define KMKKILL_FN_ROOT_SLASH 8
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct KMKKILLGLOBALS
+{
+ PKMKBUILTINCTX pCtx;
+ int cVerbosity;
+ const char *pszCur;
+} KMKKILLGLOBALS;
+
+
+
+/**
+ * Kill one process by it's PID.
+ *
+ * The name is optional and only for messaging.
+ */
+static int kmkKillProcessByPid(KMKKILLGLOBALS *pThis, pid_t pid, const char *pszName)
+{
+ int rcExit;
+ HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE /*bInherit*/, pid);
+ if (!hProcess)
+ rcExit = errx(pThis->pCtx, 1, "Failed to open pid %u (%#x%s%s): %u",
+ pid, pid, pszName ? ", " : "", pszName ? pszName : "", GetLastError());
+ else
+ {
+ if (!TerminateProcess(hProcess, DBG_TERMINATE_PROCESS))
+ rcExit = errx(pThis->pCtx, 1, "TerminateProcess failed on pid %u (%#x%s%s): %u",
+ pid, pid, pszName ? ", " : "", pszName ? pszName : "", GetLastError());
+ else if (pThis->cVerbosity > 0)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "Terminated %u (%#x)%s%s\n",
+ pid, pid, pszName ? " - " : "", pszName ? pszName : "");
+ CloseHandle(hProcess);
+ }
+ return rcExit;
+}
+
+
+/**
+ * Does simple wilcard filename matching.
+ *
+ * @returns 1 on match, 0 on mismatch.
+ * @param pszPattern The pattern.
+ * @param cchPattern The length of the pattern.
+ * @param pchFilename The filename to match. This does not have to be
+ * terminated at @a cchFilename.
+ * @param cchFilename The length of the filename that we should match.
+ * @param cDepth The recursion depth.
+ */
+static int kmkKillMatchWildcard(const char *pszPattern, size_t cchPattern,
+ const char *pchFilename, size_t cchFilename, unsigned cDepth)
+{
+ while (cchPattern > 0 && cchFilename > 0)
+ {
+ char chPat = *pszPattern++;
+ cchPattern--;
+ if (chPat != '*')
+ {
+ if (chPat != '?')
+ {
+ char chExe = *pchFilename;
+ if ( chExe != chPat
+ && tolower(chExe) != tolower(chPat))
+ return 0;
+ }
+ pchFilename++;
+ cchFilename--;
+ }
+ else
+ {
+ size_t off, cchExeMin;
+
+ while (cchPattern > 0 && *pszPattern == '*')
+ {
+ pszPattern++;
+ cchPattern--;
+ }
+
+ /* Trailing '*'? */
+ if (!cchPattern)
+ return 1;
+
+ /* Final wildcard? Match the tail. */
+ if (memchr(pszPattern, '*', cchPattern) == NULL)
+ {
+ if (memchr(pszPattern, '?', cchPattern) == NULL)
+ return cchFilename >= cchPattern
+ && strnicmp(&pchFilename[cchFilename - cchPattern], pszPattern, cchPattern) == 0;
+
+ /* Only '?', so we know the exact length of this '*' match and can do a single tail matching. */
+ return cchFilename >= cchPattern
+ && kmkKillMatchWildcard(pszPattern, cchPattern, &pchFilename[cchFilename - cchPattern], cchPattern, cDepth + 1);
+ }
+
+ /*
+ * More wildcards ('*'), so we need to need to try out all matching
+ * length for this one. We start by counting non-asterisks chars
+ * in the remaining pattern so we know when to stop trying.
+ * This must be at least 1 char.
+ */
+ if (cDepth >= 32)
+ return 0;
+
+ for (off = cchExeMin = 0; off < cchPattern; off++)
+ cchExeMin += pszPattern[off] != '*';
+ assert(cchExeMin > 0);
+
+ while (cchFilename >= cchExeMin)
+ {
+ if (kmkKillMatchWildcard(pszPattern, cchPattern, pchFilename, cchFilename, cDepth + 1))
+ return 1;
+ /* next */
+ pchFilename++;
+ cchFilename--;
+ }
+ return 0;
+ }
+ }
+
+ /* If there is more filename left, we won't have a match. */
+ if (cchFilename != 0)
+ return 0;
+
+ /* If there is pattern left, we still have a match if it's all asterisks. */
+ while (cchPattern > 0 && *pszPattern == '*')
+ {
+ pszPattern++;
+ cchPattern--;
+ }
+ return cchPattern == 0;
+}
+
+
+/**
+ * Matches a process name against the given pattern.
+ *
+ * @returns 1 if it matches, 0 if it doesn't.
+ * @param pszPattern The pattern to match against.
+ * @param cchPattern The pattern length.
+ * @param fPattern Pattern characteristics.
+ * @param pszExeFile The EXE filename to match (includes path).
+ * @param cchExeFile The length of the EXE filename.
+ */
+static int kmkKillMatchName(const char *pszPattern, size_t cchPattern, unsigned fPattern,
+ const char *pszExeFile, size_t cchExeFile)
+{
+ /*
+ * Automatically match the exe suffix if not present in the pattern.
+ */
+ if ( !(fPattern & KMKKILL_FN_EXE_SUFF)
+ && cchExeFile > 4
+ && stricmp(&pszExeFile[cchExeFile - 4], ".exe") == 0)
+ cchExeFile -= sizeof(".exe") - 1;
+
+ /*
+ * If no path in the pattern, move pszExeFile up to the filename part.
+ */
+ if (!(fPattern & KMKKILL_FN_WITH_PATH)
+ && ( memchr(pszExeFile, '\\', cchExeFile) != NULL
+ || memchr(pszExeFile, '/', cchExeFile) != NULL
+ || memchr(pszExeFile, ':', cchExeFile) != NULL))
+ {
+ size_t offFilename = cchExeFile;
+ char ch;
+ while ( offFilename > 0
+ && (ch = pszExeFile[offFilename - 1]) != '\\'
+ && ch != '//'
+ && ch != ':')
+ offFilename--;
+ cchExeFile -= offFilename;
+ pszExeFile += offFilename;
+ }
+
+ /*
+ * Wildcard? This only works for the filename part.
+ */
+ if (fPattern & KMKKILL_FN_WILDCARD)
+ return kmkKillMatchWildcard(pszPattern, cchPattern, pszExeFile, cchExeFile, 0);
+
+ /*
+ * No-wildcard pattern.
+ */
+ if (cchExeFile >= cchPattern)
+ {
+ if (strnicmp(&pszExeFile[cchExeFile - cchPattern], pszPattern, cchPattern) == 0)
+ return cchExeFile == cchPattern
+ || (fPattern & KMKKILL_FN_ROOT_SLASH)
+ || pszExeFile[cchExeFile - cchPattern - 1] == '\\'
+ || pszExeFile[cchExeFile - cchPattern - 1] == '/'
+ || pszExeFile[cchExeFile - cchPattern - 1] == ':';
+
+ /*
+ * Might be slash directions or multiple consequtive slashes
+ * making a difference here, so compare char-by-char from the end.
+ */
+ if (fPattern & KMKKILL_FN_WITH_PATH)
+ {
+ while (cchPattern > 0 && cchExeFile > 0)
+ {
+ char chPat = pszPattern[--cchPattern];
+ char chExe = pszExeFile[--cchExeFile];
+ if (chPat == chExe)
+ {
+ if (chPat != '\\' && chPat != '/')
+ {
+ if (chPat != ':' || cchPattern > 0)
+ continue;
+ return 1;
+ }
+ }
+ else
+ {
+ chPat = tolower(chPat);
+ chExe = tolower(chExe);
+ if (chPat == chExe)
+ continue;
+ if (chPat == '/')
+ chPat = '\\';
+ if (chExe == '/')
+ chExe = '\\';
+ if (chPat != chExe)
+ return 0;
+ }
+
+ while (cchPattern > 0 && ((chPat = pszPattern[cchPattern - 1] == '\\') || chPat == '/'))
+ cchPattern--;
+ if (!cchPattern)
+ return 1;
+
+ while (cchExeFile > 0 && ((chExe = pszExeFile[cchExeFile - 1] == '\\') || chExe == '/'))
+ cchExeFile--;
+ }
+
+ if ( cchExeFile == 0
+ || pszExeFile[cchExeFile - 1] == '\\'
+ || pszExeFile[cchExeFile - 1] == '/'
+ || pszExeFile[cchExeFile - 1] == ':')
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Analyzes pattern for kmkKillMatchName().
+ *
+ * @returns Pattern characteristics.
+ * @param pszPattern The pattern.
+ * @param cchPattern The pattern length.
+ */
+static unsigned kmkKillAnalyzePattern(const char *pszPattern, size_t cchPattern)
+{
+ unsigned fPattern = 0;
+ if (cchPattern > 4 && stricmp(&pszPattern[cchPattern - 4], ".exe") == 0)
+ fPattern |= KMKKILL_FN_EXE_SUFF;
+ if (memchr(pszPattern, '*', cchPattern) != NULL)
+ fPattern |= KMKKILL_FN_WILDCARD;
+ if (memchr(pszPattern, '?', cchPattern) != NULL)
+ fPattern |= KMKKILL_FN_WILDCARD;
+ if (memchr(pszPattern, '/', cchPattern) != NULL)
+ fPattern |= KMKKILL_FN_WITH_PATH;
+ if (memchr(pszPattern, '\\', cchPattern) != NULL)
+ fPattern |= KMKKILL_FN_WITH_PATH;
+ if (*pszPattern == '\\' || *pszPattern == '/')
+ fPattern |= KMKKILL_FN_ROOT_SLASH;
+ return fPattern;
+}
+
+
+/**
+ * Enumerate processes and kill the ones matching the pattern.
+ */
+static int kmkKillProcessByName(KMKKILLGLOBALS *pThis, const char *pszPattern)
+{
+ HANDLE hSnapshot;
+ int rcExit = 0;
+ size_t const cchPattern = strlen(pszPattern);
+ unsigned const fPattern = kmkKillAnalyzePattern(pszPattern, cchPattern);
+ if (cchPattern == 0)
+ return errx(pThis->pCtx, 1, "Empty process name!");
+ if ((fPattern & (KMKKILL_FN_WILDCARD | KMKKILL_FN_WITH_PATH)) == (KMKKILL_FN_WILDCARD | KMKKILL_FN_WITH_PATH))
+ return errx(pThis->pCtx, 1, "Wildcard and paths cannot be mixed: %s", pszPattern);
+
+ hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (!hSnapshot)
+ rcExit = errx(pThis->pCtx, 1, "CreateToolhelp32Snapshot failed: %d", GetLastError());
+ else
+ {
+ union
+ {
+ PROCESSENTRY32 Entry;
+ char achBuf[8192];
+ } u;
+
+ memset(&u, 0, sizeof(u));
+ u.Entry.dwSize = sizeof(u) - sizeof(".exe");
+ SetLastError(NO_ERROR);
+ if (Process32First(hSnapshot, &u.Entry))
+ {
+ for (;;)
+ {
+ /* Kill it if it matches. */
+ u.achBuf[sizeof(u.achBuf) - sizeof(".exe")] = '\0';
+ if ( u.Entry.szExeFile[0] != '\0'
+ && kmkKillMatchName(pszPattern, cchPattern, fPattern, u.Entry.szExeFile, strlen(u.Entry.szExeFile)))
+ {
+ int rcExit2 = kmkKillProcessByPid(pThis, u.Entry.th32ProcessID, u.Entry.szExeFile);
+ if (rcExit2 != 0 && rcExit == 0)
+ rcExit = rcExit2;
+ }
+
+ /* next */
+ u.Entry.dwSize = sizeof(u) - sizeof(".exe");
+ SetLastError(NO_ERROR);
+ if (!Process32Next(hSnapshot, &u.Entry))
+ {
+ if (GetLastError() != ERROR_NO_MORE_FILES)
+ rcExit = errx(pThis->pCtx, 1, "Process32Next failed: %d", GetLastError());
+ break;
+ }
+ }
+ }
+ else
+ rcExit = errx(pThis->pCtx, 1, "Process32First failed: %d", GetLastError());
+ CloseHandle(hSnapshot);
+ }
+ return rcExit;
+
+}
+
+
+/**
+ * Worker for handling one command line process target.
+ *
+ * @returns 0 on success, non-zero exit code on failure.
+ */
+static int kmkKillProcess(KMKKILLGLOBALS *pThis, const char *pszTarget)
+{
+ /*
+ * Try treat the target as a pid first, then a name pattern.
+ */
+ char *pszNext = NULL;
+ long pid;
+ errno = 0;
+ pid = strtol(pszTarget, &pszNext, 0);
+ if (pszNext && *pszNext == '\0' && errno == 0)
+ return kmkKillProcessByPid(pThis, pid, NULL);
+ return kmkKillProcessByName(pThis, pszTarget);
+}
+
+
+/**
+ * Worker for handling one command line job target.
+ *
+ * @returns 0 on success, non-zero exit code on failure.
+ */
+static int kmkKillJob(KMKKILLGLOBALS *pThis, const char *pszTarget)
+{
+ int rcExit = 0;
+ HANDLE hTempJob = NULL;
+ BOOL fIsInJob = FALSE;
+
+ /*
+ * Open the job object.
+ */
+ DWORD fRights = JOB_OBJECT_QUERY | JOB_OBJECT_TERMINATE | JOB_OBJECT_ASSIGN_PROCESS;
+ HANDLE hJob = OpenJobObjectA(fRights, FALSE /*bInheritHandle*/, pszTarget);
+ if (!hJob)
+ {
+ fRights &= ~JOB_OBJECT_ASSIGN_PROCESS;
+ hJob = OpenJobObjectA(fRights, FALSE /*bInheritHandle*/, pszTarget);
+ if (!hJob)
+ return errx(pThis->pCtx, 1, "Failed to open job '%s': %u", pszTarget, GetLastError());
+ }
+
+ /*
+ * Are we a member of this job? If so, temporarily move
+ * to a different object so we don't kill ourselves.
+ */
+ if (IsProcessInJob(hJob, GetCurrentProcess(), &fIsInJob))
+ {
+ if (fIsInJob)
+ {
+ /** @todo this probably isn't working. */
+ if (pThis->cVerbosity >= 1)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0,
+ "kmk_kill: Is myself (%u) a member of target job (%s)", getpid(), pszTarget);
+ if (!(fRights & JOB_OBJECT_ASSIGN_PROCESS))
+ rcExit = errx(pThis->pCtx, 1,
+ "Is myself member of the target job and don't have the JOB_OBJECT_ASSIGN_PROCESS right.");
+ else
+ {
+ hTempJob = CreateJobObjectA(NULL, NULL);
+ if (hTempJob)
+ {
+ if (!(AssignProcessToJobObject(hTempJob, GetCurrentProcess())))
+ rcExit = errx(pThis->pCtx, 1, "AssignProcessToJobObject(temp, me) failed: %u", GetLastError());
+ }
+ }
+ }
+ }
+ else
+ rcExit = errx(pThis->pCtx, 1, "IsProcessInJob(%s, me) failed: %u", pszTarget, GetLastError());
+
+ /*
+ * Do the killing.
+ */
+ if (rcExit == 0)
+ {
+ if (!TerminateJobObject(hJob, DBG_TERMINATE_PROCESS))
+ rcExit = errx(pThis->pCtx, 1, "TerminateJobObject(%s) failed: %u", pszTarget, GetLastError());
+ }
+
+ /*
+ * Cleanup.
+ */
+ if (hTempJob)
+ {
+ if (!(AssignProcessToJobObject(hJob, GetCurrentProcess())))
+ rcExit = errx(pThis->pCtx, 1, "AssignProcessToJobObject(%s, me) failed: %u", pszTarget, GetLastError());
+ CloseHandle(hTempJob);
+ }
+ CloseHandle(hJob);
+
+ return rcExit;
+}
+
+
+/**
+ * Show usage.
+ */
+static void kmkKillUsage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s [options] <job-name|process-name|pid> [options] [...]\n"
+ " or: %s --help\n"
+ " or: %s --version\n"
+ "\n"
+ "Options that decide what to kill next:\n"
+ " -p|--process Processes, either by name or by PID. (default)\n"
+ " -j|--job Windows jobs.\n"
+ "\n"
+ "Other options:\n"
+ " -q|--quiet Quiet operation. Only real errors are displayed.\n"
+ " -v|--verbose Increase verbosity.\n"
+ ,
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+}
+
+
+int kmk_builtin_kill(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ int rcExit = 0;
+ int i;
+ KMKKILLGLOBALS This;
+ enum { kTargetJobs, kTargetProcesses } enmTarget = kTargetProcesses;
+
+ /* Globals. */
+ This.pCtx = pCtx;
+ This.cVerbosity = 1;
+
+ /*
+ * Parse arguments.
+ */
+ if (argc <= 1)
+ {
+ kmkKillUsage(pCtx, 0);
+ return 1;
+ }
+ for (i = 1; i < argc; i++)
+ {
+ if (argv[i][0] == '-')
+ {
+ const char *pszValue;
+ const char *psz = &argv[i][1];
+ char chOpt;
+ chOpt = *psz++;
+ if (chOpt == '-')
+ {
+ /* Convert long to short option. */
+ if (!strcmp(psz, "job"))
+ chOpt = 'j';
+ else if (!strcmp(psz, "process"))
+ chOpt = 'p';
+ else if (!strcmp(psz, "quiet"))
+ chOpt = 'q';
+ else if (!strcmp(psz, "verbose"))
+ chOpt = 'v';
+ else if (!strcmp(psz, "help"))
+ chOpt = '?';
+ else if (!strcmp(psz, "version"))
+ chOpt = 'V';
+ else
+ {
+ errx(pCtx, 2, "Invalid argument '%s'.", argv[i]);
+ kmkKillUsage(pCtx, 1);
+ return 2;
+ }
+ psz = "";
+ }
+
+ /*
+ * Requires value?
+ */
+ switch (chOpt)
+ {
+ /*case '':
+ if (*psz)
+ pszValue = psz;
+ else if (++i < argc)
+ pszValue = argv[i];
+ else
+ return errx(pCtx, 2, "The '-%c' option takes a value.", chOpt);
+ break;*/
+
+ default:
+ pszValue = NULL;
+ break;
+ }
+
+
+ switch (chOpt)
+ {
+ /*
+ * What to kill
+ */
+ case 'j':
+ enmTarget = kTargetJobs;
+ break;
+
+ case 'p':
+ enmTarget = kTargetProcesses;
+ break;
+
+ /*
+ * How to kill processes...
+ */
+
+
+ /*
+ * Noise level.
+ */
+ case 'q':
+ This.cVerbosity = 0;
+ break;
+
+ case 'v':
+ This.cVerbosity += 1;
+ break;
+
+ /*
+ * The mandatory version & help.
+ */
+ case '?':
+ kmkKillUsage(pCtx, 0);
+ return rcExit;
+ case 'V':
+ return kbuild_version(argv[0]);
+
+ /*
+ * Invalid argument.
+ */
+ default:
+ errx(pCtx, 2, "Invalid argument '%s'.", argv[i]);
+ kmkKillUsage(pCtx, 1);
+ return 2;
+ }
+ }
+ else
+ {
+ /*
+ * Kill something.
+ */
+ int rcExitOne;
+ This.pszCur = argv[i];
+ if (enmTarget == kTargetJobs)
+ rcExitOne = kmkKillJob(&This, argv[i]);
+ else
+ rcExitOne = kmkKillProcess(&This, argv[i]);
+ if (rcExitOne != 0 && rcExit == 0)
+ rcExit = rcExitOne;
+ }
+ }
+
+ return rcExit;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_kill", NULL };
+ return kmk_builtin_kill(argc, argv, envp, &Ctx);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin/ln.c b/src/kmk/kmkbuiltin/ln.c
new file mode 100644
index 0000000..4f80475
--- /dev/null
+++ b/src/kmk/kmkbuiltin/ln.c
@@ -0,0 +1,287 @@
+/*-
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static char const copyright[] =
+"@(#) Copyright (c) 1987, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)ln.c 8.2 (Berkeley) 3/31/94";
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/bin/ln/ln.c,v 1.33 2005/02/09 17:37:37 ru Exp $");
+#endif /* no $id */
+
+#define FAKES_NO_GETOPT_H /* bird */
+#include "config.h"
+#ifndef _MSC_VER
+# include <sys/param.h>
+#endif
+#include <sys/stat.h>
+
+#include "err.h"
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "getopt_r.h"
+#ifdef _MSC_VER
+# include "mscfakes.h"
+#endif
+#include "kmkbuiltin.h"
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct LNINSTANCE
+{
+ PKMKBUILTINCTX pCtx;
+ int fflag; /* Unlink existing files. */
+ int hflag; /* Check new name for symlink first. */
+ int iflag; /* Interactive mode. */
+ int sflag; /* Symbolic, not hard, link. */
+ int vflag; /* Verbose output. */
+ int (*linkf)(const char *, const char *); /* System link call. */
+ char linkch;
+} LNINSTANCE;
+typedef LNINSTANCE *PLNINSTANCE;
+
+static struct option long_options[] =
+{
+ { "help", no_argument, 0, 261 },
+ { "version", no_argument, 0, 262 },
+ { 0, 0, 0, 0 },
+};
+
+
+static int linkit(PLNINSTANCE,const char *, const char *, int);
+static int usage(PKMKBUILTINCTX, int);
+
+
+int
+kmk_builtin_ln(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ LNINSTANCE This;
+ struct getopt_state_r gos;
+ struct stat sb;
+ char *sourcedir;
+ int ch, exitval;
+
+ /* initialize globals. */
+ This.pCtx = pCtx;
+ This.fflag = 0;
+ This.hflag = 0;
+ This.iflag = 0;
+ This.sflag = 0;
+ This.vflag = 0;
+ This.linkch = 0;
+ This.linkf = NULL;
+
+ getopt_initialize_r(&gos, argc, argv, "fhinsv", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1)
+ switch (ch) {
+ case 'f':
+ This.fflag = 1;
+ This.iflag = 0;
+ break;
+ case 'h':
+ case 'n':
+ This.hflag = 1;
+ break;
+ case 'i':
+ This.iflag = 1;
+ This.fflag = 0;
+ break;
+ case 's':
+ This.sflag = 1;
+ break;
+ case 'v':
+ This.vflag = 1;
+ break;
+ case 261:
+ usage(pCtx, 0);
+ return 0;
+ case 262:
+ return kbuild_version(argv[0]);
+ case '?':
+ default:
+ return usage(pCtx, 1);
+ }
+
+ argv += gos.optind;
+ argc -= gos.optind;
+
+ This.linkf = This.sflag ? symlink : link;
+ This.linkch = This.sflag ? '-' : '=';
+
+ switch(argc) {
+ case 0:
+ return usage(pCtx, 1);
+ /* NOTREACHED */
+ case 1: /* ln target */
+ return linkit(&This, argv[0], ".", 1);
+ case 2: /* ln target source */
+ return linkit(&This, argv[0], argv[1], 0);
+ default:
+ ;
+ }
+ /* ln target1 target2 directory */
+ sourcedir = argv[argc - 1];
+ if (This.hflag && lstat(sourcedir, &sb) == 0 && S_ISLNK(sb.st_mode)) {
+ /*
+ * We were asked not to follow symlinks, but found one at
+ * the target--simulate "not a directory" error
+ */
+ errno = ENOTDIR;
+ return err(pCtx, 1, "st_mode: %s", sourcedir);
+ }
+ if (stat(sourcedir, &sb))
+ return err(pCtx, 1, "stat: %s", sourcedir);
+ if (!S_ISDIR(sb.st_mode))
+ return usage(pCtx, 1);
+ for (exitval = 0; *argv != sourcedir; ++argv)
+ exitval |= linkit(&This, *argv, sourcedir, 1);
+ return exitval;
+}
+
+static int
+linkit(PLNINSTANCE pThis, const char *target, const char *source, int isdir)
+{
+ struct stat sb;
+ const char *p;
+ int ch, exists, first;
+ char path[PATH_MAX];
+
+ if (!pThis->sflag) {
+ /* If target doesn't exist, quit now. */
+ if (stat(target, &sb)) {
+ warn(pThis->pCtx, "stat: %s", target);
+ return (1);
+ }
+ /* Only symbolic links to directories. */
+ if (S_ISDIR(sb.st_mode)) {
+ errno = EISDIR;
+ warn(pThis->pCtx, "st_mode: %s", target);
+ return (1);
+ }
+ }
+
+ /*
+ * If the source is a directory (and not a symlink if hflag),
+ * append the target's name.
+ */
+ if (isdir ||
+ (lstat(source, &sb) == 0 && S_ISDIR(sb.st_mode)) ||
+ (!pThis->hflag && stat(source, &sb) == 0 && S_ISDIR(sb.st_mode))) {
+#if defined(_MSC_VER) || defined(__OS2__)
+ char *p2 = strrchr(target, '\\');
+ p = strrchr(target, '/');
+ if (p2 != NULL && (p == NULL || p2 > p))
+ p = p2;
+ if (p == NULL)
+#else
+ if ((p = strrchr(target, '/')) == NULL)
+#endif
+ p = target;
+ else
+ ++p;
+ if (snprintf(path, sizeof(path), "%s/%s", source, p) >=
+ (ssize_t)sizeof(path)) {
+ errno = ENAMETOOLONG;
+ warn(pThis->pCtx, "snprintf: %s", target);
+ return (1);
+ }
+ source = path;
+ }
+
+ exists = !lstat(source, &sb);
+ /*
+ * If the file exists, then unlink it forcibly if -f was specified
+ * and interactively if -i was specified.
+ */
+ if (pThis->fflag && exists) {
+ if (unlink(source)) {
+ warn(pThis->pCtx, "unlink: %s", source);
+ return (1);
+ }
+ } else if (pThis->iflag && exists) {
+ fflush(stdout);
+ fprintf(stderr, "replace %s? ", source);
+
+ first = ch = getchar();
+ while(ch != '\n' && ch != EOF)
+ ch = getchar();
+ if (first != 'y' && first != 'Y') {
+ kmk_builtin_ctx_printf(pThis->pCtx, 1, "not replaced\n");
+ return (1);
+ }
+
+ if (unlink(source)) {
+ warn(pThis->pCtx, "unlink: %s", source);
+ return (1);
+ }
+ }
+
+ /* Attempt the link. */
+ if ((*pThis->linkf)(target, source)) {
+ warn(pThis->pCtx, "%s: %s", pThis->linkf == link ? "link" : "symlink", source);
+ return (1);
+ }
+ if (pThis->vflag)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s %c> %s\n", source, pThis->linkch, target);
+ return (0);
+}
+
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx,fIsErr,
+ "usage: %s [-fhinsv] source_file [target_file]\n"
+ " or: %s [-fhinsv] source_file ... target_dir\n"
+ " or: %s source_file target_file\n"
+ " or: %s --help\n"
+ " or: %s --version\n",
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName,
+ pCtx->pszProgName, pCtx->pszProgName);
+ return 1;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_ln", NULL };
+ return kmk_builtin_ln(argc, argv, envp, &Ctx);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin/md5sum.c b/src/kmk/kmkbuiltin/md5sum.c
new file mode 100644
index 0000000..dc8d497
--- /dev/null
+++ b/src/kmk/kmkbuiltin/md5sum.c
@@ -0,0 +1,874 @@
+/* $Id: md5sum.c 3219 2018-03-30 22:30:15Z bird $ */
+/** @file
+ * md5sum.
+ */
+
+/*
+ * 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/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "config.h"
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#ifdef _MSC_VER
+# include <io.h>
+#else
+# include <unistd.h>
+#endif
+#include <sys/stat.h>
+#include "err.h"
+#include "kmkbuiltin.h"
+#include "../../lib/md5.h"
+#include <k/kTypes.h>
+
+/*#define MD5SUM_USE_STDIO*/
+
+
+/**
+ * Prints the usage and return 1.
+ */
+static int usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: md5sum [-bt] [-o list-file] file(s)\n"
+ " or: md5sum [-btwq] -c list-file(s)\n"
+ " or: md5sum [-btq] -C MD5 file\n"
+ "\n"
+ " -c, --check Check MD5 and files found in the specified list file(s).\n"
+ " The default is to compute MD5 sums of the specified files\n"
+ " and print them to stdout in list form.\n"
+ " -C, --check-file This is followed by an MD5 sum and the file to check.\n"
+ " -b, --binary Read files in binary mode. (default)\n"
+ " -t, --text Read files in text mode.\n"
+ " -m, --manifest Output in kBuild fetch 'manifest' format.\n"
+ " -p, --progress Show progress indicator on large files.\n"
+ " -o, --output Name of the output list file. Useful with -p.\n"
+ " -q, --status Be quiet.\n"
+ " -w, --warn Ignored. Always warn, unless quiet.\n"
+ " -h, --help This usage info.\n"
+ " -v, --version Show version information and exit.\n"
+ );
+ return 1;
+}
+
+
+/**
+ * Makes a string out of the given digest.
+ *
+ * @param pDigest The MD5 digest.
+ * @param pszDigest Where to put the digest string. Must be able to
+ * hold at least 33 bytes.
+ */
+static void digest_to_string(unsigned char pDigest[16], char *pszDigest)
+{
+ unsigned i;
+ for (i = 0; i < 16; i++)
+ {
+ static char s_achDigits[17] = "0123456789abcdef";
+ pszDigest[i*2] = s_achDigits[(pDigest[i] >> 4)];
+ pszDigest[i*2 + 1] = s_achDigits[(pDigest[i] & 0xf)];
+ }
+ pszDigest[i*2] = '\0';
+}
+
+
+/**
+ * Attempts to convert a string to a MD5 digest.
+ *
+ * @returns 0 on success, 1-based position of the failure first error.
+ * @param pszDigest The string to interpret.
+ * @param pDigest Where to put the MD5 digest.
+ */
+static int string_to_digest(const char *pszDigest, unsigned char pDigest[16])
+{
+ unsigned i;
+ unsigned iBase = 1;
+
+ /* skip blanks */
+ while ( *pszDigest == ' '
+ || *pszDigest == '\t'
+ || *pszDigest == '\n'
+ || *pszDigest == '\r')
+ pszDigest++, iBase++;
+
+ /* convert the digits. */
+ memset(pDigest, 0, 16);
+ for (i = 0; i < 32; i++, pszDigest++)
+ {
+ int iDigit;
+ if (*pszDigest >= '0' && *pszDigest <= '9')
+ iDigit = *pszDigest - '0';
+ else if (*pszDigest >= 'a' && *pszDigest <= 'f')
+ iDigit = *pszDigest - 'a' + 10;
+ else if (*pszDigest >= 'A' && *pszDigest <= 'F')
+ iDigit = *pszDigest - 'A' + 10;
+ else
+ return i + iBase;
+ if (i & 1)
+ pDigest[i >> 1] |= iDigit;
+ else
+ pDigest[i >> 1] |= iDigit << 4;
+ }
+
+ /* the rest of the string must now be blanks. */
+ while ( *pszDigest == ' '
+ || *pszDigest == '\t'
+ || *pszDigest == '\n'
+ || *pszDigest == '\r')
+ pszDigest++, i++;
+
+ return *pszDigest ? i + iBase : 0;
+}
+
+
+/**
+ * Opens the specified file for md5 sum calculation.
+ *
+ * @returns Opaque pointer on success, NULL and errno on failure.
+ * @param pszFilename The filename.
+ * @param fText Whether text or binary mode should be used.
+ */
+static void *open_file(const char *pszFilename, unsigned fText)
+{
+#if defined(MD5SUM_USE_STDIO)
+ FILE *pFile;
+
+ errno = 0;
+ pFile = fopen(pszFilename,
+ fText ? "r" KMK_FOPEN_NO_INHERIT_MODE
+ : "rb" KMK_FOPEN_NO_INHERIT_MODE);
+ if (!pFile && errno == EINVAL && !fText)
+ pFile = fopen(pszFilename, "r" KMK_FOPEN_NO_INHERIT_MODE);
+ return pFile;
+
+#else
+ int fd;
+ int fFlags;
+
+ /* figure out the appropriate flags. */
+ fFlags = O_RDONLY | KMK_OPEN_NO_INHERIT;
+#ifdef O_SEQUENTIAL
+ fFlags |= _O_SEQUENTIAL;
+#elif defined(_O_SEQUENTIAL)
+ fFlags |= _O_SEQUENTIAL;
+#endif
+#ifdef O_BINARY
+ if (!fText) fFlags |= O_BINARY;
+#elif defined(_O_BINARY)
+ if (!fText) fFlags |= _O_BINARY;
+#endif
+#ifdef O_TEXT
+ if (fText) fFlags |= O_TEXT;
+#elif defined(O_TEXT)
+ if (fText) fFlags |= _O_TEXT;
+#else
+ (void)fText;
+#endif
+
+ errno = 0;
+ fd = open(pszFilename, fFlags, 0755);
+ if (fd >= 0)
+ {
+ int *pFd = malloc(sizeof(*pFd));
+ if (pFd)
+ {
+ *pFd = fd;
+ return pFd;
+ }
+ close(fd);
+ errno = ENOMEM;
+ }
+
+ return NULL;
+#endif
+}
+
+
+/**
+ * Closes a file opened by open_file.
+ *
+ * @param pvFile The opaque pointer returned by open_file.
+ */
+static void close_file(void *pvFile)
+{
+#if defined(MD5SUM_USE_STDIO)
+ fclose((FILE *)pvFile);
+#else
+ close(*(int *)pvFile);
+ free(pvFile);
+#endif
+}
+
+
+/**
+ * Reads from a file opened by open_file.
+ *
+ * @returns Number of bytes read on success.
+ * 0 on EOF.
+ * Negated errno on read error.
+ * @param pvFile The opaque pointer returned by open_file.
+ * @param pvBuf Where to put the number of read bytes.
+ * @param cbBuf The max number of bytes to read.
+ * Must be less than a INT_MAX.
+ */
+static int read_file(void *pvFile, void *pvBuf, size_t cbBuf)
+{
+#if defined(MD5SUM_USE_STDIO)
+ int cb;
+
+ errno = 0;
+ cb = (int)fread(pvBuf, 1, cbBuf, (FILE *)pvFile);
+ if (cb >= 0)
+ return (int)cb;
+ if (!errno)
+ return -EINVAL;
+ return -errno;
+#else
+ int cb;
+
+ errno = 0;
+ cb = (int)read(*(int *)pvFile, pvBuf, (int)cbBuf);
+ if (cb >= 0)
+ return (int)cb;
+ if (!errno)
+ return -EINVAL;
+ return -errno;
+#endif
+}
+
+
+/**
+ * Gets the size of the file.
+ * This is informational and not necessarily 100% accurate.
+ *
+ * @returns File size.
+ * @param pvFile The opaque pointer returned by open_file
+ */
+static KU64 size_file(void *pvFile)
+{
+#if defined(_MSC_VER)
+ __int64 cb;
+# if defined(MD5SUM_USE_STDIO)
+ cb = _filelengthi64(fileno((FILE *)pvFile));
+# else
+ cb = _filelengthi64(*(int *)pvFile);
+# endif
+ if (cb >= 0)
+ return cb;
+
+#elif defined(MD5SUM_USE_STDIO)
+ struct stat st;
+ if (!fstat(fileno((FILE *)pvFile), &st))
+ return st.st_size;
+
+#else
+ struct stat st;
+ if (!fstat(*(int *)pvFile, &st))
+ return st.st_size;
+#endif
+ return 1024;
+}
+
+
+/**
+ * Calculates the md5sum of the sepecified file stream.
+ *
+ * @returns errno on failure, 0 on success.
+ * @param pvFile The file stream.
+ * @param pDigest Where to store the MD5 digest.
+ * @param fProgress Whether to show a progress bar.
+ * @param pcbFile Where to return the file size. Optional.
+ */
+static int calc_md5sum(void *pvFile, unsigned char pDigest[16], unsigned fProgress, KU64 *pcbFile)
+{
+ int cb;
+ int rc = 0;
+ struct MD5Context Ctx;
+ unsigned uPercent = 0;
+ KU64 off = 0;
+ KU64 const cbFile = size_file(pvFile);
+
+ /* Get a decent sized buffer assuming we'll be spending more time reading
+ from the storage than doing MD5 sums. (2MB was choosen based on recent
+ SATA storage benchmarks which used that block size for sequential
+ tests.) We align the buffer address on a 16K boundrary to avoid most
+ transfer alignment issues. */
+ char *pabBufAligned;
+ size_t const cbBufAlign = 16*1024 - 1;
+ size_t const cbBufMax = 2048*1024;
+ size_t cbBuf = cbFile >= cbBufMax ? cbBufMax : ((size_t)cbFile + cbBufAlign) & ~(size_t)cbBufAlign;
+ char *pabBuf = (char *)malloc(cbBuf + cbBufAlign);
+ if (pabBuf)
+ pabBufAligned = (char *)(((uintptr_t)pabBuf + cbBufAlign) & ~(uintptr_t)cbBufAlign );
+ else
+ {
+ do
+ {
+ cbBuf /= 2;
+ pabBuf = (char *)malloc(cbBuf);
+ } while (!pabBuf && cbBuf > 4096);
+ if (!pabBuf)
+ return ENOMEM;
+ pabBufAligned = pabBuf;
+ }
+
+ if (cbFile < cbBuf * 4)
+ fProgress = 0;
+
+ MD5Init(&Ctx);
+ for (;;)
+ {
+ /* process a chunk. */
+ cb = read_file(pvFile, pabBufAligned, cbBuf);
+ if (cb > 0)
+ MD5Update(&Ctx, (unsigned char *)pabBufAligned, cb);
+ else if (!cb)
+ break;
+ else
+ {
+ rc = -cb;
+ break;
+ }
+ off += cb;
+
+ /* update the progress indicator. */
+ if (fProgress)
+ {
+ unsigned uNewPercent;
+ uNewPercent = (unsigned)(((double)off / cbFile) * 100);
+ if (uNewPercent != uPercent)
+ {
+ if (uPercent)
+ printf("\b\b\b\b");
+ printf("%3d%%", uNewPercent);
+ fflush(stdout);
+ uPercent = uNewPercent;
+ }
+ }
+ }
+ MD5Final(pDigest, &Ctx);
+
+ if (pcbFile)
+ *pcbFile = off;
+
+ if (fProgress)
+ printf("\b\b\b\b \b\b\b\b");
+
+ free(pabBuf);
+ return rc;
+}
+
+
+/**
+ * Checks the if the specified digest matches the digest of the file stream.
+ *
+ * @returns 0 on match, -1 on mismatch, errno value (positive) on failure.
+ * @param pvFile The file stream.
+ * @param Digest The MD5 digest.
+ * @param fProgress Whether to show an progress indicator on large files.
+ */
+static int check_md5sum(void *pvFile, unsigned char Digest[16], unsigned fProgress)
+{
+ unsigned char DigestFile[16];
+ int rc;
+
+ rc = calc_md5sum(pvFile, DigestFile, fProgress, NULL);
+ if (!rc)
+ rc = memcmp(Digest, DigestFile, 16) ? -1 : 0;
+ return rc;
+}
+
+
+/**
+ * Checks if the specified file matches the given MD5 digest.
+ *
+ * @returns 0 if it matches, 1 if it doesn't or an error occurs.
+ * @param pCtx The command execution context.
+ * @param pszFilename The name of the file to check.
+ * @param pszDigest The MD5 digest string.
+ * @param fText Whether to open the file in text or binary mode.
+ * @param fQuiet Whether to go about this in a quiet fashion or not.
+ * @param fProgress Whether to show an progress indicator on large files.
+ */
+static int check_one_file(PKMKBUILTINCTX pCtx, const char *pszFilename, const char *pszDigest, unsigned fText,
+ unsigned fQuiet, unsigned fProgress)
+{
+ unsigned char Digest[16];
+ int rc;
+
+ rc = string_to_digest(pszDigest, Digest);
+ if (!rc)
+ {
+ void *pvFile;
+
+ pvFile = open_file(pszFilename, fText);
+ if (pvFile)
+ {
+ if (!fQuiet)
+ kmk_builtin_ctx_printf(pCtx, 0, "%s: ", pszFilename);
+ rc = check_md5sum(pvFile, Digest, fProgress);
+ close_file(pvFile);
+ if (!fQuiet)
+ {
+ kmk_builtin_ctx_printf(pCtx, 0, "%s\n", !rc ? "OK" : rc < 0 ? "FAILURE" : "ERROR");
+ if (rc > 0)
+ errx(pCtx, 1, "Error reading '%s': %s", pszFilename, strerror(rc));
+ }
+ if (rc)
+ rc = 1;
+ }
+ else
+ {
+ if (!fQuiet)
+ errx(pCtx, 1, "Failed to open '%s': %s", pszFilename, strerror(errno));
+ rc = 1;
+ }
+ }
+ else
+ {
+ errx(pCtx, 1, "Malformed MD5 digest '%s'!", pszDigest);
+ errx(pCtx, 1, " %*s^", rc - 1, "");
+ rc = 1;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Checks the specified md5.lst file.
+ *
+ * @returns 0 if all checks out file, 1 if one or more fails or there are read errors.
+ * @param pCtx The command execution context.
+ * @param pszFilename The name of the file.
+ * @param fText The default mode, text or binary. Only used when fBinaryTextOpt is true.
+ * @param fBinaryTextOpt Whether a -b or -t option was specified and should be used.
+ * @param fQuiet Whether to be quiet.
+ * @param fProgress Whether to show an progress indicator on large files.
+ */
+static int check_files(PKMKBUILTINCTX pCtx, const char *pszFilename, int fText, int fBinaryTextOpt,
+ int fQuiet, unsigned fProgress)
+{
+ int rc = 0;
+ FILE *pFile;
+
+ /*
+ * Try open the md5.lst file and process it line by line.
+ */
+ pFile = fopen(pszFilename, "r" KMK_FOPEN_NO_INHERIT_MODE);
+ if (pFile)
+ {
+ int iLine = 0;
+ char szLine[8192];
+ while (fgets(szLine, sizeof(szLine), pFile))
+ {
+ const char *pszDigest;
+ int fLineText;
+ char *psz;
+ int rc2;
+
+ iLine++;
+ psz = szLine;
+
+ /* leading blanks */
+ while (*psz == ' ' || *psz == '\t' || *psz == '\n')
+ psz++;
+
+ /* skip blank or comment lines. */
+ if (!*psz || *psz == '#' || *psz == ';' || *psz == '/')
+ continue;
+
+ /* remove the trailing newline. */
+ rc2 = (int)strlen(psz);
+ if (psz[rc2 - 1] == '\n')
+ psz[rc2 - (rc2 >= 2 && psz[rc2 - 2] == '\r' ? 2 : 1)] = '\0';
+
+ /* skip to the end of the digest and terminate it. */
+ pszDigest = psz;
+ while (*psz != ' ' && *psz != '\t' && *psz)
+ psz++;
+ if (*psz)
+ {
+ *psz++ = '\0';
+
+ /* blanks */
+ while (*psz == ' ' || *psz == '\t' || *psz == '\n')
+ psz++;
+
+ /* check for binary asterix */
+ if (*psz != '*')
+ fLineText = fBinaryTextOpt ? fText : 0;
+ else
+ {
+ fLineText = 0;
+ psz++;
+ }
+ if (*psz)
+ {
+ unsigned char Digest[16];
+
+ /* the rest is filename. */
+ pszFilename = psz;
+
+ /*
+ * Do the job.
+ */
+ rc2 = string_to_digest(pszDigest, Digest);
+ if (!rc2)
+ {
+ void *pvFile = open_file(pszFilename, fLineText);
+ if (pvFile)
+ {
+ if (!fQuiet)
+ kmk_builtin_ctx_printf(pCtx, 0, "%s: ", pszFilename);
+ rc2 = check_md5sum(pvFile, Digest, fProgress);
+ close_file(pvFile);
+ if (!fQuiet)
+ {
+ kmk_builtin_ctx_printf(pCtx, 0, "%s\n", !rc2 ? "OK" : rc2 < 0 ? "FAILURE" : "ERROR");
+ if (rc2 > 0)
+ errx(pCtx, 1, "Error reading '%s': %s", pszFilename, strerror(rc2));
+ }
+ if (rc2)
+ rc = 1;
+ }
+ else
+ {
+ if (!fQuiet)
+ errx(pCtx, 1, "Failed to open '%s': %s", pszFilename, strerror(errno));
+ rc = 1;
+ }
+ }
+ else if (!fQuiet)
+ {
+ errx(pCtx, 1, "%s (%d): Ignoring malformed digest '%s' (digest)", pszFilename, iLine, pszDigest);
+ errx(pCtx, 1, "%s (%d): %*s^", pszFilename, iLine, rc2 - 1, "");
+ }
+ }
+ else if (!fQuiet)
+ errx(pCtx, 1, "%s (%d): Ignoring malformed line!", pszFilename, iLine);
+ }
+ else if (!fQuiet)
+ errx(pCtx, 1, "%s (%d): Ignoring malformed line!", pszFilename, iLine);
+ } /* while more lines */
+
+ fclose(pFile);
+ }
+ else
+ {
+ errx(pCtx, 1, "Failed to open '%s': %s", pszFilename, strerror(errno));
+ rc = 1;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Calculates the MD5 sum for one file and prints it.
+ *
+ * @returns 0 on success, 1 on any kind of failure.
+ * @param pCtx Command context.
+ * @param pszFilename The file to process.
+ * @param fText The mode to open the file in.
+ * @param fQuiet Whether to be quiet or verbose about errors.
+ * @param fManifest Whether to format the output like a fetch manifest.
+ * @param fProgress Whether to show an progress indicator on large files.
+ * @param pOutput Where to write the list. Progress is always written to stdout.
+ */
+static int md5sum_file(PKMKBUILTINCTX pCtx, const char *pszFilename, unsigned fText, unsigned fQuiet, unsigned fProgress,
+ unsigned fManifest, FILE *pOutput)
+{
+ int rc;
+ void *pvFile;
+
+ /*
+ * Calculate and print the MD5 sum for one file.
+ */
+ pvFile = open_file(pszFilename, fText);
+ if (pvFile)
+ {
+ unsigned char Digest[16];
+ KU64 cbFile = 0;
+
+ if (fProgress && pOutput)
+ fprintf(stdout, "%s: ", pszFilename);
+
+ rc = calc_md5sum(pvFile, Digest, fProgress, &cbFile);
+ close_file(pvFile);
+
+ if (fProgress && pOutput)
+ {
+ size_t cch = strlen(pszFilename) + 2;
+ while (cch-- > 0)
+ fputc('\b', stdout);
+ }
+
+ if (!rc)
+ {
+ char szDigest[36];
+ digest_to_string(Digest, szDigest);
+ if (!fManifest)
+ {
+ if (pOutput)
+ fprintf(pOutput, "%s %s%s\n", szDigest, fText ? "" : "*", pszFilename);
+ kmk_builtin_ctx_printf(pCtx, 0, "%s %s%s\n", szDigest, fText ? "" : "*", pszFilename);
+ }
+ else
+ {
+ if (pOutput)
+ fprintf(pOutput, "%s_SIZE := %" KU64_PRI "\n%s_MD5 := %s\n", pszFilename, cbFile, pszFilename, szDigest);
+ kmk_builtin_ctx_printf(pCtx, 0, "%s_SIZE := %" KU64_PRI "\n%s_MD5 := %s\n",
+ pszFilename, cbFile, pszFilename, szDigest);
+ }
+ if (pOutput)
+ fflush(pOutput);
+ }
+ else
+ {
+ if (!fQuiet)
+ errx(pCtx, 1, "Failed to open '%s': %s", pszFilename, strerror(rc));
+ rc = 1;
+ }
+ }
+ else
+ {
+ if (!fQuiet)
+ errx(pCtx, 1, "Failed to open '%s': %s", pszFilename, strerror(errno));
+ rc = 1;
+ }
+ return rc;
+}
+
+
+
+/**
+ * md5sum, calculates and checks the md5sum of files.
+ * Somewhat similar to the GNU coreutil md5sum command.
+ */
+int kmk_builtin_md5sum(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ int i;
+ int rc = 0;
+ int fText = 0;
+ int fBinaryTextOpt = 0;
+ int fQuiet = 0;
+ int fChecking = 0;
+ int fManifest = 0;
+ int fProgress = 0;
+ int fNoMoreOptions = 0;
+ const char *pszOutput = NULL;
+ FILE *pOutput = NULL;
+
+ /*
+ * Print usage if no arguments.
+ */
+ if (argc <= 1)
+ return usage(pCtx, 1);
+
+ /*
+ * Process the arguments, FIFO style.
+ */
+ i = 1;
+ while (i < argc)
+ {
+ char *psz = argv[i];
+ if (!fNoMoreOptions && psz[0] == '-' && psz[1] == '-' && !psz[2])
+ fNoMoreOptions = 1;
+ else if (*psz == '-' && !fNoMoreOptions)
+ {
+ psz++;
+
+ /* convert long options for gnu just for fun */
+ if (*psz == '-')
+ {
+ if (!strcmp(psz, "-binary"))
+ psz = "b";
+ else if (!strcmp(psz, "-text"))
+ psz = "t";
+ else if (!strcmp(psz, "-check"))
+ psz = "c";
+ else if (!strcmp(psz, "-check-file"))
+ psz = "C";
+ else if (!strcmp(psz, "-manifest"))
+ psz = "m";
+ else if (!strcmp(psz, "-output"))
+ psz = "o";
+ else if (!strcmp(psz, "-progress"))
+ psz = "p";
+ else if (!strcmp(psz, "-status"))
+ psz = "q";
+ else if (!strcmp(psz, "-warn"))
+ psz = "w";
+ else if (!strcmp(psz, "-help"))
+ psz = "h";
+ else if (!strcmp(psz, "-version"))
+ psz = "v";
+ }
+
+ /* short options */
+ do
+ {
+ switch (*psz)
+ {
+ case 'c':
+ fChecking = 1;
+ break;
+
+ case 'b':
+ fText = 0;
+ fBinaryTextOpt = 1;
+ break;
+
+ case 't':
+ fText = 1;
+ fBinaryTextOpt = 1;
+ break;
+
+ case 'm':
+ fManifest = 1;
+ break;
+
+ case 'p':
+ fProgress = 1 && isatty(fileno(stdout))
+#ifndef KMK_BUILTIN_STANDALONE
+ && (!pCtx->pOut || !pCtx->pOut->syncout)
+#endif
+ ;
+ break;
+
+ case 'q':
+ fQuiet = 1;
+ break;
+
+ case 'w':
+ /* ignored */
+ break;
+
+ case 'h':
+ usage(pCtx, 0);
+ return 0;
+
+ case 'v':
+ return kbuild_version(argv[0]);
+
+ /*
+ * -C md5 file
+ */
+ case 'C':
+ {
+ const char *pszFilename;
+ const char *pszDigest;
+
+ if (psz[1])
+ pszDigest = &psz[1];
+ else if (i + 1 < argc)
+ pszDigest = argv[++i];
+ else
+ {
+ errx(pCtx, 1, "'-C' is missing the MD5 sum!");
+ return 1;
+ }
+ if (i + 1 < argc)
+ pszFilename = argv[++i];
+ else
+ {
+ errx(pCtx, 1, "'-C' is missing the filename!");
+ return 1;
+ }
+
+ rc |= check_one_file(pCtx, pszFilename, pszDigest, fText, fQuiet, fProgress && !fQuiet);
+ psz = "\0";
+ break;
+ }
+
+ /*
+ * Output file.
+ */
+ case 'o':
+ {
+ if (fChecking)
+ {
+ errx(pCtx, 1, "'-o' cannot be used with -c or -C!");
+ return 1;
+ }
+
+ if (psz[1])
+ pszOutput = &psz[1];
+ else if (i + 1 < argc)
+ pszOutput = argv[++i];
+ else
+ {
+ errx(pCtx, 1, "'-o' is missing the file name!");
+ return 1;
+ }
+
+ psz = "\0";
+ break;
+ }
+
+ default:
+ errx(pCtx, 1, "Invalid option '%c'! (%s)", *psz, argv[i]);
+ return usage(pCtx, 1);
+ }
+ } while (*++psz);
+ }
+ else if (fChecking)
+ rc |= check_files(pCtx, argv[i], fText, fBinaryTextOpt, fQuiet, fProgress && !fQuiet);
+ else
+ {
+ /* lazily open the output if specified. */
+ if (pszOutput)
+ {
+ if (pOutput)
+ fclose(pOutput);
+ pOutput = fopen(pszOutput, "w" KMK_FOPEN_NO_INHERIT_MODE);
+ if (!pOutput)
+ {
+ rc = err(pCtx, 1, "fopen(\"%s\", \"w" KMK_FOPEN_NO_INHERIT_MODE "\") failed", pszOutput);
+ break;
+ }
+ pszOutput = NULL;
+ }
+
+ rc |= md5sum_file(pCtx, argv[i], fText, fQuiet, fProgress && !fQuiet && !fManifest, fManifest, pOutput);
+ }
+ i++;
+ }
+
+ if (pOutput)
+ fclose(pOutput);
+ return rc;
+}
+
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_md5sum", NULL };
+ return kmk_builtin_md5sum(argc, argv, envp, &Ctx);
+}
+#endif
+
+
diff --git a/src/kmk/kmkbuiltin/mkdir.c b/src/kmk/kmkbuiltin/mkdir.c
new file mode 100644
index 0000000..65e961f
--- /dev/null
+++ b/src/kmk/kmkbuiltin/mkdir.c
@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) 1983, 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static char const copyright[] =
+"@(#) Copyright (c) 1983, 1992, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)mkdir.c 8.2 (Berkeley) 1/25/94";
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/bin/mkdir/mkdir.c,v 1.28 2004/04/06 20:06:48 markm Exp $");
+#endif
+
+#define FAKES_NO_GETOPT_H /* bird */
+#include "config.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "err.h"
+#include <errno.h>
+#include <assert.h>
+#ifndef _MSC_VER
+# include <libgen.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef __HAIKU__
+# include <sysexits.h>
+#endif
+#include <unistd.h>
+#ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+#endif
+#include "getopt_r.h"
+#ifdef __HAIKU__
+# include "haikufakes.h"
+#endif
+#ifdef _MSC_VER
+# include <malloc.h>
+# include "mscfakes.h"
+#endif
+#include "kmkbuiltin.h"
+
+
+static struct option long_options[] =
+{
+ { "help", no_argument, 0, 261 },
+ { "version", no_argument, 0, 262 },
+ { 0, 0, 0, 0 },
+};
+
+extern mode_t g_fUMask;
+
+extern void * bsd_setmode(const char *p);
+extern mode_t bsd_getmode(const void *bbox, mode_t omode);
+
+static int build(PKMKBUILTINCTX pCtx, char *, mode_t, int);
+static int usage(PKMKBUILTINCTX pCtx, int fIsErr);
+
+
+int
+kmk_builtin_mkdir(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ struct getopt_state_r gos;
+ int ch, exitval, success, pflag, vflag;
+ mode_t omode, *set = (mode_t *)NULL;
+ char *mode;
+
+ omode = pflag = vflag = 0;
+ mode = NULL;
+
+ getopt_initialize_r(&gos, argc, argv, "m:pv", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1)
+ switch(ch) {
+ case 'm':
+ mode = gos.optarg;
+ break;
+ case 'p':
+ pflag = 1;
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ case 261:
+ usage(pCtx, 0);
+ return 0;
+ case 262:
+ return kbuild_version(argv[0]);
+ case '?':
+ default:
+ return usage(pCtx, 1);
+ }
+
+ argc -= gos.optind;
+ argv += gos.optind;
+ if (argv[0] == NULL)
+ return usage(pCtx, 1);
+
+ if (mode == NULL) {
+ omode = S_IRWXU | S_IRWXG | S_IRWXO;
+ } else {
+ if ((set = bsd_setmode(mode)) == NULL)
+ return errx(pCtx, 1, "invalid file mode: %s", mode);
+ omode = bsd_getmode(set, S_IRWXU | S_IRWXG | S_IRWXO);
+ free(set);
+ }
+
+ for (exitval = 0; *argv != NULL; ++argv) {
+ success = 1;
+ if (pflag) {
+ if (build(pCtx, *argv, omode, vflag))
+ success = 0;
+ } else if (mkdir(*argv, omode) < 0) {
+ if (errno == ENOTDIR || errno == ENOENT)
+ warn(pCtx, "mkdir: %s", dirname(*argv));
+ else
+ warn(pCtx, "mkdir: %s", *argv);
+ success = 0;
+ } else if (vflag)
+ kmk_builtin_ctx_printf(pCtx, 0, "%s\n", *argv);
+
+ if (!success)
+ exitval = 1;
+ /*
+ * The mkdir() and umask() calls both honor only the low
+ * nine bits, so if you try to set a mode including the
+ * sticky, setuid, setgid bits you lose them. Don't do
+ * this unless the user has specifically requested a mode,
+ * as chmod will (obviously) ignore the umask.
+ */
+ if (success && mode != NULL && chmod(*argv, omode) == -1) {
+ warn(pCtx, "chmod: %s", *argv);
+ exitval = 1;
+ }
+ }
+ return exitval;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+mode_t g_fUMask;
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_mkdir", NULL };
+ umask(g_fUMask = umask(0077));
+ return kmk_builtin_mkdir(argc, argv, envp, &Ctx);
+}
+#endif
+
+static int
+build(PKMKBUILTINCTX pCtx, char *path, mode_t omode, int vflag)
+{
+ struct stat sb;
+ mode_t numask, oumask;
+ int first, last, retval;
+ char *p;
+
+ const size_t len = strlen(path);
+ p = alloca(len + 1);
+ path = memcpy(p, path, len + 1);
+
+#if defined(_MSC_VER) || defined(__EMX__)
+ /* correct slashes. */
+ p = strchr(path, '\\');
+ while (p) {
+ *p++ = '/';
+ p = strchr(p, '\\');
+ }
+#endif
+
+ p = path;
+ oumask = 0;
+ retval = 0;
+#if defined(_MSC_VER) || defined(__EMX__)
+ if ( ( (p[0] >= 'A' && p[0] <= 'Z')
+ || (p[0] >= 'a' && p[0] <= 'z'))
+ && p[1] == ':')
+ p += 2; /* skip the drive letter */
+ else if ( p[0] == '/'
+ && p[1] == '/'
+ && p[2] != '/')
+ {
+ /* UNC */
+ p += 2;
+ while (*p && *p != '/') /* server */
+ p++;
+ while (*p == '/')
+ p++;
+ while (*p && *p != '/') /* share */
+ p++;
+ }
+#endif
+ if (p[0] == '/') /* Skip leading '/'. */
+ ++p;
+ for (first = 1, last = 0; !last ; ++p) {
+ if (p[0] == '\0')
+ last = 1;
+ else if (p[0] != '/')
+ continue;
+ *p = '\0';
+ if (!last && p[1] == '\0')
+ last = 1;
+ if (first) {
+ /*
+ * POSIX 1003.2:
+ * For each dir operand that does not name an existing
+ * directory, effects equivalent to those cased by the
+ * following command shall occcur:
+ *
+ * mkdir -p -m $(umask -S),u+wx $(dirname dir) &&
+ * mkdir [-m mode] dir
+ *
+ * We change the user's umask and then restore it,
+ * instead of doing chmod's.
+ */
+#ifdef KMK_BUILTIN_STANDALONE
+ oumask = umask(0077);
+#else
+ oumask = g_fUMask;
+ assert(oumask == umask(g_fUMask));
+#endif
+ numask = oumask & ~(S_IWUSR | S_IXUSR);
+ if (numask != oumask)
+ (void)umask(numask);
+ first = 0;
+ }
+ if (last && oumask != numask)
+ (void)umask(oumask);
+ if (mkdir(path, last ? omode : S_IRWXU | S_IRWXG | S_IRWXO) < 0) {
+ if (errno == EEXIST || errno == EISDIR
+ || errno == ENOSYS /* (solaris crap) */
+ || errno == EACCES /* (ditto) */) {
+ if (stat(path, &sb) < 0) {
+ warn(pCtx, "stat: %s", path);
+ retval = 1;
+ break;
+ }
+ if (!S_ISDIR(sb.st_mode)) {
+ if (last)
+ errno = EEXIST;
+ else
+ errno = ENOTDIR;
+ warn(pCtx, "st_mode: %s", path);
+ retval = 1;
+ break;
+ }
+ } else {
+ warn(pCtx, "mkdir: %s", path);
+ retval = 1;
+ break;
+ }
+ } else if (vflag)
+ kmk_builtin_ctx_printf(pCtx, 0, "%s\n", path);
+ if (!last)
+ *p = '/';
+ }
+ if (!first && !last && oumask != numask)
+ (void)umask(oumask);
+ return (retval);
+}
+
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s [-pv] [-m mode] directory ...\n"
+ " or: %s --help\n"
+ " or: %s --version\n",
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+ return EX_USAGE;
+}
+
diff --git a/src/kmk/kmkbuiltin/mscfakes.c b/src/kmk/kmkbuiltin/mscfakes.c
new file mode 100644
index 0000000..7256cb6
--- /dev/null
+++ b/src/kmk/kmkbuiltin/mscfakes.c
@@ -0,0 +1,839 @@
+/* $Id: mscfakes.c 3387 2020-06-26 16:51:19Z bird $ */
+/** @file
+ * Fake Unix stuff for MSC.
+ */
+
+/*
+ * Copyright (c) 2005-2015 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/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "config.h"
+#include <assert.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <io.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/timeb.h>
+#include "err.h"
+#include "mscfakes.h"
+
+#include "nt/ntutimes.h"
+#undef utimes
+#undef lutimes
+
+#include "console.h"
+
+
+
+/*******************************************************************************
+* Internal Functions *
+*******************************************************************************/
+static BOOL isPipeFd(int fd);
+
+
+/**
+ * Makes corrections to a directory path that ends with a trailing slash.
+ *
+ * @returns temporary buffer to free.
+ * @param ppszPath The path pointer. This is updated when necessary.
+ * @param pfMustBeDir This is set if it must be a directory, otherwise it's cleared.
+ */
+static char *
+msc_fix_path(const char **ppszPath, int *pfMustBeDir)
+{
+ const char *pszPath = *ppszPath;
+ const char *psz;
+ char *pszNew;
+ *pfMustBeDir = 0;
+
+ /*
+ * Skip any compusory trailing slashes
+ */
+ if (pszPath[0] == '/' || pszPath[0] == '\\')
+ {
+ if ( (pszPath[1] == '/' || pszPath[1] == '\\')
+ && pszPath[2] != '/'
+ && pszPath[2] != '\\')
+ /* unc */
+ pszPath += 2;
+ else
+ /* root slash(es) */
+ pszPath++;
+ }
+ else if ( isalpha(pszPath[0])
+ && pszPath[1] == ':')
+ {
+ if (pszPath[2] == '/' || pszPath[2] == '\\')
+ /* drive w/ slash */
+ pszPath += 3;
+ else
+ /* drive relative path. */
+ pszPath += 2;
+ }
+ /* else: relative path, no skipping necessary. */
+
+ /*
+ * Any trailing slashes to drop off?
+ */
+ psz = strchr(pszPath, '\0');
+ if (pszPath <= psz)
+ return NULL;
+ if ( psz[-1] != '/'
+ || psz[-1] != '\\')
+ return NULL;
+
+ /* figure how many, make a copy and strip them off. */
+ while ( psz > pszPath
+ && ( psz[-1] == '/'
+ || psz[-1] == '\\'))
+ psz--;
+ pszNew = strdup(pszPath);
+ pszNew[psz - pszPath] = '\0';
+
+ *pfMustBeDir = 1;
+ *ppszPath = pszNew; /* use this one */
+ return pszNew;
+}
+
+
+int
+birdSetErrno(unsigned dwErr)
+{
+ switch (dwErr)
+ {
+ default:
+ case ERROR_INVALID_FUNCTION: errno = EINVAL; break;
+ case ERROR_FILE_NOT_FOUND: errno = ENOENT; break;
+ case ERROR_PATH_NOT_FOUND: errno = ENOENT; break;
+ case ERROR_TOO_MANY_OPEN_FILES: errno = EMFILE; break;
+ case ERROR_ACCESS_DENIED: errno = EACCES; break;
+ case ERROR_INVALID_HANDLE: errno = EBADF; break;
+ case ERROR_ARENA_TRASHED: errno = ENOMEM; break;
+ case ERROR_NOT_ENOUGH_MEMORY: errno = ENOMEM; break;
+ case ERROR_INVALID_BLOCK: errno = ENOMEM; break;
+ case ERROR_BAD_ENVIRONMENT: errno = E2BIG; break;
+ case ERROR_BAD_FORMAT: errno = ENOEXEC; break;
+ case ERROR_INVALID_ACCESS: errno = EINVAL; break;
+ case ERROR_INVALID_DATA: errno = EINVAL; break;
+ case ERROR_INVALID_DRIVE: errno = ENOENT; break;
+ case ERROR_CURRENT_DIRECTORY: errno = EACCES; break;
+ case ERROR_NOT_SAME_DEVICE: errno = EXDEV; break;
+ case ERROR_NO_MORE_FILES: errno = ENOENT; break;
+ case ERROR_LOCK_VIOLATION: errno = EACCES; break;
+ case ERROR_BAD_NETPATH: errno = ENOENT; break;
+ case ERROR_NETWORK_ACCESS_DENIED: errno = EACCES; break;
+ case ERROR_BAD_NET_NAME: errno = ENOENT; break;
+ case ERROR_FILE_EXISTS: errno = EEXIST; break;
+ case ERROR_CANNOT_MAKE: errno = EACCES; break;
+ case ERROR_FAIL_I24: errno = EACCES; break;
+ case ERROR_INVALID_PARAMETER: errno = EINVAL; break;
+ case ERROR_NO_PROC_SLOTS: errno = EAGAIN; break;
+ case ERROR_DRIVE_LOCKED: errno = EACCES; break;
+ case ERROR_BROKEN_PIPE: errno = EPIPE; break;
+ case ERROR_DISK_FULL: errno = ENOSPC; break;
+ case ERROR_INVALID_TARGET_HANDLE: errno = EBADF; break;
+ case ERROR_WAIT_NO_CHILDREN: errno = ECHILD; break;
+ case ERROR_CHILD_NOT_COMPLETE: errno = ECHILD; break;
+ case ERROR_DIRECT_ACCESS_HANDLE: errno = EBADF; break;
+ case ERROR_NEGATIVE_SEEK: errno = EINVAL; break;
+ case ERROR_SEEK_ON_DEVICE: errno = EACCES; break;
+ case ERROR_DIR_NOT_EMPTY: errno = ENOTEMPTY; break;
+ case ERROR_NOT_LOCKED: errno = EACCES; break;
+ case ERROR_BAD_PATHNAME: errno = ENOENT; break;
+ case ERROR_MAX_THRDS_REACHED: errno = EAGAIN; break;
+ case ERROR_LOCK_FAILED: errno = EACCES; break;
+ case ERROR_ALREADY_EXISTS: errno = EEXIST; break;
+ case ERROR_FILENAME_EXCED_RANGE: errno = ENOENT; break;
+ case ERROR_NESTING_NOT_ALLOWED: errno = EAGAIN; break;
+#ifdef EMLINK
+ case ERROR_TOO_MANY_LINKS: errno = EMLINK; break;
+#endif
+ }
+
+ return -1;
+}
+
+char *dirname(char *path)
+{
+ /** @todo later */
+ return path;
+}
+
+
+int lchmod(const char *pszPath, mode_t mode)
+{
+ int rc = 0;
+ int fMustBeDir;
+ char *pszPathFree = msc_fix_path(&pszPath, &fMustBeDir);
+
+ /*
+ * Get the current attributes
+ */
+ DWORD fAttr = GetFileAttributes(pszPath);
+ if (fAttr == INVALID_FILE_ATTRIBUTES)
+ rc = birdSetErrno(GetLastError());
+ else if (fMustBeDir & !(fAttr & FILE_ATTRIBUTE_DIRECTORY))
+ {
+ errno = ENOTDIR;
+ rc = -1;
+ }
+ else
+ {
+ /*
+ * Modify the attributes and try set them.
+ */
+ if (mode & _S_IWRITE)
+ {
+ fAttr &= ~FILE_ATTRIBUTE_READONLY;
+ if (fAttr == 0)
+ fAttr = FILE_ATTRIBUTE_NORMAL;
+ }
+ else
+ {
+ fAttr &= ~FILE_ATTRIBUTE_NORMAL;
+ fAttr |= FILE_ATTRIBUTE_READONLY;
+ }
+ if (!SetFileAttributes(pszPath, fAttr))
+ rc = birdSetErrno(GetLastError());
+ }
+
+ if (pszPathFree)
+ {
+ int saved_errno = errno;
+ free(pszPathFree);
+ errno = saved_errno;
+ }
+ return rc;
+}
+
+
+int msc_chmod(const char *pszPath, mode_t mode)
+{
+ int rc = 0;
+ int fMustBeDir;
+ char *pszPathFree = msc_fix_path(&pszPath, &fMustBeDir);
+
+ /*
+ * Get the current attributes.
+ */
+ DWORD fAttr = GetFileAttributes(pszPath);
+ if (fAttr == INVALID_FILE_ATTRIBUTES)
+ rc = birdSetErrno(GetLastError());
+ else if (fMustBeDir & !(fAttr & FILE_ATTRIBUTE_DIRECTORY))
+ {
+ errno = ENOTDIR;
+ rc = -1;
+ }
+ else if (fAttr & FILE_ATTRIBUTE_REPARSE_POINT)
+ {
+ errno = ENOSYS; /** @todo resolve symbolic link / rewrite to NtSetInformationFile. */
+ rc = -1;
+ }
+ else
+ {
+ /*
+ * Modify the attributes and try set them.
+ */
+ if (mode & _S_IWRITE)
+ {
+ fAttr &= ~FILE_ATTRIBUTE_READONLY;
+ if (fAttr == 0)
+ fAttr = FILE_ATTRIBUTE_NORMAL;
+ }
+ else
+ {
+ fAttr &= ~FILE_ATTRIBUTE_NORMAL;
+ fAttr |= FILE_ATTRIBUTE_READONLY;
+ }
+ if (!SetFileAttributes(pszPath, fAttr))
+ rc = birdSetErrno(GetLastError());
+ }
+
+ if (pszPathFree)
+ {
+ int saved_errno = errno;
+ free(pszPathFree);
+ errno = saved_errno;
+ }
+ return rc;
+}
+
+
+typedef BOOL (WINAPI *PFNCREATEHARDLINKA)(LPCSTR, LPCSTR, LPSECURITY_ATTRIBUTES);
+int link(const char *pszDst, const char *pszLink)
+{
+ static PFNCREATEHARDLINKA s_pfnCreateHardLinkA = NULL;
+ static int s_fTried = FALSE;
+
+ /* The API was introduced in Windows 2000, so resolve it dynamically. */
+ if (!s_pfnCreateHardLinkA)
+ {
+ if (!s_fTried)
+ {
+ HMODULE hmod = LoadLibrary("KERNEL32.DLL");
+ if (hmod)
+ *(FARPROC *)&s_pfnCreateHardLinkA = GetProcAddress(hmod, "CreateHardLinkA");
+ s_fTried = TRUE;
+ }
+ if (!s_pfnCreateHardLinkA)
+ {
+ errno = ENOSYS;
+ return -1;
+ }
+ }
+
+ if (s_pfnCreateHardLinkA(pszLink, pszDst, NULL))
+ return 0;
+ return birdSetErrno(GetLastError());
+}
+
+
+int mkdir_msc(const char *path, mode_t mode)
+{
+ int rc = (mkdir)(path);
+ if (rc)
+ {
+ size_t len = strlen(path);
+ if (len > 0 && (path[len - 1] == '/' || path[len - 1] == '\\'))
+ {
+ char *str = strdup(path);
+ while (len > 0 && (str[len - 1] == '/' || str[len - 1] == '\\'))
+ str[--len] = '\0';
+ rc = (mkdir)(str);
+ free(str);
+ }
+ }
+ return rc;
+}
+
+int rmdir_msc(const char *path)
+{
+ int rc = (rmdir)(path);
+ if (rc)
+ {
+ size_t len = strlen(path);
+ if (len > 0 && (path[len - 1] == '/' || path[len - 1] == '\\'))
+ {
+ char *str = strdup(path);
+ while (len > 0 && (str[len - 1] == '/' || str[len - 1] == '\\'))
+ str[--len] = '\0';
+ rc = (rmdir)(str);
+ free(str);
+ }
+ }
+ return rc;
+}
+
+
+static int doname(char *pszX, char *pszEnd)
+{
+ static char s_szChars[] = "Xabcdefghijklmnopqrstuwvxyz1234567890";
+ int rc = 0;
+ do
+ {
+ char ch;
+
+ pszEnd++;
+ ch = *(strchr(s_szChars, *pszEnd) + 1);
+ if (ch)
+ {
+ *pszEnd = ch;
+ return 0;
+ }
+ *pszEnd = 'a';
+ } while (pszEnd != pszX);
+ return 1;
+}
+
+
+int mkstemp(char *temp)
+{
+ char *pszX = strchr(temp, 'X');
+ char *pszEnd = strchr(pszX, '\0');
+ int cTries = 1000;
+ while (--cTries > 0)
+ {
+ int fd;
+ if (doname(pszX, pszEnd))
+ return -1;
+ fd = open(temp, _O_EXCL | _O_CREAT | _O_BINARY | _O_RDWR | KMK_OPEN_NO_INHERIT, 0777);
+ if (fd >= 0)
+ return fd;
+ }
+ return -1;
+}
+
+
+/** Unix to DOS. */
+static char *fix_slashes(char *psz)
+{
+ char *pszRet = psz;
+ for (; *psz; psz++)
+ if (*psz == '/')
+ *psz = '\\';
+ return pszRet;
+}
+
+
+/** Calcs the SYMBOLIC_LINK_FLAG_DIRECTORY flag for CreatesymbolcLink. */
+static DWORD is_directory(const char *pszPath, const char *pszRelativeTo)
+{
+ size_t cchPath = strlen(pszPath);
+ struct stat st;
+ if (cchPath > 0 && pszPath[cchPath - 1] == '\\' || pszPath[cchPath - 1] == '/')
+ return 1; /* SYMBOLIC_LINK_FLAG_DIRECTORY */
+
+ if (stat(pszPath, &st))
+ {
+ size_t cchRelativeTo = strlen(pszRelativeTo);
+ char *psz = malloc(cchPath + cchRelativeTo + 4);
+ memcpy(psz, pszRelativeTo, cchRelativeTo);
+ memcpy(psz + cchRelativeTo, "\\", 1);
+ memcpy(psz + cchRelativeTo + 1, pszPath, cchPath + 1);
+ if (stat(pszPath, &st))
+ st.st_mode = _S_IFREG;
+ free(psz);
+ }
+
+ return (st.st_mode & _S_IFMT) == _S_IFDIR ? 1 : 0;
+}
+
+
+int symlink(const char *pszDst, const char *pszLink)
+{
+ static BOOLEAN (WINAPI *s_pfnCreateSymbolicLinkA)(LPCSTR, LPCSTR, DWORD) = 0;
+ static BOOL s_fTried = FALSE;
+
+ if (!s_fTried)
+ {
+ HMODULE hmod = LoadLibrary("KERNEL32.DLL");
+ if (hmod)
+ *(FARPROC *)&s_pfnCreateSymbolicLinkA = GetProcAddress(hmod, "CreateSymbolicLinkA");
+ s_fTried = TRUE;
+ }
+
+ if (s_pfnCreateSymbolicLinkA)
+ {
+ char *pszDstCopy = fix_slashes(strdup(pszDst));
+ char *pszLinkCopy = fix_slashes(strdup(pszLink));
+ BOOLEAN fRc = s_pfnCreateSymbolicLinkA(pszLinkCopy, pszDstCopy,
+ is_directory(pszDstCopy, pszLinkCopy));
+ DWORD err = GetLastError();
+ free(pszDstCopy);
+ free(pszLinkCopy);
+ if (fRc)
+ return 0;
+ switch (err)
+ {
+ case ERROR_NOT_SUPPORTED: errno = ENOSYS; break;
+ case ERROR_ALREADY_EXISTS:
+ case ERROR_FILE_EXISTS: errno = EEXIST; break;
+ case ERROR_DIRECTORY: errno = ENOTDIR; break;
+ case ERROR_ACCESS_DENIED:
+ case ERROR_PRIVILEGE_NOT_HELD: errno = EPERM; break;
+ default: errno = EINVAL; break;
+ }
+ return -1;
+ }
+
+ fprintf(stderr, "warning: symlink() is available on this version of Windows!\n");
+ errno = ENOSYS;
+ return -1;
+}
+
+
+#if _MSC_VER < 1400
+int snprintf(char *buf, size_t size, const char *fmt, ...)
+{
+ int cch;
+ va_list args;
+ va_start(args, fmt);
+ cch = vsprintf(buf, fmt, args);
+ va_end(args);
+ return cch;
+}
+#endif
+
+
+/* We override the libc write function (in our modules only, unfortunately) so
+ we can kludge our way around a ENOSPC problem observed on build servers
+ capturing STDOUT and STDERR via pipes. Apparently this may happen when the
+ pipe buffer is full, even with the mscfake_init hack in place.
+
+ XXX: Probably need to hook into fwrite as well. */
+ssize_t msc_write(int fd, const void *pvSrc, size_t cbSrc)
+{
+#define MSC_WRITE_MAX_CHUNK (UINT_MAX / 32)
+ ssize_t cbRet;
+ if (cbSrc <= MSC_WRITE_MAX_CHUNK)
+ {
+ /* Console output optimization: */
+ if (cbSrc > 0 && is_console(fd))
+ return maybe_con_write(fd, pvSrc, cbSrc);
+
+#ifndef MSC_WRITE_TEST
+ cbRet = _write(fd, pvSrc, (unsigned int)cbSrc);
+#else
+ cbRet = -1; errno = ENOSPC;
+#endif
+ if (cbRet < 0)
+ {
+ /* ENOSPC on pipe kludge. */
+ unsigned int cbLimit;
+ int cSinceLastSuccess;
+
+ if (cbSrc == 0)
+ return 0;
+ if (errno != ENOSPC)
+ return -1;
+#ifndef MSC_WRITE_TEST
+ if (!isPipeFd(fd))
+ {
+ errno = ENOSPC;
+ return -1;
+ }
+#endif
+
+ /* Likely a full pipe buffer, try write smaller amounts and do some
+ sleeping inbetween each unsuccessful one. */
+ cbLimit = (unsigned)(cbSrc / 4);
+ if (cbLimit < 4)
+ cbLimit = 4;
+ else if (cbLimit > 512)
+ cbLimit = 512;
+ cSinceLastSuccess = 0;
+ cbRet = 0;
+#ifdef MSC_WRITE_TEST
+ cbLimit = 4;
+#endif
+
+ while ((ssize_t)cbSrc > 0)
+ {
+ unsigned int cbAttempt = cbSrc > cbLimit ? cbLimit : (unsigned int)cbSrc;
+ ssize_t cbActual = _write(fd, pvSrc, cbAttempt);
+ if (cbActual > 0)
+ {
+ /* For some reason, it seems like we cannot trust _write to return
+ a number that's less or equal to the number of bytes we passed
+ in to the call. (Also reason for signed check in loop.) */
+ if (cbActual > cbAttempt)
+ cbActual = cbAttempt;
+
+ pvSrc = (char *)pvSrc + cbActual;
+ cbSrc -= cbActual;
+ cbRet += cbActual;
+#ifndef MSC_WRITE_TEST
+ if (cbLimit < 32)
+ cbLimit = 32;
+#endif
+ cSinceLastSuccess = 0;
+ }
+ else if (errno != ENOSPC)
+ return -1;
+ else
+ {
+ /* Delay for about 30 seconds, then just give up. */
+ cSinceLastSuccess++;
+ if (cSinceLastSuccess > 1860)
+ return -1;
+ if (cSinceLastSuccess <= 2)
+ Sleep(0);
+ else if (cSinceLastSuccess <= 66)
+ {
+ if (cbLimit >= 8)
+ cbLimit /= 2; /* Just in case the pipe buffer is very very small. */
+ Sleep(1);
+ }
+ else
+ Sleep(16);
+ }
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Type limit exceeded. Split the job up.
+ */
+ cbRet = 0;
+ while (cbSrc > 0)
+ {
+ size_t cbToWrite = cbSrc > MSC_WRITE_MAX_CHUNK ? MSC_WRITE_MAX_CHUNK : cbSrc;
+ ssize_t cbWritten = msc_write(fd, pvSrc, cbToWrite);
+ if (cbWritten > 0)
+ {
+ pvSrc = (char *)pvSrc + (size_t)cbWritten;
+ cbSrc -= (size_t)cbWritten;
+ cbRet += (size_t)cbWritten;
+ }
+ else if (cbWritten == 0 || cbRet > 0)
+ break;
+ else
+ return -1;
+ }
+ }
+ return cbRet;
+}
+
+ssize_t writev(int fd, const struct iovec *vector, int count)
+{
+ ssize_t size = 0;
+ if (count > 0)
+ {
+ int i;
+
+ /* To get consistent console output, we must try combine the segments
+ when outputing to the console. */
+ if (count > 1 && is_console(fd))
+ {
+ char *pbTmp;
+ ssize_t cbTotal;
+ if (count == 1)
+ return maybe_con_write(fd, vector[0].iov_base, (int)vector[0].iov_len);
+
+ cbTotal = 0;
+ for (i = 0; i < count; i++)
+ cbTotal += vector[i].iov_len;
+ pbTmp = malloc(cbTotal);
+ if (pbTmp)
+ {
+ char *pbCur = pbTmp;
+ for (i = 0; i < count; i++)
+ {
+ memcpy(pbCur, vector[i].iov_base, vector[i].iov_len);
+ pbCur += vector[i].iov_len;
+ }
+ size = maybe_con_write(fd, pbTmp, cbTotal);
+ free(pbTmp);
+ return size;
+ }
+
+ /* fall back on segment by segment output. */
+ }
+
+ for (i = 0; i < count; i++)
+ {
+ int cb = msc_write(fd, vector[i].iov_base, (int)vector[i].iov_len);
+ if (cb < 0)
+ return cb;
+ size += cb;
+ }
+ }
+ return size;
+}
+
+
+intmax_t strtoimax(const char *nptr, char **endptr, int base)
+{
+ if (*nptr != '-')
+ return _strtoui64(nptr, endptr, base);
+ return -(intmax_t)_strtoui64(nptr + 1, endptr, base);
+}
+
+
+uintmax_t strtoumax(const char *nptr, char **endptr, int base)
+{
+ return _strtoui64(nptr, endptr, base);
+}
+
+
+int asprintf(char **strp, const char *fmt, ...)
+{
+ int rc;
+ va_list va;
+ va_start(va, fmt);
+ rc = vasprintf(strp, fmt, va);
+ va_end(va);
+ return rc;
+}
+
+
+int vasprintf(char **strp, const char *fmt, va_list va)
+{
+ int rc;
+ char *psz;
+ size_t cb = 1024;
+
+ *strp = NULL;
+ for (;;)
+ {
+ va_list va2;
+
+ psz = malloc(cb);
+ if (!psz)
+ return -1;
+
+#ifdef va_copy
+ va_copy(va2, va);
+ rc = vsnprintf(psz, cb, fmt, va2);
+ va_end(vaCopy);
+#else
+ va2 = va;
+ rc = vsnprintf(psz, cb, fmt, va2);
+#endif
+ if (rc < 0 || (size_t)rc < cb)
+ break;
+ cb *= 2;
+ free(psz);
+ }
+
+ *strp = psz;
+ return rc;
+}
+
+
+int utimes(const char *pszPath, const struct msc_timeval *paTimes)
+{
+ if (paTimes)
+ {
+ BirdTimeVal_T aTimes[2];
+ aTimes[0].tv_sec = paTimes[0].tv_sec;
+ aTimes[0].tv_usec = paTimes[0].tv_usec;
+ aTimes[1].tv_sec = paTimes[1].tv_sec;
+ aTimes[1].tv_usec = paTimes[1].tv_usec;
+ return birdUtimes(pszPath, aTimes);
+ }
+ return birdUtimes(pszPath, NULL);
+}
+
+
+int lutimes(const char *pszPath, const struct msc_timeval *paTimes)
+{
+ if (paTimes)
+ {
+ BirdTimeVal_T aTimes[2];
+ aTimes[0].tv_sec = paTimes[0].tv_sec;
+ aTimes[0].tv_usec = paTimes[0].tv_usec;
+ aTimes[1].tv_sec = paTimes[1].tv_sec;
+ aTimes[1].tv_usec = paTimes[1].tv_usec;
+ return birdUtimes(pszPath, aTimes);
+ }
+ return birdUtimes(pszPath, NULL);
+}
+
+
+int gettimeofday(struct msc_timeval *pNow, void *pvIgnored)
+{
+ struct __timeb64 Now;
+ int rc = _ftime64_s(&Now);
+ if (rc == 0)
+ {
+ pNow->tv_sec = Now.time;
+ pNow->tv_usec = Now.millitm * 1000;
+ return 0;
+ }
+ errno = rc;
+ return -1;
+}
+
+
+struct tm *localtime_r(const __time64_t *pNow, struct tm *pResult)
+{
+ int rc = _localtime64_s(pResult, pNow);
+ if (rc == 0)
+ return pResult;
+ errno = rc;
+ return NULL;
+}
+
+
+__time64_t timegm(struct tm *pNow)
+{
+ return _mkgmtime64(pNow);
+}
+
+
+/**
+ * Checks if the given file descriptor is a pipe or not.
+ *
+ * @returns TRUE if pipe, FALSE if not.
+ * @param fd The libc file descriptor number.
+ */
+static BOOL isPipeFd(int fd)
+{
+ /* Is pipe? */
+ HANDLE hFile = (HANDLE)_get_osfhandle(fd);
+ if (hFile != INVALID_HANDLE_VALUE)
+ {
+ DWORD fType = GetFileType(hFile);
+ fType &= ~FILE_TYPE_REMOTE;
+ if (fType == FILE_TYPE_PIPE)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/**
+ * This is a kludge to make pipe handles blocking.
+ *
+ * @returns TRUE if it's now blocking, FALSE if not a pipe or we failed to fix
+ * the blocking mode.
+ * @param fd The libc file descriptor number.
+ */
+static BOOL makePipeBlocking(int fd)
+{
+ if (isPipeFd(fd))
+ {
+ /* Try fix it. */
+ HANDLE hFile = (HANDLE)_get_osfhandle(fd);
+ DWORD fState = 0;
+ if (GetNamedPipeHandleState(hFile, &fState, NULL, NULL, NULL, NULL, 0))
+ {
+ fState &= ~PIPE_NOWAIT;
+ fState |= PIPE_WAIT;
+ if (SetNamedPipeHandleState(hFile, &fState, NULL, NULL))
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+
+/**
+ * Initializes the msc fake stuff.
+ * @returns 0 on success (non-zero would indicate failure, see rterr.h).
+ */
+int mscfake_init(void)
+{
+ /*
+ * Kludge against _write returning ENOSPC on non-blocking pipes.
+ */
+ makePipeBlocking(STDOUT_FILENO);
+ makePipeBlocking(STDERR_FILENO);
+
+ return 0;
+}
+
+/*
+ * Do this before main is called.
+ */
+#pragma section(".CRT$XIA", read)
+#pragma section(".CRT$XIU", read)
+#pragma section(".CRT$XIZ", read)
+typedef int (__cdecl *PFNCRTINIT)(void);
+static __declspec(allocate(".CRT$XIU")) PFNCRTINIT g_MscFakeInitVectorEntry = mscfake_init;
+
diff --git a/src/kmk/kmkbuiltin/mscfakes.h b/src/kmk/kmkbuiltin/mscfakes.h
new file mode 100644
index 0000000..95f76b2
--- /dev/null
+++ b/src/kmk/kmkbuiltin/mscfakes.h
@@ -0,0 +1,183 @@
+/* $Id: mscfakes.h 3213 2018-03-30 21:03:28Z bird $ */
+/** @file
+ * Unix fakes for MSC.
+ */
+
+/*
+ * Copyright (c) 2005-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/>
+ *
+ */
+
+#ifndef ___mscfakes_h
+#define ___mscfakes_h
+#ifdef _MSC_VER
+
+#define timeval windows_timeval
+
+/* Include the config file (kmk's) so we don't need to duplicate stuff from it here. */
+#include "config.h"
+
+#include <io.h>
+#include <direct.h>
+#include <time.h>
+#include <stdarg.h>
+#include <malloc.h>
+#ifndef FAKES_NO_GETOPT_H
+# include "getopt.h"
+#endif
+#ifndef MSCFAKES_NO_WINDOWS_H
+# include <Windows.h>
+#endif
+
+#include <sys/stat.h>
+#include <io.h>
+#include <direct.h>
+#include "nt/ntstat.h"
+#include "nt/ntunlink.h"
+#ifdef MSC_DO_64_BIT_IO
+# if _MSC_VER >= 1400 /* We want 64-bit file lengths here when possible. */
+# define off_t __int64
+# define lseek _lseeki64
+# endif
+#endif
+
+#undef timeval
+
+#undef PATH_MAX
+#define PATH_MAX _MAX_PATH
+#undef MAXPATHLEN
+#define MAXPATHLEN _MAX_PATH
+
+#define EX_OK 0
+#define EX_OSERR 1
+#define EX_NOUSER 1
+#define EX_USAGE 1
+
+#define STDIN_FILENO 0
+#define STDOUT_FILENO 1
+#define STDERR_FILENO 2
+
+#define F_OK 0
+#define X_OK 1
+#define W_OK 2
+#define R_OK 4
+
+#define EFTYPE EINVAL
+
+#define _PATH_DEVNULL "/dev/null"
+
+#ifndef MAX
+# define MAX(a,b) ((a) >= (b) ? (a) : (b))
+#endif
+
+typedef int mode_t;
+typedef unsigned short nlink_t;
+#if 0 /* found in config.h */
+typedef unsigned short uid_t;
+typedef unsigned short gid_t;
+#endif
+typedef intptr_t ssize_t;
+typedef unsigned long u_long;
+typedef unsigned int u_int;
+typedef unsigned short u_short;
+
+#if _MSC_VER >= 1600
+# include <stdint.h>
+#else
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned int uint32_t;
+typedef signed char int8_t;
+typedef signed short int16_t;
+typedef signed int int32_t;
+#endif
+
+struct msc_timeval
+{
+ __time64_t tv_sec;
+ long tv_usec;
+};
+#define timeval msc_timeval
+
+struct iovec
+{
+ char *iov_base;
+ size_t iov_len;
+};
+
+typedef __int64 intmax_t;
+#if 0 /* found in config.h */
+typedef unsigned __int64 uintmax_t;
+#endif
+
+#define chown(path, uid, gid) 0 /** @todo implement fchmod! */
+char *dirname(char *path);
+#define fsync(fd) 0
+#define fchown(fd,uid,gid) 0
+#define fchmod(fd, mode) 0 /** @todo implement fchmod! */
+#define geteuid() 0
+#define getegid() 0
+int lchmod(const char *path, mode_t mode);
+int msc_chmod(const char *path, mode_t mode);
+#define chmod msc_chmod
+#define lchown(path, uid, gid) chown(path, uid, gid)
+int link(const char *pszDst, const char *pszLink);
+int mkdir_msc(const char *path, mode_t mode);
+#define mkdir(path, mode) mkdir_msc(path, mode)
+#define mkfifo(path, mode) -1
+#define mknod(path, mode, devno) -1
+int mkstemp(char *temp);
+#define readlink(link, buf, size) -1
+#define reallocf(old, size) realloc(old, size)
+int rmdir_msc(const char *path);
+#define rmdir(path) rmdir_msc(path)
+intmax_t strtoimax(const char *nptr, char **endptr, int base);
+uintmax_t strtoumax(const char *nptr, char **endptr, int base);
+#define strtoll(a,b,c) strtoimax(a,b,c)
+#define strtoull(a,b,c) strtoumax(a,b,c)
+int asprintf(char **strp, const char *fmt, ...);
+int vasprintf(char **strp, const char *fmt, va_list ap);
+#if _MSC_VER < 1400
+int snprintf(char *buf, size_t size, const char *fmt, ...);
+#else
+#define snprintf _snprintf
+#endif
+int symlink(const char *pszDst, const char *pszLink);
+int utimes(const char *pszPath, const struct msc_timeval *paTimes);
+int lutimes(const char *pszPath, const struct msc_timeval *paTimes);
+ssize_t writev(int fd, const struct iovec *vector, int count);
+
+int gettimeofday(struct msc_timeval *pNow, void *pvIgnored);
+struct tm *localtime_r(const __time64_t *pNow, struct tm *pResult);
+__time64_t timegm(struct tm *pNow);
+#undef mktime
+#define mktime _mktime64
+
+/* bird write ENOSPC hacks. */
+#undef write
+#define write msc_write
+ssize_t msc_write(int fd, const void *pvSrc, size_t cbSrc);
+
+/*
+ * MSC fake internals / helpers.
+ */
+int birdSetErrno(unsigned dwErr);
+
+#endif /* _MSC_VER */
+#endif
+
diff --git a/src/kmk/kmkbuiltin/mv.c b/src/kmk/kmkbuiltin/mv.c
new file mode 100644
index 0000000..7db962c
--- /dev/null
+++ b/src/kmk/kmkbuiltin/mv.c
@@ -0,0 +1,529 @@
+/*-
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ken Smith of The State University of New York at Buffalo.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static char const copyright[] =
+"@(#) Copyright (c) 1989, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)mv.c 8.2 (Berkeley) 4/2/94";
+#endif /* not lint */
+#endif
+#if 0
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/bin/mv/mv.c,v 1.46 2005/09/05 04:36:08 csjp Exp $");
+#endif
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define FAKES_NO_GETOPT_H /* bird */
+#include "config.h"
+#include <sys/types.h>
+#ifndef _MSC_VER
+# ifdef CROSS_DEVICE_MOVE
+# include <sys/acl.h>
+# endif
+# include <sys/param.h>
+# include <sys/time.h>
+# include <sys/wait.h>
+# if !defined(__HAIKU__) && !defined(__gnu_hurd__)
+# include <sys/mount.h>
+# endif
+#endif
+#include <sys/stat.h>
+
+#include "err.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <limits.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef __HAIKU__
+# include <sysexits.h>
+#endif
+#include <unistd.h>
+#include "getopt_r.h"
+#ifdef __sun__
+# include "solfakes.h"
+#endif
+#ifdef __HAIKU__
+# include "haikufakes.h"
+#endif
+#ifdef _MSC_VER
+# include "mscfakes.h"
+#endif
+#include "kmkbuiltin.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct MVINSTANCE
+{
+ PKMKBUILTINCTX pCtx;
+ int fflg, iflg, nflg, vflg;
+} MVINSTANCE;
+typedef MVINSTANCE *PMVINSTANCE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static struct option long_options[] =
+{
+ { "help", no_argument, 0, 261 },
+ { "version", no_argument, 0, 262 },
+ { 0, 0, 0, 0 },
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+extern void bsd_strmode(mode_t mode, char *p); /* strmode.c */
+
+static int do_move(PMVINSTANCE, char *, char *);
+#if 0 // def CROSS_DEVICE_MOVE
+static int fastcopy(char *, char *, struct stat *);
+static int copy(char *, char *);
+#endif
+static int usage(PKMKBUILTINCTX, int);
+
+
+int
+kmk_builtin_mv(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ MVINSTANCE This;
+ struct getopt_state_r gos;
+ size_t baselen, len;
+ int rval;
+ char *p, *endp;
+ struct stat sb;
+ int ch;
+ char path[PATH_MAX];
+
+ /* Initialize instance. */
+ This.pCtx = pCtx;
+ This.fflg = 0;
+ This.iflg = 0;
+ This.nflg = 0;
+ This.vflg = 0;
+
+ getopt_initialize_r(&gos, argc, argv, "finv", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1)
+ switch (ch) {
+ case 'i':
+ This.iflg = 1;
+ This.fflg = This.nflg = 0;
+ break;
+ case 'f':
+ This.fflg = 1;
+ This.iflg = This.nflg = 0;
+ break;
+ case 'n':
+ This.nflg = 1;
+ This.fflg = This.iflg = 0;
+ break;
+ case 'v':
+ This.vflg = 1;
+ break;
+ case 261:
+ usage(pCtx, 0);
+ return 0;
+ case 262:
+ return kbuild_version(argv[0]);
+ default:
+ return usage(pCtx, 1);
+ }
+ argc -= gos.optind;
+ argv += gos.optind;
+
+ if (argc < 2)
+ return usage(pCtx, 1);
+
+ /*
+ * If the stat on the target fails or the target isn't a directory,
+ * try the move. More than 2 arguments is an error in this case.
+ */
+ if (stat(argv[argc - 1], &sb) || !S_ISDIR(sb.st_mode)) {
+ if (argc > 2)
+ return usage(pCtx, 1);
+ return do_move(&This, argv[0], argv[1]);
+ }
+
+ /* It's a directory, move each file into it. */
+ baselen = strlen(argv[argc - 1]);
+ if (baselen > sizeof(path) - 1)
+ return errx(pCtx, 1, "%s: destination pathname too long", *argv);
+ memcpy(path, argv[argc - 1], baselen);
+ endp = &path[baselen];
+ *endp = '\0';
+#if defined(_MSC_VER) || defined(__EMX__)
+ if (!baselen || (*(endp - 1) != '/' && *(endp - 1) != '\\' && *(endp - 1) != ':')) {
+#else
+ if (!baselen || *(endp - 1) != '/') {
+#endif
+ *endp++ = '/';
+ ++baselen;
+ }
+ for (rval = 0; --argc; ++argv) {
+ /*
+ * Find the last component of the source pathname. It
+ * may have trailing slashes.
+ */
+ p = *argv + strlen(*argv);
+#if defined(_MSC_VER) || defined(__EMX__)
+ while (p != *argv && (p[-1] == '/' || p[-1] == '\\'))
+ --p;
+ while (p != *argv && p[-1] != '/' && p[-1] != '/' && p[-1] != ':')
+ --p;
+#else
+ while (p != *argv && p[-1] == '/')
+ --p;
+ while (p != *argv && p[-1] != '/')
+ --p;
+#endif
+
+ if ((baselen + (len = strlen(p))) >= PATH_MAX) {
+ warnx(pCtx, "%s: destination pathname too long", *argv);
+ rval = 1;
+ } else {
+ memmove(endp, p, (size_t)len + 1);
+ if (do_move(&This, *argv, path))
+ rval = 1;
+ }
+ }
+ return rval;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_mv", NULL };
+ return kmk_builtin_mv(argc, argv, envp, &Ctx);
+}
+#endif
+
+static int
+do_move(PMVINSTANCE pThis, char *from, char *to)
+{
+ struct stat sb;
+ int ask, ch, first;
+ char modep[15];
+
+ /*
+ * Check access. If interactive and file exists, ask user if it
+ * should be replaced. Otherwise if file exists but isn't writable
+ * make sure the user wants to clobber it.
+ */
+ if (!pThis->fflg && !access(to, F_OK)) {
+
+ /* prompt only if source exist */
+ if (lstat(from, &sb) == -1) {
+ warn(pThis->pCtx, "%s", from);
+ return (1);
+ }
+
+#define YESNO "(y/n [n]) "
+ ask = 0;
+ if (pThis->nflg) {
+ if (pThis->vflg)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s not overwritten\n", to);
+ return (0);
+ } else if (pThis->iflg) {
+ (void)fprintf(stderr, "overwrite %s? %s", to, YESNO);
+ ask = 1;
+ } else if (access(to, W_OK) && !stat(to, &sb)) {
+ bsd_strmode(sb.st_mode, modep);
+#if 0 /* probably not thread safe, also BSDism. */
+ (void)fprintf(stderr, "override %s%s%s/%s for %s? %s",
+ modep + 1, modep[9] == ' ' ? "" : " ",
+ user_from_uid((unsigned long)sb.st_uid, 0),
+ group_from_gid((unsigned long)sb.st_gid, 0), to, YESNO);
+#else
+ (void)fprintf(stderr, "override %s%s%lu/%lu for %s? %s",
+ modep + 1, modep[9] == ' ' ? "" : " ",
+ (unsigned long)sb.st_uid, (unsigned long)sb.st_gid,
+ to, YESNO);
+#endif
+ ask = 1;
+ }
+ if (ask) {
+ fflush(stderr);
+ first = ch = getchar();
+ while (ch != '\n' && ch != EOF)
+ ch = getchar();
+ if (first != 'y' && first != 'Y') {
+ kmk_builtin_ctx_printf(pThis->pCtx, 1, "not overwritten\n");
+ return (0);
+ }
+ }
+ }
+ if (!rename(from, to)) {
+ if (pThis->vflg)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s -> %s\n", from, to);
+ return (0);
+ }
+#ifdef _MSC_VER
+ if (errno == EEXIST) {
+ remove(to);
+ if (!rename(from, to)) {
+ if (pThis->vflg)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s -> %s\n", from, to);
+ return (0);
+ }
+ }
+#endif
+
+ if (errno == EXDEV) {
+#if 1 //ndef CROSS_DEVICE_MOVE
+ warnx(pThis->pCtx, "cannot move `%s' to a different device: `%s'", from, to);
+ return (1);
+#else
+ struct statfs sfs;
+ char path[PATH_MAX];
+
+ /*
+ * If the source is a symbolic link and is on another
+ * filesystem, it can be recreated at the destination.
+ */
+ if (lstat(from, &sb) == -1) {
+ warn(pThis->pCtx, "%s", from);
+ return (1);
+ }
+ if (!S_ISLNK(sb.st_mode)) {
+ /* Can't mv(1) a mount point. */
+ if (realpath(from, path) == NULL) {
+ warnx(pThis->pCtx, "cannot resolve %s: %s", from, path);
+ return (1);
+ }
+ if (!statfs(path, &sfs) &&
+ !strcmp(path, sfs.f_mntonname)) {
+ warnx(pThis->pCtx, "cannot rename a mount point");
+ return (1);
+ }
+ }
+#endif
+ } else {
+ warn(pThis->pCtx, "rename %s to %s", from, to);
+ return (1);
+ }
+
+#if 0//def CROSS_DEVICE_MOVE
+ /*
+ * If rename fails because we're trying to cross devices, and
+ * it's a regular file, do the copy internally; otherwise, use
+ * cp and rm.
+ */
+ if (lstat(from, &sb)) {
+ warn(pThis->pCtx, "%s", from);
+ return (1);
+ }
+ return (S_ISREG(sb.st_mode) ?
+ fastcopy(pThis, from, to, &sb) : copy(pThis, from, to));
+#endif
+}
+
+#if 0 //def CROSS_DEVICE_MOVE - using static buffers and fork.
+int
+static fastcopy(char *from, char *to, struct stat *sbp)
+{
+ struct timeval tval[2];
+ static u_int blen;
+ static char *bp;
+ mode_t oldmode;
+ int nread, from_fd, to_fd;
+ acl_t acl;
+
+ if ((from_fd = open(from, O_RDONLY | KMK_OPEN_NO_INHERIT, 0)) < 0) {
+ warn("%s", from);
+ return (1);
+ }
+ if (blen < sbp->st_blksize) {
+ if (bp != NULL)
+ free(bp);
+ if ((bp = malloc((size_t)sbp->st_blksize)) == NULL) {
+ blen = 0;
+ warnx("malloc failed");
+ return (1);
+ }
+ blen = sbp->st_blksize;
+ }
+ while ((to_fd =
+ open(to, O_CREAT | O_EXCL | O_TRUNC | O_WRONLY | KMK_OPEN_NO_INHERIT, 0)) < 0) {
+ if (errno == EEXIST && unlink(to) == 0)
+ continue;
+ warn("%s", to);
+ (void)close(from_fd);
+ return (1);
+ }
+ while ((nread = read(from_fd, bp, (size_t)blen)) > 0)
+ if (write(to_fd, bp, (size_t)nread) != nread) {
+ warn("%s", to);
+ goto err;
+ }
+ if (nread < 0) {
+ warn("%s", from);
+err: if (unlink(to))
+ warn("%s: remove", to);
+ (void)close(from_fd);
+ (void)close(to_fd);
+ return (1);
+ }
+
+ oldmode = sbp->st_mode & ALLPERMS;
+ if (fchown(to_fd, sbp->st_uid, sbp->st_gid)) {
+ warn("%s: set owner/group (was: %lu/%lu)", to,
+ (u_long)sbp->st_uid, (u_long)sbp->st_gid);
+ if (oldmode & (S_ISUID | S_ISGID)) {
+ warnx(
+"%s: owner/group changed; clearing suid/sgid (mode was 0%03o)",
+ to, oldmode);
+ sbp->st_mode &= ~(S_ISUID | S_ISGID);
+ }
+ }
+ /*
+ * POSIX 1003.2c states that if _POSIX_ACL_EXTENDED is in effect
+ * for dest_file, then it's ACLs shall reflect the ACLs of the
+ * source_file.
+ */
+ if (fpathconf(to_fd, _PC_ACL_EXTENDED) == 1 &&
+ fpathconf(from_fd, _PC_ACL_EXTENDED) == 1) {
+ acl = acl_get_fd(from_fd);
+ if (acl == NULL)
+ warn("failed to get acl entries while setting %s",
+ from);
+ else if (acl_set_fd(to_fd, acl) < 0)
+ warn("failed to set acl entries for %s", to);
+ }
+ (void)close(from_fd);
+ if (fchmod(to_fd, sbp->st_mode))
+ warn("%s: set mode (was: 0%03o)", to, oldmode);
+ /*
+ * XXX
+ * NFS doesn't support chflags; ignore errors unless there's reason
+ * to believe we're losing bits. (Note, this still won't be right
+ * if the server supports flags and we were trying to *remove* flags
+ * on a file that we copied, i.e., that we didn't create.)
+ */
+ errno = 0;
+ if (fchflags(to_fd, (u_long)sbp->st_flags))
+ if (errno != EOPNOTSUPP || sbp->st_flags != 0)
+ warn("%s: set flags (was: 0%07o)", to, sbp->st_flags);
+
+ tval[0].tv_sec = sbp->st_atime;
+ tval[1].tv_sec = sbp->st_mtime;
+ tval[0].tv_usec = tval[1].tv_usec = 0;
+ if (utimes(to, tval))
+ warn("%s: set times", to);
+
+ if (close(to_fd)) {
+ warn("%s", to);
+ return (1);
+ }
+
+ if (unlink(from)) {
+ warn("%s: remove", from);
+ return (1);
+ }
+ if (vflg)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s -> %s\n", from, to);
+ return (0);
+}
+
+int
+copy(char *from, char *to)
+{
+ int pid, status;
+
+ if ((pid = fork()) == 0) {
+ execl(_PATH_CP, "mv", vflg ? "-PRpv" : "-PRp", "--", from, to,
+ (char *)NULL);
+ warn("%s", _PATH_CP);
+ _exit(1);
+ }
+ if (waitpid(pid, &status, 0) == -1) {
+ warn("%s: waitpid", _PATH_CP);
+ return (1);
+ }
+ if (!WIFEXITED(status)) {
+ warnx("%s: did not terminate normally", _PATH_CP);
+ return (1);
+ }
+ if (WEXITSTATUS(status)) {
+ warnx("%s: terminated with %d (non-zero) status",
+ _PATH_CP, WEXITSTATUS(status));
+ return (1);
+ }
+ if (!(pid = vfork())) {
+ execl(_PATH_RM, "mv", "-rf", "--", from, (char *)NULL);
+ warn("%s", _PATH_RM);
+ _exit(1);
+ }
+ if (waitpid(pid, &status, 0) == -1) {
+ warn("%s: waitpid", _PATH_RM);
+ return (1);
+ }
+ if (!WIFEXITED(status)) {
+ warnx("%s: did not terminate normally", _PATH_RM);
+ return (1);
+ }
+ if (WEXITSTATUS(status)) {
+ warnx("%s: terminated with %d (non-zero) status",
+ _PATH_RM, WEXITSTATUS(status));
+ return (1);
+ }
+ return (0);
+}
+#endif /* CROSS_DEVICE_MOVE */
+
+
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s [-f | -i | -n] [-v] source target\n"
+ " or: %s [-f | -i | -n] [-v] source ... directory\n"
+ " or: %s --help\n"
+ " or: %s --version\n",
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+ return EX_USAGE;
+}
diff --git a/src/kmk/kmkbuiltin/openbsd.c b/src/kmk/kmkbuiltin/openbsd.c
new file mode 100644
index 0000000..b6b59db
--- /dev/null
+++ b/src/kmk/kmkbuiltin/openbsd.c
@@ -0,0 +1,54 @@
+/* $Id: openbsd.c 2421 2010-10-17 21:27:53Z bird $ */
+/** @file
+ * Missing BSD functions in OpenBSD.
+ */
+
+/*
+ * Copyright (c) 2006-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/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "config.h"
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+int lchmod(const char *path, mode_t mode)
+{
+ struct stat st;
+ if (lstat(path, &st))
+ return -1;
+ if (S_ISLNK(st.st_mode))
+ return 0; /* pretend success */
+ return chmod(path, mode);
+}
+
+
+int lutimes(const char *path, const struct timeval *tvs)
+{
+ struct stat st;
+ if (lstat(path, &st))
+ return -1;
+ if (S_ISLNK(st.st_mode))
+ return 0; /* pretend success */
+ return utimes(path, tvs);
+}
+
diff --git a/src/kmk/kmkbuiltin/osdep.c b/src/kmk/kmkbuiltin/osdep.c
new file mode 100644
index 0000000..e9f1a77
--- /dev/null
+++ b/src/kmk/kmkbuiltin/osdep.c
@@ -0,0 +1,48 @@
+/* $Id: osdep.c 2656 2012-09-10 20:39:16Z bird $ */
+/** @file
+ * Include all the OS dependent bits when bootstrapping.
+ */
+
+/*
+ * Copyright (c) 2005-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 <config.h>
+
+/** @todo replace this by proper configure.in tests. */
+
+#if defined(_MSC_VER)
+# include "mscfakes.c"
+# include "fts.c"
+
+#elif defined(__sun__)
+# include "solfakes.c"
+# include "fts.c"
+
+#elif defined(__APPLE__)
+# include "darwin.c"
+
+#elif defined(__OpenBSD__)
+# include "openbsd.c"
+
+#elif defined(__HAIKU__)
+# include "haikufakes.c"
+
+#endif
+
diff --git a/src/kmk/kmkbuiltin/printf.c b/src/kmk/kmkbuiltin/printf.c
new file mode 100644
index 0000000..9dc5956
--- /dev/null
+++ b/src/kmk/kmkbuiltin/printf.c
@@ -0,0 +1,954 @@
+/* $NetBSD: printf.c,v 1.31 2005/03/22 23:55:46 dsl Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#ifndef lint
+#if !defined(BUILTIN) && !defined(SHELL)
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993\n\
+ The Regents of the University of California. All rights reserved.\n");
+#endif
+#endif
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)printf.c 8.2 (Berkeley) 3/22/95";
+#else
+__RCSID("$NetBSD: printf.c,v 1.31 2005/03/22 23:55:46 dsl Exp $");
+#endif
+#endif*/ /* not lint */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define FAKES_NO_GETOPT_H /* bird */
+#if !defined(KMK_BUILTIN_STANDALONE) && !defined(BUILTIN) && !defined(SHELL)
+# include "../makeint.h"
+# include "../filedef.h"
+# include "../variable.h"
+#else
+# include "config.h"
+#endif
+#include <sys/types.h>
+
+#include <ctype.h>
+#include "err.h"
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "getopt_r.h"
+#ifdef __sun__
+# include "solfakes.h"
+#endif
+#ifdef _MSC_VER
+# include "mscfakes.h"
+#endif
+
+#include "../kmkbuiltin.h"
+
+#ifdef KBUILD_OS_WINDOWS
+/* This is a trick to speed up console output on windows. */
+# include "console.h"
+# undef fwrite
+# define fwrite maybe_con_fwrite
+#endif
+
+#if 0
+#ifdef BUILTIN /* csh builtin */
+#define kmk_builtin_printf progprintf
+#endif
+
+#ifdef SHELL /* sh (aka ash) builtin */
+#define kmk_builtin_printf printfcmd
+#include "../../bin/sh/bltin/bltin.h"
+#endif /* SHELL */
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#if 0 /*def __GNUC__ - bird: gcc complains about non-ISO-standard escape. */
+#define ESCAPE '\e'
+#else
+#define ESCAPE 033
+#endif
+
+#define PF(f, func) { \
+ if (fieldwidth != -1) { \
+ if (precision != -1) \
+ (void)wrap_printf(pThis, f, fieldwidth, precision, func); \
+ else \
+ (void)wrap_printf(pThis, f, fieldwidth, func); \
+ } else if (precision != -1) \
+ (void)wrap_printf(pThis, f, precision, func); \
+ else \
+ (void)wrap_printf(pThis, f, func); \
+}
+
+#define APF(cpp, f, func) { \
+ if (fieldwidth != -1) { \
+ if (precision != -1) \
+ (void)asprintf(cpp, f, fieldwidth, precision, func); \
+ else \
+ (void)asprintf(cpp, f, fieldwidth, func); \
+ } else if (precision != -1) \
+ (void)asprintf(cpp, f, precision, func); \
+ else \
+ (void)asprintf(cpp, f, func); \
+}
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct PRINTFINSTANCE
+{
+ PKMKBUILTINCTX pCtx;
+ /* former globals */
+ size_t b_length;
+ char *b_fmt;
+ int rval;
+ char **gargv;
+#ifndef KMK_BUILTIN_STANDALONE
+ char *g_o;
+#endif
+ /* former function level statics in common_printf(); both need freeing. */
+ char *a, *t;
+
+ /* former function level statics in conv_expand(); needs freeing. */
+ char *conv_str;
+
+ /* Buffer the output because windows doesn't do line buffering of stdout. */
+ size_t g_cchBuf;
+ char g_achBuf[256];
+} PRINTFINSTANCE;
+typedef PRINTFINSTANCE *PPRINTFINSTANCE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static struct option long_options[] =
+{
+ { "help", no_argument, 0, 261 },
+ { "version", no_argument, 0, 262 },
+ { 0, 0, 0, 0 },
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int common_printf(PPRINTFINSTANCE pThis, char *argv[], PKMKBUILTINCTX pCtx);
+static int common_printf_inner(PPRINTFINSTANCE pThis, char *argv[]);
+static void conv_escape_str(PPRINTFINSTANCE, char *, void (*)(PPRINTFINSTANCE, int));
+static char *conv_escape(PPRINTFINSTANCE, char *, char *);
+static const char *conv_expand(PPRINTFINSTANCE, const char *);
+static int getchr(PPRINTFINSTANCE);
+static double getdouble(PPRINTFINSTANCE);
+static int getwidth(PPRINTFINSTANCE);
+static intmax_t getintmax(PPRINTFINSTANCE);
+static uintmax_t getuintmax(PPRINTFINSTANCE);
+static char *getstr(PPRINTFINSTANCE);
+static char *mklong(PPRINTFINSTANCE, const char *, int, char[64]);
+static void check_conversion(PPRINTFINSTANCE, const char *, const char *);
+static int usage(PKMKBUILTINCTX, int);
+
+static int flush_buffer(PPRINTFINSTANCE);
+static void b_count(PPRINTFINSTANCE, int);
+static void b_output(PPRINTFINSTANCE, int);
+static int wrap_putchar(PPRINTFINSTANCE, int ch);
+static int wrap_printf(PPRINTFINSTANCE, const char *, ...);
+
+
+
+int kmk_builtin_printf(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ PRINTFINSTANCE This;
+ struct getopt_state_r gos;
+ int ch;
+
+ getopt_initialize_r(&gos, argc, argv, "", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1) {
+ switch (ch) {
+ case 261:
+ usage(pCtx, 0);
+ return 0;
+ case 262:
+ return kbuild_version(argv[0]);
+ case '?':
+ default:
+ return usage(pCtx, 1);
+ }
+ }
+ argc -= gos.optind;
+ argv += gos.optind;
+
+ if (argc < 1)
+ return usage(pCtx, 1);
+
+#ifndef KMK_BUILTIN_STANDALONE
+ This.g_o = NULL;
+#endif
+ return common_printf(&This, argv, pCtx);
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_printf", NULL };
+ setlocale(LC_ALL, "");
+ return kmk_builtin_printf(argc, argv, envp, &Ctx);
+}
+#else /* KMK_BUILTIN_STANDALONE */
+/* entry point used by function.c $(printf ..,..). */
+char *kmk_builtin_func_printf(char *o, char **argv, const char *funcname)
+{
+ PRINTFINSTANCE This;
+ int rc;
+ int argc;
+
+ for (argc = 0; argv[argc] != NULL; argc++)
+ /* nothing */;
+ if (argc == 0)
+ fatal(NILF, strlen(funcname) + INTSTR_LENGTH, _("$(%s): no format string\n"), funcname);
+
+ This.g_o = o;
+ rc = common_printf(&This, argv, NULL);
+ o = This.g_o;
+
+ if (rc != 0)
+ fatal(NILF, strlen(funcname) + INTSTR_LENGTH, _("$(%s): failure rc=%d\n"), funcname, rc);
+ return o;
+}
+#endif /* KMK_BUILTIN_STANDALONE */
+
+static int common_printf(PPRINTFINSTANCE pThis, char *argv[], PKMKBUILTINCTX pCtx)
+{
+ int rc;
+
+ /* Init all but g_o. */
+ pThis->pCtx = pCtx;
+ pThis->b_length = 0;
+ pThis->b_fmt = NULL;
+ pThis->rval = 0;
+ pThis->gargv = NULL;
+ pThis->g_cchBuf = 0;
+ pThis->a = NULL;
+ pThis->t = NULL;
+ pThis->conv_str = NULL;
+
+ rc = common_printf_inner(pThis, argv);
+
+ /* Cleanup allocations. */
+ if (pThis->a) {
+ free(pThis->a);
+ pThis->a = NULL;
+ }
+ if (pThis->t) {
+ free(pThis->t);
+ pThis->t = NULL;
+ }
+ if (pThis->conv_str) {
+ free(pThis->conv_str);
+ pThis->conv_str = NULL;
+ }
+ return rc;
+}
+
+static int common_printf_inner(PPRINTFINSTANCE pThis, char *argv[])
+{
+ char *fmt, *start;
+ int fieldwidth, precision;
+ char nextch;
+ char *format;
+ int ch;
+ char longbuf[64];
+
+ format = *argv;
+ pThis->gargv = ++argv;
+
+#define SKIP1 "#-+ 0"
+#define SKIP2 "*0123456789"
+ do {
+ /*
+ * Basic algorithm is to scan the format string for conversion
+ * specifications -- once one is found, find out if the field
+ * width or precision is a '*'; if it is, gather up value.
+ * Note, format strings are reused as necessary to use up the
+ * provided arguments, arguments of zero/null string are
+ * provided to use up the format string.
+ */
+
+ /* find next format specification */
+ for (fmt = format; (ch = *fmt++) != '\0';) {
+ if (ch == '\\') {
+ char c_ch;
+ fmt = conv_escape(pThis, fmt, &c_ch);
+ wrap_putchar(pThis, c_ch);
+ continue;
+ }
+ if (ch != '%' || (*fmt == '%' && ++fmt)) {
+ (void)wrap_putchar(pThis, ch);
+ continue;
+ }
+
+ /* Ok - we've found a format specification,
+ Save its address for a later printf(). */
+ start = fmt - 1;
+
+ /* skip to field width */
+ fmt += strspn(fmt, SKIP1);
+ fieldwidth = *fmt == '*' ? getwidth(pThis) : -1;
+
+ /* skip to possible '.', get following precision */
+ fmt += strspn(fmt, SKIP2);
+ if (*fmt == '.')
+ ++fmt;
+ precision = *fmt == '*' ? getwidth(pThis) : -1;
+
+ fmt += strspn(fmt, SKIP2);
+
+ ch = *fmt;
+ if (!ch) {
+ flush_buffer(pThis);
+ warnx(pThis->pCtx, "missing format character");
+ return (1);
+ }
+ /* null terminate format string to we can use it
+ as an argument to printf. */
+ nextch = fmt[1];
+ fmt[1] = 0;
+ switch (ch) {
+
+ case 'B': {
+ const char *p = conv_expand(pThis, getstr(pThis));
+ *fmt = 's';
+ PF(start, p);
+ break;
+ }
+ case 'b': {
+ /* There has to be a better way to do this,
+ * but the string we generate might have
+ * embedded nulls. */
+ char *cp = getstr(pThis);
+ /* Free on entry in case shell longjumped out */
+ if (pThis->a != NULL) {
+ free(pThis->a);
+ pThis->a = NULL;
+ }
+ if (pThis->t != NULL) {
+ free(pThis->t);
+ pThis->t = NULL;
+ }
+ /* Count number of bytes we want to output */
+ pThis->b_length = 0;
+ conv_escape_str(pThis, cp, b_count);
+ pThis->t = malloc(pThis->b_length + 1);
+ if (pThis->t == NULL)
+ break;
+ memset(pThis->t, 'x', pThis->b_length);
+ pThis->t[pThis->b_length] = 0;
+ /* Get printf to calculate the lengths */
+ *fmt = 's';
+ APF(&pThis->a, start, pThis->t);
+ pThis->b_fmt = pThis->a;
+ /* Output leading spaces and data bytes */
+ conv_escape_str(pThis, cp, b_output);
+ /* Add any trailing spaces */
+ wrap_printf(pThis, "%s", pThis->b_fmt);
+ break;
+ }
+ case 'c': {
+ char p = getchr(pThis);
+ PF(start, p);
+ break;
+ }
+ case 's': {
+ char *p = getstr(pThis);
+ PF(start, p);
+ break;
+ }
+ case 'd':
+ case 'i': {
+ intmax_t p = getintmax(pThis);
+ char *f = mklong(pThis, start, ch, longbuf);
+ PF(f, p);
+ break;
+ }
+ case 'o':
+ case 'u':
+ case 'x':
+ case 'X': {
+ uintmax_t p = getuintmax(pThis);
+ char *f = mklong(pThis, start, ch, longbuf);
+ PF(f, p);
+ break;
+ }
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'g':
+ case 'G': {
+ double p = getdouble(pThis);
+ PF(start, p);
+ break;
+ }
+ default:
+ flush_buffer(pThis);
+ warnx(pThis->pCtx, "%s: invalid directive", start);
+ return 1;
+ }
+ *fmt++ = ch;
+ *fmt = nextch;
+ /* escape if a \c was encountered */
+ if (pThis->rval & 0x100) {
+ flush_buffer(pThis);
+ return pThis->rval & ~0x100;
+ }
+ }
+ } while (pThis->gargv != argv && *pThis->gargv);
+
+ flush_buffer(pThis);
+ return pThis->rval;
+}
+
+
+/* helper functions for conv_escape_str */
+
+static void
+/*ARGSUSED*/
+b_count(PPRINTFINSTANCE pThis, int ch)
+{
+ pThis->b_length++;
+ (void)ch;
+}
+
+/* Output one converted character for every 'x' in the 'format' */
+
+static void
+b_output(PPRINTFINSTANCE pThis, int ch)
+{
+ for (;;) {
+ switch (*pThis->b_fmt++) {
+ case 0:
+ pThis->b_fmt--;
+ return;
+ case ' ':
+ wrap_putchar(pThis, ' ');
+ break;
+ default:
+ wrap_putchar(pThis, ch);
+ return;
+ }
+ }
+}
+
+static int wrap_putchar(PPRINTFINSTANCE pThis, int ch)
+{
+#ifndef KMK_BUILTIN_STANDALONE
+ if (pThis->g_o) {
+ char sz[2];
+ sz[0] = ch; sz[1] = '\0';
+ pThis->g_o = variable_buffer_output(pThis->g_o, sz, 1);
+ }
+ else
+#endif
+ /* Buffered output. */
+ if (pThis->g_cchBuf + 1 < sizeof(pThis->g_achBuf)) {
+ pThis->g_achBuf[pThis->g_cchBuf++] = ch;
+ } else {
+ int rc = flush_buffer(pThis);
+ pThis->g_achBuf[pThis->g_cchBuf++] = ch;
+ if (rc)
+ return -1;
+ }
+ return 0;
+}
+
+static int wrap_printf(PPRINTFINSTANCE pThis, const char * fmt, ...)
+{
+ ssize_t cchRet;
+ va_list va;
+ char *pszTmp;
+
+ va_start(va, fmt);
+ cchRet = vasprintf(&pszTmp, fmt, va);
+ va_end(va);
+ if (cchRet >= 0) {
+#ifndef KMK_BUILTIN_STANDALONE
+ if (pThis->g_o) {
+ pThis->g_o = variable_buffer_output(pThis->g_o, pszTmp, cchRet);
+ } else
+#endif
+ {
+ if (cchRet + pThis->g_cchBuf <= sizeof(pThis->g_achBuf)) {
+ /* We've got space in the buffer. */
+ memcpy(&pThis->g_achBuf[pThis->g_cchBuf], pszTmp, cchRet);
+ pThis->g_cchBuf += cchRet;
+ } else {
+ /* Try write out complete lines. */
+ const char *pszLeft = pszTmp;
+ ssize_t cchLeft = cchRet;
+
+ while (cchLeft > 0) {
+ const char *pchNewLine = strchr(pszLeft, '\n');
+ ssize_t cchLine = pchNewLine ? pchNewLine - pszLeft + 1 : cchLeft;
+ if (pThis->g_cchBuf + cchLine <= sizeof(pThis->g_achBuf)) {
+ memcpy(&pThis->g_achBuf[pThis->g_cchBuf], pszLeft, cchLine);
+ pThis->g_cchBuf += cchLine;
+ } else {
+ if (flush_buffer(pThis) < 0) {
+ return -1;
+ }
+#ifndef KMK_BUILTIN_STANDALONE
+ if (output_write_text(pThis->pCtx->pOut, 0,pszLeft, cchLine) < 1)
+#else
+ if (fwrite(pszLeft, cchLine, 1, stdout) < 1)
+#endif
+
+ return -1;
+ }
+ pszLeft += cchLine;
+ cchLeft -= cchLine;
+ }
+ }
+ }
+ free(pszTmp);
+ }
+ return (int)cchRet;
+}
+
+/**
+ * Flushes the g_abBuf/g_cchBuf.
+ */
+static int flush_buffer(PPRINTFINSTANCE pThis)
+{
+ ssize_t cchToWrite = pThis->g_cchBuf;
+ if (cchToWrite > 0) {
+#ifndef KMK_BUILTIN_STANDALONE
+ ssize_t cchWritten = output_write_text(pThis->pCtx->pOut, 0, pThis->g_achBuf, cchToWrite);
+#else
+ ssize_t cchWritten = fwrite(pThis->g_achBuf, 1, cchToWrite, stdout);
+#endif
+ pThis->g_cchBuf = 0;
+ if (cchWritten >= cchToWrite) {
+ /* likely */
+ } else {
+ ssize_t off = cchWritten;
+ if (cchWritten >= 0) {
+ off = cchWritten;
+ } else if (errno == EINTR) {
+ cchWritten = 0;
+ } else {
+ return -1;
+ }
+
+ while (off < cchToWrite) {
+#ifndef KMK_BUILTIN_STANDALONE
+ cchWritten = output_write_text(pThis->pCtx->pOut, 0, &pThis->g_achBuf[off], cchToWrite - off);
+#else
+ cchWritten = fwrite(&pThis->g_achBuf[off], 1, cchToWrite - off, stdout);
+#endif
+ if (cchWritten > 0) {
+ off += cchWritten;
+ } else if (errno == EINTR) {
+ /* nothing */
+ } else {
+ return -1;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+
+
+/*
+ * Print SysV echo(1) style escape string
+ * Halts processing string if a \c escape is encountered.
+ */
+static void
+conv_escape_str(PPRINTFINSTANCE pThis, char *str, void (*do_putchar)(PPRINTFINSTANCE, int))
+{
+ int value;
+ int ch;
+ char c;
+
+ while ((ch = *str++) != '\0') {
+ if (ch != '\\') {
+ do_putchar(pThis, ch);
+ continue;
+ }
+
+ ch = *str++;
+ if (ch == 'c') {
+ /* \c as in SYSV echo - abort all processing.... */
+ pThis->rval |= 0x100;
+ break;
+ }
+
+ /*
+ * %b string octal constants are not like those in C.
+ * They start with a \0, and are followed by 0, 1, 2,
+ * or 3 octal digits.
+ */
+ if (ch == '0') {
+ int octnum = 0, i;
+ for (i = 0; i < 3; i++) {
+ if (!isdigit((unsigned char)*str) || *str > '7')
+ break;
+ octnum = (octnum << 3) | (*str++ - '0');
+ }
+ do_putchar(pThis, octnum);
+ continue;
+ }
+
+ /* \[M][^|-]C as defined by vis(3) */
+ if (ch == 'M' && *str == '-') {
+ do_putchar(pThis, 0200 | str[1]);
+ str += 2;
+ continue;
+ }
+ if (ch == 'M' && *str == '^') {
+ str++;
+ value = 0200;
+ ch = '^';
+ } else
+ value = 0;
+ if (ch == '^') {
+ ch = *str++;
+ if (ch == '?')
+ value |= 0177;
+ else
+ value |= ch & 037;
+ do_putchar(pThis, value);
+ continue;
+ }
+
+ /* Finally test for sequences valid in the format string */
+ str = conv_escape(pThis, str - 1, &c);
+ do_putchar(pThis, c);
+ }
+}
+
+/*
+ * Print "standard" escape characters
+ */
+static char *
+conv_escape(PPRINTFINSTANCE pThis, char *str, char *conv_ch)
+{
+ int value;
+ int ch;
+ char num_buf[4], *num_end;
+
+ ch = *str++;
+
+ switch (ch) {
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ num_buf[0] = ch;
+ ch = str[0];
+ num_buf[1] = ch;
+ num_buf[2] = ch ? str[1] : 0;
+ num_buf[3] = 0;
+ value = strtoul(num_buf, &num_end, 8);
+ str += num_end - (num_buf + 1);
+ break;
+
+ case 'x':
+ /* Hexadecimal character constants are not required to be
+ supported (by SuS v1) because there is no consistent
+ way to detect the end of the constant.
+ Supporting 2 byte constants is a compromise. */
+ ch = str[0];
+ num_buf[0] = ch;
+ num_buf[1] = ch ? str[1] : 0;
+ num_buf[2] = 0;
+ value = strtoul(num_buf, &num_end, 16);
+ str += num_end - num_buf;
+ break;
+
+ case '\\': value = '\\'; break; /* backslash */
+ case '\'': value = '\''; break; /* single quote */
+ case '"': value = '"'; break; /* double quote */
+ case 'a': value = '\a'; break; /* alert */
+ case 'b': value = '\b'; break; /* backspace */
+ case 'e': value = ESCAPE; break; /* escape */
+ case 'f': value = '\f'; break; /* form-feed */
+ case 'n': value = '\n'; break; /* newline */
+ case 'r': value = '\r'; break; /* carriage-return */
+ case 't': value = '\t'; break; /* tab */
+ case 'v': value = '\v'; break; /* vertical-tab */
+
+ default:
+ warnx(pThis->pCtx, "unknown escape sequence `\\%c'", ch);
+ pThis->rval = 1;
+ value = ch;
+ break;
+ }
+
+ *conv_ch = value;
+ return str;
+}
+
+/* expand a string so that everything is printable */
+
+static const char *
+conv_expand(PPRINTFINSTANCE pThis, const char *str)
+{
+ static const char no_memory[] = "<no memory>";
+ char *cp;
+ int ch;
+
+ if (pThis->conv_str)
+ free(pThis->conv_str);
+ /* get a buffer that is definitely large enough.... */
+ pThis->conv_str = cp = malloc(4 * strlen(str) + 1);
+ if (!cp)
+ return no_memory;
+
+ while ((ch = *(const unsigned char *)str++) != '\0') {
+ switch (ch) {
+ /* Use C escapes for expected control characters */
+ case '\\': ch = '\\'; break; /* backslash */
+ case '\'': ch = '\''; break; /* single quote */
+ case '"': ch = '"'; break; /* double quote */
+ case '\a': ch = 'a'; break; /* alert */
+ case '\b': ch = 'b'; break; /* backspace */
+ case ESCAPE: ch = 'e'; break; /* escape */
+ case '\f': ch = 'f'; break; /* form-feed */
+ case '\n': ch = 'n'; break; /* newline */
+ case '\r': ch = 'r'; break; /* carriage-return */
+ case '\t': ch = 't'; break; /* tab */
+ case '\v': ch = 'v'; break; /* vertical-tab */
+ default:
+ /* Copy anything printable */
+ if (isprint(ch)) {
+ *cp++ = ch;
+ continue;
+ }
+ /* Use vis(3) encodings for the rest */
+ *cp++ = '\\';
+ if (ch & 0200) {
+ *cp++ = 'M';
+ ch &= ~0200;
+ }
+ if (ch == 0177) {
+ *cp++ = '^';
+ *cp++ = '?';
+ continue;
+ }
+ if (ch < 040) {
+ *cp++ = '^';
+ *cp++ = ch | 0100;
+ continue;
+ }
+ *cp++ = '-';
+ *cp++ = ch;
+ continue;
+ }
+ *cp++ = '\\';
+ *cp++ = ch;
+ }
+
+ *cp = 0;
+ return pThis->conv_str;
+}
+
+static char *
+mklong(PPRINTFINSTANCE pThis, const char *str, int ch, char copy[64])
+{
+ size_t len;
+
+ len = strlen(str) - 1;
+ if (len > 64 - 5) {
+ warnx(pThis->pCtx, "format %s too complex\n", str);
+ len = 4;
+ }
+ (void)memmove(copy, str, len);
+#ifndef _MSC_VER
+ copy[len++] = 'j';
+#else
+ copy[len++] = 'I';
+ copy[len++] = '6';
+ copy[len++] = '4';
+#endif
+ copy[len++] = ch;
+ copy[len] = '\0';
+ return copy;
+}
+
+static int
+getchr(PPRINTFINSTANCE pThis)
+{
+ if (!*pThis->gargv)
+ return 0;
+ return (int)**pThis->gargv++;
+}
+
+static char *
+getstr(PPRINTFINSTANCE pThis)
+{
+ static char empty[] = "";
+ if (!*pThis->gargv)
+ return empty;
+ return *pThis->gargv++;
+}
+
+static int
+getwidth(PPRINTFINSTANCE pThis)
+{
+ long val;
+ char *s, *ep;
+
+ s = *pThis->gargv;
+ if (!s)
+ return (0);
+ pThis->gargv++;
+
+ errno = 0;
+ val = strtoul(s, &ep, 0);
+ check_conversion(pThis, s, ep);
+
+ /* Arbitrarily 'restrict' field widths to 1Mbyte */
+ if (val < 0 || val > 1 << 20) {
+ warnx(pThis->pCtx, "%s: invalid field width", s);
+ return 0;
+ }
+
+ return val;
+}
+
+static intmax_t
+getintmax(PPRINTFINSTANCE pThis)
+{
+ intmax_t val;
+ char *cp, *ep;
+
+ cp = *pThis->gargv;
+ if (cp == NULL)
+ return 0;
+ pThis->gargv++;
+
+ if (*cp == '\"' || *cp == '\'')
+ return *(cp+1);
+
+ errno = 0;
+ val = strtoimax(cp, &ep, 0);
+ check_conversion(pThis, cp, ep);
+ return val;
+}
+
+static uintmax_t
+getuintmax(PPRINTFINSTANCE pThis)
+{
+ uintmax_t val;
+ char *cp, *ep;
+
+ cp = *pThis->gargv;
+ if (cp == NULL)
+ return 0;
+ pThis->gargv++;
+
+ if (*cp == '\"' || *cp == '\'')
+ return *(cp + 1);
+
+ /* strtoumax won't error -ve values */
+ while (isspace(*(unsigned char *)cp))
+ cp++;
+ if (*cp == '-') {
+ warnx(pThis->pCtx, "%s: expected positive numeric value", cp);
+ pThis->rval = 1;
+ return 0;
+ }
+
+ errno = 0;
+ val = strtoumax(cp, &ep, 0);
+ check_conversion(pThis, cp, ep);
+ return val;
+}
+
+static double
+getdouble(PPRINTFINSTANCE pThis)
+{
+ double val;
+ char *ep;
+ char *s;
+
+ s = *pThis->gargv;
+ if (!s)
+ return (0.0);
+ pThis->gargv++;
+
+ if (*s == '\"' || *s == '\'')
+ return (double) s[1];
+
+ errno = 0;
+ val = strtod(s, &ep);
+ check_conversion(pThis, s, ep);
+ return val;
+}
+
+static void
+check_conversion(PPRINTFINSTANCE pThis, const char *s, const char *ep)
+{
+ if (*ep) {
+ if (ep == s)
+ warnx(pThis->pCtx, "%s: expected numeric value", s);
+ else
+ warnx(pThis->pCtx, "%s: not completely converted", s);
+ pThis->rval = 1;
+ } else if (errno == ERANGE) {
+ warnx(pThis->pCtx, "%s: %s", s, strerror(ERANGE));
+ pThis->rval = 1;
+ }
+}
+
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s format [arg ...]\n"
+ " or: %s --help\n"
+ " or: %s --version\n",
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+ return 1;
+}
+
diff --git a/src/kmk/kmkbuiltin/redirect.c b/src/kmk/kmkbuiltin/redirect.c
new file mode 100644
index 0000000..f0d97d2
--- /dev/null
+++ b/src/kmk/kmkbuiltin/redirect.c
@@ -0,0 +1,2066 @@
+/* $Id: redirect.c 3564 2022-03-08 11:12:18Z bird $ */
+/** @file
+ * kmk_redirect - Do simple program <-> file redirection (++).
+ */
+
+/*
+ * Copyright (c) 2007-2016 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/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#if defined(__APPLE__)
+/*# define _POSIX_C_SOURCE 1 / * 10.4 sdk and unsetenv * / - breaks O_CLOEXEC on 10.8 */
+#endif
+#include "makeint.h"
+#include <assert.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#if defined(KBUILD_OS_WINDOWS) || defined(KBUILD_OS_OS2)
+# include <process.h>
+#endif
+#ifdef KBUILD_OS_WINDOWS
+# include <Windows.h>
+#endif
+#if defined(_MSC_VER)
+# include <ctype.h>
+# include <io.h>
+# include "quote_argv.h"
+#else
+# ifdef __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
+# if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050
+# define USE_POSIX_SPAWN
+# endif
+# elif !defined(KBUILD_OS_WINDOWS) && !defined(KBUILD_OS_OS2)
+# define USE_POSIX_SPAWN
+# endif
+# include <unistd.h>
+# ifdef USE_POSIX_SPAWN
+# include <spawn.h>
+# endif
+# include <sys/wait.h>
+#endif
+
+#include <k/kDefs.h>
+#include <k/kTypes.h>
+#include "err.h"
+#include "kbuild_version.h"
+#ifdef KBUILD_OS_WINDOWS
+# include "nt/nt_child_inject_standard_handles.h"
+#endif
+#if defined(__gnu_hurd__) && !defined(KMK_BUILTIN_STANDALONE) /* need constant */
+# undef GET_PATH_MAX
+# undef PATH_MAX
+# define GET_PATH_MAX PATH_MAX
+#endif
+#include "kmkbuiltin.h"
+#ifdef KMK
+# ifdef KBUILD_OS_WINDOWS
+# ifndef CONFIG_NEW_WIN_CHILDREN
+# include "sub_proc.h"
+# else
+# include "../w32/winchildren.h"
+# endif
+# include "pathstuff.h"
+# endif
+#endif
+
+#ifdef __OS2__
+# define INCL_BASE
+# include <os2.h>
+# ifndef LIBPATHSTRICT
+# define LIBPATHSTRICT 3
+# endif
+#endif
+
+#ifndef KMK_BUILTIN_STANDALONE
+extern void kmk_cache_exec_image_a(const char *); /* imagecache.c */
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/* String + strlen tuple. */
+#define TUPLE(a_sz) a_sz, sizeof(a_sz) - 1
+
+/** Only standard handles on windows. */
+#ifdef KBUILD_OS_WINDOWS
+# define ONLY_TARGET_STANDARD_HANDLES
+#endif
+
+
+static int kmk_redirect_usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ /* 0 1 2 3 4 5 6 7 8 */
+ /* 012345678901234567890123456789012345678901234567890123456789012345678901234567890 */
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "Usage: %s [-[rwa+tb]<fd> <file>] [-d<fd>=<src-fd>] [-c<fd>] [--stdin-pipe]\n"
+ " [-Z] [-E <var=val>] [-A <var=val>] [-P <var=val>] [-D <var>]\n"
+ " [-C <dir>] [--wcc-brain-damage] [-v] -- <program> [args]\n"
+ " or: %s --help\n"
+ " or: %s --version\n"
+ "\n"
+ "Options:\n"
+ "-[rwa+tb]<fd> <file>\n"
+ " The rwa+tb is like for fopen, if not specified it defaults to w+.\n"
+ " The <fd> is either a number or an alias for the standard handles:\n"
+ " i = stdin\n"
+ " o = stdout\n"
+ " e = stderr\n"
+ "-d\n"
+ " The -d switch duplicate the right hand file descriptor (src-fd) to the left\n"
+ " hand side one (fd). The latter is limited to standard handles on windows.\n"
+ "-c <fd>, --close <fd>\n"
+ " The -c switch will close the specified file descriptor. Limited to standard\n"
+ " handles on windows.\n"
+ "--stdin-pipe\n"
+ " The --stdin-pipe switch will replace stdin with the read end of an\n"
+ " anonymous pipe. This is for tricking things like rsh.exe that blocks\n"
+ " reading on stdin.\n"
+ "-Z, --zap-env, --ignore-environment\n"
+ " The -Z switch zaps the environment.\n"
+ "-E <var=val>, --set <var=val>, --env <var=val>\n"
+ " The -E (--set, --env) switch is for making changes to the environment\n"
+ " in an putenv fashion.\n"
+ "-A <var=val>, --append <var=val>\n"
+ " The -A switch appends to an environment variable in a putenv fashion.\n"
+ "-D <var=val>, --prepend <var=val>\n"
+ " The -D switch prepends to an environment variable in a putenv fashion.\n"
+ "-U <var>, --unset <var>\n"
+ " The -U switch deletes an environment variable.\n"
+ /* 0 1 2 3 4 5 6 7 8 */
+ /* 012345678901234567890123456789012345678901234567890123456789012345678901234567890 */
+ "-C <dir>, --chdir <dir>\n"
+ " The -C switch is for changing the current directory. Please specify an\n"
+ " absolute program path as it's platform dependent whether this takes\n"
+ " effect before or after the executable is located.\n"
+ "--wcc-brain-damage, --watcom-brain-damage\n"
+ " The --wcc-brain-damage switch is to work around wcc and wcc386\n"
+ " (Open Watcom) not following normal quoting conventions on Windows and OS/2.\n"
+ "-v, --verbose\n"
+ " The -v switch is for making the thing more verbose.\n"
+ "\n"
+ "On OS/2 the kernel variables BEGINLIBPATH, ENDLIBPATH and LIBPATHSTRICT can be\n"
+ "accessed as-if they were regular enviornment variables.\n"
+ "\n"
+ "This command was originally just a quick hack to avoid invoking the shell\n"
+ "on Windows (cygwin) where forking is very expensive and has exhibited\n"
+ "stability issues on SMP machines. It has since grown into something like\n"
+ "/usr/bin/env on steroids.\n"
+ /* 0 1 2 3 4 5 6 7 8 */
+ /* 012345678901234567890123456789012345678901234567890123456789012345678901234567890 */
+ ,
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+ return 2;
+}
+
+
+/**
+ * Decoded file descriptor operations.
+ */
+typedef struct REDIRECTORDERS
+{
+ enum {
+ kRedirectOrder_Invalid = 0,
+ kRedirectOrder_Close,
+ kRedirectOrder_Open,
+ kRedirectOrder_Dup
+ } enmOrder;
+ /** The target file handle. */
+ int fdTarget;
+ /** The source file name, -1 on close only.
+ * This is an opened file if pszFilename is set. */
+ int fdSource;
+ /** Whether to remove the file on failure cleanup. */
+ int fRemoveOnFailure;
+ /** The open flags (for O_TEXT/O_BINARY) on windows. */
+ int fOpen;
+ /** The filename - NULL if close only. */
+ const char *pszFilename;
+ /** The other pipe end, needs closing in cleanup. */
+ int fdOtherPipeEnd;
+#ifndef USE_POSIX_SPAWN
+ /** Saved file descriptor. */
+ int fdSaved;
+ /** Saved flags. */
+ int fSaved;
+#endif
+} REDIRECTORDERS;
+
+
+static KBOOL kRedirectHasConflict(int fd, unsigned cOrders, REDIRECTORDERS *paOrders)
+{
+#ifdef ONLY_TARGET_STANDARD_HANDLES
+ return fd < 3;
+#else
+ while (cOrders-- > 0)
+ if (paOrders[cOrders].fdTarget == fd)
+ return K_TRUE;
+ return K_FALSE;
+#endif
+}
+
+
+/**
+ * Creates a pair of pipe descriptors that does not conflict with any previous
+ * orders.
+ *
+ * The pipe is open with both descriptors being inherited by the child as it's
+ * supposed to be a dummy pipe for stdin that won't break.
+ *
+ * @returns 0 on success, exit code on failure (error message displayed).
+ * @param pCtx The command execution context.
+ * @param paFds Where to return the pipe descriptors
+ * @param cOrders The number of orders.
+ * @param paOrders The order array.
+ * @param fdTarget The target descriptor (0).
+ */
+static int kRedirectCreateStdInPipeWithoutConflict(PKMKBUILTINCTX pCtx, int paFds[2],
+ unsigned cOrders, REDIRECTORDERS *paOrders, int fdTarget)
+{
+ struct
+ {
+ int aFds[2];
+ } aTries[32];
+ unsigned cTries = 0;
+
+ while (cTries < K_ELEMENTS(aTries))
+ {
+#ifdef _MSC_VER
+ int rc = _pipe(aTries[cTries].aFds, 0, _O_BINARY);
+#else
+ int rc = pipe(aTries[cTries].aFds);
+#endif
+ if (rc >= 0)
+ {
+ if ( !kRedirectHasConflict(aTries[cTries].aFds[0], cOrders, paOrders)
+ && !kRedirectHasConflict(aTries[cTries].aFds[1], cOrders, paOrders)
+#ifndef _MSC_VER
+ && aTries[cTries].aFds[0] != fdTarget
+ && aTries[cTries].aFds[1] != fdTarget
+#endif
+ )
+ {
+ paFds[0] = aTries[cTries].aFds[0];
+ paFds[1] = aTries[cTries].aFds[1];
+
+ while (cTries-- > 0)
+ {
+ close(aTries[cTries].aFds[0]);
+ close(aTries[cTries].aFds[1]);
+ }
+ return 0;
+ }
+ }
+ else
+ {
+ err(pCtx, -1, "failed to create stdin pipe (try #%u)", cTries + 1);
+ break;
+ }
+ cTries++;
+ }
+ if (cTries >= K_ELEMENTS(aTries))
+ errx(pCtx, -1, "failed to find a conflict free pair of pipe descriptor for stdin!");
+
+ /* cleanup */
+ while (cTries-- > 0)
+ {
+ close(aTries[cTries].aFds[0]);
+ close(aTries[cTries].aFds[1]);
+ }
+ return 1;
+}
+
+
+/**
+ * Creates a file descriptor for @a pszFilename that does not conflict with any
+ * previous orders.
+ *
+ * We need to be careful that there isn't a close or dup targetting the
+ * temporary file descriptor we return. Also, we need to take care with the
+ * descriptor's inheritability. It should only be inheritable if the returned
+ * descriptor matches the target descriptor (@a fdTarget).
+ *
+ * @returns File descriptor on success, -1 & err/errx on failure.
+ *
+ * The returned file descriptor is not inherited (i.e. close-on-exec),
+ * unless it matches @a fdTarget
+ *
+ * @param pCtx The command execution context.
+ * @param pszFilename The filename to open.
+ * @param fOpen The open flags.
+ * @param fMode The file creation mode (if applicable).
+ * @param cOrders The number of orders.
+ * @param paOrders The order array.
+ * @param fRemoveOnFailure Whether to remove the file on failure.
+ * @param fdTarget The target descriptor.
+ */
+static int kRedirectOpenWithoutConflict(PKMKBUILTINCTX pCtx, const char *pszFilename, int fOpen, mode_t fMode,
+ unsigned cOrders, REDIRECTORDERS *paOrders, int fRemoveOnFailure, int fdTarget)
+{
+#ifdef _O_NOINHERIT
+ int const fNoInherit = _O_NOINHERIT;
+#elif defined(O_NOINHERIT)
+ int const fNoInherit = O_NOINHERIT;
+#elif defined(O_CLOEXEC)
+ int const fNoInherit = O_CLOEXEC;
+#else
+ int const fNoInherit = 0;
+# define USE_FD_CLOEXEC
+#endif
+ int aFdTries[32];
+ unsigned cTries;
+ int fdOpened;
+
+#ifdef KBUILD_OS_WINDOWS
+ if (strcmp(pszFilename, "/dev/null") == 0)
+ pszFilename = "nul";
+#endif
+
+ /* Open it first. */
+ fdOpened = open(pszFilename, fOpen | fNoInherit, fMode);
+ if (fdOpened < 0)
+ return err(pCtx, -1, "open(%s,%#x,) failed", pszFilename, fOpen);
+
+ /* Check for conflicts. */
+ if (!kRedirectHasConflict(fdOpened, cOrders, paOrders))
+ {
+#ifndef KBUILD_OS_WINDOWS
+ if (fdOpened != fdTarget)
+ return fdOpened;
+# ifndef USE_FD_CLOEXEC
+ if (fcntl(fdOpened, F_SETFD, 0) != -1)
+# endif
+#endif
+ return fdOpened;
+ }
+
+ /*
+ * Do conflict resolving.
+ */
+ cTries = 1;
+ aFdTries[cTries++] = fdOpened;
+ while (cTries < K_ELEMENTS(aFdTries))
+ {
+ fdOpened = open(pszFilename, fOpen | fNoInherit, fMode);
+ if (fdOpened >= 0)
+ {
+ if (!kRedirectHasConflict(fdOpened, cOrders, paOrders))
+ {
+#ifndef KBUILD_OS_WINDOWS
+# ifdef USE_FD_CLOEXEC
+ if ( fdOpened == fdTarget
+ || fcntl(fdOpened, F_SETFD, FD_CLOEXEC) != -1)
+# else
+ if ( fdOpened != fdTarget
+ || fcntl(fdOpened, F_SETFD, 0) != -1)
+# endif
+#endif
+ {
+ while (cTries-- > 0)
+ close(aFdTries[cTries]);
+ return fdOpened;
+ }
+ }
+
+ }
+ else
+ {
+ err(pCtx, -1, "open(%s,%#x,) #%u failed", pszFilename, cTries + 1, fOpen);
+ break;
+ }
+ aFdTries[cTries++] = fdOpened;
+ }
+
+ /*
+ * Give up.
+ */
+ if (fdOpened >= 0)
+ errx(pCtx, -1, "failed to find a conflict free file descriptor for '%s'!", pszFilename);
+
+ while (cTries-- > 0)
+ close(aFdTries[cTries]);
+ return -1;
+}
+
+
+/**
+ * Cleans up the file operation orders.
+ *
+ * This does not restore stuff, just closes handles we've opened for the child.
+ *
+ * @param cOrders Number of file operation orders.
+ * @param paOrders The file operation orders.
+ * @param fFailed Set if it's a failure.
+ */
+static void kRedirectCleanupFdOrders(unsigned cOrders, REDIRECTORDERS *paOrders, KBOOL fFailure)
+{
+ unsigned i = cOrders;
+ while (i-- > 0)
+ {
+ if ( paOrders[i].enmOrder == kRedirectOrder_Open
+ && paOrders[i].fdSource != -1)
+ {
+ close(paOrders[i].fdSource);
+ paOrders[i].fdSource = -1;
+
+ if (paOrders[i].fdOtherPipeEnd >= 0)
+ {
+ close(paOrders[i].fdOtherPipeEnd);
+ paOrders[i].fdOtherPipeEnd = -1;
+ }
+
+ if ( fFailure
+ && paOrders[i].fRemoveOnFailure
+ && paOrders[i].pszFilename)
+ remove(paOrders[i].pszFilename);
+ }
+ }
+}
+
+#if !defined(USE_POSIX_SPAWN) && !defined(KBUILD_OS_WINDOWS)
+
+/**
+ * Wrapper that chooses between fprintf and kmk_builtin_ctx_printf to get
+ * an error message to the user.
+ *
+ * @param pCtx The command execution context.
+ * @param pWorkingStdErr Work stderr.
+ * @param pszFormat The message format string.
+ * @param ... Format arguments.
+ */
+static void safe_err_printf(PKMKBUILTINCTX pCtx, FILE *pWorkingStdErr, const char *pszFormat, ...)
+{
+ char szMsg[4096];
+ size_t cchMsg;
+ va_list va;
+
+ va_start(va, pszFormat);
+ vsnprintf(szMsg, sizeof(szMsg) - 1, pszFormat, va);
+ va_end(va);
+ szMsg[sizeof(szMsg) - 1] = '\0';
+ cchMsg = strlen(szMsg);
+
+#ifdef KMK_BUILTIN_STANDALONE
+ (void)pCtx;
+#else
+ if (pCtx->pOut && pCtx->pOut->syncout)
+ output_write_text(pCtx->pOut, 1, szMsg, cchMsg);
+ else
+#endif
+ fwrite(szMsg, cchMsg, 1, pWorkingStdErr);
+}
+
+
+/**
+ * Saves a file handle to one which isn't inherited and isn't affected by the
+ * file orders.
+ *
+ * @returns 0 on success, non-zero exit code on failure.
+ * @param pCtx The command execution context.
+ * @param pToSave Pointer to the file order to save the target
+ * descriptor of.
+ * @param cOrders Number of file orders.
+ * @param paOrders The array of file orders.
+ * @param ppWorkingStdErr Pointer to a pointer to a working stderr. This will
+ * get replaced if we're saving stderr, so that we'll
+ * keep having a working one to report failures to.
+ */
+static int kRedirectSaveHandle(PKMKBUILTINCTX pCtx, REDIRECTORDERS *pToSave, unsigned cOrders,
+ REDIRECTORDERS *paOrders, FILE **ppWorkingStdErr)
+{
+ int fdToSave = pToSave->fdTarget;
+ int rcRet = 10;
+
+ /*
+ * First, check if there's actually handle here that needs saving.
+ */
+ pToSave->fSaved = fcntl(pToSave->fdTarget, F_GETFD, 0);
+ if (pToSave->fSaved != -1)
+ {
+ /*
+ * Try up to 32 times to get a duplicate descriptor that doesn't conflict.
+ */
+ int aFdTries[32];
+ int cTries = 0;
+ do
+ {
+ /* Duplicate the handle (windows makes this complicated). */
+ int fdDup;
+ fdDup = dup(fdToSave);
+ if (fdDup == -1)
+ {
+ safe_err_printf(pCtx, *ppWorkingStdErr, "%s: dup(%#x) failed: %u\n", pCtx->pszProgName, fdToSave, strerror(errno));
+ break;
+ }
+ /* Is the duplicate usable? */
+ if (!kRedirectHasConflict(fdDup, cOrders, paOrders))
+ {
+ pToSave->fdSaved = fdDup;
+ if ( *ppWorkingStdErr == stderr
+ && fdToSave == fileno(*ppWorkingStdErr))
+ {
+ *ppWorkingStdErr = fdopen(fdDup, "wt");
+ if (*ppWorkingStdErr == NULL)
+ {
+ safe_err_printf(pCtx, stderr, "%s: fdopen(%d,\"wt\") failed: %s\n", pCtx->pszProgName, fdDup, strerror(errno));
+ *ppWorkingStdErr = stderr;
+ close(fdDup);
+ break;
+ }
+ }
+ rcRet = 0;
+ break;
+ }
+
+ /* Not usuable, stash it and try again. */
+ aFdTries[cTries++] = fdDup;
+ } while (cTries < K_ELEMENTS(aFdTries));
+
+ /*
+ * Clean up unused duplicates.
+ */
+ while (cTries-- > 0)
+ close(aFdTries[cTries]);
+ }
+ else
+ {
+ /*
+ * Nothing to save.
+ */
+ pToSave->fdSaved = -1;
+ rcRet = 0;
+ }
+ return rcRet;
+}
+
+
+/**
+ * Restores the target file descriptors affected by the file operation orders.
+ *
+ * @param pCtx The command execution context.
+ * @param cOrders Number of file operation orders.
+ * @param paOrders The file operation orders.
+ * @param ppWorkingStdErr Pointer to a pointer to the working stderr. If this
+ * is one of the saved file descriptors, we'll restore
+ * it to stderr.
+ */
+static void kRedirectRestoreFdOrders(PKMKBUILTINCTX pCtx, unsigned cOrders, REDIRECTORDERS *paOrders, FILE **ppWorkingStdErr)
+{
+ int iSavedErrno = errno;
+ unsigned i = cOrders;
+ while (i-- > 0)
+ {
+ if (paOrders[i].fdSaved != -1)
+ {
+ KBOOL fRestoreStdErr = *ppWorkingStdErr != stderr
+ && paOrders[i].fdSaved == fileno(*ppWorkingStdErr);
+ if (dup2(paOrders[i].fdSaved, paOrders[i].fdTarget) != -1)
+ {
+ close(paOrders[i].fdSaved);
+ paOrders[i].fdSaved = -1;
+
+ if (fRestoreStdErr)
+ {
+ *ppWorkingStdErr = stderr;
+ assert(fileno(stderr) == paOrders[i].fdTarget);
+ }
+ }
+ else
+ safe_err_printf(pCtx, *ppWorkingStdErr, "%s: dup2(%d,%d) failed: %s\n",
+ pCtx->pszProgName, paOrders[i].fdSaved, paOrders[i].fdTarget, strerror(errno));
+ }
+
+ if (paOrders[i].fSaved != -1)
+ {
+ if (fcntl(paOrders[i].fdTarget, F_SETFD, paOrders[i].fSaved & FD_CLOEXEC) != -1)
+ paOrders[i].fSaved = -1;
+ else
+ safe_err_printf(pCtx, *ppWorkingStdErr, "%s: fcntl(%d,F_SETFD,%s) failed: %s\n",
+ pCtx->pszProgName, paOrders[i].fdTarget, paOrders[i].fSaved & FD_CLOEXEC ? "FD_CLOEXEC" : "0",
+ strerror(errno));
+ }
+ }
+ errno = iSavedErrno;
+}
+
+
+/**
+ * Executes the file operation orders.
+ *
+ * @returns 0 on success, exit code on failure.
+ * @param pCtx The command execution context.
+ * @param cOrders Number of file operation orders.
+ * @param paOrders File operation orders to execute.
+ * @param ppWorkingStdErr Where to return a working stderr (mainly for
+ * kRedirectRestoreFdOrders).
+ */
+static int kRedirectExecFdOrders(PKMKBUILTINCTX pCtx, unsigned cOrders, REDIRECTORDERS *paOrders, FILE **ppWorkingStdErr)
+{
+ unsigned i;
+
+ *ppWorkingStdErr = stderr;
+ for (i = 0; i < cOrders; i++)
+ {
+ int rcExit = 10;
+ switch (paOrders[i].enmOrder)
+ {
+ case kRedirectOrder_Close:
+ {
+ /* If the handle isn't used by any of the following operation,
+ just mark it as non-inheritable if necessary. */
+ int const fdTarget = paOrders[i].fdTarget;
+ unsigned j;
+ for (j = i + 1; j < cOrders; j++)
+ if (paOrders[j].fdTarget == fdTarget)
+ break;
+ if (j >= cOrders)
+ {
+ paOrders[j].fSaved = fcntl(fdTarget, F_GETFD, 0);
+ if (paOrders[j].fSaved != -1)
+ {
+ if (paOrders[j].fSaved & FD_CLOEXEC)
+ rcExit = 0;
+ else if ( fcntl(fdTarget, F_SETFD, FD_CLOEXEC) != -1
+ || errno == EBADF)
+ rcExit = 0;
+ else
+ safe_err_printf(pCtx, *ppWorkingStdErr, "%s: fcntl(%d,F_SETFD,FD_CLOEXEC) failed: %s\n",
+ pCtx->pszProgName, fdTarget, strerror(errno));
+ }
+ else if (errno == EBADF)
+ rcExit = 0;
+ else
+ safe_err_printf(pCtx, *ppWorkingStdErr, "%s: fcntl(%d,F_GETFD,0) failed: %s\n",
+ pCtx->pszProgName, fdTarget, strerror(errno));
+ }
+ else
+ rcExit = kRedirectSaveHandle(pCtx, &paOrders[i], cOrders, paOrders, ppWorkingStdErr);
+ break;
+ }
+
+ case kRedirectOrder_Dup:
+ case kRedirectOrder_Open:
+ rcExit = kRedirectSaveHandle(pCtx, &paOrders[i], cOrders, paOrders, ppWorkingStdErr);
+ if (rcExit == 0)
+ {
+ if (dup2(paOrders[i].fdSource, paOrders[i].fdTarget) != -1)
+ rcExit = 0;
+ else
+ {
+ if (paOrders[i].enmOrder == kRedirectOrder_Open)
+ safe_err_printf(pCtx, *ppWorkingStdErr, "%s: dup2(%d [%s],%d) failed: %s\n", pCtx->pszProgName,
+ paOrders[i].fdSource, paOrders[i].pszFilename, paOrders[i].fdTarget, strerror(errno));
+ else
+ safe_err_printf(pCtx, *ppWorkingStdErr, "%s: dup2(%d,%d) failed: %s\n",
+ pCtx->pszProgName, paOrders[i].fdSource, paOrders[i].fdTarget, strerror(errno));
+ rcExit = 10;
+ }
+ }
+ break;
+
+ default:
+ safe_err_printf(pCtx, *ppWorkingStdErr, "%s: error! invalid enmOrder=%d\n", pCtx->pszProgName, paOrders[i].enmOrder);
+ rcExit = 99;
+ break;
+ }
+
+ if (rcExit != 0)
+ {
+ kRedirectRestoreFdOrders(pCtx, i, paOrders, ppWorkingStdErr);
+ return rcExit;
+ }
+ }
+
+ return 0;
+}
+
+#endif /* !USE_POSIX_SPAWN */
+#ifdef KBUILD_OS_WINDOWS
+
+/**
+ * Registers the child process with a care provider or waits on it to complete.
+ *
+ * @returns 0 or non-zero success indicator or child exit code, depending on
+ * the value pfIsChildExitCode points to.
+ * @param pCtx The command execution context.
+ * @param hProcess The child process handle.
+ * @param cVerbosity The verbosity level.
+ * @param pPidSpawned Where to return the PID of the spawned child
+ * when we're inside KMK and we're return without
+ * waiting.
+ * @param pfIsChildExitCode Where to indicate whether the return exit code
+ * is from the child or from our setup efforts.
+ */
+static int kRedirectPostnatalCareOnWindows(PKMKBUILTINCTX pCtx, HANDLE hProcess, unsigned cVerbosity,
+ pid_t *pPidSpawned, KBOOL *pfIsChildExitCode)
+{
+ int rcExit;
+ DWORD dwTmp;
+
+# ifndef KMK_BUILTIN_STANDALONE
+ /*
+ * Try register the child with a childcare provider, i.e. winchildren.c
+ * or sub_proc.c.
+ */
+# ifndef CONFIG_NEW_WIN_CHILDREN
+ if (process_kmk_register_redirect(hProcess, pPidSpawned) == 0)
+# else
+ if ( pPidSpawned
+ && MkWinChildCreateRedirect((intptr_t)hProcess, pPidSpawned) == 0)
+# endif
+ {
+ if (cVerbosity > 0)
+ warnx(pCtx, "debug: spawned %d", *pPidSpawned);
+ *pfIsChildExitCode = K_FALSE;
+ return 0;
+ }
+# ifndef CONFIG_NEW_WIN_CHILDREN
+ warn(pCtx, "sub_proc is out of slots, waiting for child...");
+# else
+ if (pPidSpawned)
+ warn(pCtx, "MkWinChildCreateRedirect failed...");
+# endif
+# endif
+
+ /*
+ * Either the provider is outbooked or we're not in a context (like
+ * standalone) where we get help with waiting and must to it ourselves
+ */
+ dwTmp = WaitForSingleObject(hProcess, INFINITE);
+ if (dwTmp != WAIT_OBJECT_0)
+ warnx(pCtx, "WaitForSingleObject failed: %#x\n", dwTmp);
+
+ if (GetExitCodeProcess(hProcess, &dwTmp))
+ rcExit = (int)dwTmp;
+ else
+ {
+ warnx(pCtx, "GetExitCodeProcess failed: %u\n", GetLastError());
+ TerminateProcess(hProcess, 127);
+ rcExit = 127;
+ }
+
+ CloseHandle(hProcess);
+ *pfIsChildExitCode = K_TRUE;
+ return rcExit;
+}
+
+
+/**
+ * Tries to locate the executable image.
+ *
+ * This isn't quite perfect yet...
+ *
+ * @returns pszExecutable or pszBuf with valid string.
+ * @param pszExecutable The specified executable.
+ * @param pszBuf Buffer to return a modified path in.
+ * @param cbBuf Size of return buffer.
+ * @param pszPath The search path.
+ */
+static const char *kRedirectCreateProcessWindowsFindImage(const char *pszExecutable, char *pszBuf, size_t cbBuf,
+ const char *pszPath)
+{
+ /*
+ * Analyze the name.
+ */
+ size_t const cchExecutable = strlen(pszExecutable);
+ BOOL fHavePath = FALSE;
+ BOOL fHaveSuffix = FALSE;
+ size_t off = cchExecutable;
+ while (off > 0)
+ {
+ char ch = pszExecutable[--off];
+ if (ch == '.')
+ {
+ fHaveSuffix = TRUE;
+ break;
+ }
+ if (ch == '\\' || ch == '/' || ch == ':')
+ {
+ fHavePath = TRUE;
+ break;
+ }
+ }
+ if (!fHavePath)
+ while (off > 0)
+ {
+ char ch = pszExecutable[--off];
+ if (ch == '\\' || ch == '/' || ch == ':')
+ {
+ fHavePath = TRUE;
+ break;
+ }
+ }
+ /*
+ * If no path, search the path value.
+ */
+ if (!fHavePath)
+ {
+ char *pszFilename;
+ DWORD cchFound = SearchPathA(pszPath, pszExecutable, fHaveSuffix ? NULL : ".exe", cbBuf, pszBuf, &pszFilename);
+ if (cchFound)
+ return pszBuf;
+ }
+
+ /*
+ * If no suffix, try add .exe.
+ */
+ if ( !fHaveSuffix
+ && GetFileAttributesA(pszExecutable) == INVALID_FILE_ATTRIBUTES
+ && cchExecutable + 4 < cbBuf)
+ {
+ memcpy(pszBuf, pszExecutable, cchExecutable);
+ memcpy(&pszBuf[cchExecutable], ".exe", 5);
+ if (GetFileAttributesA(pszBuf) != INVALID_FILE_ATTRIBUTES)
+ return pszBuf;
+ }
+
+ return pszExecutable;
+}
+
+
+/**
+ * Turns the orders into input for nt_child_inject_standard_handles and
+ * winchildren.c
+ *
+ * @returns 0 on success, non-zero on failure.
+ * @param pCtx The command execution context.
+ * @param cOrders Number of file operation orders.
+ * @param paOrders The file operation orders.
+ * @param pafReplace Replace (TRUE) or leave alone (FALSE) indicator
+ * for each of the starndard handles.
+ * @param pahChild Array of standard handles for injecting into the
+ * child. Parallel to pafReplace.
+ */
+static int kRedirectOrderToWindowsHandles(PKMKBUILTINCTX pCtx, unsigned cOrders, REDIRECTORDERS *paOrders,
+ BOOL pafReplace[3], HANDLE pahChild[3])
+{
+ int i;
+ for (i = 0; i < (int)cOrders; i++)
+ {
+ int fdTarget = paOrders[i].fdTarget;
+ assert(fdTarget >= 0 && fdTarget < 3);
+ switch (paOrders[i].enmOrder)
+ {
+ case kRedirectOrder_Open:
+ if ( (paOrders[i].fOpen & O_APPEND)
+ && lseek(paOrders[i].fdSource, 0, SEEK_END) < 0)
+ return err(pCtx, 10, "lseek-to-end failed on %d (for %d)", paOrders[i].fdSource, fdTarget);
+ /* fall thru */
+ case kRedirectOrder_Dup:
+ pahChild[fdTarget] = (HANDLE)_get_osfhandle(paOrders[i].fdSource);
+ if (pahChild[fdTarget] == NULL || pahChild[fdTarget] == INVALID_HANDLE_VALUE)
+ return err(pCtx, 10, "_get_osfhandle failed on %d (for %d)", paOrders[i].fdSource, fdTarget);
+ break;
+
+ case kRedirectOrder_Close:
+ pahChild[fdTarget] = NULL;
+ break;
+
+ default:
+ assert(0);
+ }
+ pafReplace[fdTarget] = TRUE;
+ }
+ return 0;
+}
+
+
+/**
+ * Alternative approach on windows that use CreateProcess and doesn't require
+ * any serialization wrt handles and CWD.
+ *
+ * @returns 0 on success, non-zero on failure to create.
+ * @param pCtx The command execution context.
+ * @param pszExecutable The child process executable.
+ * @param cArgs Number of arguments.
+ * @param papszArgs The child argument vector.
+ * @param papszEnvVars The child environment vector.
+ * @param pszCwd The current working directory of the child.
+ * @param cOrders Number of file operation orders.
+ * @param paOrders The file operation orders.
+ * @param phProcess Where to return process handle.
+ */
+static int kRedirectCreateProcessWindows(PKMKBUILTINCTX pCtx, const char *pszExecutable, int cArgs, char **papszArgs,
+ char **papszEnvVars, const char *pszCwd, unsigned cOrders,
+ REDIRECTORDERS *paOrders, HANDLE *phProcess)
+{
+ size_t cbArgs;
+ char *pszCmdLine;
+ size_t cbEnv;
+ char *pszzEnv;
+ char *pch;
+ int i;
+ int rc;
+
+ /*
+ * Start by making the the command line. We just need to put spaces
+ * between the arguments since quote_argv don't the quoting already.
+ */
+ cbArgs = 0;
+ for (i = 0; i < cArgs; i++)
+ cbArgs += strlen(papszArgs[i]) + 1;
+ pszCmdLine = pch = (char *)malloc(cbArgs);
+ if (!pszCmdLine)
+ return errx(pCtx, 9, "out of memory!");
+ for (i = 0; i < cArgs; i++)
+ {
+ size_t cch;
+ if (i != 0)
+ *pch++ = ' ';
+ cch = strlen(papszArgs[i]);
+ memcpy(pch, papszArgs[i], cch);
+ pch += cch;
+ }
+ *pch++ = '\0';
+ assert(pch - pszCmdLine == cbArgs);
+
+ /*
+ * The environment vector is also simple.
+ */
+ cbEnv = 0;
+ for (i = 0; papszEnvVars[i]; i++)
+ cbEnv += strlen(papszEnvVars[i]) + 1;
+ cbEnv++;
+ pszzEnv = pch = (char *)malloc(cbEnv);
+ if (pszzEnv)
+ {
+ char szAbsExe[1024];
+ const char *pszPathVal = NULL;
+ STARTUPINFOA StartupInfo;
+ PROCESS_INFORMATION ProcInfo = { NULL, NULL, 0, 0 };
+
+ for (i = 0; papszEnvVars[i]; i++)
+ {
+ size_t cbSrc = strlen(papszEnvVars[i]) + 1;
+ memcpy(pch, papszEnvVars[i], cbSrc);
+ if ( !pszPathVal
+ && cbSrc >= 5
+ && pch[4] == '='
+ && (pch[0] == 'P' || pch[0] == 'p')
+ && (pch[1] == 'A' || pch[1] == 'a')
+ && (pch[2] == 'T' || pch[2] == 't')
+ && (pch[3] == 'H' || pch[3] == 'h'))
+ pszPathVal = &pch[5];
+ pch += cbSrc;
+ }
+ *pch++ = '\0';
+ assert(pch - pszzEnv == cbEnv);
+
+ /*
+ * Locate the executable.
+ */
+ pszExecutable = kRedirectCreateProcessWindowsFindImage(pszExecutable, szAbsExe, sizeof(szAbsExe), pszPathVal);
+
+ /*
+ * Do basic startup info preparation.
+ */
+ memset(&StartupInfo, 0, sizeof(StartupInfo));
+ StartupInfo.cb = sizeof(StartupInfo);
+ GetStartupInfoA(&StartupInfo);
+ StartupInfo.lpReserved2 = 0; /* No CRT file handle + descriptor info possible, sorry. */
+ StartupInfo.cbReserved2 = 0;
+ StartupInfo.dwFlags &= ~STARTF_USESTDHANDLES;
+
+ /*
+ * If there are no redirection orders, we're good.
+ */
+ if (!cOrders)
+ {
+ if (CreateProcessA(pszExecutable, pszCmdLine, NULL /*pProcAttrs*/, NULL /*pThreadAttrs*/,
+ FALSE /*fInheritHandles*/, 0 /*fFlags*/, pszzEnv, pszCwd, &StartupInfo, &ProcInfo))
+ {
+ CloseHandle(ProcInfo.hThread);
+ *phProcess = ProcInfo.hProcess;
+# ifndef KMK_BUILTIN_STANDALONE
+ kmk_cache_exec_image_a(pszExecutable);
+# endif
+ rc = 0;
+ }
+ else
+ rc = errx(pCtx, 10, "CreateProcessA(%s) failed: %u", pszExecutable, GetLastError());
+ }
+ else
+ {
+ /*
+ * Execute the orders, ending up with three handles we need to
+ * implant into the guest process.
+ *
+ * This isn't 100% perfect wrt O_APPEND, but it'll have to do for now.
+ */
+ BOOL afReplace[3] = { FALSE, FALSE, FALSE };
+ HANDLE ahChild[3] = { NULL, NULL, NULL };
+ rc = kRedirectOrderToWindowsHandles(pCtx, cOrders, paOrders, afReplace, ahChild);
+ if (rc == 0)
+ {
+ /*
+ * Start the process in suspended animation so we can inject handles.
+ */
+ if (CreateProcessA(pszExecutable, pszCmdLine, NULL /*pProcAttrs*/, NULL /*pThreadAttrs*/,
+ FALSE /*fInheritHandles*/, CREATE_SUSPENDED, pszzEnv, pszCwd, &StartupInfo, &ProcInfo))
+ {
+ unsigned i;
+
+ /* Inject the handles and try make it start executing. */
+ char szErrMsg[128];
+ rc = nt_child_inject_standard_handles(ProcInfo.hProcess, afReplace, ahChild, szErrMsg, sizeof(szErrMsg));
+ if (rc)
+ rc = errx(pCtx, 10, "%s", szErrMsg);
+ else if (!ResumeThread(ProcInfo.hThread))
+ rc = errx(pCtx, 10, "ResumeThread failed: %u", GetLastError());
+
+ /* Duplicate the write end of any stdin pipe handles into the child. */
+ for (i = 0; i < cOrders; i++)
+ if (paOrders[i].fdOtherPipeEnd >= 0)
+ {
+ HANDLE hIgnored = INVALID_HANDLE_VALUE;
+ HANDLE hPipeW = (HANDLE)_get_osfhandle(paOrders[i].fdOtherPipeEnd);
+ if (!DuplicateHandle(GetCurrentProcess(), hPipeW, ProcInfo.hProcess, &hIgnored, 0 /*fDesiredAccess*/,
+ TRUE /*fInheritable*/, DUPLICATE_SAME_ACCESS))
+ rc = errx(pCtx, 10, "DuplicateHandle failed on other stdin pipe end %d/%p: %u",
+ paOrders[i].fdOtherPipeEnd, hPipeW, GetLastError());
+ }
+
+ /* Kill it if any of that fails. */
+ if (rc != 0)
+ TerminateProcess(ProcInfo.hProcess, rc);
+
+ CloseHandle(ProcInfo.hThread);
+ *phProcess = ProcInfo.hProcess;
+# ifndef KMK_BUILTIN_STANDALONE
+ kmk_cache_exec_image_a(pszExecutable);
+# endif
+ rc = 0;
+ }
+ else
+ rc = errx(pCtx, 10, "CreateProcessA(%s) failed: %u", pszExecutable, GetLastError());
+ }
+ }
+ free(pszzEnv);
+ }
+ else
+ rc = errx(pCtx, 9, "out of memory!");
+ free(pszCmdLine);
+ return rc;
+}
+
+# if !defined(KMK_BUILTIN_STANDALONE) && defined(CONFIG_NEW_WIN_CHILDREN)
+/**
+ * Pass the problem on to winchildren.c when we're on one of its workers.
+ *
+ * @returns 0 on success, non-zero on failure to create.
+ * @param pCtx The command execution context.
+ * @param pszExecutable The child process executable.
+ * @param cArgs Number of arguments.
+ * @param papszArgs The child argument vector.
+ * @param papszEnvVars The child environment vector.
+ * @param pszCwd The current working directory of the child.
+ * @param cOrders Number of file operation orders.
+ * @param paOrders The file operation orders.
+ * @param phProcess Where to return process handle.
+ * @param pfIsChildExitCode Where to indicate whether the return exit code
+ * is from the child or from our setup efforts.
+ */
+static int kRedirectExecProcessWithinOnWorker(PKMKBUILTINCTX pCtx, const char *pszExecutable, int cArgs, char **papszArgs,
+ char **papszEnvVars, const char *pszCwd, unsigned cOrders,
+ REDIRECTORDERS *paOrders, KBOOL *pfIsChildExitCode)
+{
+ BOOL afReplace[3] = { FALSE, FALSE, FALSE };
+ HANDLE ahChild[3] = { NULL, NULL, NULL };
+ int rc = kRedirectOrderToWindowsHandles(pCtx, cOrders, paOrders, afReplace, ahChild);
+ if (rc == 0)
+ {
+ rc = MkWinChildBuiltInExecChild(pCtx->pvWorker, pszExecutable, papszArgs, TRUE /*fQuotedArgv*/,
+ papszEnvVars, pszCwd, afReplace, ahChild);
+ *pfIsChildExitCode = K_TRUE;
+ }
+ return rc;
+}
+# endif /* !KMK_BUILTIN_STANDALONE */
+
+#endif /* KBUILD_OS_WINDOWS */
+
+/**
+ * Does the child spawning .
+ *
+ * @returns Exit code.
+ * @param pCtx The command execution context.
+ * @param pszExecutable The child process executable.
+ * @param cArgs Number of arguments.
+ * @param papszArgs The child argument vector.
+ * @param fWatcomBrainDamage Whether MSC need to do quoting according to
+ * weird Watcom WCC rules.
+ * @param papszEnvVars The child environment vector.
+ * @param pszCwd The current working directory of the child.
+ * @param pszSavedCwd The saved current working directory. This is
+ * NULL if the CWD doesn't need changing.
+ * @param cOrders Number of file operation orders.
+ * @param paOrders The file operation orders.
+ * @param pFileActions The posix_spawn file actions.
+ * @param cVerbosity The verbosity level.
+ * @param pPidSpawned Where to return the PID of the spawned child
+ * when we're inside KMK and we're return without
+ * waiting.
+ * @param pfIsChildExitCode Where to indicate whether the return exit code
+ * is from the child or from our setup efforts.
+ */
+static int kRedirectDoSpawn(PKMKBUILTINCTX pCtx, const char *pszExecutable, int cArgs, char **papszArgs, int fWatcomBrainDamage,
+ char **papszEnvVars, const char *pszCwd, const char *pszSavedCwd,
+ unsigned cOrders, REDIRECTORDERS *paOrders,
+#ifdef USE_POSIX_SPAWN
+ posix_spawn_file_actions_t *pFileActions,
+#endif
+ unsigned cVerbosity,
+#ifdef KMK
+ pid_t *pPidSpawned,
+#endif
+ KBOOL *pfIsChildExitCode)
+{
+ int rcExit = 0;
+ int i;
+#ifdef _MSC_VER
+ char **papszArgsOriginal = papszArgs;
+#endif
+ *pfIsChildExitCode = K_FALSE;
+
+#ifdef _MSC_VER
+ /*
+ * Do MSC parameter quoting.
+ */
+ papszArgs = malloc((cArgs + 1) * sizeof(papszArgs[0]));
+ if (papszArgs)
+ memcpy(papszArgs, papszArgsOriginal, (cArgs + 1) * sizeof(papszArgs[0]));
+ else
+ return errx(pCtx, 9, "out of memory!");
+
+ rcExit = quote_argv(cArgs, papszArgs, fWatcomBrainDamage, 0 /*fFreeOrLeak*/);
+ if (rcExit == 0)
+#endif
+ {
+ /*
+ * Display what we're about to execute if we're in verbose mode.
+ */
+ if (cVerbosity > 0)
+ {
+ for (i = 0; i < cArgs; i++)
+ warnx(pCtx, "debug: argv[%i]=%s<eos>", i, papszArgs[i]);
+ for (i = 0; i < (int)cOrders; i++)
+ switch (paOrders[i].enmOrder)
+ {
+ case kRedirectOrder_Close:
+ warnx(pCtx, "debug: close %d\n", paOrders[i].fdTarget);
+ break;
+ case kRedirectOrder_Dup:
+ warnx(pCtx, "debug: dup %d to %d\n", paOrders[i].fdSource, paOrders[i].fdTarget);
+ break;
+ case kRedirectOrder_Open:
+ warnx(pCtx, "debug: open '%s' (%#x) as [%d ->] %d\n",
+ paOrders[i].pszFilename, paOrders[i].fOpen, paOrders[i].fdSource, paOrders[i].fdTarget);
+ break;
+ default:
+ warnx(pCtx, "error! invalid enmOrder=%d", paOrders[i].enmOrder);
+ assert(0);
+ break;
+ }
+ if (pszSavedCwd)
+ warnx(pCtx, "debug: chdir %s\n", pszCwd);
+ }
+
+#ifndef KBUILD_OS_WINDOWS
+ /*
+ * Change working directory if so requested.
+ */
+ if (pszSavedCwd)
+ {
+ if (chdir(pszCwd) < 0)
+ rcExit = errx(pCtx, 10, "Failed to change directory to '%s'", pszCwd);
+ }
+#endif /* KBUILD_OS_WINDOWS */
+ if (rcExit == 0)
+ {
+# if !defined(USE_POSIX_SPAWN) && !defined(KBUILD_OS_WINDOWS)
+ /*
+ * Execute the file orders.
+ */
+ FILE *pWorkingStdErr = NULL;
+ rcExit = kRedirectExecFdOrders(pCtx, cOrders, paOrders, &pWorkingStdErr);
+ if (rcExit == 0)
+# endif
+ {
+# ifdef KMK
+ /*
+ * We're spawning from within kmk.
+ */
+# ifdef KBUILD_OS_WINDOWS
+ /* Windows is slightly complicated due to handles and winchildren.c. */
+ if (pPidSpawned)
+ *pPidSpawned = 0;
+# ifdef CONFIG_NEW_WIN_CHILDREN
+ if (pCtx->pvWorker && !pPidSpawned)
+ rcExit = kRedirectExecProcessWithinOnWorker(pCtx, pszExecutable, cArgs, papszArgs, papszEnvVars,
+ pszSavedCwd ? pszCwd : NULL, cOrders, paOrders,
+ pfIsChildExitCode);
+ else
+# endif
+ {
+ HANDLE hProcess = INVALID_HANDLE_VALUE;
+ rcExit = kRedirectCreateProcessWindows(pCtx, pszExecutable, cArgs, papszArgs, papszEnvVars,
+ pszSavedCwd ? pszCwd : NULL, cOrders, paOrders, &hProcess);
+ if (rcExit == 0)
+ rcExit = kRedirectPostnatalCareOnWindows(pCtx, hProcess, cVerbosity, pPidSpawned, pfIsChildExitCode);
+ }
+
+# elif defined(KBUILD_OS_OS2)
+ *pPidSpawned = _spawnvpe(P_NOWAIT, pszExecutable, papszArgs, papszEnvVars);
+ kRedirectRestoreFdOrders(pCtx, cOrders, paOrders, &pWorkingStdErr);
+ if (*pPidSpawned != -1)
+ {
+ if (cVerbosity > 0)
+ warnx(pCtx, "debug: spawned %d", *pPidSpawned);
+ }
+ else
+ {
+ rcExit = err(pCtx, 10, "_spawnvpe(%s) failed", pszExecutable);
+ *pPidSpawned = 0;
+ }
+# else
+ rcExit = posix_spawnp(pPidSpawned, pszExecutable, pFileActions, NULL /*pAttr*/, papszArgs, papszEnvVars);
+ if (rcExit == 0)
+ {
+ if (cVerbosity > 0)
+ warnx(pCtx, "debug: spawned %d", *pPidSpawned);
+ }
+ else
+ {
+ rcExit = errx(pCtx, 10, "posix_spawnp(%s) failed: %s", pszExecutable, strerror(rcExit));
+ *pPidSpawned = 0;
+ }
+# endif
+
+#else /* !KMK */
+ /*
+ * Spawning from inside the kmk_redirect executable.
+ */
+# ifdef KBUILD_OS_WINDOWS
+ HANDLE hProcess = INVALID_HANDLE_VALUE;
+ rcExit = kRedirectCreateProcessWindows(pCtx, pszExecutable, cArgs, papszArgs, papszEnvVars,
+ pszSavedCwd ? pszCwd : NULL, cOrders, paOrders, &hProcess);
+ if (rcExit == 0)
+ {
+ DWORD dwWait;
+ do
+ dwWait = WaitForSingleObject(hProcess, INFINITE);
+ while (dwWait == WAIT_IO_COMPLETION || dwWait == WAIT_TIMEOUT);
+
+ dwWait = 11;
+ if (GetExitCodeProcess(hProcess, &dwWait))
+ {
+ *pfIsChildExitCode = K_TRUE;
+ rcExit = dwWait;
+ }
+ else
+ rcExit = errx(pCtx, 11, "GetExitCodeProcess(%s) failed: %u", pszExecutable, GetLastError());
+ }
+
+#elif defined(KBUILD_OS_OS2)
+ errno = 0;
+ rcExit = (int)_spawnvpe(P_WAIT, pszExecutable, papszArgs, papszEnvVars);
+ kRedirectRestoreFdOrders(pCtx, cOrders, paOrders, &pWorkingStdErr);
+ if (rcExit != -1 || errno == 0)
+ {
+ *pfIsChildExitCode = K_TRUE;
+ if (cVerbosity > 0)
+ warnx(pCtx, "debug: exit code: %d", rcExit);
+ }
+ else
+ rcExit = err(pCtx, 10, "_spawnvpe(%s) failed", pszExecutable);
+
+# else
+ pid_t pidChild = 0;
+ rcExit = posix_spawnp(&pidChild, pszExecutable, pFileActions, NULL /*pAttr*/, papszArgs, papszEnvVars);
+ if (rcExit == 0)
+ {
+ *pfIsChildExitCode = K_TRUE;
+ if (cVerbosity > 0)
+ warnx(pCtx, "debug: spawned %d", pidChild);
+
+ /* Wait for the child. */
+ for (;;)
+ {
+ int rcExitRaw = 1;
+ pid_t pid = waitpid(pidChild, &rcExitRaw, 0 /*block*/);
+ if (pid == pidChild)
+ {
+ rcExit = WIFEXITED(rcExitRaw) ? WEXITSTATUS(rcExitRaw) : 63;
+ if (cVerbosity > 0)
+ warnx(pCtx, "debug: %d exit code: %d (%d)", pidChild, rcExit, rcExitRaw);
+ break;
+ }
+ if ( errno != EINTR
+# ifdef ERESTART
+ && errno != ERESTART
+# endif
+ )
+ {
+ rcExit = err(pCtx, 11, "waitpid failed");
+ kill(pidChild, SIGKILL);
+ break;
+ }
+ }
+ }
+ else
+ rcExit = errx(pCtx, 10, "posix_spawnp(%s) failed: %s", pszExecutable, strerror(rcExit));
+# endif
+#endif /* !KMK */
+ }
+ }
+
+#ifndef KBUILD_OS_WINDOWS
+ /*
+ * Restore the current directory.
+ */
+ if (pszSavedCwd)
+ {
+ if (chdir(pszSavedCwd) < 0)
+ warn(pCtx, "Failed to restore directory to '%s'", pszSavedCwd);
+ }
+#endif
+ }
+#ifdef _MSC_VER
+ else
+ rcExit = errx(pCtx, 9, "quite_argv failed: %u", rcExit);
+
+ /* Restore the original argv strings, freeing the quote_argv replacements. */
+ i = cArgs;
+ while (i-- > 0)
+ if (papszArgs[i] != papszArgsOriginal[i])
+ free(papszArgs[i]);
+ free(papszArgs);
+#endif
+ return rcExit;
+}
+
+
+/**
+ * The function that does almost everything here... ugly.
+ */
+int kmk_builtin_redirect(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx, struct child *pChild, pid_t *pPidSpawned)
+{
+ int rcExit = 0;
+ KBOOL fChildExitCode = K_FALSE;
+#ifdef USE_POSIX_SPAWN
+ posix_spawn_file_actions_t FileActions;
+#endif
+ unsigned cOrders = 0;
+ REDIRECTORDERS aOrders[32];
+
+ int iArg;
+ const char *pszExecutable = NULL;
+ char **papszEnvVars = NULL;
+ unsigned cAllocatedEnvVars;
+ unsigned cEnvVars;
+ int fWatcomBrainDamage = 0;
+ int cVerbosity = 0;
+ char *pszSavedCwd = NULL;
+ size_t const cbCwdBuf = GET_PATH_MAX;
+ PATH_VAR(szCwd);
+#ifdef KBUILD_OS_OS2
+ ULONG ulLibPath;
+ char *apszSavedLibPaths[LIBPATHSTRICT + 1] = { NULL, NULL, NULL, NULL };
+#endif
+
+
+ if (argc <= 1)
+ return kmk_redirect_usage(pCtx, 1);
+
+ /*
+ * Create default program environment.
+ */
+#if defined(KMK) && defined(KBUILD_OS_WINDOWS)
+ if (getcwd_fs(szCwd, cbCwdBuf) != NULL)
+#else
+ if (getcwd(szCwd, cbCwdBuf) != NULL)
+#endif
+ { /* likely */ }
+ else
+ return err(pCtx, 9, "getcwd failed");
+
+ /* We start out with a read-only enviornment from kmk or the crt, and will
+ duplicate it if we make changes to it. */
+ cAllocatedEnvVars = 0;
+ papszEnvVars = envp;
+ cEnvVars = 0;
+ while (papszEnvVars[cEnvVars] != NULL)
+ cEnvVars++;
+
+#ifdef USE_POSIX_SPAWN
+ /*
+ * Init posix attributes with stdout/err redirections according to pCtx.
+ */
+ rcExit = posix_spawn_file_actions_init(&FileActions);
+ if (rcExit != 0)
+ rcExit = errx(pCtx, 9, "posix_spawn_file_actions_init failed: %s", strerror(rcExit));
+# if !defined(KMK_BUILTIN_STANDALONE) && !defined(CONFIG_WITH_OUTPUT_IN_MEMORY)
+ if (pCtx->pOut && rcExit == 0)
+ {
+ if (pCtx->pOut->out >= 0)
+ {
+ rcExit = posix_spawn_file_actions_adddup2(&FileActions, pCtx->pOut->out, 1);
+ if (rcExit != 0)
+ rcExit = errx(pCtx, 2, "posix_spawn_file_actions_addclose(%d, 1) failed: %s", pCtx->pOut->out, strerror(rcExit));
+ }
+ if (pCtx->pOut->err >= 0 && rcExit == 0)
+ {
+ rcExit = posix_spawn_file_actions_adddup2(&FileActions, pCtx->pOut->err, 2);
+ if (rcExit != 0)
+ rcExit = errx(pCtx, 2, "posix_spawn_file_actions_addclose(%d, 1) failed: %s", pCtx->pOut->err, strerror(rcExit));
+ }
+ }
+# endif
+#endif
+
+ /*
+ * Parse arguments.
+ */
+ for (iArg = 1; rcExit == 0 && iArg < argc; iArg++)
+ {
+ char *pszArg = argv[iArg];
+ if (*pszArg == '-')
+ {
+ int fd;
+ char chOpt;
+ const char *pszValue;
+
+ chOpt = *++pszArg;
+ pszArg++;
+ if (chOpt == '-')
+ {
+ /* '--' indicates where the bits to execute start. Check if we're
+ relaunching ourselves here and just continue parsing if we are. */
+ if (*pszArg == '\0')
+ {
+ iArg++;
+ if ( iArg >= argc
+ || ( strcmp(argv[iArg], "kmk_builtin_redirect") != 0
+ && strcmp(argv[iArg], argv[0]) != 0))
+ break;
+ continue;
+ }
+
+ if ( strcmp(pszArg, "wcc-brain-damage") == 0
+ || strcmp(pszArg, "watcom-brain-damage") == 0)
+ {
+ fWatcomBrainDamage = 1;
+ continue;
+ }
+
+ /* convert to short. */
+ if (strcmp(pszArg, "help") == 0)
+ chOpt = 'h';
+ else if (strcmp(pszArg, "version") == 0)
+ chOpt = 'V';
+ else if ( strcmp(pszArg, "set") == 0
+ || strcmp(pszArg, "env") == 0)
+ chOpt = 'E';
+ else if (strcmp(pszArg, "append") == 0)
+ chOpt = 'A';
+ else if (strcmp(pszArg, "prepend") == 0)
+ chOpt = 'D';
+ else if (strcmp(pszArg, "unset") == 0)
+ chOpt = 'U';
+ else if ( strcmp(pszArg, "zap-env") == 0
+ || strcmp(pszArg, "ignore-environment") == 0 /* GNU env compatibility. */ )
+ chOpt = 'Z';
+ else if (strcmp(pszArg, "chdir") == 0)
+ chOpt = 'C';
+ else if (strcmp(pszArg, "close") == 0)
+ chOpt = 'c';
+ else if (strcmp(pszArg, "verbose") == 0)
+ chOpt = 'v';
+ else if (strcmp(pszArg, "stdin-pipe") == 0)
+ chOpt = 'I';
+ else
+ {
+ errx(pCtx, 2, "Unknown option: '%s'", pszArg - 2);
+ rcExit = kmk_redirect_usage(pCtx, 1);
+ break;
+ }
+ pszArg = "";
+ }
+
+ /*
+ * Deal with the obligatory help and version switches first to get them out of the way.
+ */
+ if (chOpt == 'h')
+ {
+ kmk_redirect_usage(pCtx, 0);
+ rcExit = -1;
+ break;
+ }
+ if (chOpt == 'V')
+ {
+ kbuild_version(argv[0]);
+ rcExit = -1;
+ break;
+ }
+
+ /*
+ * Get option value first, if the option takes one.
+ */
+ if ( chOpt == 'E'
+ || chOpt == 'A'
+ || chOpt == 'D'
+ || chOpt == 'U'
+ || chOpt == 'C'
+ || chOpt == 'c'
+ || chOpt == 'd'
+ || chOpt == 'e')
+ {
+ if (*pszArg != '\0')
+ pszValue = pszArg + (*pszArg == ':' || *pszArg == '=');
+ else if (++iArg < argc)
+ pszValue = argv[iArg];
+ else
+ {
+ errx(pCtx, 2, "syntax error: Option -%c requires a value!", chOpt);
+ rcExit = kmk_redirect_usage(pCtx, 1);
+ break;
+ }
+ }
+ else
+ pszValue = NULL;
+
+ /*
+ * Environment switch?
+ */
+ if (chOpt == 'E')
+ {
+ const char *pchEqual = strchr(pszValue, '=');
+#ifdef KBUILD_OS_OS2
+ if ( strncmp(pszValue, TUPLE("BEGINLIBPATH=")) == 0
+ || strncmp(pszValue, TUPLE("ENDLIBPATH=")) == 0
+ || strncmp(pszValue, TUPLE("LIBPATHSTRICT=")) == 0)
+ {
+ ULONG ulVar = *pszValue == 'B' ? BEGIN_LIBPATH
+ : *pszValue == 'E' ? END_LIBPATH
+ : LIBPATHSTRICT;
+ APIRET rc;
+ if (apszSavedLibPaths[ulVar] == NULL)
+ {
+ /* The max length is supposed to be 1024 bytes. */
+ apszSavedLibPaths[ulVar] = calloc(1024, 2);
+ if (apszSavedLibPaths[ulVar])
+ {
+ rc = DosQueryExtLIBPATH(apszSavedLibPaths[ulVar], ulVar);
+ if (rc)
+ {
+ rcExit = errx(pCtx, 9, "DosQueryExtLIBPATH(,%u) failed: %lu", ulVar, rc);
+ free(apszSavedLibPaths[ulVar]);
+ apszSavedLibPaths[ulVar] = NULL;
+ }
+ }
+ else
+ rcExit = errx(pCtx, 9, "out of memory!");
+ }
+ if (rcExit == 0)
+ {
+ rc = DosSetExtLIBPATH(pchEqual + 1, ulVar);
+ if (rc)
+ rcExit = errx(pCtx, 9, "error: DosSetExtLibPath(\"%s\", %.*s (%lu)): %lu",
+ pchEqual, pchEqual - pszValue, pchEqual + 1, ulVar, rc);
+ }
+ continue;
+ }
+#endif /* KBUILD_OS_OS2 */
+
+ /* We differ from kSubmit here and use putenv sematics. */
+ if (pchEqual)
+ {
+ if (pchEqual[1] != '\0')
+ rcExit = kBuiltinOptEnvSet(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
+ else
+ {
+ char *pszCopy = strdup(pszValue);
+ if (pszCopy)
+ {
+ pszCopy[pchEqual - pszValue] = '\0';
+ rcExit = kBuiltinOptEnvUnset(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszCopy);
+ free(pszCopy);
+ }
+ else
+ rcExit = errx(pCtx, 1, "out of memory!");
+ }
+ continue;
+ }
+ /* Simple unset. */
+ chOpt = 'U';
+ }
+
+ /*
+ * Append or prepend value to and environment variable.
+ */
+ if (chOpt == 'A' || chOpt == 'D')
+ {
+#ifdef KBUILD_OS_OS2
+ if ( strcmp(pszValue, "BEGINLIBPATH") == 0
+ || strcmp(pszValue, "ENDLIBPATH") == 0
+ || strcmp(pszValue, "LIBPATHSTRICT") == 0)
+ rcExit = errx(pCtx, 2, "error: '%s' cannot currently be appended or prepended to. Please use -E/--set for now.", pszValue);
+ else
+#endif
+ if (chOpt == 'A')
+ rcExit = kBuiltinOptEnvAppend(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
+ else
+ rcExit = kBuiltinOptEnvPrepend(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
+ continue;
+ }
+
+ /*
+ * Unset environment variable.
+ */
+ if (chOpt == 'U')
+ {
+#ifdef KBUILD_OS_OS2
+ if ( strcmp(pszValue, "BEGINLIBPATH") == 0
+ || strcmp(pszValue, "ENDLIBPATH") == 0
+ || strcmp(pszValue, "LIBPATHSTRICT") == 0)
+ rcExit = errx(pCtx, 2, "error: '%s' cannot be unset, only set to an empty value using -E/--set.", pszValue);
+ else
+#endif
+ rcExit = kBuiltinOptEnvUnset(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
+ continue;
+ }
+
+ /*
+ * Zap environment switch?
+ */
+ if (chOpt == 'Z') /* (no -i option here, as it's reserved for stdin) */
+ {
+ rcExit = kBuiltinOptEnvZap(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity);
+ continue;
+ }
+
+ /*
+ * Change directory switch?
+ */
+ if (chOpt == 'C')
+ {
+ if (pszSavedCwd == NULL)
+ pszSavedCwd = strdup(szCwd);
+ if (pszSavedCwd)
+ rcExit = kBuiltinOptChDir(pCtx, szCwd, cbCwdBuf, pszValue);
+ else
+ rcExit = err(pCtx, 9, "out of memory!");
+ continue;
+ }
+
+
+ /*
+ * Verbose operation switch?
+ */
+ if (chOpt == 'v')
+ {
+ cVerbosity++;
+ continue;
+ }
+
+ /*
+ * Executable image other than the first argument following '--'.
+ */
+ if (chOpt == 'e')
+ {
+ pszExecutable = pszValue;
+ continue;
+ }
+
+ /*
+ * Okay, it is some file descriptor operation. Make sure we've got room for it.
+ */
+ if (cOrders + 1 < K_ELEMENTS(aOrders))
+ {
+ aOrders[cOrders].fdTarget = -1;
+ aOrders[cOrders].fdSource = -1;
+ aOrders[cOrders].fOpen = 0;
+ aOrders[cOrders].fRemoveOnFailure = 0;
+ aOrders[cOrders].pszFilename = NULL;
+ aOrders[cOrders].fdOtherPipeEnd = -1;
+#ifndef USE_POSIX_SPAWN
+ aOrders[cOrders].fdSaved = -1;
+#endif
+ }
+ else
+ {
+ rcExit = errx(pCtx, 2, "error: too many file actions (max: %d)", K_ELEMENTS(aOrders));
+ break;
+ }
+
+ if (chOpt == 'c')
+ {
+ /*
+ * Close the specified file descriptor (no stderr/out/in aliases).
+ */
+ char *pszTmp;
+ fd = (int)strtol(pszValue, &pszTmp, 0);
+ if (pszTmp == pszValue || *pszTmp != '\0')
+ rcExit = errx(pCtx, 2, "error: failed to convert '%s' to a number", pszValue);
+ else if (fd < 0)
+ rcExit = errx(pCtx, 2, "error: negative fd %d (%s)", fd, pszValue);
+#ifdef ONLY_TARGET_STANDARD_HANDLES
+ else if (fd > 2)
+ rcExit = errx(pCtx, 2, "error: %d is not a standard descriptor number", fd);
+#endif
+ else
+ {
+ aOrders[cOrders].enmOrder = kRedirectOrder_Close;
+ aOrders[cOrders].fdTarget = fd;
+ cOrders++;
+#ifdef USE_POSIX_SPAWN
+ rcExit = posix_spawn_file_actions_addclose(&FileActions, fd);
+ if (rcExit != 0)
+ rcExit = errx(pCtx, 2, "posix_spawn_file_actions_addclose(%d) failed: %s", fd, strerror(rcExit));
+#endif
+ }
+ }
+ else if (chOpt == 'd')
+ {
+ /*
+ * Duplicate file handle. Value is fdTarget=fdSource
+ */
+ char *pszEqual;
+ fd = (int)strtol(pszValue, &pszEqual, 0);
+ if (pszEqual == pszValue)
+ rcExit = errx(pCtx, 2, "error: failed to convert target descriptor of '-d %s' to a number", pszValue);
+ else if (fd < 0)
+ rcExit = errx(pCtx, 2, "error: negative target descriptor %d ('-d %s')", fd, pszValue);
+#ifdef ONLY_TARGET_STANDARD_HANDLES
+ else if (fd > 2)
+ rcExit = errx(pCtx, 2, "error: target %d is not a standard descriptor number", fd);
+#endif
+ else if (*pszEqual != '=')
+ rcExit = errx(pCtx, 2, "syntax error: expected '=' to follow target descriptor: '-d %s'", pszValue);
+ else
+ {
+ char *pszEnd;
+ int fdSource = (int)strtol(++pszEqual, &pszEnd, 0);
+ if (pszEnd == pszEqual || *pszEnd != '\0')
+ rcExit = errx(pCtx, 2, "error: failed to convert source descriptor of '-d %s' to a number", pszValue);
+ else if (fdSource < 0)
+ rcExit = errx(pCtx, 2, "error: negative source descriptor %d ('-d %s')", fdSource, pszValue);
+ else
+ {
+ aOrders[cOrders].enmOrder = kRedirectOrder_Dup;
+ aOrders[cOrders].fdTarget = fd;
+ aOrders[cOrders].fdSource = fdSource;
+ cOrders++;
+#ifdef USE_POSIX_SPAWN
+ rcExit = posix_spawn_file_actions_adddup2(&FileActions, fdSource, fd);
+ if (rcExit != 0)
+ rcExit = errx(pCtx, 2, "posix_spawn_file_actions_adddup2(%d) failed: %s",
+ fdSource, fd, strerror(rcExit));
+#endif
+ }
+ }
+ }
+ else if (chOpt == 'I')
+ {
+ /*
+ * Replace stdin with the read end of an anonymous pipe.
+ */
+ int aFds[2] = { -1, -1 };
+ rcExit = kRedirectCreateStdInPipeWithoutConflict(pCtx, aFds, cOrders, aOrders, 0 /*fdTarget*/);
+ if (rcExit == 0)
+ {
+ aOrders[cOrders].enmOrder = kRedirectOrder_Dup;
+ aOrders[cOrders].fdTarget = 0;
+ aOrders[cOrders].fdSource = aFds[0];
+ aOrders[cOrders].fdOtherPipeEnd = aFds[1];
+ cOrders++;
+#ifdef USE_POSIX_SPAWN
+ rcExit = posix_spawn_file_actions_adddup2(&FileActions, aFds[0], 0);
+ if (rcExit != 0)
+ rcExit = errx(pCtx, 2, "posix_spawn_file_actions_adddup2(0) failed: %s", strerror(rcExit));
+#endif
+ }
+ }
+ else
+ {
+ /*
+ * Open file as a given file descriptor.
+ */
+ int fdOpened;
+ int fOpen;
+
+ /* mode */
+ switch (chOpt)
+ {
+ case 'r':
+ chOpt = *pszArg++;
+ if (chOpt == '+')
+ {
+ fOpen = O_RDWR;
+ chOpt = *pszArg++;
+ }
+ else
+ fOpen = O_RDONLY;
+ break;
+
+ case 'w':
+ chOpt = *pszArg++;
+ if (chOpt == '+')
+ {
+ fOpen = O_RDWR | O_CREAT | O_TRUNC;
+ chOpt = *pszArg++;
+ }
+ else
+ fOpen = O_WRONLY | O_CREAT | O_TRUNC;
+ aOrders[cOrders].fRemoveOnFailure = 1;
+ break;
+
+ case 'a':
+ chOpt = *pszArg++;
+ if (chOpt == '+')
+ {
+ fOpen = O_RDWR | O_CREAT | O_APPEND;
+ chOpt = *pszArg++;
+ }
+ else
+ fOpen = O_WRONLY | O_CREAT | O_APPEND;
+ break;
+
+ case 'i': /* make sure stdin is read-only. */
+ fOpen = O_RDONLY;
+ break;
+
+ case '+':
+ rcExit = errx(pCtx, 2, "syntax error: Unexpected '+' in '%s'", argv[iArg]);
+ continue;
+
+ default:
+ fOpen = O_RDWR | O_CREAT | O_TRUNC;
+ aOrders[cOrders].fRemoveOnFailure = 1;
+ break;
+ }
+
+ /* binary / text modifiers */
+ switch (chOpt)
+ {
+ case 'b':
+ chOpt = *pszArg++;
+ default:
+#ifdef O_BINARY
+ fOpen |= O_BINARY;
+#elif defined(_O_BINARY)
+ fOpen |= _O_BINARY;
+#endif
+ break;
+
+ case 't':
+#ifdef O_TEXT
+ fOpen |= O_TEXT;
+#elif defined(_O_TEXT)
+ fOpen |= _O_TEXT;
+#endif
+ chOpt = *pszArg++;
+ break;
+
+ }
+
+ /* convert to file descriptor number */
+ switch (chOpt)
+ {
+ case 'i':
+ fd = 0;
+ break;
+
+ case 'o':
+ fd = 1;
+ break;
+
+ case 'e':
+ fd = 2;
+ break;
+
+ case '0':
+ if (*pszArg == '\0')
+ {
+ fd = 0;
+ break;
+ }
+ /* fall thru */
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ pszValue = pszArg - 1;
+ fd = (int)strtol(pszValue, &pszArg, 0);
+ if (pszArg == pszValue)
+ rcExit = errx(pCtx, 2, "error: failed to convert '%s' to a number", argv[iArg]);
+ else if (fd < 0)
+ rcExit = errx(pCtx, 2, "error: negative fd %d (%s)", fd, argv[iArg]);
+#ifdef ONLY_TARGET_STANDARD_HANDLES
+ else if (fd > 2)
+ rcExit = errx(pCtx, 2, "error: %d is not a standard descriptor number", fd);
+#endif
+ else
+ break;
+ continue;
+
+ /*
+ * Invalid argument.
+ */
+ default:
+ rcExit = errx(pCtx, 2, "error: failed to convert '%s' ('%s') to a file descriptor", pszArg, argv[iArg]);
+ continue;
+ }
+
+ /*
+ * Check for the filename.
+ */
+ if (*pszArg != '\0')
+ {
+ if (*pszArg != ':' && *pszArg != '=')
+ {
+ rcExit = errx(pCtx, 2, "syntax error: characters following the file descriptor: '%s' ('%s')",
+ pszArg, argv[iArg]);
+ break;
+ }
+ pszArg++;
+ }
+ else if (++iArg < argc)
+ pszArg = argv[iArg];
+ else
+ {
+ rcExit = errx(pCtx, 2, "syntax error: missing filename argument.");
+ break;
+ }
+
+ /*
+ * Open the file. We could've used posix_spawn_file_actions_addopen here,
+ * but that means complicated error reporting. So, since we need to do
+ * this for windows anyway, just do it the same way everywhere.
+ */
+ fdOpened = kRedirectOpenWithoutConflict(pCtx, pszArg, fOpen, 0666, cOrders, aOrders,
+ aOrders[cOrders].fRemoveOnFailure, fd);
+ if (fdOpened >= 0)
+ {
+ aOrders[cOrders].enmOrder = kRedirectOrder_Open;
+ aOrders[cOrders].fdTarget = fd;
+ aOrders[cOrders].fdSource = fdOpened;
+ aOrders[cOrders].fOpen = fOpen;
+ aOrders[cOrders].pszFilename = pszArg;
+ cOrders++;
+
+#ifdef USE_POSIX_SPAWN
+ if (fdOpened != fd)
+ {
+ rcExit = posix_spawn_file_actions_adddup2(&FileActions, fdOpened, fd);
+ if (rcExit != 0)
+ rcExit = err(pCtx, 9, "posix_spawn_file_actions_adddup2(,%d [%s], %d) failed: %s",
+ fdOpened, fd, pszArg, strerror(rcExit));
+ }
+#endif
+ }
+ else
+ rcExit = 9;
+ }
+ }
+ else
+ {
+ errx(pCtx, 2, "syntax error: Invalid argument '%s'.", argv[iArg]);
+ rcExit = kmk_redirect_usage(pCtx, 1);
+ }
+ }
+ if (!pszExecutable)
+ pszExecutable = argv[iArg];
+
+ /*
+ * Make sure there's something to execute.
+ */
+ if (rcExit == 0 && iArg < argc)
+ {
+ /*
+ * Do the spawning in a separate function (main is far to large as it is by now).
+ */
+ rcExit = kRedirectDoSpawn(pCtx, pszExecutable, argc - iArg, &argv[iArg], fWatcomBrainDamage,
+ papszEnvVars,
+ szCwd, pszSavedCwd,
+#ifdef USE_POSIX_SPAWN
+ cOrders, aOrders, &FileActions, cVerbosity,
+#else
+ cOrders, aOrders, cVerbosity,
+#endif
+#ifdef KMK
+ pPidSpawned,
+#endif
+ &fChildExitCode);
+ }
+ else if (rcExit == 0)
+ {
+ errx(pCtx, 2, "syntax error: nothing to execute!");
+ rcExit = kmk_redirect_usage(pCtx, 1);
+ }
+ /* Help and version sets rcExit to -1. Change it to zero. */
+ else if (rcExit == -1)
+ rcExit = 0;
+
+ /*
+ * Cleanup.
+ */
+ kBuiltinOptEnvCleanup(&papszEnvVars, cEnvVars, &cAllocatedEnvVars);
+ if (pszSavedCwd)
+ free(pszSavedCwd);
+ kRedirectCleanupFdOrders(cOrders, aOrders, rcExit != 0 && !fChildExitCode);
+#ifdef USE_POSIX_SPAWN
+ posix_spawn_file_actions_destroy(&FileActions);
+#endif
+#ifdef KBUILD_OS_OS2
+ for (ulLibPath = 0; ulLibPath < K_ELEMENTS(apszSavedLibPaths); ulLibPath++)
+ if (apszSavedLibPaths[ulLibPath] != NULL)
+ {
+ APIRET rc = DosSetExtLIBPATH(apszSavedLibPaths[ulLibPath], ulLibPath);
+ if (rc != 0)
+ warnx(pCtx, "DosSetExtLIBPATH('%s',%u) failed with %u when restoring the original values!",
+ apszSavedLibPaths[ulLibPath], ulLibPath, rc);
+ free(apszSavedLibPaths[ulLibPath]);
+ }
+#endif
+
+ return rcExit;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_redirect", NULL };
+ return kmk_builtin_redirect(argc, argv, envp, &Ctx, NULL, NULL);
+}
+#endif
+
+
diff --git a/src/kmk/kmkbuiltin/rm.c b/src/kmk/kmkbuiltin/rm.c
new file mode 100644
index 0000000..5753b3e
--- /dev/null
+++ b/src/kmk/kmkbuiltin/rm.c
@@ -0,0 +1,844 @@
+/*-
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1990, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)rm.c 8.5 (Berkeley) 4/18/94";
+#endif /* not lint */
+#include <sys/cdefs.h>
+/*__FBSDID("$FreeBSD: src/bin/rm/rm.c,v 1.47 2004/04/06 20:06:50 markm Exp $");*/
+#endif
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define FAKES_NO_GETOPT_H /* bird */
+#include "config.h"
+#include <sys/stat.h>
+#if !defined(_MSC_VER) && !defined(__HAIKU__)
+# include <sys/param.h>
+# ifndef __gnu_hurd__
+# include <sys/mount.h>
+# endif
+#endif
+
+#include "err.h"
+#include <errno.h>
+#include <fcntl.h>
+#include "fts.h"
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef __HAIKU__
+# include <sysexits.h>
+#endif
+#include <unistd.h>
+#include <ctype.h>
+#include "getopt_r.h"
+#ifdef __HAIKU__
+# include "haikufakes.h"
+#endif
+#ifdef __NetBSD__
+# include <util.h>
+# define fflagstostr(flags) flags_to_string(flags, "")
+#endif
+#ifdef KBUILD_OS_WINDOWS
+# ifdef _MSC_VER
+# include "mscfakes.h"
+# endif
+# include "nt/ntunlink.h"
+ /* Use the special unlink implementation to do rmdir too. */
+# undef rmdir
+# define rmdir(a_pszPath) birdUnlinkForced(a_pszPath)
+#endif
+#if defined(__OS2__) || defined(_MSC_VER)
+# include <direct.h>
+# include <limits.h>
+#endif
+#include "kmkbuiltin.h"
+#include "kbuild_protection.h"
+#include "k/kDefs.h" /* for K_OS */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#if defined(__EMX__) || defined(KBUILD_OS_WINDOWS)
+# define IS_SLASH(ch) ( (ch) == '/' || (ch) == '\\' )
+# define HAVE_DOS_PATHS 1
+# define DEFAULT_PROTECTION_DEPTH 1
+#else
+# define IS_SLASH(ch) ( (ch) == '/' )
+# undef HAVE_DOS_PATHS
+# define DEFAULT_PROTECTION_DEPTH 2
+#endif
+
+#ifdef __EMX__
+#undef S_IFWHT
+#undef S_ISWHT
+#endif
+#ifndef S_IFWHT
+#define S_IFWHT 0
+#define S_ISWHT(s) 0
+#define undelete(s) (-1)
+#endif
+
+#if 1
+#define CUR_LINE_H2(x) "[line " #x "]"
+#define CUR_LINE_H1(x) CUR_LINE_H2(x)
+#define CUR_LINE() CUR_LINE_H1(__LINE__)
+#else
+# define CUR_LINE()
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct RMINSTANCE
+{
+ PKMKBUILTINCTX pCtx;
+ int dflag, eval, fflag, iflag, Pflag, vflag, Wflag, stdin_ok;
+#ifdef KBUILD_OS_WINDOWS
+ int fUseNtDeleteFile;
+#endif
+ uid_t uid;
+ KBUILDPROTECTION g_ProtData;
+} RMINSTANCE;
+typedef RMINSTANCE *PRMINSTANCE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static struct option long_options[] =
+{
+ { "help", no_argument, 0, 261 },
+ { "version", no_argument, 0, 262 },
+ { "disable-protection", no_argument, 0, 263 },
+ { "enable-protection", no_argument, 0, 264 },
+ { "enable-full-protection", no_argument, 0, 265 },
+ { "disable-full-protection", no_argument, 0, 266 },
+ { "protection-depth", required_argument, 0, 267 },
+#ifdef KBUILD_OS_WINDOWS
+ { "nt-delete-file", no_argument, 0, 268 },
+#endif
+ { 0, 0, 0, 0 },
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+extern void bsd_strmode(mode_t mode, char *p); /* strmode.c */
+
+static int check(PRMINSTANCE, char *, char *, struct stat *);
+static void checkdot(PRMINSTANCE, char **);
+static int rm_file(PRMINSTANCE, char **);
+static int rm_overwrite(PRMINSTANCE, char *, struct stat *);
+static int rm_tree(PRMINSTANCE, char **);
+static int usage(PKMKBUILTINCTX, int);
+
+
+
+/*
+ * rm --
+ * This rm is different from historic rm's, but is expected to match
+ * POSIX 1003.2 behavior. The most visible difference is that -f
+ * has two specific effects now, ignore non-existent files and force
+ * file removal.
+ */
+int
+kmk_builtin_rm(int argc, char *argv[], char **envp, PKMKBUILTINCTX pCtx)
+{
+ RMINSTANCE This;
+ struct getopt_state_r gos;
+ int ch, rflag;
+
+ /* Init global instance data */
+ This.pCtx = pCtx;
+ This.dflag = 0;
+ This.eval = 0;
+ This.fflag = 0;
+ This.iflag = 0;
+ This.Pflag = 0;
+ This.vflag = 0;
+ This.Wflag = 0;
+ This.stdin_ok = 0;
+#ifdef KBUILD_OS_WINDOWS
+ This.fUseNtDeleteFile = 0;
+#endif
+ This.uid = 0;
+ kBuildProtectionInit(&This.g_ProtData, pCtx);
+
+ rflag = 0;
+ getopt_initialize_r(&gos, argc, argv, "dfiPRvW", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1)
+ switch (ch) {
+ case 'd':
+ This.dflag = 1;
+ break;
+ case 'f':
+ This.fflag = 1;
+ This.iflag = 0;
+ break;
+ case 'i':
+ This.fflag = 0;
+ This.iflag = 1;
+ break;
+ case 'P':
+ This.Pflag = 1;
+ break;
+ case 'R':
+#if 0
+ case 'r': /* Compatibility. */
+#endif
+ rflag = 1;
+ break;
+ case 'v':
+ This.vflag = 1;
+ break;
+#ifdef FTS_WHITEOUT
+ case 'W':
+ This.Wflag = 1;
+ break;
+#endif
+ case 261:
+ kBuildProtectionTerm(&This.g_ProtData);
+ usage(pCtx, 0);
+ return 0;
+ case 262:
+ kBuildProtectionTerm(&This.g_ProtData);
+ return kbuild_version(argv[0]);
+ case 263:
+ kBuildProtectionDisable(&This.g_ProtData, KBUILDPROTECTIONTYPE_RECURSIVE);
+ break;
+ case 264:
+ kBuildProtectionEnable(&This.g_ProtData, KBUILDPROTECTIONTYPE_RECURSIVE);
+ break;
+ case 265:
+ kBuildProtectionEnable(&This.g_ProtData, KBUILDPROTECTIONTYPE_FULL);
+ break;
+ case 266:
+ kBuildProtectionDisable(&This.g_ProtData, KBUILDPROTECTIONTYPE_FULL);
+ break;
+ case 267:
+ if (kBuildProtectionSetDepth(&This.g_ProtData, gos.optarg)) {
+ kBuildProtectionTerm(&This.g_ProtData);
+ return 1;
+ }
+ break;
+#ifdef KBUILD_OS_WINDOWS
+ case 268:
+ This.fUseNtDeleteFile = 1;
+ break;
+#endif
+ case '?':
+ default:
+ kBuildProtectionTerm(&This.g_ProtData);
+ return usage(pCtx, 1);
+ }
+ argc -= gos.optind;
+ argv += gos.optind;
+
+ if (argc < 1) {
+ kBuildProtectionTerm(&This.g_ProtData);
+ if (This.fflag)
+ return (0);
+ return usage(pCtx, 1);
+ }
+
+ if (!kBuildProtectionScanEnv(&This.g_ProtData, envp, "KMK_RM_")) {
+ checkdot(&This, argv);
+ This.uid = geteuid();
+
+ if (*argv) {
+ This.stdin_ok = isatty(STDIN_FILENO);
+ if (rflag)
+ This.eval |= rm_tree(&This, argv);
+ else
+ This.eval |= rm_file(&This, argv);
+ }
+ } else {
+ This.eval = 1;
+ }
+
+ kBuildProtectionTerm(&This.g_ProtData);
+ return This.eval;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_rm", NULL };
+ return kmk_builtin_rm(argc, argv, envp, &Ctx);
+}
+#endif
+
+static int
+rm_tree(PRMINSTANCE pThis, char **argv)
+{
+ FTS *fts;
+ FTSENT *p;
+ int needstat;
+ int flags;
+ int rval;
+
+ /*
+ * Check up front before anything is deleted. This will not catch
+ * everything, but we'll check the individual items later.
+ */
+ int i;
+ for (i = 0; argv[i]; i++) {
+ if (kBuildProtectionEnforce(&pThis->g_ProtData, KBUILDPROTECTIONTYPE_RECURSIVE, argv[i])) {
+ return 1;
+ }
+ }
+
+ /*
+ * Remove a file hierarchy. If forcing removal (-f), or interactive
+ * (-i) or can't ask anyway (stdin_ok), don't stat the file.
+ */
+ needstat = !pThis->uid || (!pThis->fflag && !pThis->iflag && pThis->stdin_ok);
+
+ /*
+ * If the -i option is specified, the user can skip on the pre-order
+ * visit. The fts_number field flags skipped directories.
+ */
+#define SKIPPED 1
+
+ flags = FTS_PHYSICAL;
+#ifndef KMK_BUILTIN_STANDALONE
+ flags |= FTS_NOCHDIR; /* Must not change the directory from inside kmk! */
+#endif
+ if (!needstat)
+ flags |= FTS_NOSTAT;
+#ifdef FTS_WHITEOUT
+ if (pThis->Wflag)
+ flags |= FTS_WHITEOUT;
+#endif
+ if (!(fts = fts_open(argv, flags, NULL))) {
+ return err(pThis->pCtx, 1, "fts_open");
+ }
+ while ((p = fts_read(fts)) != NULL) {
+ const char *operation = "chflags";
+ switch (p->fts_info) {
+ case FTS_DNR:
+ if (!pThis->fflag || p->fts_errno != ENOENT)
+ pThis->eval = errx(pThis->pCtx, 1, "fts: %s: %s" CUR_LINE() "\n",
+ p->fts_path, strerror(p->fts_errno));
+ continue;
+ case FTS_ERR:
+ fts_close(fts);
+ return errx(pThis->pCtx, 1, "fts: %s: %s " CUR_LINE(), p->fts_path, strerror(p->fts_errno));
+ case FTS_NS:
+ /*
+ * Assume that since fts_read() couldn't stat the
+ * file, it can't be unlinked.
+ */
+ if (!needstat)
+ break;
+ if (!pThis->fflag || p->fts_errno != ENOENT)
+ pThis->eval = errx(pThis->pCtx, 1, "fts: %s: %s " CUR_LINE() "\n",
+ p->fts_path, strerror(p->fts_errno));
+ continue;
+ case FTS_D:
+ /* Pre-order: give user chance to skip. */
+ if (!pThis->fflag && !check(pThis, p->fts_path, p->fts_accpath, p->fts_statp)) {
+ (void)fts_set(fts, p, FTS_SKIP);
+ p->fts_number = SKIPPED;
+ }
+#ifdef UF_APPEND
+ else if (!pThis->uid &&
+ (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
+ !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
+ chflags(p->fts_accpath,
+ p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)) < 0)
+ goto err;
+#endif
+ continue;
+ case FTS_DP:
+ /* Post-order: see if user skipped. */
+ if (p->fts_number == SKIPPED)
+ continue;
+ break;
+ default:
+ if (!pThis->fflag && !check(pThis, p->fts_path, p->fts_accpath, p->fts_statp))
+ continue;
+ }
+
+ /*
+ * Protect against deleting root files and directories.
+ */
+ if (kBuildProtectionEnforce(&pThis->g_ProtData, KBUILDPROTECTIONTYPE_RECURSIVE, p->fts_accpath)) {
+ fts_close(fts);
+ return 1;
+ }
+
+ rval = 0;
+#ifdef UF_APPEND
+ if (!pThis->uid &&
+ (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
+ !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)))
+ rval = chflags(p->fts_accpath,
+ p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
+#endif
+ if (rval == 0) {
+ /*
+ * If we can't read or search the directory, may still be
+ * able to remove it. Don't print out the un{read,search}able
+ * message unless the remove fails.
+ */
+ switch (p->fts_info) {
+ case FTS_DP:
+ case FTS_DNR:
+#ifdef KBUILD_OS_WINDOWS
+ if (p->fts_parent->fts_dirfd != NT_FTS_INVALID_HANDLE_VALUE) {
+ rval = birdUnlinkForcedEx(p->fts_parent->fts_dirfd, p->fts_name);
+ } else {
+ rval = birdUnlinkForced(p->fts_accpath);
+ }
+#else
+ rval = rmdir(p->fts_accpath);
+#endif
+ if (rval == 0 || (pThis->fflag && errno == ENOENT)) {
+ if (rval == 0 && pThis->vflag)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s\n", p->fts_path);
+#if defined(KMK) && defined(KBUILD_OS_WINDOWS)
+ if (rval == 0) {
+ extern int dir_cache_deleted_directory(const char *pszDir);
+ dir_cache_deleted_directory(p->fts_accpath);
+ }
+#endif
+ continue;
+ }
+ operation = "rmdir";
+ break;
+
+#ifdef FTS_W
+ case FTS_W:
+ rval = undelete(p->fts_accpath);
+ if (rval == 0 && (pThis->fflag && errno == ENOENT)) {
+ if (pThis->vflag)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s\n", p->fts_path);
+ continue;
+ }
+ operation = "undelete";
+ break;
+#endif
+
+ case FTS_NS:
+ /*
+ * Assume that since fts_read() couldn't stat
+ * the file, it can't be unlinked.
+ */
+ if (pThis->fflag)
+ continue;
+ /* FALLTHROUGH */
+ default:
+ if (pThis->Pflag)
+ if (!rm_overwrite(pThis, p->fts_accpath, NULL))
+ continue;
+#ifdef KBUILD_OS_WINDOWS
+ if (p->fts_parent->fts_dirfd != NT_FTS_INVALID_HANDLE_VALUE) {
+ if (p->fts_info != FTS_SL && p->fts_info != FTS_SLNONE) {
+ rval = birdUnlinkForcedFastEx(p->fts_parent->fts_dirfd, p->fts_name);
+ } else { /* NtDeleteFile doesn't work on directory links, so slow symlink deletion: */
+ rval = birdUnlinkForcedEx(p->fts_parent->fts_dirfd, p->fts_name);
+ }
+ } else {
+ if (p->fts_info != FTS_SL && p->fts_info != FTS_SLNONE) {
+ rval = birdUnlinkForcedFast(p->fts_accpath);
+ } else { /* NtDeleteFile doesn't work on directory links, so slow symlink deletion: */
+ rval = birdUnlinkForced(p->fts_accpath);
+ }
+ }
+#else
+ rval = unlink(p->fts_accpath);
+#endif
+
+ if (rval == 0 || (pThis->fflag && errno == ENOENT)) {
+ if (rval == 0 && pThis->vflag)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s\n", p->fts_path);
+ continue;
+ }
+ operation = "unlink";
+ break;
+ }
+ }
+#ifdef UF_APPEND
+err:
+#endif
+ pThis->eval = errx(pThis->pCtx, 1, "%s: %s failed: %s " CUR_LINE() "\n", p->fts_path, operation, strerror(errno));
+ }
+ if (errno) {
+ pThis->eval = errx(pThis->pCtx, 1, "fts_read: %s " CUR_LINE() "\n", strerror(errno));
+ }
+ fts_close(fts);
+ return pThis->eval;
+}
+
+static int
+rm_file(PRMINSTANCE pThis, char **argv)
+{
+ struct stat sb;
+ int rval;
+ char *f;
+
+ /*
+ * Check up front before anything is deleted.
+ */
+ int i;
+ for (i = 0; argv[i]; i++) {
+ if (kBuildProtectionEnforce(&pThis->g_ProtData, KBUILDPROTECTIONTYPE_FULL, argv[i]))
+ return 1;
+ }
+
+ /*
+ * Remove a file. POSIX 1003.2 states that, by default, attempting
+ * to remove a directory is an error, so must always stat the file.
+ */
+ while ((f = *argv++) != NULL) {
+ const char *operation = "?";
+ /* Assume if can't stat the file, can't unlink it. */
+ if (lstat(f, &sb)) {
+#ifdef FTS_WHITEOUT
+ if (pThis->Wflag) {
+ sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR;
+ } else {
+#else
+ {
+#endif
+ if (!pThis->fflag || errno != ENOENT)
+ pThis->eval = errx(pThis->pCtx, 1, "%s: lstat failed: %s " CUR_LINE() "\n",
+ f, strerror(errno));
+ continue;
+ }
+#ifdef FTS_WHITEOUT
+ } else if (pThis->Wflag) {
+ errx(pThis->pCtx, 1, "%s: %s\n", f, strerror(EEXIST));
+ pThis->eval = 1;
+ continue;
+#endif
+ }
+
+ if (S_ISDIR(sb.st_mode) && !pThis->dflag) {
+ pThis->eval = errx(pThis->pCtx, 1, "%s: is a directory\n", f);
+ continue;
+ }
+ if (!pThis->fflag && !S_ISWHT(sb.st_mode) && !check(pThis, f, f, &sb))
+ continue;
+ rval = 0;
+#ifdef UF_APPEND
+ if (!pThis->uid &&
+ (sb.st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
+ !(sb.st_flags & (SF_APPEND|SF_IMMUTABLE)))
+ rval = chflags(f, sb.st_flags & ~(UF_APPEND|UF_IMMUTABLE));
+#endif
+ if (rval == 0) {
+ if (S_ISWHT(sb.st_mode)) {
+ rval = undelete(f);
+ operation = "undelete";
+ } else if (S_ISDIR(sb.st_mode)) {
+ rval = rmdir(f);
+ operation = "rmdir";
+ } else {
+ if (pThis->Pflag)
+ if (!rm_overwrite(pThis, f, &sb))
+ continue;
+#ifndef KBUILD_OS_WINDOWS
+ rval = unlink(f);
+ operation = "unlink";
+#else
+ if (pThis->fUseNtDeleteFile) {
+ rval = birdUnlinkForcedFast(f);
+ operation = "NtDeleteFile";
+ } else {
+ rval = birdUnlinkForced(f);
+ operation = "unlink";
+ }
+#endif
+ }
+ }
+ if (rval && (!pThis->fflag || errno != ENOENT))
+ pThis->eval = errx(pThis->pCtx, 1, "%s: %s failed: %s" CUR_LINE() "\n", f, operation, strerror(errno));
+ if (pThis->vflag && rval == 0)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s\n", f);
+ }
+ return pThis->eval;
+}
+
+/*
+ * rm_overwrite --
+ * Overwrite the file 3 times with varying bit patterns.
+ *
+ * XXX
+ * This is a cheap way to *really* delete files. Note that only regular
+ * files are deleted, directories (and therefore names) will remain.
+ * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
+ * System V file system). In a logging file system, you'll have to have
+ * kernel support.
+ */
+static int
+rm_overwrite(PRMINSTANCE pThis, char *file, struct stat *sbp)
+{
+ struct stat sb;
+#ifdef HAVE_FSTATFS
+ struct statfs fsb;
+#endif
+ off_t len;
+ int bsize, fd, wlen;
+ char *buf = NULL;
+ const char *operation = "lstat";
+ int error;
+
+ fd = -1;
+ if (sbp == NULL) {
+ if (lstat(file, &sb))
+ goto err;
+ sbp = &sb;
+ }
+ if (!S_ISREG(sbp->st_mode))
+ return (1);
+ operation = "open";
+ if ((fd = open(file, O_WRONLY | KMK_OPEN_NO_INHERIT, 0)) == -1)
+ goto err;
+#ifdef HAVE_FSTATFS
+ if (fstatfs(fd, &fsb) == -1)
+ goto err;
+ bsize = MAX(fsb.f_iosize, 1024);
+#elif defined(HAVE_ST_BLKSIZE)
+ bsize = MAX(sb.st_blksize, 1024);
+#else
+ bsize = 1024;
+#endif
+ if ((buf = malloc(bsize)) == NULL) {
+ err(pThis->pCtx, 1, "%s: malloc", file);
+ close(fd);
+ return 1;
+ }
+
+#define PASS(byte) { \
+ operation = "write"; \
+ memset(buf, byte, bsize); \
+ for (len = sbp->st_size; len > 0; len -= wlen) { \
+ wlen = len < bsize ? len : bsize; \
+ if (write(fd, buf, wlen) != wlen) \
+ goto err; \
+ } \
+}
+ PASS(0xff);
+ operation = "fsync/lseek";
+ if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
+ goto err;
+ PASS(0x00);
+ operation = "fsync/lseek";
+ if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
+ goto err;
+ PASS(0xff);
+ if (!fsync(fd) && !close(fd)) {
+ free(buf);
+ return (1);
+ }
+ operation = "fsync/close";
+
+err: pThis->eval = 1;
+ error = errno;
+ if (buf)
+ free(buf);
+ if (fd != -1)
+ close(fd);
+ errx(pThis->pCtx, 1, "%s: %s: %s: %s" CUR_LINE() "\n", operation, pThis->pCtx->pszProgName, file, strerror(error));
+ return (0);
+}
+
+
+static int
+check(PRMINSTANCE pThis, char *path, char *name, struct stat *sp)
+{
+ int ch, first;
+ char modep[15], *flagsp;
+
+ /* Check -i first. */
+ if (pThis->iflag)
+ (void)fprintf(stderr, "%s: remove %s? ", pThis->pCtx->pszProgName, path);
+ else {
+ /*
+ * If it's not a symbolic link and it's unwritable and we're
+ * talking to a terminal, ask. Symbolic links are excluded
+ * because their permissions are meaningless. Check stdin_ok
+ * first because we may not have stat'ed the file.
+ * Also skip this check if the -P option was specified because
+ * we will not be able to overwrite file contents and will
+ * barf later.
+ */
+ if (!pThis->stdin_ok || S_ISLNK(sp->st_mode) || pThis->Pflag ||
+ (!access(name, W_OK) &&
+#ifdef SF_APPEND
+ !(sp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
+ (!(sp->st_flags & (UF_APPEND|UF_IMMUTABLE)) || !pThis->uid))
+#else
+ 1)
+#endif
+ )
+ return (1);
+ bsd_strmode(sp->st_mode, modep);
+#if defined(SF_APPEND) && K_OS != K_OS_GNU_KFBSD && K_OS != K_OS_GNU_HURD
+ if ((flagsp = fflagstostr(sp->st_flags)) == NULL) {
+ err(pThis->pCtx, 1, "fflagstostr");
+ flagsp = "<bad-fflagstostr>";
+ }
+ (void)fprintf(stderr, "override %s%s%s/%s %s%sfor %s? ",
+ modep + 1, modep[9] == ' ' ? "" : " ",
+ user_from_uid(sp->st_uid, 0),
+ group_from_gid(sp->st_gid, 0),
+ *flagsp ? flagsp : "", *flagsp ? " " : "",
+ path);
+ free(flagsp);
+#else
+ (void)flagsp;
+ (void)fprintf(stderr, "override %s%s %d/%d for %s? ",
+ modep + 1, modep[9] == ' ' ? "" : " ",
+ sp->st_uid, sp->st_gid, path);
+#endif
+ }
+ (void)fflush(stderr);
+
+ first = ch = getchar();
+ while (ch != '\n' && ch != EOF)
+ ch = getchar();
+ return (first == 'y' || first == 'Y');
+}
+
+#define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
+static void
+checkdot(PRMINSTANCE pThis, char **argv)
+{
+ char *p, **save, **t;
+ int complained;
+
+ complained = 0;
+ for (t = argv; *t;) {
+#ifdef HAVE_DOS_PATHS
+ const char *tmp = p = *t;
+ while (*tmp) {
+ switch (*tmp) {
+ case '/':
+ case '\\':
+ case ':':
+ p = (char *)tmp + 1;
+ break;
+ }
+ tmp++;
+ }
+#else
+ if ((p = strrchr(*t, '/')) != NULL)
+ ++p;
+ else
+ p = *t;
+#endif
+ if (ISDOT(p)) {
+ if (!complained++)
+ warnx(pThis->pCtx, "\".\" and \"..\" may not be removed\n");
+ pThis->eval = 1;
+ for (save = t; (t[0] = t[1]) != NULL; ++t)
+ continue;
+ t = save;
+ } else
+ ++t;
+ }
+}
+
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s [options] file ...\n"
+ " or: %s --help\n"
+ " or: %s --version\n"
+ "\n"
+ "Options:\n"
+ " -f\n"
+ " Attempt to remove files without prompting, regardless of the file\n"
+ " permission. Ignore non-existing files. Overrides previous -i's.\n"
+ " -i\n"
+ " Prompt for each file. Always.\n"
+ " -d\n"
+ " Attempt to remove directories as well as other kinds of files.\n"
+ " -P\n"
+ " Overwrite regular files before deleting; three passes: ff,0,ff\n"
+ " -R\n"
+ " Attempt to remove the file hierachy rooted in each file argument.\n"
+ " This option implies -d and file protection.\n"
+ " -v\n"
+ " Be verbose, show files as they are removed.\n"
+ " -W\n"
+ " Undelete without files.\n"
+ " --disable-protection\n"
+ " Will disable the protection file protection applied with -R.\n"
+ " --enable-protection\n"
+ " Will enable the protection file protection applied with -R.\n"
+ " --enable-full-protection\n"
+ " Will enable the protection file protection for all operations.\n"
+ " --disable-full-protection\n"
+ " Will disable the protection file protection for all operations.\n"
+ " --protection-depth\n"
+ " Number or path indicating the file protection depth. Default: %d\n"
+ "\n"
+ "Environment:\n"
+ " KMK_RM_DISABLE_PROTECTION\n"
+ " Same as --disable-protection. Overrides command line.\n"
+ " KMK_RM_ENABLE_PROTECTION\n"
+ " Same as --enable-protection. Overrides everyone else.\n"
+ " KMK_RM_ENABLE_FULL_PROTECTION\n"
+ " Same as --enable-full-protection. Overrides everyone else.\n"
+ " KMK_RM_DISABLE_FULL_PROTECTION\n"
+ " Same as --disable-full-protection. Overrides command line.\n"
+ " KMK_RM_PROTECTION_DEPTH\n"
+ " Same as --protection-depth. Overrides command line.\n"
+ "\n"
+ "The file protection of the top %d layers of the file hierarchy is there\n"
+ "to try prevent makefiles from doing bad things to your system. This\n"
+ "protection is not bulletproof, but should help prevent you from shooting\n"
+ "yourself in the foot.\n"
+ ,
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName,
+ kBuildProtectionDefaultDepth(), kBuildProtectionDefaultDepth());
+ return EX_USAGE;
+}
+
diff --git a/src/kmk/kmkbuiltin/rmdir.c b/src/kmk/kmkbuiltin/rmdir.c
new file mode 100644
index 0000000..b4cd981
--- /dev/null
+++ b/src/kmk/kmkbuiltin/rmdir.c
@@ -0,0 +1,251 @@
+/*-
+ * Copyright (c) 1992, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static char const copyright[] =
+"@(#) Copyright (c) 1992, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)rmdir.c 8.3 (Berkeley) 4/2/94";
+#endif /* not lint */
+#endif
+#if 0
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/bin/rmdir/rmdir.c,v 1.20 2005/01/26 06:51:28 ssouhlal Exp $");
+#endif
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define FAKES_NO_GETOPT_H /* bird */
+#include "config.h"
+#include "err.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+#endif
+#include "getopt_r.h"
+#include "kmkbuiltin.h"
+
+#ifdef _MSC_VER
+# include "mscfakes.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct RMDIRINSTANCE
+{
+ PKMKBUILTINCTX pCtx;
+ int pflag;
+ int vflag;
+ int ignore_fail_on_non_empty;
+ int ignore_fail_on_not_exist;
+} RMDIRINSTANCE;
+typedef RMDIRINSTANCE *PRMDIRINSTANCE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static struct option long_options[] =
+{
+ { "help", no_argument, 0, 262 },
+ { "ignore-fail-on-non-empty", no_argument, 0, 260 },
+ { "ignore-fail-on-not-exist", no_argument, 0, 261 },
+ { "parents", no_argument, 0, 'p' },
+ { "verbose", no_argument, 0, 'v' },
+ { "version", no_argument, 0, 263 },
+ { 0, 0, 0, 0 },
+};
+
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#if !defined(KMK_BUILTIN_STANDALONE) && defined(KBUILD_OS_WINDOWS)
+extern int dir_cache_deleted_directory(const char *pszDir);
+#endif
+static int rm_path(PRMDIRINSTANCE, char *);
+static int usage(PKMKBUILTINCTX, int);
+
+
+int
+kmk_builtin_rmdir(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ RMDIRINSTANCE This;
+ struct getopt_state_r gos;
+ int ch, errors;
+
+ /* Initialize global instance. */
+ This.pCtx = pCtx;
+ This.ignore_fail_on_not_exist = 0;
+ This.ignore_fail_on_non_empty = 0;
+ This.vflag = 0;
+ This.pflag = 0;
+
+ getopt_initialize_r(&gos, argc, argv, "pv", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1)
+ switch(ch) {
+ case 'p':
+ This.pflag = 1;
+ break;
+ case 'v':
+ This.vflag = 1;
+ break;
+ case 260:
+ This.ignore_fail_on_non_empty = 1;
+ break;
+ case 261:
+ This.ignore_fail_on_not_exist = 1;
+ break;
+ case 262:
+ usage(pCtx, 0);
+ return 0;
+ case 263:
+ return kbuild_version(argv[0]);
+ case '?':
+ default:
+ return usage(pCtx, 1);
+ }
+ argc -= gos.optind;
+ argv += gos.optind;
+
+ if (argc == 0)
+ return /*usage(stderr)*/0;
+
+ for (errors = 0; *argv; argv++) {
+ if (rmdir(*argv) < 0) {
+ if ( ( !This.ignore_fail_on_non_empty
+ || (errno != ENOTEMPTY && errno != EPERM && errno != EACCES && errno != EINVAL && errno != EEXIST))
+ && ( !This.ignore_fail_on_not_exist
+ || errno != ENOENT)) {
+ warn(pCtx, "rmdir: %s", *argv);
+ errors = 1;
+ continue;
+ }
+ if (!This.ignore_fail_on_not_exist || errno != ENOENT)
+ continue;
+ /* (only ignored doesn't exist errors fall thru) */
+ } else {
+#if !defined(KMK_BUILTIN_STANDALONE) && defined(KBUILD_OS_WINDOWS)
+ dir_cache_deleted_directory(*argv);
+#endif
+ if (This.vflag)
+ kmk_builtin_ctx_printf(pCtx, 0, "%s\n", *argv);
+ }
+ if (This.pflag)
+ errors |= rm_path(&This, *argv);
+ }
+
+ return errors;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_rmdir", NULL };
+ return kmk_builtin_rmdir(argc, argv, envp, &Ctx);
+}
+#endif
+
+static int
+rm_path(PRMDIRINSTANCE pThis, char *path)
+{
+ char *p;
+ const size_t len = strlen(path);
+ p = alloca(len + 1);
+ path = memcpy(p, path, len + 1);
+
+#if defined(_MSC_VER) || defined(__EMX__)
+ p = strchr(path, '\\');
+ while (p) {
+ *p++ = '/';
+ p = strchr(p, '\\');
+ }
+#endif
+
+ p = path + len;
+ while (--p > path && *p == '/')
+ ;
+ *++p = '\0';
+ while ((p = strrchr(path, '/')) != NULL) {
+ /* Delete trailing slashes. */
+ while (--p >= path && *p == '/')
+ ;
+ *++p = '\0';
+ if (p == path)
+ break;
+#if defined(_MSC_VER) || defined(__EMX__)
+ if (p[-1] == ':' && p - 2 == path)
+ break;
+#endif
+
+ if (rmdir(path) < 0) {
+ if ( pThis->ignore_fail_on_non_empty
+ && ( errno == ENOTEMPTY || errno == EPERM || errno == EACCES || errno == EINVAL || errno == EEXIST))
+ break;
+ if (!pThis->ignore_fail_on_not_exist || errno != ENOENT) {
+ warn(pThis->pCtx, "rmdir: %s", path);
+ return (1);
+ }
+ }
+#if defined(KMK) && defined(KBUILD_OS_WINDOWS)
+ else {
+ dir_cache_deleted_directory(path);
+ }
+#endif
+ if (pThis->vflag)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s\n", path);
+ }
+
+ return (0);
+}
+
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s [-pv --ignore-fail-on-non-empty --ignore-fail-on-not-exist] directory ...\n"
+ " or: %s --help\n"
+ " or: %s --version\n",
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+ return 1;
+}
+
diff --git a/src/kmk/kmkbuiltin/setmode.c b/src/kmk/kmkbuiltin/setmode.c
new file mode 100644
index 0000000..80ed301
--- /dev/null
+++ b/src/kmk/kmkbuiltin/setmode.c
@@ -0,0 +1,506 @@
+/* $NetBSD: setmode.c,v 1.30 2003/08/07 16:42:56 agc Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Dave Borman at Cray Research, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>*/
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)setmode.c 8.2 (Berkeley) 3/25/94";
+#else
+__RCSID("$NetBSD: setmode.c,v 1.30 2003/08/07 16:42:56 agc Exp $");
+#endif
+#endif /* LIBC_SCCS and not lint */
+
+/*#include "namespace.h"*/
+#include "config.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#ifndef _MSC_VER
+#include <unistd.h>
+#else
+#include "mscfakes.h"
+#endif
+
+#ifdef SETMODE_DEBUG
+#include <stdio.h>
+#endif
+
+/*#ifdef __weak_alias
+__weak_alias(getmode,_getmode)
+__weak_alias(setmode,_setmode)
+#endif*/
+
+#define SET_LEN 6 /* initial # of bitcmd struct to malloc */
+#define SET_LEN_INCR 4 /* # of bitcmd structs to add as needed */
+
+typedef struct bitcmd {
+ char cmd;
+ char cmd2;
+ mode_t bits;
+} BITCMD;
+
+#define CMD2_CLR 0x01
+#define CMD2_SET 0x02
+#define CMD2_GBITS 0x04
+#define CMD2_OBITS 0x08
+#define CMD2_UBITS 0x10
+
+static BITCMD *addcmd(BITCMD *, int, int, int, u_int);
+static void compress_mode(BITCMD *);
+#ifdef SETMODE_DEBUG
+static void dumpmode(BITCMD *);
+#endif
+
+#ifndef _DIAGASSERT
+# define _DIAGASSERT assert
+#endif
+
+#ifndef S_ISTXT
+# ifdef S_ISVTX
+# define S_ISTXT S_ISVTX
+# else
+# define S_ISTXT 0
+# endif
+#endif /* !S_ISTXT */
+
+extern mode_t g_fUMask; /* Initialize in main() and keep up to date. */
+
+
+/*
+ * Given the old mode and an array of bitcmd structures, apply the operations
+ * described in the bitcmd structures to the old mode, and return the new mode.
+ * Note that there is no '=' command; a strict assignment is just a '-' (clear
+ * bits) followed by a '+' (set bits).
+ */
+mode_t
+bsd_getmode(bbox, omode)
+ const void *bbox;
+ mode_t omode;
+{
+ const BITCMD *set;
+ mode_t clrval, newmode, value;
+
+ _DIAGASSERT(bbox != NULL);
+
+ set = (const BITCMD *)bbox;
+ newmode = omode;
+ for (value = 0;; set++)
+ switch(set->cmd) {
+ /*
+ * When copying the user, group or other bits around, we "know"
+ * where the bits are in the mode so that we can do shifts to
+ * copy them around. If we don't use shifts, it gets real
+ * grundgy with lots of single bit checks and bit sets.
+ */
+ case 'u':
+ value = (newmode & S_IRWXU) >> 6;
+ goto common;
+
+ case 'g':
+ value = (newmode & S_IRWXG) >> 3;
+ goto common;
+
+ case 'o':
+ value = newmode & S_IRWXO;
+common: if (set->cmd2 & CMD2_CLR) {
+ clrval =
+ (set->cmd2 & CMD2_SET) ? S_IRWXO : value;
+ if (set->cmd2 & CMD2_UBITS)
+ newmode &= ~((clrval<<6) & set->bits);
+ if (set->cmd2 & CMD2_GBITS)
+ newmode &= ~((clrval<<3) & set->bits);
+ if (set->cmd2 & CMD2_OBITS)
+ newmode &= ~(clrval & set->bits);
+ }
+ if (set->cmd2 & CMD2_SET) {
+ if (set->cmd2 & CMD2_UBITS)
+ newmode |= (value<<6) & set->bits;
+ if (set->cmd2 & CMD2_GBITS)
+ newmode |= (value<<3) & set->bits;
+ if (set->cmd2 & CMD2_OBITS)
+ newmode |= value & set->bits;
+ }
+ break;
+
+ case '+':
+ newmode |= set->bits;
+ break;
+
+ case '-':
+ newmode &= ~set->bits;
+ break;
+
+ case 'X':
+ if (omode & (S_IFDIR|S_IXUSR|S_IXGRP|S_IXOTH))
+ newmode |= set->bits;
+ break;
+
+ case '\0':
+ default:
+#ifdef SETMODE_DEBUG
+ (void)printf("getmode:%04o -> %04o\n", omode, newmode);
+#endif
+ return (newmode);
+ }
+}
+
+#define ADDCMD(a, b, c, d) do { \
+ if (set >= endset) { \
+ BITCMD *newset; \
+ setlen += SET_LEN_INCR; \
+ newset = realloc(saveset, sizeof(BITCMD) * setlen); \
+ if (newset == NULL) { \
+ free(saveset); \
+ return (NULL); \
+ } \
+ set = newset + (set - saveset); \
+ saveset = newset; \
+ endset = newset + (setlen - 2); \
+ } \
+ set = addcmd(set, (a), (b), (c), (d)); \
+} while (/*CONSTCOND*/0)
+
+#define STANDARD_BITS (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO)
+
+void *
+bsd_setmode(p)
+ const char *p;
+{
+ int perm, who;
+ char op, *ep;
+ BITCMD *set, *saveset, *endset;
+#ifndef _MSC_VER
+ sigset_t signset, sigoset;
+#endif
+ mode_t mask;
+ int equalopdone = 0; /* pacify gcc */
+ int permXbits, setlen;
+
+ if (!*p)
+ return (NULL);
+
+ /*
+ * Get a copy of the mask for the permissions that are mask relative.
+ * Flip the bits, we want what's not set. Since it's possible that
+ * the caller is opening files inside a signal handler, protect them
+ * as best we can.
+ */
+#ifndef _MSC_VER
+ sigfillset(&signset);
+ (void)sigprocmask(SIG_BLOCK, &signset, &sigoset);
+#endif
+ mask = g_fUMask;
+ assert(mask == umask(g_fUMask));
+ mask = ~mask;
+#ifndef _MSC_VER
+ (void)sigprocmask(SIG_SETMASK, &sigoset, NULL);
+#endif
+
+ setlen = SET_LEN + 2;
+
+ if ((set = malloc((u_int)(sizeof(BITCMD) * setlen))) == NULL)
+ return (NULL);
+ saveset = set;
+ endset = set + (setlen - 2);
+
+ /*
+ * If an absolute number, get it and return; disallow non-octal digits
+ * or illegal bits.
+ */
+ if (isdigit((unsigned char)*p)) {
+ perm = (mode_t)strtol(p, &ep, 8);
+ if (*ep || perm & ~(STANDARD_BITS|S_ISTXT)) {
+ free(saveset);
+ return (NULL);
+ }
+ ADDCMD('=', (STANDARD_BITS|S_ISTXT), perm, mask);
+ set->cmd = 0;
+ return (saveset);
+ }
+
+ /*
+ * Build list of structures to set/clear/copy bits as described by
+ * each clause of the symbolic mode.
+ */
+ for (;;) {
+ /* First, find out which bits might be modified. */
+ for (who = 0;; ++p) {
+ switch (*p) {
+ case 'a':
+ who |= STANDARD_BITS;
+ break;
+ case 'u':
+ who |= S_ISUID|S_IRWXU;
+ break;
+ case 'g':
+ who |= S_ISGID|S_IRWXG;
+ break;
+ case 'o':
+ who |= S_IRWXO;
+ break;
+ default:
+ goto getop;
+ }
+ }
+
+getop: if ((op = *p++) != '+' && op != '-' && op != '=') {
+ free(saveset);
+ return (NULL);
+ }
+ if (op == '=')
+ equalopdone = 0;
+
+ who &= ~S_ISTXT;
+ for (perm = 0, permXbits = 0;; ++p) {
+ switch (*p) {
+ case 'r':
+ perm |= S_IRUSR|S_IRGRP|S_IROTH;
+ break;
+ case 's':
+ /*
+ * If specific bits where requested and
+ * only "other" bits ignore set-id.
+ */
+ if (who == 0 || (who & ~S_IRWXO))
+ perm |= S_ISUID|S_ISGID;
+ break;
+ case 't':
+ /*
+ * If specific bits where requested and
+ * only "other" bits ignore set-id.
+ */
+ if (who == 0 || (who & ~S_IRWXO)) {
+ who |= S_ISTXT;
+ perm |= S_ISTXT;
+ }
+ break;
+ case 'w':
+ perm |= S_IWUSR|S_IWGRP|S_IWOTH;
+ break;
+ case 'X':
+ permXbits = S_IXUSR|S_IXGRP|S_IXOTH;
+ break;
+ case 'x':
+ perm |= S_IXUSR|S_IXGRP|S_IXOTH;
+ break;
+ case 'u':
+ case 'g':
+ case 'o':
+ /*
+ * When ever we hit 'u', 'g', or 'o', we have
+ * to flush out any partial mode that we have,
+ * and then do the copying of the mode bits.
+ */
+ if (perm) {
+ ADDCMD(op, who, perm, mask);
+ perm = 0;
+ }
+ if (op == '=')
+ equalopdone = 1;
+ if (op == '+' && permXbits) {
+ ADDCMD('X', who, permXbits, mask);
+ permXbits = 0;
+ }
+ ADDCMD(*p, who, op, mask);
+ break;
+
+ default:
+ /*
+ * Add any permissions that we haven't already
+ * done.
+ */
+ if (perm || (op == '=' && !equalopdone)) {
+ if (op == '=')
+ equalopdone = 1;
+ ADDCMD(op, who, perm, mask);
+ perm = 0;
+ }
+ if (permXbits) {
+ ADDCMD('X', who, permXbits, mask);
+ permXbits = 0;
+ }
+ goto apply;
+ }
+ }
+
+apply: if (!*p)
+ break;
+ if (*p != ',')
+ goto getop;
+ ++p;
+ }
+ set->cmd = 0;
+#ifdef SETMODE_DEBUG
+ (void)printf("Before compress_mode()\n");
+ dumpmode(saveset);
+#endif
+ compress_mode(saveset);
+#ifdef SETMODE_DEBUG
+ (void)printf("After compress_mode()\n");
+ dumpmode(saveset);
+#endif
+ return (saveset);
+}
+
+static BITCMD *
+addcmd(set, op, who, oparg, mask)
+ BITCMD *set;
+ int oparg, who;
+ int op;
+ u_int mask;
+{
+
+ _DIAGASSERT(set != NULL);
+
+ switch (op) {
+ case '=':
+ set->cmd = '-';
+ set->bits = who ? who : STANDARD_BITS;
+ set++;
+
+ op = '+';
+ /* FALLTHROUGH */
+ case '+':
+ case '-':
+ case 'X':
+ set->cmd = op;
+ set->bits = (who ? (mode_t)who : mask) & oparg;
+ break;
+
+ case 'u':
+ case 'g':
+ case 'o':
+ set->cmd = op;
+ if (who) {
+ set->cmd2 = ((who & S_IRUSR) ? CMD2_UBITS : 0) |
+ ((who & S_IRGRP) ? CMD2_GBITS : 0) |
+ ((who & S_IROTH) ? CMD2_OBITS : 0);
+ set->bits = (mode_t)~0;
+ } else {
+ set->cmd2 = CMD2_UBITS | CMD2_GBITS | CMD2_OBITS;
+ set->bits = mask;
+ }
+
+ if (oparg == '+')
+ set->cmd2 |= CMD2_SET;
+ else if (oparg == '-')
+ set->cmd2 |= CMD2_CLR;
+ else if (oparg == '=')
+ set->cmd2 |= CMD2_SET|CMD2_CLR;
+ break;
+ }
+ return (set + 1);
+}
+
+#ifdef SETMODE_DEBUG
+static void
+dumpmode(set)
+ BITCMD *set;
+{
+
+ _DIAGASSERT(set != NULL);
+
+ for (; set->cmd; ++set)
+ (void)printf("cmd: '%c' bits %04o%s%s%s%s%s%s\n",
+ set->cmd, set->bits, set->cmd2 ? " cmd2:" : "",
+ set->cmd2 & CMD2_CLR ? " CLR" : "",
+ set->cmd2 & CMD2_SET ? " SET" : "",
+ set->cmd2 & CMD2_UBITS ? " UBITS" : "",
+ set->cmd2 & CMD2_GBITS ? " GBITS" : "",
+ set->cmd2 & CMD2_OBITS ? " OBITS" : "");
+}
+#endif
+
+/*
+ * Given an array of bitcmd structures, compress by compacting consecutive
+ * '+', '-' and 'X' commands into at most 3 commands, one of each. The 'u',
+ * 'g' and 'o' commands continue to be separate. They could probably be
+ * compacted, but it's not worth the effort.
+ */
+static void
+compress_mode(set)
+ BITCMD *set;
+{
+ BITCMD *nset;
+ int setbits, clrbits, Xbits, op;
+
+ _DIAGASSERT(set != NULL);
+
+ for (nset = set;;) {
+ /* Copy over any 'u', 'g' and 'o' commands. */
+ while ((op = nset->cmd) != '+' && op != '-' && op != 'X') {
+ *set++ = *nset++;
+ if (!op)
+ return;
+ }
+
+ for (setbits = clrbits = Xbits = 0;; nset++) {
+ if ((op = nset->cmd) == '-') {
+ clrbits |= nset->bits;
+ setbits &= ~nset->bits;
+ Xbits &= ~nset->bits;
+ } else if (op == '+') {
+ setbits |= nset->bits;
+ clrbits &= ~nset->bits;
+ Xbits &= ~nset->bits;
+ } else if (op == 'X')
+ Xbits |= nset->bits & ~setbits;
+ else
+ break;
+ }
+ if (clrbits) {
+ set->cmd = '-';
+ set->cmd2 = 0;
+ set->bits = clrbits;
+ set++;
+ }
+ if (setbits) {
+ set->cmd = '+';
+ set->cmd2 = 0;
+ set->bits = setbits;
+ set++;
+ }
+ if (Xbits) {
+ set->cmd = 'X';
+ set->cmd2 = 0;
+ set->bits = Xbits;
+ set++;
+ }
+ }
+}
diff --git a/src/kmk/kmkbuiltin/sleep.c b/src/kmk/kmkbuiltin/sleep.c
new file mode 100644
index 0000000..044ed9c
--- /dev/null
+++ b/src/kmk/kmkbuiltin/sleep.c
@@ -0,0 +1,179 @@
+/* $Id: sleep.c 3192 2018-03-26 20:25:56Z bird $ */
+/** @file
+ * kmk_sleep - suspend execution for an interval of time.
+ */
+
+/*
+ * Copyright (c) 2008-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/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#if defined(_MSC_VER)
+# include <Windows.h>
+#else
+# include <unistd.h>
+# include <time.h>
+#endif
+
+#include "err.h"
+#include "../kmkbuiltin.h"
+
+
+static int kmk_builtin_sleep_usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s <seconds>[s]\n"
+ " or: %s <milliseconds>ms\n"
+ " or: %s <minutes>m\n"
+ " or: %s <hours>h\n"
+ " or: %s <days>d\n"
+ " or: %s --help\n"
+ " or: %s --version\n"
+ "\n"
+ "Only integer values are accepted.\n"
+ ,
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName,
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+ return 1;
+}
+
+
+int kmk_builtin_sleep(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ long cMsToSleep;
+ long lTmp;
+ unsigned long ulFactor;
+ char *pszInterval;
+ char *pszSuff;
+
+ /*
+ * Parse arguments.
+ */
+ if (argc != 2)
+ return kmk_builtin_sleep_usage(pCtx, 1);
+
+ /* help request */
+ if ( !strcmp(argv[1], "-h")
+ || !strcmp(argv[1], "-?")
+ || !strcmp(argv[1], "-H")
+ || !strcmp(argv[1], "--help"))
+ {
+ kmk_builtin_sleep_usage(pCtx, 0);
+ return 0;
+ }
+
+ /* version request */
+ if ( !strcmp(argv[1], "-V")
+ || !strcmp(argv[1], "--version"))
+ {
+ printf("kmk_sleep - kBuild version %d.%d.%d (r%u)\n"
+ "Copyright (c) 2008-2009 knut st. osmundsen\n",
+ KBUILD_VERSION_MAJOR, KBUILD_VERSION_MINOR, KBUILD_VERSION_PATCH,
+ KBUILD_SVN_REV);
+ return 0;
+ }
+
+ /*
+ * Try convert the argument to a time period.
+ * Allow spaces before, between and after the different parts.
+ */
+ pszInterval = argv[1];
+ while (isspace(*pszInterval))
+ pszInterval++;
+
+ cMsToSleep = strtol(pszInterval, &pszSuff, 0);
+ if (pszSuff == pszInterval)
+ return errx(pCtx, 1, "malformed interval '%s'!\n", pszInterval);
+
+ while (isspace(*pszSuff))
+ pszSuff++;
+
+ if (!*pszSuff)
+ ulFactor = 1000; /* s */
+ else
+ {
+ /* find the suffix length and check that it's only white space following it. */
+ int cchSuff;
+ int i = 1;
+ while (pszSuff[i] && !isspace(pszSuff[i]))
+ i++;
+ cchSuff = i;
+ while (pszSuff[i])
+ {
+ if (!isspace(pszSuff[i]))
+ return errx(pCtx, 1, "malformed interval '%s'!\n", pszInterval);
+ i++;
+ }
+
+ if (cchSuff == 2 && !strncmp (pszSuff, "ms", 2))
+ ulFactor = 1;
+ else if (cchSuff == 1 && *pszSuff == 's')
+ ulFactor = 1000;
+ else if (cchSuff == 1 && *pszSuff == 'm')
+ ulFactor = 60*1000;
+ else if (cchSuff == 1 && *pszSuff == 'h')
+ ulFactor = 60*60*1000;
+ else if (cchSuff == 1 && *pszSuff == 'd')
+ ulFactor = 24*60*60*1000;
+ else
+ return errx(pCtx, 1, "unknown suffix '%.*s'!\n", cchSuff, pszSuff);
+ }
+
+ lTmp = cMsToSleep;
+ cMsToSleep *= ulFactor;
+ if ((cMsToSleep / ulFactor) != (unsigned long)lTmp)
+ return errx(pCtx, 1, "time interval overflow!\n");
+
+ /*
+ * Do the actual sleeping.
+ */
+ if (cMsToSleep > 0)
+ {
+#if defined(_MSC_VER)
+ Sleep(cMsToSleep);
+#else
+ if (cMsToSleep)
+ {
+ struct timespec TimeSpec;
+ TimeSpec.tv_nsec = (cMsToSleep % 1000) * 1000000;
+ TimeSpec.tv_sec = cMsToSleep / 1000;
+ nanosleep(&TimeSpec, NULL);
+ }
+#endif
+ }
+
+ return 0;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_sleep", NULL };
+ return kmk_builtin_sleep(argc, argv, envp, &Ctx);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin/solfakes.c b/src/kmk/kmkbuiltin/solfakes.c
new file mode 100644
index 0000000..6996ab4
--- /dev/null
+++ b/src/kmk/kmkbuiltin/solfakes.c
@@ -0,0 +1,99 @@
+/* $Id: solfakes.c 2901 2016-09-09 15:10:24Z bird $ */
+/** @file
+ * Fake Unix stuff for Solaris.
+ */
+
+/*
+ * Copyright (c) 2005-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/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "config.h"
+#include <errno.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include "solfakes.h"
+
+
+int asprintf(char **strp, const char *fmt, ...)
+{
+ int rc;
+ va_list va;
+ va_start(va, fmt);
+ rc = vasprintf(strp, fmt, va);
+ va_end(va);
+ return rc;
+}
+
+
+int vasprintf(char **strp, const char *fmt, va_list va)
+{
+ int rc;
+ char *psz;
+ size_t cb = 1024;
+
+ *strp = NULL;
+ for (;;)
+ {
+ va_list va2;
+
+ psz = malloc(cb);
+ if (!psz)
+ return -1;
+
+#ifdef va_copy
+ va_copy(va2, va);
+ rc = vsnprintf(psz, cb, fmt, va2);
+ va_end(va2);
+#else
+ va2 = va;
+ rc = vsnprintf(psz, cb, fmt, va2);
+#endif
+ if (rc < 0 || (size_t)rc < cb)
+ break;
+ cb *= 2;
+ free(psz);
+ }
+
+ *strp = psz;
+ return rc;
+}
+
+
+
+int sol_lchmod(const char *pszPath, mode_t mode)
+{
+ /*
+ * Weed out symbolic links.
+ */
+ struct stat s;
+ if ( !lstat(pszPath, &s)
+ && S_ISLNK(s.st_mode))
+ {
+ errno = -ENOSYS;
+ return -1;
+ }
+
+ return chmod(pszPath, mode);
+}
+
diff --git a/src/kmk/kmkbuiltin/solfakes.h b/src/kmk/kmkbuiltin/solfakes.h
new file mode 100644
index 0000000..30519cc
--- /dev/null
+++ b/src/kmk/kmkbuiltin/solfakes.h
@@ -0,0 +1,51 @@
+/* $Id: solfakes.h 3213 2018-03-30 21:03:28Z bird $ */
+/** @file
+ * Unix fakes for Solaris.
+ */
+
+/*
+ * Copyright (c) 2005-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/>
+ *
+ */
+
+#ifndef ___solfakes_h
+#define ___solfakes_h
+#ifdef __sun__
+
+#include <stdarg.h>
+#include <sys/types.h>
+#ifndef FAKES_NO_GETOPT_H
+# include "getopt.h"
+#endif
+
+#define _PATH_DEVNULL "/dev/null"
+#define ALLPERMS 0000777
+#define lutimes(path, tvs) utimes(path, tvs)
+#define lchmod sol_lchmod
+#define MAX(a,b) ((a) >= (b) ? (a) : (b))
+#ifndef USHRT_MAX
+# define USHRT_MAX 65535
+#endif
+
+int vasprintf(char **strp, const char *fmt, va_list va);
+int asprintf(char **strp, const char *fmt, ...);
+int sol_lchmod(const char *pszPath, mode_t mode);
+
+#endif /* __sun__ */
+#endif /* !___solfakes_h */
+
diff --git a/src/kmk/kmkbuiltin/strlcpy.c b/src/kmk/kmkbuiltin/strlcpy.c
new file mode 100644
index 0000000..e1be568
--- /dev/null
+++ b/src/kmk/kmkbuiltin/strlcpy.c
@@ -0,0 +1,78 @@
+/* $OpenBSD: strlcpy.c,v 1.4 1999/05/01 18:56:41 millert Exp $ */
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+static char *rcsid = "$OpenBSD: strlcpy.c,v 1.4 1999/05/01 18:56:41 millert Exp $");
+#include <sys/cdefs.h>
+//__FBSDID("$FreeBSD: src/lib/libc/string/strlcpy.c,v 1.7 2003/05/01 19:03:14 nectar Exp $");
+#endif /* LIBC_SCCS and not lint */
+
+#include <sys/types.h>
+#include <string.h>
+
+/* This trick is for bootstrap.gmk. */
+#if defined(__DARWIN_C_LEVEL) && defined(__DARWIN_C_FULL)
+# define SKIP_STRLCPY
+#endif
+#ifndef SKIP_STRLCPY
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t strlcpy(dst, src, siz)
+ char *dst;
+ const char *src;
+ size_t siz;
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0 && --n != 0) {
+ do {
+ if ((*d++ = *s++) == 0)
+ break;
+ } while (--n != 0);
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+
+ return(s - src - 1); /* count does not include NUL */
+}
+
+#endif /* !SKIP_STRLCPY */
diff --git a/src/kmk/kmkbuiltin/strmode.c b/src/kmk/kmkbuiltin/strmode.c
new file mode 100644
index 0000000..c2a99de
--- /dev/null
+++ b/src/kmk/kmkbuiltin/strmode.c
@@ -0,0 +1,197 @@
+/* $NetBSD: strmode.c,v 1.16 2004/06/20 22:20:15 jmc Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>*/
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)strmode.c 8.3 (Berkeley) 8/15/94";
+#else
+__RCSID("$NetBSD: strmode.c,v 1.16 2004/06/20 22:20:15 jmc Exp $");
+#endif
+#endif /* LIBC_SCCS and not lint */
+
+#include "config.h"
+/*#include "namespace.h"*/
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#ifndef _MSC_VER
+#include <unistd.h>
+#else
+#include "mscfakes.h"
+#endif
+
+#ifndef _DIAGASSERT
+#define _DIAGASSERT assert
+#endif
+
+void
+bsd_strmode(mode, p)
+ mode_t mode;
+ char *p;
+{
+
+ _DIAGASSERT(p != NULL);
+
+ /* print type */
+ switch (mode & S_IFMT) {
+ case S_IFDIR: /* directory */
+ *p++ = 'd';
+ break;
+ case S_IFCHR: /* character special */
+ *p++ = 'c';
+ break;
+#ifdef S_IFBLK
+ case S_IFBLK: /* block special */
+ *p++ = 'b';
+ break;
+#endif
+ case S_IFREG: /* regular */
+#ifdef S_ARCH2
+ if ((mode & S_ARCH2) != 0) {
+ *p++ = 'A';
+ } else if ((mode & S_ARCH1) != 0) {
+ *p++ = 'a';
+ } else {
+#endif
+ *p++ = '-';
+#ifdef S_ARCH2
+ }
+#endif
+ break;
+#ifdef S_IFLNK
+ case S_IFLNK: /* symbolic link */
+ *p++ = 'l';
+ break;
+#endif
+#ifdef S_IFSOCK
+ case S_IFSOCK: /* socket */
+ *p++ = 's';
+ break;
+#endif
+#ifdef S_IFIFO
+ case S_IFIFO: /* fifo */
+ *p++ = 'p';
+ break;
+#endif
+#ifdef S_IFWHT
+ case S_IFWHT: /* whiteout */
+ *p++ = 'w';
+ break;
+#endif
+#ifdef S_IFDOOR
+ case S_IFDOOR: /* door */
+ *p++ = 'D';
+ break;
+#endif
+ default: /* unknown */
+ *p++ = '?';
+ break;
+ }
+ /* usr */
+ if (mode & S_IRUSR)
+ *p++ = 'r';
+ else
+ *p++ = '-';
+ if (mode & S_IWUSR)
+ *p++ = 'w';
+ else
+ *p++ = '-';
+ switch (mode & (S_IXUSR | S_ISUID)) {
+ case 0:
+ *p++ = '-';
+ break;
+ case S_IXUSR:
+ *p++ = 'x';
+ break;
+ case S_ISUID:
+ *p++ = 'S';
+ break;
+ case S_IXUSR | S_ISUID:
+ *p++ = 's';
+ break;
+ }
+ /* group */
+ if (mode & S_IRGRP)
+ *p++ = 'r';
+ else
+ *p++ = '-';
+ if (mode & S_IWGRP)
+ *p++ = 'w';
+ else
+ *p++ = '-';
+ switch (mode & (S_IXGRP | S_ISGID)) {
+ case 0:
+ *p++ = '-';
+ break;
+ case S_IXGRP:
+ *p++ = 'x';
+ break;
+ case S_ISGID:
+ *p++ = 'S';
+ break;
+ case S_IXGRP | S_ISGID:
+ *p++ = 's';
+ break;
+ }
+ /* other */
+ if (mode & S_IROTH)
+ *p++ = 'r';
+ else
+ *p++ = '-';
+ if (mode & S_IWOTH)
+ *p++ = 'w';
+ else
+ *p++ = '-';
+#ifdef S_ISVTX
+ switch (mode & (S_IXOTH | S_ISVTX)) {
+#else
+ switch (mode & (S_IXOTH)) {
+#endif
+ case 0:
+ *p++ = '-';
+ break;
+ case S_IXOTH:
+ *p++ = 'x';
+ break;
+#ifdef S_ISVTX
+ case S_ISVTX:
+ *p++ = 'T';
+ break;
+ case S_IXOTH | S_ISVTX:
+ *p++ = 't';
+ break;
+#endif
+ }
+ *p++ = ' '; /* will be a '+' if ACL's implemented */
+ *p = '\0';
+}
diff --git a/src/kmk/kmkbuiltin/test.c b/src/kmk/kmkbuiltin/test.c
new file mode 100644
index 0000000..259f223
--- /dev/null
+++ b/src/kmk/kmkbuiltin/test.c
@@ -0,0 +1,869 @@
+/* $NetBSD: test.c,v 1.33 2007/06/24 18:54:58 christos Exp $ */
+
+/*
+ * test(1); version 7-like -- author Erik Baalbergen
+ * modified by Eric Gisin to be used as built-in.
+ * modified by Arnold Robbins to add SVR3 compatibility
+ * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket).
+ * modified by J.T. Conklin for NetBSD.
+ *
+ * This program is in the Public Domain.
+ */
+
+/*#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: test.c,v 1.33 2007/06/24 18:54:58 christos Exp $");
+#endif*/
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "config.h"
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include "err.h"
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef _MSC_VER
+# include <direct.h>
+# include <io.h>
+# include <process.h>
+# include "mscfakes.h"
+# include "quote_argv.h"
+#else
+# include <unistd.h>
+#endif
+#include <stdarg.h>
+#include <sys/stat.h>
+
+#include "kmkbuiltin.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifndef __arraycount
+# define __arraycount(a) ( sizeof(a) / sizeof(a[0]) )
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/* test(1) accepts the following grammar:
+ oexpr ::= aexpr | aexpr "-o" oexpr ;
+ aexpr ::= nexpr | nexpr "-a" aexpr ;
+ nexpr ::= primary | "!" primary
+ primary ::= unary-operator operand
+ | operand binary-operator operand
+ | operand
+ | "(" oexpr ")"
+ ;
+ unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
+ "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
+
+ binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
+ "-nt"|"-ot"|"-ef";
+ operand ::= <any legal UNIX file name>
+*/
+
+enum token {
+ EOI,
+ FILRD,
+ FILWR,
+ FILEX,
+ FILEXIST,
+ FILREG,
+ FILDIR,
+ FILCDEV,
+ FILBDEV,
+ FILFIFO,
+ FILSOCK,
+ FILSYM,
+ FILGZ,
+ FILTT,
+ FILSUID,
+ FILSGID,
+ FILSTCK,
+ FILNT,
+ FILOT,
+ FILEQ,
+ FILUID,
+ FILGID,
+ STREZ,
+ STRNZ,
+ STREQ,
+ STRNE,
+ STRLT,
+ STRGT,
+ INTEQ,
+ INTNE,
+ INTGE,
+ INTGT,
+ INTLE,
+ INTLT,
+ UNOT,
+ BAND,
+ BOR,
+ LPAREN,
+ RPAREN,
+ OPERAND
+};
+
+enum token_types {
+ UNOP,
+ BINOP,
+ BUNOP,
+ BBINOP,
+ PAREN
+};
+
+struct t_op {
+ const char *op_text;
+ short op_num, op_type;
+};
+
+/** kmk_test instance data. */
+typedef struct TESTINSTANCE
+{
+ PKMKBUILTINCTX pCtx;
+ char **t_wp;
+ struct t_op const *t_wp_op;
+} TESTINSTANCE;
+typedef TESTINSTANCE *PTESTINSTANCE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static const struct t_op cop[] = {
+ {"!", UNOT, BUNOP},
+ {"(", LPAREN, PAREN},
+ {")", RPAREN, PAREN},
+ {"<", STRLT, BINOP},
+ {"=", STREQ, BINOP},
+ {">", STRGT, BINOP},
+};
+
+static const struct t_op cop2[] = {
+ {"!=", STRNE, BINOP},
+};
+
+static const struct t_op mop3[] = {
+ {"ef", FILEQ, BINOP},
+ {"eq", INTEQ, BINOP},
+ {"ge", INTGE, BINOP},
+ {"gt", INTGT, BINOP},
+ {"le", INTLE, BINOP},
+ {"lt", INTLT, BINOP},
+ {"ne", INTNE, BINOP},
+ {"nt", FILNT, BINOP},
+ {"ot", FILOT, BINOP},
+};
+
+static const struct t_op mop2[] = {
+ {"G", FILGID, UNOP},
+ {"L", FILSYM, UNOP},
+ {"O", FILUID, UNOP},
+ {"S", FILSOCK,UNOP},
+ {"a", BAND, BBINOP},
+ {"b", FILBDEV,UNOP},
+ {"c", FILCDEV,UNOP},
+ {"d", FILDIR, UNOP},
+ {"e", FILEXIST,UNOP},
+ {"f", FILREG, UNOP},
+ {"g", FILSGID,UNOP},
+ {"h", FILSYM, UNOP}, /* for backwards compat */
+ {"k", FILSTCK,UNOP},
+ {"n", STRNZ, UNOP},
+ {"o", BOR, BBINOP},
+ {"p", FILFIFO,UNOP},
+ {"r", FILRD, UNOP},
+ {"s", FILGZ, UNOP},
+ {"t", FILTT, UNOP},
+ {"u", FILSUID,UNOP},
+ {"w", FILWR, UNOP},
+ {"x", FILEX, UNOP},
+ {"z", STREZ, UNOP},
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int syntax(PTESTINSTANCE, const char *, const char *);
+static int oexpr(PTESTINSTANCE, enum token);
+static int aexpr(PTESTINSTANCE, enum token);
+static int nexpr(PTESTINSTANCE, enum token);
+static int primary(PTESTINSTANCE, enum token);
+static int binop(PTESTINSTANCE);
+static int test_access(struct stat *, mode_t);
+static int filstat(char *, enum token);
+static enum token t_lex(PTESTINSTANCE, char *);
+static int isoperand(PTESTINSTANCE);
+static int getn(PTESTINSTANCE, const char *);
+static int newerf(const char *, const char *);
+static int olderf(const char *, const char *);
+static int equalf(const char *, const char *);
+static int usage(PKMKBUILTINCTX, int);
+
+#if !defined(KMK_BUILTIN_STANDALONE) || defined(ELECTRIC_HEAP)
+extern void *xmalloc(unsigned int);
+#else
+extern void *xmalloc(unsigned int sz)
+{
+ void *p = malloc(sz);
+ if (!p) {
+ fprintf(stderr, "kmk_test: malloc(%u) failed\n", sz);
+ exit(1);
+ }
+ return p;
+}
+#endif
+
+
+
+int kmk_builtin_test(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx, char ***ppapszArgvSpawn)
+{
+ TESTINSTANCE This;
+ int res;
+ char **argv_spawn;
+ int i;
+
+ This.pCtx = pCtx;
+ This.t_wp = NULL;
+ This.t_wp_op = NULL;
+
+ /* look for the '--', '--help' and '--version'. */
+ argv_spawn = NULL;
+ for (i = 1; i < argc; i++) {
+ if ( argv[i][0] == '-'
+ && argv[i][1] == '-') {
+ if (argv[i][2] == '\0') {
+ argc = i;
+ argv[i] = NULL;
+
+ /* skip blank arguments (happens inside kmk) */
+ while (argv[++i]) {
+ const char *psz = argv[i];
+ while (isspace(*psz))
+ psz++;
+ if (*psz)
+ break;
+ }
+ argv_spawn = &argv[i];
+ break;
+ }
+ if (!strcmp(argv[i], "--help"))
+ return usage(pCtx, 0);
+ if (!strcmp(argv[i], "--version"))
+ return kbuild_version(argv[0]);
+ }
+ }
+
+ /* are we '['? then check for ']'. */
+ if (strcmp(argv[0], "[") == 0) { /** @todo should skip the path in g_progname */
+ if (strcmp(argv[--argc], "]"))
+ return errx(pCtx, 1, "missing ]");
+ argv[argc] = NULL;
+ }
+
+ /* evaluate the expression */
+ if (argc < 2)
+ res = 1;
+ else {
+ This.t_wp = &argv[1];
+ res = oexpr(&This, t_lex(&This, *This.t_wp));
+ if (res != -42 && *This.t_wp != NULL && *++This.t_wp != NULL)
+ res = syntax(&This, *This.t_wp, "unexpected operator");
+ if (res == -42)
+ return 1; /* don't mix syntax errors with the argv_spawn ignore */
+ res = !res;
+ }
+
+ /* anything to execute on success? */
+ if (argv_spawn) {
+ if (res != 0 || !argv_spawn[0])
+ res = 0; /* ignored */
+ else {
+#ifdef KMK_BUILTIN_STANDALONE
+ /* try exec the specified process */
+# if defined(_MSC_VER)
+ int argc_spawn = 0;
+ while (argv_spawn[argc_spawn])
+ argc_spawn++;
+ if (quote_argv(argc, argv_spawn, 0 /*fWatcomBrainDamage*/, 0/*fFreeOrLeak*/) != -1)
+ {
+ res = _spawnvp(_P_WAIT, argv_spawn[0], argv_spawn);
+ if (res == -1)
+ res = err(pCtx, 1, "_spawnvp(_P_WAIT,%s,..)", argv_spawn[0]);
+ }
+ else
+ res = err(pCtx, 1, "quote_argv: out of memory");
+# else
+ execvp(argv_spawn[0], argv_spawn);
+ res = err(pCtx, 1, "execvp(%s,..)", argv_spawn[0]);
+# endif
+#else /* in kmk */
+ /* let job.c spawn the process, make a job.c style argv_spawn copy. */
+ char *cur, **argv_new;
+ size_t sz = 0;
+ int argc_new = 0;
+ while (argv_spawn[argc_new]) {
+ size_t len = strlen(argv_spawn[argc_new]) + 1;
+ sz += (len + sizeof(void *) - 1) & ~(sizeof(void *) - 1);
+ argc_new++;
+ }
+
+ argv_new = xmalloc((argc_new + 1) * sizeof(char *));
+ cur = xmalloc(sz);
+ for (i = 0; i < argc_new; i++) {
+ size_t len = strlen(argv_spawn[i]) + 1;
+ argv_new[i] = memcpy(cur, argv_spawn[i], len);
+ cur += (len + sizeof(void *) - 1) & ~(sizeof(void *) - 1);
+ }
+ argv_new[i] = NULL;
+
+ *ppapszArgvSpawn = argv_new;
+ res = 0;
+#endif /* in kmk */
+ }
+ }
+
+ return res;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_test", NULL };
+ return kmk_builtin_test(argc, argv, envp, &Ctx, NULL);
+}
+#endif
+
+static int
+syntax(PTESTINSTANCE pThis, const char *op, const char *msg)
+{
+
+ if (op && *op)
+ errx(pThis->pCtx, 1, "%s: %s", op, msg);
+ else
+ errx(pThis->pCtx, 1, "%s", msg);
+ return -42;
+}
+
+static int
+oexpr(PTESTINSTANCE pThis, enum token n)
+{
+ int res;
+
+ res = aexpr(pThis, n);
+ if (res == -42 || *pThis->t_wp == NULL)
+ return res;
+ if (t_lex(pThis, *++(pThis->t_wp)) == BOR) {
+ int res2 = oexpr(pThis, t_lex(pThis, *++(pThis->t_wp)));
+ return res2 != -42 ? res2 || res : res2;
+ }
+ pThis->t_wp--;
+ return res;
+}
+
+static int
+aexpr(PTESTINSTANCE pThis, enum token n)
+{
+ int res;
+
+ res = nexpr(pThis, n);
+ if (res == -42 || *pThis->t_wp == NULL)
+ return res;
+ if (t_lex(pThis, *++(pThis->t_wp)) == BAND) {
+ int res2 = aexpr(pThis, t_lex(pThis, *++(pThis->t_wp)));
+ return res2 != -42 ? res2 && res : res2;
+ }
+ pThis->t_wp--;
+ return res;
+}
+
+static int
+nexpr(PTESTINSTANCE pThis, enum token n)
+{
+ if (n == UNOT) {
+ int res = nexpr(pThis, t_lex(pThis, *++(pThis->t_wp)));
+ return res != -42 ? !res : res;
+ }
+ return primary(pThis, n);
+}
+
+static int
+primary(PTESTINSTANCE pThis, enum token n)
+{
+ enum token nn;
+ int res;
+
+ if (n == EOI)
+ return 0; /* missing expression */
+ if (n == LPAREN) {
+ if ((nn = t_lex(pThis, *++(pThis->t_wp))) == RPAREN)
+ return 0; /* missing expression */
+ res = oexpr(pThis, nn);
+ if (res != -42 && t_lex(pThis, *++(pThis->t_wp)) != RPAREN)
+ return syntax(pThis, NULL, "closing paren expected");
+ return res;
+ }
+ if (pThis->t_wp_op && pThis->t_wp_op->op_type == UNOP) {
+ /* unary expression */
+ if (*++(pThis->t_wp) == NULL)
+ return syntax(pThis, pThis->t_wp_op->op_text, "argument expected");
+ switch (n) {
+ case STREZ:
+ return strlen(*pThis->t_wp) == 0;
+ case STRNZ:
+ return strlen(*pThis->t_wp) != 0;
+ case FILTT:
+ return isatty(getn(pThis, *pThis->t_wp));
+ default:
+ return filstat(*pThis->t_wp, n);
+ }
+ }
+
+ if (t_lex(pThis, pThis->t_wp[1]), pThis->t_wp_op && pThis->t_wp_op->op_type == BINOP) {
+ return binop(pThis);
+ }
+
+ return strlen(*pThis->t_wp) > 0;
+}
+
+static int
+binop(PTESTINSTANCE pThis)
+{
+ const char *opnd1, *opnd2;
+ struct t_op const *op;
+
+ opnd1 = *pThis->t_wp;
+ (void) t_lex(pThis, *++(pThis->t_wp));
+ op = pThis->t_wp_op;
+
+ if ((opnd2 = *++(pThis->t_wp)) == NULL)
+ return syntax(pThis, op->op_text, "argument expected");
+
+ switch (op->op_num) {
+ case STREQ:
+ return strcmp(opnd1, opnd2) == 0;
+ case STRNE:
+ return strcmp(opnd1, opnd2) != 0;
+ case STRLT:
+ return strcmp(opnd1, opnd2) < 0;
+ case STRGT:
+ return strcmp(opnd1, opnd2) > 0;
+ case INTEQ:
+ return getn(pThis, opnd1) == getn(pThis, opnd2);
+ case INTNE:
+ return getn(pThis, opnd1) != getn(pThis, opnd2);
+ case INTGE:
+ return getn(pThis, opnd1) >= getn(pThis, opnd2);
+ case INTGT:
+ return getn(pThis, opnd1) > getn(pThis, opnd2);
+ case INTLE:
+ return getn(pThis, opnd1) <= getn(pThis, opnd2);
+ case INTLT:
+ return getn(pThis, opnd1) < getn(pThis, opnd2);
+ case FILNT:
+ return newerf(opnd1, opnd2);
+ case FILOT:
+ return olderf(opnd1, opnd2);
+ case FILEQ:
+ return equalf(opnd1, opnd2);
+ default:
+ abort();
+ /* NOTREACHED */
+#ifdef _MSC_VER
+ return -42;
+#endif
+ }
+}
+
+/*
+ * The manual, and IEEE POSIX 1003.2, suggests this should check the mode bits,
+ * not use access():
+ *
+ * True shall indicate only that the write flag is on. The file is not
+ * writable on a read-only file system even if this test indicates true.
+ *
+ * Unfortunately IEEE POSIX 1003.1-2001, as quoted in SuSv3, says only:
+ *
+ * True shall indicate that permission to read from file will be granted,
+ * as defined in "File Read, Write, and Creation".
+ *
+ * and that section says:
+ *
+ * When a file is to be read or written, the file shall be opened with an
+ * access mode corresponding to the operation to be performed. If file
+ * access permissions deny access, the requested operation shall fail.
+ *
+ * and of course access permissions are described as one might expect:
+ *
+ * * If a process has the appropriate privilege:
+ *
+ * * If read, write, or directory search permission is requested,
+ * access shall be granted.
+ *
+ * * If execute permission is requested, access shall be granted if
+ * execute permission is granted to at least one user by the file
+ * permission bits or by an alternate access control mechanism;
+ * otherwise, access shall be denied.
+ *
+ * * Otherwise:
+ *
+ * * The file permission bits of a file contain read, write, and
+ * execute/search permissions for the file owner class, file group
+ * class, and file other class.
+ *
+ * * Access shall be granted if an alternate access control mechanism
+ * is not enabled and the requested access permission bit is set for
+ * the class (file owner class, file group class, or file other class)
+ * to which the process belongs, or if an alternate access control
+ * mechanism is enabled and it allows the requested access; otherwise,
+ * access shall be denied.
+ *
+ * and when I first read this I thought: surely we can't go about using
+ * open(O_WRONLY) to try this test! However the POSIX 1003.1-2001 Rationale
+ * section for test does in fact say:
+ *
+ * On historical BSD systems, test -w directory always returned false
+ * because test tried to open the directory for writing, which always
+ * fails.
+ *
+ * and indeed this is in fact true for Seventh Edition UNIX, UNIX 32V, and UNIX
+ * System III, and thus presumably also for BSD up to and including 4.3.
+ *
+ * Secondly I remembered why using open() and/or access() are bogus. They
+ * don't work right for detecting read and write permissions bits when called
+ * by root.
+ *
+ * Interestingly the 'test' in 4.4BSD was closer to correct (as per
+ * 1003.2-1992) and it was implemented efficiently with stat() instead of
+ * open().
+ *
+ * This was apparently broken in NetBSD around about 1994/06/30 when the old
+ * 4.4BSD implementation was replaced with a (arguably much better coded)
+ * implementation derived from pdksh.
+ *
+ * Note that modern pdksh is yet different again, but still not correct, at
+ * least not w.r.t. 1003.2-1992.
+ *
+ * As I think more about it and read more of the related IEEE docs I don't like
+ * that wording about 'test -r' and 'test -w' in 1003.1-2001 at all. I very
+ * much prefer the original wording in 1003.2-1992. It is much more useful,
+ * and so that's what I've implemented.
+ *
+ * (Note that a strictly conforming implementation of 1003.1-2001 is in fact
+ * totally useless for the case in question since its 'test -w' and 'test -r'
+ * can never fail for root for any existing files, i.e. files for which 'test
+ * -e' succeeds.)
+ *
+ * The rationale for 1003.1-2001 suggests that the wording was "clarified" in
+ * 1003.1-2001 to align with the 1003.2b draft. 1003.2b Draft 12 (July 1999),
+ * which is the latest copy I have, does carry the same suggested wording as is
+ * in 1003.1-2001, with its rationale saying:
+ *
+ * This change is a clarification and is the result of interpretation
+ * request PASC 1003.2-92 #23 submitted for IEEE Std 1003.2-1992.
+ *
+ * That interpretation can be found here:
+ *
+ * http://www.pasc.org/interps/unofficial/db/p1003.2/pasc-1003.2-23.html
+ *
+ * Not terribly helpful, unfortunately. I wonder who that fence sitter was.
+ *
+ * Worse, IMVNSHO, I think the authors of 1003.2b-D12 have mis-interpreted the
+ * PASC interpretation and appear to be gone against at least one widely used
+ * implementation (namely 4.4BSD). The problem is that for file access by root
+ * this means that if test '-r' and '-w' are to behave as if open() were called
+ * then there's no way for a shell script running as root to check if a file
+ * has certain access bits set other than by the grotty means of interpreting
+ * the output of 'ls -l'. This was widely considered to be a bug in V7's
+ * "test" and is, I believe, one of the reasons why direct use of access() was
+ * avoided in some more recent implementations!
+ *
+ * I have always interpreted '-r' to match '-w' and '-x' as per the original
+ * wording in 1003.2-1992, not the other way around. I think 1003.2b goes much
+ * too far the wrong way without any valid rationale and that it's best if we
+ * stick with 1003.2-1992 and test the flags, and not mimic the behaviour of
+ * open() since we already know very well how it will work -- existance of the
+ * file is all that matters to open() for root.
+ *
+ * Unfortunately the SVID is no help at all (which is, I guess, partly why
+ * we're in this mess in the first place :-).
+ *
+ * The SysV implementation (at least in the 'test' builtin in /bin/sh) does use
+ * access(name, 2) even though it also goes to much greater lengths for '-x'
+ * matching the 1003.2-1992 definition (which is no doubt where that definition
+ * came from).
+ *
+ * The ksh93 implementation uses access() for '-r' and '-w' if
+ * (euid==uid&&egid==gid), but uses st_mode for '-x' iff running as root.
+ * i.e. it does strictly conform to 1003.1-2001 (and presumably 1003.2b).
+ */
+static int
+test_access(struct stat *sp, mode_t stmode)
+{
+#ifdef _MSC_VER
+ /* just pretend to be root for now. */
+ stmode = (stmode << 6) | (stmode << 3) | stmode;
+ return !!(sp->st_mode & stmode);
+#else
+ gid_t *groups;
+ register int n;
+ uid_t euid;
+ int maxgroups;
+
+ /*
+ * I suppose we could use access() if not running as root and if we are
+ * running with ((euid == uid) && (egid == gid)), but we've already
+ * done the stat() so we might as well just test the permissions
+ * directly instead of asking the kernel to do it....
+ */
+ euid = geteuid();
+ if (euid == 0) /* any bit is good enough */
+ stmode = (stmode << 6) | (stmode << 3) | stmode;
+ else if (sp->st_uid == euid)
+ stmode <<= 6;
+ else if (sp->st_gid == getegid())
+ stmode <<= 3;
+ else {
+ /* XXX stolen almost verbatim from ksh93.... */
+ /* on some systems you can be in several groups */
+ if ((maxgroups = getgroups(0, NULL)) <= 0)
+ maxgroups = NGROUPS_MAX; /* pre-POSIX system? */
+ groups = xmalloc((maxgroups + 1) * sizeof(gid_t));
+ n = getgroups(maxgroups, groups);
+ while (--n >= 0) {
+ if (groups[n] == sp->st_gid) {
+ stmode <<= 3;
+ break;
+ }
+ }
+ free(groups);
+ }
+
+ return !!(sp->st_mode & stmode);
+#endif
+}
+
+static int
+filstat(char *nm, enum token mode)
+{
+ struct stat s;
+
+ if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
+ return 0;
+
+ switch (mode) {
+ case FILRD:
+ return test_access(&s, S_IROTH);
+ case FILWR:
+ return test_access(&s, S_IWOTH);
+ case FILEX:
+ return test_access(&s, S_IXOTH);
+ case FILEXIST:
+ return 1; /* the successful lstat()/stat() is good enough */
+ case FILREG:
+ return S_ISREG(s.st_mode);
+ case FILDIR:
+ return S_ISDIR(s.st_mode);
+ case FILCDEV:
+#ifdef S_ISCHR
+ return S_ISCHR(s.st_mode);
+#else
+ return 0;
+#endif
+ case FILBDEV:
+#ifdef S_ISBLK
+ return S_ISBLK(s.st_mode);
+#else
+ return 0;
+#endif
+ case FILFIFO:
+#ifdef S_ISFIFO
+ return S_ISFIFO(s.st_mode);
+#else
+ return 0;
+#endif
+ case FILSOCK:
+#ifdef S_ISSOCK
+ return S_ISSOCK(s.st_mode);
+#else
+ return 0;
+#endif
+ case FILSYM:
+#ifdef S_ISLNK
+ return S_ISLNK(s.st_mode);
+#else
+ return 0;
+#endif
+ case FILSUID:
+ return (s.st_mode & S_ISUID) != 0;
+ case FILSGID:
+ return (s.st_mode & S_ISGID) != 0;
+ case FILSTCK:
+#ifdef S_ISVTX
+ return (s.st_mode & S_ISVTX) != 0;
+#else
+ return 0;
+#endif
+ case FILGZ:
+ return s.st_size > (off_t)0;
+ case FILUID:
+ return s.st_uid == geteuid();
+ case FILGID:
+ return s.st_gid == getegid();
+ default:
+ return 1;
+ }
+}
+
+#define VTOC(x) (const unsigned char *)((const struct t_op *)x)->op_text
+
+static int
+compare1(const void *va, const void *vb)
+{
+ const unsigned char *a = va;
+ const unsigned char *b = VTOC(vb);
+
+ return a[0] - b[0];
+}
+
+static int
+compare2(const void *va, const void *vb)
+{
+ const unsigned char *a = va;
+ const unsigned char *b = VTOC(vb);
+ int z = a[0] - b[0];
+
+ return z ? z : (a[1] - b[1]);
+}
+
+static struct t_op const *
+findop(const char *s)
+{
+ if (s[0] == '-') {
+ if (s[1] == '\0')
+ return NULL;
+ if (s[2] == '\0')
+ return bsearch(s + 1, mop2, __arraycount(mop2), sizeof(*mop2), compare1);
+ else if (s[3] != '\0')
+ return NULL;
+ else
+ return bsearch(s + 1, mop3, __arraycount(mop3), sizeof(*mop3), compare2);
+ } else {
+ if (s[1] == '\0')
+ return bsearch(s, cop, __arraycount(cop), sizeof(*cop), compare1);
+ else if (strcmp(s, cop2[0].op_text) == 0)
+ return cop2;
+ else
+ return NULL;
+ }
+}
+
+static enum token
+t_lex(PTESTINSTANCE pThis, char *s)
+{
+ struct t_op const *op;
+
+ if (s == NULL) {
+ pThis->t_wp_op = NULL;
+ return EOI;
+ }
+
+ if ((op = findop(s)) != NULL) {
+ if (!((op->op_type == UNOP && isoperand(pThis)) ||
+ (op->op_num == LPAREN && *(pThis->t_wp+1) == 0))) {
+ pThis->t_wp_op = op;
+ return op->op_num;
+ }
+ }
+ pThis->t_wp_op = NULL;
+ return OPERAND;
+}
+
+static int
+isoperand(PTESTINSTANCE pThis)
+{
+ struct t_op const *op;
+ char *s, *t;
+
+ if ((s = *(pThis->t_wp+1)) == 0)
+ return 1;
+ if ((t = *(pThis->t_wp+2)) == 0)
+ return 0;
+ if ((op = findop(s)) != NULL)
+ return op->op_type == BINOP && (t[0] != ')' || t[1] != '\0');
+ return 0;
+}
+
+/* atoi with error detection */
+static int
+getn(PTESTINSTANCE pThis, const char *s)
+{
+ char *p;
+ long r;
+
+ errno = 0;
+ r = strtol(s, &p, 10);
+
+ if (errno != 0)
+ return errx(pThis->pCtx, -42, "%s: out of range", s);
+
+ while (isspace((unsigned char)*p))
+ p++;
+
+ if (*p)
+ return errx(pThis->pCtx, -42, "%s: bad number", s);
+
+ return (int) r;
+}
+
+static int
+newerf(const char *f1, const char *f2)
+{
+ struct stat b1, b2;
+
+ return (stat(f1, &b1) == 0 &&
+ stat(f2, &b2) == 0 &&
+ b1.st_mtime > b2.st_mtime);
+}
+
+static int
+olderf(const char *f1, const char *f2)
+{
+ struct stat b1, b2;
+
+ return (stat(f1, &b1) == 0 &&
+ stat(f2, &b2) == 0 &&
+ b1.st_mtime < b2.st_mtime);
+}
+
+static int
+equalf(const char *f1, const char *f2)
+{
+ struct stat b1, b2;
+
+ return (stat(f1, &b1) == 0 &&
+ stat(f2, &b2) == 0 &&
+ b1.st_dev == b2.st_dev &&
+ b1.st_ino == b2.st_ino);
+}
+
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr, "usage: %s expression [-- <prog> [args]]\n", pCtx->pszProgName);
+ return 0; /* only used in --help. */
+}
+
diff --git a/src/kmk/kmkbuiltin/touch.c b/src/kmk/kmkbuiltin/touch.c
new file mode 100644
index 0000000..046b0d3
--- /dev/null
+++ b/src/kmk/kmkbuiltin/touch.c
@@ -0,0 +1,952 @@
+/* $Id: touch.c 3282 2019-01-05 00:57:52Z bird $ */
+/** @file
+ * kmk_touch - Simple touch implementation.
+ */
+
+/*
+ * Copyright (c) 2017 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/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "makeint.h"
+#include <assert.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#if defined(_MSC_VER)
+# include <ctype.h>
+# include <io.h>
+# include <sys/timeb.h>
+#else
+# include <unistd.h>
+#endif
+
+#include <k/kDefs.h>
+#include <k/kTypes.h>
+#include "err.h"
+#include "kbuild_version.h"
+#include "kmkbuiltin.h"
+
+#ifdef _MSC_VER
+# include "nt/ntstat.h"
+# undef FILE_TIMESTAMP_HI_RES
+# define FILE_TIMESTAMP_HI_RES 1
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The program name to use in message. */
+#ifdef KMK
+# define TOUCH_NAME "kmk_builtin_touch"
+#else
+# define TOUCH_NAME "kmk_touch"
+#endif
+/** Converts a two digit decimal field to a number. */
+#define TWO_CHARS_TO_INT(chHigh, chLow) ( ((unsigned)(chHigh) - (unsigned)'0') * 10 + ((unsigned)(chLow) - (unsigned)'0') )
+/** Checks an alleged digit. */
+#define IS_DIGIT(chDigit, uMax) ( ((unsigned)(chDigit) - (unsigned)'0') <= (unsigned)(uMax) )
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef enum KMKTOUCHTARGET
+{
+ kTouchAccessAndModify,
+ kTouchAccessOnly,
+ kTouchModifyOnly
+} KMKTOUCHTARGET;
+
+typedef enum KMKTOUCHACTION
+{
+ kTouchActionCurrent,
+ kTouchActionSet,
+ kTouchActionAdjust
+} KMKTOUCHACTION;
+
+
+typedef struct KMKTOUCHOPTS
+{
+ /** Command execution context. */
+ PKMKBUILTINCTX pCtx;
+ /** What timestamps to modify on the files. */
+ KMKTOUCHTARGET enmWhatToTouch;
+ /** How to update the time. */
+ KMKTOUCHACTION enmAction;
+ /** Whether to create files (K_TRUE) or ignore missing (K_FALSE). */
+ KBOOL fCreate;
+ /** Whether to dereference files. */
+ KBOOL fDereference;
+ /** The new access time value. */
+ struct timeval NewATime;
+ /** The new modified time value. */
+ struct timeval NewMTime;
+
+ /** Number of files. */
+ int cFiles;
+ /** The specified files. */
+ char **papszFiles;
+} KMKTOUCHOPTS;
+typedef KMKTOUCHOPTS *PKMKTOUCHOPTS;
+
+
+static int touch_usage(void)
+{
+ fputs("Usage: " TOUCH_NAME " [options] [MMDDhhmm[YY]] <file1> [.. [fileN]]\n"
+ "\n"
+ "Options:\n"
+ " -A [-][[hh]mm]SS, --adjust=[-][[hh]mm]SS\n"
+ " Adjust timestamps by given delta.\n"
+ " -a, --time=atime, --time=access\n"
+ " Only change the accessed timestamp.\n"
+ " -c, --no-create\n"
+ " Ignore missing files and don't create them. (default create empty file)\n"
+ " -d YYYY-MM-DDThh:mm:SS[.frac][tz], --date=YYYY-MM-DDThh:mm:SS[.frac][tz]\n"
+ " Set the timestamps to the given one.\n"
+ " -f\n"
+ " Ignored for compatbility reasons.\n"
+ " -h, --no-dereference\n"
+ " Don't follow links, touch links. (Not applicable to -r.)\n"
+ " -m, --time=mtime, --time=modify\n"
+ " Only changed the modified timestamp.\n"
+ " -r <file>, --reference=<file>\n"
+ " Take the timestamps from <file>.\n"
+ " -t [[CC]YY]MMDDhhmm[.SS]\n"
+ " Set the timestamps to the given one.\n"
+ "\n"
+ "Note. For compatibility reasons the first file can be taken to be a 8 or 10\n"
+ " character long timestamp if it matches the given pattern and none of\n"
+ " the -A, -d, --date, -r, --reference, or -t options are given. So, use\n"
+ " absolute or relative paths when specifying more than one file.\n"
+ , stdout);
+ return 0;
+}
+
+
+#if K_OS == K_OS_SOLARIS
+/**
+ * Solaris doesn't have lutimes because System V doesn't believe in stuff like file modes on symbolic links.
+ */
+static int lutimes(const char *pszFile, struct timeval aTimes[2])
+{
+ struct stat Stat;
+ if (stat(pszFile, &Stat) != -1)
+ {
+ if (!S_ISLNK(Stat.st_mode))
+ return utimes(pszFile, aTimes);
+ return 0;
+ }
+ return -1;
+}
+#endif
+
+
+/**
+ * Parses adjustment value: [-][[hh]mm]SS
+ */
+static int touch_parse_adjust(PKMKBUILTINCTX pCtx, const char *pszValue, int *piAdjustValue)
+{
+ const char * const pszInValue = pszValue;
+ size_t cchValue = strlen(pszValue);
+ KBOOL fNegative = K_FALSE;
+
+ /* Deal with negativity */
+ if (pszValue[0] == '-')
+ {
+ fNegative = K_TRUE;
+ pszValue++;
+ cchValue--;
+ }
+
+ /* Validate and convert. */
+ *piAdjustValue = 0;
+ switch (cchValue)
+ {
+ case 6:
+ if ( !IS_DIGIT(pszValue[0], 9)
+ || !IS_DIGIT(pszValue[0], 9))
+ return errx(pCtx, 2, "Malformed hour part of -A value: %s", pszInValue);
+ *piAdjustValue = TWO_CHARS_TO_INT(pszValue[0], pszValue[1]) * 60 * 60;
+ /* fall thru */
+ case 4:
+ if ( !IS_DIGIT(pszValue[cchValue - 4], 9) /* don't bother limit to 60 minutes */
+ || !IS_DIGIT(pszValue[cchValue - 3], 9))
+ return errx(pCtx, 2, "Malformed minute part of -A value: %s", pszInValue);
+ *piAdjustValue += TWO_CHARS_TO_INT(pszValue[cchValue - 4], pszValue[cchValue - 3]) * 60;
+ /* fall thru */
+ case 2:
+ if ( !IS_DIGIT(pszValue[cchValue - 2], 9) /* don't bother limit to 60 seconds */
+ || !IS_DIGIT(pszValue[cchValue - 1], 9))
+ return errx(pCtx, 2, "Malformed second part of -A value: %s", pszInValue);
+ *piAdjustValue += TWO_CHARS_TO_INT(pszValue[cchValue - 2], pszValue[cchValue - 1]);
+ break;
+
+ default:
+ return errx(pCtx, 2, "Invalid -A value (length): %s", pszInValue);
+ }
+
+ /* Apply negativity. */
+ if (fNegative)
+ *piAdjustValue = -*piAdjustValue;
+
+ return 0;
+}
+
+
+/**
+ * Parse -d timestamp: YYYY-MM-DDThh:mm:SS[.frac][tz]
+ */
+static int touch_parse_d_ts(PKMKBUILTINCTX pCtx, const char *pszTs, struct timeval *pDst)
+{
+ const char * const pszTsIn = pszTs;
+ struct tm ExpTime;
+
+ /*
+ * Validate and parse the timestamp into the tm structure.
+ */
+ memset(&ExpTime, 0, sizeof(ExpTime));
+
+ /* year */
+ if ( !IS_DIGIT(pszTs[0], 9)
+ || !IS_DIGIT(pszTs[1], 9)
+ || !IS_DIGIT(pszTs[2], 9)
+ || !IS_DIGIT(pszTs[3], 9)
+ || pszTs[4] != '-')
+ return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: expected to start with 4 digit year followed by a dash",
+ pszTsIn);
+ ExpTime.tm_year = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]) * 100
+ + TWO_CHARS_TO_INT(pszTs[2], pszTs[3])
+ - 1900;
+ pszTs += 5;
+
+ /* month */
+ if ( !IS_DIGIT(pszTs[0], 1)
+ || !IS_DIGIT(pszTs[1], 9)
+ || pszTs[2] != '-')
+ return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: expected to two digit month at position 6 followed by a dash",
+ pszTsIn);
+ ExpTime.tm_mon = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]) - 1;
+ pszTs += 3;
+
+ /* day */
+ if ( !IS_DIGIT(pszTs[0], 3)
+ || !IS_DIGIT(pszTs[1], 9)
+ || (pszTs[2] != 'T' && pszTs[2] != 't' && pszTs[2] != ' ') )
+ return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: expected to two digit day of month at position 9 followed by 'T' or space",
+ pszTsIn);
+ ExpTime.tm_mday = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]);
+ pszTs += 3;
+
+ /* hour */
+ if ( !IS_DIGIT(pszTs[0], 2)
+ || !IS_DIGIT(pszTs[1], 9)
+ || pszTs[2] != ':')
+ return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: expected to two digit hour at position 12 followed by colon",
+ pszTsIn);
+ ExpTime.tm_hour = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]);
+ pszTs += 3;
+
+ /* minute */
+ if ( !IS_DIGIT(pszTs[0], 5)
+ || !IS_DIGIT(pszTs[1], 9)
+ || pszTs[2] != ':')
+ return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: expected to two digit minute at position 15 followed by colon",
+ pszTsIn);
+ ExpTime.tm_min = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]);
+ pszTs += 3;
+
+ /* seconds */
+ if ( !IS_DIGIT(pszTs[0], 5)
+ || !IS_DIGIT(pszTs[1], 9))
+ return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: expected to two digit seconds at position 12", pszTsIn);
+ ExpTime.tm_sec = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]);
+ pszTs += 2;
+
+ /* fraction */
+ pDst->tv_usec = 0;
+ if (*pszTs == '.' || *pszTs == ',')
+ {
+ int iFactor;
+
+ pszTs++;
+ if (!IS_DIGIT(*pszTs, 9))
+ return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: empty fraction", pszTsIn);
+
+ iFactor = 100000;
+ do
+ {
+ pDst->tv_usec += ((unsigned)*pszTs - (unsigned)'0') * iFactor;
+ iFactor /= 10;
+ pszTs++;
+ } while (IS_DIGIT(*pszTs, 9));
+ }
+
+ /* zulu time indicator */
+ ExpTime.tm_isdst = -1;
+ if (*pszTs == 'Z' || *pszTs == 'z')
+ {
+ ExpTime.tm_isdst = 0;
+ pszTs++;
+ if (*pszTs != '\0')
+ return errx(pCtx, 2,
+ "Malformed timestamp '%s' given to -d: Unexpected character(s) after zulu time indicator at end of timestamp",
+ pszTsIn);
+ }
+ else if (*pszTs != '\0')
+ return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: expected to 'Z' (zulu) or nothing at end of timestamp", pszTsIn);
+
+ /*
+ * Convert to UTC seconds using either timegm or mktime.
+ */
+ ExpTime.tm_yday = -1;
+ ExpTime.tm_wday = -1;
+ if (ExpTime.tm_isdst == 0)
+ {
+#if K_OS == K_OS_SOLARIS || K_OS == K_OS_OS2
+ pDst->tv_sec = mktime(&ExpTime) - timezone; /* best we can do for now */
+#else
+ pDst->tv_sec = timegm(&ExpTime);
+#endif
+ if (pDst->tv_sec == -1)
+ return errx(pCtx, 1, "timegm failed on '%s': %s", pszTs, strerror(errno));
+ }
+ else
+ {
+ pDst->tv_sec = mktime(&ExpTime);
+ if (pDst->tv_sec == -1)
+ return errx(pCtx, 1, "mktime failed on '%s': %s", pszTs, strerror(errno));
+ }
+ return 0;
+}
+
+
+/**
+ * Parse -t timestamp: [[CC]YY]MMDDhhmm[.SS]
+ */
+static int touch_parse_ts(PKMKBUILTINCTX pCtx, const char *pszTs, struct timeval *pDst)
+{
+ size_t const cchTs = strlen(pszTs);
+ size_t cchTsNoSec;
+ struct tm ExpTime;
+ struct tm *pExpTime;
+ struct timeval Now;
+ int rc;
+
+ /*
+ * Do some input validations first.
+ */
+ if ((cchTs & 1) && pszTs[cchTs - 3] != '.')
+ return errx(pCtx, 2, "Invalid timestamp given to -t: %s", pszTs);
+ switch (cchTs)
+ {
+ case 8: /* MMDDhhmm */
+ case 8 + 2: /* YYMMDDhhmm */
+ case 8 + 2 + 2: /* CCYYMMDDhhmm */
+ cchTsNoSec = cchTs;
+ break;
+ case 8 + 3: /* MMDDhhmm.SS */
+ case 8 + 3 + 2: /* YYMMDDhhmm.SS */
+ case 8 + 3 + 2 + 2: /* CCYYMMDDhhmm.SS */
+ if (pszTs[cchTs - 3] != '.')
+ return errx(pCtx, 2, "Invalid timestamp (-t) '%s': missing dot for seconds part", pszTs);
+ if ( !IS_DIGIT(pszTs[cchTs - 2], 5)
+ || !IS_DIGIT(pszTs[cchTs - 1], 9))
+ return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed seconds part", pszTs);
+ cchTsNoSec = cchTs - 3;
+ break;
+ default:
+ return errx(pCtx, 1, "Invalid timestamp (-t) '%s': wrong length (%d)", pszTs, (int)cchTs);
+ }
+
+ switch (cchTsNoSec)
+ {
+ case 8 + 2 + 2: /* CCYYMMDDhhmm */
+ if ( !IS_DIGIT(pszTs[cchTsNoSec - 12], 9)
+ || !IS_DIGIT(pszTs[cchTsNoSec - 11], 9))
+ return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed CC part", pszTs);
+ /* fall thru */
+ case 8 + 2: /* YYMMDDhhmm */
+ if ( !IS_DIGIT(pszTs[cchTsNoSec - 10], 9)
+ || !IS_DIGIT(pszTs[cchTsNoSec - 9], 9))
+ return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed YY part", pszTs);
+ /* fall thru */
+ case 8: /* MMDDhhmm */
+ if ( !IS_DIGIT(pszTs[cchTsNoSec - 8], 1)
+ || !IS_DIGIT(pszTs[cchTsNoSec - 7], 9) )
+ return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed month part", pszTs);
+ if ( !IS_DIGIT(pszTs[cchTsNoSec - 6], 3)
+ || !IS_DIGIT(pszTs[cchTsNoSec - 5], 9) )
+ return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed day part", pszTs);
+ if ( !IS_DIGIT(pszTs[cchTsNoSec - 4], 2)
+ || !IS_DIGIT(pszTs[cchTsNoSec - 3], 9) )
+ return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed hour part", pszTs);
+ if ( !IS_DIGIT(pszTs[cchTsNoSec - 2], 5)
+ || !IS_DIGIT(pszTs[cchTsNoSec - 1], 9) )
+ return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed minute part", pszTs);
+ break;
+ }
+
+ /*
+ * Get the current time and explode it.
+ */
+ rc = gettimeofday(&Now, NULL);
+ if (rc != 0)
+ return errx(pCtx, 1, "gettimeofday failed: %s", strerror(errno));
+
+ pExpTime = localtime_r(&Now.tv_sec, &ExpTime);
+ if (pExpTime == NULL)
+ return errx(pCtx, 1, "localtime_r failed: %s", strerror(errno));
+
+ /*
+ * Do the decoding.
+ */
+ if (cchTs & 1)
+ pExpTime->tm_sec = TWO_CHARS_TO_INT(pszTs[cchTs - 2], pszTs[cchTs - 1]);
+ else
+ pExpTime->tm_sec = 0;
+
+ if (cchTsNoSec == 8 + 2 + 2) /* CCYY */
+ pExpTime->tm_year = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]) * 100
+ + TWO_CHARS_TO_INT(pszTs[2], pszTs[3])
+ - 1900;
+ else if (cchTsNoSec == 8 + 2) /* YY */
+ {
+ pExpTime->tm_year = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]);
+ if (pExpTime->tm_year < 69)
+ pExpTime->tm_year += 100;
+ }
+
+ pExpTime->tm_mon = TWO_CHARS_TO_INT(pszTs[cchTsNoSec - 8], pszTs[cchTsNoSec - 7]) - 1;
+ pExpTime->tm_mday = TWO_CHARS_TO_INT(pszTs[cchTsNoSec - 6], pszTs[cchTsNoSec - 5]);
+ pExpTime->tm_hour = TWO_CHARS_TO_INT(pszTs[cchTsNoSec - 4], pszTs[cchTsNoSec - 3]);
+ pExpTime->tm_min = TWO_CHARS_TO_INT(pszTs[cchTsNoSec - 2], pszTs[cchTsNoSec - 1]);
+
+ /*
+ * Use mktime to convert to UTC seconds.
+ */
+ pExpTime->tm_isdst = -1;
+ pExpTime->tm_yday = -1;
+ pExpTime->tm_wday = -1;
+ pDst->tv_usec = 0;
+ pDst->tv_sec = mktime(pExpTime);
+ if (pDst->tv_sec != -1)
+ return 0;
+ return errx(pCtx, 1, "mktime failed on '%s': %s", pszTs, strerror(errno));
+}
+
+
+/**
+ * Check for old timestamp: MMDDhhmm[YY]
+ */
+static int touch_parse_old_ts(PKMKBUILTINCTX pCtx, const char *pszOldTs, time_t Now, struct timeval *pDst)
+{
+ /*
+ * Check if this is a valid timestamp.
+ */
+ size_t const cchOldTs = strlen(pszOldTs);
+ if ( ( cchOldTs == 8
+ || cchOldTs == 10)
+ && IS_DIGIT(pszOldTs[0], 1)
+ && IS_DIGIT(pszOldTs[1], 9)
+ && IS_DIGIT(pszOldTs[2], 3)
+ && IS_DIGIT(pszOldTs[3], 9)
+ && IS_DIGIT(pszOldTs[4], 2)
+ && IS_DIGIT(pszOldTs[5], 9)
+ && IS_DIGIT(pszOldTs[6], 5)
+ && IS_DIGIT(pszOldTs[7], 9)
+ && ( cchOldTs == 8
+ || ( IS_DIGIT(pszOldTs[8], 9)
+ && IS_DIGIT(pszOldTs[9], 9) )) )
+ {
+ /*
+ * Explode the current time as local.
+ */
+ struct tm ExpTime;
+ struct tm *pExpTime;
+ pExpTime = localtime_r(&Now, &ExpTime);
+ if (pExpTime == NULL)
+ return errx(pCtx, 1, "localtime_r failed: %s", strerror(errno));
+
+ /*
+ * Decode the bits we've got.
+ */
+ pExpTime->tm_mon = TWO_CHARS_TO_INT(pszOldTs[0], pszOldTs[1]) - 1;
+ pExpTime->tm_mday = TWO_CHARS_TO_INT(pszOldTs[2], pszOldTs[3]);
+ pExpTime->tm_hour = TWO_CHARS_TO_INT(pszOldTs[4], pszOldTs[5]);
+ pExpTime->tm_min = TWO_CHARS_TO_INT(pszOldTs[6], pszOldTs[7]);
+ if (cchOldTs == 10)
+ {
+ pExpTime->tm_year = TWO_CHARS_TO_INT(pszOldTs[8], pszOldTs[9]);
+ if (pExpTime->tm_year <= 38) /* up to 2038, 32-bit time_t style logic. */
+ pExpTime->tm_year += 100;
+ }
+
+ /*
+ * Use mktime to convert to UTC seconds.
+ */
+ pExpTime->tm_isdst = -1;
+ pExpTime->tm_yday = -1;
+ pExpTime->tm_wday = -1;
+ pDst->tv_usec = 0;
+ pDst->tv_sec = mktime(pExpTime);
+ if (pDst->tv_sec != -1)
+ return 0;
+ return errx(pCtx, 1, "mktime failed on '%s': %s", pszOldTs, strerror(errno));
+ }
+
+ /* No valid timestamp present. */
+ return -1;
+}
+
+
+/**
+ * Parses the arguments into pThis.
+ *
+ * @returns exit code.
+ * @param pThis Options structure to return the parsed info in.
+ * Caller initalizes this with defaults.
+ * @param cArgs The number of arguments.
+ * @param papszArgs The arguments.
+ * @param pfExit Indicates whether to exit or to start processing
+ * files.
+ */
+static int touch_parse_args(PKMKTOUCHOPTS pThis, int cArgs, char **papszArgs, KBOOL *pfExit)
+{
+ int iAdjustValue = 0;
+ int iArg;
+ int rc;
+
+ *pfExit = K_TRUE;
+ /*
+ * Parse arguments, skipping all files (GNU style).
+ */
+ for (iArg = 1; iArg < cArgs; iArg++)
+ {
+ const char *pszArg = papszArgs[iArg];
+ if (*pszArg == '-')
+ {
+ const char *pszLongValue = NULL;
+ char ch = *++pszArg;
+ pszArg++;
+
+ /*
+ * Deal with long options first, preferably translating them into short ones.
+ */
+ if (ch == '-')
+ {
+ const char *pszLong = pszArg;
+ ch = *pszArg++;
+ if (!ch)
+ {
+ while (++iArg < cArgs)
+ pThis->papszFiles[pThis->cFiles++] = papszArgs[iArg];
+ break; /* '--' */
+ }
+
+ /* Translate long options. */
+ pszArg = "";
+ if (strcmp(pszLong, "adjust") == 0)
+ ch = 'A';
+ else if (strncmp(pszLong, "adjust=", sizeof("adjust=") - 1) == 0)
+ {
+ ch = 'A';
+ pszLongValue = pszArg = pszLong + sizeof("adjust=") - 1;
+ }
+ else if (strcmp(pszLong, "no-create") == 0)
+ ch = 'c';
+ else if (strcmp(pszLong, "date") == 0)
+ ch = 'd';
+ else if (strncmp(pszLong, "date=", sizeof("date=") - 1) == 0)
+ {
+ ch = 'd';
+ pszLongValue = pszArg = pszLong + sizeof("date=") - 1;
+ }
+ else if (strcmp(pszLong, "no-dereference") == 0)
+ ch = 'h';
+ else if (strcmp(pszLong, "reference") == 0)
+ ch = 'r';
+ else if (strncmp(pszLong, "reference=", sizeof("reference=") - 1) == 0)
+ {
+ ch = 'r';
+ pszLongValue = pszArg = pszLong + sizeof("reference=") - 1;
+ }
+ else if (strcmp(pszLong, "time") == 0)
+ ch = 'T';
+ else if (strncmp(pszLong, "time=", sizeof("time=") - 1) == 0)
+ {
+ ch = 'T';
+ pszLongValue = pszArg = pszLong + sizeof("time=") - 1;
+ }
+ else if (strcmp(pszLong, "help") == 0)
+ return touch_usage();
+ else if (strcmp(pszLong, "version") == 0)
+ return kbuild_version(papszArgs[0]);
+ else
+ return errx(pThis->pCtx, 2, "Unknown option: --%s", pszLong);
+ }
+
+ /*
+ * Process short options.
+ */
+ do
+ {
+ /* Some options takes a value. */
+ const char *pszValue;
+ switch (ch)
+ {
+ case 'A':
+ case 'd':
+ case 'r':
+ case 't':
+ case 'T':
+ if (*pszArg || pszLongValue)
+ {
+ pszValue = pszArg;
+ pszArg = "";
+ }
+ else if (iArg + 1 < cArgs)
+ pszValue = papszArgs[++iArg];
+ else
+ return errx(pThis->pCtx, 2, "Option -%c requires a value", ch);
+ break;
+
+ default:
+ pszValue = NULL;
+ break;
+ }
+
+ switch (ch)
+ {
+ /* -A [-][[HH]MM]SS */
+ case 'A':
+ rc = touch_parse_adjust(pThis->pCtx, pszValue, &iAdjustValue);
+ if (rc != 0)
+ return rc;
+ if (pThis->enmAction != kTouchActionSet)
+ {
+ pThis->enmAction = kTouchActionAdjust;
+ pThis->NewATime.tv_sec = iAdjustValue;
+ pThis->NewATime.tv_usec = 0;
+ pThis->NewMTime = pThis->NewATime;
+ }
+ /* else: applied after parsing everything. */
+ break;
+
+ case 'a':
+ pThis->enmWhatToTouch = kTouchAccessOnly;
+ break;
+
+ case 'c':
+ pThis->fCreate = K_FALSE;
+ break;
+
+ case 'd':
+ rc = touch_parse_d_ts(pThis->pCtx, pszValue, &pThis->NewATime);
+ if (rc != 0)
+ return rc;
+ pThis->enmAction = kTouchActionSet;
+ pThis->NewMTime = pThis->NewATime;
+ break;
+
+ case 'f':
+ /* some historical thing, ignored. */
+ break;
+
+ case 'h':
+ pThis->fDereference = K_FALSE;
+ break;
+
+ case 'm':
+ pThis->enmWhatToTouch = kTouchModifyOnly;
+ break;
+
+ case 'r':
+ {
+ struct stat St;
+ if (stat(pszValue, &St) != 0)
+ return errx(pThis->pCtx, 1, "Failed to stat '%s' (-r option): %s", pszValue, strerror(errno));
+
+ pThis->enmAction = kTouchActionSet;
+ pThis->NewATime.tv_sec = St.st_atime;
+ pThis->NewMTime.tv_sec = St.st_mtime;
+#if FILE_TIMESTAMP_HI_RES
+ pThis->NewATime.tv_usec = St.ST_ATIM_NSEC / 1000;
+ pThis->NewMTime.tv_usec = St.ST_MTIM_NSEC / 1000;
+#else
+ pThis->NewATime.tv_usec = 0;
+ pThis->NewMTime.tv_usec = 0;
+#endif
+ break;
+ }
+
+ case 't':
+ rc = touch_parse_ts(pThis->pCtx, pszValue, &pThis->NewATime);
+ if (rc != 0)
+ return rc;
+ pThis->enmAction = kTouchActionSet;
+ pThis->NewMTime = pThis->NewATime;
+ break;
+
+ case 'T':
+ if ( strcmp(pszValue, "atime") == 0
+ || strcmp(pszValue, "access") == 0)
+ pThis->enmWhatToTouch = kTouchAccessOnly;
+ else if ( strcmp(pszValue, "mtime") == 0
+ || strcmp(pszValue, "modify") == 0)
+ pThis->enmWhatToTouch = kTouchModifyOnly;
+ else
+ return errx(pThis->pCtx, 2, "Unknown --time value: %s", pszValue);
+ break;
+
+ case 'V':
+ return kbuild_version(papszArgs[0]);
+
+ default:
+ return errx(pThis->pCtx, 2, "Unknown option: -%c (%c%s)", ch, ch, pszArg);
+ }
+
+ } while ((ch = *pszArg++) != '\0');
+ }
+ else
+ pThis->papszFiles[pThis->cFiles++] = papszArgs[iArg];
+ }
+
+ /*
+ * Allow adjusting specified timestamps too like BSD does.
+ */
+ if ( pThis->enmAction == kTouchActionSet
+ && iAdjustValue != 0)
+ {
+ if ( pThis->enmWhatToTouch == kTouchAccessAndModify
+ || pThis->enmWhatToTouch == kTouchAccessOnly)
+ pThis->NewATime.tv_sec += iAdjustValue;
+ if ( pThis->enmWhatToTouch == kTouchAccessAndModify
+ || pThis->enmWhatToTouch == kTouchModifyOnly)
+ pThis->NewMTime.tv_sec += iAdjustValue;
+ }
+
+ /*
+ * Check for old timestamp: MMDDhhmm[YY]
+ */
+ if ( pThis->enmAction == kTouchActionCurrent
+ && pThis->cFiles >= 2)
+ {
+ struct timeval OldTs;
+ rc = touch_parse_old_ts(pThis->pCtx, pThis->papszFiles[0], pThis->NewATime.tv_sec, &OldTs);
+ if (rc == 0)
+ {
+ int iFile;
+
+ pThis->NewATime = OldTs;
+ pThis->NewMTime = OldTs;
+ pThis->enmAction = kTouchActionSet;
+
+ for (iFile = 1; iFile < pThis->cFiles; iFile++)
+ pThis->papszFiles[iFile - 1] = pThis->papszFiles[iFile];
+ pThis->cFiles--;
+ }
+ else if (rc > 0)
+ return rc;
+ }
+
+ /*
+ * Check that we've found at least one file argument.
+ */
+ if (pThis->cFiles > 0)
+ {
+ *pfExit = K_FALSE;
+ return 0;
+ }
+ return errx(pThis->pCtx, 2, "No file specified");
+}
+
+
+/**
+ * Touches one file.
+ *
+ * @returns exit code.
+ * @param pThis The options.
+ * @param pszFile The file to touch.
+ */
+static int touch_process_file(PKMKTOUCHOPTS pThis, const char *pszFile)
+{
+ int fd;
+ int rc;
+ struct stat St;
+ struct timeval aTimes[2];
+
+ /*
+ * Create the file if it doesn't exists. If the --no-create/-c option is
+ * in effect, we silently skip the file if it doesn't already exist.
+ */
+ if (pThis->fDereference)
+ rc = stat(pszFile, &St);
+ else
+ rc = lstat(pszFile, &St);
+ if (rc != 0)
+ {
+ if (errno != ENOENT)
+ return errx(pThis->pCtx, 1, "Failed to stat '%s': %s", pszFile, strerror(errno));
+
+ if (!pThis->fCreate)
+ return 0;
+ fd = open(pszFile, O_WRONLY | O_CREAT | KMK_OPEN_NO_INHERIT, 0666);
+ if (fd == -1)
+ return errx(pThis->pCtx, 1, "Failed to create '%s': %s", pszFile, strerror(errno));
+
+ /* If we're not setting the current time, we may need value stat info
+ on the file, so get it thru the file descriptor before closing it. */
+ if (pThis->enmAction == kTouchActionCurrent)
+ rc = 0;
+ else
+ rc = fstat(fd, &St);
+ if (close(fd) != 0)
+ return errx(pThis->pCtx, 1, "Failed to close '%s' after creation: %s", pszFile, strerror(errno));
+ if (rc != 0)
+ return errx(pThis->pCtx, 1, "Failed to fstat '%s' after creation: %s", pszFile, strerror(errno));
+
+ /* We're done now if we're setting the current time. */
+ if (pThis->enmAction == kTouchActionCurrent)
+ return 0;
+ }
+
+ /*
+ * Create aTimes and call utimes/lutimes.
+ */
+ aTimes[0].tv_sec = St.st_atime;
+ aTimes[1].tv_sec = St.st_mtime;
+#if FILE_TIMESTAMP_HI_RES
+ aTimes[0].tv_usec = St.ST_ATIM_NSEC / 1000;
+ aTimes[1].tv_usec = St.ST_MTIM_NSEC / 1000;
+#else
+ aTimes[0].tv_usec = 0;
+ aTimes[1].tv_usec = 0;
+#endif
+ if ( pThis->enmWhatToTouch == kTouchAccessAndModify
+ || pThis->enmWhatToTouch == kTouchAccessOnly)
+ {
+ if ( pThis->enmAction == kTouchActionCurrent
+ || pThis->enmAction == kTouchActionSet)
+ aTimes[0] = pThis->NewATime;
+ else
+ aTimes[0].tv_sec += pThis->NewATime.tv_sec;
+ }
+ if ( pThis->enmWhatToTouch == kTouchAccessAndModify
+ || pThis->enmWhatToTouch == kTouchModifyOnly)
+ {
+ if ( pThis->enmAction == kTouchActionCurrent
+ || pThis->enmAction == kTouchActionSet)
+ aTimes[1] = pThis->NewMTime;
+ else
+ aTimes[1].tv_sec += pThis->NewMTime.tv_sec;
+ }
+
+ /*
+ * Try set the times. If we're setting current time, fall back on calling
+ * [l]utimes with a NULL timeval vector since that has slightly different
+ * permissions checks. (Note that we don't do that by default because it
+ * may do more than what we want (st_ctime).)
+ */
+ if (pThis->fDereference)
+ rc = utimes(pszFile, aTimes);
+ else
+ rc = lutimes(pszFile, aTimes);
+ if (rc != 0)
+ {
+ if (pThis->enmAction == kTouchActionCurrent)
+ {
+ if (pThis->fDereference)
+ rc = utimes(pszFile, NULL);
+ else
+ rc = lutimes(pszFile, NULL);
+ }
+ if (rc != 0)
+ rc = errx(pThis->pCtx, 1, "%stimes failed on '%s': %s", pThis->fDereference ? "" : "l", pszFile, strerror(errno));
+ }
+
+ return rc;
+}
+
+
+/**
+ * Actual main function for the touch command.
+ */
+int kmk_builtin_touch(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ int rc;
+ KMKTOUCHOPTS This;
+ K_NOREF(envp);
+
+ /*
+ * Initialize options with defaults and parse them.
+ */
+ This.pCtx = pCtx;
+ This.enmWhatToTouch = kTouchAccessAndModify;
+ This.enmAction = kTouchActionCurrent;
+ This.fCreate = K_TRUE;
+ This.fDereference = K_TRUE;
+ This.cFiles = 0;
+ This.papszFiles = (char **)calloc(argc, sizeof(char *));
+ if (This.papszFiles)
+ {
+ rc = gettimeofday(&This.NewATime, NULL);
+ if (rc == 0)
+ {
+ KBOOL fExit;
+ This.NewMTime = This.NewATime;
+
+ rc = touch_parse_args(&This, argc, argv, &fExit);
+ if (rc == 0 && !fExit)
+ {
+ /*
+ * Process the files.
+ */
+ int iFile;
+ for (iFile = 0; iFile < This.cFiles; iFile++)
+ {
+ int rc2 = touch_process_file(&This, This.papszFiles[iFile]);
+ if (rc2 != 0 && rc == 0)
+ rc = rc2;
+ }
+ }
+ }
+ else
+ rc = errx(pCtx, 2, "gettimeofday failed: %s", strerror(errno));
+ free(This.papszFiles);
+ }
+ else
+ rc = errx(pCtx, 2, "calloc failed");
+ return rc;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_touch", NULL };
+ return kmk_builtin_touch(argc, argv, envp, &Ctx);
+}
+#endif
+