summaryrefslogtreecommitdiffstats
path: root/src/bldprogs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bldprogs')
-rw-r--r--src/bldprogs/Makefile.kmk119
-rw-r--r--src/bldprogs/VBoxCPP.cpp5542
-rw-r--r--src/bldprogs/VBoxCheckImports.cpp381
-rw-r--r--src/bldprogs/VBoxCmp.cpp141
-rw-r--r--src/bldprogs/VBoxCompilerPlugIns.h137
-rw-r--r--src/bldprogs/VBoxCompilerPlugInsCommon.cpp448
-rw-r--r--src/bldprogs/VBoxCompilerPlugInsGcc.cpp1012
-rw-r--r--src/bldprogs/VBoxDef2LazyLoad.cpp1624
-rw-r--r--src/bldprogs/VBoxEditCoffLib.cpp476
-rw-r--r--src/bldprogs/VBoxPeSetVersion.cpp404
-rw-r--r--src/bldprogs/VBoxTpG.cpp2665
-rw-r--r--src/bldprogs/bin2c.c274
-rw-r--r--src/bldprogs/biossums.c255
-rwxr-xr-xsrc/bldprogs/checkUndefined.sh102
-rw-r--r--src/bldprogs/deftoimp.sed66
-rw-r--r--src/bldprogs/filesplitter.cpp388
-rw-r--r--src/bldprogs/genalias.cpp500
-rw-r--r--src/bldprogs/preload.cpp222
-rw-r--r--src/bldprogs/scm.cpp3269
-rw-r--r--src/bldprogs/scm.h498
-rw-r--r--src/bldprogs/scmdiff.cpp450
-rw-r--r--src/bldprogs/scmdiff.h74
-rw-r--r--src/bldprogs/scmparser.cpp1199
-rw-r--r--src/bldprogs/scmrw-kmk.cpp2273
-rw-r--r--src/bldprogs/scmrw.cpp3613
-rw-r--r--src/bldprogs/scmstream.cpp1478
-rw-r--r--src/bldprogs/scmstream.h152
-rw-r--r--src/bldprogs/scmsubversion.cpp1690
-rw-r--r--src/bldprogs/solgcc/.scm-settings29
-rw-r--r--src/bldprogs/solgcc/config/i386/sol2-gas.h0
-rw-r--r--src/bldprogs/solgcc/config/usegas.h0
-rw-r--r--src/bldprogs/test-gccplugin-2.c57
-rw-r--r--src/bldprogs/test-gccplugin-3.c47
-rw-r--r--src/bldprogs/test-gccplugin.c36
34 files changed, 29621 insertions, 0 deletions
diff --git a/src/bldprogs/Makefile.kmk b/src/bldprogs/Makefile.kmk
new file mode 100644
index 00000000..37f5ad31
--- /dev/null
+++ b/src/bldprogs/Makefile.kmk
@@ -0,0 +1,119 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for various generic build tools (there is currently only one of them).
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program 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, in version 3 of the
+# License.
+#
+# This program 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 <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../..
+include $(KBUILD_PATH)/subheader.kmk
+
+BLDPROGS += bin2c
+ifn1of ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH), solaris.sparc64)
+ BLDPROGS += biossums filesplitter genalias VBoxCmp
+endif
+
+bin2c_TEMPLATE = VBoxBldProg
+bin2c_SOURCES = \
+ bin2c.c
+
+biossums_TEMPLATE = VBoxBldProg
+biossums_SOURCES = biossums.c
+
+filesplitter_TEMPLATE = VBoxBldProg
+filesplitter_SOURCES = filesplitter.cpp
+
+genalias_TEMPLATE = VBoxBldProg
+genalias_SOURCES = genalias.cpp
+
+VBoxCmp_TEMPLATE = VBoxBldProg
+VBoxCmp_SOURCES = VBoxCmp.cpp
+
+ifndef VBOX_ONLY_BUILD
+ PROGRAMS += scm
+ scm_TEMPLATE = VBoxR3Tool
+ scm_SOURCES = \
+ scm.cpp \
+ scmdiff.cpp \
+ scmrw.cpp \
+ scmrw-kmk.cpp \
+ scmparser.cpp \
+ scmstream.cpp \
+ scmsubversion.cpp
+ ifdef VBOX_PATH_SUBVERSION_INCS
+ scm_INCS += $(VBOX_PATH_SUBVERSION_INCS) $(VBOX_PATH_APACHE_RUNTIME_INCS)
+ scm_DEFS += SCM_WITH_SVN_HEADERS
+ endif
+
+ BLDPROGS += VBoxCPP
+ VBoxCPP_TEMPLATE = VBoxAdvBldProg
+ VBoxCPP_SOURCES = \
+ VBoxCPP.cpp \
+ scmstream.cpp
+endif
+
+if !defined(VBOX_ONLY_BUILD) || defined(VBOX_ONLY_EXTPACKS)
+ BLDPROGS += VBoxTpG
+ VBoxTpG_TEMPLATE = VBoxAdvBldProg
+ VBoxTpG_SOURCES = \
+ VBoxTpG.cpp \
+ scmstream.cpp
+endif
+
+ifeq ($(KBUILD_TARGET),win)
+ BLDPROGS += VBoxPeSetVersion
+endif
+VBoxPeSetVersion_TEMPLATE = VBoxBldProg
+VBoxPeSetVersion_SOURCES = VBoxPeSetVersion.cpp
+
+BLDPROGS.win += VBoxCheckImports
+VBoxCheckImports_TEMPLATE = VBoxBldProg
+VBoxCheckImports_SOURCES = VBoxCheckImports.cpp
+
+ifneq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),solaris.sparc64)
+ BLDPROGS += VBoxDef2LazyLoad
+endif
+VBoxDef2LazyLoad_TEMPLATE = VBoxBldProg
+VBoxDef2LazyLoad_SOURCES = VBoxDef2LazyLoad.cpp
+
+ifeq ($(KBUILD_TARGET),win)
+ BLDPROGS += VBoxEditCoffLib
+endif
+VBoxEditCoffLib_TEMPLATE = VBoxBldProg
+VBoxEditCoffLib_SOURCES = VBoxEditCoffLib.cpp
+
+# temp hack.
+VBoxCompilerPlugInsGcc.o VBoxCompilerPlugInsCommon.o VBoxCompilerPlugIns.o gccplugin: gccplugin$(SUFF_DLL)
+gccplugin$(SUFF_DLL): VBoxCompilerPlugInsGcc.cpp VBoxCompilerPlugInsCommon.cpp VBoxCompilerPlugIns.h
+ $(TOOL_GXX3_CXX) -shared -fPIC -fno-rtti -g \
+ -DIN_RING3 \
+ $(if-expr "$(KBUILD_TYPE)" != "release",-DDEBUG,) \
+ -I$(shell $(TOOL_GXX3_CXX) -print-file-name=plugin)/include \
+ -I$(PATH_ROOT)/include \
+ $(if-expr "$(KBUILD_HOST)" == "solaris", -I/usr/include/gmp -I$(PATH_ROOT)/src/bldprogs/solgcc/,) \
+ -o $@ \
+ VBoxCompilerPlugInsGcc.cpp \
+ VBoxCompilerPlugInsCommon.cpp
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/bldprogs/VBoxCPP.cpp b/src/bldprogs/VBoxCPP.cpp
new file mode 100644
index 00000000..eb5a7d44
--- /dev/null
+++ b/src/bldprogs/VBoxCPP.cpp
@@ -0,0 +1,5542 @@
+/* $Id: VBoxCPP.cpp $ */
+/** @file
+ * VBox Build Tool - A mini C Preprocessor.
+ *
+ * Purposes to which this preprocessor will be put:
+ * - Preprocessig vm.h into dtrace/lib/vm.d so we can access the VM
+ * structure (as well as substructures) from DTrace without having
+ * to handcraft it all.
+ * - Removing \#ifdefs relating to a new feature that has become
+ * stable and no longer needs \#ifdef'ing.
+ * - Pretty printing preprocessor directives. This will be used by
+ * SCM.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/VBoxTpG.h>
+
+#include <iprt/alloca.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+#include "scmstream.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The bitmap type. */
+#define VBCPP_BITMAP_TYPE uint64_t
+/** The bitmap size as a multiple of VBCPP_BITMAP_TYPE. */
+#define VBCPP_BITMAP_SIZE (128 / 64)
+/** Checks if a bit is set. */
+#define VBCPP_BITMAP_IS_SET(a_bm, a_ch) ASMBitTest(a_bm, (a_ch) & 0x7f)
+/** Sets a bit. */
+#define VBCPP_BITMAP_SET(a_bm, a_ch) ASMBitSet(a_bm, (a_ch) & 0x7f)
+/** Empties the bitmap. */
+#define VBCPP_BITMAP_EMPTY(a_bm) do { (a_bm)[0] = 0; (a_bm)[1] = 0; } while (0)
+/** Joins to bitmaps by OR'ing their values.. */
+#define VBCPP_BITMAP_OR(a_bm1, a_bm2) do { (a_bm1)[0] |= (a_bm2)[0]; (a_bm1)[1] |= (a_bm2)[1]; } while (0)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to the C preprocessor instance data. */
+typedef struct VBCPP *PVBCPP;
+
+
+/**
+ * Variable string buffer (very simple version of SCMSTREAM).
+ */
+typedef struct VBCPPSTRBUF
+{
+ /** The preprocessor instance (for error reporting). */
+ struct VBCPP *pThis;
+ /** The length of the string in the buffer. */
+ size_t cchBuf;
+ /** The string storage. */
+ char *pszBuf;
+ /** Allocated buffer space. */
+ size_t cbBufAllocated;
+} VBCPPSTRBUF;
+/** Pointer to a variable string buffer. */
+typedef VBCPPSTRBUF *PVBCPPSTRBUF;
+
+
+/**
+ * The preprocessor mode.
+ */
+typedef enum VBCPPMODE
+{
+ kVBCppMode_Invalid = 0,
+ kVBCppMode_Standard,
+ kVBCppMode_Selective,
+ kVBCppMode_SelectiveD,
+ kVBCppMode_End
+} VBCPPMODE;
+
+
+/**
+ * A macro (aka define).
+ */
+typedef struct VBCPPMACRO
+{
+ /** The string space core. */
+ RTSTRSPACECORE Core;
+#if 0
+ /** For linking macros that have the fExpanding flag set. */
+ struct VBCPPMACRO *pUpExpanding;
+#endif
+ /** Whether it's a function. */
+ bool fFunction;
+ /** Variable argument count. */
+ bool fVarArg;
+ /** Set if originating on the command line. */
+ bool fCmdLine;
+ /** Set if this macro is currently being expanded and should not be
+ * recursively applied. */
+ bool fExpanding;
+ /** The number of known arguments. */
+ uint32_t cArgs;
+ /** Pointer to a list of argument names. */
+ const char **papszArgs;
+ /** Lead character bitmap for the argument names. */
+ VBCPP_BITMAP_TYPE bmArgs[VBCPP_BITMAP_SIZE];
+ /** The value length. */
+ size_t cchValue;
+ /** The define value. (This is followed by the name and arguments.) */
+ char szValue[1];
+} VBCPPMACRO;
+/** Pointer to a macro. */
+typedef VBCPPMACRO *PVBCPPMACRO;
+
+
+/**
+ * Macro expansion data.
+ */
+typedef struct VBCPPMACROEXP
+{
+ /** The expansion buffer. */
+ VBCPPSTRBUF StrBuf;
+#if 0
+ /** List of expanding macros (Stack). */
+ PVBCPPMACRO pMacroStack;
+#endif
+ /** The input stream (in case we want to look for parameter lists). */
+ PSCMSTREAM pStrmInput;
+ /** Array of argument values. Used when expanding function style macros. */
+ char **papszArgs;
+ /** The number of argument values current in papszArgs. */
+ uint32_t cArgs;
+ /** The number of argument values papszArgs can currently hold */
+ uint32_t cArgsAlloced;
+} VBCPPMACROEXP;
+/** Pointer to macro expansion data. */
+typedef VBCPPMACROEXP *PVBCPPMACROEXP;
+
+
+/**
+ * The vbcppMacroExpandReScan mode of operation.
+ */
+typedef enum VBCPPMACRORESCANMODE
+{
+ /** Invalid mode. */
+ kMacroReScanMode_Invalid = 0,
+ /** Normal expansion mode. */
+ kMacroReScanMode_Normal,
+ /** Replaces known macros and heeds the 'defined' operator. */
+ kMacroReScanMode_Expression,
+ /** End of valid modes. */
+ kMacroReScanMode_End
+} VBCPPMACRORESCANMODE;
+
+
+/**
+ * Expression node type.
+ */
+typedef enum VBCPPEXPRKIND
+{
+ kVBCppExprKind_Invalid = 0,
+ kVBCppExprKind_Unary,
+ kVBCppExprKind_Binary,
+ kVBCppExprKind_Ternary,
+ kVBCppExprKind_SignedValue,
+ kVBCppExprKind_UnsignedValue,
+ kVBCppExprKind_End
+} VBCPPEXPRKIND;
+
+
+/** Macro used for the precedence field. */
+#define VBCPPOP_PRECEDENCE(a_iPrecedence) ((a_iPrecedence) << 8)
+/** Mask for getting the precedence field value. */
+#define VBCPPOP_PRECEDENCE_MASK 0xff00
+/** Operator associativity - Left to right. */
+#define VBCPPOP_L2R (1 << 16)
+/** Operator associativity - Right to left. */
+#define VBCPPOP_R2L (2 << 16)
+
+/**
+ * Unary operators.
+ */
+typedef enum VBCPPUNARYOP
+{
+ kVBCppUnaryOp_Invalid = 0,
+ kVBCppUnaryOp_Pluss = VBCPPOP_R2L | VBCPPOP_PRECEDENCE( 3) | 5,
+ kVBCppUnaryOp_Minus = VBCPPOP_R2L | VBCPPOP_PRECEDENCE( 3) | 6,
+ kVBCppUnaryOp_LogicalNot = VBCPPOP_R2L | VBCPPOP_PRECEDENCE( 3) | 7,
+ kVBCppUnaryOp_BitwiseNot = VBCPPOP_R2L | VBCPPOP_PRECEDENCE( 3) | 8,
+ kVBCppUnaryOp_Parenthesis = VBCPPOP_R2L | VBCPPOP_PRECEDENCE(15) | 9,
+ kVBCppUnaryOp_End
+} VBCPPUNARYOP;
+
+/**
+ * Binary operators.
+ */
+typedef enum VBCPPBINARYOP
+{
+ kVBCppBinary_Invalid = 0,
+ kVBCppBinary_Multiplication = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 5) | 2,
+ kVBCppBinary_Division = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 5) | 4,
+ kVBCppBinary_Modulo = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 5) | 5,
+ kVBCppBinary_Addition = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 6) | 6,
+ kVBCppBinary_Subtraction = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 6) | 7,
+ kVBCppBinary_LeftShift = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 7) | 8,
+ kVBCppBinary_RightShift = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 7) | 9,
+ kVBCppBinary_LessThan = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 8) | 10,
+ kVBCppBinary_LessThanOrEqual = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 8) | 11,
+ kVBCppBinary_GreaterThan = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 8) | 12,
+ kVBCppBinary_GreaterThanOrEqual = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 8) | 13,
+ kVBCppBinary_EqualTo = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 9) | 14,
+ kVBCppBinary_NotEqualTo = VBCPPOP_L2R | VBCPPOP_PRECEDENCE( 9) | 15,
+ kVBCppBinary_BitwiseAnd = VBCPPOP_L2R | VBCPPOP_PRECEDENCE(10) | 16,
+ kVBCppBinary_BitwiseXor = VBCPPOP_L2R | VBCPPOP_PRECEDENCE(11) | 17,
+ kVBCppBinary_BitwiseOr = VBCPPOP_L2R | VBCPPOP_PRECEDENCE(12) | 18,
+ kVBCppBinary_LogicalAnd = VBCPPOP_L2R | VBCPPOP_PRECEDENCE(13) | 19,
+ kVBCppBinary_LogicalOr = VBCPPOP_L2R | VBCPPOP_PRECEDENCE(14) | 20,
+ kVBCppBinary_End
+} VBCPPBINARYOP;
+
+/** The precedence of the ternary operator (expr ? true : false). */
+#define VBCPPTERNAROP_PRECEDENCE VBCPPOP_PRECEDENCE(16)
+
+
+/** Pointer to an expression parsing node. */
+typedef struct VBCPPEXPR *PVBCPPEXPR;
+/**
+ * Expression parsing node.
+ */
+typedef struct VBCPPEXPR
+{
+ /** Parent expression. */
+ PVBCPPEXPR pParent;
+ /** Whether the expression is complete or not. */
+ bool fComplete;
+ /** The kind of expression. */
+ VBCPPEXPRKIND enmKind;
+ /** Kind specific content. */
+ union
+ {
+ /** kVBCppExprKind_Unary */
+ struct
+ {
+ VBCPPUNARYOP enmOperator;
+ PVBCPPEXPR pArg;
+ } Unary;
+
+ /** kVBCppExprKind_Binary */
+ struct
+ {
+ VBCPPBINARYOP enmOperator;
+ PVBCPPEXPR pLeft;
+ PVBCPPEXPR pRight;
+ } Binary;
+
+ /** kVBCppExprKind_Ternary */
+ struct
+ {
+ PVBCPPEXPR pExpr;
+ PVBCPPEXPR pTrue;
+ PVBCPPEXPR pFalse;
+ } Ternary;
+
+ /** kVBCppExprKind_SignedValue */
+ struct
+ {
+ int64_t s64;
+ } SignedValue;
+
+ /** kVBCppExprKind_UnsignedValue */
+ struct
+ {
+ uint64_t u64;
+ } UnsignedValue;
+ } u;
+} VBCPPEXPR;
+
+
+/**
+ * Operator return statuses.
+ */
+typedef enum VBCPPEXPRRET
+{
+ kExprRet_Error = -1,
+ kExprRet_Ok = 0,
+ kExprRet_UnaryOperator,
+ kExprRet_Value,
+ kExprRet_EndOfExpr,
+ kExprRet_End
+} VBCPPEXPRRET;
+
+/**
+ * Expression parser context.
+ */
+typedef struct VBCPPEXPRPARSER
+{
+ /** The current expression posistion. */
+ const char *pszCur;
+ /** The root node. */
+ PVBCPPEXPR pRoot;
+ /** The current expression node. */
+ PVBCPPEXPR pCur;
+ /** Where to insert the next expression. */
+ PVBCPPEXPR *ppCur;
+ /** The expression. */
+ const char *pszExpr;
+ /** The number of undefined macros we've encountered while parsing. */
+ size_t cUndefined;
+ /** Pointer to the C preprocessor instance. */
+ PVBCPP pThis;
+} VBCPPEXPRPARSER;
+/** Pointer to an expression parser context. */
+typedef VBCPPEXPRPARSER *PVBCPPEXPRPARSER;
+
+
+/**
+ * Evaluation result.
+ */
+typedef enum VBCPPEVAL
+{
+ kVBCppEval_Invalid = 0,
+ kVBCppEval_True,
+ kVBCppEval_False,
+ kVBCppEval_Undecided,
+ kVBCppEval_End
+} VBCPPEVAL;
+
+
+/**
+ * The condition kind.
+ */
+typedef enum VBCPPCONDKIND
+{
+ kVBCppCondKind_Invalid = 0,
+ /** \#if expr */
+ kVBCppCondKind_If,
+ /** \#ifdef define */
+ kVBCppCondKind_IfDef,
+ /** \#ifndef define */
+ kVBCppCondKind_IfNDef,
+ /** \#elif expr */
+ kVBCppCondKind_ElIf,
+ /** The end of valid values. */
+ kVBCppCondKind_End
+} VBCPPCONDKIND;
+
+
+/**
+ * Conditional stack entry.
+ */
+typedef struct VBCPPCOND
+{
+ /** The next conditional on the stack. */
+ struct VBCPPCOND *pUp;
+ /** The kind of conditional. This changes on encountering \#elif. */
+ VBCPPCONDKIND enmKind;
+ /** Evaluation result. */
+ VBCPPEVAL enmResult;
+ /** The evaluation result of the whole stack. */
+ VBCPPEVAL enmStackResult;
+
+ /** Whether we've seen the last else. */
+ bool fSeenElse;
+ /** Set if we have an else if which has already been decided. */
+ bool fElIfDecided;
+ /** The nesting level of this condition. */
+ uint16_t iLevel;
+ /** The nesting level of this condition wrt the ones we keep. */
+ uint16_t iKeepLevel;
+
+ /** The condition string. (Points within the stream buffer.) */
+ const char *pchCond;
+ /** The condition length. */
+ size_t cchCond;
+} VBCPPCOND;
+/** Pointer to a conditional stack entry. */
+typedef VBCPPCOND *PVBCPPCOND;
+
+
+/**
+ * Input buffer stack entry.
+ */
+typedef struct VBCPPINPUT
+{
+ /** Pointer to the next input on the stack. */
+ struct VBCPPINPUT *pUp;
+ /** The input stream. */
+ SCMSTREAM StrmInput;
+ /** Pointer into szName to the part which was specified. */
+ const char *pszSpecified;
+ /** The input file name with include path. */
+ char szName[1];
+} VBCPPINPUT;
+/** Pointer to a input buffer stack entry */
+typedef VBCPPINPUT *PVBCPPINPUT;
+
+
+/**
+ * The action to take with \#include.
+ */
+typedef enum VBCPPINCLUDEACTION
+{
+ kVBCppIncludeAction_Invalid = 0,
+ kVBCppIncludeAction_Include,
+ kVBCppIncludeAction_PassThru,
+ kVBCppIncludeAction_Drop,
+ kVBCppIncludeAction_End
+} VBCPPINCLUDEACTION;
+
+
+/**
+ * C Preprocessor instance data.
+ */
+typedef struct VBCPP
+{
+ /** @name Options
+ * @{ */
+ /** The preprocessing mode. */
+ VBCPPMODE enmMode;
+ /** Whether to keep comments. */
+ bool fKeepComments;
+ /** Whether to respect source defines. */
+ bool fRespectSourceDefines;
+ /** Whether to let source defines overrides the ones on the command
+ * line. */
+ bool fAllowRedefiningCmdLineDefines;
+ /** Whether to pass thru defines. */
+ bool fPassThruDefines;
+ /** Whether to allow undecided conditionals. */
+ bool fUndecidedConditionals;
+ /** Whether to pass thru D pragmas. */
+ bool fPassThruPragmaD;
+ /** Whether to pass thru STD pragmas. */
+ bool fPassThruPragmaSTD;
+ /** Whether to pass thru other pragmas. */
+ bool fPassThruPragmaOther;
+ /** Whether to remove dropped lines from the output. */
+ bool fRemoveDroppedLines;
+ /** Whether to preforme line splicing.
+ * @todo implement line splicing */
+ bool fLineSplicing;
+ /** What to do about include files. */
+ VBCPPINCLUDEACTION enmIncludeAction;
+
+ /** The number of include directories. */
+ uint32_t cIncludes;
+ /** Array of directories to search for include files. */
+ char **papszIncludes;
+
+ /** The name of the input file. */
+ const char *pszInput;
+ /** The name of the output file. NULL if stdout. */
+ const char *pszOutput;
+ /** @} */
+
+ /** The define string space. */
+ RTSTRSPACE StrSpace;
+ /** The string space holding explicitly undefined macros for selective
+ * preprocessing runs. */
+ RTSTRSPACE UndefStrSpace;
+ /** Indicates whether a C-word might need expansion.
+ * The bitmap is indexed by C-word lead character. Bits that are set
+ * indicates that the lead character is used in a \#define that we know and
+ * should expand. */
+ VBCPP_BITMAP_TYPE bmDefined[VBCPP_BITMAP_SIZE];
+
+ /** The current depth of the conditional stack. */
+ uint32_t cCondStackDepth;
+ /** Conditional stack. */
+ PVBCPPCOND pCondStack;
+ /** The current condition evaluates to kVBCppEval_False, don't output. */
+ bool fIf0Mode;
+ /** Just dropped a line and should maybe drop the current line. */
+ bool fJustDroppedLine;
+
+ /** Whether the current line could be a preprocessor line.
+ * This is set when EOL is encountered and cleared again when a
+ * non-comment-or-space character is encountered. See vbcppPreprocess. */
+ bool fMaybePreprocessorLine;
+
+ /** The input stack depth */
+ uint32_t cInputStackDepth;
+ /** The input buffer stack. */
+ PVBCPPINPUT pInputStack;
+
+ /** The output stream. */
+ SCMSTREAM StrmOutput;
+
+ /** The status of the whole job, as far as we know. */
+ RTEXITCODE rcExit;
+ /** Whether StrmOutput is valid (for vbcppTerm). */
+ bool fStrmOutputValid;
+} VBCPP;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static PVBCPPMACRO vbcppMacroLookup(PVBCPP pThis, const char *pszDefine, size_t cchDefine);
+static RTEXITCODE vbcppMacroExpandIt(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t offMacro, PVBCPPMACRO pMacro, size_t offParameters);
+static RTEXITCODE vbcppMacroExpandReScan(PVBCPP pThis, PVBCPPMACROEXP pExp, VBCPPMACRORESCANMODE enmMode, size_t *pcReplacements);
+static void vbcppMacroExpandCleanup(PVBCPPMACROEXP pExp);
+
+
+
+/*
+ *
+ *
+ * Message Handling.
+ * Message Handling.
+ * Message Handling.
+ * Message Handling.
+ * Message Handling.
+ *
+ *
+ */
+
+
+/**
+ * Displays an error message.
+ *
+ * @returns RTEXITCODE_FAILURE
+ * @param pThis The C preprocessor instance.
+ * @param pszMsg The message.
+ * @param va Message arguments.
+ */
+static RTEXITCODE vbcppErrorV(PVBCPP pThis, const char *pszMsg, va_list va)
+{
+ NOREF(pThis);
+ if (pThis->pInputStack)
+ {
+ PSCMSTREAM pStrm = &pThis->pInputStack->StrmInput;
+
+ size_t const off = ScmStreamTell(pStrm);
+ size_t const iLine = ScmStreamTellLine(pStrm);
+ ScmStreamSeekByLine(pStrm, iLine);
+ size_t const offLine = ScmStreamTell(pStrm);
+
+ RTPrintf("%s:%d:%zd: error: %N.\n", pThis->pInputStack->szName, iLine + 1, off - offLine + 1, pszMsg, va);
+
+ size_t cchLine;
+ SCMEOL enmEof;
+ const char *pszLine = ScmStreamGetLineByNo(pStrm, iLine, &cchLine, &enmEof);
+ if (pszLine)
+ RTPrintf(" %.*s\n"
+ " %*s^\n",
+ cchLine, pszLine, off - offLine, "");
+
+ ScmStreamSeekAbsolute(pStrm, off);
+ }
+ else
+ RTMsgErrorV(pszMsg, va);
+ return pThis->rcExit = RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Displays an error message.
+ *
+ * @returns RTEXITCODE_FAILURE
+ * @param pThis The C preprocessor instance.
+ * @param pszMsg The message.
+ * @param ... Message arguments.
+ */
+static RTEXITCODE vbcppError(PVBCPP pThis, const char *pszMsg, ...)
+{
+ va_list va;
+ va_start(va, pszMsg);
+ RTEXITCODE rcExit = vbcppErrorV(pThis, pszMsg, va);
+ va_end(va);
+ return rcExit;
+}
+
+
+/**
+ * Displays an error message.
+ *
+ * @returns RTEXITCODE_FAILURE
+ * @param pThis The C preprocessor instance.
+ * @param pszPos Pointer to the offending character.
+ * @param pszMsg The message.
+ * @param ... Message arguments.
+ */
+static RTEXITCODE vbcppErrorPos(PVBCPP pThis, const char *pszPos, const char *pszMsg, ...)
+{
+ NOREF(pszPos); NOREF(pThis);
+ va_list va;
+ va_start(va, pszMsg);
+ RTMsgErrorV(pszMsg, va);
+ va_end(va);
+ return pThis->rcExit = RTEXITCODE_FAILURE;
+}
+
+
+
+
+
+
+
+/*
+ *
+ *
+ * Variable String Buffers.
+ * Variable String Buffers.
+ * Variable String Buffers.
+ * Variable String Buffers.
+ * Variable String Buffers.
+ *
+ *
+ */
+
+
+/**
+ * Initializes a string buffer.
+ *
+ * @param pStrBuf The buffer structure to initialize.
+ * @param pThis The C preprocessor instance.
+ */
+static void vbcppStrBufInit(PVBCPPSTRBUF pStrBuf, PVBCPP pThis)
+{
+ pStrBuf->pThis = pThis;
+ pStrBuf->cchBuf = 0;
+ pStrBuf->cbBufAllocated = 0;
+ pStrBuf->pszBuf = NULL;
+}
+
+
+/**
+ * Deletes a string buffer.
+ *
+ * @param pStrBuf Pointer to the string buffer.
+ */
+static void vbcppStrBufDelete(PVBCPPSTRBUF pStrBuf)
+{
+ RTMemFree(pStrBuf->pszBuf);
+ pStrBuf->pszBuf = NULL;
+}
+
+
+/**
+ * Ensures that sufficient bufferspace is available, growing the buffer if
+ * necessary.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pStrBuf Pointer to the string buffer.
+ * @param cbMin The minimum buffer size.
+ */
+static RTEXITCODE vbcppStrBufGrow(PVBCPPSTRBUF pStrBuf, size_t cbMin)
+{
+ if (pStrBuf->cbBufAllocated >= cbMin)
+ return RTEXITCODE_SUCCESS;
+
+ size_t cbNew = pStrBuf->cbBufAllocated * 2;
+ if (cbNew < cbMin)
+ cbNew = RT_ALIGN_Z(cbMin, _1K);
+ void *pv = RTMemRealloc(pStrBuf->pszBuf, cbNew);
+ if (!pv)
+ return vbcppError(pStrBuf->pThis, "out of memory (%zu bytes)", cbNew);
+
+ pStrBuf->pszBuf = (char *)pv;
+ pStrBuf->cbBufAllocated = cbNew;
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Appends a substring.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pStrBuf Pointer to the string buffer.
+ * @param pchSrc Pointer to the first character in the substring.
+ * @param cchSrc The length of the substring.
+ */
+static RTEXITCODE vbcppStrBufAppendN(PVBCPPSTRBUF pStrBuf, const char *pchSrc, size_t cchSrc)
+{
+ size_t cchBuf = pStrBuf->cchBuf;
+ if (cchBuf + cchSrc + 1 > pStrBuf->cbBufAllocated)
+ {
+ RTEXITCODE rcExit = vbcppStrBufGrow(pStrBuf, cchBuf + cchSrc + 1);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ }
+
+ memcpy(&pStrBuf->pszBuf[cchBuf], pchSrc, cchSrc);
+ cchBuf += cchSrc;
+ pStrBuf->pszBuf[cchBuf] = '\0';
+ pStrBuf->cchBuf = cchBuf;
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Appends a character.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pStrBuf Pointer to the string buffer.
+ * @param ch The charater to append.
+ */
+static RTEXITCODE vbcppStrBufAppendCh(PVBCPPSTRBUF pStrBuf, char ch)
+{
+ size_t cchBuf = pStrBuf->cchBuf;
+ if (cchBuf + 2 > pStrBuf->cbBufAllocated)
+ {
+ RTEXITCODE rcExit = vbcppStrBufGrow(pStrBuf, cchBuf + 2);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ }
+
+ pStrBuf->pszBuf[cchBuf++] = ch;
+ pStrBuf->pszBuf[cchBuf] = '\0';
+ pStrBuf->cchBuf = cchBuf;
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Appends a string to the buffer.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pStrBuf Pointer to the string buffer.
+ * @param psz The string to append.
+ */
+static RTEXITCODE vbcppStrBufAppend(PVBCPPSTRBUF pStrBuf, const char *psz)
+{
+ return vbcppStrBufAppendN(pStrBuf, psz, strlen(psz));
+}
+
+
+/**
+ * Gets the last char in the buffer.
+ *
+ * @returns Last character, 0 if empty.
+ * @param pStrBuf Pointer to the string buffer.
+ */
+static char vbcppStrBufLastCh(PVBCPPSTRBUF pStrBuf)
+{
+ if (!pStrBuf->cchBuf)
+ return '\0';
+ return pStrBuf->pszBuf[pStrBuf->cchBuf - 1];
+}
+
+
+
+
+
+
+
+/*
+ *
+ *
+ * C Identifier/Word Parsing.
+ * C Identifier/Word Parsing.
+ * C Identifier/Word Parsing.
+ * C Identifier/Word Parsing.
+ * C Identifier/Word Parsing.
+ *
+ *
+ */
+
+
+/**
+ * Checks if the given character is a valid C identifier lead character.
+ *
+ * @returns true / false.
+ * @param ch The character to inspect.
+ */
+DECLINLINE(bool) vbcppIsCIdentifierLeadChar(char ch)
+{
+ return RT_C_IS_ALPHA(ch)
+ || ch == '_';
+}
+
+
+/**
+ * Checks if the given character is a valid C identifier character.
+ *
+ * @returns true / false.
+ * @param ch The character to inspect.
+ */
+DECLINLINE(bool) vbcppIsCIdentifierChar(char ch)
+{
+ return RT_C_IS_ALNUM(ch)
+ || ch == '_';
+}
+
+
+
+/**
+ *
+ * @returns @c true if valid, @c false if not. Error message already displayed
+ * on failure.
+ * @param pThis The C preprocessor instance.
+ * @param pchIdentifier The start of the identifier to validate.
+ * @param cchIdentifier The length of the identifier. RTSTR_MAX if not
+ * known.
+ */
+static bool vbcppValidateCIdentifier(PVBCPP pThis, const char *pchIdentifier, size_t cchIdentifier)
+{
+ if (cchIdentifier == RTSTR_MAX)
+ cchIdentifier = strlen(pchIdentifier);
+
+ if (cchIdentifier == 0)
+ {
+ vbcppErrorPos(pThis, pchIdentifier, "Zero length identifier");
+ return false;
+ }
+
+ if (!vbcppIsCIdentifierLeadChar(*pchIdentifier))
+ {
+ vbcppErrorPos(pThis, pchIdentifier, "Bad lead chararacter in identifier: '%.*s'", cchIdentifier, pchIdentifier);
+ return false;
+ }
+
+ for (size_t off = 1; off < cchIdentifier; off++)
+ {
+ if (!vbcppIsCIdentifierChar(pchIdentifier[off]))
+ {
+ vbcppErrorPos(pThis, pchIdentifier + off, "Illegal chararacter in identifier: '%.*s' (#%zu)", cchIdentifier, pchIdentifier, off + 1);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+#if 0
+
+/**
+ * Checks if the given character is valid C punctuation.
+ *
+ * @returns true / false.
+ * @param ch The character to inspect.
+ */
+DECLINLINE(bool) vbcppIsCPunctuationLeadChar(char ch)
+{
+ switch (ch)
+ {
+ case '!':
+ case '#':
+ case '%':
+ case '&':
+ case '(':
+ case ')':
+ case '*':
+ case '+':
+ case ',':
+ case '-':
+ case '.':
+ case '/':
+ case ':':
+ case ';':
+ case '<':
+ case '=':
+ case '>':
+ case '?':
+ case '[':
+ case ']':
+ case '^':
+ case '{':
+ case '|':
+ case '}':
+ case '~':
+ return true;
+ default:
+ return false;
+ }
+}
+
+
+/**
+ * Checks if the given string start with valid C punctuation.
+ *
+ * @returns 0 if not, otherwise the length of the punctuation.
+ * @param pch The which start we should evaluate.
+ * @param cchMax The maximum string length.
+ */
+static size_t vbcppIsCPunctuationLeadChar(const char *psz, size_t cchMax)
+{
+ if (!cchMax)
+ return 0;
+
+ switch (psz[0])
+ {
+ case '!':
+ case '*':
+ case '/':
+ case '=':
+ case '^':
+ if (cchMax >= 2 && psz[1] == '=')
+ return 2;
+ return 1;
+
+ case '#':
+ if (cchMax >= 2 && psz[1] == '#')
+ return 2;
+ return 1;
+
+ case '%':
+ if (cchMax >= 2 && (psz[1] == '=' || psz[1] == '>'))
+ return 2;
+ if (cchMax >= 2 && psz[1] == ':')
+ {
+ if (cchMax >= 4 && psz[2] == '%' && psz[3] == ':')
+ return 4;
+ return 2;
+ }
+ return 1;
+
+ case '&':
+ if (cchMax >= 2 && (psz[1] == '=' || psz[1] == '&'))
+ return 2;
+ return 1;
+
+ case '(':
+ case ')':
+ case ',':
+ case '?':
+ case '[':
+ case ']':
+ case '{':
+ case '}':
+ return 1;
+
+ case '+':
+ if (cchMax >= 2 && (psz[1] == '=' || psz[1] == '+'))
+ return 2;
+ return 1;
+
+ case '-':
+ if (cchMax >= 2 && (psz[1] == '=' || psz[1] == '-' || psz[1] == '>'))
+ return 2;
+ return 1;
+
+ case ':':
+ if (cchMax >= 2 && psz[1] == '>')
+ return 2;
+ return 1;
+
+ case ';':
+ return 1;
+
+ case '<':
+ if (cchMax >= 2 && psz[1] == '<')
+ {
+ if (cchMax >= 3 && psz[2] == '=')
+ return 3;
+ return 2;
+ }
+ if (cchMax >= 2 && (psz[1] == '=' || psz[1] == ':' || psz[1] == '%'))
+ return 2;
+ return 1;
+
+ case '.':
+ if (cchMax >= 3 && psz[1] == '.' && psz[2] == '.')
+ return 3;
+ return 1;
+
+ case '>':
+ if (cchMax >= 2 && psz[1] == '>')
+ {
+ if (cchMax >= 3 && psz[2] == '=')
+ return 3;
+ return 2;
+ }
+ if (cchMax >= 2 && psz[1] == '=')
+ return 2;
+ return 1;
+
+ case '|':
+ if (cchMax >= 2 && (psz[1] == '=' || psz[1] == '|'))
+ return 2;
+ return 1;
+
+ case '~':
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+#endif
+
+
+
+
+
+/*
+ *
+ *
+ * Output
+ * Output
+ * Output
+ * Output
+ * Output
+ *
+ *
+ */
+
+
+/**
+ * Outputs a character.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param ch The character to output.
+ */
+static RTEXITCODE vbcppOutputCh(PVBCPP pThis, char ch)
+{
+ int rc = ScmStreamPutCh(&pThis->StrmOutput, ch);
+ if (RT_SUCCESS(rc))
+ return RTEXITCODE_SUCCESS;
+ return vbcppError(pThis, "Output error: %Rrc", rc);
+}
+
+
+/**
+ * Outputs a string.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pch The string.
+ * @param cch The number of characters to write.
+ */
+static RTEXITCODE vbcppOutputWrite(PVBCPP pThis, const char *pch, size_t cch)
+{
+ int rc = ScmStreamWrite(&pThis->StrmOutput, pch, cch);
+ if (RT_SUCCESS(rc))
+ return RTEXITCODE_SUCCESS;
+ return vbcppError(pThis, "Output error: %Rrc", rc);
+}
+
+
+static RTEXITCODE vbcppOutputComment(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart, size_t cchOutputted,
+ size_t cchMinIndent)
+{
+ RT_NOREF_PV(cchMinIndent); /** @todo cchMinIndent */
+
+ size_t offCur = ScmStreamTell(pStrmInput);
+ if (offStart < offCur)
+ {
+ int rc = ScmStreamSeekAbsolute(pStrmInput, offStart);
+ AssertRCReturn(rc, vbcppError(pThis, "Input seek error: %Rrc", rc));
+
+ /*
+ * Use the same indent, if possible.
+ */
+ size_t cchIndent = offStart - ScmStreamTellOffsetOfLine(pStrmInput, ScmStreamTellLine(pStrmInput));
+ if (cchOutputted < cchIndent)
+ rc = ScmStreamPrintf(&pThis->StrmOutput, "%*s", cchIndent - cchOutputted, "");
+ else
+ rc = ScmStreamPutCh(&pThis->StrmOutput, ' ');
+ if (RT_FAILURE(rc))
+ return vbcppError(pThis, "Output error: %Rrc", rc);
+
+ /*
+ * Copy the bytes.
+ */
+ while (ScmStreamTell(pStrmInput) < offCur)
+ {
+ unsigned ch = ScmStreamGetCh(pStrmInput);
+ if (ch == ~(unsigned)0)
+ return vbcppError(pThis, "Input error: %Rrc", rc);
+ rc = ScmStreamPutCh(&pThis->StrmOutput, ch);
+ if (RT_FAILURE(rc))
+ return vbcppError(pThis, "Output error: %Rrc", rc);
+ }
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+
+
+
+/*
+ *
+ *
+ * Input
+ * Input
+ * Input
+ * Input
+ * Input
+ *
+ *
+ */
+
+
+#if 0 /* unused */
+/**
+ * Skips white spaces, including escaped new-lines.
+ *
+ * @param pStrmInput The input stream.
+ */
+static void vbcppProcessSkipWhiteAndEscapedEol(PSCMSTREAM pStrmInput)
+{
+ unsigned chPrev = ~(unsigned)0;
+ unsigned ch;
+ while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0)
+ {
+ if (ch == '\r' || ch == '\n')
+ {
+ if (chPrev != '\\')
+ break;
+ chPrev = ch;
+ ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1);
+ }
+ else if (RT_C_IS_SPACE(ch))
+ {
+ chPrev = ch;
+ ch = ScmStreamGetCh(pStrmInput);
+ Assert(ch == chPrev);
+ }
+ else
+ break;
+ }
+}
+#endif
+
+
+/**
+ * Skips white spaces, escaped new-lines and multi line comments.
+ *
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ */
+static RTEXITCODE vbcppProcessSkipWhiteEscapedEolAndComments(PVBCPP pThis, PSCMSTREAM pStrmInput)
+{
+ unsigned chPrev = ~(unsigned)0;
+ unsigned ch;
+ while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0)
+ {
+ if (!RT_C_IS_SPACE(ch))
+ {
+ /* Multi-line Comment? */
+ if (ch != '/')
+ break; /* most definitely, not. */
+
+ size_t offSaved = ScmStreamTell(pStrmInput);
+ ScmStreamGetCh(pStrmInput);
+ if (ScmStreamPeekCh(pStrmInput) != '*')
+ {
+ ScmStreamSeekAbsolute(pStrmInput, offSaved);
+ break; /* no */
+ }
+
+ /* Skip to the end of the comment. */
+ while ((ch = ScmStreamGetCh(pStrmInput)) != ~(unsigned)0)
+ {
+ if (ch == '*')
+ {
+ ch = ScmStreamGetCh(pStrmInput);
+ if (ch == '/')
+ break;
+ if (ch == ~(unsigned)0)
+ break;
+ }
+ }
+ if (ch == ~(unsigned)0)
+ return vbcppError(pThis, "unterminated multi-line comment");
+ chPrev = '/';
+ }
+ /* New line (also matched by RT_C_IS_SPACE). */
+ else if (ch == '\r' || ch == '\n')
+ {
+ /* Stop if not escaped. */
+ if (chPrev != '\\')
+ break;
+ chPrev = ch;
+ ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1);
+ }
+ /* Real space char. */
+ else
+ {
+ chPrev = ch;
+ ch = ScmStreamGetCh(pStrmInput);
+ Assert(ch == chPrev);
+ }
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Skips white spaces, escaped new-lines, and multi line comments, then checking
+ * that we're at the end of a line.
+ *
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ */
+static RTEXITCODE vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(PVBCPP pThis, PSCMSTREAM pStrmInput)
+{
+ RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ unsigned ch = ScmStreamPeekCh(pStrmInput);
+ if ( ch != ~(unsigned)0
+ && ch != '\r'
+ && ch != '\n')
+ rcExit = vbcppError(pThis, "Did not expected anything more on this line");
+ }
+ return rcExit;
+}
+
+
+/**
+ * Skips white spaces.
+ *
+ * @returns The current location upon return.
+ * @param pStrmInput The input stream.
+ */
+static size_t vbcppProcessSkipWhite(PSCMSTREAM pStrmInput)
+{
+ unsigned ch;
+ while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0)
+ {
+ if (!RT_C_IS_SPACE(ch) || ch == '\r' || ch == '\n')
+ break;
+ unsigned chCheck = ScmStreamGetCh(pStrmInput);
+ AssertBreak(chCheck == ch);
+ }
+ return ScmStreamTell(pStrmInput);
+}
+
+
+/**
+ * Looks for a left parenthesis in the input stream.
+ *
+ * Used during macro expansion. Will ignore comments, newlines and other
+ * whitespace.
+ *
+ * @retval true if found. The stream position at opening parenthesis.
+ * @retval false if not found. The stream position is unchanged.
+ *
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ */
+static bool vbcppInputLookForLeftParenthesis(PVBCPP pThis, PSCMSTREAM pStrmInput)
+{
+ size_t offSaved = ScmStreamTell(pStrmInput);
+ /*RTEXITCODE rcExit =*/ vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput);
+ unsigned ch = ScmStreamPeekCh(pStrmInput);
+ if (ch == '(')
+ return true;
+
+ int rc = ScmStreamSeekAbsolute(pStrmInput, offSaved);
+ AssertFatalRC(rc);
+ return false;
+}
+
+
+/**
+ * Skips input until the real end of the current directive line has been
+ * reached.
+ *
+ * This includes multiline comments starting on the same line
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ * @param poffComment Where to note down the position of the final
+ * comment. Optional.
+ */
+static RTEXITCODE vbcppInputSkipToEndOfDirectiveLine(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t *poffComment)
+{
+ if (poffComment)
+ *poffComment = ~(size_t)0;
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ bool fInComment = false;
+ unsigned chPrev = 0;
+ unsigned ch;
+ while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0)
+ {
+ if (ch == '\r' || ch == '\n')
+ {
+ if (chPrev == '\\')
+ {
+ ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1);
+ continue;
+ }
+ if (!fInComment)
+ break;
+ /* The expression continues after multi-line comments. Cool. :-) */
+ }
+ else if (!fInComment)
+ {
+ if (chPrev == '/' && ch == '*' )
+ {
+ fInComment = true;
+ if (poffComment)
+ *poffComment = ScmStreamTell(pStrmInput) - 1;
+ }
+ else if (chPrev == '/' && ch == '/')
+ {
+ if (poffComment)
+ *poffComment = ScmStreamTell(pStrmInput) - 1;
+ rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput);
+ break; /* done */
+ }
+ }
+ else if (ch == '/' && chPrev == '*')
+ fInComment = false;
+
+ /* advance */
+ chPrev = ch;
+ ch = ScmStreamGetCh(pStrmInput); Assert(ch == chPrev);
+ }
+ return rcExit;
+}
+
+
+/**
+ * Processes a multi-line comment.
+ *
+ * Must either string the comment or keep it. If the latter, we must refrain
+ * from replacing C-words in it.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ */
+static RTEXITCODE vbcppProcessMultiLineComment(PVBCPP pThis, PSCMSTREAM pStrmInput)
+{
+ /* The open comment sequence. */
+ ScmStreamGetCh(pStrmInput); /* '*' */
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ if ( pThis->fKeepComments
+ && !pThis->fIf0Mode)
+ rcExit = vbcppOutputWrite(pThis, "/*", 2);
+
+ /* The comment.*/
+ unsigned ch;
+ while ( rcExit == RTEXITCODE_SUCCESS
+ && (ch = ScmStreamGetCh(pStrmInput)) != ~(unsigned)0 )
+ {
+ if (ch == '*')
+ {
+ /* Closing sequence? */
+ unsigned ch2 = ScmStreamPeekCh(pStrmInput);
+ if (ch2 == '/')
+ {
+ ScmStreamGetCh(pStrmInput);
+ if ( pThis->fKeepComments
+ && !pThis->fIf0Mode)
+ rcExit = vbcppOutputWrite(pThis, "*/", 2);
+ break;
+ }
+ }
+
+ if (ch == '\r' || ch == '\n')
+ {
+ if ( ( pThis->fKeepComments
+ && !pThis->fIf0Mode)
+ || !pThis->fRemoveDroppedLines
+ || !ScmStreamIsAtStartOfLine(&pThis->StrmOutput))
+ rcExit = vbcppOutputCh(pThis, ch);
+ pThis->fJustDroppedLine = false;
+ pThis->fMaybePreprocessorLine = true;
+ }
+ else if ( pThis->fKeepComments
+ && !pThis->fIf0Mode)
+ rcExit = vbcppOutputCh(pThis, ch);
+
+ if (rcExit != RTEXITCODE_SUCCESS)
+ break;
+ }
+ return rcExit;
+}
+
+
+/**
+ * Processes a single line comment.
+ *
+ * Must either string the comment or keep it. If the latter, we must refrain
+ * from replacing C-words in it.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ */
+static RTEXITCODE vbcppProcessOneLineComment(PVBCPP pThis, PSCMSTREAM pStrmInput)
+{
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pszLine = ScmStreamGetLine(pStrmInput, &cchLine, &enmEol); Assert(pszLine);
+ pszLine--; cchLine++; /* unfetching the first slash. */
+ for (;;)
+ {
+ if ( pThis->fKeepComments
+ && !pThis->fIf0Mode)
+ rcExit = vbcppOutputWrite(pThis, pszLine, cchLine + enmEol);
+ else if ( !pThis->fIf0Mode
+ || !pThis->fRemoveDroppedLines
+ || !ScmStreamIsAtStartOfLine(&pThis->StrmOutput) )
+ rcExit = vbcppOutputWrite(pThis, pszLine + cchLine, enmEol);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ break;
+ if ( cchLine == 0
+ || pszLine[cchLine - 1] != '\\')
+ break;
+
+ pszLine = ScmStreamGetLine(pStrmInput, &cchLine, &enmEol);
+ if (!pszLine)
+ break;
+ }
+ pThis->fJustDroppedLine = false;
+ pThis->fMaybePreprocessorLine = true;
+ return rcExit;
+}
+
+
+/**
+ * Processes a double quoted string.
+ *
+ * Must not replace any C-words in strings.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ */
+static RTEXITCODE vbcppProcessStringLitteral(PVBCPP pThis, PSCMSTREAM pStrmInput)
+{
+ RTEXITCODE rcExit = vbcppOutputCh(pThis, '"');
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ bool fEscaped = false;
+ for (;;)
+ {
+ unsigned ch = ScmStreamGetCh(pStrmInput);
+ if (ch == ~(unsigned)0)
+ {
+ rcExit = vbcppError(pThis, "Unterminated double quoted string");
+ break;
+ }
+
+ rcExit = vbcppOutputCh(pThis, ch);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ break;
+
+ if (ch == '"' && !fEscaped)
+ break;
+ fEscaped = !fEscaped && ch == '\\';
+ }
+ }
+ return rcExit;
+}
+
+
+/**
+ * Processes a single quoted constant.
+ *
+ * Must not replace any C-words in character constants.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ */
+static RTEXITCODE vbcppProcessCharacterConstant(PVBCPP pThis, PSCMSTREAM pStrmInput)
+{
+ RTEXITCODE rcExit = vbcppOutputCh(pThis, '\'');
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ bool fEscaped = false;
+ for (;;)
+ {
+ unsigned ch = ScmStreamGetCh(pStrmInput);
+ if (ch == ~(unsigned)0)
+ {
+ rcExit = vbcppError(pThis, "Unterminated singled quoted string");
+ break;
+ }
+
+ rcExit = vbcppOutputCh(pThis, ch);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ break;
+
+ if (ch == '\'' && !fEscaped)
+ break;
+ fEscaped = !fEscaped && ch == '\\';
+ }
+ }
+ return rcExit;
+}
+
+
+/**
+ * Processes a integer or floating point number constant.
+ *
+ * Must not replace the type suffix.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ * @param chFirst The first character.
+ */
+static RTEXITCODE vbcppProcessNumber(PVBCPP pThis, PSCMSTREAM pStrmInput, char chFirst)
+{
+ RTEXITCODE rcExit = vbcppOutputCh(pThis, chFirst);
+
+ unsigned ch;
+ while ( rcExit == RTEXITCODE_SUCCESS
+ && (ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0)
+ {
+ if ( !vbcppIsCIdentifierChar(ch)
+ && ch != '.')
+ break;
+
+ unsigned ch2 = ScmStreamGetCh(pStrmInput);
+ AssertBreakStmt(ch2 == ch, rcExit = vbcppError(pThis, "internal error"));
+ rcExit = vbcppOutputCh(pThis, ch);
+ }
+
+ return rcExit;
+}
+
+
+/**
+ * Processes a identifier, possibly replacing it with a definition.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ */
+static RTEXITCODE vbcppProcessIdentifier(PVBCPP pThis, PSCMSTREAM pStrmInput)
+{
+ RTEXITCODE rcExit;
+ size_t cchDefine;
+ const char *pchDefine = ScmStreamCGetWordM1(pStrmInput, &cchDefine);
+ AssertReturn(pchDefine, vbcppError(pThis, "Internal error in ScmStreamCGetWordM1"));
+
+ /*
+ * Does this look like a define we know?
+ */
+ PVBCPPMACRO pMacro = vbcppMacroLookup(pThis, pchDefine, cchDefine);
+ if ( pMacro
+ && ( !pMacro->fFunction
+ || vbcppInputLookForLeftParenthesis(pThis, pStrmInput)) )
+ {
+ /*
+ * Expand it.
+ */
+ VBCPPMACROEXP ExpCtx;
+#if 0
+ ExpCtx.pMacroStack = NULL;
+#endif
+ ExpCtx.pStrmInput = pStrmInput;
+ ExpCtx.papszArgs = NULL;
+ ExpCtx.cArgs = 0;
+ ExpCtx.cArgsAlloced = 0;
+ vbcppStrBufInit(&ExpCtx.StrBuf, pThis);
+ rcExit = vbcppStrBufAppendN(&ExpCtx.StrBuf, pchDefine, cchDefine);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = vbcppMacroExpandIt(pThis, &ExpCtx, 0 /* offset */, pMacro, cchDefine);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = vbcppMacroExpandReScan(pThis, &ExpCtx, kMacroReScanMode_Normal, NULL);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Insert it into the output stream. Make sure there is a
+ * whitespace following it.
+ */
+ int rc = ScmStreamWrite(&pThis->StrmOutput, ExpCtx.StrBuf.pszBuf, ExpCtx.StrBuf.cchBuf);
+ if (RT_SUCCESS(rc))
+ {
+ unsigned chAfter = ScmStreamPeekCh(pStrmInput);
+ if (chAfter != ~(unsigned)0 && !RT_C_IS_SPACE(chAfter))
+ rcExit = vbcppOutputCh(pThis, ' ');
+ }
+ else
+ rcExit = vbcppError(pThis, "Output error: %Rrc", rc);
+ }
+ vbcppMacroExpandCleanup(&ExpCtx);
+ }
+ else
+ {
+ /*
+ * Not a macro or a function-macro name match but no invocation, just
+ * output the text unchanged.
+ */
+ int rc = ScmStreamWrite(&pThis->StrmOutput, pchDefine, cchDefine);
+ if (RT_SUCCESS(rc))
+ rcExit = RTEXITCODE_SUCCESS;
+ else
+ rcExit = vbcppError(pThis, "Output error: %Rrc", rc);
+ }
+ return rcExit;
+}
+
+
+
+
+
+
+
+/*
+ *
+ *
+ * D E F I N E S / M A C R O S
+ * D E F I N E S / M A C R O S
+ * D E F I N E S / M A C R O S
+ * D E F I N E S / M A C R O S
+ * D E F I N E S / M A C R O S
+ *
+ *
+ */
+
+
+/**
+ * Checks if a define exists.
+ *
+ * @returns true or false.
+ * @param pThis The C preprocessor instance.
+ * @param pszDefine The define name and optionally the argument
+ * list.
+ * @param cchDefine The length of the name. RTSTR_MAX is ok.
+ */
+static bool vbcppMacroExists(PVBCPP pThis, const char *pszDefine, size_t cchDefine)
+{
+ return cchDefine > 0
+ && VBCPP_BITMAP_IS_SET(pThis->bmDefined, *pszDefine)
+ && RTStrSpaceGetN(&pThis->StrSpace, pszDefine, cchDefine) != NULL;
+}
+
+
+/**
+ * Looks up a define.
+ *
+ * @returns Pointer to the define if found, NULL if not.
+ * @param pThis The C preprocessor instance.
+ * @param pszDefine The define name and optionally the argument
+ * list.
+ * @param cchDefine The length of the name. RTSTR_MAX is ok.
+ */
+static PVBCPPMACRO vbcppMacroLookup(PVBCPP pThis, const char *pszDefine, size_t cchDefine)
+{
+ if (!cchDefine)
+ return NULL;
+ if (!VBCPP_BITMAP_IS_SET(pThis->bmDefined, *pszDefine))
+ return NULL;
+ return (PVBCPPMACRO)RTStrSpaceGetN(&pThis->StrSpace, pszDefine, cchDefine);
+}
+
+
+static uint32_t vbcppMacroLookupArg(PVBCPPMACRO pMacro, const char *pchName, size_t cchName)
+{
+ Assert(cchName > 0);
+
+ char const ch = *pchName;
+ for (uint32_t i = 0; i < pMacro->cArgs; i++)
+ if ( pMacro->papszArgs[i][0] == ch
+ && !strncmp(pMacro->papszArgs[i], pchName, cchName)
+ && pMacro->papszArgs[i][cchName] == '\0')
+ return i;
+
+ if ( pMacro->fVarArg
+ && cchName == sizeof("__VA_ARGS__") - 1
+ && !strncmp(pchName, "__VA_ARGS__", sizeof("__VA_ARGS__") - 1) )
+ return pMacro->cArgs;
+
+ return UINT32_MAX;
+}
+
+
+static RTEXITCODE vbcppMacroExpandReplace(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t off, size_t cchToReplace,
+ const char *pchReplacement, size_t cchReplacement)
+{
+ RT_NOREF_PV(pThis);
+
+ /*
+ * Figure how much space we actually need.
+ * (Hope this whitespace stuff is correct...)
+ */
+ bool const fLeadingSpace = off > 0
+ && !RT_C_IS_SPACE(pExp->StrBuf.pszBuf[off - 1]);
+ bool const fTrailingSpace = off + cchToReplace < pExp->StrBuf.cchBuf
+ && !RT_C_IS_SPACE(pExp->StrBuf.pszBuf[off + cchToReplace]);
+ size_t const cchActualReplacement = fLeadingSpace + cchReplacement + fTrailingSpace;
+
+ /*
+ * Adjust the buffer size and contents.
+ */
+ if (cchActualReplacement > cchToReplace)
+ {
+ size_t const offMore = cchActualReplacement - cchToReplace;
+
+ /* Ensure enough buffer space. */
+ size_t cbMinBuf = offMore + pExp->StrBuf.cchBuf + 1;
+ RTEXITCODE rcExit = vbcppStrBufGrow(&pExp->StrBuf, cbMinBuf);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+ /* Push the chars following the replacement area down to make room. */
+ memmove(&pExp->StrBuf.pszBuf[off + cchToReplace + offMore],
+ &pExp->StrBuf.pszBuf[off + cchToReplace],
+ pExp->StrBuf.cchBuf - off - cchToReplace + 1);
+ pExp->StrBuf.cchBuf += offMore;
+
+ }
+ else if (cchActualReplacement < cchToReplace)
+ {
+ size_t const offLess = cchToReplace - cchActualReplacement;
+
+ /* Pull the chars following the replacement area up. */
+ memmove(&pExp->StrBuf.pszBuf[off + cchToReplace - offLess],
+ &pExp->StrBuf.pszBuf[off + cchToReplace],
+ pExp->StrBuf.cchBuf - off - cchToReplace + 1);
+ pExp->StrBuf.cchBuf -= offLess;
+ }
+
+ /*
+ * Insert the replacement string.
+ */
+ char *pszCur = &pExp->StrBuf.pszBuf[off];
+ if (fLeadingSpace)
+ *pszCur++ = ' ';
+ memcpy(pszCur, pchReplacement, cchReplacement);
+ if (fTrailingSpace)
+ *pszCur++ = ' ';
+
+ Assert(strlen(pExp->StrBuf.pszBuf) == pExp->StrBuf.cchBuf);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static unsigned vbcppMacroExpandPeekCh(PVBCPPMACROEXP pExp, size_t *poff)
+{
+ size_t off = *poff;
+ if (off >= pExp->StrBuf.cchBuf)
+ return pExp->pStrmInput ? ScmStreamPeekCh(pExp->pStrmInput) : ~(unsigned)0;
+ return pExp->StrBuf.pszBuf[off];
+}
+
+
+static unsigned vbcppMacroExpandGetCh(PVBCPPMACROEXP pExp, size_t *poff)
+{
+ size_t off = *poff;
+ if (off >= pExp->StrBuf.cchBuf)
+ return pExp->pStrmInput ? ScmStreamGetCh(pExp->pStrmInput) : ~(unsigned)0;
+ *poff = off + 1;
+ return pExp->StrBuf.pszBuf[off];
+}
+
+
+static RTEXITCODE vbcppMacroExpandSkipEolEx(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff, unsigned chFirst)
+{
+ if (chFirst == '\r')
+ {
+ unsigned ch2 = vbcppMacroExpandPeekCh(pExp, poff);
+ if (ch2 == '\n')
+ {
+ ch2 = ScmStreamGetCh(pExp->pStrmInput);
+ AssertReturn(ch2 == '\n', vbcppError(pThis, "internal error"));
+ }
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE vbcppMacroExpandSkipEol(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff)
+{
+ unsigned ch = vbcppMacroExpandGetCh(pExp, poff);
+ AssertReturn(ch == '\r' || ch == '\n', vbcppError(pThis, "internal error"));
+ return vbcppMacroExpandSkipEolEx(pThis, pExp, poff, ch);
+}
+
+
+static RTEXITCODE vbcppMacroExpandSkipCommentLine(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff)
+{
+ unsigned ch = vbcppMacroExpandGetCh(pExp, poff);
+ AssertReturn(ch == '/', vbcppError(pThis, "Internal error - expected '/' got '%c'", ch));
+
+ unsigned chPrev = 0;
+ while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0)
+ {
+ if (ch == '\r' || ch == '\n')
+ {
+ RTEXITCODE rcExit = vbcppMacroExpandSkipEolEx(pThis, pExp, poff, ch);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ if (chPrev != '\\')
+ break;
+ }
+
+ chPrev = ch;
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE vbcppMacroExpandSkipComment(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff)
+{
+ unsigned ch = vbcppMacroExpandGetCh(pExp, poff);
+ AssertReturn(ch == '*', vbcppError(pThis, "Internal error - expected '*' got '%c'", ch));
+
+ unsigned chPrev2 = 0;
+ unsigned chPrev = 0;
+ while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0)
+ {
+ if (ch == '/' && chPrev == '*')
+ break;
+
+ if (ch == '\r' || ch == '\n')
+ {
+ RTEXITCODE rcExit = vbcppMacroExpandSkipEolEx(pThis, pExp, poff, ch);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ if (chPrev == '\\')
+ {
+ chPrev = chPrev2; /* for line splicing */
+ continue;
+ }
+ }
+
+ chPrev2 = chPrev;
+ chPrev = ch;
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE vbcppMacroExpandGrowArgArray(PVBCPP pThis, PVBCPPMACROEXP pExp, uint32_t cMinArgs)
+{
+ if (cMinArgs > pExp->cArgsAlloced)
+ {
+ void *pv = RTMemRealloc(pExp->papszArgs, cMinArgs * sizeof(char *));
+ if (!pv)
+ return vbcppError(pThis, "out of memory");
+ pExp->papszArgs = (char **)pv;
+ pExp->cArgsAlloced = cMinArgs;
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE vbcppMacroExpandAddEmptyParameter(PVBCPP pThis, PVBCPPMACROEXP pExp)
+{
+ RTEXITCODE rcExit = vbcppMacroExpandGrowArgArray(pThis, pExp, pExp->cArgs + 1);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ char *pszArg = (char *)RTMemAllocZ(1);
+ if (pszArg)
+ pExp->papszArgs[pExp->cArgs++] = pszArg;
+ else
+ rcExit = vbcppError(pThis, "out of memory");
+ }
+ return rcExit;
+}
+
+
+static RTEXITCODE vbcppMacroExpandGatherParameters(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff, uint32_t cArgsHint)
+{
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+
+ /*
+ * Free previous argument values.
+ */
+ while (pExp->cArgs > 0)
+ {
+ RTMemFree(pExp->papszArgs[--pExp->cArgs]);
+ pExp->papszArgs[pExp->cArgs] = NULL;
+ }
+
+ /*
+ * The current character should be an opening parenthsis.
+ */
+ unsigned ch = vbcppMacroExpandGetCh(pExp, poff);
+ if (ch != '(')
+ return vbcppError(pThis, "Internal error - expected '(', found '%c' (#x)", ch, ch);
+
+ /*
+ * Parse the argument list.
+ */
+ char chQuote = 0;
+ size_t cbArgAlloc = 0;
+ size_t cchArg = 0;
+ char *pszArg = NULL;
+ size_t cParentheses = 1;
+ unsigned chPrev = 0;
+ while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0)
+ {
+/** @todo check for '#directives'! */
+ if (ch == ')' && !chQuote)
+ {
+ Assert(cParentheses >= 1);
+ cParentheses--;
+
+ /* The end? */
+ if (!cParentheses)
+ {
+ if (cchArg)
+ while (cchArg > 0 && RT_C_IS_SPACE(pszArg[cchArg - 1]))
+ pszArg[--cchArg] = '\0';
+ else if (pExp->cArgs || cArgsHint > 0)
+ rcExit = vbcppMacroExpandAddEmptyParameter(pThis, pExp);
+ break;
+ }
+ }
+ else if (ch == '(' && !chQuote)
+ cParentheses++;
+ else if (ch == ',' && cParentheses == 1 && !chQuote)
+ {
+ /* End of one argument, start of the next. */
+ if (cchArg)
+ while (cchArg > 0 && RT_C_IS_SPACE(pszArg[cchArg - 1]))
+ pszArg[--cchArg] = '\0';
+ else
+ {
+ rcExit = vbcppMacroExpandAddEmptyParameter(pThis, pExp);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ break;
+ }
+
+ cbArgAlloc = 0;
+ cchArg = 0;
+ pszArg = NULL;
+ continue;
+ }
+ else if (ch == '/' && !chQuote)
+ {
+ /* Comment? */
+ unsigned ch2 = vbcppMacroExpandPeekCh(pExp, poff);
+ /** @todo This ain't right wrt line splicing. */
+ if (ch2 == '/' || ch == '*')
+ {
+ if (ch2 == '/')
+ rcExit = vbcppMacroExpandSkipCommentLine(pThis, pExp, poff);
+ else
+ rcExit = vbcppMacroExpandSkipComment(pThis, pExp, poff);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ break;
+ continue;
+ }
+ }
+ else if (ch == '"')
+ {
+ if (!chQuote)
+ chQuote = '"';
+ else if (chPrev != '\\')
+ chQuote = 0;
+ }
+ else if (ch == '\'')
+ {
+ if (!chQuote)
+ chQuote = '\'';
+ else if (chPrev != '\\')
+ chQuote = 0;
+ }
+ else if (ch == '\\')
+ {
+ /* Splice lines? */
+ unsigned ch2 = vbcppMacroExpandPeekCh(pExp, poff);
+ if (ch2 == '\r' || ch2 == '\n')
+ {
+ rcExit = vbcppMacroExpandSkipEol(pThis, pExp, poff);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ break;
+ continue;
+ }
+ }
+ else if (cchArg == 0 && RT_C_IS_SPACE(ch))
+ continue; /* ignore spaces leading up to an argument value */
+
+ /* Append the character to the argument value, adding the argument
+ to the output array if it's first character in it. */
+ if (cchArg + 1 >= cbArgAlloc)
+ {
+ /* Add argument to the vector. */
+ if (!cchArg)
+ {
+ rcExit = vbcppMacroExpandGrowArgArray(pThis, pExp, RT_MAX(pExp->cArgs + 1, cArgsHint));
+ if (rcExit != RTEXITCODE_SUCCESS)
+ break;
+ pExp->papszArgs[pExp->cArgs++] = pszArg;
+ }
+
+ /* Resize the argument value buffer. */
+ cbArgAlloc = cbArgAlloc ? cbArgAlloc * 2 : 16;
+ pszArg = (char *)RTMemRealloc(pszArg, cbArgAlloc);
+ if (!pszArg)
+ {
+ rcExit = vbcppError(pThis, "out of memory");
+ break;
+ }
+ pExp->papszArgs[pExp->cArgs - 1] = pszArg;
+ }
+
+ pszArg[cchArg++] = ch;
+ pszArg[cchArg] = '\0';
+ }
+
+ /*
+ * Check that we're leaving on good terms.
+ */
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ if (cParentheses)
+ rcExit = vbcppError(pThis, "Missing ')'");
+ }
+
+ return rcExit;
+}
+
+
+/**
+ * Expands the arguments referenced in the macro value.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg.
+ * @param pThis The C preprocessor instance.
+ * @param pExp The expansion context.
+ * @param pMacro The macro. Must be a function macro.
+ * @param pStrBuf String buffer containing the result. The caller
+ * should initialize and destroy this!
+ */
+static RTEXITCODE vbcppMacroExpandValueWithArguments(PVBCPP pThis, PVBCPPMACROEXP pExp, PVBCPPMACRO pMacro,
+ PVBCPPSTRBUF pStrBuf)
+{
+ Assert(pMacro->fFunction);
+
+ /*
+ * Empty?
+ */
+ if ( !pMacro->cchValue
+ || (pMacro->cchValue == 1 && pMacro->szValue[0] == '#'))
+ return RTEXITCODE_SUCCESS;
+
+ /*
+ * Parse the value.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ const char *pszSrc = pMacro->szValue;
+ const char *pszSrcSeq;
+ char ch;
+ while ((ch = *pszSrc++) != '\0')
+ {
+ Assert(ch != '\r'); Assert(ch != '\n'); /* probably not true atm. */
+ if (ch == '#')
+ {
+ if (*pszSrc == '#')
+ {
+ /* Concatenate operator. */
+ rcExit = vbcppError(pThis, "The '##' operatore is not yet implemented");
+ }
+ else
+ {
+ /* Stringify macro argument. */
+ rcExit = vbcppError(pThis, "The '#' operatore is not yet implemented");
+ }
+ return rcExit;
+ }
+ else if (ch == '"')
+ {
+ /* String litteral. */
+ pszSrcSeq = pszSrc - 1;
+ while ((ch = *pszSrc++) != '"')
+ {
+ if (ch == '\\')
+ ch = *pszSrc++;
+ if (ch == '\0')
+ {
+ rcExit = vbcppError(pThis, "String litteral is missing closing quote (\").");
+ break;
+ }
+ }
+ rcExit = vbcppStrBufAppendN(pStrBuf, pszSrcSeq, pszSrc - pszSrcSeq);
+ }
+ else if (ch == '\'')
+ {
+ /* Character constant. */
+ pszSrcSeq = pszSrc - 1;
+ while ((ch = *pszSrc++) != '\'')
+ {
+ if (ch == '\\')
+ ch = *pszSrc++;
+ if (ch == '\0')
+ {
+ rcExit = vbcppError(pThis, "Character constant is missing closing quote (').");
+ break;
+ }
+ }
+ rcExit = vbcppStrBufAppendN(pStrBuf, pszSrcSeq, pszSrc - pszSrcSeq);
+ }
+ else if (RT_C_IS_DIGIT(ch))
+ {
+ /* Process numerical constants correctly (i.e. don't mess with the suffix). */
+ pszSrcSeq = pszSrc - 1;
+ while ( (ch = *pszSrc) != '\0'
+ && ( vbcppIsCIdentifierChar(ch)
+ || ch == '.') )
+ pszSrc++;
+ rcExit = vbcppStrBufAppendN(pStrBuf, pszSrcSeq, pszSrc - pszSrcSeq);
+ }
+ else if (RT_C_IS_SPACE(ch))
+ {
+ /* join spaces */
+ if (RT_C_IS_SPACE(vbcppStrBufLastCh(pStrBuf)))
+ continue;
+ rcExit = vbcppStrBufAppendCh(pStrBuf, ch);
+ }
+ else if (vbcppIsCIdentifierLeadChar(ch))
+ {
+ /* Something we should replace? */
+ pszSrcSeq = pszSrc - 1;
+ while ( (ch = *pszSrc) != '\0'
+ && vbcppIsCIdentifierChar(ch))
+ pszSrc++;
+ size_t cchDefine = pszSrc - pszSrcSeq;
+ uint32_t iArg;
+ if ( VBCPP_BITMAP_IS_SET(pMacro->bmArgs, *pszSrcSeq)
+ && (iArg = vbcppMacroLookupArg(pMacro, pszSrcSeq, cchDefine)) != UINT32_MAX)
+ {
+ /** @todo check out spaces here! */
+ if (iArg < pMacro->cArgs)
+ {
+ Assert(iArg < pExp->cArgs);
+ rcExit = vbcppStrBufAppend(pStrBuf, pExp->papszArgs[iArg]);
+ if (*pExp->papszArgs[iArg] != '\0' && rcExit == RTEXITCODE_SUCCESS)
+ rcExit = vbcppStrBufAppendCh(pStrBuf, ' ');
+ }
+ else
+ {
+ /* __VA_ARGS__ */
+ if (iArg < pExp->cArgs)
+ {
+ for (;;)
+ {
+ rcExit = vbcppStrBufAppend(pStrBuf, pExp->papszArgs[iArg]);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ break;
+ iArg++;
+ if (iArg >= pExp->cArgs)
+ break;
+ rcExit = vbcppStrBufAppendCh(pStrBuf, ',');
+ if (rcExit != RTEXITCODE_SUCCESS)
+ break;
+ }
+ }
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = vbcppStrBufAppendCh(pStrBuf, ' ');
+ }
+ }
+ /* Not an argument needing replacing. */
+ else
+ rcExit = vbcppStrBufAppendN(pStrBuf, pszSrcSeq, cchDefine);
+ }
+ else
+ {
+ rcExit = vbcppStrBufAppendCh(pStrBuf, ch);
+ }
+ }
+
+ return rcExit;
+}
+
+
+
+/**
+ * Expands the given macro.
+ *
+ * Caller already checked if a function macro should be expanded, i.e. whether
+ * there is a parameter list.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg.
+ * @param pThis The C preprocessor instance.
+ * @param pExp The expansion context.
+ * @param offMacro Offset into the expansion buffer of the macro
+ * invocation.
+ * @param pMacro The macro.
+ * @param offParameters The start of the parameter list if applicable.
+ * Ignored if not function macro. If the
+ * parameter list starts at the current stream
+ * position shall be at the end of the expansion
+ * buffer.
+ */
+static RTEXITCODE vbcppMacroExpandIt(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t offMacro, PVBCPPMACRO pMacro,
+ size_t offParameters)
+{
+ RTEXITCODE rcExit;
+ Assert(offMacro + pMacro->Core.cchString <= pExp->StrBuf.cchBuf);
+ Assert(!pMacro->fExpanding);
+
+ /*
+ * Function macros are kind of difficult...
+ */
+ if (pMacro->fFunction)
+ {
+ rcExit = vbcppMacroExpandGatherParameters(pThis, pExp, &offParameters, pMacro->cArgs + pMacro->fVarArg);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ if (pExp->cArgs > pMacro->cArgs && !pMacro->fVarArg)
+ rcExit = vbcppError(pThis, "Too many arguments to macro '%s' - found %u, expected %u",
+ pMacro->Core.pszString, pExp->cArgs, pMacro->cArgs);
+ else if (pExp->cArgs < pMacro->cArgs)
+ rcExit = vbcppError(pThis, "Too few arguments to macro '%s' - found %u, expected %u",
+ pMacro->Core.pszString, pExp->cArgs, pMacro->cArgs);
+ }
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ VBCPPSTRBUF ValueBuf;
+ vbcppStrBufInit(&ValueBuf, pThis);
+ rcExit = vbcppMacroExpandValueWithArguments(pThis, pExp, pMacro, &ValueBuf);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = vbcppMacroExpandReplace(pThis, pExp, offMacro, offParameters - offMacro,
+ ValueBuf.pszBuf, ValueBuf.cchBuf);
+ vbcppStrBufDelete(&ValueBuf);
+ }
+ }
+ /*
+ * Object-like macros are easy. :-)
+ */
+ else
+ rcExit = vbcppMacroExpandReplace(pThis, pExp, offMacro, pMacro->Core.cchString, pMacro->szValue, pMacro->cchValue);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+#if 0 /* wrong */
+ /*
+ * Push the macro onto the stack.
+ */
+ pMacro->fExpanding = true;
+ pMacro->pUpExpanding = pExp->pMacroStack;
+ pExp->pMacroStack = pMacro;
+#endif
+ }
+
+ return rcExit;
+}
+
+
+/**
+ * Looks for a left parenthesis in the macro expansion buffer and the input
+ * stream.
+ *
+ * @retval true if found. The stream position at opening parenthesis.
+ * @retval false if not found. The stream position is unchanged.
+ *
+ * @param pThis The C preprocessor instance.
+ * @param pExp The expansion context.
+ * @param poff The current offset in the expansion context.
+ * Will be updated on success.
+ *
+ * @sa vbcppInputLookForLeftParenthesis
+ */
+static bool vbcppMacroExpandLookForLeftParenthesis(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t *poff)
+{
+ /*
+ * Search the buffer first. (No comments there.)
+ */
+ size_t off = *poff;
+ while (off < pExp->StrBuf.cchBuf)
+ {
+ char ch = pExp->StrBuf.pszBuf[off];
+ if (!RT_C_IS_SPACE(ch))
+ {
+ if (ch == '(')
+ {
+ *poff = off;
+ return true;
+ }
+ return false;
+ }
+ off++;
+ }
+
+ /*
+ * Reached the end of the buffer, continue searching in the stream.
+ */
+ PSCMSTREAM pStrmInput = pExp->pStrmInput;
+ size_t offSaved = ScmStreamTell(pStrmInput);
+ /*RTEXITCODE rcExit = */ vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput);
+ unsigned ch = ScmStreamPeekCh(pStrmInput);
+ if (ch == '(')
+ {
+ *poff = pExp->StrBuf.cchBuf;
+ return true;
+ }
+
+ int rc = ScmStreamSeekAbsolute(pStrmInput, offSaved);
+ AssertFatalRC(rc);
+ return false;
+}
+
+
+/**
+ * Implements the 'defined' unary operator for \#if and \#elif expressions.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg.
+ * @param pThis The C preprocessor instance.
+ * @param pExp The expansion context.
+ * @param offStart The expansion buffer offset where the 'defined'
+ * occurs.
+ * @param poff Where to store the offset at which the re-scan
+ * shall resume upon return.
+ */
+static RTEXITCODE vbcppMacroExpandDefinedOperator(PVBCPP pThis, PVBCPPMACROEXP pExp, size_t offStart, size_t *poff)
+{
+ Assert(!pExp->pStrmInput); /* offset usage below. */
+
+ /*
+ * Skip white space.
+ */
+ unsigned ch;
+ while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0)
+ if (!RT_C_IS_SPACE(ch))
+ break;
+ bool const fWithParenthesis = ch == '(';
+ if (fWithParenthesis)
+ while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0)
+ if (!RT_C_IS_SPACE(ch))
+ break;
+
+ /*
+ * Macro identifier.
+ */
+ if (!vbcppIsCIdentifierLeadChar(ch))
+ return vbcppError(pThis, "Expected macro name after 'defined' operator");
+
+ size_t const offDefine = *poff - 1;
+ while ((ch = vbcppMacroExpandGetCh(pExp, poff)) != ~(unsigned)0)
+ if (!vbcppIsCIdentifierChar(ch))
+ break;
+ size_t const cchDefine = *poff - offDefine - 1;
+
+ /*
+ * Check for closing parenthesis.
+ */
+ if (fWithParenthesis)
+ {
+ while (RT_C_IS_SPACE(ch))
+ ch = vbcppMacroExpandGetCh(pExp, poff);
+ if (ch != ')')
+ return vbcppError(pThis, "Expected closing parenthesis after macro name");
+ }
+
+ /*
+ * Do the job.
+ */
+ const char *pszResult = vbcppMacroExists(pThis, &pExp->StrBuf.pszBuf[offDefine], cchDefine)
+ ? "1" : "0";
+ RTEXITCODE rcExit = vbcppMacroExpandReplace(pThis, pExp, offStart, *poff - offStart, pszResult, 1);
+ *poff = offStart + 1;
+ return rcExit;
+}
+
+
+/**
+ * Re-scan the expanded macro.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg.
+ * @param pThis The C preprocessor instance.
+ * @param pExp The expansion context.
+ * @param enmMode The re-scan mode.
+ * @param pcReplacements Where to return the number of replacements
+ * performed. Optional.
+ */
+static RTEXITCODE vbcppMacroExpandReScan(PVBCPP pThis, PVBCPPMACROEXP pExp, VBCPPMACRORESCANMODE enmMode, size_t *pcReplacements)
+{
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ size_t cReplacements = 0;
+ size_t off = 0;
+ unsigned ch;
+ while ( off < pExp->StrBuf.cchBuf
+ && (ch = vbcppMacroExpandGetCh(pExp, &off)) != ~(unsigned)0)
+ {
+ /*
+ * String litteral or character constant.
+ */
+ if (ch == '\'' || ch == '"')
+ {
+ unsigned const chEndQuote = ch;
+ while ( off < pExp->StrBuf.cchBuf
+ && (ch = vbcppMacroExpandGetCh(pExp, &off)) != ~(unsigned)0)
+ {
+ if (ch == '\\')
+ {
+ ch = vbcppMacroExpandGetCh(pExp, &off);
+ if (ch == ~(unsigned)0)
+ break;
+ }
+ else if (ch == chEndQuote)
+ break;
+ }
+ if (ch == ~(unsigned)0)
+ return vbcppError(pThis, "Missing end quote (%c)", chEndQuote);
+ }
+ /*
+ * Number constant.
+ */
+ else if ( RT_C_IS_DIGIT(ch)
+ || ( ch == '.'
+ && off + 1 < pExp->StrBuf.cchBuf
+ && RT_C_IS_DIGIT(vbcppMacroExpandPeekCh(pExp, &off))
+ )
+ )
+ {
+ while ( off < pExp->StrBuf.cchBuf
+ && (ch = vbcppMacroExpandPeekCh(pExp, &off)) != ~(unsigned)0
+ && vbcppIsCIdentifierChar(ch) )
+ vbcppMacroExpandGetCh(pExp, &off);
+ }
+ /*
+ * Something that can be replaced?
+ */
+ else if (vbcppIsCIdentifierLeadChar(ch))
+ {
+ size_t offDefine = off - 1;
+ while ( off < pExp->StrBuf.cchBuf
+ && (ch = vbcppMacroExpandPeekCh(pExp, &off)) != ~(unsigned)0
+ && vbcppIsCIdentifierChar(ch) )
+ vbcppMacroExpandGetCh(pExp, &off);
+ size_t cchDefine = off - offDefine;
+
+ PVBCPPMACRO pMacro = vbcppMacroLookup(pThis, &pExp->StrBuf.pszBuf[offDefine], cchDefine);
+ if ( pMacro
+ && ( !pMacro->fFunction
+ || vbcppMacroExpandLookForLeftParenthesis(pThis, pExp, &off)) )
+ {
+ cReplacements++;
+ rcExit = vbcppMacroExpandIt(pThis, pExp, offDefine, pMacro, off);
+ off = offDefine;
+ }
+ else
+ {
+ if ( !pMacro
+ && enmMode == kMacroReScanMode_Expression
+ && cchDefine == sizeof("defined") - 1
+ && !strncmp(&pExp->StrBuf.pszBuf[offDefine], "defined", cchDefine))
+ {
+ cReplacements++;
+ rcExit = vbcppMacroExpandDefinedOperator(pThis, pExp, offDefine, &off);
+ }
+ else
+ off = offDefine + cchDefine;
+ }
+ }
+ else
+ {
+ Assert(RT_C_IS_SPACE(ch) || RT_C_IS_PUNCT(ch));
+ Assert(ch != '\r' && ch != '\n');
+ }
+ }
+
+ if (pcReplacements)
+ *pcReplacements = cReplacements;
+ return rcExit;
+}
+
+
+/**
+ * Cleans up the expansion context.
+ *
+ * This involves clearing VBCPPMACRO::fExpanding and VBCPPMACRO::pUpExpanding,
+ * and freeing the memory resources associated with the expansion context.
+ *
+ * @param pExp The expansion context.
+ */
+static void vbcppMacroExpandCleanup(PVBCPPMACROEXP pExp)
+{
+#if 0
+ while (pExp->pMacroStack)
+ {
+ PVBCPPMACRO pMacro = pExp->pMacroStack;
+ pExp->pMacroStack = pMacro->pUpExpanding;
+
+ pMacro->fExpanding = false;
+ pMacro->pUpExpanding = NULL;
+ }
+#endif
+
+ while (pExp->cArgs > 0)
+ {
+ RTMemFree(pExp->papszArgs[--pExp->cArgs]);
+ pExp->papszArgs[pExp->cArgs] = NULL;
+ }
+
+ RTMemFree(pExp->papszArgs);
+ pExp->papszArgs = NULL;
+
+ vbcppStrBufDelete(&pExp->StrBuf);
+}
+
+
+
+/**
+ * Frees a define.
+ *
+ * @returns VINF_SUCCESS (used when called by RTStrSpaceDestroy)
+ * @param pStr Pointer to the VBCPPMACRO::Core member.
+ * @param pvUser Unused.
+ */
+static DECLCALLBACK(int) vbcppMacroFree(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ RTMemFree(pStr);
+ NOREF(pvUser);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Removes a define.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg.
+ * @param pThis The C preprocessor instance.
+ * @param pszDefine The define name, no argument list or anything.
+ * @param cchDefine The length of the name. RTSTR_MAX is ok.
+ * @param fExplicitUndef Explicit undefinition, that is, in a selective
+ * preprocessing run it will evaluate to undefined.
+ */
+static RTEXITCODE vbcppMacroUndef(PVBCPP pThis, const char *pszDefine, size_t cchDefine, bool fExplicitUndef)
+{
+ PRTSTRSPACECORE pHit = RTStrSpaceGetN(&pThis->StrSpace, pszDefine, cchDefine);
+ if (pHit)
+ {
+ RTStrSpaceRemove(&pThis->StrSpace, pHit->pszString);
+ vbcppMacroFree(pHit, NULL);
+ }
+
+ if (fExplicitUndef)
+ {
+ if (cchDefine == RTSTR_MAX)
+ cchDefine = strlen(pszDefine);
+
+ PRTSTRSPACECORE pStr = (PRTSTRSPACECORE)RTMemAlloc(sizeof(*pStr) + cchDefine + 1);
+ if (!pStr)
+ return vbcppError(pThis, "out of memory");
+ char *pszDst = (char *)(pStr + 1);
+ pStr->pszString = pszDst;
+ memcpy(pszDst, pszDefine, cchDefine);
+ pszDst[cchDefine] = '\0';
+ if (!RTStrSpaceInsert(&pThis->UndefStrSpace, pStr))
+ RTMemFree(pStr);
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Inserts a define (rejecting and freeing it in some case).
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg.
+ * @param pThis The C preprocessor instance.
+ * @param pMacro The define to insert.
+ */
+static RTEXITCODE vbcppMacroInsert(PVBCPP pThis, PVBCPPMACRO pMacro)
+{
+ /*
+ * Reject illegal macro names.
+ */
+ if (!strcmp(pMacro->Core.pszString, "defined"))
+ {
+ RTEXITCODE rcExit = vbcppError(pThis, "Cannot use '%s' as a macro name", pMacro->Core.pszString);
+ vbcppMacroFree(&pMacro->Core, NULL);
+ return rcExit;
+ }
+
+ /*
+ * Ignore in source-file defines when doing selective preprocessing.
+ */
+ if ( !pThis->fRespectSourceDefines
+ && !pMacro->fCmdLine)
+ {
+ /* Ignore*/
+ vbcppMacroFree(&pMacro->Core, NULL);
+ return RTEXITCODE_SUCCESS;
+ }
+
+ /*
+ * Insert it and update the lead character hint bitmap.
+ */
+ if (RTStrSpaceInsert(&pThis->StrSpace, &pMacro->Core))
+ VBCPP_BITMAP_SET(pThis->bmDefined, *pMacro->Core.pszString);
+ else
+ {
+ /*
+ * Duplicate. When doing selective D preprocessing, let the command
+ * line take precendece.
+ */
+ PVBCPPMACRO pOld = (PVBCPPMACRO)RTStrSpaceGet(&pThis->StrSpace, pMacro->Core.pszString); Assert(pOld);
+ if ( pThis->fAllowRedefiningCmdLineDefines
+ || pMacro->fCmdLine == pOld->fCmdLine)
+ {
+ if (pMacro->fCmdLine)
+ RTMsgWarning("Redefining '%s'", pMacro->Core.pszString);
+
+ RTStrSpaceRemove(&pThis->StrSpace, pOld->Core.pszString);
+ vbcppMacroFree(&pOld->Core, NULL);
+
+ bool fRc = RTStrSpaceInsert(&pThis->StrSpace, &pMacro->Core);
+ Assert(fRc); NOREF(fRc);
+ }
+ else
+ {
+ RTMsgWarning("Ignoring redefinition of '%s'", pMacro->Core.pszString);
+ vbcppMacroFree(&pMacro->Core, NULL);
+ }
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Adds a define.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg.
+ * @param pThis The C preprocessor instance.
+ * @param pszDefine The define name, no parameter list.
+ * @param cchDefine The length of the name.
+ * @param pszParams The parameter list.
+ * @param cchParams The length of the parameter list.
+ * @param pszValue The value.
+ * @param cchDefine The length of the value.
+ * @param fCmdLine Set if originating on the command line.
+ */
+static RTEXITCODE vbcppMacroAddFn(PVBCPP pThis, const char *pszDefine, size_t cchDefine,
+ const char *pszParams, size_t cchParams,
+ const char *pszValue, size_t cchValue,
+ bool fCmdLine)
+
+{
+ Assert(RTStrNLen(pszDefine, cchDefine) == cchDefine);
+ Assert(RTStrNLen(pszParams, cchParams) == cchParams);
+ Assert(RTStrNLen(pszValue, cchValue) == cchValue);
+
+ /*
+ * Determin the number of arguments and how much space their names
+ * requires. Performing syntax validation while parsing.
+ */
+ uint32_t cchArgNames = 0;
+ uint32_t cArgs = 0;
+ for (size_t off = 0; off < cchParams; off++)
+ {
+ /* Skip blanks and maybe one comma. */
+ bool fIgnoreComma = cArgs != 0;
+ while (off < cchParams)
+ {
+ if (!RT_C_IS_SPACE(pszParams[off]))
+ {
+ if (pszParams[off] != ',' || !fIgnoreComma)
+ {
+ if (vbcppIsCIdentifierLeadChar(pszParams[off]))
+ break;
+ /** @todo variadic macros. */
+ return vbcppErrorPos(pThis, &pszParams[off], "Unexpected character");
+ }
+ fIgnoreComma = false;
+ }
+ off++;
+ }
+ if (off >= cchParams)
+ break;
+
+ /* Found and argument. First character is already validated. */
+ cArgs++;
+ cchArgNames += 2;
+ off++;
+ while ( off < cchParams
+ && vbcppIsCIdentifierChar(pszParams[off]))
+ off++, cchArgNames++;
+ }
+
+ /*
+ * Allocate a structure.
+ */
+ size_t cbDef = RT_UOFFSETOF_DYN(VBCPPMACRO, szValue[cchValue + 1 + cchDefine + 1 + cchArgNames])
+ + sizeof(const char *) * cArgs;
+ cbDef = RT_ALIGN_Z(cbDef, sizeof(const char *));
+ PVBCPPMACRO pMacro = (PVBCPPMACRO)RTMemAlloc(cbDef);
+ if (!pMacro)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "out of memory");
+
+ char *pszDst = &pMacro->szValue[cchValue + 1];
+ pMacro->Core.pszString = pszDst;
+ memcpy(pszDst, pszDefine, cchDefine);
+ pszDst += cchDefine;
+ *pszDst++ = '\0';
+ pMacro->fFunction = true;
+ pMacro->fVarArg = false;
+ pMacro->fCmdLine = fCmdLine;
+ pMacro->fExpanding = false;
+ pMacro->cArgs = cArgs;
+ pMacro->papszArgs = (const char **)((uintptr_t)pMacro + cbDef - sizeof(const char *) * cArgs);
+ VBCPP_BITMAP_EMPTY(pMacro->bmArgs);
+ pMacro->cchValue = cchValue;
+ memcpy(pMacro->szValue, pszValue, cchValue);
+ pMacro->szValue[cchValue] = '\0';
+
+ /*
+ * Set up the arguments.
+ */
+ uint32_t iArg = 0;
+ for (size_t off = 0; off < cchParams; off++)
+ {
+ /* Skip blanks and maybe one comma. */
+ bool fIgnoreComma = cArgs != 0;
+ while (off < cchParams)
+ {
+ if (!RT_C_IS_SPACE(pszParams[off]))
+ {
+ if (pszParams[off] != ',' || !fIgnoreComma)
+ break;
+ fIgnoreComma = false;
+ }
+ off++;
+ }
+ if (off >= cchParams)
+ break;
+
+ /* Found and argument. First character is already validated. */
+ VBCPP_BITMAP_SET(pMacro->bmArgs, pszParams[off]);
+ pMacro->papszArgs[iArg] = pszDst;
+ do
+ {
+ *pszDst++ = pszParams[off++];
+ } while ( off < cchParams
+ && vbcppIsCIdentifierChar(pszParams[off]));
+ *pszDst++ = '\0';
+ iArg++;
+ }
+ Assert((uintptr_t)pszDst <= (uintptr_t)pMacro->papszArgs);
+
+ return vbcppMacroInsert(pThis, pMacro);
+}
+
+
+/**
+ * Adds a define.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + msg.
+ * @param pThis The C preprocessor instance.
+ * @param pszDefine The define name and optionally the argument
+ * list.
+ * @param cchDefine The length of the name. RTSTR_MAX is ok.
+ * @param pszValue The value.
+ * @param cchDefine The length of the value. RTSTR_MAX is ok.
+ * @param fCmdLine Set if originating on the command line.
+ */
+static RTEXITCODE vbcppMacroAdd(PVBCPP pThis, const char *pszDefine, size_t cchDefine,
+ const char *pszValue, size_t cchValue, bool fCmdLine)
+{
+ /*
+ * We need the lengths. Trim the input.
+ */
+ if (cchDefine == RTSTR_MAX)
+ cchDefine = strlen(pszDefine);
+ while (cchDefine > 0 && RT_C_IS_SPACE(*pszDefine))
+ pszDefine++, cchDefine--;
+ while (cchDefine > 0 && RT_C_IS_SPACE(pszDefine[cchDefine - 1]))
+ cchDefine--;
+ if (!cchDefine)
+ return vbcppErrorPos(pThis, pszDefine, "The define has no name");
+
+ if (cchValue == RTSTR_MAX)
+ cchValue = strlen(pszValue);
+ while (cchValue > 0 && RT_C_IS_SPACE(*pszValue))
+ pszValue++, cchValue--;
+ while (cchValue > 0 && RT_C_IS_SPACE(pszValue[cchValue - 1]))
+ cchValue--;
+
+ /*
+ * Arguments make the job a bit more annoying. Handle that elsewhere
+ */
+ const char *pszParams = (const char *)memchr(pszDefine, '(', cchDefine);
+ if (pszParams)
+ {
+ size_t cchParams = pszDefine + cchDefine - pszParams;
+ cchDefine -= cchParams;
+ if (!vbcppValidateCIdentifier(pThis, pszDefine, cchDefine))
+ return RTEXITCODE_FAILURE;
+ if (pszParams[cchParams - 1] != ')')
+ return vbcppErrorPos(pThis, pszParams + cchParams - 1, "Missing closing parenthesis");
+ pszParams++;
+ cchParams -= 2;
+ return vbcppMacroAddFn(pThis, pszDefine, cchDefine, pszParams, cchParams, pszValue, cchValue, fCmdLine);
+ }
+
+ /*
+ * Simple define, no arguments.
+ */
+ if (!vbcppValidateCIdentifier(pThis, pszDefine, cchDefine))
+ return RTEXITCODE_FAILURE;
+
+ PVBCPPMACRO pMacro = (PVBCPPMACRO)RTMemAlloc(RT_UOFFSETOF_DYN(VBCPPMACRO, szValue[cchValue + 1 + cchDefine + 1]));
+ if (!pMacro)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "out of memory");
+
+ pMacro->Core.pszString = &pMacro->szValue[cchValue + 1];
+ memcpy((char *)pMacro->Core.pszString, pszDefine, cchDefine);
+ ((char *)pMacro->Core.pszString)[cchDefine] = '\0';
+ pMacro->fFunction = false;
+ pMacro->fVarArg = false;
+ pMacro->fCmdLine = fCmdLine;
+ pMacro->fExpanding = false;
+ pMacro->cArgs = 0;
+ pMacro->papszArgs = NULL;
+ VBCPP_BITMAP_EMPTY(pMacro->bmArgs);
+ pMacro->cchValue = cchValue;
+ memcpy(pMacro->szValue, pszValue, cchValue);
+ pMacro->szValue[cchValue] = '\0';
+
+ return vbcppMacroInsert(pThis, pMacro);
+}
+
+
+/**
+ * Tries to convert a define into an inline D constant.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pMacro The macro.
+ */
+static RTEXITCODE vbcppMacroTryConvertToInlineD(PVBCPP pThis, PVBCPPMACRO pMacro)
+{
+ AssertReturn(pMacro, vbcppError(pThis, "Internal error"));
+ if (pMacro->fFunction)
+ return RTEXITCODE_SUCCESS;
+
+ /*
+ * Do some simple macro resolving. (Mostly to make x86.h work.)
+ */
+ const char *pszDefine = pMacro->Core.pszString;
+ const char *pszValue = pMacro->szValue;
+ size_t cchValue = pMacro->cchValue;
+
+ unsigned i = 0;
+ PVBCPPMACRO pMacro2;
+ while ( i < 10
+ && cchValue > 0
+ && vbcppIsCIdentifierLeadChar(*pszValue)
+ && (pMacro2 = vbcppMacroLookup(pThis, pszValue, cchValue)) != NULL
+ && !pMacro2->fFunction )
+ {
+ pszValue = pMacro2->szValue;
+ cchValue = pMacro2->cchValue;
+ i++;
+ }
+
+ if (!pMacro->cchValue)
+ return RTEXITCODE_SUCCESS;
+
+
+ /*
+ * A lone value?
+ */
+ ssize_t cch = 0;
+ uint64_t u64;
+ char *pszNext;
+ int rc = RTStrToUInt64Ex(pszValue, &pszNext, 0, &u64);
+ if (RT_SUCCESS(rc))
+ {
+ if ( rc == VWRN_TRAILING_SPACES
+ || rc == VWRN_NEGATIVE_UNSIGNED
+ || rc == VWRN_NUMBER_TOO_BIG)
+ return RTEXITCODE_SUCCESS;
+ const char *pszType;
+ if (rc == VWRN_TRAILING_CHARS)
+ {
+ if (!strcmp(pszNext, "u") || !strcmp(pszNext, "U"))
+ pszType = "uint32_t";
+ else if (!strcmp(pszNext, "ul") || !strcmp(pszNext, "UL"))
+ pszType = "uintptr_t";
+ else if (!strcmp(pszNext, "ull") || !strcmp(pszNext, "ULL"))
+ pszType = "uint64_t";
+ else
+ pszType = NULL;
+ }
+ else if (u64 <= UINT8_MAX)
+ pszType = "uint8_t";
+ else if (u64 <= UINT16_MAX)
+ pszType = "uint16_t";
+ else if (u64 <= UINT32_MAX)
+ pszType = "uint32_t";
+ else
+ pszType = "uint64_t";
+ if (!pszType)
+ return RTEXITCODE_SUCCESS;
+ cch = ScmStreamPrintf(&pThis->StrmOutput, "inline %s %s = %.*s;\n",
+ pszType, pszDefine, pszNext - pszValue, pszValue);
+ }
+ /*
+ * A value wrapped in a constant macro?
+ */
+ else if ( (pszNext = (char *)strchr(pszValue, '(')) != NULL
+ && pszValue[cchValue - 1] == ')' )
+ {
+ size_t cchPrefix = pszNext - pszValue;
+ size_t cchInnerValue = cchValue - cchPrefix - 2;
+ const char *pchInnerValue = &pszValue[cchPrefix + 1];
+ while (cchInnerValue > 0 && RT_C_IS_SPACE(*pchInnerValue))
+ cchInnerValue--, pchInnerValue++;
+ while (cchInnerValue > 0 && RT_C_IS_SPACE(pchInnerValue[cchInnerValue - 1]))
+ cchInnerValue--;
+ if (!cchInnerValue || !RT_C_IS_XDIGIT(*pchInnerValue))
+ return RTEXITCODE_SUCCESS;
+
+ rc = RTStrToUInt64Ex(pchInnerValue, &pszNext, 0, &u64);
+ if ( RT_FAILURE(rc)
+ || rc == VWRN_TRAILING_SPACES
+ || rc == VWRN_NEGATIVE_UNSIGNED
+ || rc == VWRN_NUMBER_TOO_BIG)
+ return RTEXITCODE_SUCCESS;
+
+ const char *pszType;
+#define MY_MATCH_STR(a_sz) (sizeof(a_sz) - 1 == cchPrefix && !strncmp(pszValue, a_sz, sizeof(a_sz) - 1))
+ if (MY_MATCH_STR("UINT8_C"))
+ pszType = "uint8_t";
+ else if (MY_MATCH_STR("UINT16_C"))
+ pszType = "uint16_t";
+ else if (MY_MATCH_STR("UINT32_C"))
+ pszType = "uint32_t";
+ else if (MY_MATCH_STR("UINT64_C"))
+ pszType = "uint64_t";
+ else
+ pszType = NULL;
+ if (pszType)
+ cch = ScmStreamPrintf(&pThis->StrmOutput, "inline %s %s = %.*s;\n",
+ pszType, pszDefine, cchInnerValue, pchInnerValue);
+ else if (MY_MATCH_STR("RT_BIT") || MY_MATCH_STR("RT_BIT_32"))
+ cch = ScmStreamPrintf(&pThis->StrmOutput, "inline uint32_t %s = 1U << %llu;\n",
+ pszDefine, u64);
+ else if (MY_MATCH_STR("RT_BIT_64"))
+ cch = ScmStreamPrintf(&pThis->StrmOutput, "inline uint64_t %s = 1ULL << %llu;\n",
+ pszDefine, u64);
+ else
+ return RTEXITCODE_SUCCESS;
+#undef MY_MATCH_STR
+ }
+ /* Dunno what this is... */
+ else
+ return RTEXITCODE_SUCCESS;
+
+ /*
+ * Check for output error and clear the output suppression indicator.
+ */
+ if (cch < 0)
+ return vbcppError(pThis, "Output error");
+
+ pThis->fJustDroppedLine = false;
+ return RTEXITCODE_SUCCESS;
+}
+
+
+
+/**
+ * Processes a abbreviated line number directive.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ * @param offStart The stream position where the directive
+ * started (for pass thru).
+ */
+static RTEXITCODE vbcppDirectiveDefine(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart)
+{
+ RT_NOREF_PV(offStart);
+
+ /*
+ * Parse it.
+ */
+ RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ size_t cchDefine;
+ const char *pchDefine = ScmStreamCGetWord(pStrmInput, &cchDefine);
+ if (pchDefine)
+ {
+ /* If it's a function style define, parse out the parameter list. */
+ size_t cchParams = 0;
+ const char *pchParams = NULL;
+ unsigned ch = ScmStreamPeekCh(pStrmInput);
+ if (ch == '(')
+ {
+ ScmStreamGetCh(pStrmInput);
+ pchParams = ScmStreamGetCur(pStrmInput);
+
+ unsigned chPrev = ch;
+ while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0)
+ {
+ if (ch == '\r' || ch == '\n')
+ {
+ if (chPrev != '\\')
+ {
+ rcExit = vbcppError(pThis, "Missing ')'");
+ break;
+ }
+ ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1);
+ }
+ if (ch == ')')
+ {
+ cchParams = ScmStreamGetCur(pStrmInput) - pchParams;
+ ScmStreamGetCh(pStrmInput);
+ break;
+ }
+ ScmStreamGetCh(pStrmInput);
+ }
+ }
+ /* The simple kind. */
+ else if (!RT_C_IS_SPACE(ch) && ch != ~(unsigned)0)
+ rcExit = vbcppError(pThis, "Expected whitespace after macro name");
+
+ /* Parse out the value. */
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ size_t offValue = ScmStreamTell(pStrmInput);
+ const char *pchValue = ScmStreamGetCur(pStrmInput);
+ unsigned chPrev = ch;
+ while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0)
+ {
+ if (ch == '\r' || ch == '\n')
+ {
+ if (chPrev != '\\')
+ break;
+ ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1);
+ }
+ chPrev = ScmStreamGetCh(pStrmInput);
+ }
+ size_t cchValue = ScmStreamGetCur(pStrmInput) - pchValue;
+
+ /*
+ * Execute.
+ */
+ if (pchParams)
+ rcExit = vbcppMacroAddFn(pThis, pchDefine, cchDefine, pchParams, cchParams, pchValue, cchValue, false);
+ else
+ rcExit = vbcppMacroAdd(pThis, pchDefine, cchDefine, pchValue, cchValue, false);
+
+ /*
+ * Pass thru?
+ */
+ if ( rcExit == RTEXITCODE_SUCCESS
+ && pThis->fPassThruDefines)
+ {
+ unsigned cchIndent = pThis->pCondStack ? pThis->pCondStack->iKeepLevel : 0;
+ ssize_t cch;
+ if (pchParams)
+ cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sdefine %.*s(%.*s)",
+ cchIndent, "", cchDefine, pchDefine, cchParams, pchParams);
+ else
+ cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sdefine %.*s",
+ cchIndent, "", cchDefine, pchDefine);
+ if (cch > 0)
+ vbcppOutputComment(pThis, pStrmInput, offValue, cch, 1);
+ else
+ rcExit = vbcppError(pThis, "output error");
+ }
+ else if ( rcExit == RTEXITCODE_SUCCESS
+ && pThis->enmMode == kVBCppMode_SelectiveD)
+ rcExit = vbcppMacroTryConvertToInlineD(pThis, vbcppMacroLookup(pThis, pchDefine, cchDefine));
+ else
+ pThis->fJustDroppedLine = true;
+ }
+ }
+ }
+ return rcExit;
+}
+
+
+/**
+ * Processes a abbreviated line number directive.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ * @param offStart The stream position where the directive
+ * started (for pass thru).
+ */
+static RTEXITCODE vbcppDirectiveUndef(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart)
+{
+ RT_NOREF_PV(offStart);
+
+ /*
+ * Parse it.
+ */
+ RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ size_t cchDefine;
+ const char *pchDefine = ScmStreamCGetWord(pStrmInput, &cchDefine);
+ if (pchDefine)
+ {
+ size_t offMaybeComment = vbcppProcessSkipWhite(pStrmInput);
+ rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Take action.
+ */
+ PVBCPPMACRO pMacro = vbcppMacroLookup(pThis, pchDefine, cchDefine);
+ if ( pMacro
+ && pThis->fRespectSourceDefines
+ && ( !pMacro->fCmdLine
+ || pThis->fAllowRedefiningCmdLineDefines ) )
+ {
+ RTStrSpaceRemove(&pThis->StrSpace, pMacro->Core.pszString);
+ vbcppMacroFree(&pMacro->Core, NULL);
+ }
+
+ /*
+ * Pass thru.
+ */
+ if ( rcExit == RTEXITCODE_SUCCESS
+ && pThis->fPassThruDefines)
+ {
+ unsigned cchIndent = pThis->pCondStack ? pThis->pCondStack->iKeepLevel : 0;
+ ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sundef %.*s",
+ cchIndent, "", cchDefine, pchDefine);
+ if (cch > 0)
+ vbcppOutputComment(pThis, pStrmInput, offMaybeComment, cch, 1);
+ else
+ rcExit = vbcppError(pThis, "output error");
+ }
+
+ }
+ }
+ else
+ rcExit = vbcppError(pThis, "Malformed #ifndef");
+ }
+ return rcExit;
+
+}
+
+
+
+
+
+/*
+ *
+ *
+ * C O N D I T I O N A L S
+ * C O N D I T I O N A L S
+ * C O N D I T I O N A L S
+ * C O N D I T I O N A L S
+ * C O N D I T I O N A L S
+ *
+ *
+ */
+
+
+/**
+ * Combines current stack result with the one being pushed.
+ *
+ * @returns Combined result.
+ * @param enmEvalPush The result of the condition being pushed.
+ * @param enmEvalStack The current stack result.
+ */
+static VBCPPEVAL vbcppCondCombine(VBCPPEVAL enmEvalPush, VBCPPEVAL enmEvalStack)
+{
+ if (enmEvalStack == kVBCppEval_False)
+ return kVBCppEval_False;
+ return enmEvalPush;
+}
+
+
+/**
+ * Pushes an conditional onto the stack.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The current input stream.
+ * @param offStart Not currently used, using @a pchCondition and
+ * @a cchCondition instead.
+ * @param enmKind The kind of conditional.
+ * @param enmResult The result of the evaluation.
+ * @param pchCondition The raw condition.
+ * @param cchCondition The length of @a pchCondition.
+ */
+static RTEXITCODE vbcppCondPush(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart,
+ VBCPPCONDKIND enmKind, VBCPPEVAL enmResult,
+ const char *pchCondition, size_t cchCondition)
+{
+ RT_NOREF_PV(offStart); RT_NOREF_PV(pStrmInput);
+
+
+ if (pThis->cCondStackDepth >= _64K)
+ return vbcppError(pThis, "Too many nested #if/#ifdef/#ifndef statements");
+
+ /*
+ * Allocate a new entry and push it.
+ */
+ PVBCPPCOND pCond = (PVBCPPCOND)RTMemAlloc(sizeof(*pCond));
+ if (!pCond)
+ return vbcppError(pThis, "out of memory");
+
+ PVBCPPCOND pUp = pThis->pCondStack;
+ pCond->enmKind = enmKind;
+ pCond->enmResult = enmResult;
+ pCond->enmStackResult = pUp ? vbcppCondCombine(enmResult, pUp->enmStackResult) : enmResult;
+ pCond->fSeenElse = false;
+ pCond->fElIfDecided = enmResult == kVBCppEval_True;
+ pCond->iLevel = pThis->cCondStackDepth;
+ pCond->iKeepLevel = (pUp ? pUp->iKeepLevel : 0) + enmResult == kVBCppEval_Undecided;
+ pCond->pchCond = pchCondition;
+ pCond->cchCond = cchCondition;
+
+ pCond->pUp = pThis->pCondStack;
+ pThis->pCondStack = pCond;
+ pThis->fIf0Mode = pCond->enmStackResult == kVBCppEval_False;
+
+ /*
+ * Do pass thru.
+ */
+ if ( !pThis->fIf0Mode
+ && enmResult == kVBCppEval_Undecided)
+ {
+ /** @todo this is stripping comments of \#ifdef and \#ifndef atm. */
+ const char *pszDirective;
+ switch (enmKind)
+ {
+ case kVBCppCondKind_If: pszDirective = "if"; break;
+ case kVBCppCondKind_IfDef: pszDirective = "ifdef"; break;
+ case kVBCppCondKind_IfNDef: pszDirective = "ifndef"; break;
+ case kVBCppCondKind_ElIf: pszDirective = "elif"; break;
+ default: AssertFailedReturn(RTEXITCODE_FAILURE);
+ }
+ ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*s%s %.*s",
+ pCond->iKeepLevel - 1, "", pszDirective, cchCondition, pchCondition);
+ if (cch < 0)
+ return vbcppError(pThis, "Output error %Rrc", (int)cch);
+ }
+ else
+ pThis->fJustDroppedLine = true;
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Recursively destroys the expression tree.
+ *
+ * @param pExpr The root of the expression tree to destroy.
+ */
+static void vbcppExprDestoryTree(PVBCPPEXPR pExpr)
+{
+ if (!pExpr)
+ return;
+
+ switch (pExpr->enmKind)
+ {
+ case kVBCppExprKind_Unary:
+ vbcppExprDestoryTree(pExpr->u.Unary.pArg);
+ break;
+ case kVBCppExprKind_Binary:
+ vbcppExprDestoryTree(pExpr->u.Binary.pLeft);
+ vbcppExprDestoryTree(pExpr->u.Binary.pRight);
+ break;
+ case kVBCppExprKind_Ternary:
+ vbcppExprDestoryTree(pExpr->u.Ternary.pExpr);
+ vbcppExprDestoryTree(pExpr->u.Ternary.pExpr);
+ vbcppExprDestoryTree(pExpr->u.Ternary.pFalse);
+ break;
+ case kVBCppExprKind_SignedValue:
+ case kVBCppExprKind_UnsignedValue:
+ break;
+ default:
+ AssertFailed();
+ return;
+ }
+ RTMemFree(pExpr);
+}
+
+
+/**
+ * Report error during expression parsing.
+ *
+ * @returns kExprRet_Error
+ * @param pParser The parser instance.
+ * @param pszMsg The error message.
+ * @param ... Format arguments.
+ */
+static VBCPPEXPRRET vbcppExprParseError(PVBCPPEXPRPARSER pParser, const char *pszMsg, ...)
+{
+ va_list va;
+ va_start(va, pszMsg);
+ vbcppErrorV(pParser->pThis, pszMsg, va);
+ va_end(va);
+ return kExprRet_Error;
+}
+
+
+/**
+ * Skip white space.
+ *
+ * @param pParser The parser instance.
+ */
+static void vbcppExprParseSkipWhiteSpace(PVBCPPEXPRPARSER pParser)
+{
+ while (RT_C_IS_SPACE(*pParser->pszCur))
+ pParser->pszCur++;
+}
+
+
+/**
+ * Allocate a new
+ *
+ * @returns Pointer to the node. NULL+msg on failure.
+ * @param pParser The parser instance.
+ */
+static PVBCPPEXPR vbcppExprParseAllocNode(PVBCPPEXPRPARSER pParser)
+{
+ PVBCPPEXPR pExpr = (PVBCPPEXPR)RTMemAllocZ(sizeof(*pExpr));
+ if (!pExpr)
+ vbcppExprParseError(pParser, "out of memory (expression node)");
+ return pExpr;
+}
+
+
+/**
+ * Looks for right parentheses and/or end of expression.
+ *
+ * @returns Expression status.
+ * @retval kExprRet_Ok
+ * @retval kExprRet_Error with msg.
+ * @retval kExprRet_EndOfExpr
+ * @param pParser The parser instance.
+ */
+static VBCPPEXPRRET vbcppExprParseMaybeRParenOrEoe(PVBCPPEXPRPARSER pParser)
+{
+ Assert(!pParser->ppCur);
+ for (;;)
+ {
+ vbcppExprParseSkipWhiteSpace(pParser);
+ char ch = *pParser->pszCur;
+ if (ch == '\0')
+ return kExprRet_EndOfExpr;
+ if (ch != ')')
+ break;
+ pParser->pszCur++;
+
+ PVBCPPEXPR pCur = pParser->pCur;
+ while ( pCur
+ && ( pCur->enmKind != kVBCppExprKind_Unary
+ || pCur->u.Unary.enmOperator != kVBCppUnaryOp_Parenthesis))
+ {
+ switch (pCur->enmKind)
+ {
+ case kVBCppExprKind_SignedValue:
+ case kVBCppExprKind_UnsignedValue:
+ Assert(pCur->fComplete);
+ break;
+ case kVBCppExprKind_Unary:
+ AssertReturn(pCur->u.Unary.pArg, vbcppExprParseError(pParser, "internal error"));
+ pCur->fComplete = true;
+ break;
+ case kVBCppExprKind_Binary:
+ AssertReturn(pCur->u.Binary.pLeft, vbcppExprParseError(pParser, "internal error"));
+ AssertReturn(pCur->u.Binary.pRight, vbcppExprParseError(pParser, "internal error"));
+ pCur->fComplete = true;
+ break;
+ case kVBCppExprKind_Ternary:
+#if 1 /** @todo Check out the ternary operator implementation. */
+ return vbcppExprParseError(pParser, "The ternary operator is not implemented");
+#else
+ Assert(pCur->u.Ternary.pExpr);
+ if (!pCur->u.Ternary.pTrue)
+ return vbcppExprParseError(pParser, "?!?!?");
+ if (!pCur->u.Ternary.pFalse)
+ return vbcppExprParseError(pParser, "?!?!?!?");
+ pCur->fComplete = true;
+#endif
+ break;
+ default:
+ return vbcppExprParseError(pParser, "Internal error (enmKind=%d)", pCur->enmKind);
+ }
+ pCur = pCur->pParent;
+ }
+ if (!pCur)
+ return vbcppExprParseError(pParser, "Right parenthesis without a left one");
+ pCur->fComplete = true;
+
+ while ( pCur->enmKind == kVBCppExprKind_Unary
+ && pCur->u.Unary.enmOperator != kVBCppUnaryOp_Parenthesis
+ && pCur->pParent)
+ {
+ AssertReturn(pCur->u.Unary.pArg, vbcppExprParseError(pParser, "internal error"));
+ pCur->fComplete = true;
+ pCur = pCur->pParent;
+ }
+ }
+
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Parses an binary operator.
+ *
+ * @returns Expression status.
+ * @retval kExprRet_Ok
+ * @retval kExprRet_Error with msg.
+ * @param pParser The parser instance.
+ */
+static VBCPPEXPRRET vbcppExprParseBinaryOperator(PVBCPPEXPRPARSER pParser)
+{
+ /*
+ * Binary or ternary operator should follow now.
+ */
+ VBCPPBINARYOP enmOp;
+ char ch = *pParser->pszCur;
+ switch (ch)
+ {
+ case '*':
+ if (pParser->pszCur[1] == '=')
+ return vbcppExprParseError(pParser, "The assignment by product operator is not valid in a preprocessor expression");
+ enmOp = kVBCppBinary_Multiplication;
+ break;
+ case '/':
+ if (pParser->pszCur[1] == '=')
+ return vbcppExprParseError(pParser, "The assignment by quotient operator is not valid in a preprocessor expression");
+ enmOp = kVBCppBinary_Division;
+ break;
+ case '%':
+ if (pParser->pszCur[1] == '=')
+ return vbcppExprParseError(pParser, "The assignment by remainder operator is not valid in a preprocessor expression");
+ enmOp = kVBCppBinary_Modulo;
+ break;
+ case '+':
+ if (pParser->pszCur[1] == '=')
+ return vbcppExprParseError(pParser, "The assignment by sum operator is not valid in a preprocessor expression");
+ enmOp = kVBCppBinary_Addition;
+ break;
+ case '-':
+ if (pParser->pszCur[1] == '=')
+ return vbcppExprParseError(pParser, "The assignment by difference operator is not valid in a preprocessor expression");
+ enmOp = kVBCppBinary_Subtraction;
+ break;
+ case '<':
+ enmOp = kVBCppBinary_LessThan;
+ if (pParser->pszCur[1] == '=')
+ {
+ pParser->pszCur++;
+ enmOp = kVBCppBinary_LessThanOrEqual;
+ }
+ else if (pParser->pszCur[1] == '<')
+ {
+ pParser->pszCur++;
+ if (pParser->pszCur[1] == '=')
+ return vbcppExprParseError(pParser, "The assignment by bitwise left shift operator is not valid in a preprocessor expression");
+ enmOp = kVBCppBinary_LeftShift;
+ }
+ break;
+ case '>':
+ enmOp = kVBCppBinary_GreaterThan;
+ if (pParser->pszCur[1] == '=')
+ {
+ pParser->pszCur++;
+ enmOp = kVBCppBinary_GreaterThanOrEqual;
+ }
+ else if (pParser->pszCur[1] == '<')
+ {
+ pParser->pszCur++;
+ if (pParser->pszCur[1] == '=')
+ return vbcppExprParseError(pParser, "The assignment by bitwise right shift operator is not valid in a preprocessor expression");
+ enmOp = kVBCppBinary_LeftShift;
+ }
+ break;
+ case '=':
+ if (pParser->pszCur[1] != '=')
+ return vbcppExprParseError(pParser, "The assignment operator is not valid in a preprocessor expression");
+ pParser->pszCur++;
+ enmOp = kVBCppBinary_EqualTo;
+ break;
+
+ case '!':
+ if (pParser->pszCur[1] != '=')
+ return vbcppExprParseError(pParser, "Expected binary operator, found the unary operator logical NOT");
+ pParser->pszCur++;
+ enmOp = kVBCppBinary_NotEqualTo;
+ break;
+
+ case '&':
+ if (pParser->pszCur[1] == '=')
+ return vbcppExprParseError(pParser, "The assignment by bitwise AND operator is not valid in a preprocessor expression");
+ if (pParser->pszCur[1] == '&')
+ {
+ pParser->pszCur++;
+ enmOp = kVBCppBinary_LogicalAnd;
+ }
+ else
+ enmOp = kVBCppBinary_BitwiseAnd;
+ break;
+ case '^':
+ if (pParser->pszCur[1] == '=')
+ return vbcppExprParseError(pParser, "The assignment by bitwise XOR operator is not valid in a preprocessor expression");
+ enmOp = kVBCppBinary_BitwiseXor;
+ break;
+ case '|':
+ if (pParser->pszCur[1] == '=')
+ return vbcppExprParseError(pParser, "The assignment by bitwise AND operator is not valid in a preprocessor expression");
+ if (pParser->pszCur[1] == '|')
+ {
+ pParser->pszCur++;
+ enmOp = kVBCppBinary_LogicalOr;
+ }
+ else
+ enmOp = kVBCppBinary_BitwiseOr;
+ break;
+ case '~':
+ return vbcppExprParseError(pParser, "Expected binary operator, found the unary operator bitwise NOT");
+
+ case ':':
+ case '?':
+ return vbcppExprParseError(pParser, "The ternary operator is not yet implemented");
+
+ default:
+ return vbcppExprParseError(pParser, "Expected binary operator, found '%.20s'", pParser->pszCur);
+ }
+ pParser->pszCur++;
+
+ /*
+ * Create a binary operator node.
+ */
+ PVBCPPEXPR pExpr = vbcppExprParseAllocNode(pParser);
+ if (!pExpr)
+ return kExprRet_Error;
+ pExpr->fComplete = true;
+ pExpr->enmKind = kVBCppExprKind_Binary;
+ pExpr->u.Binary.enmOperator = enmOp;
+ pExpr->u.Binary.pLeft = NULL;
+ pExpr->u.Binary.pRight = NULL;
+
+ /*
+ * Back up the tree until we find our spot.
+ */
+ PVBCPPEXPR *ppPlace = NULL;
+ PVBCPPEXPR pChild = NULL;
+ PVBCPPEXPR pParent = pParser->pCur;
+ while (pParent)
+ {
+ if (pParent->enmKind == kVBCppExprKind_Unary)
+ {
+ if (pParent->u.Unary.enmOperator == kVBCppUnaryOp_Parenthesis)
+ {
+ ppPlace = &pParent->u.Unary.pArg;
+ break;
+ }
+ AssertReturn(pParent->u.Unary.pArg, vbcppExprParseError(pParser, "internal error"));
+ pParent->fComplete = true;
+ }
+ else if (pParent->enmKind == kVBCppExprKind_Binary)
+ {
+ AssertReturn(pParent->u.Binary.pLeft, vbcppExprParseError(pParser, "internal error"));
+ AssertReturn(pParent->u.Binary.pRight, vbcppExprParseError(pParser, "internal error"));
+ if ((pParent->u.Binary.enmOperator & VBCPPOP_PRECEDENCE_MASK) >= (enmOp & VBCPPOP_PRECEDENCE_MASK))
+ {
+ AssertReturn(pChild, vbcppExprParseError(pParser, "internal error"));
+
+ if (pParent->u.Binary.pRight == pChild)
+ ppPlace = &pParent->u.Binary.pRight;
+ else
+ ppPlace = &pParent->u.Binary.pLeft;
+ AssertReturn(*ppPlace == pChild, vbcppExprParseError(pParser, "internal error"));
+ break;
+ }
+ pParent->fComplete = true;
+ }
+ else if (pParent->enmKind == kVBCppExprKind_Ternary)
+ {
+ return vbcppExprParseError(pParser, "The ternary operator is not implemented");
+ }
+ else
+ AssertReturn( pParent->enmKind == kVBCppExprKind_SignedValue
+ || pParent->enmKind == kVBCppExprKind_UnsignedValue,
+ vbcppExprParseError(pParser, "internal error"));
+
+ /* Up on level */
+ pChild = pParent;
+ pParent = pParent->pParent;
+ }
+
+ /*
+ * Do the rotation.
+ */
+ Assert(pChild);
+ Assert(pChild->pParent == pParent);
+ pChild->pParent = pExpr;
+
+ pExpr->u.Binary.pLeft = pChild;
+ pExpr->pParent = pParent;
+
+ if (!pParent)
+ pParser->pRoot = pExpr;
+ else
+ *ppPlace = pExpr;
+
+ pParser->ppCur = &pExpr->u.Binary.pRight;
+ pParser->pCur = pExpr;
+
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Deals with right paretheses or/and end of expression, looks for binary
+ * operators.
+ *
+ * @returns Expression status.
+ * @retval kExprRet_Ok if binary operator was found processed.
+ * @retval kExprRet_Error with msg.
+ * @retval kExprRet_EndOfExpr
+ * @param pParser The parser instance.
+ */
+static VBCPPEXPRRET vbcppExprParseBinaryOrEoeOrRparen(PVBCPPEXPRPARSER pParser)
+{
+ VBCPPEXPRRET enmRet = vbcppExprParseMaybeRParenOrEoe(pParser);
+ if (enmRet != kExprRet_Ok)
+ return enmRet;
+ return vbcppExprParseBinaryOperator(pParser);
+}
+
+
+/**
+ * Parses an identifier in the expression, replacing it by 0.
+ *
+ * All known identifiers has already been replaced by their macro values, so
+ * what's left are unknown macros. These are replaced by 0.
+ *
+ * @returns Expression status.
+ * @retval kExprRet_Value
+ * @retval kExprRet_Error with msg.
+ * @param pParser The parser instance.
+ */
+static VBCPPEXPRRET vbcppExprParseIdentifier(PVBCPPEXPRPARSER pParser)
+{
+/** @todo don't increment if it's an actively undefined macro. Need to revise
+ * the expression related code wrt selective preprocessing. */
+ pParser->cUndefined++;
+
+ /* Find the end. */
+ const char *pszMacro = pParser->pszCur;
+ const char *pszNext = pszMacro + 1;
+ while (vbcppIsCIdentifierChar(*pszNext))
+ pszNext++;
+ size_t cchMacro = pszNext - pszMacro;
+
+ /* Create a signed value node. */
+ PVBCPPEXPR pExpr = vbcppExprParseAllocNode(pParser);
+ if (!pExpr)
+ return kExprRet_Error;
+ pExpr->fComplete = true;
+ pExpr->enmKind = kVBCppExprKind_UnsignedValue;
+ pExpr->u.UnsignedValue.u64 = 0;
+
+ /* Link it. */
+ pExpr->pParent = pParser->pCur;
+ pParser->pCur = pExpr;
+ *pParser->ppCur = pExpr;
+ pParser->ppCur = NULL;
+
+ /* Skip spaces and check for parenthesis. */
+ pParser->pszCur = pszNext;
+ vbcppExprParseSkipWhiteSpace(pParser);
+ if (*pParser->pszCur == '(')
+ return vbcppExprParseError(pParser, "Unknown unary operator '%.*s'", cchMacro, pszMacro);
+
+ return kExprRet_Value;
+}
+
+
+/**
+ * Parses an numeric constant in the expression.
+ *
+ * @returns Expression status.
+ * @retval kExprRet_Value
+ * @retval kExprRet_Error with msg.
+ * @param pParser The parser instance.
+ */
+static VBCPPEXPRRET vbcppExprParseNumber(PVBCPPEXPRPARSER pParser)
+{
+ bool fSigned;
+ char *pszNext;
+ uint64_t u64;
+ char ch = *pParser->pszCur++;
+ char ch2 = *pParser->pszCur;
+ if ( ch == '0'
+ && (ch == 'x' || ch == 'X'))
+ {
+ ch2 = *++pParser->pszCur;
+ if (!RT_C_IS_XDIGIT(ch2))
+ return vbcppExprParseError(pParser, "Expected hex digit following '0x'");
+ int rc = RTStrToUInt64Ex(pParser->pszCur, &pszNext, 16, &u64);
+ if ( RT_FAILURE(rc)
+ || rc == VWRN_NUMBER_TOO_BIG)
+ return vbcppExprParseError(pParser, "Invalid hex value '%.20s...' (%Rrc)", pParser->pszCur, rc);
+ fSigned = false;
+ }
+ else if (ch == '0')
+ {
+ int rc = RTStrToUInt64Ex(pParser->pszCur - 1, &pszNext, 8, &u64);
+ if ( RT_FAILURE(rc)
+ || rc == VWRN_NUMBER_TOO_BIG)
+ return vbcppExprParseError(pParser, "Invalid octal value '%.20s...' (%Rrc)", pParser->pszCur, rc);
+ fSigned = u64 > (uint64_t)INT64_MAX ? false : true;
+ }
+ else
+ {
+ int rc = RTStrToUInt64Ex(pParser->pszCur - 1, &pszNext, 10, &u64);
+ if ( RT_FAILURE(rc)
+ || rc == VWRN_NUMBER_TOO_BIG)
+ return vbcppExprParseError(pParser, "Invalid decimal value '%.20s...' (%Rrc)", pParser->pszCur, rc);
+ fSigned = u64 > (uint64_t)INT64_MAX ? false : true;
+ }
+
+ /* suffix. */
+ if (vbcppIsCIdentifierLeadChar(*pszNext))
+ {
+ size_t cchSuffix = 1;
+ while (vbcppIsCIdentifierLeadChar(pszNext[cchSuffix]))
+ cchSuffix++;
+
+ if (cchSuffix == '1' && (*pszNext == 'u' || *pszNext == 'U'))
+ fSigned = false;
+ else if ( cchSuffix == '1'
+ && (*pszNext == 'l' || *pszNext == 'L'))
+ fSigned = true;
+ else if ( cchSuffix == '2'
+ && (!strncmp(pszNext, "ul", 2) || !strncmp(pszNext, "UL", 2)))
+ fSigned = false;
+ else if ( cchSuffix == '2'
+ && (!strncmp(pszNext, "ll", 2) || !strncmp(pszNext, "LL", 2)))
+ fSigned = true;
+ else if ( cchSuffix == '3'
+ && (!strncmp(pszNext, "ull", 3) || !strncmp(pszNext, "ULL", 3)))
+ fSigned = false;
+ else
+ return vbcppExprParseError(pParser, "Invalid number suffix '%.*s'", cchSuffix, pszNext);
+
+ pszNext += cchSuffix;
+ }
+ pParser->pszCur = pszNext;
+
+ /* Create a signed value node. */
+ PVBCPPEXPR pExpr = vbcppExprParseAllocNode(pParser);
+ if (!pExpr)
+ return kExprRet_Error;
+ pExpr->fComplete = true;
+ if (fSigned)
+ {
+ pExpr->enmKind = kVBCppExprKind_SignedValue;
+ pExpr->u.SignedValue.s64 = (int64_t)u64;
+ }
+ else
+ {
+ pExpr->enmKind = kVBCppExprKind_UnsignedValue;
+ pExpr->u.UnsignedValue.u64 = u64;
+ }
+
+ /* Link it. */
+ pExpr->pParent = pParser->pCur;
+ pParser->pCur = pExpr;
+ *pParser->ppCur = pExpr;
+ pParser->ppCur = NULL;
+
+ return kExprRet_Value;
+}
+
+
+/**
+ * Parses an character constant in the expression.
+ *
+ * @returns Expression status.
+ * @retval kExprRet_Value
+ * @retval kExprRet_Error with msg.
+ * @param pParser The parser instance.
+ */
+static VBCPPEXPRRET vbcppExprParseCharacterConstant(PVBCPPEXPRPARSER pParser)
+{
+ Assert(*pParser->pszCur == '\'');
+ pParser->pszCur++;
+ char ch2 = *pParser->pszCur++;
+ if (ch2 == '\'')
+ return vbcppExprParseError(pParser, "Empty character constant");
+ int64_t s64;
+ if (ch2 == '\\')
+ {
+ ch2 = *pParser->pszCur++;
+ switch (ch2)
+ {
+ case '0': s64 = 0x00; break;
+ case 'n': s64 = 0x0d; break;
+ case 'r': s64 = 0x0a; break;
+ case 't': s64 = 0x09; break;
+ default:
+ return vbcppExprParseError(pParser, "Escape character '%c' is not implemented", ch2);
+ }
+ }
+ else
+ s64 = ch2;
+ if (*pParser->pszCur != '\'')
+ return vbcppExprParseError(pParser, "Character constant contains more than one character");
+
+ /* Create a signed value node. */
+ PVBCPPEXPR pExpr = vbcppExprParseAllocNode(pParser);
+ if (!pExpr)
+ return kExprRet_Error;
+ pExpr->fComplete = true;
+ pExpr->enmKind = kVBCppExprKind_SignedValue;
+ pExpr->u.SignedValue.s64 = s64;
+
+ /* Link it. */
+ pExpr->pParent = pParser->pCur;
+ pParser->pCur = pExpr;
+ *pParser->ppCur = pExpr;
+ pParser->ppCur = NULL;
+
+ return kExprRet_Value;
+}
+
+
+/**
+ * Parses a unary operator or a value.
+ *
+ * @returns Expression status.
+ * @retval kExprRet_Value if value was found and processed.
+ * @retval kExprRet_UnaryOperator if an unary operator was found and processed.
+ * @retval kExprRet_Error with msg.
+ * @param pParser The parser instance.
+ */
+static VBCPPEXPRRET vbcppExprParseUnaryOrValue(PVBCPPEXPRPARSER pParser)
+{
+ vbcppExprParseSkipWhiteSpace(pParser);
+ char ch = *pParser->pszCur;
+ if (ch == '\0')
+ return vbcppExprParseError(pParser, "Premature end of expression");
+
+ /*
+ * Value?
+ */
+ if (ch == '\'')
+ return vbcppExprParseCharacterConstant(pParser);
+ if (RT_C_IS_DIGIT(ch))
+ return vbcppExprParseNumber(pParser);
+ if (ch == '"')
+ return vbcppExprParseError(pParser, "String litteral");
+ if (vbcppIsCIdentifierLeadChar(ch))
+ return vbcppExprParseIdentifier(pParser);
+
+ /*
+ * Operator?
+ */
+ VBCPPUNARYOP enmOperator;
+ if (ch == '+')
+ {
+ enmOperator = kVBCppUnaryOp_Pluss;
+ if (pParser->pszCur[1] == '+')
+ return vbcppExprParseError(pParser, "The prefix increment operator is not valid in a preprocessor expression");
+ }
+ else if (ch == '-')
+ {
+ enmOperator = kVBCppUnaryOp_Minus;
+ if (pParser->pszCur[1] == '-')
+ return vbcppExprParseError(pParser, "The prefix decrement operator is not valid in a preprocessor expression");
+ }
+ else if (ch == '!')
+ enmOperator = kVBCppUnaryOp_LogicalNot;
+ else if (ch == '~')
+ enmOperator = kVBCppUnaryOp_BitwiseNot;
+ else if (ch == '(')
+ enmOperator = kVBCppUnaryOp_Parenthesis;
+ else
+ return vbcppExprParseError(pParser, "Unknown token '%.*s'", 32, pParser->pszCur - 1);
+ pParser->pszCur++;
+
+ /* Create an operator node. */
+ PVBCPPEXPR pExpr = vbcppExprParseAllocNode(pParser);
+ if (!pExpr)
+ return kExprRet_Error;
+ pExpr->fComplete = false;
+ pExpr->enmKind = kVBCppExprKind_Unary;
+ pExpr->u.Unary.enmOperator = enmOperator;
+ pExpr->u.Unary.pArg = NULL;
+
+ /* Link it into the tree. */
+ pExpr->pParent = pParser->pCur;
+ pParser->pCur = pExpr;
+ *pParser->ppCur = pExpr;
+ pParser->ppCur = &pExpr->u.Unary.pArg;
+
+ return kExprRet_UnaryOperator;
+}
+
+
+/**
+ * Parses an expanded preprocessor expression.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pszExpr The expression to parse.
+ * @param cchExpr The length of the expression in case we need it.
+ * @param ppExprTree Where to return the parse tree.
+ * @param pcUndefined Where to return the number of unknown undefined
+ * macros. Optional.
+ */
+static RTEXITCODE vbcppExprParse(PVBCPP pThis, char *pszExpr, size_t cchExpr, PVBCPPEXPR *ppExprTree, size_t *pcUndefined)
+{
+ RTEXITCODE rcExit = RTEXITCODE_FAILURE;
+ NOREF(cchExpr);
+
+ /*
+ * Initialize the parser context structure.
+ */
+ VBCPPEXPRPARSER Parser;
+ Parser.pszCur = pszExpr;
+ Parser.pRoot = NULL;
+ Parser.pCur = NULL;
+ Parser.ppCur = &Parser.pRoot;
+ Parser.pszExpr = pszExpr;
+ Parser.cUndefined = 0;
+ Parser.pThis = pThis;
+
+ /*
+ * Do the parsing.
+ */
+ VBCPPEXPRRET enmRet;
+ for (;;)
+ {
+ /*
+ * Eat unary operators until we hit a value.
+ */
+ do
+ enmRet = vbcppExprParseUnaryOrValue(&Parser);
+ while (enmRet == kExprRet_UnaryOperator);
+ if (enmRet == kExprRet_Error)
+ break;
+ AssertBreakStmt(enmRet == kExprRet_Value, enmRet = vbcppExprParseError(&Parser, "Expected value (enmRet=%d)", enmRet));
+
+ /*
+ * Non-unary operator, right parenthesis or end of expression is up next.
+ */
+ enmRet = vbcppExprParseBinaryOrEoeOrRparen(&Parser);
+ if (enmRet == kExprRet_Error)
+ break;
+ if (enmRet == kExprRet_EndOfExpr)
+ {
+ /** @todo check if there are any open parentheses. */
+ rcExit = RTEXITCODE_SUCCESS;
+ break;
+ }
+ AssertBreakStmt(enmRet == kExprRet_Ok, enmRet = vbcppExprParseError(&Parser, "Expected value (enmRet=%d)", enmRet));
+ }
+
+ if (rcExit != RTEXITCODE_SUCCESS)
+ {
+ vbcppExprDestoryTree(Parser.pRoot);
+ return rcExit;
+ }
+
+ if (pcUndefined)
+ *pcUndefined = Parser.cUndefined;
+ *ppExprTree = Parser.pRoot;
+ return rcExit;
+}
+
+
+/**
+ * Checks if an expression value value is evaluates to @c true or @c false.
+ *
+ * @returns @c true or @c false.
+ * @param pExpr The value expression.
+ */
+static bool vbcppExprIsExprTrue(PVBCPPEXPR pExpr)
+{
+ Assert(pExpr->enmKind == kVBCppExprKind_SignedValue || pExpr->enmKind == kVBCppExprKind_UnsignedValue);
+
+ return pExpr->enmKind == kVBCppExprKind_SignedValue
+ ? pExpr->u.SignedValue.s64 != 0
+ : pExpr->u.UnsignedValue.u64 != 0;
+}
+
+
+/**
+ * Evalutes a parse (sub-)tree.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pRoot The root of the parse (sub-)tree.
+ * @param pResult Where to store the result value.
+ */
+static RTEXITCODE vbcppExprEvaluteTree(PVBCPP pThis, PVBCPPEXPR pRoot, PVBCPPEXPR pResult)
+{
+ RTEXITCODE rcExit;
+ switch (pRoot->enmKind)
+ {
+ case kVBCppExprKind_SignedValue:
+ pResult->enmKind = kVBCppExprKind_SignedValue;
+ pResult->u.SignedValue.s64 = pRoot->u.SignedValue.s64;
+ return RTEXITCODE_SUCCESS;
+
+ case kVBCppExprKind_UnsignedValue:
+ pResult->enmKind = kVBCppExprKind_UnsignedValue;
+ pResult->u.UnsignedValue.u64 = pRoot->u.UnsignedValue.u64;
+ return RTEXITCODE_SUCCESS;
+
+ case kVBCppExprKind_Unary:
+ rcExit = vbcppExprEvaluteTree(pThis, pRoot->u.Unary.pArg, pResult);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+ /* Apply the unary operator to the value */
+ switch (pRoot->u.Unary.enmOperator)
+ {
+ case kVBCppUnaryOp_Minus:
+ if (pResult->enmKind == kVBCppExprKind_SignedValue)
+ pResult->u.SignedValue.s64 = -pResult->u.SignedValue.s64;
+ else
+ pResult->u.UnsignedValue.u64 = (uint64_t)-(int64_t)pResult->u.UnsignedValue.u64;
+ break;
+
+ case kVBCppUnaryOp_LogicalNot:
+ if (pResult->enmKind == kVBCppExprKind_SignedValue)
+ pResult->u.SignedValue.s64 = !pResult->u.SignedValue.s64;
+ else
+ pResult->u.UnsignedValue.u64 = !pResult->u.UnsignedValue.u64;
+ break;
+
+ case kVBCppUnaryOp_BitwiseNot:
+ if (pResult->enmKind == kVBCppExprKind_SignedValue)
+ pResult->u.SignedValue.s64 = ~pResult->u.SignedValue.s64;
+ else
+ pResult->u.UnsignedValue.u64 = ~pResult->u.UnsignedValue.u64;
+ break;
+
+ case kVBCppUnaryOp_Pluss:
+ case kVBCppUnaryOp_Parenthesis:
+ /* do nothing. */
+ break;
+
+ default:
+ return vbcppError(pThis, "Internal error: u.Unary.enmOperator=%d", pRoot->u.Unary.enmOperator);
+ }
+ return RTEXITCODE_SUCCESS;
+
+ case kVBCppExprKind_Binary:
+ {
+ /* Always evalute the left side. */
+ rcExit = vbcppExprEvaluteTree(pThis, pRoot->u.Binary.pLeft, pResult);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+ /* If logical AND or OR we can sometimes skip evaluting the right side. */
+ if ( pRoot->u.Binary.enmOperator == kVBCppBinary_LogicalAnd
+ && !vbcppExprIsExprTrue(pResult))
+ return RTEXITCODE_SUCCESS;
+
+ if ( pRoot->u.Binary.enmOperator == kVBCppBinary_LogicalOr
+ && vbcppExprIsExprTrue(pResult))
+ return RTEXITCODE_SUCCESS;
+
+ /* Evalute the right side. */
+ VBCPPEXPR Result2;
+ rcExit = vbcppExprEvaluteTree(pThis, pRoot->u.Binary.pRight, &Result2);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+ /* If one of them is unsigned, promote the other to unsigned as well. */
+ if ( pResult->enmKind == kVBCppExprKind_UnsignedValue
+ && Result2.enmKind == kVBCppExprKind_SignedValue)
+ {
+ Result2.enmKind = kVBCppExprKind_UnsignedValue;
+ Result2.u.UnsignedValue.u64 = Result2.u.SignedValue.s64;
+ }
+ else if ( pResult->enmKind == kVBCppExprKind_SignedValue
+ && Result2.enmKind == kVBCppExprKind_UnsignedValue)
+ {
+ pResult->enmKind = kVBCppExprKind_UnsignedValue;
+ pResult->u.UnsignedValue.u64 = pResult->u.SignedValue.s64;
+ }
+
+ /* Perform the operation. */
+ if (pResult->enmKind == kVBCppExprKind_UnsignedValue)
+ {
+ switch (pRoot->u.Binary.enmOperator)
+ {
+ case kVBCppBinary_Multiplication:
+ pResult->u.UnsignedValue.u64 *= Result2.u.UnsignedValue.u64;
+ break;
+ case kVBCppBinary_Division:
+ if (!Result2.u.UnsignedValue.u64)
+ return vbcppError(pThis, "Divide by zero");
+ pResult->u.UnsignedValue.u64 /= Result2.u.UnsignedValue.u64;
+ break;
+ case kVBCppBinary_Modulo:
+ if (!Result2.u.UnsignedValue.u64)
+ return vbcppError(pThis, "Divide by zero");
+ pResult->u.UnsignedValue.u64 %= Result2.u.UnsignedValue.u64;
+ break;
+ case kVBCppBinary_Addition:
+ pResult->u.UnsignedValue.u64 += Result2.u.UnsignedValue.u64;
+ break;
+ case kVBCppBinary_Subtraction:
+ pResult->u.UnsignedValue.u64 -= Result2.u.UnsignedValue.u64;
+ break;
+ case kVBCppBinary_LeftShift:
+ pResult->u.UnsignedValue.u64 <<= Result2.u.UnsignedValue.u64;
+ break;
+ case kVBCppBinary_RightShift:
+ pResult->u.UnsignedValue.u64 >>= Result2.u.UnsignedValue.u64;
+ break;
+ case kVBCppBinary_LessThan:
+ pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 < Result2.u.UnsignedValue.u64;
+ break;
+ case kVBCppBinary_LessThanOrEqual:
+ pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 <= Result2.u.UnsignedValue.u64;
+ break;
+ case kVBCppBinary_GreaterThan:
+ pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 > Result2.u.UnsignedValue.u64;
+ break;
+ case kVBCppBinary_GreaterThanOrEqual:
+ pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 >= Result2.u.UnsignedValue.u64;
+ break;
+ case kVBCppBinary_EqualTo:
+ pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 == Result2.u.UnsignedValue.u64;
+ break;
+ case kVBCppBinary_NotEqualTo:
+ pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 != Result2.u.UnsignedValue.u64;
+ break;
+ case kVBCppBinary_BitwiseAnd:
+ pResult->u.UnsignedValue.u64 &= Result2.u.UnsignedValue.u64;
+ break;
+ case kVBCppBinary_BitwiseXor:
+ pResult->u.UnsignedValue.u64 ^= Result2.u.UnsignedValue.u64;
+ break;
+ case kVBCppBinary_BitwiseOr:
+ pResult->u.UnsignedValue.u64 |= Result2.u.UnsignedValue.u64;
+ break;
+ case kVBCppBinary_LogicalAnd:
+ pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 && Result2.u.UnsignedValue.u64;
+ break;
+ case kVBCppBinary_LogicalOr:
+ pResult->u.UnsignedValue.u64 = pResult->u.UnsignedValue.u64 || Result2.u.UnsignedValue.u64;
+ break;
+ default:
+ return vbcppError(pThis, "Internal error: u.Binary.enmOperator=%d", pRoot->u.Binary.enmOperator);
+ }
+ }
+ else
+ {
+ switch (pRoot->u.Binary.enmOperator)
+ {
+ case kVBCppBinary_Multiplication:
+ pResult->u.SignedValue.s64 *= Result2.u.SignedValue.s64;
+ break;
+ case kVBCppBinary_Division:
+ if (!Result2.u.SignedValue.s64)
+ return vbcppError(pThis, "Divide by zero");
+ pResult->u.SignedValue.s64 /= Result2.u.SignedValue.s64;
+ break;
+ case kVBCppBinary_Modulo:
+ if (!Result2.u.SignedValue.s64)
+ return vbcppError(pThis, "Divide by zero");
+ pResult->u.SignedValue.s64 %= Result2.u.SignedValue.s64;
+ break;
+ case kVBCppBinary_Addition:
+ pResult->u.SignedValue.s64 += Result2.u.SignedValue.s64;
+ break;
+ case kVBCppBinary_Subtraction:
+ pResult->u.SignedValue.s64 -= Result2.u.SignedValue.s64;
+ break;
+ case kVBCppBinary_LeftShift:
+ pResult->u.SignedValue.s64 <<= Result2.u.SignedValue.s64;
+ break;
+ case kVBCppBinary_RightShift:
+ pResult->u.SignedValue.s64 >>= Result2.u.SignedValue.s64;
+ break;
+ case kVBCppBinary_LessThan:
+ pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 < Result2.u.SignedValue.s64;
+ break;
+ case kVBCppBinary_LessThanOrEqual:
+ pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 <= Result2.u.SignedValue.s64;
+ break;
+ case kVBCppBinary_GreaterThan:
+ pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 > Result2.u.SignedValue.s64;
+ break;
+ case kVBCppBinary_GreaterThanOrEqual:
+ pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 >= Result2.u.SignedValue.s64;
+ break;
+ case kVBCppBinary_EqualTo:
+ pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 == Result2.u.SignedValue.s64;
+ break;
+ case kVBCppBinary_NotEqualTo:
+ pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 != Result2.u.SignedValue.s64;
+ break;
+ case kVBCppBinary_BitwiseAnd:
+ pResult->u.SignedValue.s64 &= Result2.u.SignedValue.s64;
+ break;
+ case kVBCppBinary_BitwiseXor:
+ pResult->u.SignedValue.s64 ^= Result2.u.SignedValue.s64;
+ break;
+ case kVBCppBinary_BitwiseOr:
+ pResult->u.SignedValue.s64 |= Result2.u.SignedValue.s64;
+ break;
+ case kVBCppBinary_LogicalAnd:
+ pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 && Result2.u.SignedValue.s64;
+ break;
+ case kVBCppBinary_LogicalOr:
+ pResult->u.SignedValue.s64 = pResult->u.SignedValue.s64 || Result2.u.SignedValue.s64;
+ break;
+ default:
+ return vbcppError(pThis, "Internal error: u.Binary.enmOperator=%d", pRoot->u.Binary.enmOperator);
+ }
+ }
+ return rcExit;
+ }
+
+ case kVBCppExprKind_Ternary:
+ rcExit = vbcppExprEvaluteTree(pThis, pRoot->u.Ternary.pExpr, pResult);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ if (vbcppExprIsExprTrue(pResult))
+ return vbcppExprEvaluteTree(pThis, pRoot->u.Ternary.pTrue, pResult);
+ return vbcppExprEvaluteTree(pThis, pRoot->u.Ternary.pFalse, pResult);
+
+ default:
+ return vbcppError(pThis, "Internal error: enmKind=%d", pRoot->enmKind);
+ }
+}
+
+
+/**
+ * Evalutes the expression.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pszExpr The expression.
+ * @param cchExpr The length of the expression.
+ * @param penmResult Where to store the result.
+ */
+static RTEXITCODE vbcppExprEval(PVBCPP pThis, char *pszExpr, size_t cchExpr, size_t cReplacements, VBCPPEVAL *penmResult)
+{
+ Assert(strlen(pszExpr) == cchExpr);
+ RT_NOREF_PV(cReplacements);
+
+ size_t cUndefined;
+ PVBCPPEXPR pExprTree;
+ RTEXITCODE rcExit = vbcppExprParse(pThis, pszExpr, cchExpr, &pExprTree, &cUndefined);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ if ( !cUndefined
+ || pThis->enmMode == kVBCppMode_SelectiveD
+ || pThis->enmMode == kVBCppMode_Standard)
+ {
+ VBCPPEXPR Result;
+ rcExit = vbcppExprEvaluteTree(pThis, pExprTree, &Result);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ if (vbcppExprIsExprTrue(&Result))
+ *penmResult = kVBCppEval_True;
+ else
+ *penmResult = kVBCppEval_False;
+ }
+ }
+ else
+ *penmResult = kVBCppEval_Undecided;
+ }
+ return rcExit;
+}
+
+
+static RTEXITCODE vbcppExtractSkipCommentLine(PVBCPP pThis, PSCMSTREAM pStrmInput)
+{
+ RT_NOREF_PV(pThis);
+
+ unsigned chPrev = ScmStreamGetCh(pStrmInput); Assert(chPrev == '/');
+ unsigned ch;
+ while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0)
+ {
+ if (ch == '\r' || ch == '\n')
+ {
+ if (chPrev != '\\')
+ break;
+ ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1);
+ chPrev = ch;
+ }
+ else
+ {
+ chPrev = ScmStreamGetCh(pStrmInput);
+ Assert(chPrev == ch);
+ }
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE vbcppExtractSkipComment(PVBCPP pThis, PSCMSTREAM pStrmInput)
+{
+ unsigned ch = ScmStreamGetCh(pStrmInput); Assert(ch == '*');
+ while ((ch = ScmStreamGetCh(pStrmInput)) != ~(unsigned)0)
+ {
+ if (ch == '*')
+ {
+ ch = ScmStreamGetCh(pStrmInput);
+ if (ch == '/')
+ return RTEXITCODE_SUCCESS;
+ }
+ }
+ return vbcppError(pThis, "Expected '*/'");
+}
+
+
+static RTEXITCODE vbcppExtractQuotedString(PVBCPP pThis, PSCMSTREAM pStrmInput, PVBCPPSTRBUF pStrBuf,
+ char chOpen, char chClose)
+{
+ unsigned ch = ScmStreamGetCh(pStrmInput);
+ Assert(ch == (unsigned)chOpen);
+
+ RTEXITCODE rcExit = vbcppStrBufAppendCh(pStrBuf, chOpen);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+ for (;;)
+ {
+ ch = ScmStreamGetCh(pStrmInput);
+ if (ch == '\\')
+ {
+ ch = ScmStreamGetCh(pStrmInput);
+ if (ch == ~(unsigned)0)
+ break;
+ rcExit = vbcppStrBufAppendCh(pStrBuf, '\\');
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = vbcppStrBufAppendCh(pStrBuf, ch);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ }
+ else if (ch != ~(unsigned)0)
+ {
+ rcExit = vbcppStrBufAppendCh(pStrBuf, ch);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ if (ch == (unsigned)chClose)
+ return RTEXITCODE_SUCCESS;
+ }
+ else
+ break;
+ }
+
+ return vbcppError(pThis, "File ended with an open character constant");
+}
+
+
+/**
+ * Extracts a line from the stream, stripping it for comments and maybe
+ * optimzing some of the whitespace.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ * @param pStrBuf Where to store the extracted line. Caller must
+ * initialize this prior to the call an delete it
+ * after use (even on failure).
+ * @param poffComment Where to note down the position of the final
+ * comment. Optional.
+ */
+static RTEXITCODE vbcppExtractDirectiveLine(PVBCPP pThis, PSCMSTREAM pStrmInput, PVBCPPSTRBUF pStrBuf, size_t *poffComment)
+{
+ size_t offComment = ~(size_t)0;
+ unsigned ch;
+ while ((ch = ScmStreamPeekCh(pStrmInput)) != ~(unsigned)0)
+ {
+ RTEXITCODE rcExit;
+ if (ch == '/')
+ {
+ /* Comment? */
+ unsigned ch2 = ScmStreamGetCh(pStrmInput); Assert(ch == ch2); NOREF(ch2);
+ ch = ScmStreamPeekCh(pStrmInput);
+ if (ch == '*')
+ {
+ offComment = ScmStreamTell(pStrmInput) - 1;
+ rcExit = vbcppExtractSkipComment(pThis, pStrmInput);
+ }
+ else if (ch == '/')
+ {
+ offComment = ScmStreamTell(pStrmInput) - 1;
+ rcExit = vbcppExtractSkipCommentLine(pThis, pStrmInput);
+ }
+ else
+ rcExit = vbcppStrBufAppendCh(pStrBuf, '/');
+ }
+ else if (ch == '\'')
+ {
+ offComment = ~(size_t)0;
+ rcExit = vbcppExtractQuotedString(pThis, pStrmInput, pStrBuf, '\'', '\'');
+ }
+ else if (ch == '"')
+ {
+ offComment = ~(size_t)0;
+ rcExit = vbcppExtractQuotedString(pThis, pStrmInput, pStrBuf, '"', '"');
+ }
+ else if (ch == '\r' || ch == '\n')
+ break; /* done */
+ else if ( RT_C_IS_SPACE(ch)
+ && ( RT_C_IS_SPACE(vbcppStrBufLastCh(pStrBuf))
+ || vbcppStrBufLastCh(pStrBuf) == '\0') )
+ {
+ unsigned ch2 = ScmStreamGetCh(pStrmInput);
+ Assert(ch == ch2); NOREF(ch2);
+ rcExit = RTEXITCODE_SUCCESS;
+ }
+ else
+ {
+ unsigned ch2 = ScmStreamGetCh(pStrmInput); Assert(ch == ch2);
+
+ /* Escaped newline? */
+ if ( ch == '\\'
+ && ( (ch2 = ScmStreamPeekCh(pStrmInput)) == '\r'
+ || ch2 == '\n'))
+ {
+ ScmStreamSeekByLine(pStrmInput, ScmStreamTellLine(pStrmInput) + 1);
+ rcExit = RTEXITCODE_SUCCESS;
+ }
+ else
+ {
+ offComment = ~(size_t)0;
+ rcExit = vbcppStrBufAppendCh(pStrBuf, ch);
+ }
+ }
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ }
+
+ if (poffComment)
+ *poffComment = offComment;
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Processes a abbreviated line number directive.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ * @param offStart The stream position where the directive
+ * started (for pass thru).
+ * @param enmKind The kind of directive we're processing.
+ */
+static RTEXITCODE vbcppDirectiveIfOrElif(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart,
+ VBCPPCONDKIND enmKind)
+{
+ /*
+ * Check for missing #if if #elif.
+ */
+ if ( enmKind == kVBCppCondKind_ElIf
+ && !pThis->pCondStack )
+ return vbcppError(pThis, "#elif without #if");
+
+ /*
+ * Extract the expression string.
+ */
+ const char *pchCondition = ScmStreamGetCur(pStrmInput);
+ size_t offComment;
+ VBCPPMACROEXP ExpCtx;
+#if 0
+ ExpCtx.pMacroStack = NULL;
+#endif
+ ExpCtx.pStrmInput = NULL;
+ ExpCtx.papszArgs = NULL;
+ ExpCtx.cArgs = 0;
+ ExpCtx.cArgsAlloced = 0;
+ vbcppStrBufInit(&ExpCtx.StrBuf, pThis);
+ RTEXITCODE rcExit = vbcppExtractDirectiveLine(pThis, pStrmInput, &ExpCtx.StrBuf, &offComment);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ size_t const cchCondition = ScmStreamGetCur(pStrmInput) - pchCondition;
+
+ /*
+ * Expand known macros in it.
+ */
+ size_t cReplacements;
+ rcExit = vbcppMacroExpandReScan(pThis, &ExpCtx, kMacroReScanMode_Expression, &cReplacements);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Strip it and check that it's not empty.
+ */
+ char *pszExpr = ExpCtx.StrBuf.pszBuf;
+ size_t cchExpr = ExpCtx.StrBuf.cchBuf;
+ while (cchExpr > 0 && RT_C_IS_SPACE(*pszExpr))
+ pszExpr++, cchExpr--;
+
+ while (cchExpr > 0 && RT_C_IS_SPACE(pszExpr[cchExpr - 1]))
+ {
+ pszExpr[--cchExpr] = '\0';
+ ExpCtx.StrBuf.cchBuf--;
+ }
+ if (cchExpr)
+ {
+ /*
+ * Now, evalute the expression.
+ */
+ VBCPPEVAL enmResult;
+ rcExit = vbcppExprEval(pThis, pszExpr, cchExpr, cReplacements, &enmResult);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Take action.
+ */
+ if (enmKind != kVBCppCondKind_ElIf)
+ rcExit = vbcppCondPush(pThis, pStrmInput, offComment, enmKind, enmResult,
+ pchCondition, cchCondition);
+ else
+ {
+ PVBCPPCOND pCond = pThis->pCondStack;
+ if ( pCond->enmResult != kVBCppEval_Undecided
+ && ( !pCond->pUp
+ || pCond->pUp->enmStackResult == kVBCppEval_True))
+ {
+ Assert(enmResult == kVBCppEval_True || enmResult == kVBCppEval_False);
+ if ( pCond->enmResult == kVBCppEval_False
+ && enmResult == kVBCppEval_True
+ && !pCond->fElIfDecided)
+ {
+ pCond->enmStackResult = kVBCppEval_True;
+ pCond->fElIfDecided = true;
+ }
+ else
+ pCond->enmStackResult = kVBCppEval_False;
+ pThis->fIf0Mode = pCond->enmStackResult == kVBCppEval_False;
+ }
+ pCond->enmKind = kVBCppCondKind_ElIf;
+ pCond->enmResult = enmResult;
+ pCond->pchCond = pchCondition;
+ pCond->cchCond = cchCondition;
+
+ /*
+ * Do #elif pass thru.
+ */
+ if ( !pThis->fIf0Mode
+ && pCond->enmResult == kVBCppEval_Undecided)
+ {
+ ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*selif", pCond->iKeepLevel - 1, "");
+ if (cch > 0)
+ rcExit = vbcppOutputComment(pThis, pStrmInput, offStart, cch, 2);
+ else
+ rcExit = vbcppError(pThis, "Output error %Rrc", (int)cch);
+ }
+ else
+ pThis->fJustDroppedLine = true;
+ }
+ }
+ }
+ else
+ rcExit = vbcppError(pThis, "Empty #if expression");
+ }
+ }
+ vbcppMacroExpandCleanup(&ExpCtx);
+ return rcExit;
+}
+
+
+/**
+ * Processes a abbreviated line number directive.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ * @param offStart The stream position where the directive
+ * started (for pass thru).
+ */
+static RTEXITCODE vbcppDirectiveIfDef(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart)
+{
+ /*
+ * Parse it.
+ */
+ RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ size_t cchDefine;
+ const char *pchDefine = ScmStreamCGetWord(pStrmInput, &cchDefine);
+ if (pchDefine)
+ {
+ rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Evaluate it.
+ */
+ VBCPPEVAL enmEval;
+ if (vbcppMacroExists(pThis, pchDefine, cchDefine))
+ enmEval = kVBCppEval_True;
+ else if ( !pThis->fUndecidedConditionals
+ || RTStrSpaceGetN(&pThis->UndefStrSpace, pchDefine, cchDefine) != NULL)
+ enmEval = kVBCppEval_False;
+ else
+ enmEval = kVBCppEval_Undecided;
+ rcExit = vbcppCondPush(pThis, pStrmInput, offStart, kVBCppCondKind_IfDef, enmEval,
+ pchDefine, cchDefine);
+ }
+ }
+ else
+ rcExit = vbcppError(pThis, "Malformed #ifdef");
+ }
+ return rcExit;
+}
+
+
+/**
+ * Processes a abbreviated line number directive.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ * @param offStart The stream position where the directive
+ * started (for pass thru).
+ */
+static RTEXITCODE vbcppDirectiveIfNDef(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart)
+{
+ /*
+ * Parse it.
+ */
+ RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ size_t cchDefine;
+ const char *pchDefine = ScmStreamCGetWord(pStrmInput, &cchDefine);
+ if (pchDefine)
+ {
+ rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Evaluate it.
+ */
+ VBCPPEVAL enmEval;
+ if (vbcppMacroExists(pThis, pchDefine, cchDefine))
+ enmEval = kVBCppEval_False;
+ else if ( !pThis->fUndecidedConditionals
+ || RTStrSpaceGetN(&pThis->UndefStrSpace, pchDefine, cchDefine) != NULL)
+ enmEval = kVBCppEval_True;
+ else
+ enmEval = kVBCppEval_Undecided;
+ rcExit = vbcppCondPush(pThis, pStrmInput, offStart, kVBCppCondKind_IfNDef, enmEval,
+ pchDefine, cchDefine);
+ }
+ }
+ else
+ rcExit = vbcppError(pThis, "Malformed #ifndef");
+ }
+ return rcExit;
+}
+
+
+/**
+ * Processes a abbreviated line number directive.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ * @param offStart The stream position where the directive
+ * started (for pass thru).
+ */
+static RTEXITCODE vbcppDirectiveElse(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart)
+{
+ /*
+ * Nothing to parse, just comment positions to find and note down.
+ */
+ offStart = vbcppProcessSkipWhite(pStrmInput);
+ RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Execute.
+ */
+ PVBCPPCOND pCond = pThis->pCondStack;
+ if (pCond)
+ {
+ if (!pCond->fSeenElse)
+ {
+ pCond->fSeenElse = true;
+ if ( pCond->enmResult != kVBCppEval_Undecided
+ && ( !pCond->pUp
+ || pCond->pUp->enmStackResult == kVBCppEval_True))
+ {
+ if ( pCond->enmResult == kVBCppEval_True
+ || pCond->fElIfDecided)
+
+ pCond->enmStackResult = kVBCppEval_False;
+ else
+ pCond->enmStackResult = kVBCppEval_True;
+ pThis->fIf0Mode = pCond->enmStackResult == kVBCppEval_False;
+ }
+
+ /*
+ * Do pass thru.
+ */
+ if ( !pThis->fIf0Mode
+ && pCond->enmResult == kVBCppEval_Undecided)
+ {
+ ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*selse", pCond->iKeepLevel - 1, "");
+ if (cch > 0)
+ rcExit = vbcppOutputComment(pThis, pStrmInput, offStart, cch, 2);
+ else
+ rcExit = vbcppError(pThis, "Output error %Rrc", (int)cch);
+ }
+ else
+ pThis->fJustDroppedLine = true;
+ }
+ else
+ rcExit = vbcppError(pThis, "Double #else or/and missing #endif");
+ }
+ else
+ rcExit = vbcppError(pThis, "#else without #if");
+ }
+ return rcExit;
+}
+
+
+/**
+ * Processes a abbreviated line number directive.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ * @param offStart The stream position where the directive
+ * started (for pass thru).
+ */
+static RTEXITCODE vbcppDirectiveEndif(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart)
+{
+ /*
+ * Nothing to parse, just comment positions to find and note down.
+ */
+ offStart = vbcppProcessSkipWhite(pStrmInput);
+ RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Execute.
+ */
+ PVBCPPCOND pCond = pThis->pCondStack;
+ if (pCond)
+ {
+ pThis->pCondStack = pCond->pUp;
+ pThis->fIf0Mode = pCond->pUp && pCond->pUp->enmStackResult == kVBCppEval_False;
+
+ /*
+ * Do pass thru.
+ */
+ if ( !pThis->fIf0Mode
+ && pCond->enmResult == kVBCppEval_Undecided)
+ {
+ ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sendif", pCond->iKeepLevel - 1, "");
+ if (cch > 0)
+ rcExit = vbcppOutputComment(pThis, pStrmInput, offStart, cch, 1);
+ else
+ rcExit = vbcppError(pThis, "Output error %Rrc", (int)cch);
+ }
+ else
+ pThis->fJustDroppedLine = true;
+ }
+ else
+ rcExit = vbcppError(pThis, "#endif without #if");
+ }
+ return rcExit;
+}
+
+
+
+
+
+/*
+ *
+ *
+ * Misc Directives
+ * Misc Directives
+ * Misc Directives
+ * Misc Directives
+ *
+ *
+ */
+
+
+/**
+ * Adds an include directory.
+ *
+ * @returns Program exit code, with error message on failure.
+ * @param pThis The C preprocessor instance.
+ * @param pszDir The directory to add.
+ */
+static RTEXITCODE vbcppAddInclude(PVBCPP pThis, const char *pszDir)
+{
+ uint32_t cIncludes = pThis->cIncludes;
+ if (cIncludes >= _64K)
+ return vbcppError(pThis, "Too many include directories");
+
+ void *pv = RTMemRealloc(pThis->papszIncludes, (cIncludes + 1) * sizeof(char **));
+ if (!pv)
+ return vbcppError(pThis, "No memory for include directories");
+ pThis->papszIncludes = (char **)pv;
+
+ int rc = RTStrDupEx(&pThis->papszIncludes[cIncludes], pszDir);
+ if (RT_FAILURE(rc))
+ return vbcppError(pThis, "No string memory for include directories");
+
+ pThis->cIncludes = cIncludes + 1;
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Processes a abbreviated line number directive.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ * @param offStart The stream position where the directive
+ * started (for pass thru).
+ */
+static RTEXITCODE vbcppDirectiveInclude(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart)
+{
+ RT_NOREF_PV(offStart);
+
+ /*
+ * Parse it.
+ */
+ RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ size_t cchFileSpec = 0;
+ const char *pchFileSpec = NULL;
+ size_t cchFilename = 0;
+ const char *pchFilename = NULL;
+
+ unsigned ch = ScmStreamPeekCh(pStrmInput);
+ unsigned chType = ch;
+ if (ch == '"' || ch == '<')
+ {
+ ScmStreamGetCh(pStrmInput);
+ pchFileSpec = pchFilename = ScmStreamGetCur(pStrmInput);
+ unsigned chEnd = chType == '<' ? '>' : '"';
+ while ( (ch = ScmStreamGetCh(pStrmInput)) != ~(unsigned)0
+ && ch != chEnd)
+ {
+ if (ch == '\r' || ch == '\n')
+ {
+ rcExit = vbcppError(pThis, "Multi-line include file specfications are not supported");
+ break;
+ }
+ }
+
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ if (ch != ~(unsigned)0)
+ cchFileSpec = cchFilename = ScmStreamGetCur(pStrmInput) - pchFilename - 1;
+ else
+ rcExit = vbcppError(pThis, "Expected '%c'", chType);
+ }
+ }
+ else if (vbcppIsCIdentifierLeadChar(ch))
+ {
+ //pchFileSpec = ScmStreamCGetWord(pStrmInput, &cchFileSpec);
+ rcExit = vbcppError(pThis, "Including via a define is not implemented yet");
+ }
+ else
+ rcExit = vbcppError(pThis, "Malformed include directive");
+
+ /*
+ * Take down the location of the next non-white space, in case we need
+ * to pass thru the directive further down. Then skip to the end of the
+ * line.
+ */
+ size_t const offIncEnd = vbcppProcessSkipWhite(pStrmInput);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = vbcppProcessSkipWhiteEscapedEolAndCommentsCheckEol(pThis, pStrmInput);
+
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Execute it.
+ */
+ if (pThis->enmIncludeAction == kVBCppIncludeAction_Include)
+ {
+ /** @todo Search for the include file and push it onto the input stack.
+ * Not difficult, just unnecessary rigth now. */
+ rcExit = vbcppError(pThis, "Includes are fully implemented");
+ }
+ else if (pThis->enmIncludeAction == kVBCppIncludeAction_PassThru)
+ {
+ /* Pretty print the passthru. */
+ unsigned cchIndent = pThis->pCondStack ? pThis->pCondStack->iKeepLevel : 0;
+ ssize_t cch;
+ if (chType == '<')
+ cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sinclude <%.*s>",
+ cchIndent, "", cchFileSpec, pchFileSpec);
+ else if (chType == '"')
+ cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sinclude \"%.*s\"",
+ cchIndent, "", cchFileSpec, pchFileSpec);
+ else
+ cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*sinclude %.*s",
+ cchIndent, "", cchFileSpec, pchFileSpec);
+ if (cch > 0)
+ rcExit = vbcppOutputComment(pThis, pStrmInput, offIncEnd, cch, 1);
+ else
+ rcExit = vbcppError(pThis, "Output error %Rrc", (int)cch);
+
+ }
+ else
+ {
+ Assert(pThis->enmIncludeAction == kVBCppIncludeAction_Drop);
+ pThis->fJustDroppedLine = true;
+ }
+ }
+ }
+ return rcExit;
+}
+
+
+/**
+ * Processes a abbreviated line number directive.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ * @param offStart The stream position where the directive
+ * started (for pass thru).
+ */
+static RTEXITCODE vbcppDirectivePragma(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart)
+{
+ RT_NOREF_PV(offStart);
+
+ /*
+ * Parse out the first word.
+ */
+ RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ size_t cchPragma;
+ const char *pchPragma = ScmStreamCGetWord(pStrmInput, &cchPragma);
+ if (pchPragma)
+ {
+ size_t const off2nd = vbcppProcessSkipWhite(pStrmInput);
+ size_t offComment;
+ rcExit = vbcppInputSkipToEndOfDirectiveLine(pThis, pStrmInput, &offComment);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * What to do about this
+ */
+ bool fPassThru = false;
+ if ( cchPragma == 1
+ && *pchPragma == 'D')
+ fPassThru = pThis->fPassThruPragmaD;
+ else if ( cchPragma == 3
+ && !strncmp(pchPragma, "STD", 3))
+ fPassThru = pThis->fPassThruPragmaSTD;
+ else
+ fPassThru = pThis->fPassThruPragmaOther;
+ if (fPassThru)
+ {
+ unsigned cchIndent = pThis->pCondStack ? pThis->pCondStack->iKeepLevel : 0;
+ ssize_t cch = ScmStreamPrintf(&pThis->StrmOutput, "#%*spragma %.*s",
+ cchIndent, "", cchPragma, pchPragma);
+ if (cch > 0)
+ rcExit = vbcppOutputComment(pThis, pStrmInput, off2nd, cch, 1);
+ else
+ rcExit = vbcppError(pThis, "output error");
+ }
+ else
+ pThis->fJustDroppedLine = true;
+ }
+ }
+ else
+ rcExit = vbcppError(pThis, "Malformed #pragma");
+ }
+
+ return rcExit;
+}
+
+
+/**
+ * Processes an error directive.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ * @param offStart The stream position where the directive
+ * started (for pass thru).
+ */
+static RTEXITCODE vbcppDirectiveError(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart)
+{
+ RT_NOREF_PV(offStart);
+ RT_NOREF_PV(pStrmInput);
+ return vbcppError(pThis, "Hit an #error");
+}
+
+
+/**
+ * Processes a abbreviated line number directive.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ * @param offStart The stream position where the directive
+ * started (for pass thru).
+ */
+static RTEXITCODE vbcppDirectiveLineNo(PVBCPP pThis, PSCMSTREAM pStrmInput, size_t offStart)
+{
+ RT_NOREF_PV(offStart);
+ RT_NOREF_PV(pStrmInput);
+ return vbcppError(pThis, "Not implemented: %s", __FUNCTION__);
+}
+
+
+/**
+ * Processes a abbreviated line number directive.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ */
+static RTEXITCODE vbcppDirectiveLineNoShort(PVBCPP pThis, PSCMSTREAM pStrmInput)
+{
+ RT_NOREF_PV(pStrmInput);
+ return vbcppError(pThis, "Not implemented: %s", __FUNCTION__);
+}
+
+
+/**
+ * Handles a preprocessor directive.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pThis The C preprocessor instance.
+ * @param pStrmInput The input stream.
+ */
+static RTEXITCODE vbcppProcessDirective(PVBCPP pThis, PSCMSTREAM pStrmInput)
+{
+ /*
+ * Get the directive and do a string switch on it.
+ */
+ RTEXITCODE rcExit = vbcppProcessSkipWhiteEscapedEolAndComments(pThis, pStrmInput);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ size_t cchDirective;
+ const char *pchDirective = ScmStreamCGetWord(pStrmInput, &cchDirective);
+ if (pchDirective)
+ {
+ size_t const offStart = ScmStreamTell(pStrmInput);
+#define IS_DIRECTIVE(a_sz) ( sizeof(a_sz) - 1 == cchDirective && strncmp(pchDirective, a_sz, sizeof(a_sz) - 1) == 0)
+ if (IS_DIRECTIVE("if"))
+ rcExit = vbcppDirectiveIfOrElif(pThis, pStrmInput, offStart, kVBCppCondKind_If);
+ else if (IS_DIRECTIVE("elif"))
+ rcExit = vbcppDirectiveIfOrElif(pThis, pStrmInput, offStart, kVBCppCondKind_ElIf);
+ else if (IS_DIRECTIVE("ifdef"))
+ rcExit = vbcppDirectiveIfDef(pThis, pStrmInput, offStart);
+ else if (IS_DIRECTIVE("ifndef"))
+ rcExit = vbcppDirectiveIfNDef(pThis, pStrmInput, offStart);
+ else if (IS_DIRECTIVE("else"))
+ rcExit = vbcppDirectiveElse(pThis, pStrmInput, offStart);
+ else if (IS_DIRECTIVE("endif"))
+ rcExit = vbcppDirectiveEndif(pThis, pStrmInput, offStart);
+ else if (!pThis->fIf0Mode)
+ {
+ if (IS_DIRECTIVE("include"))
+ rcExit = vbcppDirectiveInclude(pThis, pStrmInput, offStart);
+ else if (IS_DIRECTIVE("define"))
+ rcExit = vbcppDirectiveDefine(pThis, pStrmInput, offStart);
+ else if (IS_DIRECTIVE("undef"))
+ rcExit = vbcppDirectiveUndef(pThis, pStrmInput, offStart);
+ else if (IS_DIRECTIVE("pragma"))
+ rcExit = vbcppDirectivePragma(pThis, pStrmInput, offStart);
+ else if (IS_DIRECTIVE("error"))
+ rcExit = vbcppDirectiveError(pThis, pStrmInput, offStart);
+ else if (IS_DIRECTIVE("line"))
+ rcExit = vbcppDirectiveLineNo(pThis, pStrmInput, offStart);
+ else
+ rcExit = vbcppError(pThis, "Unknown preprocessor directive '#%.*s'", cchDirective, pchDirective);
+ }
+#undef IS_DIRECTIVE
+ }
+ else if (!pThis->fIf0Mode)
+ {
+ /* Could it be a # <num> "file" directive? */
+ unsigned ch = ScmStreamPeekCh(pStrmInput);
+ if (RT_C_IS_DIGIT(ch))
+ rcExit = vbcppDirectiveLineNoShort(pThis, pStrmInput);
+ else
+ rcExit = vbcppError(pThis, "Malformed preprocessor directive");
+ }
+ return rcExit;
+}
+
+
+/*
+ *
+ *
+ * M a i n b o d y.
+ * M a i n b o d y.
+ * M a i n b o d y.
+ * M a i n b o d y.
+ * M a i n b o d y.
+ *
+ *
+ */
+
+
+/**
+ * Does the actually preprocessing of the input file.
+ *
+ * @returns Exit code.
+ * @param pThis The C preprocessor instance.
+ */
+static RTEXITCODE vbcppPreprocess(PVBCPP pThis)
+{
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+
+ /*
+ * Parse.
+ */
+ while (pThis->pInputStack)
+ {
+ pThis->fMaybePreprocessorLine = true;
+
+ PSCMSTREAM pStrmInput = &pThis->pInputStack->StrmInput;
+ unsigned ch;
+ while ((ch = ScmStreamGetCh(pStrmInput)) != ~(unsigned)0)
+ {
+ if (ch == '/')
+ {
+ ch = ScmStreamPeekCh(pStrmInput);
+ if (ch == '*')
+ rcExit = vbcppProcessMultiLineComment(pThis, pStrmInput);
+ else if (ch == '/')
+ rcExit = vbcppProcessOneLineComment(pThis, pStrmInput);
+ else
+ {
+ pThis->fMaybePreprocessorLine = false;
+ if (!pThis->fIf0Mode)
+ rcExit = vbcppOutputCh(pThis, '/');
+ }
+ }
+ else if (ch == '#' && pThis->fMaybePreprocessorLine)
+ {
+ rcExit = vbcppProcessDirective(pThis, pStrmInput);
+ pStrmInput = &pThis->pInputStack->StrmInput;
+ }
+ else if (ch == '\r' || ch == '\n')
+ {
+ if ( ( !pThis->fIf0Mode
+ && !pThis->fJustDroppedLine)
+ || !pThis->fRemoveDroppedLines
+ || !ScmStreamIsAtStartOfLine(&pThis->StrmOutput))
+ rcExit = vbcppOutputCh(pThis, ch);
+ pThis->fJustDroppedLine = false;
+ pThis->fMaybePreprocessorLine = true;
+ }
+ else if (RT_C_IS_SPACE(ch))
+ {
+ if (!pThis->fIf0Mode)
+ rcExit = vbcppOutputCh(pThis, ch);
+ }
+ else
+ {
+ pThis->fMaybePreprocessorLine = false;
+ if (!pThis->fIf0Mode)
+ {
+ if (ch == '"')
+ rcExit = vbcppProcessStringLitteral(pThis, pStrmInput);
+ else if (ch == '\'')
+ rcExit = vbcppProcessCharacterConstant(pThis, pStrmInput);
+ else if (vbcppIsCIdentifierLeadChar(ch))
+ rcExit = vbcppProcessIdentifier(pThis, pStrmInput);
+ else if (RT_C_IS_DIGIT(ch))
+ rcExit = vbcppProcessNumber(pThis, pStrmInput, ch);
+ else
+ rcExit = vbcppOutputCh(pThis, ch);
+ }
+ }
+ if (rcExit != RTEXITCODE_SUCCESS)
+ break;
+ }
+
+ /*
+ * Check for errors.
+ */
+ if (rcExit != RTEXITCODE_SUCCESS)
+ break;
+
+ /*
+ * Pop the input stack.
+ */
+ PVBCPPINPUT pPopped = pThis->pInputStack;
+ pThis->pInputStack = pPopped->pUp;
+ RTMemFree(pPopped);
+ }
+
+ return rcExit;
+}
+
+
+/**
+ * Opens the input and output streams.
+ *
+ * @returns Exit code.
+ * @param pThis The C preprocessor instance.
+ */
+static RTEXITCODE vbcppOpenStreams(PVBCPP pThis)
+{
+ if (!pThis->pszInput)
+ return vbcppError(pThis, "Preprocessing the standard input stream is currently not supported");
+
+ size_t cchName = strlen(pThis->pszInput);
+ PVBCPPINPUT pInput = (PVBCPPINPUT)RTMemAlloc(RT_UOFFSETOF_DYN(VBCPPINPUT, szName[cchName + 1]));
+ if (!pInput)
+ return vbcppError(pThis, "out of memory");
+ pInput->pUp = pThis->pInputStack;
+ pInput->pszSpecified = pInput->szName;
+ memcpy(pInput->szName, pThis->pszInput, cchName + 1);
+ pThis->pInputStack = pInput;
+ int rc = ScmStreamInitForReading(&pInput->StrmInput, pThis->pszInput);
+ if (RT_FAILURE(rc))
+ return vbcppError(pThis, "ScmStreamInitForReading returned %Rrc when opening input file (%s)",
+ rc, pThis->pszInput);
+
+ rc = ScmStreamInitForWriting(&pThis->StrmOutput, &pInput->StrmInput);
+ if (RT_FAILURE(rc))
+ return vbcppError(pThis, "ScmStreamInitForWriting returned %Rrc", rc);
+
+ pThis->fStrmOutputValid = true;
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Changes the preprocessing mode.
+ *
+ * @param pThis The C preprocessor instance.
+ * @param enmMode The new mode.
+ */
+static void vbcppSetMode(PVBCPP pThis, VBCPPMODE enmMode)
+{
+ switch (enmMode)
+ {
+ case kVBCppMode_Standard:
+ pThis->fKeepComments = false;
+ pThis->fRespectSourceDefines = true;
+ pThis->fAllowRedefiningCmdLineDefines = true;
+ pThis->fPassThruDefines = false;
+ pThis->fUndecidedConditionals = false;
+ pThis->fPassThruPragmaD = false;
+ pThis->fPassThruPragmaSTD = true;
+ pThis->fPassThruPragmaOther = true;
+ pThis->fRemoveDroppedLines = false;
+ pThis->fLineSplicing = true;
+ pThis->enmIncludeAction = kVBCppIncludeAction_Include;
+ break;
+
+ case kVBCppMode_Selective:
+ pThis->fKeepComments = true;
+ pThis->fRespectSourceDefines = false;
+ pThis->fAllowRedefiningCmdLineDefines = false;
+ pThis->fPassThruDefines = true;
+ pThis->fUndecidedConditionals = true;
+ pThis->fPassThruPragmaD = true;
+ pThis->fPassThruPragmaSTD = true;
+ pThis->fPassThruPragmaOther = true;
+ pThis->fRemoveDroppedLines = true;
+ pThis->fLineSplicing = false;
+ pThis->enmIncludeAction = kVBCppIncludeAction_PassThru;
+ break;
+
+ case kVBCppMode_SelectiveD:
+ pThis->fKeepComments = true;
+ pThis->fRespectSourceDefines = true;
+ pThis->fAllowRedefiningCmdLineDefines = false;
+ pThis->fPassThruDefines = false;
+ pThis->fUndecidedConditionals = false;
+ pThis->fPassThruPragmaD = true;
+ pThis->fPassThruPragmaSTD = false;
+ pThis->fPassThruPragmaOther = false;
+ pThis->fRemoveDroppedLines = true;
+ pThis->fLineSplicing = false;
+ pThis->enmIncludeAction = kVBCppIncludeAction_Drop;
+ break;
+
+ default:
+ AssertFailedReturnVoid();
+ }
+ pThis->enmMode = enmMode;
+}
+
+
+/**
+ * Parses the command line options.
+ *
+ * @returns Program exit code. Exit on non-success or if *pfExit is set.
+ * @param pThis The C preprocessor instance.
+ * @param argc The argument count.
+ * @param argv The argument vector.
+ * @param pfExit Pointer to the exit indicator.
+ */
+static RTEXITCODE vbcppParseOptions(PVBCPP pThis, int argc, char **argv, bool *pfExit)
+{
+ RTEXITCODE rcExit;
+
+ *pfExit = false;
+
+ /*
+ * Option config.
+ */
+ static RTGETOPTDEF const s_aOpts[] =
+ {
+ { "--define", 'D', RTGETOPT_REQ_STRING },
+ { "--include-dir", 'I', RTGETOPT_REQ_STRING },
+ { "--undefine", 'U', RTGETOPT_REQ_STRING },
+ { "--keep-comments", 'C', RTGETOPT_REQ_NOTHING },
+ { "--strip-comments", 'c', RTGETOPT_REQ_NOTHING },
+ { "--D-strip", 'd', RTGETOPT_REQ_NOTHING },
+ };
+
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetOptState;
+ int rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertReleaseRCReturn(rc, RTEXITCODE_FAILURE);
+
+ /*
+ * Process the options.
+ */
+ while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
+ {
+ switch (rc)
+ {
+ case 'c':
+ pThis->fKeepComments = false;
+ break;
+
+ case 'C':
+ pThis->fKeepComments = false;
+ break;
+
+ case 'd':
+ vbcppSetMode(pThis, kVBCppMode_SelectiveD);
+ break;
+
+ case 'D':
+ {
+ const char *pszEqual = strchr(ValueUnion.psz, '=');
+ if (pszEqual)
+ rcExit = vbcppMacroAdd(pThis, ValueUnion.psz, pszEqual - ValueUnion.psz, pszEqual + 1, RTSTR_MAX, true);
+ else
+ rcExit = vbcppMacroAdd(pThis, ValueUnion.psz, RTSTR_MAX, "1", 1, true);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ break;
+ }
+
+ case 'I':
+ rcExit = vbcppAddInclude(pThis, ValueUnion.psz);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ break;
+
+ case 'U':
+ rcExit = vbcppMacroUndef(pThis, ValueUnion.psz, RTSTR_MAX, true);
+ break;
+
+ case 'h':
+ RTPrintf("No help yet, sorry\n");
+ *pfExit = true;
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ {
+ /* The following is assuming that svn does it's job here. */
+ static const char s_szRev[] = "$Revision: 155244 $";
+ const char *psz = RTStrStripL(strchr(s_szRev, ' '));
+ RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);
+ *pfExit = true;
+ return RTEXITCODE_SUCCESS;
+ }
+
+ case VINF_GETOPT_NOT_OPTION:
+ if (!pThis->pszInput)
+ pThis->pszInput = ValueUnion.psz;
+ else if (!pThis->pszOutput)
+ pThis->pszOutput = ValueUnion.psz;
+ else
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "too many file arguments");
+ break;
+
+
+ /*
+ * Errors and bugs.
+ */
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Terminates the preprocessor.
+ *
+ * This may return failure if an error was delayed.
+ *
+ * @returns Exit code.
+ * @param pThis The C preprocessor instance.
+ */
+static RTEXITCODE vbcppTerm(PVBCPP pThis)
+{
+ /*
+ * Flush the output first.
+ */
+ if (pThis->fStrmOutputValid)
+ {
+ if (pThis->pszOutput)
+ {
+ int rc = ScmStreamWriteToFile(&pThis->StrmOutput, "%s", pThis->pszOutput);
+ if (RT_FAILURE(rc))
+ vbcppError(pThis, "ScmStreamWriteToFile failed with %Rrc when writing '%s'", rc, pThis->pszOutput);
+ }
+ else
+ {
+ int rc = ScmStreamWriteToStdOut(&pThis->StrmOutput);
+ if (RT_FAILURE(rc))
+ vbcppError(pThis, "ScmStreamWriteToStdOut failed with %Rrc", rc);
+ }
+ }
+
+ /*
+ * Cleanup.
+ */
+ while (pThis->pInputStack)
+ {
+ ScmStreamDelete(&pThis->pInputStack->StrmInput);
+ void *pvFree = pThis->pInputStack;
+ pThis->pInputStack = pThis->pInputStack->pUp;
+ RTMemFree(pvFree);
+ }
+
+ ScmStreamDelete(&pThis->StrmOutput);
+
+ RTStrSpaceDestroy(&pThis->StrSpace, vbcppMacroFree, NULL);
+ pThis->StrSpace = NULL;
+
+ uint32_t i = pThis->cIncludes;
+ while (i-- > 0)
+ RTStrFree(pThis->papszIncludes[i]);
+ RTMemFree(pThis->papszIncludes);
+ pThis->papszIncludes = NULL;
+
+ return pThis->rcExit;
+}
+
+
+/**
+ * Initializes the C preprocessor instance data.
+ *
+ * @param pThis The C preprocessor instance data.
+ */
+static void vbcppInit(PVBCPP pThis)
+{
+ vbcppSetMode(pThis, kVBCppMode_Selective);
+ pThis->cIncludes = 0;
+ pThis->papszIncludes = NULL;
+ pThis->pszInput = NULL;
+ pThis->pszOutput = NULL;
+ pThis->StrSpace = NULL;
+ pThis->UndefStrSpace = NULL;
+ pThis->cCondStackDepth = 0;
+ pThis->pCondStack = NULL;
+ pThis->fIf0Mode = false;
+ pThis->fJustDroppedLine = false;
+ pThis->fMaybePreprocessorLine = true;
+ VBCPP_BITMAP_EMPTY(pThis->bmDefined);
+ pThis->cCondStackDepth = 0;
+ pThis->pInputStack = NULL;
+ RT_ZERO(pThis->StrmOutput);
+ pThis->rcExit = RTEXITCODE_SUCCESS;
+ pThis->fStrmOutputValid = false;
+}
+
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Do the job. The code says it all.
+ */
+ VBCPP This;
+ vbcppInit(&This);
+ bool fExit;
+ RTEXITCODE rcExit = vbcppParseOptions(&This, argc, argv, &fExit);
+ if (!fExit && rcExit == RTEXITCODE_SUCCESS)
+ {
+ rcExit = vbcppOpenStreams(&This);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = vbcppPreprocess(&This);
+ }
+
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = vbcppTerm(&This);
+ else
+ vbcppTerm(&This);
+ return rcExit;
+}
+
diff --git a/src/bldprogs/VBoxCheckImports.cpp b/src/bldprogs/VBoxCheckImports.cpp
new file mode 100644
index 00000000..42dd40c0
--- /dev/null
+++ b/src/bldprogs/VBoxCheckImports.cpp
@@ -0,0 +1,381 @@
+/* $Id: VBoxCheckImports.cpp $ */
+/** @file
+ * IPRT - Checks that a windows image only imports from a given set of DLLs.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/formats/mz.h>
+#include <iprt/formats/pecoff.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct
+{
+ const char *pszImage;
+ FILE *pFile;
+ bool f32Bit;
+ union
+ {
+ IMAGE_NT_HEADERS32 Nt32;
+ IMAGE_NT_HEADERS64 Nt64;
+ } Hdrs;
+
+ uint32_t cSections;
+ PIMAGE_SECTION_HEADER paSections;
+} MYIMAGE;
+
+
+static bool Failed(MYIMAGE *pThis, const char *pszFmt, ...)
+{
+ va_list va;
+ fprintf(stderr, "error '%s': ", pThis->pszImage);
+ va_start(va, pszFmt);
+ vfprintf(stderr, pszFmt, va);
+ va_end(va);
+ fprintf(stderr, "\n");
+ return false;
+}
+
+
+static bool ReadPeHeaders(MYIMAGE *pThis)
+{
+ /*
+ * MZ header.
+ */
+ IMAGE_DOS_HEADER MzHdr;
+ if (fread(&MzHdr, sizeof(MzHdr), 1, pThis->pFile) != 1)
+ return Failed(pThis, "Reading DOS header");
+
+ if (MzHdr.e_magic != IMAGE_DOS_SIGNATURE)
+ return Failed(pThis, "No MZ magic (found %#x)", MzHdr.e_magic);
+
+ if (fseek(pThis->pFile, MzHdr.e_lfanew, SEEK_SET) != 0)
+ return Failed(pThis, "Seeking to %#lx", (unsigned long)MzHdr.e_lfanew);
+
+ /*
+ * NT signature + file header.
+ */
+ if (fread(&pThis->Hdrs.Nt32, offsetof(IMAGE_NT_HEADERS32, OptionalHeader), 1, pThis->pFile) != 1)
+ return Failed(pThis, "Reading NT file header");
+
+ if (pThis->Hdrs.Nt32.Signature != IMAGE_NT_SIGNATURE)
+ return Failed(pThis, "No PE magic (found %#x)", pThis->Hdrs.Nt32.Signature);
+
+ if (pThis->Hdrs.Nt32.FileHeader.SizeOfOptionalHeader == sizeof(pThis->Hdrs.Nt32.OptionalHeader))
+ pThis->f32Bit = true;
+ else if (pThis->Hdrs.Nt32.FileHeader.SizeOfOptionalHeader == sizeof(pThis->Hdrs.Nt64.OptionalHeader))
+ pThis->f32Bit = false;
+ else
+ return Failed(pThis, "Unsupported SizeOfOptionalHeaders value: %#x",
+ pThis->Hdrs.Nt32.FileHeader.SizeOfOptionalHeader);
+
+ /*
+ * NT optional header.
+ */
+ if (fread(&pThis->Hdrs.Nt32.OptionalHeader, pThis->Hdrs.Nt32.FileHeader.SizeOfOptionalHeader, 1, pThis->pFile) != 1)
+ return Failed(pThis, "Reading NT optional header");
+
+ if ( pThis->Hdrs.Nt32.OptionalHeader.Magic
+ != (pThis->f32Bit ? IMAGE_NT_OPTIONAL_HDR32_MAGIC : IMAGE_NT_OPTIONAL_HDR64_MAGIC) )
+ return Failed(pThis, "Bad optional header magic: %#x", pThis->Hdrs.Nt32.OptionalHeader.Magic);
+
+ uint32_t NumberOfRvaAndSizes = pThis->f32Bit
+ ? pThis->Hdrs.Nt32.OptionalHeader.NumberOfRvaAndSizes
+ : pThis->Hdrs.Nt64.OptionalHeader.NumberOfRvaAndSizes;
+ if (NumberOfRvaAndSizes != IMAGE_NUMBEROF_DIRECTORY_ENTRIES)
+ return Failed(pThis, "Unsupported NumberOfRvaAndSizes value: %#x", NumberOfRvaAndSizes);
+
+ /*
+ * Read the section table.
+ */
+ pThis->cSections = pThis->Hdrs.Nt32.FileHeader.NumberOfSections;
+ if (!pThis->cSections)
+ return Failed(pThis, "No sections in image!");
+ pThis->paSections = (PIMAGE_SECTION_HEADER)calloc(sizeof(pThis->paSections[0]), pThis->cSections);
+ if (!pThis->paSections)
+ return Failed(pThis, "Out of memory!");
+ if (fread(pThis->paSections, sizeof(pThis->paSections[0]), pThis->cSections, pThis->pFile) != pThis->cSections)
+ return Failed(pThis, "Reading NT section headers");
+
+
+ return true;
+}
+
+
+static bool ReadAtRva(MYIMAGE *pThis, uint32_t uRva, void *pvBuf, size_t cbToRead)
+{
+ unsigned const uRvaOrg = uRva;
+ size_t const cbToReadOrg = cbToRead;
+
+ /*
+ * Header section.
+ */
+ int iSh = -1;
+ uint32_t uSectRva = 0;
+ uint32_t offSectRaw = 0;
+ uint32_t cbSectRaw = pThis->f32Bit
+ ? pThis->Hdrs.Nt32.OptionalHeader.SizeOfHeaders
+ : pThis->Hdrs.Nt64.OptionalHeader.SizeOfHeaders;
+ uint32_t cbSectMax = pThis->paSections[0].VirtualAddress;
+
+ for (;;)
+ {
+ /* Read if we've got a match. */
+ uint32_t off = uRva - uSectRva;
+ if (off < cbSectMax)
+ {
+ uint32_t cbThis = cbSectMax - off;
+ if (cbThis > cbToRead)
+ cbThis = (uint32_t)cbToRead;
+
+ memset(pvBuf, 0, cbThis);
+
+ if (off < cbSectRaw)
+ {
+ if (fseek(pThis->pFile, offSectRaw + off, SEEK_SET) != 0)
+ return Failed(pThis, "Seeking to %#x", offSectRaw + off);
+ if (fread(pvBuf, RT_MIN(cbThis, cbSectRaw - off), 1, pThis->pFile) != 1)
+ return Failed(pThis, "Reading %u bytes at %#x", RT_MIN(cbThis, cbSectRaw - off), offSectRaw + off);
+ }
+
+ cbToRead -= cbThis;
+ if (!cbToRead)
+ return true;
+ uRva += cbThis;
+ pvBuf = (uint8_t *)pvBuf + cbThis;
+ }
+
+ /* next section */
+ iSh++;
+ if ((unsigned)iSh >= pThis->cSections)
+ return Failed(pThis, "RVA %#x LB %u is outside the image", uRvaOrg, cbToReadOrg);
+ uSectRva = pThis->paSections[iSh].VirtualAddress;
+ offSectRaw = pThis->paSections[iSh].PointerToRawData;
+ cbSectRaw = pThis->paSections[iSh].SizeOfRawData;
+ if ((unsigned)iSh + 1 < pThis->cSections)
+ cbSectMax = pThis->paSections[iSh + 1].VirtualAddress - uSectRva;
+ else
+ cbSectMax = pThis->paSections[iSh].Misc.VirtualSize;
+ }
+}
+
+
+static bool ReadStringAtRva(MYIMAGE *pThis, uint32_t uRva, char *pszBuf, size_t cbMax)
+{
+ uint32_t const uRvaOrg = uRva;
+
+ /*
+ * Try read the whole string at once.
+ */
+ uint32_t cbImage = pThis->f32Bit
+ ? pThis->Hdrs.Nt32.OptionalHeader.SizeOfImage
+ : pThis->Hdrs.Nt64.OptionalHeader.SizeOfImage;
+ uint32_t cbThis = uRva < cbImage ? cbImage - uRva : 1;
+ if (cbThis > cbMax)
+ cbThis = (uint32_t)cbMax;
+ if (!ReadAtRva(pThis, uRva, pszBuf, cbThis))
+ return false;
+ if (memchr(pszBuf, 0, cbThis) != NULL)
+ return true;
+
+ /*
+ * Read more, byte-by-byte.
+ */
+ for (;;)
+ {
+ cbMax -= cbThis;
+ if (!cbMax)
+ return Failed(pThis, "String to long at %#x", uRvaOrg);
+ pszBuf += cbThis;
+ uRva += cbThis;
+
+ cbThis = 1;
+ if (!ReadAtRva(pThis, uRva, pszBuf, cbThis))
+ return false;
+ if (!*pszBuf)
+ return true;
+ }
+}
+
+
+static void *ReadAtRvaAlloc(MYIMAGE *pThis, uint32_t uRva, size_t cbToRead)
+{
+ void *pvBuf = malloc(cbToRead);
+ if (pvBuf)
+ {
+ if (ReadAtRva(pThis, uRva, pvBuf, cbToRead))
+ return pvBuf;
+ free(pvBuf);
+ }
+ else
+ Failed(pThis, "Out of memory!");
+ return NULL;
+}
+
+
+static bool ParseAndCheckImports(MYIMAGE *pThis, const char **papszAllowed, unsigned cAllowed)
+{
+ /*
+ * Do we have an import directory? If so, read it.
+ */
+ IMAGE_DATA_DIRECTORY ImpDir = pThis->f32Bit
+ ? pThis->Hdrs.Nt32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
+ : pThis->Hdrs.Nt64.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
+ if (ImpDir.Size == 0)
+ return true;
+
+ uint32_t cImps = ImpDir.Size / sizeof(IMAGE_IMPORT_DESCRIPTOR);
+ if (cImps * sizeof(IMAGE_IMPORT_DESCRIPTOR) != ImpDir.Size)
+ return Failed(pThis, "Import directory size is not a multiple of IMAGE_IMPORT_DESCRIPTOR: %#x", ImpDir.Size);
+
+ PIMAGE_IMPORT_DESCRIPTOR paImps = (PIMAGE_IMPORT_DESCRIPTOR)ReadAtRvaAlloc(pThis, ImpDir.VirtualAddress, ImpDir.Size);
+ if (!paImps)
+ return false;
+
+ /* There is usually an empty entry at the end. */
+ if ( paImps[cImps - 1].Name == 0
+ || paImps[cImps - 1].FirstThunk == 0)
+ cImps--;
+
+ /*
+ * Do the processing.
+ */
+ bool fRc = true;
+ for (uint32_t i = 0; i < cImps; i++)
+ {
+ /* Read the import name string. */
+ char szName[128];
+ if (!ReadStringAtRva(pThis, paImps[i].Name, szName, sizeof(szName)))
+ {
+ fRc = false;
+ break;
+ }
+
+ /* Check it against the list of allowed DLLs. */
+ bool fFound = false;
+ unsigned j = cAllowed;
+ while (j-- > 0)
+ if (stricmp(papszAllowed[j], szName) == 0)
+ {
+ fFound = true;
+ break;
+ }
+ if (!fFound)
+ fRc = Failed(pThis, "Illegal import: '%s'", szName);
+ }
+
+ free(paImps);
+ return fRc;
+}
+
+
+static int usage(const char *argv0)
+{
+ printf("usage: %s --image <image> [allowed-dll [..]]\n", argv0);
+ return RTEXITCODE_SUCCESS;
+}
+
+
+int main(int argc, char **argv)
+{
+ /*
+ * Parse arguments.
+ */
+ const char *pszImage = NULL;
+ const char **papszAllowed = (const char **)calloc(argc, sizeof(const char *));
+ unsigned cAllowed = 0;
+
+ for (int i = 1; i < argc; i++)
+ {
+ const char *psz = argv[i];
+ if (*psz == '-')
+ {
+ if (!strcmp(psz, "--image") || !strcmp(psz, "-i"))
+ {
+ if (++i >= argc)
+ {
+ fprintf(stderr, "syntax error: File name expected after '%s'.\n", psz);
+ return RTEXITCODE_SYNTAX;
+ }
+ pszImage = argv[i];
+ }
+ else if ( !strcmp(psz, "--help")
+ || !strcmp(psz, "-help")
+ || !strcmp(psz, "-h")
+ || !strcmp(psz, "-?") )
+ return usage(argv[0]);
+ else if ( !strcmp(psz, "--version")
+ || !strcmp(psz, "-V"))
+ {
+ printf("$Revision: 155244 $\n");
+ return RTEXITCODE_SUCCESS;
+ }
+ else
+ {
+ fprintf(stderr, "syntax error: Unknown option '%s'.\n", psz);
+ return RTEXITCODE_SYNTAX;
+ }
+ }
+ else
+ papszAllowed[cAllowed++] = argv[i];
+ }
+
+ if (!pszImage)
+ {
+ fprintf(stderr, "syntax error: No input file specified.\n");
+ return RTEXITCODE_SYNTAX;
+ }
+
+ /*
+ * Open the image and process headers.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_FAILURE;
+ MYIMAGE MyImage;
+ memset(&MyImage, 0, sizeof(MyImage));
+ MyImage.pszImage = pszImage;
+ MyImage.pFile = fopen(pszImage, "rb");
+ if (MyImage.pFile)
+ {
+ if ( ReadPeHeaders(&MyImage)
+ && ParseAndCheckImports(&MyImage, papszAllowed, cAllowed))
+ rcExit = RTEXITCODE_SUCCESS;
+
+ fclose(MyImage.pFile);
+ free(MyImage.paSections);
+ }
+ else
+ Failed(&MyImage, "Failed to open image for binary reading.");
+
+ return rcExit;
+}
+
diff --git a/src/bldprogs/VBoxCmp.cpp b/src/bldprogs/VBoxCmp.cpp
new file mode 100644
index 00000000..c4f08950
--- /dev/null
+++ b/src/bldprogs/VBoxCmp.cpp
@@ -0,0 +1,141 @@
+/* $Id: VBoxCmp.cpp $ */
+/** @file
+ * File Compare - Compares two files byte by byte.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <iprt/string.h>
+#include <iprt/stdarg.h>
+
+
+/**
+ * Writes an error message.
+ *
+ * @returns RTEXITCODE_FAILURE.
+ * @param pcszFormat Error message.
+ * @param ... Format argument referenced in the message.
+ */
+static RTEXITCODE printErr(const char *pcszFormat, ...)
+{
+ va_list va;
+
+ fprintf(stderr, "VBoxCmp: ");
+ va_start(va, pcszFormat);
+ vfprintf(stderr, pcszFormat, va);
+ va_end(va);
+
+ return RTEXITCODE_FAILURE;
+}
+
+
+static FILE *openFile(const char *pszFile)
+{
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ FILE *pFile = fopen(pszFile, "rb");
+#else
+ FILE *pFile = fopen(pszFile, "r");
+#endif
+ if (!pFile)
+ printErr("Failed to open '%s': %s\n", pszFile, strerror(errno));
+ return pFile;
+}
+
+
+static RTEXITCODE compareFiles(FILE *pFile1, FILE *pFile2)
+{
+ if (!pFile1 || !pFile2)
+ return RTEXITCODE_FAILURE;
+
+ uint32_t cMismatches = 1;
+ RTEXITCODE rcRet = RTEXITCODE_SUCCESS;
+ uint64_t off = 0;
+ for (;;)
+ {
+ uint8_t b1;
+ size_t cb1 = fread(&b1, sizeof(b1), 1, pFile1);
+ uint8_t b2;
+ size_t cb2 = fread(&b2, sizeof(b2), 1, pFile2);
+ if (cb1 != 1 || cb2 != 1)
+ break;
+ if (b1 != b2)
+ {
+ printErr("0x%x%08x: %#04x (%3d) != %#04x (%3d)\n", (uint32_t)(off >> 32), (uint32_t)off, b1, b1, b2, b2);
+ rcRet = RTEXITCODE_FAILURE;
+ cMismatches++;
+ if (cMismatches > 128)
+ {
+ printErr("Too many mismatches, giving up\n");
+ return rcRet;
+ }
+ }
+ off++;
+ }
+
+ if (!feof(pFile1) || !feof(pFile2))
+ {
+ if (!feof(pFile1) && ferror(pFile1))
+ rcRet = printErr("Read error on file #1.\n");
+ else if (!feof(pFile2) && ferror(pFile2))
+ rcRet = printErr("Read error on file #2.\n");
+ else if (!feof(pFile2))
+ rcRet = printErr("0x%x%08x: file #1 ends before file #2\n", (uint32_t)(off >> 32), (uint32_t)off);
+ else
+ rcRet = printErr("0x%x%08x: file #2 ends before file #1\n", (uint32_t)(off >> 32), (uint32_t)off);
+ }
+
+ return rcRet;
+}
+
+
+int main(int argc, char *argv[])
+{
+ RTEXITCODE rcExit;
+
+ if (argc == 3)
+ {
+ const char *pszFile1 = argv[1];
+ const char *pszFile2 = argv[2];
+ FILE *pFile1 = openFile(pszFile1);
+ FILE *pFile2 = openFile(pszFile2);
+ rcExit = compareFiles(pFile1, pFile2);
+ if (pFile1)
+ fclose(pFile1);
+ if (pFile2)
+ fclose(pFile2);
+ }
+ else
+ rcExit = printErr("Syntax error: usage: VBoxCmp <file1> <file2>\n");
+ return rcExit;
+}
+
diff --git a/src/bldprogs/VBoxCompilerPlugIns.h b/src/bldprogs/VBoxCompilerPlugIns.h
new file mode 100644
index 00000000..9f521449
--- /dev/null
+++ b/src/bldprogs/VBoxCompilerPlugIns.h
@@ -0,0 +1,137 @@
+/* $Id: VBoxCompilerPlugIns.h $ */
+/** @file
+ * VBoxCompilerPlugIns - Types, Prototypes and Macros common to the VBox compiler plug-ins.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_bldprogs_VBoxCompilerPlugIns_h
+#define VBOX_INCLUDED_SRC_bldprogs_VBoxCompilerPlugIns_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/types.h>
+#include <stdio.h>
+
+
+/** @def dprintf
+ * Macro for debug printing using fprintf. Only active when DEBUG is defined.
+ */
+#ifdef DEBUG
+# define dprintf(...) do { fprintf(stderr, __VA_ARGS__); } while (0)
+#else
+# define dprintf(...) do { } while (0)
+#endif
+
+
+/**
+ * Checker state.
+ */
+typedef struct VFMTCHKSTATE
+{
+ long iFmt;
+ long iArgs;
+ const char *pszFmt;
+ bool fMaybeNull;
+#if defined(__GNUC__) && !defined(VBOX_COMPILER_PLUG_IN_AGNOSTIC)
+# if RT_GNUC_PREREQ(6, 0)
+ gimple const *hStmt;
+# else
+ gimple hStmt;
+# endif
+ location_t hFmtLoc;
+#endif
+} VFMTCHKSTATE;
+/** Pointer to my checker state. */
+typedef VFMTCHKSTATE *PVFMTCHKSTATE;
+
+#define MYSTATE_FMT_FILE(a_pState) VFmtChkGetFmtLocFile(a_pState)
+#define MYSTATE_FMT_LINE(a_pState) VFmtChkGetFmtLocLine(a_pState)
+#define MYSTATE_FMT_COLUMN(a_pState) VFmtChkGetFmtLocColumn(a_pState)
+
+const char *VFmtChkGetFmtLocFile(PVFMTCHKSTATE pState);
+
+unsigned int VFmtChkGetFmtLocLine(PVFMTCHKSTATE pState);
+
+unsigned int VFmtChkGetFmtLocColumn(PVFMTCHKSTATE pState);
+
+/**
+ * Implements checking format string replacement (%M).
+ *
+ * Caller will have checked all that can be checked. This means that there is a
+ * string argument present, or it won't make the call.
+ *
+ * @param pState The format string checking state.
+ * @param pszPctM The position of the '%M'.
+ * @param iArg The next argument number.
+ */
+void VFmtChkHandleReplacementFormatString(PVFMTCHKSTATE pState, const char *pszPctM, unsigned iArg);
+
+/**
+ * Warning.
+ *
+ * @returns
+ * @param pState .
+ * @param pszLoc .
+ * @param pszFormat .
+ * @param ... .
+ */
+void VFmtChkWarnFmt(PVFMTCHKSTATE pState, const char *pszLoc, const char *pszFormat, ...);
+
+/**
+ * Error.
+ *
+ * @returns
+ * @param pState .
+ * @param pszLoc .
+ * @param pszFormat .
+ * @param ... .
+ */
+void VFmtChkErrFmt(PVFMTCHKSTATE pState, const char *pszLoc, const char *pszFormat, ...);
+
+/**
+ * Checks that @a iFmtArg isn't present or a valid final dummy argument.
+ *
+ * Will issue warning/error if there are more arguments at @a iFmtArg.
+ *
+ * @param pState The format string checking state.
+ * @param iArg The index of the end of arguments, this is
+ * relative to VFMTCHKSTATE::iArgs.
+ */
+void VFmtChkVerifyEndOfArgs(PVFMTCHKSTATE pState, unsigned iArg);
+
+bool VFmtChkRequirePresentArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage);
+
+bool VFmtChkRequireIntArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage);
+
+bool VFmtChkRequireStringArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage);
+
+bool VFmtChkRequireVaListPtrArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage);
+
+/* VBoxCompilerPlugInsCommon.cpp */
+void MyCheckFormatCString(PVFMTCHKSTATE pState, const char *pszFmt);
+
+
+#endif /* !VBOX_INCLUDED_SRC_bldprogs_VBoxCompilerPlugIns_h */
+
diff --git a/src/bldprogs/VBoxCompilerPlugInsCommon.cpp b/src/bldprogs/VBoxCompilerPlugInsCommon.cpp
new file mode 100644
index 00000000..071e9951
--- /dev/null
+++ b/src/bldprogs/VBoxCompilerPlugInsCommon.cpp
@@ -0,0 +1,448 @@
+/* $Id: VBoxCompilerPlugInsCommon.cpp $ */
+/** @file
+ * VBoxCompilerPlugInsCommon - Code common to the compiler plug-ins.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define VBOX_COMPILER_PLUG_IN_AGNOSTIC
+#include "VBoxCompilerPlugIns.h"
+
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define MY_ISDIGIT(c) ((c) >= '0' && (c) <= '9')
+
+/** @name RTSTR_Z_XXX - Size modifiers
+ * @{ */
+#define RTSTR_Z_DEFAULT UINT16_C(0x0001)
+#define RTSTR_Z_LONG UINT16_C(0x0002) /**< l */
+#define RTSTR_Z_LONGLONG UINT16_C(0x0004) /**< ll, L, q. */
+#define RTSTR_Z_HALF UINT16_C(0x0008) /**< h */
+#define RTSTR_Z_HALFHALF UINT16_C(0x0010) /**< hh (internally H) */
+#define RTSTR_Z_SIZE UINT16_C(0x0020) /**< z */
+#define RTSTR_Z_PTRDIFF UINT16_C(0x0040) /**< t */
+#define RTSTR_Z_INTMAX UINT16_C(0x0080) /**< j */
+#define RTSTR_Z_MS_I32 UINT16_C(0x1000) /**< I32 */
+#define RTSTR_Z_MS_I64 UINT16_C(0x2000) /**< I64 */
+#define RTSTR_Z_ALL_INT UINT16_C(0x30fe) /**< short hand for integers. */
+/** @} */
+
+
+/** @name VFMTCHKTYPE_F_XXX - Type flags.
+ * @{ */
+/** Pointers type. */
+#define VFMTCHKTYPE_F_PTR UINT8_C(0x01)
+/** Both const and non-const pointer types. */
+#define VFMTCHKTYPE_F_CPTR (UINT8_C(0x02) | VFMTCHKTYPE_F_PTR)
+/** @} */
+
+/** @name VFMTCHKTYPE_Z_XXX - Special type sizes
+ * @{ */
+#define VFMTCHKTYPE_Z_CHAR UINT8_C(0xe0)
+#define VFMTCHKTYPE_Z_SHORT UINT8_C(0xe1)
+#define VFMTCHKTYPE_Z_INT UINT8_C(0xe2)
+#define VFMTCHKTYPE_Z_LONG UINT8_C(0xe3)
+#define VFMTCHKTYPE_Z_LONGLONG UINT8_C(0xe4)
+#define VFMTCHKTYPE_Z_PTR UINT8_C(0xe5) /**< ASSUMED to be the same for 'void *', 'size_t' and 'ptrdiff_t'. */
+/** @} */
+
+/** @name VFMTCHKTYPE_NM_XXX - Standard C type names.
+ * @{ */
+#define VFMTCHKTYPE_NM_INT "int"
+#define VFMTCHKTYPE_NM_UINT "unsigned int"
+#define VFMTCHKTYPE_NM_LONG "long"
+#define VFMTCHKTYPE_NM_ULONG "unsigned long"
+#define VFMTCHKTYPE_NM_LONGLONG "long long"
+#define VFMTCHKTYPE_NM_ULONGLONG "unsigned long long"
+#define VFMTCHKTYPE_NM_SHORT "short"
+#define VFMTCHKTYPE_NM_USHORT "unsigned short"
+#define VFMTCHKTYPE_NM_CHAR "char"
+#define VFMTCHKTYPE_NM_SCHAR "signed char"
+#define VFMTCHKTYPE_NM_UCHAR "unsigned char"
+/** @} */
+
+
+/** @name VFMTCHKDESC_F_XXX - Format descriptor flags.
+ * @{ */
+#define VFMTCHKDESC_F_NONE UINT32_C(0)
+#define VFMTCHKDESC_F_SIGNED RT_BIT_32(0)
+#define VFMTCHKDESC_F_UNSIGNED RT_BIT_32(1)
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Format check type entry.
+ */
+typedef struct VFMTCHKTYPE
+{
+ /** The format size flag(s). */
+ uint16_t fSize;
+ /** The argument size. */
+ uint8_t cbArg;
+ /** Argument flags (VFMTCHKTYPE_F_XXX). */
+ uint8_t fFlags;
+ /** List of strings with acceptable types, if NULL only check the sizes. */
+ const char *pszzTypeNames;
+} VFMTCHKTYPE;
+/** Pointer to a read only format check type entry. */
+typedef VFMTCHKTYPE const *PCVFMTCHKTYPE;
+
+/** For use as an initializer in VFMTCHKDESK where it indicates that
+ * everything is covered by VFMTCHKDESC::paMoreTypes. Useful for repeating
+ * stuff. */
+#define VFMTCHKTYPE_USE_MORE_TYPES { 0, 0, 0, NULL }
+
+/**
+ * Format type descriptor.
+ */
+typedef struct VFMTCHKDESC
+{
+ /** The format type. */
+ const char *pszType;
+ /** Recognized format flags (RTSTR_F_XXX). */
+ uint16_t fFmtFlags;
+ /** Recognized format sizes (RTSTR_Z_XXX). */
+ uint16_t fFmtSize;
+ /** Flags (VFMTCHKDESC_F_XXX). */
+ uint32_t fFlags;
+ /** Primary type. */
+ VFMTCHKTYPE Type;
+ /** More recognized types (optional). */
+ PCVFMTCHKTYPE paMoreTypes;
+} VFMTCHKDESC;
+typedef VFMTCHKDESC const *PCVFMTCHKDESC;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Integer type specs for 'x', 'd', 'u', 'i', ++
+ *
+ * @todo RTUINT32U and friends... The whole type matching thing.
+ */
+static VFMTCHKTYPE const g_aIntTypes[] =
+{
+ { RTSTR_Z_DEFAULT, VFMTCHKTYPE_Z_INT, 0, VFMTCHKTYPE_NM_INT "\0" VFMTCHKTYPE_NM_UINT "\0" },
+ { RTSTR_Z_LONG, VFMTCHKTYPE_Z_LONG, 0, VFMTCHKTYPE_NM_LONG "\0" VFMTCHKTYPE_NM_ULONG "\0" },
+ { RTSTR_Z_LONGLONG, VFMTCHKTYPE_Z_LONGLONG, 0, VFMTCHKTYPE_NM_LONGLONG "\0" VFMTCHKTYPE_NM_ULONGLONG "\0" },
+ { RTSTR_Z_HALF, VFMTCHKTYPE_Z_SHORT, 0, VFMTCHKTYPE_NM_SHORT "\0" VFMTCHKTYPE_NM_USHORT "\0" },
+ { RTSTR_Z_HALFHALF, VFMTCHKTYPE_Z_CHAR, 0, VFMTCHKTYPE_NM_SCHAR "\0" VFMTCHKTYPE_NM_UCHAR "\0" VFMTCHKTYPE_NM_CHAR "\0" },
+ { RTSTR_Z_SIZE, VFMTCHKTYPE_Z_PTR, 0, "size_t\0" "RTUINTPTR\0" "RTINTPTR\0" },
+ { RTSTR_Z_PTRDIFF, VFMTCHKTYPE_Z_PTR, 0, "ptrdiff_t\0" "RTUINTPTR\0" "RTINTPTR\0" },
+ { RTSTR_Z_INTMAX, VFMTCHKTYPE_Z_PTR, 0, "uint64_t\0" "int64_t\0" "RTUINT64U\0" VFMTCHKTYPE_NM_LONGLONG "\0" VFMTCHKTYPE_NM_ULONGLONG "\0" },
+ { RTSTR_Z_MS_I32, sizeof(uint32_t), 0, "uint32_t\0" "int32_t\0" "RTUINT32U\0" },
+ { RTSTR_Z_MS_I64, sizeof(uint64_t), 0, "uint64_t\0" "int64_t\0" "RTUINT64U\0" },
+};
+
+/** String type specs for 's', 'ls' and 'Ls'.
+ */
+static VFMTCHKTYPE const g_aStringTypes[] =
+{
+ { RTSTR_Z_DEFAULT, VFMTCHKTYPE_Z_PTR, VFMTCHKTYPE_F_CPTR, VFMTCHKTYPE_NM_CHAR "\0" },
+ { RTSTR_Z_LONG, VFMTCHKTYPE_Z_PTR, VFMTCHKTYPE_F_CPTR, "RTUTF16\0" },
+ { RTSTR_Z_LONGLONG, VFMTCHKTYPE_Z_PTR, VFMTCHKTYPE_F_CPTR, "RTUNICP\0" },
+};
+
+static VFMTCHKDESC const g_aFmtDescs[] =
+{
+ { "s",
+ RTSTR_F_LEFT | RTSTR_F_WIDTH | RTSTR_F_PRECISION,
+ RTSTR_Z_DEFAULT | RTSTR_Z_LONG | RTSTR_Z_LONGLONG,
+ VFMTCHKDESC_F_UNSIGNED,
+ VFMTCHKTYPE_USE_MORE_TYPES,
+ g_aStringTypes
+ },
+ { "x",
+ RTSTR_F_LEFT | RTSTR_F_ZEROPAD | RTSTR_F_SPECIAL | RTSTR_F_WIDTH | RTSTR_F_PRECISION,
+ RTSTR_Z_ALL_INT,
+ VFMTCHKDESC_F_UNSIGNED,
+ VFMTCHKTYPE_USE_MORE_TYPES,
+ g_aIntTypes
+ },
+ { "RX32",
+ RTSTR_F_LEFT | RTSTR_F_ZEROPAD | RTSTR_F_SPECIAL | RTSTR_F_WIDTH | RTSTR_F_PRECISION,
+ RTSTR_Z_ALL_INT,
+ VFMTCHKDESC_F_UNSIGNED,
+ { RTSTR_Z_DEFAULT, sizeof(uint32_t), 0, "uint32_t\0" "int32_t\0" },
+ NULL
+ },
+
+
+
+};
+
+
+/**
+ * Does the actual format string checking.
+ *
+ * @todo Move this to different file common to both GCC and CLANG later.
+ *
+ * @param pState The format string checking state.
+ * @param pszFmt The format string.
+ */
+void MyCheckFormatCString(PVFMTCHKSTATE pState, const char *pszFmt)
+{
+ dprintf("checker2: \"%s\" at %s:%d col %d\n", pszFmt,
+ MYSTATE_FMT_FILE(pState), MYSTATE_FMT_LINE(pState), MYSTATE_FMT_COLUMN(pState));
+ pState->pszFmt = pszFmt;
+
+ unsigned iArg = 0;
+ for (;;)
+ {
+ /*
+ * Skip to the next argument.
+ * Quits the loop with the first char following the '%' in 'ch'.
+ */
+ char ch;
+ for (;;)
+ {
+ ch = *pszFmt++;
+ if (ch == '%')
+ {
+ ch = *pszFmt++;
+ if (ch != '%')
+ break;
+ }
+ else if (ch == '\0')
+ {
+ VFmtChkVerifyEndOfArgs(pState, iArg);
+ return;
+ }
+ }
+ const char * const pszPct = pszFmt - 2;
+
+ /*
+ * Flags
+ */
+ uint32_t fFmtFlags = 0;
+ for (;;)
+ {
+ uint32_t fFlag;
+ switch (ch)
+ {
+ case '#': fFlag = RTSTR_F_SPECIAL; break;
+ case '-': fFlag = RTSTR_F_LEFT; break;
+ case '+': fFlag = RTSTR_F_PLUS; break;
+ case ' ': fFlag = RTSTR_F_BLANK; break;
+ case '0': fFlag = RTSTR_F_ZEROPAD; break;
+ case '\'': fFlag = RTSTR_F_THOUSAND_SEP; break;
+ default: fFlag = 0; break;
+ }
+ if (!fFlag)
+ break;
+ if (fFmtFlags & fFlag)
+ VFmtChkWarnFmt(pState, pszPct, "duplicate flag '%c'", ch);
+ fFmtFlags |= fFlag;
+ ch = *pszFmt++;
+ }
+
+ /*
+ * Width.
+ */
+ int cchWidth = -1;
+ if (MY_ISDIGIT(ch))
+ {
+ cchWidth = ch - '0';
+ while ( (ch = *pszFmt++) != '\0'
+ && MY_ISDIGIT(ch))
+ {
+ cchWidth *= 10;
+ cchWidth += ch - '0';
+ }
+ fFmtFlags |= RTSTR_F_WIDTH;
+ }
+ else if (ch == '*')
+ {
+ VFmtChkRequireIntArg(pState, pszPct, iArg, "width should be an 'int' sized argument");
+ iArg++;
+ cchWidth = 0;
+ fFmtFlags |= RTSTR_F_WIDTH;
+ ch = *pszFmt++;
+ }
+
+ /*
+ * Precision
+ */
+ int cchPrecision = -1;
+ if (ch == '.')
+ {
+ ch = *pszFmt++;
+ if (MY_ISDIGIT(ch))
+ {
+ cchPrecision = ch - '0';
+ while ( (ch = *pszFmt++) != '\0'
+ && MY_ISDIGIT(ch))
+ {
+ cchPrecision *= 10;
+ cchPrecision += ch - '0';
+ }
+ }
+ else if (ch == '*')
+ {
+ VFmtChkRequireIntArg(pState, pszPct, iArg, "precision should be an 'int' sized argument");
+ iArg++;
+ cchPrecision = 0;
+ ch = *pszFmt++;
+ }
+ else
+ VFmtChkErrFmt(pState, pszPct, "Missing precision value, only got the '.'");
+ if (cchPrecision < 0)
+ {
+ VFmtChkErrFmt(pState, pszPct, "Negative precision value: %d", cchPrecision);
+ cchPrecision = 0;
+ }
+ fFmtFlags |= RTSTR_F_PRECISION;
+ }
+
+ /*
+ * Argument size.
+ */
+ uint16_t fFmtSize = RTSTR_Z_DEFAULT;
+ switch (ch)
+ {
+ default:
+ fFmtSize = RTSTR_Z_DEFAULT;
+ break;
+
+ case 'z':
+ fFmtSize = RTSTR_Z_SIZE;
+ ch = *pszFmt++;
+ break;
+ case 'j':
+ fFmtSize = RTSTR_Z_INTMAX;
+ ch = *pszFmt++;
+ break;
+ case 't':
+ fFmtSize = RTSTR_Z_PTRDIFF;
+ ch = *pszFmt++;
+ break;
+
+ case 'l':
+ fFmtSize = RTSTR_Z_LONG;
+ ch = *pszFmt++;
+ if (ch == 'l')
+ {
+ fFmtSize = RTSTR_Z_LONGLONG;
+ ch = *pszFmt++;
+ }
+ break;
+
+ case 'q': /* Used on BSD platforms. */
+ case 'L':
+ fFmtSize = RTSTR_Z_LONGLONG;
+ ch = *pszFmt++;
+ break;
+
+ case 'h':
+ fFmtSize = RTSTR_Z_HALF;
+ ch = *pszFmt++;
+ if (ch == 'h')
+ {
+ fFmtSize = RTSTR_Z_HALFHALF;
+ ch = *pszFmt++;
+ }
+ break;
+
+ case 'I': /* Used by Win32/64 compilers. */
+ if ( pszFmt[0] == '6'
+ && pszFmt[1] == '4')
+ {
+ pszFmt += 2;
+ fFmtSize = RTSTR_Z_MS_I64;
+ }
+ else if ( pszFmt[0] == '3'
+ && pszFmt[1] == '2')
+ {
+ pszFmt += 2;
+ fFmtSize = RTSTR_Z_MS_I32;
+ }
+ else
+ {
+ VFmtChkErrFmt(pState, pszFmt, "Unknow format type/size/flag 'I%c'", pszFmt[0]);
+ fFmtSize = RTSTR_Z_INTMAX;
+ }
+ ch = *pszFmt++;
+ break;
+ }
+
+ /*
+ * The type.
+ */
+ switch (ch)
+ {
+ /*
+ * Nested extensions.
+ */
+ case 'M': /* replace the format string (not stacked yet). */
+ {
+ if (*pszFmt)
+ VFmtChkErrFmt(pState, pszFmt, "Characters following '%%M' will be ignored");
+ if (fFmtSize != RTSTR_Z_DEFAULT)
+ VFmtChkWarnFmt(pState, pszFmt, "'%%M' does not support any size flags (%#x)", fFmtSize);
+ if (fFmtFlags != 0)
+ VFmtChkWarnFmt(pState, pszFmt, "'%%M' does not support any format flags (%#x)", fFmtFlags);
+ if (VFmtChkRequireStringArg(pState, pszPct, iArg, "'%M' expects a format string"))
+ VFmtChkHandleReplacementFormatString(pState, pszPct, iArg);
+ return;
+ }
+
+ case 'N': /* real nesting. */
+ {
+ if (fFmtSize != RTSTR_Z_DEFAULT)
+ VFmtChkWarnFmt(pState, pszFmt, "'%%N' does not support any size flags (%#x)", fFmtSize);
+ if (fFmtFlags != 0)
+ VFmtChkWarnFmt(pState, pszFmt, "'%%N' does not support any format flags (%#x)", fFmtFlags);
+ VFmtChkRequireStringArg(pState, pszPct, iArg, "'%N' expects a string followed by a va_list pointer");
+ VFmtChkRequireVaListPtrArg(pState, pszPct, iArg + 1, "'%N' expects a string followed by a va_list pointer");
+ iArg += 2;
+ break;
+ }
+
+ case 'R':
+ if ( pszFmt[0] == 'h'
+ && pszFmt[1] == 'X')
+ {
+ VFmtChkRequirePresentArg(pState, pszPct, iArg, "Expected argument");
+ iArg++;
+ }
+ RT_FALL_THROUGH();
+
+ default:
+ VFmtChkRequirePresentArg(pState, pszPct, iArg, "Expected argument");
+ iArg++;
+ break;
+ }
+ }
+}
+
diff --git a/src/bldprogs/VBoxCompilerPlugInsGcc.cpp b/src/bldprogs/VBoxCompilerPlugInsGcc.cpp
new file mode 100644
index 00000000..04a0a133
--- /dev/null
+++ b/src/bldprogs/VBoxCompilerPlugInsGcc.cpp
@@ -0,0 +1,1012 @@
+/* $Id: VBoxCompilerPlugInsGcc.cpp $ */
+/** @file
+ * gccplugin - GCC plugin for checking IPRT format strings.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <stdio.h>
+#include <iprt/cdefs.h>
+#include <iprt/stdarg.h>
+
+#if RT_GNUC_PREREQ(5, 1)
+# include "gcc-plugin.h"
+# include "plugin-version.h"
+#endif
+#if __GNUC__ == 4 && __GNUC_MINOR__ == 5
+# include "gmp.h"
+extern "C" {
+#endif
+#if __GNUC__ == 4 && __GNUC_MINOR__ == 5
+# include "coretypes.h"
+#endif
+#include "plugin.h"
+#include "basic-block.h"
+#include "tree.h"
+#include "tree-pass.h"
+#if __GNUC__ == 5 && __GNUC_MINOR__ == 4
+# include "tree-ssa-alias.h"
+# include "gimple-expr.h"
+#endif
+#include "gimple.h"
+#if RT_GNUC_PREREQ(4, 9)
+# include "gimple-iterator.h"
+# include "context.h" /* for g */
+#endif
+#include "cp/cp-tree.h"
+#if RT_GNUC_PREREQ(10, 0)
+# include "stringpool.h"
+# include "attribs.h"
+#endif
+#if __GNUC__ == 4 && __GNUC_MINOR__ == 5
+}
+#endif
+
+#include "VBoxCompilerPlugIns.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** License indicator. */
+int plugin_is_GPL_compatible;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Convencience macro not present in earlier gcc versions. */
+#ifndef VAR_P
+# define VAR_P(a_hNode) (TREE_CODE(a_hNode) == VAR_DECL)
+#endif
+/** Replacement for the 4.9.0 get_tree_code_name function. */
+#if !RT_GNUC_PREREQ(4, 9)
+# define get_tree_code_name(a_enmTreeCode) (tree_code_name[a_enmTreeCode])
+#endif
+
+
+/** For use with messages.
+ * @todo needs some more work... Actually, seems we're a bit handicapped by
+ * working on gimplified stuff. */
+#define MY_LOC(a_hPreferred, a_pState) EXPR_LOC_OR_LOC(a_hPreferred, (a_pState)->hFmtLoc)
+
+/** @name Compatibility glue
+ * @{ */
+#if __GNUC__ == 4 && __GNUC_MINOR__ == 5
+# define linemap_location_from_macro_expansion_p(a, b) false
+#endif
+#if __GNUC__ == 4 && __GNUC_MINOR__ == 5
+static tree gimple_call_fntype(gimple hStmt)
+{
+ tree hDecl = gimple_call_fndecl(hStmt);
+ if (hDecl)
+ return TREE_TYPE(hDecl);
+ hDecl = gimple_call_fn(hStmt);
+ if (TREE_CODE(hDecl) == OBJ_TYPE_REF)
+ hDecl = OBJ_TYPE_REF_EXPR(hDecl);
+ if (DECL_P(hDecl))
+ {
+ tree hType = TREE_TYPE(hDecl);
+ if (POINTER_TYPE_P(hType))
+ hType = TREE_TYPE(hType);
+ return hType;
+ }
+ return NULL_TREE; /* caller bitches about this*/
+}
+#endif
+
+///* Integer to HOST_WIDE_INT conversion fun. */
+//#if RT_GNUC_PREREQ(4, 6)
+//# define MY_INT_FITS_SHWI(hNode) (hNode).fits_shwi()
+//# define MY_INT_TO_SHWI(hNode) (hNode).to_shwi()
+//#else
+//# define MY_INT_FITS_SHWI(hNode) double_int_fits_in_shwi_p(hNode)
+//# define MY_INT_TO_SHWI(hNode) double_int_to_shwi(hNode)
+//#endif
+
+/* Integer to HOST_WIDE_INT conversion fun. */
+#if RT_GNUC_PREREQ(5, 1)
+# define MY_DOUBLE_INT_FITS_SHWI(hNode) tree_fits_shwi_p(hNode)
+# define MY_DOUBLE_INT_TO_SHWI(hNode) tree_to_shwi(hNode)
+#elif RT_GNUC_PREREQ(4, 6)
+# define MY_DOUBLE_INT_FITS_SHWI(hNode) (TREE_INT_CST(hNode).fits_shwi())
+# define MY_DOUBLE_INT_TO_SHWI(hNode) (TREE_INT_CST(hNode).to_shwi())
+#else
+# define MY_DOUBLE_INT_FITS_SHWI(hNode) double_int_fits_in_shwi_p(TREE_INT_CST(hNode))
+# define MY_DOUBLE_INT_TO_SHWI(hNode) double_int_to_shwi(TREE_INT_CST(hNode))
+#endif
+
+#ifndef EXPR_LOC_OR_LOC
+# define EXPR_LOC_OR_LOC(a,b) (b)
+#endif
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static bool MyPassGateCallback(void);
+static unsigned int MyPassExecuteCallback(void);
+static unsigned int MyPassExecuteCallbackWithFunction(struct function *pFun);
+static tree AttributeHandler(tree *, tree, tree, int, bool *);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Plug-in info. */
+static const struct plugin_info g_PlugInInfo =
+{
+ version: "0.0.0-ALPHA",
+ help : "Implements the __iprt_format__ attribute for checking format strings and arguments."
+};
+
+#if RT_GNUC_PREREQ(4, 9)
+
+/** My pass. */
+static const pass_data g_MyPassData =
+{
+ type : GIMPLE_PASS,
+ name : "*iprt-format-checks", /* asterisk = no dump */
+# if RT_GNUC_PREREQ(10, 0)
+ optinfo_flags : OPTGROUP_NONE,
+# else
+ optinfo_flags : 0,
+# endif
+ tv_id : TV_NONE,
+ properties_required : 0,
+ properties_provided : 0,
+ properties_destroyed : 0,
+ todo_flags_start : 0,
+ todo_flags_finish : 0,
+};
+
+class MyPass : public gimple_opt_pass
+{
+public:
+ MyPass(gcc::context *pCtx) : gimple_opt_pass(g_MyPassData, pCtx)
+ { }
+
+ virtual bool gate(function *pFun)
+ {
+ NOREF(pFun);
+ return MyPassGateCallback();
+ }
+
+ virtual unsigned int execute(function *pFun)
+ {
+ NOREF(pFun);
+ return MyPassExecuteCallbackWithFunction(pFun);
+ }
+};
+
+#else /* < 4.9.0 */
+
+/** My pass. */
+static struct gimple_opt_pass g_MyPass =
+{
+ pass:
+ {
+ type : GIMPLE_PASS,
+ name : "*iprt-format-checks", /* asterisk = no dump */
+# if RT_GNUC_PREREQ(4, 6)
+ optinfo_flags : 0,
+# endif
+ gate : MyPassGateCallback,
+ execute : MyPassExecuteCallback,
+ sub : NULL,
+ next : NULL,
+ static_pass_number : 0,
+ tv_id : TV_NONE,
+ properties_required : 0,
+ properties_provided : 0,
+ properties_destroyed : 0,
+ todo_flags_start : 0,
+ todo_flags_finish : 0,
+ }
+};
+
+/** The registration info for my pass. */
+static const struct register_pass_info g_MyPassInfo =
+{
+ pass : &g_MyPass.pass,
+ reference_pass_name : "ssa",
+ ref_pass_instance_number : 1,
+ pos_op : PASS_POS_INSERT_BEFORE,
+};
+
+#endif /* < 4.9.0 */
+
+
+/** Attribute specifications. */
+static const struct attribute_spec g_AttribSpecs[] =
+{
+ {
+ name : "iprt_format",
+ min_length : 2,
+ max_length : 2,
+ decl_required : false,
+ type_required : true,
+ function_type_required : true,
+// gcc 7.3 at least moves this field to after "handler", and with 8.3 it is back
+#if RT_GNUC_PREREQ(4, 6) && !(RT_GNUC_PREREQ(7, 0) && !RT_GNUC_PREREQ(8, 0))
+ affects_type_identity : false,
+#endif
+ handler : AttributeHandler,
+#if RT_GNUC_PREREQ(7, 0) && !RT_GNUC_PREREQ(8, 0)
+ affects_type_identity : false,
+#endif
+#if RT_GNUC_PREREQ(8, 0)
+ exclude : NULL,
+#endif
+ },
+ {
+ name : "iprt_format_maybe_null",
+ min_length : 2,
+ max_length : 2,
+ decl_required : false,
+ type_required : true,
+ function_type_required : true,
+#if RT_GNUC_PREREQ(4, 6) && !(RT_GNUC_PREREQ(7, 0) && !RT_GNUC_PREREQ(8, 0))
+ affects_type_identity : false,
+#endif
+ handler : AttributeHandler,
+#if RT_GNUC_PREREQ(7, 0) && !RT_GNUC_PREREQ(8, 0)
+ affects_type_identity : false,
+#endif
+#if RT_GNUC_PREREQ(8, 0)
+ exclude : NULL,
+#endif
+ }
+};
+
+
+#ifdef DEBUG
+
+/**
+ * Debug function for printing the scope of a decl.
+ * @param hDecl Declaration to print scope for.
+ */
+static void dprintScope(tree hDecl)
+{
+# if 0 /* later? */
+ tree hScope = CP_DECL_CONTEXT(hDecl);
+ if (hScope == global_namespace)
+ return;
+ if (TREE_CODE(hScope) == RECORD_TYPE)
+ hScope = TYPE_NAME(hScope);
+
+ /* recurse */
+ dprintScope(hScope);
+
+ /* name the scope. */
+ dprintf("::%s", DECL_NAME(hScope) ? IDENTIFIER_POINTER(DECL_NAME(hScope)) : "<noname>");
+# endif
+}
+
+
+/**
+ * Debug function for printing a declaration.
+ * @param hDecl The declaration to print.
+ */
+static void dprintDecl(tree hDecl)
+{
+ enum tree_code const enmDeclCode = TREE_CODE(hDecl);
+ tree const hType = TREE_TYPE(hDecl);
+ enum tree_code const enmTypeCode = hType ? TREE_CODE(hType) : (enum tree_code)-1;
+#if 0
+ if ( enmTypeCode == RECORD_TYPE
+ && enmDeclCode == TYPE_DECL
+ && DECL_ARTIFICIAL(hDecl))
+ dprint_class(hType);
+#endif
+
+ dprintf("%s ", get_tree_code_name(enmDeclCode));
+ dprintScope(hDecl);
+ dprintf("::%s", DECL_NAME(hDecl) ? IDENTIFIER_POINTER(DECL_NAME(hDecl)) : "<noname>");
+ if (hType)
+ dprintf(" type %s", get_tree_code_name(enmTypeCode));
+ dprintf(" @%s:%d", DECL_SOURCE_FILE(hDecl), DECL_SOURCE_LINE(hDecl));
+}
+
+#endif /* DEBUG */
+
+
+static location_t MyGetLocationPlusColumnOffset(location_t hLoc, unsigned int offColumn)
+{
+ /*
+ * Skip NOOPs, reserved locations and macro expansion.
+ */
+ if ( offColumn != 0
+ && hLoc >= RESERVED_LOCATION_COUNT
+ && !linemap_location_from_macro_expansion_p(line_table, hLoc))
+ {
+#if __GNUC__ >= 5 /** @todo figure this... */
+ /*
+ * There is an API for doing this, nice.
+ */
+ location_t hNewLoc = linemap_position_for_loc_and_offset(line_table, hLoc, offColumn);
+ if (hNewLoc && hNewLoc != hLoc)
+ {
+ dprintf("MyGetLocationPlusColumnOffset: hNewLoc=%#x hLoc=%#x offColumn=%u\n", hNewLoc, hLoc, offColumn);
+ return hNewLoc;
+ }
+
+#elif __GNUC_MINOR__ > 5
+ /*
+ * Have to do the job ourselves, it seems. This is a bit hairy...
+ */
+ line_map const *pMap = NULL;
+ location_t hLoc2 = linemap_resolve_location(line_table, hLoc, LRK_SPELLING_LOCATION, &pMap);
+ if (hLoc2)
+ hLoc = hLoc2;
+
+ /* Guard against wrap arounds and overlaps. */
+ if ( hLoc + offColumn > MAP_START_LOCATION(pMap) /** @todo Use MAX_SOURCE_LOCATION? */
+ && ( pMap == LINEMAPS_LAST_ORDINARY_MAP(line_table)
+ || hLoc + offColumn < MAP_START_LOCATION((pMap + 1))))
+ {
+ /* Calc new column and check that it's within the valid range. */
+ unsigned int uColumn = SOURCE_COLUMN(pMap, hLoc) + offColumn;
+ if (uColumn < RT_BIT_32(ORDINARY_MAP_NUMBER_OF_COLUMN_BITS(pMap)))
+ {
+ /* Try add the position. If we get a valid result, replace the location. */
+ source_location hNewLoc = linemap_position_for_line_and_column((line_map *)pMap, SOURCE_LINE(pMap, hLoc), uColumn);
+ if ( hNewLoc <= line_table->highest_location
+ && linemap_lookup(line_table, hNewLoc) != NULL)
+ {
+ dprintf("MyGetLocationPlusColumnOffset: hNewLoc=%#x hLoc=%#x offColumn=%u uColumn=%u\n",
+ hNewLoc, hLoc, offColumn, uColumn);
+ return hNewLoc;
+ }
+ }
+ }
+#endif
+ }
+ dprintf("MyGetLocationPlusColumnOffset: taking fallback\n");
+ return hLoc;
+}
+
+
+#if 0
+DECLINLINE(int) MyGetLineLength(const char *pszLine)
+{
+ if (pszLine)
+ {
+ const char *pszEol = strpbrk(pszLine, "\n\r");
+ if (!pszEol)
+ pszEol = strchr(pszLine, '\0');
+ return (int)(pszEol - pszLine);
+ }
+ return 0;
+}
+#endif
+
+static location_t MyGetFormatStringLocation(PVFMTCHKSTATE pState, const char *pszLoc)
+{
+ location_t hLoc = pState->hFmtLoc;
+#if RT_GNUC_PREREQ(4,6)
+ intptr_t offString = pszLoc - pState->pszFmt;
+ if ( offString >= 0
+ && !linemap_location_from_macro_expansion_p(line_table, hLoc))
+ {
+ unsigned uCol = 1 + offString;
+# if 0 /* apparently not needed */
+ expanded_location XLoc = expand_location_to_spelling_point(hLoc);
+# if RT_GNUC_PREREQ(10,0)
+ char_span Span = location_get_source_line(XLoc.file, XLoc.line);
+ const char *pszLine = Span.m_ptr; /** @todo if enabled */
+ int cchLine = (int)Span.m_n_elts;
+# elif RT_GNUC_PREREQ(6,0)
+ int cchLine = 0;
+ const char *pszLine = location_get_source_line(XLoc.file, XLoc.line, &cchLine);
+# elif RT_GNUC_PREREQ(5,0)
+ int cchLine = 0;
+ const char *pszLine = location_get_source_line(XLoc, &cchLine);
+# else
+ const char *pszLine = location_get_source_line(XLoc);
+ int cchLine = MyGetLineLength(pszLine);
+# endif
+ if (pszLine)
+ {
+ /** @todo Adjust the position by parsing the source. */
+ pszLine += XLoc.column - 1;
+ cchLine -= XLoc.column - 1;
+ }
+# endif
+
+ hLoc = MyGetLocationPlusColumnOffset(hLoc, uCol);
+ }
+#endif
+ return hLoc;
+}
+
+
+/**
+ * Non-recursive worker for MyCheckFormatRecursive.
+ *
+ * This will attempt to result @a hFmtArg into a string literal which it then
+ * passes on to MyCheckFormatString for the actual analyzis.
+ *
+ * @param pState The format string checking state.
+ * @param hFmtArg The format string node.
+ */
+DECL_NO_INLINE(static, void) MyCheckFormatNonRecursive(PVFMTCHKSTATE pState, tree hFmtArg)
+{
+ dprintf("checker: hFmtArg=%p %s\n", hFmtArg, get_tree_code_name(TREE_CODE(hFmtArg)));
+
+ /*
+ * Try resolve variables into constant strings.
+ */
+ if (VAR_P(hFmtArg))
+ {
+ hFmtArg = decl_constant_value(hFmtArg);
+ STRIP_NOPS(hFmtArg); /* Used as argument and assigned call result. */
+ dprintf("checker1: variable => hFmtArg=%p %s\n", hFmtArg, get_tree_code_name(TREE_CODE(hFmtArg)));
+ }
+
+ /*
+ * Fend off NULLs.
+ */
+ if (integer_zerop(hFmtArg))
+ {
+ if (pState->fMaybeNull)
+ VFmtChkVerifyEndOfArgs(pState, 0);
+ else
+ error_at(MY_LOC(hFmtArg, pState), "Format string should not be NULL");
+ }
+ /*
+ * Need address expression to get any further.
+ */
+ else if (TREE_CODE(hFmtArg) != ADDR_EXPR)
+ dprintf("checker1: Not address expression (%s)\n", get_tree_code_name(TREE_CODE(hFmtArg)));
+ else
+ {
+ pState->hFmtLoc = EXPR_LOC_OR_LOC(hFmtArg, pState->hFmtLoc);
+ hFmtArg = TREE_OPERAND(hFmtArg, 0);
+
+ /*
+ * Deal with fixed string indexing, if possible.
+ */
+ HOST_WIDE_INT off = 0;
+ if ( TREE_CODE(hFmtArg) == ARRAY_REF
+ && MY_DOUBLE_INT_FITS_SHWI(TREE_OPERAND(hFmtArg, 1))
+ && MY_DOUBLE_INT_FITS_SHWI(TREE_OPERAND(hFmtArg, 1)) )
+ {
+ off = MY_DOUBLE_INT_TO_SHWI(TREE_OPERAND(hFmtArg, 1));
+ if (off < 0)
+ {
+ dprintf("checker1: ARRAY_REF, off=%ld\n", off);
+ return;
+ }
+ hFmtArg = TREE_OPERAND(hFmtArg, 0);
+ dprintf("checker1: ARRAY_REF => hFmtArg=%p %s, off=%ld\n", hFmtArg, get_tree_code_name(TREE_CODE(hFmtArg)), off);
+ }
+
+ /*
+ * Deal with static const char g_szFmt[] = "qwerty"; Take care as
+ * the actual string constant may not necessarily include the terminator.
+ */
+ tree hArraySize = NULL_TREE;
+ if ( VAR_P(hFmtArg)
+ && TREE_CODE(TREE_TYPE(hFmtArg)) == ARRAY_TYPE)
+ {
+ tree hArrayInitializer = decl_constant_value(hFmtArg);
+ if ( hArrayInitializer != hFmtArg
+ && TREE_CODE(hArrayInitializer) == STRING_CST)
+ {
+ hArraySize = DECL_SIZE_UNIT(hFmtArg);
+ hFmtArg = hArrayInitializer;
+ }
+ }
+
+ /*
+ * Are we dealing with a string literal now?
+ */
+ if (TREE_CODE(hFmtArg) != STRING_CST)
+ dprintf("checker1: Not string literal (%s)\n", get_tree_code_name(TREE_CODE(hFmtArg)));
+ else if (TYPE_MAIN_VARIANT(TREE_TYPE(TREE_TYPE(hFmtArg))) != char_type_node)
+ warning_at(pState->hFmtLoc, 0, "expected 'char' type string literal");
+ else
+ {
+ /*
+ * Yes we are, so get the pointer to the string and its length.
+ */
+ const char *pszFmt = TREE_STRING_POINTER(hFmtArg);
+ int cchFmt = TREE_STRING_LENGTH(hFmtArg);
+
+ /* Adjust cchFmt to the initialized array size if appropriate. */
+ if (hArraySize != NULL_TREE)
+ {
+ if (TREE_CODE(hArraySize) != INTEGER_CST)
+ warning_at(pState->hFmtLoc, 0, "Expected integer array size (not %s)", get_tree_code_name(TREE_CODE(hArraySize)));
+ else if (!MY_DOUBLE_INT_FITS_SHWI(hArraySize))
+ warning_at(pState->hFmtLoc, 0, "Unexpected integer overflow in array size constant");
+ else
+ {
+ HOST_WIDE_INT cbArray = MY_DOUBLE_INT_TO_SHWI(hArraySize);
+ if ( cbArray <= 0
+ || cbArray != (int)cbArray)
+ warning_at(pState->hFmtLoc, 0, "Unexpected integer array size constant value: %ld", cbArray);
+ else if (cchFmt > cbArray)
+ {
+ dprintf("checker1: cchFmt=%d => cchFmt=%ld (=cbArray)\n", cchFmt, cbArray);
+ cchFmt = (int)cbArray;
+ }
+ }
+ }
+
+ /* Apply the offset, if given. */
+ if (off)
+ {
+ if (off >= cchFmt)
+ {
+ dprintf("checker1: off=%ld >= cchFmt=%d -> skipping\n", off, cchFmt);
+ return;
+ }
+ pszFmt += off;
+ cchFmt -= (int)off;
+ }
+
+ /*
+ * Check for unterminated strings.
+ */
+ if ( cchFmt < 1
+ || pszFmt[cchFmt - 1] != '\0')
+ warning_at(pState->hFmtLoc, 0, "Unterminated format string (cchFmt=%d)", cchFmt);
+ /*
+ * Call worker to check the actual string.
+ */
+ else
+ MyCheckFormatCString(pState, pszFmt);
+ }
+ }
+}
+
+
+/**
+ * Deal recursively with special format string constructs.
+ *
+ * This will call MyCheckFormatNonRecursive to validate each format string.
+ *
+ * @param pState The format string checking state.
+ * @param hFmtArg The format string node.
+ */
+static void MyCheckFormatRecursive(PVFMTCHKSTATE pState, tree hFmtArg)
+{
+ /*
+ * Catch wrong attribute use.
+ */
+ if (hFmtArg == NULL_TREE)
+ error_at(pState->hFmtLoc, "IPRT format attribute is probably used incorrectly (hFmtArg is NULL)");
+ /*
+ * NULL format strings may cause crashes.
+ */
+ else if (integer_zerop(hFmtArg))
+ {
+ if (pState->fMaybeNull)
+ VFmtChkVerifyEndOfArgs(pState, 0);
+ else
+ error_at(MY_LOC(hFmtArg, pState), "Format string should not be NULL");
+ }
+ /*
+ * Check both branches of a ternary operator.
+ */
+ else if (TREE_CODE(hFmtArg) == COND_EXPR)
+ {
+ MyCheckFormatRecursive(pState, TREE_OPERAND(hFmtArg, 1));
+ MyCheckFormatRecursive(pState, TREE_OPERAND(hFmtArg, 2));
+ }
+ /*
+ * Strip coercion.
+ */
+ else if ( CONVERT_EXPR_P(hFmtArg)
+ && TYPE_PRECISION(TREE_TYPE(hFmtArg)) == TYPE_PRECISION(TREE_TYPE(TREE_OPERAND(hFmtArg, 0))) )
+ MyCheckFormatRecursive(pState, TREE_OPERAND(hFmtArg, 0));
+ /*
+ * We're good, hand it to the non-recursive worker.
+ */
+ else
+ MyCheckFormatNonRecursive(pState, hFmtArg);
+}
+
+
+#if !RT_GNUC_PREREQ(4, 9)
+/**
+ * Execute my pass.
+ * @returns Flags indicates stuff todo, we return 0.
+ */
+static unsigned int MyPassExecuteCallback(void)
+{
+ return MyPassExecuteCallbackWithFunction(cfun);
+}
+#endif
+
+/**
+ * Execute my pass.
+ * @returns Flags indicates stuff todo, we return 0.
+ */
+static unsigned int MyPassExecuteCallbackWithFunction(struct function *pFun)
+{
+ dprintf("MyPassExecuteCallback:\n");
+
+ /*
+ * Enumerate the basic blocks.
+ */
+ basic_block hBasicBlock;
+ FOR_EACH_BB_FN(hBasicBlock, pFun)
+ {
+ dprintf(" hBasicBlock=%p\n", hBasicBlock);
+
+ /*
+ * Enumerate the statements in the current basic block.
+ * We're interested in calls to functions with the __iprt_format__ attribute.
+ */
+ for (gimple_stmt_iterator hStmtItr = gsi_start_bb(hBasicBlock); !gsi_end_p(hStmtItr); gsi_next(&hStmtItr))
+ {
+#if RT_GNUC_PREREQ(6, 0)
+ const gimple * const hStmt = gsi_stmt(hStmtItr);
+#else
+ gimple const hStmt = gsi_stmt(hStmtItr);
+#endif
+
+ enum gimple_code const enmCode = gimple_code(hStmt);
+#ifdef DEBUG
+ unsigned const cOps = gimple_num_ops(hStmt);
+ dprintf(" hStmt=%p %s (%d) ops=%d\n", hStmt, gimple_code_name[enmCode], enmCode, cOps);
+ for (unsigned iOp = 0; iOp < cOps; iOp++)
+ {
+ tree const hOp = gimple_op(hStmt, iOp);
+ if (hOp)
+ dprintf(" %02d: %p, code %s(%d)\n", iOp, hOp, get_tree_code_name(TREE_CODE(hOp)), TREE_CODE(hOp));
+ else
+ dprintf(" %02d: NULL_TREE\n", iOp);
+ }
+#endif
+ if (enmCode == GIMPLE_CALL)
+ {
+ /*
+ * Check if the function type has the __iprt_format__ attribute.
+ */
+ tree const hFn = gimple_call_fn(hStmt);
+ dprintf(" hFn =%p %s(%d); args=%d\n",
+ hFn, hFn ? get_tree_code_name(TREE_CODE(hFn)) : NULL, hFn ? TREE_CODE(hFn) : - 1,
+ gimple_call_num_args(hStmt));
+#ifdef DEBUG
+ if (hFn && DECL_P(hFn))
+ dprintf(" hFn is decl: %s %s:%d\n",
+ DECL_NAME(hFn) ? IDENTIFIER_POINTER(DECL_NAME(hFn)) : "<unamed>",
+ DECL_SOURCE_FILE(hFn), DECL_SOURCE_LINE(hFn));
+#endif
+ tree const hFnDecl = gimple_call_fndecl(hStmt);
+ if (hFnDecl)
+ dprintf(" hFnDecl=%p %s(%d) %s type=%p %s:%d\n",
+ hFnDecl, get_tree_code_name(TREE_CODE(hFnDecl)), TREE_CODE(hFnDecl),
+ DECL_NAME(hFnDecl) ? IDENTIFIER_POINTER(DECL_NAME(hFnDecl)) : "<unamed>",
+ TREE_TYPE(hFnDecl), DECL_SOURCE_FILE(hFnDecl), DECL_SOURCE_LINE(hFnDecl));
+ tree const hFnType = gimple_call_fntype(hStmt);
+ if (hFnType == NULL_TREE)
+ {
+ if ( hFnDecl == NULL_TREE
+ && gimple_call_internal_p(hStmt) /* va_arg() kludge */)
+ continue;
+ error_at(gimple_location(hStmt), "Failed to resolve function type [fn=%s fndecl=%s]\n",
+ hFn ? get_tree_code_name(TREE_CODE(hFn)) : "<null>",
+ hFnDecl ? get_tree_code_name(TREE_CODE(hFnDecl)) : "<null>");
+ }
+ else if (POINTER_TYPE_P(hFnType))
+ error_at(gimple_location(hStmt), "Got a POINTER_TYPE when expecting a function type [fn=%s]\n",
+ get_tree_code_name(TREE_CODE(hFn)));
+ if (hFnType)
+ dprintf(" hFnType=%p %s(%d) %s\n", hFnType, get_tree_code_name(TREE_CODE(hFnType)), TREE_CODE(hFnType),
+ TYPE_NAME(hFnType) && DECL_NAME(TYPE_NAME(hFnType))
+ ? IDENTIFIER_POINTER(DECL_NAME(TYPE_NAME(hFnType))) : "<unamed>");
+
+ tree const hAttr = hFnType ? lookup_attribute("iprt_format", TYPE_ATTRIBUTES(hFnType)) : NULL_TREE;
+ tree const hAttrMaybe0 = hFnType ? lookup_attribute("iprt_format_maybe_null", TYPE_ATTRIBUTES(hFnType)) : NULL_TREE;
+ if (hAttr || hAttrMaybe0)
+ {
+ /*
+ * Yeah, it has the attribute!
+ */
+ tree const hAttrArgs = hAttr ? TREE_VALUE(hAttr) : TREE_VALUE(hAttrMaybe0);
+ VFMTCHKSTATE State;
+ State.iFmt = MY_DOUBLE_INT_TO_SHWI(TREE_VALUE(hAttrArgs));
+ State.iArgs = MY_DOUBLE_INT_TO_SHWI(TREE_VALUE(TREE_CHAIN(hAttrArgs)));
+ State.pszFmt = NULL;
+ State.fMaybeNull = hAttr == NULL_TREE;
+ State.hStmt = hStmt;
+ State.hFmtLoc = gimple_location(hStmt);
+ dprintf(" %s() __iprt_format%s__(iFmt=%ld, iArgs=%ld)\n",
+ hFnDecl && DECL_NAME(hFnDecl) ? IDENTIFIER_POINTER(DECL_NAME(hFnDecl)) : "<unamed>",
+ State.fMaybeNull ? "_maybe_null" : "", State.iFmt, State.iArgs);
+
+ unsigned cCallArgs = gimple_call_num_args(hStmt);
+ if (cCallArgs >= State.iFmt)
+ MyCheckFormatRecursive(&State, gimple_call_arg(hStmt, State.iFmt - 1));
+ else
+ error_at(gimple_location(hStmt),
+ "Call has only %d arguments; %s() format string is argument #%lu (1-based), thus missing\n",
+ cCallArgs, DECL_NAME(hFnDecl) ? IDENTIFIER_POINTER(DECL_NAME(hFnDecl)) : "<unamed>", State.iFmt);
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Gate callback for my pass that indicates whether it should execute or not.
+ * @returns true to execute.
+ */
+static bool MyPassGateCallback(void)
+{
+ dprintf("MyPassGateCallback:\n");
+ return true;
+}
+
+
+/**
+ * Validate the use of an attribute.
+ *
+ * @returns ??
+ * @param phOnNode The node the attribute is being used on.
+ * @param hAttrName The attribute name.
+ * @param hAttrArgs The attribute arguments.
+ * @param fFlags Some kind of flags...
+ * @param pfDontAddAttrib Whether to add the attribute to this node or not.
+ */
+static tree AttributeHandler(tree *phOnNode, tree hAttrName, tree hAttrArgs, int fFlags, bool *pfDontAddAttrib)
+{
+ dprintf("AttributeHandler: name=%s fFlags=%#x", IDENTIFIER_POINTER(hAttrName), fFlags);
+ long iFmt = MY_DOUBLE_INT_TO_SHWI(TREE_VALUE(hAttrArgs));
+ long iArgs = MY_DOUBLE_INT_TO_SHWI(TREE_VALUE(TREE_CHAIN(hAttrArgs)));
+ dprintf(" iFmt=%ld iArgs=%ld", iFmt, iArgs);
+
+ tree hType = *phOnNode;
+ dprintf(" hType=%p %s(%d)\n", hType, get_tree_code_name(TREE_CODE(hType)), TREE_CODE(hType));
+
+ if (pfDontAddAttrib)
+ *pfDontAddAttrib = false;
+ return NULL_TREE;
+}
+
+
+/**
+ * Called when we can register attributes.
+ *
+ * @param pvEventData Ignored.
+ * @param pvUser Ignored.
+ */
+static void RegisterAttributesEvent(void *pvEventData, void *pvUser)
+{
+ NOREF(pvEventData); NOREF(pvUser);
+ dprintf("RegisterAttributesEvent: pvEventData=%p\n", pvEventData);
+
+ register_attribute(&g_AttribSpecs[0]);
+ register_attribute(&g_AttribSpecs[1]);
+}
+
+
+/**
+ * The plug-in entry point.
+ *
+ * @returns 0 to indicate success?
+ * @param pPlugInInfo Plugin info structure.
+ * @param pGccVer GCC Version.
+ */
+int plugin_init(plugin_name_args *pPlugInInfo, plugin_gcc_version *pGccVer)
+{
+ dprintf("plugin_init: %s\n", pPlugInInfo->full_name);
+ dprintf("gcc version: basever=%s datestamp=%s devphase=%s revision=%s\n",
+ pGccVer->basever, pGccVer->datestamp, pGccVer->devphase, pGccVer->revision);
+
+ /* Ask for callback in which we may register the attribute. */
+ register_callback(pPlugInInfo->base_name, PLUGIN_ATTRIBUTES, RegisterAttributesEvent, NULL /*pvUser*/);
+
+ /* Register our pass. */
+#if RT_GNUC_PREREQ(4, 9)
+ /** The registration info for my pass. */
+ struct register_pass_info MyPassInfo;
+ MyPassInfo.pass = new MyPass(g);
+ MyPassInfo.reference_pass_name = "ssa";
+ MyPassInfo.ref_pass_instance_number = 1;
+ MyPassInfo.pos_op = PASS_POS_INSERT_BEFORE;
+ register_callback(pPlugInInfo->base_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &MyPassInfo);
+#else
+ register_callback(pPlugInInfo->base_name, PLUGIN_PASS_MANAGER_SETUP, NULL, (void *)&g_MyPassInfo);
+#endif
+
+ /* Register plug-in info. */
+ register_callback(pPlugInInfo->base_name, PLUGIN_INFO, NULL, (void *)&g_PlugInInfo);
+
+ return 0;
+}
+
+
+
+
+/*
+ *
+ * Functions used by the common code.
+ * Functions used by the common code.
+ * Functions used by the common code.
+ *
+ */
+
+void VFmtChkWarnFmt(PVFMTCHKSTATE pState, const char *pszLoc, const char *pszFormat, ...)
+{
+ char szTmp[1024];
+ va_list va;
+ va_start(va, pszFormat);
+ vsnprintf(szTmp, sizeof(szTmp), pszFormat, va);
+ va_end(va);
+
+ /* display the warning. */
+ warning_at(MyGetFormatStringLocation(pState, pszLoc), 0, "%s", szTmp);
+}
+
+
+void VFmtChkErrFmt(PVFMTCHKSTATE pState, const char *pszLoc, const char *pszFormat, ...)
+{
+ char szTmp[1024];
+ va_list va;
+ va_start(va, pszFormat);
+ vsnprintf(szTmp, sizeof(szTmp), pszFormat, va);
+ va_end(va);
+
+ /* display the warning. */
+ error_at(MyGetFormatStringLocation(pState, pszLoc), "%s", szTmp);
+}
+
+
+
+void VFmtChkVerifyEndOfArgs(PVFMTCHKSTATE pState, unsigned iArg)
+{
+ dprintf("VFmtChkVerifyEndOfArgs: iArg=%u iArgs=%ld cArgs=%u\n", iArg, pState->iArgs, gimple_call_num_args(pState->hStmt));
+ if (pState->iArgs > 0)
+ {
+ iArg += pState->iArgs - 1;
+ unsigned cArgs = gimple_call_num_args(pState->hStmt);
+ if (iArg == cArgs)
+ { /* fine */ }
+ else if (iArg < cArgs)
+ {
+ tree hArg = gimple_call_arg(pState->hStmt, iArg);
+ if (cArgs - iArg > 1)
+ error_at(MY_LOC(hArg, pState), "%u extra arguments not consumed by format string", cArgs - iArg);
+ else if ( TREE_CODE(hArg) != INTEGER_CST
+ || !MY_DOUBLE_INT_FITS_SHWI(hArg)
+ || MY_DOUBLE_INT_TO_SHWI(hArg) != -99) /* ignore final dummy argument: ..., -99); */
+ error_at(MY_LOC(hArg, pState), "one extra argument not consumed by format string");
+ }
+ /* This should be handled elsewhere, but just in case. */
+ else if (iArg - 1 == cArgs)
+ error_at(pState->hFmtLoc, "one argument too few");
+ else
+ error_at(pState->hFmtLoc, "%u arguments too few", iArg - cArgs);
+ }
+}
+
+
+bool VFmtChkRequirePresentArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage)
+{
+ if (pState->iArgs > 0)
+ {
+ iArg += pState->iArgs - 1;
+ unsigned cArgs = gimple_call_num_args(pState->hStmt);
+ if (iArg >= cArgs)
+ {
+ VFmtChkErrFmt(pState, pszLoc, "Missing argument! %s", pszMessage);
+ return false;
+ }
+
+ tree hArg = gimple_call_arg(pState->hStmt, iArg);
+ tree hType = TREE_TYPE(hArg);
+ dprintf("arg%u: hArg=%p [%s] hType=%p [%s] cls=%s\n", iArg, hArg, get_tree_code_name(TREE_CODE(hArg)),
+ hType, get_tree_code_name(TREE_CODE(hType)), tree_code_class_strings[TREE_CODE_CLASS(TREE_CODE(hType))]);
+ dprintf(" nm=%p\n", TYPE_NAME(hType));
+ dprintf(" cb=%p %s value=%ld\n", TYPE_SIZE(hType), get_tree_code_name(TREE_CODE(TYPE_SIZE(hType))),
+ MY_DOUBLE_INT_TO_SHWI(TYPE_SIZE(hType)) );
+ dprintf(" unit=%p %s value=%ld\n", TYPE_SIZE_UNIT(hType), get_tree_code_name(TREE_CODE(TYPE_SIZE_UNIT(hType))),
+ MY_DOUBLE_INT_TO_SHWI(TYPE_SIZE_UNIT(hType)) );
+ tree hTypeNm = TYPE_NAME(hType);
+ if (hTypeNm)
+ dprintf(" typenm=%p %s '%s'\n", hTypeNm, get_tree_code_name(TREE_CODE(hTypeNm)),
+ IDENTIFIER_POINTER(DECL_NAME(hTypeNm)));
+ }
+ return true;
+}
+
+
+bool VFmtChkRequireIntArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage)
+{
+ if (VFmtChkRequirePresentArg(pState, pszLoc, iArg, pszMessage))
+ {
+ /** @todo type check. */
+ return true;
+ }
+ return false;
+}
+
+
+bool VFmtChkRequireStringArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage)
+{
+ if (VFmtChkRequirePresentArg(pState, pszLoc, iArg, pszMessage))
+ {
+ /** @todo type check. */
+ return true;
+ }
+ return false;
+}
+
+
+bool VFmtChkRequireVaListPtrArg(PVFMTCHKSTATE pState, const char *pszLoc, unsigned iArg, const char *pszMessage)
+{
+ if (VFmtChkRequirePresentArg(pState, pszLoc, iArg, pszMessage))
+ {
+ /** @todo type check. */
+ return true;
+ }
+ return false;
+}
+
+
+void VFmtChkHandleReplacementFormatString(PVFMTCHKSTATE pState, const char *pszPctM, unsigned iArg)
+{
+ if (pState->iArgs > 0)
+ {
+ pState->iFmt = pState->iArgs + iArg;
+ pState->iArgs = pState->iFmt + 1;
+ pState->fMaybeNull = false;
+ MyCheckFormatRecursive(pState, gimple_call_arg(pState->hStmt, pState->iFmt - 1));
+ }
+}
+
+
+const char *VFmtChkGetFmtLocFile(PVFMTCHKSTATE pState)
+{
+ return LOCATION_FILE(pState->hFmtLoc);
+}
+
+
+unsigned int VFmtChkGetFmtLocLine(PVFMTCHKSTATE pState)
+{
+ return LOCATION_LINE(pState->hFmtLoc);
+}
+
+
+unsigned int VFmtChkGetFmtLocColumn(PVFMTCHKSTATE pState)
+{
+#ifdef LOCATION_COLUMN
+ return LOCATION_COLUMN(pState->hFmtLoc);
+#else
+ return 1;
+#endif
+}
+
diff --git a/src/bldprogs/VBoxDef2LazyLoad.cpp b/src/bldprogs/VBoxDef2LazyLoad.cpp
new file mode 100644
index 00000000..30321377
--- /dev/null
+++ b/src/bldprogs/VBoxDef2LazyLoad.cpp
@@ -0,0 +1,1624 @@
+/* $Id: VBoxDef2LazyLoad.cpp $ */
+/** @file
+ * VBoxDef2LazyLoad - Lazy Library Loader Generator.
+ *
+ * @note Only tested on win.amd64 & darwin.amd64.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <iprt/types.h>
+#include <iprt/ldr.h> /* For RTLDRARCH. */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct MYEXPORT
+{
+ struct MYEXPORT *pNext;
+ /** Pointer to unmangled name for stdcall (after szName), NULL if not. */
+ char *pszUnstdcallName;
+ /** Pointer to the exported name. */
+ char const *pszExportedNm;
+ unsigned uOrdinal;
+ bool fNoName;
+ char szName[1];
+} MYEXPORT;
+typedef MYEXPORT *PMYEXPORT;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** @name Options
+ * @{ */
+static const char *g_pszOutput = NULL;
+static const char *g_pszLibrary = NULL;
+static const char *g_apszInputs[8] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
+static unsigned g_cInputs = 0;
+static bool g_fIgnoreData = true;
+static bool g_fWithExplictLoadFunction = false;
+static bool g_fSystemLibrary = false;
+#if defined(RT_ARCH_AMD64)
+static RTLDRARCH g_enmTarget = RTLDRARCH_AMD64;
+#elif defined(RT_ARCH_X86)
+static RTLDRARCH g_enmTarget = RTLDRARCH_X86_32;
+#elif defined(RT_ARCH_ARM64)
+static RTLDRARCH g_enmTarget = RTLDRARCH_ARM64;
+#else
+# error "Port me!"
+#endif
+/** @} */
+
+/** Pointer to the export name list head. */
+static PMYEXPORT g_pExpHead = NULL;
+/** Pointer to the next pointer for insertion. */
+static PMYEXPORT *g_ppExpNext = &g_pExpHead;
+
+
+
+#if 0 /* unused */
+static const char *leftStrip(const char *psz)
+{
+ while (isspace(*psz))
+ psz++;
+ return psz;
+}
+#endif
+
+
+static char *leftStrip(char *psz)
+{
+ while (isspace(*psz))
+ psz++;
+ return psz;
+}
+
+
+static unsigned wordLength(const char *pszWord)
+{
+ unsigned off = 0;
+ char ch;
+ while ( (ch = pszWord[off]) != '\0'
+ && ch != '='
+ && ch != ','
+ && ch != ':'
+ && !isspace(ch) )
+ off++;
+ return off;
+}
+
+
+/**
+ * Parses the module definition file, collecting export information.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE, in the latter case full
+ * details has been displayed.
+ * @param pInput The input stream.
+ */
+static RTEXITCODE parseInputInner(FILE *pInput, const char *pszInput)
+{
+ /*
+ * Process the file line-by-line.
+ */
+ bool fInExports = false;
+ unsigned iLine = 0;
+ char szLine[16384];
+ while (fgets(szLine, sizeof(szLine), pInput))
+ {
+ iLine++;
+
+ /*
+ * Strip leading and trailing spaces from the line as well as
+ * trailing comments.
+ */
+ char *psz = leftStrip(szLine);
+ if (*psz == ';')
+ continue; /* comment line. */
+
+ char *pszComment = strchr(psz, ';');
+ if (pszComment)
+ *pszComment = '\0';
+
+ unsigned cch = (unsigned)strlen(psz);
+ while (cch > 0 && (isspace(psz[cch - 1]) || psz[cch - 1] == '\r' || psz[cch - 1] == '\n'))
+ psz[--cch] = '\0';
+
+ if (!cch)
+ continue;
+
+ /*
+ * Check for known directives.
+ */
+ size_t cchWord0 = wordLength(psz);
+#define WORD_CMP(pszWord1, cchWord1, szWord2) \
+ ( (cchWord1) == sizeof(szWord2) - 1 && memcmp(pszWord1, szWord2, sizeof(szWord2) - 1) == 0 )
+ if (WORD_CMP(psz, cchWord0, "EXPORTS"))
+ {
+ fInExports = true;
+
+ /* In case there is an export on the same line. (Really allowed?) */
+ psz = leftStrip(psz + sizeof("EXPORTS") - 1);
+ if (!*psz)
+ continue;
+ }
+ /* Directives that we don't care about, but need to catch in order to
+ terminate the EXPORTS section in a timely manner. */
+ else if ( WORD_CMP(psz, cchWord0, "NAME")
+ || WORD_CMP(psz, cchWord0, "LIBRARY")
+ || WORD_CMP(psz, cchWord0, "DESCRIPTION")
+ || WORD_CMP(psz, cchWord0, "STACKSIZE")
+ || WORD_CMP(psz, cchWord0, "SECTIONS")
+ || WORD_CMP(psz, cchWord0, "SEGMENTS")
+ || WORD_CMP(psz, cchWord0, "VERSION")
+ )
+ {
+ fInExports = false;
+ }
+
+ /*
+ * Process exports:
+ * entryname[=internalname] [@ordinal[ ][NONAME]] [DATA] [PRIVATE]
+ */
+ if (fInExports)
+ {
+ const char *pchName = psz;
+ unsigned cchName = wordLength(psz);
+
+ psz = leftStrip(psz + cchName);
+ if (*psz == '=')
+ {
+ psz = leftStrip(psz + 1);
+ psz = leftStrip(psz + wordLength(psz));
+ }
+
+ bool fNoName = false;
+ unsigned uOrdinal = ~0U;
+ if (*psz == '@')
+ {
+ psz++;
+ if (!isdigit(*psz))
+ {
+ fprintf(stderr, "%s:%u: error: Invalid ordinal spec.\n", pszInput, iLine);
+ return RTEXITCODE_FAILURE;
+ }
+ uOrdinal = *psz++ - '0';
+ while (isdigit(*psz))
+ {
+ uOrdinal *= 10;
+ uOrdinal += *psz++ - '0';
+ }
+ psz = leftStrip(psz);
+ cch = wordLength(psz);
+ if (WORD_CMP(psz, cch, "NONAME"))
+ {
+ fNoName = true;
+ psz = leftStrip(psz + cch);
+ }
+ }
+
+ while (*psz)
+ {
+ cch = wordLength(psz);
+ if (WORD_CMP(psz, cch, "DATA"))
+ {
+ if (!g_fIgnoreData)
+ {
+ fprintf(stderr, "%s:%u: error: Cannot wrap up DATA export '%.*s'.\n",
+ pszInput, iLine, cchName, pchName);
+ return RTEXITCODE_SUCCESS;
+ }
+ }
+ else if (!WORD_CMP(psz, cch, "PRIVATE"))
+ {
+ fprintf(stderr, "%s:%u: error: Cannot wrap up DATA export '%.*s'.\n",
+ pszInput, iLine, cchName, pchName);
+ return RTEXITCODE_SUCCESS;
+ }
+ psz = leftStrip(psz + cch);
+ }
+
+ /*
+ * Check for stdcall mangling.
+ */
+ size_t cbExp = sizeof(MYEXPORT) + cchName;
+ unsigned cchStdcall = 0;
+ if (cchName > 3 && *pchName == '_' && isdigit(pchName[cchName - 1]))
+ {
+ if (cchName > 3 && pchName[cchName - 2] == '@')
+ cchStdcall = 2;
+ else if (cchName > 4 && pchName[cchName - 3] == '@' && isdigit(pchName[cchName - 2]))
+ cchStdcall = 3;
+ if (cchStdcall)
+ cbExp += cchName - 1 - cchStdcall;
+ }
+
+ /*
+ * Add the export.
+ */
+
+ PMYEXPORT pExp = (PMYEXPORT)malloc(cbExp);
+ if (!pExp)
+ {
+ fprintf(stderr, "%s:%u: error: Out of memory.\n", pszInput, iLine);
+ return RTEXITCODE_SUCCESS;
+ }
+ memcpy(pExp->szName, pchName, cchName);
+ pExp->szName[cchName] = '\0';
+ if (!cchStdcall)
+ {
+ pExp->pszUnstdcallName = NULL;
+ pExp->pszExportedNm = pExp->szName;
+ }
+ else
+ {
+ pExp->pszUnstdcallName = &pExp->szName[cchName + 1];
+ memcpy(pExp->pszUnstdcallName, pchName + 1, cchName - 1 - cchStdcall);
+ pExp->pszUnstdcallName[cchName - 1 - cchStdcall] = '\0';
+ pExp->pszExportedNm = pExp->pszUnstdcallName;
+ }
+ pExp->uOrdinal = uOrdinal;
+ pExp->fNoName = fNoName;
+ pExp->pNext = NULL;
+ *g_ppExpNext = pExp;
+ g_ppExpNext = &pExp->pNext;
+ }
+ }
+
+ /*
+ * Why did we quit the loop, EOF or error?
+ */
+ if (feof(pInput))
+ return RTEXITCODE_SUCCESS;
+ fprintf(stderr, "error: Read while reading '%s' (iLine=%u).\n", pszInput, iLine);
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Parses a_apszInputs, populating the list pointed to by g_pExpHead.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE, in the latter case full
+ * details has been displayed.
+ */
+static RTEXITCODE parseInputs(void)
+{
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ for (unsigned i = 0; i < g_cInputs; i++)
+ {
+ FILE *pInput = fopen(g_apszInputs[i], "r");
+ if (pInput)
+ {
+ RTEXITCODE rcExit2 = parseInputInner(pInput, g_apszInputs[i]);
+ fclose(pInput);
+ if (rcExit2 == RTEXITCODE_SUCCESS && !g_pExpHead)
+ {
+ fprintf(stderr, "error: Found no exports in '%s'.\n", g_apszInputs[i]);
+ rcExit2 = RTEXITCODE_FAILURE;
+ }
+ if (rcExit2 != RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+ }
+ else
+ {
+ fprintf(stderr, "error: Failed to open '%s' for reading.\n", g_apszInputs[i]);
+ rcExit = RTEXITCODE_FAILURE;
+ }
+ }
+ return rcExit;
+}
+
+
+/**
+ * Generates the assembly source code for AMD64 and x86, writing it
+ * to @a pOutput.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE, in the latter case full
+ * details has been displayed.
+ * @param pOutput The output stream (caller checks it for errors
+ * when closing).
+ */
+static RTEXITCODE generateOutputInnerX86AndAMD64(FILE *pOutput)
+{
+ fprintf(pOutput, ";;\n");
+ for (unsigned i = 0; i < g_cInputs; i++)
+ fprintf(pOutput, ";; Autogenerated from '%s'.\n", g_apszInputs[i]);
+
+ fprintf(pOutput,
+ ";; DO NOT EDIT!\n"
+ ";;\n"
+ "\n"
+ "\n"
+ "%%include \"iprt/asmdefs.mac\"\n"
+ "\n"
+ "\n");
+
+ /*
+ * Put the thunks first for alignment and other reasons. It's the hot part of the code.
+ */
+ fprintf(pOutput,
+ ";\n"
+ "; Thunks.\n"
+ ";\n"
+ "BEGINCODE\n");
+ for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext)
+ if (!pExp->pszUnstdcallName)
+ fprintf(pOutput,
+ "BEGINPROC %s\n"
+ " jmp RTCCPTR_PRE [g_pfn%s xWrtRIP]\n"
+ "ENDPROC %s\n",
+ pExp->szName, pExp->szName, pExp->szName);
+ else
+ fprintf(pOutput,
+ "%%ifdef RT_ARCH_X86\n"
+ "global %s\n"
+ "%s:\n"
+ " jmp RTCCPTR_PRE [g_pfn%s xWrtRIP]\n"
+ "%%else\n"
+ "BEGINPROC %s\n"
+ " jmp RTCCPTR_PRE [g_pfn%s xWrtRIP]\n"
+ "ENDPROC %s\n"
+ "%%endif\n",
+ pExp->szName, pExp->szName, pExp->pszUnstdcallName,
+ pExp->pszUnstdcallName, pExp->pszUnstdcallName, pExp->pszUnstdcallName);
+
+ fprintf(pOutput,
+ "\n"
+ "\n");
+
+ /*
+ * Import pointers
+ */
+ fprintf(pOutput,
+ ";\n"
+ "; Import pointers. Initialized to point a lazy loading stubs.\n"
+ ";\n"
+ "BEGINDATA\n"
+ "g_apfnImports:\n");
+ for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext)
+ if (pExp->pszUnstdcallName)
+ fprintf(pOutput,
+ "%%ifdef ASM_FORMAT_PE\n"
+ " %%ifdef RT_ARCH_X86\n"
+ "global __imp_%s\n"
+ "__imp_%s:\n"
+ " %%else\n"
+ "global __imp_%s\n"
+ "__imp_%s:\n"
+ " %%endif\n"
+ "%%endif\n"
+ "g_pfn%s RTCCPTR_DEF ___LazyLoad___%s\n"
+ "\n",
+ pExp->szName,
+ pExp->szName,
+ pExp->pszUnstdcallName,
+ pExp->pszUnstdcallName,
+ pExp->pszExportedNm,
+ pExp->pszExportedNm);
+ else
+ fprintf(pOutput,
+ "%%ifdef ASM_FORMAT_PE\n"
+ "global __imp_%s\n"
+ "__imp_%s:\n"
+ "%%endif\n"
+ "g_pfn%s RTCCPTR_DEF ___LazyLoad___%s\n"
+ "\n",
+ pExp->szName,
+ pExp->szName,
+ pExp->pszExportedNm,
+ pExp->pszExportedNm);
+ fprintf(pOutput,
+ "RTCCPTR_DEF 0 ; Terminator entry for traversal.\n"
+ "\n"
+ "\n");
+
+ /*
+ * Now for the less important stuff, starting with the names.
+ *
+ * We keep the names separate so we can traverse them in parallel to
+ * g_apfnImports in the load-everything routine further down.
+ */
+ fprintf(pOutput,
+ ";\n"
+ "; Imported names.\n"
+ ";\n"
+ "BEGINCODE\n"
+ "g_szLibrary: db '%s',0\n"
+ "\n"
+ "g_szzNames:\n",
+ g_pszLibrary);
+ for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext)
+ if (!pExp->fNoName)
+ fprintf(pOutput, " g_sz%s:\n db '%s',0\n", pExp->pszExportedNm, pExp->pszExportedNm);
+ else
+ fprintf(pOutput, " g_sz%s:\n db '#%u',0\n", pExp->pszExportedNm, pExp->uOrdinal);
+ fprintf(pOutput,
+ "g_EndOfNames: db 0\n"
+ "\n"
+ "g_szFailLoadFmt: db 'Lazy loader failed to load \"%%s\": %%Rrc', 10, 0\n"
+ "g_szFailResolveFmt: db 'Lazy loader failed to resolve symbol \"%%s\" in \"%%s\": %%Rrc', 10, 0\n"
+ "\n"
+ "\n");
+
+ /*
+ * The per import lazy load code.
+ */
+ fprintf(pOutput,
+ ";\n"
+ "; Lazy load+resolve stubs.\n"
+ ";\n"
+ "BEGINCODE\n");
+ for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext)
+ {
+ if (!pExp->fNoName)
+ fprintf(pOutput,
+ "___LazyLoad___%s:\n"
+ /* "int3\n" */
+ "%%ifdef RT_ARCH_AMD64\n"
+ " lea rax, [g_sz%s wrt rip]\n"
+ " lea r10, [g_pfn%s wrt rip]\n"
+ " call LazyLoadResolver\n"
+ "%%elifdef RT_ARCH_X86\n"
+ " push g_sz%s\n"
+ " push g_pfn%s\n"
+ " call LazyLoadResolver\n"
+ " add esp, 8h\n"
+ "%%else\n"
+ " %%error \"Unsupported architecture\"\n"
+ "%%endif\n"
+ ,
+ pExp->pszExportedNm,
+ pExp->pszExportedNm,
+ pExp->pszExportedNm,
+ pExp->pszExportedNm,
+ pExp->pszExportedNm);
+ else
+ fprintf(pOutput,
+ "___LazyLoad___%s:\n"
+ /* "int3\n" */
+ "%%ifdef RT_ARCH_AMD64\n"
+ " mov eax, %u\n"
+ " lea r10, [g_pfn%s wrt rip]\n"
+ " call LazyLoadResolver\n"
+ "%%elifdef RT_ARCH_X86\n"
+ " push %u\n"
+ " push g_pfn%s\n"
+ " call LazyLoadResolver\n"
+ " add esp, 8h\n"
+ "%%else\n"
+ " %%error \"Unsupported architecture\"\n"
+ "%%endif\n"
+ ,
+ pExp->pszExportedNm,
+ pExp->uOrdinal,
+ pExp->pszExportedNm,
+ pExp->uOrdinal,
+ pExp->pszExportedNm);
+ if (!pExp->pszUnstdcallName)
+ fprintf(pOutput, " jmp NAME(%s)\n", pExp->szName);
+ else
+ fprintf(pOutput,
+ "%%ifdef RT_ARCH_X86\n"
+ " jmp %s\n"
+ "%%else\n"
+ " jmp NAME(%s)\n"
+ "%%endif\n"
+ ,
+ pExp->szName, pExp->pszUnstdcallName);
+ fprintf(pOutput, "\n");
+ }
+ fprintf(pOutput,
+ "\n"
+ "\n"
+ "\n");
+
+ /*
+ * The code that does the loading and resolving.
+ */
+ fprintf(pOutput,
+ ";\n"
+ "; The module handle.\n"
+ ";\n"
+ "BEGINDATA\n"
+ "g_hMod RTCCPTR_DEF 0\n"
+ "\n"
+ "\n"
+ "\n");
+
+ /*
+ * How we load the module needs to be selectable later on.
+ *
+ * The LazyLoading routine returns the module handle in RCX/ECX, caller
+ * saved all necessary registers.
+ */
+ if (!g_fSystemLibrary)
+ fprintf(pOutput,
+ ";\n"
+ ";SUPR3DECL(int) SUPR3HardenedLdrLoadAppPriv(const char *pszFilename, PRTLDRMOD phLdrMod,\n"
+ "; uint32_t fFlags, PRTERRINFO pErrInfo);\n"
+ ";\n"
+ "EXTERN_IMP2 SUPR3HardenedLdrLoadAppPriv\n"
+ "%%ifdef IN_RT_R3\n"
+ "extern NAME(RTAssertMsg2Weak)\n"
+ "%%else\n"
+ "EXTERN_IMP2 RTAssertMsg2Weak\n"
+ "%%endif\n"
+ "BEGINCODE\n"
+ "\n"
+ "LazyLoading:\n"
+ " mov xCX, [g_hMod xWrtRIP]\n"
+ " or xCX, xCX\n"
+ " jnz .return\n"
+ "\n"
+ "%%ifdef ASM_CALL64_GCC\n"
+ " xor rcx, rcx ; pErrInfo\n"
+ " xor rdx, rdx ; fFlags (local load)\n"
+ " lea rsi, [g_hMod wrt rip] ; phLdrMod\n"
+ " lea rdi, [g_szLibrary wrt rip] ; pszFilename\n"
+ " sub rsp, 08h\n"
+ " call IMP2(SUPR3HardenedLdrLoadAppPriv)\n"
+ " add rsp, 08h\n"
+ "\n"
+ "%%elifdef ASM_CALL64_MSC\n"
+ " xor r9, r9 ; pErrInfo\n"
+ " xor r8, r8 ; fFlags (local load)\n"
+ " lea rdx, [g_hMod wrt rip] ; phLdrMod\n"
+ " lea rcx, [g_szLibrary wrt rip] ; pszFilename\n"
+ " sub rsp, 28h\n"
+ " call IMP2(SUPR3HardenedLdrLoadAppPriv)\n"
+ " add rsp, 28h\n"
+ "\n"
+ "%%elifdef RT_ARCH_X86\n"
+ " sub xSP, 0ch\n"
+ " push 0 ; pErrInfo\n"
+ " push 0 ; fFlags (local load)\n"
+ " push g_hMod ; phLdrMod\n"
+ " push g_szLibrary ; pszFilename\n"
+ " call IMP2(SUPR3HardenedLdrLoadAppPriv)\n"
+ " add esp, 1ch\n"
+ "%%else\n"
+ " %%error \"Unsupported architecture\"\n"
+ "%%endif\n");
+ else
+ fprintf(pOutput,
+ ";\n"
+ "; RTDECL(int) RTLdrLoadSystem(const char *pszFilename, bool fNoUnload, PRTLDRMOD phLdrMod);\n"
+ ";\n"
+ "%%ifdef IN_RT_R3\n"
+ "extern NAME(RTLdrLoadSystem)\n"
+ "extern NAME(RTAssertMsg2Weak)\n"
+ "%%else\n"
+ "EXTERN_IMP2 RTLdrLoadSystem\n"
+ "EXTERN_IMP2 RTAssertMsg2Weak\n"
+ "%%endif\n"
+ "BEGINCODE\n"
+ "\n"
+ "LazyLoading:\n"
+ " mov xCX, [g_hMod xWrtRIP]\n"
+ " or xCX, xCX\n"
+ " jnz .return\n"
+ "\n"
+ "%%ifdef ASM_CALL64_GCC\n"
+ " lea rdx, [g_hMod wrt rip] ; phLdrMod\n"
+ " mov esi, 1 ; fNoUnload=true\n"
+ " lea rdi, [g_szLibrary wrt rip] ; pszFilename\n"
+ " sub rsp, 08h\n"
+ " %%ifdef IN_RT_R3\n"
+ " call NAME(RTLdrLoadSystem)\n"
+ " %%else\n"
+ " call IMP2(RTLdrLoadSystem)\n"
+ " %%endif\n"
+ " add rsp, 08h\n"
+ "\n"
+ "%%elifdef ASM_CALL64_MSC\n"
+ " lea r8, [g_hMod wrt rip] ; phLdrMod\n"
+ " mov edx, 1 ; fNoUnload=true\n"
+ " lea rcx, [g_szLibrary wrt rip] ; pszFilename\n"
+ " sub rsp, 28h\n"
+ " %%ifdef IN_RT_R3\n"
+ " call NAME(RTLdrLoadSystem)\n"
+ " %%else\n"
+ " call IMP2(RTLdrLoadSystem)\n"
+ " %%endif\n"
+ " add rsp, 28h\n"
+ "\n"
+ "%%elifdef RT_ARCH_X86\n"
+ " push g_hMod ; phLdrMod\n"
+ " push 1 ; fNoUnload=true\n"
+ " push g_szLibrary ; pszFilename\n"
+ " %%ifdef IN_RT_R3\n"
+ " call NAME(RTLdrLoadSystem)\n"
+ " %%else\n"
+ " call IMP2(RTLdrLoadSystem)\n"
+ " %%endif\n"
+ " add esp, 0ch\n"
+ "%%else\n"
+ " %%error \"Unsupported architecture\"\n"
+ "%%endif\n");
+ fprintf(pOutput,
+ " or eax, eax\n"
+ " jnz .badload\n"
+ " mov xCX, [g_hMod xWrtRIP]\n"
+ ".return:\n"
+ " ret\n"
+ "\n"
+ ".badload:\n"
+ "%%ifdef ASM_CALL64_GCC\n"
+ " mov edx, eax\n"
+ " lea rsi, [g_szLibrary wrt rip]\n"
+ " lea rdi, [g_szFailLoadFmt wrt rip]\n"
+ " sub rsp, 08h\n"
+ "%%elifdef ASM_CALL64_MSC\n"
+ " mov r8d, eax\n"
+ " lea rdx, [g_szLibrary wrt rip]\n"
+ " lea rcx, [g_szFailLoadFmt wrt rip]\n"
+ " sub rsp, 28h\n"
+ "%%elifdef RT_ARCH_X86\n"
+ " push eax\n"
+ " push g_szLibrary\n"
+ " push g_szFailLoadFmt\n"
+ "%%endif\n"
+ "%%ifdef IN_RT_R3\n"
+ " call NAME(RTAssertMsg2Weak)\n"
+ "%%else\n"
+ " call IMP2(RTAssertMsg2Weak)\n"
+ "%%endif\n"
+ ".badloadloop:\n"
+ " int3\n"
+ " jmp .badloadloop\n"
+ "LazyLoading_End:\n"
+ "\n"
+ "\n");
+
+
+ fprintf(pOutput,
+ ";\n"
+ ";RTDECL(int) RTLdrGetSymbol(RTLDRMOD hLdrMod, const char *pszSymbol, void **ppvValue);\n"
+ ";\n"
+ "%%ifdef IN_RT_R3\n"
+ "extern NAME(RTLdrGetSymbol)\n"
+ "%%else\n"
+ "EXTERN_IMP2 RTLdrGetSymbol\n"
+ "%%endif\n"
+ "BEGINCODE\n"
+ "LazyLoadResolver:\n"
+ "%%ifdef RT_ARCH_AMD64\n"
+ " push rbp\n"
+ " mov rbp, rsp\n"
+ " push r15\n"
+ " push r14\n"
+ " mov r15, rax ; name\n"
+ " mov r14, r10 ; ppfn\n"
+ " push r9\n"
+ " push r8\n"
+ " push rcx\n"
+ " push rdx\n"
+ " push r12\n"
+ " %%ifdef ASM_CALL64_GCC\n"
+ " push rsi\n"
+ " push rdi\n"
+ " mov r12, rsp\n"
+ " %%else\n"
+ " mov r12, rsp\n"
+ " sub rsp, 20h\n"
+ " %%endif\n"
+ " and rsp, 0fffffff0h ; Try make sure the stack is aligned\n"
+ "\n"
+ " call LazyLoading ; returns handle in rcx\n"
+ " %%ifdef ASM_CALL64_GCC\n"
+ " mov rdi, rcx ; hLdrMod\n"
+ " mov rsi, r15 ; pszSymbol\n"
+ " mov rdx, r14 ; ppvValue\n"
+ " %%else\n"
+ " mov rdx, r15 ; pszSymbol\n"
+ " mov r8, r14 ; ppvValue\n"
+ " %%endif\n"
+ " %%ifdef IN_RT_R3\n"
+ " call NAME(RTLdrGetSymbol)\n"
+ " %%else\n"
+ " call IMP2(RTLdrGetSymbol)\n"
+ " %%endif\n"
+ " or eax, eax\n"
+ " jnz .badsym\n"
+ "\n"
+ " mov rsp, r12\n"
+ " %%ifdef ASM_CALL64_GCC\n"
+ " pop rdi\n"
+ " pop rsi\n"
+ " %%endif\n"
+ " pop r12\n"
+ " pop rdx\n"
+ " pop rcx\n"
+ " pop r8\n"
+ " pop r9\n"
+ " pop r14\n"
+ " pop r15\n"
+ " leave\n"
+ "\n"
+ "%%elifdef RT_ARCH_X86\n"
+ " push ebp\n"
+ " mov ebp, esp\n"
+ " push eax\n"
+ " push ecx\n"
+ " push edx\n"
+ " and esp, 0fffffff0h\n"
+ "\n"
+ ".loaded:\n"
+ " call LazyLoading ; returns handle in ecx\n"
+ " push dword [ebp + 8] ; value addr\n"
+ " push dword [ebp + 12] ; symbol name\n"
+ " push ecx\n"
+ " %%ifdef IN_RT_R3\n"
+ " call NAME(RTLdrGetSymbol)\n"
+ " %%else\n"
+ " call IMP2(RTLdrGetSymbol)\n"
+ " %%endif\n"
+ " or eax, eax\n"
+ " jnz .badsym\n"
+ " lea esp, [ebp - 0ch]\n"
+ " pop edx\n"
+ " pop ecx\n"
+ " pop eax\n"
+ " leave\n"
+ "%%else\n"
+ " %%error \"Unsupported architecture\"\n"
+ "%%endif\n"
+ " ret\n"
+ "\n"
+ ".badsym:\n"
+ "%%ifdef ASM_CALL64_GCC\n"
+ " mov ecx, eax\n"
+ " lea rdx, [g_szLibrary wrt rip]\n"
+ " mov rsi, r15\n"
+ " lea rdi, [g_szFailResolveFmt wrt rip]\n"
+ " sub rsp, 08h\n"
+ "%%elifdef ASM_CALL64_MSC\n"
+ " mov r9d, eax\n"
+ " mov r8, r15\n"
+ " lea rdx, [g_szLibrary wrt rip]\n"
+ " lea rcx, [g_szFailResolveFmt wrt rip]\n"
+ " sub rsp, 28h\n"
+ "%%elifdef RT_ARCH_X86\n"
+ " push eax\n"
+ " push dword [ebp + 12]\n"
+ " push g_szLibrary\n"
+ " push g_szFailResolveFmt\n"
+ "%%endif\n"
+ "%%ifdef IN_RT_R3\n"
+ " call NAME(RTAssertMsg2Weak)\n"
+ "%%else\n"
+ " call IMP2(RTAssertMsg2Weak)\n"
+ "%%endif\n"
+ ".badsymloop:\n"
+ " int3\n"
+ " jmp .badsymloop\n"
+ "\n"
+ "LazyLoadResolver_End:\n"
+ "\n"
+ "\n"
+ );
+
+
+
+ /*
+ * C callable method for explicitly loading the library and optionally
+ * resolving all the imports.
+ */
+ if (g_fWithExplictLoadFunction)
+ {
+ if (g_fSystemLibrary) /* Lazy bird. */
+ {
+ fprintf(stderr, "error: cannot use --system with --explicit-load-function, sorry\n");
+ return RTEXITCODE_FAILURE;
+ }
+
+ int cchLibBaseName = (int)(strchr(g_pszLibrary, '.') ? strchr(g_pszLibrary, '.') - g_pszLibrary : strlen(g_pszLibrary));
+ fprintf(pOutput,
+ ";;\n"
+ "; ExplicitlyLoad%.*s(bool fResolveAllImports, pErrInfo);\n"
+ ";\n"
+ "EXTERN_IMP2 RTErrInfoSet\n"
+ "BEGINCODE\n"
+ "BEGINPROC ExplicitlyLoad%.*s\n"
+ " push xBP\n"
+ " mov xBP, xSP\n"
+ " push xBX\n"
+ "%%ifdef ASM_CALL64_GCC\n"
+ " %%define pszCurStr r14\n"
+ " push r14\n"
+ "%%else\n"
+ " %%define pszCurStr xDI\n"
+ " push xDI\n"
+ "%%endif\n"
+ " sub xSP, 40h\n"
+ "\n"
+ " ;\n"
+ " ; Save parameters on stack (64-bit only).\n"
+ " ;\n"
+ "%%ifdef ASM_CALL64_GCC\n"
+ " mov [xBP - xCB * 3], rdi ; fResolveAllImports\n"
+ " mov [xBP - xCB * 4], rsi ; pErrInfo\n"
+ "%%elifdef ASM_CALL64_MSC\n"
+ " mov [xBP - xCB * 3], rcx ; fResolveAllImports\n"
+ " mov [xBP - xCB * 4], rdx ; pErrInfo\n"
+ "%%endif\n"
+ "\n"
+ " ;\n"
+ " ; Is the module already loaded?\n"
+ " ;\n"
+ " cmp RTCCPTR_PRE [g_hMod xWrtRIP], 0\n"
+ " jnz .loaded\n"
+ "\n"
+ " ;\n"
+ " ; Load the module.\n"
+ " ;\n"
+ "%%ifdef ASM_CALL64_GCC\n"
+ " mov rcx, [xBP - xCB * 4] ; pErrInfo\n"
+ " xor rdx, rdx ; fFlags (local load)\n"
+ " lea rsi, [g_hMod wrt rip] ; phLdrMod\n"
+ " lea rdi, [g_szLibrary wrt rip] ; pszFilename\n"
+ " call IMP2(SUPR3HardenedLdrLoadAppPriv)\n"
+ "\n"
+ "%%elifdef ASM_CALL64_MSC\n"
+ " mov r9, [xBP - xCB * 4] ; pErrInfo\n"
+ " xor r8, r8 ; fFlags (local load)\n"
+ " lea rdx, [g_hMod wrt rip] ; phLdrMod\n"
+ " lea rcx, [g_szLibrary wrt rip] ; pszFilename\n"
+ " call IMP2(SUPR3HardenedLdrLoadAppPriv)\n"
+ "\n"
+ "%%elifdef RT_ARCH_X86\n"
+ " sub xSP, 0ch\n"
+ " push dword [xBP + 12] ; pErrInfo\n"
+ " push 0 ; fFlags (local load)\n"
+ " push g_hMod ; phLdrMod\n"
+ " push g_szLibrary ; pszFilename\n"
+ " call IMP2(SUPR3HardenedLdrLoadAppPriv)\n"
+ " add esp, 1ch\n"
+ "%%else\n"
+ " %%error \"Unsupported architecture\"\n"
+ "%%endif\n"
+ " or eax, eax\n"
+ " jnz .return\n"
+ "\n"
+ " ;\n"
+ " ; Resolve the imports too if requested to do so.\n"
+ " ;\n"
+ ".loaded:\n"
+ "%%ifdef ASM_ARCH_X86\n"
+ " cmp byte [xBP + 8], 0\n"
+ "%%else\n"
+ " cmp byte [xBP - xCB * 3], 0\n"
+ "%%endif\n"
+ " je .return\n"
+ "\n"
+ " lea pszCurStr, [g_szzNames xWrtRIP]\n"
+ " lea xBX, [g_apfnImports xWrtRIP]\n"
+ ".next_import:\n"
+ " cmp RTCCPTR_PRE [xBX], 0\n"
+ " je .return\n"
+ "%%ifdef ASM_CALL64_GCC\n"
+ " mov rdx, xBX ; ppvValue\n"
+ " mov rsi, pszCurStr ; pszSymbol\n"
+ " mov rdi, [g_hMod wrt rip] ; hLdrMod\n"
+ " call IMP2(RTLdrGetSymbol)\n"
+ "%%elifdef ASM_CALL64_MSC\n"
+ " mov r8, xBX ; ppvValue\n"
+ " mov rdx, pszCurStr ; pszSymbol\n"
+ " mov rcx, [g_hMod wrt rip] ; pszSymbol\n"
+ " call IMP2(RTLdrGetSymbol)\n"
+ "%%else\n"
+ " push xBX ; ppvValue\n"
+ " push pszCurStr ; pszSymbol\n"
+ " push RTCCPTR_PRE [g_hMod] ; hLdrMod\n"
+ " call IMP2(RTLdrGetSymbol)\n"
+ " add xSP, 0ch\n"
+ "%%endif\n"
+ " or eax, eax\n"
+ " jnz .symbol_error\n"
+ "\n"
+ " ; Advance.\n"
+ " add xBX, RTCCPTR_CB\n"
+ " xor eax, eax\n"
+ " mov xCX, 0ffffffffh\n"
+ "%%ifdef ASM_CALL64_GCC\n"
+ " mov xDI, pszCurStr\n"
+ " repne scasb\n"
+ " mov pszCurStr, xDI\n"
+ "%%else\n"
+ " repne scasb\n"
+ "%%endif\n"
+ " jmp .next_import\n"
+ "\n"
+ " ;\n"
+ " ; Error loading a symbol. Call RTErrInfoSet on pErrInfo (preserves eax).\n"
+ " ;\n"
+ ".symbol_error:\n"
+ "%%ifdef ASM_CALL64_GCC\n"
+ " mov rdx, pszCurStr ; pszMsg\n"
+ " mov esi, eax ; rc\n"
+ " mov rdi, [xBP - xCB * 4] ; pErrInfo\n"
+ " call IMP2(RTErrInfoSet)\n"
+ "%%elifdef ASM_CALL64_MSC\n"
+ " mov r8, pszCurStr ; pszMsg\n"
+ " mov edx, eax ; rc\n"
+ " mov rcx, [xBP - xCB * 4] ; pErrInfo\n"
+ " call IMP2(RTErrInfoSet)\n"
+ "%%else\n"
+ " push pszCurStr ; pszMsg\n"
+ " push eax ; pszSymbol\n"
+ " push dword [xBP + 0ch] ; pErrInfo\n"
+ " call IMP2(RTErrInfoSet)\n"
+ " add xSP, 0ch\n"
+ "%%endif\n"
+ " "
+ "\n"
+ ".return:\n"
+ " mov pszCurStr, [xBP - xCB * 2]\n"
+ " mov xBX, [xBP - xCB * 1]\n"
+ " leave\n"
+ " ret\n"
+ "ENDPROC ExplicitlyLoad%.*s\n"
+ "\n"
+ "\n"
+ ,
+ cchLibBaseName, g_pszLibrary,
+ cchLibBaseName, g_pszLibrary,
+ cchLibBaseName, g_pszLibrary
+ );
+ }
+
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Generates the assembly source code for ARM64, writing it
+ * to @a pOutput.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE, in the latter case full
+ * details has been displayed.
+ * @param pOutput The output stream (caller checks it for errors
+ * when closing).
+ */
+static RTEXITCODE generateOutputInnerArm64(FILE *pOutput)
+{
+// bool fMachO = true;
+// bool fDarwin = true;
+ const char *pszNmPfx = "_";
+
+ fprintf(pOutput, ";;\n");
+ for (unsigned i = 0; i < g_cInputs; i++)
+ fprintf(pOutput, ";; Autogenerated from '%s'.\n", g_apszInputs[i]);
+
+ fprintf(pOutput,
+ ";; DO NOT EDIT!\n"
+ ";;\n"
+ "\n"
+ "\n"
+ /*"%%include \"iprt/asmdefs.mac\"\n"*/
+ "\n"
+ "\n");
+
+ /*
+ * Put the thunks first for alignment and other reasons. It's the hot part of the code.
+ */
+ fprintf(pOutput,
+ ";\n"
+ "; Thunks.\n"
+ ";\n"
+ ".section __TEXT,__text,regular,pure_instructions\n");
+ for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext)
+ fprintf(pOutput,
+ ".p2align 3\n"
+ ".globl %s%s\n"
+ "%s%s:\n"
+ " adrp x9, %sg_pfn%s@PAGE\n"
+ " ldr x9, [x9, %sg_pfn%s@PAGEOFF]\n"
+ " br x9\n",
+ pszNmPfx, pExp->szName, pszNmPfx, pExp->szName, pszNmPfx, pExp->szName, pszNmPfx, pExp->szName);
+ fprintf(pOutput,
+ "\n"
+ "\n");
+
+ /*
+ * Import pointers
+ */
+ fprintf(pOutput,
+ ";\n"
+ "; Import pointers. Initialized to point a lazy loading stubs.\n"
+ ";\n"
+ ".section __DATA,__data\n"
+ ".p2align 3\n"
+ "g_apfnImports:\n");
+ for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext)
+ fprintf(pOutput,
+ ".globl __imp_%s\n"
+ "__imp_%s:\n"
+ ".globl %sg_pfn%s\n"
+ "%sg_pfn%s:\n"
+ " .quad ___LazyLoad___%s\n"
+ "\n",
+ pExp->szName, pExp->szName,
+ pszNmPfx, pExp->szName, pszNmPfx, pExp->szName,
+ pExp->pszExportedNm);
+ fprintf(pOutput,
+ " .quad 0 ; Terminator entry for traversal.\n"
+ "\n"
+ "\n");
+
+ /*
+ * Now for the less important stuff, starting with the names.
+ *
+ * We keep the names separate so we can traverse them in parallel to
+ * g_apfnImports in the load-everything routine further down.
+ */
+ fprintf(pOutput,
+ ";\n"
+ "; Imported names.\n"
+ ";\n"
+ ".section __TEXT,__cstring,cstring_literals\n"
+ "g_szLibrary:\n"
+ " .asciz \"%s\"\n"
+ "\n"
+ "g_szzNames:\n",
+ g_pszLibrary);
+ for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext)
+ if (!pExp->fNoName)
+ fprintf(pOutput, " g_sz%s:\n .asciz \"%s\"\n", pExp->pszExportedNm, pExp->pszExportedNm);
+ else
+ fprintf(pOutput, " g_sz%s:\n .asciz \"#%u\"\n", pExp->pszExportedNm, pExp->uOrdinal);
+ fprintf(pOutput,
+ "g_EndOfNames: .byte 0\n"
+ "\n"
+ "g_szFailLoadFmt: .asciz \"Lazy loader failed to load \\\"%%s\\\": %%Rrc\\n\"\n"
+ "g_szFailResolveFmt: .asciz \"Lazy loader failed to resolve symbol \\\"%%s\\\" in \\\"%%s\\\": %%Rrc\\n\"\n"
+ "\n"
+ "\n");
+
+ /*
+ * The per import lazy load code.
+ */
+ fprintf(pOutput,
+ ";\n"
+ "; Lazy load+resolve stubs.\n"
+ ";\n"
+ ".section __TEXT,__text,regular,pure_instructions\n"
+ ".p2align 3\n");
+ for (PMYEXPORT pExp = g_pExpHead; pExp; pExp = pExp->pNext)
+ {
+ if (!pExp->fNoName)
+ fprintf(pOutput,
+ "___LazyLoad___%s:\n"
+ " adrp x9, g_sz%s@PAGE\n"
+ " add x9, x9, g_sz%s@PAGEOFF\n"
+ " adrp x10, %sg_pfn%s@PAGE\n"
+ " add x10, x10, %sg_pfn%s@PAGEOFF\n"
+ " bl LazyLoadResolver\n"
+ , pExp->pszExportedNm,
+ pExp->pszExportedNm, pExp->pszExportedNm,
+ pszNmPfx, pExp->pszExportedNm, pszNmPfx, pExp->pszExportedNm);
+ else
+ fprintf(pOutput,
+ "___LazyLoad___%s:\n"
+ " movk w9, #%u\n"
+ " adrp x10, %sg_pfn%s@PAGE\n"
+ " add x10, x10, %sg_pfn%s@PAGEOFF\n"
+ , pExp->pszExportedNm,
+ pExp->uOrdinal,
+ pszNmPfx, pExp->pszExportedNm, pszNmPfx, pExp->pszExportedNm);
+ fprintf(pOutput, " b %s%s\n", pszNmPfx, pExp->szName);
+ fprintf(pOutput, "\n");
+ }
+ fprintf(pOutput,
+ "\n"
+ "\n"
+ "\n");
+
+ /*
+ * The code that does the loading and resolving.
+ */
+ fprintf(pOutput,
+ ";\n"
+ "; The module handle.\n"
+ ";\n"
+ ".section __DATA,__data\n"
+ "g_hMod:\n"
+ " .quad 0\n"
+ "\n"
+ "\n"
+ "\n");
+
+ /*
+ * Common lazy loader and resolved.
+ */
+ fprintf(pOutput,
+ ";\n"
+ "; The resolver code.\n"
+ ";\n"
+ ".section __TEXT,__text,regular,pure_instructions\n"
+ ".p2align 3\n"
+ "LazyLoadResolver:\n"
+ " .cfi_startproc\n"
+ " ; Create frame.\n"
+ " sub sp, sp, #(16 + 192)\n"
+ " stp x29, x30, [sp, #192]\n"
+ " add x29, sp, #192\n"
+ " .cfi_def_cfa x29, 16\n"
+ " .cfi_offset x30, -8\n"
+ " .cfi_offset x29, -16\n"
+ " ; Save all argument registers and a handful of preserved ones.\n"
+ " stp x0, x1, [sp, #(192 - 16)]\n"
+ " .cfi_offset x0, -32\n"
+ " .cfi_offset x1, -24\n"
+ " stp x2, x3, [sp, #(192 - 32)]\n"
+ " .cfi_offset x3, -40\n"
+ " .cfi_offset x2, -48\n"
+ " stp x4, x5, [sp, #(192 - 48)]\n"
+ " .cfi_offset x6, -56\n"
+ " .cfi_offset x5, -64\n"
+ " stp x6, x7, [sp, #(192 - 64)]\n"
+ " .cfi_offset x7, -72\n"
+ " .cfi_offset x6, -80\n"
+ " stp x16, x17, [sp, #(192 - 80)]\n"
+ " .cfi_offset x17, -88\n"
+ " .cfi_offset x16, -96\n"
+ " stp x18, x19, [sp, #(192 - 96)]\n"
+ " .cfi_offset x19, -104\n"
+ " .cfi_offset x18, -112\n"
+ " stp x20, x21, [sp, #(192 - 112)]\n"
+ " .cfi_offset x21, -120\n"
+ " .cfi_offset x20, -128\n"
+ " stp x22, x23, [sp, #(192 - 128)]\n"
+ " .cfi_offset x23, -136\n"
+ " .cfi_offset x22, -144\n"
+ " str x8, [sp, #(192 - 144)]\n"
+ "\n"
+ " ; Shift the symbol name to x19 and g_pfnXXXX pointer to x20 as these are preserved registers\n"
+ " ; (in case we need to call LazyLoadModule/RTLdrLoad)\n"
+ " mov x19, x9\n"
+ " mov x20, x10\n"
+ "\n"
+ " ; Get the module handle and call RTLdrGetSymbol(RTLDRMOD hLdrMod, const char *pszSymbol, void **ppvValue)\n"
+ " adrp x0, g_hMod@PAGE\n"
+ " ldr x0, [x0, g_hMod@PAGEOFF]\n"
+ " cmp x0, #0\n"
+ " b.eq LazyLoading\n"
+ " mov x1, x19\n"
+ " mov x2, x20\n"
+ " bl %sRTLdrGetSymbol\n"
+ "\n"
+ " cmp w0, #0\n"
+ " b.eq Lreturn\n"
+ "\n"
+ "Lbadsym: ; Call sRTAssertMsg2Weak. Variadic (...) arguments are passed on the stack it seems.\n"
+ " mov x3, x0\n"
+ " adrp x2, g_szLibrary@PAGE\n"
+ " add x2, x2, g_szLibrary@PAGEOFF\n"
+ " mov x1, x19\n"
+ " adrp x0, g_szFailLoadFmt@PAGE\n"
+ " add x0, x0, g_szFailLoadFmt@PAGEOFF\n"
+ " stp x1, x2, [sp]\n"
+ " str x3, [sp, #16]\n"
+ " bl %sRTAssertMsg2Weak\n"
+ "Lbadsymloop:\n"
+ " brk #0x1\n"
+ " b Lbadsymloop\n"
+
+ "Lreturn:\n"
+ " ; Restore saved register\n"
+ " ldr x8, [sp, #(192 - 144)]\n"
+ " .cfi_restore x8\n"
+ " ldp x22, x23, [sp, #(192 - 128)]\n"
+ " .cfi_restore x23\n"
+ " .cfi_restore x22\n"
+ " ldp x20, x21, [sp, #(192 - 112)]\n"
+ " .cfi_restore x21\n"
+ " .cfi_restore x20\n"
+ " ldp x18, x19, [sp, #(192 - 96)]\n"
+ " .cfi_restore x19\n"
+ " .cfi_restore x18\n"
+ " ldp x16, x17, [sp, #(192 - 80)]\n"
+ " .cfi_restore x17\n"
+ " .cfi_restore x18\n"
+ " ldp x6, x7, [sp, #(192 - 64)]\n"
+ " .cfi_restore x7\n"
+ " .cfi_restore x6\n"
+ " ldp x4, x5, [sp, #(192 - 48)]\n"
+ " .cfi_restore x5\n"
+ " .cfi_restore x4\n"
+ " ldp x2, x3, [sp, #(192 - 32)]\n"
+ " .cfi_restore x3\n"
+ " .cfi_restore x2\n"
+ " ldp x0, x1, [sp, #(192 - 16)]\n"
+ " .cfi_restore x1\n"
+ " .cfi_restore x0\n"
+ "\n"
+ " ldp x29, x30, [sp, #192]\n"
+ " .cfi_restore x29\n"
+ " .cfi_restore x30\n"
+ " add sp, sp, #(16 + 192)\n"
+ " ret\n"
+ " .cfi_endproc\n"
+ "\n"
+ "\n"
+ , pszNmPfx, pszNmPfx);
+
+ fprintf(pOutput,
+ ";\n"
+ "; Loads the module.\n"
+ "; ASSUMES called from LazyLoadResolver where all relevant registers are already saved.\n"
+ ";\n"
+ "LazyLoading:\n"
+ " .cfi_startproc\n"
+ " ; Create frame.\n"
+ " sub sp, sp, #(16 + 48)\n"
+ " stp x29, x30, [sp, #48]\n"
+ " add x29, sp, #48\n"
+ " .cfi_def_cfa x29, 16\n"
+ " .cfi_offset x30, -8\n"
+ " .cfi_offset x29, -16\n"
+ "\n");
+
+ if (!g_fSystemLibrary)
+ fprintf(pOutput,
+ " ; Call SUPR3HardenedLdrLoadAppPriv(const char *pszFilename, PRTLDRMOD phLdrMod, uint32_t fFlags, PRTERRINFO pErrInfo);\n"
+ " mov x3, #0\n"
+ " mov x2, #0\n"
+ " adrp x1, g_hMod@PAGE\n"
+ " add x1, x1, g_hMod@PAGEOFF\n"
+ " adrp x0, g_szLibrary@PAGE\n"
+ " add x0, x0, g_szLibrary@PAGEOFF\n"
+ " bl %sSUPR3HardenedLdrLoadAppPriv\n"
+ , pszNmPfx);
+ else
+ fprintf(pOutput,
+ " ; Call RTLdrLoadSystem(const char *pszFilename, bool fNoUnload, PRTLDRMOD phLdrMod);\n"
+ " adrp x2, g_hMod@PAGE\n"
+ " add x2, x2, g_hMod@PAGEOFF\n"
+ " mov x1, #1\n"
+ " adrp x0, g_szLibrary@PAGE\n"
+ " add x0, x0, g_szLibrary@PAGEOFF\n"
+ " bl %sRTLdrLoadSystem\n"
+ , pszNmPfx);
+
+ fprintf(pOutput,
+ " cmp w0, #0\n"
+ " b.eq Lload_return\n"
+ "\n"
+ "Lbadload: ; Call sRTAssertMsg2Weak. Variadic (...) arguments are passed on the stack it seems.\n"
+ " mov x2, x0\n"
+ " adrp x1, g_szLibrary@PAGE\n"
+ " add x1, x1, g_szLibrary@PAGEOFF\n"
+ " adrp x0, g_szFailResolveFmt@PAGE\n"
+ " add x0, x0, g_szFailResolveFmt@PAGEOFF\n"
+ " stp x1, x2, [sp]\n"
+ " bl %sRTAssertMsg2Weak\n"
+ "Lbadloadloop:\n"
+ " brk #0x1\n"
+ " b Lbadloadloop\n"
+ "Lload_return:\n"
+ " adrp x0, g_hMod@PAGE\n"
+ " ldr x0, [x0, g_hMod@PAGEOFF]\n"
+ " ldp x29, x30, [sp, #48]\n"
+ " .cfi_restore x29\n"
+ " .cfi_restore x30\n"
+ " add sp, sp, #(16 + 48)\n"
+ " ret\n"
+ " .cfi_endproc\n"
+ "\n"
+ "\n"
+ , pszNmPfx);
+
+ /*
+ * C callable method for explicitly loading the library and optionally
+ * resolving all the imports.
+ */
+ if (g_fWithExplictLoadFunction)
+ {
+ if (g_fSystemLibrary) /* Lazy bird. */
+ {
+ fprintf(stderr, "error: cannot use --system with --explicit-load-function, sorry\n");
+ return RTEXITCODE_FAILURE;
+ }
+
+ int cchLibBaseName = (int)(strchr(g_pszLibrary, '.') ? strchr(g_pszLibrary, '.') - g_pszLibrary : strlen(g_pszLibrary));
+ fprintf(pOutput,
+ ";;\n"
+ "; ExplicitlyLoad%.*s(bool fResolveAllImports, pErrInfo);\n"
+ ";\n"
+ ".section __TEXT,__text,regular,pure_instructions\n"
+ ".p2align 3\n"
+ ".globl %sExplicitlyLoad%.*s\n"
+ "%sExplicitlyLoad%.*s:\n"
+ " .cfi_startproc\n"
+ " ; Create frame.\n"
+ " sub sp, sp, #(16 + 96)\n"
+ " stp x29, x30, [sp, #96]\n"
+ " add x29, sp, #96\n"
+ " .cfi_def_cfa x29, 16\n"
+ " .cfi_offset x30, -8\n"
+ " .cfi_offset x29, -16\n"
+ "\n"
+ " stp x20, x21, [sp, #(96 - 16)]\n"
+ " .cfi_offset x21, -24\n"
+ " .cfi_offset x20, -32\n"
+ " stp x22, x23, [sp, #(96 - 32)]\n"
+ " .cfi_offset x23, -40\n"
+ " .cfi_offset x22, -48\n"
+
+ " ; Save the input parameters.\n"
+ " mov x20, x0\n"
+ " mov x21, x1\n"
+ "\n"
+ " ;\n"
+ " ; Is the module already loaded?\n"
+ " ;\n"
+ " adrp x0, g_hMod@PAGE\n"
+ " ldr x0, [x0, g_hMod@PAGEOFF]\n"
+ " cmp x0, #0\n"
+ " b.ne Lexplicit_loaded_module\n"
+ "\n"
+ ,
+ cchLibBaseName, g_pszLibrary,
+ pszNmPfx, cchLibBaseName, g_pszLibrary,
+ pszNmPfx, cchLibBaseName, g_pszLibrary);
+ fprintf(pOutput,
+ "Lexplicit_load_module:\n"
+ " ; Call SUPR3HardenedLdrLoadAppPriv(const char *pszFilename, PRTLDRMOD phLdrMod, uint32_t fFlags, PRTERRINFO pErrInfo);\n"
+ " mov x3, #0\n"
+ " mov x2, #0\n"
+ " adrp x1, g_hMod@PAGE\n"
+ " add x1, x1, g_hMod@PAGEOFF\n"
+ " adrp x0, g_szLibrary@PAGE\n"
+ " add x0, x0, g_szLibrary@PAGEOFF\n"
+ " bl %sSUPR3HardenedLdrLoadAppPriv\n"
+ " cmp x0, #0\n"
+ " b.ne Lexplicit_load_return\n"
+ "\n"
+ , pszNmPfx);
+
+ fprintf(pOutput,
+ " ;\n"
+ " ; Resolve the imports too if requested to do so.\n"
+ " ;\n"
+ "Lexplicit_loaded_module:\n"
+ " cmp w20, #0\n"
+ " b.eq Lexplicit_load_return\n"
+ "\n"
+ " adrp x22, g_szzNames@PAGE\n"
+ " add x22, x22, g_szzNames@PAGEOFF\n"
+ " adrp x23, g_apfnImports@PAGE\n"
+ " add x23, x23, g_apfnImports@PAGEOFF\n"
+ "Lexplicit_load_next_import:\n"
+ " ldr x0, [x23]\n"
+ " cmp x0, #0\n"
+ " b.eq Lexplicit_load_return\n"
+ "\n"
+ " ; Get the module handle and call RTLdrGetSymbol(RTLDRMOD hLdrMod, const char *pszSymbol, void **ppvValue)\n"
+ " adrp x0, g_hMod@PAGE\n"
+ " ldr x0, [x0, g_hMod@PAGEOFF]\n"
+ " mov x1, x22\n"
+ " mov x2, x23\n"
+ " bl %sRTLdrGetSymbol\n"
+ " cmp x0, #0\n"
+ " b.ne Lexplicit_load_symbol_error\n"
+ "\n"
+ " ; Advance.\n"
+ " add x23, x23, #8\n"
+ "Lexplict_load_advance_string:\n"
+ " ldrb w0, [x22]\n"
+ " add x22, x22, #1\n"
+ " cmp w0, #0\n"
+ " b.ne Lexplict_load_advance_string\n"
+ " b Lexplicit_load_next_import\n"
+ "\n"
+ " ;\n"
+ " ; Error loading a symbol. Call RTErrInfoSet(PRTERRINFO pErrInfo, int rc, const char *pszMsg) on pErrInfo (preserves x0).\n"
+ " ;\n"
+ "Lexplicit_load_symbol_error:\n"
+ " mov x2, x22\n"
+ " mov x1, x0\n"
+ " mov x0, x21\n"
+ " bl %sRTErrInfoSet\n"
+ " b Lexplicit_load_return"
+ " "
+ "\n"
+ "Lexplicit_load_return:\n"
+ " ldp x22, x23, [sp, #(96 - 32)]\n"
+ " .cfi_restore x23\n"
+ " .cfi_restore x22\n"
+ " ldp x20, x21, [sp, #(96 - 16)]\n"
+ " .cfi_restore x21\n"
+ " .cfi_restore x20\n"
+ "\n"
+ " ldp x29, x30, [sp, #96]\n"
+ " .cfi_restore x29\n"
+ " .cfi_restore x30\n"
+ " add sp, sp, #(16 + 96)\n"
+ " ret\n"
+ " .cfi_endproc\n"
+ "\n"
+ "\n"
+ , pszNmPfx, pszNmPfx);
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Generates the assembly source code, writing it to g_pszOutput.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE, in the latter case full
+ * details has been displayed.
+ */
+static RTEXITCODE generateOutput(void)
+{
+ RTEXITCODE rcExit = RTEXITCODE_FAILURE;
+ FILE *pOutput = fopen(g_pszOutput, "w");
+ if (pOutput)
+ {
+ switch (g_enmTarget)
+ {
+ case RTLDRARCH_AMD64:
+ case RTLDRARCH_X86_32:
+ rcExit = generateOutputInnerX86AndAMD64(pOutput);
+ break;
+ case RTLDRARCH_ARM64:
+ rcExit = generateOutputInnerArm64(pOutput);
+ break;
+ default:
+ rcExit = RTEXITCODE_FAILURE;
+ break;
+ }
+ if (fclose(pOutput))
+ {
+ fprintf(stderr, "error: Error closing '%s'.\n", g_pszOutput);
+ rcExit = RTEXITCODE_FAILURE;
+ }
+ }
+ else
+ fprintf(stderr, "error: Failed to open '%s' for writing.\n", g_pszOutput);
+ return rcExit;
+}
+
+
+/**
+ * Displays usage information.
+ *
+ * @returns RTEXITCODE_SUCCESS.
+ * @param pszArgv0 The argv[0] string.
+ */
+static int usage(const char *pszArgv0)
+{
+ printf("usage: %s [options] --libary <loadname> --output <lazyload.asm> <input.def>\n"
+ "\n"
+ "Options:\n"
+ " --explicit-load-function, --no-explicit-load-function\n"
+ " Whether to include the explicit load function, default is not to.\n"
+ "\n"
+ "Copyright (C) 2013-2016 Oracle Corporation\n"
+ , pszArgv0);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+int main(int argc, char **argv)
+{
+ /*
+ * Parse options.
+ */
+ for (int i = 1; i < argc; i++)
+ {
+ const char *psz = argv[i];
+ if (*psz == '-')
+ {
+ if (!strcmp(psz, "--output") || !strcmp(psz, "-o"))
+ {
+ if (++i >= argc)
+ {
+ fprintf(stderr, "syntax error: File name expected after '%s'.\n", psz);
+ return RTEXITCODE_SYNTAX;
+ }
+ g_pszOutput = argv[i];
+ }
+ else if (!strcmp(psz, "--library") || !strcmp(psz, "-l"))
+ {
+ if (++i >= argc)
+ {
+ fprintf(stderr, "syntax error: Library name expected after '%s'.\n", psz);
+ return RTEXITCODE_SYNTAX;
+ }
+ g_pszLibrary = argv[i];
+ }
+ else if (!strcmp(psz, "--explicit-load-function"))
+ g_fWithExplictLoadFunction = true;
+ else if (!strcmp(psz, "--no-explicit-load-function"))
+ g_fWithExplictLoadFunction = false;
+ else if (!strcmp(psz, "--system"))
+ g_fSystemLibrary = true;
+ /** @todo Support different load methods so this can be used on system libs and
+ * such if we like. */
+ else if ( !strcmp(psz, "--help")
+ || !strcmp(psz, "-help")
+ || !strcmp(psz, "-h")
+ || !strcmp(psz, "-?") )
+ return usage(argv[0]);
+ else if ( !strcmp(psz, "--version")
+ || !strcmp(psz, "-V"))
+ {
+ printf("$Revision: 155244 $\n");
+ return RTEXITCODE_SUCCESS;
+ }
+ else
+ {
+ fprintf(stderr, "syntax error: Unknown option '%s'.\n", psz);
+ return RTEXITCODE_SYNTAX;
+ }
+ }
+ else
+ {
+ if (g_cInputs >= RT_ELEMENTS(g_apszInputs))
+ {
+ fprintf(stderr, "syntax error: Too many input files, max is %d.\n", (int)RT_ELEMENTS(g_apszInputs));
+ return RTEXITCODE_SYNTAX;
+ }
+ g_apszInputs[g_cInputs++] = argv[i];
+ }
+ }
+ if (g_cInputs == 0)
+ {
+ fprintf(stderr, "syntax error: No input file specified.\n");
+ return RTEXITCODE_SYNTAX;
+ }
+ if (!g_pszOutput)
+ {
+ fprintf(stderr, "syntax error: No output file specified.\n");
+ return RTEXITCODE_SYNTAX;
+ }
+ if (!g_pszLibrary)
+ {
+ fprintf(stderr, "syntax error: No library name specified.\n");
+ return RTEXITCODE_SYNTAX;
+ }
+
+ /*
+ * Do the job.
+ */
+ RTEXITCODE rcExit = parseInputs();
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = generateOutput();
+ return rcExit;
+}
+
diff --git a/src/bldprogs/VBoxEditCoffLib.cpp b/src/bldprogs/VBoxEditCoffLib.cpp
new file mode 100644
index 00000000..42bd5494
--- /dev/null
+++ b/src/bldprogs/VBoxEditCoffLib.cpp
@@ -0,0 +1,476 @@
+/* $Id: VBoxEditCoffLib.cpp $ */
+/** @file
+ * VBoxEditCoffLib - Simple COFF editor for library files.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <iprt/assertcompile.h>
+#include <iprt/types.h>
+#include <iprt/ctype.h>
+#include <iprt/formats/pecoff.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct ARHDR
+{
+ char achName[16];
+ char achDate[12];
+ char achUid[6];
+ char achGid[6];
+ char achMode[8];
+ char achSize[10];
+ char achMagic[2];
+} ARHDR;
+AssertCompileSize(ARHDR, 16+12+6+6+8+10+2);
+
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Verbosity level. */
+static int g_cVerbosity = 0;
+
+/** The binary size. */
+static unsigned g_cbBinary = 0;
+/** The binary data we're editing. */
+static uint8_t *g_pbBinary = NULL;
+
+/** Size of the currently selected member. */
+static unsigned g_cbMember = 0;
+/** Pointer to the data for the currently selected member. */
+static uint8_t *g_pbMember = NULL;
+
+
+/**
+ * File size.
+ *
+ * @returns file size in bytes.
+ * @returns 0 on failure.
+ * @param pFile File to size.
+ */
+static unsigned fsize(FILE *pFile)
+{
+ long cbFile;
+ off_t Pos = ftell(pFile);
+ if ( Pos >= 0
+ && !fseek(pFile, 0, SEEK_END))
+ {
+ cbFile = ftell(pFile);
+ if ( cbFile >= 0
+ && !fseek(pFile, 0, SEEK_SET))
+ return cbFile;
+ }
+ return 0;
+}
+
+
+/**
+ * Reports a problem.
+ *
+ * @returns RTEXITCODE_FAILURE
+ */
+static int error(const char *pszFormat, ...)
+{
+ fprintf(stderr, "error: ");
+ va_list va;
+ va_start(va, pszFormat);
+ vfprintf(stderr, pszFormat, va);
+ va_end(va);
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Reports a syntax problem.
+ *
+ * @returns RTEXITCODE_SYNTAX
+ */
+static int syntax(const char *pszFormat, ...)
+{
+ fprintf(stderr, "syntax error: ");
+ va_list va;
+ va_start(va, pszFormat);
+ vfprintf(stderr, pszFormat, va);
+ va_end(va);
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Display usage
+ *
+ * @returns success if stdout, syntax error if stderr.
+ */
+static int usage(FILE *pOut, const char *argv0)
+{
+ fprintf(pOut,
+ "usage: %s --input <in.lib> --output <out.lib> [options and operations]\n"
+ "\n"
+ "Operations and Options (processed in place):\n"
+ " --verbose Noisier.\n"
+ " --quiet Quiet execution.\n"
+ " --select <member>\n"
+ " Selects archive member which name ends in the given string.\n"
+ " --redefine-sym <old>=<new>\n"
+ " Redefine the symbol <old> to <new>.\n"
+ " Note! the length must be the same!\n"
+ , argv0);
+ return pOut == stdout ? RTEXITCODE_SUCCESS : RTEXITCODE_SYNTAX;
+}
+
+
+/**
+ * Helper for SelectMember.
+ */
+static bool AreAllDigits(const char *pch, size_t cch, size_t *puValue)
+{
+ *puValue = 0;
+ do
+ {
+ if (!RT_C_IS_DIGIT(*pch))
+ return false;
+ *puValue = *puValue * 10 + *pch - '0';
+ pch++;
+ cch--;
+ } while (cch > 0);
+ return true;
+}
+
+
+/**
+ * Selects archive member ending with the given name.
+ *
+ * Updates g_cbMember and g_pbMember.
+ */
+static int SelectMember(const char *pszEndsWith)
+{
+ size_t const cchEndsWith = strlen(pszEndsWith);
+
+ /*
+ * Check the header.
+ */
+ if (memcmp(g_pbBinary, RT_STR_TUPLE("!<arch>\n")))
+ return error("Not an AR library!\n");
+
+ /*
+ * Work the members.
+ */
+ const char *pszStringTab = NULL;
+ size_t cbStringTab = 0;
+ for (size_t off = sizeof("!<arch>\n") - 1; off < g_cbBinary;)
+ {
+ ARHDR *pHdr = (ARHDR *)&g_pbBinary[off];
+ char szTmp[16 + 8];
+ size_t uValue;
+ char *pszIgn;
+#define COPY_AND_TRIM(a_pchSrc, a_cbSrc) do { \
+ memcpy(szTmp, (a_pchSrc), (a_cbSrc)); \
+ size_t offCopy = (a_cbSrc); \
+ while (offCopy > 0 && (szTmp[offCopy - 1] == ' ' || szTmp[offCopy - 1] == '\0')) \
+ offCopy--; \
+ szTmp[offCopy] = '\0'; \
+ } while (0)
+
+ /*
+ * Parse the header.
+ */
+
+ /* The size: */
+ COPY_AND_TRIM(pHdr->achSize, sizeof(pHdr->achSize));
+ size_t cbFile = strtol(szTmp, &pszIgn, 10);
+
+ /* The name: */
+ size_t cbExtra = 0;
+ size_t cchName = sizeof(pHdr->achName);
+ const char *pchName = pHdr->achName;
+ if ( pchName[0] == '#'
+ && pchName[1] == '1'
+ && pchName[2] == '/')
+ {
+ COPY_AND_TRIM(&pchName[3], cchName - 3);
+ cchName = cbExtra = strtol(szTmp, &pszIgn, 10);
+ pchName = (char *)(pHdr + 1);
+ }
+ else
+ {
+ while (cchName > 0 && (pchName[cchName - 1] == ' ' || pchName[cchName - 1] == '\0'))
+ cchName--;
+
+ /* Long filename string table? */
+ if ( (cchName == 2 && pchName[0] == '/' && pchName[1] == '/')
+ || (cchName == sizeof("ARFILENAMES/") - 1 && memcmp(pchName, RT_STR_TUPLE("ARFILENAMES/")) == 0))
+ {
+ pszStringTab = (char *)(pHdr + 1);
+ cbStringTab = cbFile;
+ }
+ /* Long filename string table reference? */
+ else if ( cchName >= 2
+ && ( pchName[0] == '/' /* System V */
+ || pchName[0] == ' ' /* Other */)
+ && AreAllDigits(&pchName[1], cchName - 1, &uValue) && uValue < cbStringTab)
+ {
+ pchName = &pszStringTab[uValue];
+ cchName = strlen(pchName); /** @todo unsafe! */
+ }
+ /* Drop trailing slash in case of System V filename: */
+ else if (cchName > 1 && pchName[cchName - 1] == '/')
+ cchName -= 1;
+ }
+
+ if (g_cVerbosity > 2)
+ fprintf(stderr, "debug: %#08x: %#010x %*.*s\n",
+ (unsigned)off, (unsigned)(cbFile - cbExtra), (int)cchName, (int)cchName, pchName);
+
+ /*
+ * Do matching.
+ */
+ if ( cchName >= cchEndsWith
+ && strncmp(&pchName[cchName - cchEndsWith], pszEndsWith, cchEndsWith) == 0)
+ {
+ g_pbMember = (uint8_t *)(pHdr + 1) + cbExtra;
+ g_cbMember = (unsigned)(cbFile - cbExtra);
+ if (g_cVerbosity > 1)
+ fprintf(stderr, "debug: selected '%*.*s': %#x LB %#x\n",
+ (int)cchName, (int)cchName, pchName, (unsigned)(off + sizeof(*pHdr) + cbExtra), g_cbMember);
+ return 0;
+ }
+
+ /*
+ * Advance.
+ */
+ off += sizeof(ARHDR) + cbFile + (cbFile & 1);
+ }
+
+ return error("No member ending with '%s' was found!\n", pszEndsWith);
+}
+
+
+/**
+ * @note Borrowed from VBoxBs3objConverter.cpp
+ */
+static const char *coffGetSymbolName(PCIMAGE_SYMBOL pSym, const char *pchStrTab, uint32_t cbStrTab, char pszShortName[16])
+{
+ if (pSym->N.Name.Short != 0)
+ {
+ memcpy(pszShortName, pSym->N.ShortName, 8);
+ pszShortName[8] = '\0';
+ return pszShortName;
+ }
+ if (pSym->N.Name.Long < cbStrTab)
+ {
+ uint32_t const cbLeft = cbStrTab - pSym->N.Name.Long;
+ const char *pszRet = pchStrTab + pSym->N.Name.Long;
+ if (memchr(pszRet, '\0', cbLeft) != NULL)
+ return pszRet;
+ }
+ error("Invalid string table index %#x!\n", pSym->N.Name.Long);
+ return "Invalid Symbol Table Entry";
+}
+
+
+/**
+ * Redefine a symbol with a different name.
+ */
+static int RedefineSymbol(const char *pszOldEqualNew)
+{
+ /*
+ * Check state and split up the input.
+ */
+ if (!g_pbMember)
+ return error("No selected archive member!\n");
+
+ const char *pszNew = strchr(pszOldEqualNew, '=');
+ if (!pszNew || pszNew[1] == '\0')
+ return error("Malformed 'old=new' argument: %s\n", pszOldEqualNew);
+ const char *pszOld = pszOldEqualNew;
+ size_t const cchOld = pszNew - pszOldEqualNew;
+ pszNew += 1;
+ size_t const cchNew = strlen(pszNew);
+ if (cchNew > cchOld)
+ return error("The new symbol must not be longer than the old symbol: %#x vs %#x (%s)\n", cchNew, cchOld, pszOldEqualNew);
+
+ if (g_cVerbosity > 2)
+ fprintf(stderr, "debug: redefining symbol '%*.*s' to '%*.*s'...\n",
+ (int)cchOld, (int)cchOld, pszOld, (int)cchNew, (int)cchNew, pszNew);
+
+ /*
+ * Parse COFF header.
+ */
+ const IMAGE_FILE_HEADER *pHdr = (const IMAGE_FILE_HEADER *)g_pbMember;
+ if (sizeof(*pHdr) >= g_cbMember)
+ return error("member too small for COFF\n");
+ if ( pHdr->Machine != IMAGE_FILE_MACHINE_AMD64
+ && pHdr->Machine != IMAGE_FILE_MACHINE_I386)
+ return error("Unsupported COFF machine: %#x\n", pHdr->Machine);
+ if ( pHdr->PointerToSymbolTable >= g_cbMember
+ || pHdr->PointerToSymbolTable < sizeof(*pHdr))
+ return error("PointerToSymbolTable is out of bounds: %#x, max %#x\n", pHdr->PointerToSymbolTable, g_cbMember);
+ unsigned const cSymbols = pHdr->NumberOfSymbols;
+ if ( cSymbols >= g_cbMember - pHdr->PointerToSymbolTable
+ || cSymbols * sizeof(IMAGE_SYMBOL) > g_cbMember - pHdr->PointerToSymbolTable)
+ return error("PointerToSymbolTable + NumberOfSymbols is out of bounds: %#x + %#x * %#x (%#x), max %#x\n",
+ pHdr->PointerToSymbolTable, cSymbols, sizeof(IMAGE_SYMBOL),
+ pHdr->PointerToSymbolTable + cSymbols * sizeof(IMAGE_SYMBOL), g_cbMember);
+
+ /*
+ * Work the symbol table.
+ */
+ unsigned cRenames = 0;
+ PIMAGE_SYMBOL const paSymTab = (PIMAGE_SYMBOL)&g_pbMember[pHdr->PointerToSymbolTable];
+ const char * const pchStrTab = (const char *)&paSymTab[pHdr->NumberOfSymbols];
+ uint32_t const cbStrTab = (uint32_t)((uintptr_t)&g_pbMember[g_cbMember] - (uintptr_t)pchStrTab);
+ for (unsigned iSym = 0; iSym < cSymbols; iSym++)
+ {
+ char szShort[16];
+ const char *pszSymName = coffGetSymbolName(&paSymTab[iSym], pchStrTab, cbStrTab, szShort);
+ size_t cchSymName = strlen(pszSymName);
+ if (g_cVerbosity > 3 && cchSymName > 0)
+ fprintf(stderr, "debug: symbol %u: %s\n", iSym, pszSymName);
+ if ( cchSymName == cchOld
+ && memcmp(pszSymName, pszOld, cchSymName) == 0)
+ {
+ size_t const offStrTab = (size_t)(pszSymName - pchStrTab);
+ if (offStrTab < cbStrTab)
+ {
+ if (g_cVerbosity > 1)
+ fprintf(stderr, "debug: Found symbol '%s' in at string table offset %#x, renaming to '%s'.\n",
+ pszSymName, (uint32_t)offStrTab, pszNew);
+ if (offStrTab > 0 && pchStrTab[offStrTab - 1] != '\0')
+ return error("Cannot rename sub-string!\n");
+ memset((char *)pszSymName, 0, cchOld);
+ memcpy((char *)pszSymName, pszNew, cchNew);
+ }
+ else
+ {
+ if (g_cVerbosity > 1)
+ fprintf(stderr, "debug: Found symbol '%s' in symbol table, renaming to '%s'.\n", pszSymName, pszNew);
+ memset(paSymTab[iSym].N.ShortName, 0, sizeof(paSymTab[iSym].N.ShortName));
+ memcpy(paSymTab[iSym].N.ShortName, pszNew, cchNew);
+ }
+ cRenames++;
+ }
+
+ /* Skip AUX symbols. */
+ uint8_t cAuxSyms = paSymTab[iSym].NumberOfAuxSymbols;
+ while (cAuxSyms-- > 0)
+ iSym++;
+ }
+
+ if (cRenames > 0)
+ return RTEXITCODE_SUCCESS;
+ return error("Symbol '%*.*s' was not found!\n", cchOld, cchOld, pszOld);
+}
+
+
+int main(int argc, char **argv)
+{
+ /*
+ * Parse arguments.
+ */
+ const char *pszIn = NULL;
+ const char *pszOut = NULL;
+ for (int i = 1; i < argc; i++)
+ {
+ const char *pszArg = argv[i];
+
+ /* Options without values first: */
+ if ( strcmp(pszArg, "--verbose") == 0
+ || strcmp(pszArg, "-v") == 0)
+ g_cVerbosity += 1;
+ else if ( strcmp(pszArg, "--quiet") == 0
+ || strcmp(pszArg, "--q") == 0)
+ g_cVerbosity = 0;
+ else if ( strcmp(pszArg, "--help") == 0
+ || strcmp(pszArg, "-h") == 0
+ || strcmp(pszArg, "-?") == 0)
+ return usage(stdout, argv[0]);
+ else if (i + 1 >= argc)
+ return syntax("Missing argument value or unknown option '%s'!\n", pszArg);
+ else
+ {
+ i++;
+ const char *pszValue = argv[i];
+ int rc = 0;
+ if (strcmp(pszArg, "--input") == 0)
+ {
+ if (pszIn)
+ return syntax("--input can only be specified once!\n");
+ pszIn = pszValue;
+
+ /* Load it into memory: */
+ FILE *pIn = fopen(pszIn, "rb");
+ if (!pIn)
+ return error("Failed to open '%s' for reading!\n", pszIn);
+ g_cbBinary = fsize(pIn);
+ if (!g_cbBinary)
+ return error("Failed to determin the size of '%s'!\n", pszIn);
+ if (g_cbBinary > _128M)
+ return error("'%s' is too large: %x, max %x\n", g_cbBinary, (size_t)_128M);
+ g_pbBinary = (uint8_t *)calloc(1, g_cbBinary + 4096);
+ if (!g_pbBinary)
+ return error("Out of memory!\n");
+ if (fread(g_pbBinary, g_cbBinary, 1, pIn) != 1)
+ return error("Failed to read '%s' into memory!\n", pszIn);
+ fclose(pIn);
+ }
+ else if (strcmp(pszArg, "--output") == 0)
+ pszOut = pszValue;
+ else if (strcmp(pszArg, "--select") == 0)
+ rc = SelectMember(pszValue);
+ else if (strcmp(pszArg, "--redefine-sym") == 0)
+ rc = RedefineSymbol(pszValue);
+ else
+ return syntax("Unknown option: %s\n", pszArg);
+ if (rc != RTEXITCODE_SUCCESS)
+ return rc;
+ }
+ }
+
+ if (!pszIn || !pszOut)
+ return syntax("No %s specified!\n", pszIn ? "output file" : "intput library file");
+
+ /*
+ * Write out the result.
+ */
+ FILE *pOut = fopen(pszOut, "wb");
+ if (!pOut)
+ return error("Failed to open '%s' for writing!\n", pszOut);
+ if (fwrite(g_pbBinary, g_cbBinary, 1, pOut) != 1)
+ return error("Error writing %#x bytes to '%s'!\n", g_cbBinary, pszOut);
+ if (fclose(pOut) != 0)
+ return error("Error closing '%s'!\n", pszOut);
+ return RTEXITCODE_SUCCESS;
+}
+
diff --git a/src/bldprogs/VBoxPeSetVersion.cpp b/src/bldprogs/VBoxPeSetVersion.cpp
new file mode 100644
index 00000000..0c17c971
--- /dev/null
+++ b/src/bldprogs/VBoxPeSetVersion.cpp
@@ -0,0 +1,404 @@
+/* $Id: VBoxPeSetVersion.cpp $ */
+/** @file
+ * IPRT - Change the OS and SubSystem version to value suitable for NT v3.1.
+ *
+ * Also make sure the IAT is writable, since NT v3.1 expects this. These are
+ * tricks necessary to make binaries created by newer Visual C++ linkers work
+ * on ancient NT version like W2K, NT4 and NT 3.x.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/formats/mz.h>
+#include <iprt/formats/pecoff.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define MK_VER(a_uHi, a_uLo) ( ((a_uHi) << 8) | (a_uLo))
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static const char *g_pszFilename;
+static unsigned g_cVerbosity = 0;
+
+
+static int Error(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ char szTmp[1024];
+ _vsnprintf(szTmp, sizeof(szTmp), pszFormat, va);
+ va_end(va);
+ fprintf(stderr, "VBoxPeSetVersion: %s: error: %s\n", g_pszFilename, szTmp);
+ return RTEXITCODE_FAILURE;
+}
+
+
+static void Info(unsigned iLevel, const char *pszFormat, ...)
+{
+ if (iLevel <= g_cVerbosity)
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ char szTmp[1024];
+ _vsnprintf(szTmp, sizeof(szTmp), pszFormat, va);
+ va_end(va);
+ fprintf(stderr, "VBoxPeSetVersion: %s: info: %s\n", g_pszFilename, szTmp);
+ }
+}
+
+
+static int UpdateFile(FILE *pFile, unsigned uNtVersion, PIMAGE_SECTION_HEADER *ppaShdr)
+{
+ /*
+ * Locate and read the PE header.
+ *
+ * Note! We'll be reading the 64-bit size even for 32-bit since the difference
+ * is 16 bytes, which is less than a section header, so it won't be a problem.
+ */
+ unsigned long offNtHdrs;
+ {
+ IMAGE_DOS_HEADER MzHdr;
+ if (fread(&MzHdr, sizeof(MzHdr), 1, pFile) != 1)
+ return Error("Failed to read MZ header: %s", strerror(errno));
+ if (MzHdr.e_magic != IMAGE_DOS_SIGNATURE)
+ return Error("Invalid MZ magic: %#x", MzHdr.e_magic);
+ offNtHdrs = MzHdr.e_lfanew;
+ }
+
+ if (fseek(pFile, offNtHdrs, SEEK_SET) != 0)
+ return Error("Failed to seek to PE header at %#lx: %s", offNtHdrs, strerror(errno));
+ union
+ {
+ IMAGE_NT_HEADERS32 x32;
+ IMAGE_NT_HEADERS64 x64;
+ } NtHdrs,
+ NtHdrsNew;
+ if (fread(&NtHdrs, sizeof(NtHdrs), 1, pFile) != 1)
+ return Error("Failed to read PE header at %#lx: %s", offNtHdrs, strerror(errno));
+
+ /*
+ * Validate it a little bit.
+ */
+ if (NtHdrs.x32.Signature != IMAGE_NT_SIGNATURE)
+ return Error("Invalid PE signature: %#x", NtHdrs.x32.Signature);
+ uint32_t cbNewHdrs;
+ if (NtHdrs.x32.FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64)
+ {
+ if (NtHdrs.x64.FileHeader.SizeOfOptionalHeader != sizeof(NtHdrs.x64.OptionalHeader))
+ return Error("Invalid optional header size: %#x", NtHdrs.x64.FileHeader.SizeOfOptionalHeader);
+ if (NtHdrs.x64.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC)
+ return Error("Invalid optional header magic: %#x", NtHdrs.x64.OptionalHeader.Magic);
+ if (!uNtVersion)
+ uNtVersion = MK_VER(5, 2);
+ else if (uNtVersion < MK_VER(5, 2))
+ return Error("Selected version is too old for AMD64: %u.%u", uNtVersion >> 8, uNtVersion & 0xff);
+ cbNewHdrs = sizeof(NtHdrsNew.x64);
+ }
+ else if (NtHdrs.x32.FileHeader.Machine != IMAGE_FILE_MACHINE_I386)
+ return Error("Not I386 or AMD64 machine: %#x", NtHdrs.x32.FileHeader.Machine);
+ else
+ {
+ if (NtHdrs.x32.FileHeader.SizeOfOptionalHeader != sizeof(NtHdrs.x32.OptionalHeader))
+ return Error("Invalid optional header size: %#x", NtHdrs.x32.FileHeader.SizeOfOptionalHeader);
+ if (NtHdrs.x32.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC)
+ return Error("Invalid optional header magic: %#x", NtHdrs.x32.OptionalHeader.Magic);
+ if (!uNtVersion)
+ uNtVersion = MK_VER(3, 10);
+ cbNewHdrs = sizeof(NtHdrsNew.x32);
+ }
+
+ /*
+ * Do the header modifications.
+ */
+ memcpy(&NtHdrsNew, &NtHdrs, sizeof(NtHdrsNew));
+ NtHdrsNew.x32.OptionalHeader.MajorOperatingSystemVersion = uNtVersion >> 8;
+ NtHdrsNew.x32.OptionalHeader.MinorOperatingSystemVersion = uNtVersion & 0xff;
+ NtHdrsNew.x32.OptionalHeader.MajorSubsystemVersion = uNtVersion >> 8;
+ NtHdrsNew.x32.OptionalHeader.MinorSubsystemVersion = uNtVersion & 0xff;
+ AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MajorOperatingSystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MajorOperatingSystemVersion);
+ AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MinorOperatingSystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MinorOperatingSystemVersion);
+ AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MajorSubsystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MajorSubsystemVersion);
+ AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MinorSubsystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MinorSubsystemVersion);
+
+ if (uNtVersion <= MK_VER(3, 50))
+ {
+ NtHdrsNew.x32.OptionalHeader.MajorOperatingSystemVersion = 1;
+ NtHdrsNew.x32.OptionalHeader.MinorOperatingSystemVersion = 0;
+ AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MajorOperatingSystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MajorOperatingSystemVersion);
+ AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MinorOperatingSystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MinorOperatingSystemVersion);
+ }
+
+ if (memcmp(&NtHdrsNew, &NtHdrs, sizeof(NtHdrs)))
+ {
+ /** @todo calc checksum. */
+ NtHdrsNew.x32.OptionalHeader.CheckSum = 0;
+ AssertCompileMembersAtSameOffset(IMAGE_NT_HEADERS32, OptionalHeader.MinorOperatingSystemVersion, IMAGE_NT_HEADERS64, OptionalHeader.MinorOperatingSystemVersion);
+
+ if ( NtHdrsNew.x32.OptionalHeader.MajorOperatingSystemVersion != NtHdrs.x32.OptionalHeader.MajorOperatingSystemVersion
+ || NtHdrsNew.x32.OptionalHeader.MinorOperatingSystemVersion != NtHdrs.x32.OptionalHeader.MinorOperatingSystemVersion)
+ Info(1,"OperatingSystemVersion %u.%u -> %u.%u",
+ NtHdrs.x32.OptionalHeader.MajorOperatingSystemVersion, NtHdrs.x32.OptionalHeader.MinorOperatingSystemVersion,
+ NtHdrsNew.x32.OptionalHeader.MajorOperatingSystemVersion, NtHdrsNew.x32.OptionalHeader.MinorOperatingSystemVersion);
+ if ( NtHdrsNew.x32.OptionalHeader.MajorSubsystemVersion != NtHdrs.x32.OptionalHeader.MajorSubsystemVersion
+ || NtHdrsNew.x32.OptionalHeader.MinorSubsystemVersion != NtHdrs.x32.OptionalHeader.MinorSubsystemVersion)
+ Info(1,"SubsystemVersion %u.%u -> %u.%u",
+ NtHdrs.x32.OptionalHeader.MajorSubsystemVersion, NtHdrs.x32.OptionalHeader.MinorSubsystemVersion,
+ NtHdrsNew.x32.OptionalHeader.MajorSubsystemVersion, NtHdrsNew.x32.OptionalHeader.MinorSubsystemVersion);
+
+ if (fseek(pFile, offNtHdrs, SEEK_SET) != 0)
+ return Error("Failed to seek to PE header at %#lx: %s", offNtHdrs, strerror(errno));
+ if (fwrite(&NtHdrsNew, cbNewHdrs, 1, pFile) != 1)
+ return Error("Failed to write PE header at %#lx: %s", offNtHdrs, strerror(errno));
+ }
+
+ /*
+ * Make the IAT writable for NT 3.1 and drop the non-cachable flag from .bss.
+ *
+ * The latter is a trick we use to prevent the linker from merging .data and .bss,
+ * because NT 3.1 does not honor Misc.VirtualSize and won't zero padd the .bss part
+ * if it's not zero padded in the file. This seemed simpler than adding zero padding.
+ */
+ if ( uNtVersion <= MK_VER(3, 10)
+ && NtHdrsNew.x32.FileHeader.NumberOfSections > 0)
+ {
+ uint32_t cbShdrs = sizeof(IMAGE_SECTION_HEADER) * NtHdrsNew.x32.FileHeader.NumberOfSections;
+ PIMAGE_SECTION_HEADER paShdrs = (PIMAGE_SECTION_HEADER)calloc(1, cbShdrs);
+ if (!paShdrs)
+ return Error("Out of memory");
+ *ppaShdr = paShdrs;
+
+ unsigned long offShdrs = offNtHdrs
+ + RT_UOFFSETOF_DYN(IMAGE_NT_HEADERS32,
+ OptionalHeader.DataDirectory[NtHdrsNew.x32.OptionalHeader.NumberOfRvaAndSizes]);
+ if (fseek(pFile, offShdrs, SEEK_SET) != 0)
+ return Error("Failed to seek to section headers at %#lx: %s", offShdrs, strerror(errno));
+ if (fread(paShdrs, cbShdrs, 1, pFile) != 1)
+ return Error("Failed to read section headers at %#lx: %s", offShdrs, strerror(errno));
+
+ bool fFoundBss = false;
+ uint32_t uRvaEnd = NtHdrsNew.x32.OptionalHeader.SizeOfImage;
+ uint32_t uRvaIat = NtHdrsNew.x32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size > 0
+ ? NtHdrsNew.x32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress : UINT32_MAX;
+ uint32_t i = NtHdrsNew.x32.FileHeader.NumberOfSections;
+ while (i-- > 0)
+ if (!(paShdrs[i].Characteristics & IMAGE_SCN_TYPE_NOLOAD))
+ {
+ bool fModified = false;
+ if (uRvaIat >= paShdrs[i].VirtualAddress && uRvaIat < uRvaEnd)
+ {
+ if (!(paShdrs[i].Characteristics & IMAGE_SCN_MEM_WRITE))
+ {
+ paShdrs[i].Characteristics |= IMAGE_SCN_MEM_WRITE;
+ fModified = true;
+ }
+ uRvaIat = UINT32_MAX;
+ }
+
+ if ( !fFoundBss
+ && strcmp((const char *)paShdrs[i].Name, ".bss") == 0)
+ {
+ if (paShdrs[i].Characteristics & IMAGE_SCN_MEM_NOT_CACHED)
+ {
+ paShdrs[i].Characteristics &= ~IMAGE_SCN_MEM_NOT_CACHED;
+ fModified = true;
+ }
+ fFoundBss = true;
+ }
+
+ if (fModified)
+ {
+ unsigned long offShdr = offShdrs + i * sizeof(IMAGE_SECTION_HEADER);
+ if (fseek(pFile, offShdr, SEEK_SET) != 0)
+ return Error("Failed to seek to section header #%u at %#lx: %s", i, offShdr, strerror(errno));
+ if (fwrite(&paShdrs[i], sizeof(IMAGE_SECTION_HEADER), 1, pFile) != 1)
+ return Error("Failed to write %8.8s section header header at %#lx: %s",
+ paShdrs[i].Name, offShdr, strerror(errno));
+ if (uRvaIat == UINT32_MAX && fFoundBss)
+ break;
+ }
+
+ /* Advance */
+ uRvaEnd = paShdrs[i].VirtualAddress;
+ }
+
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static int Usage(FILE *pOutput)
+{
+ fprintf(pOutput,
+ "Usage: VBoxPeSetVersion [options] <PE-image>\n"
+ "Options:\n"
+ " -v, --verbose\n"
+ " Increases verbosity.\n"
+ " -q, --quiet\n"
+ " Quiet operation (default).\n"
+ " --nt31, --nt350, --nt351, --nt4, --w2k, --xp, --w2k3, --vista,\n"
+ " --w7, --w8, --w81, --w10\n"
+ " Which version to set. Default: --nt31\n"
+ );
+ return RTEXITCODE_SYNTAX;
+}
+
+
+/** @todo Rewrite this so it can take options and print out error messages. */
+int main(int argc, char **argv)
+{
+ /*
+ * Parse arguments.
+ * This stucks
+ */
+ unsigned uNtVersion = 0;
+ const char *pszFilename = NULL;
+ bool fAcceptOptions = true;
+ for (int i = 1; i < argc; i++)
+ {
+ const char *psz = argv[i];
+ if (fAcceptOptions && *psz == '-')
+ {
+ char ch = psz[1];
+ psz += 2;
+ if (ch == '-')
+ {
+ if (!*psz)
+ {
+ fAcceptOptions = false;
+ continue;
+ }
+
+ if (strcmp(psz, "verbose") == 0)
+ ch = 'v';
+ else if (strcmp(psz, "quiet") == 0)
+ ch = 'q';
+ else if (strcmp(psz, "help") == 0)
+ ch = 'h';
+ else if (strcmp(psz, "version") == 0)
+ ch = 'V';
+ else
+ {
+ if (strcmp(psz, "nt31") == 0)
+ uNtVersion = MK_VER(3,10);
+ else if (strcmp(psz, "nt350") == 0)
+ uNtVersion = MK_VER(3,50);
+ else if (strcmp(psz, "nt351") == 0)
+ uNtVersion = MK_VER(3,51);
+ else if (strcmp(psz, "nt4") == 0)
+ uNtVersion = MK_VER(4,0);
+ else if (strcmp(psz, "w2k") == 0)
+ uNtVersion = MK_VER(5,0);
+ else if (strcmp(psz, "xp") == 0)
+ uNtVersion = MK_VER(5,1);
+ else if (strcmp(psz, "w2k3") == 0)
+ uNtVersion = MK_VER(5,2);
+ else if (strcmp(psz, "vista") == 0)
+ uNtVersion = MK_VER(6,0);
+ else if (strcmp(psz, "w7") == 0)
+ uNtVersion = MK_VER(6,1);
+ else if (strcmp(psz, "w8") == 0)
+ uNtVersion = MK_VER(6,2);
+ else if (strcmp(psz, "w81") == 0)
+ uNtVersion = MK_VER(6,3);
+ else if (strcmp(psz, "w10") == 0)
+ uNtVersion = MK_VER(10,0);
+ else
+ {
+ fprintf(stderr, "VBoxPeSetVersion: syntax error: Unknown option: --%s\n", psz);
+ return RTEXITCODE_SYNTAX;
+ }
+ continue;
+ }
+ psz = " ";
+ }
+ do
+ {
+ switch (ch)
+ {
+ case 'q':
+ g_cVerbosity = 0;
+ break;
+ case 'v':
+ g_cVerbosity++;
+ break;
+ case 'V':
+ printf("2.0\n");
+ return RTEXITCODE_SUCCESS;
+ case 'h':
+ Usage(stdout);
+ return RTEXITCODE_SUCCESS;
+ default:
+ fprintf(stderr, "VBoxPeSetVersion: syntax error: Unknown option: -%c\n", ch ? ch : ' ');
+ return RTEXITCODE_SYNTAX;
+ }
+ } while ((ch = *psz++) != '\0');
+
+ }
+ else if (!pszFilename)
+ pszFilename = psz;
+ else
+ {
+ fprintf(stderr, "VBoxPeSetVersion: syntax error: More than one PE-image specified!\n");
+ return RTEXITCODE_SYNTAX;
+ }
+ }
+
+ if (!pszFilename)
+ {
+ fprintf(stderr, "VBoxPeSetVersion: syntax error: No PE-image specified!\n");
+ return RTEXITCODE_SYNTAX;
+ }
+ g_pszFilename = pszFilename;
+
+ /*
+ * Process the file.
+ */
+ int rcExit;
+ FILE *pFile = fopen(pszFilename, "r+b");
+ if (pFile)
+ {
+ PIMAGE_SECTION_HEADER paShdrs = NULL;
+ rcExit = UpdateFile(pFile, uNtVersion, &paShdrs);
+ if (paShdrs)
+ free(paShdrs);
+ if (fclose(pFile) != 0)
+ rcExit = Error("fclose failed on '%s': %s", pszFilename, strerror(errno));
+ }
+ else
+ rcExit = Error("Failed to open '%s' for updating: %s", pszFilename, strerror(errno));
+ return rcExit;
+}
+
diff --git a/src/bldprogs/VBoxTpG.cpp b/src/bldprogs/VBoxTpG.cpp
new file mode 100644
index 00000000..516b749e
--- /dev/null
+++ b/src/bldprogs/VBoxTpG.cpp
@@ -0,0 +1,2665 @@
+/* $Id: VBoxTpG.cpp $ */
+/** @file
+ * VBox Build Tool - VBox Tracepoint Generator.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/VBoxTpG.h>
+
+#include <iprt/alloca.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/env.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+
+#include "scmstream.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct VTGPROBE *PVTGPROBE;
+
+typedef struct VTGATTRS
+{
+ kVTGStability enmCode;
+ kVTGStability enmData;
+ kVTGClass enmDataDep;
+} VTGATTRS;
+typedef VTGATTRS *PVTGATTRS;
+
+
+typedef struct VTGARG
+{
+ RTLISTNODE ListEntry;
+ /** The argument name. (heap) */
+ char *pszName;
+ /** The type presented to the tracer (in string table). */
+ const char *pszTracerType;
+ /** The argument type used in the probe method in that context. (heap) */
+ char *pszCtxType;
+ /** Argument passing format string. First and only argument is the name.
+ * (const string) */
+ const char *pszArgPassingFmt;
+ /** The type flags. */
+ uint32_t fType;
+ /** The argument number (0-based) for complaining/whatever. */
+ uint16_t iArgNo;
+ /** The probe the argument belongs to (for complaining/whatever). */
+ PVTGPROBE pProbe;
+ /** The absolute source position. */
+ size_t offSrc;
+} VTGARG;
+typedef VTGARG *PVTGARG;
+
+typedef struct VTGPROBE
+{
+ RTLISTNODE ListEntry;
+ char *pszMangledName;
+ const char *pszUnmangledName;
+ RTLISTANCHOR ArgHead;
+ uint32_t cArgs;
+ bool fHaveLargeArgs;
+ uint32_t offArgList;
+ uint32_t iProbe;
+ size_t iLine;
+} VTGPROBE;
+
+typedef struct VTGPROVIDER
+{
+ RTLISTNODE ListEntry;
+ const char *pszName;
+
+ uint16_t iFirstProbe;
+ uint16_t cProbes;
+
+ VTGATTRS AttrSelf;
+ VTGATTRS AttrModules;
+ VTGATTRS AttrFunctions;
+ VTGATTRS AttrName;
+ VTGATTRS AttrArguments;
+
+ RTLISTANCHOR ProbeHead;
+} VTGPROVIDER;
+typedef VTGPROVIDER *PVTGPROVIDER;
+
+/**
+ * A string table string.
+ */
+typedef struct VTGSTRING
+{
+ /** The string space core. */
+ RTSTRSPACECORE Core;
+ /** The string table offset. */
+ uint32_t offStrTab;
+ /** The actual string. */
+ char szString[1];
+} VTGSTRING;
+typedef VTGSTRING *PVTGSTRING;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The string space organizing the string table strings. Each node is a VTGSTRING. */
+static RTSTRSPACE g_StrSpace = NULL;
+/** Used by the string table enumerator to set VTGSTRING::offStrTab. */
+static uint32_t g_offStrTab;
+/** List of providers created by the parser. */
+static RTLISTANCHOR g_ProviderHead;
+/** The number of type errors. */
+static uint32_t g_cTypeErrors = 0;
+
+
+/** @name Options
+ * @{ */
+static enum
+{
+ kVBoxTpGAction_Nothing,
+ kVBoxTpGAction_GenerateHeader,
+ kVBoxTpGAction_GenerateWrapperHeader,
+ kVBoxTpGAction_GenerateObject
+} g_enmAction = kVBoxTpGAction_Nothing;
+static uint32_t g_cBits = HC_ARCH_BITS;
+static uint32_t g_cHostBits = HC_ARCH_BITS;
+static uint32_t g_fTypeContext = VTG_TYPE_CTX_R0;
+static const char *g_pszContextDefine = "IN_RING0";
+static const char *g_pszContextDefine2 = NULL;
+static bool g_fApplyCpp = false;
+static uint32_t g_cVerbosity = 0;
+static const char *g_pszOutput = NULL;
+static const char *g_pszScript = NULL;
+static const char *g_pszTempAsm = NULL;
+#ifdef RT_OS_DARWIN
+static const char *g_pszAssembler = "yasm";
+static const char *g_pszAssemblerFmtOpt = "-f";
+static const char g_szAssemblerFmtVal32[] = "macho32";
+static const char g_szAssemblerFmtVal64[] = "macho64";
+static const char g_szAssemblerOsDef[] = "RT_OS_DARWIN";
+#elif defined(RT_OS_OS2)
+static const char *g_pszAssembler = "nasm.exe";
+static const char *g_pszAssemblerFmtOpt = "-f";
+static const char g_szAssemblerFmtVal32[] = "obj";
+static const char g_szAssemblerFmtVal64[] = "elf64";
+static const char g_szAssemblerOsDef[] = "RT_OS_OS2";
+#elif defined(RT_OS_WINDOWS)
+static const char *g_pszAssembler = "yasm.exe";
+static const char *g_pszAssemblerFmtOpt = "-f";
+static const char g_szAssemblerFmtVal32[] = "win32";
+static const char g_szAssemblerFmtVal64[] = "win64";
+static const char g_szAssemblerOsDef[] = "RT_OS_WINDOWS";
+#else
+static const char *g_pszAssembler = "yasm";
+static const char *g_pszAssemblerFmtOpt = "-f";
+static const char g_szAssemblerFmtVal32[] = "elf32";
+static const char g_szAssemblerFmtVal64[] = "elf64";
+# ifdef RT_OS_FREEBSD
+static const char g_szAssemblerOsDef[] = "RT_OS_FREEBSD";
+# elif defined(RT_OS_NETBSD)
+static const char g_szAssemblerOsDef[] = "RT_OS_NETBSD";
+# elif defined(RT_OS_OPENBSD)
+static const char g_szAssemblerOsDef[] = "RT_OS_OPENBSD";
+# elif defined(RT_OS_LINUX)
+static const char g_szAssemblerOsDef[] = "RT_OS_LINUX";
+# elif defined(RT_OS_SOLARIS)
+static const char g_szAssemblerOsDef[] = "RT_OS_SOLARIS";
+# else
+# error "Port me!"
+# endif
+#endif
+static const char *g_pszAssemblerFmtVal = RT_CONCAT(g_szAssemblerFmtVal, HC_ARCH_BITS);
+static const char *g_pszAssemblerDefOpt = "-D";
+static const char *g_pszAssemblerIncOpt = "-I";
+static char g_szAssemblerIncVal[RTPATH_MAX];
+static const char *g_pszAssemblerIncVal = __FILE__ "/../../../include/";
+static const char *g_pszAssemblerOutputOpt = "-o";
+static unsigned g_cAssemblerOptions = 0;
+static const char *g_apszAssemblerOptions[32];
+static const char *g_pszProbeFnName = "SUPR0TracerFireProbe";
+static bool g_fProbeFnImported = true;
+static bool g_fPic = false;
+/** @} */
+
+
+
+
+/**
+ * Inserts a string into the string table, reusing any matching existing string
+ * if possible.
+ *
+ * @returns Read only string.
+ * @param pch The string to insert (need not be terminated).
+ * @param cch The length of the string.
+ */
+static const char *strtabInsertN(const char *pch, size_t cch)
+{
+ PVTGSTRING pStr = (PVTGSTRING)RTStrSpaceGetN(&g_StrSpace, pch, cch);
+ if (pStr)
+ return pStr->szString;
+
+ /*
+ * Create a new entry.
+ */
+ pStr = (PVTGSTRING)RTMemAlloc(RT_UOFFSETOF_DYN(VTGSTRING, szString[cch + 1]));
+ if (!pStr)
+ return NULL;
+
+ pStr->Core.pszString = pStr->szString;
+ memcpy(pStr->szString, pch, cch);
+ pStr->szString[cch] = '\0';
+ pStr->offStrTab = UINT32_MAX;
+
+ bool fRc = RTStrSpaceInsert(&g_StrSpace, &pStr->Core);
+ Assert(fRc); NOREF(fRc);
+ return pStr->szString;
+}
+
+
+/**
+ * Retrieves the string table offset of the given string table string.
+ *
+ * @returns String table offset.
+ * @param pszStrTabString The string table string.
+ */
+static uint32_t strtabGetOff(const char *pszStrTabString)
+{
+ PVTGSTRING pStr = RT_FROM_MEMBER(pszStrTabString, VTGSTRING, szString[0]);
+ Assert(pStr->Core.pszString == pszStrTabString);
+ return pStr->offStrTab;
+}
+
+
+/**
+ * Invokes the assembler.
+ *
+ * @returns Exit code.
+ * @param pszOutput The output file.
+ * @param pszTempAsm The source file.
+ */
+static RTEXITCODE generateInvokeAssembler(const char *pszOutput, const char *pszTempAsm)
+{
+ const char *apszArgs[64];
+ unsigned iArg = 0;
+
+ apszArgs[iArg++] = g_pszAssembler;
+ apszArgs[iArg++] = g_pszAssemblerFmtOpt;
+ apszArgs[iArg++] = g_pszAssemblerFmtVal;
+ apszArgs[iArg++] = g_pszAssemblerDefOpt;
+ if (!strcmp(g_pszAssemblerFmtVal, "macho32") || !strcmp(g_pszAssemblerFmtVal, "macho64"))
+ apszArgs[iArg++] = "ASM_FORMAT_MACHO";
+ else if (!strcmp(g_pszAssemblerFmtVal, "obj") || !strcmp(g_pszAssemblerFmtVal, "omf"))
+ apszArgs[iArg++] = "ASM_FORMAT_OMF";
+ else if ( !strcmp(g_pszAssemblerFmtVal, "win32")
+ || !strcmp(g_pszAssemblerFmtVal, "win64")
+ || !strcmp(g_pszAssemblerFmtVal, "pe32")
+ || !strcmp(g_pszAssemblerFmtVal, "pe64")
+ || !strcmp(g_pszAssemblerFmtVal, "pe") )
+ apszArgs[iArg++] = "ASM_FORMAT_PE";
+ else if ( !strcmp(g_pszAssemblerFmtVal, "elf32")
+ || !strcmp(g_pszAssemblerFmtVal, "elf64")
+ || !strcmp(g_pszAssemblerFmtVal, "elf"))
+ apszArgs[iArg++] = "ASM_FORMAT_ELF";
+ else
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unknown assembler format '%s'", g_pszAssemblerFmtVal);
+ apszArgs[iArg++] = g_pszAssemblerDefOpt;
+ if (g_cBits == 32)
+ apszArgs[iArg++] = "ARCH_BITS=32";
+ else
+ apszArgs[iArg++] = "ARCH_BITS=64";
+ apszArgs[iArg++] = g_pszAssemblerDefOpt;
+ if (g_cHostBits == 32)
+ apszArgs[iArg++] = "HC_ARCH_BITS=32";
+ else
+ apszArgs[iArg++] = "HC_ARCH_BITS=64";
+ apszArgs[iArg++] = g_pszAssemblerDefOpt;
+ if (g_cBits == 32)
+ apszArgs[iArg++] = "RT_ARCH_X86";
+ else
+ apszArgs[iArg++] = "RT_ARCH_AMD64";
+ apszArgs[iArg++] = g_pszAssemblerDefOpt;
+ apszArgs[iArg++] = g_pszContextDefine;
+ if (g_pszContextDefine2)
+ {
+ apszArgs[iArg++] = g_pszAssemblerDefOpt;
+ apszArgs[iArg++] = g_pszContextDefine2;
+ }
+ if (g_szAssemblerOsDef[0])
+ {
+ apszArgs[iArg++] = g_pszAssemblerDefOpt;
+ apszArgs[iArg++] = g_szAssemblerOsDef;
+ }
+ apszArgs[iArg++] = g_pszAssemblerIncOpt;
+ apszArgs[iArg++] = g_pszAssemblerIncVal;
+ apszArgs[iArg++] = g_pszAssemblerOutputOpt;
+ apszArgs[iArg++] = pszOutput;
+ for (unsigned i = 0; i < g_cAssemblerOptions; i++)
+ apszArgs[iArg++] = g_apszAssemblerOptions[i];
+ apszArgs[iArg++] = pszTempAsm;
+ apszArgs[iArg] = NULL;
+ Assert(iArg <= RT_ELEMENTS(apszArgs));
+
+ if (g_cVerbosity > 1)
+ {
+ RTMsgInfo("Starting assmbler '%s' with arguments:\n", g_pszAssembler);
+ for (unsigned i = 0; i < iArg; i++)
+ RTMsgInfo(" #%02u: '%s'\n", i, apszArgs[i]);
+ }
+
+ RTPROCESS hProc;
+ int rc = RTProcCreate(apszArgs[0], apszArgs, RTENV_DEFAULT, RTPROC_FLAGS_SEARCH_PATH, &hProc);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to start '%s' (assembler): %Rrc", apszArgs[0], rc);
+
+ RTPROCSTATUS Status;
+ rc = RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, &Status);
+ if (RT_FAILURE(rc))
+ {
+ RTProcTerminate(hProc);
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcWait failed: %Rrc", rc);
+ }
+ if (Status.enmReason == RTPROCEXITREASON_SIGNAL)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "The assembler failed: signal %d", Status.iStatus);
+ if (Status.enmReason != RTPROCEXITREASON_NORMAL)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "The assembler failed: abend");
+ if (Status.iStatus != 0)
+ return RTMsgErrorExit((RTEXITCODE)Status.iStatus, "The assembler failed: exit code %d", Status.iStatus);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Worker that does the boring bits when generating a file.
+ *
+ * @returns Exit code.
+ * @param pszOutput The name of the output file.
+ * @param pszWhat What kind of file it is.
+ * @param pfnGenerator The callback function that provides the contents
+ * of the file.
+ */
+static RTEXITCODE generateFile(const char *pszOutput, const char *pszWhat,
+ RTEXITCODE (*pfnGenerator)(PSCMSTREAM))
+{
+ SCMSTREAM Strm;
+ int rc = ScmStreamInitForWriting(&Strm, NULL);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "ScmStreamInitForWriting returned %Rrc when generating the %s file",
+ rc, pszWhat);
+
+ RTEXITCODE rcExit = pfnGenerator(&Strm);
+ if (RT_FAILURE(ScmStreamGetStatus(&Strm)))
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Stream error %Rrc generating the %s file",
+ ScmStreamGetStatus(&Strm), pszWhat);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ rc = ScmStreamWriteToFile(&Strm, "%s", pszOutput);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "ScmStreamWriteToFile returned %Rrc when writing '%s' (%s)",
+ rc, pszOutput, pszWhat);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ if (g_cVerbosity > 0)
+ RTMsgInfo("Successfully generated '%s'.", pszOutput);
+ if (g_cVerbosity > 1)
+ {
+ RTMsgInfo("================ %s - start ================", pszWhat);
+ ScmStreamRewindForReading(&Strm);
+ const char *pszLine;
+ size_t cchLine;
+ SCMEOL enmEol;
+ while ((pszLine = ScmStreamGetLine(&Strm, &cchLine, &enmEol)) != NULL)
+ RTPrintf("%.*s\n", cchLine, pszLine);
+ RTMsgInfo("================ %s - end ================", pszWhat);
+ }
+ }
+ }
+ ScmStreamDelete(&Strm);
+ return rcExit;
+}
+
+
+/**
+ * @callback_method_impl{FNRTSTRSPACECALLBACK, Writes the string table strings.}
+ */
+static DECLCALLBACK(int) generateAssemblyStrTabCallback(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ PVTGSTRING pVtgStr = (PVTGSTRING)pStr;
+ PSCMSTREAM pStrm = (PSCMSTREAM)pvUser;
+
+ pVtgStr->offStrTab = g_offStrTab;
+ g_offStrTab += (uint32_t)pVtgStr->Core.cchString + 1;
+
+ ScmStreamPrintf(pStrm,
+ " db '%s', 0 ; off=%u len=%zu\n",
+ pVtgStr->szString, pVtgStr->offStrTab, pVtgStr->Core.cchString);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Generate assembly source that can be turned into an object file.
+ *
+ * (This is a generateFile callback.)
+ *
+ * @returns Exit code.
+ * @param pStrm The output stream.
+ */
+static RTEXITCODE generateAssembly(PSCMSTREAM pStrm)
+{
+ PVTGPROVIDER pProvider;
+ PVTGPROBE pProbe;
+ PVTGARG pArg;
+
+
+ if (g_cVerbosity > 0)
+ RTMsgInfo("Generating assembly code...");
+
+ /*
+ * Write the file header.
+ */
+ ScmStreamPrintf(pStrm,
+ "; $Id: VBoxTpG.cpp $ \n"
+ ";; @file\n"
+ "; Automatically generated from %s. Do NOT edit!\n"
+ ";\n"
+ "\n"
+ "%%include \"iprt/asmdefs.mac\"\n"
+ "\n"
+ "\n"
+ ";"
+ "; We put all the data in a dedicated section / segment.\n"
+ ";\n"
+ "; In order to find the probe location specifiers, we do the necessary\n"
+ "; trickery here, ASSUMING that this object comes in first in the link\n"
+ "; editing process.\n"
+ ";\n"
+ "%%ifdef ASM_FORMAT_OMF\n"
+ " %%macro VTG_GLOBAL 2\n"
+ " global NAME(%%1)\n"
+ " NAME(%%1):\n"
+ " %%endmacro\n"
+ " segment VTG.Obj public CLASS=VTG align=4096 use32\n"
+ "\n"
+ "%%elifdef ASM_FORMAT_MACHO\n"
+ " %%macro VTG_GLOBAL 2\n"
+ " global NAME(%%1)\n"
+ " NAME(%%1):\n"
+ " %%endmacro\n"
+ " %%ifdef IN_RING3\n"
+ " %%define VTG_NEW_MACHO_LINKER\n"
+ " %%elif ARCH_BITS == 64\n"
+ " %%define VTG_NEW_MACHO_LINKER\n"
+ " %%elifdef IN_RING0_AGNOSTIC\n"
+ " %%define VTG_NEW_MACHO_LINKER\n"
+ " %%endif\n"
+ " %%ifdef VTG_NEW_MACHO_LINKER\n"
+ " ; Section order hack!\n"
+ " ; With the ld64-97.17 linker there was a problem with it determining the section\n"
+ " ; order based on symbol references. The references to the start and end of the\n"
+ " ; __VTGPrLc section forced it in front of __VTGObj, we want __VTGObj first.\n"
+ " extern section$start$__VTG$__VTGObj\n"
+ " extern section$end$__VTG$__VTGObj\n"
+ " %%else\n"
+ " ; Creating 32-bit kext of the type MH_OBJECT. No fancy section end/start symbols handy.\n"
+ " [section __VTG __VTGObj align=16]\n"
+ "VTG_GLOBAL g_aVTGObj_LinkerPleaseNoticeMe, data\n"
+ " [section __VTG __VTGPrLc.Begin align=16]\n"
+ " dq 0, 0 ; Paranoia, related to the fudge below.\n"
+ "VTG_GLOBAL g_aVTGPrLc, data\n"
+ " [section __VTG __VTGPrLc align=16]\n"
+ "VTG_GLOBAL g_aVTGPrLc_LinkerPleaseNoticeMe, data\n"
+ " [section __VTG __VTGPrLc.End align=16]\n"
+ "VTG_GLOBAL g_aVTGPrLc_End, data\n"
+ " dq 0, 0 ; Fudge to work around unidentified linker where it would otherwise generate\n"
+ " ; a fix up of the first dword in __VTGPrLc.Begin despite the fact that it were\n"
+ " ; an empty section with nothing whatsoever to fix up.\n"
+ " %%endif\n"
+ " [section __VTG __VTGObj]\n"
+ "\n"
+ "%%elifdef ASM_FORMAT_PE\n"
+ " %%macro VTG_GLOBAL 2\n"
+ " global NAME(%%1)\n"
+ " NAME(%%1):\n"
+ " %%endmacro\n"
+ " [section VTGPrLc.Begin data align=64]\n"
+ /*" times 16 db 0xcc\n"*/
+ "VTG_GLOBAL g_aVTGPrLc, data\n"
+ " [section VTGPrLc.Data data align=4]\n"
+ " [section VTGPrLc.End data align=4]\n"
+ "VTG_GLOBAL g_aVTGPrLc_End, data\n"
+ /*" times 16 db 0xcc\n"*/
+ " [section VTGObj data align=32]\n"
+ "\n"
+ "%%elifdef ASM_FORMAT_ELF\n"
+ " %%macro VTG_GLOBAL 2\n"
+ " global NAME(%%1):%%2 hidden\n"
+ " NAME(%%1):\n"
+ " %%endmacro\n"
+ " [section .VTGData progbits alloc noexec write align=4096]\n"
+ " [section .VTGPrLc.Begin progbits alloc noexec write align=32]\n"
+ " dd 0,0,0,0, 0,0,0,0\n"
+ "VTG_GLOBAL g_aVTGPrLc, data\n"
+ " [section .VTGPrLc progbits alloc noexec write align=1]\n"
+ " [section .VTGPrLc.End progbits alloc noexec write align=1]\n"
+ "VTG_GLOBAL g_aVTGPrLc_End, data\n"
+ " dd 0,0,0,0, 0,0,0,0\n"
+ " [section .VTGData]\n"
+ "\n"
+ "%%else\n"
+ " %%error \"ASM_FORMAT_XXX is not defined\"\n"
+ "%%endif\n"
+ "\n"
+ "\n"
+ "VTG_GLOBAL g_VTGObjHeader, data\n"
+ " ;0 1 2 3\n"
+ " ;012345678901234567890123456789012\n"
+ " db 'VTG Object Header v1.7', 0, 0\n"
+ " dd %u\n"
+ " dd NAME(g_acVTGProbeEnabled_End) - NAME(g_VTGObjHeader)\n"
+ " dd NAME(g_achVTGStringTable) - NAME(g_VTGObjHeader)\n"
+ " dd NAME(g_achVTGStringTable_End) - NAME(g_achVTGStringTable)\n"
+ " dd NAME(g_aVTGArgLists) - NAME(g_VTGObjHeader)\n"
+ " dd NAME(g_aVTGArgLists_End) - NAME(g_aVTGArgLists)\n"
+ " dd NAME(g_aVTGProbes) - NAME(g_VTGObjHeader)\n"
+ " dd NAME(g_aVTGProbes_End) - NAME(g_aVTGProbes)\n"
+ " dd NAME(g_aVTGProviders) - NAME(g_VTGObjHeader)\n"
+ " dd NAME(g_aVTGProviders_End) - NAME(g_aVTGProviders)\n"
+ " dd NAME(g_acVTGProbeEnabled) - NAME(g_VTGObjHeader)\n"
+ " dd NAME(g_acVTGProbeEnabled_End) - NAME(g_acVTGProbeEnabled)\n"
+ " dd 0\n"
+ " dd 0\n"
+ "%%ifdef VTG_NEW_MACHO_LINKER\n"
+ " extern section$start$__VTG$__VTGPrLc\n"
+ " RTCCPTR_DEF section$start$__VTG$__VTGPrLc\n"
+ " %%if ARCH_BITS == 32\n"
+ " dd 0\n"
+ " %%endif\n"
+ " extern section$end$__VTG$__VTGPrLc\n"
+ " RTCCPTR_DEF section$end$__VTG$__VTGPrLc\n"
+ " %%if ARCH_BITS == 32\n"
+ " dd 0\n"
+ " %%endif\n"
+ "%%else\n"
+ " RTCCPTR_DEF NAME(g_aVTGPrLc)\n"
+ " %%if ARCH_BITS == 32\n"
+ " dd 0\n"
+ " %%endif\n"
+ " RTCCPTR_DEF NAME(g_aVTGPrLc_End)\n"
+ " %%if ARCH_BITS == 32\n"
+ " dd 0\n"
+ " %%endif\n"
+ "%%endif\n"
+ ,
+ g_pszScript, g_cBits);
+ RTUUID Uuid;
+ int rc = RTUuidCreate(&Uuid);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTUuidCreate failed: %Rrc", rc);
+ ScmStreamPrintf(pStrm,
+ " dd 0%08xh, 0%08xh, 0%08xh, 0%08xh\n"
+ "%%ifdef VTG_NEW_MACHO_LINKER\n"
+ " RTCCPTR_DEF section$start$__VTG$__VTGObj\n"
+ " %%if ARCH_BITS == 32\n"
+ " dd 0\n"
+ " %%endif\n"
+ "%%else\n"
+ " dd 0, 0\n"
+ "%%endif\n"
+ " dd 0, 0\n"
+ , Uuid.au32[0], Uuid.au32[1], Uuid.au32[2], Uuid.au32[3]);
+
+ /*
+ * Dump the string table before we start using the strings.
+ */
+ ScmStreamPrintf(pStrm,
+ "\n"
+ ";\n"
+ "; The string table.\n"
+ ";\n"
+ "VTG_GLOBAL g_achVTGStringTable, data\n");
+ g_offStrTab = 0;
+ RTStrSpaceEnumerate(&g_StrSpace, generateAssemblyStrTabCallback, pStrm);
+ ScmStreamPrintf(pStrm,
+ "VTG_GLOBAL g_achVTGStringTable_End, data\n");
+
+ /*
+ * Write out the argument lists before we use them.
+ */
+ ScmStreamPrintf(pStrm,
+ "\n"
+ ";\n"
+ "; The argument lists.\n"
+ ";\n"
+ "ALIGNDATA(16)\n"
+ "VTG_GLOBAL g_aVTGArgLists, data\n");
+ uint32_t off = 0;
+ RTListForEach(&g_ProviderHead, pProvider, VTGPROVIDER, ListEntry)
+ {
+ RTListForEach(&pProvider->ProbeHead, pProbe, VTGPROBE, ListEntry)
+ {
+ if (pProbe->offArgList != UINT32_MAX)
+ continue;
+
+ /* Write it. */
+ pProbe->offArgList = off;
+ ScmStreamPrintf(pStrm,
+ " ; off=%u\n"
+ " db %2u ; Argument count\n"
+ " db %u ; fHaveLargeArgs\n"
+ " db 0, 0 ; Reserved\n"
+ , off, pProbe->cArgs, (int)pProbe->fHaveLargeArgs);
+ off += 4;
+ RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry)
+ {
+ ScmStreamPrintf(pStrm,
+ " dd %8u ; type '%s' (name '%s')\n"
+ " dd 0%08xh ; type flags\n",
+ strtabGetOff(pArg->pszTracerType), pArg->pszTracerType, pArg->pszName,
+ pArg->fType);
+ off += 8;
+ }
+
+ /* Look for matching argument lists (lazy bird walks the whole list). */
+ PVTGPROVIDER pProv2;
+ RTListForEach(&g_ProviderHead, pProv2, VTGPROVIDER, ListEntry)
+ {
+ PVTGPROBE pProbe2;
+ RTListForEach(&pProvider->ProbeHead, pProbe2, VTGPROBE, ListEntry)
+ {
+ if (pProbe2->offArgList != UINT32_MAX)
+ continue;
+ if (pProbe2->cArgs != pProbe->cArgs)
+ continue;
+
+ PVTGARG pArg2;
+ pArg = RTListNodeGetNext(&pProbe->ArgHead, VTGARG, ListEntry);
+ pArg2 = RTListNodeGetNext(&pProbe2->ArgHead, VTGARG, ListEntry);
+ int32_t cArgs = pProbe->cArgs;
+ while ( cArgs-- > 0
+ && pArg2->pszTracerType == pArg->pszTracerType
+ && pArg2->fType == pArg->fType)
+ {
+ pArg = RTListNodeGetNext(&pArg->ListEntry, VTGARG, ListEntry);
+ pArg2 = RTListNodeGetNext(&pArg2->ListEntry, VTGARG, ListEntry);
+ }
+ if (cArgs >= 0)
+ continue;
+ pProbe2->offArgList = pProbe->offArgList;
+ }
+ }
+ }
+ }
+ ScmStreamPrintf(pStrm,
+ "VTG_GLOBAL g_aVTGArgLists_End, data\n");
+
+
+ /*
+ * Probe definitions.
+ */
+ ScmStreamPrintf(pStrm,
+ "\n"
+ ";\n"
+ "; Prob definitions.\n"
+ ";\n"
+ "ALIGNDATA(16)\n"
+ "VTG_GLOBAL g_aVTGProbes, data\n"
+ "\n");
+ uint32_t iProvider = 0;
+ uint32_t iProbe = 0;
+ RTListForEach(&g_ProviderHead, pProvider, VTGPROVIDER, ListEntry)
+ {
+ pProvider->iFirstProbe = iProbe;
+ RTListForEach(&pProvider->ProbeHead, pProbe, VTGPROBE, ListEntry)
+ {
+ ScmStreamPrintf(pStrm,
+ "VTG_GLOBAL g_VTGProbeData_%s_%s, data ; idx=#%4u\n"
+ " dd %6u ; offName\n"
+ " dd %6u ; offArgList\n"
+ " dw (NAME(g_cVTGProbeEnabled_%s_%s) - NAME(g_acVTGProbeEnabled)) / 4 ; idxEnabled\n"
+ " dw %6u ; idxProvider\n"
+ " dd NAME(g_VTGObjHeader) - NAME(g_VTGProbeData_%s_%s) ; offObjHdr\n"
+ ,
+ pProvider->pszName, pProbe->pszMangledName, iProbe,
+ strtabGetOff(pProbe->pszUnmangledName),
+ pProbe->offArgList,
+ pProvider->pszName, pProbe->pszMangledName,
+ iProvider,
+ pProvider->pszName, pProbe->pszMangledName
+ );
+ pProbe->iProbe = iProbe;
+ iProbe++;
+ }
+ pProvider->cProbes = iProbe - pProvider->iFirstProbe;
+ iProvider++;
+ }
+ ScmStreamPrintf(pStrm, "VTG_GLOBAL g_aVTGProbes_End, data\n");
+
+ /*
+ * The provider data.
+ */
+ ScmStreamPrintf(pStrm,
+ "\n"
+ ";\n"
+ "; Provider data.\n"
+ ";\n"
+ "ALIGNDATA(16)\n"
+ "VTG_GLOBAL g_aVTGProviders, data\n");
+ iProvider = 0;
+ RTListForEach(&g_ProviderHead, pProvider, VTGPROVIDER, ListEntry)
+ {
+ ScmStreamPrintf(pStrm,
+ " ; idx=#%4u - %s\n"
+ " dd %6u ; name\n"
+ " dw %6u ; index of first probe\n"
+ " dw %6u ; count of probes\n"
+ " db %d, %d, %d ; AttrSelf\n"
+ " db %d, %d, %d ; AttrModules\n"
+ " db %d, %d, %d ; AttrFunctions\n"
+ " db %d, %d, %d ; AttrName\n"
+ " db %d, %d, %d ; AttrArguments\n"
+ " db 0 ; reserved\n"
+ "VTG_GLOBAL g_cVTGProviderProbesEnabled_%s, data\n"
+ " dd 0\n"
+ "VTG_GLOBAL g_cVTGProviderSettingsSeqNo_%s, data\n"
+ " dd 0\n"
+ ,
+ iProvider, pProvider->pszName,
+ strtabGetOff(pProvider->pszName),
+ pProvider->iFirstProbe,
+ pProvider->cProbes,
+ pProvider->AttrSelf.enmCode, pProvider->AttrSelf.enmData, pProvider->AttrSelf.enmDataDep,
+ pProvider->AttrModules.enmCode, pProvider->AttrModules.enmData, pProvider->AttrModules.enmDataDep,
+ pProvider->AttrFunctions.enmCode, pProvider->AttrFunctions.enmData, pProvider->AttrFunctions.enmDataDep,
+ pProvider->AttrName.enmCode, pProvider->AttrName.enmData, pProvider->AttrName.enmDataDep,
+ pProvider->AttrArguments.enmCode, pProvider->AttrArguments.enmData, pProvider->AttrArguments.enmDataDep,
+ pProvider->pszName,
+ pProvider->pszName);
+ iProvider++;
+ }
+ ScmStreamPrintf(pStrm, "VTG_GLOBAL g_aVTGProviders_End, data\n");
+
+ /*
+ * Declare the probe enable flags.
+ *
+ * These must be placed at the end so they'll end up adjacent to the probe
+ * locations. This is important for reducing the amount of memory we need
+ * to lock down for user mode modules.
+ */
+ ScmStreamPrintf(pStrm,
+ ";\n"
+ "; Probe enabled flags.\n"
+ ";\n"
+ "ALIGNDATA(16)\n"
+ "VTG_GLOBAL g_acVTGProbeEnabled, data\n"
+ );
+ uint32_t cProbes = 0;
+ RTListForEach(&g_ProviderHead, pProvider, VTGPROVIDER, ListEntry)
+ {
+ RTListForEach(&pProvider->ProbeHead, pProbe, VTGPROBE, ListEntry)
+ {
+ ScmStreamPrintf(pStrm,
+ "VTG_GLOBAL g_cVTGProbeEnabled_%s_%s, data\n"
+ " dd 0\n",
+ pProvider->pszName, pProbe->pszMangledName);
+ cProbes++;
+ }
+ }
+ ScmStreamPrintf(pStrm, "VTG_GLOBAL g_acVTGProbeEnabled_End, data\n");
+ if (cProbes >= _32K)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Too many probes: %u (max %u)", cProbes, _32K - 1);
+
+
+ /*
+ * Emit code for the stub functions.
+ */
+ bool const fWin64 = g_cBits == 64 && (!strcmp(g_pszAssemblerFmtVal, "win64") || !strcmp(g_pszAssemblerFmtVal, "pe64"));
+ bool const fElf = !strcmp(g_pszAssemblerFmtVal, "elf32") || !strcmp(g_pszAssemblerFmtVal, "elf64");
+ ScmStreamPrintf(pStrm,
+ "\n"
+ ";\n"
+ "; Prob stubs.\n"
+ ";\n"
+ "BEGINCODE\n"
+ );
+ if (g_fProbeFnImported)
+ ScmStreamPrintf(pStrm,
+ "EXTERN_IMP2 %s\n"
+ "BEGINCODE ; EXTERN_IMP2 changes section\n",
+ g_pszProbeFnName);
+ else
+ ScmStreamPrintf(pStrm, "extern NAME(%s)\n", g_pszProbeFnName);
+
+ RTListForEach(&g_ProviderHead, pProvider, VTGPROVIDER, ListEntry)
+ {
+ RTListForEach(&pProvider->ProbeHead, pProbe, VTGPROBE, ListEntry)
+ {
+ ScmStreamPrintf(pStrm,
+ "\n"
+ "VTG_GLOBAL VTGProbeStub_%s_%s, function; (VBOXTPGPROBELOC pVTGProbeLoc",
+ pProvider->pszName, pProbe->pszMangledName);
+ RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry)
+ {
+ ScmStreamPrintf(pStrm, ", %s %s", pArg->pszTracerType, pArg->pszName);
+ }
+ ScmStreamPrintf(pStrm,
+ ");\n");
+
+ /*
+ * Check if the probe in question is enabled.
+ */
+ if (g_cBits == 32)
+ ScmStreamPrintf(pStrm,
+ " mov eax, [esp + 4]\n"
+ " test byte [eax+3], 0x80 ; fEnabled == true?\n"
+ " jz .return ; jump on false\n");
+ else if (fWin64)
+ ScmStreamPrintf(pStrm,
+ " test byte [rcx+3], 0x80 ; fEnabled == true?\n"
+ " jz .return ; jump on false\n");
+ else
+ ScmStreamPrintf(pStrm,
+ " test byte [rdi+3], 0x80 ; fEnabled == true?\n"
+ " jz .return ; jump on false\n");
+
+ /*
+ * Jump to the fire-probe function.
+ */
+ if (g_cBits == 32)
+ ScmStreamPrintf(pStrm, g_fPic && fElf ?
+ " jmp %s wrt ..plt\n"
+ : g_fProbeFnImported ?
+ " mov ecx, IMP2(%s)\n"
+ " jmp ecx\n"
+ :
+ " jmp NAME(%s)\n"
+ , g_pszProbeFnName);
+ else
+ ScmStreamPrintf(pStrm, g_fPic && fElf ?
+ " jmp [rel %s wrt ..got]\n"
+ : g_fProbeFnImported ?
+ " jmp IMP2(%s)\n"
+ :
+ " jmp NAME(%s)\n"
+ , g_pszProbeFnName);
+
+ ScmStreamPrintf(pStrm,
+ ".return:\n"
+ " ret ; The probe was disabled, return\n"
+ "\n");
+ }
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE generateObject(const char *pszOutput, const char *pszTempAsm)
+{
+ if (!pszTempAsm)
+ {
+ size_t cch = strlen(pszOutput);
+ char *psz = (char *)alloca(cch + sizeof(".asm"));
+ memcpy(psz, pszOutput, cch);
+ memcpy(psz + cch, ".asm", sizeof(".asm"));
+ pszTempAsm = psz;
+ }
+
+ RTEXITCODE rcExit = generateFile(pszTempAsm, "assembly", generateAssembly);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = generateInvokeAssembler(pszOutput, pszTempAsm);
+ RTFileDelete(pszTempAsm);
+ return rcExit;
+}
+
+
+static RTEXITCODE generateProbeDefineName(char *pszBuf, size_t cbBuf, const char *pszProvider, const char *pszProbe)
+{
+ size_t cbMax = strlen(pszProvider) + 1 + strlen(pszProbe) + 1;
+ if (cbMax > cbBuf || cbMax > 80)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Probe '%s' in provider '%s' ends up with a too long defined\n", pszProbe, pszProvider);
+
+ while (*pszProvider)
+ *pszBuf++ = RT_C_TO_UPPER(*pszProvider++);
+
+ *pszBuf++ = '_';
+
+ while (*pszProbe)
+ {
+ if (pszProbe[0] == '_' && pszProbe[1] == '_')
+ pszProbe++;
+ *pszBuf++ = RT_C_TO_UPPER(*pszProbe++);
+ }
+
+ *pszBuf = '\0';
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE generateProviderDefineName(char *pszBuf, size_t cbBuf, const char *pszProvider)
+{
+ size_t cbMax = strlen(pszProvider) + 1;
+ if (cbMax > cbBuf || cbMax > 80)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Provider '%s' ends up with a too long defined\n", pszProvider);
+
+ while (*pszProvider)
+ *pszBuf++ = RT_C_TO_UPPER(*pszProvider++);
+
+ *pszBuf = '\0';
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Called via generateFile to generate the header file.
+ *
+ * @returns Exit code status.
+ * @param pStrm The output stream.
+ */
+static RTEXITCODE generateHeader(PSCMSTREAM pStrm)
+{
+ /*
+ * Calc the double inclusion blocker define and then write the file header.
+ */
+ char szTmp[4096];
+ const char *pszName = RTPathFilename(g_pszScript);
+ size_t cchName = strlen(pszName);
+ if (cchName >= sizeof(szTmp) - 64)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "File name is too long '%s'", pszName);
+ szTmp[0] = '_';
+ szTmp[1] = '_';
+ szTmp[2] = '_';
+ memcpy(&szTmp[3], pszName, cchName);
+ szTmp[3 + cchName + 0] = '_';
+ szTmp[3 + cchName + 1] = '_';
+ szTmp[3 + cchName + 2] = '_';
+ szTmp[3 + cchName + 3] = '\0';
+ char *psz = &szTmp[3];
+ while (*psz)
+ {
+ if (!RT_C_IS_ALNUM(*psz) && *psz != '_')
+ *psz = '_';
+ psz++;
+ }
+
+ ScmStreamPrintf(pStrm,
+ "/* $Id: VBoxTpG.cpp $ */\n"
+ "/** @file\n"
+ " * Automatically generated from %s. Do NOT edit!\n"
+ " */\n"
+ "\n"
+ "#ifndef %s\n"
+ "#define %s\n"
+ "#ifndef RT_WITHOUT_PRAGMA_ONCE\n"
+ "# pragma once\n"
+ "#endif\n"
+ "\n"
+ "#include <VBox/VBoxTpG.h>\n"
+ "\n"
+ "#ifndef %s\n"
+ "# error \"Expected '%s' to be defined\"\n"
+ "#endif\n"
+ "\n"
+ "RT_C_DECLS_BEGIN\n"
+ "\n"
+ "#ifdef VBOX_WITH_DTRACE\n"
+ "\n"
+ "# ifdef _MSC_VER\n"
+ "# pragma data_seg(VTG_LOC_SECT)\n"
+ "# pragma data_seg()\n"
+ "# endif\n"
+ "\n"
+ ,
+ g_pszScript,
+ szTmp,
+ szTmp,
+ g_pszContextDefine,
+ g_pszContextDefine);
+
+ /*
+ * Declare data, code and macros for each probe.
+ */
+ PVTGPROVIDER pProv;
+ PVTGPROBE pProbe;
+ PVTGARG pArg;
+ RTListForEach(&g_ProviderHead, pProv, VTGPROVIDER, ListEntry)
+ {
+ /* This macro is not available in ring-3 because we don't have
+ anything similar available for native dtrace. */
+ ScmStreamPrintf(pStrm, "\n\n");
+ if (g_fTypeContext != VTG_TYPE_CTX_R3)
+ {
+ generateProviderDefineName(szTmp, sizeof(szTmp), pProv->pszName);
+ ScmStreamPrintf(pStrm,
+ "extern uint32_t const volatile g_cVTGProviderProbesEnabled_%s;\n"
+ "# define %s_ANY_PROBES_ENABLED() \\\n"
+ " (RT_UNLIKELY(g_cVTGProviderProbesEnabled_%s != 0))\n"
+ "extern uint32_t const volatile g_cVTGProviderSettingsSeqNo_%s;\n"
+ "# define %s_GET_SETTINGS_SEQ_NO() (g_cVTGProviderSettingsSeqNo_%s)\n"
+ "\n",
+ pProv->pszName,
+ szTmp, pProv->pszName,
+ pProv->pszName,
+ szTmp, pProv->pszName);
+ }
+
+ RTListForEach(&pProv->ProbeHead, pProbe, VTGPROBE, ListEntry)
+ {
+ ScmStreamPrintf(pStrm,
+ "extern uint32_t const volatile g_cVTGProbeEnabled_%s_%s;\n"
+ "extern VTGDESCPROBE g_VTGProbeData_%s_%s;\n"
+ "DECLASM(void) VTGProbeStub_%s_%s(PVTGPROBELOC",
+ pProv->pszName, pProbe->pszMangledName,
+ pProv->pszName, pProbe->pszMangledName,
+ pProv->pszName, pProbe->pszMangledName);
+ RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry)
+ {
+ ScmStreamPrintf(pStrm, ", %s", pArg->pszCtxType);
+ }
+ generateProbeDefineName(szTmp, sizeof(szTmp), pProv->pszName, pProbe->pszMangledName);
+ ScmStreamPrintf(pStrm,
+ ");\n"
+ "# define %s_ENABLED() (RT_UNLIKELY(g_cVTGProbeEnabled_%s_%s != 0))\n"
+ "# define %s_ENABLED_RAW() (g_cVTGProbeEnabled_%s_%s)\n"
+ "# define %s("
+ ,
+ szTmp, pProv->pszName, pProbe->pszMangledName,
+ szTmp, pProv->pszName, pProbe->pszMangledName,
+ szTmp);
+ RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry)
+ {
+ if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry))
+ ScmStreamPrintf(pStrm, "%s", pArg->pszName);
+ else
+ ScmStreamPrintf(pStrm, ", %s", pArg->pszName);
+ }
+ ScmStreamPrintf(pStrm,
+ ") \\\n"
+ " do { \\\n"
+ " if (RT_UNLIKELY(g_cVTGProbeEnabled_%s_%s)) \\\n"
+ " { \\\n"
+ " VTG_DECL_VTGPROBELOC(s_VTGProbeLoc) = \\\n"
+ " { __LINE__, 0, 0, __FUNCTION__, &g_VTGProbeData_%s_%s }; \\\n"
+ " VTGProbeStub_%s_%s(&s_VTGProbeLoc",
+ pProv->pszName, pProbe->pszMangledName,
+ pProv->pszName, pProbe->pszMangledName,
+ pProv->pszName, pProbe->pszMangledName);
+ RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry)
+ {
+ ScmStreamPrintf(pStrm, pArg->pszArgPassingFmt, pArg->pszName);
+ }
+ ScmStreamPrintf(pStrm,
+ "); \\\n"
+ " } \\\n"
+ " { \\\n" );
+ uint32_t iArg = 0;
+ RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry)
+ {
+ if ((pArg->fType & (VTG_TYPE_FIXED_SIZED | VTG_TYPE_AUTO_CONV_PTR)) == VTG_TYPE_FIXED_SIZED)
+ ScmStreamPrintf(pStrm,
+ " AssertCompile(sizeof(%s) == %u); \\\n"
+ " AssertCompile(sizeof(%s) <= %u); \\\n",
+ pArg->pszTracerType, pArg->fType & VTG_TYPE_SIZE_MASK,
+ pArg->pszName, pArg->fType & VTG_TYPE_SIZE_MASK);
+ else if (pArg->fType & (VTG_TYPE_POINTER | VTG_TYPE_HC_ARCH_SIZED))
+ ScmStreamPrintf(pStrm,
+ " AssertCompile(sizeof(%s) <= sizeof(uintptr_t)); \\\n"
+ " AssertCompile(sizeof(%s) <= sizeof(uintptr_t)); \\\n",
+ pArg->pszName,
+ pArg->pszTracerType);
+ iArg++;
+ }
+ ScmStreamPrintf(pStrm,
+ " } \\\n"
+ " } while (0)\n"
+ "\n");
+ }
+ }
+
+ ScmStreamPrintf(pStrm,
+ "\n"
+ "#else\n"
+ "\n");
+ RTListForEach(&g_ProviderHead, pProv, VTGPROVIDER, ListEntry)
+ {
+ if (g_fTypeContext != VTG_TYPE_CTX_R3)
+ {
+ generateProviderDefineName(szTmp, sizeof(szTmp), pProv->pszName);
+ ScmStreamPrintf(pStrm,
+ "# define %s_ANY_PROBES_ENABLED() (false)\n"
+ "# define %s_GET_SETTINGS_SEQ_NO() UINT32_C(0)\n"
+ "\n",
+ szTmp, szTmp);
+ }
+
+ RTListForEach(&pProv->ProbeHead, pProbe, VTGPROBE, ListEntry)
+ {
+ generateProbeDefineName(szTmp, sizeof(szTmp), pProv->pszName, pProbe->pszMangledName);
+ ScmStreamPrintf(pStrm,
+ "# define %s_ENABLED() (false)\n"
+ "# define %s_ENABLED_RAW() UINT32_C(0)\n"
+ "# define %s("
+ , szTmp, szTmp, szTmp);
+ RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry)
+ {
+ if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry))
+ ScmStreamPrintf(pStrm, "%s", pArg->pszName);
+ else
+ ScmStreamPrintf(pStrm, ", %s", pArg->pszName);
+ }
+ ScmStreamPrintf(pStrm,
+ ") do { } while (0)\n");
+ }
+ }
+
+ ScmStreamWrite(pStrm, RT_STR_TUPLE("\n"
+ "#endif\n"
+ "\n"
+ "RT_C_DECLS_END\n"
+ "#endif\n"));
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Called via generateFile to generate the wrapper header file.
+ *
+ * @returns Exit code status.
+ * @param pStrm The output stream.
+ */
+static RTEXITCODE generateWrapperHeader(PSCMSTREAM pStrm)
+{
+ /*
+ * Calc the double inclusion blocker define and then write the file header.
+ */
+ char szTmp[4096];
+ const char *pszName = RTPathFilename(g_pszScript);
+ size_t cchName = strlen(pszName);
+ if (cchName >= sizeof(szTmp) - 64)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "File name is too long '%s'", pszName);
+ szTmp[0] = '_';
+ szTmp[1] = '_';
+ szTmp[2] = '_';
+ memcpy(&szTmp[3], pszName, cchName);
+ strcpy(&szTmp[3 + cchName ], "___WRAPPER___");
+ char *psz = &szTmp[3];
+ while (*psz)
+ {
+ if (!RT_C_IS_ALNUM(*psz) && *psz != '_')
+ *psz = '_';
+ psz++;
+ }
+
+ ScmStreamPrintf(pStrm,
+ "/* $Id: VBoxTpG.cpp $ */\n"
+ "/** @file\n"
+ " * Automatically generated from %s. Do NOT edit!\n"
+ " */\n"
+ "\n"
+ "#ifndef %s\n"
+ "#define %s\n"
+ "\n"
+ "#include <VBox/VBoxTpG.h>\n"
+ "\n"
+ "#ifndef %s\n"
+ "# error \"Expected '%s' to be defined\"\n"
+ "#endif\n"
+ "\n"
+ "#ifdef VBOX_WITH_DTRACE\n"
+ "\n"
+ ,
+ g_pszScript,
+ szTmp,
+ szTmp,
+ g_pszContextDefine,
+ g_pszContextDefine);
+
+ /*
+ * Declare macros for each probe.
+ */
+ PVTGPROVIDER pProv;
+ PVTGPROBE pProbe;
+ PVTGARG pArg;
+ RTListForEach(&g_ProviderHead, pProv, VTGPROVIDER, ListEntry)
+ {
+ RTListForEach(&pProv->ProbeHead, pProbe, VTGPROBE, ListEntry)
+ {
+ generateProbeDefineName(szTmp, sizeof(szTmp), pProv->pszName, pProbe->pszMangledName);
+ ScmStreamPrintf(pStrm,
+ "# define %s("
+ , szTmp);
+ RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry)
+ {
+ if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry))
+ ScmStreamPrintf(pStrm, "%s", pArg->pszName);
+ else
+ ScmStreamPrintf(pStrm, ", %s", pArg->pszName);
+ }
+ ScmStreamPrintf(pStrm,
+ ") \\\n"
+ " do { \\\n"
+ " if (RT_UNLIKELY(%s_ENABLED())) \\\n"
+ " { \\\n"
+ " %s_ORIGINAL("
+ , szTmp, szTmp);
+ RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry)
+ {
+ const char *pszFmt = pArg->pszArgPassingFmt;
+ if (pArg->fType & VTG_TYPE_AUTO_CONV_PTR)
+ {
+ /* Casting is required. ASSUMES sizeof(RTR0PTR) == sizeof(RTR3PTR) - safe! */
+ pszFmt += sizeof(", ") - 1;
+ if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry))
+ ScmStreamPrintf(pStrm, "(%s)%M", pArg->pszTracerType, pszFmt, pArg->pszName);
+ else
+ ScmStreamPrintf(pStrm, ", (%s)%M", pArg->pszTracerType, pszFmt, pArg->pszName);
+ }
+ else if (pArg->fType & VTG_TYPE_CONST_CHAR_PTR)
+ {
+ /* Casting from 'const char *' (probe) to 'char *' (dtrace) is required to shut up warnings. */
+ pszFmt += sizeof(", ") - 1;
+ if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry))
+ ScmStreamPrintf(pStrm, "(char *)%M", pszFmt, pArg->pszName);
+ else
+ ScmStreamPrintf(pStrm, ", (char *)%M", pszFmt, pArg->pszName);
+ }
+ else
+ {
+ if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry))
+ ScmStreamPrintf(pStrm, pArg->pszArgPassingFmt + sizeof(", ") - 1, pArg->pszName);
+ else
+ ScmStreamPrintf(pStrm, pArg->pszArgPassingFmt, pArg->pszName);
+ }
+ }
+ ScmStreamPrintf(pStrm,
+ "); \\\n"
+ " } \\\n"
+ " } while (0)\n"
+ "\n");
+ }
+ }
+
+ ScmStreamPrintf(pStrm,
+ "\n"
+ "#else\n"
+ "\n");
+ RTListForEach(&g_ProviderHead, pProv, VTGPROVIDER, ListEntry)
+ {
+ RTListForEach(&pProv->ProbeHead, pProbe, VTGPROBE, ListEntry)
+ {
+ generateProbeDefineName(szTmp, sizeof(szTmp), pProv->pszName, pProbe->pszMangledName);
+ ScmStreamPrintf(pStrm,
+ "# define %s("
+ , szTmp);
+ RTListForEach(&pProbe->ArgHead, pArg, VTGARG, ListEntry)
+ {
+ if (RTListNodeIsFirst(&pProbe->ArgHead, &pArg->ListEntry))
+ ScmStreamPrintf(pStrm, "%s", pArg->pszName);
+ else
+ ScmStreamPrintf(pStrm, ", %s", pArg->pszName);
+ }
+ ScmStreamPrintf(pStrm,
+ ") do { } while (0)\n");
+ }
+ }
+
+ ScmStreamWrite(pStrm, RT_STR_TUPLE("\n"
+ "#endif\n"
+ "\n"
+ "#endif\n"));
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Parser error with line and position.
+ *
+ * @returns RTEXITCODE_FAILURE.
+ * @param pStrm The stream.
+ * @param fAbs Absolute or relative offset.
+ * @param offSeek When @a fAbs is @c false, the offset from the current
+ * position to the point of failure. When @a fAbs is @c
+ * true, it's the absolute position.
+ * @param pszFormat The message format string.
+ * @param va Format arguments.
+ */
+static RTEXITCODE parseErrorExV(PSCMSTREAM pStrm, bool fAbs, size_t offSeek, const char *pszFormat, va_list va)
+{
+ if (fAbs)
+ ScmStreamSeekAbsolute(pStrm, offSeek);
+ else if (offSeek != 0)
+ ScmStreamSeekRelative(pStrm, -(ssize_t)offSeek);
+ size_t const off = ScmStreamTell(pStrm);
+ size_t const iLine = ScmStreamTellLine(pStrm);
+ ScmStreamSeekByLine(pStrm, iLine);
+ size_t const offLine = ScmStreamTell(pStrm);
+
+ va_list va2;
+ va_copy(va2, va);
+ RTPrintf("%s:%d:%zd: error: %N.\n", g_pszScript, iLine + 1, off - offLine + 1, pszFormat, &va2);
+ va_end(va2);
+
+ size_t cchLine;
+ SCMEOL enmEof;
+ const char *pszLine = ScmStreamGetLineByNo(pStrm, iLine, &cchLine, &enmEof);
+ if (pszLine)
+ RTPrintf(" %.*s\n"
+ " %*s^\n",
+ cchLine, pszLine, off - offLine, "");
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Parser error with line and position.
+ *
+ * @returns RTEXITCODE_FAILURE.
+ * @param pStrm The stream.
+ * @param off The offset from the current position to the point of
+ * failure.
+ * @param pszFormat The message format string.
+ * @param ... Format arguments.
+ */
+static RTEXITCODE parseError(PSCMSTREAM pStrm, size_t off, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTEXITCODE rcExit = parseErrorExV(pStrm, false, off, pszFormat, va);
+ va_end(va);
+ return rcExit;
+}
+
+
+/**
+ * Parser error with line and position, absolute version.
+ *
+ * @returns RTEXITCODE_FAILURE.
+ * @param pStrm The stream.
+ * @param off The offset from the current position to the point of
+ * failure.
+ * @param pszFormat The message format string.
+ * @param ... Format arguments.
+ */
+static RTEXITCODE parseErrorAbs(PSCMSTREAM pStrm, size_t off, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTEXITCODE rcExit = parseErrorExV(pStrm, true /*fAbs*/, off, pszFormat, va);
+ va_end(va);
+ return rcExit;
+}
+
+
+/**
+ * Parser warning with line and position.
+ *
+ * @param pStrm The stream.
+ * @param fAbs Absolute or relative offset.
+ * @param offSeek When @a fAbs is @c false, the offset from the current
+ * position to the point of failure. When @a fAbs is @c
+ * true, it's the absolute position.
+ * @param pszFormat The message format string.
+ * @param va Format arguments.
+ */
+static void parseWarnExV(PSCMSTREAM pStrm, bool fAbs, size_t offSeek, const char *pszFormat, va_list va)
+{
+ /* Save the stream position. */
+ size_t const offOrg = ScmStreamTell(pStrm);
+
+ if (fAbs)
+ ScmStreamSeekAbsolute(pStrm, offSeek);
+ else if (offSeek != 0)
+ ScmStreamSeekRelative(pStrm, -(ssize_t)offSeek);
+ size_t const off = ScmStreamTell(pStrm);
+ size_t const iLine = ScmStreamTellLine(pStrm);
+ ScmStreamSeekByLine(pStrm, iLine);
+ size_t const offLine = ScmStreamTell(pStrm);
+
+ va_list va2;
+ va_copy(va2, va);
+ RTPrintf("%s:%d:%zd: warning: %N.\n", g_pszScript, iLine + 1, off - offLine + 1, pszFormat, &va2);
+ va_end(va2);
+
+ size_t cchLine;
+ SCMEOL enmEof;
+ const char *pszLine = ScmStreamGetLineByNo(pStrm, iLine, &cchLine, &enmEof);
+ if (pszLine)
+ RTPrintf(" %.*s\n"
+ " %*s^\n",
+ cchLine, pszLine, off - offLine, "");
+
+ /* restore the position. */
+ int rc = ScmStreamSeekAbsolute(pStrm, offOrg);
+ AssertRC(rc);
+}
+
+#if 0 /* unused */
+/**
+ * Parser warning with line and position.
+ *
+ * @param pStrm The stream.
+ * @param off The offset from the current position to the point of
+ * failure.
+ * @param pszFormat The message format string.
+ * @param ... Format arguments.
+ */
+static void parseWarn(PSCMSTREAM pStrm, size_t off, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ parseWarnExV(pStrm, false, off, pszFormat, va);
+ va_end(va);
+}
+#endif /* unused */
+
+/**
+ * Parser warning with line and position, absolute version.
+ *
+ * @param pStrm The stream.
+ * @param off The offset from the current position to the point of
+ * failure.
+ * @param pszFormat The message format string.
+ * @param ... Format arguments.
+ */
+static void parseWarnAbs(PSCMSTREAM pStrm, size_t off, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ parseWarnExV(pStrm, true /*fAbs*/, off, pszFormat, va);
+ va_end(va);
+}
+
+
+/**
+ * Handles a C++ one line comment.
+ *
+ * @returns Exit code.
+ * @param pStrm The stream.
+ */
+static RTEXITCODE parseOneLineComment(PSCMSTREAM pStrm)
+{
+ ScmStreamSeekByLine(pStrm, ScmStreamTellLine(pStrm) + 1);
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Handles a multi-line C/C++ comment.
+ *
+ * @returns Exit code.
+ * @param pStrm The stream.
+ */
+static RTEXITCODE parseMultiLineComment(PSCMSTREAM pStrm)
+{
+ unsigned ch;
+ while ((ch = ScmStreamGetCh(pStrm)) != ~(unsigned)0)
+ {
+ if (ch == '*')
+ {
+ do
+ ch = ScmStreamGetCh(pStrm);
+ while (ch == '*');
+ if (ch == '/')
+ return RTEXITCODE_SUCCESS;
+ }
+ }
+
+ parseError(pStrm, 1, "Expected end of comment, got end of file");
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Skips spaces and comments.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE.
+ * @param pStrm The stream..
+ */
+static RTEXITCODE parseSkipSpacesAndComments(PSCMSTREAM pStrm)
+{
+ unsigned ch;
+ while ((ch = ScmStreamPeekCh(pStrm)) != ~(unsigned)0)
+ {
+ if (!RT_C_IS_SPACE(ch) && ch != '/')
+ return RTEXITCODE_SUCCESS;
+ unsigned ch2 = ScmStreamGetCh(pStrm); AssertBreak(ch == ch2); NOREF(ch2);
+ if (ch == '/')
+ {
+ ch = ScmStreamGetCh(pStrm);
+ RTEXITCODE rcExit;
+ if (ch == '*')
+ rcExit = parseMultiLineComment(pStrm);
+ else if (ch == '/')
+ rcExit = parseOneLineComment(pStrm);
+ else
+ rcExit = parseError(pStrm, 2, "Unexpected character");
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ }
+ }
+
+ return parseError(pStrm, 0, "Unexpected end of file");
+}
+
+
+/**
+ * Skips spaces and comments, returning the next character.
+ *
+ * @returns Next non-space-non-comment character. ~(unsigned)0 on EOF or
+ * failure.
+ * @param pStrm The stream.
+ */
+static unsigned parseGetNextNonSpaceNonCommentCh(PSCMSTREAM pStrm)
+{
+ unsigned ch;
+ while ((ch = ScmStreamGetCh(pStrm)) != ~(unsigned)0)
+ {
+ if (!RT_C_IS_SPACE(ch) && ch != '/')
+ return ch;
+ if (ch == '/')
+ {
+ ch = ScmStreamGetCh(pStrm);
+ RTEXITCODE rcExit;
+ if (ch == '*')
+ rcExit = parseMultiLineComment(pStrm);
+ else if (ch == '/')
+ rcExit = parseOneLineComment(pStrm);
+ else
+ rcExit = parseError(pStrm, 2, "Unexpected character");
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return ~(unsigned)0;
+ }
+ }
+
+ parseError(pStrm, 0, "Unexpected end of file");
+ return ~(unsigned)0;
+}
+
+
+/**
+ * Get the next non-space-non-comment character on a preprocessor line.
+ *
+ * @returns The next character. On error message and ~(unsigned)0.
+ * @param pStrm The stream.
+ */
+static unsigned parseGetNextNonSpaceNonCommentChOnPpLine(PSCMSTREAM pStrm)
+{
+ size_t off = ScmStreamTell(pStrm) - 1;
+ unsigned ch;
+ while ((ch = ScmStreamGetCh(pStrm)) != ~(unsigned)0)
+ {
+ if (RT_C_IS_SPACE(ch))
+ {
+ if (ch == '\n' || ch == '\r')
+ {
+ parseErrorAbs(pStrm, off, "Invalid preprocessor statement");
+ break;
+ }
+ }
+ else if (ch == '\\')
+ {
+ size_t off2 = ScmStreamTell(pStrm) - 1;
+ ch = ScmStreamGetCh(pStrm);
+ if (ch == '\r')
+ ch = ScmStreamGetCh(pStrm);
+ if (ch != '\n')
+ {
+ parseErrorAbs(pStrm, off2, "Expected new line");
+ break;
+ }
+ }
+ else
+ return ch;
+ }
+ return ~(unsigned)0;
+}
+
+
+
+/**
+ * Skips spaces and comments.
+ *
+ * @returns Same as ScmStreamCGetWord
+ * @param pStrm The stream..
+ * @param pcchWord Where to return the length.
+ */
+static const char *parseGetNextCWord(PSCMSTREAM pStrm, size_t *pcchWord)
+{
+ if (parseSkipSpacesAndComments(pStrm) != RTEXITCODE_SUCCESS)
+ return NULL;
+ return ScmStreamCGetWord(pStrm, pcchWord);
+}
+
+
+
+/**
+ * Parses interface stability.
+ *
+ * @returns Interface stability if parsed correctly, otherwise error message and
+ * kVTGStability_Invalid.
+ * @param pStrm The stream.
+ * @param ch The first character in the stability spec.
+ */
+static kVTGStability parseStability(PSCMSTREAM pStrm, unsigned ch)
+{
+ switch (ch)
+ {
+ case 'E':
+ if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("External")))
+ return kVTGStability_External;
+ if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Evolving")))
+ return kVTGStability_Evolving;
+ break;
+ case 'I':
+ if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Internal")))
+ return kVTGStability_Internal;
+ break;
+ case 'O':
+ if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Obsolete")))
+ return kVTGStability_Obsolete;
+ break;
+ case 'P':
+ if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Private")))
+ return kVTGStability_Private;
+ break;
+ case 'S':
+ if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Stable")))
+ return kVTGStability_Stable;
+ if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Standard")))
+ return kVTGStability_Standard;
+ break;
+ case 'U':
+ if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Unstable")))
+ return kVTGStability_Unstable;
+ break;
+ }
+ parseError(pStrm, 1, "Unknown stability specifier");
+ return kVTGStability_Invalid;
+}
+
+
+/**
+ * Parses data depndency class.
+ *
+ * @returns Data dependency class if parsed correctly, otherwise error message
+ * and kVTGClass_Invalid.
+ * @param pStrm The stream.
+ * @param ch The first character in the stability spec.
+ */
+static kVTGClass parseDataDepClass(PSCMSTREAM pStrm, unsigned ch)
+{
+ switch (ch)
+ {
+ case 'C':
+ if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Common")))
+ return kVTGClass_Common;
+ if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Cpu")))
+ return kVTGClass_Cpu;
+ break;
+ case 'G':
+ if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Group")))
+ return kVTGClass_Group;
+ break;
+ case 'I':
+ if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Isa")))
+ return kVTGClass_Isa;
+ break;
+ case 'P':
+ if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Platform")))
+ return kVTGClass_Platform;
+ break;
+ case 'U':
+ if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("Unknown")))
+ return kVTGClass_Unknown;
+ break;
+ }
+ parseError(pStrm, 1, "Unknown data dependency class specifier");
+ return kVTGClass_Invalid;
+}
+
+/**
+ * Parses a pragma D attributes statement.
+ *
+ * @returns Suitable exit code, errors message already written on failure.
+ * @param pStrm The stream.
+ */
+static RTEXITCODE parsePragmaDAttributes(PSCMSTREAM pStrm)
+{
+ /*
+ * "CodeStability/DataStability/DataDepClass" - no spaces allowed.
+ */
+ unsigned ch = parseGetNextNonSpaceNonCommentChOnPpLine(pStrm);
+ if (ch == ~(unsigned)0)
+ return RTEXITCODE_FAILURE;
+
+ kVTGStability enmCode = parseStability(pStrm, ch);
+ if (enmCode == kVTGStability_Invalid)
+ return RTEXITCODE_FAILURE;
+ ch = ScmStreamGetCh(pStrm);
+ if (ch != '/')
+ return parseError(pStrm, 1, "Expected '/' following the code stability specifier");
+
+ kVTGStability enmData = parseStability(pStrm, ScmStreamGetCh(pStrm));
+ if (enmData == kVTGStability_Invalid)
+ return RTEXITCODE_FAILURE;
+ ch = ScmStreamGetCh(pStrm);
+ if (ch != '/')
+ return parseError(pStrm, 1, "Expected '/' following the data stability specifier");
+
+ kVTGClass enmDataDep = parseDataDepClass(pStrm, ScmStreamGetCh(pStrm));
+ if (enmDataDep == kVTGClass_Invalid)
+ return RTEXITCODE_FAILURE;
+
+ /*
+ * Expecting 'provider' followed by the name of an provider defined earlier.
+ */
+ ch = parseGetNextNonSpaceNonCommentChOnPpLine(pStrm);
+ if (ch == ~(unsigned)0)
+ return RTEXITCODE_FAILURE;
+ if (ch != 'p' || !ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("provider")))
+ return parseError(pStrm, 1, "Expected 'provider'");
+
+ size_t cchName;
+ const char *pszName = parseGetNextCWord(pStrm, &cchName);
+ if (!pszName)
+ return parseError(pStrm, 1, "Expected provider name");
+
+ PVTGPROVIDER pProv;
+ RTListForEach(&g_ProviderHead, pProv, VTGPROVIDER, ListEntry)
+ {
+ if ( !strncmp(pProv->pszName, pszName, cchName)
+ && pProv->pszName[cchName] == '\0')
+ break;
+ }
+ if (RTListNodeIsDummy(&g_ProviderHead, pProv, VTGPROVIDER, ListEntry))
+ return parseError(pStrm, cchName, "Provider not found");
+
+ /*
+ * Which aspect of the provider?
+ */
+ size_t cchAspect;
+ const char *pszAspect = parseGetNextCWord(pStrm, &cchAspect);
+ if (!pszAspect)
+ return parseError(pStrm, 1, "Expected provider aspect");
+
+ PVTGATTRS pAttrs;
+ if (cchAspect == 8 && !memcmp(pszAspect, "provider", 8))
+ pAttrs = &pProv->AttrSelf;
+ else if (cchAspect == 8 && !memcmp(pszAspect, "function", 8))
+ pAttrs = &pProv->AttrFunctions;
+ else if (cchAspect == 6 && !memcmp(pszAspect, "module", 6))
+ pAttrs = &pProv->AttrModules;
+ else if (cchAspect == 4 && !memcmp(pszAspect, "name", 4))
+ pAttrs = &pProv->AttrName;
+ else if (cchAspect == 4 && !memcmp(pszAspect, "args", 4))
+ pAttrs = &pProv->AttrArguments;
+ else
+ return parseError(pStrm, cchAspect, "Unknown aspect");
+
+ if (pAttrs->enmCode != kVTGStability_Invalid)
+ return parseError(pStrm, cchAspect, "You have already specified these attributes");
+
+ pAttrs->enmCode = enmCode;
+ pAttrs->enmData = enmData;
+ pAttrs->enmDataDep = enmDataDep;
+ return RTEXITCODE_SUCCESS;
+}
+
+/**
+ * Parses a D pragma statement.
+ *
+ * @returns Suitable exit code, errors message already written on failure.
+ * @param pStrm The stream.
+ */
+static RTEXITCODE parsePragma(PSCMSTREAM pStrm)
+{
+ RTEXITCODE rcExit;
+ unsigned ch = parseGetNextNonSpaceNonCommentChOnPpLine(pStrm);
+ if (ch == ~(unsigned)0)
+ rcExit = RTEXITCODE_FAILURE;
+ else if (ch == 'D' && ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("D")))
+ {
+ ch = parseGetNextNonSpaceNonCommentChOnPpLine(pStrm);
+ if (ch == ~(unsigned)0)
+ rcExit = RTEXITCODE_FAILURE;
+ else if (ch == 'a' && ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("attributes")))
+ rcExit = parsePragmaDAttributes(pStrm);
+ else
+ rcExit = parseError(pStrm, 1, "Unknown pragma D");
+ }
+ else
+ rcExit = parseError(pStrm, 1, "Unknown pragma");
+ return rcExit;
+}
+
+
+/**
+ * Classifies the given type expression.
+ *
+ * @return Type flags.
+ * @param pszType The type expression.
+ * @param pStrm The input stream (for errors + warnings).
+ * @param offSrc The absolute source position of this expression (for
+ * warnings).
+ */
+static uint32_t parseTypeExpression(const char *pszType, PSCMSTREAM pStrm, size_t offSrc)
+{
+ size_t cchType = strlen(pszType);
+#define MY_STRMATCH(a_sz) (cchType == sizeof(a_sz) - 1 && !memcmp(a_sz, pszType, sizeof(a_sz) - 1))
+
+ /*
+ * Try detect pointers.
+ */
+ if (pszType[cchType - 1] == '*')
+ {
+ if (MY_STRMATCH("const char *")) return VTG_TYPE_POINTER | VTG_TYPE_CONST_CHAR_PTR;
+ return VTG_TYPE_POINTER;
+ }
+ if (pszType[cchType - 1] == '&')
+ {
+ parseWarnAbs(pStrm, offSrc, "Please avoid using references like '%s' for probe arguments!", pszType);
+ return VTG_TYPE_POINTER;
+ }
+
+ /*
+ * Standard integer types and IPRT variants.
+ * It's important that we catch all types larger than 32-bit here or we'll
+ * screw up the probe argument handling.
+ */
+ if (MY_STRMATCH("int")) return VTG_TYPE_FIXED_SIZED | sizeof(int) | VTG_TYPE_SIGNED;
+ if (MY_STRMATCH("uintptr_t")) return VTG_TYPE_HC_ARCH_SIZED | VTG_TYPE_UNSIGNED;
+ if (MY_STRMATCH("intptr_t")) return VTG_TYPE_HC_ARCH_SIZED | VTG_TYPE_SIGNED;
+
+ //if (MY_STRMATCH("uint128_t")) return VTG_TYPE_FIXED_SIZED | sizeof(uint128_t) | VTG_TYPE_UNSIGNED;
+ if (MY_STRMATCH("uint64_t")) return VTG_TYPE_FIXED_SIZED | sizeof(uint64_t) | VTG_TYPE_UNSIGNED;
+ if (MY_STRMATCH("uint32_t")) return VTG_TYPE_FIXED_SIZED | sizeof(uint32_t) | VTG_TYPE_UNSIGNED;
+ if (MY_STRMATCH("uint16_t")) return VTG_TYPE_FIXED_SIZED | sizeof(uint16_t) | VTG_TYPE_UNSIGNED;
+ if (MY_STRMATCH("uint8_t")) return VTG_TYPE_FIXED_SIZED | sizeof(uint8_t) | VTG_TYPE_UNSIGNED;
+
+ //if (MY_STRMATCH("int128_t")) return VTG_TYPE_FIXED_SIZED | sizeof(int128_t) | VTG_TYPE_SIGNED;
+ if (MY_STRMATCH("int64_t")) return VTG_TYPE_FIXED_SIZED | sizeof(int64_t) | VTG_TYPE_SIGNED;
+ if (MY_STRMATCH("int32_t")) return VTG_TYPE_FIXED_SIZED | sizeof(int32_t) | VTG_TYPE_SIGNED;
+ if (MY_STRMATCH("int16_t")) return VTG_TYPE_FIXED_SIZED | sizeof(int16_t) | VTG_TYPE_SIGNED;
+ if (MY_STRMATCH("int8_t")) return VTG_TYPE_FIXED_SIZED | sizeof(int8_t) | VTG_TYPE_SIGNED;
+
+ if (MY_STRMATCH("RTUINT64U")) return VTG_TYPE_FIXED_SIZED | sizeof(uint64_t) | VTG_TYPE_UNSIGNED;
+ if (MY_STRMATCH("RTUINT32U")) return VTG_TYPE_FIXED_SIZED | sizeof(uint32_t) | VTG_TYPE_UNSIGNED;
+ if (MY_STRMATCH("RTUINT16U")) return VTG_TYPE_FIXED_SIZED | sizeof(uint16_t) | VTG_TYPE_UNSIGNED;
+
+ if (MY_STRMATCH("RTMSINTERVAL")) return VTG_TYPE_FIXED_SIZED | sizeof(RTMSINTERVAL) | VTG_TYPE_UNSIGNED;
+ if (MY_STRMATCH("RTTIMESPEC")) return VTG_TYPE_FIXED_SIZED | sizeof(RTTIMESPEC) | VTG_TYPE_SIGNED;
+ if (MY_STRMATCH("RTPROCESS")) return VTG_TYPE_FIXED_SIZED | sizeof(RTPROCESS) | VTG_TYPE_UNSIGNED;
+ if (MY_STRMATCH("RTHCPHYS")) return VTG_TYPE_FIXED_SIZED | sizeof(RTHCPHYS) | VTG_TYPE_UNSIGNED | VTG_TYPE_PHYS;
+
+ if (MY_STRMATCH("RTR3PTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3;
+ if (MY_STRMATCH("RTR0PTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R0;
+ if (MY_STRMATCH("RTRCPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_RC;
+ if (MY_STRMATCH("RTHCPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_CTX_R0;
+
+ if (MY_STRMATCH("RTR3UINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_UNSIGNED;
+ if (MY_STRMATCH("RTR0UINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R0 | VTG_TYPE_UNSIGNED;
+ if (MY_STRMATCH("RTRCUINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_RC | VTG_TYPE_UNSIGNED;
+ if (MY_STRMATCH("RTHCUINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_CTX_R0 | VTG_TYPE_UNSIGNED;
+
+ if (MY_STRMATCH("RTR3INTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_SIGNED;
+ if (MY_STRMATCH("RTR0INTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R0 | VTG_TYPE_SIGNED;
+ if (MY_STRMATCH("RTRCINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_RC | VTG_TYPE_SIGNED;
+ if (MY_STRMATCH("RTHCINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_CTX_R0 | VTG_TYPE_SIGNED;
+
+ if (MY_STRMATCH("RTUINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_CTX_R0 | VTG_TYPE_CTX_RC | VTG_TYPE_UNSIGNED;
+ if (MY_STRMATCH("RTINTPTR")) return VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R3 | VTG_TYPE_CTX_R0 | VTG_TYPE_CTX_RC | VTG_TYPE_SIGNED;
+
+ if (MY_STRMATCH("RTHCUINTREG")) return VTG_TYPE_HC_ARCH_SIZED | VTG_TYPE_CTX_R3 | VTG_TYPE_CTX_R0 | VTG_TYPE_UNSIGNED;
+ if (MY_STRMATCH("RTR3UINTREG")) return VTG_TYPE_HC_ARCH_SIZED | VTG_TYPE_CTX_R3 | VTG_TYPE_UNSIGNED;
+ if (MY_STRMATCH("RTR0UINTREG")) return VTG_TYPE_HC_ARCH_SIZED | VTG_TYPE_CTX_R3 | VTG_TYPE_UNSIGNED;
+
+ if (MY_STRMATCH("RTGCUINTREG")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCUINTREG) | VTG_TYPE_UNSIGNED | VTG_TYPE_CTX_GST;
+ if (MY_STRMATCH("RTGCPTR")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCPTR) | VTG_TYPE_UNSIGNED | VTG_TYPE_CTX_GST;
+ if (MY_STRMATCH("RTGCINTPTR")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCUINTPTR) | VTG_TYPE_SIGNED | VTG_TYPE_CTX_GST;
+ if (MY_STRMATCH("RTGCPTR32")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCPTR32) | VTG_TYPE_SIGNED | VTG_TYPE_CTX_GST;
+ if (MY_STRMATCH("RTGCPTR64")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCPTR64) | VTG_TYPE_SIGNED | VTG_TYPE_CTX_GST;
+ if (MY_STRMATCH("RTGCPHYS")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCPHYS) | VTG_TYPE_UNSIGNED | VTG_TYPE_PHYS | VTG_TYPE_CTX_GST;
+ if (MY_STRMATCH("RTGCPHYS32")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCPHYS32) | VTG_TYPE_UNSIGNED | VTG_TYPE_PHYS | VTG_TYPE_CTX_GST;
+ if (MY_STRMATCH("RTGCPHYS64")) return VTG_TYPE_FIXED_SIZED | sizeof(RTGCPHYS64) | VTG_TYPE_UNSIGNED | VTG_TYPE_PHYS | VTG_TYPE_CTX_GST;
+
+ /*
+ * The special VBox types.
+ */
+ if (MY_STRMATCH("PVM")) return VTG_TYPE_POINTER;
+ if (MY_STRMATCH("PVMCPU")) return VTG_TYPE_POINTER;
+ if (MY_STRMATCH("PCPUMCTX")) return VTG_TYPE_POINTER;
+
+ /*
+ * Preaching time.
+ */
+ if ( MY_STRMATCH("unsigned long")
+ || MY_STRMATCH("unsigned long long")
+ || MY_STRMATCH("signed long")
+ || MY_STRMATCH("signed long long")
+ || MY_STRMATCH("long")
+ || MY_STRMATCH("long long")
+ || MY_STRMATCH("char")
+ || MY_STRMATCH("signed char")
+ || MY_STRMATCH("unsigned char")
+ || MY_STRMATCH("double")
+ || MY_STRMATCH("long double")
+ || MY_STRMATCH("float")
+ )
+ {
+ RTMsgError("Please do NOT use the type '%s' for probe arguments!", pszType);
+ g_cTypeErrors++;
+ return 0;
+ }
+
+ if ( MY_STRMATCH("unsigned")
+ || MY_STRMATCH("signed")
+ || MY_STRMATCH("signed int")
+ || MY_STRMATCH("unsigned int")
+ || MY_STRMATCH("short")
+ || MY_STRMATCH("signed short")
+ || MY_STRMATCH("unsigned short")
+ )
+ parseWarnAbs(pStrm, offSrc, "Please avoid using the type '%s' for probe arguments!", pszType);
+ if (MY_STRMATCH("unsigned")) return VTG_TYPE_FIXED_SIZED | sizeof(int) | VTG_TYPE_UNSIGNED;
+ if (MY_STRMATCH("unsigned int")) return VTG_TYPE_FIXED_SIZED | sizeof(int) | VTG_TYPE_UNSIGNED;
+ if (MY_STRMATCH("signed")) return VTG_TYPE_FIXED_SIZED | sizeof(int) | VTG_TYPE_SIGNED;
+ if (MY_STRMATCH("signed int")) return VTG_TYPE_FIXED_SIZED | sizeof(int) | VTG_TYPE_SIGNED;
+ if (MY_STRMATCH("short")) return VTG_TYPE_FIXED_SIZED | sizeof(short) | VTG_TYPE_SIGNED;
+ if (MY_STRMATCH("signed short")) return VTG_TYPE_FIXED_SIZED | sizeof(short) | VTG_TYPE_SIGNED;
+ if (MY_STRMATCH("unsigned short")) return VTG_TYPE_FIXED_SIZED | sizeof(short) | VTG_TYPE_UNSIGNED;
+
+ /*
+ * What we haven't caught by now is either unknown to us or wrong.
+ */
+ if (pszType[0] == 'P')
+ {
+ RTMsgError("Type '%s' looks like a pointer typedef, please do NOT use those "
+ "but rather the non-pointer typedef or struct with '*'",
+ pszType);
+ g_cTypeErrors++;
+ return VTG_TYPE_POINTER;
+ }
+
+ RTMsgError("Don't know '%s' - please change or fix VBoxTpG", pszType);
+ g_cTypeErrors++;
+
+#undef MY_STRCMP
+ return 0;
+}
+
+
+/**
+ * Initializes the members of an argument.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE+msg.
+ * @param pProbe The probe.
+ * @param pArg The argument.
+ * @param pStrm The input stream (for errors + warnings).
+ * @param pchType The type.
+ * @param cchType The type length.
+ * @param pchName The name.
+ * @param cchName The name length.
+ */
+static RTEXITCODE parseInitArgument(PVTGPROBE pProbe, PVTGARG pArg, PSCMSTREAM pStrm,
+ char *pchType, size_t cchType, char *pchName, size_t cchName)
+{
+ Assert(!pArg->pszName); Assert(!pArg->pszTracerType); Assert(!pArg->pszCtxType); Assert(!pArg->fType);
+
+ pArg->pszArgPassingFmt = ", %s";
+ pArg->pszName = RTStrDupN(pchName, cchName);
+ pArg->pszTracerType = strtabInsertN(pchType, cchType);
+ if (!pArg->pszTracerType || !pArg->pszName)
+ return parseError(pStrm, 1, "Out of memory");
+ pArg->fType = parseTypeExpression(pArg->pszTracerType, pStrm, pArg->offSrc);
+
+ if ( (pArg->fType & VTG_TYPE_POINTER)
+ && !(g_fTypeContext & VTG_TYPE_CTX_R0) )
+ {
+ pArg->fType &= ~VTG_TYPE_POINTER;
+ if ( !strcmp(pArg->pszTracerType, "struct VM *") || !strcmp(pArg->pszTracerType, "PVM")
+ || !strcmp(pArg->pszTracerType, "struct VMCPU *") || !strcmp(pArg->pszTracerType, "PVMCPU")
+ || !strcmp(pArg->pszTracerType, "struct CPUMCTX *") || !strcmp(pArg->pszTracerType, "PCPUMCTX")
+ )
+ {
+ pArg->fType |= VTG_TYPE_CTX_POINTER | VTG_TYPE_CTX_R0
+ | VTG_TYPE_FIXED_SIZED | (g_cHostBits / 8)
+ | VTG_TYPE_AUTO_CONV_PTR;
+ pArg->pszCtxType = RTStrDup("RTR0PTR");
+
+ if (!strcmp(pArg->pszTracerType, "struct VM *") || !strcmp(pArg->pszTracerType, "PVM"))
+ pArg->pszArgPassingFmt = ", VTG_VM_TO_R0(%s)";
+ else if (!strcmp(pArg->pszTracerType, "struct VMCPU *") || !strcmp(pArg->pszTracerType, "PVMCPU"))
+ pArg->pszArgPassingFmt = ", VTG_VMCPU_TO_R0(%s)";
+ else
+ {
+ PVTGARG pFirstArg = RTListGetFirst(&pProbe->ArgHead, VTGARG, ListEntry);
+ if ( !pFirstArg
+ || pFirstArg == pArg
+ || strcmp(pFirstArg->pszName, "a_pVCpu")
+ || ( strcmp(pFirstArg->pszTracerType, "struct VMCPU *")
+ && strcmp(pFirstArg->pszTracerType, "PVMCPU *")) )
+ return parseError(pStrm, 1, "The automatic ring-0 pointer conversion requires 'a_pVCpu' with type 'struct VMCPU *' as the first argument");
+
+ if (!strcmp(pArg->pszTracerType, "struct CPUMCTX *")|| !strcmp(pArg->pszTracerType, "PCPUMCTX"))
+ pArg->pszArgPassingFmt = ", VTG_CPUMCTX_TO_R0(a_pVCpu, %s)";
+ else
+ pArg->pszArgPassingFmt = ", VBoxTpG-Is-Buggy!!";
+ }
+ }
+ else
+ {
+ pArg->fType |= VTG_TYPE_CTX_POINTER | g_fTypeContext | VTG_TYPE_FIXED_SIZED | (g_cBits / 8);
+ pArg->pszCtxType = RTStrDupN(pchType, cchType);
+ }
+ }
+ else
+ pArg->pszCtxType = RTStrDupN(pchType, cchType);
+ if (!pArg->pszCtxType)
+ return parseError(pStrm, 1, "Out of memory");
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Unmangles the probe name.
+ *
+ * This involves translating double underscore to dash.
+ *
+ * @returns Pointer to the unmangled name in the string table.
+ * @param pszMangled The mangled name.
+ */
+static const char *parseUnmangleProbeName(const char *pszMangled)
+{
+ size_t cchMangled = strlen(pszMangled);
+ char *pszTmp = (char *)alloca(cchMangled + 2);
+ const char *pszSrc = pszMangled;
+ char *pszDst = pszTmp;
+
+ while (*pszSrc)
+ {
+ if (pszSrc[0] == '_' && pszSrc[1] == '_' && pszSrc[2] != '_')
+ {
+ *pszDst++ = '-';
+ pszSrc += 2;
+ }
+ else
+ *pszDst++ = *pszSrc++;
+ }
+ *pszDst = '\0';
+
+ return strtabInsertN(pszTmp, pszDst - pszTmp);
+}
+
+
+/**
+ * Parses a D probe statement.
+ *
+ * @returns Suitable exit code, errors message already written on failure.
+ * @param pStrm The stream.
+ * @param pProv The provider being parsed.
+ */
+static RTEXITCODE parseProbe(PSCMSTREAM pStrm, PVTGPROVIDER pProv)
+{
+ size_t const iProbeLine = ScmStreamTellLine(pStrm);
+
+ /*
+ * Next up is a name followed by an opening parenthesis.
+ */
+ size_t cchProbe;
+ const char *pszProbe = parseGetNextCWord(pStrm, &cchProbe);
+ if (!pszProbe)
+ return parseError(pStrm, 1, "Expected a probe name starting with an alphabetical character");
+ unsigned ch = parseGetNextNonSpaceNonCommentCh(pStrm);
+ if (ch != '(')
+ return parseError(pStrm, 1, "Expected '(' after the probe name");
+
+ /*
+ * Create a probe instance.
+ */
+ PVTGPROBE pProbe = (PVTGPROBE)RTMemAllocZ(sizeof(*pProbe));
+ if (!pProbe)
+ return parseError(pStrm, 0, "Out of memory");
+ RTListInit(&pProbe->ArgHead);
+ RTListAppend(&pProv->ProbeHead, &pProbe->ListEntry);
+ pProbe->offArgList = UINT32_MAX;
+ pProbe->iLine = iProbeLine;
+ pProbe->pszMangledName = RTStrDupN(pszProbe, cchProbe);
+ if (!pProbe->pszMangledName)
+ return parseError(pStrm, 0, "Out of memory");
+ pProbe->pszUnmangledName = parseUnmangleProbeName(pProbe->pszMangledName);
+ if (!pProbe->pszUnmangledName)
+ return parseError(pStrm, 0, "Out of memory");
+
+ /*
+ * Parse loop for the argument.
+ */
+ PVTGARG pArg = NULL;
+ size_t cchName = 0;
+ size_t cchArg = 0;
+ char szArg[4096];
+ for (;;)
+ {
+ ch = parseGetNextNonSpaceNonCommentCh(pStrm);
+ switch (ch)
+ {
+ case ')':
+ case ',':
+ {
+ /* commit the argument */
+ if (pArg)
+ {
+ if (!cchName)
+ return parseError(pStrm, 1, "Argument has no name");
+ if (cchArg - cchName - 1 >= 128)
+ return parseError(pStrm, 1, "Argument type too long");
+ RTEXITCODE rcExit = parseInitArgument(pProbe, pArg, pStrm,
+ szArg, cchArg - cchName - 1,
+ &szArg[cchArg - cchName], cchName);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ if (VTG_TYPE_IS_LARGE(pArg->fType))
+ pProbe->fHaveLargeArgs = true;
+ pArg = NULL;
+ cchName = cchArg = 0;
+ }
+ if (ch == ')')
+ {
+ size_t off = ScmStreamTell(pStrm);
+ ch = parseGetNextNonSpaceNonCommentCh(pStrm);
+ if (ch != ';')
+ return parseErrorAbs(pStrm, off, "Expected ';'");
+ return RTEXITCODE_SUCCESS;
+ }
+ break;
+ }
+
+ default:
+ {
+ size_t cchWord;
+ const char *pszWord = ScmStreamCGetWordM1(pStrm, &cchWord);
+ if (!pszWord)
+ return parseError(pStrm, 0, "Expected argument");
+ if (!pArg)
+ {
+ pArg = (PVTGARG)RTMemAllocZ(sizeof(*pArg));
+ if (!pArg)
+ return parseError(pStrm, 1, "Out of memory");
+ RTListAppend(&pProbe->ArgHead, &pArg->ListEntry);
+ pArg->iArgNo = pProbe->cArgs++;
+ pArg->pProbe = pProbe;
+ pArg->offSrc = ScmStreamTell(pStrm) - cchWord;
+
+ if (cchWord + 1 > sizeof(szArg))
+ return parseError(pStrm, 1, "Too long parameter declaration");
+ memcpy(szArg, pszWord, cchWord);
+ szArg[cchWord] = '\0';
+ cchArg = cchWord;
+ cchName = 0;
+ }
+ else
+ {
+ if (cchArg + 1 + cchWord + 1 > sizeof(szArg))
+ return parseError(pStrm, 1, "Too long parameter declaration");
+
+ szArg[cchArg++] = ' ';
+ memcpy(&szArg[cchArg], pszWord, cchWord);
+ cchArg += cchWord;
+ szArg[cchArg] = '\0';
+ cchName = cchWord;
+ }
+ break;
+ }
+
+ case '*':
+ {
+ if (!pArg)
+ return parseError(pStrm, 1, "A parameter type does not start with an asterix");
+ if (cchArg + sizeof(" *") >= sizeof(szArg))
+ return parseError(pStrm, 1, "Too long parameter declaration");
+ szArg[cchArg++] = ' ';
+ szArg[cchArg++] = '*';
+ szArg[cchArg ] = '\0';
+ cchName = 0;
+ break;
+ }
+
+ case ~(unsigned)0:
+ return parseError(pStrm, 0, "Missing closing ')' on probe");
+ }
+ }
+}
+
+/**
+ * Parses a D provider statement.
+ *
+ * @returns Suitable exit code, errors message already written on failure.
+ * @param pStrm The stream.
+ */
+static RTEXITCODE parseProvider(PSCMSTREAM pStrm)
+{
+ /*
+ * Next up is a name followed by a curly bracket. Ignore comments.
+ */
+ RTEXITCODE rcExit = parseSkipSpacesAndComments(pStrm);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return parseError(pStrm, 1, "Expected a provider name starting with an alphabetical character");
+ size_t cchName;
+ const char *pszName = ScmStreamCGetWord(pStrm, &cchName);
+ if (!pszName)
+ return parseError(pStrm, 0, "Bad provider name");
+ if (RT_C_IS_DIGIT(pszName[cchName - 1]))
+ return parseError(pStrm, 1, "A provider name cannot end with digit");
+
+ unsigned ch = parseGetNextNonSpaceNonCommentCh(pStrm);
+ if (ch != '{')
+ return parseError(pStrm, 1, "Expected '{' after the provider name");
+
+ /*
+ * Create a provider instance.
+ */
+ PVTGPROVIDER pProv = (PVTGPROVIDER)RTMemAllocZ(sizeof(*pProv));
+ if (!pProv)
+ return parseError(pStrm, 0, "Out of memory");
+ RTListInit(&pProv->ProbeHead);
+ RTListAppend(&g_ProviderHead, &pProv->ListEntry);
+ pProv->pszName = strtabInsertN(pszName, cchName);
+ if (!pProv->pszName)
+ return parseError(pStrm, 0, "Out of memory");
+
+ /*
+ * Parse loop.
+ */
+ for (;;)
+ {
+ ch = parseGetNextNonSpaceNonCommentCh(pStrm);
+ switch (ch)
+ {
+ case 'p':
+ if (ScmStreamCMatchingWordM1(pStrm, RT_STR_TUPLE("probe")))
+ rcExit = parseProbe(pStrm, pProv);
+ else
+ rcExit = parseError(pStrm, 1, "Unexpected character");
+ break;
+
+ case '}':
+ {
+ size_t off = ScmStreamTell(pStrm);
+ ch = parseGetNextNonSpaceNonCommentCh(pStrm);
+ if (ch == ';')
+ return RTEXITCODE_SUCCESS;
+ rcExit = parseErrorAbs(pStrm, off, "Expected ';'");
+ break;
+ }
+
+ case ~(unsigned)0:
+ rcExit = parseError(pStrm, 0, "Missing closing '}' on provider");
+ break;
+
+ default:
+ rcExit = parseError(pStrm, 1, "Unexpected character");
+ break;
+ }
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ }
+}
+
+
+static RTEXITCODE parseScript(const char *pszScript)
+{
+ SCMSTREAM Strm;
+ int rc = ScmStreamInitForReading(&Strm, pszScript);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open & read '%s' into memory: %Rrc", pszScript, rc);
+ if (g_cVerbosity > 0)
+ RTMsgInfo("Parsing '%s'...", pszScript);
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ unsigned ch;
+ while ((ch = ScmStreamGetCh(&Strm)) != ~(unsigned)0)
+ {
+ if (RT_C_IS_SPACE(ch))
+ continue;
+ switch (ch)
+ {
+ case '/':
+ ch = ScmStreamGetCh(&Strm);
+ if (ch == '*')
+ rcExit = parseMultiLineComment(&Strm);
+ else if (ch == '/')
+ rcExit = parseOneLineComment(&Strm);
+ else
+ rcExit = parseError(&Strm, 2, "Unexpected character");
+ break;
+
+ case 'p':
+ if (ScmStreamCMatchingWordM1(&Strm, RT_STR_TUPLE("provider")))
+ rcExit = parseProvider(&Strm);
+ else
+ rcExit = parseError(&Strm, 1, "Unexpected character");
+ break;
+
+ case '#':
+ {
+ ch = parseGetNextNonSpaceNonCommentChOnPpLine(&Strm);
+ if (ch == ~(unsigned)0)
+ rcExit = RTEXITCODE_FAILURE;
+ else if (ch == 'p' && ScmStreamCMatchingWordM1(&Strm, RT_STR_TUPLE("pragma")))
+ rcExit = parsePragma(&Strm);
+ else
+ rcExit = parseError(&Strm, 1, "Unsupported preprocessor directive");
+ break;
+ }
+
+ default:
+ rcExit = parseError(&Strm, 1, "Unexpected character");
+ break;
+ }
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ }
+
+ ScmStreamDelete(&Strm);
+ if (g_cVerbosity > 0 && rcExit == RTEXITCODE_SUCCESS)
+ RTMsgInfo("Successfully parsed '%s'.", pszScript);
+ return rcExit;
+}
+
+
+/**
+ * Parses the arguments.
+ */
+static RTEXITCODE parseArguments(int argc, char **argv)
+{
+ /*
+ * Set / Adjust defaults.
+ */
+ int rc = RTPathAbs(g_pszAssemblerIncVal, g_szAssemblerIncVal, sizeof(g_szAssemblerIncVal) - 1);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAbs failed: %Rrc", rc);
+ strcat(g_szAssemblerIncVal, "/");
+ g_pszAssemblerIncVal = g_szAssemblerIncVal;
+
+ /*
+ * Option config.
+ */
+ enum
+ {
+ kVBoxTpGOpt_32Bit = 1000,
+ kVBoxTpGOpt_64Bit,
+ kVBoxTpGOpt_GenerateWrapperHeader,
+ kVBoxTpGOpt_Assembler,
+ kVBoxTpGOpt_AssemblerFmtOpt,
+ kVBoxTpGOpt_AssemblerFmtVal,
+ kVBoxTpGOpt_AssemblerOutputOpt,
+ kVBoxTpGOpt_AssemblerOption,
+ kVBoxTpGOpt_Pic,
+ kVBoxTpGOpt_ProbeFnName,
+ kVBoxTpGOpt_ProbeFnImported,
+ kVBoxTpGOpt_ProbeFnNotImported,
+ kVBoxTpGOpt_Host32Bit,
+ kVBoxTpGOpt_Host64Bit,
+ kVBoxTpGOpt_RawModeContext,
+ kVBoxTpGOpt_Ring0Context,
+ kVBoxTpGOpt_Ring0ContextAgnostic,
+ kVBoxTpGOpt_Ring3Context,
+ kVBoxTpGOpt_End
+ };
+
+ static RTGETOPTDEF const s_aOpts[] =
+ {
+ /* dtrace w/ long options */
+ { "-32", kVBoxTpGOpt_32Bit, RTGETOPT_REQ_NOTHING },
+ { "-64", kVBoxTpGOpt_64Bit, RTGETOPT_REQ_NOTHING },
+ { "--apply-cpp", 'C', RTGETOPT_REQ_NOTHING },
+ { "--generate-obj", 'G', RTGETOPT_REQ_NOTHING },
+ { "--generate-header", 'h', RTGETOPT_REQ_NOTHING },
+ { "--output", 'o', RTGETOPT_REQ_STRING },
+ { "--script", 's', RTGETOPT_REQ_STRING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ /* our stuff */
+ { "--generate-wrapper-header", kVBoxTpGOpt_GenerateWrapperHeader, RTGETOPT_REQ_NOTHING },
+ { "--assembler", kVBoxTpGOpt_Assembler, RTGETOPT_REQ_STRING },
+ { "--assembler-fmt-opt", kVBoxTpGOpt_AssemblerFmtOpt, RTGETOPT_REQ_STRING },
+ { "--assembler-fmt-val", kVBoxTpGOpt_AssemblerFmtVal, RTGETOPT_REQ_STRING },
+ { "--assembler-output-opt", kVBoxTpGOpt_AssemblerOutputOpt, RTGETOPT_REQ_STRING },
+ { "--assembler-option", kVBoxTpGOpt_AssemblerOption, RTGETOPT_REQ_STRING },
+ { "--pic", kVBoxTpGOpt_Pic, RTGETOPT_REQ_NOTHING },
+ { "--probe-fn-name", kVBoxTpGOpt_ProbeFnName, RTGETOPT_REQ_STRING },
+ { "--probe-fn-imported", kVBoxTpGOpt_ProbeFnImported, RTGETOPT_REQ_NOTHING },
+ { "--probe-fn-not-imported", kVBoxTpGOpt_ProbeFnNotImported, RTGETOPT_REQ_NOTHING },
+ { "--host-32-bit", kVBoxTpGOpt_Host32Bit, RTGETOPT_REQ_NOTHING },
+ { "--host-64-bit", kVBoxTpGOpt_Host64Bit, RTGETOPT_REQ_NOTHING },
+ { "--raw-mode-context", kVBoxTpGOpt_RawModeContext, RTGETOPT_REQ_NOTHING },
+ { "--ring-0-context", kVBoxTpGOpt_Ring0Context, RTGETOPT_REQ_NOTHING },
+ { "--ring-0-context-agnostic", kVBoxTpGOpt_Ring0ContextAgnostic, RTGETOPT_REQ_NOTHING },
+ { "--ring-3-context", kVBoxTpGOpt_Ring3Context, RTGETOPT_REQ_NOTHING },
+ /** @todo We're missing a bunch of assembler options! */
+ };
+
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetOptState;
+ rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertReleaseRCReturn(rc, RTEXITCODE_FAILURE);
+
+ /*
+ * Process the options.
+ */
+ while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
+ {
+ switch (rc)
+ {
+ /*
+ * DTrace compatible options.
+ */
+ case kVBoxTpGOpt_32Bit:
+ g_cHostBits = g_cBits = 32;
+ g_pszAssemblerFmtVal = g_szAssemblerFmtVal32;
+ break;
+
+ case kVBoxTpGOpt_64Bit:
+ g_cHostBits = g_cBits = 64;
+ g_pszAssemblerFmtVal = g_szAssemblerFmtVal64;
+ break;
+
+ case 'C':
+ g_fApplyCpp = true;
+ RTMsgWarning("Ignoring the -C option - no preprocessing of the D script will be performed");
+ break;
+
+ case 'G':
+ if ( g_enmAction != kVBoxTpGAction_Nothing
+ && g_enmAction != kVBoxTpGAction_GenerateObject)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "-G does not mix with -h or --generate-wrapper-header");
+ g_enmAction = kVBoxTpGAction_GenerateObject;
+ break;
+
+ case 'h':
+ if (!strcmp(GetOptState.pDef->pszLong, "--generate-header"))
+ {
+ if ( g_enmAction != kVBoxTpGAction_Nothing
+ && g_enmAction != kVBoxTpGAction_GenerateHeader)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "-h does not mix with -G or --generate-wrapper-header");
+ g_enmAction = kVBoxTpGAction_GenerateHeader;
+ }
+ else
+ {
+ /* --help or similar */
+ RTPrintf("VirtualBox Tracepoint Generator\n"
+ "\n"
+ "Usage: %s [options]\n"
+ "\n"
+ "Options:\n", RTProcShortName());
+ for (size_t i = 0; i < RT_ELEMENTS(s_aOpts); i++)
+ if ((unsigned)s_aOpts[i].iShort < 128)
+ RTPrintf(" -%c,%s\n", s_aOpts[i].iShort, s_aOpts[i].pszLong);
+ else
+ RTPrintf(" %s\n", s_aOpts[i].pszLong);
+ return RTEXITCODE_SUCCESS;
+ }
+ break;
+
+ case 'o':
+ if (g_pszOutput)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Output file is already set to '%s'", g_pszOutput);
+ g_pszOutput = ValueUnion.psz;
+ break;
+
+ case 's':
+ if (g_pszScript)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Script file is already set to '%s'", g_pszScript);
+ g_pszScript = ValueUnion.psz;
+ break;
+
+ case 'v':
+ g_cVerbosity++;
+ break;
+
+ case 'V':
+ {
+ /* The following is assuming that svn does it's job here. */
+ static const char s_szRev[] = "$Revision: 155244 $";
+ const char *psz = RTStrStripL(strchr(s_szRev, ' '));
+ RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);
+ return RTEXITCODE_SUCCESS;
+ }
+
+ case VINF_GETOPT_NOT_OPTION:
+ if (g_enmAction == kVBoxTpGAction_GenerateObject)
+ break; /* object files, ignore them. */
+ return RTGetOptPrintError(rc, &ValueUnion);
+
+
+ /*
+ * Our options.
+ */
+ case kVBoxTpGOpt_GenerateWrapperHeader:
+ if ( g_enmAction != kVBoxTpGAction_Nothing
+ && g_enmAction != kVBoxTpGAction_GenerateWrapperHeader)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "--generate-wrapper-header does not mix with -h or -G");
+ g_enmAction = kVBoxTpGAction_GenerateWrapperHeader;
+ break;
+
+ case kVBoxTpGOpt_Assembler:
+ g_pszAssembler = ValueUnion.psz;
+ break;
+
+ case kVBoxTpGOpt_AssemblerFmtOpt:
+ g_pszAssemblerFmtOpt = ValueUnion.psz;
+ break;
+
+ case kVBoxTpGOpt_AssemblerFmtVal:
+ g_pszAssemblerFmtVal = ValueUnion.psz;
+ break;
+
+ case kVBoxTpGOpt_AssemblerOutputOpt:
+ g_pszAssemblerOutputOpt = ValueUnion.psz;
+ break;
+
+ case kVBoxTpGOpt_AssemblerOption:
+ if (g_cAssemblerOptions >= RT_ELEMENTS(g_apszAssemblerOptions))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many assembly options (max %u)", RT_ELEMENTS(g_apszAssemblerOptions));
+ g_apszAssemblerOptions[g_cAssemblerOptions] = ValueUnion.psz;
+ g_cAssemblerOptions++;
+ break;
+
+ case kVBoxTpGOpt_Pic:
+ g_fPic = true;
+ break;
+
+ case kVBoxTpGOpt_ProbeFnName:
+ g_pszProbeFnName = ValueUnion.psz;
+ break;
+
+ case kVBoxTpGOpt_ProbeFnImported:
+ g_fProbeFnImported = true;
+ break;
+
+ case kVBoxTpGOpt_ProbeFnNotImported:
+ g_fProbeFnImported = false;
+ break;
+
+ case kVBoxTpGOpt_Host32Bit:
+ g_cHostBits = 32;
+ break;
+
+ case kVBoxTpGOpt_Host64Bit:
+ g_cHostBits = 64;
+ break;
+
+ case kVBoxTpGOpt_RawModeContext:
+ g_fTypeContext = VTG_TYPE_CTX_RC;
+ g_pszContextDefine = "IN_RC";
+ g_pszContextDefine2 = NULL;
+ break;
+
+ case kVBoxTpGOpt_Ring0Context:
+ g_fTypeContext = VTG_TYPE_CTX_R0;
+ g_pszContextDefine = "IN_RING0";
+ g_pszContextDefine2 = NULL;
+ break;
+
+ case kVBoxTpGOpt_Ring0ContextAgnostic:
+ g_fTypeContext = VTG_TYPE_CTX_R0;
+ g_pszContextDefine = "IN_RING0_AGNOSTIC";
+ g_pszContextDefine2 = "IN_RING0";
+ break;
+
+ case kVBoxTpGOpt_Ring3Context:
+ g_fTypeContext = VTG_TYPE_CTX_R3;
+ g_pszContextDefine = "IN_RING3";
+ g_pszContextDefine2 = NULL;
+ break;
+
+
+ /*
+ * Errors and bugs.
+ */
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ /*
+ * Check that we've got all we need.
+ */
+ if (g_enmAction == kVBoxTpGAction_Nothing)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No action specified (-h, -G or --generate-wrapper-header)");
+ if (!g_pszScript)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No script file specified (-s)");
+ if (!g_pszOutput)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No output file specified (-o)");
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return 1;
+
+ RTEXITCODE rcExit = parseArguments(argc, argv);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Parse the script.
+ */
+ RTListInit(&g_ProviderHead);
+ rcExit = parseScript(g_pszScript);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Take action.
+ */
+ if (g_enmAction == kVBoxTpGAction_GenerateHeader)
+ rcExit = generateFile(g_pszOutput, "header", generateHeader);
+ else if (g_enmAction == kVBoxTpGAction_GenerateWrapperHeader)
+ rcExit = generateFile(g_pszOutput, "wrapper header", generateWrapperHeader);
+ else
+ rcExit = generateObject(g_pszOutput, g_pszTempAsm);
+ }
+ }
+
+ if (rcExit == RTEXITCODE_SUCCESS && g_cTypeErrors > 0)
+ rcExit = RTEXITCODE_FAILURE;
+ return rcExit;
+}
+
diff --git a/src/bldprogs/bin2c.c b/src/bldprogs/bin2c.c
new file mode 100644
index 00000000..5e7c2f19
--- /dev/null
+++ b/src/bldprogs/bin2c.c
@@ -0,0 +1,274 @@
+/* $Id: bin2c.c $ */
+/** @file
+ * bin2c - Binary 2 C Structure Converter.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+
+/**
+ * File size.
+ *
+ * @returns file size in bytes.
+ * @returns 0 on failure.
+ * @param pFile File to size.
+ */
+static size_t fsize(FILE *pFile)
+{
+ long cbFile;
+ off_t Pos = ftell(pFile);
+ if ( Pos >= 0
+ && !fseek(pFile, 0, SEEK_END))
+ {
+ cbFile = ftell(pFile);
+ if ( cbFile >= 0
+ && !fseek(pFile, 0, SEEK_SET))
+ return cbFile;
+ }
+ return 0;
+}
+
+static int usage(const char *argv0)
+{
+ fprintf(stderr,
+ "Syntax: %s [options] <arrayname> <binaryfile> <outname>\n"
+ " --min <n> check if <binaryfile> is not smaller than <n>KB\n"
+ " --max <n> check if <binaryfile> is not bigger than <n>KB\n"
+ " --mask <n> check if size of binaryfile is <n>-aligned\n"
+ " --width <n> number of bytes per line (default: 16)\n"
+ " --break <n> break every <n> lines (default: -1)\n"
+ , argv0);
+ fprintf(stderr,
+ " --ascii show ASCII representation of binary as comment\n"
+ " --export emit DECLEXPORT\n"
+ " --append append to the output file (default: truncate)\n"
+ " --no-size Skip the size.\n"
+ " --static Static data scope.\n");
+
+ return 1;
+}
+
+int main(int argc, char *argv[])
+{
+ FILE *pFileIn;
+ FILE *pFileOut;
+ int iArg;
+ size_t cbMin = 0;
+ size_t cbMax = ~0U;
+ size_t uMask = 0;
+ int fAscii = 0;
+ int fAppend = 0;
+ int fExport = 0;
+ int fNoSize = 0;
+ int fStatic = 0;
+ long iBreakEvery = -1;
+ unsigned char abLine[32];
+ size_t cbLine = 16;
+ size_t off;
+ size_t cbRead;
+ size_t cbBin;
+ int rc = 1; /* assume the worst... */
+
+ if (argc < 2)
+ return usage(argv[0]);
+
+ for (iArg = 1; iArg < argc; iArg++)
+ {
+ if (!strcmp(argv[iArg], "--min") || !strcmp(argv[iArg], "-min"))
+ {
+ if (++iArg >= argc)
+ return usage(argv[0]);
+ cbMin = 1024 * strtoul(argv[iArg], NULL, 0);
+ }
+ else if (!strcmp(argv[iArg], "--max") || !strcmp(argv[iArg], "-max"))
+ {
+ if (++iArg >= argc)
+ return usage(argv[0]);
+ cbMax = 1024 * strtoul(argv[iArg], NULL, 0);
+ }
+ else if (!strcmp(argv[iArg], "--mask") || !strcmp(argv[iArg], "-mask"))
+ {
+ if (++iArg >= argc)
+ return usage(argv[0]);
+ uMask = strtoul(argv[iArg], NULL, 0);
+ }
+ else if (!strcmp(argv[iArg], "--ascii") || !strcmp(argv[iArg], "-ascii"))
+ fAscii = 1;
+ else if (!strcmp(argv[iArg], "--append"))
+ fAppend = 1;
+ else if (!strcmp(argv[iArg], "--export") || !strcmp(argv[iArg], "-export"))
+ fExport = 1;
+ else if (!strcmp(argv[iArg], "--no-size"))
+ fNoSize = 1;
+ else if (!strcmp(argv[iArg], "--static"))
+ fStatic = 1;
+ else if (!strcmp(argv[iArg], "--width") || !strcmp(argv[iArg], "-width"))
+ {
+ if (++iArg >= argc)
+ return usage(argv[0]);
+ cbLine = strtoul(argv[iArg], NULL, 0);
+ if (cbLine == 0 || cbLine > sizeof(abLine))
+ {
+ fprintf(stderr, "%s: '%s' is too wide, max %u\n",
+ argv[0], argv[iArg], (unsigned)sizeof(abLine));
+ return 1;
+ }
+ }
+ else if (!strcmp(argv[iArg], "--break") || !strcmp(argv[iArg], "-break"))
+ {
+ if (++iArg >= argc)
+ return usage(argv[0]);
+ iBreakEvery = strtol(argv[iArg], NULL, 0);
+ if (iBreakEvery <= 0 && iBreakEvery != -1)
+ {
+ fprintf(stderr, "%s: -break value '%s' is not >= 1 or -1.\n",
+ argv[0], argv[iArg]);
+ return 1;
+ }
+ }
+ else if (iArg == argc - 3)
+ break;
+ else
+ {
+ fprintf(stderr, "%s: syntax error: Unknown argument '%s'\n",
+ argv[0], argv[iArg]);
+ return usage(argv[0]);
+ }
+ }
+
+ pFileIn = fopen(argv[iArg+1], "rb");
+ if (!pFileIn)
+ {
+ fprintf(stderr, "Error: failed to open input file '%s'!\n", argv[iArg+1]);
+ return 1;
+ }
+
+ pFileOut = fopen(argv[iArg+2], fAppend ? "a" : "w"); /* no b! */
+ if (!pFileOut)
+ {
+ fprintf(stderr, "Error: failed to open output file '%s'!\n", argv[iArg+2]);
+ fclose(pFileIn);
+ return 1;
+ }
+
+ cbBin = fsize(pFileIn);
+
+ fprintf(pFileOut,
+ "/*\n"
+ " * This file was automatically generated\n"
+ " * from %s\n"
+ " * by %s.\n"
+ " */\n"
+ "\n"
+ "#include <iprt/cdefs.h>\n"
+ "\n"
+ "%sconst unsigned char%s g_ab%s[] =\n"
+ "{\n",
+ argv[iArg+1], argv[0], fStatic ? "static " : fExport ? "DECLEXPORT(" : "", !fStatic && fExport ? ")" : "", argv[iArg]);
+
+ /* check size restrictions */
+ if (uMask && (cbBin & uMask))
+ fprintf(stderr, "%s: size=%ld - Not aligned!\n", argv[0], (long)cbBin);
+ else if (cbBin < cbMin || cbBin > cbMax)
+ fprintf(stderr, "%s: size=%ld - Not %ld-%ldb in size!\n",
+ argv[0], (long)cbBin, (long)cbMin, (long)cbMax);
+ else
+ {
+ /* the binary data */
+ off = 0;
+ while ((cbRead = fread(&abLine[0], 1, cbLine, pFileIn)) > 0)
+ {
+ size_t j;
+
+ if ( iBreakEvery > 0
+ && off
+ && (off / cbLine) % iBreakEvery == 0)
+ fprintf(pFileOut, "\n");
+
+ fprintf(pFileOut, " ");
+ for (j = 0; j < cbRead; j++)
+ fprintf(pFileOut, " 0x%02x,", abLine[j]);
+ for (; j < cbLine; j++)
+ fprintf(pFileOut, " ");
+ if (fAscii)
+ {
+ fprintf(pFileOut, " /* 0x%08lx: ", (long)off);
+ for (j = 0; j < cbRead; j++)
+ /* be careful with '/' prefixed/followed by a '*'! */
+ fprintf(pFileOut, "%c",
+ isprint(abLine[j]) && abLine[j] != '/' ? abLine[j] : '.');
+ for (; j < cbLine; j++)
+ fprintf(pFileOut, " ");
+ fprintf(pFileOut, " */");
+ }
+ fprintf(pFileOut, "\n");
+
+ off += cbRead;
+ }
+
+ /* check for errors */
+ if (ferror(pFileIn) && !feof(pFileIn))
+ fprintf(stderr, "%s: read error\n", argv[0]);
+ else if (off != cbBin)
+ fprintf(stderr, "%s: read error off=%ld cbBin=%ld\n", argv[0], (long)off, (long)cbBin);
+ else
+ {
+ /* no errors, finish the structure. */
+ fprintf(pFileOut,
+ "};\n");
+
+ if (!fNoSize)
+ fprintf(pFileOut,
+ "\n"
+ "%sconst unsigned%s g_cb%s = sizeof(g_ab%s);\n",
+ fExport ? "DECLEXPORT(" : "", fExport ? ")" : "", argv[iArg], argv[iArg]);
+
+ fprintf(pFileOut, "/* end of file */\n");
+
+ /* flush output and check for error. */
+ fflush(pFileOut);
+ if (ferror(pFileOut))
+ fprintf(stderr, "%s: write error\n", argv[0]);
+ else
+ rc = 0; /* success! */
+ }
+ }
+
+ /* cleanup, delete the output file on failure. */
+ fclose(pFileOut);
+ fclose(pFileIn);
+ if (rc)
+ remove(argv[iArg+2]);
+
+ return rc;
+}
diff --git a/src/bldprogs/biossums.c b/src/bldprogs/biossums.c
new file mode 100644
index 00000000..37dbc8fb
--- /dev/null
+++ b/src/bldprogs/biossums.c
@@ -0,0 +1,255 @@
+/* $Id: biossums.c $ */
+/** @file
+ * Tool for modifying a BIOS image to write the BIOS checksum.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <errno.h>
+#ifndef RT_OS_WINDOWS
+# include <unistd.h> /* unlink */
+#endif
+
+typedef unsigned char uint8_t;
+
+static uint8_t abBios[64*1024];
+static FILE *g_pIn = NULL;
+static FILE *g_pOut = NULL;
+static const char *g_pszOutFile = NULL;
+static const char *g_argv0;
+
+/**
+ * Find where the filename starts in the given path.
+ */
+static const char *name(const char *pszPath)
+{
+ const char *psz = strrchr(pszPath, '/');
+#if defined(_MSC_VER) || defined(__OS2__)
+ const char *psz2 = strrchr(pszPath, '\\');
+ if (!psz2)
+ psz2 = strrchr(pszPath, ':');
+ if (psz2 && (!psz || psz2 > psz))
+ psz = psz2;
+#endif
+ return psz ? psz + 1 : pszPath;
+}
+
+/**
+ * Report an error.
+ */
+static int fatal(const char *pszFormat, ...)
+{
+ va_list va;
+
+ fprintf(stderr, "%s: ", name(g_argv0));
+
+ va_start(va, pszFormat);
+ vfprintf(stderr, pszFormat, va);
+ va_end(va);
+
+ /* clean up */
+ if (g_pIn)
+ fclose(g_pIn);
+ if (g_pOut)
+ fclose(g_pOut);
+ if (g_pszOutFile)
+ unlink(g_pszOutFile);
+
+ return 1;
+}
+
+/**
+ * Calculate the checksum.
+ */
+static uint8_t calculateChecksum(uint8_t *pb, size_t cb, size_t iChecksum)
+{
+ uint8_t u8Sum = 0;
+ size_t i;
+
+ for (i = 0; i < cb; i++)
+ if (i != iChecksum)
+ u8Sum += pb[i];
+
+ return -u8Sum;
+}
+
+/**
+ * Find a header in the binary.
+ *
+ * @param pb Where to search for the signature
+ * @param cb Size of the search area
+ * @param pbHeader Pointer to the start of the signature
+ * @returns 0 if signature was not found, 1 if found or
+ * 2 if more than one signature was found */
+static int searchHeader(uint8_t *pb, size_t cb, const char *pszHeader, uint8_t **pbHeader)
+{
+ int fFound = 0;
+ unsigned int i;
+ size_t cbSignature = strlen(pszHeader);
+
+ for (i = 0; i < cb; i += 16)
+ if (!memcmp(pb + i, pszHeader, cbSignature))
+ {
+ if (fFound++)
+ return 2;
+ *pbHeader = pb + i;
+ }
+
+ return fFound;
+}
+
+int main(int argc, char **argv)
+{
+ FILE *pIn, *pOut;
+ size_t cbIn, cbOut;
+ int fAdapterBios = 0;
+
+ g_argv0 = argv[0];
+
+ if (argc != 3)
+ return fatal("Input file name and output file name required.\n");
+
+ pIn = g_pIn = fopen(argv[1], "rb");
+ if (!pIn)
+ return fatal("Error opening '%s' for reading (%s).\n", argv[1], strerror(errno));
+
+ pOut = g_pOut = fopen(argv[2], "wb");
+ if (!pOut)
+ return fatal("Error opening '%s' for writing (%s).\n", argv[2], strerror(errno));
+ g_pszOutFile = argv[2];
+
+ /* safety precaution (aka. complete paranoia :-) */
+ memset(abBios, 0, sizeof(abBios));
+
+ cbIn = fread(abBios, 1, sizeof(abBios), pIn);
+ if (ferror(pIn))
+ return fatal("Error reading from '%s' (%s).\n", argv[1], strerror(errno));
+ g_pIn = NULL;
+ fclose(pIn);
+
+ fAdapterBios = abBios[0] == 0x55 && abBios[1] == 0xaa;
+
+ /* align size to page size */
+ if ((cbIn % 4096) != 0)
+ cbIn = (cbIn + 4095) & ~4095;
+
+ if (!fAdapterBios && cbIn != 64*1024)
+ return fatal("Size of system BIOS is not 64KB!\n");
+
+ if (fAdapterBios)
+ {
+ /* adapter BIOS */
+
+ /* set the length indicator */
+ abBios[2] = (uint8_t)(cbIn / 512);
+ }
+ else
+ {
+ /* system BIOS */
+ size_t cbChecksum;
+ uint8_t u8Checksum;
+ uint8_t *pbHeader;
+
+ /* Set the BIOS32 header checksum. */
+ switch (searchHeader(abBios, cbIn, "_32_", &pbHeader))
+ {
+ case 0:
+ return fatal("No BIOS32 header not found!\n");
+ case 2:
+ return fatal("More than one BIOS32 header found!\n");
+ case 1:
+ cbChecksum = (size_t)pbHeader[9] * 16;
+ u8Checksum = calculateChecksum(pbHeader, cbChecksum, 10);
+ pbHeader[10] = u8Checksum;
+ break;
+ }
+
+ /* Set the PIR header checksum according to PCI IRQ Routing table
+ * specification version 1.0, Microsoft Corporation, 1996 */
+ switch (searchHeader(abBios, cbIn, "$PIR", &pbHeader))
+ {
+ case 0:
+ return fatal("No PCI IRQ routing table found!\n");
+ case 2:
+ return fatal("More than one PCI IRQ routing table found!\n");
+ case 1:
+ cbChecksum = (size_t)pbHeader[6] + (size_t)pbHeader[7] * 256;
+ u8Checksum = calculateChecksum(pbHeader, cbChecksum, 31);
+ pbHeader[31] = u8Checksum;
+ break;
+ }
+
+ /* Set the SMBIOS header checksum according to System Management BIOS
+ * Reference Specification Version 2.5, DSP0134. */
+ switch (searchHeader(abBios, cbIn, "_SM_", &pbHeader))
+ {
+ case 0:
+ return fatal("No SMBIOS header found!\n");
+ case 2:
+ return fatal("More than one SMBIOS header found!\n");
+ case 1:
+ /* at first fix the DMI header starting at SMBIOS header offset 16 */
+ u8Checksum = calculateChecksum(pbHeader+16, 15, 5);
+ pbHeader[21] = u8Checksum;
+
+ /* now fix the checksum of the whole SMBIOS header */
+ cbChecksum = (size_t)pbHeader[5];
+ u8Checksum = calculateChecksum(pbHeader, cbChecksum, 4);
+ pbHeader[4] = u8Checksum;
+ break;
+ }
+
+ /* If there is a VPD table, adjust its checksum. */
+ switch (searchHeader(abBios, cbIn, "\xAA\x55VPD", &pbHeader))
+ {
+ case 0:
+ break; /* VPD is optional */
+ case 2:
+ return fatal("More than one VPD header found!\n");
+ case 1:
+ cbChecksum = (size_t)pbHeader[5];
+ if (cbChecksum < 0x30)
+ return fatal("VPD size too small!\n");
+ u8Checksum = calculateChecksum(pbHeader, cbChecksum, cbChecksum - 1);
+ pbHeader[cbChecksum - 1] = u8Checksum;
+ break;
+ }
+ }
+
+ /* set the BIOS checksum */
+ abBios[cbIn-1] = calculateChecksum(abBios, cbIn, cbIn - 1);
+
+ cbOut = fwrite(abBios, 1, cbIn, pOut);
+ if (ferror(pOut))
+ return fatal("Error writing to '%s' (%s).\n", g_pszOutFile, strerror(errno));
+ g_pOut = NULL;
+ if (fclose(pOut))
+ return fatal("Error closing '%s' (%s).\n", g_pszOutFile, strerror(errno));
+
+ return 0;
+}
+
diff --git a/src/bldprogs/checkUndefined.sh b/src/bldprogs/checkUndefined.sh
new file mode 100755
index 00000000..cb5165f5
--- /dev/null
+++ b/src/bldprogs/checkUndefined.sh
@@ -0,0 +1,102 @@
+#!/bin/sh
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program 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, in version 3 of the
+# License.
+#
+# This program 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 <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+#
+# Compare undefined symbols in a shared or static object against a new-line
+# separated list of grep patterns in a set of text files and complain if
+# symbols are found which aren't in the files.
+#
+# Usage: /bin/sh <script name> <object> [--static] <undefined symbol file...>
+#
+# Currently only works for native objects on Linux (and Solaris?) platforms.
+#
+
+echoerr()
+{
+ echo $* 1>&2
+}
+
+hostos="${1}"
+target="${2}"
+shift 2
+if test "${1}" = "--static"; then
+ static="${1}"
+ shift
+fi
+
+if test $# -lt 1; then
+ echoerr "${0}: Wrong number of arguments"
+ args_ok="no"
+fi
+if test ! -r "${target}"; then
+ echoerr "${0}: '${target}' not readable"
+ args_ok="no"
+fi
+for i in "${@}"; do
+ if test ! -r "${i}"; then
+ echoerr "${0}: '${i}' not readable"
+ args_ok="no"
+ fi
+done
+
+if test "$args_ok" = "no"; then
+ echoerr "Usage: $0 <object> [--static] <undefined symbol file...>"
+ exit 1
+fi
+
+if test "$hostos" = "solaris"; then
+ objdumpbin=/usr/sfw/bin/gobjdump
+ grepbin=/usr/sfw/bin/ggrep
+elif test "$hostos" = "linux"; then
+ objdumpbin=`which objdump`
+ grepbin=`which grep`
+else
+ echoerr "$0: '$hostos' not a valid hostos string. supported 'linux' 'solaris'"
+ exit 1
+fi
+
+command="-T"
+if test "$static" = "--static"; then
+ command="-t"
+fi
+
+if test ! -x "${objdumpbin}"; then
+ echoerr "${0}: '${objdumpbin}' not found or not executable."
+ exit 1
+fi
+
+undefined=`"${objdumpbin}" ${command} "${target}" | kmk_sed -n 's/.*\*UND\*.*\s\([:graph:]*\)/\1/p'`
+for i in "${@}"; do
+ undefined=`echo "${undefined}" | "${grepbin}" -w -v -f "${i}"`
+done
+num_undef=`echo $undefined | wc -w`
+
+if test $num_undef -ne 0; then
+ echoerr "${0}: following symbols not defined in the files ${@}:"
+ echoerr "${undefined}"
+ exit 1
+fi
+# Return code
+exit 0
+
diff --git a/src/bldprogs/deftoimp.sed b/src/bldprogs/deftoimp.sed
new file mode 100644
index 00000000..c4d0793b
--- /dev/null
+++ b/src/bldprogs/deftoimp.sed
@@ -0,0 +1,66 @@
+# $Id: deftoimp.sed $
+## @file
+# SED script for generating a dummy .c from a windows .def file.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program 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, in version 3 of the
+# License.
+#
+# This program 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 <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+#
+# Remove comments and space. Skip empty lines.
+#
+s/;.*$//g
+s/^[[:space:]][[:space:]]*//g
+s/[[:space:]][[:space:]]*$//g
+/^$/d
+
+# Handle text after EXPORTS
+/EXPORTS/,//{
+s/^EXPORTS$//
+/^$/b end
+
+
+/[[:space:]]DATA$/b data
+
+#
+# Function export
+#
+:code
+s/^\(.*\)$/EXPORT\nvoid \1(void);\nvoid \1(void){}/
+b end
+
+
+#
+# Data export
+#
+:data
+s/^\(.*\)[[:space:]]*DATA$/EXPORT_DATA void *\1 = (void *)0;/
+b end
+
+}
+d
+b end
+
+
+# next expression
+:end
+
diff --git a/src/bldprogs/filesplitter.cpp b/src/bldprogs/filesplitter.cpp
new file mode 100644
index 00000000..90c7dbe7
--- /dev/null
+++ b/src/bldprogs/filesplitter.cpp
@@ -0,0 +1,388 @@
+/* $Id: filesplitter.cpp $ */
+/** @file
+ * File splitter - Splits a text file according to ###### markers in it.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <iprt/string.h>
+#include <iprt/stdarg.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifndef S_ISDIR
+# define S_ISDIR(a_fMode) ( (S_IFMT & (a_fMode)) == S_IFDIR )
+#endif
+
+
+/**
+ * Calculates the line number for a file position.
+ *
+ * @returns Line number.
+ * @param pcszContent The file content.
+ * @param pcszPos The current position.
+ */
+static unsigned long lineNumber(const char *pcszContent, const char *pcszPos)
+{
+ unsigned long cLine = 0;
+ while ( *pcszContent
+ && (uintptr_t)pcszContent < (uintptr_t)pcszPos)
+ {
+ pcszContent = strchr(pcszContent, '\n');
+ if (!pcszContent)
+ break;
+ ++cLine;
+ ++pcszContent;
+ }
+
+ return cLine;
+}
+
+
+/**
+ * Writes an error message.
+ *
+ * @returns RTEXITCODE_FAILURE.
+ * @param pcszFormat Error message.
+ * @param ... Format argument referenced in the message.
+ */
+static int printErr(const char *pcszFormat, ...)
+{
+ va_list va;
+
+ fprintf(stderr, "filesplitter: ");
+ va_start(va, pcszFormat);
+ vfprintf(stderr, pcszFormat, va);
+ va_end(va);
+
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Opens the makefile list for writing.
+ *
+ * @returns Exit code.
+ * @param pcszPath The path to the file.
+ * @param pcszVariableName The make variable name.
+ * @param ppFile Where to return the file stream.
+ */
+static int openMakefileList(const char *pcszPath, const char *pcszVariableName, FILE **ppFile)
+{
+ *ppFile = NULL;
+
+ FILE *pFile= fopen(pcszPath, "w");
+ if (!pFile)
+#ifdef _MSC_VER
+ return printErr("Failed to open \"%s\" for writing the file list: %s (win32: %d)\n",
+ pcszPath, strerror(errno), _doserrno);
+#else
+ return printErr("Failed to open \"%s\" for writing the file list: %s\n", pcszPath, strerror(errno));
+#endif
+
+ if (fprintf(pFile, "%s := \\\n", pcszVariableName) <= 0)
+ {
+ fclose(pFile);
+ return printErr("Error writing to the makefile list.\n");
+ }
+
+ *ppFile = pFile;
+ return 0;
+}
+
+
+/**
+ * Adds the given file to the makefile list.
+ *
+ * @returns Exit code.
+ * @param pFile The file stream of the makefile list.
+ * @param pszFilename The file name to add.
+ */
+static int addFileToMakefileList(FILE *pFile, char *pszFilename)
+{
+ if (pFile)
+ {
+ char *pszSlash = pszFilename;
+ while ((pszSlash = strchr(pszSlash, '\\')) != NULL)
+ *pszSlash++ = '/';
+
+ if (fprintf(pFile, "\t%s \\\n", pszFilename) <= 0)
+ return printErr("Error adding file to makefile list.\n");
+ }
+ return 0;
+}
+
+
+/**
+ * Closes the makefile list.
+ *
+ * @returns Exit code derived from @a rc.
+ * @param pFile The file stream of the makefile list.
+ * @param rc The current exit code.
+ */
+static int closeMakefileList(FILE *pFile, int rc)
+{
+ fprintf(pFile, "\n\n");
+ if (fclose(pFile))
+ return printErr("Error closing the file list file: %s\n", strerror(errno));
+ return rc;
+}
+
+
+/**
+ * Reads in a file.
+ *
+ * @returns Exit code.
+ * @param pcszFile The path to the file.
+ * @param ppszFile Where to return the buffer.
+ * @param pcchFile Where to return the file size.
+ */
+static int readFile(const char *pcszFile, char **ppszFile, size_t *pcchFile)
+{
+ FILE *pFile;
+ struct stat FileStat;
+ int rc;
+
+ if (stat(pcszFile, &FileStat))
+ return printErr("Failed to stat \"%s\": %s\n", pcszFile, strerror(errno));
+
+ pFile = fopen(pcszFile, "r");
+ if (!pFile)
+ return printErr("Failed to open \"%s\": %s\n", pcszFile, strerror(errno));
+
+ *ppszFile = (char *)malloc(FileStat.st_size + 1);
+ if (*ppszFile)
+ {
+ errno = 0;
+ size_t cbRead = fread(*ppszFile, 1, FileStat.st_size, pFile);
+ if ( cbRead <= (size_t)FileStat.st_size
+ && (cbRead > 0 || !ferror(pFile)) )
+ {
+ if (ftell(pFile) == FileStat.st_size) /* (\r\n vs \n in the DOS world) */
+ {
+ (*ppszFile)[cbRead] = '\0';
+ if (pcchFile)
+ *pcchFile = (size_t)cbRead;
+
+ fclose(pFile);
+ return 0;
+ }
+ }
+
+ rc = printErr("Error reading \"%s\": %s\n", pcszFile, strerror(errno));
+ free(*ppszFile);
+ *ppszFile = NULL;
+ }
+ else
+ rc = printErr("Failed to allocate %lu bytes\n", (unsigned long)(FileStat.st_size + 1));
+ fclose(pFile);
+ return rc;
+}
+
+
+/**
+ * Checks whether the sub-file already exists and has the exact
+ * same content.
+ *
+ * @returns @c true if the existing file matches exactly, otherwise @c false.
+ * @param pcszFilename The path to the file.
+ * @param pcszSubContent The content to write.
+ * @param cchSubContent The length of the content.
+ */
+static bool compareSubFile(const char *pcszFilename, const char *pcszSubContent, size_t cchSubContent)
+{
+ struct stat FileStat;
+ if (stat(pcszFilename, &FileStat))
+ return false;
+ if ((size_t)FileStat.st_size < cchSubContent)
+ return false;
+
+ size_t cchExisting;
+ char *pszExisting;
+ int rc = readFile(pcszFilename, &pszExisting, &cchExisting);
+ if (rc)
+ return false;
+
+ bool fRc = cchExisting == cchSubContent
+ && !memcmp(pcszSubContent, pszExisting, cchSubContent);
+ free(pszExisting);
+
+ return fRc;
+}
+
+
+/**
+ * Writes out a sub-file.
+ *
+ * @returns exit code.
+ * @param pcszFilename The path to the sub-file.
+ * @param pcszSubContent The content of the file.
+ * @param cchSubContent The size of the content.
+ */
+static int writeSubFile(const char *pcszFilename, const char *pcszSubContent, size_t cchSubContent)
+{
+ FILE *pFile = fopen(pcszFilename, "w");
+ if (!pFile)
+#ifdef _MSC_VER
+ return printErr("Failed to open \"%s\" for writing: %s (win32: %d)\n", pcszFilename, strerror(errno), _doserrno);
+#else
+ return printErr("Failed to open \"%s\" for writing: %s\n", pcszFilename, strerror(errno));
+#endif
+
+ errno = 0;
+ int rc = 0;
+ if (fwrite(pcszSubContent, cchSubContent, 1, pFile) != 1)
+ rc = printErr("Error writing \"%s\": %s\n", pcszFilename, strerror(errno));
+
+ errno = 0;
+ int rc2 = fclose(pFile);
+ if (rc2 == EOF)
+ rc = printErr("Error closing \"%s\": %s\n", pcszFilename, strerror(errno));
+ return rc;
+}
+
+
+/**
+ * Does the actual file splitting.
+ *
+ * @returns exit code.
+ * @param pcszOutDir Path to the output directory.
+ * @param pcszContent The content to split up.
+ * @param pFileList The file stream of the makefile list. Can be NULL.
+ */
+static int splitFile(const char *pcszOutDir, const char *pcszContent, FILE *pFileList)
+{
+ static char const s_szBeginMarker[] = "\n// ##### BEGINFILE \"";
+ static char const s_szEndMarker[] = "\n// ##### ENDFILE";
+ const size_t cchBeginMarker = sizeof(s_szBeginMarker) - 1;
+ const char *pcszSearch = pcszContent;
+ size_t const cchOutDir = strlen(pcszOutDir);
+ unsigned long cFilesWritten = 0;
+ unsigned long cFilesUnchanged = 0;
+ int rc = 0;
+
+ do
+ {
+ /* find begin marker */
+ const char *pcszBegin = strstr(pcszSearch, s_szBeginMarker);
+ if (!pcszBegin)
+ break;
+
+ /* find line after begin marker */
+ const char *pcszLineAfterBegin = strchr(pcszBegin + cchBeginMarker, '\n');
+ if (!pcszLineAfterBegin)
+ return printErr("No newline after begin-file marker found.\n");
+ ++pcszLineAfterBegin;
+
+ /* find filename end quote in begin marker line */
+ const char *pcszStartFilename = pcszBegin + cchBeginMarker;
+ const char *pcszEndQuote = (const char *)memchr(pcszStartFilename, '\"', pcszLineAfterBegin - pcszStartFilename);
+ if (!pcszEndQuote)
+ return printErr("Can't parse filename after begin-file marker (line %lu).\n",
+ lineNumber(pcszContent, s_szBeginMarker));
+
+ /* find end marker */
+ const char *pcszEnd = strstr(pcszLineAfterBegin, s_szEndMarker);
+ if (!pcszEnd)
+ return printErr("No matching end-line marker for begin-file marker found (line %lu).\n",
+ lineNumber(pcszContent, s_szBeginMarker));
+
+ /* construct output filename */
+ size_t cchFilename = pcszEndQuote - pcszStartFilename;
+ char *pszFilename = (char *)malloc(cchOutDir + 1 + cchFilename + 1);
+ if (!pszFilename)
+ return printErr("Can't allocate memory for filename.\n");
+
+ memcpy(pszFilename, pcszOutDir, cchOutDir);
+ pszFilename[cchOutDir] = '/';
+ memcpy(pszFilename + cchOutDir + 1, pcszStartFilename, cchFilename);
+ pszFilename[cchFilename + 1 + cchOutDir] = '\0';
+
+ /* Write the file only if necessary. */
+ if (compareSubFile(pszFilename, pcszLineAfterBegin, pcszEnd - pcszLineAfterBegin))
+ cFilesUnchanged++;
+ else
+ {
+ rc = writeSubFile(pszFilename, pcszLineAfterBegin, pcszEnd - pcszLineAfterBegin);
+ cFilesWritten++;
+ }
+
+ if (!rc)
+ rc = addFileToMakefileList(pFileList, pszFilename);
+
+ free(pszFilename);
+
+ pcszSearch = pcszEnd;
+ } while (rc == 0 && pcszSearch);
+
+ printf("filesplitter: Out of %lu files: %lu rewritten, %lu unchanged. (%s)\n",
+ cFilesWritten + cFilesUnchanged, cFilesWritten, cFilesUnchanged, pcszOutDir);
+ return rc;
+}
+
+
+int main(int argc, char *argv[])
+{
+ int rc = 0;
+
+ if (argc == 3 || argc == 5)
+ {
+ struct stat DirStat;
+ if ( stat(argv[2], &DirStat) == 0
+ && S_ISDIR(DirStat.st_mode))
+ {
+ char *pszContent;
+ rc = readFile(argv[1], &pszContent, NULL);
+ if (!rc)
+ {
+ FILE *pFileList = NULL;
+ if (argc == 5)
+ rc = openMakefileList(argv[3], argv[4], &pFileList);
+
+ if (argc < 4 || pFileList)
+ rc = splitFile(argv[2], pszContent, pFileList);
+
+ if (pFileList)
+ rc = closeMakefileList(pFileList, rc);
+ free(pszContent);
+ }
+ }
+ else
+ rc = printErr("Given argument \"%s\" is not a valid directory.\n", argv[2]);
+ }
+ else
+ rc = printErr("Syntax error: usage: filesplitter <infile> <outdir> [<list.kmk> <kmkvar>]\n");
+ return rc;
+}
diff --git a/src/bldprogs/genalias.cpp b/src/bldprogs/genalias.cpp
new file mode 100644
index 00000000..073f169f
--- /dev/null
+++ b/src/bldprogs/genalias.cpp
@@ -0,0 +1,500 @@
+/* $Id: genalias.cpp $ */
+/** @file
+ * genalias - generate a number of alias objects.
+ *
+ * @note The code has its origin with kLIBC and was added to VBox by the author.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <stdarg.h>
+#include <stdio.h>
+#include <iprt/stdint.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#if defined(RT_OS_DARWIN) || (defined(RT_ARCH_X86) && (defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)))
+# define GENALIAS_UNDERSCORED 1
+#else
+# define GENALIAS_UNDERSCORED 0
+#endif
+
+
+
+static int Error(const char *pszFormat, ...)
+{
+ va_list va;
+ fprintf(stderr, "genalias: error: ");
+ va_start(va, pszFormat);
+ vfprintf(stderr, pszFormat, va);
+ va_end(va);
+ return 1;
+}
+
+static int SyntaxError(const char *pszFormat, ...)
+{
+ va_list va;
+ fprintf(stderr, "genalias: syntax error: ");
+ va_start(va, pszFormat);
+ vfprintf(stderr, pszFormat, va);
+ va_end(va);
+ return 1;
+}
+
+static int WriteAliasObjectAOUT(FILE *pOutput, const char *pszAlias, const char *pszReal)
+{
+#pragma pack(1)
+ /* ASSUMES 32-bit target. */
+ struct AoutHdr
+ {
+ uint32_t a_info;
+ uint32_t a_text;
+ uint32_t a_data;
+ uint32_t a_bss;
+ uint32_t a_syms;
+ uint32_t a_entry;
+ uint32_t a_trsize;
+ uint32_t a_drsize;
+ } Hdr;
+#define OMAGIC 0407
+ struct AoutSym
+ {
+ uint32_t n_strx;
+ uint8_t n_type;
+ int8_t n_other;
+ uint16_t n_desc;
+ uint32_t n_value;
+ } Sym;
+#define N_EXT 1
+#define N_INDR 10
+#pragma pack()
+ const uint32_t cchAlias = (uint32_t)strlen(pszAlias);
+ const uint32_t cchReal = (uint32_t)strlen(pszReal);
+ uint32_t u32;
+
+ /* write the header. */
+ memset(&Hdr, 0, sizeof(Hdr));
+ Hdr.a_info = OMAGIC;
+ Hdr.a_syms = 2 * sizeof(Sym);
+ if (fwrite(&Hdr, sizeof(Hdr), 1, pOutput) != 1)
+ return -2;
+
+ /* The alias symbol. */
+ Sym.n_strx = 4 + cchReal + 1 + GENALIAS_UNDERSCORED;
+ Sym.n_type = N_INDR | N_EXT;
+ Sym.n_other = 0;
+ Sym.n_desc = 0;
+ Sym.n_value = 0;
+ if (fwrite(&Sym, sizeof(Sym), 1, pOutput) != 1)
+ return -2;
+
+ /* The real symbol. */
+ Sym.n_strx = 4;
+ Sym.n_type = N_EXT;
+ Sym.n_other = 0;
+ Sym.n_desc = 0;
+ Sym.n_value = 0;
+ if (fwrite(&Sym, sizeof(Sym), 1, pOutput) != 1)
+ return -2;
+
+ /* the string table. */
+ u32 = 4 + cchReal + 1 + cchAlias + 1 + GENALIAS_UNDERSCORED * 2;
+ if (fwrite(&u32, 4, 1, pOutput) != 1)
+ return -2;
+#if GENALIAS_UNDERSCORED
+ if (fputc('_', pOutput) == EOF)
+ return -2;
+#endif
+ if (fwrite(pszReal, cchReal + 1, 1, pOutput) != 1)
+ return -2;
+#if GENALIAS_UNDERSCORED
+ if (fputc('_', pOutput) == EOF)
+ return -2;
+#endif
+ if (fwrite(pszAlias, cchAlias + 1, 1, pOutput) != 1)
+ return -2;
+ return 0;
+}
+
+static int WriteAliasObjectCOFF(FILE *pOutput, const char *pszAlias, const char *pszReal, bool fUnderscored)
+{
+#pragma pack(1)
+ struct CoffHdr
+ {
+ uint16_t Machine;
+ uint16_t NumberOfSections;
+ uint32_t TimeDateStamp;
+ uint32_t PointerToSymbolTable;
+ uint32_t NumberOfSymbols;
+ uint16_t SizeOfOptionalHeader;
+ uint16_t Characteristics;
+ } Hdr;
+ struct CoffShdr
+ {
+ char Name[8];
+ uint32_t VirtualSize;
+ uint32_t VirtualAddress;
+ uint32_t SizeOfRawData;
+ uint32_t PointerToRawData;
+ uint32_t PointerToRelocations;
+ uint32_t PointerToLinenumbers;
+ uint16_t NumberOfRelocations;
+ uint16_t NumberOfLinenumbers;
+ uint32_t Characteristics;
+ } Shdr;
+#define IMAGE_SCN_LNK_INFO 0x200
+#define IMAGE_SCN_LNK_REMOVE 0x800
+ struct CoffSym
+ {
+ union
+ {
+ char ShortName[8];
+ struct
+ {
+ uint32_t Zeros;
+ uint32_t Offset;
+ } s;
+ } u;
+ uint32_t Value;
+ uint16_t SectionNumber;
+ uint16_t Type;
+ uint8_t StorageClass;
+ uint8_t NumberOfAuxSymbols;
+ } Sym;
+#define IMAGE_SYM_UNDEFINED 0
+#define IMAGE_SYM_TYPE_NULL 0
+#define IMAGE_SYM_CLASS_EXTERNAL 2
+#define IMAGE_SYM_CLASS_WEAK_EXTERNAL 105
+ struct CoffAuxWeakExt
+ {
+ uint32_t TagIndex;
+ uint32_t Characteristics;
+ uint8_t padding[10];
+ } Aux;
+#define IMAGE_WEAK_EXTERN_SEARCH_ALIAS 3
+ assert(sizeof(Hdr) == 20); assert(sizeof(Sym) == 18); assert(sizeof(Aux) == sizeof(Sym));
+#pragma pack()
+ const uint32_t cchAlias = (uint32_t)strlen(pszAlias);
+ const uint32_t cchReal = (uint32_t)strlen(pszReal);
+ uint32_t u32;
+
+ /* write the header. */
+ Hdr.Machine = 0 /*unknown*/; //0x14c /* i386 */;
+ Hdr.NumberOfSections = 1;
+ Hdr.TimeDateStamp = time(NULL);
+ Hdr.PointerToSymbolTable = sizeof(Hdr) + sizeof(Shdr);
+ Hdr.NumberOfSymbols = 3;
+ Hdr.SizeOfOptionalHeader = 0;
+ Hdr.Characteristics = 0;
+ if (fwrite(&Hdr, sizeof(Hdr), 1, pOutput) != 1)
+ return -2;
+
+ /* The directive section. */
+ if (Hdr.NumberOfSections == 1)
+ {
+ memset(&Shdr, 0, sizeof(Shdr));
+ memcpy(Shdr.Name, ".drectve", 8);
+ Shdr.Characteristics = IMAGE_SCN_LNK_REMOVE | IMAGE_SCN_LNK_INFO;
+ if (fwrite(&Shdr, sizeof(Shdr), 1, pOutput) != 1)
+ return -2;
+ }
+
+ /* The real symbol. */
+ memset(&Sym, 0, sizeof(Sym));
+ Sym.u.s.Offset = 4;
+ Sym.SectionNumber = IMAGE_SYM_UNDEFINED;
+ Sym.Type = IMAGE_SYM_TYPE_NULL;
+ Sym.StorageClass = IMAGE_SYM_CLASS_EXTERNAL;
+ if (fwrite(&Sym, sizeof(Sym), 1, pOutput) != 1)
+ return -2;
+
+ /* The alias symbol. */
+ memset(&Sym, 0, sizeof(Sym));
+ Sym.u.s.Offset = fUnderscored + cchReal + 1 + 4;
+ Sym.SectionNumber = IMAGE_SYM_UNDEFINED;
+ Sym.Type = IMAGE_SYM_TYPE_NULL;
+ Sym.StorageClass = IMAGE_SYM_CLASS_WEAK_EXTERNAL;
+ Sym.NumberOfAuxSymbols = 1;
+ if (fwrite(&Sym, sizeof(Sym), 1, pOutput) != 1)
+ return -2;
+
+ /* aux entry for that. */
+ memset(&Aux, 0, sizeof(Aux));
+ Aux.TagIndex = 0;
+ Aux.Characteristics = IMAGE_WEAK_EXTERN_SEARCH_ALIAS;
+ if (fwrite(&Aux, sizeof(Aux), 1, pOutput) != 1)
+ return -2;
+
+ /* the string table. */
+ u32 = 4 + cchReal + 1 + cchAlias + 1 + fUnderscored * 2;
+ if (fwrite(&u32, 4, 1, pOutput) != 1)
+ return -2;
+ if (fUnderscored)
+ if (fputc('_', pOutput) == EOF)
+ return -2;
+ if (fwrite(pszReal, cchReal + 1, 1, pOutput) != 1)
+ return -2;
+ if (fUnderscored)
+ if (fputc('_', pOutput) == EOF)
+ return -2;
+ if (fwrite(pszAlias, cchAlias + 1, 1, pOutput) != 1)
+ return -2;
+ return 0;
+}
+
+static int WriteAliasObjectTargetCOFF(FILE *pOutput, const char *pszAlias, const char *pszReal)
+{
+ return WriteAliasObjectCOFF(pOutput, pszAlias, pszReal, GENALIAS_UNDERSCORED);
+}
+
+static int WriteAliasObjectX86COFF(FILE *pOutput, const char *pszAlias, const char *pszReal)
+{
+ return WriteAliasObjectCOFF(pOutput, pszAlias, pszReal, true /*fUnderscored*/);
+}
+
+static int WriteAliasObjectAmd64COFF(FILE *pOutput, const char *pszAlias, const char *pszReal)
+{
+ return WriteAliasObjectCOFF(pOutput, pszAlias, pszReal, false /*fUnderscored*/);
+}
+
+
+static int WriteAliasObjectELF(FILE *pOutput, const char *pszAlias, const char *pszReal)
+{
+ RT_NOREF(pOutput, pszAlias, pszReal);
+ fprintf(stderr, "ELF does not support proper aliasing, only option seems to be adding weak symbols with the strong one.\n");
+ return -1;
+}
+
+static int WriteAliasObjectOMF(FILE *pOutput, const char *pszAlias, const char *pszReal)
+{
+ const uint32_t cchAlias = (uint32_t)strlen(pszAlias);
+ const uint32_t cchReal = (uint32_t)strlen(pszReal);
+ //const uint32_t cchName = cchAlias > 250 ? 250 : cchAlias;
+ uint32_t cch;
+
+ if (cchReal >= 250)
+ return Error("Symbol '%s' is too long!\n", pszReal);
+ if (cchAlias >= 250)
+ return Error("Symbol '%s' is too long!\n", pszAlias);
+
+ /* THEADR */
+ fputc(0x80, pOutput);
+ cch = cchAlias + 2;
+ fputc(cch & 0xff, pOutput);
+ fputc(cch >> 8, pOutput);
+ fputc(cchAlias, pOutput);
+ fwrite(pszAlias, cchAlias, 1, pOutput);
+ fputc(0, pOutput); /* CRC */
+
+ /* ALIAS */
+ fputc(0xc6, pOutput);
+ cch = cchAlias + 1 + cchReal + 1 + GENALIAS_UNDERSCORED * 2 + 1;
+ fputc(cch & 0xff, pOutput);
+ fputc(cch >> 8, pOutput);
+ fputc(cchAlias + GENALIAS_UNDERSCORED, pOutput);
+ if (GENALIAS_UNDERSCORED)
+ fputc('_', pOutput);
+ fwrite(pszAlias, cchAlias, 1, pOutput);
+ fputc(cchReal + GENALIAS_UNDERSCORED, pOutput);
+ if (GENALIAS_UNDERSCORED)
+ fputc('_', pOutput);
+ fwrite(pszReal, cchReal, 1, pOutput);
+ fputc(0, pOutput); /* CRC */
+
+ /* MODEND32 */
+ fputc(0x8b, pOutput);
+ fputc(2, pOutput);
+ fputc(0, pOutput);
+ fputc(0, pOutput);
+ if (fputc(0, pOutput) == EOF) /* CRC */
+ return -2;
+ return 0;
+}
+
+static int WriteAliasObjectMACHO(FILE *pOutput, const char *pszAlias, const char *pszReal)
+{
+ RT_NOREF(pOutput, pszAlias, pszReal);
+ fprintf(stderr, "Mach-O support not implemented yet\n");
+ return -1;
+}
+
+static int CreateAlias(char *pszBuf, size_t cchInput, char *pszFileBuf, char *pszFilename,
+ int (*pfnWriter)(FILE *, const char *, const char *))
+{
+ char *pszAlias = pszBuf;
+ char *pszReal;
+ char *pszFile;
+ FILE *pOutput;
+ int rc;
+ RT_NOREF(cchInput);
+
+ /*
+ * Parse input.
+ */
+ pszReal = strchr(pszBuf, '=');
+ if (!pszReal)
+ return Error("Malformed request: '%s'\n", pszBuf);
+ *pszReal++ = '\0';
+
+ pszFile = strchr(pszReal, '=');
+ if (pszFile)
+ {
+ *pszFile++ = '\0';
+ strcpy(pszFilename, pszFile);
+ }
+ else
+ strcat(strcpy(pszFilename, pszAlias), ".o");
+
+ /*
+ * Open the output file.
+ */
+ pOutput = fopen(pszFileBuf, "wb");
+ if (!pOutput)
+ return Error("Failed to open '%s' for writing!\n", pszFileBuf);
+ rc = pfnWriter(pOutput, pszAlias, pszReal);
+ if (rc == -2)
+ rc = Error("Write error writing '%s'!\n", pszFileBuf);
+ fclose(pOutput);
+ return rc;
+}
+
+
+static int Syntax(void)
+{
+ printf("syntax: genalias -f <format> -D <output-dir> alias=real[=file] [alias2=real2[=file2] [..]]\n"
+ " OR\n"
+ " genalias -f <format> -D <output-dir> -r <response-file>\n"
+ "\n"
+ "Format can be: aout, coff or omf\n"
+ "The responsefile is a single argument per line.\n");
+ return 1;
+}
+
+
+int main(int argc, char **argv)
+{
+ static char s_szBuf[4096];
+ static char s_szFile[1024 + sizeof(s_szBuf)];
+ int (*pfnWriter)(FILE *pOutput, const char *pszAlias, const char *pszReal);
+ char *pszFilename;
+ int i;
+ int rc;
+
+ /*
+ * Parse arguments.
+ */
+ if (argc <= 5)
+ return Syntax();
+ if (strcmp(argv[1], "-f"))
+ return SyntaxError("Expected -f as the 1st argument.\n");
+ if (!strcmp(argv[2], "aout"))
+ pfnWriter = WriteAliasObjectAOUT;
+ else if (!strcmp(argv[2], "coff"))
+ pfnWriter = WriteAliasObjectTargetCOFF;
+ else if (!strcmp(argv[2], "coff.x86"))
+ pfnWriter = WriteAliasObjectX86COFF;
+ else if (!strcmp(argv[2], "coff.amd64"))
+ pfnWriter = WriteAliasObjectAmd64COFF;
+ else if (!strcmp(argv[2], "elf"))
+ pfnWriter = WriteAliasObjectELF;
+ else if (!strcmp(argv[2], "omf"))
+ pfnWriter = WriteAliasObjectOMF;
+ else if (!strcmp(argv[2], "macho"))
+ pfnWriter = WriteAliasObjectMACHO;
+ else
+ return SyntaxError("Unknown format '%s'.\n", argv[2]);
+ if (strcmp(argv[3], "-D"))
+ return SyntaxError("Expected -D as the 3rd argument\n");
+ if (!*argv[4])
+ return SyntaxError("The output directory name is empty.\n");
+ size_t cchFile = strlen(argv[4]);
+ if (cchFile > sizeof(s_szFile) - sizeof(s_szBuf))
+ return SyntaxError("The output directory name is too long.\n");
+ memcpy(s_szFile, argv[4], cchFile);
+ s_szFile[cchFile++] = '/';
+ pszFilename = &s_szFile[cchFile];
+
+ /* anything to do? */
+ if (argc == 5)
+ return 0;
+
+ rc = 0;
+ if (!strcmp(argv[5], "-r"))
+ {
+ /*
+ * Responsefile.
+ */
+ FILE *pResp;
+ if (argc <= 6)
+ return SyntaxError("Missing response file name\n");
+ pResp = fopen(argv[6], "rt");
+ if (!pResp)
+ return Error("Failed to open '%s' for reading.\n", argv[6]);
+
+ i = 0;
+ while (fgets(s_szBuf, sizeof(s_szBuf), pResp))
+ {
+ size_t cch = strlen(s_szBuf);
+ i++;
+ if (cch == sizeof(s_szBuf) && s_szBuf[cch - 1] != '\n')
+ {
+ rc = Error("Line %d is too long!\n", i);
+ break;
+ }
+ if (cch && s_szBuf[cch - 1] == '\n')
+ s_szBuf[--cch] = '\0';
+ rc = CreateAlias(s_szBuf, cch, s_szFile, pszFilename, pfnWriter);
+ if (rc)
+ break;
+ }
+
+ fclose(pResp);
+ }
+ else
+ {
+ /*
+ * Alias descriptors.
+ */
+ for (i = 5; i < argc; i++)
+ {
+ size_t cch = strlen(argv[i]);
+ if (cch >= sizeof(s_szBuf))
+ return SyntaxError("Argument %d is too long\n", i);
+ memcpy(s_szBuf, argv[i], cch + 1);
+ rc = CreateAlias(s_szBuf, cch, s_szFile, pszFilename, pfnWriter);
+ if (rc)
+ break;
+ }
+ }
+ return rc;
+}
+
diff --git a/src/bldprogs/preload.cpp b/src/bldprogs/preload.cpp
new file mode 100644
index 00000000..5285047c
--- /dev/null
+++ b/src/bldprogs/preload.cpp
@@ -0,0 +1,222 @@
+/* $Id: preload.cpp $ */
+/** @file
+ * bin2c - Binary 2 C Structure Converter.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifdef RT_OS_WINDOWS
+# include <Windows.h>
+#else
+# include <sys/mman.h>
+# include <sys/stat.h>
+# include <fcntl.h>
+# include <unistd.h>
+# include <errno.h>
+#endif
+#include <stdio.h>
+#include <string.h>
+
+
+static int load(const char *pszImage)
+{
+#ifdef RT_OS_WINDOWS
+ HANDLE hFile = CreateFile(pszImage,
+ GENERIC_READ,
+ FILE_SHARE_READ,
+ NULL /*pSecurityAttributes*/,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL /*hTemplateFile*/);
+ if (hFile == INVALID_HANDLE_VALUE)
+ {
+ printf("error: CreateFile('%s',): %d\n", pszImage, GetLastError());
+ return 1;
+ }
+
+ DWORD cbHigh = 0;
+ DWORD cbLow = GetFileSize(hFile, &cbHigh);
+ size_t cbFile = cbLow != INVALID_FILE_SIZE
+ ? cbHigh == 0
+ ? cbLow
+ : ~(DWORD)0 / 4
+ : 64;
+
+ HANDLE hMap = CreateFileMapping(hFile,
+ NULL /*pAttributes*/,
+ PAGE_READONLY | SEC_COMMIT,
+ 0 /*dwMaximumSizeHigh -> file size*/,
+ 0 /*dwMaximumSizeLow -> file size*/,
+ NULL /*pName*/);
+ if (hMap == INVALID_HANDLE_VALUE)
+ printf("error: CreateFile('%s',): %d\n", pszImage, GetLastError());
+ CloseHandle(hFile);
+ if (hMap == INVALID_HANDLE_VALUE)
+ return 1;
+
+ void *pvWhere = MapViewOfFile(hMap,
+ FILE_MAP_READ,
+ 0 /*dwFileOffsetHigh*/,
+ 0 /*dwFileOffsetLow*/,
+ 0 /*dwNumberOfBytesToMap - file size */);
+ if (!pvWhere)
+ {
+ printf("error: MapViewOfView('%s',): %d\n", pszImage, GetLastError());
+ CloseHandle(hMap);
+ return 1;
+ }
+
+#else
+ int fd = open(pszImage, O_RDONLY, 0);
+ if (fd < 0)
+ {
+ printf("error: open('%s',): %d\n", pszImage, errno);
+ return 1;
+ }
+
+ struct stat st;
+ memset(&st, 0, sizeof(st));
+ if (fstat(fd, &st))
+ st.st_size = 64;
+ size_t cbFile = st.st_size < ~(size_t)0
+ ? (size_t)st.st_size
+ : ~(size_t)0 / 4;
+
+ void *pvWhere = mmap(NULL /*addr*/, cbFile, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0 /*offset*/);
+ if (pvWhere == MAP_FAILED)
+ printf("error: mmap(,%lu,)/'%s': %d\n", (unsigned long)cbFile, pszImage, errno);
+ close(fd);
+ if (pvWhere == MAP_FAILED)
+ return 1;
+
+#endif
+
+ /* Touch the whole image... do a dummy crc to keep the optimizer from begin
+ smart with us. */
+ unsigned char *puchFile = (unsigned char *)pvWhere;
+ size_t off = 0;
+ unsigned int uCrc = 0;
+ while (off < cbFile)
+ uCrc += puchFile[off++];
+ printf("info: %p/%#lx/%#x - %s\n", pvWhere, (unsigned long)cbFile, (unsigned char)uCrc, pszImage);
+
+ return 0;
+}
+
+static int usage(const char *argv0)
+{
+ printf("Generic executable image preloader.\n"
+ "Usage: %s [dll|exe|file []]\n", argv0);
+ return 1;
+}
+
+int main(int argc, char **argv)
+{
+ /*
+ * Check for options.
+ */
+ for (int i = 1; i < argc; i++)
+ {
+ if (argv[i][0] == '-')
+ {
+ if ( argv[i][1] == '-'
+ && argv[i][2] == '\0')
+ break;
+ if ( !strcmp(argv[i], "--help")
+ || !strcmp(argv[i], "-help")
+ || !strcmp(argv[i], "-h")
+ || !strcmp(argv[i], "-?"))
+ {
+ usage(argv[0]);
+ return 1;
+ }
+ if ( !strcmp(argv[i], "--version")
+ || !strcmp(argv[i], "-V"))
+ {
+ printf("$Revision: 155244 $\n");
+ return 0;
+ }
+ fprintf(stderr, "syntax error: unknown option '%s'\n", argv[i]);
+ return 1;
+ }
+ }
+ if (argc <= 1)
+ return usage(argv[0]);
+
+ /*
+ * Do the loading.
+ */
+ for (int i = 1; i < argc; i++)
+ {
+ if (!strcmp(argv[i], "--"))
+ continue;
+ if (argv[i][0] == '@')
+ {
+ FILE *pFile = fopen(&argv[i][1], "r");
+ if (pFile)
+ {
+ char szLine[4096];
+ while (fgets(szLine, sizeof(szLine), pFile))
+ {
+ char *psz = szLine;
+ while (*psz == ' ' || *psz == '\t')
+ psz++;
+ size_t off = strlen(psz);
+ while ( off > 0
+ && ( psz[off - 1] == ' '
+ || psz[off - 1] == '\t'
+ || psz[off - 1] == '\n'
+ || psz[off - 1] == '\r')
+ )
+ psz[--off] = '\0';
+
+ if (*psz && *psz != '#')
+ load(psz);
+ }
+ fclose(pFile);
+ }
+ else
+ fprintf(stderr, "error: fopen('%s','r'): %d\n", &argv[i][1], errno);
+ }
+ else
+ load(argv[i]);
+ }
+
+ /*
+ * Sleep for ever.
+ */
+ for (;;)
+ {
+#ifdef RT_OS_WINDOWS
+ Sleep(3600*1000);
+#else
+ sleep(3600);
+#endif
+ }
+
+ return 0;
+}
diff --git a/src/bldprogs/scm.cpp b/src/bldprogs/scm.cpp
new file mode 100644
index 00000000..0b093255
--- /dev/null
+++ b/src/bldprogs/scm.cpp
@@ -0,0 +1,3269 @@
+/* $Id: scm.cpp $ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+#include "scm.h"
+#include "scmdiff.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The name of the settings files. */
+#define SCM_SETTINGS_FILENAME ".scm-settings"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Option identifiers.
+ *
+ * @note The first chunk, down to SCMOPT_TAB_SIZE, are alternately set &
+ * clear. So, the option setting a flag (boolean) will have an even
+ * number and the one clearing it will have an odd number.
+ * @note Down to SCMOPT_LAST_SETTINGS corresponds exactly to SCMSETTINGSBASE.
+ */
+typedef enum SCMOPT
+{
+ SCMOPT_CONVERT_EOL = 10000,
+ SCMOPT_NO_CONVERT_EOL,
+ SCMOPT_CONVERT_TABS,
+ SCMOPT_NO_CONVERT_TABS,
+ SCMOPT_FORCE_FINAL_EOL,
+ SCMOPT_NO_FORCE_FINAL_EOL,
+ SCMOPT_FORCE_TRAILING_LINE,
+ SCMOPT_NO_FORCE_TRAILING_LINE,
+ SCMOPT_STRIP_TRAILING_BLANKS,
+ SCMOPT_NO_STRIP_TRAILING_BLANKS,
+ SCMOPT_STRIP_TRAILING_LINES,
+ SCMOPT_NO_STRIP_TRAILING_LINES,
+ SCMOPT_FIX_FLOWER_BOX_MARKERS,
+ SCMOPT_NO_FIX_FLOWER_BOX_MARKERS,
+ SCMOPT_FIX_HEADER_GUARDS,
+ SCMOPT_NO_FIX_HEADER_GUARDS,
+ SCMOPT_PRAGMA_ONCE,
+ SCMOPT_NO_PRAGMA_ONCE,
+ SCMOPT_FIX_HEADER_GUARD_ENDIF,
+ SCMOPT_NO_FIX_HEADER_GUARD_ENDIF,
+ SCMOPT_ENDIF_GUARD_COMMENT,
+ SCMOPT_NO_ENDIF_GUARD_COMMENT,
+ SCMOPT_GUARD_PREFIX,
+ SCMOPT_GUARD_RELATIVE_TO_DIR,
+ SCMOPT_FIX_TODOS,
+ SCMOPT_NO_FIX_TODOS,
+ SCMOPT_FIX_ERR_H,
+ SCMOPT_NO_FIX_ERR_H,
+ SCMOPT_ONLY_GUEST_HOST_PAGE,
+ SCMOPT_NO_ASM_MEM_PAGE_USE,
+ SCMOPT_UNRESTRICTED_ASM_MEM_PAGE_USE,
+ SCMOPT_NO_PAGE_RESTRICTIONS,
+ SCMOPT_NO_RC_USE,
+ SCMOPT_UNRESTRICTED_RC_USE,
+ SCMOPT_STANDARIZE_KMK,
+ SCMOPT_NO_STANDARIZE_KMK,
+ SCMOPT_UPDATE_COPYRIGHT_YEAR,
+ SCMOPT_NO_UPDATE_COPYRIGHT_YEAR,
+ SCMOPT_EXTERNAL_COPYRIGHT,
+ SCMOPT_NO_EXTERNAL_COPYRIGHT,
+ SCMOPT_NO_UPDATE_LICENSE,
+ SCMOPT_LICENSE_OSE_GPL,
+ SCMOPT_LICENSE_OSE_DUAL_GPL_CDDL,
+ SCMOPT_LICENSE_OSE_CDDL,
+ SCMOPT_LICENSE_LGPL,
+ SCMOPT_LICENSE_MIT,
+ SCMOPT_LICENSE_BASED_ON_MIT,
+ SCMOPT_LGPL_DISCLAIMER,
+ SCMOPT_NO_LGPL_DISCLAIMER,
+ SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS,
+ SCMOPT_ONLY_SVN_DIRS,
+ SCMOPT_NOT_ONLY_SVN_DIRS,
+ SCMOPT_ONLY_SVN_FILES,
+ SCMOPT_NOT_ONLY_SVN_FILES,
+ SCMOPT_SET_SVN_EOL,
+ SCMOPT_DONT_SET_SVN_EOL,
+ SCMOPT_SET_SVN_EXECUTABLE,
+ SCMOPT_DONT_SET_SVN_EXECUTABLE,
+ SCMOPT_SET_SVN_KEYWORDS,
+ SCMOPT_DONT_SET_SVN_KEYWORDS,
+ SCMOPT_SKIP_SVN_SYNC_PROCESS,
+ SCMOPT_DONT_SKIP_SVN_SYNC_PROCESS,
+ SCMOPT_SKIP_UNICODE_CHECKS,
+ SCMOPT_DONT_SKIP_UNICODE_CHECKS,
+ SCMOPT_TAB_SIZE,
+ SCMOPT_WIDTH,
+ SCMOPT_FILTER_OUT_DIRS,
+ SCMOPT_FILTER_FILES,
+ SCMOPT_FILTER_OUT_FILES,
+ SCMOPT_TREAT_AS,
+ SCMOPT_ADD_ACTION,
+ SCMOPT_DEL_ACTION,
+ SCMOPT_LAST_SETTINGS = SCMOPT_DEL_ACTION,
+ //
+ SCMOPT_CHECK_RUN,
+ SCMOPT_DIFF_IGNORE_EOL,
+ SCMOPT_DIFF_NO_IGNORE_EOL,
+ SCMOPT_DIFF_IGNORE_SPACE,
+ SCMOPT_DIFF_NO_IGNORE_SPACE,
+ SCMOPT_DIFF_IGNORE_LEADING_SPACE,
+ SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE,
+ SCMOPT_DIFF_IGNORE_TRAILING_SPACE,
+ SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE,
+ SCMOPT_DIFF_SPECIAL_CHARS,
+ SCMOPT_DIFF_NO_SPECIAL_CHARS,
+ SCMOPT_HELP_CONFIG,
+ SCMOPT_HELP_ACTIONS,
+ SCMOPT_END
+} SCMOPT;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+const char g_szTabSpaces[16+1] = " ";
+const char g_szAsterisks[255+1] =
+"****************************************************************************************************"
+"****************************************************************************************************"
+"*******************************************************";
+const char g_szSpaces[255+1] =
+" "
+" "
+" ";
+static const char g_szProgName[] = "scm";
+static const char *g_pszChangedSuff = "";
+static bool g_fDryRun = true;
+static bool g_fDiffSpecialChars = true;
+static bool g_fDiffIgnoreEol = false;
+static bool g_fDiffIgnoreLeadingWS = false;
+static bool g_fDiffIgnoreTrailingWS = false;
+static int g_iVerbosity = 2;//99; //0;
+uint32_t g_uYear = 0; /**< The current year. */
+/** @name Statistics
+ * @{ */
+static uint32_t g_cDirsProcessed = 0;
+static uint32_t g_cFilesProcessed = 0;
+static uint32_t g_cFilesModified = 0;
+static uint32_t g_cFilesSkipped = 0;
+static uint32_t g_cFilesNotInSvn = 0;
+static uint32_t g_cFilesNoRewriters = 0;
+static uint32_t g_cFilesBinaries = 0;
+static uint32_t g_cFilesRequiringManualFixing = 0;
+/** @} */
+
+/** The global settings. */
+static SCMSETTINGSBASE const g_Defaults =
+{
+ /* .fConvertEol = */ true,
+ /* .fConvertTabs = */ true,
+ /* .fForceFinalEol = */ true,
+ /* .fForceTrailingLine = */ false,
+ /* .fStripTrailingBlanks = */ true,
+ /* .fStripTrailingLines = */ true,
+ /* .fFixFlowerBoxMarkers = */ true,
+ /* .cMinBlankLinesBeforeFlowerBoxMakers = */ 2,
+ /* .fFixHeaderGuards = */ true,
+ /* .fPragmaOnce = */ true,
+ /* .fFixHeaderGuardEndif = */ true,
+ /* .fEndifGuardComment = */ true,
+ /* .pszGuardPrefix = */ (char *)"VBOX_INCLUDED_SRC_",
+ /* .pszGuardRelativeToDir = */ (char *)"{parent}",
+ /* .fFixTodos = */ true,
+ /* .fFixErrH = */ true,
+ /* .fOnlyGuestHostPage = */ false,
+ /* .fNoASMMemPageUse = */ false,
+ /* .fOnlyHrcVrcInsteadOfRc */ false,
+ /* .fStandarizeKmk */ true,
+ /* .fUpdateCopyrightYear = */ false,
+ /* .fExternalCopyright = */ false,
+ /* .fLgplDisclaimer = */ false,
+ /* .enmUpdateLicense = */ kScmLicense_OseGpl,
+ /* .fOnlySvnFiles = */ false,
+ /* .fOnlySvnDirs = */ false,
+ /* .fSetSvnEol = */ false,
+ /* .fSetSvnExecutable = */ false,
+ /* .fSetSvnKeywords = */ false,
+ /* .fSkipSvnSyncProcess = */ false,
+ /* .fSkipUnicodeChecks = */ false,
+ /* .cchTab = */ 8,
+ /* .cchWidth = */ 130,
+ /* .fFreeTreatAs = */ false,
+ /* .pTreatAs = */ NULL,
+ /* .pszFilterFiles = */ (char *)"",
+ /* .pszFilterOutFiles = */ (char *)"*.exe|*.com|20*-*-*.log",
+ /* .pszFilterOutDirs = */ (char *)".svn|.hg|.git|CVS",
+};
+
+/** Option definitions for the base settings. */
+static RTGETOPTDEF g_aScmOpts[] =
+{
+ /* rewriters */
+ { "--convert-eol", SCMOPT_CONVERT_EOL, RTGETOPT_REQ_NOTHING },
+ { "--no-convert-eol", SCMOPT_NO_CONVERT_EOL, RTGETOPT_REQ_NOTHING },
+ { "--convert-tabs", SCMOPT_CONVERT_TABS, RTGETOPT_REQ_NOTHING },
+ { "--no-convert-tabs", SCMOPT_NO_CONVERT_TABS, RTGETOPT_REQ_NOTHING },
+ { "--force-final-eol", SCMOPT_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING },
+ { "--no-force-final-eol", SCMOPT_NO_FORCE_FINAL_EOL, RTGETOPT_REQ_NOTHING },
+ { "--force-trailing-line", SCMOPT_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING },
+ { "--no-force-trailing-line", SCMOPT_NO_FORCE_TRAILING_LINE, RTGETOPT_REQ_NOTHING },
+ { "--strip-trailing-blanks", SCMOPT_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING },
+ { "--no-strip-trailing-blanks", SCMOPT_NO_STRIP_TRAILING_BLANKS, RTGETOPT_REQ_NOTHING },
+ { "--strip-trailing-lines", SCMOPT_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING },
+ { "--strip-no-trailing-lines", SCMOPT_NO_STRIP_TRAILING_LINES, RTGETOPT_REQ_NOTHING },
+ { "--min-blank-lines-before-flower-box-makers", SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS, RTGETOPT_REQ_UINT8 },
+ { "--fix-flower-box-markers", SCMOPT_FIX_FLOWER_BOX_MARKERS, RTGETOPT_REQ_NOTHING },
+ { "--no-fix-flower-box-markers", SCMOPT_NO_FIX_FLOWER_BOX_MARKERS, RTGETOPT_REQ_NOTHING },
+ { "--fix-header-guards", SCMOPT_FIX_HEADER_GUARDS, RTGETOPT_REQ_NOTHING },
+ { "--no-fix-header-guards", SCMOPT_NO_FIX_HEADER_GUARDS, RTGETOPT_REQ_NOTHING },
+ { "--pragma-once", SCMOPT_PRAGMA_ONCE, RTGETOPT_REQ_NOTHING },
+ { "--no-pragma-once", SCMOPT_NO_PRAGMA_ONCE, RTGETOPT_REQ_NOTHING },
+ { "--fix-header-guard-endif", SCMOPT_FIX_HEADER_GUARD_ENDIF, RTGETOPT_REQ_NOTHING },
+ { "--no-fix-header-guard-endif", SCMOPT_NO_FIX_HEADER_GUARD_ENDIF, RTGETOPT_REQ_NOTHING },
+ { "--endif-guard-comment", SCMOPT_ENDIF_GUARD_COMMENT, RTGETOPT_REQ_NOTHING },
+ { "--no-endif-guard-comment", SCMOPT_NO_ENDIF_GUARD_COMMENT, RTGETOPT_REQ_NOTHING },
+ { "--guard-prefix", SCMOPT_GUARD_PREFIX, RTGETOPT_REQ_STRING },
+ { "--guard-relative-to-dir", SCMOPT_GUARD_RELATIVE_TO_DIR, RTGETOPT_REQ_STRING },
+ { "--fix-todos", SCMOPT_FIX_TODOS, RTGETOPT_REQ_NOTHING },
+ { "--no-fix-todos", SCMOPT_NO_FIX_TODOS, RTGETOPT_REQ_NOTHING },
+ { "--fix-err-h", SCMOPT_FIX_ERR_H, RTGETOPT_REQ_NOTHING },
+ { "--no-fix-err-h", SCMOPT_NO_FIX_ERR_H, RTGETOPT_REQ_NOTHING },
+ { "--only-guest-host-page", SCMOPT_ONLY_GUEST_HOST_PAGE, RTGETOPT_REQ_NOTHING },
+ { "--no-page-restrictions", SCMOPT_NO_PAGE_RESTRICTIONS, RTGETOPT_REQ_NOTHING },
+ { "--no-ASMMemPage-use", SCMOPT_NO_ASM_MEM_PAGE_USE, RTGETOPT_REQ_NOTHING },
+ { "--unrestricted-ASMMemPage-use", SCMOPT_UNRESTRICTED_ASM_MEM_PAGE_USE, RTGETOPT_REQ_NOTHING },
+ { "--no-rc-use", SCMOPT_NO_RC_USE, RTGETOPT_REQ_NOTHING },
+ { "--unrestricted-rc-use", SCMOPT_UNRESTRICTED_RC_USE, RTGETOPT_REQ_NOTHING },
+ { "--standarize-kmk", SCMOPT_STANDARIZE_KMK, RTGETOPT_REQ_NOTHING },
+ { "--no-standarize-kmk", SCMOPT_NO_STANDARIZE_KMK, RTGETOPT_REQ_NOTHING },
+ { "--update-copyright-year", SCMOPT_UPDATE_COPYRIGHT_YEAR, RTGETOPT_REQ_NOTHING },
+ { "--no-update-copyright-year", SCMOPT_NO_UPDATE_COPYRIGHT_YEAR, RTGETOPT_REQ_NOTHING },
+ { "--external-copyright", SCMOPT_EXTERNAL_COPYRIGHT, RTGETOPT_REQ_NOTHING },
+ { "--no-external-copyright", SCMOPT_NO_EXTERNAL_COPYRIGHT, RTGETOPT_REQ_NOTHING },
+ { "--no-update-license", SCMOPT_NO_UPDATE_LICENSE, RTGETOPT_REQ_NOTHING },
+ { "--license-ose-gpl", SCMOPT_LICENSE_OSE_GPL, RTGETOPT_REQ_NOTHING },
+ { "--license-ose-dual", SCMOPT_LICENSE_OSE_DUAL_GPL_CDDL, RTGETOPT_REQ_NOTHING },
+ { "--license-ose-cddl", SCMOPT_LICENSE_OSE_CDDL, RTGETOPT_REQ_NOTHING },
+ { "--license-lgpl", SCMOPT_LICENSE_LGPL, RTGETOPT_REQ_NOTHING },
+ { "--license-mit", SCMOPT_LICENSE_MIT, RTGETOPT_REQ_NOTHING },
+ { "--license-based-on-mit", SCMOPT_LICENSE_BASED_ON_MIT, RTGETOPT_REQ_NOTHING },
+ { "--lgpl-disclaimer", SCMOPT_LGPL_DISCLAIMER, RTGETOPT_REQ_NOTHING },
+ { "--no-lgpl-disclaimer", SCMOPT_NO_LGPL_DISCLAIMER, RTGETOPT_REQ_NOTHING },
+ { "--set-svn-eol", SCMOPT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING },
+ { "--dont-set-svn-eol", SCMOPT_DONT_SET_SVN_EOL, RTGETOPT_REQ_NOTHING },
+ { "--set-svn-executable", SCMOPT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING },
+ { "--dont-set-svn-executable", SCMOPT_DONT_SET_SVN_EXECUTABLE, RTGETOPT_REQ_NOTHING },
+ { "--set-svn-keywords", SCMOPT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING },
+ { "--dont-set-svn-keywords", SCMOPT_DONT_SET_SVN_KEYWORDS, RTGETOPT_REQ_NOTHING },
+ { "--skip-svn-sync-process", SCMOPT_SKIP_SVN_SYNC_PROCESS, RTGETOPT_REQ_NOTHING },
+ { "--dont-skip-svn-sync-process", SCMOPT_DONT_SKIP_SVN_SYNC_PROCESS, RTGETOPT_REQ_NOTHING },
+ { "--skip-unicode-checks", SCMOPT_SKIP_UNICODE_CHECKS, RTGETOPT_REQ_NOTHING },
+ { "--dont-skip-unicode-checks", SCMOPT_DONT_SKIP_UNICODE_CHECKS, RTGETOPT_REQ_NOTHING },
+ { "--tab-size", SCMOPT_TAB_SIZE, RTGETOPT_REQ_UINT8 },
+ { "--width", SCMOPT_WIDTH, RTGETOPT_REQ_UINT8 },
+
+ /* input selection */
+ { "--only-svn-dirs", SCMOPT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING },
+ { "--not-only-svn-dirs", SCMOPT_NOT_ONLY_SVN_DIRS, RTGETOPT_REQ_NOTHING },
+ { "--only-svn-files", SCMOPT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING },
+ { "--not-only-svn-files", SCMOPT_NOT_ONLY_SVN_FILES, RTGETOPT_REQ_NOTHING },
+ { "--filter-out-dirs", SCMOPT_FILTER_OUT_DIRS, RTGETOPT_REQ_STRING },
+ { "--filter-files", SCMOPT_FILTER_FILES, RTGETOPT_REQ_STRING },
+ { "--filter-out-files", SCMOPT_FILTER_OUT_FILES, RTGETOPT_REQ_STRING },
+
+ /* rewriter selection */
+ { "--treat-as", SCMOPT_TREAT_AS, RTGETOPT_REQ_STRING },
+ { "--add-action", SCMOPT_ADD_ACTION, RTGETOPT_REQ_STRING },
+ { "--del-action", SCMOPT_DEL_ACTION, RTGETOPT_REQ_STRING },
+
+ /* Additional help */
+ { "--help-config", SCMOPT_HELP_CONFIG, RTGETOPT_REQ_NOTHING },
+ { "--help-actions", SCMOPT_HELP_ACTIONS, RTGETOPT_REQ_NOTHING },
+};
+
+/** Consider files matching the following patterns (base names only). */
+static const char *g_pszFileFilter = NULL;
+
+/* The rewriter configuration. */
+#define SCM_REWRITER_CFG(a_Global, a_szName, fnRewriter) static const SCMREWRITERCFG a_Global = { &fnRewriter, a_szName }
+SCM_REWRITER_CFG(g_StripTrailingBlanks, "strip-trailing-blanks", rewrite_StripTrailingBlanks);
+SCM_REWRITER_CFG(g_ExpandTabs, "expand-tabs", rewrite_ExpandTabs);
+SCM_REWRITER_CFG(g_ForceNativeEol, "force-native-eol", rewrite_ForceNativeEol);
+SCM_REWRITER_CFG(g_ForceLF, "force-lf", rewrite_ForceLF);
+SCM_REWRITER_CFG(g_ForceCRLF, "force-crlf", rewrite_ForceCRLF);
+SCM_REWRITER_CFG(g_AdjustTrailingLines, "adjust-trailing-lines", rewrite_AdjustTrailingLines);
+SCM_REWRITER_CFG(g_SvnNoExecutable, "svn-no-executable", rewrite_SvnNoExecutable);
+SCM_REWRITER_CFG(g_SvnNoKeywords, "svn-no-keywords", rewrite_SvnNoKeywords);
+SCM_REWRITER_CFG(g_SvnNoEolStyle, "svn-no-eol-style", rewrite_SvnNoEolStyle);
+SCM_REWRITER_CFG(g_SvnBinary, "svn-binary", rewrite_SvnBinary);
+SCM_REWRITER_CFG(g_SvnKeywords, "svn-keywords", rewrite_SvnKeywords);
+SCM_REWRITER_CFG(g_SvnSyncProcess, "svn-sync-process", rewrite_SvnSyncProcess);
+SCM_REWRITER_CFG(g_UnicodeChecks, "unicode-checks", rewrite_UnicodeChecks);
+SCM_REWRITER_CFG(g_PageChecks, "page-checks", rewrite_PageChecks);
+SCM_REWRITER_CFG(g_ForceHrcVrcInsteadOfRc, "force-hrc-vrc-no-rc", rewrite_ForceHrcVrcInsteadOfRc);
+SCM_REWRITER_CFG(g_Copyright_CstyleComment, "copyright-c-style", rewrite_Copyright_CstyleComment);
+SCM_REWRITER_CFG(g_Copyright_HashComment, "copyright-hash-style", rewrite_Copyright_HashComment);
+SCM_REWRITER_CFG(g_Copyright_PythonComment, "copyright-python-style", rewrite_Copyright_PythonComment);
+SCM_REWRITER_CFG(g_Copyright_RemComment, "copyright-rem-style", rewrite_Copyright_RemComment);
+SCM_REWRITER_CFG(g_Copyright_SemicolonComment, "copyright-semicolon-style", rewrite_Copyright_SemicolonComment);
+SCM_REWRITER_CFG(g_Copyright_SqlComment, "copyright-sql-style", rewrite_Copyright_SqlComment);
+SCM_REWRITER_CFG(g_Copyright_TickComment, "copyright-tick-style", rewrite_Copyright_TickComment);
+SCM_REWRITER_CFG(g_Copyright_XmlComment, "copyright-xml-style", rewrite_Copyright_XmlComment);
+SCM_REWRITER_CFG(g_Makefile_kup, "makefile-kup", rewrite_Makefile_kup);
+SCM_REWRITER_CFG(g_Makefile_kmk, "makefile-kmk", rewrite_Makefile_kmk);
+SCM_REWRITER_CFG(g_FixFlowerBoxMarkers, "fix-flower-boxes", rewrite_FixFlowerBoxMarkers);
+SCM_REWRITER_CFG(g_FixHeaderGuards, "fix-header-guard", rewrite_FixHeaderGuards);
+SCM_REWRITER_CFG(g_Fix_C_and_CPP_Todos, "fix-c-todos", rewrite_Fix_C_and_CPP_Todos);
+SCM_REWRITER_CFG(g_Fix_Err_H, "fix-err-h", rewrite_Fix_Err_H);
+SCM_REWRITER_CFG(g_C_and_CPP, "c-and-cpp", rewrite_C_and_CPP);
+
+/** The rewriter actions. */
+static PCSCMREWRITERCFG const g_papRewriterActions[] =
+{
+ &g_StripTrailingBlanks,
+ &g_ExpandTabs,
+ &g_ForceNativeEol,
+ &g_ForceLF,
+ &g_ForceCRLF,
+ &g_AdjustTrailingLines,
+ &g_SvnNoExecutable,
+ &g_SvnNoKeywords,
+ &g_SvnNoEolStyle,
+ &g_SvnBinary,
+ &g_SvnKeywords,
+ &g_SvnSyncProcess,
+ &g_Copyright_CstyleComment,
+ &g_Copyright_HashComment,
+ &g_Copyright_PythonComment,
+ &g_Copyright_RemComment,
+ &g_Copyright_SemicolonComment,
+ &g_Copyright_SqlComment,
+ &g_Copyright_TickComment,
+ &g_Makefile_kup,
+ &g_Makefile_kmk,
+ &g_FixFlowerBoxMarkers,
+ &g_FixHeaderGuards,
+ &g_Fix_C_and_CPP_Todos,
+ &g_Fix_Err_H,
+ &g_UnicodeChecks,
+ &g_PageChecks,
+ &g_ForceHrcVrcInsteadOfRc,
+ &g_C_and_CPP,
+};
+
+
+static PCSCMREWRITERCFG const g_apRewritersFor_Makefile_kup[] =
+{
+ &g_SvnNoExecutable,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Makefile_kup
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_Makefile_kmk[] =
+{
+ &g_ForceNativeEol,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnNoExecutable,
+ &g_SvnKeywords,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_HashComment,
+ &g_Makefile_kmk
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_OtherMakefiles[] =
+{
+ &g_ForceNativeEol,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnNoExecutable,
+ &g_SvnKeywords,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_HashComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_C_and_CPP[] =
+{
+ &g_ForceNativeEol,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnNoExecutable,
+ &g_SvnKeywords,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_PageChecks,
+ &g_ForceHrcVrcInsteadOfRc,
+ &g_Copyright_CstyleComment,
+ &g_FixFlowerBoxMarkers,
+ &g_Fix_C_and_CPP_Todos,
+ &g_Fix_Err_H,
+ &g_C_and_CPP,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_H_and_HPP[] =
+{
+ &g_ForceNativeEol,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnNoExecutable,
+ &g_SvnKeywords,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_PageChecks,
+ &g_ForceHrcVrcInsteadOfRc,
+ &g_Copyright_CstyleComment,
+ /// @todo &g_FixFlowerBoxMarkers,
+ &g_FixHeaderGuards,
+ &g_C_and_CPP
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_RC[] =
+{
+ &g_ForceNativeEol,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnNoExecutable,
+ &g_SvnKeywords,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_CstyleComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_DTrace[] =
+{
+ &g_ForceNativeEol,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnKeywords,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_CstyleComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_DSL[] =
+{
+ &g_ForceNativeEol,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnNoExecutable,
+ &g_SvnKeywords,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_CstyleComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_ASM[] =
+{
+ &g_ForceNativeEol,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnNoExecutable,
+ &g_SvnKeywords,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_SemicolonComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_DEF[] =
+{
+ &g_ForceNativeEol,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnNoExecutable,
+ &g_SvnKeywords,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_SemicolonComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_ShellScripts[] =
+{
+ &g_ForceLF,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_HashComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_BatchFiles[] =
+{
+ &g_ForceCRLF,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_RemComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_BasicScripts[] =
+{
+ &g_ForceCRLF,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_TickComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_SedScripts[] =
+{
+ &g_ForceLF,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_HashComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_Python[] =
+{
+ /** @todo &g_ForceLFIfExecutable */
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnKeywords,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_PythonComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_Perl[] =
+{
+ /** @todo &g_ForceLFIfExecutable */
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnKeywords,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_HashComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_DriverInfFiles[] =
+{
+ &g_ForceNativeEol,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnKeywords,
+ &g_SvnNoExecutable,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_SemicolonComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_NsisFiles[] =
+{
+ &g_ForceNativeEol,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnKeywords,
+ &g_SvnNoExecutable,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_SemicolonComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_Java[] =
+{
+ &g_ForceNativeEol,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnNoExecutable,
+ &g_SvnKeywords,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_CstyleComment,
+ &g_FixFlowerBoxMarkers,
+ &g_Fix_C_and_CPP_Todos,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_ScmSettings[] =
+{
+ &g_ForceNativeEol,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnNoExecutable,
+ &g_SvnKeywords,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_HashComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_Images[] =
+{
+ &g_SvnNoExecutable,
+ &g_SvnBinary,
+ &g_SvnSyncProcess,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_Xslt[] =
+{
+ &g_ForceNativeEol,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnNoExecutable,
+ &g_SvnKeywords,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_XmlComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_Xml[] =
+{
+ &g_ForceNativeEol,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnNoExecutable,
+ &g_SvnKeywords,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_XmlComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_Wix[] =
+{
+ &g_ForceNativeEol,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnNoExecutable,
+ &g_SvnKeywords,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_XmlComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_QtProject[] =
+{
+ &g_ForceNativeEol,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnNoExecutable,
+ &g_SvnKeywords,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_HashComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_QtResourceFiles[] =
+{
+ &g_ForceNativeEol,
+ &g_SvnNoExecutable,
+ &g_SvnKeywords,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ /** @todo figure out copyright for Qt resource XML files. */
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_QtTranslations[] =
+{
+ &g_ForceNativeEol,
+ &g_SvnNoExecutable,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_QtUiFiles[] =
+{
+ &g_ForceNativeEol,
+ &g_SvnNoExecutable,
+ &g_SvnKeywords,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ /** @todo copyright is in an XML 'comment' element. */
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_SifFiles[] =
+{
+ &g_ForceCRLF,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnKeywords,
+ &g_SvnNoExecutable,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_SemicolonComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_SqlFiles[] =
+{
+ &g_ForceNativeEol,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnKeywords,
+ &g_SvnNoExecutable,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_SqlComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_GnuAsm[] =
+{
+ &g_ForceNativeEol,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnKeywords,
+ &g_SvnNoExecutable,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_CstyleComment,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_TextFiles[] =
+{
+ &g_ForceNativeEol,
+ &g_StripTrailingBlanks,
+ &g_SvnKeywords,
+ &g_SvnNoExecutable,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ /** @todo check for plain copyright + license in text files. */
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_PlainTextFiles[] =
+{
+ &g_ForceNativeEol,
+ &g_StripTrailingBlanks,
+ &g_SvnKeywords,
+ &g_SvnNoExecutable,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_BinaryFiles[] =
+{
+ &g_SvnBinary,
+ &g_SvnSyncProcess,
+};
+
+static PCSCMREWRITERCFG const g_apRewritersFor_FileLists[] = /* both makefile and shell script */
+{
+ &g_ForceLF,
+ &g_ExpandTabs,
+ &g_StripTrailingBlanks,
+ &g_AdjustTrailingLines,
+ &g_SvnSyncProcess,
+ &g_UnicodeChecks,
+ &g_Copyright_HashComment,
+};
+
+
+/**
+ * Array of standard rewriter configurations.
+ */
+static SCMCFGENTRY const g_aConfigs[] =
+{
+#define SCM_CFG_ENTRY(a_szName, a_aRewriters, a_fBinary, a_szFilePatterns) \
+ { RT_ELEMENTS(a_aRewriters), &a_aRewriters[0], a_fBinary, a_szFilePatterns, a_szName }
+ SCM_CFG_ENTRY("kup", g_apRewritersFor_Makefile_kup, false, "Makefile.kup" ),
+ SCM_CFG_ENTRY("kmk", g_apRewritersFor_Makefile_kmk, false, "*.kmk" ),
+ SCM_CFG_ENTRY("c", g_apRewritersFor_C_and_CPP, false, "*.c|*.cpp|*.C|*.CPP|*.cxx|*.cc|*.m|*.mm|*.lds" ),
+ SCM_CFG_ENTRY("h", g_apRewritersFor_H_and_HPP, false, "*.h|*.hpp" ),
+ SCM_CFG_ENTRY("rc", g_apRewritersFor_RC, false, "*.rc" ),
+ SCM_CFG_ENTRY("asm", g_apRewritersFor_ASM, false, "*.asm|*.mac|*.inc" ),
+ SCM_CFG_ENTRY("dtrace", g_apRewritersFor_DTrace, false, "*.d" ),
+ SCM_CFG_ENTRY("def", g_apRewritersFor_DEF, false, "*.def" ),
+ SCM_CFG_ENTRY("iasl", g_apRewritersFor_DSL, false, "*.dsl" ),
+ SCM_CFG_ENTRY("shell", g_apRewritersFor_ShellScripts, false, "*.sh|configure" ),
+ SCM_CFG_ENTRY("batch", g_apRewritersFor_BatchFiles, false, "*.bat|*.cmd|*.btm" ),
+ SCM_CFG_ENTRY("vbs", g_apRewritersFor_BasicScripts, false, "*.vbs|*.vb" ),
+ SCM_CFG_ENTRY("sed", g_apRewritersFor_SedScripts, false, "*.sed" ),
+ SCM_CFG_ENTRY("python", g_apRewritersFor_Python, false, "*.py" ),
+ SCM_CFG_ENTRY("perl", g_apRewritersFor_Perl, false, "*.pl|*.pm" ),
+ SCM_CFG_ENTRY("drvinf", g_apRewritersFor_DriverInfFiles, false, "*.inf" ),
+ SCM_CFG_ENTRY("nsis", g_apRewritersFor_NsisFiles, false, "*.nsh|*.nsi|*.nsis" ),
+ SCM_CFG_ENTRY("java", g_apRewritersFor_Java, false, "*.java" ),
+ SCM_CFG_ENTRY("scm", g_apRewritersFor_ScmSettings, false, "*.scm-settings" ),
+ SCM_CFG_ENTRY("image", g_apRewritersFor_Images, true, "*.png|*.bmp|*.jpg|*.pnm|*.ico|*.icns|*.tiff|*.tif|"
+ "*.xcf|*.gif|*.jar|*.dll|*.exe|*.ttf|*.woff|*.woff2" ),
+ SCM_CFG_ENTRY("xslt", g_apRewritersFor_Xslt, false, "*.xsl" ),
+ SCM_CFG_ENTRY("xml", g_apRewritersFor_Xml, false, "*.xml|*.dist|*.qhcp" ),
+ SCM_CFG_ENTRY("wix", g_apRewritersFor_Wix, false, "*.wxi|*.wxs|*.wxl" ),
+ SCM_CFG_ENTRY("qt-pro", g_apRewritersFor_QtProject, false, "*.pro" ),
+ SCM_CFG_ENTRY("qt-rc", g_apRewritersFor_QtResourceFiles, false, "*.qrc" ),
+ SCM_CFG_ENTRY("qt-ts", g_apRewritersFor_QtTranslations, false, "*.ts" ),
+ SCM_CFG_ENTRY("qt-ui", g_apRewritersFor_QtUiFiles, false, "*.ui" ),
+ SCM_CFG_ENTRY("sif", g_apRewritersFor_SifFiles, false, "*.sif" ),
+ SCM_CFG_ENTRY("sql", g_apRewritersFor_SqlFiles, false, "*.pgsql|*.sql" ),
+ SCM_CFG_ENTRY("gas", g_apRewritersFor_GnuAsm, false, "*.S" ),
+ SCM_CFG_ENTRY("binary", g_apRewritersFor_BinaryFiles, true, "*.bin|*.pdf|*.zip|*.bz2|*.gz" ),
+ /* These should be be last: */
+ SCM_CFG_ENTRY("make", g_apRewritersFor_OtherMakefiles, false, "Makefile|makefile|GNUmakefile|SMakefile|Makefile.am|Makefile.in|*.cmake|*.gmk" ),
+ SCM_CFG_ENTRY("text", g_apRewritersFor_TextFiles, false, "*.txt|README*|readme*|ReadMe*|NOTE*|TODO*" ),
+ SCM_CFG_ENTRY("plaintext", g_apRewritersFor_PlainTextFiles, false, "LICENSE|ChangeLog|FAQ|AUTHORS|INSTALL|NEWS" ),
+ SCM_CFG_ENTRY("file-list", g_apRewritersFor_FileLists, false, "files_*" ),
+};
+
+
+
+/* -=-=-=-=-=- settings -=-=-=-=-=- */
+
+/**
+ * Delete the given config entry.
+ *
+ * @param pEntry The configuration entry to delete.
+ */
+static void scmCfgEntryDelete(PSCMCFGENTRY pEntry)
+{
+ RTMemFree((void *)pEntry->paRewriters);
+ pEntry->paRewriters = NULL;
+ RTMemFree(pEntry);
+}
+
+/**
+ * Create a new configuration entry.
+ *
+ * @returns The new entry. NULL if out of memory.
+ * @param pEntry The configuration entry to duplicate.
+ */
+static PSCMCFGENTRY scmCfgEntryNew(void)
+{
+ PSCMCFGENTRY pNew = (PSCMCFGENTRY)RTMemAlloc(sizeof(*pNew));
+ if (pNew)
+ {
+ pNew->pszName = "custom";
+ pNew->pszFilePattern = "custom";
+ pNew->cRewriters = 0;
+ pNew->paRewriters = NULL;
+ pNew->fBinary = false;
+ }
+ return pNew;
+}
+
+/**
+ * Duplicate the given config entry.
+ *
+ * @returns The duplicate. NULL if out of memory.
+ * @param pEntry The configuration entry to duplicate.
+ */
+static PSCMCFGENTRY scmCfgEntryDup(PCSCMCFGENTRY pEntry)
+{
+ if (pEntry)
+ {
+ PSCMCFGENTRY pDup = (PSCMCFGENTRY)RTMemDup(pEntry, sizeof(*pEntry));
+ if (pDup)
+ {
+ size_t cbSrcRewriters = sizeof(pEntry->paRewriters[0]) * pEntry->cRewriters;
+ size_t cbDstRewriters = sizeof(pEntry->paRewriters[0]) * RT_ALIGN_Z(pEntry->cRewriters, 8);
+ pDup->paRewriters = (PCSCMREWRITERCFG const *)RTMemDupEx(pEntry->paRewriters, cbSrcRewriters,
+ cbDstRewriters - cbSrcRewriters);
+ if (pDup->paRewriters)
+ return pDup;
+
+ RTMemFree(pDup);
+ }
+ return NULL;
+ }
+ return scmCfgEntryNew();
+}
+
+/**
+ * Adds a rewriter action to the given config entry (--add-action).
+ *
+ * @returns VINF_SUCCESS.
+ * @param pEntry The configuration entry.
+ * @param pAction The rewriter action to add.
+ */
+static int scmCfgEntryAddAction(PSCMCFGENTRY pEntry, PCSCMREWRITERCFG pAction)
+{
+ PCSCMREWRITERCFG *paRewriters = (PCSCMREWRITERCFG *)pEntry->paRewriters;
+ if (pEntry->cRewriters % 8 == 0)
+ {
+ size_t cbRewriters = sizeof(pEntry->paRewriters[0]) * RT_ALIGN_Z((pEntry->cRewriters + 1), 8);
+ void *pvNew = RTMemRealloc(paRewriters, cbRewriters);
+ if (pvNew)
+ pEntry->paRewriters = paRewriters = (PCSCMREWRITERCFG *)pvNew;
+ else
+ return VERR_NO_MEMORY;
+ }
+
+ paRewriters[pEntry->cRewriters++] = pAction;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Delets an rewriter action from the given config entry (--del-action).
+ *
+ * @param pEntry The configuration entry.
+ * @param pAction The rewriter action to remove.
+ */
+static void scmCfgEntryDelAction(PSCMCFGENTRY pEntry, PCSCMREWRITERCFG pAction)
+{
+ PCSCMREWRITERCFG *paRewriters = (PCSCMREWRITERCFG *)pEntry->paRewriters;
+ size_t const cEntries = pEntry->cRewriters;
+ size_t iDst = 0;
+ for (size_t iSrc = 0; iSrc < cEntries; iSrc++)
+ {
+ PCSCMREWRITERCFG pCurAction = paRewriters[iSrc];
+ if (pCurAction != pAction)
+ paRewriters[iDst++] = pCurAction;
+ }
+ pEntry->cRewriters = iDst;
+}
+
+/**
+ * Init a settings structure with settings from @a pSrc.
+ *
+ * @returns IPRT status code
+ * @param pSettings The settings.
+ * @param pSrc The source settings.
+ */
+static int scmSettingsBaseInitAndCopy(PSCMSETTINGSBASE pSettings, PCSCMSETTINGSBASE pSrc)
+{
+ *pSettings = *pSrc;
+
+ int rc = RTStrDupEx(&pSettings->pszFilterFiles, pSrc->pszFilterFiles);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrDupEx(&pSettings->pszFilterOutFiles, pSrc->pszFilterOutFiles);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrDupEx(&pSettings->pszFilterOutDirs, pSrc->pszFilterOutDirs);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrDupEx(&pSettings->pszGuardPrefix, pSrc->pszGuardPrefix);
+ if (RT_SUCCESS(rc))
+ {
+ if (pSrc->pszGuardRelativeToDir)
+ rc = RTStrDupEx(&pSettings->pszGuardRelativeToDir, pSrc->pszGuardRelativeToDir);
+ if (RT_SUCCESS(rc))
+ {
+
+ if (!pSrc->fFreeTreatAs)
+ return VINF_SUCCESS;
+
+ pSettings->pTreatAs = scmCfgEntryDup(pSrc->pTreatAs);
+ if (pSettings->pTreatAs)
+ return VINF_SUCCESS;
+
+ RTStrFree(pSettings->pszGuardRelativeToDir);
+ }
+ RTStrFree(pSettings->pszGuardPrefix);
+ }
+ }
+ RTStrFree(pSettings->pszFilterOutFiles);
+ }
+ RTStrFree(pSettings->pszFilterFiles);
+ }
+
+ pSettings->pszGuardRelativeToDir = NULL;
+ pSettings->pszGuardPrefix = NULL;
+ pSettings->pszFilterFiles = NULL;
+ pSettings->pszFilterOutFiles = NULL;
+ pSettings->pszFilterOutDirs = NULL;
+ pSettings->pTreatAs = NULL;
+ return rc;
+}
+
+/**
+ * Init a settings structure.
+ *
+ * @returns IPRT status code
+ * @param pSettings The settings.
+ */
+static int scmSettingsBaseInit(PSCMSETTINGSBASE pSettings)
+{
+ return scmSettingsBaseInitAndCopy(pSettings, &g_Defaults);
+}
+
+/**
+ * Deletes the settings, i.e. free any dynamically allocated content.
+ *
+ * @param pSettings The settings.
+ */
+static void scmSettingsBaseDelete(PSCMSETTINGSBASE pSettings)
+{
+ if (pSettings)
+ {
+ Assert(pSettings->cchTab != UINT8_MAX);
+ pSettings->cchTab = UINT8_MAX;
+
+ RTStrFree(pSettings->pszGuardPrefix);
+ RTStrFree(pSettings->pszGuardRelativeToDir);
+ RTStrFree(pSettings->pszFilterFiles);
+ RTStrFree(pSettings->pszFilterOutFiles);
+ RTStrFree(pSettings->pszFilterOutDirs);
+ if (pSettings->fFreeTreatAs)
+ scmCfgEntryDelete((PSCMCFGENTRY)pSettings->pTreatAs);
+
+ pSettings->pszGuardPrefix = NULL;
+ pSettings->pszGuardRelativeToDir = NULL;
+ pSettings->pszFilterOutDirs = NULL;
+ pSettings->pszFilterOutFiles = NULL;
+ pSettings->pszFilterFiles = NULL;
+ pSettings->pTreatAs = NULL;
+ pSettings->fFreeTreatAs = false;
+ }
+}
+
+/**
+ * Processes a RTGetOpt result.
+ *
+ * @retval VINF_SUCCESS if handled.
+ * @retval VERR_OUT_OF_RANGE if the option value was out of range.
+ * @retval VERR_GETOPT_UNKNOWN_OPTION if the option was not recognized.
+ *
+ * @param pSettings The settings to change.
+ * @param rc The RTGetOpt return value.
+ * @param pValueUnion The RTGetOpt value union.
+ * @param pchDir The absolute path to the directory relative
+ * components in pchLine should be relative to.
+ * @param cchDir The length of the @a pchDir string.
+ */
+static int scmSettingsBaseHandleOpt(PSCMSETTINGSBASE pSettings, int rc, PRTGETOPTUNION pValueUnion,
+ const char *pchDir, size_t cchDir)
+{
+ Assert(pchDir[cchDir - 1] == '/');
+
+ switch (rc)
+ {
+ case SCMOPT_CONVERT_EOL:
+ pSettings->fConvertEol = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NO_CONVERT_EOL:
+ pSettings->fConvertEol = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_CONVERT_TABS:
+ pSettings->fConvertTabs = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NO_CONVERT_TABS:
+ pSettings->fConvertTabs = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_FORCE_FINAL_EOL:
+ pSettings->fForceFinalEol = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NO_FORCE_FINAL_EOL:
+ pSettings->fForceFinalEol = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_FORCE_TRAILING_LINE:
+ pSettings->fForceTrailingLine = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NO_FORCE_TRAILING_LINE:
+ pSettings->fForceTrailingLine = false;
+ return VINF_SUCCESS;
+
+
+ case SCMOPT_STRIP_TRAILING_BLANKS:
+ pSettings->fStripTrailingBlanks = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NO_STRIP_TRAILING_BLANKS:
+ pSettings->fStripTrailingBlanks = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS:
+ pSettings->cMinBlankLinesBeforeFlowerBoxMakers = pValueUnion->u8;
+ return VINF_SUCCESS;
+
+
+ case SCMOPT_STRIP_TRAILING_LINES:
+ pSettings->fStripTrailingLines = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NO_STRIP_TRAILING_LINES:
+ pSettings->fStripTrailingLines = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_FIX_FLOWER_BOX_MARKERS:
+ pSettings->fFixFlowerBoxMarkers = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NO_FIX_FLOWER_BOX_MARKERS:
+ pSettings->fFixFlowerBoxMarkers = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_FIX_HEADER_GUARDS:
+ pSettings->fFixHeaderGuards = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NO_FIX_HEADER_GUARDS:
+ pSettings->fFixHeaderGuards = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_PRAGMA_ONCE:
+ pSettings->fPragmaOnce = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NO_PRAGMA_ONCE:
+ pSettings->fPragmaOnce = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_FIX_HEADER_GUARD_ENDIF:
+ pSettings->fFixHeaderGuardEndif = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NO_FIX_HEADER_GUARD_ENDIF:
+ pSettings->fFixHeaderGuardEndif = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_ENDIF_GUARD_COMMENT:
+ pSettings->fEndifGuardComment = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NO_ENDIF_GUARD_COMMENT:
+ pSettings->fEndifGuardComment = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_GUARD_PREFIX:
+ RTStrFree(pSettings->pszGuardPrefix);
+ pSettings->pszGuardPrefix = NULL;
+ return RTStrDupEx(&pSettings->pszGuardPrefix, pValueUnion->psz);
+
+ case SCMOPT_GUARD_RELATIVE_TO_DIR:
+ RTStrFree(pSettings->pszGuardRelativeToDir);
+ pSettings->pszGuardRelativeToDir = NULL;
+ if (*pValueUnion->psz != '\0')
+ {
+ if ( strcmp(pValueUnion->psz, "{dir}") == 0
+ || strcmp(pValueUnion->psz, "{parent}") == 0)
+ return RTStrDupEx(&pSettings->pszGuardRelativeToDir, pValueUnion->psz);
+ if (cchDir == 1 && *pchDir == '/')
+ {
+ pSettings->pszGuardRelativeToDir = RTPathAbsDup(pValueUnion->psz);
+ if (pSettings->pszGuardRelativeToDir)
+ return VINF_SUCCESS;
+ }
+ else
+ {
+ char *pszDir = RTStrDupN(pchDir, cchDir);
+ if (pszDir)
+ {
+ pSettings->pszGuardRelativeToDir = RTPathAbsExDup(pszDir, pValueUnion->psz, RTPATH_STR_F_STYLE_HOST);
+ RTStrFree(pszDir);
+ if (pSettings->pszGuardRelativeToDir)
+ return VINF_SUCCESS;
+ }
+ }
+ RTMsgError("Failed to abspath --guard-relative-to-dir value '%s' - probably out of memory\n", pValueUnion->psz);
+ return VERR_NO_STR_MEMORY;
+ }
+ return VINF_SUCCESS;
+
+ case SCMOPT_FIX_TODOS:
+ pSettings->fFixTodos = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NO_FIX_TODOS:
+ pSettings->fFixTodos = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_FIX_ERR_H:
+ pSettings->fFixErrH = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NO_FIX_ERR_H:
+ pSettings->fFixErrH = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_ONLY_GUEST_HOST_PAGE:
+ pSettings->fOnlyGuestHostPage = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NO_PAGE_RESTRICTIONS:
+ pSettings->fOnlyGuestHostPage = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_NO_ASM_MEM_PAGE_USE:
+ pSettings->fNoASMMemPageUse = true;
+ return VINF_SUCCESS;
+ case SCMOPT_UNRESTRICTED_ASM_MEM_PAGE_USE:
+ pSettings->fNoASMMemPageUse = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_NO_RC_USE:
+ pSettings->fOnlyHrcVrcInsteadOfRc = true;
+ return VINF_SUCCESS;
+ case SCMOPT_UNRESTRICTED_RC_USE:
+ pSettings->fOnlyHrcVrcInsteadOfRc = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_STANDARIZE_KMK:
+ pSettings->fStandarizeKmk = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NO_STANDARIZE_KMK:
+ pSettings->fStandarizeKmk = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_UPDATE_COPYRIGHT_YEAR:
+ pSettings->fUpdateCopyrightYear = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NO_UPDATE_COPYRIGHT_YEAR:
+ pSettings->fUpdateCopyrightYear = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_EXTERNAL_COPYRIGHT:
+ pSettings->fExternalCopyright = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NO_EXTERNAL_COPYRIGHT:
+ pSettings->fExternalCopyright = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_NO_UPDATE_LICENSE:
+ pSettings->enmUpdateLicense = kScmLicense_LeaveAlone;
+ return VINF_SUCCESS;
+ case SCMOPT_LICENSE_OSE_GPL:
+ pSettings->enmUpdateLicense = kScmLicense_OseGpl;
+ return VINF_SUCCESS;
+ case SCMOPT_LICENSE_OSE_DUAL_GPL_CDDL:
+ pSettings->enmUpdateLicense = kScmLicense_OseDualGplCddl;
+ return VINF_SUCCESS;
+ case SCMOPT_LICENSE_OSE_CDDL:
+ pSettings->enmUpdateLicense = kScmLicense_OseCddl;
+ return VINF_SUCCESS;
+ case SCMOPT_LICENSE_LGPL:
+ pSettings->enmUpdateLicense = kScmLicense_Lgpl;
+ return VINF_SUCCESS;
+ case SCMOPT_LICENSE_MIT:
+ pSettings->enmUpdateLicense = kScmLicense_Mit;
+ return VINF_SUCCESS;
+ case SCMOPT_LICENSE_BASED_ON_MIT:
+ pSettings->enmUpdateLicense = kScmLicense_BasedOnMit;
+ return VINF_SUCCESS;
+
+ case SCMOPT_LGPL_DISCLAIMER:
+ pSettings->fLgplDisclaimer = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NO_LGPL_DISCLAIMER:
+ pSettings->fLgplDisclaimer = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_ONLY_SVN_DIRS:
+ pSettings->fOnlySvnDirs = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NOT_ONLY_SVN_DIRS:
+ pSettings->fOnlySvnDirs = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_ONLY_SVN_FILES:
+ pSettings->fOnlySvnFiles = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NOT_ONLY_SVN_FILES:
+ pSettings->fOnlySvnFiles = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_SET_SVN_EOL:
+ pSettings->fSetSvnEol = true;
+ return VINF_SUCCESS;
+ case SCMOPT_DONT_SET_SVN_EOL:
+ pSettings->fSetSvnEol = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_SET_SVN_EXECUTABLE:
+ pSettings->fSetSvnExecutable = true;
+ return VINF_SUCCESS;
+ case SCMOPT_DONT_SET_SVN_EXECUTABLE:
+ pSettings->fSetSvnExecutable = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_SET_SVN_KEYWORDS:
+ pSettings->fSetSvnKeywords = true;
+ return VINF_SUCCESS;
+ case SCMOPT_DONT_SET_SVN_KEYWORDS:
+ pSettings->fSetSvnKeywords = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_SKIP_SVN_SYNC_PROCESS:
+ pSettings->fSkipSvnSyncProcess = true;
+ return VINF_SUCCESS;
+ case SCMOPT_DONT_SKIP_SVN_SYNC_PROCESS:
+ pSettings->fSkipSvnSyncProcess = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_SKIP_UNICODE_CHECKS:
+ pSettings->fSkipUnicodeChecks = true;
+ return VINF_SUCCESS;
+ case SCMOPT_DONT_SKIP_UNICODE_CHECKS:
+ pSettings->fSkipUnicodeChecks = false;
+ return VINF_SUCCESS;
+
+ case SCMOPT_TAB_SIZE:
+ if ( pValueUnion->u8 < 1
+ || pValueUnion->u8 >= RT_ELEMENTS(g_szTabSpaces))
+ {
+ RTMsgError("Invalid tab size: %u - must be in {1..%u}\n",
+ pValueUnion->u8, RT_ELEMENTS(g_szTabSpaces) - 1);
+ return VERR_OUT_OF_RANGE;
+ }
+ pSettings->cchTab = pValueUnion->u8;
+ return VINF_SUCCESS;
+
+ case SCMOPT_WIDTH:
+ if (pValueUnion->u8 < 20 || pValueUnion->u8 > 200)
+ {
+ RTMsgError("Invalid width size: %u - must be in {20..200} range\n", pValueUnion->u8);
+ return VERR_OUT_OF_RANGE;
+ }
+ pSettings->cchWidth = pValueUnion->u8;
+ return VINF_SUCCESS;
+
+ case SCMOPT_FILTER_OUT_DIRS:
+ case SCMOPT_FILTER_FILES:
+ case SCMOPT_FILTER_OUT_FILES:
+ {
+ char **ppsz = NULL;
+ switch (rc)
+ {
+ case SCMOPT_FILTER_OUT_DIRS: ppsz = &pSettings->pszFilterOutDirs; break;
+ case SCMOPT_FILTER_FILES: ppsz = &pSettings->pszFilterFiles; break;
+ case SCMOPT_FILTER_OUT_FILES: ppsz = &pSettings->pszFilterOutFiles; break;
+ }
+
+ /*
+ * An empty string zaps the current list.
+ */
+ if (!*pValueUnion->psz)
+ return RTStrATruncate(ppsz, 0);
+
+ /*
+ * Non-empty strings are appended to the pattern list.
+ *
+ * Strip leading and trailing pattern separators before attempting
+ * to append it. If it's just separators, don't do anything.
+ */
+ const char *pszSrc = pValueUnion->psz;
+ while (*pszSrc == '|')
+ pszSrc++;
+ size_t cchSrc = strlen(pszSrc);
+ while (cchSrc > 0 && pszSrc[cchSrc - 1] == '|')
+ cchSrc--;
+ if (!cchSrc)
+ return VINF_SUCCESS;
+
+ /* Append it pattern by pattern, turning settings-relative paths into absolute ones. */
+ for (;;)
+ {
+ const char *pszEnd = (const char *)memchr(pszSrc, '|', cchSrc);
+ size_t cchPattern = pszEnd ? pszEnd - pszSrc : cchSrc;
+ int rc2;
+ if (*pszSrc == '/')
+ rc2 = RTStrAAppendExN(ppsz, 3,
+ "|", *ppsz && **ppsz != '\0' ? (size_t)1 : (size_t)0,
+ pchDir, cchDir - 1,
+ pszSrc, cchPattern);
+ else
+ rc2 = RTStrAAppendExN(ppsz, 2,
+ "|", *ppsz && **ppsz != '\0' ? (size_t)1 : (size_t)0,
+ pszSrc, cchPattern);
+ if (RT_FAILURE(rc2))
+ return rc2;
+
+ /* next */
+ cchSrc -= cchPattern;
+ if (!cchSrc)
+ return VINF_SUCCESS;
+ cchSrc -= 1;
+ pszSrc += cchPattern + 1;
+ }
+ /* not reached */
+ }
+
+ case SCMOPT_TREAT_AS:
+ if (pSettings->fFreeTreatAs)
+ {
+ scmCfgEntryDelete((PSCMCFGENTRY)pSettings->pTreatAs);
+ pSettings->pTreatAs = NULL;
+ pSettings->fFreeTreatAs = false;
+ }
+
+ if (*pValueUnion->psz)
+ {
+ /* first check the names, then patterns (legacy). */
+ for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
+ if (strcmp(g_aConfigs[iCfg].pszName, pValueUnion->psz) == 0)
+ {
+ pSettings->pTreatAs = &g_aConfigs[iCfg];
+ return VINF_SUCCESS;
+ }
+ for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
+ if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX,
+ pValueUnion->psz, RTSTR_MAX, NULL))
+ {
+ pSettings->pTreatAs = &g_aConfigs[iCfg];
+ return VINF_SUCCESS;
+ }
+ /* Special help for listing the possibilities? */
+ if (strcmp(pValueUnion->psz, "help") == 0)
+ {
+ RTPrintf("Possible --treat-as values:\n");
+ for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
+ RTPrintf(" %s (%s)\n", g_aConfigs[iCfg].pszName, g_aConfigs[iCfg].pszFilePattern);
+ }
+ return VERR_NOT_FOUND;
+ }
+
+ pSettings->pTreatAs = NULL;
+ return VINF_SUCCESS;
+
+ case SCMOPT_ADD_ACTION:
+ for (uint32_t iAction = 0; iAction < RT_ELEMENTS(g_papRewriterActions); iAction++)
+ if (strcmp(g_papRewriterActions[iAction]->pszName, pValueUnion->psz) == 0)
+ {
+ PSCMCFGENTRY pEntry = (PSCMCFGENTRY)pSettings->pTreatAs;
+ if (!pSettings->fFreeTreatAs)
+ {
+ pEntry = scmCfgEntryDup(pEntry);
+ if (!pEntry)
+ return VERR_NO_MEMORY;
+ pSettings->pTreatAs = pEntry;
+ pSettings->fFreeTreatAs = true;
+ }
+ return scmCfgEntryAddAction(pEntry, g_papRewriterActions[iAction]);
+ }
+ RTMsgError("Unknown --add-action value '%s'. Try --help-actions for a list.", pValueUnion->psz);
+ return VERR_NOT_FOUND;
+
+ case SCMOPT_DEL_ACTION:
+ {
+ uint32_t cActions = 0;
+ for (uint32_t iAction = 0; iAction < RT_ELEMENTS(g_papRewriterActions); iAction++)
+ if (RTStrSimplePatternMatch(pValueUnion->psz, g_papRewriterActions[iAction]->pszName))
+ {
+ cActions++;
+ PSCMCFGENTRY pEntry = (PSCMCFGENTRY)pSettings->pTreatAs;
+ if (!pSettings->fFreeTreatAs)
+ {
+ pEntry = scmCfgEntryDup(pEntry);
+ if (!pEntry)
+ return VERR_NO_MEMORY;
+ pSettings->pTreatAs = pEntry;
+ pSettings->fFreeTreatAs = true;
+ }
+ scmCfgEntryDelAction(pEntry, g_papRewriterActions[iAction]);
+ if (!strchr(pValueUnion->psz, '*'))
+ return VINF_SUCCESS;
+ }
+ if (cActions > 0)
+ return VINF_SUCCESS;
+ RTMsgError("Unknown --del-action value '%s'. Try --help-actions for a list.", pValueUnion->psz);
+ return VERR_NOT_FOUND;
+ }
+
+ default:
+ return VERR_GETOPT_UNKNOWN_OPTION;
+ }
+}
+
+/**
+ * Parses an option string.
+ *
+ * @returns IPRT status code.
+ * @param pBase The base settings structure to apply the options
+ * to.
+ * @param pszOptions The options to parse.
+ * @param pchDir The absolute path to the directory relative
+ * components in pchLine should be relative to.
+ * @param cchDir The length of the @a pchDir string.
+ */
+static int scmSettingsBaseParseString(PSCMSETTINGSBASE pBase, const char *pszLine, const char *pchDir, size_t cchDir)
+{
+ int cArgs;
+ char **papszArgs;
+ int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetOptState;
+ rc = RTGetOptInit(&GetOptState, cArgs, papszArgs, &g_aScmOpts[0], RT_ELEMENTS(g_aScmOpts), 0, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
+ {
+ rc = scmSettingsBaseHandleOpt(pBase, rc, &ValueUnion, pchDir, cchDir);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ RTGetOptArgvFree(papszArgs);
+ }
+
+ return rc;
+}
+
+/**
+ * Parses an unterminated option string.
+ *
+ * @returns IPRT status code.
+ * @param pBase The base settings structure to apply the options
+ * to.
+ * @param pchLine The line.
+ * @param cchLine The line length.
+ * @param pchDir The absolute path to the directory relative
+ * components in pchLine should be relative to.
+ * @param cchDir The length of the @a pchDir string.
+ */
+static int scmSettingsBaseParseStringN(PSCMSETTINGSBASE pBase, const char *pchLine, size_t cchLine,
+ const char *pchDir, size_t cchDir)
+{
+ char *pszLine = RTStrDupN(pchLine, cchLine);
+ if (!pszLine)
+ return VERR_NO_MEMORY;
+ int rc = scmSettingsBaseParseString(pBase, pszLine, pchDir, cchDir);
+ RTStrFree(pszLine);
+ return rc;
+}
+
+/**
+ * Verifies the options string.
+ *
+ * @returns IPRT status code.
+ * @param pszOptions The options to verify .
+ */
+static int scmSettingsBaseVerifyString(const char *pszOptions)
+{
+ SCMSETTINGSBASE Base;
+ int rc = scmSettingsBaseInit(&Base);
+ if (RT_SUCCESS(rc))
+ {
+ rc = scmSettingsBaseParseString(&Base, pszOptions, "/", 1);
+ scmSettingsBaseDelete(&Base);
+ }
+ return rc;
+}
+
+/**
+ * Loads settings found in editor and SCM settings directives within the
+ * document (@a pStream).
+ *
+ * @returns IPRT status code.
+ * @param pBase The settings base to load settings into.
+ * @param pStream The stream to scan for settings directives.
+ */
+static int scmSettingsBaseLoadFromDocument(PSCMSETTINGSBASE pBase, PSCMSTREAM pStream)
+{
+ /** @todo Editor and SCM settings directives in documents. */
+ RT_NOREF2(pBase, pStream);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Creates a new settings file struct, cloning @a pSettings.
+ *
+ * @returns IPRT status code.
+ * @param ppSettings Where to return the new struct.
+ * @param pSettingsBase The settings to inherit from.
+ */
+static int scmSettingsCreate(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pSettingsBase)
+{
+ PSCMSETTINGS pSettings = (PSCMSETTINGS)RTMemAlloc(sizeof(*pSettings));
+ if (!pSettings)
+ return VERR_NO_MEMORY;
+ int rc = scmSettingsBaseInitAndCopy(&pSettings->Base, pSettingsBase);
+ if (RT_SUCCESS(rc))
+ {
+ pSettings->pDown = NULL;
+ pSettings->pUp = NULL;
+ pSettings->paPairs = NULL;
+ pSettings->cPairs = 0;
+ *ppSettings = pSettings;
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pSettings);
+ return rc;
+}
+
+/**
+ * Destroys a settings structure.
+ *
+ * @param pSettings The settings structure to destroy. NULL is OK.
+ */
+static void scmSettingsDestroy(PSCMSETTINGS pSettings)
+{
+ if (pSettings)
+ {
+ scmSettingsBaseDelete(&pSettings->Base);
+ for (size_t i = 0; i < pSettings->cPairs; i++)
+ {
+ RTStrFree(pSettings->paPairs[i].pszPattern);
+ RTStrFree(pSettings->paPairs[i].pszOptions);
+ RTStrFree(pSettings->paPairs[i].pszRelativeTo);
+ pSettings->paPairs[i].pszPattern = NULL;
+ pSettings->paPairs[i].pszOptions = NULL;
+ pSettings->paPairs[i].pszRelativeTo = NULL;
+ }
+ RTMemFree(pSettings->paPairs);
+ pSettings->paPairs = NULL;
+ RTMemFree(pSettings);
+ }
+}
+
+/**
+ * Adds a pattern/options pair to the settings structure.
+ *
+ * @returns IPRT status code.
+ * @param pSettings The settings.
+ * @param pchLine The line containing the unparsed pair.
+ * @param cchLine The length of the line.
+ * @param offColon The offset of the colon into the line.
+ * @param pchDir The absolute path to the directory relative
+ * components in pchLine should be relative to.
+ * @param cchDir The length of the @a pchDir string.
+ */
+static int scmSettingsAddPair(PSCMSETTINGS pSettings, const char *pchLine, size_t cchLine, size_t offColon,
+ const char *pchDir, size_t cchDir)
+{
+ Assert(pchLine[offColon] == ':' && offColon < cchLine);
+ Assert(pchDir[cchDir - 1] == '/');
+
+ /*
+ * Split the string.
+ */
+ size_t cchPattern = offColon;
+ size_t cchOptions = cchLine - cchPattern - 1;
+
+ /* strip spaces everywhere */
+ while (cchPattern > 0 && RT_C_IS_SPACE(pchLine[cchPattern - 1]))
+ cchPattern--;
+ while (cchPattern > 0 && RT_C_IS_SPACE(*pchLine))
+ cchPattern--, pchLine++;
+
+ const char *pchOptions = &pchLine[offColon + 1];
+ while (cchOptions > 0 && RT_C_IS_SPACE(pchOptions[cchOptions - 1]))
+ cchOptions--;
+ while (cchOptions > 0 && RT_C_IS_SPACE(*pchOptions))
+ cchOptions--, pchOptions++;
+
+ /* Quietly ignore empty patterns and empty options. */
+ if (!cchOptions || !cchPattern)
+ return VINF_SUCCESS;
+
+ /*
+ * Prepair the pair and verify the option string.
+ */
+ uint32_t iPair = pSettings->cPairs;
+ if ((iPair % 32) == 0)
+ {
+ void *pvNew = RTMemRealloc(pSettings->paPairs, (iPair + 32) * sizeof(pSettings->paPairs[0]));
+ if (!pvNew)
+ return VERR_NO_MEMORY;
+ pSettings->paPairs = (PSCMPATRNOPTPAIR)pvNew;
+ }
+
+ pSettings->paPairs[iPair].pszPattern = RTStrDupN(pchLine, cchPattern);
+ pSettings->paPairs[iPair].pszOptions = RTStrDupN(pchOptions, cchOptions);
+ pSettings->paPairs[iPair].pszRelativeTo = RTStrDupN(pchDir, cchDir);
+ int rc;
+ if ( pSettings->paPairs[iPair].pszPattern
+ && pSettings->paPairs[iPair].pszOptions
+ && pSettings->paPairs[iPair].pszRelativeTo)
+ rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions);
+ else
+ rc = VERR_NO_MEMORY;
+
+ /*
+ * If it checked out fine, expand any relative paths in the pattern.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ size_t cPattern = 1;
+ size_t cRelativePaths = 0;
+ const char *pszSrc = pSettings->paPairs[iPair].pszPattern;
+ for (;;)
+ {
+ if (*pszSrc == '/')
+ cRelativePaths++;
+ pszSrc = strchr(pszSrc, '|');
+ if (!pszSrc)
+ break;
+ pszSrc++;
+ cPattern++;
+ }
+ pSettings->paPairs[iPair].fMultiPattern = cPattern > 1;
+ if (cRelativePaths > 0)
+ {
+ char *pszNewPattern = RTStrAlloc(cchPattern + cRelativePaths * (cchDir - 1) + 1);
+ if (pszNewPattern)
+ {
+ char *pszDst = pszNewPattern;
+ pszSrc = pSettings->paPairs[iPair].pszPattern;
+ for (;;)
+ {
+ if (*pszSrc == '/')
+ {
+ memcpy(pszDst, pchDir, cchDir);
+ pszDst += cchDir;
+ pszSrc += 1;
+ }
+
+ /* Look for the next relative path. */
+ const char *pszSrcNext = strchr(pszSrc, '|');
+ while (pszSrcNext && pszSrcNext[1] != '/')
+ pszSrcNext = strchr(pszSrcNext, '|');
+ if (!pszSrcNext)
+ break;
+
+ /* Copy stuff between current and the next path. */
+ pszSrcNext++;
+ memcpy(pszDst, pszSrc, pszSrcNext - pszSrc);
+ pszDst += pszSrcNext - pszSrc;
+ pszSrc = pszSrcNext;
+ }
+
+ /* Copy the final portion and replace the pattern. */
+ strcpy(pszDst, pszSrc);
+
+ RTStrFree(pSettings->paPairs[iPair].pszPattern);
+ pSettings->paPairs[iPair].pszPattern = pszNewPattern;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ if (RT_SUCCESS(rc))
+ /*
+ * Commit the pair.
+ */
+ pSettings->cPairs = iPair + 1;
+ else
+ {
+ RTStrFree(pSettings->paPairs[iPair].pszPattern);
+ RTStrFree(pSettings->paPairs[iPair].pszOptions);
+ RTStrFree(pSettings->paPairs[iPair].pszRelativeTo);
+ }
+ return rc;
+}
+
+/**
+ * Loads in the settings from @a pszFilename.
+ *
+ * @returns IPRT status code.
+ * @param pSettings Where to load the settings file.
+ * @param pszFilename The file to load.
+ */
+static int scmSettingsLoadFile(PSCMSETTINGS pSettings, const char *pszFilename)
+{
+ ScmVerbose(NULL, 3, "Loading settings file '%s'...\n", pszFilename);
+
+ /* Turn filename into an absolute path and drop the filename. */
+ char szAbsPath[RTPATH_MAX];
+ int rc = RTPathAbs(pszFilename, szAbsPath, sizeof(szAbsPath));
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("%s: RTPathAbs -> %Rrc\n", pszFilename, rc);
+ return rc;
+ }
+ RTPathChangeToUnixSlashes(szAbsPath, true);
+ size_t cchDir = RTPathFilename(szAbsPath) - &szAbsPath[0];
+
+ /* Try open it.*/
+ SCMSTREAM Stream;
+ rc = ScmStreamInitForReading(&Stream, pszFilename);
+ if (RT_SUCCESS(rc))
+ {
+ SCMEOL enmEol;
+ const char *pchLine;
+ size_t cchLine;
+ while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
+ {
+ /* Ignore leading spaces. */
+ while (cchLine > 0 && RT_C_IS_SPACE(*pchLine))
+ pchLine++, cchLine--;
+
+ /* Ignore empty lines and comment lines. */
+ if (cchLine < 1 || *pchLine == '#')
+ continue;
+
+ /* Deal with escaped newlines. */
+ size_t iFirstLine = ~(size_t)0;
+ char *pszFreeLine = NULL;
+ if ( pchLine[cchLine - 1] == '\\'
+ && ( cchLine < 2
+ || pchLine[cchLine - 2] != '\\') )
+ {
+ iFirstLine = ScmStreamTellLine(&Stream);
+
+ cchLine--;
+ while (cchLine > 0 && RT_C_IS_SPACE(pchLine[cchLine - 1]))
+ cchLine--;
+
+ size_t cchTotal = cchLine;
+ pszFreeLine = RTStrDupN(pchLine, cchLine);
+ if (pszFreeLine)
+ {
+ /* Append following lines. */
+ while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
+ {
+ while (cchLine > 0 && RT_C_IS_SPACE(*pchLine))
+ pchLine++, cchLine--;
+
+ bool const fDone = cchLine == 0
+ || pchLine[cchLine - 1] != '\\'
+ || (cchLine >= 2 && pchLine[cchLine - 2] == '\\');
+ if (!fDone)
+ {
+ cchLine--;
+ while (cchLine > 0 && RT_C_IS_SPACE(pchLine[cchLine - 1]))
+ cchLine--;
+ }
+
+ rc = RTStrRealloc(&pszFreeLine, cchTotal + 1 + cchLine + 1);
+ if (RT_FAILURE(rc))
+ break;
+ pszFreeLine[cchTotal++] = ' ';
+ memcpy(&pszFreeLine[cchTotal], pchLine, cchLine);
+ cchTotal += cchLine;
+ pszFreeLine[cchTotal] = '\0';
+
+ if (fDone)
+ break;
+ }
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+
+ if (RT_FAILURE(rc))
+ {
+ RTStrFree(pszFreeLine);
+ rc = RTMsgErrorRc(VERR_NO_MEMORY, "%s: Ran out of memory deal with escaped newlines", pszFilename);
+ break;
+ }
+
+ pchLine = pszFreeLine;
+ cchLine = cchTotal;
+ }
+
+ /* What kind of line is it? */
+ const char *pchColon = (const char *)memchr(pchLine, ':', cchLine);
+ if (pchColon)
+ rc = scmSettingsAddPair(pSettings, pchLine, cchLine, pchColon - pchLine, szAbsPath, cchDir);
+ else
+ rc = scmSettingsBaseParseStringN(&pSettings->Base, pchLine, cchLine, szAbsPath, cchDir);
+ if (pszFreeLine)
+ RTStrFree(pszFreeLine);
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("%s:%d: %Rrc\n",
+ pszFilename, iFirstLine == ~(size_t)0 ? ScmStreamTellLine(&Stream) : iFirstLine, rc);
+ break;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = ScmStreamGetStatus(&Stream);
+ if (RT_FAILURE(rc))
+ RTMsgError("%s: ScmStreamGetStatus- > %Rrc\n", pszFilename, rc);
+ }
+ ScmStreamDelete(&Stream);
+ }
+ else
+ RTMsgError("%s: ScmStreamInitForReading -> %Rrc\n", pszFilename, rc);
+ return rc;
+}
+
+#if 0 /* unused */
+/**
+ * Parse the specified settings file creating a new settings struct from it.
+ *
+ * @returns IPRT status code
+ * @param ppSettings Where to return the new settings.
+ * @param pszFilename The file to parse.
+ * @param pSettingsBase The base settings we inherit from.
+ */
+static int scmSettingsCreateFromFile(PSCMSETTINGS *ppSettings, const char *pszFilename, PCSCMSETTINGSBASE pSettingsBase)
+{
+ PSCMSETTINGS pSettings;
+ int rc = scmSettingsCreate(&pSettings, pSettingsBase);
+ if (RT_SUCCESS(rc))
+ {
+ rc = scmSettingsLoadFile(pSettings, pszFilename, RTPathFilename(pszFilename) - pszFilename);
+ if (RT_SUCCESS(rc))
+ {
+ *ppSettings = pSettings;
+ return VINF_SUCCESS;
+ }
+
+ scmSettingsDestroy(pSettings);
+ }
+ *ppSettings = NULL;
+ return rc;
+}
+#endif
+
+
+/**
+ * Create an initial settings structure when starting processing a new file or
+ * directory.
+ *
+ * This will look for .scm-settings files from the root and down to the
+ * specified directory, combining them into the returned settings structure.
+ *
+ * @returns IPRT status code.
+ * @param ppSettings Where to return the pointer to the top stack
+ * object.
+ * @param pBaseSettings The base settings we inherit from (globals
+ * typically).
+ * @param pszPath The absolute path to the new directory or file.
+ */
+static int scmSettingsCreateForPath(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pBaseSettings, const char *pszPath)
+{
+ *ppSettings = NULL; /* try shut up gcc. */
+
+ /*
+ * We'll be working with a stack copy of the path.
+ */
+ char szFile[RTPATH_MAX];
+ size_t cchDir = strlen(pszPath);
+ if (cchDir >= sizeof(szFile) - sizeof(SCM_SETTINGS_FILENAME))
+ return VERR_FILENAME_TOO_LONG;
+
+ /*
+ * Create the bottom-most settings.
+ */
+ PSCMSETTINGS pSettings;
+ int rc = scmSettingsCreate(&pSettings, pBaseSettings);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Enumerate the path components from the root and down. Load any setting
+ * files we find.
+ */
+ size_t cComponents = RTPathCountComponents(pszPath);
+ for (size_t i = 1; i <= cComponents; i++)
+ {
+ rc = RTPathCopyComponents(szFile, sizeof(szFile), pszPath, i);
+ if (RT_SUCCESS(rc))
+ rc = RTPathAppend(szFile, sizeof(szFile), SCM_SETTINGS_FILENAME);
+ if (RT_FAILURE(rc))
+ break;
+ RTPathChangeToUnixSlashes(szFile, true);
+
+ if (RTFileExists(szFile))
+ {
+ rc = scmSettingsLoadFile(pSettings, szFile);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppSettings = pSettings;
+ else
+ scmSettingsDestroy(pSettings);
+ return rc;
+}
+
+/**
+ * Pushes a new settings set onto the stack.
+ *
+ * @param ppSettingsStack The pointer to the pointer to the top stack
+ * element. This will be used as input and output.
+ * @param pSettings The settings to push onto the stack.
+ */
+static void scmSettingsStackPush(PSCMSETTINGS *ppSettingsStack, PSCMSETTINGS pSettings)
+{
+ PSCMSETTINGS pOld = *ppSettingsStack;
+ pSettings->pDown = pOld;
+ pSettings->pUp = NULL;
+ if (pOld)
+ pOld->pUp = pSettings;
+ *ppSettingsStack = pSettings;
+}
+
+/**
+ * Pushes the settings of the specified directory onto the stack.
+ *
+ * We will load any .scm-settings in the directory. A stack entry is added even
+ * if no settings file was found.
+ *
+ * @returns IPRT status code.
+ * @param ppSettingsStack The pointer to the pointer to the top stack
+ * element. This will be used as input and output.
+ * @param pszDir The directory to do this for.
+ */
+static int scmSettingsStackPushDir(PSCMSETTINGS *ppSettingsStack, const char *pszDir)
+{
+ char szFile[RTPATH_MAX];
+ int rc = RTPathJoin(szFile, sizeof(szFile), pszDir, SCM_SETTINGS_FILENAME);
+ if (RT_SUCCESS(rc))
+ {
+ RTPathChangeToUnixSlashes(szFile, true);
+
+ PSCMSETTINGS pSettings;
+ rc = scmSettingsCreate(&pSettings, &(*ppSettingsStack)->Base);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFileExists(szFile))
+ rc = scmSettingsLoadFile(pSettings, szFile);
+ if (RT_SUCCESS(rc))
+ {
+ scmSettingsStackPush(ppSettingsStack, pSettings);
+ return VINF_SUCCESS;
+ }
+
+ scmSettingsDestroy(pSettings);
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Pops a settings set off the stack.
+ *
+ * @returns The popped settings.
+ * @param ppSettingsStack The pointer to the pointer to the top stack
+ * element. This will be used as input and output.
+ */
+static PSCMSETTINGS scmSettingsStackPop(PSCMSETTINGS *ppSettingsStack)
+{
+ PSCMSETTINGS pRet = *ppSettingsStack;
+ PSCMSETTINGS pNew = pRet ? pRet->pDown : NULL;
+ *ppSettingsStack = pNew;
+ if (pNew)
+ pNew->pUp = NULL;
+ if (pRet)
+ {
+ pRet->pUp = NULL;
+ pRet->pDown = NULL;
+ }
+ return pRet;
+}
+
+/**
+ * Pops and destroys the top entry of the stack.
+ *
+ * @param ppSettingsStack The pointer to the pointer to the top stack
+ * element. This will be used as input and output.
+ */
+static void scmSettingsStackPopAndDestroy(PSCMSETTINGS *ppSettingsStack)
+{
+ scmSettingsDestroy(scmSettingsStackPop(ppSettingsStack));
+}
+
+/**
+ * Constructs the base settings for the specified file name.
+ *
+ * @returns IPRT status code.
+ * @param pSettingsStack The top element on the settings stack.
+ * @param pszFilename The file name.
+ * @param pszBasename The base name (pointer within @a pszFilename).
+ * @param cchBasename The length of the base name. (For passing to
+ * RTStrSimplePatternMultiMatch.)
+ * @param pBase Base settings to initialize.
+ */
+static int scmSettingsStackMakeFileBase(PCSCMSETTINGS pSettingsStack, const char *pszFilename,
+ const char *pszBasename, size_t cchBasename, PSCMSETTINGSBASE pBase)
+{
+ ScmVerbose(NULL, 5, "scmSettingsStackMakeFileBase(%s, %.*s)\n", pszFilename, cchBasename, pszBasename);
+
+ int rc = scmSettingsBaseInitAndCopy(pBase, &pSettingsStack->Base);
+ if (RT_SUCCESS(rc))
+ {
+ /* find the bottom entry in the stack. */
+ PCSCMSETTINGS pCur = pSettingsStack;
+ while (pCur->pDown)
+ pCur = pCur->pDown;
+
+ /* Work our way up thru the stack and look for matching pairs. */
+ while (pCur)
+ {
+ size_t const cPairs = pCur->cPairs;
+ if (cPairs)
+ {
+ for (size_t i = 0; i < cPairs; i++)
+ if ( !pCur->paPairs[i].fMultiPattern
+ ? RTStrSimplePatternNMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
+ pszBasename, cchBasename)
+ || RTStrSimplePatternMatch(pCur->paPairs[i].pszPattern, pszFilename)
+ : RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
+ pszBasename, cchBasename, NULL)
+ || RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
+ pszFilename, RTSTR_MAX, NULL))
+ {
+ ScmVerbose(NULL, 5, "scmSettingsStackMakeFileBase: Matched '%s' : '%s'\n",
+ pCur->paPairs[i].pszPattern, pCur->paPairs[i].pszOptions);
+ rc = scmSettingsBaseParseString(pBase, pCur->paPairs[i].pszOptions,
+ pCur->paPairs[i].pszRelativeTo, strlen(pCur->paPairs[i].pszRelativeTo));
+ if (RT_FAILURE(rc))
+ break;
+ }
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ /* advance */
+ pCur = pCur->pUp;
+ }
+ }
+ if (RT_FAILURE(rc))
+ scmSettingsBaseDelete(pBase);
+ return rc;
+}
+
+
+/* -=-=-=-=-=- misc -=-=-=-=-=- */
+
+
+/**
+ * Prints the per file banner needed and the message level is high enough.
+ *
+ * @param pState The rewrite state.
+ * @param iLevel The required verbosity level.
+ */
+void ScmVerboseBanner(PSCMRWSTATE pState, int iLevel)
+{
+ if (iLevel <= g_iVerbosity && !pState->fFirst)
+ {
+ RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
+ pState->fFirst = true;
+ }
+}
+
+
+/**
+ * Prints a verbose message if the level is high enough.
+ *
+ * @param pState The rewrite state. Optional.
+ * @param iLevel The required verbosity level.
+ * @param pszFormat The message format string. Can be NULL if we
+ * only want to trigger the per file message.
+ * @param ... Format arguments.
+ */
+void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)
+{
+ if (iLevel <= g_iVerbosity)
+ {
+ if (pState && !pState->fFirst)
+ {
+ RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
+ pState->fFirst = true;
+ }
+ RTPrintf(pState
+ ? "%s: info: "
+ : "%s: info: ",
+ g_szProgName);
+ va_list va;
+ va_start(va, pszFormat);
+ RTPrintfV(pszFormat, va);
+ va_end(va);
+ }
+}
+
+
+/**
+ * Prints an error message.
+ *
+ * @returns kScmUnmodified
+ * @param pState The rewrite state. Optional.
+ * @param rc The error code.
+ * @param pszFormat The message format string.
+ * @param ... Format arguments.
+ */
+SCMREWRITERRES ScmError(PSCMRWSTATE pState, int rc, const char *pszFormat, ...)
+{
+ if (RT_SUCCESS(pState->rc))
+ pState->rc = rc;
+
+ if (!pState->fFirst)
+ {
+ RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
+ pState->fFirst = true;
+ }
+ va_list va;
+ va_start(va, pszFormat);
+ RTPrintf("%s: error: %s: %N", g_szProgName, pState->pszFilename, pszFormat, &va);
+ va_end(va);
+
+ return kScmUnmodified;
+}
+
+
+/**
+ * Prints message indicating that something requires manual fixing.
+ *
+ * @returns false
+ * @param pState The rewrite state. Optional.
+ * @param rc The error code.
+ * @param pszFormat The message format string.
+ * @param ... Format arguments.
+ */
+bool ScmFixManually(PSCMRWSTATE pState, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ ScmFixManuallyV(pState, pszFormat, va);
+ va_end(va);
+ return false;
+}
+
+
+/**
+ * Prints message indicating that something requires manual fixing.
+ *
+ * @returns false
+ * @param pState The rewrite state. Optional.
+ * @param rc The error code.
+ * @param pszFormat The message format string.
+ * @param va Format arguments.
+ */
+bool ScmFixManuallyV(PSCMRWSTATE pState, const char *pszFormat, va_list va)
+{
+ pState->fNeedsManualRepair = true;
+
+ if (!pState->fFirst)
+ {
+ RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
+ pState->fFirst = true;
+ }
+ va_list vaCopy;
+ va_copy(vaCopy, va);
+ RTPrintf("%s: error/fixme: %s: %N", g_szProgName, pState->pszFilename, pszFormat, &vaCopy);
+ va_end(vaCopy);
+
+ return false;
+}
+
+
+/* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */
+
+
+/**
+ * Processes a file.
+ *
+ * @returns IPRT status code.
+ * @param pState The rewriter state.
+ * @param pszFilename The file name.
+ * @param pszBasename The base name (pointer within @a pszFilename).
+ * @param cchBasename The length of the base name. (For passing to
+ * RTStrSimplePatternMultiMatch.)
+ * @param pBaseSettings The base settings to use. It's OK to modify
+ * these.
+ */
+static int scmProcessFileInner(PSCMRWSTATE pState, const char *pszFilename, const char *pszBasename, size_t cchBasename,
+ PSCMSETTINGSBASE pBaseSettings)
+{
+ /*
+ * Do the file level filtering.
+ */
+ if ( pBaseSettings->pszFilterFiles
+ && *pBaseSettings->pszFilterFiles
+ && !RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterFiles, RTSTR_MAX, pszBasename, cchBasename, NULL))
+ {
+ ScmVerbose(NULL, 5, "skipping '%s': file filter mismatch\n", pszFilename);
+ g_cFilesSkipped++;
+ return VINF_SUCCESS;
+ }
+ if ( pBaseSettings->pszFilterOutFiles
+ && *pBaseSettings->pszFilterOutFiles
+ && ( RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszBasename, cchBasename, NULL)
+ || RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszFilename, RTSTR_MAX, NULL)) )
+ {
+ ScmVerbose(NULL, 5, "skipping '%s': filterd out\n", pszFilename);
+ g_cFilesSkipped++;
+ return VINF_SUCCESS;
+ }
+ if ( pBaseSettings->fOnlySvnFiles
+ && !ScmSvnIsInWorkingCopy(pState))
+ {
+ ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename);
+ g_cFilesNotInSvn++;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Create an input stream from the file and check that it's text.
+ */
+ SCMSTREAM Stream1;
+ int rc = ScmStreamInitForReading(&Stream1, pszFilename);
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("Failed to read '%s': %Rrc\n", pszFilename, rc);
+ return rc;
+ }
+ bool const fIsText = ScmStreamIsText(&Stream1);
+
+ /*
+ * Try find a matching rewrite config for this filename.
+ */
+ PCSCMCFGENTRY pCfg = pBaseSettings->pTreatAs;
+ if (!pCfg)
+ {
+ for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
+ if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, pszBasename, cchBasename, NULL))
+ {
+ pCfg = &g_aConfigs[iCfg];
+ break;
+ }
+ if (!pCfg)
+ {
+ /* On failure try check for hash-bang stuff before giving up. */
+ if (fIsText)
+ {
+ SCMEOL enmIgn;
+ size_t cchFirst;
+ const char *pchFirst = ScmStreamGetLine(&Stream1, &cchFirst, &enmIgn);
+ if (cchFirst >= 9 && pchFirst && *pchFirst == '#')
+ {
+ do
+ {
+ pchFirst++;
+ cchFirst--;
+ } while (cchFirst > 0 && RT_C_IS_BLANK(*pchFirst));
+ if (*pchFirst == '!')
+ {
+ do
+ {
+ pchFirst++;
+ cchFirst--;
+ } while (cchFirst > 0 && RT_C_IS_BLANK(*pchFirst));
+ const char *pszTreatAs = NULL;
+ if ( (cchFirst >= 7 && strncmp(pchFirst, "/bin/sh", 7) == 0)
+ || (cchFirst >= 9 && strncmp(pchFirst, "/bin/bash", 9) == 0)
+ || (cchFirst >= 4+9 && strncmp(pchFirst, "/usr/bin/bash", 4+9) == 0) )
+ pszTreatAs = "shell";
+ else if ( (cchFirst >= 15 && strncmp(pchFirst, "/usr/bin/python", 15) == 0)
+ || (cchFirst >= 19 && strncmp(pchFirst, "/usr/bin/env python", 19) == 0) )
+ pszTreatAs = "python";
+ else if ( (cchFirst >= 13 && strncmp(pchFirst, "/usr/bin/perl", 13) == 0)
+ || (cchFirst >= 17 && strncmp(pchFirst, "/usr/bin/env perl", 17) == 0) )
+ pszTreatAs = "perl";
+ if (pszTreatAs)
+ {
+ for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
+ if (strcmp(pszTreatAs, g_aConfigs[iCfg].pszName) == 0)
+ {
+ pCfg = &g_aConfigs[iCfg];
+ break;
+ }
+ Assert(pCfg);
+ }
+ }
+ }
+ ScmStreamRewindForReading(&Stream1);
+ }
+ if (!pCfg)
+ {
+ ScmVerbose(NULL, 2, "skipping '%s': no rewriters configured\n", pszFilename);
+ g_cFilesNoRewriters++;
+ ScmStreamDelete(&Stream1);
+ return VINF_SUCCESS;
+ }
+ }
+ ScmVerbose(pState, 4, "matched \"%s\" (%s)\n", pCfg->pszFilePattern, pCfg->pszName);
+ }
+ else
+ ScmVerbose(pState, 4, "treat-as \"%s\"\n", pCfg->pszName);
+
+ if (fIsText || pCfg->fBinary)
+ {
+ ScmVerboseBanner(pState, 3);
+
+ /*
+ * Gather SCM and editor settings from the stream.
+ */
+ rc = scmSettingsBaseLoadFromDocument(pBaseSettings, &Stream1);
+ if (RT_SUCCESS(rc))
+ {
+ ScmStreamRewindForReading(&Stream1);
+
+ /*
+ * Create two more streams for output and push the text thru all the
+ * rewriters, switching the two streams around when something is
+ * actually rewritten. Stream1 remains unchanged.
+ */
+ SCMSTREAM Stream2;
+ rc = ScmStreamInitForWriting(&Stream2, &Stream1);
+ if (RT_SUCCESS(rc))
+ {
+ SCMSTREAM Stream3;
+ rc = ScmStreamInitForWriting(&Stream3, &Stream1);
+ if (RT_SUCCESS(rc))
+ {
+ bool fModified = false;
+ PSCMSTREAM pIn = &Stream1;
+ PSCMSTREAM pOut = &Stream2;
+ for (size_t iRw = 0; iRw < pCfg->cRewriters; iRw++)
+ {
+ pState->rc = VINF_SUCCESS;
+ SCMREWRITERRES enmRes = pCfg->paRewriters[iRw]->pfnRewriter(pState, pIn, pOut, pBaseSettings);
+ if (RT_FAILURE(pState->rc))
+ break;
+ if (enmRes == kScmMaybeModified)
+ enmRes = ScmStreamAreIdentical(pIn, pOut) ? kScmUnmodified : kScmModified;
+ if (enmRes == kScmModified)
+ {
+ PSCMSTREAM pTmp = pOut;
+ pOut = pIn == &Stream1 ? &Stream3 : pIn;
+ pIn = pTmp;
+ fModified = true;
+ }
+
+ ScmStreamRewindForReading(pIn);
+ ScmStreamRewindForWriting(pOut);
+ }
+
+ rc = pState->rc;
+ if (RT_SUCCESS(rc))
+ {
+ rc = ScmStreamGetStatus(&Stream1);
+ if (RT_SUCCESS(rc))
+ rc = ScmStreamGetStatus(&Stream2);
+ if (RT_SUCCESS(rc))
+ rc = ScmStreamGetStatus(&Stream3);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * If rewritten, write it back to disk.
+ */
+ if (fModified && !pCfg->fBinary)
+ {
+ if (!g_fDryRun)
+ {
+ ScmVerbose(pState, 1, "writing modified file to \"%s%s\"\n", pszFilename, g_pszChangedSuff);
+ rc = ScmStreamWriteToFile(pIn, "%s%s", pszFilename, g_pszChangedSuff);
+ if (RT_FAILURE(rc))
+ RTMsgError("Error writing '%s%s': %Rrc\n", pszFilename, g_pszChangedSuff, rc);
+ }
+ else
+ {
+ ScmVerboseBanner(pState, 1);
+ ScmDiffStreams(pszFilename, &Stream1, pIn, g_fDiffIgnoreEol,
+ g_fDiffIgnoreLeadingWS, g_fDiffIgnoreTrailingWS, g_fDiffSpecialChars,
+ pBaseSettings->cchTab, g_pStdOut);
+ ScmVerbose(pState, 2, "would have modified the file \"%s%s\"\n",
+ pszFilename, g_pszChangedSuff);
+ }
+ g_cFilesModified++;
+ }
+ else if (fModified)
+ rc = RTMsgErrorRc(VERR_INTERNAL_ERROR, "Rewriters modified binary file! Impossible!");
+
+ /*
+ * If pending SVN property changes, apply them.
+ */
+ if (pState->cSvnPropChanges && RT_SUCCESS(rc))
+ {
+ if (!g_fDryRun)
+ {
+ rc = ScmSvnApplyChanges(pState);
+ if (RT_FAILURE(rc))
+ RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc);
+ }
+ else
+ ScmSvnDisplayChanges(pState);
+ if (!fModified)
+ g_cFilesModified++;
+ }
+
+ if (!fModified && !pState->cSvnPropChanges)
+ ScmVerbose(pState, 3, "%s: no change\n", pszFilename);
+ }
+ else
+ RTMsgError("%s: stream error %Rrc\n", pszFilename, rc);
+ }
+ ScmStreamDelete(&Stream3);
+ }
+ else
+ RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
+ ScmStreamDelete(&Stream2);
+ }
+ else
+ RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
+ }
+ else
+ RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);
+ }
+ else
+ {
+ ScmVerbose(pState, 2, "not text file: \"%s\"\n", pszFilename);
+ g_cFilesBinaries++;
+ }
+ ScmStreamDelete(&Stream1);
+
+ return rc;
+}
+
+/**
+ * Processes a file.
+ *
+ * This is just a wrapper for scmProcessFileInner for avoid wasting stack in the
+ * directory recursion method.
+ *
+ * @returns IPRT status code.
+ * @param pszFilename The file name.
+ * @param pszBasename The base name (pointer within @a pszFilename).
+ * @param cchBasename The length of the base name. (For passing to
+ * RTStrSimplePatternMultiMatch.)
+ * @param pSettingsStack The settings stack (pointer to the top element).
+ */
+static int scmProcessFile(const char *pszFilename, const char *pszBasename, size_t cchBasename,
+ PSCMSETTINGS pSettingsStack)
+{
+ SCMSETTINGSBASE Base;
+ int rc = scmSettingsStackMakeFileBase(pSettingsStack, pszFilename, pszBasename, cchBasename, &Base);
+ if (RT_SUCCESS(rc))
+ {
+ SCMRWSTATE State;
+ State.pszFilename = pszFilename;
+ State.fFirst = false;
+ State.fNeedsManualRepair = false;
+ State.fIsInSvnWorkingCopy = 0;
+ State.cSvnPropChanges = 0;
+ State.paSvnPropChanges = NULL;
+ State.rc = VINF_SUCCESS;
+
+ rc = scmProcessFileInner(&State, pszFilename, pszBasename, cchBasename, &Base);
+
+ size_t i = State.cSvnPropChanges;
+ while (i-- > 0)
+ {
+ RTStrFree(State.paSvnPropChanges[i].pszName);
+ RTStrFree(State.paSvnPropChanges[i].pszValue);
+ }
+ RTMemFree(State.paSvnPropChanges);
+
+ scmSettingsBaseDelete(&Base);
+
+ if (State.fNeedsManualRepair)
+ g_cFilesRequiringManualFixing++;
+ g_cFilesProcessed++;
+ }
+ return rc;
+}
+
+/**
+ * Tries to correct RTDIRENTRY_UNKNOWN.
+ *
+ * @returns Corrected type.
+ * @param pszPath The path to the object in question.
+ */
+static RTDIRENTRYTYPE scmFigureUnknownType(const char *pszPath)
+{
+ RTFSOBJINFO Info;
+ int rc = RTPathQueryInfo(pszPath, &Info, RTFSOBJATTRADD_NOTHING);
+ if (RT_FAILURE(rc))
+ return RTDIRENTRYTYPE_UNKNOWN;
+ if (RTFS_IS_DIRECTORY(Info.Attr.fMode))
+ return RTDIRENTRYTYPE_DIRECTORY;
+ if (RTFS_IS_FILE(Info.Attr.fMode))
+ return RTDIRENTRYTYPE_FILE;
+ return RTDIRENTRYTYPE_UNKNOWN;
+}
+
+/**
+ * Recurse into a sub-directory and process all the files and directories.
+ *
+ * @returns IPRT status code.
+ * @param pszBuf Path buffer containing the directory path on
+ * entry. This ends with a dot. This is passed
+ * along when recursing in order to save stack space
+ * and avoid needless copying.
+ * @param cchDir Length of our path in pszbuf.
+ * @param pEntry Directory entry buffer. This is also passed
+ * along when recursing to save stack space.
+ * @param pSettingsStack The settings stack (pointer to the top element).
+ * @param iRecursion The recursion depth. This is used to restrict
+ * the recursions.
+ */
+static int scmProcessDirTreeRecursion(char *pszBuf, size_t cchDir, PRTDIRENTRY pEntry,
+ PSCMSETTINGS pSettingsStack, unsigned iRecursion)
+{
+ int rc;
+ Assert(cchDir > 1 && pszBuf[cchDir - 1] == '.');
+
+ /*
+ * Make sure we stop somewhere.
+ */
+ if (iRecursion > 128)
+ {
+ RTMsgError("recursion too deep: %d\n", iRecursion);
+ return VINF_SUCCESS; /* ignore */
+ }
+
+ /*
+ * Check if it's excluded by --only-svn-dir.
+ */
+ if (pSettingsStack->Base.fOnlySvnDirs)
+ {
+ if (!ScmSvnIsDirInWorkingCopy(pszBuf))
+ return VINF_SUCCESS;
+ }
+ g_cDirsProcessed++;
+
+ /*
+ * Try open and read the directory.
+ */
+ RTDIR hDir;
+ rc = RTDirOpenFiltered(&hDir, pszBuf, RTDIRFILTER_NONE, 0 /*fFlags*/);
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc);
+ return rc;
+ }
+ for (;;)
+ {
+ /* Read the next entry. */
+ rc = RTDirRead(hDir, pEntry, NULL);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+ else
+ RTMsgError("RTDirRead -> %Rrc\n", rc);
+ break;
+ }
+
+ /* Skip '.' and '..'. */
+ if ( pEntry->szName[0] == '.'
+ && ( pEntry->cbName == 1
+ || ( pEntry->cbName == 2
+ && pEntry->szName[1] == '.')))
+ continue;
+
+ /* Enter it into the buffer so we've got a full name to work
+ with when needed. */
+ if (pEntry->cbName + cchDir >= RTPATH_MAX)
+ {
+ RTMsgError("Skipping too long entry: %s", pEntry->szName);
+ continue;
+ }
+ memcpy(&pszBuf[cchDir - 1], pEntry->szName, pEntry->cbName + 1);
+
+ /* Figure the type. */
+ RTDIRENTRYTYPE enmType = pEntry->enmType;
+ if (enmType == RTDIRENTRYTYPE_UNKNOWN)
+ enmType = scmFigureUnknownType(pszBuf);
+
+ /* Process the file or directory, skip the rest. */
+ if (enmType == RTDIRENTRYTYPE_FILE)
+ rc = scmProcessFile(pszBuf, pEntry->szName, pEntry->cbName, pSettingsStack);
+ else if (enmType == RTDIRENTRYTYPE_DIRECTORY)
+ {
+ /* Append the dot for the benefit of the pattern matching. */
+ if (pEntry->cbName + cchDir + 5 >= RTPATH_MAX)
+ {
+ RTMsgError("Skipping too deep dir entry: %s", pEntry->szName);
+ continue;
+ }
+ memcpy(&pszBuf[cchDir - 1 + pEntry->cbName], "/.", sizeof("/."));
+ size_t cchSubDir = cchDir - 1 + pEntry->cbName + sizeof("/.") - 1;
+
+ if ( !pSettingsStack->Base.pszFilterOutDirs
+ || !*pSettingsStack->Base.pszFilterOutDirs
+ || ( !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
+ pEntry->szName, pEntry->cbName, NULL)
+ && !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
+ pszBuf, cchSubDir, NULL)
+ )
+ )
+ {
+ rc = scmSettingsStackPushDir(&pSettingsStack, pszBuf);
+ if (RT_SUCCESS(rc))
+ {
+ rc = scmProcessDirTreeRecursion(pszBuf, cchSubDir, pEntry, pSettingsStack, iRecursion + 1);
+ scmSettingsStackPopAndDestroy(&pSettingsStack);
+ }
+ }
+ }
+ if (RT_FAILURE(rc))
+ break;
+ }
+ RTDirClose(hDir);
+ return rc;
+
+}
+
+/**
+ * Process a directory tree.
+ *
+ * @returns IPRT status code.
+ * @param pszDir The directory to start with. This is pointer to
+ * a RTPATH_MAX sized buffer.
+ */
+static int scmProcessDirTree(char *pszDir, PSCMSETTINGS pSettingsStack)
+{
+ /*
+ * Setup the recursion.
+ */
+ int rc = RTPathAppend(pszDir, RTPATH_MAX, ".");
+ if (RT_SUCCESS(rc))
+ {
+ RTPathChangeToUnixSlashes(pszDir, true);
+
+ RTDIRENTRY Entry;
+ rc = scmProcessDirTreeRecursion(pszDir, strlen(pszDir), &Entry, pSettingsStack, 0);
+ }
+ else
+ RTMsgError("RTPathAppend: %Rrc\n", rc);
+ return rc;
+}
+
+
+/**
+ * Processes a file or directory specified as an command line argument.
+ *
+ * @returns IPRT status code
+ * @param pszSomething What we found in the command line arguments.
+ * @param pSettingsStack The settings stack (pointer to the top element).
+ */
+static int scmProcessSomething(const char *pszSomething, PSCMSETTINGS pSettingsStack)
+{
+ char szBuf[RTPATH_MAX];
+ int rc = RTPathAbs(pszSomething, szBuf, sizeof(szBuf));
+ if (RT_SUCCESS(rc))
+ {
+ RTPathChangeToUnixSlashes(szBuf, false /*fForce*/);
+
+ PSCMSETTINGS pSettings;
+ rc = scmSettingsCreateForPath(&pSettings, &pSettingsStack->Base, szBuf);
+ if (RT_SUCCESS(rc))
+ {
+ scmSettingsStackPush(&pSettingsStack, pSettings);
+
+ if (RTFileExists(szBuf))
+ {
+ const char *pszBasename = RTPathFilename(szBuf);
+ if (pszBasename)
+ {
+ size_t cchBasename = strlen(pszBasename);
+ rc = scmProcessFile(szBuf, pszBasename, cchBasename, pSettingsStack);
+ }
+ else
+ {
+ RTMsgError("RTPathFilename: NULL\n");
+ rc = VERR_IS_A_DIRECTORY;
+ }
+ }
+ else
+ rc = scmProcessDirTree(szBuf, pSettingsStack);
+
+ PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);
+ Assert(pPopped == pSettings); RT_NOREF_PV(pPopped);
+ scmSettingsDestroy(pSettings);
+ }
+ else
+ RTMsgError("scmSettingsInitStack: %Rrc\n", rc);
+ }
+ else
+ RTMsgError("RTPathAbs: %Rrc\n", rc);
+ return rc;
+}
+
+/**
+ * Print some stats.
+ */
+static void scmPrintStats(void)
+{
+ ScmVerbose(NULL, 0,
+ g_fDryRun
+ ? "%u out of %u file%s in %u dir%s would be modified (%u without rewriter%s, %u binar%s, %u not in svn, %u skipped)\n"
+ : "%u out of %u file%s in %u dir%s was modified (%u without rewriter%s, %u binar%s, %u not in svn, %u skipped)\n",
+ g_cFilesModified,
+ g_cFilesProcessed, g_cFilesProcessed == 1 ? "" : "s",
+ g_cDirsProcessed, g_cDirsProcessed == 1 ? "" : "s",
+ g_cFilesNoRewriters, g_cFilesNoRewriters == 1 ? "" : "s",
+ g_cFilesBinaries, g_cFilesBinaries == 1 ? "y" : "ies",
+ g_cFilesNotInSvn, g_cFilesSkipped);
+}
+
+/**
+ * Display the rewriter actions.
+ *
+ * @returns RTEXITCODE_SUCCESS.
+ */
+static int scmHelpActions(void)
+{
+ RTPrintf("Available rewriter actions:\n");
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_papRewriterActions); i++)
+ RTPrintf(" %s\n", g_papRewriterActions[i]->pszName);
+ return RTEXITCODE_SUCCESS;
+}
+
+/**
+ * Display the default configuration.
+ *
+ * @returns RTEXITCODE_SUCCESS.
+ */
+static int scmHelpConfig(void)
+{
+ RTPrintf("Rewriter configuration:\n");
+ for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
+ {
+ RTPrintf("\n %s%s - %s:\n",
+ g_aConfigs[iCfg].pszName, g_aConfigs[iCfg].fBinary ? " (binary)" : "", g_aConfigs[iCfg].pszFilePattern);
+ for (size_t i = 0; i < g_aConfigs[iCfg].cRewriters; i++)
+ RTPrintf(" %s\n", g_aConfigs[iCfg].paRewriters[i]->pszName);
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+/**
+ * Display the primary help text.
+ *
+ * @returns RTEXITCODE_SUCCESS.
+ * @param paOpts Options.
+ * @param cOpts Number of options.
+ */
+static int scmHelp(PCRTGETOPTDEF paOpts, size_t cOpts)
+{
+ RTPrintf("VirtualBox Source Code Massager\n"
+ "\n"
+ "Usage: %s [options] <files & dirs>\n"
+ "\n"
+ "General options:\n", g_szProgName);
+ for (size_t i = 0; i < cOpts; i++)
+ {
+ /* Grouping. */
+ switch (paOpts[i].iShort)
+ {
+ case SCMOPT_DIFF_IGNORE_EOL:
+ RTPrintf("\nDiff options (dry runs):\n");
+ break;
+ case SCMOPT_CONVERT_EOL:
+ RTPrintf("\nRewriter action options:\n");
+ break;
+ case SCMOPT_ONLY_SVN_DIRS:
+ RTPrintf("\nInput selection options:\n");
+ break;
+ case SCMOPT_TREAT_AS:
+ RTPrintf("\nMisc options:\n");
+ break;
+ }
+
+ size_t cExtraAdvance = 0;
+ if ((paOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING)
+ {
+ cExtraAdvance = i + 1 < cOpts
+ && ( strstr(paOpts[i+1].pszLong, "-no-") != NULL
+ || strstr(paOpts[i+1].pszLong, "-not-") != NULL
+ || strstr(paOpts[i+1].pszLong, "-dont-") != NULL
+ || strstr(paOpts[i+1].pszLong, "-unrestricted-") != NULL
+ || (paOpts[i].iShort == 'q' && paOpts[i+1].iShort == 'v')
+ || (paOpts[i].iShort == 'd' && paOpts[i+1].iShort == 'D')
+ );
+ if (cExtraAdvance)
+ RTPrintf(" %s, %s\n", paOpts[i].pszLong, paOpts[i + 1].pszLong);
+ else if (paOpts[i].iShort != SCMOPT_NO_UPDATE_LICENSE)
+ RTPrintf(" %s\n", paOpts[i].pszLong);
+ else
+ {
+ RTPrintf(" %s,\n"
+ " %s,\n"
+ " %s,\n"
+ " %s,\n"
+ " %s,\n"
+ " %s,\n"
+ " %s\n",
+ paOpts[i].pszLong,
+ paOpts[i + 1].pszLong,
+ paOpts[i + 2].pszLong,
+ paOpts[i + 3].pszLong,
+ paOpts[i + 4].pszLong,
+ paOpts[i + 5].pszLong,
+ paOpts[i + 6].pszLong);
+ cExtraAdvance = 6;
+ }
+ }
+ else if ((paOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING)
+ switch (paOpts[i].iShort)
+ {
+ case SCMOPT_DEL_ACTION:
+ RTPrintf(" %s pattern\n", paOpts[i].pszLong);
+ break;
+ case SCMOPT_FILTER_OUT_DIRS:
+ case SCMOPT_FILTER_FILES:
+ case SCMOPT_FILTER_OUT_FILES:
+ RTPrintf(" %s multi-pattern\n", paOpts[i].pszLong);
+ break;
+ default:
+ RTPrintf(" %s string\n", paOpts[i].pszLong);
+ }
+ else
+ RTPrintf(" %s value\n", paOpts[i].pszLong);
+ switch (paOpts[i].iShort)
+ {
+ case 'd':
+ case 'D': RTPrintf(" Default: --dry-run\n"); break;
+ case SCMOPT_CHECK_RUN: RTPrintf(" Default: --dry-run\n"); break;
+ case 'f': RTPrintf(" Default: none\n"); break;
+ case 'q':
+ case 'v': RTPrintf(" Default: -vv\n"); break;
+ case SCMOPT_HELP_CONFIG: RTPrintf(" Shows the standard file rewriter configurations.\n"); break;
+ case SCMOPT_HELP_ACTIONS: RTPrintf(" Shows the available rewriter actions.\n"); break;
+
+ case SCMOPT_DIFF_IGNORE_EOL: RTPrintf(" Default: false\n"); break;
+ case SCMOPT_DIFF_IGNORE_SPACE: RTPrintf(" Default: false\n"); break;
+ case SCMOPT_DIFF_IGNORE_LEADING_SPACE: RTPrintf(" Default: false\n"); break;
+ case SCMOPT_DIFF_IGNORE_TRAILING_SPACE: RTPrintf(" Default: false\n"); break;
+ case SCMOPT_DIFF_SPECIAL_CHARS: RTPrintf(" Default: true\n"); break;
+
+ case SCMOPT_CONVERT_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertEol); break;
+ case SCMOPT_CONVERT_TABS: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertTabs); break;
+ case SCMOPT_FORCE_FINAL_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceFinalEol); break;
+ case SCMOPT_FORCE_TRAILING_LINE: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceTrailingLine); break;
+ case SCMOPT_STRIP_TRAILING_BLANKS: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingBlanks); break;
+ case SCMOPT_STRIP_TRAILING_LINES: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingLines); break;
+ case SCMOPT_FIX_FLOWER_BOX_MARKERS: RTPrintf(" Default: %RTbool\n", g_Defaults.fFixFlowerBoxMarkers); break;
+ case SCMOPT_MIN_BLANK_LINES_BEFORE_FLOWER_BOX_MARKERS: RTPrintf(" Default: %u\n", g_Defaults.cMinBlankLinesBeforeFlowerBoxMakers); break;
+
+ case SCMOPT_FIX_HEADER_GUARDS:
+ RTPrintf(" Fix header guards and #pragma once. Default: %RTbool\n", g_Defaults.fFixHeaderGuards);
+ break;
+ case SCMOPT_PRAGMA_ONCE:
+ RTPrintf(" Whether to include #pragma once with the header guard. Default: %RTbool\n", g_Defaults.fPragmaOnce);
+ break;
+ case SCMOPT_FIX_HEADER_GUARD_ENDIF:
+ RTPrintf(" Whether to fix the #endif of a header guard. Default: %RTbool\n", g_Defaults.fFixHeaderGuardEndif);
+ break;
+ case SCMOPT_ENDIF_GUARD_COMMENT:
+ RTPrintf(" Put a comment on the header guard #endif or not. Default: %RTbool\n", g_Defaults.fEndifGuardComment);
+ break;
+ case SCMOPT_GUARD_RELATIVE_TO_DIR:
+ RTPrintf(" Header guard should be normalized relative to given dir.\n"
+ " When relative to settings files, no preceeding slash.\n"
+ " Header relative directory specification: {dir} and {parent}\n"
+ " If empty no normalization takes place. Default: '%s'\n", g_Defaults.pszGuardRelativeToDir);
+ break;
+ case SCMOPT_GUARD_PREFIX:
+ RTPrintf(" Prefix to use with --guard-relative-to-dir. Default: %s\n", g_Defaults.pszGuardPrefix);
+ break;
+ case SCMOPT_FIX_TODOS:
+ RTPrintf(" Fix @todo statements so doxygen sees them. Default: %RTbool\n", g_Defaults.fFixTodos);
+ break;
+ case SCMOPT_FIX_ERR_H:
+ RTPrintf(" Fix err.h/errcore.h usage. Default: %RTbool\n", g_Defaults.fFixErrH);
+ break;
+ case SCMOPT_ONLY_GUEST_HOST_PAGE:
+ RTPrintf(" No PAGE_SIZE, PAGE_SHIFT or PAGE_OFFSET_MASK allowed, must have\n"
+ " GUEST_ or HOST_ prefix. Also forbids use of PAGE_BASE_MASK,\n"
+ " PAGE_BASE_HC_MASK, PAGE_BASE_GC_MASK, PAGE_ADDRESS,\n"
+ " PHYS_PAGE_ADDRESS. Default: %RTbool\n", g_Defaults.fOnlyGuestHostPage);
+ break;
+ case SCMOPT_NO_ASM_MEM_PAGE_USE:
+ RTPrintf(" No ASMMemIsZeroPage or ASMMemZeroPage allowed, must instead use\n"
+ " ASMMemIsZero and RT_BZERO with appropriate page size. Default: %RTbool\n",
+ g_Defaults.fNoASMMemPageUse);
+ break;
+ case SCMOPT_NO_RC_USE:
+ RTPrintf(" No rc declaration allowed, must instead use\n"
+ " vrc for IPRT status codes and hrc for COM status codes. Default: %RTbool\n",
+ g_Defaults.fOnlyHrcVrcInsteadOfRc);
+ break;
+ case SCMOPT_STANDARIZE_KMK:
+ RTPrintf(" Clean up kmk files (the makefile-kmk action). Default: %RTbool\n", g_Defaults.fStandarizeKmk);
+ break;
+ case SCMOPT_UPDATE_COPYRIGHT_YEAR:
+ RTPrintf(" Update the copyright year. Default: %RTbool\n", g_Defaults.fUpdateCopyrightYear);
+ break;
+ case SCMOPT_EXTERNAL_COPYRIGHT:
+ RTPrintf(" Only external copyright holders. Default: %RTbool\n", g_Defaults.fExternalCopyright);
+ break;
+ case SCMOPT_NO_UPDATE_LICENSE:
+ RTPrintf(" License selection. Default: --license-ose-gpl\n");
+ break;
+
+ case SCMOPT_LGPL_DISCLAIMER:
+ RTPrintf(" Include LGPL version disclaimer. Default: --no-lgpl-disclaimer\n");
+ break;
+
+ case SCMOPT_SET_SVN_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnEol); break;
+ case SCMOPT_SET_SVN_EXECUTABLE: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnExecutable); break;
+ case SCMOPT_SET_SVN_KEYWORDS: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnKeywords); break;
+ case SCMOPT_SKIP_SVN_SYNC_PROCESS: RTPrintf(" Default: %RTbool\n", g_Defaults.fSkipSvnSyncProcess); break;
+ case SCMOPT_SKIP_UNICODE_CHECKS: RTPrintf(" Default: %RTbool\n", g_Defaults.fSkipUnicodeChecks); break;
+ case SCMOPT_TAB_SIZE: RTPrintf(" Default: %u\n", g_Defaults.cchTab); break;
+ case SCMOPT_WIDTH: RTPrintf(" Default: %u\n", g_Defaults.cchWidth); break;
+
+ case SCMOPT_ONLY_SVN_DIRS: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnDirs); break;
+ case SCMOPT_ONLY_SVN_FILES: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnFiles); break;
+ case SCMOPT_FILTER_OUT_DIRS: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutDirs); break;
+ case SCMOPT_FILTER_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterFiles); break;
+ case SCMOPT_FILTER_OUT_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutFiles); break;
+
+ case SCMOPT_TREAT_AS:
+ RTPrintf(" For treat the input file(s) differently, restting any --add-action.\n"
+ " If the value is empty defaults will be used again. Possible values:\n");
+ for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
+ RTPrintf(" %s (%s)\n", g_aConfigs[iCfg].pszName, g_aConfigs[iCfg].pszFilePattern);
+ break;
+
+ case SCMOPT_ADD_ACTION:
+ RTPrintf(" Adds a rewriter action. The first use after a --treat-as will copy and\n"
+ " the action list selected by the --treat-as. The action list will be\n"
+ " flushed by --treat-as.\n");
+ break;
+
+ case SCMOPT_DEL_ACTION:
+ RTPrintf(" Deletes one or more rewriter action (pattern). Best used after\n"
+ " a --treat-as.\n");
+ break;
+
+ default: AssertMsgFailed(("i=%d %d %s\n", i, paOpts[i].iShort, paOpts[i].pszLong));
+ }
+ i += cExtraAdvance;
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return 1;
+
+ /*
+ * Init the current year.
+ */
+ RTTIMESPEC Now;
+ RTTIME Time;
+ RTTimeExplode(&Time, RTTimeNow(&Now));
+ g_uYear = Time.i32Year;
+
+ /*
+ * Init the settings.
+ */
+ PSCMSETTINGS pSettings;
+ rc = scmSettingsCreate(&pSettings, &g_Defaults);
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("scmSettingsCreate: %Rrc\n", rc);
+ return 1;
+ }
+
+ /*
+ * Parse arguments and process input in order (because this is the only
+ * thing that works at the moment).
+ */
+ static RTGETOPTDEF s_aOpts[14 + RT_ELEMENTS(g_aScmOpts)] =
+ {
+ { "--dry-run", 'd', RTGETOPT_REQ_NOTHING },
+ { "--real-run", 'D', RTGETOPT_REQ_NOTHING },
+ { "--check-run", SCMOPT_CHECK_RUN, RTGETOPT_REQ_NOTHING },
+ { "--file-filter", 'f', RTGETOPT_REQ_STRING },
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--diff-ignore-eol", SCMOPT_DIFF_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
+ { "--diff-no-ignore-eol", SCMOPT_DIFF_NO_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
+ { "--diff-ignore-space", SCMOPT_DIFF_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
+ { "--diff-no-ignore-space", SCMOPT_DIFF_NO_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
+ { "--diff-ignore-leading-space", SCMOPT_DIFF_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
+ { "--diff-no-ignore-leading-space", SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
+ { "--diff-ignore-trailing-space", SCMOPT_DIFF_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
+ { "--diff-no-ignore-trailing-space", SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
+ { "--diff-special-chars", SCMOPT_DIFF_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
+ { "--diff-no-special-chars", SCMOPT_DIFF_NO_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
+ };
+ memcpy(&s_aOpts[RT_ELEMENTS(s_aOpts) - RT_ELEMENTS(g_aScmOpts)], &g_aScmOpts[0], sizeof(g_aScmOpts));
+
+ bool fCheckRun = false;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetOptState;
+ rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertReleaseRCReturn(rc, 1);
+
+ while ( (rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0
+ && rc != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (rc)
+ {
+ case 'd':
+ g_fDryRun = true;
+ fCheckRun = false;
+ break;
+ case 'D':
+ g_fDryRun = fCheckRun = false;
+ break;
+ case SCMOPT_CHECK_RUN:
+ g_fDryRun = fCheckRun = true;
+ break;
+
+ case 'f':
+ g_pszFileFilter = ValueUnion.psz;
+ break;
+
+ case 'h':
+ return scmHelp(s_aOpts, RT_ELEMENTS(s_aOpts));
+
+ case SCMOPT_HELP_CONFIG:
+ return scmHelpConfig();
+
+ case SCMOPT_HELP_ACTIONS:
+ return scmHelpActions();
+
+ case 'q':
+ g_iVerbosity = 0;
+ break;
+
+ case 'v':
+ g_iVerbosity++;
+ break;
+
+ case 'V':
+ {
+ /* The following is assuming that svn does it's job here. */
+ static const char s_szRev[] = "$Revision: 155710 $";
+ const char *psz = RTStrStripL(strchr(s_szRev, ' '));
+ RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);
+ return 0;
+ }
+
+ case SCMOPT_DIFF_IGNORE_EOL:
+ g_fDiffIgnoreEol = true;
+ break;
+ case SCMOPT_DIFF_NO_IGNORE_EOL:
+ g_fDiffIgnoreEol = false;
+ break;
+
+ case SCMOPT_DIFF_IGNORE_SPACE:
+ g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = true;
+ break;
+ case SCMOPT_DIFF_NO_IGNORE_SPACE:
+ g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = false;
+ break;
+
+ case SCMOPT_DIFF_IGNORE_LEADING_SPACE:
+ g_fDiffIgnoreLeadingWS = true;
+ break;
+ case SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE:
+ g_fDiffIgnoreLeadingWS = false;
+ break;
+
+ case SCMOPT_DIFF_IGNORE_TRAILING_SPACE:
+ g_fDiffIgnoreTrailingWS = true;
+ break;
+ case SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE:
+ g_fDiffIgnoreTrailingWS = false;
+ break;
+
+ case SCMOPT_DIFF_SPECIAL_CHARS:
+ g_fDiffSpecialChars = true;
+ break;
+ case SCMOPT_DIFF_NO_SPECIAL_CHARS:
+ g_fDiffSpecialChars = false;
+ break;
+
+ default:
+ {
+ int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion, "/", 1);
+ if (RT_SUCCESS(rc2))
+ break;
+ if (rc2 != VERR_GETOPT_UNKNOWN_OPTION)
+ return 2;
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+ }
+
+ /*
+ * Process non-options.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ if (rc == VINF_GETOPT_NOT_OPTION)
+ {
+ ScmSvnInit();
+
+ bool fWarned = g_fDryRun;
+ while (rc == VINF_GETOPT_NOT_OPTION)
+ {
+ if (!fWarned)
+ {
+ RTPrintf("%s: Warning! This program will make changes to your source files and\n"
+ "%s: there is a slight risk that bugs or a full disk may cause\n"
+ "%s: LOSS OF DATA. So, please make sure you have checked in\n"
+ "%s: all your changes already. If you didn't, then don't blame\n"
+ "%s: anyone for not warning you!\n"
+ "%s:\n"
+ "%s: Press any key to continue...\n",
+ g_szProgName, g_szProgName, g_szProgName, g_szProgName, g_szProgName,
+ g_szProgName, g_szProgName);
+ RTStrmGetCh(g_pStdIn);
+ fWarned = true;
+ }
+
+ rc = scmProcessSomething(ValueUnion.psz, pSettings);
+ if (RT_FAILURE(rc))
+ {
+ rcExit = RTEXITCODE_FAILURE;
+ break;
+ }
+
+ /* next */
+ rc = RTGetOpt(&GetOptState, &ValueUnion);
+ if (RT_FAILURE(rc))
+ rcExit = RTGetOptPrintError(rc, &ValueUnion);
+ }
+
+ scmPrintStats();
+ ScmSvnTerm();
+ }
+ else
+ RTMsgWarning("No files or directories specified. Doing nothing");
+
+ scmSettingsDestroy(pSettings);
+
+ /* If we're in checking mode, fail if any files needed modification. */
+ if ( rcExit == RTEXITCODE_SUCCESS
+ && fCheckRun
+ && g_cFilesModified > 0)
+ {
+ RTMsgError("Checking mode failed! %u file%s needs modifications", g_cFilesBinaries, g_cFilesBinaries > 1 ? "s" : "");
+ rcExit = RTEXITCODE_FAILURE;
+ }
+
+ /* Fail if any files require manual repair. */
+ if (g_cFilesRequiringManualFixing > 0)
+ {
+ RTMsgError("%u file%s needs manual modifications", g_cFilesRequiringManualFixing,
+ g_cFilesRequiringManualFixing > 1 ? "s" : "");
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = RTEXITCODE_FAILURE;
+ }
+
+ return rcExit;
+}
+
diff --git a/src/bldprogs/scm.h b/src/bldprogs/scm.h
new file mode 100644
index 00000000..76819108
--- /dev/null
+++ b/src/bldprogs/scm.h
@@ -0,0 +1,498 @@
+/* $Id: scm.h $ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_bldprogs_scm_h
+#define VBOX_INCLUDED_SRC_bldprogs_scm_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "scmstream.h"
+
+RT_C_DECLS_BEGIN
+
+/** Pointer to the rewriter state. */
+typedef struct SCMRWSTATE *PSCMRWSTATE;
+/** Pointer to const massager settings. */
+typedef struct SCMSETTINGSBASE const *PCSCMSETTINGSBASE;
+
+
+/** @name Subversion Access
+ * @{ */
+
+/**
+ * SVN property.
+ */
+typedef struct SCMSVNPROP
+{
+ /** The property. */
+ char *pszName;
+ /** The value.
+ * When used to record updates, this can be set to NULL to trigger the
+ * deletion of the property. */
+ char *pszValue;
+} SCMSVNPROP;
+/** Pointer to a SVN property. */
+typedef SCMSVNPROP *PSCMSVNPROP;
+/** Pointer to a const SVN property. */
+typedef SCMSVNPROP const *PCSCMSVNPROP;
+
+
+void ScmSvnInit(void);
+void ScmSvnTerm(void);
+bool ScmSvnIsDirInWorkingCopy(const char *pszDir);
+bool ScmSvnIsInWorkingCopy(PSCMRWSTATE pState);
+int ScmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue);
+int ScmSvnQueryParentProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue);
+int ScmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue);
+int ScmSvnDelProperty(PSCMRWSTATE pState, const char *pszName);
+int ScmSvnDisplayChanges(PSCMRWSTATE pState);
+int ScmSvnApplyChanges(PSCMRWSTATE pState);
+
+/** @} */
+
+
+/** @name Code Parsing
+ * @{ */
+
+/**
+ * Comment style.
+ */
+typedef enum SCMCOMMENTSTYLE
+{
+ kScmCommentStyle_Invalid = 0,
+ kScmCommentStyle_C,
+ kScmCommentStyle_Hash,
+ kScmCommentStyle_Python, /**< Same as hash, except for copyright/license. */
+ kScmCommentStyle_Semicolon,
+ kScmCommentStyle_Rem_Upper,
+ kScmCommentStyle_Rem_Lower,
+ kScmCommentStyle_Rem_Camel,
+ kScmCommentStyle_Sql,
+ kScmCommentStyle_Tick,
+ kScmCommentStyle_Xml,
+ kScmCommentStyle_End
+} SCMCOMMENTSTYLE;
+
+/**
+ * Comment types.
+ */
+typedef enum SCMCOMMENTTYPE
+{
+ kScmCommentType_Invalid = 0, /**< Customary invalid zero value. */
+ kScmCommentType_Line, /**< Line comment. */
+ kScmCommentType_Line_JavaDoc, /**< Line comment, JavaDoc style. */
+ kScmCommentType_Line_JavaDoc_After, /**< Line comment, JavaDoc after-member style. */
+ kScmCommentType_Line_Qt, /**< Line comment, JavaDoc style. */
+ kScmCommentType_Line_Qt_After, /**< Line comment, JavaDoc after-member style. */
+ kScmCommentType_MultiLine, /**< Multi-line comment (e.g. ansi C). */
+ kScmCommentType_MultiLine_JavaDoc, /**< Multi-line comment, JavaDoc style. */
+ kScmCommentType_MultiLine_JavaDoc_After, /**< Multi-line comment, JavaDoc after-member style. */
+ kScmCommentType_MultiLine_Qt, /**< Multi-line comment, Qt style. */
+ kScmCommentType_MultiLine_Qt_After, /**< Multi-line comment, Qt after-member style. */
+ kScmCommentType_DocString, /**< Triple quoted python doc string. */
+ kScmCommentType_Xml, /**< XML comment style. */
+ kScmCommentType_End /**< Customary exclusive end value. */
+} SCMCOMMENTTYPE;
+
+
+/**
+ * Comment information.
+ */
+typedef struct SCMCOMMENTINFO
+{
+ /** Comment type. */
+ SCMCOMMENTTYPE enmType;
+ /** Start line number (0-based). */
+ uint32_t iLineStart;
+ /** Start line offset (0-based). */
+ uint32_t offStart;
+ /** End line number (0-based). */
+ uint32_t iLineEnd;
+ /** End line offset (0-based). */
+ uint32_t offEnd;
+ /** Number of blank lines before the body (@a pszBody). */
+ uint32_t cBlankLinesBefore;
+ /** Number of blank lines after the body (@a pszBody + @a cchBody). */
+ uint32_t cBlankLinesAfter;
+ /** @todo add min/max indent. Raw length. Etc. */
+} SCMCOMMENTINFO;
+/** Pointer to comment info. */
+typedef SCMCOMMENTINFO *PSCMCOMMENTINFO;
+/** Pointer to const comment info. */
+typedef SCMCOMMENTINFO const *PCSCMCOMMENTINFO;
+
+
+/**
+ * Comment enumeration callback function.
+ *
+ * @returns IPRT style status code. Failures causes immediate return. While an
+ * informational status code is saved (first one) and returned later.
+ * @param pInfo Additional comment info.
+ * @param pszBody The comment body. This is somewhat stripped.
+ * @param cchBody The comment body length.
+ * @param pvUser User callback argument.
+ */
+typedef DECLCALLBACKTYPE(int, FNSCMCOMMENTENUMERATOR,(PCSCMCOMMENTINFO pInfo, const char *pszBody, size_t cchBody, void *pvUser));
+/** Poiter to a omment enumeration callback function. */
+typedef FNSCMCOMMENTENUMERATOR *PFNSCMCOMMENTENUMERATOR;
+
+int ScmEnumerateComments(PSCMSTREAM pIn, SCMCOMMENTSTYLE enmCommentStyle, PFNSCMCOMMENTENUMERATOR pfnCallback, void *pvUser);
+
+
+/**
+ * Include directive type.
+ */
+typedef enum SCMINCLUDEDIR
+{
+ kScmIncludeDir_Invalid = 0, /**< Constomary invalid enum value. */
+ kScmIncludeDir_Quoted, /**< \#include \"filename.h\" */
+ kScmIncludeDir_Bracketed, /**< \#include \<filename.h\> */
+ kScmIncludeDir_Macro, /**< \#include MACRO_H */
+ kScmIncludeDir_End /**< End of valid enum values. */
+} SCMINCLUDEDIR;
+
+SCMINCLUDEDIR ScmMaybeParseCIncludeLine(PSCMRWSTATE pState, const char *pchLine, size_t cchLine,
+ const char **ppchFilename, size_t *pcchFilename);
+
+/**
+ * Checks if the given character is a valid C identifier lead character.
+ *
+ * @returns true / false.
+ * @param ch The character to inspect.
+ * @sa vbcppIsCIdentifierLeadChar
+ */
+DECLINLINE(bool) ScmIsCIdentifierLeadChar(char ch)
+{
+ return RT_C_IS_ALPHA(ch)
+ || ch == '_';
+}
+
+
+/**
+ * Checks if the given character is a valid C identifier character.
+ *
+ * @returns true / false.
+ * @param ch The character to inspect.
+ * @sa vbcppIsCIdentifierChar
+ */
+DECLINLINE(bool) ScmIsCIdentifierChar(char ch)
+{
+ return RT_C_IS_ALNUM(ch)
+ || ch == '_';
+}
+
+size_t ScmCalcSpacesForSrcSpan(const char *pchLine, size_t offStart, size_t offEnd, PCSCMSETTINGSBASE pSettings);
+
+/** @} */
+
+
+/** @name Rewriters
+ * @{ */
+
+/**
+ * Rewriter state.
+ */
+typedef struct SCMRWSTATE
+{
+ /** The filename. */
+ const char *pszFilename;
+ /** Set after the printing the first verbose message about a file under
+ * rewrite. */
+ bool fFirst;
+ /** Set if the file requires manual repair. */
+ bool fNeedsManualRepair;
+ /** Cached ScmSvnIsInWorkingCopy response. 0 indicates not known, 1 means it
+ * is in WC, -1 means it doesn't. */
+ int8_t fIsInSvnWorkingCopy;
+ /** The number of SVN property changes. */
+ size_t cSvnPropChanges;
+ /** Pointer to an array of SVN property changes. */
+ struct SCMSVNPROP *paSvnPropChanges;
+ /** For error propagation. */
+ int32_t rc;
+} SCMRWSTATE;
+
+/** Rewriter result. */
+typedef enum { kScmUnmodified = 0, kScmModified, kScmMaybeModified } SCMREWRITERRES;
+
+/**
+ * A rewriter.
+ *
+ * This works like a stream editor, reading @a pIn, modifying it and writing it
+ * to @a pOut.
+ *
+ * @returns kScmUnmodified, kScmModified or kScmMaybeModified.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+typedef SCMREWRITERRES FNSCMREWRITER(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
+/** Pointer to a rewriter method. */
+typedef FNSCMREWRITER *PFNSCMREWRITER;
+
+FNSCMREWRITER rewrite_StripTrailingBlanks;
+FNSCMREWRITER rewrite_ExpandTabs;
+FNSCMREWRITER rewrite_ForceNativeEol;
+FNSCMREWRITER rewrite_ForceLF;
+FNSCMREWRITER rewrite_ForceCRLF;
+FNSCMREWRITER rewrite_AdjustTrailingLines;
+FNSCMREWRITER rewrite_SvnNoExecutable;
+FNSCMREWRITER rewrite_SvnNoKeywords;
+FNSCMREWRITER rewrite_SvnNoEolStyle;
+FNSCMREWRITER rewrite_SvnBinary;
+FNSCMREWRITER rewrite_SvnKeywords;
+FNSCMREWRITER rewrite_SvnSyncProcess;
+FNSCMREWRITER rewrite_UnicodeChecks;
+FNSCMREWRITER rewrite_PageChecks;
+FNSCMREWRITER rewrite_ForceHrcVrcInsteadOfRc;
+FNSCMREWRITER rewrite_Copyright_CstyleComment;
+FNSCMREWRITER rewrite_Copyright_HashComment;
+FNSCMREWRITER rewrite_Copyright_PythonComment;
+FNSCMREWRITER rewrite_Copyright_RemComment;
+FNSCMREWRITER rewrite_Copyright_SemicolonComment;
+FNSCMREWRITER rewrite_Copyright_SqlComment;
+FNSCMREWRITER rewrite_Copyright_TickComment;
+FNSCMREWRITER rewrite_Copyright_XmlComment;
+FNSCMREWRITER rewrite_Makefile_kup;
+FNSCMREWRITER rewrite_Makefile_kmk;
+FNSCMREWRITER rewrite_FixFlowerBoxMarkers;
+FNSCMREWRITER rewrite_Fix_C_and_CPP_Todos;
+FNSCMREWRITER rewrite_Fix_Err_H;
+FNSCMREWRITER rewrite_FixHeaderGuards;
+FNSCMREWRITER rewrite_C_and_CPP;
+
+/**
+ * Rewriter configuration.
+ */
+typedef struct SCMREWRITERCFG
+{
+ /** The rewriter function. */
+ PFNSCMREWRITER pfnRewriter;
+ /** The name of the rewriter. */
+ const char *pszName;
+} SCMREWRITERCFG;
+/** Pointer to a const rewriter config. */
+typedef SCMREWRITERCFG const *PCSCMREWRITERCFG;
+
+/** @} */
+
+
+/** @name Settings
+ * @{ */
+
+/**
+ * Configuration entry.
+ */
+typedef struct SCMCFGENTRY
+{
+ /** Number of rewriters. */
+ size_t cRewriters;
+ /** Pointer to an array of rewriters. */
+ PCSCMREWRITERCFG const *paRewriters;
+ /** Set if the entry handles binaries. */
+ bool fBinary;
+ /** File pattern (simple). */
+ const char *pszFilePattern;
+ /** Name (for treat as). */
+ const char *pszName;
+} SCMCFGENTRY;
+typedef SCMCFGENTRY *PSCMCFGENTRY;
+typedef SCMCFGENTRY const *PCSCMCFGENTRY;
+
+
+/** License update options. */
+typedef enum SCMLICENSE
+{
+ kScmLicense_LeaveAlone = 0, /**< Leave it alone. */
+ kScmLicense_OseGpl, /**< VBox OSE GPL if public. */
+ kScmLicense_OseDualGplCddl, /**< VBox OSE dual GPL & CDDL if public. */
+ kScmLicense_OseCddl, /**< VBox OSE CDDL if public. */
+ kScmLicense_Lgpl, /**< LGPL if public. */
+ kScmLicense_Mit, /**< MIT if public. */
+ kScmLicense_BasedOnMit, /**< Copyright us but based on someone else's MIT. */
+ kScmLicense_End
+} SCMLICENSE;
+
+/**
+ * Source Code Massager Settings.
+ */
+typedef struct SCMSETTINGSBASE
+{
+ bool fConvertEol;
+ bool fConvertTabs;
+ bool fForceFinalEol;
+ bool fForceTrailingLine;
+ bool fStripTrailingBlanks;
+ bool fStripTrailingLines;
+
+ /** Whether to fix C/C++ flower box section markers. */
+ bool fFixFlowerBoxMarkers;
+ /** The minimum number of blank lines we want before flowerbox markers. */
+ uint8_t cMinBlankLinesBeforeFlowerBoxMakers;
+
+ /** Whether to fix C/C++ header guards and \#pragma once directives. */
+ bool fFixHeaderGuards;
+ /** Whether to include a pragma once statement with the header guard. */
+ bool fPragmaOnce;
+ /** Whether to fix the \#endif part of a header guard. */
+ bool fFixHeaderGuardEndif;
+ /** Whether to add a comment on the \#endif part of the header guard. */
+ bool fEndifGuardComment;
+ /** The guard name prefix. */
+ char *pszGuardPrefix;
+ /** Header guards should be normalized using prefix and this directory.
+ * When NULL the guard identifiers found in the header is preserved. */
+ char *pszGuardRelativeToDir;
+
+ /** Whether to fix C/C++ todos. */
+ bool fFixTodos;
+ /** Whether to fix C/C++ err.h/errcore.h usage. */
+ bool fFixErrH;
+ /** No PAGE_SIZE, PAGE_SHIFT, PAGE_OFFSET_MASK allowed in C/C++, only the GUEST_
+ * or HOST_ prefixed versions. */
+ bool fOnlyGuestHostPage;
+ /** No ASMMemIsZeroPage or ASMMemZeroPage calls allowed (C/C++). */
+ bool fNoASMMemPageUse;
+ /** No rc declarations allowed, only hrc or vrc depending on the result type. */
+ bool fOnlyHrcVrcInsteadOfRc;
+
+ /** Whether to standarize kmk makefiles. */
+ bool fStandarizeKmk;
+
+ /** Update the copyright year. */
+ bool fUpdateCopyrightYear;
+ /** Only external copyright holders. */
+ bool fExternalCopyright;
+ /** Whether there should be a LGPL disclaimer. */
+ bool fLgplDisclaimer;
+ /** How to update the license. */
+ SCMLICENSE enmUpdateLicense;
+
+ /** Only process files that are part of a SVN working copy. */
+ bool fOnlySvnFiles;
+ /** Only recurse into directories containing an .svn dir. */
+ bool fOnlySvnDirs;
+ /** Set svn:eol-style if missing or incorrect. */
+ bool fSetSvnEol;
+ /** Set svn:executable according to type (unusually this means deleting it). */
+ bool fSetSvnExecutable;
+ /** Set svn:keyword if completely or partially missing. */
+ bool fSetSvnKeywords;
+ /** Skip checking svn:sync-process. */
+ bool fSkipSvnSyncProcess;
+ /** Skip the unicode checks. */
+ bool fSkipUnicodeChecks;
+ /** Tab size. */
+ uint8_t cchTab;
+ /** Optimal source code width. */
+ uint8_t cchWidth;
+ /** Free the treat as structure. */
+ bool fFreeTreatAs;
+ /** Prematched config entry. */
+ PCSCMCFGENTRY pTreatAs;
+ /** Only consider files matching these patterns. This is only applied to the
+ * base names. */
+ char *pszFilterFiles;
+ /** Filter out files matching the following patterns. This is applied to base
+ * names as well as the absolute paths. */
+ char *pszFilterOutFiles;
+ /** Filter out directories matching the following patterns. This is applied
+ * to base names as well as the absolute paths. All absolute paths ends with a
+ * slash and dot ("/."). */
+ char *pszFilterOutDirs;
+} SCMSETTINGSBASE;
+/** Pointer to massager settings. */
+typedef SCMSETTINGSBASE *PSCMSETTINGSBASE;
+
+/**
+ * File/dir pattern + options.
+ */
+typedef struct SCMPATRNOPTPAIR
+{
+ char *pszPattern;
+ char *pszOptions;
+ char *pszRelativeTo;
+ bool fMultiPattern;
+} SCMPATRNOPTPAIR;
+/** Pointer to a pattern + option pair. */
+typedef SCMPATRNOPTPAIR *PSCMPATRNOPTPAIR;
+
+
+/** Pointer to a settings set. */
+typedef struct SCMSETTINGS *PSCMSETTINGS;
+/**
+ * Settings set.
+ *
+ * This structure is constructed from the command line arguments or any
+ * .scm-settings file found in a directory we recurse into. When recursing in
+ * and out of a directory, we push and pop a settings set for it.
+ *
+ * The .scm-settings file has two kinds of setttings, first there are the
+ * unqualified base settings and then there are the settings which applies to a
+ * set of files or directories. The former are lines with command line options.
+ * For the latter, the options are preceded by a string pattern and a colon.
+ * The pattern specifies which files (and/or directories) the options applies
+ * to.
+ *
+ * We parse the base options into the Base member and put the others into the
+ * paPairs array.
+ */
+typedef struct SCMSETTINGS
+{
+ /** Pointer to the setting file below us in the stack. */
+ PSCMSETTINGS pDown;
+ /** Pointer to the setting file above us in the stack. */
+ PSCMSETTINGS pUp;
+ /** File/dir patterns and their options. */
+ PSCMPATRNOPTPAIR paPairs;
+ /** The number of entires in paPairs. */
+ uint32_t cPairs;
+ /** The base settings that was read out of the file. */
+ SCMSETTINGSBASE Base;
+} SCMSETTINGS;
+/** Pointer to a const settings set. */
+typedef SCMSETTINGS const *PCSCMSETTINGS;
+
+/** @} */
+
+
+void ScmVerboseBanner(PSCMRWSTATE pState, int iLevel);
+void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(3, 4);
+SCMREWRITERRES ScmError(PSCMRWSTATE pState, int rc, const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(3, 4);
+bool ScmFixManually(PSCMRWSTATE pState, const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(2, 3);
+bool ScmFixManuallyV(PSCMRWSTATE pState, const char *pszFormat, va_list va) RT_IPRT_FORMAT_ATTR(2, 0);
+
+extern const char g_szTabSpaces[16+1];
+extern const char g_szAsterisks[255+1];
+extern const char g_szSpaces[255+1];
+extern uint32_t g_uYear;
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_bldprogs_scm_h */
+
diff --git a/src/bldprogs/scmdiff.cpp b/src/bldprogs/scmdiff.cpp
new file mode 100644
index 00000000..df642c4b
--- /dev/null
+++ b/src/bldprogs/scmdiff.cpp
@@ -0,0 +1,450 @@
+/* $Id: scmdiff.cpp $ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/message.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+#include "scmdiff.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static const char g_szTabSpaces[16+1] = " ";
+
+
+
+/**
+ * Prints a range of lines with a prefix.
+ *
+ * @param pState The diff state.
+ * @param chPrefix The prefix.
+ * @param pStream The stream to get the lines from.
+ * @param iLine The first line.
+ * @param cLines The number of lines.
+ */
+static void scmDiffPrintLines(PSCMDIFFSTATE pState, char chPrefix, PSCMSTREAM pStream, size_t iLine, size_t cLines)
+{
+ while (cLines-- > 0)
+ {
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine = ScmStreamGetLineByNo(pStream, iLine, &cchLine, &enmEol);
+
+ RTStrmPutCh(pState->pDiff, chPrefix);
+ if (pchLine && cchLine)
+ {
+ if (!pState->fSpecialChars)
+ RTStrmWrite(pState->pDiff, pchLine, cchLine);
+ else
+ {
+ size_t offVir = 0;
+ const char *pchStart = pchLine;
+ const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);
+ while (pchTab)
+ {
+ RTStrmWrite(pState->pDiff, pchStart, pchTab - pchStart);
+ offVir += pchTab - pchStart;
+
+ size_t cchTab = pState->cchTab - offVir % pState->cchTab;
+ switch (cchTab)
+ {
+ case 1: RTStrmPutStr(pState->pDiff, "."); break;
+ case 2: RTStrmPutStr(pState->pDiff, ".."); break;
+ case 3: RTStrmPutStr(pState->pDiff, "[T]"); break;
+ case 4: RTStrmPutStr(pState->pDiff, "[TA]"); break;
+ case 5: RTStrmPutStr(pState->pDiff, "[TAB]"); break;
+ default: RTStrmPrintf(pState->pDiff, "[TAB%.*s]", cchTab - 5, g_szTabSpaces); break;
+ }
+ offVir += cchTab;
+
+ /* next */
+ pchStart = pchTab + 1;
+ pchTab = (const char *)memchr(pchStart, '\t', cchLine - (pchStart - pchLine));
+ }
+ size_t cchLeft = cchLine - (pchStart - pchLine);
+ if (cchLeft)
+ RTStrmWrite(pState->pDiff, pchStart, cchLeft);
+ }
+ }
+
+ if (!pState->fSpecialChars)
+ RTStrmPutCh(pState->pDiff, '\n');
+ else if (enmEol == SCMEOL_LF)
+ RTStrmPutStr(pState->pDiff, "[LF]\n");
+ else if (enmEol == SCMEOL_CRLF)
+ RTStrmPutStr(pState->pDiff, "[CRLF]\n");
+ else
+ RTStrmPutStr(pState->pDiff, "[NONE]\n");
+
+ iLine++;
+ }
+}
+
+
+/**
+ * Reports a difference and propels the streams to the lines following the
+ * resync.
+ *
+ *
+ * @returns New pState->cDiff value (just to return something).
+ * @param pState The diff state. The cDiffs member will be
+ * incremented.
+ * @param cMatches The resync length.
+ * @param iLeft Where the difference starts on the left side.
+ * @param cLeft How long it is on this side. ~(size_t)0 is used
+ * to indicate that it goes all the way to the end.
+ * @param iRight Where the difference starts on the right side.
+ * @param cRight How long it is.
+ */
+static size_t scmDiffReport(PSCMDIFFSTATE pState, size_t cMatches,
+ size_t iLeft, size_t cLeft,
+ size_t iRight, size_t cRight)
+{
+ /*
+ * Adjust the input.
+ */
+ if (cLeft == ~(size_t)0)
+ {
+ size_t c = ScmStreamCountLines(pState->pLeft);
+ if (c >= iLeft)
+ cLeft = c - iLeft;
+ else
+ {
+ iLeft = c;
+ cLeft = 0;
+ }
+ }
+
+ if (cRight == ~(size_t)0)
+ {
+ size_t c = ScmStreamCountLines(pState->pRight);
+ if (c >= iRight)
+ cRight = c - iRight;
+ else
+ {
+ iRight = c;
+ cRight = 0;
+ }
+ }
+
+ /*
+ * Print header if it's the first difference
+ */
+ if (!pState->cDiffs)
+ RTStrmPrintf(pState->pDiff, "diff %s %s\n", pState->pszFilename, pState->pszFilename);
+
+ /*
+ * Emit the change description.
+ */
+ char ch = cLeft == 0
+ ? 'a'
+ : cRight == 0
+ ? 'd'
+ : 'c';
+ if (cLeft > 1 && cRight > 1)
+ RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu,%zu\n", iLeft + 1, iLeft + cLeft, ch, iRight + 1, iRight + cRight);
+ else if (cLeft > 1)
+ RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu\n", iLeft + 1, iLeft + cLeft, ch, iRight + 1);
+ else if (cRight > 1)
+ RTStrmPrintf(pState->pDiff, "%zu%c%zu,%zu\n", iLeft + 1, ch, iRight + 1, iRight + cRight);
+ else
+ RTStrmPrintf(pState->pDiff, "%zu%c%zu\n", iLeft + 1, ch, iRight + 1);
+
+ /*
+ * And the lines.
+ */
+ if (cLeft)
+ scmDiffPrintLines(pState, '<', pState->pLeft, iLeft, cLeft);
+ if (cLeft && cRight)
+ RTStrmPrintf(pState->pDiff, "---\n");
+ if (cRight)
+ scmDiffPrintLines(pState, '>', pState->pRight, iRight, cRight);
+
+ /*
+ * Reposition the streams (safely ignores return value).
+ */
+ ScmStreamSeekByLine(pState->pLeft, iLeft + cLeft + cMatches);
+ ScmStreamSeekByLine(pState->pRight, iRight + cRight + cMatches);
+
+ pState->cDiffs++;
+ return pState->cDiffs;
+}
+
+/**
+ * Helper for scmDiffCompare that takes care of trailing spaces and stuff
+ * like that.
+ */
+static bool scmDiffCompareSlow(PSCMDIFFSTATE pState,
+ const char *pchLeft, size_t cchLeft, SCMEOL enmEolLeft,
+ const char *pchRight, size_t cchRight, SCMEOL enmEolRight)
+{
+ if (pState->fIgnoreTrailingWhite)
+ {
+ while (cchLeft > 0 && RT_C_IS_SPACE(pchLeft[cchLeft - 1]))
+ cchLeft--;
+ while (cchRight > 0 && RT_C_IS_SPACE(pchRight[cchRight - 1]))
+ cchRight--;
+ }
+
+ if (pState->fIgnoreLeadingWhite)
+ {
+ while (cchLeft > 0 && RT_C_IS_SPACE(*pchLeft))
+ pchLeft++, cchLeft--;
+ while (cchRight > 0 && RT_C_IS_SPACE(*pchRight))
+ pchRight++, cchRight--;
+ }
+
+ if ( cchLeft != cchRight
+ || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)
+ || memcmp(pchLeft, pchRight, cchLeft))
+ return false;
+ return true;
+}
+
+/**
+ * Compare two lines.
+ *
+ * @returns true if the are equal, false if not.
+ */
+DECLINLINE(bool) scmDiffCompare(PSCMDIFFSTATE pState,
+ const char *pchLeft, size_t cchLeft, SCMEOL enmEolLeft,
+ const char *pchRight, size_t cchRight, SCMEOL enmEolRight)
+{
+ if ( cchLeft != cchRight
+ || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)
+ || memcmp(pchLeft, pchRight, cchLeft))
+ {
+ if ( pState->fIgnoreTrailingWhite
+ || pState->fIgnoreLeadingWhite)
+ return scmDiffCompareSlow(pState,
+ pchLeft, cchLeft, enmEolLeft,
+ pchRight, cchRight, enmEolRight);
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Compares two sets of lines from the two files.
+ *
+ * @returns true if they matches, false if they don't.
+ * @param pState The diff state.
+ * @param iLeft Where to start in the left stream.
+ * @param iRight Where to start in the right stream.
+ * @param cLines How many lines to compare.
+ */
+static bool scmDiffCompareLines(PSCMDIFFSTATE pState, size_t iLeft, size_t iRight, size_t cLines)
+{
+ for (size_t iLine = 0; iLine < cLines; iLine++)
+ {
+ SCMEOL enmEolLeft;
+ size_t cchLeft;
+ const char *pchLeft = ScmStreamGetLineByNo(pState->pLeft, iLeft + iLine, &cchLeft, &enmEolLeft);
+
+ SCMEOL enmEolRight;
+ size_t cchRight;
+ const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iRight + iLine, &cchRight, &enmEolRight);
+
+ if (!scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight))
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * Resynchronize the two streams and reports the difference.
+ *
+ * Upon return, the streams will be positioned after the block of @a cMatches
+ * lines where it resynchronized them.
+ *
+ * @returns pState->cDiffs (just so we can use it in a return statement).
+ * @param pState The state.
+ * @param cMatches The number of lines that needs to match for the
+ * stream to be considered synchronized again.
+ */
+static size_t scmDiffSynchronize(PSCMDIFFSTATE pState, size_t cMatches)
+{
+ size_t const iStartLeft = ScmStreamTellLine(pState->pLeft) - 1;
+ size_t const iStartRight = ScmStreamTellLine(pState->pRight) - 1;
+ Assert(cMatches > 0);
+
+ /*
+ * Compare each new line from each of the streams will all the preceding
+ * ones, including iStartLeft/Right.
+ */
+ for (size_t iRange = 1; ; iRange++)
+ {
+ /*
+ * Get the next line in the left stream and compare it against all the
+ * preceding lines on the right side.
+ */
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iRange, &cchLine, &enmEol);
+ if (!pchLine)
+ return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0);
+
+ for (size_t iRight = cMatches - 1; iRight < iRange; iRight++)
+ {
+ SCMEOL enmEolRight;
+ size_t cchRight;
+ const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRight,
+ &cchRight, &enmEolRight);
+ if ( scmDiffCompare(pState, pchLine, cchLine, enmEol, pchRight, cchRight, enmEolRight)
+ && scmDiffCompareLines(pState,
+ iStartLeft + iRange + 1 - cMatches,
+ iStartRight + iRight + 1 - cMatches,
+ cMatches - 1)
+ )
+ return scmDiffReport(pState, cMatches,
+ iStartLeft, iRange + 1 - cMatches,
+ iStartRight, iRight + 1 - cMatches);
+ }
+
+ /*
+ * Get the next line in the right stream and compare it against all the
+ * lines on the right side.
+ */
+ pchLine = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRange, &cchLine, &enmEol);
+ if (!pchLine)
+ return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0);
+
+ for (size_t iLeft = cMatches - 1; iLeft <= iRange; iLeft++)
+ {
+ SCMEOL enmEolLeft;
+ size_t cchLeft;
+ const char *pchLeft = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iLeft,
+ &cchLeft, &enmEolLeft);
+ if ( scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchLine, cchLine, enmEol)
+ && scmDiffCompareLines(pState,
+ iStartLeft + iLeft + 1 - cMatches,
+ iStartRight + iRange + 1 - cMatches,
+ cMatches - 1)
+ )
+ return scmDiffReport(pState, cMatches,
+ iStartLeft, iLeft + 1 - cMatches,
+ iStartRight, iRange + 1 - cMatches);
+ }
+ }
+}
+
+/**
+ * Creates a diff of the changes between the streams @a pLeft and @a pRight.
+ *
+ * This currently only implements the simplest diff format, so no contexts.
+ *
+ * Also, note that we won't detect differences in the final newline of the
+ * streams.
+ *
+ * @returns The number of differences.
+ * @param pszFilename The filename.
+ * @param pLeft The left side stream.
+ * @param pRight The right side stream.
+ * @param fIgnoreEol Whether to ignore end of line markers.
+ * @param fIgnoreLeadingWhite Set if leading white space should be ignored.
+ * @param fIgnoreTrailingWhite Set if trailing white space should be ignored.
+ * @param fSpecialChars Whether to print special chars in a human
+ * readable form or not.
+ * @param cchTab The tab size.
+ * @param pDiff Where to write the diff.
+ */
+size_t ScmDiffStreams(const char *pszFilename, PSCMSTREAM pLeft, PSCMSTREAM pRight, bool fIgnoreEol,
+ bool fIgnoreLeadingWhite, bool fIgnoreTrailingWhite, bool fSpecialChars,
+ size_t cchTab, PRTSTREAM pDiff)
+{
+#ifdef RT_STRICT
+ ScmStreamCheckItegrity(pLeft);
+ ScmStreamCheckItegrity(pRight);
+#endif
+
+ /*
+ * Set up the diff state.
+ */
+ SCMDIFFSTATE State;
+ State.cDiffs = 0;
+ State.pszFilename = pszFilename;
+ State.pLeft = pLeft;
+ State.pRight = pRight;
+ State.fIgnoreEol = fIgnoreEol;
+ State.fIgnoreLeadingWhite = fIgnoreLeadingWhite;
+ State.fIgnoreTrailingWhite = fIgnoreTrailingWhite;
+ State.fSpecialChars = fSpecialChars;
+ State.cchTab = cchTab;
+ State.pDiff = pDiff;
+
+ /*
+ * Compare them line by line.
+ */
+ ScmStreamRewindForReading(pLeft);
+ ScmStreamRewindForReading(pRight);
+ const char *pchLeft;
+ const char *pchRight;
+
+ for (;;)
+ {
+ SCMEOL enmEolLeft;
+ size_t cchLeft;
+ pchLeft = ScmStreamGetLine(pLeft, &cchLeft, &enmEolLeft);
+
+ SCMEOL enmEolRight;
+ size_t cchRight;
+ pchRight = ScmStreamGetLine(pRight, &cchRight, &enmEolRight);
+ if (!pchLeft || !pchRight)
+ break;
+
+ if (!scmDiffCompare(&State, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight))
+ scmDiffSynchronize(&State, 3);
+ }
+
+ /*
+ * Deal with any remaining differences.
+ */
+ if (pchLeft)
+ scmDiffReport(&State, 0, ScmStreamTellLine(pLeft) - 1, ~(size_t)0, ScmStreamTellLine(pRight), 0);
+ else if (pchRight)
+ scmDiffReport(&State, 0, ScmStreamTellLine(pLeft), 0, ScmStreamTellLine(pRight) - 1, ~(size_t)0);
+
+ /*
+ * Report any errors.
+ */
+ if (RT_FAILURE(ScmStreamGetStatus(pLeft)))
+ RTMsgError("Left diff stream error: %Rrc\n", ScmStreamGetStatus(pLeft));
+ if (RT_FAILURE(ScmStreamGetStatus(pRight)))
+ RTMsgError("Right diff stream error: %Rrc\n", ScmStreamGetStatus(pRight));
+
+ return State.cDiffs;
+}
+
diff --git a/src/bldprogs/scmdiff.h b/src/bldprogs/scmdiff.h
new file mode 100644
index 00000000..040904f7
--- /dev/null
+++ b/src/bldprogs/scmdiff.h
@@ -0,0 +1,74 @@
+/* $Id: scmdiff.h $ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager Diff Code.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_bldprogs_scmdiff_h
+#define VBOX_INCLUDED_SRC_bldprogs_scmdiff_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/stream.h>
+#include "scmstream.h"
+
+RT_C_DECLS_BEGIN
+
+/**
+ * Diff state.
+ */
+typedef struct SCMDIFFSTATE
+{
+ size_t cDiffs;
+ const char *pszFilename;
+
+ PSCMSTREAM pLeft;
+ PSCMSTREAM pRight;
+
+ /** Whether to ignore end of line markers when diffing. */
+ bool fIgnoreEol;
+ /** Whether to ignore trailing whitespace. */
+ bool fIgnoreTrailingWhite;
+ /** Whether to ignore leading whitespace. */
+ bool fIgnoreLeadingWhite;
+ /** Whether to print special characters in human readable form or not. */
+ bool fSpecialChars;
+ /** The tab size. */
+ size_t cchTab;
+ /** Where to push the diff. */
+ PRTSTREAM pDiff;
+} SCMDIFFSTATE;
+/** Pointer to a diff state. */
+typedef SCMDIFFSTATE *PSCMDIFFSTATE;
+
+
+size_t ScmDiffStreams(const char *pszFilename, PSCMSTREAM pLeft, PSCMSTREAM pRight, bool fIgnoreEol,
+ bool fIgnoreLeadingWhite, bool fIgnoreTrailingWhite, bool fSpecialChars,
+ size_t cchTab, PRTSTREAM pDiff);
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_bldprogs_scmdiff_h */
+
diff --git a/src/bldprogs/scmparser.cpp b/src/bldprogs/scmparser.cpp
new file mode 100644
index 00000000..557190c5
--- /dev/null
+++ b/src/bldprogs/scmparser.cpp
@@ -0,0 +1,1199 @@
+/* $Id: scmparser.cpp $ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager, Code Parsers.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/errcore.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+#include "scm.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef size_t (*PFNISCOMMENT)(const char *pchLine, size_t cchLine, bool fSecond);
+
+
+/**
+ * Callback for checking if C++ line comment.
+ */
+static size_t isCppLineComment(const char *pchLine, size_t cchLine, bool fSecond)
+{
+ if ( cchLine >= 2
+ && pchLine[0] == '/'
+ && pchLine[1] == '/')
+ {
+ if (!fSecond)
+ return 2;
+ if (cchLine >= 3 && pchLine[2] == '/')
+ return 3;
+ }
+ return 0;
+}
+
+
+/**
+ * Callback for checking if hash comment.
+ */
+static size_t isHashComment(const char *pchLine, size_t cchLine, bool fSecond)
+{
+ if (cchLine >= 1 && *pchLine == '#')
+ {
+ if (!fSecond)
+ return 1;
+ if (cchLine >= 2 && pchLine[1] == '#')
+ return 2;
+ }
+ return 0;
+}
+
+
+/**
+ * Callback for checking if semicolon comment.
+ */
+static size_t isSemicolonComment(const char *pchLine, size_t cchLine, bool fSecond)
+{
+ if (cchLine >= 1 && *pchLine == ';')
+ {
+ if (!fSecond)
+ return 1;
+ if (cchLine >= 2 && pchLine[1] == ';')
+ return 2;
+ }
+ return 0;
+}
+
+
+/** Macro for checking for a XML comment start. */
+#define IS_XML_COMMENT_START(a_pch, a_off, a_cch) \
+ ( (a_off) + 4 <= (a_cch) \
+ && (a_pch)[(a_off) ] == '<' \
+ && (a_pch)[(a_off) + 1] == '!' \
+ && (a_pch)[(a_off) + 2] == '-' \
+ && (a_pch)[(a_off) + 3] == '-' \
+ && ((a_off) + 4 == (a_cch) || RT_C_IS_SPACE((a_pch)[(a_off) + 4])) )
+
+/** Macro for checking for a XML comment end. */
+#define IS_XML_COMMENT_END(a_pch, a_off, a_cch) \
+ ( (a_off) + 3 <= (a_cch) \
+ && (a_pch)[(a_off) ] == '-' \
+ && (a_pch)[(a_off) + 1] == '-' \
+ && (a_pch)[(a_off) + 2] == '>')
+
+
+/** Macro for checking for a batch file comment prefix. */
+#define IS_REM(a_pch, a_off, a_cch) \
+ ( (a_off) + 3 <= (a_cch) \
+ && ((a_pch)[(a_off) ] == 'R' || (a_pch)[(a_off) ] == 'r') \
+ && ((a_pch)[(a_off) + 1] == 'E' || (a_pch)[(a_off) + 1] == 'e') \
+ && ((a_pch)[(a_off) + 2] == 'M' || (a_pch)[(a_off) + 2] == 'm') \
+ && ((a_off) + 3 == (a_cch) || RT_C_IS_SPACE((a_pch)[(a_off) + 3])) )
+
+
+/**
+ * Callback for checking if batch comment.
+ */
+static size_t isBatchComment(const char *pchLine, size_t cchLine, bool fSecond)
+{
+ if (!fSecond)
+ {
+ if (IS_REM(pchLine, 0, cchLine))
+ return 3;
+ }
+ else
+ {
+ /* Check for the 2nd in "rem rem" lines. */
+ if ( cchLine >= 4
+ && RT_C_IS_SPACE(*pchLine)
+ && IS_REM(pchLine, 1, cchLine))
+ return 4;
+ }
+ return 0;
+}
+
+/**
+ * Callback for checking if SQL comment.
+ */
+static size_t isSqlComment(const char *pchLine, size_t cchLine, bool fSecond)
+{
+ if ( cchLine >= 2
+ && pchLine[0] == '-'
+ && pchLine[1] == '-')
+ {
+ if (!fSecond)
+ return 2;
+ if ( cchLine >= 3
+ && pchLine[2] == '-')
+ return 3;
+ }
+ return 0;
+}
+
+/**
+ * Callback for checking if tick comment.
+ */
+static size_t isTickComment(const char *pchLine, size_t cchLine, bool fSecond)
+{
+ if (cchLine >= 1 && *pchLine == '\'')
+ {
+ if (!fSecond)
+ return 1;
+ if (cchLine >= 2 && pchLine[1] == '\'')
+ return 2;
+ }
+ return 0;
+}
+
+
+/**
+ * Common worker for enumeratePythonComments and enumerateSimpleLineComments.
+ *
+ * @returns IPRT status code.
+ * @param pIn The input stream.
+ * @param pfnIsComment Comment tester function.
+ * @param pfnCallback The callback.
+ * @param pvUser The user argument for the callback.
+ * @param ppchLine Pointer to the line variable.
+ * @param pcchLine Pointer to the line length variable.
+ * @param penmEol Pointer to the line ending type variable.
+ * @param piLine Pointer to the line number variable.
+ * @param poff Pointer to the line offset variable. On input this
+ * is positioned at the start of the comment.
+ */
+static int handleLineComment(PSCMSTREAM pIn, PFNISCOMMENT pfnIsComment,
+ PFNSCMCOMMENTENUMERATOR pfnCallback, void *pvUser,
+ const char **ppchLine, size_t *pcchLine, PSCMEOL penmEol,
+ uint32_t *piLine, size_t *poff)
+{
+ /* Unpack input/output variables. */
+ uint32_t iLine = *piLine;
+ const char *pchLine = *ppchLine;
+ size_t cchLine = *pcchLine;
+ size_t off = *poff;
+ SCMEOL enmEol = *penmEol;
+
+ /*
+ * Take down the basic info about the comment.
+ */
+ SCMCOMMENTINFO Info;
+ Info.iLineStart = iLine;
+ Info.iLineEnd = iLine;
+ Info.offStart = (uint32_t)off;
+ Info.offEnd = (uint32_t)cchLine;
+
+ size_t cchSkip = pfnIsComment(&pchLine[off], cchLine - off, false);
+ Assert(cchSkip > 0);
+ off += cchSkip;
+
+ /* Determine comment type. */
+ Info.enmType = kScmCommentType_Line;
+ char ch;
+ cchSkip = 1;
+ if ( off < cchLine
+ && ( (ch = pchLine[off]) == '!'
+ || (cchSkip = pfnIsComment(&pchLine[off], cchLine - off, true)) > 0) )
+ {
+ unsigned ch2;
+ if ( off + cchSkip == cchLine
+ || RT_C_IS_SPACE(ch2 = pchLine[off + cchSkip]) )
+ {
+ Info.enmType = ch != '!' ? kScmCommentType_Line_JavaDoc : kScmCommentType_Line_Qt;
+ off += cchSkip;
+ }
+ else if ( ch2 == '<'
+ && ( off + cchSkip + 1 == cchLine
+ || RT_C_IS_SPACE(pchLine[off + cchSkip + 1]) ))
+ {
+ Info.enmType = ch == '!' ? kScmCommentType_Line_JavaDoc_After : kScmCommentType_Line_Qt_After;
+ off += cchSkip + 1;
+ }
+ }
+
+ /*
+ * Copy body of the first line. Like for C, we ignore a single space in the first comment line.
+ */
+ if (off < cchLine && RT_C_IS_SPACE(pchLine[off]))
+ off++;
+ size_t cchBody = cchLine;
+ while (cchBody > off && RT_C_IS_SPACE(pchLine[cchBody - 1]))
+ cchBody--;
+ cchBody -= off;
+ size_t cbBodyAlloc = RT_MAX(_1K, RT_ALIGN_Z(cchBody + 64, 128));
+ char *pszBody = (char *)RTMemAlloc(cbBodyAlloc);
+ if (!pszBody)
+ return VERR_NO_MEMORY;
+ memcpy(pszBody, &pchLine[off], cchBody);
+ pszBody[cchBody] = '\0';
+
+ Info.cBlankLinesBefore = cchBody == 0;
+
+ /*
+ * Look for more comment lines and append them to the body.
+ */
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ iLine++;
+
+ /* Skip leading spaces. */
+ off = 0;
+ while (off < cchLine && RT_C_IS_SPACE(pchLine[off]))
+ off++;
+
+ /* Check if it's a comment. */
+ if ( off >= cchLine
+ || (cchSkip = pfnIsComment(&pchLine[off], cchLine - off, false)) == 0)
+ break;
+ off += cchSkip;
+
+ /* Split on doxygen comment start (if not already in one). */
+ if ( Info.enmType == kScmCommentType_Line
+ && off + 1 < cchLine
+ && ( pfnIsComment(&pchLine[off], cchLine - off, true) > 0
+ || ( pchLine[off + 1] == '!'
+ && ( off + 2 == cchLine
+ || pchLine[off + 2] != '!') ) ) )
+ {
+ off -= cchSkip;
+ break;
+ }
+
+ /* Append the body w/o trailing spaces and some leading ones. */
+ if (off < cchLine && RT_C_IS_SPACE(pchLine[off]))
+ off++;
+ while (off < cchLine && off < Info.offStart + 3 && RT_C_IS_SPACE(pchLine[off]))
+ off++;
+ size_t cchAppend = cchLine;
+ while (cchAppend > off && RT_C_IS_SPACE(pchLine[cchAppend - 1]))
+ cchAppend--;
+ cchAppend -= off;
+
+ size_t cchNewBody = cchBody + 1 + cchAppend;
+ if (cchNewBody >= cbBodyAlloc)
+ {
+ cbBodyAlloc = RT_MAX(cbBodyAlloc ? cbBodyAlloc * 2 : _1K, RT_ALIGN_Z(cchNewBody + 64, 128));
+ void *pvNew = RTMemRealloc(pszBody, cbBodyAlloc);
+ if (pvNew)
+ pszBody = (char *)pvNew;
+ else
+ {
+ RTMemFree(pszBody);
+ return VERR_NO_MEMORY;
+ }
+ }
+
+ if ( cchBody > 0
+ || cchAppend > 0)
+ {
+ if (cchBody > 0)
+ pszBody[cchBody++] = '\n';
+ memcpy(&pszBody[cchBody], &pchLine[off], cchAppend);
+ cchBody += cchAppend;
+ pszBody[cchBody] = '\0';
+ }
+ else
+ Info.cBlankLinesBefore++;
+
+ /* Advance. */
+ Info.offEnd = (uint32_t)cchLine;
+ Info.iLineEnd = iLine;
+ }
+
+ /*
+ * Strip trailing empty lines in the body.
+ */
+ Info.cBlankLinesAfter = 0;
+ while (cchBody >= 1 && pszBody[cchBody - 1] == '\n')
+ {
+ Info.cBlankLinesAfter++;
+ pszBody[--cchBody] = '\0';
+ }
+
+ /*
+ * Do the callback and return.
+ */
+ int rc = pfnCallback(&Info, pszBody, cchBody, pvUser);
+
+ RTMemFree(pszBody);
+
+ *piLine = iLine;
+ *ppchLine = pchLine;
+ *pcchLine = cchLine;
+ *poff = off;
+ *penmEol = enmEol;
+ return rc;
+}
+
+
+
+/**
+ * Common string literal handler.
+ *
+ * @returns new pchLine value.
+ * @param pIn The input string.
+ * @param chType The quotation type.
+ * @param pchLine The current line.
+ * @param ppchLine Pointer to the line variable.
+ * @param pcchLine Pointer to the line length variable.
+ * @param penmEol Pointer to the line ending type variable.
+ * @param piLine Pointer to the line number variable.
+ * @param poff Pointer to the line offset variable.
+ */
+static const char *handleStringLiteral(PSCMSTREAM pIn, char chType, const char *pchLine, size_t *pcchLine, PSCMEOL penmEol,
+ uint32_t *piLine, size_t *poff)
+{
+ size_t off = *poff;
+ for (;;)
+ {
+ bool fEnd = false;
+ bool fEscaped = false;
+ size_t const cchLine = *pcchLine;
+ while (off < cchLine)
+ {
+ char ch = pchLine[off++];
+ if (!fEscaped)
+ {
+ if (ch != chType)
+ {
+ if (ch != '\\')
+ { /* likely */ }
+ else
+ fEscaped = true;
+ }
+ else
+ {
+ fEnd = true;
+ break;
+ }
+ }
+ else
+ fEscaped = false;
+ }
+ if (fEnd)
+ break;
+
+ /* next line */
+ pchLine = ScmStreamGetLine(pIn, pcchLine, penmEol);
+ if (!pchLine)
+ break;
+ *piLine += 1;
+ off = 0;
+ }
+
+ *poff = off;
+ return pchLine;
+}
+
+
+/**
+ * Deals with comments in C and C++ code.
+ *
+ * @returns VBox status code / callback return code.
+ * @param pIn The stream to parse.
+ * @param pfnCallback The callback.
+ * @param pvUser The user parameter for the callback.
+ */
+static int enumerateCStyleComments(PSCMSTREAM pIn, PFNSCMCOMMENTENUMERATOR pfnCallback, void *pvUser)
+{
+ AssertCompile('\'' < '/');
+ AssertCompile('"' < '/');
+
+ int rcRet = VINF_SUCCESS;
+ uint32_t iLine = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ size_t off = 0;
+ while (off < cchLine)
+ {
+ unsigned ch = pchLine[off++];
+ if (ch > (unsigned)'/')
+ { /* not interesting */ }
+ else if (ch == '/')
+ {
+ if (off < cchLine)
+ {
+ ch = pchLine[off++];
+ if (ch == '*')
+ {
+ /*
+ * Multiline comment. Find the end.
+ *
+ * Note! This is very similar to the python doc string handling further down.
+ */
+ SCMCOMMENTINFO Info;
+ Info.iLineStart = iLine;
+ Info.offStart = (uint32_t)off - 2;
+ Info.iLineEnd = UINT32_MAX;
+ Info.offEnd = UINT32_MAX;
+ Info.cBlankLinesBefore = 0;
+
+ /* Determine comment type (same as for line-comments). */
+ Info.enmType = kScmCommentType_MultiLine;
+ if ( off < cchLine
+ && ( (ch = pchLine[off]) == '*'
+ || ch == '!') )
+ {
+ unsigned ch2;
+ if ( off + 1 == cchLine
+ || RT_C_IS_SPACE(ch2 = pchLine[off + 1]) )
+ {
+ Info.enmType = ch == '*' ? kScmCommentType_MultiLine_JavaDoc : kScmCommentType_MultiLine_Qt;
+ off += 1;
+ }
+ else if ( ch2 == '<'
+ && ( off + 2 == cchLine
+ || RT_C_IS_SPACE(pchLine[off + 2]) ))
+ {
+ Info.enmType = ch == '*' ? kScmCommentType_MultiLine_JavaDoc_After
+ : kScmCommentType_MultiLine_Qt_After;
+ off += 2;
+ }
+ }
+
+ /*
+ * Copy the body and find the end of the multiline comment.
+ */
+ size_t cbBodyAlloc = 0;
+ size_t cchBody = 0;
+ char *pszBody = NULL;
+ for (;;)
+ {
+ /* Parse the line up to the end-of-comment or end-of-line. */
+ size_t offLineStart = off;
+ size_t offLastNonBlank = off;
+ size_t offFirstNonBlank = ~(size_t)0;
+ while (off < cchLine)
+ {
+ ch = pchLine[off++];
+ if (ch != '*' || off >= cchLine || pchLine[off] != '/')
+ {
+ if (RT_C_IS_BLANK(ch))
+ {/* kind of likely */}
+ else
+ {
+ offLastNonBlank = off - 1;
+ if (offFirstNonBlank != ~(size_t)0)
+ {/* likely */}
+ else if ( ch != '*' /* ignore continuation-asterisks */
+ || off > Info.offStart + 1 + 1
+ || off > cchLine
+ || ( off < cchLine
+ && !RT_C_IS_SPACE(pchLine[off]))
+ || pszBody == NULL)
+ offFirstNonBlank = off - 1;
+ }
+ }
+ else
+ {
+ Info.offEnd = (uint32_t)++off;
+ Info.iLineEnd = iLine;
+ break;
+ }
+ }
+
+ /* Append line content to the comment body string. */
+ size_t cchAppend;
+ if (offFirstNonBlank == ~(size_t)0)
+ cchAppend = 0; /* empty line */
+ else
+ {
+ if (pszBody)
+ offLineStart = RT_MIN(Info.offStart + 3, offFirstNonBlank);
+ else if (offFirstNonBlank > Info.offStart + 2) /* Skip one leading blank at the start of the comment. */
+ offLineStart++;
+ cchAppend = offLastNonBlank + 1 - offLineStart;
+ Assert(cchAppend <= cchLine);
+ }
+
+ size_t cchNewBody = cchBody + (cchBody > 0) + cchAppend;
+ if (cchNewBody >= cbBodyAlloc)
+ {
+ cbBodyAlloc = RT_MAX(cbBodyAlloc ? cbBodyAlloc * 2 : _1K, RT_ALIGN_Z(cchNewBody + 64, 128));
+ void *pvNew = RTMemRealloc(pszBody, cbBodyAlloc);
+ if (pvNew)
+ pszBody = (char *)pvNew;
+ else
+ {
+ RTMemFree(pszBody);
+ return VERR_NO_MEMORY;
+ }
+ }
+
+ if (cchBody > 0) /* no leading blank lines */
+ pszBody[cchBody++] = '\n';
+ else if (cchAppend == 0)
+ Info.cBlankLinesBefore++;
+ memcpy(&pszBody[cchBody], &pchLine[offLineStart], cchAppend);
+ cchBody += cchAppend;
+ pszBody[cchBody] = '\0';
+
+ /* Advance to the next line, if we haven't yet seen the end of this comment. */
+ if (Info.iLineEnd != UINT32_MAX)
+ break;
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ if (!pchLine)
+ {
+ Info.offEnd = (uint32_t)cchLine;
+ Info.iLineEnd = iLine;
+ break;
+ }
+ iLine++;
+ off = 0;
+ }
+
+ /* Strip trailing empty lines in the body. */
+ Info.cBlankLinesAfter = 0;
+ while (cchBody >= 1 && pszBody[cchBody - 1] == '\n')
+ {
+ Info.cBlankLinesAfter++;
+ pszBody[--cchBody] = '\0';
+ }
+
+ /* Do the callback. */
+ int rc = pfnCallback(&Info, pszBody, cchBody, pvUser);
+ RTMemFree(pszBody);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (rc > VINF_SUCCESS && rcRet == VINF_SUCCESS)
+ rcRet = rc;
+ }
+ else if (ch == '/')
+ {
+ /*
+ * Line comment. Join the other line comment guys.
+ */
+ off -= 2;
+ int rc = handleLineComment(pIn, isCppLineComment, pfnCallback, pvUser,
+ &pchLine, &cchLine, &enmEol, &iLine, &off);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (rcRet == VINF_SUCCESS)
+ rcRet = rc;
+ }
+
+ if (!pchLine)
+ break;
+ }
+ }
+ else if (ch == '"')
+ {
+ /*
+ * String literal may include sequences that looks like comments. So,
+ * they needs special handling to avoid confusion.
+ */
+ pchLine = handleStringLiteral(pIn, '"', pchLine, &cchLine, &enmEol, &iLine, &off);
+ }
+ /* else: We don't have to deal with character literal as these shouldn't
+ include comment-like sequences. */
+ } /* for each character in the line */
+
+ iLine++;
+ } /* for each line in the stream */
+
+ int rcStream = ScmStreamGetStatus(pIn);
+ if (RT_SUCCESS(rcStream))
+ return rcRet;
+ return rcStream;
+}
+
+
+/**
+ * Deals with comments in Python code.
+ *
+ * @returns VBox status code / callback return code.
+ * @param pIn The stream to parse.
+ * @param pfnCallback The callback.
+ * @param pvUser The user parameter for the callback.
+ */
+static int enumeratePythonComments(PSCMSTREAM pIn, PFNSCMCOMMENTENUMERATOR pfnCallback, void *pvUser)
+{
+ AssertCompile('#' < '\'');
+ AssertCompile('"' < '\'');
+
+ int rcRet = VINF_SUCCESS;
+ uint32_t iLine = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ SCMCOMMENTINFO Info;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ size_t off = 0;
+ while (off < cchLine)
+ {
+ char ch = pchLine[off++];
+ if ((unsigned char)ch > (unsigned char)'\'')
+ { /* not interesting */ }
+ else if (ch == '#')
+ {
+ /*
+ * Line comment. Join paths with the others.
+ */
+ off -= 1;
+ int rc = handleLineComment(pIn, isHashComment, pfnCallback, pvUser,
+ &pchLine, &cchLine, &enmEol, &iLine, &off);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (rcRet == VINF_SUCCESS)
+ rcRet = rc;
+
+ if (!pchLine)
+ break;
+ }
+ else if (ch == '"' || ch == '\'')
+ {
+ /*
+ * String literal may be doc strings and they may legally include hashes.
+ */
+ const char chType = ch;
+ if ( off + 1 >= cchLine
+ || pchLine[off] != chType
+ || pchLine[off + 1] != chType)
+ pchLine = handleStringLiteral(pIn, chType, pchLine, &cchLine, &enmEol, &iLine, &off);
+ else
+ {
+ /*
+ * Doc string (/ long string).
+ *
+ * Note! This is very similar to the multiline C comment handling above.
+ */
+ Info.iLineStart = iLine;
+ Info.offStart = (uint32_t)off - 1;
+ Info.iLineEnd = UINT32_MAX;
+ Info.offEnd = UINT32_MAX;
+ Info.cBlankLinesBefore = 0;
+ Info.enmType = kScmCommentType_DocString;
+
+ off += 2;
+
+ /* Copy the body and find the end of the doc string comment. */
+ size_t cbBodyAlloc = 0;
+ size_t cchBody = 0;
+ char *pszBody = NULL;
+ for (;;)
+ {
+ /* Parse the line up to the end-of-comment or end-of-line. */
+ size_t offLineStart = off;
+ size_t offLastNonBlank = off;
+ size_t offFirstNonBlank = ~(size_t)0;
+ bool fEscaped = false;
+ while (off < cchLine)
+ {
+ ch = pchLine[off++];
+ if (!fEscaped)
+ {
+ if ( off + 1 >= cchLine
+ || ch != chType
+ || pchLine[off] != chType
+ || pchLine[off + 1] != chType)
+ {
+ if (RT_C_IS_BLANK(ch))
+ {/* kind of likely */}
+ else
+ {
+ offLastNonBlank = off - 1;
+ if (offFirstNonBlank != ~(size_t)0)
+ {/* likely */}
+ else if ( ch != '*' /* ignore continuation-asterisks */
+ || off > Info.offStart + 1 + 1
+ || off > cchLine
+ || ( off < cchLine
+ && !RT_C_IS_SPACE(pchLine[off]))
+ || pszBody == NULL)
+ offFirstNonBlank = off - 1;
+
+ if (ch != '\\')
+ {/* likely */ }
+ else
+ fEscaped = true;
+ }
+ }
+ else
+ {
+ off += 2;
+ Info.offEnd = (uint32_t)off;
+ Info.iLineEnd = iLine;
+ break;
+ }
+ }
+ else
+ fEscaped = false;
+ }
+
+ /* Append line content to the comment body string. */
+ size_t cchAppend;
+ if (offFirstNonBlank == ~(size_t)0)
+ cchAppend = 0; /* empty line */
+ else
+ {
+ if (pszBody)
+ offLineStart = RT_MIN(Info.offStart + 3, offFirstNonBlank);
+ else if (offFirstNonBlank > Info.offStart + 2) /* Skip one leading blank at the start of the comment. */
+ offLineStart++;
+ cchAppend = offLastNonBlank + 1 - offLineStart;
+ Assert(cchAppend <= cchLine);
+ }
+
+ size_t cchNewBody = cchBody + (cchBody > 0) + cchAppend;
+ if (cchNewBody >= cbBodyAlloc)
+ {
+ cbBodyAlloc = RT_MAX(cbBodyAlloc ? cbBodyAlloc * 2 : _1K, RT_ALIGN_Z(cchNewBody + 64, 128));
+ void *pvNew = RTMemRealloc(pszBody, cbBodyAlloc);
+ if (pvNew)
+ pszBody = (char *)pvNew;
+ else
+ {
+ RTMemFree(pszBody);
+ return VERR_NO_MEMORY;
+ }
+ }
+
+ if (cchBody > 0) /* no leading blank lines */
+ pszBody[cchBody++] = '\n';
+ else if (cchAppend == 0)
+ Info.cBlankLinesBefore++;
+ memcpy(&pszBody[cchBody], &pchLine[offLineStart], cchAppend);
+ cchBody += cchAppend;
+ pszBody[cchBody] = '\0';
+
+ /* Advance to the next line, if we haven't yet seen the end of this comment. */
+ if (Info.iLineEnd != UINT32_MAX)
+ break;
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ if (!pchLine)
+ {
+ Info.offEnd = (uint32_t)cchLine;
+ Info.iLineEnd = iLine;
+ break;
+ }
+ iLine++;
+ off = 0;
+ }
+
+ /* Strip trailing empty lines in the body. */
+ Info.cBlankLinesAfter = 0;
+ while (cchBody >= 1 && pszBody[cchBody - 1] == '\n')
+ {
+ Info.cBlankLinesAfter++;
+ pszBody[--cchBody] = '\0';
+ }
+
+ /* Do the callback. */
+ int rc = pfnCallback(&Info, pszBody, cchBody, pvUser);
+ RTMemFree(pszBody);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (rc > VINF_SUCCESS && rcRet == VINF_SUCCESS)
+ rcRet = rc;
+ }
+
+ if (!pchLine)
+ break;
+ }
+ /* else: We don't have to deal with character literal as these shouldn't
+ include comment-like sequences. */
+ } /* for each character in the line */
+
+ iLine++;
+ } /* for each line in the stream */
+
+ int rcStream = ScmStreamGetStatus(pIn);
+ if (RT_SUCCESS(rcStream))
+ return rcRet;
+ return rcStream;
+}
+
+
+/**
+ * Deals with XML comments.
+ *
+ * @returns VBox status code / callback return code.
+ * @param pIn The stream to parse.
+ * @param pfnCallback The callback.
+ * @param pvUser The user parameter for the callback.
+ */
+static int enumerateXmlComments(PSCMSTREAM pIn, PFNSCMCOMMENTENUMERATOR pfnCallback, void *pvUser)
+{
+ int rcRet = VINF_SUCCESS;
+ uint32_t iLine = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ size_t off = 0;
+ while (off < cchLine)
+ {
+ /*
+ * Skip leading blanks and check for start of XML comment.
+ */
+ while (off + 3 < cchLine && RT_C_IS_SPACE(pchLine[off]))
+ off++;
+ if (IS_XML_COMMENT_START(pchLine, off, cchLine))
+ {
+ /*
+ * XML comment. Find the end.
+ *
+ * Note! This is very similar to the python doc string handling above.
+ */
+ SCMCOMMENTINFO Info;
+ Info.iLineStart = iLine;
+ Info.offStart = (uint32_t)off;
+ Info.iLineEnd = UINT32_MAX;
+ Info.offEnd = UINT32_MAX;
+ Info.cBlankLinesBefore = 0;
+ Info.enmType = kScmCommentType_Xml;
+
+ off += 4;
+
+ /*
+ * Copy the body and find the end of the XML comment.
+ */
+ size_t cbBodyAlloc = 0;
+ size_t cchBody = 0;
+ char *pszBody = NULL;
+ for (;;)
+ {
+ /* Parse the line up to the end-of-comment or end-of-line. */
+ size_t offLineStart = off;
+ size_t offLastNonBlank = off;
+ size_t offFirstNonBlank = ~(size_t)0;
+ while (off < cchLine)
+ {
+ if (!IS_XML_COMMENT_END(pchLine, off, cchLine))
+ {
+ char ch = pchLine[off++];
+ if (RT_C_IS_BLANK(ch))
+ {/* kind of likely */}
+ else
+ {
+ offLastNonBlank = off - 1;
+ if (offFirstNonBlank != ~(size_t)0)
+ {/* likely */}
+ else if ( (ch != '*' && ch != '#') /* ignore continuation-asterisks */
+ || off > Info.offStart + 1 + 1
+ || off > cchLine
+ || ( off < cchLine
+ && !RT_C_IS_SPACE(pchLine[off]))
+ || pszBody == NULL)
+ offFirstNonBlank = off - 1;
+ }
+ }
+ else
+ {
+ off += 3;
+ Info.offEnd = (uint32_t)off;
+ Info.iLineEnd = iLine;
+ break;
+ }
+ }
+
+ /* Append line content to the comment body string. */
+ size_t cchAppend;
+ if (offFirstNonBlank == ~(size_t)0)
+ cchAppend = 0; /* empty line */
+ else
+ {
+ offLineStart = offFirstNonBlank;
+ cchAppend = offLastNonBlank + 1 - offLineStart;
+ Assert(cchAppend <= cchLine);
+ }
+
+ size_t cchNewBody = cchBody + (cchBody > 0) + cchAppend;
+ if (cchNewBody >= cbBodyAlloc)
+ {
+ cbBodyAlloc = RT_MAX(cbBodyAlloc ? cbBodyAlloc * 2 : _1K, RT_ALIGN_Z(cchNewBody + 64, 128));
+ void *pvNew = RTMemRealloc(pszBody, cbBodyAlloc);
+ if (pvNew)
+ pszBody = (char *)pvNew;
+ else
+ {
+ RTMemFree(pszBody);
+ return VERR_NO_MEMORY;
+ }
+ }
+
+ if (cchBody > 0) /* no leading blank lines */
+ pszBody[cchBody++] = '\n';
+ else if (cchAppend == 0)
+ Info.cBlankLinesBefore++;
+ memcpy(&pszBody[cchBody], &pchLine[offLineStart], cchAppend);
+ cchBody += cchAppend;
+ pszBody[cchBody] = '\0';
+
+ /* Advance to the next line, if we haven't yet seen the end of this comment. */
+ if (Info.iLineEnd != UINT32_MAX)
+ break;
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ if (!pchLine)
+ {
+ Info.offEnd = (uint32_t)cchLine;
+ Info.iLineEnd = iLine;
+ break;
+ }
+ iLine++;
+ off = 0;
+ }
+
+ /* Strip trailing empty lines in the body. */
+ Info.cBlankLinesAfter = 0;
+ while (cchBody >= 1 && pszBody[cchBody - 1] == '\n')
+ {
+ Info.cBlankLinesAfter++;
+ pszBody[--cchBody] = '\0';
+ }
+
+ /* Do the callback. */
+ int rc = pfnCallback(&Info, pszBody, cchBody, pvUser);
+ RTMemFree(pszBody);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (rc > VINF_SUCCESS && rcRet == VINF_SUCCESS)
+ rcRet = rc;
+ }
+ else
+ off++;
+ } /* for each character in the line */
+
+ iLine++;
+ } /* for each line in the stream */
+
+ int rcStream = ScmStreamGetStatus(pIn);
+ if (RT_SUCCESS(rcStream))
+ return rcRet;
+ return rcStream;
+}
+
+
+/**
+ * Deals with comments in DOS batch files.
+ *
+ * @returns VBox status code / callback return code.
+ * @param pIn The stream to parse.
+ * @param pfnCallback The callback.
+ * @param pvUser The user parameter for the callback.
+ */
+static int enumerateBatchComments(PSCMSTREAM pIn, PFNSCMCOMMENTENUMERATOR pfnCallback, void *pvUser)
+{
+ int rcRet = VINF_SUCCESS;
+ uint32_t iLine = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ while (pchLine != NULL)
+ {
+ /*
+ * Skip leading blanks and check for 'rem'.
+ * At the moment we do not parse '::label-comments'.
+ */
+ size_t off = 0;
+ while (off + 3 < cchLine && RT_C_IS_SPACE(pchLine[off]))
+ off++;
+ if (!IS_REM(pchLine, off, cchLine))
+ {
+ iLine++;
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ }
+ else
+ {
+ int rc = handleLineComment(pIn, isBatchComment, pfnCallback, pvUser,
+ &pchLine, &cchLine, &enmEol, &iLine, &off);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (rcRet == VINF_SUCCESS)
+ rcRet = rc;
+ }
+ }
+
+ int rcStream = ScmStreamGetStatus(pIn);
+ if (RT_SUCCESS(rcStream))
+ return rcRet;
+ return rcStream;
+}
+
+
+/**
+ * Deals with comments in SQL files.
+ *
+ * @returns VBox status code / callback return code.
+ * @param pIn The stream to parse.
+ * @param pfnCallback The callback.
+ * @param pvUser The user parameter for the callback.
+ */
+static int enumerateSqlComments(PSCMSTREAM pIn, PFNSCMCOMMENTENUMERATOR pfnCallback, void *pvUser)
+{
+ int rcRet = VINF_SUCCESS;
+ uint32_t iLine = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ while (pchLine != NULL)
+ {
+ /*
+ * Skip leading blanks and check for '--'.
+ */
+ size_t off = 0;
+ while (off + 3 < cchLine && RT_C_IS_SPACE(pchLine[off]))
+ off++;
+ if ( cchLine < 2
+ || pchLine[0] != '-'
+ || pchLine[1] != '-')
+ {
+ iLine++;
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ }
+ else
+ {
+ int rc = handleLineComment(pIn, isSqlComment, pfnCallback, pvUser,
+ &pchLine, &cchLine, &enmEol, &iLine, &off);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (rcRet == VINF_SUCCESS)
+ rcRet = rc;
+ }
+ }
+
+ int rcStream = ScmStreamGetStatus(pIn);
+ if (RT_SUCCESS(rcStream))
+ return rcRet;
+ return rcStream;
+}
+
+
+/**
+ * Deals with simple line comments.
+ *
+ * @returns VBox status code / callback return code.
+ * @param pIn The stream to parse.
+ * @param chStart The start of comment character.
+ * @param pfnIsComment Comment tester function.
+ * @param pfnCallback The callback.
+ * @param pvUser The user parameter for the callback.
+ */
+static int enumerateSimpleLineComments(PSCMSTREAM pIn, char chStart, PFNISCOMMENT pfnIsComment,
+ PFNSCMCOMMENTENUMERATOR pfnCallback, void *pvUser)
+{
+ int rcRet = VINF_SUCCESS;
+ uint32_t iLine = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ size_t off = 0;
+ while (off < cchLine)
+ {
+ char ch = pchLine[off++];
+ if (ch != chStart)
+ { /* not interesting */ }
+ else
+ {
+ off -= 1;
+ int rc = handleLineComment(pIn, pfnIsComment, pfnCallback, pvUser,
+ &pchLine, &cchLine, &enmEol, &iLine, &off);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (rcRet == VINF_SUCCESS)
+ rcRet = rc;
+
+ if (!pchLine)
+ break;
+ }
+ } /* for each character in the line */
+
+ iLine++;
+ } /* for each line in the stream */
+
+ int rcStream = ScmStreamGetStatus(pIn);
+ if (RT_SUCCESS(rcStream))
+ return rcRet;
+ return rcStream;
+}
+
+
+/**
+ * Enumerates the comments in the given stream, calling @a pfnCallback for each.
+ *
+ * @returns IPRT status code.
+ * @param pIn The stream to parse.
+ * @param enmCommentStyle The comment style of the source stream.
+ * @param pfnCallback The function to call.
+ * @param pvUser User argument to the callback.
+ */
+int ScmEnumerateComments(PSCMSTREAM pIn, SCMCOMMENTSTYLE enmCommentStyle, PFNSCMCOMMENTENUMERATOR pfnCallback, void *pvUser)
+{
+ switch (enmCommentStyle)
+ {
+ case kScmCommentStyle_C:
+ return enumerateCStyleComments(pIn, pfnCallback, pvUser);
+
+ case kScmCommentStyle_Python:
+ return enumeratePythonComments(pIn, pfnCallback, pvUser);
+
+ case kScmCommentStyle_Semicolon:
+ return enumerateSimpleLineComments(pIn, ';', isSemicolonComment, pfnCallback, pvUser);
+
+ case kScmCommentStyle_Hash:
+ return enumerateSimpleLineComments(pIn, '#', isHashComment, pfnCallback, pvUser);
+
+ case kScmCommentStyle_Rem_Upper:
+ case kScmCommentStyle_Rem_Lower:
+ case kScmCommentStyle_Rem_Camel:
+ return enumerateBatchComments(pIn, pfnCallback, pvUser);
+
+ case kScmCommentStyle_Sql:
+ return enumerateSqlComments(pIn, pfnCallback, pvUser);
+
+ case kScmCommentStyle_Tick:
+ return enumerateSimpleLineComments(pIn, '\'', isTickComment, pfnCallback, pvUser);
+
+ case kScmCommentStyle_Xml:
+ return enumerateXmlComments(pIn, pfnCallback, pvUser);
+
+ default:
+ AssertFailedReturn(VERR_INVALID_PARAMETER);
+ }
+}
+
diff --git a/src/bldprogs/scmrw-kmk.cpp b/src/bldprogs/scmrw-kmk.cpp
new file mode 100644
index 00000000..7907150b
--- /dev/null
+++ b/src/bldprogs/scmrw-kmk.cpp
@@ -0,0 +1,2273 @@
+/* $Id: scmrw-kmk.cpp $ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager, Makefile.kmk/kup.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+#include "scm.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef enum KMKASSIGNTYPE
+{
+ kKmkAssignType_Recursive,
+ kKmkAssignType_Conditional,
+ kKmkAssignType_Appending,
+ kKmkAssignType_Prepending,
+ kKmkAssignType_Simple,
+ kKmkAssignType_Immediate
+} KMKASSIGNTYPE;
+
+/** Context for scmKmkWordLength. */
+typedef enum
+{
+ /** Target file or assignment.
+ * Separators: space, '=', ':' */
+ kKmkWordCtx_TargetFileOrAssignment,
+ /** Target file.
+ * Separators: space, ':' */
+ kKmkWordCtx_TargetFile,
+ /** Dependency file or (target variable) assignment.
+ * Separators: space, '=', ':', '|' */
+ kKmkWordCtx_DepFileOrAssignment,
+ /** Dependency file.
+ * Separators: space, '|' */
+ kKmkWordCtx_DepFile,
+ /** Last context which may do double expansion. */
+ kKmkWordCtx_LastDoubleExpansion = kKmkWordCtx_DepFile
+} KMKWORDCTX;
+
+typedef struct KMKWORDSTATE
+{
+ uint16_t uDepth;
+ char chOpen;
+} KMKWORDSTATE;
+
+typedef enum KMKTOKEN
+{
+ kKmkToken_Word = 0,
+ kKmkToken_Comment,
+
+ /* Conditionals: */
+ kKmkToken_ifeq,
+ kKmkToken_ifneq,
+ kKmkToken_if1of,
+ kKmkToken_ifn1of,
+ kKmkToken_ifdef,
+ kKmkToken_ifndef,
+ kKmkToken_if,
+ kKmkToken_else,
+ kKmkToken_endif,
+
+ /* Includes: */
+ kKmkToken_include,
+ kKmkToken_sinclude,
+ kKmkToken_dash_include,
+ kKmkToken_includedep,
+ kKmkToken_includedep_queue,
+ kKmkToken_includedep_flush,
+
+ /* Others: */
+ kKmkToken_define,
+ kKmkToken_endef,
+ kKmkToken_export,
+ kKmkToken_unexport,
+ kKmkToken_local,
+ kKmkToken_override,
+ kKmkToken_undefine
+} KMKTOKEN;
+
+typedef struct KMKPARSER
+{
+ struct
+ {
+ KMKTOKEN enmToken;
+ bool fIgnoreNesting;
+ size_t iLine;
+ } aDepth[64];
+ unsigned iDepth;
+ unsigned iActualDepth;
+ bool fInRecipe;
+
+ /** The EOL type of the current line. */
+ SCMEOL enmEol;
+ /** The length of the current line. */
+ size_t cchLine;
+ /** Pointer to the start of the current line. */
+ char const *pchLine;
+
+ /** @name Only used for rule/assignment parsing.
+ * @{ */
+ /** Number of continuation lines at current rule/assignment. */
+ uint32_t cLines;
+ /** Characters in continuation lines at current rule/assignment. */
+ size_t cchTotalLine;
+ /** @} */
+
+ /** The SCM rewriter state. */
+ PSCMRWSTATE pState;
+ /** The input stream. */
+ PSCMSTREAM pIn;
+ /** The output stream. */
+ PSCMSTREAM pOut;
+ /** The settings. */
+ PCSCMSETTINGSBASE pSettings;
+ /** Scratch buffer. */
+ char szBuf[4096];
+} KMKPARSER;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static char const g_szTabs[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
+
+
+static KMKTOKEN scmKmkIdentifyToken(const char *pchWord, size_t cchWord)
+{
+ static struct { const char *psz; uint32_t cch; KMKTOKEN enmToken; } s_aTokens[] =
+ {
+ { RT_STR_TUPLE("if"), kKmkToken_if },
+ { RT_STR_TUPLE("ifeq"), kKmkToken_ifeq },
+ { RT_STR_TUPLE("ifneq"), kKmkToken_ifneq },
+ { RT_STR_TUPLE("if1of"), kKmkToken_if1of },
+ { RT_STR_TUPLE("ifn1of"), kKmkToken_ifn1of },
+ { RT_STR_TUPLE("ifdef"), kKmkToken_ifdef },
+ { RT_STR_TUPLE("ifndef"), kKmkToken_ifndef },
+ { RT_STR_TUPLE("else"), kKmkToken_else },
+ { RT_STR_TUPLE("endif"), kKmkToken_endif },
+ { RT_STR_TUPLE("include"), kKmkToken_include },
+ { RT_STR_TUPLE("sinclude"), kKmkToken_sinclude },
+ { RT_STR_TUPLE("-include"), kKmkToken_dash_include },
+ { RT_STR_TUPLE("includedep"), kKmkToken_includedep },
+ { RT_STR_TUPLE("includedep-queue"), kKmkToken_includedep_queue },
+ { RT_STR_TUPLE("includedep-flush"), kKmkToken_includedep_flush },
+ { RT_STR_TUPLE("define"), kKmkToken_define },
+ { RT_STR_TUPLE("endef"), kKmkToken_endef },
+ { RT_STR_TUPLE("export"), kKmkToken_export },
+ { RT_STR_TUPLE("unexport"), kKmkToken_unexport },
+ { RT_STR_TUPLE("local"), kKmkToken_local },
+ { RT_STR_TUPLE("override"), kKmkToken_override },
+ { RT_STR_TUPLE("undefine"), kKmkToken_undefine },
+ };
+ char chFirst = *pchWord;
+ if ( chFirst == 'i'
+ || chFirst == 'e'
+ || chFirst == 'd'
+ || chFirst == 's'
+ || chFirst == '-'
+ || chFirst == 'u'
+ || chFirst == 'l'
+ || chFirst == 'o')
+ {
+ for (size_t i = 0; i < RT_ELEMENTS(s_aTokens); i++)
+ if ( s_aTokens[i].cch == cchWord
+ && *s_aTokens[i].psz == chFirst
+ && memcmp(s_aTokens[i].psz, pchWord, cchWord) == 0)
+ return s_aTokens[i].enmToken;
+ }
+#ifdef VBOX_STRICT
+ else
+ for (size_t i = 0; i < RT_ELEMENTS(s_aTokens); i++)
+ Assert(chFirst != *s_aTokens[i].psz);
+#endif
+
+ if (chFirst == '#')
+ return kKmkToken_Comment;
+ return kKmkToken_Word;
+}
+
+
+/**
+ * Modifies the fInRecipe state variable, logging changes in verbose mode.
+ */
+static void scmKmkSetInRecipe(KMKPARSER *pParser, bool fInRecipe)
+{
+ if (pParser->fInRecipe != fInRecipe)
+ ScmVerbose(pParser->pState, 4, "%u: debug: %s\n",
+ ScmStreamTellLine(pParser->pIn), fInRecipe ? "in-recipe" : "not-in-recipe");
+ pParser->fInRecipe = fInRecipe;
+}
+
+
+/**
+ * Gives up on the current line, copying it as it and requesting manual repair.
+ */
+static bool scmKmkGiveUp(KMKPARSER *pParser, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ ScmFixManually(pParser->pState, "%u: %N\n", ScmStreamTellLine(pParser->pIn), pszFormat, &va);
+ va_end(va);
+
+ ScmStreamPutLine(pParser->pOut, pParser->pchLine, pParser->cchLine, pParser->enmEol);
+ return false;
+}
+
+
+static bool scmKmkIsLineWithContinuationSlow(const char *pchLine, size_t cchLine)
+{
+ size_t cchSlashes = 1;
+ cchLine--;
+ while (cchSlashes < cchLine && pchLine[cchLine - cchSlashes - 1] == '\\')
+ cchSlashes++;
+ return RT_BOOL(cchSlashes & 1);
+}
+
+
+DECLINLINE(bool) scmKmkIsLineWithContinuation(const char *pchLine, size_t cchLine)
+{
+ if (cchLine == 0 || pchLine[cchLine - 1] != '\\')
+ return false;
+ return scmKmkIsLineWithContinuationSlow(pchLine, cchLine);
+}
+
+
+/**
+ * Finds the length of a line where line continuation is in play.
+ *
+ * @returns Length from start of current line to the final unescaped EOL.
+ * @param pParser The KMK parser state.
+ * @param pcLine Where to return the number of lines. Optional.
+ * @param pcchMaxLeadWord Where to return the max lead word length on
+ * subsequent lines. Used to help balance multi-line
+ * 'if' statements (imperfect). Optional.
+ */
+static size_t scmKmkLineContinuationPeek(KMKPARSER *pParser, uint32_t *pcLines, size_t *pcchMaxLeadWord)
+{
+ size_t const offSaved = ScmStreamTell(pParser->pIn);
+ uint32_t cLines = 1;
+ size_t cchMaxLeadWord = 0;
+ const char *pchLine = pParser->pchLine;
+ size_t cchLine = pParser->cchLine;
+ SCMEOL enmEol;
+ for (;;)
+ {
+ /* Return if no line continuation (or end of stream): */
+ if ( cchLine == 0
+ || !scmKmkIsLineWithContinuation(pchLine, cchLine)
+ || ScmStreamIsEndOfStream(pParser->pIn))
+ {
+ ScmStreamSeekAbsolute(pParser->pIn, offSaved);
+ if (pcLines)
+ *pcLines = cLines;
+ if (pcchMaxLeadWord)
+ *pcchMaxLeadWord = cchMaxLeadWord;
+ return (size_t)(pchLine - pParser->pchLine) + cchLine;
+ }
+
+ /* Get the next line: */
+ pchLine = ScmStreamGetLine(pParser->pIn, &cchLine, &enmEol);
+ cLines++;
+
+ /* Check the length of the first word if requested: */
+ if (pcchMaxLeadWord)
+ {
+ size_t offLine = 0;
+ while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
+ offLine++;
+
+ size_t const offStartWord = offLine;
+ while (offLine < cchLine && !RT_C_IS_BLANK(pchLine[offLine]))
+ offLine++;
+
+ if (offLine - offStartWord > cchMaxLeadWord)
+ cchMaxLeadWord = offLine - offStartWord;
+ }
+ }
+}
+
+
+/**
+ * Checks if the given line contains a comment with the @a pszMarker word in it.
+ *
+ * This can be used to disable warnings.
+ *
+ * @returns true if this is the case, false if not
+ * @param pchLine The line.
+ * @param cchLine The line length.
+ * @param offLine The current line position, 0 if uncertain.
+ * @param pszMarker The marker to check for.
+ * @param cchMarker The length of the marker string.
+ */
+static bool scmKmkHasCommentMarker(const char *pchLine, size_t cchLine, size_t offLine, const char *pszMarker, size_t cchMarker)
+{
+ const char *pchCur = (const char *)memchr(&pchLine[offLine], '#', cchLine - RT_MIN(offLine, cchLine));
+ if (pchCur)
+ {
+ pchCur++;
+ size_t cchLeft = (size_t)(&pchLine[cchLine] - pchCur);
+ while (cchLeft >= cchMarker)
+ {
+ const char *pchHit = (char *)memchr(pchCur, *pszMarker, cchLeft - cchMarker + 1);
+ if (!pchHit)
+ break;
+ if (memcmp(pchHit, pszMarker, cchMarker) == 0)
+ return true;
+ pchCur = pchHit + 1;
+ cchLeft = (size_t)(&pchLine[cchLine] - pchCur);
+ }
+ }
+ return false;
+}
+
+
+/**
+ * Pushes a if or define on the nesting stack.
+ */
+static bool scmKmkPushNesting(KMKPARSER *pParser, KMKTOKEN enmToken)
+{
+ uint32_t iDepth = pParser->iDepth;
+ if (iDepth + 1 >= RT_ELEMENTS(pParser->aDepth))
+ {
+ ScmError(pParser->pState, VERR_ASN1_TOO_DEEPLY_NESTED /*?*/,
+ "%u: Too deep if/define nesting!\n", ScmStreamTellLine(pParser->pIn));
+ return false;
+ }
+
+ pParser->aDepth[iDepth].enmToken = enmToken;
+ pParser->aDepth[iDepth].iLine = ScmStreamTellLine(pParser->pIn);
+ pParser->aDepth[iDepth].fIgnoreNesting = false;
+ pParser->iDepth = iDepth + 1;
+ pParser->iActualDepth += 1;
+ ScmVerbose(pParser->pState, 5, "%u: debug: nesting %u (token %u)\n", pParser->aDepth[iDepth].iLine, iDepth + 1, enmToken);
+ return true;
+}
+
+
+/**
+ * Checks if we're inside a define or not.
+ */
+static bool scmKmkIsInsideDefine(KMKPARSER const *pParser)
+{
+ unsigned iDepth = pParser->iDepth;
+ while (iDepth-- > 0)
+ if (pParser->aDepth[iDepth].enmToken == kKmkToken_define)
+ return true;
+ return false;
+}
+
+
+/**
+ * Skips a string stopping at @a chStop1 or @a chStop2, taking $() and ${} into
+ * account.
+ */
+static size_t scmKmkSkipExpString(const char *pchLine, size_t cchLine, size_t off, char chStop1, char chStop2 = '\0')
+{
+ unsigned iExpDepth = 0;
+ char ch;
+ while ( off < cchLine
+ && (ch = pchLine[off])
+ && ( (ch != chStop1 && ch != chStop2)
+ || iExpDepth > 0))
+ {
+ off++;
+ if (ch == '$')
+ {
+ ch = pchLine[off];
+ if (ch == '(' || ch == '{')
+ {
+ iExpDepth++;
+ off++;
+ }
+ }
+ else if ((ch == ')' || ch == '}') && iExpDepth > 0)
+ iExpDepth--;
+ }
+ return off;
+}
+
+
+/**
+ * Finds the length of the word (file) @a offStart.
+ *
+ * This only takes one line into account, so variable expansions (function
+ * calls) spanning multiple lines will be handled as one word per line with help
+ * from @a pState. This allows the caller to properly continutation intend the
+ * additional lines.
+ *
+ * @returns Length of word starting at @a offStart. Zero if there is whitespace
+ * at given offset or it's beyond the end of the line (both cases will
+ * assert).
+ * @param pchLine The line.
+ * @param cchLine The line length.
+ * @param offStart Offset to the start of the word.
+ * @param pState Where multiline variable expansion is tracked.
+ */
+static size_t scmKmkWordLength(const char *pchLine, size_t cchLine, size_t offStart, KMKWORDCTX enmCtx, KMKWORDSTATE *pState)
+{
+ AssertReturn(offStart < cchLine && !RT_C_IS_BLANK(pchLine[offStart]), 0);
+
+ /*
+ * Drop any line continuation slash from the line length, so we don't count
+ * it into the word length. Also, any spaces preceeding it (for multiline
+ * variable function expansion). ASSUMES no trailing slash escaping.
+ */
+ if (cchLine > 0 && pchLine[cchLine - 1] == '\\')
+ do
+ cchLine--;
+ while (cchLine > offStart && RT_C_IS_SPACE(pchLine[cchLine - 1]));
+
+ /*
+ * If we were inside a variable function expansion, continue till we reach the end.
+ * This kind of duplicates the code below.
+ */
+ size_t off = offStart;
+ if (pState->uDepth > 0)
+ {
+ Assert(pState->chOpen == '(' || pState->chOpen == '{');
+ char const chOpen = pState->chOpen;
+ char const chClose = chOpen == '(' ? ')' : '}';
+ unsigned uDepth = pState->uDepth;
+ for (;;)
+ {
+ char ch;
+ if (off < cchLine)
+ ch = pchLine[off++];
+ else /* Reached the end while still inside the expansion. */
+ {
+ pState->chOpen = chOpen;
+ pState->uDepth = (uint16_t)uDepth;
+ return cchLine - offStart;
+ }
+ if (ch == chOpen)
+ uDepth++;
+ else if (ch == chClose && --uDepth == 0)
+ break;
+ }
+ pState->uDepth = 0;
+ pState->chOpen = 0;
+ }
+
+ /*
+ * Process till we find blank or end of the line.
+ */
+ while (off < cchLine)
+ {
+ char ch = pchLine[off];
+ if (RT_C_IS_BLANK(ch))
+ break;
+
+ if (ch == '$')
+ {
+ /*
+ * Skip variable expansion. ASSUMING double expansion being enabled
+ * for rules, we respond to both $() and $$() here, $$$$()
+ */
+ size_t cDollars = 0;
+ do
+ {
+ off++;
+ if (off >= cchLine)
+ return cchLine - offStart;
+ cDollars++;
+ ch = pchLine[off];
+ } while (ch == '$');
+ if ((cDollars & 1) || (cDollars == 2 && enmCtx <= kKmkWordCtx_LastDoubleExpansion))
+ {
+ char const chOpen = ch;
+ if (ch == '(' || ch == '{')
+ {
+ char const chClose = chOpen == '(' ? ')' : '}';
+ unsigned uDepth = 1;
+ off++;
+ for (;;)
+ {
+ if (off < cchLine)
+ ch = pchLine[off++];
+ else /* Reached the end while inside the expansion. */
+ {
+ pState->chOpen = chOpen;
+ pState->uDepth = (uint16_t)uDepth;
+ return cchLine - offStart;
+ }
+ if (ch == chOpen)
+ uDepth++;
+ else if (ch == chClose && --uDepth == 0)
+ break;
+ }
+ }
+ else if (cDollars & 1)
+ off++; /* $X */
+ }
+ continue;
+ }
+ else if (ch == ':')
+ {
+ /*
+ * Check for plain driver letter, omitting the archive member variant.
+ */
+ if (off - offStart != 1 || !RT_C_IS_ALPHA(pchLine[off - 1]))
+ {
+ if (off == offStart)
+ {
+ /* We need to check for single and double colon rules as well as
+ simple and immediate assignments here. */
+ off++;
+ if (pchLine[off] == ':')
+ {
+ off++;
+ if (pchLine[off] == '=')
+ {
+ if (enmCtx == kKmkWordCtx_TargetFileOrAssignment || enmCtx == kKmkWordCtx_DepFileOrAssignment)
+ return 3; /* ::= - immediate assignment. */
+ off++;
+ }
+ else if (enmCtx != kKmkWordCtx_DepFile)
+ return 2; /* :: - double colon rule */
+ }
+ else if (pchLine[off] == '=')
+ {
+ if (enmCtx == kKmkWordCtx_TargetFileOrAssignment || enmCtx == kKmkWordCtx_DepFileOrAssignment)
+ return 2; /* := - simple assignment. */
+ off++;
+ }
+ else if (enmCtx != kKmkWordCtx_DepFile)
+ return 1; /* : - regular rule. */
+ continue;
+ }
+ /* ':' is a separator except in DepFile context. */
+ else if (enmCtx != kKmkWordCtx_DepFile)
+ return off - offStart;
+ }
+ }
+ else if (ch == '=')
+ {
+ /*
+ * Assignment. We check for the previous character too so we'll catch
+ * append, prepend and conditional assignments. Simple and immediate
+ * assignments are handled above.
+ */
+ if ( enmCtx == kKmkWordCtx_TargetFileOrAssignment
+ || enmCtx == kKmkWordCtx_DepFileOrAssignment)
+ {
+ if (off > offStart)
+ {
+ ch = pchLine[off - 1];
+ if (ch == '?' || ch == '+' || ch == '>')
+ off = off - 1 == offStart
+ ? off + 2 /* return '+=', '?=', '<=' */
+ : off - 1; /* up to '+=', '?=', '<=' */
+ else
+ Assert(ch != ':'); /* handled above */
+ }
+ else
+ off++; /* '=' */
+ return off - offStart;
+ }
+ }
+ else if (ch == '|')
+ {
+ /*
+ * This is rather straight forward.
+ */
+ if (enmCtx == kKmkWordCtx_DepFileOrAssignment || enmCtx == kKmkWordCtx_DepFile)
+ {
+ if (off == offStart)
+ return 1;
+ return off - offStart;
+ }
+ }
+ off++;
+ }
+ return off - offStart;
+}
+
+
+static bool scmKmkTailComment(KMKPARSER *pParser, const char *pchLine, size_t cchLine, size_t offSrc, char **ppszDst)
+{
+ /* Wind back offSrc to the first blank space (not all callers can do this). */
+ Assert(offSrc <= cchLine);
+ while (offSrc > 0 && RT_C_IS_SPACE(pchLine[offSrc - 1]))
+ offSrc--;
+ size_t const offSrcStart = offSrc;
+
+ /* Skip blanks. */
+ while (offSrc < cchLine && RT_C_IS_SPACE(pchLine[offSrc]))
+ offSrc++;
+ if (offSrc >= cchLine)
+ return true;
+
+ /* Is it a comment? */
+ char *pszDst = *ppszDst;
+ if (pchLine[offSrc] == '#')
+ {
+ /* Try preserve the start column number. */
+/** @todo tabs */
+ size_t const offDst = pszDst - pParser->szBuf;
+ if (offDst < offSrc)
+ {
+ memset(pszDst, ' ', offSrc - offDst);
+ pszDst += offSrc - offDst;
+ }
+ else if (offSrc != offSrcStart)
+ *pszDst++ = ' ';
+
+ *ppszDst = pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchLine - offSrc);
+ return false; /*dummy*/
+ }
+
+ /* Complain and copy out the text unmodified. */
+ ScmError(pParser->pState, VERR_PARSE_ERROR, "%u:%u: Expected comment, found: %.*s",
+ ScmStreamTellLine(pParser->pIn), offSrc, cchLine - offSrc, &pchLine[offSrc]);
+ *ppszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchLine - offSrcStart);
+ return false; /*dummy*/
+}
+
+
+/**
+ * Deals with: ifeq, ifneq, if1of and ifn1of
+ *
+ * @returns dummy (false) to facility return + call.
+ */
+static bool scmKmkHandleIfParentheses(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchToken, bool fElse)
+{
+ const char * const pchLine = pParser->pchLine;
+ size_t const cchLine = pParser->cchLine;
+ uint32_t const cchIndent = pParser->iActualDepth
+ - (fElse && pParser->iActualDepth > 0 && !pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting);
+
+ /*
+ * Push it onto the stack. All these nestings are relevant.
+ */
+ if (!fElse)
+ {
+ if (!scmKmkPushNesting(pParser, enmToken))
+ return false;
+ }
+ else
+ {
+ pParser->aDepth[pParser->iDepth - 1].enmToken = enmToken;
+ pParser->aDepth[pParser->iDepth - 1].iLine = ScmStreamTellLine(pParser->pIn);
+ }
+
+ /*
+ * We do not allow line continuation for these.
+ */
+ if (scmKmkIsLineWithContinuation(pchLine, cchLine))
+ return scmKmkGiveUp(pParser, "Line continuation not allowed with '%.*s' directive.", cchToken, &pchLine[offToken]);
+
+ /*
+ * We stage the modified line in the buffer, so check that the line isn't
+ * too long (it seriously should be).
+ */
+ if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf))
+ return scmKmkGiveUp(pParser, "Line too long for a '%.*s' directive: %u chars", cchToken, &pchLine[offToken], cchLine);
+ char *pszDst = pParser->szBuf;
+
+ /*
+ * Emit indent and initial token.
+ */
+ memset(pszDst, ' ', cchIndent);
+ pszDst += cchIndent;
+
+ if (fElse)
+ pszDst = (char *)mempcpy(pszDst, RT_STR_TUPLE("else "));
+
+ memcpy(pszDst, &pchLine[offToken], cchToken);
+ pszDst += cchToken;
+
+ size_t offSrc = offToken + cchToken;
+
+ /*
+ * There shall be exactly one space between the token and the opening parenthesis.
+ */
+ if (pchLine[offSrc] == ' ' && pchLine[offSrc + 1] == '(')
+ offSrc += 2;
+ else
+ {
+ while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
+ offSrc++;
+ if (pchLine[offSrc] != '(')
+ return scmKmkGiveUp(pParser, "Expected '(' to follow '%.*s'", cchToken, &pchLine[offToken]);
+ offSrc++;
+ }
+ *pszDst++ = ' ';
+ *pszDst++ = '(';
+
+ /*
+ * Skip spaces after the opening parenthesis.
+ */
+ while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
+ offSrc++;
+
+ /*
+ * Work up to the ',' separator. It shall likewise not be preceeded by any spaces.
+ * Need to take $(func 1,2,3) calls into account here, so we trac () and {} while
+ * skipping ahead.
+ */
+ if (pchLine[offSrc] != ',')
+ {
+ size_t const offSrcStart = offSrc;
+ offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ',');
+ if (pchLine[offSrc] != ',')
+ return scmKmkGiveUp(pParser, "Expected ',' somewhere after '%.*s('", cchToken, &pchLine[offToken]);
+
+ size_t cchCopy = offSrc - offSrcStart;
+ while (cchCopy > 0 && RT_C_IS_BLANK(pchLine[offSrcStart + cchCopy - 1]))
+ cchCopy--;
+
+ pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchCopy);
+ }
+ /* 'if1of(, stuff)' does not make sense in committed code: */
+ else if (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of)
+ return scmKmkGiveUp(pParser, "Left set cannot be empty for '%.*s'", cchToken, &pchLine[offToken]);
+ offSrc++;
+ *pszDst++ = ',';
+
+ /*
+ * For if1of and ifn1of we require a space after the comma, whereas ifeq and
+ * ifneq shall not have any blanks. This is to help tell them apart.
+ */
+ if (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of)
+ {
+ *pszDst++ = ' ';
+ if (pchLine[offSrc] == ' ')
+ offSrc++;
+ }
+ while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
+ offSrc++;
+
+ if (pchLine[offSrc] != ')')
+ {
+ size_t const offSrcStart = offSrc;
+ offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ')');
+ if (pchLine[offSrc] != ')')
+ return scmKmkGiveUp(pParser, "No closing parenthesis for '%.*s'?", cchToken, &pchLine[offToken]);
+
+ size_t cchCopy = offSrc - offSrcStart;
+ while (cchCopy > 0 && RT_C_IS_BLANK(pchLine[offSrcStart + cchCopy - 1]))
+ cchCopy--;
+
+ pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], cchCopy);
+ }
+ /* 'if1of(stuff, )' does not make sense in committed code: */
+ else if ( (enmToken == kKmkToken_if1of || enmToken == kKmkToken_ifn1of)
+ && !scmKmkHasCommentMarker(pchLine, cchLine, offSrc, RT_STR_TUPLE("scm:ignore-empty-if1of-set")))
+ return scmKmkGiveUp(pParser, "Right set cannot be empty for '%.*s'", cchToken, &pchLine[offToken]);
+ offSrc++;
+ *pszDst++ = ')';
+
+ /*
+ * Handle comment.
+ */
+ if (offSrc < cchLine)
+ scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
+
+ /*
+ * Done.
+ */
+ *pszDst = '\0';
+ ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
+ return false; /* dummy */
+}
+
+
+/**
+ * Deals with: if, ifdef and ifndef
+ *
+ * @returns dummy (false) to facility return + call.
+ */
+static bool scmKmkHandleIfSpace(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchToken, bool fElse)
+{
+ const char *pchLine = pParser->pchLine;
+ size_t cchLine = pParser->cchLine;
+ uint32_t const cchIndent = pParser->iActualDepth
+ - (fElse && pParser->iActualDepth > 0 && !pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting);
+
+ /*
+ * Push it onto the stack.
+ *
+ * For ifndef we ignore the outmost ifndef in non-Makefile.kmk files, if
+ * the define matches the typical pattern for a file blocker.
+ */
+ bool fIgnoredNesting = false;
+ if (!fElse)
+ {
+ if (!scmKmkPushNesting(pParser, enmToken))
+ return false;
+ if (enmToken == kKmkToken_ifndef)
+ {
+ /** @todo */
+ }
+ }
+ else
+ {
+ pParser->aDepth[pParser->iDepth - 1].enmToken = enmToken;
+ pParser->aDepth[pParser->iDepth - 1].iLine = ScmStreamTellLine(pParser->pIn);
+ }
+
+ /*
+ * We do not allow line continuation for these.
+ */
+ uint32_t cLines = 1;
+ size_t cchMaxLeadWord = 0;
+ size_t cchTotalLine = cchLine;
+ if (scmKmkIsLineWithContinuation(pchLine, cchLine))
+ {
+ if (enmToken != kKmkToken_if)
+ return scmKmkGiveUp(pParser, "Line continuation not allowed with '%.*s' directive.", cchToken, &pchLine[offToken]);
+ cchTotalLine = scmKmkLineContinuationPeek(pParser, &cLines, &cchMaxLeadWord);
+ }
+
+ /*
+ * We stage the modified line in the buffer, so check that the line isn't
+ * too long (plain if can be long, but not ifndef/ifdef).
+ */
+ if (cchTotalLine + pParser->iActualDepth + 32 > sizeof(pParser->szBuf))
+ return scmKmkGiveUp(pParser, "Line too long for a '%.*s' directive: %u chars",
+ cchToken, &pchLine[offToken], cchTotalLine);
+ char *pszDst = pParser->szBuf;
+
+ /*
+ * Emit indent and initial token.
+ */
+ memset(pszDst, ' ', cchIndent);
+ pszDst += cchIndent;
+
+ if (fElse)
+ pszDst = (char *)mempcpy(pszDst, RT_STR_TUPLE("else "));
+
+ memcpy(pszDst, &pchLine[offToken], cchToken);
+ pszDst += cchToken;
+
+ size_t offSrc = offToken + cchToken;
+
+ /*
+ * ifndef/ifdef shall have exactly one space. For 'if' we allow up to 4, but
+ * we'll deal with that further down.
+ */
+ size_t cchSpaces = 0;
+ while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
+ {
+ cchSpaces++;
+ offSrc++;
+ }
+ if (cchSpaces == 0)
+ return scmKmkGiveUp(pParser, "Nothing following '%.*s' or bogus line continuation?", cchToken, &pchLine[offToken]);
+ *pszDst++ = ' ';
+
+ /*
+ * For ifdef and ifndef there now comes a single word.
+ */
+ if (enmToken != kKmkToken_if)
+ {
+ size_t const offSrcStart = offSrc;
+ offSrc = scmKmkSkipExpString(pchLine, cchLine, offSrc, ' ', '\t'); /** @todo probably not entirely correct */
+ if (offSrc == offSrcStart)
+ return scmKmkGiveUp(pParser, "No word following '%.*s'?", cchToken, &pchLine[offToken]);
+
+ pszDst = (char *)mempcpy(pszDst, &pchLine[offSrcStart], offSrc - offSrcStart);
+ }
+ /*
+ * While for 'if' things are more complicated, especially if it spans more
+ * than one line.
+ */
+ else if (cLines <= 1)
+ {
+ /* Single line expression: Just assume the expression goes up to the
+ EOL or comment hash. Strip and copy as-is for now. */
+ const char *pchSrcHash = (const char *)memchr(&pchLine[offSrc], '#', cchLine - offSrc);
+ size_t cchExpr = pchSrcHash ? pchSrcHash - &pchLine[offSrc] : cchLine - offSrc;
+ while (cchExpr > 0 && RT_C_IS_BLANK(pchLine[offSrc + cchExpr - 1]))
+ cchExpr--;
+
+ pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchExpr);
+ offSrc += cchExpr;
+ }
+ else
+ {
+ /* Multi line expression: We normalize leading whitespace using
+ cchMaxLeadWord for now. Expression on line 2+ are indented by two
+ extra characters, because we'd otherwise be puttin the operator on
+ the same level as the 'if', which would be confusing. Thus:
+
+ if expr1
+ + expr2
+ endif
+
+ if expr1
+ || expr2
+ endif
+
+ if expr3
+ vtg expr4
+ endif
+
+ We do '#' / EOL handling for the final line the same way as above.
+
+ Later we should add the ability to rework the expression properly,
+ making sure new lines starts with operators and such. */
+ /** @todo Implement simples expression parser and indenter, possibly also
+ * removing unnecessary parentheses. Can be shared with C/C++. */
+ if (cchMaxLeadWord > 3)
+ return scmKmkGiveUp(pParser,
+ "Bogus multi-line 'if' expression! Extra lines must start with operator (cchMaxLeadWord=%u).",
+ cchMaxLeadWord);
+ memset(pszDst, ' ', cchMaxLeadWord);
+ pszDst += cchMaxLeadWord;
+
+ size_t cchSrcContIndent = offToken + 2;
+ for (uint32_t iSubLine = 0; iSubLine < cLines - 1; iSubLine++)
+ {
+ /* Trim the line. */
+ size_t offSrcEnd = cchLine;
+ Assert(pchLine[offSrcEnd - 1] == '\\');
+ offSrcEnd--;
+
+ if (pchLine[offSrcEnd - 1] == '\\')
+ return scmKmkGiveUp(pParser, "Escaped '\\' before line continuation in 'if' expression is not allowed!");
+
+ while (offSrcEnd > offSrc && RT_C_IS_BLANK(pchLine[offSrcEnd - 1]))
+ offSrcEnd--;
+
+ /* Comments with line continuation is not allowed in commited makefiles. */
+ if (offSrc < offSrcEnd && memchr(&pchLine[offSrc], '#', cchLine - offSrc) != NULL)
+ return scmKmkGiveUp(pParser, "Comment in multi-line 'if' expression is not allowed to start before the final line!");
+
+ /* Output it. */
+ if (offSrc < offSrcEnd)
+ {
+ if (iSubLine > 0 && offSrc > cchSrcContIndent)
+ {
+ memset(pszDst, ' ', offSrc - cchSrcContIndent);
+ pszDst += offSrc - cchSrcContIndent;
+ }
+ pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], offSrcEnd - offSrc);
+ *pszDst++ = ' ';
+ }
+ else if (iSubLine == 0)
+ return scmKmkGiveUp(pParser, "Expected expression after 'if', not line continuation!");
+ *pszDst++ = '\\';
+ *pszDst = '\0';
+ size_t cchDst = (size_t)(pszDst - pParser->szBuf);
+ ScmStreamPutLine(pParser->pOut, pParser->szBuf, cchDst, pParser->enmEol);
+
+ /*
+ * Fetch the next line and start processing it.
+ */
+ pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
+ if (!pchLine)
+ {
+ ScmError(pParser->pState, VERR_INTERNAL_ERROR_3, "ScmStreamGetLine unexpectedly returned NULL!");
+ return false;
+ }
+ cchLine = pParser->cchLine;
+
+ /* Skip leading whitespace and adjust the source continuation indent: */
+ offSrc = 0;
+ while (offSrc < cchLine && RT_C_IS_SPACE(pchLine[offSrc]))
+ offSrc++;
+ /** @todo tabs */
+
+ if (iSubLine == 0)
+ cchSrcContIndent = offSrc;
+
+ /* Initial indent: */
+ pszDst = pParser->szBuf;
+ memset(pszDst, ' ', cchIndent + 2);
+ pszDst += cchIndent + 2;
+ }
+
+ /* Output the expression on the final line. */
+ const char *pchSrcHash = (const char *)memchr(&pchLine[offSrc], '#', cchLine - offSrc);
+ size_t cchExpr = pchSrcHash ? pchSrcHash - &pchLine[offSrc] : cchLine - offSrc;
+ while (cchExpr > 0 && RT_C_IS_BLANK(pchLine[offSrc + cchExpr - 1]))
+ cchExpr--;
+
+ pszDst = (char *)mempcpy(pszDst, &pchLine[offSrc], cchExpr);
+ offSrc += cchExpr;
+ }
+
+
+ /*
+ * Handle comment.
+ *
+ * Here we check for the "scm:ignore-nesting" directive that makes us not
+ * add indentation for this directive. We do this on the destination buffer
+ * as that can be zero terminated and is therefore usable with strstr.
+ */
+ if (offSrc >= cchLine)
+ *pszDst = '\0';
+ else
+ {
+ char * const pszDstSrc = pszDst;
+ scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
+ *pszDst = '\0';
+
+ /* Check for special comment making us ignore the nesting. We do this
+ on the destination buffer since it's zero terminated allowing normal
+ strstr use. */
+ if (!fIgnoredNesting && strstr(pszDstSrc, "scm:ignore-nesting") != NULL)
+ {
+ pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting = true;
+ pParser->iActualDepth--;
+ ScmVerbose(pParser->pState, 5, "%u: debug: ignoring nesting - actual depth: %u\n",
+ pParser->aDepth[pParser->iDepth - 1].iLine, pParser->iActualDepth);
+ }
+ }
+
+ /*
+ * Done.
+ */
+ ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
+ return false; /* dummy */
+}
+
+
+/**
+ * Deals with: else
+ *
+ * @returns dummy (false) to facility return + call.
+ */
+static bool scmKmkHandleElse(KMKPARSER *pParser, size_t offToken)
+{
+ const char * const pchLine = pParser->pchLine;
+ size_t const cchLine = pParser->cchLine;
+
+ if (pParser->iDepth < 1)
+ return scmKmkGiveUp(pParser, "Lone 'else'");
+ uint32_t const cchIndent = pParser->iActualDepth
+ - (pParser->iActualDepth > 0 && !pParser->aDepth[pParser->iDepth - 1].fIgnoreNesting);
+
+ /*
+ * Look past the else and check if there any ifxxx token following it.
+ */
+ size_t offSrc = offToken + 4;
+ while (offSrc < cchLine && RT_C_IS_BLANK(pchLine[offSrc]))
+ offSrc++;
+ if (offSrc < cchLine)
+ {
+ size_t cchWord = 0;
+ while (offSrc + cchWord < cchLine && RT_C_IS_ALNUM(pchLine[offSrc + cchWord]))
+ cchWord++;
+ if (cchWord)
+ {
+ KMKTOKEN enmToken = scmKmkIdentifyToken(&pchLine[offSrc], cchWord);
+ switch (enmToken)
+ {
+ case kKmkToken_ifeq:
+ case kKmkToken_ifneq:
+ case kKmkToken_if1of:
+ case kKmkToken_ifn1of:
+ return scmKmkHandleIfParentheses(pParser, offSrc, enmToken, cchWord, true /*fElse*/);
+
+ case kKmkToken_ifdef:
+ case kKmkToken_ifndef:
+ case kKmkToken_if:
+ return scmKmkHandleIfSpace(pParser, offSrc, enmToken, cchWord, true /*fElse*/);
+
+ default:
+ break;
+ }
+ }
+ }
+
+ /*
+ * We do not allow line continuation for these.
+ */
+ if (scmKmkIsLineWithContinuation(pchLine, cchLine))
+ return scmKmkGiveUp(pParser, "Line continuation not allowed with 'else' directive.");
+
+ /*
+ * We stage the modified line in the buffer, so check that the line isn't
+ * too long (it seriously should be).
+ */
+ if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf))
+ return scmKmkGiveUp(pParser, "Line too long for a 'else' directive: %u chars", cchLine);
+ char *pszDst = pParser->szBuf;
+
+ /*
+ * Emit indent and initial token.
+ */
+ memset(pszDst, ' ', cchIndent);
+ pszDst = (char *)mempcpy(&pszDst[cchIndent], RT_STR_TUPLE("else"));
+
+ offSrc = offToken + 4;
+
+ /*
+ * Handle comment.
+ */
+ if (offSrc < cchLine)
+ scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
+
+ /*
+ * Done.
+ */
+ *pszDst = '\0';
+ ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
+ return false; /* dummy */
+}
+
+
+/**
+ * Deals with: endif
+ *
+ * @returns dummy (false) to facility return + call.
+ */
+static bool scmKmkHandleEndif(KMKPARSER *pParser, size_t offToken)
+{
+ const char * const pchLine = pParser->pchLine;
+ size_t const cchLine = pParser->cchLine;
+
+ /*
+ * Pop a nesting.
+ */
+ if (pParser->iDepth < 1)
+ return scmKmkGiveUp(pParser, "Lone 'endif'");
+ uint32_t iDepth = pParser->iDepth - 1;
+ pParser->iDepth = iDepth;
+ if (!pParser->aDepth[iDepth].fIgnoreNesting)
+ {
+ AssertStmt(pParser->iActualDepth > 0, pParser->iActualDepth++);
+ pParser->iActualDepth -= 1;
+ }
+ ScmVerbose(pParser->pState, 5, "%u: debug: unnesting %u/%u (endif)\n",
+ ScmStreamTellLine(pParser->pIn), iDepth, pParser->iActualDepth);
+ uint32_t const cchIndent = pParser->iActualDepth;
+
+ /*
+ * We do not allow line continuation for these.
+ */
+ if (scmKmkIsLineWithContinuation(pchLine, cchLine))
+ return scmKmkGiveUp(pParser, "Line continuation not allowed with 'endif' directive.");
+
+ /*
+ * We stage the modified line in the buffer, so check that the line isn't
+ * too long (it seriously should be).
+ */
+ if (cchLine + cchIndent + 32 > sizeof(pParser->szBuf))
+ return scmKmkGiveUp(pParser, "Line too long for a 'else' directive: %u chars", cchLine);
+ char *pszDst = pParser->szBuf;
+
+ /*
+ * Emit indent and initial token.
+ */
+ memset(pszDst, ' ', cchIndent);
+ pszDst = (char *)mempcpy(&pszDst[cchIndent], RT_STR_TUPLE("endif"));
+
+ size_t offSrc = offToken + 5;
+
+ /*
+ * Handle comment.
+ */
+ if (offSrc < cchLine)
+ scmKmkTailComment(pParser, pchLine, cchLine, offSrc, &pszDst);
+
+ /*
+ * Done.
+ */
+ *pszDst = '\0';
+ ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
+ return false; /* dummy */
+}
+
+
+/**
+ * Passing thru any line continuation lines following the current one.
+ */
+static bool scmKmkPassThruLineContinuationLines(KMKPARSER *pParser)
+{
+ while (scmKmkIsLineWithContinuation(pParser->pchLine, pParser->cchLine))
+ {
+ pParser->pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
+ if (!pParser->pchLine)
+ break;
+ ScmStreamPutLine(pParser->pOut, pParser->pchLine, pParser->cchLine, pParser->enmEol);
+ }
+ return false; /* dummy */
+}
+
+
+/**
+ * For dealing with a directive w/o special formatting rules (yet).
+ *
+ * @returns dummy (false) to facility return + call.
+ */
+static bool scmKmkHandleSimple(KMKPARSER *pParser, size_t offToken, bool fIndentIt = true)
+{
+ const char *pchLine = pParser->pchLine;
+ size_t cchLine = pParser->cchLine;
+ uint32_t const cchIndent = fIndentIt ? pParser->iActualDepth : 0;
+
+ /*
+ * Just reindent the statement.
+ */
+ ScmStreamWrite(pParser->pOut, g_szSpaces, cchIndent);
+ ScmStreamWrite(pParser->pOut, &pchLine[offToken], cchLine - offToken);
+ ScmStreamPutEol(pParser->pOut, pParser->enmEol);
+
+ /*
+ * Check for line continuation and output concatenated lines.
+ */
+ scmKmkPassThruLineContinuationLines(pParser);
+ return false; /* dummy */
+}
+
+
+static bool scmKmkHandleDefine(KMKPARSER *pParser, size_t offToken)
+{
+ scmKmkHandleSimple(pParser, offToken);
+
+ /* Hack Alert! Start out parsing the define in recipe mode.
+
+ Technically, we shouldn't evaluate the content of a define till it's
+ used. However, we ASSUME they are either makefile code snippets or
+ recipe templates. */
+ scmKmkPushNesting(pParser, kKmkToken_define);
+ scmKmkSetInRecipe(pParser, true);
+ return false;
+}
+
+
+static bool scmKmkHandleEndef(KMKPARSER *pParser, size_t offToken)
+{
+ /* Leaving a define resets the recipt mode. */
+ scmKmkSetInRecipe(pParser, false);
+
+ /*
+ * Pop a nesting.
+ */
+ if (pParser->iDepth < 1)
+ return scmKmkGiveUp(pParser, "Lone 'endef'");
+ uint32_t iDepth = pParser->iDepth - 1;
+ if (pParser->aDepth[iDepth].enmToken != kKmkToken_define)
+ return scmKmkGiveUp(pParser, "Unpexected 'endef', expected 'endif' for line %u", pParser->aDepth[iDepth].iLine);
+ pParser->iDepth = iDepth;
+ if (!pParser->aDepth[iDepth].fIgnoreNesting)
+ {
+ AssertStmt(pParser->iActualDepth > 0, pParser->iActualDepth++);
+ pParser->iActualDepth -= 1;
+ }
+ ScmVerbose(pParser->pState, 5, "%u: debug: unnesting %u/%u (endef)\n",
+ ScmStreamTellLine(pParser->pIn), iDepth, pParser->iActualDepth);
+
+ return scmKmkHandleSimple(pParser, offToken);
+}
+
+
+/**
+ * Checks for escaped trailing slashes on a line, giving up and asking the
+ * developer to fix those manually.
+ *
+ * @returns true if we gave up. false if no escaped slashed and we didn't.
+ */
+static bool scmKmkGiveUpIfTrailingEscapedSlashed(KMKPARSER *pParser, const char *pchLine, size_t cchLine)
+{
+ if (cchLine > 2 && pchLine[cchLine - 2] == '\\' && pchLine[cchLine - 1] == '\\')
+ {
+ scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
+ return true;
+ }
+ return false;
+}
+
+/**
+ * @returns dummy (false) to facility return + call.
+ */
+static bool scmKmkHandleAssignment2(KMKPARSER *pParser, size_t offVarStart, size_t offVarEnd, KMKASSIGNTYPE enmType,
+ size_t offAssignOp, unsigned fFlags)
+{
+ unsigned const cchIndent = pParser->iActualDepth;
+ const char *pchLine = pParser->pchLine;
+ size_t cchLine = pParser->cchLine;
+ uint32_t const cLines = pParser->cLines;
+ uint32_t iSubLine = 0;
+
+ RT_NOREF(fFlags);
+ Assert(offVarStart < cchLine);
+ Assert(offVarEnd <= cchLine);
+ Assert(offVarStart < offVarEnd);
+ Assert(!RT_C_IS_SPACE(pchLine[offVarStart]));
+ Assert(!RT_C_IS_SPACE(pchLine[offVarEnd - 1]));
+
+ /* Assignments takes us out of recipe mode. */
+ ScmVerbose(pParser->pState, 6, "%u: debug: assignment\n", ScmStreamTellLine(pParser->pIn));
+ scmKmkSetInRecipe(pParser, false);
+
+ /* This is too much hazzle to deal with. */
+ if (cLines > 1 && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
+ return false;
+ if (cchLine + 64 > sizeof(pParser->szBuf))
+ return scmKmkGiveUp(pParser, "Line too long!");
+
+ /*
+ * Indent and output the variable name.
+ */
+ char *pszDst = pParser->szBuf;
+ memset(pszDst, ' ', cchIndent);
+ pszDst += cchIndent;
+ pszDst = (char *)mempcpy(pszDst, &pchLine[offVarStart], offVarEnd - offVarStart);
+
+ /*
+ * Try preserve the assignment operator position, but make sure we've got a
+ * space in front of it.
+ */
+ if (offAssignOp < cchLine)
+ {
+ size_t offDst = (size_t)(pszDst - pParser->szBuf);
+ size_t offEffAssignOp = ScmCalcSpacesForSrcSpan(pchLine, 0, offAssignOp, pParser->pSettings);
+ if (offDst < offEffAssignOp)
+ {
+ size_t cchSpacesToWrite = offEffAssignOp - offDst;
+ memset(pszDst, ' ', cchSpacesToWrite);
+ pszDst += cchSpacesToWrite;
+ }
+ else
+ *pszDst++ = ' ';
+ }
+ else
+ {
+ /* Pull up the assignment operator to the variable line. */
+ *pszDst++ = ' ';
+
+ /* Eat up lines till we hit the operator. */
+ while (offAssignOp < cchLine)
+ {
+ const char * const pchPrevLine = pchLine;
+ Assert(iSubLine + 1 < cLines);
+ pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
+ AssertReturn(pchLine, false /*dummy*/);
+ cchLine = pParser->cchLine;
+ iSubLine++;
+ if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
+ return false;
+
+ /* Adjust offAssignOp: */
+ offAssignOp -= (uintptr_t)pchLine - (uintptr_t)pchPrevLine;
+ Assert(offAssignOp < ~(size_t)0 / 2);
+ }
+
+ if ((size_t)(pszDst - pParser->szBuf) > sizeof(pParser->szBuf))
+ return scmKmkGiveUp(pParser, "Line too long!");
+ }
+
+ /*
+ * Emit the operator.
+ */
+ size_t offLine = offAssignOp;
+ switch (enmType)
+ {
+ default:
+ AssertReleaseFailed();
+ RT_FALL_THRU();
+ case kKmkAssignType_Recursive:
+ *pszDst++ = '=';
+ Assert(pchLine[offLine] == '=');
+ offLine++;
+ break;
+ case kKmkAssignType_Conditional:
+ *pszDst++ = '?';
+ *pszDst++ = '=';
+ Assert(pchLine[offLine] == '?'); Assert(pchLine[offLine + 1] == '=');
+ offLine += 2;
+ break;
+ case kKmkAssignType_Appending:
+ *pszDst++ = '+';
+ *pszDst++ = '=';
+ Assert(pchLine[offLine] == '+'); Assert(pchLine[offLine + 1] == '=');
+ offLine += 2;
+ break;
+ case kKmkAssignType_Prepending:
+ *pszDst++ = '<';
+ *pszDst++ = '=';
+ Assert(pchLine[offLine] == '<'); Assert(pchLine[offLine + 1] == '=');
+ offLine += 2;
+ break;
+ case kKmkAssignType_Immediate:
+ *pszDst++ = ':';
+ Assert(pchLine[offLine] == ':');
+ offLine++;
+ RT_FALL_THRU();
+ case kKmkAssignType_Simple:
+ *pszDst++ = ':';
+ *pszDst++ = '=';
+ Assert(pchLine[offLine] == ':'); Assert(pchLine[offLine + 1] == '=');
+ offLine += 2;
+ break;
+ }
+
+ /*
+ * Skip space till we hit the value or comment.
+ */
+ while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
+ offLine++;
+
+/** @todo this block can probably be merged into the final loop below. */
+ unsigned cPendingEols = 0;
+ while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
+ {
+ pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
+ AssertReturn(pchLine, false /*dummy*/);
+ cchLine = pParser->cchLine;
+ iSubLine++;
+ if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
+ {
+ *pszDst++ = ' ';
+ *pszDst++ = '\\';
+ *pszDst = '\0';
+ ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
+ return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
+ }
+ cPendingEols = 1;
+
+ /* Skip indent/whitespace. */
+ offLine = 0;
+ while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
+ offLine++;
+ }
+
+ /*
+ * Okay, we've gotten to the value / comment part.
+ */
+ for (;;)
+ {
+ /*
+ * The end? Flush what we've got.
+ */
+ if (offLine == cchLine)
+ {
+ Assert(iSubLine + 1 == cLines);
+ *pszDst = '\0';
+ ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
+ if (cPendingEols > 0)
+ ScmStreamPutEol(pParser->pOut, pParser->enmEol);
+ return false; /* dummy */
+ }
+
+ /*
+ * Output any non-comment stuff, stripping off newlines.
+ */
+ const char *pchHash = (const char *)memchr(&pchLine[offLine], '#', cchLine - offLine);
+ if (pchHash != &pchLine[offLine])
+ {
+ /* Add space or flush pending EOLs. */
+ if (!cPendingEols)
+ *pszDst++ = ' ';
+ else
+ {
+ unsigned iEol = 0;
+ cPendingEols = RT_MIN(2, cPendingEols); /* reduce to two, i.e. only one empty separator line */
+ do
+ {
+ if (iEol++ == 0) /* skip this for the 2nd empty line. */
+ *pszDst++ = ' ';
+ *pszDst++ = '\\';
+ *pszDst = '\0';
+ ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
+
+ pszDst = pParser->szBuf;
+ memset(pszDst, ' ', cchIndent);
+ pszDst += cchIndent;
+ *pszDst++ = '\t';
+ cPendingEols--;
+ } while (cPendingEols > 0);
+ }
+
+ /* Strip backwards. */
+ size_t const offValueEnd2 = pchHash ? (size_t)(pchHash - pchLine) : cchLine - (iSubLine + 1 < cLines);
+ size_t offValueEnd = offValueEnd2;
+ while (offValueEnd > offLine && RT_C_IS_BLANK(pchLine[offValueEnd - 1]))
+ offValueEnd--;
+ Assert(offValueEnd > offLine);
+
+ /* Append the value part we found. */
+ pszDst = (char *)mempcpy(pszDst, &pchLine[offLine], offValueEnd - offLine);
+ offLine = offValueEnd2;
+ }
+
+ /*
+ * If we found a comment hash, emit it and whatever follows just as-is w/o
+ * any particular reformatting. Comments within a variable definition are
+ * usually to disable portitions of a property like _DEFS or _SOURCES.
+ */
+ if (pchHash != NULL)
+ {
+ if (cPendingEols == 0)
+ scmKmkTailComment(pParser, pchLine, cchLine, offLine, &pszDst);
+ size_t const cchDst = (size_t)(pszDst - pParser->szBuf);
+ *pszDst = '\0';
+ ScmStreamPutLine(pParser->pOut, pParser->szBuf, cchDst, pParser->enmEol);
+
+ if (cPendingEols > 1)
+ ScmStreamPutEol(pParser->pOut, pParser->enmEol);
+
+ if (cPendingEols > 0)
+ ScmStreamPutLine(pParser->pOut, pchLine, cchLine, pParser->enmEol);
+ scmKmkPassThruLineContinuationLines(pParser);
+ return false; /* dummy */
+ }
+
+ /*
+ * Fetch another line, if we've got one.
+ */
+ if (iSubLine + 1 >= cLines)
+ Assert(offLine == cchLine);
+ else
+ {
+ Assert(offLine + 1 == cchLine);
+ while (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
+ {
+ pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
+ AssertReturn(pchLine, false /*dummy*/);
+ cchLine = pParser->cchLine;
+ iSubLine++;
+ if (iSubLine + 1 < cLines && pchLine[cchLine - 2] == '\\')
+ {
+ *pszDst++ = ' ';
+ *pszDst++ = '\\';
+ *pszDst = '\0';
+ ScmStreamPutLine(pParser->pOut, pParser->szBuf, pszDst - pParser->szBuf, pParser->enmEol);
+ if (cPendingEols > 1)
+ ScmError(pParser->pState, VERR_NOT_SUPPORTED, "oops #1: Manually fix the next issue after reverting edits!");
+ return scmKmkGiveUp(pParser, "Escaped slashes at end of line not allowed. Insert space before line continuation slash!");
+ }
+ cPendingEols++;
+
+ /* Deal with indent/whitespace. */
+ offLine = 0;
+ while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
+ offLine++;
+ }
+ }
+ }
+}
+
+
+/**
+ * A rule.
+ *
+ * This is a bit involved. Sigh.
+ *
+ * @returns dummy (false) to facility return + call.
+ */
+static bool scmKmkHandleRule(KMKPARSER *pParser, size_t offFirstWord, bool fDoubleColon, size_t offColon)
+{
+ SCMSTREAM *pOut = pParser->pOut;
+ unsigned const cchIndent = pParser->iActualDepth;
+ const char *pchLine = pParser->pchLine;
+ size_t cchLine = pParser->cchLine;
+ Assert(offFirstWord < cchLine);
+ uint32_t const cLines = pParser->cLines;
+ uint32_t iSubLine = 0;
+
+ /* Following this, we'll be in recipe-mode. */
+ ScmVerbose(pParser->pState, 4, "%u: debug: start rule\n", ScmStreamTellLine(pParser->pIn));
+ scmKmkSetInRecipe(pParser, true);
+
+ /* This is too much hazzle to deal with. */
+ if (cLines > 0 && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
+ return false;
+
+ /* Too special case. */
+ if (offColon <= offFirstWord)
+ return scmKmkGiveUp(pParser, "Missing target file before colon!");
+
+ /*
+ * Indent it.
+ */
+ ScmStreamWrite(pOut, g_szSpaces, cchIndent);
+ size_t offLine = offFirstWord;
+
+ /*
+ * Process word by word past the colon, taking new lines into account.
+ */
+ KMKWORDSTATE WordState = { 0, 0 };
+ KMKWORDCTX enmCtx = kKmkWordCtx_TargetFileOrAssignment;
+ unsigned cPendingEols = 0;
+ for (;;)
+ {
+ /*
+ * Output the next word.
+ */
+ size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx, &WordState);
+ Assert(offLine + cchWord <= offColon);
+ ScmStreamWrite(pOut, &pchLine[offLine], cchWord);
+ offLine += cchWord;
+
+ /* Skip whitespace (if any). */
+ while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
+ offLine++;
+
+ /* Have we reached the colon already? */
+ if (offLine >= offColon)
+ {
+ Assert(pchLine[offLine] == ':');
+ Assert(!fDoubleColon || pchLine[offLine + 1] == ':');
+ offLine += fDoubleColon ? 2 : 1;
+
+ ScmStreamPutCh(pOut, ':');
+ if (fDoubleColon)
+ ScmStreamPutCh(pOut, ':');
+ break;
+ }
+
+ /* Deal with new line and emit indentation. */
+ if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
+ {
+ /* Get the next input line. */
+ for (;;)
+ {
+ const char * const pchPrevLine = pchLine;
+ Assert(iSubLine + 1 < cLines);
+ pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
+ AssertReturn(pchLine, false /*dummy*/);
+ cchLine = pParser->cchLine;
+ iSubLine++;
+ if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
+ return false;
+
+ /* Adjust offColon: */
+ offColon -= (uintptr_t)pchLine - (uintptr_t)pchPrevLine;
+ Assert(offColon < ~(size_t)0 / 2);
+
+ /* Skip leading spaces. */
+ offLine = 0;
+ while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
+ offLine++;
+
+ /* Just drop empty lines. */
+ if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
+ continue;
+
+ /* Complete the current line and emit indent, unless we reached the colon: */
+ if (offLine >= offColon)
+ {
+ Assert(pchLine[offLine] == ':');
+ Assert(!fDoubleColon || pchLine[offLine + 1] == ':');
+ offLine += fDoubleColon ? 2 : 1;
+
+ ScmStreamPutCh(pOut, ':');
+ if (fDoubleColon)
+ ScmStreamPutCh(pOut, ':');
+
+ cPendingEols = 1;
+ }
+ else
+ {
+ ScmStreamWrite(pOut, RT_STR_TUPLE(" \\"));
+ ScmStreamPutEol(pOut, pParser->enmEol);
+ ScmStreamWrite(pOut, g_szSpaces, cchIndent);
+ if (WordState.uDepth > 0)
+ ScmStreamWrite(pOut, g_szTabs, RT_MIN(WordState.uDepth, sizeof(g_szTabs) - 1));
+ }
+ break;
+ }
+ if (offLine >= offColon)
+ break;
+ }
+ else
+ ScmStreamPutCh(pOut, ' ');
+ enmCtx = kKmkWordCtx_TargetFile;
+ }
+
+ /*
+ * We're immediately past the colon now, so eat whitespace and newlines and
+ * whatever till we get to a solid word or the end of the line.
+ */
+ /* Skip spaces - there should be exactly one. */
+ while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
+ offLine++;
+
+ /* Deal with new lines: */
+ while (offLine + 1 == cchLine && pchLine[offLine] == '\\')
+ {
+ cPendingEols = 1;
+
+ Assert(iSubLine + 1 < cLines);
+ pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
+ AssertReturn(pchLine, false /*dummy*/);
+ cchLine = pParser->cchLine;
+ iSubLine++;
+ if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
+ return false;
+
+ /* Skip leading spaces. */
+ offLine = 0;
+ while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
+ offLine++;
+
+ /* Just drop empty lines. */
+ if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
+ continue;
+ }
+
+ /*
+ * Special case: No dependencies.
+ */
+ if (offLine == cchLine && iSubLine + 1 >= cLines)
+ {
+ ScmStreamPutEol(pOut, pParser->enmEol);
+ return false /*dummy*/;
+ }
+
+ /*
+ * Work the dependencies word for word. Indent in spaces + two tabs.
+ * (Pattern rules will also end up here, but we'll just ignore that for now.)
+ */
+ enmCtx = kKmkWordCtx_DepFileOrAssignment;
+ for (;;)
+ {
+ /* Indent the next word. */
+ if (cPendingEols == 0)
+ ScmStreamPutCh(pOut, ' ');
+ else
+ {
+ ScmStreamWrite(pOut, RT_STR_TUPLE(" \\"));
+ ScmStreamPutEol(pOut, pParser->enmEol);
+ ScmStreamWrite(pOut, g_szSpaces, cchIndent);
+ ScmStreamWrite(pOut, RT_STR_TUPLE("\t\t"));
+ if (cPendingEols > 1)
+ {
+ ScmStreamWrite(pOut, RT_STR_TUPLE("\\"));
+ ScmStreamPutEol(pOut, pParser->enmEol);
+ ScmStreamWrite(pOut, g_szSpaces, cchIndent);
+ ScmStreamWrite(pOut, RT_STR_TUPLE("\t\t"));
+ }
+ cPendingEols = 0;
+ }
+ if (WordState.uDepth > 0)
+ ScmStreamWrite(pOut, g_szTabs, RT_MIN(WordState.uDepth, sizeof(g_szTabs) - 1));
+
+ /* Get the next word and output it. */
+ size_t cchWord = scmKmkWordLength(pchLine, cchLine, offLine, enmCtx, &WordState);
+ Assert(offLine + cchWord <= cchLine);
+
+ ScmStreamWrite(pOut, &pchLine[offLine], cchWord);
+ offLine += cchWord;
+
+ /* Skip whitespace (if any). */
+ while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
+ offLine++;
+
+ /* Deal with new line and emit indentation. */
+ if (iSubLine + 1 < cLines && offLine + 1 == cchLine && pchLine[offLine] == '\\')
+ {
+ /* Get the next input line. */
+ for (;;)
+ {
+ Assert(iSubLine + 1 < cLines);
+ pParser->pchLine = pchLine = ScmStreamGetLine(pParser->pIn, &pParser->cchLine, &pParser->enmEol);
+ AssertReturn(pchLine, false /*dummy*/);
+ cchLine = pParser->cchLine;
+ iSubLine++;
+ if (iSubLine + 1 < cLines && scmKmkGiveUpIfTrailingEscapedSlashed(pParser, pchLine, cchLine))
+ return false;
+
+ /* Skip leading spaces. */
+ offLine = 0;
+ while (offLine < cchLine && RT_C_IS_SPACE(pchLine[offLine]))
+ offLine++;
+
+ /* Just drop empty lines, we'll re-add one of them afterward if we find more dependencies. */
+ cPendingEols++;
+ if (offLine + 1 == cchLine && pchLine[offLine] == '\\')
+ continue;
+ break;
+ }
+ }
+
+ if (offLine >= cchLine)
+ {
+ /* End of input. */
+/** @todo deal with comments */
+ Assert(iSubLine + 1 == cLines);
+ ScmStreamPutEol(pOut, pParser->enmEol);
+ return false; /* dummmy */
+ }
+ enmCtx = kKmkWordCtx_DepFile;
+ }
+}
+
+
+/**
+ * Checks if the (extended) line is a variable assignment.
+ *
+ * We scan past line continuation stuff here as the assignment operator could be
+ * on the next line, even if that's very unlikely it is recommened by the coding
+ * guide lines if the line needs to be split. Fortunately, though, the caller
+ * already removes empty empty leading lines, so we only have to consider the
+ * line continuation issue if no '=' was found on the first line.
+ *
+ * @returns Modified or not.
+ * @param pParser The parser.
+ * @param cLines Number of lines to consider.
+ * @param cchTotalLine Total length of all the lines to consider.
+ * @param offWord Where the first word of the line starts.
+ * @param pfIsAssignment Where to return whether this is an assignment or
+ * not.
+ */
+static bool scmKmkHandleAssignmentOrRule(KMKPARSER *pParser, size_t offWord)
+{
+ const char *pchLine = pParser->pchLine;
+ size_t const cchTotalLine = pParser->cchTotalLine;
+
+ /*
+ * Scan words till we find ':' or '='.
+ */
+ uint32_t iWord = 0;
+ size_t offCurWord = offWord;
+ size_t offEndPrev = 0;
+ size_t offLine = offWord;
+ while (offLine < cchTotalLine)
+ {
+ char ch = pchLine[offLine++];
+ if (ch == '$')
+ {
+ /*
+ * Skip variable expansion.
+ */
+ char const chOpen = pchLine[offLine++];
+ if (chOpen == '(' || chOpen == '{')
+ {
+ char const chClose = chOpen == '(' ? ')' : '}';
+ unsigned cDepth = 1;
+ while (offLine < cchTotalLine)
+ {
+ ch = pchLine[offLine++];
+ if (ch == chOpen)
+ cDepth++;
+ else if (ch == chClose)
+ if (!--cDepth)
+ break;
+ }
+ }
+ /* else: $x or $$, so just skip the next character. */
+ }
+ else if (RT_C_IS_SPACE(ch))
+ {
+ /*
+ * End of word. Skip whitespace till the next word starts.
+ */
+ offEndPrev = offLine - 1;
+ Assert(offLine != offWord);
+ while (offLine < cchTotalLine)
+ {
+ ch = pchLine[offLine];
+ if (RT_C_IS_SPACE(ch))
+ offLine++;
+ else if (ch == '\\' && (pchLine[offLine] == '\r' || pchLine[offLine] == '\n'))
+ offLine += 2;
+ else
+ break;
+ }
+ offCurWord = offLine;
+ iWord++;
+
+ /*
+ * To simplify the assignment operator checks, we just check the
+ * start of the 2nd word when we're here.
+ */
+ if (iWord == 1 && offLine < cchTotalLine)
+ {
+ ch = pchLine[offLine];
+ if (ch == '=')
+ return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Recursive, offLine, 0);
+ if (offLine + 1 < cchTotalLine && pchLine[offLine + 1] == '=')
+ {
+ if (ch == ':')
+ return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Simple, offLine, 0);
+ if (ch == '+')
+ return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Appending, offLine, 0);
+ if (ch == '<')
+ return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Prepending, offLine, 0);
+ if (ch == '?')
+ return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Conditional, offLine, 0);
+ }
+ else if ( ch == ':'
+ && pchLine[offLine + 1] == ':'
+ && pchLine[offLine + 2] == '=')
+ return scmKmkHandleAssignment2(pParser, offWord, offEndPrev, kKmkAssignType_Immediate, offLine, 0);
+
+ /* Check for rule while we're here. */
+ if (ch == ':')
+ return scmKmkHandleRule(pParser, offWord, pchLine[offLine + 1] == ':', offLine);
+ }
+ }
+ /*
+ * If '=' is found in the first word it's an assignment.
+ */
+ else if (ch == '=')
+ {
+ if (iWord == 0)
+ {
+ KMKASSIGNTYPE enmType = kKmkAssignType_Recursive;
+ ch = pchLine[offLine - 2];
+ if (ch == '+')
+ enmType = kKmkAssignType_Appending;
+ else if (ch == '?')
+ enmType = kKmkAssignType_Conditional;
+ else if (ch == '<')
+ enmType = kKmkAssignType_Prepending;
+ else
+ {
+ Assert(ch != ':');
+ return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, enmType, offLine - 1, 0);
+ }
+ return scmKmkHandleAssignment2(pParser, offWord, offLine - 2, enmType, offLine - 2, 0);
+ }
+ }
+ /*
+ * When ':' is found it can mean a drive letter, a rule or in the
+ * first word a simple or immediate assignment.
+ */
+ else if (ch == ':')
+ {
+ /* Check for drive letters (we ignore the archive form): */
+ if (offLine - offWord == 2 && RT_C_IS_ALPHA(pchLine[offLine - 2]))
+ { /* ignore */ }
+ else
+ {
+ /* Simple or immediate assignment? */
+ ch = pchLine[offLine];
+ if (iWord == 0)
+ {
+ if (ch == '=')
+ return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Simple, offLine - 1, 0);
+ if (ch == ':' && pchLine[offLine + 1] == '=')
+ return scmKmkHandleAssignment2(pParser, offWord, offLine - 1, kKmkAssignType_Immediate, offLine - 1, 0);
+ }
+
+ /* Okay, it's a rule then. */
+ return scmKmkHandleRule(pParser, offWord, ch == ':', offLine - 1);
+ }
+ }
+ }
+
+ /*
+ * Check if this is a $(error ) or similar function call line.
+ *
+ * If we're inside a 'define' we treat $$ as $ as it's probably a case of
+ * double expansion (e.g. def_vmm_lib_dtrace_preprocess in VMM/Makefile.kmk).
+ */
+ if (pchLine[offWord] == '$')
+ {
+ size_t const cDollars = pchLine[offWord + 1] != '$' || !scmKmkIsInsideDefine(pParser) ? 1 : 2;
+ if ( pchLine[offWord + cDollars] == '('
+ || pchLine[offWord + cDollars] == '{')
+ {
+ size_t const cchLine = pParser->cchLine;
+ size_t offEnd = offWord + cDollars + 1;
+ char ch = '\0';
+ while (offEnd < cchLine && (RT_C_IS_LOWER(ch = pchLine[offEnd]) || RT_C_IS_DIGIT(ch) || ch == '-'))
+ offEnd++;
+ if (offEnd >= cchLine || RT_C_IS_SPACE(ch) || (offEnd == cchLine - 1 && ch == '\\'))
+ {
+ static const RTSTRTUPLE s_aAllowedFunctions[] =
+ {
+ { RT_STR_TUPLE("info") },
+ { RT_STR_TUPLE("error") },
+ { RT_STR_TUPLE("warning") },
+ { RT_STR_TUPLE("set-umask") },
+ { RT_STR_TUPLE("foreach") },
+ { RT_STR_TUPLE("call") },
+ { RT_STR_TUPLE("eval") },
+ { RT_STR_TUPLE("evalctx") },
+ { RT_STR_TUPLE("evalval") },
+ { RT_STR_TUPLE("evalvalctx") },
+ { RT_STR_TUPLE("evalcall") },
+ { RT_STR_TUPLE("evalcall2") },
+ { RT_STR_TUPLE("eval-opt-var") },
+ { RT_STR_TUPLE("kb-src-one") },
+ };
+ size_t cchFunc = offEnd - offWord - cDollars - 1;
+ for (size_t i = 0; i < RT_ELEMENTS(s_aAllowedFunctions); i++)
+ if ( cchFunc == s_aAllowedFunctions[i].cch
+ && memcmp(&pchLine[offWord + cDollars + 1], s_aAllowedFunctions[i].psz, cchFunc) == 0)
+ return scmKmkHandleSimple(pParser, offWord);
+ }
+ }
+ }
+
+ /*
+ * If we didn't find anything, output it as-as.
+ * We use scmKmkHandleSimple in a special way to do this.
+ */
+ if (!RTStrStartsWith(pchLine, "$(TOOL_")) /* ValKit/Config.kmk */
+ ScmVerbose(pParser->pState, 1, "%u: debug: Unable to make sense of this line!\n", ScmStreamTellLine(pParser->pIn));
+ return scmKmkHandleSimple(pParser, 0 /*offToken*/, false /*fIndentIt*/);
+}
+
+
+static bool scmKmkHandleAssignKeyword(KMKPARSER *pParser, size_t offToken, KMKTOKEN enmToken, size_t cchWord,
+ bool fMustBeAssignment)
+{
+ /* Assignments takes us out of recipe mode. */
+ scmKmkSetInRecipe(pParser, false);
+
+ RT_NOREF(pParser, offToken, enmToken, cchWord, fMustBeAssignment);
+ return scmKmkHandleSimple(pParser, offToken);
+}
+
+
+/**
+ * Rewrite a kBuild makefile.
+ *
+ * @returns kScmMaybeModified or kScmUnmodified.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ *
+ * @todo
+ *
+ * Ideas for Makefile.kmk and Config.kmk:
+ * - sort if1of/ifn1of sets.
+ * - line continuation slashes should only be preceded by one space.
+ */
+SCMREWRITERRES rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ if (!pSettings->fStandarizeKmk)
+ return kScmUnmodified;
+
+ /*
+ * Parser state.
+ */
+ KMKPARSER Parser;
+ Parser.iDepth = 0;
+ Parser.iActualDepth = 0;
+ Parser.fInRecipe = false;
+ Parser.pState = pState;
+ Parser.pIn = pIn;
+ Parser.pOut = pOut;
+ Parser.pSettings = pSettings;
+
+ /*
+ * Iterate the file.
+ */
+ const char *pchLine;
+ while ((Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol)) != NULL)
+ {
+ size_t cchLine = Parser.cchLine;
+
+ /*
+ * If we're in the command part of a recipe, anything starting with a
+ * tab is considered another command for the recipe.
+ */
+ if (Parser.fInRecipe && *pchLine == '\t')
+ {
+ /* Do we do anything here? */
+ }
+ else
+ {
+ /*
+ * Skip leading whitespace and check for directives (simplified).
+ *
+ * This is simplified in the sense that GNU make first checks for variable
+ * assignments, so that directive can be used as variable names. We don't
+ * want that, so we do the variable assignment check later.
+ */
+ size_t offLine = 0;
+ while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
+ offLine++;
+
+ /* Find end of word (if any) - only looking for keywords here: */
+ size_t cchWord = 0;
+ while ( offLine + cchWord < cchLine
+ && ( RT_C_IS_ALNUM(pchLine[offLine + cchWord])
+ || pchLine[offLine + cchWord] == '-'))
+ cchWord++;
+ if (cchWord > 0)
+ {
+ /* If the line is just a line continuation slash, simply remove it
+ (this also makes the parsing a lot easier). */
+ if (cchWord == 1 && offLine == cchLine - 1 && pchLine[cchLine] == '\\')
+ continue;
+
+ /* Unlike the GNU make parser, we won't recognize 'if' or any other
+ directives as variable names, so we can */
+ KMKTOKEN enmToken = scmKmkIdentifyToken(&pchLine[offLine], cchWord);
+ switch (enmToken)
+ {
+ case kKmkToken_ifeq:
+ case kKmkToken_ifneq:
+ case kKmkToken_if1of:
+ case kKmkToken_ifn1of:
+ scmKmkHandleIfParentheses(&Parser, offLine, enmToken, cchWord, false /*fElse*/);
+ continue;
+
+ case kKmkToken_ifdef:
+ case kKmkToken_ifndef:
+ case kKmkToken_if:
+ scmKmkHandleIfSpace(&Parser, offLine, enmToken, cchWord, false /*fElse*/);
+ continue;
+
+ case kKmkToken_else:
+ scmKmkHandleElse(&Parser, offLine);
+ continue;
+
+ case kKmkToken_endif:
+ scmKmkHandleEndif(&Parser, offLine);
+ continue;
+
+ /* Includes: */
+ case kKmkToken_include:
+ case kKmkToken_sinclude:
+ case kKmkToken_dash_include:
+ case kKmkToken_includedep:
+ case kKmkToken_includedep_queue:
+ case kKmkToken_includedep_flush:
+ scmKmkHandleSimple(&Parser, offLine);
+ continue;
+
+ /* Others: */
+ case kKmkToken_define:
+ scmKmkHandleDefine(&Parser, offLine);
+ continue;
+ case kKmkToken_endef:
+ scmKmkHandleEndef(&Parser, offLine);
+ continue;
+
+ case kKmkToken_override:
+ case kKmkToken_local:
+ scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, true /*fMustBeAssignment*/);
+ continue;
+
+ case kKmkToken_export:
+ scmKmkHandleAssignKeyword(&Parser, offLine, enmToken, cchWord, false /*fMustBeAssignment*/);
+ continue;
+
+ case kKmkToken_unexport:
+ case kKmkToken_undefine:
+ scmKmkHandleSimple(&Parser, offLine);
+ continue;
+
+ case kKmkToken_Comment:
+ AssertFailed(); /* not possible */
+ break;
+
+ /*
+ * Check if it's perhaps an variable assignment or start of a rule.
+ * We'll do this in a very simple fashion.
+ */
+ case kKmkToken_Word:
+ {
+ Parser.cLines = 1;
+ Parser.cchTotalLine = cchLine;
+ if (scmKmkIsLineWithContinuation(pchLine, cchLine))
+ Parser.cchTotalLine = scmKmkLineContinuationPeek(&Parser, &Parser.cLines, NULL);
+ scmKmkHandleAssignmentOrRule(&Parser, offLine);
+ continue;
+ }
+ }
+ }
+ /*
+ * Not keyword, check for assignment, rule or comment:
+ */
+ else if (offLine < cchLine)
+ {
+ if (pchLine[offLine] != '#')
+ {
+ Parser.cLines = 1;
+ Parser.cchTotalLine = cchLine;
+ if (scmKmkIsLineWithContinuation(pchLine, cchLine))
+ Parser.cchTotalLine = scmKmkLineContinuationPeek(&Parser, &Parser.cLines, NULL);
+ scmKmkHandleAssignmentOrRule(&Parser, offLine);
+ continue;
+ }
+
+ /*
+ * Indent comment lines, unless the comment is too far too the right.
+ */
+ size_t const offEffLine = ScmCalcSpacesForSrcSpan(pchLine, 0, offLine, pSettings);
+ if (offEffLine <= Parser.iActualDepth + 7)
+ {
+ ScmStreamWrite(pOut, g_szSpaces, Parser.iActualDepth);
+ ScmStreamWrite(pOut, &pchLine[offLine], cchLine - offLine);
+ ScmStreamPutEol(pOut, Parser.enmEol);
+
+ /* If line continuation is used, it's typically to disable
+ a property variable, so we just pass it thru as-is */
+ while (scmKmkIsLineWithContinuation(pchLine, cchLine))
+ {
+ Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol);
+ if (!pchLine)
+ break;
+ cchLine = Parser.cchLine;
+ ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
+ }
+ continue;
+ }
+ }
+ }
+
+ /*
+ * Pass it thru as-is with line continuation.
+ */
+ while (scmKmkIsLineWithContinuation(pchLine, cchLine))
+ {
+ ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
+ Parser.pchLine = pchLine = ScmStreamGetLine(pIn, &Parser.cchLine, &Parser.enmEol);
+ if (!pchLine)
+ break;
+ cchLine = Parser.cchLine;
+ }
+ if (pchLine)
+ ScmStreamPutLine(pOut, pchLine, cchLine, Parser.enmEol);
+ }
+
+ return kScmMaybeModified; /* Make the caller check */
+}
+
+
+/**
+ * Makefile.kup are empty files, enforce this.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+SCMREWRITERRES rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ RT_NOREF2(pOut, pSettings);
+
+ /* These files should be zero bytes. */
+ if (pIn->cb == 0)
+ return kScmUnmodified;
+ ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
+ return kScmModified;
+}
+
diff --git a/src/bldprogs/scmrw.cpp b/src/bldprogs/scmrw.cpp
new file mode 100644
index 00000000..526a38da
--- /dev/null
+++ b/src/bldprogs/scmrw.cpp
@@ -0,0 +1,3613 @@
+/* $Id: scmrw.cpp $ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+#include "scm.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** License types. */
+typedef enum SCMLICENSETYPE
+{
+ kScmLicenseType_Invalid = 0,
+ kScmLicenseType_OseGpl,
+ kScmLicenseType_OseDualGplCddl,
+ kScmLicenseType_OseCddl,
+ kScmLicenseType_VBoxLgpl,
+ kScmLicenseType_Mit,
+ kScmLicenseType_Confidential
+} SCMLICENSETYPE;
+
+/** A license. */
+typedef struct SCMLICENSETEXT
+{
+ /** The license type. */
+ SCMLICENSETYPE enmType;
+ /** The license option. */
+ SCMLICENSE enmOpt;
+ /** The license text. */
+ const char *psz;
+ /** The license text length. */
+ size_t cch;
+} SCMLICENSETEXT;
+/** Pointer to a license. */
+typedef SCMLICENSETEXT const *PCSCMLICENSETEXT;
+
+/**
+ * Copyright + license rewriter state.
+ */
+typedef struct SCMCOPYRIGHTINFO
+{
+ /** State. */
+ PSCMRWSTATE pState; /**< input */
+ /** The comment style (neede for C/C++). */
+ SCMCOMMENTSTYLE enmCommentStyle; /**< input */
+
+ /** Number of comments we've parsed. */
+ uint32_t cComments;
+
+ /** Copy of the contributed-by line if present. */
+ char *pszContributedBy;
+
+ /** @name Common info
+ * @{ */
+ uint32_t iLineComment;
+ uint32_t cLinesComment; /**< This excludes any external license lines. */
+ /** @} */
+
+ /** @name Copyright info
+ * @{ */
+ uint32_t iLineCopyright;
+ uint32_t uFirstYear;
+ uint32_t uLastYear;
+ bool fWellFormedCopyright;
+ bool fUpToDateCopyright;
+ /** @} */
+
+ /** @name License info
+ * @{ */
+ bool fOpenSource; /**< input */
+ PCSCMLICENSETEXT pExpectedLicense; /**< input */
+ PCSCMLICENSETEXT paLicenses; /**< input */
+ SCMLICENSE enmLicenceOpt; /**< input */
+ uint32_t iLineLicense;
+ uint32_t cLinesLicense;
+ PCSCMLICENSETEXT pCurrentLicense;
+ bool fIsCorrectLicense;
+ bool fWellFormedLicense;
+ bool fExternalLicense;
+ /** @} */
+
+ /** @name LGPL licence notice and disclaimer info
+ * @{ */
+ /** Wheter to check for LGPL license notices and disclaimers. */
+ bool fCheckforLgpl;
+ /** The approximate line we found the (first) LGPL licence notice on. */
+ uint32_t iLineLgplNotice;
+ /** The line number after the LGPL notice comment. */
+ uint32_t iLineAfterLgplComment;
+ /** The LGPL disclaimer line. */
+ uint32_t iLineLgplDisclaimer;
+ /** @} */
+
+} SCMCOPYRIGHTINFO;
+typedef SCMCOPYRIGHTINFO *PSCMCOPYRIGHTINFO;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** --license-ose-gpl */
+static const char g_szVBoxOseGpl[] =
+ "This file is part of VirtualBox base platform packages, as\n"
+ "available from https://www.virtualbox.org.\n"
+ "\n"
+ "This program is free software; you can redistribute it and/or\n"
+ "modify it under the terms of the GNU General Public License\n"
+ "as published by the Free Software Foundation, in version 3 of the\n"
+ "License.\n"
+ "\n"
+ "This program is distributed in the hope that it will be useful, but\n"
+ "WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n"
+ "General Public License for more details.\n"
+ "\n"
+ "You should have received a copy of the GNU General Public License\n"
+ "along with this program; if not, see <https://www.gnu.org/licenses>.\n"
+ "\n"
+ "SPDX-License-Identifier: GPL-3.0-only\n";
+
+static const char g_szVBoxOseOldGpl2[] =
+ "This file is part of VirtualBox Open Source Edition (OSE), as\n"
+ "available from http://www.virtualbox.org. This file is free software;\n"
+ "you can redistribute it and/or modify it under the terms of the GNU\n"
+ "General Public License (GPL) as published by the Free Software\n"
+ "Foundation, in version 2 as it comes in the \"COPYING\" file of the\n"
+ "VirtualBox OSE distribution. VirtualBox OSE is distributed in the\n"
+ "hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.\n";
+
+/** --license-ose-dual */
+static const char g_szVBoxOseDualGplCddl[] =
+ "This file is part of VirtualBox base platform packages, as\n"
+ "available from https://www.virtualbox.org.\n"
+ "\n"
+ "This program is free software; you can redistribute it and/or\n"
+ "modify it under the terms of the GNU General Public License\n"
+ "as published by the Free Software Foundation, in version 3 of the\n"
+ "License.\n"
+ "\n"
+ "This program is distributed in the hope that it will be useful, but\n"
+ "WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n"
+ "General Public License for more details.\n"
+ "\n"
+ "You should have received a copy of the GNU General Public License\n"
+ "along with this program; if not, see <https://www.gnu.org/licenses>.\n"
+ "\n"
+ "The contents of this file may alternatively be used under the terms\n"
+ "of the Common Development and Distribution License Version 1.0\n"
+ "(CDDL), a copy of it is provided in the \"COPYING.CDDL\" file included\n"
+ "in the VirtualBox distribution, in which case the provisions of the\n"
+ "CDDL are applicable instead of those of the GPL.\n"
+ "\n"
+ "You may elect to license modified versions of this file under the\n"
+ "terms and conditions of either the GPL or the CDDL or both.\n"
+ "\n"
+ "SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0\n";
+
+static const char g_szVBoxOseOldDualGpl2Cddl[] =
+ "This file is part of VirtualBox Open Source Edition (OSE), as\n"
+ "available from http://www.virtualbox.org. This file is free software;\n"
+ "you can redistribute it and/or modify it under the terms of the GNU\n"
+ "General Public License (GPL) as published by the Free Software\n"
+ "Foundation, in version 2 as it comes in the \"COPYING\" file of the\n"
+ "VirtualBox OSE distribution. VirtualBox OSE is distributed in the\n"
+ "hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.\n"
+ "\n"
+ "The contents of this file may alternatively be used under the terms\n"
+ "of the Common Development and Distribution License Version 1.0\n"
+ "(CDDL) only, as it comes in the \"COPYING.CDDL\" file of the\n"
+ "VirtualBox OSE distribution, in which case the provisions of the\n"
+ "CDDL are applicable instead of those of the GPL.\n"
+ "\n"
+ "You may elect to license modified versions of this file under the\n"
+ "terms and conditions of either the GPL or the CDDL or both.\n";
+
+/** --license-ose-cddl */
+static const char g_szVBoxOseCddl[] =
+ "This file is part of VirtualBox base platform packages, as\n"
+ "available from http://www.virtualbox.org.\n"
+ "\n"
+ "The contents of this file are subject to the terms of the Common\n"
+ "Development and Distribution License Version 1.0 (CDDL) only, as it\n"
+ "comes in the \"COPYING.CDDL\" file of the VirtualBox distribution.\n"
+ "\n"
+ "SPDX-License-Identifier: CDDL-1.0\n";
+
+static const char g_szVBoxOseOldCddl[] =
+ "This file is part of VirtualBox Open Source Edition (OSE), as\n"
+ "available from http://www.virtualbox.org. This file is free software;\n"
+ "you can redistribute it and/or modify it under the terms of the Common\n"
+ "Development and Distribution License Version 1.0 (CDDL) only, as it\n"
+ "comes in the \"COPYING.CDDL\" file of the VirtualBox OSE distribution.\n"
+ "VirtualBox OSE is distributed in the hope that it will be useful, but\n"
+ "WITHOUT ANY WARRANTY of any kind.\n";
+
+/** --license-lgpl */
+static const char g_szVBoxLgpl[] =
+ "This file is part of a free software library; you can redistribute\n"
+ "it and/or modify it under the terms of the GNU Lesser General\n"
+ "Public License version 2.1 as published by the Free Software\n"
+ "Foundation and shipped in the \"COPYING.LIB\" file with this library.\n"
+ "The library is distributed in the hope that it will be useful,\n"
+ "but WITHOUT ANY WARRANTY of any kind.\n"
+ "\n"
+ "Oracle LGPL Disclaimer: For the avoidance of doubt, except that if\n"
+ "any license choice other than GPL or LGPL is available it will\n"
+ "apply instead, Oracle elects to use only the Lesser General Public\n"
+ "License version 2.1 (LGPLv2) at this time for any software where\n"
+ "a choice of LGPL license versions is made available with the\n"
+ "language indicating that LGPLv2 or any later version may be used,\n"
+ "or where a choice of which version of the LGPL is applied is\n"
+ "otherwise unspecified.\n"
+ "\n"
+ "SPDX-License-Identifier: LGPL-2.1-only\n";
+
+/** --license-mit
+ * @note This isn't detectable as VirtualBox or Oracle specific.
+ */
+static const char g_szMit[] =
+ "Permission is hereby granted, free of charge, to any person\n"
+ "obtaining a copy of this software and associated documentation\n"
+ "files (the \"Software\"), to deal in the Software without\n"
+ "restriction, including without limitation the rights to use,\n"
+ "copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
+ "copies of the Software, and to permit persons to whom the\n"
+ "Software is furnished to do so, subject to the following\n"
+ "conditions:\n"
+ "\n"
+ "The above copyright notice and this permission notice shall be\n"
+ "included in all copies or substantial portions of the Software.\n"
+ "\n"
+ "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
+ "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n"
+ "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
+ "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n"
+ "HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n"
+ "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n"
+ "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n"
+ "OTHER DEALINGS IN THE SOFTWARE.\n";
+
+/** --license-mit, alternative wording \#1.
+ * @note This differes from g_szMit in "AUTHORS OR COPYRIGHT HOLDERS" is written
+ * "COPYRIGHT HOLDER(S) OR AUTHOR(S)". Its layout is wider, so it is a
+ * couple of lines shorter. */
+static const char g_szMitAlt1[] =
+ "Permission is hereby granted, free of charge, to any person obtaining a\n"
+ "copy of this software and associated documentation files (the \"Software\"),\n"
+ "to deal in the Software without restriction, including without limitation\n"
+ "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
+ "and/or sell copies of the Software, and to permit persons to whom the\n"
+ "Software is furnished to do so, subject to the following conditions:\n"
+ "\n"
+ "The above copyright notice and this permission notice shall be included in\n"
+ "all copies or substantial portions of the Software.\n"
+ "\n"
+ "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
+ "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
+ "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n"
+ "THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR\n"
+ "OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n"
+ "ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n"
+ "OTHER DEALINGS IN THE SOFTWARE.\n";
+
+/** --license-mit, alternative wording \#2.
+ * @note This differes from g_szMit in that "AUTHORS OR COPYRIGHT HOLDERS" is
+ * replaced with "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS".
+ * Its layout is wider, so it is a couple of lines shorter. */
+static const char g_szMitAlt2[] =
+ "Permission is hereby granted, free of charge, to any person obtaining a\n"
+ "copy of this software and associated documentation files (the \"Software\"),\n"
+ "to deal in the Software without restriction, including without limitation\n"
+ "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
+ "and/or sell copies of the Software, and to permit persons to whom the\n"
+ "Software is furnished to do so, subject to the following conditions:\n"
+ "\n"
+ "The above copyright notice and this permission notice shall be included in\n"
+ "all copies or substantial portions of the Software.\n"
+ "\n"
+ "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
+ "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
+ "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
+ "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
+ "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
+ "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
+ "USE OR OTHER DEALINGS IN THE SOFTWARE.\n";
+
+/** --license-mit, alternative wording \#3.
+ * @note This differes from g_szMitAlt2 in that the second and third sections
+ * have been switch. */
+static const char g_szMitAlt3[] =
+ "Permission is hereby granted, free of charge, to any person obtaining a\n"
+ "copy of this software and associated documentation files (the \"Software\"),\n"
+ "to deal in the Software without restriction, including without limitation\n"
+ "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
+ "and/or sell copies of the Software, and to permit persons to whom the\n"
+ "Software is furnished to do so, subject to the following conditions:\n"
+ "\n"
+ "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
+ "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
+ "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
+ "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
+ "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
+ "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
+ "USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
+ "\n"
+ "The above copyright notice and this permission notice shall be included in\n"
+ "all copies or substantial portions of the Software.\n";
+
+/** --license-(based-on)mit, alternative wording \#4.
+ * @note This differs from g_szMitAlt2 in injecting "(including the next
+ * paragraph)". */
+static const char g_szMitAlt4[] =
+ "Permission is hereby granted, free of charge, to any person obtaining a\n"
+ "copy of this software and associated documentation files (the \"Software\"),\n"
+ "to deal in the Software without restriction, including without limitation\n"
+ "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
+ "and/or sell copies of the Software, and to permit persons to whom the\n"
+ "Software is furnished to do so, subject to the following conditions:\n"
+ "\n"
+ "The above copyright notice and this permission notice (including the next\n"
+ "paragraph) shall be included in all copies or substantial portions of the\n"
+ "Software.\n"
+ "\n"
+ "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
+ "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
+ "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n"
+ "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
+ "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n"
+ "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n"
+ "DEALINGS IN THE SOFTWARE.\n";
+
+/** --license-(based-on)mit, alternative wording \#5.
+ * @note This differs from g_szMitAlt3 in using "sub license" instead of
+ * "sublicense" and adding an illogical "(including the next
+ * paragraph)" remark to the final paragraph. (vbox_ttm.c) */
+static const char g_szMitAlt5[] =
+ "Permission is hereby granted, free of charge, to any person obtaining a\n"
+ "copy of this software and associated documentation files (the\n"
+ "\"Software\"), to deal in the Software without restriction, including\n"
+ "without limitation the rights to use, copy, modify, merge, publish,\n"
+ "distribute, sub license, and/or sell copies of the Software, and to\n"
+ "permit persons to whom the Software is furnished to do so, subject to\n"
+ "the following conditions:\n"
+ "\n"
+ "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
+ "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
+ "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
+ "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
+ "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
+ "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
+ "USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
+ "\n"
+ "The above copyright notice and this permission notice (including the\n"
+ "next paragraph) shall be included in all copies or substantial portions\n"
+ "of the Software.\n";
+
+/** Oracle confidential. */
+static const char g_szOracleConfidential[] =
+ "Oracle Corporation confidential\n";
+
+/** Oracle confidential, old style. */
+static const char g_szOracleConfidentialOld[] =
+ "Oracle Corporation confidential\n"
+ "All rights reserved\n";
+
+/** Licenses to detect when --license-mit isn't used. */
+static const SCMLICENSETEXT g_aLicenses[] =
+{
+ { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseGpl)},
+ { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseOldGpl2)},
+ { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseDualGplCddl) },
+ { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseOldDualGpl2Cddl) },
+ { kScmLicenseType_OseCddl, kScmLicense_OseCddl, RT_STR_TUPLE(g_szVBoxOseCddl) },
+ { kScmLicenseType_OseCddl, kScmLicense_OseCddl, RT_STR_TUPLE(g_szVBoxOseOldCddl) },
+ { kScmLicenseType_VBoxLgpl, kScmLicense_Lgpl, RT_STR_TUPLE(g_szVBoxLgpl)},
+ { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidential) },
+ { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidentialOld) },
+ { kScmLicenseType_Invalid, kScmLicense_End, NULL, 0 },
+};
+
+/** Licenses to detect when --license-mit or --license-based-on-mit are used. */
+static const SCMLICENSETEXT g_aLicensesWithMit[] =
+{
+ { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMit) },
+ { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt1) },
+ { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt2) },
+ { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt3) },
+ { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt4) },
+ { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt5) },
+ { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseGpl)},
+ { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseOldGpl2)},
+ { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseDualGplCddl) },
+ { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseOldDualGpl2Cddl) },
+ { kScmLicenseType_VBoxLgpl, kScmLicense_Lgpl, RT_STR_TUPLE(g_szVBoxLgpl)},
+ { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidential) },
+ { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidentialOld) },
+ { kScmLicenseType_Invalid, kScmLicense_End, NULL, 0 },
+};
+
+/** Copyright holder. */
+static const char g_szCopyrightHolder[] = "Oracle and/or its affiliates.";
+
+/** Old copyright holder. */
+static const char g_szOldCopyrightHolder[] = "Oracle Corporation";
+
+/** LGPL disclaimer. */
+static const char g_szLgplDisclaimer[] =
+ "Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice\n"
+ "other than GPL or LGPL is available it will apply instead, Oracle elects to use only\n"
+ "the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where\n"
+ "a choice of LGPL license versions is made available with the language indicating\n"
+ "that LGPLv2 or any later version may be used, or where a choice of which version\n"
+ "of the LGPL is applied is otherwise unspecified.\n";
+
+/** Copyright+license comment start for each SCMCOMMENTSTYLE. */
+static RTSTRTUPLE const g_aCopyrightCommentStart[] =
+{
+ { RT_STR_TUPLE("<invalid> ") },
+ { RT_STR_TUPLE("/*") },
+ { RT_STR_TUPLE("#") },
+ { RT_STR_TUPLE("\"\"\"") },
+ { RT_STR_TUPLE(";") },
+ { RT_STR_TUPLE("REM") },
+ { RT_STR_TUPLE("rem") },
+ { RT_STR_TUPLE("Rem") },
+ { RT_STR_TUPLE("--") },
+ { RT_STR_TUPLE("'") },
+ { RT_STR_TUPLE("<!--") },
+ { RT_STR_TUPLE("<end>") },
+};
+
+/** Copyright+license line prefix for each SCMCOMMENTSTYLE. */
+static RTSTRTUPLE const g_aCopyrightCommentPrefix[] =
+{
+ { RT_STR_TUPLE("<invalid> ") },
+ { RT_STR_TUPLE(" * ") },
+ { RT_STR_TUPLE("# ") },
+ { RT_STR_TUPLE("") },
+ { RT_STR_TUPLE("; ") },
+ { RT_STR_TUPLE("REM ") },
+ { RT_STR_TUPLE("rem ") },
+ { RT_STR_TUPLE("Rem ") },
+ { RT_STR_TUPLE("-- ") },
+ { RT_STR_TUPLE("' ") },
+ { RT_STR_TUPLE(" ") },
+ { RT_STR_TUPLE("<end>") },
+};
+
+/** Copyright+license empty line for each SCMCOMMENTSTYLE. */
+static RTSTRTUPLE const g_aCopyrightCommentEmpty[] =
+{
+ { RT_STR_TUPLE("<invalid>") },
+ { RT_STR_TUPLE(" *") },
+ { RT_STR_TUPLE("#") },
+ { RT_STR_TUPLE("") },
+ { RT_STR_TUPLE(";") },
+ { RT_STR_TUPLE("REM") },
+ { RT_STR_TUPLE("rem") },
+ { RT_STR_TUPLE("Rem") },
+ { RT_STR_TUPLE("--") },
+ { RT_STR_TUPLE("'") },
+ { RT_STR_TUPLE("") },
+ { RT_STR_TUPLE("<end>") },
+};
+
+/** Copyright+license end of comment for each SCMCOMMENTSTYLE. */
+static RTSTRTUPLE const g_aCopyrightCommentEnd[] =
+{
+ { RT_STR_TUPLE("<invalid> ") },
+ { RT_STR_TUPLE(" */") },
+ { RT_STR_TUPLE("#") },
+ { RT_STR_TUPLE("\"\"\"") },
+ { RT_STR_TUPLE(";") },
+ { RT_STR_TUPLE("REM") },
+ { RT_STR_TUPLE("rem") },
+ { RT_STR_TUPLE("Rem") },
+ { RT_STR_TUPLE("--") },
+ { RT_STR_TUPLE("'") },
+ { RT_STR_TUPLE("-->") },
+ { RT_STR_TUPLE("<end>") },
+};
+
+
+/**
+ * Figures out the predominant casing of the "REM" keyword in a batch file.
+ *
+ * @returns Predominant comment style.
+ * @param pIn The file to scan. Will be rewound.
+ */
+static SCMCOMMENTSTYLE determineBatchFileCommentStyle(PSCMSTREAM pIn)
+{
+ /*
+ * Figure out whether it's using upper or lower case REM comments before
+ * doing the work.
+ */
+ uint32_t cUpper = 0;
+ uint32_t cLower = 0;
+ uint32_t cCamel = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ while ( cchLine > 2
+ && RT_C_IS_SPACE(*pchLine))
+ {
+ pchLine++;
+ cchLine--;
+ }
+ if ( ( cchLine > 3
+ && RT_C_IS_SPACE(pchLine[2]))
+ || cchLine == 3)
+ {
+ if ( pchLine[0] == 'R'
+ && pchLine[1] == 'E'
+ && pchLine[2] == 'M')
+ cUpper++;
+ else if ( pchLine[0] == 'r'
+ && pchLine[1] == 'e'
+ && pchLine[2] == 'm')
+ cLower++;
+ else if ( pchLine[0] == 'R'
+ && pchLine[1] == 'e'
+ && pchLine[2] == 'm')
+ cCamel++;
+ }
+ }
+
+ ScmStreamRewindForReading(pIn);
+
+ if (cLower >= cUpper && cLower >= cCamel)
+ return kScmCommentStyle_Rem_Lower;
+ if (cCamel >= cLower && cCamel >= cUpper)
+ return kScmCommentStyle_Rem_Camel;
+ return kScmCommentStyle_Rem_Upper;
+}
+
+
+/**
+ * Calculates the number of spaces from @a offStart to @a offEnd in @a pchLine,
+ * taking tabs into account.
+ */
+size_t ScmCalcSpacesForSrcSpan(const char *pchLine, size_t offStart, size_t offEnd, PCSCMSETTINGSBASE pSettings)
+{
+ size_t cchRet = 0;
+ if (offStart < offEnd)
+ {
+ offEnd -= offStart; /* becomes cchLeft now */
+ pchLine += offStart;
+ while (offEnd > 0)
+ {
+ const char *pszTab = (const char *)memchr(pchLine, '\t', offEnd);
+ if (!pszTab)
+ {
+ cchRet += offEnd;
+ break;
+ }
+ size_t offTab = (size_t)(pszTab - pchLine);
+ size_t cchToTab = pSettings->cchTab - offTab % pSettings->cchTab;
+ cchRet += offTab + cchToTab;
+ offEnd -= offTab + 1;
+ pchLine = pszTab + 1;
+ }
+ }
+ return cchRet;
+}
+
+
+/**
+ * Worker for isBlankLine.
+ *
+ * @returns true if blank, false if not.
+ * @param pchLine Pointer to the start of the line.
+ * @param cchLine The (encoded) length of the line, excluding EOL char.
+ */
+static bool isBlankLineSlow(const char *pchLine, size_t cchLine)
+{
+ /*
+ * From the end, more likely to hit a non-blank char there.
+ */
+ while (cchLine-- > 0)
+ if (!RT_C_IS_BLANK(pchLine[cchLine]))
+ return false;
+ return true;
+}
+
+/**
+ * Helper for checking whether a line is blank.
+ *
+ * @returns true if blank, false if not.
+ * @param pchLine Pointer to the start of the line.
+ * @param cchLine The (encoded) length of the line, excluding EOL char.
+ */
+DECLINLINE(bool) isBlankLine(const char *pchLine, size_t cchLine)
+{
+ if (cchLine == 0)
+ return true;
+ /*
+ * We're more likely to fine a non-space char at the end of the line than
+ * at the start, due to source code indentation.
+ */
+ if (pchLine[cchLine - 1])
+ return false;
+
+ /*
+ * Don't bother inlining loop code.
+ */
+ return isBlankLineSlow(pchLine, cchLine);
+}
+
+
+/**
+ * Checks if there are @a cch blanks at @a pch.
+ *
+ * @returns true if span of @a cch blanks, false if not.
+ * @param pch The start of the span to check.
+ * @param cch The length of the span.
+ */
+DECLINLINE(bool) isSpanOfBlanks(const char *pch, size_t cch)
+{
+ while (cch-- > 0)
+ {
+ char const ch = *pch++;
+ if (!RT_C_IS_BLANK(ch))
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * Strip trailing blanks (space & tab).
+ *
+ * @returns Modification state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+SCMREWRITERRES rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ if (!pSettings->fStripTrailingBlanks)
+ return kScmUnmodified;
+
+ bool fModified = false;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ int rc;
+ if ( cchLine == 0
+ || !RT_C_IS_BLANK(pchLine[cchLine - 1]) )
+ rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ else
+ {
+ cchLine--;
+ while (cchLine > 0 && RT_C_IS_BLANK(pchLine[cchLine - 1]))
+ cchLine--;
+ rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ fModified = true;
+ }
+ if (RT_FAILURE(rc))
+ return kScmUnmodified;
+ }
+ if (fModified)
+ ScmVerbose(pState, 2, " * Stripped trailing blanks\n");
+ return fModified ? kScmModified : kScmUnmodified;
+}
+
+/**
+ * Expand tabs.
+ *
+ * @returns Modification state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+SCMREWRITERRES rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ if (!pSettings->fConvertTabs)
+ return kScmUnmodified;
+
+ size_t const cchTab = pSettings->cchTab;
+ bool fModified = false;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ int rc;
+ const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);
+ if (!pchTab)
+ rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ else
+ {
+ size_t offTab = 0;
+ const char *pchChunk = pchLine;
+ for (;;)
+ {
+ size_t cchChunk = pchTab - pchChunk;
+ offTab += cchChunk;
+ ScmStreamWrite(pOut, pchChunk, cchChunk);
+
+ size_t cchToTab = cchTab - offTab % cchTab;
+ ScmStreamWrite(pOut, g_szTabSpaces, cchToTab);
+ offTab += cchToTab;
+
+ pchChunk = pchTab + 1;
+ size_t cchLeft = cchLine - (pchChunk - pchLine);
+ pchTab = (const char *)memchr(pchChunk, '\t', cchLeft);
+ if (!pchTab)
+ {
+ rc = ScmStreamPutLine(pOut, pchChunk, cchLeft, enmEol);
+ break;
+ }
+ }
+
+ fModified = true;
+ }
+ if (RT_FAILURE(rc))
+ return kScmUnmodified;
+ }
+ if (fModified)
+ ScmVerbose(pState, 2, " * Expanded tabs\n");
+ return fModified ? kScmModified : kScmUnmodified;
+}
+
+/**
+ * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF.
+ *
+ * @returns Modification state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ * @param enmDesiredEol The desired end of line indicator type.
+ * @param pszDesiredSvnEol The desired svn:eol-style.
+ */
+static SCMREWRITERRES rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
+ SCMEOL enmDesiredEol, const char *pszDesiredSvnEol)
+{
+ if (!pSettings->fConvertEol)
+ return kScmUnmodified;
+
+ bool fModified = false;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ if ( enmEol != enmDesiredEol
+ && enmEol != SCMEOL_NONE)
+ {
+ fModified = true;
+ enmEol = enmDesiredEol;
+ }
+ int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ if (RT_FAILURE(rc))
+ return kScmUnmodified;
+ }
+ if (fModified)
+ ScmVerbose(pState, 2, " * Converted EOL markers\n");
+
+ /* Check svn:eol-style if appropriate */
+ if ( pSettings->fSetSvnEol
+ && ScmSvnIsInWorkingCopy(pState))
+ {
+ char *pszEol;
+ int rc = ScmSvnQueryProperty(pState, "svn:eol-style", &pszEol);
+ if ( (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol))
+ || rc == VERR_NOT_FOUND)
+ {
+ if (rc == VERR_NOT_FOUND)
+ ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (missing)\n", pszDesiredSvnEol);
+ else
+ ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol);
+ int rc2 = ScmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol);
+ if (RT_FAILURE(rc2))
+ ScmError(pState, rc2, "ScmSvnSetProperty: %Rrc\n", rc2);
+ }
+ if (RT_SUCCESS(rc))
+ RTStrFree(pszEol);
+ }
+
+ /** @todo also check the subversion svn:eol-style state! */
+ return fModified ? kScmModified : kScmUnmodified;
+}
+
+/**
+ * Force native end of line indicator.
+ *
+ * @returns Modification state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+SCMREWRITERRES rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "native");
+#else
+ return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "native");
+#endif
+}
+
+/**
+ * Force the stream to use LF as the end of line indicator.
+ *
+ * @returns Modification state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+SCMREWRITERRES rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "LF");
+}
+
+/**
+ * Force the stream to use CRLF as the end of line indicator.
+ *
+ * @returns Modification state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+SCMREWRITERRES rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "CRLF");
+}
+
+/**
+ * Strip trailing blank lines and/or make sure there is exactly one blank line
+ * at the end of the file.
+ *
+ * @returns Modification state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ *
+ * @remarks ASSUMES trailing white space has been removed already.
+ */
+SCMREWRITERRES rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ if ( !pSettings->fStripTrailingLines
+ && !pSettings->fForceTrailingLine
+ && !pSettings->fForceFinalEol)
+ return kScmUnmodified;
+
+ size_t const cLines = ScmStreamCountLines(pIn);
+
+ /* Empty files remains empty. */
+ if (cLines <= 1)
+ return kScmUnmodified;
+
+ /* Figure out if we need to adjust the number of lines or not. */
+ size_t cLinesNew = cLines;
+
+ if ( pSettings->fStripTrailingLines
+ && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
+ {
+ while ( cLinesNew > 1
+ && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))
+ cLinesNew--;
+ }
+
+ if ( pSettings->fForceTrailingLine
+ && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
+ cLinesNew++;
+
+ bool fFixMissingEol = pSettings->fForceFinalEol
+ && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;
+
+ if ( !fFixMissingEol
+ && cLines == cLinesNew)
+ return kScmUnmodified;
+
+ /* Copy the number of lines we've arrived at. */
+ ScmStreamRewindForReading(pIn);
+
+ size_t cCopied = RT_MIN(cLinesNew, cLines);
+ ScmStreamCopyLines(pOut, pIn, cCopied);
+
+ if (cCopied != cLinesNew)
+ {
+ while (cCopied++ < cLinesNew)
+ ScmStreamPutLine(pOut, "", 0, ScmStreamGetEol(pIn));
+ }
+ /* Fix missing EOL if required. */
+ else if (fFixMissingEol)
+ {
+ if (ScmStreamGetEol(pIn) == SCMEOL_LF)
+ ScmStreamWrite(pOut, "\n", 1);
+ else
+ ScmStreamWrite(pOut, "\r\n", 2);
+ }
+
+ ScmVerbose(pState, 2, " * Adjusted trailing blank lines\n");
+ return kScmModified;
+}
+
+/**
+ * Make sure there is no svn:executable property on the current file.
+ *
+ * @returns kScmUnmodified - the state carries these kinds of changes.
+ * @param pState The rewriter state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+SCMREWRITERRES rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ RT_NOREF2(pIn, pOut);
+ if ( !pSettings->fSetSvnExecutable
+ || !ScmSvnIsInWorkingCopy(pState))
+ return kScmUnmodified;
+
+ int rc = ScmSvnQueryProperty(pState, "svn:executable", NULL);
+ if (RT_SUCCESS(rc))
+ {
+ ScmVerbose(pState, 2, " * removing svn:executable\n");
+ rc = ScmSvnDelProperty(pState, "svn:executable");
+ if (RT_FAILURE(rc))
+ ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
+ }
+ return kScmUnmodified;
+}
+
+/**
+ * Make sure there is no svn:keywords property on the current file.
+ *
+ * @returns kScmUnmodified - the state carries these kinds of changes.
+ * @param pState The rewriter state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+SCMREWRITERRES rewrite_SvnNoKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ RT_NOREF2(pIn, pOut);
+ if ( !pSettings->fSetSvnExecutable
+ || !ScmSvnIsInWorkingCopy(pState))
+ return kScmUnmodified;
+
+ int rc = ScmSvnQueryProperty(pState, "svn:keywords", NULL);
+ if (RT_SUCCESS(rc))
+ {
+ ScmVerbose(pState, 2, " * removing svn:keywords\n");
+ rc = ScmSvnDelProperty(pState, "svn:keywords");
+ if (RT_FAILURE(rc))
+ ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
+ }
+ return kScmUnmodified;
+}
+
+/**
+ * Make sure there is no svn:eol-style property on the current file.
+ *
+ * @returns kScmUnmodified - the state carries these kinds of changes.
+ * @param pState The rewriter state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+SCMREWRITERRES rewrite_SvnNoEolStyle(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ RT_NOREF2(pIn, pOut);
+ if ( !pSettings->fSetSvnExecutable
+ || !ScmSvnIsInWorkingCopy(pState))
+ return kScmUnmodified;
+
+ int rc = ScmSvnQueryProperty(pState, "svn:eol-style", NULL);
+ if (RT_SUCCESS(rc))
+ {
+ ScmVerbose(pState, 2, " * removing svn:eol-style\n");
+ rc = ScmSvnDelProperty(pState, "svn:eol-style");
+ if (RT_FAILURE(rc))
+ ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
+ }
+ return kScmUnmodified;
+}
+
+/**
+ * Makes sure the svn properties are appropriate for a binary.
+ *
+ * @returns kScmUnmodified - the state carries these kinds of changes.
+ * @param pState The rewriter state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+SCMREWRITERRES rewrite_SvnBinary(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ RT_NOREF2(pIn, pOut);
+ if ( !pSettings->fSetSvnExecutable
+ || !ScmSvnIsInWorkingCopy(pState))
+ return kScmUnmodified;
+
+ /* remove svn:eol-style and svn:keywords */
+ static const char * const s_apszRemove[] = { "svn:eol-style", "svn:keywords" };
+ for (uint32_t i = 0; i < RT_ELEMENTS(s_apszRemove); i++)
+ {
+ char *pszValue;
+ int rc = ScmSvnQueryProperty(pState, s_apszRemove[i], &pszValue);
+ if (RT_SUCCESS(rc))
+ {
+ ScmVerbose(pState, 2, " * removing %s=%s\n", s_apszRemove[i], pszValue);
+ RTStrFree(pszValue);
+ rc = ScmSvnDelProperty(pState, s_apszRemove[i]);
+ if (RT_FAILURE(rc))
+ ScmError(pState, rc, "ScmSvnSetProperty(,%s): %Rrc\n", s_apszRemove[i], rc);
+ }
+ else if (rc != VERR_NOT_FOUND)
+ ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
+ }
+
+ /* Make sure there is a svn:mime-type set. */
+ int rc = ScmSvnQueryProperty(pState, "svn:mime-type", NULL);
+ if (rc == VERR_NOT_FOUND)
+ {
+ ScmVerbose(pState, 2, " * settings svn:mime-type\n");
+ rc = ScmSvnSetProperty(pState, "svn:mime-type", "application/octet-stream");
+ if (RT_FAILURE(rc))
+ ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
+ }
+ else if (RT_FAILURE(rc))
+ ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
+
+ return kScmUnmodified;
+}
+
+/**
+ * Make sure the Id and Revision keywords are expanded.
+ *
+ * @returns kScmUnmodified - the state carries these kinds of changes.
+ * @param pState The rewriter state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+SCMREWRITERRES rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ RT_NOREF2(pIn, pOut);
+ if ( !pSettings->fSetSvnKeywords
+ || !ScmSvnIsInWorkingCopy(pState))
+ return kScmUnmodified;
+
+ char *pszKeywords;
+ int rc = ScmSvnQueryProperty(pState, "svn:keywords", &pszKeywords);
+ if ( RT_SUCCESS(rc)
+ && ( !strstr(pszKeywords, "Id") /** @todo need some function for finding a word in a string. */
+ || !strstr(pszKeywords, "Revision")) )
+ {
+ if (!strstr(pszKeywords, "Id") && !strstr(pszKeywords, "Revision"))
+ rc = RTStrAAppend(&pszKeywords, " Id Revision");
+ else if (!strstr(pszKeywords, "Id"))
+ rc = RTStrAAppend(&pszKeywords, " Id");
+ else
+ rc = RTStrAAppend(&pszKeywords, " Revision");
+ if (RT_SUCCESS(rc))
+ {
+ ScmVerbose(pState, 2, " * changing svn:keywords to '%s'\n", pszKeywords);
+ rc = ScmSvnSetProperty(pState, "svn:keywords", pszKeywords);
+ if (RT_FAILURE(rc))
+ ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
+ }
+ else
+ ScmError(pState, rc, "RTStrAppend: %Rrc\n", rc);
+ RTStrFree(pszKeywords);
+ }
+ else if (rc == VERR_NOT_FOUND)
+ {
+ ScmVerbose(pState, 2, " * setting svn:keywords to 'Id Revision'\n");
+ rc = ScmSvnSetProperty(pState, "svn:keywords", "Id Revision");
+ if (RT_FAILURE(rc))
+ ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
+ }
+ else if (RT_SUCCESS(rc))
+ RTStrFree(pszKeywords);
+
+ return kScmUnmodified;
+}
+
+/**
+ * Checks the svn:sync-process value and that parent is exported too.
+ *
+ * @returns kScmUnmodified - the state carries these kinds of changes.
+ * @param pState The rewriter state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+SCMREWRITERRES rewrite_SvnSyncProcess(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ RT_NOREF2(pIn, pOut);
+ if ( pSettings->fSkipSvnSyncProcess
+ || !ScmSvnIsInWorkingCopy(pState))
+ return kScmUnmodified;
+
+ char *pszSyncProcess;
+ int rc = ScmSvnQueryProperty(pState, "svn:sync-process", &pszSyncProcess);
+ if (RT_SUCCESS(rc))
+ {
+ if (strcmp(pszSyncProcess, "export") == 0)
+ {
+ char *pszParentSyncProcess;
+ rc = ScmSvnQueryParentProperty(pState, "svn:sync-process", &pszParentSyncProcess);
+ if (RT_SUCCESS(rc))
+ {
+ if (strcmp(pszSyncProcess, "export") != 0)
+ ScmError(pState, VERR_INVALID_STATE,
+ "svn:sync-process=export, but parent directory differs: %s\n"
+ "WARNING! Make sure to unexport everything inside the directory first!\n"
+ " Then you may export the directory and stuff inside it if you want.\n"
+ " (Just exporting the directory will not make anything inside it externally visible.)\n"
+ , pszParentSyncProcess);
+ RTStrFree(pszParentSyncProcess);
+ }
+ else if (rc == VERR_NOT_FOUND)
+ ScmError(pState, VERR_NOT_FOUND,
+ "svn:sync-process=export, but parent directory is not exported!\n"
+ "WARNING! Make sure to unexport everything inside the directory first!\n"
+ " Then you may export the directory and stuff inside it if you want.\n"
+ " (Just exporting the directory will not make anything inside it externally visible.)\n");
+ else
+ ScmError(pState, rc, "ScmSvnQueryParentProperty: %Rrc\n", rc);
+ }
+ else if (strcmp(pszSyncProcess, "ignore") != 0)
+ ScmError(pState, VERR_INVALID_NAME, "Bad sync-process value: %s\n", pszSyncProcess);
+ RTStrFree(pszSyncProcess);
+ }
+ else if (rc != VERR_NOT_FOUND)
+ ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
+
+ return kScmUnmodified;
+}
+
+/**
+ * Checks the that there is no bidirectional unicode fun in the file.
+ *
+ * @returns kScmUnmodified - the state carries these kinds of changes.
+ * @param pState The rewriter state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+SCMREWRITERRES rewrite_UnicodeChecks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ RT_NOREF2(pIn, pOut);
+ if (pSettings->fSkipUnicodeChecks)
+ return kScmUnmodified;
+
+ /*
+ * Just scan the input for weird stuff and fail if we find anything we don't like.
+ */
+ uint32_t iLine = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ iLine++;
+ const char *pchCur = pchLine;
+ size_t cchLeft = cchLine;
+ while (cchLeft > 0)
+ {
+ RTUNICP uc = 0;
+ int rc = RTStrGetCpNEx(&pchCur, &cchLeft, &uc);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszWhat;
+ switch (uc)
+ {
+ default:
+ continue;
+
+ /* Potentially evil bi-directional control codes (Table I, trojan-source.pdf): */
+ case 0x202a: pszWhat = "LRE - left-to-right embedding"; break;
+ case 0x202b: pszWhat = "RLE - right-to-left embedding"; break;
+ case 0x202d: pszWhat = "LRO - left-to-right override"; break;
+ case 0x202e: pszWhat = "RLO - right-to-left override"; break;
+ case 0x2066: pszWhat = "LRI - left-to-right isolate"; break;
+ case 0x2067: pszWhat = "RLI - right-to-left isolate"; break;
+ case 0x2068: pszWhat = "FSI - first strong isolate"; break;
+ case 0x202c: pszWhat = "PDF - pop directional formatting (LRE, RLE, LRO, RLO)"; break;
+ case 0x2069: pszWhat = "PDI - pop directional isolate (LRI, RLI)"; break;
+
+ /** @todo add checks for homoglyphs too. */
+ }
+ ScmFixManually(pState, "%u:%zu: Evil unicode codepoint: %s\n", iLine, pchCur - pchLine, pszWhat);
+ }
+ else
+ ScmFixManually(pState, "%u:%zu: Invalid UTF-8 encoding: %Rrc\n", iLine, pchCur - pchLine, rc);
+ }
+ }
+
+ return kScmUnmodified;
+}
+
+
+
+/*********************************************************************************************************************************
+* Copyright & License *
+*********************************************************************************************************************************/
+
+/**
+ * Compares two strings word-by-word, ignoring spaces, punctuation and case.
+ *
+ * Assumes ASCII strings.
+ *
+ * @returns true if they match, false if not.
+ * @param psz1 The first string. This is typically the known one.
+ * @param psz2 The second string. This is typically the unknown one,
+ * which is why we return a next pointer for this one.
+ * @param ppsz2Next Where to return the next part of the 2nd string. If
+ * this is NULL, the whole string must match.
+ */
+static bool IsEqualWordByWordIgnoreCase(const char *psz1, const char *psz2, const char **ppsz2Next)
+{
+ for (;;)
+ {
+ /* Try compare raw strings first. */
+ char ch1 = *psz1;
+ char ch2 = *psz2;
+ if ( ch1 == ch2
+ || RT_C_TO_LOWER(ch1) == RT_C_TO_LOWER(ch2))
+ {
+ if (ch1)
+ {
+ psz1++;
+ psz2++;
+ }
+ else
+ {
+ if (ppsz2Next)
+ *ppsz2Next = psz2;
+ return true;
+ }
+ }
+ else
+ {
+ /* Try skip spaces an punctuation. */
+ while ( RT_C_IS_SPACE(ch1)
+ || RT_C_IS_PUNCT(ch1))
+ ch1 = *++psz1;
+
+ if (ch1 == '\0' && ppsz2Next)
+ {
+ *ppsz2Next = psz2;
+ return true;
+ }
+
+ while ( RT_C_IS_SPACE(ch2)
+ || RT_C_IS_PUNCT(ch2))
+ ch2 = *++psz2;
+
+ if ( ch1 != ch2
+ && RT_C_TO_LOWER(ch1) != RT_C_TO_LOWER(ch2))
+ {
+ if (ppsz2Next)
+ *ppsz2Next = psz2;
+ return false;
+ }
+ }
+ }
+}
+
+/**
+ * Looks for @a pszFragment anywhere in @a pszText, ignoring spaces, punctuation
+ * and case.
+ *
+ * @returns true if found, false if not.
+ * @param pszText The haystack to search in.
+ * @param cchText The length @a pszText.
+ * @param pszFragment The needle to search for.
+ * @param ppszStart Where to return the address in @a pszText where
+ * the fragment was found. Optional.
+ * @param ppszNext Where to return the pointer to the first char in
+ * @a pszText after the fragment. Optional.
+ *
+ * @remarks First character of @a pszFragment must be an 7-bit ASCII character!
+ * This character must not be space or punctuation.
+ */
+static bool scmContainsWordByWordIgnoreCase(const char *pszText, size_t cchText, const char *pszFragment,
+ const char **ppszStart, const char **ppszNext)
+{
+ Assert(!((unsigned)*pszFragment & 0x80));
+ Assert(pszText[cchText] == '\0');
+ Assert(!RT_C_IS_BLANK(*pszFragment));
+ Assert(!RT_C_IS_PUNCT(*pszFragment));
+
+ char chLower = RT_C_TO_LOWER(*pszFragment);
+ char chUpper = RT_C_TO_UPPER(*pszFragment);
+ for (;;)
+ {
+ const char *pszHit = (const char *)memchr(pszText, chLower, cchText);
+ const char *pszHit2 = (const char *)memchr(pszText, chUpper, cchText);
+ if (!pszHit && !pszHit2)
+ {
+ if (ppszStart)
+ *ppszStart = NULL;
+ if (ppszNext)
+ *ppszNext = NULL;
+ return false;
+ }
+
+ if ( pszHit == NULL
+ || ( pszHit2 != NULL
+ && ((uintptr_t)pszHit2 < (uintptr_t)pszHit)) )
+ pszHit = pszHit2;
+
+ const char *pszNext;
+ if (IsEqualWordByWordIgnoreCase(pszFragment, pszHit, &pszNext))
+ {
+ if (ppszStart)
+ *ppszStart = pszHit;
+ if (ppszNext)
+ *ppszNext = pszNext;
+ return true;
+ }
+
+ cchText -= pszHit - pszText + 1;
+ pszText = pszHit + 1;
+ }
+}
+
+
+/**
+ * Counts the number of lines in the given substring.
+ *
+ * @returns The number of lines.
+ * @param psz The start of the substring.
+ * @param cch The length of the substring.
+ */
+static uint32_t CountLinesInSubstring(const char *psz, size_t cch)
+{
+ uint32_t cLines = 0;
+ for (;;)
+ {
+ const char *pszEol = (const char *)memchr(psz, '\n', cch);
+ if (pszEol)
+ cLines++;
+ else
+ return cLines + (*psz != '\0');
+ cch -= pszEol + 1 - psz;
+ if (!cch)
+ return cLines;
+ psz = pszEol + 1;
+ }
+}
+
+
+/**
+ * Comment parser callback for locating copyright and license.
+ */
+static DECLCALLBACK(int)
+rewrite_Copyright_CommentCallback(PCSCMCOMMENTINFO pInfo, const char *pszBody, size_t cchBody, void *pvUser)
+{
+ PSCMCOPYRIGHTINFO pState = (PSCMCOPYRIGHTINFO)pvUser;
+ Assert(strlen(pszBody) == cchBody);
+ //RTPrintf("--- comment at %u, type %u ---\n%s\n--- end ---\n", pInfo->iLineStart, pInfo->enmType, pszBody);
+ ScmVerbose(pState->pState, 5,
+ "--- comment at %u col %u, %u lines, type %u, %u lines before body, %u lines after body\n",
+ pInfo->iLineStart, pInfo->offStart, pInfo->iLineEnd - pInfo->iLineStart + 1, pInfo->enmType,
+ pInfo->cBlankLinesBefore, pInfo->cBlankLinesAfter);
+
+ pState->cComments++;
+
+ uint32_t iLine = pInfo->iLineStart + pInfo->cBlankLinesBefore;
+
+ /*
+ * Look for a 'contributed by' or 'includes contributions from' line, these
+ * comes first when present.
+ */
+ const char *pchContributedBy = NULL;
+ size_t cchContributedBy = 0;
+ size_t cBlankLinesAfterContributedBy = 0;
+ if ( pState->pszContributedBy == NULL
+ && ( pState->iLineCopyright == UINT32_MAX
+ || pState->iLineLicense == UINT32_MAX)
+ && ( ( cchBody > sizeof("Contributed by")
+ && RTStrNICmp(pszBody, RT_STR_TUPLE("contributed by")) == 0)
+ || ( cchBody > sizeof("Includes contributions from")
+ && RTStrNICmp(pszBody, RT_STR_TUPLE("Includes contributions from")) == 0) ) )
+ {
+ const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
+ while (pszNextLine && pszNextLine[1] != '\n')
+ pszNextLine = (const char *)memchr(pszNextLine + 1, '\n', cchBody);
+ if (pszNextLine)
+ {
+ pchContributedBy = pszBody;
+ cchContributedBy = pszNextLine - pszBody;
+
+ /* Skip the copyright line and any blank lines following it. */
+ cchBody -= cchContributedBy + 1;
+ pszBody = pszNextLine + 1;
+ iLine += 1;
+ while (*pszBody == '\n')
+ {
+ pszBody++;
+ cchBody--;
+ iLine++;
+ cBlankLinesAfterContributedBy++;
+ }
+ }
+ }
+
+ /*
+ * Look for the copyright line.
+ */
+ bool fFoundCopyright = false;
+ uint32_t cBlankLinesAfterCopyright = 0;
+ if ( pState->iLineCopyright == UINT32_MAX
+ && cchBody > sizeof("Copyright") + RT_MIN(sizeof(g_szCopyrightHolder), sizeof(g_szOldCopyrightHolder))
+ && RTStrNICmp(pszBody, RT_STR_TUPLE("copyright")) == 0)
+ {
+ const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
+
+ /* Oracle copyright? */
+ const char *pszEnd = pszNextLine ? pszNextLine : &pszBody[cchBody];
+ while (RT_C_IS_SPACE(pszEnd[-1]))
+ pszEnd--;
+ if ( ( (uintptr_t)(pszEnd - pszBody) > sizeof(g_szCopyrightHolder)
+ && (*(unsigned char *)(pszEnd - sizeof(g_szCopyrightHolder) + 1) & 0x80) == 0 /* to avoid annoying assertion */
+ && RTStrNICmp(pszEnd - sizeof(g_szCopyrightHolder) + 1, RT_STR_TUPLE(g_szCopyrightHolder)) == 0)
+ || ( (uintptr_t)(pszEnd - pszBody) > sizeof(g_szOldCopyrightHolder)
+ && (*(unsigned char *)(pszEnd - sizeof(g_szOldCopyrightHolder) + 1) & 0x80) == 0 /* to avoid annoying assertion */
+ && RTStrNICmp(pszEnd - sizeof(g_szOldCopyrightHolder) + 1, RT_STR_TUPLE(g_szOldCopyrightHolder)) == 0) )
+ {
+ /* Parse out the year(s). */
+ const char *psz = pszBody + sizeof("copyright");
+ while ((uintptr_t)psz < (uintptr_t)pszEnd && !RT_C_IS_DIGIT(*psz))
+ psz++;
+ if (RT_C_IS_DIGIT(*psz))
+ {
+ char *pszNext;
+ int rc = RTStrToUInt32Ex(psz, &pszNext, 10, &pState->uFirstYear);
+ if ( RT_SUCCESS(rc)
+ && rc != VWRN_NUMBER_TOO_BIG
+ && rc != VWRN_NEGATIVE_UNSIGNED)
+ {
+ if ( pState->uFirstYear < 1975
+ || pState->uFirstYear > 3000)
+ {
+ char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
+ RTStrPurgeEncoding(pszCopy);
+ ScmError(pState->pState, VERR_OUT_OF_RANGE, "Copyright year is out of range: %u ('%s')\n",
+ pState->uFirstYear, pszCopy);
+ RTStrFree(pszCopy);
+ pState->uFirstYear = UINT32_MAX;
+ }
+
+ while (RT_C_IS_SPACE(*pszNext))
+ pszNext++;
+ if (*pszNext == '-')
+ {
+ do
+ pszNext++;
+ while (RT_C_IS_SPACE(*pszNext));
+ rc = RTStrToUInt32Ex(pszNext, &pszNext, 10, &pState->uLastYear);
+ if ( RT_SUCCESS(rc)
+ && rc != VWRN_NUMBER_TOO_BIG
+ && rc != VWRN_NEGATIVE_UNSIGNED)
+ {
+ if ( pState->uLastYear < 1975
+ || pState->uLastYear > 3000)
+ {
+ char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
+ RTStrPurgeEncoding(pszCopy);
+ ScmError(pState->pState, VERR_OUT_OF_RANGE, "Second copyright year is out of range: %u ('%s')\n",
+ pState->uLastYear, pszCopy);
+ RTStrFree(pszCopy);
+ pState->uLastYear = UINT32_MAX;
+ }
+ else if (pState->uFirstYear > pState->uLastYear)
+ {
+ char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
+ RTStrPurgeEncoding(pszCopy);
+ RTMsgWarning("Copyright years switched(?): '%s'\n", pszCopy);
+ RTStrFree(pszCopy);
+ uint32_t iTmp = pState->uLastYear;
+ pState->uLastYear = pState->uFirstYear;
+ pState->uFirstYear = iTmp;
+ }
+ }
+ else
+ {
+ pState->uLastYear = UINT32_MAX;
+ char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
+ RTStrPurgeEncoding(pszCopy);
+ ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
+ "Failed to parse second copyright year: '%s'\n", pszCopy);
+ RTMemFree(pszCopy);
+ }
+ }
+ else if (*pszNext != g_szCopyrightHolder[0])
+ {
+ char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
+ RTStrPurgeEncoding(pszCopy);
+ ScmError(pState->pState, VERR_PARSE_ERROR,
+ "Failed to parse copyright: '%s'\n", pszCopy);
+ RTMemFree(pszCopy);
+ } else
+ pState->uLastYear = pState->uFirstYear;
+ }
+ else
+ {
+ pState->uFirstYear = UINT32_MAX;
+ char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
+ RTStrPurgeEncoding(pszCopy);
+ ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
+ "Failed to parse copyright year: '%s'\n", pszCopy);
+ RTMemFree(pszCopy);
+ }
+ }
+
+ /* The copyright comment must come before the license. */
+ if (pState->iLineLicense != UINT32_MAX)
+ ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright (line %u) must come before the license (line %u)!\n",
+ iLine, pState->iLineLicense);
+
+ /* In C/C++ code, this must be a multiline comment. While in python it
+ must be a */
+ if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
+ ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a multiline comment (no doxygen stuff)\n");
+ else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
+ ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a doc-string\n");
+
+ /* The copyright must be followed by the license. */
+ if (!pszNextLine)
+ ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
+
+ /* Quit if we've flagged a failure. */
+ if (RT_FAILURE(pState->pState->rc))
+ return VERR_CALLBACK_RETURN;
+
+ /* Check if it's well formed and up to date. */
+ char szWellFormed[256];
+ size_t cchWellFormed;
+ if (pState->uFirstYear == pState->uLastYear)
+ cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u %s",
+ pState->uFirstYear, g_szCopyrightHolder);
+ else
+ cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u-%u %s",
+ pState->uFirstYear, pState->uLastYear, g_szCopyrightHolder);
+ pState->fUpToDateCopyright = pState->uLastYear == g_uYear;
+ pState->iLineCopyright = iLine;
+ pState->fWellFormedCopyright = cchWellFormed == (uintptr_t)(pszEnd - pszBody)
+ && memcmp(pszBody, szWellFormed, cchWellFormed) == 0;
+ if (!pState->fWellFormedCopyright)
+ ScmVerbose(pState->pState, 1, "* copyright isn't well formed\n");
+
+ /* If there wasn't exactly one blank line before the comment, trigger a rewrite. */
+ if (pInfo->cBlankLinesBefore != 1)
+ {
+ ScmVerbose(pState->pState, 1, "* copyright comment is preceeded by %u blank lines instead of 1\n",
+ pInfo->cBlankLinesBefore);
+ pState->fWellFormedCopyright = false;
+ }
+
+ /* If the comment doesn't start in column 1, trigger rewrite. */
+ if (pInfo->offStart != 0)
+ {
+ ScmVerbose(pState->pState, 1, "* copyright comment starts in column %u instead of 1\n", pInfo->offStart + 1);
+ pState->fWellFormedCopyright = false;
+ /** @todo check that there isn't any code preceeding the comment. */
+ }
+
+ if (pchContributedBy)
+ {
+ pState->pszContributedBy = RTStrDupN(pchContributedBy, cchContributedBy);
+ if (cBlankLinesAfterContributedBy != 1)
+ {
+ ScmVerbose(pState->pState, 1, "* %u blank lines between contributed by and copyright, should be 1\n",
+ cBlankLinesAfterContributedBy);
+ pState->fWellFormedCopyright = false;
+ }
+ }
+
+ fFoundCopyright = true;
+ ScmVerbose(pState->pState, 3, "oracle copyright %u-%u: up-to-date=%RTbool well-formed=%RTbool\n",
+ pState->uFirstYear, pState->uLastYear, pState->fUpToDateCopyright, pState->fWellFormedCopyright);
+ }
+ else
+ {
+ char *pszCopy = RTStrDupN(pszBody, pszEnd - pszBody);
+ RTStrPurgeEncoding(pszCopy);
+ ScmVerbose(pState->pState, 3, "not oracle copyright: '%s'\n", pszCopy);
+ RTStrFree(pszCopy);
+ }
+
+ if (!pszNextLine)
+ return VINF_SUCCESS;
+
+ /* Skip the copyright line and any blank lines following it. */
+ cchBody -= pszNextLine - pszBody + 1;
+ pszBody = pszNextLine + 1;
+ iLine += 1;
+ while (*pszBody == '\n')
+ {
+ pszBody++;
+ cchBody--;
+ iLine++;
+ cBlankLinesAfterCopyright++;
+ }
+
+ /*
+ * If we have a based-on-mit scenario, check for the lead in now and
+ * complain if not found.
+ */
+ if ( fFoundCopyright
+ && pState->enmLicenceOpt == kScmLicense_BasedOnMit
+ && pState->iLineLicense == UINT32_MAX)
+ {
+ if (RTStrNICmp(pszBody, RT_STR_TUPLE("This file is based on ")) == 0)
+ {
+ /* Take down a comment area which goes up to 'this file is based on'.
+ The license line and length isn't used but gets set to cover the current line. */
+ pState->iLineComment = pInfo->iLineStart;
+ pState->cLinesComment = iLine - pInfo->iLineStart;
+ pState->iLineLicense = iLine;
+ pState->cLinesLicense = 1;
+ pState->fExternalLicense = true;
+ pState->fIsCorrectLicense = true;
+ pState->fWellFormedLicense = true;
+
+ /* Check if we've got a MIT a license here or not. */
+ pState->pCurrentLicense = NULL;
+ do
+ {
+ const char *pszEol = (const char *)memchr(pszBody, '\n', cchBody);
+ if (!pszEol || pszEol[1] == '\0')
+ {
+ pszBody += cchBody;
+ cchBody = 0;
+ break;
+ }
+ cchBody -= pszEol - pszBody + 1;
+ pszBody = pszEol + 1;
+ iLine++;
+
+ for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++)
+ {
+ const char *pszNext;
+ if ( pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */
+ && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext))
+ {
+ pState->pCurrentLicense = pCur;
+ break;
+ }
+ }
+ } while (!pState->pCurrentLicense);
+ if (!pState->pCurrentLicense)
+ ScmError(pState->pState, VERR_NOT_FOUND, "Could not find the based-on license!\n");
+ else if (pState->pCurrentLicense->enmType != kScmLicenseType_Mit)
+ ScmError(pState->pState, VERR_NOT_FOUND, "The based-on license is not MIT (%.32s...)\n",
+ pState->pCurrentLicense->psz);
+ }
+ else
+ ScmError(pState->pState, VERR_WRONG_ORDER, "Expected 'This file is based on ...' after our copyright!\n");
+ return VINF_SUCCESS;
+ }
+ }
+
+ /*
+ * Look for LGPL like text in the comment.
+ */
+ if (pState->fCheckforLgpl && cchBody > 128)
+ {
+ /* We look for typical LGPL notices. */
+ if (pState->iLineLgplNotice == UINT32_MAX)
+ {
+ static const char * const s_apszFragments[] =
+ {
+ "under the terms of the GNU Lesser General Public License",
+ };
+ for (unsigned i = 0; i < RT_ELEMENTS(s_apszFragments); i++)
+ if (scmContainsWordByWordIgnoreCase(pszBody, cchBody, s_apszFragments[i], NULL, NULL))
+ {
+ pState->iLineLgplNotice = iLine;
+ pState->iLineAfterLgplComment = pInfo->iLineEnd + 1;
+ ScmVerbose(pState->pState, 3, "Found LGPL notice at %u\n", iLine);
+ break;
+ }
+ }
+
+ if ( pState->iLineLgplDisclaimer == UINT32_MAX
+ && scmContainsWordByWordIgnoreCase(pszBody, cchBody, g_szLgplDisclaimer, NULL, NULL))
+ {
+ pState->iLineLgplDisclaimer = iLine;
+ ScmVerbose(pState->pState, 3, "Found LGPL disclaimer at %u\n", iLine);
+ }
+ }
+
+ /*
+ * Look for the license text.
+ */
+ if (pState->iLineLicense == UINT32_MAX)
+ {
+ for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++)
+ {
+ const char *pszNext;
+ if ( pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */
+ && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext))
+ {
+ while ( RT_C_IS_SPACE(*pszNext)
+ || (RT_C_IS_PUNCT(*pszNext) && *pszNext != '-'))
+ pszNext++;
+
+ uint32_t cDashes = 0;
+ while (*pszNext == '-')
+ cDashes++, pszNext++;
+ bool fExternal = cDashes > 10;
+
+ if ( *pszNext == '\0'
+ || fExternal)
+ {
+ /* In C/C++ code, this must be a multiline comment. While in python it
+ must be a doc-string. */
+ if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
+ ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a multiline comment (no doxygen stuff)\n");
+ else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
+ ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a doc-string\n");
+
+ /* Quit if we've flagged a failure. */
+ if (RT_FAILURE(pState->pState->rc))
+ return VERR_CALLBACK_RETURN;
+
+ /* Record it. */
+ pState->iLineLicense = iLine;
+ pState->cLinesLicense = CountLinesInSubstring(pszBody, pszNext - pszBody) - fExternal;
+ pState->pCurrentLicense = pCur;
+ pState->fExternalLicense = fExternal;
+ pState->fIsCorrectLicense = pCur == pState->pExpectedLicense;
+ pState->fWellFormedLicense = memcmp(pszBody, pCur->psz, pCur->cch - 1) == 0;
+ if (!pState->fWellFormedLicense)
+ ScmVerbose(pState->pState, 1, "* license text isn't well-formed\n");
+
+ /* If there was more than one blank line between the copyright and the
+ license text, extend the license text area and force a rewrite of it. */
+ if (cBlankLinesAfterCopyright > 1)
+ {
+ ScmVerbose(pState->pState, 1, "* %u blank lines between copyright and license text, instead of 1\n",
+ cBlankLinesAfterCopyright);
+ pState->iLineLicense -= cBlankLinesAfterCopyright - 1;
+ pState->cLinesLicense += cBlankLinesAfterCopyright - 1;
+ pState->fWellFormedLicense = false;
+ }
+
+ /* If there was more than one blank line after the license, trigger a rewrite. */
+ if (!fExternal && pInfo->cBlankLinesAfter != 1)
+ {
+ ScmVerbose(pState->pState, 1, "* copyright comment is followed by %u blank lines instead of 1\n",
+ pInfo->cBlankLinesAfter);
+ pState->fWellFormedLicense = false;
+ }
+
+ /** @todo Check that the last comment line doesn't have any code on it. */
+ /** @todo Check that column 2 contains '*' for C/C++ files. */
+
+ ScmVerbose(pState->pState, 3,
+ "Found license %d/%d at %u..%u: is-correct=%RTbool well-formed=%RTbool external-part=%RTbool open-source=%RTbool\n",
+ pCur->enmType, pCur->enmOpt, pState->iLineLicense, pState->iLineLicense + pState->cLinesLicense,
+ pState->fIsCorrectLicense, pState->fWellFormedLicense,
+ pState->fExternalLicense, pState->fOpenSource);
+
+ if (fFoundCopyright)
+ {
+ pState->iLineComment = pInfo->iLineStart;
+ pState->cLinesComment = (fExternal ? pState->iLineLicense + pState->cLinesLicense : pInfo->iLineEnd + 1)
+ - pInfo->iLineStart;
+ }
+ else
+ ScmError(pState->pState, VERR_WRONG_ORDER, "License should be preceeded by the copyright!\n");
+ break;
+ }
+ }
+ }
+ }
+
+ if (fFoundCopyright && pState->iLineLicense == UINT32_MAX)
+ ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
+
+ /*
+ * Stop looking for stuff after 100 comments.
+ */
+ if (pState->cComments > 100)
+ return VERR_CALLBACK_RETURN;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Writes comment body text.
+ *
+ * @returns Stream status.
+ * @param pOut The output stream.
+ * @param pszText The text to write.
+ * @param cchText The length of the text.
+ * @param enmCommentStyle The comment style.
+ * @param enmEol The EOL style.
+ */
+static int scmWriteCommentBody(PSCMSTREAM pOut, const char *pszText, size_t cchText,
+ SCMCOMMENTSTYLE enmCommentStyle, SCMEOL enmEol)
+{
+ Assert(pszText[cchText - 1] == '\n');
+ Assert(pszText[cchText - 2] != '\n');
+ NOREF(cchText);
+ do
+ {
+ const char *pszEol = strchr(pszText, '\n');
+ if (pszEol != pszText)
+ {
+ ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
+ g_aCopyrightCommentPrefix[enmCommentStyle].cch);
+ ScmStreamWrite(pOut, pszText, pszEol - pszText);
+ ScmStreamPutEol(pOut, enmEol);
+ }
+ else
+ ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
+ g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
+ pszText = pszEol + 1;
+ } while (*pszText != '\0');
+ return ScmStreamGetStatus(pOut);
+}
+
+
+/**
+ * Updates the copyright year and/or license text.
+ *
+ * @returns Modification state.
+ * @param pState The rewriter state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ * @param enmCommentStyle The comment style used by the file.
+ */
+static SCMREWRITERRES rewrite_Copyright_Common(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut,
+ PCSCMSETTINGSBASE pSettings, SCMCOMMENTSTYLE enmCommentStyle)
+{
+ if ( !pSettings->fUpdateCopyrightYear
+ && pSettings->enmUpdateLicense == kScmLicense_LeaveAlone)
+ return kScmUnmodified;
+
+ /*
+ * Try locate the relevant comments.
+ */
+ SCMCOPYRIGHTINFO Info =
+ {
+ /*.pState = */ pState,
+ /*.enmCommentStyle = */ enmCommentStyle,
+
+ /*.cComments = */ 0,
+
+ /*.pszContributedBy = */ NULL,
+
+ /*.iLineComment = */ UINT32_MAX,
+ /*.cLinesComment = */ 0,
+
+ /*.iLineCopyright = */ UINT32_MAX,
+ /*.uFirstYear = */ UINT32_MAX,
+ /*.uLastYear = */ UINT32_MAX,
+ /*.fWellFormedCopyright = */ false,
+ /*.fUpToDateCopyright = */ false,
+
+ /*.fOpenSource = */ true,
+ /*.pExpectedLicense = */ NULL,
+ /*.paLicenses = */ pSettings->enmUpdateLicense != kScmLicense_Mit
+ && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit
+ ? &g_aLicenses[0] : &g_aLicensesWithMit[0],
+ /*.enmLicenceOpt = */ pSettings->enmUpdateLicense,
+ /*.iLineLicense = */ UINT32_MAX,
+ /*.cLinesLicense = */ 0,
+ /*.pCurrentLicense = */ NULL,
+ /*.fIsCorrectLicense = */ false,
+ /*.fWellFormedLicense = */ false,
+ /*.fExternalLicense = */ false,
+
+ /*.fCheckForLgpl = */ true,
+ /*.iLineLgplNotice = */ UINT32_MAX,
+ /*.iLineAfterLgplComment = */ UINT32_MAX,
+ /*.iLineLgplDisclaimer = */ UINT32_MAX,
+ };
+
+ /* Figure Info.fOpenSource and the desired license: */
+ char *pszSyncProcess;
+ int rc = ScmSvnQueryProperty(pState, "svn:sync-process", &pszSyncProcess);
+ if (RT_SUCCESS(rc))
+ {
+ Info.fOpenSource = strcmp(RTStrStrip(pszSyncProcess), "export") == 0;
+ RTStrFree(pszSyncProcess);
+ }
+ else if (rc == VERR_NOT_FOUND)
+ Info.fOpenSource = false;
+ else
+ return ScmError(pState, rc, "ScmSvnQueryProperty(svn:sync-process): %Rrc\n", rc);
+
+ Info.pExpectedLicense = Info.paLicenses;
+ if (Info.fOpenSource)
+ {
+ if ( pSettings->enmUpdateLicense != kScmLicense_Mit
+ && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit)
+ while (Info.pExpectedLicense->enmOpt != pSettings->enmUpdateLicense)
+ Info.pExpectedLicense++;
+ else
+ Assert(Info.pExpectedLicense->enmOpt == kScmLicense_Mit);
+ }
+ else
+ while (Info.pExpectedLicense->enmType != kScmLicenseType_Confidential)
+ Info.pExpectedLicense++;
+
+ /* Scan the comments. */
+ rc = ScmEnumerateComments(pIn, enmCommentStyle, rewrite_Copyright_CommentCallback, &Info);
+ if ( (rc == VERR_CALLBACK_RETURN || RT_SUCCESS(rc))
+ && RT_SUCCESS(pState->rc))
+ {
+ /*
+ * Do conformity checks.
+ */
+ bool fAddLgplDisclaimer = false;
+ if (Info.fCheckforLgpl)
+ {
+ if ( Info.iLineLgplNotice != UINT32_MAX
+ && Info.iLineLgplDisclaimer == UINT32_MAX)
+ {
+ if (!pSettings->fLgplDisclaimer) /** @todo reconcile options with common sense. */
+ ScmError(pState, VERR_NOT_FOUND, "LGPL licence notice on line %u, but no LGPL disclaimer was found!\n",
+ Info.iLineLgplNotice + 1);
+ else
+ {
+ ScmVerbose(pState, 1, "* Need to add LGPL disclaimer\n");
+ fAddLgplDisclaimer = true;
+ }
+ }
+ else if ( Info.iLineLgplNotice == UINT32_MAX
+ && Info.iLineLgplDisclaimer != UINT32_MAX)
+ ScmError(pState, VERR_NOT_FOUND, "LGPL disclaimer on line %u, but no LGPL copyright notice!\n",
+ Info.iLineLgplDisclaimer + 1);
+ }
+
+ if (!pSettings->fExternalCopyright)
+ {
+ if (Info.iLineCopyright == UINT32_MAX)
+ ScmError(pState, VERR_NOT_FOUND, "Missing copyright!\n");
+ if (Info.iLineLicense == UINT32_MAX)
+ ScmError(pState, VERR_NOT_FOUND, "Missing license!\n");
+ }
+ else if (Info.iLineCopyright != UINT32_MAX)
+ ScmError(pState, VERR_NOT_FOUND,
+ "Marked as external copyright only, but found non-external copyright statement at line %u!\n",
+ Info.iLineCopyright + 1);
+
+
+ if (RT_SUCCESS(pState->rc))
+ {
+ /*
+ * Do we need to make any changes?
+ */
+ bool fUpdateCopyright = !pSettings->fExternalCopyright
+ && ( !Info.fWellFormedCopyright
+ || (!Info.fUpToDateCopyright && pSettings->fUpdateCopyrightYear));
+ bool fUpdateLicense = !pSettings->fExternalCopyright
+ && Info.enmLicenceOpt != kScmLicense_LeaveAlone
+ && ( !Info.fWellFormedLicense
+ || !Info.fIsCorrectLicense);
+ if ( fUpdateCopyright
+ || fUpdateLicense
+ || fAddLgplDisclaimer)
+ {
+ Assert(Info.iLineComment != UINT32_MAX);
+ Assert(Info.cLinesComment > 0);
+
+ /*
+ * Okay, do the work.
+ */
+ ScmStreamRewindForReading(pIn);
+
+ if (pSettings->fUpdateCopyrightYear)
+ Info.uLastYear = g_uYear;
+
+ uint32_t iLine = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ if ( iLine == Info.iLineComment
+ && (fUpdateCopyright || fUpdateLicense) )
+ {
+ /* Leading blank line. */
+ ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz,
+ g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol);
+
+ /* Contributed by someone? */
+ if (Info.pszContributedBy)
+ {
+ const char *psz = Info.pszContributedBy;
+ for (;;)
+ {
+ const char *pszEol = strchr(psz, '\n');
+ size_t cchContribLine = pszEol ? pszEol - psz : strlen(psz);
+ ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
+ g_aCopyrightCommentPrefix[enmCommentStyle].cch);
+ ScmStreamWrite(pOut, psz, cchContribLine);
+ ScmStreamPutEol(pOut, enmEol);
+ if (!pszEol)
+ break;
+ psz = pszEol + 1;
+ }
+
+ ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
+ g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
+ }
+
+ /* Write the copyright comment line. */
+ ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
+ g_aCopyrightCommentPrefix[enmCommentStyle].cch);
+
+ char szCopyright[256];
+ size_t cchCopyright;
+ if (Info.uFirstYear == Info.uLastYear)
+ cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u %s",
+ Info.uFirstYear, g_szCopyrightHolder);
+ else
+ cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u-%u %s",
+ Info.uFirstYear, Info.uLastYear, g_szCopyrightHolder);
+
+ ScmStreamWrite(pOut, szCopyright, cchCopyright);
+ ScmStreamPutEol(pOut, enmEol);
+
+ if (pSettings->enmUpdateLicense != kScmLicense_BasedOnMit)
+ {
+ /* Blank line separating the two. */
+ ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
+ g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
+
+ /* Write the license text. */
+ scmWriteCommentBody(pOut, Info.pExpectedLicense->psz, Info.pExpectedLicense->cch,
+ enmCommentStyle, enmEol);
+
+ /* Final comment line. */
+ if (!Info.fExternalLicense)
+ ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz,
+ g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol);
+ }
+ else
+ Assert(Info.fExternalLicense);
+
+ /* Skip the copyright and license text in the input file. */
+ rc = ScmStreamGetStatus(pOut);
+ if (RT_SUCCESS(rc))
+ {
+ iLine = Info.iLineComment + Info.cLinesComment;
+ rc = ScmStreamSeekByLine(pIn, iLine);
+ }
+ }
+ /*
+ * Add LGPL disclaimer?
+ */
+ else if ( iLine == Info.iLineAfterLgplComment
+ && fAddLgplDisclaimer)
+ {
+ ScmStreamPutEol(pOut, enmEol);
+ ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz,
+ g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol);
+ scmWriteCommentBody(pOut, g_szLgplDisclaimer, sizeof(g_szLgplDisclaimer) - 1,
+ enmCommentStyle, enmEol);
+ ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz,
+ g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol);
+
+ /* put the actual line */
+ rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ iLine++;
+ }
+ else
+ {
+ rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ iLine++;
+ }
+ if (RT_FAILURE(rc))
+ {
+ RTStrFree(Info.pszContributedBy);
+ return kScmUnmodified;
+ }
+ } /* for each source line */
+
+ RTStrFree(Info.pszContributedBy);
+ return kScmModified;
+ }
+ }
+ }
+ else
+ ScmError(pState, rc, "ScmEnumerateComments: %Rrc\n", rc);
+ NOREF(pState); NOREF(pOut);
+ RTStrFree(Info.pszContributedBy);
+ return kScmUnmodified;
+}
+
+
+/** Copyright updater for C-style comments. */
+SCMREWRITERRES rewrite_Copyright_CstyleComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_C);
+}
+
+/** Copyright updater for hash-prefixed comments. */
+SCMREWRITERRES rewrite_Copyright_HashComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Hash);
+}
+
+/** Copyright updater for REM-prefixed comments. */
+SCMREWRITERRES rewrite_Copyright_RemComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, determineBatchFileCommentStyle(pIn));
+}
+
+/** Copyright updater for python comments. */
+SCMREWRITERRES rewrite_Copyright_PythonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Python);
+}
+
+/** Copyright updater for semicolon-prefixed comments. */
+SCMREWRITERRES rewrite_Copyright_SemicolonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut,
+ PCSCMSETTINGSBASE pSettings)
+{
+ return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Semicolon);
+}
+
+/** Copyright updater for sql comments. */
+SCMREWRITERRES rewrite_Copyright_SqlComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Sql);
+}
+
+/** Copyright updater for tick-prefixed comments. */
+SCMREWRITERRES rewrite_Copyright_TickComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Tick);
+}
+
+/** Copyright updater for XML comments. */
+SCMREWRITERRES rewrite_Copyright_XmlComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Xml);
+}
+
+
+
+/*********************************************************************************************************************************
+* Flower Box Section Markers *
+*********************************************************************************************************************************/
+
+static bool isFlowerBoxSectionMarker(PSCMSTREAM pIn, const char *pchLine, size_t cchLine, uint32_t cchWidth,
+ const char **ppchText, size_t *pcchText, bool *pfNeedFixing)
+{
+ *ppchText = NULL;
+ *pcchText = 0;
+ *pfNeedFixing = false;
+
+ /*
+ * The first line.
+ */
+ if (pchLine[0] != '/')
+ return false;
+ size_t offLine = 1;
+ while (offLine < cchLine && pchLine[offLine] == '*')
+ offLine++;
+ if (offLine < 20) /* (Code below depend on a reasonable minimum here.) */
+ return false;
+ while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
+ offLine++;
+ if (offLine != cchLine)
+ return false;
+
+ size_t const cchBox = cchLine;
+ *pfNeedFixing = cchBox != cchWidth;
+
+ /*
+ * The next line, extracting the text.
+ */
+ SCMEOL enmEol;
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ if (cchLine < cchBox - 3)
+ return false;
+
+ offLine = 0;
+ if (RT_C_IS_BLANK(pchLine[0]))
+ {
+ *pfNeedFixing = true;
+ offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
+ }
+
+ if (pchLine[offLine] != '*')
+ return false;
+ offLine++;
+
+ if (!RT_C_IS_BLANK(pchLine[offLine + 1]))
+ return false;
+ offLine++;
+
+ while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
+ offLine++;
+ if (offLine >= cchLine)
+ return false;
+ if (!RT_C_IS_UPPER(pchLine[offLine]))
+ return false;
+
+ if (offLine != 4 || cchLine != cchBox)
+ *pfNeedFixing = true;
+
+ *ppchText = &pchLine[offLine];
+ size_t const offText = offLine;
+
+ /* From the end now. */
+ offLine = cchLine - 1;
+ while (RT_C_IS_BLANK(pchLine[offLine]))
+ offLine--;
+
+ if (pchLine[offLine] != '*')
+ return false;
+ offLine--;
+ if (!RT_C_IS_BLANK(pchLine[offLine]))
+ return false;
+ offLine--;
+ while (RT_C_IS_BLANK(pchLine[offLine]))
+ offLine--;
+ *pcchText = offLine - offText + 1;
+
+ /*
+ * Third line closes the box.
+ */
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ if (cchLine < cchBox - 3)
+ return false;
+
+ offLine = 0;
+ if (RT_C_IS_BLANK(pchLine[0]))
+ {
+ *pfNeedFixing = true;
+ offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
+ }
+ while (offLine < cchLine && pchLine[offLine] == '*')
+ offLine++;
+ if (offLine < cchBox - 4)
+ return false;
+
+ if (pchLine[offLine] != '/')
+ return false;
+ offLine++;
+
+ if (offLine != cchBox)
+ *pfNeedFixing = true;
+
+ while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
+ offLine++;
+ if (offLine != cchLine)
+ return false;
+
+ return true;
+}
+
+
+/**
+ * Flower box marker comments in C and C++ code.
+ *
+ * @returns Modification state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+SCMREWRITERRES rewrite_FixFlowerBoxMarkers(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ if (!pSettings->fFixFlowerBoxMarkers)
+ return kScmUnmodified;
+
+ /*
+ * Work thru the file line by line looking for flower box markers.
+ */
+ size_t cChanges = 0;
+ size_t cBlankLines = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ /*
+ * Get a likely match for a first line.
+ */
+ if ( pchLine[0] == '/'
+ && cchLine > 20
+ && pchLine[1] == '*'
+ && pchLine[2] == '*'
+ && pchLine[3] == '*')
+ {
+ size_t const offSaved = ScmStreamTell(pIn);
+ char const *pchText;
+ size_t cchText;
+ bool fNeedFixing;
+ bool fIsFlowerBoxSection = isFlowerBoxSectionMarker(pIn, pchLine, cchLine, pSettings->cchWidth,
+ &pchText, &cchText, &fNeedFixing);
+ if ( fIsFlowerBoxSection
+ && ( fNeedFixing
+ || cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers) )
+ {
+ while (cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers)
+ {
+ ScmStreamPutEol(pOut, enmEol);
+ cBlankLines++;
+ }
+
+ ScmStreamPutCh(pOut, '/');
+ ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
+ ScmStreamPutEol(pOut, enmEol);
+
+ static const char s_szLead[] = "* ";
+ ScmStreamWrite(pOut, s_szLead, sizeof(s_szLead) - 1);
+ ScmStreamWrite(pOut, pchText, cchText);
+ size_t offCurPlus1 = sizeof(s_szLead) - 1 + cchText + 1;
+ ScmStreamWrite(pOut, g_szSpaces, offCurPlus1 < pSettings->cchWidth ? pSettings->cchWidth - offCurPlus1 : 1);
+ ScmStreamPutCh(pOut, '*');
+ ScmStreamPutEol(pOut, enmEol);
+
+ ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
+ ScmStreamPutCh(pOut, '/');
+ ScmStreamPutEol(pOut, enmEol);
+
+ cChanges++;
+ cBlankLines = 0;
+ continue;
+ }
+
+ int rc = ScmStreamSeekAbsolute(pIn, offSaved);
+ if (RT_FAILURE(rc))
+ return kScmUnmodified;
+ }
+
+ int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ if (RT_FAILURE(rc))
+ return kScmUnmodified;
+
+ /* Do blank line accounting so we can ensure at least two blank lines
+ before each section marker. */
+ if (!isBlankLine(pchLine, cchLine))
+ cBlankLines = 0;
+ else
+ cBlankLines++;
+ }
+ if (cChanges > 0)
+ ScmVerbose(pState, 2, " * Converted %zu flower boxer markers\n", cChanges);
+ return cChanges != 0 ? kScmModified : kScmUnmodified;
+}
+
+
+/**
+ * Looks for the start of a todo comment.
+ *
+ * @returns Offset into the line of the comment start sequence.
+ * @param pchLine The line to search.
+ * @param cchLineBeforeTodo The length of the line before the todo.
+ * @param pfSameLine Indicates whether it's refering to a statemtn on
+ * the same line comment (true), or the next
+ * statement (false).
+ */
+static size_t findTodoCommentStart(char const *pchLine, size_t cchLineBeforeTodo, bool *pfSameLine)
+{
+ *pfSameLine = false;
+
+ /* Skip one '@' or '\\'. */
+ char ch;
+ if ( cchLineBeforeTodo > 2
+ && ( ((ch = pchLine[cchLineBeforeTodo - 1]) == '@')
+ || ch == '\\' ) )
+ cchLineBeforeTodo--;
+
+ /* Skip blanks. */
+ while ( cchLineBeforeTodo > 2
+ && RT_C_IS_BLANK(pchLine[cchLineBeforeTodo - 1]))
+ cchLineBeforeTodo--;
+
+ /* Look for same line indicator. */
+ if ( cchLineBeforeTodo > 0
+ && pchLine[cchLineBeforeTodo - 1] == '<')
+ {
+ *pfSameLine = true;
+ cchLineBeforeTodo--;
+ }
+
+ /* Skip *s */
+ while ( cchLineBeforeTodo > 1
+ && pchLine[cchLineBeforeTodo - 1] == '*')
+ cchLineBeforeTodo--;
+
+ /* Do we have a comment opening sequence. */
+ if ( cchLineBeforeTodo > 0
+ && pchLine[cchLineBeforeTodo - 1] == '/'
+ && ( ( cchLineBeforeTodo >= 2
+ && pchLine[cchLineBeforeTodo - 2] == '/')
+ || pchLine[cchLineBeforeTodo] == '*'))
+ {
+ /* Skip slashes at the start. */
+ while ( cchLineBeforeTodo > 0
+ && pchLine[cchLineBeforeTodo - 1] == '/')
+ cchLineBeforeTodo--;
+
+ return cchLineBeforeTodo;
+ }
+
+ return ~(size_t)0;
+}
+
+
+/**
+ * Looks for a TODO or todo in the given line.
+ *
+ * @returns Offset into the line of found, ~(size_t)0 if not.
+ * @param pchLine The line to search.
+ * @param cchLine The length of the line.
+ */
+static size_t findTodo(char const *pchLine, size_t cchLine)
+{
+ if (cchLine >= 4 + 2)
+ {
+ /* We don't search the first to chars because we need the start of a comment.
+ Also, skip the last three chars since we need at least four for a match. */
+ size_t const cchLineT = cchLine - 3;
+ if ( memchr(pchLine + 2, 't', cchLineT - 2) != NULL
+ || memchr(pchLine + 2, 'T', cchLineT - 2) != NULL)
+ {
+ for (size_t off = 2; off < cchLineT; off++)
+ {
+ char ch = pchLine[off];
+ if ( ( ch != 't'
+ && ch != 'T')
+ || ( (ch = pchLine[off + 1]) != 'o'
+ && ch != 'O')
+ || ( (ch = pchLine[off + 2]) != 'd'
+ && ch != 'D')
+ || ( (ch = pchLine[off + 3]) != 'o'
+ && ch != 'O')
+ || ( off + 4 != cchLine
+ && (ch = pchLine[off + 4]) != ' '
+ && ch != '\t'
+ && ch != ':' /** @todo */
+ && (ch != '*' || off + 5 > cchLine || pchLine[off + 5] != '/') /** @todo */
+ ) )
+ { /* not a hit - likely */ }
+ else
+ return off;
+ }
+ }
+ }
+ return ~(size_t)0;
+}
+
+
+/**
+ * Doxygen todos in C and C++ code.
+ *
+ * @returns Modification state.
+ * @param pState The rewriter state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+SCMREWRITERRES rewrite_Fix_C_and_CPP_Todos(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ if (!pSettings->fFixTodos)
+ return kScmUnmodified;
+
+ /*
+ * Work thru the file line by line looking for the start of todo comments.
+ */
+ size_t cChanges = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ /*
+ * Look for the word 'todo' in the line. We're currently only trying
+ * to catch comments starting with the word todo and adjust the start of
+ * the doxygen statement.
+ */
+ size_t offTodo = findTodo(pchLine, cchLine);
+ if ( offTodo != ~(size_t)0
+ && offTodo >= 2)
+ {
+ /* Work backwards to find the start of the comment. */
+ bool fSameLine = false;
+ size_t offCommentStart = findTodoCommentStart(pchLine, offTodo, &fSameLine);
+ if (offCommentStart != ~(size_t)0)
+ {
+ char szNew[64];
+ size_t cchNew = 0;
+ szNew[cchNew++] = '/';
+ szNew[cchNew++] = pchLine[offCommentStart + 1];
+ szNew[cchNew++] = pchLine[offCommentStart + 1];
+ if (fSameLine)
+ szNew[cchNew++] = '<';
+ szNew[cchNew++] = ' ';
+ szNew[cchNew++] = '@';
+ szNew[cchNew++] = 't';
+ szNew[cchNew++] = 'o';
+ szNew[cchNew++] = 'd';
+ szNew[cchNew++] = 'o';
+
+ /* Figure out wheter to continue after the @todo statement opening, we'll strip ':'
+ but need to take into account that we might be at the end of the line before
+ adding the space. */
+ size_t offTodoAfter = offTodo + 4;
+ if ( offTodoAfter < cchLine
+ && pchLine[offTodoAfter] == ':')
+ offTodoAfter++;
+ if ( offTodoAfter < cchLine
+ && RT_C_IS_BLANK(pchLine[offTodoAfter]))
+ offTodoAfter++;
+ if (offTodoAfter < cchLine)
+ szNew[cchNew++] = ' ';
+
+ /* Write it out. */
+ ScmStreamWrite(pOut, pchLine, offCommentStart);
+ ScmStreamWrite(pOut, szNew, cchNew);
+ if (offTodoAfter < cchLine)
+ ScmStreamWrite(pOut, &pchLine[offTodoAfter], cchLine - offTodoAfter);
+ ScmStreamPutEol(pOut, enmEol);
+
+ /* Check whether we actually made any changes. */
+ if ( cchNew != offTodoAfter - offCommentStart
+ || memcmp(szNew, &pchLine[offCommentStart], cchNew))
+ cChanges++;
+ continue;
+ }
+ }
+
+ int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ if (RT_FAILURE(rc))
+ return kScmUnmodified;
+ }
+ if (cChanges > 0)
+ ScmVerbose(pState, 2, " * Converted %zu todo statements.\n", cChanges);
+ return cChanges != 0 ? kScmModified : kScmUnmodified;
+}
+
+
+/**
+ * Tries to parse a C/C++ preprocessor include directive.
+ *
+ * This is resonably forgiving and expects sane input.
+ *
+ * @retval kScmIncludeDir_Invalid if not a valid include directive.
+ * @retval kScmIncludeDir_Quoted
+ * @retval kScmIncludeDir_Bracketed
+ * @retval kScmIncludeDir_Macro
+ *
+ * @param pState The rewriter state (for repording malformed
+ * directives).
+ * @param pchLine The line to try parse as an include statement.
+ * @param cchLine The line length.
+ * @param ppchFilename Where to return the pointer to the filename part.
+ * @param pcchFilename Where to return the length of the filename.
+ */
+SCMINCLUDEDIR ScmMaybeParseCIncludeLine(PSCMRWSTATE pState, const char *pchLine, size_t cchLine,
+ const char **ppchFilename, size_t *pcchFilename)
+{
+ /* Skip leading spaces: */
+ while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
+ cchLine--, pchLine++;
+
+ /* Check for '#': */
+ if (cchLine > 0 && *pchLine == '#')
+ {
+ cchLine--;
+ pchLine++;
+
+ /* Skip spaces after '#' (optional): */
+ while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
+ cchLine--, pchLine++;
+
+ /* Check for 'include': */
+ static char const s_szInclude[] = "include";
+ if ( cchLine >= sizeof(s_szInclude)
+ && memcmp(pchLine, RT_STR_TUPLE(s_szInclude)) == 0)
+ {
+ cchLine -= sizeof(s_szInclude) - 1;
+ pchLine += sizeof(s_szInclude) - 1;
+
+ /* Skip spaces after 'include' word (optional): */
+ while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
+ cchLine--, pchLine++;
+ if (cchLine > 0)
+ {
+ /* Quoted or bracketed? */
+ char const chFirst = *pchLine;
+ if (chFirst == '"' || chFirst == '<')
+ {
+ cchLine--;
+ pchLine++;
+ const char *pchEnd = (const char *)memchr(pchLine, chFirst == '"' ? '"' : '>', cchLine);
+ if (pchEnd)
+ {
+ if (ppchFilename)
+ *ppchFilename = pchLine;
+ if (pcchFilename)
+ *pcchFilename = pchEnd - pchLine;
+ return chFirst == '"' ? kScmIncludeDir_Quoted : kScmIncludeDir_Bracketed;
+ }
+ ScmError(pState, VERR_PARSE_ERROR, "Unbalanced #include filename %s: %.*s\n",
+ chFirst == '"' ? "quotes" : "brackets" , cchLine, pchLine);
+ }
+ /* C prepreprocessor macro? */
+ else if (ScmIsCIdentifierLeadChar(chFirst))
+ {
+ size_t cchFilename = 1;
+ while ( cchFilename < cchLine
+ && ScmIsCIdentifierChar(pchLine[cchFilename]))
+ cchFilename++;
+ if (ppchFilename)
+ *ppchFilename = pchLine;
+ if (pcchFilename)
+ *pcchFilename = cchFilename;
+ return kScmIncludeDir_Macro;
+ }
+ else
+ ScmError(pState, VERR_PARSE_ERROR, "Malformed #include filename part: %.*s\n", cchLine, pchLine);
+ }
+ else
+ ScmError(pState, VERR_PARSE_ERROR, "Missing #include filename!\n");
+ }
+ }
+
+ if (ppchFilename)
+ *ppchFilename = NULL;
+ if (pcchFilename)
+ *pcchFilename = 0;
+ return kScmIncludeDir_Invalid;
+}
+
+
+/**
+ * Fix err.h/errcore.h usage.
+ *
+ * @returns Modification state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+SCMREWRITERRES rewrite_Fix_Err_H(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ if (!pSettings->fFixErrH)
+ return kScmUnmodified;
+
+ static struct
+ {
+ const char *pszHeader;
+ unsigned cchHeader;
+ int iLevel;
+ } const s_aHeaders[] =
+ {
+ { RT_STR_TUPLE("iprt/errcore.h"), 1 },
+ { RT_STR_TUPLE("iprt/err.h"), 2 },
+ { RT_STR_TUPLE("VBox/err.h"), 3 },
+ };
+ static RTSTRTUPLE const g_aLevel1Statuses[] = /* Note! Keep in sync with errcore.h content! */
+ {
+ { RT_STR_TUPLE("VINF_SUCCESS") },
+ { RT_STR_TUPLE("VERR_GENERAL_FAILURE") },
+ { RT_STR_TUPLE("VERR_INVALID_PARAMETER") },
+ { RT_STR_TUPLE("VWRN_INVALID_PARAMETER") },
+ { RT_STR_TUPLE("VERR_INVALID_MAGIC") },
+ { RT_STR_TUPLE("VWRN_INVALID_MAGIC") },
+ { RT_STR_TUPLE("VERR_INVALID_HANDLE") },
+ { RT_STR_TUPLE("VWRN_INVALID_HANDLE") },
+ { RT_STR_TUPLE("VERR_INVALID_POINTER") },
+ { RT_STR_TUPLE("VERR_NO_MEMORY") },
+ { RT_STR_TUPLE("VERR_PERMISSION_DENIED") },
+ { RT_STR_TUPLE("VINF_PERMISSION_DENIED") },
+ { RT_STR_TUPLE("VERR_VERSION_MISMATCH") },
+ { RT_STR_TUPLE("VERR_NOT_IMPLEMENTED") },
+ { RT_STR_TUPLE("VERR_INVALID_FLAGS") },
+ { RT_STR_TUPLE("VERR_WRONG_ORDER") },
+ { RT_STR_TUPLE("VERR_INVALID_FUNCTION") },
+ { RT_STR_TUPLE("VERR_NOT_SUPPORTED") },
+ { RT_STR_TUPLE("VINF_NOT_SUPPORTED") },
+ { RT_STR_TUPLE("VERR_ACCESS_DENIED") },
+ { RT_STR_TUPLE("VERR_INTERRUPTED") },
+ { RT_STR_TUPLE("VINF_INTERRUPTED") },
+ { RT_STR_TUPLE("VERR_TIMEOUT") },
+ { RT_STR_TUPLE("VINF_TIMEOUT") },
+ { RT_STR_TUPLE("VERR_BUFFER_OVERFLOW") },
+ { RT_STR_TUPLE("VINF_BUFFER_OVERFLOW") },
+ { RT_STR_TUPLE("VERR_TOO_MUCH_DATA") },
+ { RT_STR_TUPLE("VERR_TRY_AGAIN") },
+ { RT_STR_TUPLE("VINF_TRY_AGAIN") },
+ { RT_STR_TUPLE("VERR_PARSE_ERROR") },
+ { RT_STR_TUPLE("VERR_OUT_OF_RANGE") },
+ { RT_STR_TUPLE("VERR_NUMBER_TOO_BIG") },
+ { RT_STR_TUPLE("VWRN_NUMBER_TOO_BIG") },
+ { RT_STR_TUPLE("VERR_CANCELLED") },
+ { RT_STR_TUPLE("VERR_TRAILING_CHARS") },
+ { RT_STR_TUPLE("VWRN_TRAILING_CHARS") },
+ { RT_STR_TUPLE("VERR_TRAILING_SPACES") },
+ { RT_STR_TUPLE("VWRN_TRAILING_SPACES") },
+ { RT_STR_TUPLE("VERR_NOT_FOUND") },
+ { RT_STR_TUPLE("VWRN_NOT_FOUND") },
+ { RT_STR_TUPLE("VERR_INVALID_STATE") },
+ { RT_STR_TUPLE("VWRN_INVALID_STATE") },
+ { RT_STR_TUPLE("VERR_OUT_OF_RESOURCES") },
+ { RT_STR_TUPLE("VWRN_OUT_OF_RESOURCES") },
+ { RT_STR_TUPLE("VERR_END_OF_STRING") },
+ { RT_STR_TUPLE("VERR_CALLBACK_RETURN") },
+ { RT_STR_TUPLE("VINF_CALLBACK_RETURN") },
+ { RT_STR_TUPLE("VERR_DUPLICATE") },
+ { RT_STR_TUPLE("VERR_MISSING") },
+ { RT_STR_TUPLE("VERR_BUFFER_UNDERFLOW") },
+ { RT_STR_TUPLE("VINF_BUFFER_UNDERFLOW") },
+ { RT_STR_TUPLE("VERR_NOT_AVAILABLE") },
+ { RT_STR_TUPLE("VERR_MISMATCH") },
+ { RT_STR_TUPLE("VERR_WRONG_TYPE") },
+ { RT_STR_TUPLE("VWRN_WRONG_TYPE") },
+ { RT_STR_TUPLE("VERR_WRONG_PARAMETER_COUNT") },
+ { RT_STR_TUPLE("VERR_WRONG_PARAMETER_TYPE") },
+ { RT_STR_TUPLE("VERR_INVALID_CLIENT_ID") },
+ { RT_STR_TUPLE("VERR_INVALID_SESSION_ID") },
+ { RT_STR_TUPLE("VERR_INCOMPATIBLE_CONFIG") },
+ { RT_STR_TUPLE("VERR_INTERNAL_ERROR") },
+ { RT_STR_TUPLE("VINF_GETOPT_NOT_OPTION") },
+ { RT_STR_TUPLE("VERR_GETOPT_UNKNOWN_OPTION") },
+ };
+
+ /*
+ * First pass: Scout #include err.h/errcore.h locations and usage.
+ *
+ * Note! This isn't entirely optimal since it's also parsing comments and
+ * strings, not just code. However it does a decent job for now.
+ */
+ int iIncludeLevel = 0;
+ int iUsageLevel = 0;
+ uint32_t iLine = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ iLine++;
+ if (cchLine < 6)
+ continue;
+
+ /*
+ * Look for #includes.
+ */
+ const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
+ if ( pchHash
+ && isSpanOfBlanks(pchLine, pchHash - pchLine))
+ {
+ const char *pchFilename;
+ size_t cchFilename;
+ SCMINCLUDEDIR enmIncDir = ScmMaybeParseCIncludeLine(pState, pchLine, cchLine, &pchFilename, &cchFilename);
+ if ( enmIncDir == kScmIncludeDir_Bracketed
+ || enmIncDir == kScmIncludeDir_Quoted)
+ {
+ unsigned i = RT_ELEMENTS(s_aHeaders);
+ while (i-- > 0)
+ if ( s_aHeaders[i].cchHeader == cchFilename
+ && RTStrNICmpAscii(pchFilename, s_aHeaders[i].pszHeader, cchFilename) == 0)
+ {
+ if (iIncludeLevel < s_aHeaders[i].iLevel)
+ iIncludeLevel = s_aHeaders[i].iLevel;
+ break;
+ }
+
+ /* Special hack for error info. */
+ if (cchFilename == sizeof("errmsgdata.h") - 1 && memcmp(pchFilename, RT_STR_TUPLE("errmsgdata.h")) == 0)
+ iUsageLevel = 4;
+
+ /* Special hack for code templates. */
+ if ( cchFilename >= sizeof(".cpp.h")
+ && memcmp(&pchFilename[cchFilename - sizeof(".cpp.h") + 1], RT_STR_TUPLE(".cpp.h")) == 0)
+ iUsageLevel = 4;
+ continue;
+ }
+ }
+ /*
+ * Look for VERR_, VWRN_, VINF_ prefixed identifiers in the current line.
+ */
+ const char *pchHit = (const char *)memchr(pchLine, 'V', cchLine);
+ if (pchHit)
+ {
+ const char *pchLeft = pchLine;
+ size_t cchLeft = cchLine;
+ do
+ {
+ size_t cchLeftHit = &pchLeft[cchLeft] - pchHit;
+ if (cchLeftHit < 6)
+ break;
+ if ( pchHit[4] == '_'
+ && ( pchHit == pchLine
+ || !ScmIsCIdentifierChar(pchHit[-1]))
+ && ( (pchHit[1] == 'E' && pchHit[2] == 'R' && pchHit[3] == 'R')
+ || (pchHit[1] == 'W' && pchHit[2] == 'R' && pchHit[3] == 'N')
+ || (pchHit[1] == 'I' && pchHit[2] == 'N' && pchHit[3] == 'F') ) )
+ {
+ size_t cchIdentifier = 5;
+ while (cchIdentifier < cchLeftHit && ScmIsCIdentifierChar(pchHit[cchIdentifier]))
+ cchIdentifier++;
+ ScmVerbose(pState, 4, "--- status code at %u col %zu: %.*s\n",
+ iLine, pchHit - pchLine, cchIdentifier, pchHit);
+
+ if (iUsageLevel <= 1)
+ {
+ iUsageLevel = 3; /* Cannot distingish between iprt/err.h and VBox/err.h, so pick the latter for now. */
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aLevel1Statuses); i++)
+ if ( cchIdentifier == g_aLevel1Statuses[i].cch
+ && memcmp(pchHit, g_aLevel1Statuses[i].psz, cchIdentifier) == 0)
+ {
+ iUsageLevel = 1;
+ break;
+ }
+ }
+
+ pchLeft = pchHit + cchIdentifier;
+ cchLeft = cchLeftHit - cchIdentifier;
+ }
+ else
+ {
+ pchLeft = pchHit + 1;
+ cchLeft = cchLeftHit - 1;
+ }
+ pchHit = (const char *)memchr(pchLeft, 'V', cchLeft);
+ } while (pchHit != NULL);
+ }
+ }
+ ScmVerbose(pState, 3, "--- iIncludeLevel=%d iUsageLevel=%d\n", iIncludeLevel, iUsageLevel);
+
+ /*
+ * Second pass: Change err.h to errcore.h if we detected a need for change.
+ */
+ if ( iIncludeLevel <= iUsageLevel
+ || iIncludeLevel <= 1 /* we cannot safely eliminate errcore.h includes atm. */)
+ return kScmUnmodified;
+
+ unsigned cChanges = 0;
+ ScmStreamRewindForReading(pIn);
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ /*
+ * Look for #includes to modify.
+ */
+ if (cchLine >= 6)
+ {
+ const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
+ if ( pchHash
+ && isSpanOfBlanks(pchLine, pchHash - pchLine))
+ {
+ const char *pchFilename;
+ size_t cchFilename;
+ SCMINCLUDEDIR enmIncDir = ScmMaybeParseCIncludeLine(pState, pchLine, cchLine, &pchFilename, &cchFilename);
+ if ( enmIncDir == kScmIncludeDir_Bracketed
+ || enmIncDir == kScmIncludeDir_Quoted)
+ {
+ unsigned i = RT_ELEMENTS(s_aHeaders);
+ while (i-- > 0)
+ if ( s_aHeaders[i].cchHeader == cchFilename
+ && RTStrNICmpAscii(pchFilename, s_aHeaders[i].pszHeader, cchFilename) == 0)
+ {
+ ScmStreamWrite(pOut, pchLine, pchFilename - pchLine - 1);
+ ScmStreamWrite(pOut, RT_STR_TUPLE("<iprt/errcore.h>"));
+ size_t cchTrailing = &pchLine[cchLine] - &pchFilename[cchFilename + 1];
+ if (cchTrailing > 0)
+ ScmStreamWrite(pOut, &pchFilename[cchFilename + 1], cchTrailing);
+ ScmStreamPutEol(pOut, enmEol);
+ cChanges++;
+ pchLine = NULL;
+ break;
+ }
+ if (!pchLine)
+ continue;
+ }
+ }
+ }
+
+ int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ if (RT_FAILURE(rc))
+ return kScmUnmodified;
+ }
+ ScmVerbose(pState, 2, " * Converted %zu err.h/errcore.h include statements.\n", cChanges);
+ return kScmModified;
+}
+
+typedef struct
+{
+ const char *pch;
+ uint8_t cch;
+ uint8_t cchSpaces; /**< Number of expected spaces before the word. */
+ bool fSpacesBefore : 1; /**< Whether there may be spaces or tabs before the word. */
+ bool fIdentifier : 1; /**< Whether we're to expect a C/C++ identifier rather than pch/cch. */
+} SCMMATCHWORD;
+
+
+int ScmMatchWords(const char *pchLine, size_t cchLine, SCMMATCHWORD const *paWords, size_t cWords,
+ size_t *poffNext, PRTSTRTUPLE paIdentifiers, PRTERRINFO pErrInfo)
+{
+ int rc = VINF_SUCCESS;
+
+ size_t offLine = 0;
+ for (size_t i = 0; i < cWords; i++)
+ {
+ SCMMATCHWORD const *pWord = &paWords[i];
+
+ /*
+ * Deal with spaces preceeding the word first:
+ */
+ if (pWord->fSpacesBefore)
+ {
+ size_t cchSpaces = 0;
+ size_t cchTabs = 0;
+ while (offLine < cchLine)
+ {
+ const char ch = pchLine[offLine];
+ if (ch == ' ')
+ cchSpaces++;
+ else if (ch == '\t')
+ cchTabs++;
+ else
+ break;
+ offLine++;
+ }
+
+ if (cchSpaces == pWord->cchSpaces && cchTabs == 0)
+ { /* likely */ }
+ else if (cchSpaces == 0 && cchTabs == 0)
+ return RTErrInfoSetF(pErrInfo, VERR_PARSE_ERROR, "expected space at offset %u", offLine);
+ else
+ rc = VWRN_TRAILING_SPACES;
+ }
+ else
+ Assert(pWord->cchSpaces == 0);
+
+ /*
+ * C/C++ identifier?
+ */
+ if (pWord->fIdentifier)
+ {
+ if (offLine >= cchLine)
+ return RTErrInfoSetF(pErrInfo, VERR_END_OF_STRING,
+ "expected '%.*s' (C/C++ identifier) at offset %u, not end of string",
+ pWord->cch, pWord->pch, offLine);
+ if (!ScmIsCIdentifierLeadChar(pchLine[offLine]))
+ return RTErrInfoSetF(pErrInfo, VERR_MISMATCH, "expected '%.*s' (C/C++ identifier) at offset %u",
+ pWord->cch, pWord->pch, offLine);
+ size_t const offStart = offLine++;
+ while (offLine < cchLine && ScmIsCIdentifierChar(pchLine[offLine]))
+ offLine++;
+ if (paIdentifiers)
+ {
+ paIdentifiers->cch = offLine - offStart;
+ paIdentifiers->psz = &pchLine[offStart];
+ paIdentifiers++;
+ }
+ }
+ /*
+ * Match the exact word.
+ */
+ else if ( pWord->cch == 0
+ || ( pWord->cch <= cchLine - offLine
+ && !memcmp(pWord->pch, &pchLine[offLine], pWord->cch)))
+ offLine += pWord->cch;
+ else
+ return RTErrInfoSetF(pErrInfo, VERR_MISMATCH, "expected '%.*s' at offset %u", pWord->cch, pWord->pch, offLine);
+ }
+
+ /*
+ * Check for trailing characters/whatnot.
+ */
+ if (poffNext)
+ *poffNext = offLine;
+ else if (offLine != cchLine)
+ rc = RTErrInfoSetF(pErrInfo, VERR_TRAILING_CHARS, "unexpected trailing characters at offset %u", offLine);
+ return rc;
+}
+
+
+/**
+ * Fix header file include guards and \#pragma once.
+ *
+ * @returns Modification state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+SCMREWRITERRES rewrite_FixHeaderGuards(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ if (!pSettings->fFixHeaderGuards)
+ return kScmUnmodified;
+
+ /* always skip .cpp.h files */
+ size_t cchFilename = strlen(pState->pszFilename);
+ if ( cchFilename > sizeof(".cpp.h")
+ && RTStrICmpAscii(&pState->pszFilename[cchFilename - sizeof(".cpp.h") + 1], ".cpp.h") == 0)
+ return kScmUnmodified;
+
+ RTERRINFOSTATIC ErrInfo;
+ char szNormalized[168];
+ size_t cchNormalized = 0;
+ int rc;
+ bool fRet = false;
+
+ /*
+ * Calculate the expected guard for this file, if so tasked.
+ * ASSUMES pState->pszFilename is absolute as is pSettings->pszGuardRelativeToDir.
+ */
+ szNormalized[0] = '\0';
+ if (pSettings->pszGuardRelativeToDir)
+ {
+ rc = RTStrCopy(szNormalized, sizeof(szNormalized), pSettings->pszGuardPrefix);
+ if (RT_FAILURE(rc))
+ return ScmError(pState, rc, "Guard prefix too long (or something): %s\n", pSettings->pszGuardPrefix);
+ cchNormalized = strlen(szNormalized);
+ if (strcmp(pSettings->pszGuardRelativeToDir, "{dir}") == 0)
+ rc = RTStrCopy(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized,
+ RTPathFilename(pState->pszFilename));
+ else if (strcmp(pSettings->pszGuardRelativeToDir, "{parent}") == 0)
+ {
+ const char *pszSrc = RTPathFilename(pState->pszFilename);
+ if (!pszSrc || (uintptr_t)&pszSrc[-2] < (uintptr_t)pState->pszFilename || !RTPATH_IS_SLASH(pszSrc[-1]))
+ return ScmError(pState, VERR_INTERNAL_ERROR, "Error calculating {parent} header guard!\n");
+ pszSrc -= 2;
+ while ( (uintptr_t)pszSrc > (uintptr_t)pState->pszFilename
+ && !RTPATH_IS_SLASH(pszSrc[-1])
+ && !RTPATH_IS_VOLSEP(pszSrc[-1]))
+ pszSrc--;
+ rc = RTStrCopy(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized, pszSrc);
+ }
+ else
+ rc = RTPathCalcRelative(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized,
+ pSettings->pszGuardRelativeToDir, false /*fFromFile*/, pState->pszFilename);
+ if (RT_FAILURE(rc))
+ return ScmError(pState, rc, "Error calculating guard prefix (RTPathCalcRelative): %Rrc\n", rc);
+ char ch;
+ while ((ch = szNormalized[cchNormalized]) != '\0')
+ {
+ if (!ScmIsCIdentifierChar(ch))
+ szNormalized[cchNormalized] = '_';
+ cchNormalized++;
+ }
+ }
+
+ /*
+ * First part looks for the #ifndef xxxx paired with #define xxxx.
+ *
+ * We blindly assume the first preprocessor directive in the file is the guard
+ * and will be upset if this isn't the case.
+ */
+ RTSTRTUPLE Guard = { NULL, 0 };
+ uint32_t cBlankLines = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ for (;;)
+ {
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ if (pchLine == NULL)
+ return ScmError(pState, VERR_PARSE_ERROR, "Did not find any include guards!\n");
+ if (cchLine >= 2)
+ {
+ const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
+ if ( pchHash
+ && isSpanOfBlanks(pchLine, pchHash - pchLine))
+ {
+ /* #ifndef xxxx */
+ static const SCMMATCHWORD s_aIfndefGuard[] =
+ {
+ { RT_STR_TUPLE("#"), 0, true, false },
+ { RT_STR_TUPLE("ifndef"), 0, true, false },
+ { RT_STR_TUPLE("IDENTIFIER"), 1, true, true },
+ { RT_STR_TUPLE(""), 0, true, false },
+ };
+ rc = ScmMatchWords(pchLine, cchLine, s_aIfndefGuard, RT_ELEMENTS(s_aIfndefGuard),
+ NULL /*poffNext*/, &Guard, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return ScmError(pState, rc, "%u: Expected first preprocessor directive to be '#ifndef xxxx'. %s (%.*s)\n",
+ ScmStreamTellLine(pIn) - 1, ErrInfo.Core.pszMsg, cchLine, pchLine);
+ fRet |= rc != VINF_SUCCESS;
+ ScmVerbose(pState, 3, "line %u in %s: #ifndef %.*s\n",
+ ScmStreamTellLine(pIn) - 1, pState->pszFilename, Guard.cch, Guard.psz);
+
+ /* #define xxxx */
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ if (!pchLine)
+ return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef %.*s'\n",
+ ScmStreamTellLine(pIn) - 1, Guard.cch, Guard.psz);
+ const SCMMATCHWORD aDefineGuard[] =
+ {
+ { RT_STR_TUPLE("#"), 0, true, false },
+ { RT_STR_TUPLE("define"), 0, true, false },
+ { Guard.psz, (uint8_t)Guard.cch, 1, true, false },
+ { RT_STR_TUPLE(""), 0, true, false },
+ };
+ rc = ScmMatchWords(pchLine, cchLine, aDefineGuard, RT_ELEMENTS(aDefineGuard),
+ NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return ScmError(pState, rc, "%u: Expected '#define %.*s' to follow '#ifndef %.*s'. %s (%.*s)\n",
+ ScmStreamTellLine(pIn) - 1, Guard.cch, Guard.psz, Guard.cch, Guard.psz,
+ ErrInfo.Core.pszMsg, cchLine, pchLine);
+ fRet |= rc != VINF_SUCCESS;
+
+ if (Guard.cch >= sizeof(szNormalized))
+ return ScmError(pState, VERR_BUFFER_OVERFLOW, "%u: Guard macro too long! %.*s\n",
+ ScmStreamTellLine(pIn) - 2, Guard.cch, Guard.psz);
+
+ if (szNormalized[0] != '\0')
+ {
+ if ( Guard.cch != cchNormalized
+ || memcmp(Guard.psz, szNormalized, cchNormalized) != 0)
+ {
+ ScmVerbose(pState, 2, "guard changed from %.*s to %s\n", Guard.cch, Guard.psz, szNormalized);
+ ScmVerbose(pState, 2, "grep -rw %.*s ${WCROOT} | grep -Fv %s\n",
+ Guard.cch, Guard.psz, pState->pszFilename);
+ fRet = true;
+ }
+ Guard.psz = szNormalized;
+ Guard.cch = cchNormalized;
+ }
+
+ /*
+ * Write guard, making sure we've got a single blank line preceeding it.
+ */
+ ScmStreamPutEol(pOut, enmEol);
+ ScmStreamWrite(pOut, RT_STR_TUPLE("#ifndef "));
+ ScmStreamWrite(pOut, Guard.psz, Guard.cch);
+ ScmStreamPutEol(pOut, enmEol);
+ ScmStreamWrite(pOut, RT_STR_TUPLE("#define "));
+ ScmStreamWrite(pOut, Guard.psz, Guard.cch);
+ rc = ScmStreamPutEol(pOut, enmEol);
+ if (RT_FAILURE(rc))
+ return kScmUnmodified;
+ break;
+ }
+ }
+
+ if (!isBlankLine(pchLine, cchLine))
+ {
+ while (cBlankLines-- > 0)
+ ScmStreamPutEol(pOut, enmEol);
+ cBlankLines = 0;
+ rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ if (RT_FAILURE(rc))
+ return kScmUnmodified;
+ }
+ else
+ cBlankLines++;
+ }
+
+ /*
+ * Look for pragma once wrapped in #ifndef RT_WITHOUT_PRAGMA_ONCE.
+ */
+ size_t const iPragmaOnce = ScmStreamTellLine(pIn);
+ static const SCMMATCHWORD s_aIfndefRtWithoutPragmaOnce[] =
+ {
+ { RT_STR_TUPLE("#"), 0, true, false },
+ { RT_STR_TUPLE("ifndef"), 0, true, false },
+ { RT_STR_TUPLE("RT_WITHOUT_PRAGMA_ONCE"), 1, true, false },
+ { RT_STR_TUPLE(""), 0, true, false },
+ };
+ static const SCMMATCHWORD s_aPragmaOnce[] =
+ {
+ { RT_STR_TUPLE("#"), 0, true, false },
+ { RT_STR_TUPLE("pragma"), 1, true, false },
+ { RT_STR_TUPLE("once"), 1, true, false},
+ { RT_STR_TUPLE(""), 0, true, false },
+ };
+ static const SCMMATCHWORD s_aEndif[] =
+ {
+ { RT_STR_TUPLE("#"), 0, true, false },
+ { RT_STR_TUPLE("endif"), 0, true, false },
+ { RT_STR_TUPLE(""), 0, true, false },
+ };
+
+ /* #ifndef RT_WITHOUT_PRAGMA_ONCE */
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ if (!pchLine)
+ return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after header guard!\n", iPragmaOnce + 1);
+ size_t offNext;
+ rc = ScmMatchWords(pchLine, cchLine, s_aIfndefRtWithoutPragmaOnce, RT_ELEMENTS(s_aIfndefRtWithoutPragmaOnce),
+ &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ fRet |= rc != VINF_SUCCESS;
+ if (offNext != cchLine)
+ return ScmError(pState, VERR_PARSE_ERROR, "%u: Characters trailing '#ifndef RT_WITHOUT_PRAGMA_ONCE' (%.*s)\n",
+ iPragmaOnce + 1, cchLine, pchLine);
+
+ /* # pragma once */
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ if (!pchLine)
+ return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef RT_WITHOUT_PRAGMA_ONCE'\n",
+ iPragmaOnce + 2);
+ rc = ScmMatchWords(pchLine, cchLine, s_aPragmaOnce, RT_ELEMENTS(s_aPragmaOnce),
+ NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ fRet |= rc != VINF_SUCCESS;
+ else
+ return ScmError(pState, rc, "%u: Expected '# pragma once' to follow '#ifndef RT_WITHOUT_PRAGMA_ONCE'! %s (%.*s)\n",
+ iPragmaOnce + 2, ErrInfo.Core.pszMsg, cchLine, pchLine);
+
+ /* #endif */
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ if (!pchLine)
+ return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef RT_WITHOUT_PRAGMA_ONCE' and '#pragma once'\n",
+ iPragmaOnce + 3);
+ rc = ScmMatchWords(pchLine, cchLine, s_aEndif, RT_ELEMENTS(s_aEndif),
+ NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ fRet |= rc != VINF_SUCCESS;
+ else
+ return ScmError(pState, rc,
+ "%u: Expected '#endif' to follow '#ifndef RT_WITHOUT_PRAGMA_ONCE' and '# pragma once'! %s (%.*s)\n",
+ iPragmaOnce + 3, ErrInfo.Core.pszMsg, cchLine, pchLine);
+ ScmVerbose(pState, 3, "Found pragma once\n");
+ fRet |= !pSettings->fPragmaOnce;
+ }
+ else
+ {
+ rc = ScmStreamSeekByLine(pIn, iPragmaOnce);
+ if (RT_FAILURE(rc))
+ return ScmError(pState, rc, "seek error\n");
+ fRet |= pSettings->fPragmaOnce;
+ ScmVerbose(pState, pSettings->fPragmaOnce ? 2 : 3, "Missing #pragma once\n");
+ }
+
+ /*
+ * Write the pragma once stuff.
+ */
+ if (pSettings->fPragmaOnce)
+ {
+ ScmStreamPutLine(pOut, RT_STR_TUPLE("#ifndef RT_WITHOUT_PRAGMA_ONCE"), enmEol);
+ ScmStreamPutLine(pOut, RT_STR_TUPLE("# pragma once"), enmEol);
+ rc = ScmStreamPutLine(pOut, RT_STR_TUPLE("#endif"), enmEol);
+ if (RT_FAILURE(rc))
+ return kScmUnmodified;
+ }
+
+ /*
+ * Copy the rest of the file and remove pragma once statements, while
+ * looking for the last #endif in the file.
+ */
+ size_t iEndIfIn = 0;
+ size_t iEndIfOut = 0;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ if (cchLine > 2)
+ {
+ const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
+ if ( pchHash
+ && isSpanOfBlanks(pchLine, pchHash - pchLine))
+ {
+ size_t off = pchHash - pchLine + 1;
+ while (off < cchLine && RT_C_IS_BLANK(pchLine[off]))
+ off++;
+ /* #pragma once */
+ if ( off + sizeof("pragma") - 1 <= cchLine
+ && !memcmp(&pchLine[off], RT_STR_TUPLE("pragma")))
+ {
+ rc = ScmMatchWords(pchLine, cchLine, s_aPragmaOnce, RT_ELEMENTS(s_aPragmaOnce),
+ &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ fRet = true;
+ continue;
+ }
+ }
+ /* #endif */
+ else if ( off + sizeof("endif") - 1 <= cchLine
+ && !memcmp(&pchLine[off], RT_STR_TUPLE("endif")))
+ {
+ iEndIfIn = ScmStreamTellLine(pIn) - 1;
+ iEndIfOut = ScmStreamTellLine(pOut);
+ }
+ }
+ }
+
+ rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ if (RT_FAILURE(rc))
+ return kScmUnmodified;
+ }
+
+ /*
+ * Check out the last endif, making sure it's well formed and make sure it has the
+ * right kind of comment following it.
+ */
+ if (pSettings->fFixHeaderGuardEndif)
+ {
+ if (iEndIfOut == 0)
+ return ScmError(pState, VERR_PARSE_ERROR, "Expected '#endif' at the end of the file...\n");
+ rc = ScmStreamSeekByLine(pIn, iEndIfIn);
+ if (RT_FAILURE(rc))
+ return kScmUnmodified;
+ rc = ScmStreamSeekByLine(pOut, iEndIfOut);
+ if (RT_FAILURE(rc))
+ return kScmUnmodified;
+
+ pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
+ if (!pchLine)
+ return ScmError(pState, VERR_INTERNAL_ERROR, "ScmStreamGetLine failed re-reading #endif!\n");
+
+ char szTmp[64 + sizeof(szNormalized)];
+ size_t cchTmp;
+ if (pSettings->fEndifGuardComment)
+ cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "#endif /* !%.*s */", Guard.cch, Guard.psz);
+ else
+ cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "#endif"); /* lazy bird */
+ fRet |= cchTmp != cchLine || memcmp(szTmp, pchLine, cchTmp) != 0;
+ rc = ScmStreamPutLine(pOut, szTmp, cchTmp, enmEol);
+ if (RT_FAILURE(rc))
+ return kScmUnmodified;
+
+ /* Copy out the remaining lines (assumes no #pragma once here). */
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
+ if (RT_FAILURE(rc))
+ return kScmUnmodified;
+ }
+ }
+
+ return fRet ? kScmModified : kScmUnmodified;
+}
+
+
+/**
+ * Checks for PAGE_SIZE, PAGE_SHIFT and PAGE_OFFSET_MASK w/o a GUEST_ or HOST_
+ * prefix as well as banning PAGE_BASE_HC_MASK, PAGE_BASE_GC_MASK and
+ * PAGE_BASE_MASK.
+ *
+ * @returns kScmUnmodified - requires manual fix.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+SCMREWRITERRES rewrite_PageChecks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ RT_NOREF(pOut);
+ if (!pSettings->fOnlyGuestHostPage && !pSettings->fNoASMMemPageUse)
+ return kScmUnmodified;
+
+ static RTSTRTUPLE const g_aWords[] =
+ {
+ { RT_STR_TUPLE("PAGE_SIZE") },
+ { RT_STR_TUPLE("PAGE_SHIFT") },
+ { RT_STR_TUPLE("PAGE_OFFSET_MASK") },
+ { RT_STR_TUPLE("PAGE_BASE_MASK") },
+ { RT_STR_TUPLE("PAGE_BASE_GC_MASK") },
+ { RT_STR_TUPLE("PAGE_BASE_HC_MASK") },
+ { RT_STR_TUPLE("PAGE_ADDRESS") },
+ { RT_STR_TUPLE("PHYS_PAGE_ADDRESS") },
+ { RT_STR_TUPLE("ASMMemIsZeroPage") },
+ { RT_STR_TUPLE("ASMMemZeroPage") },
+ };
+ size_t const iFirstWord = pSettings->fOnlyGuestHostPage ? 0 : 7;
+ size_t const iEndWords = pSettings->fNoASMMemPageUse ? 9 : 7;
+
+ uint32_t iLine = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ iLine++;
+ for (size_t i = iFirstWord; i < iEndWords; i++)
+ {
+ size_t const cchWord = g_aWords[i].cch;
+ if (cchLine >= cchWord)
+ {
+ const char * const pszWord = g_aWords[i].psz;
+ const char *pchHit = (const char *)memchr(pchLine, *pszWord, cchLine);
+ while (pchHit)
+ {
+ size_t cchLeft = (uintptr_t)&pchLine[cchLine] - (uintptr_t)pchHit;
+ if ( cchLeft >= cchWord
+ && memcmp(pchHit, pszWord, cchWord) == 0
+ && ( pchHit == pchLine
+ || !ScmIsCIdentifierChar(pchHit[-1]))
+ && ( cchLeft == cchWord
+ || !ScmIsCIdentifierChar(pchHit[cchWord])) )
+ {
+ if (i < 3)
+ ScmFixManually(pState, "%u:%zu: %s is not allow! Use GUEST_%s or HOST_%s instead.\n",
+ iLine, pchHit - pchLine + 1, pszWord, pszWord, pszWord);
+ else if (i < 7)
+ ScmFixManually(pState, "%u:%zu: %s is not allow! Rewrite using GUEST/HOST_PAGE_OFFSET_MASK.\n",
+ iLine, pchHit - pchLine + 1, pszWord);
+ else
+ ScmFixManually(pState, "%u:%zu: %s is not allow! Use %s with correct page size instead.\n",
+ iLine, pchHit - pchLine + 1, pszWord, i == 3 ? "ASMMemIsZero" : "RT_BZERO");
+ }
+
+ /* next */
+ cchLeft -= 1;
+ if (cchLeft < cchWord)
+ break;
+ pchHit = (const char *)memchr(pchHit + 1, *pszWord, cchLeft);
+ }
+ }
+ }
+ }
+
+ return kScmUnmodified;
+}
+
+
+/**
+ * Checks for usage of rc in code instead of vrc for IPRT status codes (int) and hrc for COM
+ * status codes (HRESULT).
+ *
+ * @returns kScmUnmodified - requires manual fix.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ *
+ * @note Used in Main to avoid ambiguity when just using rc.
+ */
+SCMREWRITERRES rewrite_ForceHrcVrcInsteadOfRc(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ RT_NOREF(pOut);
+ if (!pSettings->fOnlyHrcVrcInsteadOfRc)
+ return kScmUnmodified;
+
+ static const SCMMATCHWORD s_aHresultVrc[] =
+ {
+ { RT_STR_TUPLE("HRESULT"), 0, true, false },
+ { RT_STR_TUPLE("vrc"), 1, true, false }
+ };
+
+ static const SCMMATCHWORD s_aIntHrc[] =
+ {
+ { RT_STR_TUPLE("int"), 0, true, false },
+ { RT_STR_TUPLE("hrc"), 1, true, false }
+ };
+
+ uint32_t iLine = 0;
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ RTERRINFOSTATIC ErrInfo;
+ while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
+ {
+ iLine++;
+
+ /* Look for forbidden declarations first. */
+ size_t offNext = 0;
+ int rc = ScmMatchWords(pchLine, cchLine, s_aHresultVrc, RT_ELEMENTS(s_aHresultVrc),
+ &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ ScmFixManually(pState, "%u:%zu: 'HRESULT vrc' is not allowed! Use 'HRESULT hrc' instead.\n",
+ iLine, offNext);
+ continue;
+ }
+
+ rc = ScmMatchWords(pchLine, cchLine, s_aIntHrc, RT_ELEMENTS(s_aIntHrc),
+ &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ ScmFixManually(pState, "%u:%zu: 'int hrc' is not allowed! Use 'int vrc' instead.\n",
+ iLine, offNext);
+ continue;
+ }
+
+#if 0 /* This is too broad and triggers on things we don't want to trigger on (like autoCaller.rc()). */
+ const RTSTRTUPLE RcTuple = { RT_STR_TUPLE("rc") };
+ size_t const cchWord = RcTuple.cch;
+ if (cchLine >= cchWord)
+ {
+ const char *pchHit = (const char *)memchr(pchLine, *RcTuple.psz, cchLine);
+ while (pchHit)
+ {
+ size_t cchLeft = (uintptr_t)&pchLine[cchLine] - (uintptr_t)pchHit;
+ if ( cchLeft >= cchWord
+ && memcmp(pchHit, RcTuple.psz, cchWord) == 0
+ && ( pchHit == pchLine
+ || !ScmIsCIdentifierChar(pchHit[-1]))
+ && ( cchLeft == cchWord
+ || !ScmIsCIdentifierChar(pchHit[cchWord])) )
+ ScmFixManually(pState, "%u:%zu: %s is not allowed! Use hrc or vrc instead.\n",
+ iLine, pchHit - pchLine + 1, RcTuple.psz);
+
+ /* next */
+ cchLeft -= 1;
+ if (cchLeft < cchWord)
+ break;
+ pchHit = (const char *)memchr(pchHit + 1, *RcTuple.psz, cchLeft);
+ }
+ }
+#else
+ /* Trigger on declarations of 'HRESULT rc' and 'int rc'. */
+ static const SCMMATCHWORD s_aHresultRc[] =
+ {
+ { RT_STR_TUPLE("HRESULT"), 0, true, false },
+ { RT_STR_TUPLE("rc"), 1, true, false }
+ };
+
+ static const SCMMATCHWORD s_aIntRc[] =
+ {
+ { RT_STR_TUPLE("int"), 0, true, false },
+ { RT_STR_TUPLE("rc"), 1, true, false }
+ };
+
+ rc = ScmMatchWords(pchLine, cchLine, s_aHresultRc, RT_ELEMENTS(s_aHresultRc),
+ &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ ScmFixManually(pState, "%u:%zu: 'HRESULT rc' is not allowed! Use 'HRESULT hrc' instead.\n",
+ iLine, offNext);
+ continue;
+ }
+
+ rc = ScmMatchWords(pchLine, cchLine, s_aIntRc, RT_ELEMENTS(s_aIntRc),
+ &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ ScmFixManually(pState, "%u:%zu: 'int rc' is not allowed! Use 'int vrc' instead.\n",
+ iLine, offNext);
+ continue;
+ }
+#endif
+ }
+
+ return kScmUnmodified;
+}
+
+
+/**
+ * Rewrite a C/C++ source or header file.
+ *
+ * @returns Modification state.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ *
+ * @todo
+ *
+ * Ideas for C/C++:
+ * - space after if, while, for, switch
+ * - spaces in for (i=0;i<x;i++)
+ * - complex conditional, bird style.
+ * - remove unnecessary parentheses.
+ * - sort defined RT_OS_*|| and RT_ARCH
+ * - sizeof without parenthesis.
+ * - defined without parenthesis.
+ * - trailing spaces.
+ * - parameter indentation.
+ * - space after comma.
+ * - while (x--); -> multi line + comment.
+ * - else statement;
+ * - space between function and left parenthesis.
+ * - TODO, XXX, @todo cleanup.
+ * - Space before/after '*'.
+ * - ensure new line at end of file.
+ * - Indentation of precompiler statements (#ifdef, #defines).
+ * - space between functions.
+ * - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
+ */
+SCMREWRITERRES rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+
+ RT_NOREF4(pState, pIn, pOut, pSettings);
+ return kScmUnmodified;
+}
+
diff --git a/src/bldprogs/scmstream.cpp b/src/bldprogs/scmstream.cpp
new file mode 100644
index 00000000..e14cdf30
--- /dev/null
+++ b/src/bldprogs/scmstream.cpp
@@ -0,0 +1,1478 @@
+/* $Id: scmstream.cpp $ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager Stream Code.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/handle.h>
+#include <iprt/mem.h>
+#include <iprt/pipe.h>
+#include <iprt/string.h>
+
+#include "scmstream.h"
+
+
+/**
+ * Initializes the stream structure.
+ *
+ * @param pStream The stream structure.
+ * @param fWriteOrRead The value of the fWriteOrRead stream member.
+ */
+static void scmStreamInitInternal(PSCMSTREAM pStream, bool fWriteOrRead)
+{
+ pStream->pch = NULL;
+ pStream->off = 0;
+ pStream->cb = 0;
+ pStream->cbAllocated = 0;
+
+ pStream->paLines = NULL;
+ pStream->iLine = 0;
+ pStream->cLines = 0;
+ pStream->cLinesAllocated = 0;
+
+ pStream->fWriteOrRead = fWriteOrRead;
+ pStream->fFileMemory = false;
+ pStream->fFullyLineated = false;
+
+ pStream->rc = VINF_SUCCESS;
+}
+
+/**
+ * Initialize an input stream.
+ *
+ * @returns IPRT status code.
+ * @param pStream The stream to initialize.
+ * @param pszFilename The file to take the stream content from.
+ */
+int ScmStreamInitForReading(PSCMSTREAM pStream, const char *pszFilename)
+{
+ scmStreamInitInternal(pStream, false /*fWriteOrRead*/);
+
+ void *pvFile;
+ size_t cbFile;
+ int rc = pStream->rc = RTFileReadAll(pszFilename, &pvFile, &cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ pStream->pch = (char *)pvFile;
+ pStream->cb = cbFile;
+ pStream->cbAllocated = cbFile;
+ pStream->fFileMemory = true;
+ }
+ return rc;
+}
+
+/**
+ * Initialize an output stream.
+ *
+ * @returns IPRT status code
+ * @param pStream The stream to initialize.
+ * @param pRelatedStream Pointer to a related stream. NULL is fine.
+ */
+int ScmStreamInitForWriting(PSCMSTREAM pStream, PCSCMSTREAM pRelatedStream)
+{
+ scmStreamInitInternal(pStream, true /*fWriteOrRead*/);
+
+ /* allocate stuff */
+ size_t cbEstimate = !pRelatedStream ? _64K
+ : pRelatedStream->cb > 0 ? pRelatedStream->cb + pRelatedStream->cb / 10 : 64;
+ cbEstimate = RT_ALIGN(cbEstimate, _4K);
+ pStream->pch = (char *)RTMemAlloc(cbEstimate);
+ if (pStream->pch)
+ {
+ size_t cLinesEstimate = pRelatedStream && pRelatedStream->fFullyLineated
+ ? pRelatedStream->cLines + pRelatedStream->cLines / 10
+ : cbEstimate / 24;
+ cLinesEstimate = RT_ALIGN(cLinesEstimate, 512);
+ if (cLinesEstimate == 0)
+ cLinesEstimate = 16;
+ pStream->paLines = (PSCMSTREAMLINE)RTMemAlloc(cLinesEstimate * sizeof(SCMSTREAMLINE));
+ if (pStream->paLines)
+ {
+ pStream->paLines[0].off = 0;
+ pStream->paLines[0].cch = 0;
+ pStream->paLines[0].enmEol = SCMEOL_NONE;
+ pStream->cbAllocated = cbEstimate;
+ pStream->cLinesAllocated = cLinesEstimate;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pStream->pch);
+ pStream->pch = NULL;
+ }
+ return pStream->rc = VERR_NO_MEMORY;
+}
+
+/**
+ * Frees the resources associated with the stream.
+ *
+ * Nothing is happens to whatever the stream was initialized from or dumped to.
+ *
+ * @param pStream The stream to delete.
+ */
+void ScmStreamDelete(PSCMSTREAM pStream)
+{
+ if (pStream->pch)
+ {
+ if (pStream->fFileMemory)
+ RTFileReadAllFree(pStream->pch, pStream->cbAllocated);
+ else
+ RTMemFree(pStream->pch);
+ pStream->pch = NULL;
+ }
+ pStream->cbAllocated = 0;
+
+ if (pStream->paLines)
+ {
+ RTMemFree(pStream->paLines);
+ pStream->paLines = NULL;
+ }
+ pStream->cLinesAllocated = 0;
+}
+
+/**
+ * Get the stream status code.
+ *
+ * @returns IPRT status code.
+ * @param pStream The stream.
+ */
+int ScmStreamGetStatus(PCSCMSTREAM pStream)
+{
+ return pStream->rc;
+}
+
+/**
+ * Grows the buffer of a write stream.
+ *
+ * @returns IPRT status code.
+ * @param pStream The stream. Must be in write mode.
+ * @param cbAppending The minimum number of bytes to grow the buffer
+ * with.
+ */
+static int scmStreamGrowBuffer(PSCMSTREAM pStream, size_t cbAppending)
+{
+ size_t cbAllocated = pStream->cbAllocated;
+ cbAllocated += RT_MAX(0x1000 + cbAppending, cbAllocated);
+ cbAllocated = RT_ALIGN(cbAllocated, 0x1000);
+ void *pvNew;
+ if (!pStream->fFileMemory)
+ {
+ pvNew = RTMemRealloc(pStream->pch, cbAllocated);
+ if (!pvNew)
+ return pStream->rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ pvNew = RTMemDupEx(pStream->pch, pStream->off, cbAllocated - pStream->off);
+ if (!pvNew)
+ return pStream->rc = VERR_NO_MEMORY;
+ RTFileReadAllFree(pStream->pch, pStream->cbAllocated);
+ pStream->fFileMemory = false;
+ }
+ pStream->pch = (char *)pvNew;
+ pStream->cbAllocated = cbAllocated;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Grows the line array of a stream.
+ *
+ * @returns IPRT status code.
+ * @param pStream The stream.
+ * @param iMinLine Minimum line number.
+ */
+static int scmStreamGrowLines(PSCMSTREAM pStream, size_t iMinLine)
+{
+ size_t cLinesAllocated = pStream->cLinesAllocated;
+ cLinesAllocated += RT_MAX(512 + iMinLine, cLinesAllocated);
+ cLinesAllocated = RT_ALIGN(cLinesAllocated, 512);
+ void *pvNew = RTMemRealloc(pStream->paLines, cLinesAllocated * sizeof(SCMSTREAMLINE));
+ if (!pvNew)
+ return pStream->rc = VERR_NO_MEMORY;
+
+ pStream->paLines = (PSCMSTREAMLINE)pvNew;
+ pStream->cLinesAllocated = cLinesAllocated;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Rewinds the stream and sets the mode to read.
+ *
+ * @param pStream The stream.
+ */
+void ScmStreamRewindForReading(PSCMSTREAM pStream)
+{
+ pStream->off = 0;
+ pStream->iLine = 0;
+ pStream->fWriteOrRead = false;
+ pStream->rc = VINF_SUCCESS;
+}
+
+/**
+ * Rewinds the stream and sets the mode to write.
+ *
+ * @param pStream The stream.
+ */
+void ScmStreamRewindForWriting(PSCMSTREAM pStream)
+{
+ pStream->off = 0;
+ pStream->iLine = 0;
+ pStream->cLines = 0;
+ pStream->fWriteOrRead = true;
+ pStream->fFullyLineated = true;
+ pStream->rc = VINF_SUCCESS;
+
+ /* Initialize the first line with a zero length so ScmStreamWrite won't misbehave. */
+ if (pStream->cLinesAllocated == 0)
+ scmStreamGrowLines(pStream, 1);
+ if (pStream->cLinesAllocated > 0)
+ {
+ pStream->paLines[0].off = 0;
+ pStream->paLines[0].cch = 0;
+ pStream->paLines[0].enmEol = SCMEOL_NONE;
+ }
+}
+
+/**
+ * Checks if it's a text stream.
+ *
+ * Not 100% proof.
+ *
+ * @returns true if it probably is a text file, false if not.
+ * @param pStream The stream. Write or read, doesn't matter.
+ */
+bool ScmStreamIsText(PSCMSTREAM pStream)
+{
+ if (RTStrEnd(pStream->pch, pStream->cb))
+ return false;
+ if (!pStream->cb)
+ return true;
+ return true;
+}
+
+/**
+ * Performs an integrity check of the stream.
+ *
+ * @returns IPRT status code.
+ * @param pStream The stream.
+ */
+int ScmStreamCheckItegrity(PSCMSTREAM pStream)
+{
+ /*
+ * Perform sanity checks.
+ */
+ size_t const cbFile = pStream->cb;
+ for (size_t iLine = 0; iLine < pStream->cLines; iLine++)
+ {
+ size_t offEol = pStream->paLines[iLine].off + pStream->paLines[iLine].cch;
+ AssertReturn(offEol + pStream->paLines[iLine].enmEol <= cbFile, VERR_INTERNAL_ERROR_2);
+ switch (pStream->paLines[iLine].enmEol)
+ {
+ case SCMEOL_LF:
+ AssertReturn(pStream->pch[offEol] == '\n', VERR_INTERNAL_ERROR_3);
+ break;
+ case SCMEOL_CRLF:
+ AssertReturn(pStream->pch[offEol] == '\r', VERR_INTERNAL_ERROR_3);
+ AssertReturn(pStream->pch[offEol + 1] == '\n', VERR_INTERNAL_ERROR_3);
+ break;
+ case SCMEOL_NONE:
+ AssertReturn(iLine + 1 >= pStream->cLines, VERR_INTERNAL_ERROR_4);
+ break;
+ default:
+ AssertReturn(iLine + 1 >= pStream->cLines, VERR_INTERNAL_ERROR_5);
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * Writes the stream to a file.
+ *
+ * @returns IPRT status code
+ * @param pStream The stream.
+ * @param pszFilenameFmt The filename format string.
+ * @param ... Format arguments.
+ */
+int ScmStreamWriteToFile(PSCMSTREAM pStream, const char *pszFilenameFmt, ...)
+{
+ int rc;
+
+#ifdef RT_STRICT
+ /*
+ * Check that what we're going to write makes sense first.
+ */
+ rc = ScmStreamCheckItegrity(pStream);
+ if (RT_FAILURE(rc))
+ return rc;
+#endif
+
+ /*
+ * Do the actual writing.
+ */
+ RTFILE hFile;
+ va_list va;
+ va_start(va, pszFilenameFmt);
+ rc = RTFileOpenV(&hFile, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_WRITE, pszFilenameFmt, va);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileWrite(hFile, pStream->pch, pStream->cb, NULL);
+ RTFileClose(hFile);
+ }
+ va_end(va);
+ return rc;
+}
+
+/**
+ * Writes the stream to standard output.
+ *
+ * @returns IPRT status code
+ * @param pStream The stream.
+ */
+int ScmStreamWriteToStdOut(PSCMSTREAM pStream)
+{
+ int rc;
+
+#ifdef RT_STRICT
+ /*
+ * Check that what we're going to write makes sense first.
+ */
+ rc = ScmStreamCheckItegrity(pStream);
+ if (RT_FAILURE(rc))
+ return rc;
+#endif
+
+ /*
+ * Do the actual writing.
+ */
+ RTHANDLE h;
+ rc = RTHandleGetStandard(RTHANDLESTD_OUTPUT, true /*fLeaveOpen*/, &h);
+ if (RT_SUCCESS(rc))
+ {
+ switch (h.enmType)
+ {
+ case RTHANDLETYPE_FILE:
+ rc = RTFileWrite(h.u.hFile, pStream->pch, pStream->cb, NULL);
+ /** @todo RTFileClose */
+ break;
+ case RTHANDLETYPE_PIPE:
+ rc = RTPipeWriteBlocking(h.u.hPipe, pStream->pch, pStream->cb, NULL);
+ RTPipeClose(h.u.hPipe);
+ break;
+ default:
+ rc = VERR_INVALID_HANDLE;
+ break;
+ }
+ }
+ return rc;
+}
+
+/**
+ * Worker for ScmStreamGetLine that builds the line number index while parsing
+ * the stream.
+ *
+ * @returns Same as SCMStreamGetLine.
+ * @param pStream The stream. Must be in read mode.
+ * @param pcchLine Where to return the line length.
+ * @param penmEol Where to return the kind of end of line marker.
+ */
+static const char *scmStreamGetLineInternal(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol)
+{
+ AssertReturn(!pStream->fWriteOrRead, NULL);
+ if (RT_FAILURE(pStream->rc))
+ return NULL;
+
+ size_t off = pStream->off;
+ size_t cb = pStream->cb;
+ if (RT_UNLIKELY(off >= cb))
+ {
+ pStream->fFullyLineated = true;
+ return NULL;
+ }
+
+ size_t iLine = pStream->iLine;
+ if (RT_UNLIKELY(iLine >= pStream->cLinesAllocated))
+ {
+ int rc = scmStreamGrowLines(pStream, iLine);
+ if (RT_FAILURE(rc))
+ return NULL;
+ }
+ pStream->paLines[iLine].off = off;
+
+ cb -= off;
+ const char *pchRet = &pStream->pch[off];
+ const char *pch = (const char *)memchr(pchRet, '\n', cb);
+ if (RT_LIKELY(pch))
+ {
+ cb = pch - pchRet;
+ pStream->off = off + cb + 1;
+ if ( cb < 1
+ || pch[-1] != '\r')
+ pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_LF;
+ else
+ {
+ pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_CRLF;
+ cb--;
+ }
+ }
+ else
+ {
+ pStream->off = off + cb;
+ pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_NONE;
+ }
+ *pcchLine = cb;
+ pStream->paLines[iLine].cch = cb;
+ pStream->cLines = pStream->iLine = ++iLine;
+
+ return pchRet;
+}
+
+/**
+ * Internal worker that delineates a stream.
+ *
+ * @returns IPRT status code.
+ * @param pStream The stream. Caller must check that it is in
+ * read mode.
+ */
+static int scmStreamLineate(PSCMSTREAM pStream)
+{
+ /* Save the stream position. */
+ size_t const offSaved = pStream->off;
+ size_t const iLineSaved = pStream->iLine;
+
+ /* Get each line. */
+ size_t cchLine;
+ SCMEOL enmEol;
+ while (scmStreamGetLineInternal(pStream, &cchLine, &enmEol))
+ /* nothing */;
+ Assert(RT_FAILURE(pStream->rc) || pStream->fFullyLineated);
+
+ /* Restore the position */
+ pStream->off = offSaved;
+ pStream->iLine = iLineSaved;
+
+ return pStream->rc;
+}
+
+/**
+ * Get the current stream position as an byte offset.
+ *
+ * @returns The current byte offset
+ * @param pStream The stream.
+ */
+size_t ScmStreamTell(PSCMSTREAM pStream)
+{
+ return pStream->off;
+}
+
+/**
+ * Get the current stream position as a line number.
+ *
+ * @returns The current line (0-based).
+ * @param pStream The stream.
+ */
+size_t ScmStreamTellLine(PSCMSTREAM pStream)
+{
+ return pStream->iLine;
+}
+
+
+/**
+ * Gets the stream offset of a given line.
+ *
+ * @returns The offset of the line, or the stream size if the line number is too
+ * high.
+ * @param pStream The stream. Must be in read mode.
+ * @param iLine The line we're asking about.
+ */
+size_t ScmStreamTellOffsetOfLine(PSCMSTREAM pStream, size_t iLine)
+{
+ AssertReturn(!pStream->fWriteOrRead, pStream->cb);
+ if (!pStream->fFullyLineated)
+ {
+ int rc = scmStreamLineate(pStream);
+ AssertRCReturn(rc, pStream->cb);
+ }
+ if (iLine >= pStream->cLines)
+ return pStream->cb;
+ return pStream->paLines[iLine].off;
+}
+
+
+/**
+ * Get the current stream size in bytes.
+ *
+ * @returns Count of bytes.
+ * @param pStream The stream.
+ */
+size_t ScmStreamSize(PSCMSTREAM pStream)
+{
+ return pStream->cb;
+}
+
+/**
+ * Gets the number of lines in the stream.
+ *
+ * @returns The number of lines.
+ * @param pStream The stream.
+ */
+size_t ScmStreamCountLines(PSCMSTREAM pStream)
+{
+ if (!pStream->fFullyLineated)
+ scmStreamLineate(pStream);
+ return pStream->cLines;
+}
+
+/**
+ * Seeks to a given byte offset in the stream.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_SEEK if the new stream position is the middle of an EOL marker.
+ * This is a temporary restriction.
+ *
+ * @param pStream The stream. Must be in read mode.
+ * @param offAbsolute The offset to seek to. If this is beyond the
+ * end of the stream, the position is set to the
+ * end.
+ */
+int ScmStreamSeekAbsolute(PSCMSTREAM pStream, size_t offAbsolute)
+{
+ AssertReturn(!pStream->fWriteOrRead, VERR_ACCESS_DENIED);
+ if (RT_FAILURE(pStream->rc))
+ return pStream->rc;
+
+ /* Must be fully delineated. (lazy bird) */
+ if (RT_UNLIKELY(!pStream->fFullyLineated))
+ {
+ int rc = scmStreamLineate(pStream);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /* Ok, do the job. */
+ if (offAbsolute < pStream->cb)
+ {
+ /** @todo Should do a binary search here, but I'm too darn lazy tonight. */
+ pStream->off = ~(size_t)0;
+ for (size_t i = 0; i < pStream->cLines; i++)
+ {
+ if (offAbsolute < pStream->paLines[i].off + pStream->paLines[i].cch + pStream->paLines[i].enmEol)
+ {
+ pStream->off = offAbsolute;
+ pStream->iLine = i;
+ if (offAbsolute > pStream->paLines[i].off + pStream->paLines[i].cch)
+ return pStream->rc = VERR_SEEK;
+ break;
+ }
+ }
+ AssertReturn(pStream->off != ~(size_t)0, pStream->rc = VERR_INTERNAL_ERROR_3);
+ }
+ else
+ {
+ pStream->off = pStream->cb;
+ pStream->iLine = pStream->cLines;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Seeks a number of bytes relative to the current stream position.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_SEEK if the new stream position is the middle of an EOL marker.
+ * This is a temporary restriction.
+ *
+ * @param pStream The stream. Must be in read mode.
+ * @param offRelative The offset to seek to. A negative offset
+ * rewinds and positive one fast forwards the
+ * stream. Will quietly stop at the beginning and
+ * end of the stream.
+ */
+int ScmStreamSeekRelative(PSCMSTREAM pStream, ssize_t offRelative)
+{
+ size_t offAbsolute;
+ if (offRelative >= 0)
+ offAbsolute = pStream->off + offRelative;
+ else if ((size_t)-offRelative <= pStream->off)
+ offAbsolute = pStream->off + offRelative;
+ else
+ offAbsolute = 0;
+ return ScmStreamSeekAbsolute(pStream, offAbsolute);
+}
+
+/**
+ * Seeks to a given line in the stream.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pStream The stream. Must be in read mode.
+ * @param iLine The line to seek to. If this is beyond the end
+ * of the stream, the position is set to the end.
+ */
+int ScmStreamSeekByLine(PSCMSTREAM pStream, size_t iLine)
+{
+ if (RT_FAILURE(pStream->rc))
+ return pStream->rc;
+
+ /* Must be fully delineated. (lazy bird) */
+ if (RT_UNLIKELY(!pStream->fFullyLineated))
+ {
+ AssertReturn(!pStream->fWriteOrRead, VERR_ACCESS_DENIED);
+ int rc = scmStreamLineate(pStream);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /* Ok, do the job. */
+ if (iLine < pStream->cLines)
+ {
+ pStream->iLine = iLine;
+ pStream->off = pStream->paLines[iLine].off;
+ if (pStream->fWriteOrRead)
+ {
+ pStream->cb = pStream->paLines[iLine].off;
+ pStream->cLines = iLine;
+ pStream->paLines[iLine].cch = 0;
+ pStream->paLines[iLine].enmEol = SCMEOL_NONE;
+ }
+ }
+ else
+ {
+ AssertReturn(!pStream->fWriteOrRead, VERR_ACCESS_DENIED);
+ pStream->off = pStream->cb;
+ pStream->iLine = pStream->cLines;
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * Checks if the stream position is at the start of a line.
+ *
+ * @returns @c true if at the start, @c false if not.
+ * @param pStream The stream.
+ */
+bool ScmStreamIsAtStartOfLine(PSCMSTREAM pStream)
+{
+ if ( !pStream->fFullyLineated
+ && !pStream->fWriteOrRead)
+ {
+ int rc = scmStreamLineate(pStream);
+ if (RT_FAILURE(rc))
+ return false;
+ }
+ return pStream->off == pStream->paLines[pStream->iLine].off;
+}
+
+/**
+ * Compares the two streams from the start to end, binary fashion.
+ *
+ * The stream position does not change nor does it matter whether they are
+ * writable or readable.
+ *
+ * @returns true if identical, false if not.
+ * @param pStream1 The first stream.
+ * @param pStream2 The second stream.
+ */
+bool ScmStreamAreIdentical(PCSCMSTREAM pStream1, PCSCMSTREAM pStream2)
+{
+ return pStream1->cb == pStream2->cb
+ && memcmp(pStream1->pch, pStream2->pch, pStream1->cb) == 0;
+}
+
+
+/**
+ * Worker for ScmStreamGetLineByNo and ScmStreamGetLine.
+ *
+ * Works on a fully lineated stream.
+ *
+ * @returns Pointer to the first character in the line, not NULL terminated.
+ * NULL if the end of the stream has been reached or some problem
+ * occurred.
+ *
+ * @param pStream The stream. Must be in read mode.
+ * @param iLine The line to get (0-based).
+ * @param pcchLine The length.
+ * @param penmEol Where to return the end of line type indicator.
+ */
+DECLINLINE(const char *) scmStreamGetLineByNoCommon(PSCMSTREAM pStream, size_t iLine, size_t *pcchLine, PSCMEOL penmEol)
+{
+ Assert(!pStream->fWriteOrRead);
+ Assert(pStream->fFullyLineated);
+
+ /* Check stream status. */
+ if (RT_SUCCESS(pStream->rc))
+ {
+ /* Not at the end of the stream yet? */
+ if (RT_LIKELY(iLine < pStream->cLines))
+ {
+ /* Get the data. */
+ const char *pchRet = &pStream->pch[pStream->paLines[iLine].off];
+ *pcchLine = pStream->paLines[iLine].cch;
+ *penmEol = pStream->paLines[iLine].enmEol;
+
+ /* update the stream position. */
+ pStream->off = pStream->paLines[iLine].off + pStream->paLines[iLine].cch + pStream->paLines[iLine].enmEol;
+ pStream->iLine = iLine + 1;
+ return pchRet;
+ }
+ pStream->off = pStream->cb;
+ pStream->iLine = pStream->cLines;
+ }
+ *pcchLine = 0;
+ *penmEol = SCMEOL_NONE;
+ return NULL;
+}
+
+
+/**
+ * Get a numbered line from the stream (changes the position).
+ *
+ * A line is always delimited by a LF character or the end of the stream. The
+ * delimiter is not included in returned line length, but instead returned via
+ * the @a penmEol indicator.
+ *
+ * @returns Pointer to the first character in the line, not NULL terminated.
+ * NULL if the end of the stream has been reached or some problem
+ * occurred (*pcchLine set to zero and *penmEol to SCMEOL_NONE).
+ *
+ * @param pStream The stream. Must be in read mode.
+ * @param iLine The line to get (0-based).
+ * @param pcchLine The length.
+ * @param penmEol Where to return the end of line type indicator.
+ */
+const char *ScmStreamGetLineByNo(PSCMSTREAM pStream, size_t iLine, size_t *pcchLine, PSCMEOL penmEol)
+{
+ AssertReturn(!pStream->fWriteOrRead, NULL);
+
+ /* Make sure it's fully delineated so we can use the index. */
+ if (RT_LIKELY(pStream->fFullyLineated))
+ return scmStreamGetLineByNoCommon(pStream, iLine, pcchLine, penmEol);
+
+ int rc = pStream->rc;
+ if (RT_SUCCESS(rc))
+ {
+ rc = scmStreamLineate(pStream);
+ if (RT_SUCCESS(rc))
+ return scmStreamGetLineByNoCommon(pStream, iLine, pcchLine, penmEol);
+ }
+
+ *pcchLine = 0;
+ *penmEol = SCMEOL_NONE;
+ return NULL;
+}
+
+/**
+ * Get a line from the stream.
+ *
+ * A line is always delimited by a LF character or the end of the stream. The
+ * delimiter is not included in returned line length, but instead returned via
+ * the @a penmEol indicator.
+ *
+ * @returns Pointer to the first character in the line, not NULL terminated.
+ * NULL if the end of the stream has been reached or some problem
+ * occurred (*pcchLine set to zero and *penmEol to SCMEOL_NONE).
+ *
+ * @param pStream The stream. Must be in read mode.
+ * @param pcchLine The length.
+ * @param penmEol Where to return the end of line type indicator.
+ */
+const char *ScmStreamGetLine(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol)
+{
+ if (RT_LIKELY(pStream->fFullyLineated))
+ {
+ size_t offCur = pStream->off;
+ size_t iCurLine = pStream->iLine;
+ const char *pszLine = scmStreamGetLineByNoCommon(pStream, iCurLine, pcchLine, penmEol);
+ if ( pszLine
+ && offCur > pStream->paLines[iCurLine].off)
+ {
+ offCur -= pStream->paLines[iCurLine].off;
+ Assert(offCur <= pStream->paLines[iCurLine].cch + pStream->paLines[iCurLine].enmEol);
+ if (offCur < pStream->paLines[iCurLine].cch)
+ *pcchLine -= offCur;
+ else
+ *pcchLine = 0;
+ pszLine += offCur;
+ }
+ return pszLine;
+ }
+ return scmStreamGetLineInternal(pStream, pcchLine, penmEol);
+}
+
+/**
+ * Get the current buffer pointer.
+ *
+ * @returns Buffer pointer on success, NULL on failure (asserted).
+ * @param pStream The stream. Must be in read mode.
+ */
+const char *ScmStreamGetCur(PSCMSTREAM pStream)
+{
+ AssertReturn(!pStream->fWriteOrRead, NULL);
+ return pStream->pch + pStream->off;
+}
+
+/**
+ * Gets a character from the stream.
+ *
+ * @returns The next unsigned character in the stream.
+ * ~(unsigned)0 on failure.
+ * @param pStream The stream. Must be in read mode.
+ */
+unsigned ScmStreamGetCh(PSCMSTREAM pStream)
+{
+ /* Check stream state. */
+ AssertReturn(!pStream->fWriteOrRead, ~(unsigned)0);
+ if (RT_FAILURE(pStream->rc))
+ return ~(unsigned)0;
+ if (RT_UNLIKELY(!pStream->fFullyLineated))
+ {
+ int rc = scmStreamLineate(pStream);
+ if (RT_FAILURE(rc))
+ return ~(unsigned)0;
+ }
+
+ /* If there isn't enough stream left, fail already. */
+ if (RT_UNLIKELY(pStream->off >= pStream->cb))
+ return ~(unsigned)0;
+
+ /* Read a character. */
+ char ch = pStream->pch[pStream->off++];
+
+ /* Advance the line indicator. */
+ size_t iLine = pStream->iLine;
+ if (pStream->off >= pStream->paLines[iLine].off + pStream->paLines[iLine].cch + pStream->paLines[iLine].enmEol)
+ pStream->iLine++;
+
+ return (unsigned)ch;
+}
+
+
+/**
+ * Peeks at the next character from the stream.
+ *
+ * @returns The next unsigned character in the stream.
+ * ~(unsigned)0 on failure.
+ * @param pStream The stream. Must be in read mode.
+ */
+unsigned ScmStreamPeekCh(PSCMSTREAM pStream)
+{
+ /* Check stream state. */
+ AssertReturn(!pStream->fWriteOrRead, ~(unsigned)0);
+ if (RT_FAILURE(pStream->rc))
+ return ~(unsigned)0;
+ if (RT_UNLIKELY(!pStream->fFullyLineated))
+ {
+ int rc = scmStreamLineate(pStream);
+ if (RT_FAILURE(rc))
+ return ~(unsigned)0;
+ }
+
+ /* If there isn't enough stream left, fail already. */
+ if (RT_UNLIKELY(pStream->off >= pStream->cb))
+ return ~(unsigned)0;
+
+ /* Peek at the next character. */
+ char ch = pStream->pch[pStream->off];
+ return (unsigned)ch;
+}
+
+
+/**
+ * Reads @a cbToRead bytes into @a pvBuf.
+ *
+ * Will fail if end of stream is encountered before the entire read has been
+ * completed.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_EOF if there isn't @a cbToRead bytes left to read. Stream
+ * position will be unchanged.
+ *
+ * @param pStream The stream. Must be in read mode.
+ * @param pvBuf The buffer to read into.
+ * @param cbToRead The number of bytes to read.
+ */
+int ScmStreamRead(PSCMSTREAM pStream, void *pvBuf, size_t cbToRead)
+{
+ AssertReturn(!pStream->fWriteOrRead, VERR_PERMISSION_DENIED);
+ if (RT_FAILURE(pStream->rc))
+ return pStream->rc;
+
+ /* If there isn't enough stream left, fail already. */
+ if (RT_UNLIKELY(pStream->cb - pStream->off < cbToRead))
+ return VERR_EOF;
+
+ /* Copy the data and simply seek to the new stream position. */
+ memcpy(pvBuf, &pStream->pch[pStream->off], cbToRead);
+ return ScmStreamSeekAbsolute(pStream, pStream->off + cbToRead);
+}
+
+
+/**
+ * Checks if we're at the end of the stream.
+ *
+ * @returns true if end of stream, false if not.
+ * @param pStream The stream. Must be in read mode.
+ */
+bool ScmStreamIsEndOfStream(PSCMSTREAM pStream)
+{
+ AssertReturn(!pStream->fWriteOrRead, false);
+ return pStream->off >= pStream->cb
+ || RT_FAILURE(pStream->rc);
+}
+
+
+/**
+ * Checks if the given line is empty or full of white space.
+ *
+ * @returns true if white space only, false if not (or if non-existant).
+ * @param pStream The stream. Must be in read mode.
+ * @param iLine The line in question.
+ */
+bool ScmStreamIsWhiteLine(PSCMSTREAM pStream, size_t iLine)
+{
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine = ScmStreamGetLineByNo(pStream, iLine, &cchLine, &enmEol);
+ if (!pchLine)
+ return false;
+ while (cchLine && RT_C_IS_SPACE(*pchLine))
+ pchLine++, cchLine--;
+ return cchLine == 0;
+}
+
+
+/**
+ * Try figure out the end of line style of the give stream.
+ *
+ * @returns Most likely end of line style.
+ * @param pStream The stream.
+ */
+SCMEOL ScmStreamGetEol(PSCMSTREAM pStream)
+{
+ SCMEOL enmEol;
+ if (pStream->cLines > 0)
+ enmEol = pStream->paLines[0].enmEol;
+ else if (pStream->cb == 0)
+ enmEol = SCMEOL_NONE;
+ else
+ {
+ const char *pchLF = (const char *)memchr(pStream->pch, '\n', pStream->cb);
+ if (pchLF && pchLF != pStream->pch && pchLF[-1] == '\r')
+ enmEol = SCMEOL_CRLF;
+ else
+ enmEol = SCMEOL_LF;
+ }
+
+ if (enmEol == SCMEOL_NONE)
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ enmEol = SCMEOL_CRLF;
+#else
+ enmEol = SCMEOL_LF;
+#endif
+ return enmEol;
+}
+
+
+/**
+ * Get the end of line indicator type for a line.
+ *
+ * @returns The EOL indicator. If the line isn't found, the default EOL
+ * indicator is return.
+ * @param pStream The stream.
+ * @param iLine The line (0-base).
+ */
+SCMEOL ScmStreamGetEolByLine(PSCMSTREAM pStream, size_t iLine)
+{
+ SCMEOL enmEol;
+ if (iLine < pStream->cLines)
+ enmEol = pStream->paLines[iLine].enmEol;
+ else
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ enmEol = SCMEOL_CRLF;
+#else
+ enmEol = SCMEOL_LF;
+#endif
+ return enmEol;
+}
+
+
+/**
+ * Appends a line to the stream.
+ *
+ * @returns IPRT status code.
+ * @param pStream The stream. Must be in write mode.
+ * @param pchLine Pointer to the line.
+ * @param cchLine Line length.
+ * @param enmEol Which end of line indicator to use.
+ */
+int ScmStreamPutLine(PSCMSTREAM pStream, const char *pchLine, size_t cchLine, SCMEOL enmEol)
+{
+ AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);
+ if (RT_FAILURE(pStream->rc))
+ return pStream->rc;
+
+ /*
+ * Make sure the previous line has a new-line indicator.
+ */
+ size_t off = pStream->off;
+ size_t iLine = pStream->iLine;
+ if (RT_UNLIKELY( iLine != 0
+ && pStream->paLines[iLine - 1].enmEol == SCMEOL_NONE))
+ {
+ AssertReturn(pStream->paLines[iLine].cch == 0, VERR_INTERNAL_ERROR_3);
+ SCMEOL enmEol2 = enmEol != SCMEOL_NONE ? enmEol : ScmStreamGetEol(pStream);
+ if (RT_UNLIKELY(off + cchLine + enmEol + enmEol2 > pStream->cbAllocated))
+ {
+ int rc = scmStreamGrowBuffer(pStream, cchLine + enmEol + enmEol2);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ if (enmEol2 == SCMEOL_LF)
+ pStream->pch[off++] = '\n';
+ else
+ {
+ pStream->pch[off++] = '\r';
+ pStream->pch[off++] = '\n';
+ }
+ pStream->paLines[iLine - 1].enmEol = enmEol2;
+ pStream->paLines[iLine].off = off;
+ pStream->off = off;
+ pStream->cb = off;
+ }
+
+ /*
+ * Ensure we've got sufficient buffer space.
+ */
+ if (RT_UNLIKELY(off + cchLine + enmEol > pStream->cbAllocated))
+ {
+ int rc = scmStreamGrowBuffer(pStream, cchLine + enmEol);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Add a line record.
+ */
+ if (RT_UNLIKELY(iLine + 1 >= pStream->cLinesAllocated))
+ {
+ int rc = scmStreamGrowLines(pStream, iLine);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ pStream->paLines[iLine].cch = off - pStream->paLines[iLine].off + cchLine;
+ pStream->paLines[iLine].enmEol = enmEol;
+
+ iLine++;
+ pStream->cLines = iLine;
+ pStream->iLine = iLine;
+
+ /*
+ * Copy the line
+ */
+ memcpy(&pStream->pch[off], pchLine, cchLine);
+ off += cchLine;
+ if (enmEol == SCMEOL_LF)
+ pStream->pch[off++] = '\n';
+ else if (enmEol == SCMEOL_CRLF)
+ {
+ pStream->pch[off++] = '\r';
+ pStream->pch[off++] = '\n';
+ }
+ pStream->off = off;
+ pStream->cb = off;
+
+ /*
+ * Start a new line.
+ */
+ pStream->paLines[iLine].off = off;
+ pStream->paLines[iLine].cch = 0;
+ pStream->paLines[iLine].enmEol = SCMEOL_NONE;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Writes to the stream.
+ *
+ * @returns IPRT status code
+ * @param pStream The stream. Must be in write mode.
+ * @param pchBuf What to write.
+ * @param cchBuf How much to write.
+ */
+int ScmStreamWrite(PSCMSTREAM pStream, const char *pchBuf, size_t cchBuf)
+{
+ AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);
+ if (RT_FAILURE(pStream->rc))
+ return pStream->rc;
+
+ /*
+ * Ensure we've got sufficient buffer space.
+ */
+ size_t off = pStream->off;
+ if (RT_UNLIKELY(off + cchBuf > pStream->cbAllocated))
+ {
+ int rc = scmStreamGrowBuffer(pStream, cchBuf);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Deal with the odd case where we've already pushed a line with SCMEOL_NONE.
+ */
+ size_t iLine = pStream->iLine;
+ if (RT_UNLIKELY( iLine > 0
+ && pStream->paLines[iLine - 1].enmEol == SCMEOL_NONE))
+ {
+ iLine--;
+ pStream->cLines = iLine;
+ pStream->iLine = iLine;
+ }
+
+ /*
+ * Deal with lines.
+ */
+ const char *pchLF = (const char *)memchr(pchBuf, '\n', cchBuf);
+ if (!pchLF)
+ pStream->paLines[iLine].cch += cchBuf;
+ else
+ {
+ const char *pchLine = pchBuf;
+ for (;;)
+ {
+ if (RT_UNLIKELY(iLine + 1 >= pStream->cLinesAllocated))
+ {
+ int rc = scmStreamGrowLines(pStream, iLine);
+ if (RT_FAILURE(rc))
+ {
+ iLine = pStream->iLine;
+ pStream->paLines[iLine].cch = off - pStream->paLines[iLine].off;
+ pStream->paLines[iLine].enmEol = SCMEOL_NONE;
+ return rc;
+ }
+ }
+
+ size_t cchLine = pchLF - pchLine;
+ if ( cchLine
+ ? pchLF[-1] != '\r'
+ : !pStream->paLines[iLine].cch
+ || pStream->pch[pStream->paLines[iLine].off + pStream->paLines[iLine].cch - 1] != '\r')
+ pStream->paLines[iLine].enmEol = SCMEOL_LF;
+ else
+ {
+ pStream->paLines[iLine].enmEol = SCMEOL_CRLF;
+ cchLine--;
+ }
+ pStream->paLines[iLine].cch += cchLine;
+
+ iLine++;
+ size_t offBuf = pchLF + 1 - pchBuf;
+ pStream->paLines[iLine].off = off + offBuf;
+ pStream->paLines[iLine].cch = 0;
+ pStream->paLines[iLine].enmEol = SCMEOL_NONE;
+
+ size_t cchLeft = cchBuf - offBuf;
+ pchLine = pchLF + 1;
+ pchLF = (const char *)memchr(pchLine, '\n', cchLeft);
+ if (!pchLF)
+ {
+ pStream->paLines[iLine].cch = cchLeft;
+ break;
+ }
+ }
+
+ pStream->iLine = iLine;
+ pStream->cLines = iLine;
+ }
+
+ /*
+ * Copy the data and update position and size.
+ */
+ memcpy(&pStream->pch[off], pchBuf, cchBuf);
+ off += cchBuf;
+ pStream->off = off;
+ pStream->cb = off;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write a character to the stream.
+ *
+ * @returns IPRT status code
+ * @param pStream The stream. Must be in write mode.
+ * @param pchBuf What to write.
+ * @param cchBuf How much to write.
+ */
+int ScmStreamPutCh(PSCMSTREAM pStream, char ch)
+{
+ AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);
+ if (RT_FAILURE(pStream->rc))
+ return pStream->rc;
+
+ /*
+ * Only deal with the simple cases here, use ScmStreamWrite for the
+ * annoying stuff.
+ */
+ size_t off = pStream->off;
+ if ( ch == '\n'
+ || RT_UNLIKELY(off + 1 > pStream->cbAllocated))
+ return ScmStreamWrite(pStream, &ch, 1);
+
+ /*
+ * Just append it.
+ */
+ pStream->pch[off] = ch;
+ pStream->off = off + 1;
+ pStream->paLines[pStream->iLine].cch++;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Puts an EOL marker to the stream.
+ *
+ * @returns IPRt status code.
+ * @param pStream The stream. Must be in write mode.
+ * @param enmEol The end-of-line marker to write.
+ */
+int ScmStreamPutEol(PSCMSTREAM pStream, SCMEOL enmEol)
+{
+ if (enmEol == SCMEOL_LF)
+ return ScmStreamWrite(pStream, "\n", 1);
+ if (enmEol == SCMEOL_CRLF)
+ return ScmStreamWrite(pStream, "\r\n", 2);
+ if (enmEol == SCMEOL_NONE)
+ return VINF_SUCCESS;
+ AssertFailedReturn(VERR_INVALID_PARAMETER);
+}
+
+/**
+ * Formats a string and writes it to the SCM stream.
+ *
+ * @returns The number of bytes written (>= 0). Negative value are IPRT error
+ * status codes.
+ * @param pStream The stream to write to.
+ * @param pszFormat The format string.
+ * @param va The arguments to format.
+ */
+ssize_t ScmStreamPrintfV(PSCMSTREAM pStream, const char *pszFormat, va_list va)
+{
+ char *psz;
+ ssize_t cch = RTStrAPrintfV(&psz, pszFormat, va);
+ if (cch)
+ {
+ int rc = ScmStreamWrite(pStream, psz, cch);
+ RTStrFree(psz);
+ if (RT_FAILURE(rc))
+ cch = rc;
+ }
+ return cch;
+}
+
+/**
+ * Formats a string and writes it to the SCM stream.
+ *
+ * @returns The number of bytes written (>= 0). Negative value are IPRT error
+ * status codes.
+ * @param pStream The stream to write to.
+ * @param pszFormat The format string.
+ * @param ... The arguments to format.
+ */
+ssize_t ScmStreamPrintf(PSCMSTREAM pStream, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ ssize_t cch = ScmStreamPrintfV(pStream, pszFormat, va);
+ va_end(va);
+ return cch;
+}
+
+/**
+ * Copies @a cLines from the @a pSrc stream onto the @a pDst stream.
+ *
+ * The stream positions will be used and changed in both streams.
+ *
+ * @returns IPRT status code.
+ * @param pDst The destination stream. Must be in write mode.
+ * @param cLines The number of lines. (0 is accepted.)
+ * @param pSrc The source stream. Must be in read mode.
+ */
+int ScmStreamCopyLines(PSCMSTREAM pDst, PSCMSTREAM pSrc, size_t cLines)
+{
+ AssertReturn(pDst->fWriteOrRead, VERR_ACCESS_DENIED);
+ if (RT_FAILURE(pDst->rc))
+ return pDst->rc;
+
+ AssertReturn(!pSrc->fWriteOrRead, VERR_ACCESS_DENIED);
+ if (RT_FAILURE(pSrc->rc))
+ return pSrc->rc;
+
+ while (cLines-- > 0)
+ {
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine = ScmStreamGetLine(pSrc, &cchLine, &enmEol);
+ if (!pchLine)
+ return pDst->rc = RT_FAILURE(pSrc->rc) ? pSrc->rc : VERR_EOF;
+
+ int rc = ScmStreamPutLine(pDst, pchLine, cchLine, enmEol);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * If the given C word is at off - 1, return @c true and skip beyond it,
+ * otherwise return @c false.
+ *
+ * @retval true if the given C-word is at the current position minus one char.
+ * The stream position changes.
+ * @retval false if not. The stream position is unchanged.
+ *
+ * @param pStream The stream.
+ * @param cchWord The length of the word.
+ * @param pszWord The word.
+ */
+bool ScmStreamCMatchingWordM1(PSCMSTREAM pStream, const char *pszWord, size_t cchWord)
+{
+ /* Check stream state. */
+ AssertReturn(!pStream->fWriteOrRead, false);
+ AssertReturn(RT_SUCCESS(pStream->rc), false);
+ AssertReturn(pStream->fFullyLineated, false);
+
+ /* Sufficient chars left on the line? */
+ size_t const iLine = pStream->iLine;
+ AssertReturn(pStream->off > pStream->paLines[iLine].off, false);
+ size_t const cchLeft = pStream->paLines[iLine].cch + pStream->paLines[iLine].off - (pStream->off - 1);
+ if (cchWord > cchLeft)
+ return false;
+
+ /* Do they match? */
+ const char *psz = &pStream->pch[pStream->off - 1];
+ if (memcmp(psz, pszWord, cchWord))
+ return false;
+
+ /* Is it the end of a C word? */
+ if (cchWord < cchLeft)
+ {
+ psz += cchWord;
+ if (RT_C_IS_ALNUM(*psz) || *psz == '_')
+ return false;
+ }
+
+ /* Skip ahead. */
+ pStream->off += cchWord - 1;
+ return true;
+}
+
+
+/**
+ * Get's the C word starting at the current position.
+ *
+ * @returns Pointer to the word on success and the stream position advanced to
+ * the end of it.
+ * NULL on failure, stream position normally unchanged.
+ * @param pStream The stream to get the C word from.
+ * @param pcchWord Where to return the word length.
+ */
+const char *ScmStreamCGetWord(PSCMSTREAM pStream, size_t *pcchWord)
+{
+ /* Check stream state. */
+ AssertReturn(!pStream->fWriteOrRead, NULL);
+ AssertReturn(RT_SUCCESS(pStream->rc), NULL);
+ AssertReturn(pStream->fFullyLineated, NULL);
+
+ /* Get the number of chars left on the line and locate the current char. */
+ size_t const iLine = pStream->iLine;
+ size_t const cchLeft = pStream->paLines[iLine].cch + pStream->paLines[iLine].off - pStream->off;
+ const char *psz = &pStream->pch[pStream->off];
+
+ /* Is it a leading C character. */
+ if (!RT_C_IS_ALPHA(*psz) && *psz != '_')
+ return NULL;
+
+ /* Find the end of the word. */
+ char ch;
+ size_t off = 1;
+ while ( off < cchLeft
+ && ( (ch = psz[off]) == '_'
+ || RT_C_IS_ALNUM(ch)))
+ off++;
+
+ pStream->off += off;
+ *pcchWord = off;
+ return psz;
+}
+
+
+/**
+ * Get's the C word starting at the current position minus one.
+ *
+ * @returns Pointer to the word on success and the stream position advanced to
+ * the end of it.
+ * NULL on failure, stream position normally unchanged.
+ * @param pStream The stream to get the C word from.
+ * @param pcchWord Where to return the word length.
+ */
+const char *ScmStreamCGetWordM1(PSCMSTREAM pStream, size_t *pcchWord)
+{
+ /* Check stream state. */
+ AssertReturn(!pStream->fWriteOrRead, NULL);
+ AssertReturn(RT_SUCCESS(pStream->rc), NULL);
+ AssertReturn(pStream->fFullyLineated, NULL);
+
+ /* Get the number of chars left on the line and locate the current char. */
+ size_t const iLine = pStream->iLine;
+ size_t const cchLeft = pStream->paLines[iLine].cch + pStream->paLines[iLine].off - (pStream->off - 1);
+ const char *psz = &pStream->pch[pStream->off - 1];
+
+ /* Is it a leading C character. */
+ if (!RT_C_IS_ALPHA(*psz) && *psz != '_')
+ return NULL;
+
+ /* Find the end of the word. */
+ char ch;
+ size_t off = 1;
+ while ( off < cchLeft
+ && ( (ch = psz[off]) == '_'
+ || RT_C_IS_ALNUM(ch)))
+ off++;
+
+ pStream->off += off - 1;
+ *pcchWord = off;
+ return psz;
+}
+
diff --git a/src/bldprogs/scmstream.h b/src/bldprogs/scmstream.h
new file mode 100644
index 00000000..e66311ca
--- /dev/null
+++ b/src/bldprogs/scmstream.h
@@ -0,0 +1,152 @@
+/* $Id: scmstream.h $ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager Stream Code.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_bldprogs_scmstream_h
+#define VBOX_INCLUDED_SRC_bldprogs_scmstream_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/types.h>
+
+RT_C_DECLS_BEGIN
+
+/** End of line marker type. */
+typedef enum SCMEOL
+{
+ SCMEOL_NONE = 0,
+ SCMEOL_LF = 1,
+ SCMEOL_CRLF = 2
+} SCMEOL;
+/** Pointer to an end of line marker type. */
+typedef SCMEOL *PSCMEOL;
+
+/**
+ * Line record.
+ */
+typedef struct SCMSTREAMLINE
+{
+ /** The offset of the line. */
+ size_t off;
+ /** The line length, excluding the LF character.
+ * @todo This could be derived from the offset of the next line if that wasn't
+ * so tedious. */
+ size_t cch;
+ /** The end of line marker type. */
+ SCMEOL enmEol;
+} SCMSTREAMLINE;
+/** Pointer to a line record. */
+typedef SCMSTREAMLINE *PSCMSTREAMLINE;
+
+/**
+ * Source code massager stream.
+ */
+typedef struct SCMSTREAM
+{
+ /** Pointer to the file memory. */
+ char *pch;
+ /** The current stream position. */
+ size_t off;
+ /** The current stream size. */
+ size_t cb;
+ /** The size of the memory pb points to. */
+ size_t cbAllocated;
+
+ /** Line records. */
+ PSCMSTREAMLINE paLines;
+ /** The current line. */
+ size_t iLine;
+ /** The current stream size given in lines. */
+ size_t cLines;
+ /** The sizeof the memory backing paLines. */
+ size_t cLinesAllocated;
+
+ /** Set if write-only, clear if read-only. */
+ bool fWriteOrRead;
+ /** Set if the memory pb points to is from RTFileReadAll. */
+ bool fFileMemory;
+ /** Set if fully broken into lines. */
+ bool fFullyLineated;
+
+ /** Stream status code (IPRT). */
+ int rc;
+} SCMSTREAM;
+/** Pointer to a SCM stream. */
+typedef SCMSTREAM *PSCMSTREAM;
+/** Pointer to a const SCM stream. */
+typedef SCMSTREAM const *PCSCMSTREAM;
+
+
+int ScmStreamInitForReading(PSCMSTREAM pStream, const char *pszFilename);
+int ScmStreamInitForWriting(PSCMSTREAM pStream, PCSCMSTREAM pRelatedStream);
+void ScmStreamDelete(PSCMSTREAM pStream);
+int ScmStreamGetStatus(PCSCMSTREAM pStream);
+void ScmStreamRewindForReading(PSCMSTREAM pStream);
+void ScmStreamRewindForWriting(PSCMSTREAM pStream);
+bool ScmStreamIsText(PSCMSTREAM pStream);
+int ScmStreamCheckItegrity(PSCMSTREAM pStream);
+int ScmStreamWriteToFile(PSCMSTREAM pStream, const char *pszFilenameFmt, ...);
+int ScmStreamWriteToStdOut(PSCMSTREAM pStream);
+
+size_t ScmStreamTell(PSCMSTREAM pStream);
+size_t ScmStreamTellLine(PSCMSTREAM pStream);
+size_t ScmStreamTellOffsetOfLine(PSCMSTREAM pStream, size_t iLine);
+size_t ScmStreamSize(PSCMSTREAM pStream);
+size_t ScmStreamCountLines(PSCMSTREAM pStream);
+int ScmStreamSeekAbsolute(PSCMSTREAM pStream, size_t offAbsolute);
+int ScmStreamSeekRelative(PSCMSTREAM pStream, ssize_t offRelative);
+int ScmStreamSeekByLine(PSCMSTREAM pStream, size_t iLine);
+bool ScmStreamIsAtStartOfLine(PSCMSTREAM pStream);
+bool ScmStreamAreIdentical(PCSCMSTREAM pStream1, PCSCMSTREAM pStream2);
+
+const char *ScmStreamGetLineByNo(PSCMSTREAM pStream, size_t iLine, size_t *pcchLine, PSCMEOL penmEol);
+const char *ScmStreamGetLine(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol);
+unsigned ScmStreamGetCh(PSCMSTREAM pStream);
+const char *ScmStreamGetCur(PSCMSTREAM pStream);
+unsigned ScmStreamPeekCh(PSCMSTREAM pStream);
+int ScmStreamRead(PSCMSTREAM pStream, void *pvBuf, size_t cbToRead);
+bool ScmStreamIsEndOfStream(PSCMSTREAM pStream);
+bool ScmStreamIsWhiteLine(PSCMSTREAM pStream, size_t iLine);
+SCMEOL ScmStreamGetEol(PSCMSTREAM pStream);
+SCMEOL ScmStreamGetEolByLine(PSCMSTREAM pStream, size_t iLine);
+
+int ScmStreamPutLine(PSCMSTREAM pStream, const char *pchLine, size_t cchLine, SCMEOL enmEol);
+int ScmStreamWrite(PSCMSTREAM pStream, const char *pchBuf, size_t cchBuf);
+int ScmStreamPutCh(PSCMSTREAM pStream, char ch);
+int ScmStreamPutEol(PSCMSTREAM pStream, SCMEOL enmEol);
+ssize_t ScmStreamPrintf(PSCMSTREAM pStream, const char *pszFormat, ...);
+ssize_t ScmStreamPrintfV(PSCMSTREAM pStream, const char *pszFormat, va_list va);
+int ScmStreamCopyLines(PSCMSTREAM pDst, PSCMSTREAM pSrc, size_t cLines);
+
+bool ScmStreamCMatchingWordM1(PSCMSTREAM pStream, const char *pszWord, size_t cchWord);
+const char *ScmStreamCGetWord(PSCMSTREAM pStream, size_t *pcchWord);
+const char *ScmStreamCGetWordM1(PSCMSTREAM pStream, size_t *pcchWord);
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_bldprogs_scmstream_h */
+
diff --git a/src/bldprogs/scmsubversion.cpp b/src/bldprogs/scmsubversion.cpp
new file mode 100644
index 00000000..f55ee85e
--- /dev/null
+++ b/src/bldprogs/scmsubversion.cpp
@@ -0,0 +1,1690 @@
+/* $Id: scmsubversion.cpp $ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager, Subversion Access.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define SCM_WITH_DYNAMIC_LIB_SVN
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/handle.h>
+#include <iprt/initterm.h>
+#include <iprt/ldr.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/pipe.h>
+#include <iprt/poll.h>
+#include <iprt/process.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+#include "scm.h"
+
+#if defined(SCM_WITH_DYNAMIC_LIB_SVN) && defined(SCM_WITH_SVN_HEADERS)
+# include <svn_client.h>
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifdef SCM_WITH_DYNAMIC_LIB_SVN
+# if defined(RT_OS_WINDOWS) && defined(RT_ARCH_X86)
+# define APR_CALL __stdcall
+# define SVN_CALL /* __stdcall ?? */
+# else
+# define APR_CALL
+# define SVN_CALL
+# endif
+#endif
+#if defined(SCM_WITH_DYNAMIC_LIB_SVN) && !defined(SCM_WITH_SVN_HEADERS)
+# define SVN_ERR_MISC_CATEGORY_START 200000
+# define SVN_ERR_UNVERSIONED_RESOURCE (SVN_ERR_MISC_CATEGORY_START + 5)
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+#if defined(SCM_WITH_DYNAMIC_LIB_SVN) && !defined(SCM_WITH_SVN_HEADERS)
+typedef int apr_status_t;
+typedef int64_t apr_time_t;
+typedef struct apr_pool_t apr_pool_t;
+typedef struct apr_hash_t apr_hash_t;
+typedef struct apr_hash_index_t apr_hash_index_t;
+typedef struct apr_array_header_t apr_array_header_t;
+
+
+typedef struct svn_error_t
+{
+ apr_status_t apr_err;
+ const char *_dbgr_message;
+ struct svn_error_t *_dbgr_child;
+ apr_pool_t *_dbgr_pool;
+ const char *_dbgr_file;
+ long _dbgr_line;
+} svn_error_t;
+typedef int svn_boolean_t;
+typedef long int svn_revnum_t;
+typedef struct svn_client_ctx_t svn_client_ctx_t;
+typedef enum svn_opt_revision_kind
+{
+ svn_opt_revision_unspecified = 0,
+ svn_opt_revision_number,
+ svn_opt_revision_date,
+ svn_opt_revision_committed,
+ svn_opt_revision_previous,
+ svn_opt_revision_base,
+ svn_opt_revision_working,
+ svn_opt_revision_head
+} svn_opt_revision_kind;
+typedef union svn_opt_revision_value_t
+{
+ svn_revnum_t number;
+ apr_time_t date;
+} svn_opt_revision_value_t;
+typedef struct svn_opt_revision_t
+{
+ svn_opt_revision_kind kind;
+ svn_opt_revision_value_t value;
+} svn_opt_revision_t;
+typedef enum svn_depth_t
+{
+ svn_depth_unknown = -2,
+ svn_depth_exclude,
+ svn_depth_empty,
+ svn_depth_files,
+ svn_depth_immediates,
+ svn_depth_infinity
+} svn_depth_t;
+
+#endif /* SCM_WITH_DYNAMIC_LIB_SVN && !SCM_WITH_SVN_HEADERS */
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static char g_szSvnPath[RTPATH_MAX];
+static enum
+{
+ kScmSvnVersion_Ancient = 1,
+ kScmSvnVersion_1_6,
+ kScmSvnVersion_1_7,
+ kScmSvnVersion_1_8,
+ kScmSvnVersion_End
+} g_enmSvnVersion = kScmSvnVersion_Ancient;
+
+
+#ifdef SCM_WITH_DYNAMIC_LIB_SVN
+/** Set if all the function pointers are valid. */
+static bool g_fSvnFunctionPointersValid;
+/** @name SVN and APR imports.
+ * @{ */
+static apr_status_t (APR_CALL *g_pfnAprInitialize)(void);
+static apr_hash_index_t * (APR_CALL *g_pfnAprHashFirst)(apr_pool_t *pPool, apr_hash_t *pHashTab);
+static apr_hash_index_t * (APR_CALL *g_pfnAprHashNext)(apr_hash_index_t *pCurIdx);
+static void * (APR_CALL *g_pfnAprHashThisVal)(apr_hash_index_t *pHashIdx);
+static apr_pool_t * (SVN_CALL *g_pfnSvnPoolCreateEx)(apr_pool_t *pParent, void *pvAllocator);
+static void (APR_CALL *g_pfnAprPoolClear)(apr_pool_t *pPool);
+static void (APR_CALL *g_pfnAprPoolDestroy)(apr_pool_t *pPool);
+
+static svn_error_t * (SVN_CALL *g_pfnSvnClientCreateContext)(svn_client_ctx_t **ppCtx, apr_pool_t *pPool);
+static svn_error_t * (SVN_CALL *g_pfnSvnClientPropGet4)(apr_hash_t **ppHashProps, const char *pszPropName,
+ const char *pszTarget, const svn_opt_revision_t *pPeggedRev,
+ const svn_opt_revision_t *pRevision, svn_revnum_t *pActualRev,
+ svn_depth_t enmDepth, const apr_array_header_t *pChangeList,
+ svn_client_ctx_t *pCtx, apr_pool_t *pResultPool,
+ apr_pool_t *pScratchPool);
+/**@} */
+
+/** Cached APR pool. */
+static apr_pool_t *g_pSvnPool = NULL;
+/** Cached SVN client context. */
+static svn_client_ctx_t *g_pSvnClientCtx = NULL;
+/** Number of times the current context has been used. */
+static uint32_t g_cSvnClientCtxUsed = 0;
+
+#endif
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#ifdef SCM_WITH_DYNAMIC_LIB_SVN
+static void scmSvnFlushClientContextAndPool(void);
+#endif
+
+
+
+/**
+ * Callback that is call for each path to search.
+ */
+static DECLCALLBACK(int) scmSvnFindSvnBinaryCallback(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
+{
+ char *pszDst = (char *)pvUser1;
+ size_t cchDst = (size_t)pvUser2;
+ if (cchDst > cchPath)
+ {
+ memcpy(pszDst, pchPath, cchPath);
+ pszDst[cchPath] = '\0';
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ int rc = RTPathAppend(pszDst, cchDst, "svn.exe");
+#else
+ int rc = RTPathAppend(pszDst, cchDst, "svn");
+#endif
+ if ( RT_SUCCESS(rc)
+ && RTFileExists(pszDst))
+ return VINF_SUCCESS;
+ }
+ return VERR_TRY_AGAIN;
+}
+
+
+/**
+ * Reads from a pipe.
+ *
+ * @returns @a rc or other status code.
+ * @param rc The current status of the operation. Error status
+ * are preserved and returned.
+ * @param phPipeR Pointer to the pipe handle.
+ * @param pcbAllocated Pointer to the buffer size variable.
+ * @param poffCur Pointer to the buffer offset variable.
+ * @param ppszBuffer Pointer to the buffer pointer variable.
+ */
+static int rtProcProcessOutput(int rc, PRTPIPE phPipeR, size_t *pcbAllocated, size_t *poffCur, char **ppszBuffer,
+ RTPOLLSET hPollSet, uint32_t idPollSet)
+{
+ size_t cbRead;
+ char szTmp[_4K - 1];
+ for (;;)
+ {
+ int rc2 = RTPipeRead(*phPipeR, szTmp, sizeof(szTmp), &cbRead);
+ if (RT_SUCCESS(rc2) && cbRead)
+ {
+ /* Resize the buffer. */
+ if (*poffCur + cbRead >= *pcbAllocated)
+ {
+ if (*pcbAllocated >= _1G)
+ {
+ RTPollSetRemove(hPollSet, idPollSet);
+ rc2 = RTPipeClose(*phPipeR); AssertRC(rc2);
+ *phPipeR = NIL_RTPIPE;
+ return RT_SUCCESS(rc) ? VERR_TOO_MUCH_DATA : rc;
+ }
+
+ size_t cbNew = *pcbAllocated ? *pcbAllocated * 2 : sizeof(szTmp) + 1;
+ Assert(*poffCur + cbRead < cbNew);
+ rc2 = RTStrRealloc(ppszBuffer, cbNew);
+ if (RT_FAILURE(rc2))
+ {
+ RTPollSetRemove(hPollSet, idPollSet);
+ rc2 = RTPipeClose(*phPipeR); AssertRC(rc2);
+ *phPipeR = NIL_RTPIPE;
+ return RT_SUCCESS(rc) ? rc2 : rc;
+ }
+ *pcbAllocated = cbNew;
+ }
+
+ /* Append the new data, terminating it. */
+ memcpy(*ppszBuffer + *poffCur, szTmp, cbRead);
+ *poffCur += cbRead;
+ (*ppszBuffer)[*poffCur] = '\0';
+
+ /* Check for null terminators in the string. */
+ if (RT_SUCCESS(rc) && memchr(szTmp, '\0', cbRead))
+ rc = VERR_NO_TRANSLATION;
+
+ /* If we read a full buffer, try read some more. */
+ if (RT_SUCCESS(rc) && cbRead == sizeof(szTmp))
+ continue;
+ }
+ else if (rc2 != VINF_TRY_AGAIN)
+ {
+ if (RT_FAILURE(rc) && rc2 != VERR_BROKEN_PIPE)
+ rc = rc2;
+ RTPollSetRemove(hPollSet, idPollSet);
+ rc2 = RTPipeClose(*phPipeR); AssertRC(rc2);
+ *phPipeR = NIL_RTPIPE;
+ }
+ return rc;
+ }
+}
+
+/** @name RTPROCEXEC_FLAGS_XXX - flags for RTProcExec and RTProcExecToString.
+ * @{ */
+/** Redirect /dev/null to standard input. */
+#define RTPROCEXEC_FLAGS_STDIN_NULL RT_BIT_32(0)
+/** Redirect standard output to /dev/null. */
+#define RTPROCEXEC_FLAGS_STDOUT_NULL RT_BIT_32(1)
+/** Redirect standard error to /dev/null. */
+#define RTPROCEXEC_FLAGS_STDERR_NULL RT_BIT_32(2)
+/** Redirect all standard output to /dev/null as well as directing /dev/null
+ * to standard input. */
+#define RTPROCEXEC_FLAGS_STD_NULL ( RTPROCEXEC_FLAGS_STDIN_NULL \
+ | RTPROCEXEC_FLAGS_STDOUT_NULL \
+ | RTPROCEXEC_FLAGS_STDERR_NULL)
+/** Mask containing the valid flags. */
+#define RTPROCEXEC_FLAGS_VALID_MASK UINT32_C(0x00000007)
+/** @} */
+
+/**
+ * Runs a process, collecting the standard output and/or standard error.
+ *
+ *
+ * @returns IPRT status code
+ * @retval VERR_NO_TRANSLATION if the output of the program isn't valid UTF-8
+ * or contains a nul character.
+ * @retval VERR_TOO_MUCH_DATA if the process produced too much data.
+ *
+ * @param pszExec Executable image to use to create the child process.
+ * @param papszArgs Pointer to an array of arguments to the child. The
+ * array terminated by an entry containing NULL.
+ * @param hEnv Handle to the environment block for the child.
+ * @param fFlags A combination of RTPROCEXEC_FLAGS_XXX. The @a
+ * ppszStdOut and @a ppszStdErr parameters takes precedence
+ * over redirection flags.
+ * @param pStatus Where to return the status on success.
+ * @param ppszStdOut Where to return the text written to standard output. If
+ * NULL then standard output will not be collected and go
+ * to the standard output handle of the process.
+ * Free with RTStrFree, regardless of return status.
+ * @param ppszStdErr Where to return the text written to standard error. If
+ * NULL then standard output will not be collected and go
+ * to the standard error handle of the process.
+ * Free with RTStrFree, regardless of return status.
+ */
+int RTProcExecToString(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
+ PRTPROCSTATUS pStatus, char **ppszStdOut, char **ppszStdErr)
+{
+ int rc2;
+
+ /*
+ * Clear output arguments (no returning failure here, simply crash!).
+ */
+ AssertPtr(pStatus);
+ pStatus->enmReason = RTPROCEXITREASON_ABEND;
+ pStatus->iStatus = RTEXITCODE_FAILURE;
+ AssertPtrNull(ppszStdOut);
+ if (ppszStdOut)
+ *ppszStdOut = NULL;
+ AssertPtrNull(ppszStdOut);
+ if (ppszStdErr)
+ *ppszStdErr = NULL;
+
+ /*
+ * Check input arguments.
+ */
+ AssertReturn(!(fFlags & ~RTPROCEXEC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
+
+ /*
+ * Do we need a standard input bitbucket?
+ */
+ int rc = VINF_SUCCESS;
+ PRTHANDLE phChildStdIn = NULL;
+ RTHANDLE hChildStdIn;
+ hChildStdIn.enmType = RTHANDLETYPE_FILE;
+ hChildStdIn.u.hFile = NIL_RTFILE;
+ if ((fFlags & RTPROCEXEC_FLAGS_STDIN_NULL) && RT_SUCCESS(rc))
+ {
+ phChildStdIn = &hChildStdIn;
+ rc = RTFileOpenBitBucket(&hChildStdIn.u.hFile, RTFILE_O_READ);
+ }
+
+ /*
+ * Create the output pipes / bitbuckets.
+ */
+ RTPIPE hPipeStdOutR = NIL_RTPIPE;
+ PRTHANDLE phChildStdOut = NULL;
+ RTHANDLE hChildStdOut;
+ hChildStdOut.enmType = RTHANDLETYPE_PIPE;
+ hChildStdOut.u.hPipe = NIL_RTPIPE;
+ if (ppszStdOut && RT_SUCCESS(rc))
+ {
+ phChildStdOut = &hChildStdOut;
+ rc = RTPipeCreate(&hPipeStdOutR, &hChildStdOut.u.hPipe, 0 /*fFlags*/);
+ }
+ else if ((fFlags & RTPROCEXEC_FLAGS_STDOUT_NULL) && RT_SUCCESS(rc))
+ {
+ phChildStdOut = &hChildStdOut;
+ hChildStdOut.enmType = RTHANDLETYPE_FILE;
+ hChildStdOut.u.hFile = NIL_RTFILE;
+ rc = RTFileOpenBitBucket(&hChildStdOut.u.hFile, RTFILE_O_WRITE);
+ }
+
+ RTPIPE hPipeStdErrR = NIL_RTPIPE;
+ PRTHANDLE phChildStdErr = NULL;
+ RTHANDLE hChildStdErr;
+ hChildStdErr.enmType = RTHANDLETYPE_PIPE;
+ hChildStdErr.u.hPipe = NIL_RTPIPE;
+ if (ppszStdErr && RT_SUCCESS(rc))
+ {
+ phChildStdErr = &hChildStdErr;
+ rc = RTPipeCreate(&hPipeStdErrR, &hChildStdErr.u.hPipe, 0 /*fFlags*/);
+ }
+ else if ((fFlags & RTPROCEXEC_FLAGS_STDERR_NULL) && RT_SUCCESS(rc))
+ {
+ phChildStdErr = &hChildStdErr;
+ hChildStdErr.enmType = RTHANDLETYPE_FILE;
+ hChildStdErr.u.hFile = NIL_RTFILE;
+ rc = RTFileOpenBitBucket(&hChildStdErr.u.hFile, RTFILE_O_WRITE);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ RTPOLLSET hPollSet;
+ rc = RTPollSetCreate(&hPollSet);
+ if (RT_SUCCESS(rc))
+ {
+ if (hPipeStdOutR != NIL_RTPIPE && RT_SUCCESS(rc))
+ rc = RTPollSetAddPipe(hPollSet, hPipeStdOutR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, 1);
+ if (hPipeStdErrR != NIL_RTPIPE)
+ rc = RTPollSetAddPipe(hPollSet, hPipeStdErrR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, 2);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create the process.
+ */
+ RTPROCESS hProc;
+ rc = RTProcCreateEx(pszExec,
+ papszArgs,
+ hEnv,
+ 0 /*fFlags*/,
+ NULL /*phStdIn*/,
+ phChildStdOut,
+ phChildStdErr,
+ NULL /*pszAsUser*/,
+ NULL /*pszPassword*/,
+ NULL /*pvExtraData*/,
+ &hProc);
+ rc2 = RTHandleClose(&hChildStdErr); AssertRC(rc2);
+ rc2 = RTHandleClose(&hChildStdOut); AssertRC(rc2);
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Process output and wait for the process to finish.
+ */
+ size_t cbStdOut = 0;
+ size_t offStdOut = 0;
+ size_t cbStdErr = 0;
+ size_t offStdErr = 0;
+ for (;;)
+ {
+ if (hPipeStdOutR != NIL_RTPIPE)
+ rc = rtProcProcessOutput(rc, &hPipeStdOutR, &cbStdOut, &offStdOut, ppszStdOut, hPollSet, 1);
+ if (hPipeStdErrR != NIL_RTPIPE)
+ rc = rtProcProcessOutput(rc, &hPipeStdErrR, &cbStdErr, &offStdErr, ppszStdErr, hPollSet, 2);
+ if (hPipeStdOutR == NIL_RTPIPE && hPipeStdErrR == NIL_RTPIPE)
+ break;
+
+ if (hProc != NIL_RTPROCESS)
+ {
+ rc2 = RTProcWait(hProc, RTPROCWAIT_FLAGS_NOBLOCK, pStatus);
+ if (rc2 != VERR_PROCESS_RUNNING)
+ {
+ if (RT_FAILURE(rc2))
+ rc = rc2;
+ hProc = NIL_RTPROCESS;
+ }
+ }
+
+ rc2 = RTPoll(hPollSet, 10000, NULL, NULL);
+ Assert(RT_SUCCESS(rc2) || rc2 == VERR_TIMEOUT);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if ( (ppszStdOut && *ppszStdOut && !RTStrIsValidEncoding(*ppszStdOut))
+ || (ppszStdErr && *ppszStdErr && !RTStrIsValidEncoding(*ppszStdErr)) )
+ rc = VERR_NO_TRANSLATION;
+ }
+
+ /*
+ * No more output, just wait for it to finish.
+ */
+ if (hProc != NIL_RTPROCESS)
+ {
+ rc2 = RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, pStatus);
+ if (RT_FAILURE(rc2))
+ rc = rc2;
+ }
+ }
+ RTPollSetDestroy(hPollSet);
+ }
+ }
+
+ rc2 = RTHandleClose(&hChildStdErr); AssertRC(rc2);
+ rc2 = RTHandleClose(&hChildStdOut); AssertRC(rc2);
+ rc2 = RTHandleClose(&hChildStdIn); AssertRC(rc2);
+ rc2 = RTPipeClose(hPipeStdErrR); AssertRC(rc2);
+ rc2 = RTPipeClose(hPipeStdOutR); AssertRC(rc2);
+ return rc;
+}
+
+
+/**
+ * Runs a process, waiting for it to complete.
+ *
+ * @returns IPRT status code
+ *
+ * @param pszExec Executable image to use to create the child process.
+ * @param papszArgs Pointer to an array of arguments to the child. The
+ * array terminated by an entry containing NULL.
+ * @param hEnv Handle to the environment block for the child.
+ * @param fFlags A combination of RTPROCEXEC_FLAGS_XXX.
+ * @param pStatus Where to return the status on success.
+ */
+int RTProcExec(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
+ PRTPROCSTATUS pStatus)
+{
+ int rc;
+
+ /*
+ * Clear output argument (no returning failure here, simply crash!).
+ */
+ AssertPtr(pStatus);
+ pStatus->enmReason = RTPROCEXITREASON_ABEND;
+ pStatus->iStatus = RTEXITCODE_FAILURE;
+
+ /*
+ * Check input arguments.
+ */
+ AssertReturn(!(fFlags & ~RTPROCEXEC_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
+
+ /*
+ * Set up /dev/null redirections.
+ */
+ PRTHANDLE aph[3] = { NULL, NULL, NULL };
+ RTHANDLE ah[3];
+ for (uint32_t i = 0; i < 3; i++)
+ {
+ ah[i].enmType = RTHANDLETYPE_FILE;
+ ah[i].u.hFile = NIL_RTFILE;
+ }
+ rc = VINF_SUCCESS;
+ if ((fFlags & RTPROCEXEC_FLAGS_STDIN_NULL) && RT_SUCCESS(rc))
+ {
+ aph[0] = &ah[0];
+ rc = RTFileOpenBitBucket(&ah[0].u.hFile, RTFILE_O_READ);
+ }
+ if ((fFlags & RTPROCEXEC_FLAGS_STDOUT_NULL) && RT_SUCCESS(rc))
+ {
+ aph[1] = &ah[1];
+ rc = RTFileOpenBitBucket(&ah[1].u.hFile, RTFILE_O_WRITE);
+ }
+ if ((fFlags & RTPROCEXEC_FLAGS_STDERR_NULL) && RT_SUCCESS(rc))
+ {
+ aph[2] = &ah[2];
+ rc = RTFileOpenBitBucket(&ah[2].u.hFile, RTFILE_O_WRITE);
+ }
+
+ /*
+ * Create the process.
+ */
+ RTPROCESS hProc = NIL_RTPROCESS;
+ if (RT_SUCCESS(rc))
+ rc = RTProcCreateEx(pszExec,
+ papszArgs,
+ hEnv,
+ 0 /*fFlags*/,
+ aph[0],
+ aph[1],
+ aph[2],
+ NULL /*pszAsUser*/,
+ NULL /*pszPassword*/,
+ NULL /*pvExtraData*/,
+ &hProc);
+
+ for (uint32_t i = 0; i < 3; i++)
+ RTFileClose(ah[i].u.hFile);
+
+ if (RT_SUCCESS(rc))
+ rc = RTProcWait(hProc, RTPROCWAIT_FLAGS_BLOCK, pStatus);
+ return rc;
+}
+
+
+
+/**
+ * Executes SVN and gets the output.
+ *
+ * Standard error is suppressed.
+ *
+ * @returns VINF_SUCCESS if the command executed successfully.
+ * @param pState The rewrite state to work on. Can be NULL.
+ * @param papszArgs The SVN argument.
+ * @param fNormalFailureOk Whether normal failure is ok.
+ * @param ppszStdOut Where to return the output on success.
+ */
+static int scmSvnRunAndGetOutput(PSCMRWSTATE pState, const char **papszArgs, bool fNormalFailureOk, char **ppszStdOut)
+{
+ *ppszStdOut = NULL;
+
+#ifdef SCM_WITH_DYNAMIC_LIB_SVN
+ scmSvnFlushClientContextAndPool();
+#endif
+
+ char *pszCmdLine = NULL;
+ int rc = RTGetOptArgvToString(&pszCmdLine, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
+ if (RT_FAILURE(rc))
+ return rc;
+ ScmVerbose(pState, 2, "executing: %s\n", pszCmdLine);
+
+ RTPROCSTATUS Status;
+ rc = RTProcExecToString(g_szSvnPath, papszArgs, RTENV_DEFAULT,
+ RTPROCEXEC_FLAGS_STD_NULL, &Status, ppszStdOut, NULL);
+
+ if ( RT_SUCCESS(rc)
+ && ( Status.enmReason != RTPROCEXITREASON_NORMAL
+ || Status.iStatus != 0) )
+ {
+ if (fNormalFailureOk || Status.enmReason != RTPROCEXITREASON_NORMAL)
+ RTMsgError("%s: %s -> %s %u\n",
+ pState ? pState->pszFilename : "<NONE>", pszCmdLine,
+ Status.enmReason == RTPROCEXITREASON_NORMAL ? "exit code"
+ : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"
+ : Status.enmReason == RTPROCEXITREASON_ABEND ? "abnormal end"
+ : "abducted by alien",
+ Status.iStatus);
+ rc = VERR_GENERAL_FAILURE;
+ }
+ else if (RT_FAILURE(rc))
+ {
+ if (pState)
+ RTMsgError("%s: executing: %s => %Rrc\n", pState->pszFilename, pszCmdLine, rc);
+ else
+ RTMsgError("executing: %s => %Rrc\n", pszCmdLine, rc);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ RTStrFree(*ppszStdOut);
+ *ppszStdOut = NULL;
+ }
+ RTStrFree(pszCmdLine);
+ return rc;
+}
+
+
+/**
+ * Executes SVN.
+ *
+ * Standard error and standard output is suppressed.
+ *
+ * @returns VINF_SUCCESS if the command executed successfully.
+ * @param pState The rewrite state to work on.
+ * @param papszArgs The SVN argument.
+ * @param fNormalFailureOk Whether normal failure is ok.
+ */
+static int scmSvnRun(PSCMRWSTATE pState, const char **papszArgs, bool fNormalFailureOk)
+{
+#ifdef SCM_WITH_DYNAMIC_LIB_SVN
+ scmSvnFlushClientContextAndPool();
+#endif
+
+ char *pszCmdLine = NULL;
+ int rc = RTGetOptArgvToString(&pszCmdLine, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
+ if (RT_FAILURE(rc))
+ return rc;
+ ScmVerbose(pState, 2, "executing: %s\n", pszCmdLine);
+
+ /* Lazy bird uses RTProcExec. */
+ RTPROCSTATUS Status;
+ rc = RTProcExec(g_szSvnPath, papszArgs, RTENV_DEFAULT, RTPROCEXEC_FLAGS_STD_NULL, &Status);
+
+ if ( RT_SUCCESS(rc)
+ && ( Status.enmReason != RTPROCEXITREASON_NORMAL
+ || Status.iStatus != 0) )
+ {
+ if (fNormalFailureOk || Status.enmReason != RTPROCEXITREASON_NORMAL)
+ RTMsgError("%s: %s -> %s %u\n",
+ pState->pszFilename,
+ pszCmdLine,
+ Status.enmReason == RTPROCEXITREASON_NORMAL ? "exit code"
+ : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"
+ : Status.enmReason == RTPROCEXITREASON_ABEND ? "abnormal end"
+ : "abducted by alien",
+ Status.iStatus);
+ rc = VERR_GENERAL_FAILURE;
+ }
+ else if (RT_FAILURE(rc))
+ RTMsgError("%s: %s -> %Rrc\n", pState->pszFilename, pszCmdLine, rc);
+
+ RTStrFree(pszCmdLine);
+ return rc;
+}
+
+
+#ifdef SCM_WITH_DYNAMIC_LIB_SVN
+/**
+ * Attempts to resolve the necessary subversion and apache portable runtime APIs
+ * we require dynamically.
+ *
+ * Will set all global function pointers and g_fSvnFunctionPointersValid to true
+ * on success.
+ */
+static void scmSvnTryResolveFunctions(void)
+{
+ char szPath[RTPATH_MAX];
+ int rc = RTStrCopy(szPath, sizeof(szPath), g_szSvnPath);
+ if (RT_SUCCESS(rc))
+ {
+ RTPathStripFilename(szPath);
+ char *pszEndPath = strchr(szPath, '\0');
+# ifdef RT_OS_WINDOWS
+ RTPathChangeToDosSlashes(szPath, false);
+# endif
+
+ /*
+ * Try various prefixes/suffxies/locations.
+ */
+ static struct
+ {
+ const char *pszPrefix;
+ const char *pszSuffix;
+ } const s_aVariations[] =
+ {
+# ifdef RT_OS_WINDOWS
+ { "SlikSvn-lib", "-1.dll" }, /* SlikSVN */
+ { "lib", "-1.dll" }, /* Win32Svn,CollabNet,++ */
+# elif defined(RT_OS_DARWIN)
+ { "../lib/lib", "-1.dylib" },
+# else
+ { "../lib/lib", ".so" },
+ { "../lib/lib", "-1.so" },
+# if ARCH_BITS == 32
+ { "../lib32/lib", ".so" },
+ { "../lib32/lib", "-1.so" },
+# else
+ { "../lib64/lib", ".so" },
+ { "../lib64/lib", "-1.so" },
+# ifdef RT_OS_SOLARIS
+ { "../lib/svn/amd64/lib", ".so" },
+ { "../lib/svn/amd64/lib", "-1.so" },
+ { "../apr/1.6/lib/amd64/lib", ".so" },
+ { "../apr/1.6/lib/amd64/lib", "-1.so" },
+# endif
+# endif
+# ifdef RT_ARCH_X86
+ { "../lib/i386-linux-gnu/lib", ".so" },
+ { "../lib/i386-linux-gnu/lib", "-1.so" },
+# elif defined(RT_ARCH_AMD64)
+ { "../lib/x86_64-linux-gnu/lib", ".so" },
+ { "../lib/x86_64-linux-gnu/lib", "-1.so" },
+# endif
+# endif
+ };
+ for (unsigned iVar = 0; iVar < RT_ELEMENTS(s_aVariations); iVar++)
+ {
+ /*
+ * Try load the svn_client library ...
+ */
+ static const char * const s_apszLibraries[] = { "svn_client", "svn_subr", "apr" };
+ RTLDRMOD ahMods[RT_ELEMENTS(s_apszLibraries)] = { NIL_RTLDRMOD, NIL_RTLDRMOD, NIL_RTLDRMOD };
+
+ rc = VINF_SUCCESS;
+ unsigned iLib;
+ for (iLib = 0; iLib < RT_ELEMENTS(s_apszLibraries) && RT_SUCCESS(rc); iLib++)
+ {
+ static const char * const s_apszSuffixes[] = { "", ".0", ".1" };
+ for (unsigned iSuff = 0; iSuff < RT_ELEMENTS(s_apszSuffixes); iSuff++)
+ {
+ *pszEndPath = '\0';
+ rc = RTPathAppend(szPath, sizeof(szPath), s_aVariations[iVar].pszPrefix);
+ if (RT_SUCCESS(rc))
+ rc = RTStrCat(szPath, sizeof(szPath), s_apszLibraries[iLib]);
+ if (RT_SUCCESS(rc))
+ rc = RTStrCat(szPath, sizeof(szPath), s_aVariations[iVar].pszSuffix);
+ if (RT_SUCCESS(rc))
+ rc = RTStrCat(szPath, sizeof(szPath), s_apszSuffixes[iSuff]);
+ if (RT_SUCCESS(rc))
+ {
+# ifdef RT_OS_WINDOWS
+ RTPathChangeToDosSlashes(pszEndPath, false);
+# endif
+ rc = RTLdrLoadEx(szPath, &ahMods[iLib], RTLDRLOAD_FLAGS_NT_SEARCH_DLL_LOAD_DIR , NULL);
+ if (RT_SUCCESS(rc))
+ {
+ RTMEM_WILL_LEAK(ahMods[iLib]);
+ break;
+ }
+ }
+ }
+# ifdef RT_OS_SOLARIS
+ /*
+ * HACK: Solaris may keep libapr.so separately from svn, so do a separate search for it.
+ */
+ /** @todo It would make a lot more sense to use the dlfcn.h machinery to figure
+ * out which libapr*.so* file was loaded into the process together with
+ * the two svn libraries and get a dlopen handle for it. We risk ending
+ * up with the completely wrong libapr here! */
+ if (iLib == RT_ELEMENTS(s_apszLibraries) - 1 && RT_FAILURE(rc))
+ {
+ ahMods[iLib] = NIL_RTLDRMOD;
+ for (unsigned iVar2 = 0; iVar2 < RT_ELEMENTS(s_aVariations) && ahMods[iLib] == NIL_RTLDRMOD; iVar2++)
+ for (unsigned iSuff2 = 0; iSuff2 < RT_ELEMENTS(s_apszSuffixes) && ahMods[iLib] == NIL_RTLDRMOD; iSuff2++)
+ {
+ *pszEndPath = '\0';
+ rc = RTPathAppend(szPath, sizeof(szPath), s_aVariations[iVar2].pszPrefix);
+ if (RT_SUCCESS(rc))
+ rc = RTStrCat(szPath, sizeof(szPath), s_apszLibraries[iLib]);
+ if (RT_SUCCESS(rc))
+ rc = RTStrCat(szPath, sizeof(szPath), s_aVariations[iVar2].pszSuffix);
+ if (RT_SUCCESS(rc))
+ rc = RTStrCat(szPath, sizeof(szPath), s_apszSuffixes[iSuff2]);
+ if (RT_SUCCESS(rc))
+ rc = RTLdrLoadEx(szPath, &ahMods[iLib], RTLDRLOAD_FLAGS_NT_SEARCH_DLL_LOAD_DIR, NULL);
+ if (RT_SUCCESS(rc))
+ RTMEM_WILL_LEAK(ahMods[iLib]);
+ else
+ ahMods[iLib] = NIL_RTLDRMOD;
+ }
+ }
+# endif /* RT_OS_SOLARIS */
+ }
+ if (iLib == RT_ELEMENTS(s_apszLibraries) && RT_SUCCESS(rc))
+ {
+ static const struct
+ {
+ unsigned iLib;
+ const char *pszSymbol;
+ uintptr_t *ppfn; /**< The nothrow attrib of PFNRT goes down the wrong way with Clang 11, thus uintptr_t. */
+ } s_aSymbols[] =
+ {
+ { 2, "apr_initialize", (uintptr_t *)&g_pfnAprInitialize },
+ { 2, "apr_hash_first", (uintptr_t *)&g_pfnAprHashFirst },
+ { 2, "apr_hash_next", (uintptr_t *)&g_pfnAprHashNext },
+ { 2, "apr_hash_this_val", (uintptr_t *)&g_pfnAprHashThisVal },
+ { 1, "svn_pool_create_ex", (uintptr_t *)&g_pfnSvnPoolCreateEx },
+ { 2, "apr_pool_clear", (uintptr_t *)&g_pfnAprPoolClear },
+ { 2, "apr_pool_destroy", (uintptr_t *)&g_pfnAprPoolDestroy },
+ { 0, "svn_client_create_context", (uintptr_t *)&g_pfnSvnClientCreateContext },
+ { 0, "svn_client_propget4", (uintptr_t *)&g_pfnSvnClientPropGet4 },
+ };
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aSymbols); i++)
+ {
+ rc = RTLdrGetSymbol(ahMods[s_aSymbols[i].iLib], s_aSymbols[i].pszSymbol,
+ (void **)(uintptr_t)s_aSymbols[i].ppfn);
+ if (RT_FAILURE(rc))
+ {
+ ScmVerbose(NULL, 0, "Failed to resolve '%s' in '%s'",
+ s_aSymbols[i].pszSymbol, s_apszLibraries[s_aSymbols[i].iLib]);
+ break;
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ apr_status_t rcApr = g_pfnAprInitialize();
+ if (rcApr == 0)
+ {
+ ScmVerbose(NULL, 1, "Found subversion APIs.\n");
+ g_fSvnFunctionPointersValid = true;
+ }
+ else
+ {
+ ScmVerbose(NULL, 0, "apr_initialize failed: %#x (%d)\n", rcApr, rcApr);
+ AssertMsgFailed(("%#x (%d)\n", rc, rc));
+ }
+ return;
+ }
+ }
+
+ while (iLib-- > 0)
+ RTLdrClose(ahMods[iLib]);
+ }
+ }
+}
+#endif /* SCM_WITH_DYNAMIC_LIB_SVN */
+
+
+/**
+ * Finds the svn binary, updating g_szSvnPath and g_enmSvnVersion.
+ */
+static void scmSvnFindSvnBinary(PSCMRWSTATE pState)
+{
+ /* Already been called? */
+ if (g_szSvnPath[0] != '\0')
+ return;
+
+ /*
+ * Locate it.
+ */
+ /** @todo code page fun... */
+#ifdef RT_OS_WINDOWS
+ const char *pszEnvVar = RTEnvGet("Path");
+#else
+ const char *pszEnvVar = RTEnvGet("PATH");
+#endif
+ if (pszEnvVar)
+ {
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, g_szSvnPath, (void *)sizeof(g_szSvnPath));
+#else
+ int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, g_szSvnPath, (void *)sizeof(g_szSvnPath));
+#endif
+ if (RT_FAILURE(rc))
+ strcpy(g_szSvnPath, "svn");
+ }
+ else
+ strcpy(g_szSvnPath, "svn");
+
+ /*
+ * Check the version.
+ */
+ const char *apszArgs[] = { g_szSvnPath, "--version", "--quiet", NULL };
+ char *pszVersion;
+ int rc = scmSvnRunAndGetOutput(pState, apszArgs, false, &pszVersion);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszStripped = RTStrStrip(pszVersion);
+ if (RTStrVersionCompare(pszStripped, "1.8") >= 0)
+ g_enmSvnVersion = kScmSvnVersion_1_8;
+ else if (RTStrVersionCompare(pszStripped, "1.7") >= 0)
+ g_enmSvnVersion = kScmSvnVersion_1_7;
+ else if (RTStrVersionCompare(pszStripped, "1.6") >= 0)
+ g_enmSvnVersion = kScmSvnVersion_1_6;
+ else
+ g_enmSvnVersion = kScmSvnVersion_Ancient;
+ RTStrFree(pszVersion);
+ }
+ else
+ g_enmSvnVersion = kScmSvnVersion_Ancient;
+
+#ifdef SCM_WITH_DYNAMIC_LIB_SVN
+ /*
+ * If we got version 1.8 or later, try see if we can locate a few of the
+ * simpler SVN APIs.
+ */
+ g_fSvnFunctionPointersValid = false;
+ if (g_enmSvnVersion >= kScmSvnVersion_1_8)
+ scmSvnTryResolveFunctions();
+#endif
+}
+
+
+/**
+ * Construct a dot svn filename for the file being rewritten.
+ *
+ * @returns IPRT status code.
+ * @param pState The rewrite state (for the name).
+ * @param pszDir The directory, including ".svn/".
+ * @param pszSuff The filename suffix.
+ * @param pszDst The output buffer. RTPATH_MAX in size.
+ */
+static int scmSvnConstructName(PSCMRWSTATE pState, const char *pszDir, const char *pszSuff, char *pszDst)
+{
+ strcpy(pszDst, pState->pszFilename); /* ASSUMES sizeof(szBuf) <= sizeof(szPath) */
+ RTPathStripFilename(pszDst);
+
+ int rc = RTPathAppend(pszDst, RTPATH_MAX, pszDir);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPathAppend(pszDst, RTPATH_MAX, RTPathFilename(pState->pszFilename));
+ if (RT_SUCCESS(rc))
+ {
+ size_t cchDst = strlen(pszDst);
+ size_t cchSuff = strlen(pszSuff);
+ if (cchDst + cchSuff < RTPATH_MAX)
+ {
+ memcpy(&pszDst[cchDst], pszSuff, cchSuff + 1);
+ return VINF_SUCCESS;
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ }
+ return rc;
+}
+
+/**
+ * Interprets the specified string as decimal numbers.
+ *
+ * @returns true if parsed successfully, false if not.
+ * @param pch The string (not terminated).
+ * @param cch The string length.
+ * @param pu Where to return the value.
+ */
+static bool scmSvnReadNumber(const char *pch, size_t cch, size_t *pu)
+{
+ size_t u = 0;
+ while (cch-- > 0)
+ {
+ char ch = *pch++;
+ if (ch < '0' || ch > '9')
+ return false;
+ u *= 10;
+ u += ch - '0';
+ }
+ *pu = u;
+ return true;
+}
+
+
+#ifdef SCM_WITH_DYNAMIC_LIB_SVN
+
+/**
+ * Wrapper around RTPathAbs.
+ * @returns Same as RTPathAbs.
+ * @param pszPath The relative path.
+ * @param pszAbsPath Where to return the absolute path.
+ * @param cbAbsPath Size of the @a pszAbsPath buffer.
+ */
+static int scmSvnAbsPath(const char *pszPath, char *pszAbsPath, size_t cbAbsPath)
+{
+ int rc = RTPathAbs(pszPath, pszAbsPath, cbAbsPath);
+# if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ if (RT_SUCCESS(rc))
+ {
+ RTPathChangeToUnixSlashes(pszAbsPath, true /*fForce*/);
+ /* To avoid: svn: E235000: In file '..\..\..\subversion\libsvn_client\prop_commands.c' line 796: assertion failed (svn_dirent_is_absolute(target)) */
+ if (pszAbsPath[1] == ':')
+ pszAbsPath[0] = RT_C_TO_UPPER(pszAbsPath[0]);
+ }
+# endif
+ return rc;
+}
+
+
+/**
+ * Gets a client context and pool.
+ *
+ * This implements caching.
+ *
+ * @returns IPRT status code.
+ * @param ppCtx Where to return the context
+ * @param ppPool Where to return the pool.
+ */
+static int scmSvnGetClientContextAndPool(svn_client_ctx_t **ppCtx, apr_pool_t **ppPool)
+{
+ /*
+ * Use cached if present.
+ */
+ if (g_pSvnClientCtx && g_pSvnPool)
+ {
+ g_cSvnClientCtxUsed++;
+ *ppCtx = g_pSvnClientCtx;
+ *ppPool = g_pSvnPool;
+ return VINF_SUCCESS;
+ }
+ Assert(!g_pSvnClientCtx);
+ Assert(!g_pSvnPool);
+
+ /*
+ * Create new pool and context.
+ */
+ apr_pool_t *pPool = g_pfnSvnPoolCreateEx(NULL, NULL);
+ if (pPool)
+ {
+ svn_client_ctx_t *pCtx = NULL;
+ svn_error_t *pErr = g_pfnSvnClientCreateContext(&pCtx, pPool);
+ if (!pErr)
+ {
+ g_cSvnClientCtxUsed = 1;
+ g_pSvnClientCtx = *ppCtx = pCtx;
+ g_pSvnPool = *ppPool = pPool;
+ return VINF_SUCCESS;
+ }
+ g_pfnAprPoolDestroy(pPool);
+ }
+
+ *ppCtx = NULL;
+ *ppPool = NULL;
+ return VERR_GENERAL_FAILURE;
+}
+
+
+/**
+ * Puts back a client context and pool after use.
+ *
+ * @param pCtx The context.
+ * @param pPool The pool.
+ * @param fFlush Whether to flush it.
+ */
+static void scmSvnPutClientContextAndPool(svn_client_ctx_t *pCtx, apr_pool_t *pPool, bool fFlush)
+{
+ if (fFlush || g_cSvnClientCtxUsed > 4096) /* Disable this to force new context every time. */
+ {
+ g_pfnAprPoolDestroy(pPool);
+ g_pSvnPool = NULL;
+ g_pSvnClientCtx = NULL;
+ }
+ RT_NOREF(pCtx, fFlush);
+}
+
+
+/**
+ * Flushes the cached client context and pool
+ */
+static void scmSvnFlushClientContextAndPool(void)
+{
+ if (g_pSvnPool)
+ scmSvnPutClientContextAndPool(g_pSvnClientCtx, g_pSvnPool, true /*fFlush*/);
+ Assert(!g_pSvnPool);
+}
+
+
+/**
+ * Checks if @a pszPath exists in the current WC.
+ *
+ * @returns true, false or -1. In the latter case, please use the fallback.
+ * @param pszPath Path to the object that should be investigated.
+ */
+static int scmSvnIsObjectInWorkingCopy(const char *pszPath)
+{
+ /* svn_client_propget4 and later requires absolute target path. */
+ char szAbsPath[RTPATH_MAX];
+ int rc = scmSvnAbsPath(pszPath, szAbsPath, sizeof(szAbsPath));
+ if (RT_SUCCESS(rc))
+ {
+ apr_pool_t *pPool;
+ svn_client_ctx_t *pCtx = NULL;
+ rc = scmSvnGetClientContextAndPool(&pCtx, &pPool);
+ if (RT_SUCCESS(rc))
+ {
+ /* Make the call. */
+ apr_hash_t *pHash = NULL;
+ svn_opt_revision_t Rev;
+ RT_ZERO(Rev);
+ Rev.kind = svn_opt_revision_working;
+ Rev.value.number = -1L;
+ svn_error_t *pErr = g_pfnSvnClientPropGet4(&pHash, "svn:no-such-property", szAbsPath, &Rev, &Rev,
+ NULL /*pActualRev*/, svn_depth_empty, NULL /*pChangeList*/,
+ pCtx, pPool, pPool);
+ if (!pErr)
+ rc = true;
+ else if (pErr->apr_err == SVN_ERR_UNVERSIONED_RESOURCE)
+ rc = false;
+
+ scmSvnPutClientContextAndPool(pCtx, pPool, false);
+ }
+ }
+ return rc;
+}
+
+#endif /* SCM_WITH_DYNAMIC_LIB_SVN */
+
+
+/**
+ * Checks if the file we're operating on is part of a SVN working copy.
+ *
+ * @returns true if it is, false if it isn't or we cannot tell.
+ * @param pState The rewrite state to work on. Will use the
+ * fIsInSvnWorkingCopy member for caching the result.
+ */
+bool ScmSvnIsInWorkingCopy(PSCMRWSTATE pState)
+{
+ /*
+ * We don't ask SVN twice as that's expensive.
+ */
+ if (pState->fIsInSvnWorkingCopy != 0)
+ return pState->fIsInSvnWorkingCopy > 0;
+
+#ifdef SCM_WITH_DYNAMIC_LIB_SVN
+ if (g_fSvnFunctionPointersValid)
+ {
+ int rc = scmSvnIsObjectInWorkingCopy(pState->pszFilename);
+ if (rc == (int)true || rc == (int)false)
+ {
+ pState->fIsInSvnWorkingCopy = rc == (int)true ? 1 : -1;
+ return rc == (int)true;
+ }
+ }
+
+ /* Fallback: */
+#endif
+ if (g_enmSvnVersion < kScmSvnVersion_1_7)
+ {
+ /*
+ * Hack: check if the .svn/text-base/<file>.svn-base file exists.
+ */
+ char szPath[RTPATH_MAX];
+ int rc = scmSvnConstructName(pState, ".svn/text-base/", ".svn-base", szPath);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFileExists(szPath))
+ {
+ pState->fIsInSvnWorkingCopy = 1;
+ return true;
+ }
+ }
+ }
+ else
+ {
+ const char *apszArgs[] = { g_szSvnPath, "proplist", pState->pszFilename, NULL };
+ char *pszValue;
+ int rc = scmSvnRunAndGetOutput(pState, apszArgs, true, &pszValue);
+ if (RT_SUCCESS(rc))
+ {
+ RTStrFree(pszValue);
+ pState->fIsInSvnWorkingCopy = 1;
+ return true;
+ }
+ }
+ pState->fIsInSvnWorkingCopy = -1;
+ return false;
+}
+
+
+/**
+ * Checks if the specified directory is part of a SVN working copy.
+ *
+ * @returns true if it is, false if it isn't or we cannot tell.
+ * @param pszDir The directory in question.
+ */
+bool ScmSvnIsDirInWorkingCopy(const char *pszDir)
+{
+#ifdef SCM_WITH_DYNAMIC_LIB_SVN
+ if (g_fSvnFunctionPointersValid)
+ {
+ int rc = scmSvnIsObjectInWorkingCopy(pszDir);
+ if (rc == (int)true || rc == (int)false)
+ return rc == (int)true;
+ }
+
+ /* Fallback: */
+#endif
+ if (g_enmSvnVersion < kScmSvnVersion_1_7)
+ {
+ /*
+ * Hack: check if the .svn/ dir exists.
+ */
+ char szPath[RTPATH_MAX];
+ int rc = RTPathJoin(szPath, sizeof(szPath), pszDir, ".svn");
+ if (RT_SUCCESS(rc))
+ return RTDirExists(szPath);
+ }
+ else
+ {
+ const char *apszArgs[] = { g_szSvnPath, "propget", "svn:no-such-property", pszDir, NULL };
+ char *pszValue;
+ int rc = scmSvnRunAndGetOutput(NULL, apszArgs, true, &pszValue);
+ if (RT_SUCCESS(rc))
+ {
+ RTStrFree(pszValue);
+ return true;
+ }
+ }
+ return false;
+}
+
+
+#ifdef SCM_WITH_DYNAMIC_LIB_SVN
+/**
+ * Checks if @a pszPath exists in the current WC.
+ *
+ * @returns IPRT status code - VERR_NOT_SUPPORT if fallback should be attempted.
+ * @param pszPath Path to the object that should be investigated.
+ * @param pszProperty The property name.
+ * @param ppszValue Where to return the property value. Optional.
+ */
+static int scmSvnQueryPropertyUsingApi(const char *pszPath, const char *pszProperty, char **ppszValue)
+{
+ /* svn_client_propget4 and later requires absolute target path. */
+ char szAbsPath[RTPATH_MAX];
+ int rc = scmSvnAbsPath(pszPath, szAbsPath, sizeof(szAbsPath));
+ if (RT_SUCCESS(rc))
+ {
+ apr_pool_t *pPool;
+ svn_client_ctx_t *pCtx = NULL;
+ rc = scmSvnGetClientContextAndPool(&pCtx, &pPool);
+ if (RT_SUCCESS(rc))
+ {
+ /* Make the call. */
+ apr_hash_t *pHash = NULL;
+ svn_opt_revision_t Rev;
+ RT_ZERO(Rev);
+ Rev.kind = svn_opt_revision_working;
+ Rev.value.number = -1L;
+ svn_error_t *pErr = g_pfnSvnClientPropGet4(&pHash, pszProperty, szAbsPath, &Rev, &Rev,
+ NULL /*pActualRev*/, svn_depth_empty, NULL /*pChangeList*/,
+ pCtx, pPool, pPool);
+ if (!pErr)
+ {
+ /* Get the first value, if any. */
+ rc = VERR_NOT_FOUND;
+ apr_hash_index_t *pHashIdx = g_pfnAprHashFirst(pPool, pHash);
+ if (pHashIdx)
+ {
+ const char **ppszFirst = (const char **)g_pfnAprHashThisVal(pHashIdx);
+ if (ppszFirst && *ppszFirst)
+ {
+ if (ppszValue)
+ rc = RTStrDupEx(ppszValue, *ppszFirst);
+ else
+ rc = VINF_SUCCESS;
+ }
+ }
+ }
+ else if (pErr->apr_err == SVN_ERR_UNVERSIONED_RESOURCE)
+ rc = VERR_INVALID_STATE;
+ else
+ rc = VERR_GENERAL_FAILURE;
+
+ scmSvnPutClientContextAndPool(pCtx, pPool, false);
+ }
+ }
+ return rc;
+}
+#endif /* SCM_WITH_DYNAMIC_LIB_SVN */
+
+
+/**
+ * Queries the value of an SVN property.
+ *
+ * This will automatically adjust for scheduled changes.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_INVALID_STATE if not a SVN WC file.
+ * @retval VERR_NOT_FOUND if the property wasn't found.
+ * @param pState The rewrite state to work on.
+ * @param pszName The property name.
+ * @param ppszValue Where to return the property value. Free this
+ * using RTStrFree. Optional.
+ */
+int ScmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)
+{
+ int rc;
+
+ /*
+ * Look it up in the scheduled changes.
+ */
+ size_t i = pState->cSvnPropChanges;
+ while (i-- > 0)
+ if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
+ {
+ const char *pszValue = pState->paSvnPropChanges[i].pszValue;
+ if (!pszValue)
+ return VERR_NOT_FOUND;
+ if (ppszValue)
+ return RTStrDupEx(ppszValue, pszValue);
+ return VINF_SUCCESS;
+ }
+
+#ifdef SCM_WITH_DYNAMIC_LIB_SVN
+ if (g_fSvnFunctionPointersValid)
+ {
+ rc = scmSvnQueryPropertyUsingApi(pState->pszFilename, pszName, ppszValue);
+ if (rc != VERR_NOT_SUPPORTED)
+ return rc;
+ /* Fallback: */
+ }
+#endif
+
+ if (g_enmSvnVersion < kScmSvnVersion_1_7)
+ {
+ /*
+ * Hack: Read the .svn/props/<file>.svn-work file exists.
+ */
+ char szPath[RTPATH_MAX];
+ rc = scmSvnConstructName(pState, ".svn/props/", ".svn-work", szPath);
+ if (RT_SUCCESS(rc) && !RTFileExists(szPath))
+ rc = scmSvnConstructName(pState, ".svn/prop-base/", ".svn-base", szPath);
+ if (RT_SUCCESS(rc))
+ {
+ SCMSTREAM Stream;
+ rc = ScmStreamInitForReading(&Stream, szPath);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * The current format is K len\n<name>\nV len\n<value>\n" ... END.
+ */
+ rc = VERR_NOT_FOUND;
+ size_t const cchName = strlen(pszName);
+ SCMEOL enmEol;
+ size_t cchLine;
+ const char *pchLine;
+ while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
+ {
+ /*
+ * Parse the 'K num' / 'END' line.
+ */
+ if ( cchLine == 3
+ && !memcmp(pchLine, "END", 3))
+ break;
+ size_t cchKey;
+ if ( cchLine < 3
+ || pchLine[0] != 'K'
+ || pchLine[1] != ' '
+ || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey)
+ || cchKey == 0
+ || cchKey > 4096)
+ {
+ RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
+ rc = VERR_PARSE_ERROR;
+ break;
+ }
+
+ /*
+ * Match the key and skip to the value line. Don't bother with
+ * names containing EOL markers.
+ */
+ size_t const offKey = ScmStreamTell(&Stream);
+ bool fMatch = cchName == cchKey;
+ if (fMatch)
+ {
+ pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
+ if (!pchLine)
+ break;
+ fMatch = cchLine == cchName
+ && !memcmp(pchLine, pszName, cchName);
+ }
+
+ if (RT_FAILURE(ScmStreamSeekAbsolute(&Stream, offKey + cchKey)))
+ break;
+ if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
+ break;
+
+ /*
+ * Read and Parse the 'V num' line.
+ */
+ pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
+ if (!pchLine)
+ break;
+ size_t cchValue;
+ if ( cchLine < 3
+ || pchLine[0] != 'V'
+ || pchLine[1] != ' '
+ || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchValue)
+ || cchValue > _1M)
+ {
+ RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
+ rc = VERR_PARSE_ERROR;
+ break;
+ }
+
+ /*
+ * If we have a match, allocate a return buffer and read the
+ * value into it. Otherwise skip this value and continue
+ * searching.
+ */
+ if (fMatch)
+ {
+ if (!ppszValue)
+ rc = VINF_SUCCESS;
+ else
+ {
+ char *pszValue;
+ rc = RTStrAllocEx(&pszValue, cchValue + 1);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ScmStreamRead(&Stream, pszValue, cchValue);
+ if (RT_SUCCESS(rc))
+ *ppszValue = pszValue;
+ else
+ RTStrFree(pszValue);
+ }
+ }
+ break;
+ }
+
+ if (RT_FAILURE(ScmStreamSeekRelative(&Stream, cchValue)))
+ break;
+ if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
+ break;
+ }
+
+ if (RT_FAILURE(ScmStreamGetStatus(&Stream)))
+ {
+ rc = ScmStreamGetStatus(&Stream);
+ RTMsgError("%s: stream error %Rrc\n", szPath, rc);
+ }
+ ScmStreamDelete(&Stream);
+ }
+ }
+
+ if (rc == VERR_FILE_NOT_FOUND)
+ rc = VERR_NOT_FOUND;
+ }
+ else
+ {
+ const char *apszArgs[] = { g_szSvnPath, "propget", "--strict", pszName, pState->pszFilename, NULL };
+ char *pszValue;
+ rc = scmSvnRunAndGetOutput(pState, apszArgs, false, &pszValue);
+ if (RT_SUCCESS(rc))
+ {
+ if (pszValue && *pszValue)
+ {
+ if (ppszValue)
+ {
+ *ppszValue = pszValue;
+ pszValue = NULL;
+ }
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ RTStrFree(pszValue);
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Queries the value of an SVN property on the parent dir/whatever.
+ *
+ * This will not adjust for scheduled changes to the parent!
+ *
+ * @returns IPRT status code.
+ * @retval VERR_INVALID_STATE if not a SVN WC file.
+ * @retval VERR_NOT_FOUND if the property wasn't found.
+ * @param pState The rewrite state to work on.
+ * @param pszName The property name.
+ * @param ppszValue Where to return the property value. Free this
+ * using RTStrFree. Optional.
+ */
+int ScmSvnQueryParentProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)
+{
+ /*
+ * Strip the filename and use ScmSvnQueryProperty.
+ */
+ char szPath[RTPATH_MAX];
+ int rc = RTStrCopy(szPath, sizeof(szPath), pState->pszFilename);
+ if (RT_SUCCESS(rc))
+ {
+ RTPathStripFilename(szPath);
+ SCMRWSTATE ParentState;
+ ParentState.pszFilename = szPath;
+ ParentState.fFirst = false;
+ ParentState.fNeedsManualRepair = false;
+ ParentState.fIsInSvnWorkingCopy = true;
+ ParentState.cSvnPropChanges = 0;
+ ParentState.paSvnPropChanges = NULL;
+ ParentState.rc = VINF_SUCCESS;
+ rc = ScmSvnQueryProperty(&ParentState, pszName, ppszValue);
+ if (RT_SUCCESS(rc))
+ rc = ParentState.rc;
+ }
+ return rc;
+}
+
+
+/**
+ * Schedules the setting of a property.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_INVALID_STATE if not a SVN WC file.
+ * @param pState The rewrite state to work on.
+ * @param pszName The name of the property to set.
+ * @param pszValue The value. NULL means deleting it.
+ */
+int ScmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue)
+{
+ /*
+ * Update any existing entry first.
+ */
+ size_t i = pState->cSvnPropChanges;
+ while (i-- > 0)
+ if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
+ {
+ if (!pszValue)
+ {
+ RTStrFree(pState->paSvnPropChanges[i].pszValue);
+ pState->paSvnPropChanges[i].pszValue = NULL;
+ }
+ else
+ {
+ char *pszCopy;
+ int rc = RTStrDupEx(&pszCopy, pszValue);
+ if (RT_FAILURE(rc))
+ return rc;
+ pState->paSvnPropChanges[i].pszValue = pszCopy;
+ }
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Insert a new entry.
+ */
+ i = pState->cSvnPropChanges;
+ if ((i % 32) == 0)
+ {
+ void *pvNew = RTMemRealloc(pState->paSvnPropChanges, (i + 32) * sizeof(SCMSVNPROP));
+ if (!pvNew)
+ return VERR_NO_MEMORY;
+ pState->paSvnPropChanges = (PSCMSVNPROP)pvNew;
+ }
+
+ pState->paSvnPropChanges[i].pszName = RTStrDup(pszName);
+ pState->paSvnPropChanges[i].pszValue = pszValue ? RTStrDup(pszValue) : NULL;
+ if ( pState->paSvnPropChanges[i].pszName
+ && (pState->paSvnPropChanges[i].pszValue || !pszValue) )
+ pState->cSvnPropChanges = i + 1;
+ else
+ {
+ RTStrFree(pState->paSvnPropChanges[i].pszName);
+ pState->paSvnPropChanges[i].pszName = NULL;
+ RTStrFree(pState->paSvnPropChanges[i].pszValue);
+ pState->paSvnPropChanges[i].pszValue = NULL;
+ return VERR_NO_MEMORY;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Schedules a property deletion.
+ *
+ * @returns IPRT status code.
+ * @param pState The rewrite state to work on.
+ * @param pszName The name of the property to delete.
+ */
+int ScmSvnDelProperty(PSCMRWSTATE pState, const char *pszName)
+{
+ return ScmSvnSetProperty(pState, pszName, NULL);
+}
+
+
+/**
+ * Applies any SVN property changes to the work copy of the file.
+ *
+ * @returns IPRT status code.
+ * @param pState The rewrite state which SVN property changes
+ * should be applied.
+ */
+int ScmSvnDisplayChanges(PSCMRWSTATE pState)
+{
+ size_t i = pState->cSvnPropChanges;
+ while (i-- > 0)
+ {
+ const char *pszName = pState->paSvnPropChanges[i].pszName;
+ const char *pszValue = pState->paSvnPropChanges[i].pszValue;
+ if (pszValue)
+ ScmVerbose(pState, 0, "svn propset '%s' '%s' %s\n", pszName, pszValue, pState->pszFilename);
+ else
+ ScmVerbose(pState, 0, "svn propdel '%s' %s\n", pszName, pState->pszFilename);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Applies any SVN property changes to the work copy of the file.
+ *
+ * @returns IPRT status code.
+ * @param pState The rewrite state which SVN property changes
+ * should be applied.
+ */
+int ScmSvnApplyChanges(PSCMRWSTATE pState)
+{
+#ifdef SCM_WITH_LATER
+ if (0)
+ {
+ return ...;
+ }
+
+ /* Fallback: */
+#endif
+
+ /*
+ * Iterate thru the changes and apply them by starting the svn client.
+ */
+ for (size_t i = 0; i < pState->cSvnPropChanges; i++)
+ {
+ const char *apszArgv[6];
+ apszArgv[0] = g_szSvnPath;
+ apszArgv[1] = pState->paSvnPropChanges[i].pszValue ? "propset" : "propdel";
+ apszArgv[2] = pState->paSvnPropChanges[i].pszName;
+ int iArg = 3;
+ if (pState->paSvnPropChanges[i].pszValue)
+ apszArgv[iArg++] = pState->paSvnPropChanges[i].pszValue;
+ apszArgv[iArg++] = pState->pszFilename;
+ apszArgv[iArg++] = NULL;
+
+ int rc = scmSvnRun(pState, apszArgv, false);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Initializes the subversion interface.
+ */
+void ScmSvnInit(void)
+{
+ scmSvnFindSvnBinary(NULL);
+}
+
+
+void ScmSvnTerm(void)
+{
+#ifdef SCM_WITH_DYNAMIC_LIB_SVN
+ scmSvnFlushClientContextAndPool();
+#endif
+}
diff --git a/src/bldprogs/solgcc/.scm-settings b/src/bldprogs/solgcc/.scm-settings
new file mode 100644
index 00000000..04f23d1c
--- /dev/null
+++ b/src/bldprogs/solgcc/.scm-settings
@@ -0,0 +1,29 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for solgcc
+#
+
+#
+# Copyright (C) 2017-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program 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, in version 3 of the
+# License.
+#
+# This program 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 <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+/config/*: --external-copyright --no-fix-header-guards
+
diff --git a/src/bldprogs/solgcc/config/i386/sol2-gas.h b/src/bldprogs/solgcc/config/i386/sol2-gas.h
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/bldprogs/solgcc/config/i386/sol2-gas.h
diff --git a/src/bldprogs/solgcc/config/usegas.h b/src/bldprogs/solgcc/config/usegas.h
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/bldprogs/solgcc/config/usegas.h
diff --git a/src/bldprogs/test-gccplugin-2.c b/src/bldprogs/test-gccplugin-2.c
new file mode 100644
index 00000000..15ad61d2
--- /dev/null
+++ b/src/bldprogs/test-gccplugin-2.c
@@ -0,0 +1,57 @@
+/* $Id: test-gccplugin-2.c $ */
+/** @file
+ * Compiler plugin testcase \#2.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/* Only valid stuff in this one. */
+extern void MyIprtPrintf(const char *pszFormat, ...) __attribute__((__iprt_format__(1,2)));
+extern void foo(const char *pszFormat, ...);
+
+extern unsigned long long g_ull;
+
+typedef unsigned long long RTGCPHYS;
+extern RTGCPHYS g_GCPhys;
+
+typedef RTGCPHYS *PRTGCPHYS;
+extern PRTGCPHYS g_pGCPhys;
+
+#include <stdarg.h>
+
+void foo(const char *pszFormat, ...)
+{
+ MyIprtPrintf("%s %RX64 %RGp %p %RGp", "foobar", g_ull, g_GCPhys, g_pGCPhys, *g_pGCPhys);
+ MyIprtPrintf("%RX32 %d %s\n", 10, 42, "string");
+ MyIprtPrintf("%u %.8Rhxs %d\n", 10, &g_ull, 42);
+ MyIprtPrintf("%u %.8RhxD %d\n", 10, &g_ull, 42);
+ {
+ va_list va;
+ int iValue;
+ va_start(va, pszFormat);
+ iValue = va_arg(va, int);
+ va_end(va);
+ MyIprtPrintf("%u\n", iValue);
+ }
+}
+
diff --git a/src/bldprogs/test-gccplugin-3.c b/src/bldprogs/test-gccplugin-3.c
new file mode 100644
index 00000000..cc982560
--- /dev/null
+++ b/src/bldprogs/test-gccplugin-3.c
@@ -0,0 +1,47 @@
+/* $Id: test-gccplugin-3.c $ */
+/** @file
+ * Compiler plugin testcase \#3.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/* Only valid stuff in this one. */
+#if defined(__i386__) || defined(_M_IX86) || defined(__X86__)
+# define RTCALL __attribute__((__cdecl__,__regparm__(0)))
+#else
+# define RTCALL
+#endif
+typedef struct HELPERS
+{
+ void (RTCALL * pfnPrintf)(struct HELPERS *pHlp, const char *pszFormat, ...)
+ __attribute__((__iprt_format__(2, 3)));
+} HELPERS;
+
+extern void foo(struct HELPERS *pHlp);
+
+
+void foo(struct HELPERS *pHlp)
+{
+ pHlp->pfnPrintf(pHlp, "%36 %#x %#x", "string", 42, 42); /// @todo missing 's', need to detect this.
+}
+
diff --git a/src/bldprogs/test-gccplugin.c b/src/bldprogs/test-gccplugin.c
new file mode 100644
index 00000000..e121fa71
--- /dev/null
+++ b/src/bldprogs/test-gccplugin.c
@@ -0,0 +1,36 @@
+/* $Id: test-gccplugin.c $ */
+/** @file
+ * Compiler plugin testcase \#2.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program 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, in version 3 of the
+ * License.
+ *
+ * This program 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+extern void MyIprtPrintf(const char *pszFormat, ...) __attribute__((__iprt_format__(1,2)));
+extern void foo(void);
+
+void foo(void)
+{
+ MyIprtPrintf(0);
+ MyIprtPrintf("%RX32 %d %s\n", 10, 42, "string");
+}
+