From 29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:21:29 +0200 Subject: Adding upstream version 1:0.1.9998svn3589+dfsg. Signed-off-by: Daniel Baumann --- src/kmk/kmkbuiltin/Makefile.kup | 0 src/kmk/kmkbuiltin/append.c | 459 ++++++ src/kmk/kmkbuiltin/cat.c | 416 ++++++ src/kmk/kmkbuiltin/chmod.c | 288 ++++ src/kmk/kmkbuiltin/cmp.c | 153 ++ src/kmk/kmkbuiltin/cmp_extern.h | 49 + src/kmk/kmkbuiltin/cmp_util.c | 562 +++++++ src/kmk/kmkbuiltin/common-env-and-cwd-opt.c | 516 +++++++ src/kmk/kmkbuiltin/cp.c | 779 ++++++++++ src/kmk/kmkbuiltin/cp_extern.h | 55 + src/kmk/kmkbuiltin/cp_utils.c | 397 +++++ src/kmk/kmkbuiltin/darwin.c | 55 + src/kmk/kmkbuiltin/echo.c | 125 ++ src/kmk/kmkbuiltin/err.c | 340 +++++ src/kmk/kmkbuiltin/err.h | 38 + src/kmk/kmkbuiltin/expr.c | 617 ++++++++ src/kmk/kmkbuiltin/fts.c | 1461 ++++++++++++++++++ src/kmk/kmkbuiltin/ftsfake.h | 159 ++ src/kmk/kmkbuiltin/getopt1_r.c | 186 +++ src/kmk/kmkbuiltin/getopt_r.c | 1090 ++++++++++++++ src/kmk/kmkbuiltin/getopt_r.h | 182 +++ src/kmk/kmkbuiltin/haikufakes.c | 53 + src/kmk/kmkbuiltin/haikufakes.h | 42 + src/kmk/kmkbuiltin/install.c | 1248 ++++++++++++++++ src/kmk/kmkbuiltin/kDepIDB.c | 860 +++++++++++ src/kmk/kmkbuiltin/kDepObj.c | 1250 ++++++++++++++++ src/kmk/kmkbuiltin/kSubmit.c | 2116 +++++++++++++++++++++++++++ src/kmk/kmkbuiltin/kbuild_protection.c | 376 +++++ src/kmk/kmkbuiltin/kbuild_protection.h | 67 + src/kmk/kmkbuiltin/kill.c | 653 +++++++++ src/kmk/kmkbuiltin/ln.c | 287 ++++ src/kmk/kmkbuiltin/md5sum.c | 874 +++++++++++ src/kmk/kmkbuiltin/mkdir.c | 302 ++++ src/kmk/kmkbuiltin/mscfakes.c | 839 +++++++++++ src/kmk/kmkbuiltin/mscfakes.h | 183 +++ src/kmk/kmkbuiltin/mv.c | 529 +++++++ src/kmk/kmkbuiltin/openbsd.c | 54 + src/kmk/kmkbuiltin/osdep.c | 48 + src/kmk/kmkbuiltin/printf.c | 954 ++++++++++++ src/kmk/kmkbuiltin/redirect.c | 2066 ++++++++++++++++++++++++++ src/kmk/kmkbuiltin/rm.c | 844 +++++++++++ src/kmk/kmkbuiltin/rmdir.c | 251 ++++ src/kmk/kmkbuiltin/setmode.c | 506 +++++++ src/kmk/kmkbuiltin/sleep.c | 179 +++ src/kmk/kmkbuiltin/solfakes.c | 99 ++ src/kmk/kmkbuiltin/solfakes.h | 51 + src/kmk/kmkbuiltin/strlcpy.c | 78 + src/kmk/kmkbuiltin/strmode.c | 197 +++ src/kmk/kmkbuiltin/test.c | 869 +++++++++++ src/kmk/kmkbuiltin/touch.c | 952 ++++++++++++ 50 files changed, 24754 insertions(+) create mode 100644 src/kmk/kmkbuiltin/Makefile.kup create mode 100644 src/kmk/kmkbuiltin/append.c create mode 100644 src/kmk/kmkbuiltin/cat.c create mode 100644 src/kmk/kmkbuiltin/chmod.c create mode 100644 src/kmk/kmkbuiltin/cmp.c create mode 100644 src/kmk/kmkbuiltin/cmp_extern.h create mode 100644 src/kmk/kmkbuiltin/cmp_util.c create mode 100644 src/kmk/kmkbuiltin/common-env-and-cwd-opt.c create mode 100644 src/kmk/kmkbuiltin/cp.c create mode 100644 src/kmk/kmkbuiltin/cp_extern.h create mode 100644 src/kmk/kmkbuiltin/cp_utils.c create mode 100644 src/kmk/kmkbuiltin/darwin.c create mode 100644 src/kmk/kmkbuiltin/echo.c create mode 100644 src/kmk/kmkbuiltin/err.c create mode 100644 src/kmk/kmkbuiltin/err.h create mode 100644 src/kmk/kmkbuiltin/expr.c create mode 100644 src/kmk/kmkbuiltin/fts.c create mode 100644 src/kmk/kmkbuiltin/ftsfake.h create mode 100644 src/kmk/kmkbuiltin/getopt1_r.c create mode 100644 src/kmk/kmkbuiltin/getopt_r.c create mode 100644 src/kmk/kmkbuiltin/getopt_r.h create mode 100644 src/kmk/kmkbuiltin/haikufakes.c create mode 100644 src/kmk/kmkbuiltin/haikufakes.h create mode 100644 src/kmk/kmkbuiltin/install.c create mode 100644 src/kmk/kmkbuiltin/kDepIDB.c create mode 100644 src/kmk/kmkbuiltin/kDepObj.c create mode 100644 src/kmk/kmkbuiltin/kSubmit.c create mode 100644 src/kmk/kmkbuiltin/kbuild_protection.c create mode 100644 src/kmk/kmkbuiltin/kbuild_protection.h create mode 100644 src/kmk/kmkbuiltin/kill.c create mode 100644 src/kmk/kmkbuiltin/ln.c create mode 100644 src/kmk/kmkbuiltin/md5sum.c create mode 100644 src/kmk/kmkbuiltin/mkdir.c create mode 100644 src/kmk/kmkbuiltin/mscfakes.c create mode 100644 src/kmk/kmkbuiltin/mscfakes.h create mode 100644 src/kmk/kmkbuiltin/mv.c create mode 100644 src/kmk/kmkbuiltin/openbsd.c create mode 100644 src/kmk/kmkbuiltin/osdep.c create mode 100644 src/kmk/kmkbuiltin/printf.c create mode 100644 src/kmk/kmkbuiltin/redirect.c create mode 100644 src/kmk/kmkbuiltin/rm.c create mode 100644 src/kmk/kmkbuiltin/rmdir.c create mode 100644 src/kmk/kmkbuiltin/setmode.c create mode 100644 src/kmk/kmkbuiltin/sleep.c create mode 100644 src/kmk/kmkbuiltin/solfakes.c create mode 100644 src/kmk/kmkbuiltin/solfakes.h create mode 100644 src/kmk/kmkbuiltin/strlcpy.c create mode 100644 src/kmk/kmkbuiltin/strmode.c create mode 100644 src/kmk/kmkbuiltin/test.c create mode 100644 src/kmk/kmkbuiltin/touch.c (limited to 'src/kmk/kmkbuiltin') diff --git a/src/kmk/kmkbuiltin/Makefile.kup b/src/kmk/kmkbuiltin/Makefile.kup new file mode 100644 index 0000000..e69de29 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 + * + * 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 + * + */ + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifndef KMK_BUILTIN_STANDALONE +# include "makeint.h" +# include "filedef.h" +# include "variable.h" +#else +# include "config.h" +#endif +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +# include +#endif +#ifdef _MSC_VER +# include +#endif +#ifdef HAVE_ALLOCA_H +# include +#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 +__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 +#endif +#include +#ifndef NO_UDOM_SUPPORT +# include +# include +# include +#endif + +#include +#include "err.h" +#include +#include +#include +#include +#include +#include +#include +#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 */ +/*__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 +#include + +#include "err.h" +#include +#include "fts.h" +#include +#include +#include +#include +#ifndef _MSC_VER +# include +#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 +#include "err.h" +#include +#include +#include +#include +#include +#ifndef _MSC_VER +# include +#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 +#include +#if defined(__FreeBSD__) || defined(__NetBSD__) /** @todo more mmap capable OSes. */ +# define CMP_USE_MMAP +# include +# include +#endif +#include +#include +#include +#include +#include +#ifndef _MSC_VER +# include +# 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 + * + * 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 + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include "config.h" +#include +#include +#include +#include + +#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 +__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 +#include + +#include +#include "err.h" +#include +#include "fts.h" +#include +#include +#include +#include +#include +#include +#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 +__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 +#endif +#include +#ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED +# include +#endif + +#include "err.h" +#include +#include +#include "fts.h" +#include +#include +#include +#include +#ifndef __HAIKU__ +# include +#endif +#include +#ifdef __sun__ +# include "solfakes.h" +#endif +#ifdef __HAIKU__ +# include "haikufakes.h" +#endif +#ifdef _MSC_VER +# include "mscfakes.h" +#else +# include +#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 + * + * 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 + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include "config.h" +#include +#include +#include + + +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 + * + * 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 + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "config.h" + +#include +#include +#include +#ifdef HAVE_UNISTD_H +# include +#endif +#ifdef _MSC_VER +# include +#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 + * + * 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 + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +# ifdef HAVE_STDLIB_H +# include +# endif +# ifdef HAVE_STDINT_H +# include +# endif +#else +# include +# define snprintf _snprintf +#endif +#include +#include +#include +#include +#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 + * + * 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 + * + */ + +#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 . + * Public domain. + */ + +#include "config.h" +#include +#include +#include +#include +#include +#ifdef KMK_WITH_REGEX +#include +#endif +#include +#include +#ifdef HAVE_ALLOCA_H +# include +#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 */ +#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 +#endif +#include + +#include +#include +#include +#include +#include "ftsfake.h" +#include +#include +#include +#ifdef HAVE_ALLOCA_H +# include +#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 for correct reference") +__warn_references(fts_close, + "warning: reference to compatibility fts_close();" + " include for correct reference") +__warn_references(fts_open, + "warning: reference to compatibility fts_open();" + " include for correct reference") +__warn_references(fts_read, + "warning: reference to compatibility fts_read();" + " include for correct reference") +__warn_references(fts_set, + "warning: reference to compatibility fts_set();" + " include 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 . + +Modifications: + Copyright (c) 2018 knut st. osmundsen +*/ + +#define FAKES_NO_GETOPT_H /* bird */ +#ifdef HAVE_CONFIG_H +#include +#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 + +#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 +#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 +#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 + +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 . + +Modifications: + Copyright (c) 2018 knut st. osmundsen +*/ + +/* This tells Alpha OSF/1 not to define a getopt prototype in . + Ditto for AIX 3.2 and . */ +#ifndef _NO_PROTO +# define _NO_PROTO +#endif + +#define FAKES_NO_GETOPT_H /* bird */ +#ifdef HAVE_CONFIG_H +# include +#endif + +#if !defined __STDC__ || !__STDC__ +/* This is a separate conditional since some stdc systems + reject `defined (const)'. */ +# ifndef const +# define const +# endif +#endif + +#include + +#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 +# 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 +# include +#endif /* GNU C library. */ +#include /* bird: we don't define getopt, so we're good. */ + +#ifdef VMS +# include +# if HAVE_STRING_H - 0 +# include +# 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 + +#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 +//# define my_index strchr +#else + +# if HAVE_STRING_H +# include +# else +# include +# 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 . + +Modifications: + Copyright (c) 2018 knut st. osmundsen +*/ + +/* 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 + +#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 + * + * 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 + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include "config.h" +#include +#include +#include +#include +#include +#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 + * + * 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 + * + */ + +#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 +__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 +# if !defined(__HAIKU__) && !defined(__gnu_hurd__) +# include +# endif +# include +# include +#endif /* !_MSC_VER */ +#include + +#include +#include "err.h" +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef __HAIKU__ +# include +#endif +#ifdef __NetBSD__ +# include +# define strtofflags(a, b, c) string_to_flags(a, b, c) +#endif +#include +#if defined(__EMX__) || defined(_MSC_VER) +# include +#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 + * + * 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 + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "config.h" +#include +#include +#include +#include +#include +#include +#ifdef HAVE_ALLOCA_H +# include +#endif +#if !defined(_MSC_VER) +# include +#else +# include +#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 -t [-fqs] \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 + * + * 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 + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#define MSCFAKES_NO_WINDOWS_H +#include +#include +#include +#include +#include +#include +#include +#if !defined(_MSC_VER) +# include +#else +# include +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 -t [-fqs] [-e ] \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 + * + * 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 + * + */ + +/******************************************************************************* +* 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 +#include +#include +#include +#include +#ifdef HAVE_ALLOCA_H +# include +#endif +#if defined(_MSC_VER) +# include +# include +# include +# include +#else +# include +#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 ] [-U|--unset ]\n" + " [-A|--append ] [-D|--prepend ]\n" + " [-s|--special-env ] [-C|--chdir ]\n" + " [--wcc-brain-damage] [--no-pch-caching]\n" + " [-3|--32-bit] [-6|--64-bit] [-v] [--debug-dump-history]\n" + " [-P|--post-cmd [args]] -- [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 =[value]\n" + " Sets an environment variable putenv fashion. Position dependent.\n" + " -U, --unset \n" + " Removes an environment variable. Position dependent.\n" + " -A, --append =\n" + " Appends the given value to the environment variable.\n" + " -D,--prepend =\n" + " Prepends the given value to the environment variable.\n" + " -s,--special-env =\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 \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 ...\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 + * + * 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 + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include "config.h" +#include +#include +#include +#include +#include +#if defined(_MSC_VER) || defined(__OS2__) +# include +# include +#else +# include +#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 + * + * 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 + * + */ + +#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 + * + * 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 + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include +#include +#include +#include +#ifdef WINDOWS32 +# include +# include +# include +#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] [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 +__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 +#endif +#include + +#include "err.h" +#include +#include +#include +#include +#include +#include +#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 + * + * 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 + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include "config.h" +#include +#include +#include +#include +#ifdef _MSC_VER +# include +#else +# include +#endif +#include +#include "err.h" +#include "kmkbuiltin.h" +#include "../../lib/md5.h" +#include + +/*#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 +__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 +#include + +#include "err.h" +#include +#include +#ifndef _MSC_VER +# include +#endif +#include +#include +#include +#ifndef __HAIKU__ +# include +#endif +#include +#ifdef HAVE_ALLOCA_H +# include +#endif +#include "getopt_r.h" +#ifdef __HAIKU__ +# include "haikufakes.h" +#endif +#ifdef _MSC_VER +# include +# 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 + * + * 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 + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 + * + * 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 + * + */ + +#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 +#include +#include +#include +#include +#ifndef FAKES_NO_GETOPT_H +# include "getopt.h" +#endif +#ifndef MSCFAKES_NO_WINDOWS_H +# include +#endif + +#include +#include +#include +#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 +#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 +__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 +#ifndef _MSC_VER +# ifdef CROSS_DEVICE_MOVE +# include +# endif +# include +# include +# include +# if !defined(__HAIKU__) && !defined(__gnu_hurd__) +# include +# endif +#endif +#include + +#include "err.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef __HAIKU__ +# include +#endif +#include +#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 + * + * 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 + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include "config.h" +#include +#include + + +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 + * + * 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 + * + */ + +#include + +/** @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 +#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 + +#include +#include "err.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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[] = ""; + 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 + * + * 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 + * + */ + +/******************************************************************************* +* 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 +#include +#include +#include +#include +#include +#include +#if defined(KBUILD_OS_WINDOWS) || defined(KBUILD_OS_OS2) +# include +#endif +#ifdef KBUILD_OS_WINDOWS +# include +#endif +#if defined(_MSC_VER) +# include +# include +# 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 +# ifdef USE_POSIX_SPAWN +# include +# endif +# include +#endif + +#include +#include +#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 +# 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] ] [-d=] [-c] [--stdin-pipe]\n" + " [-Z] [-E ] [-A ] [-P ] [-D ]\n" + " [-C ] [--wcc-brain-damage] [-v] -- [args]\n" + " or: %s --help\n" + " or: %s --version\n" + "\n" + "Options:\n" + "-[rwa+tb] \n" + " The rwa+tb is like for fopen, if not specified it defaults to w+.\n" + " The 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 , --close \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 , --set , --env \n" + " The -E (--set, --env) switch is for making changes to the environment\n" + " in an putenv fashion.\n" + "-A , --append \n" + " The -A switch appends to an environment variable in a putenv fashion.\n" + "-D , --prepend \n" + " The -D switch prepends to an environment variable in a putenv fashion.\n" + "-U , --unset \n" + " The -U switch deletes an environment variable.\n" + /* 0 1 2 3 4 5 6 7 8 */ + /* 012345678901234567890123456789012345678901234567890123456789012345678901234567890 */ + "-C , --chdir \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", 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 +/*__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 +#if !defined(_MSC_VER) && !defined(__HAIKU__) +# include +# ifndef __gnu_hurd__ +# include +# endif +#endif + +#include "err.h" +#include +#include +#include "fts.h" +#include +#include +#include +#include +#include +#ifndef __HAIKU__ +# include +#endif +#include +#include +#include "getopt_r.h" +#ifdef __HAIKU__ +# include "haikufakes.h" +#endif +#ifdef __NetBSD__ +# include +# 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 +# include +#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 = ""; + } + (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 +__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 +#include +#include +#include +#include +#ifdef HAVE_ALLOCA_H +# include +#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 */ +#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 +#include + +#include +#include +#include +#include +#include +#ifndef _MSC_VER +#include +#else +#include "mscfakes.h" +#endif + +#ifdef SETMODE_DEBUG +#include +#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 + * + * 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 + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include "config.h" +#include +#include +#include +#include +#include +#if defined(_MSC_VER) +# include +#else +# include +# include +#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 [s]\n" + " or: %s ms\n" + " or: %s m\n" + " or: %s h\n" + " or: %s 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 + * + * 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 + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include "config.h" +#include +#include +#include +#include +#include +#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 + * + * 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 + * + */ + +#ifndef ___solfakes_h +#define ___solfakes_h +#ifdef __sun__ + +#include +#include +#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 + * 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 +//__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 +#include + +/* 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 */ +#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 +#include + +#include +#ifndef _MSC_VER +#include +#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 +#ifndef lint +__RCSID("$NetBSD: test.c,v 1.33 2007/06/24 18:54:58 christos Exp $"); +#endif*/ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "config.h" +#include +#include + +#include +#include "err.h" +#include +#include +#include +#include +#include +#ifdef _MSC_VER +# include +# include +# include +# include "mscfakes.h" +# include "quote_argv.h" +#else +# include +#endif +#include +#include + +#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 ::= +*/ + +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 [-- [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 + * + * 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 + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include "makeint.h" +#include +#include +#include +#include +#include +#include +#include +#if defined(_MSC_VER) +# include +# include +# include +#else +# include +#endif + +#include +#include +#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]] [.. [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 , --reference=\n" + " Take the timestamps from .\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 + -- cgit v1.2.3