summaryrefslogtreecommitdiff
path: root/src/bldprogs
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@baserock.org>2012-10-26 16:25:44 +0000
committer <>2012-11-12 12:15:52 +0000
commit58ed4748338f9466599adfc8a9171280ed99e23f (patch)
tree02027d99ded4fb56a64aa9489ac2eb487e7858ab /src/bldprogs
downloadVirtualBox-58ed4748338f9466599adfc8a9171280ed99e23f.tar.gz
Imported from /home/lorry/working-area/delta_VirtualBox/VirtualBox-4.2.4.tar.bz2.VirtualBox-4.2.4
Diffstat (limited to 'src/bldprogs')
-rw-r--r--src/bldprogs/Makefile.kmk65
-rw-r--r--src/bldprogs/VBoxCPP.cpp5501
-rw-r--r--src/bldprogs/VBoxCmp.cpp131
-rw-r--r--src/bldprogs/VBoxPeSetVersion.cpp82
-rw-r--r--src/bldprogs/VBoxTpG.cpp2471
-rw-r--r--src/bldprogs/bin2c.c249
-rw-r--r--src/bldprogs/biossums.c229
-rwxr-xr-xsrc/bldprogs/checkUndefined.sh85
-rw-r--r--src/bldprogs/deftoimp.sed57
-rw-r--r--src/bldprogs/filesplitter.cpp369
-rw-r--r--src/bldprogs/preload.cpp211
-rw-r--r--src/bldprogs/scm.cpp1613
-rw-r--r--src/bldprogs/scm.h226
-rw-r--r--src/bldprogs/scmdiff.cpp439
-rw-r--r--src/bldprogs/scmdiff.h61
-rw-r--r--src/bldprogs/scmrw.cpp460
-rw-r--r--src/bldprogs/scmstream.cpp1362
-rw-r--r--src/bldprogs/scmstream.h136
-rw-r--r--src/bldprogs/scmsubversion.cpp1063
19 files changed, 14810 insertions, 0 deletions
diff --git a/src/bldprogs/Makefile.kmk b/src/bldprogs/Makefile.kmk
new file mode 100644
index 00000000..95cb2973
--- /dev/null
+++ b/src/bldprogs/Makefile.kmk
@@ -0,0 +1,65 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for various generic build tools (there is currently only one of them).
+#
+
+#
+# Copyright (C) 2006-2012 Oracle Corporation
+#
+# This file is part of VirtualBox Open Source Edition (OSE), as
+# available from http://www.virtualbox.org. This file is free software;
+# you can redistribute it and/or modify it under the terms of the GNU
+# General Public License (GPL) as published by the Free Software
+# Foundation, in version 2 as it comes in the "COPYING" file of the
+# VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+#
+
+SUB_DEPTH = ../..
+include $(KBUILD_PATH)/subheader.kmk
+
+BLDPROGS += bin2c biossums filesplitter VBoxCmp
+
+bin2c_TEMPLATE = VBOXBLDPROG
+bin2c_SOURCES = bin2c.c
+
+biossums_TEMPLATE = VBOXBLDPROG
+biossums_SOURCES = biossums.c
+
+filesplitter_TEMPLATE = VBOXBLDPROG
+filesplitter_SOURCES = filesplitter.cpp
+
+VBoxCmp_TEMPLATE = VBOXBLDPROG
+VBoxCmp_SOURCES = VBoxCmp.cpp
+
+ifndef VBOX_ONLY_BUILD
+ BLDPROGS += scm
+ scm_TEMPLATE = VBoxAdvBldProg
+ scm_SOURCES = \
+ scm.cpp \
+ scmdiff.cpp \
+ scmrw.cpp \
+ scmstream.cpp \
+ scmsubversion.cpp
+
+ 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
+
+BLDPROGS.win += VBoxPeSetVersion
+VBoxPeSetVersion_TEMPLATE = VBOXBLDPROG
+VBoxPeSetVersion_SOURCES = VBoxPeSetVersion.cpp
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/bldprogs/VBoxCPP.cpp b/src/bldprogs/VBoxCPP.cpp
new file mode 100644
index 00000000..7cc08171
--- /dev/null
+++ b/src/bldprogs/VBoxCPP.cpp
@@ -0,0 +1,5501 @@
+/* $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 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*******************************************************************************
+* 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)
+{
+ 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
+ *
+ *
+ */
+
+
+/**
+ * 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))
+ {
+ ch = chPrev;
+ ch = ScmStreamGetCh(pStrmInput);
+ Assert(ch == chPrev);
+ }
+ else
+ break;
+ }
+}
+
+
+/**
+ * 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.
+ * @param ch The first character.
+ */
+static RTEXITCODE vbcppProcessIdentifier(PVBCPP pThis, PSCMSTREAM pStrmInput, char ch)
+{
+ 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)
+{
+ /*
+ * 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;
+
+ /*
+ * 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)) )
+ {
+ 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))
+ 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');
+ }
+ }
+
+ 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);
+ }
+ 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_OFFSETOF(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_OFFSETOF(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)
+{
+ /*
+ * 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)
+{
+ /*
+ * 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)
+{
+ 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; break;
+ 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)
+{
+ char ch = *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);
+ 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)
+{
+ 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);
+ 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);
+ 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)
+{
+ /*
+ * 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 == '<' ? '>' : '"';
+ unsigned chPrev = ch;
+ 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)
+{
+ /*
+ * 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)
+{
+ 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)
+{
+ 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)
+{
+ 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, ch);
+ 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_OFFSETOF(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: 79724 $";
+ 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/VBoxCmp.cpp b/src/bldprogs/VBoxCmp.cpp
new file mode 100644
index 00000000..e0a3ffdb
--- /dev/null
+++ b/src/bldprogs/VBoxCmp.cpp
@@ -0,0 +1,131 @@
+/* $Id: VBoxCmp.cpp $ */
+/** @file
+ * File Compare - Compares two files byte by byte.
+ */
+
+/*
+ * Copyright (C) 2006-2012 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*******************************************************************************
+* 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/VBoxPeSetVersion.cpp b/src/bldprogs/VBoxPeSetVersion.cpp
new file mode 100644
index 00000000..0dc8b21d
--- /dev/null
+++ b/src/bldprogs/VBoxPeSetVersion.cpp
@@ -0,0 +1,82 @@
+/* $Id: VBoxPeSetVersion.cpp $ */
+/** @file
+ * IPRT - Change the OS and SubSystem version to 4.0 (VS2010 trick).
+ */
+
+/*
+ * Copyright (C) 2012 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include <Windows.h>
+#include <stdio.h>
+#include <string.h>
+
+
+/** @todo Rewrite this so it can take options and print out error messages. */
+int main(int argc, char **argv)
+{
+ if (argc != 2)
+ return 30;
+ FILE *pFile = fopen(argv[1], "r+b");
+ if (!pFile)
+ return 1;
+ IMAGE_DOS_HEADER MzHdr;
+ if (fread(&MzHdr, sizeof(MzHdr), 1, pFile) != 1)
+ return 2;
+
+ if (fseek(pFile, MzHdr.e_lfanew, SEEK_SET) != 0)
+ return 3;
+
+ IMAGE_NT_HEADERS32 NtHdrs;
+ if (fread(&NtHdrs, sizeof(NtHdrs), 1, pFile) != 1)
+ return 4;
+ if (NtHdrs.Signature != IMAGE_NT_SIGNATURE)
+ return 5;
+ if (NtHdrs.FileHeader.Machine != IMAGE_FILE_MACHINE_I386)
+ return 6;
+ if (NtHdrs.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC)
+ return 7;
+
+ if (NtHdrs.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC)
+ return 7;
+
+ IMAGE_NT_HEADERS32 NtHdrsNew = NtHdrs;
+ if (NtHdrsNew.OptionalHeader.MajorOperatingSystemVersion > 4)
+ {
+ NtHdrsNew.OptionalHeader.MajorOperatingSystemVersion = 4;
+ NtHdrsNew.OptionalHeader.MinorOperatingSystemVersion = 0;
+ }
+ if (NtHdrsNew.OptionalHeader.MajorSubsystemVersion > 4)
+ {
+ NtHdrsNew.OptionalHeader.MajorSubsystemVersion = 4;
+ NtHdrsNew.OptionalHeader.MinorSubsystemVersion = 0;
+ }
+
+ if (memcmp(&NtHdrsNew, &NtHdrs, sizeof(NtHdrs)))
+ {
+ /** @todo calc checksum. */
+ NtHdrsNew.OptionalHeader.CheckSum = 0;
+
+ if (fseek(pFile, MzHdr.e_lfanew, SEEK_SET) != 0)
+ return 10;
+ if (fwrite(&NtHdrsNew, sizeof(NtHdrsNew), 1, pFile) != 1)
+ return 11;
+ }
+
+ if (fclose(pFile) != 0)
+ return 29;
+ return 0;
+}
+
diff --git a/src/bldprogs/VBoxTpG.cpp b/src/bldprogs/VBoxTpG.cpp
new file mode 100644
index 00000000..9c0d851a
--- /dev/null
+++ b/src/bldprogs/VBoxTpG.cpp
@@ -0,0 +1,2471 @@
+/* $Id: VBoxTpG.cpp $ */
+/** @file
+ * VBox Build Tool - VBox Tracepoint Generator.
+ */
+
+/*
+ * Copyright (C) 2012 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include <VBox/VBoxTpG.h>
+
+#include <iprt/alloca.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/env.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/process.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+
+#include "scmstream.h"
+
+
+/*******************************************************************************
+* Structures and Typedefs *
+*******************************************************************************/
+
+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;
+} 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;
+} VTGPROBE;
+typedef VTGPROBE *PVTGPROBE;
+
+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 *pszAssembler = "nasm.exe";
+static const char *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_OFFSETOF(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"
+ "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"
+ " %%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.5', 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"
+ ,
+ 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);
+ 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 fMachO64 = g_cBits == 64 && !strcmp(g_pszAssemblerFmtVal, "macho64");
+ bool const fMachO32 = g_cBits == 32 && !strcmp(g_pszAssemblerFmtVal, "macho32");
+ ScmStreamPrintf(pStrm,
+ "\n"
+ ";\n"
+ "; Prob stubs.\n"
+ ";\n"
+ "BEGINCODE\n"
+ "extern %sNAME(%s)\n",
+ g_fProbeFnImported ? "IMP" : "",
+ g_pszProbeFnName);
+ if (fMachO64 && g_fProbeFnImported && !g_fPic)
+ ScmStreamPrintf(pStrm,
+ "g_pfnVtgProbeFn:\n"
+ " dq 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 ?
+ " jmp %s wrt ..plt\n"
+ : g_fProbeFnImported ?
+ " mov ecx, IMP2(%s)\n"
+ " jmp ecx\n"
+ :
+ " jmp NAME(%s)\n"
+ , g_pszProbeFnName);
+ else if (fWin64)
+ ScmStreamPrintf(pStrm, g_fProbeFnImported ?
+ " mov rax, IMP2(%s)\n"
+ " jmp rax\n"
+ :
+ " jmp NAME(%s)\n"
+ , g_pszProbeFnName);
+ else if (fMachO64 && g_fProbeFnImported)
+ ScmStreamPrintf(pStrm,
+ " jmp [g_pfnVtgProbeFn wrt rip]\n");
+ else
+ ScmStreamPrintf(pStrm, g_fPic ?
+ " jmp [rel %s wrt ..got]\n"
+ : g_fProbeFnImported ?
+ " lea rax, [IMP2(%s)]\n"
+ " jmp rax\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;
+}
+
+
+/**
+ * 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"
+ "\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)
+ {
+ RTListForEach(&pProv->ProbeHead, pProbe, VTGPROBE, ListEntry)
+ {
+ PVTGARG const pFirstArg = RTListGetFirst(&pProbe->ArgHead, VTGARG, ListEntry);
+
+ ScmStreamPrintf(pStrm,
+ "extern uint32_t 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() \\\n"
+ " (RT_UNLIKELY(g_cVTGProbeEnabled_%s_%s)) \n"
+ "# define %s("
+ , 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)
+ {
+ RTListForEach(&pProv->ProbeHead, pProbe, VTGPROBE, ListEntry)
+ {
+ generateProbeDefineName(szTmp, sizeof(szTmp), pProv->pszName, pProbe->pszMangledName);
+ ScmStreamPrintf(pStrm,
+ "# define %s_ENABLED() (false)\n"
+ "# define %s("
+ , 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)
+ {
+ PVTGARG const pFirstArg = RTListGetFirst(&pProbe->ArgHead, VTGARG, 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 (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 cb The offset from the current position to the
+ * point of failure.
+ * @param pszMsg The message to display.
+ */
+static RTEXITCODE parseError(PSCMSTREAM pStrm, size_t cb, const char *pszMsg)
+{
+ if (cb)
+ ScmStreamSeekRelative(pStrm, -(ssize_t)cb);
+ 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: %s.\n", g_pszScript, iLine + 1, off - offLine + 1, pszMsg);
+
+ 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 cb The offset from the current position to the
+ * point of failure.
+ * @param pszMsg The message to display.
+ */
+static RTEXITCODE parseErrorAbs(PSCMSTREAM pStrm, size_t off, const char *pszMsg)
+{
+ ScmStreamSeekAbsolute(pStrm, off);
+ return parseError(pStrm, 0, pszMsg);
+}
+
+/**
+ * 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.
+ */
+static uint32_t parseTypeExpression(const char *pszType)
+{
+ 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] == '*') return VTG_TYPE_POINTER;
+ if (pszType[cchType - 1] == '&')
+ {
+ RTMsgWarning("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")
+ )
+ RTMsgWarning("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).
+ * @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);
+
+ 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)
+{
+ /*
+ * 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->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);
+ pProbe->cArgs++;
+
+ 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: 80390 $";
+ 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..a0a973ce
--- /dev/null
+++ b/src/bldprogs/bin2c.c
@@ -0,0 +1,249 @@
+/* $Id: bin2c.c $ */
+/** @file
+ * bin2c - Binary 2 C Structure Converter.
+ */
+
+/*
+ * Copyright (C) 2006-2007 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+/*******************************************************************************
+* 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"
+ " -ascii show ASCII representation of binary as comment\n",
+ argv0);
+
+ 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 fExport = 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"))
+ {
+ if (++iArg >= argc)
+ return usage(argv[0]);
+ cbMin = 1024 * strtoul(argv[iArg], NULL, 0);
+ }
+ else if (!strcmp(argv[iArg], "-max"))
+ {
+ if (++iArg >= argc)
+ return usage(argv[0]);
+ cbMax = 1024 * strtoul(argv[iArg], NULL, 0);
+ }
+ else if (!strcmp(argv[iArg], "-mask"))
+ {
+ if (++iArg >= argc)
+ return usage(argv[0]);
+ uMask = strtoul(argv[iArg], NULL, 0);
+ }
+ else if (!strcmp(argv[iArg], "-ascii"))
+ {
+ fAscii = 1;
+ }
+ else if (!strcmp(argv[iArg], "-export"))
+ {
+ fExport = 1;
+ }
+ else if (!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"))
+ {
+ 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], "wb");
+ 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], fExport ? "DECLEXPORT(" : "", 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"
+ "\n"
+ "%sconst unsigned%s g_cb%s = sizeof(g_ab%s);\n"
+ "/* end of file */\n",
+ fExport ? "DECLEXPORT(" : "", fExport ? ")" : "", argv[iArg], argv[iArg]);
+
+ /* 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..e179f649
--- /dev/null
+++ b/src/bldprogs/biossums.c
@@ -0,0 +1,229 @@
+/* $Id: biossums.c $ */
+/** @file
+ * Tool for modifying a BIOS image to write the BIOS checksum.
+ */
+
+/*
+ * Copyright (C) 2006-2007 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#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;
+ }
+ }
+
+ /* 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..aeca4f9d
--- /dev/null
+++ b/src/bldprogs/checkUndefined.sh
@@ -0,0 +1,85 @@
+#!/bin/sh
+
+#
+# Copyright (C) 2006-2010 Oracle Corporation
+#
+# This file is part of VirtualBox Open Source Edition (OSE), as
+# available from http://www.virtualbox.org. This file is free software;
+# you can redistribute it and/or modify it under the terms of the GNU
+# General Public License (GPL) as published by the Free Software
+# Foundation, in version 2 as it comes in the "COPYING" file of the
+# VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+#
+
+#
+# Compare undefined symbols in a shared or static object against a new-line
+# separated list of grep patterns in a text file.
+#
+# Usage: /bin/sh <script name> <object> <allowed undefined symbols> [--static]
+#
+# Currently only works for native objects on Linux platforms
+#
+
+echoerr()
+{
+ echo $* 1>&2
+}
+
+hostos=$1
+target=$2
+symbols=$3
+static=$4
+
+if test $# -lt 3 || test $# -gt 4 || test ! -r "$target" || test ! -r "$symbols"; then
+ if test ! -r "$target"; then
+ echoerr "$0: '$target' not readable"
+ elif test ! -r "$symbols"; then
+ echoerr "$0: '$symbols' not readable"
+ else
+ echoerr "$0: Wrong number of arguments"
+ fi
+ args_ok="no"
+fi
+
+if test $# -eq 4 && test "$static" != "--static"; then
+ args_ok="no"
+fi
+
+if test "$args_ok" = "no"; then
+ echoerr "Usage: $0 <object> <allowed undefined symbols> [--static]"
+ 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 | $grepbin '*UND*' | $grepbin -v -f $symbols | kmk_sed -e 's/^.*[[:blank:]]\(.*\)/\1/'`
+num_undef=`echo $undefined | wc -w`
+
+if test $num_undef -ne 0; then
+ echoerr "$0: following symbols not defined in $symbols:"
+ 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..56b4de32
--- /dev/null
+++ b/src/bldprogs/deftoimp.sed
@@ -0,0 +1,57 @@
+# $Id: deftoimp.sed $
+## @file
+# SED script for generating a dummy .c from a windows .def file.
+#
+
+#
+#
+# Copyright (C) 2006-2010 Oracle Corporation
+#
+# This file is part of VirtualBox Open Source Edition (OSE), as
+# available from http://www.virtualbox.org. This file is free software;
+# you can redistribute it and/or modify it under the terms of the GNU
+# General Public License (GPL) as published by the Free Software
+# Foundation, in version 2 as it comes in the "COPYING" file of the
+# VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+#
+
+#
+# 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..487e044d
--- /dev/null
+++ b/src/bldprogs/filesplitter.cpp
@@ -0,0 +1,369 @@
+/* $Id: filesplitter.cpp $ */
+/** @file
+ * File splitter - Splits a text file according to ###### markers in it.
+ */
+
+/*
+ * Copyright (C) 2006-2012 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*******************************************************************************
+* 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)
+ return printErr("Failed to open \"%s\" for writing the file list");
+
+ 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)
+ return printErr("Failed to open \"%s\" for writing: %s\n", pcszFilename, strerror(errno));
+
+ 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/preload.cpp b/src/bldprogs/preload.cpp
new file mode 100644
index 00000000..07def9a0
--- /dev/null
+++ b/src/bldprogs/preload.cpp
@@ -0,0 +1,211 @@
+/* $Id: preload.cpp $ */
+/** @file
+ * bin2c - Binary 2 C Structure Converter.
+ */
+
+/*
+ * Copyright (C) 2006-2007 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+/*******************************************************************************
+* 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: 60692 $\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..5e5d69d1
--- /dev/null
+++ b/src/bldprogs/scm.cpp
@@ -0,0 +1,1613 @@
+/* $Id: scm.cpp $ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager.
+ */
+
+/*
+ * Copyright (C) 2010-2012 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+/*******************************************************************************
+* 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_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_TAB_SIZE,
+ SCMOPT_FILTER_OUT_DIRS,
+ SCMOPT_FILTER_FILES,
+ SCMOPT_FILTER_OUT_FILES,
+ SCMOPT_LAST_SETTINGS = SCMOPT_FILTER_OUT_FILES,
+ //
+ 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_END
+} SCMOPT;
+
+
+/*******************************************************************************
+* Global Variables *
+*******************************************************************************/
+const char g_szTabSpaces[16+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;
+
+/** The global settings. */
+static SCMSETTINGSBASE const g_Defaults =
+{
+ /* .fConvertEol = */ true,
+ /* .fConvertTabs = */ true,
+ /* .fForceFinalEol = */ true,
+ /* .fForceTrailingLine = */ false,
+ /* .fStripTrailingBlanks = */ true,
+ /* .fStripTrailingLines = */ true,
+ /* .fOnlySvnFiles = */ false,
+ /* .fOnlySvnDirs = */ false,
+ /* .fSetSvnEol = */ false,
+ /* .fSetSvnExecutable = */ false,
+ /* .fSetSvnKeywords = */ false,
+ /* .cchTab = */ 8,
+ /* .pszFilterFiles = */ (char *)"",
+ /* .pszFilterOutFiles = */ (char *)"*.exe|*.com|20*-*-*.log",
+ /* .pszFilterOutDirs = */ (char *)".svn|.hg|.git|CVS",
+};
+
+/** Option definitions for the base settings. */
+static RTGETOPTDEF g_aScmOpts[] =
+{
+ { "--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 },
+ { "--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 },
+ { "--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 },
+ { "--tab-size", SCMOPT_TAB_SIZE, RTGETOPT_REQ_UINT8 },
+ { "--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 },
+};
+
+/** Consider files matching the following patterns (base names only). */
+static const char *g_pszFileFilter = NULL;
+
+static PFNSCMREWRITER const g_aRewritersFor_Makefile_kup[] =
+{
+ rewrite_SvnNoExecutable,
+ rewrite_Makefile_kup
+};
+
+static PFNSCMREWRITER const g_aRewritersFor_Makefile_kmk[] =
+{
+ rewrite_ForceNativeEol,
+ rewrite_StripTrailingBlanks,
+ rewrite_AdjustTrailingLines,
+ rewrite_SvnNoExecutable,
+ rewrite_SvnKeywords,
+ rewrite_Makefile_kmk
+};
+
+static PFNSCMREWRITER const g_aRewritersFor_C_and_CPP[] =
+{
+ rewrite_ForceNativeEol,
+ rewrite_ExpandTabs,
+ rewrite_StripTrailingBlanks,
+ rewrite_AdjustTrailingLines,
+ rewrite_SvnNoExecutable,
+ rewrite_SvnKeywords,
+ rewrite_C_and_CPP
+};
+
+static PFNSCMREWRITER const g_aRewritersFor_H_and_HPP[] =
+{
+ rewrite_ForceNativeEol,
+ rewrite_ExpandTabs,
+ rewrite_StripTrailingBlanks,
+ rewrite_AdjustTrailingLines,
+ rewrite_SvnNoExecutable,
+ rewrite_C_and_CPP
+};
+
+static PFNSCMREWRITER const g_aRewritersFor_RC[] =
+{
+ rewrite_ForceNativeEol,
+ rewrite_ExpandTabs,
+ rewrite_StripTrailingBlanks,
+ rewrite_AdjustTrailingLines,
+ rewrite_SvnNoExecutable,
+ rewrite_SvnKeywords
+};
+
+static PFNSCMREWRITER const g_aRewritersFor_ShellScripts[] =
+{
+ rewrite_ForceLF,
+ rewrite_ExpandTabs,
+ rewrite_StripTrailingBlanks
+};
+
+static PFNSCMREWRITER const g_aRewritersFor_BatchFiles[] =
+{
+ rewrite_ForceCRLF,
+ rewrite_ExpandTabs,
+ rewrite_StripTrailingBlanks
+};
+
+static SCMCFGENTRY const g_aConfigs[] =
+{
+ { RT_ELEMENTS(g_aRewritersFor_Makefile_kup), &g_aRewritersFor_Makefile_kup[0], "Makefile.kup" },
+ { RT_ELEMENTS(g_aRewritersFor_Makefile_kmk), &g_aRewritersFor_Makefile_kmk[0], "Makefile.kmk|Config.kmk" },
+ { RT_ELEMENTS(g_aRewritersFor_C_and_CPP), &g_aRewritersFor_C_and_CPP[0], "*.c|*.cpp|*.C|*.CPP|*.cxx|*.cc" },
+ { RT_ELEMENTS(g_aRewritersFor_H_and_HPP), &g_aRewritersFor_H_and_HPP[0], "*.h|*.hpp" },
+ { RT_ELEMENTS(g_aRewritersFor_RC), &g_aRewritersFor_RC[0], "*.rc" },
+ { RT_ELEMENTS(g_aRewritersFor_ShellScripts), &g_aRewritersFor_ShellScripts[0], "*.sh|configure" },
+ { RT_ELEMENTS(g_aRewritersFor_BatchFiles), &g_aRewritersFor_BatchFiles[0], "*.bat|*.cmd|*.btm|*.vbs|*.ps1" },
+};
+
+
+
+/* -=-=-=-=-=- settings -=-=-=-=-=- */
+
+
+/**
+ * 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))
+ return VINF_SUCCESS;
+
+ RTStrFree(pSettings->pszFilterOutFiles);
+ }
+ RTStrFree(pSettings->pszFilterFiles);
+ }
+
+ pSettings->pszFilterFiles = NULL;
+ pSettings->pszFilterOutFiles = NULL;
+ pSettings->pszFilterOutDirs = 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 != ~(unsigned)0);
+ pSettings->cchTab = ~(unsigned)0;
+
+ RTStrFree(pSettings->pszFilterFiles);
+ pSettings->pszFilterFiles = NULL;
+
+ RTStrFree(pSettings->pszFilterOutFiles);
+ pSettings->pszFilterOutFiles = NULL;
+
+ RTStrFree(pSettings->pszFilterOutDirs);
+ pSettings->pszFilterOutDirs = NULL;
+ }
+}
+
+
+/**
+ * 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.
+ */
+static int scmSettingsBaseHandleOpt(PSCMSETTINGSBASE pSettings, int rc, PRTGETOPTUNION pValueUnion)
+{
+ 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_STRIP_TRAILING_LINES:
+ pSettings->fStripTrailingLines = true;
+ return VINF_SUCCESS;
+ case SCMOPT_NO_STRIP_TRAILING_LINES:
+ pSettings->fStripTrailingLines = 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_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_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;
+
+ return RTStrAAppendExN(ppsz, 2,
+ "|", *ppsz && **ppsz ? 1 : 0,
+ pszSrc, cchSrc);
+ }
+
+ 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.
+ */
+static int scmSettingsBaseParseString(PSCMSETTINGSBASE pBase, const char *pszLine)
+{
+ int cArgs;
+ char **papszArgs;
+ int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, 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);
+ 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.
+ */
+static int scmSettingsBaseParseStringN(PSCMSETTINGSBASE pBase, const char *pchLine, size_t cchLine)
+{
+ char *pszLine = RTStrDupN(pchLine, cchLine);
+ if (!pszLine)
+ return VERR_NO_MEMORY;
+ int rc = scmSettingsBaseParseString(pBase, pszLine);
+ 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);
+ 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. */
+ 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);
+ pSettings->paPairs[i].pszPattern = NULL;
+ pSettings->paPairs[i].pszOptions = 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.
+ */
+static int scmSettingsAddPair(PSCMSETTINGS pSettings, const char *pchLine, size_t cchLine)
+{
+ /*
+ * Split the string.
+ */
+ const char *pchOptions = (const char *)memchr(pchLine, ':', cchLine);
+ if (!pchOptions)
+ return VERR_INVALID_PARAMETER;
+ size_t cchPattern = pchOptions - pchLine;
+ size_t cchOptions = cchLine - cchPattern - 1;
+ pchOptions++;
+
+ /* strip spaces everywhere */
+ while (cchPattern > 0 && RT_C_IS_SPACE(pchLine[cchPattern - 1]))
+ cchPattern--;
+ while (cchPattern > 0 && RT_C_IS_SPACE(*pchLine))
+ cchPattern--, pchLine++;
+
+ 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;
+
+ /*
+ * Add 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);
+ int rc;
+ if ( pSettings->paPairs[iPair].pszPattern
+ && pSettings->paPairs[iPair].pszOptions)
+ rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions);
+ else
+ rc = VERR_NO_MEMORY;
+ if (RT_SUCCESS(rc))
+ pSettings->cPairs = iPair + 1;
+ else
+ {
+ RTStrFree(pSettings->paPairs[iPair].pszPattern);
+ RTStrFree(pSettings->paPairs[iPair].pszOptions);
+ }
+ 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)
+{
+ SCMSTREAM Stream;
+ int rc = ScmStreamInitForReading(&Stream, pszFilename);
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("%s: ScmStreamInitForReading -> %Rrc\n", pszFilename, rc);
+ return 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;
+
+ /* What kind of line is it? */
+ const char *pchColon = (const char *)memchr(pchLine, ':', cchLine);
+ if (pchColon)
+ rc = scmSettingsAddPair(pSettings, pchLine, cchLine);
+ else
+ rc = scmSettingsBaseParseStringN(&pSettings->Base, pchLine, cchLine);
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("%s:%d: %Rrc\n", pszFilename, ScmStreamTellLine(&Stream), rc);
+ break;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = ScmStreamGetStatus(&Stream);
+ if (RT_FAILURE(rc))
+ RTMsgError("%s: ScmStreamGetStatus- > %Rrc\n", pszFilename, rc);
+ }
+
+ ScmStreamDelete(&Stream);
+ return rc;
+}
+
+/**
+ * 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);
+ if (RT_SUCCESS(rc))
+ {
+ *ppSettings = pSettings;
+ return VINF_SUCCESS;
+ }
+
+ scmSettingsDestroy(pSettings);
+ }
+ *ppSettings = NULL;
+ return rc;
+}
+
+
+/**
+ * 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;
+ 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))
+ {
+ 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 setttings.
+ * @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)
+{
+ 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 ( RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
+ pszBasename, cchBasename, NULL)
+ || RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
+ pszFilename, RTSTR_MAX, NULL))
+ {
+ rc = scmSettingsBaseParseString(pBase, pCur->paPairs[i].pszOptions);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ /* advance */
+ pCur = pCur->pUp;
+ }
+ }
+ if (RT_FAILURE(rc))
+ scmSettingsBaseDelete(pBase);
+ return rc;
+}
+
+
+/* -=-=-=-=-=- misc -=-=-=-=-=- */
+
+
+/**
+ * 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;
+ }
+ if (pszFormat)
+ {
+ RTPrintf(pState
+ ? "%s: info: "
+ : "%s: info: ",
+ g_szProgName);
+ va_list va;
+ va_start(va, pszFormat);
+ RTPrintfV(pszFormat, va);
+ va_end(va);
+ }
+ }
+}
+
+
+/* -=-=-=-=-=- 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);
+ 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);
+ return VINF_SUCCESS;
+ }
+ if ( pBaseSettings->fOnlySvnFiles
+ && !ScmSvnIsInWorkingCopy(pState))
+ {
+ ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename);
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Try find a matching rewrite config for this filename.
+ */
+ PCSCMCFGENTRY pCfg = NULL;
+ 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)
+ {
+ ScmVerbose(NULL, 4, "skipping '%s': no rewriters configured\n", pszFilename);
+ return VINF_SUCCESS;
+ }
+ ScmVerbose(pState, 4, "matched \"%s\"\n", pCfg->pszFilePattern);
+
+ /*
+ * 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;
+ }
+ if (ScmStreamIsText(&Stream1))
+ {
+ ScmVerbose(pState, 3, NULL);
+
+ /*
+ * 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++)
+ {
+ bool fRc = pCfg->papfnRewriter[iRw](pState, pIn, pOut, pBaseSettings);
+ if (fRc)
+ {
+ PSCMSTREAM pTmp = pOut;
+ pOut = pIn == &Stream1 ? &Stream3 : pIn;
+ pIn = pTmp;
+ fModified = true;
+ }
+ ScmStreamRewindForReading(pIn);
+ ScmStreamRewindForWriting(pOut);
+ }
+
+ 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)
+ {
+ 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
+ {
+ ScmVerbose(pState, 1, NULL);
+ 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);
+ }
+ }
+
+ /*
+ * 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 && !pState->cSvnPropChanges)
+ ScmVerbose(pState, 3, "no change\n", pszFilename);
+ }
+ else
+ RTMsgError("%s: stream error %Rrc\n", pszFilename);
+ 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, 4, "not text file: \"%s\"\n", pszFilename);
+ 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.fFirst = false;
+ State.pszFilename = pszFilename;
+ State.cSvnPropChanges = 0;
+ State.paSvnPropChanges = NULL;
+
+ 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);
+ }
+ 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;
+ }
+
+ /*
+ * Try open and read the directory.
+ */
+ PRTDIR pDir;
+ rc = RTDirOpenFiltered(&pDir, pszBuf, RTDIRFILTER_NONE, 0);
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc);
+ return rc;
+ }
+ for (;;)
+ {
+ /* Read the next entry. */
+ rc = RTDirRead(pDir, 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(pDir);
+ 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))
+ {
+ 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);
+ scmSettingsDestroy(pSettings);
+ }
+ else
+ RTMsgError("scmSettingsInitStack: %Rrc\n", rc);
+ }
+ else
+ RTMsgError("RTPathAbs: %Rrc\n", rc);
+ return rc;
+}
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return 1;
+
+ /*
+ * 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 },
+ { "--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));
+
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetOptState;
+ rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertReleaseRCReturn(rc, 1);
+ size_t cProcessed = 0;
+
+ while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
+ {
+ switch (rc)
+ {
+ case 'd':
+ g_fDryRun = true;
+ break;
+ case 'D':
+ g_fDryRun = false;
+ break;
+
+ case 'f':
+ g_pszFileFilter = ValueUnion.psz;
+ break;
+
+ case 'h':
+ RTPrintf("VirtualBox Source Code Massager\n"
+ "\n"
+ "Usage: %s [options] <files & dirs>\n"
+ "\n"
+ "Options:\n", g_szProgName);
+ for (size_t i = 0; i < RT_ELEMENTS(s_aOpts); i++)
+ {
+ bool fAdvanceTwo = false;
+ if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING)
+ {
+ fAdvanceTwo = i + 1 < RT_ELEMENTS(s_aOpts)
+ && ( strstr(s_aOpts[i+1].pszLong, "-no-") != NULL
+ || strstr(s_aOpts[i+1].pszLong, "-not-") != NULL
+ || strstr(s_aOpts[i+1].pszLong, "-dont-") != NULL
+ );
+ if (fAdvanceTwo)
+ RTPrintf(" %s, %s\n", s_aOpts[i].pszLong, s_aOpts[i + 1].pszLong);
+ else
+ RTPrintf(" %s\n", s_aOpts[i].pszLong);
+ }
+ else if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING)
+ RTPrintf(" %s string\n", s_aOpts[i].pszLong);
+ else
+ RTPrintf(" %s value\n", s_aOpts[i].pszLong);
+ switch (s_aOpts[i].iShort)
+ {
+ 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_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_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_TAB_SIZE: RTPrintf(" Default: %u\n", g_Defaults.cchTab); 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;
+ }
+ i += fAdvanceTwo;
+ }
+ return 1;
+
+ 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: 78836 $";
+ 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;
+
+ case VINF_GETOPT_NOT_OPTION:
+ {
+ if (!g_fDryRun)
+ {
+ if (!cProcessed)
+ {
+ 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);
+ }
+ cProcessed++;
+ }
+ rc = scmProcessSomething(ValueUnion.psz, pSettings);
+ if (RT_FAILURE(rc))
+ return rc;
+ break;
+ }
+
+ default:
+ {
+ int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion);
+ if (RT_SUCCESS(rc2))
+ break;
+ if (rc2 != VERR_GETOPT_UNKNOWN_OPTION)
+ return 2;
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+ }
+
+ scmSettingsDestroy(pSettings);
+ return 0;
+}
+
diff --git a/src/bldprogs/scm.h b/src/bldprogs/scm.h
new file mode 100644
index 00000000..2ac4200e
--- /dev/null
+++ b/src/bldprogs/scm.h
@@ -0,0 +1,226 @@
+/* $Id: scm.h $ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager.
+ */
+
+/*
+ * Copyright (C) 2010-2012 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+#ifndef ___scm_h___
+#define ___scm_h___
+
+#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;
+
+
+bool ScmSvnIsDirInWorkingCopy(const char *pszDir);
+bool ScmSvnIsInWorkingCopy(PSCMRWSTATE pState);
+int ScmSvnQueryProperty(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 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;
+ /** The number of SVN property changes. */
+ size_t cSvnPropChanges;
+ /** Pointer to an array of SVN property changes. */
+ struct SCMSVNPROP *paSvnPropChanges;
+} SCMRWSTATE;
+
+/**
+ * A rewriter.
+ *
+ * This works like a stream editor, reading @a pIn, modifying it and writing it
+ * to @a pOut.
+ *
+ * @returns true if any changes were made, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+typedef bool 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_SvnKeywords;
+FNSCMREWRITER rewrite_Makefile_kup;
+FNSCMREWRITER rewrite_Makefile_kmk;
+FNSCMREWRITER rewrite_C_and_CPP;
+
+/** @} */
+
+
+/** @name Settings
+ * @{ */
+
+/**
+ * Configuration entry.
+ */
+typedef struct SCMCFGENTRY
+{
+ /** Number of rewriters. */
+ size_t cRewriters;
+ /** Pointer to an array of rewriters. */
+ PFNSCMREWRITER const *papfnRewriter;
+ /** File pattern (simple). */
+ const char *pszFilePattern;
+} SCMCFGENTRY;
+typedef SCMCFGENTRY *PSCMCFGENTRY;
+typedef SCMCFGENTRY const *PCSCMCFGENTRY;
+
+
+/**
+ * Source Code Massager Settings.
+ */
+typedef struct SCMSETTINGSBASE
+{
+ bool fConvertEol;
+ bool fConvertTabs;
+ bool fForceFinalEol;
+ bool fForceTrailingLine;
+ bool fStripTrailingBlanks;
+ bool fStripTrailingLines;
+ /** 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;
+ /** */
+ unsigned cchTab;
+ /** 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;
+} 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 ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...);
+
+extern const char g_szTabSpaces[16+1];
+
+RT_C_DECLS_END
+
+#endif
+
diff --git a/src/bldprogs/scmdiff.cpp b/src/bldprogs/scmdiff.cpp
new file mode 100644
index 00000000..28463adb
--- /dev/null
+++ b/src/bldprogs/scmdiff.cpp
@@ -0,0 +1,439 @@
+/* $Id: scmdiff.cpp $ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager.
+ */
+
+/*
+ * Copyright (C) 2010-2012 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+/*******************************************************************************
+* 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->fIgnoreTrailingWhite)
+ 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..6827dcf2
--- /dev/null
+++ b/src/bldprogs/scmdiff.h
@@ -0,0 +1,61 @@
+/* $Id: scmdiff.h $ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager Diff Code.
+ */
+
+/*
+ * Copyright (C) 2010-2012 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef ___scmdiff_h___
+#define ___scmdiff_h___
+
+#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
+
diff --git a/src/bldprogs/scmrw.cpp b/src/bldprogs/scmrw.cpp
new file mode 100644
index 00000000..ea722960
--- /dev/null
+++ b/src/bldprogs/scmrw.cpp
@@ -0,0 +1,460 @@
+/* $Id: scmrw.cpp $ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager.
+ */
+
+/*
+ * Copyright (C) 2010-2012 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+/*******************************************************************************
+* 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"
+
+
+
+/**
+ * Strip trailing blanks (space & tab).
+ *
+ * @returns True if modified, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ if (!pSettings->fStripTrailingBlanks)
+ return false;
+
+ 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 false;
+ }
+ if (fModified)
+ ScmVerbose(pState, 2, " * Stripped trailing blanks\n");
+ return fModified;
+}
+
+/**
+ * Expand tabs.
+ *
+ * @returns True if modified, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ if (!pSettings->fConvertTabs)
+ return false;
+
+ 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 false;
+ }
+ if (fModified)
+ ScmVerbose(pState, 2, " * Expanded tabs\n");
+ return fModified;
+}
+
+/**
+ * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF.
+ *
+ * @returns true if modifications were made, false if not.
+ * @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 bool rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
+ SCMEOL enmDesiredEol, const char *pszDesiredSvnEol)
+{
+ if (!pSettings->fConvertEol)
+ return false;
+
+ 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 false;
+ }
+ 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))
+ RTMsgError("ScmSvnSetProperty: %Rrc\n", rc2); /** @todo propagate the error somehow... */
+ }
+ if (RT_SUCCESS(rc))
+ RTStrFree(pszEol);
+ }
+
+ /** @todo also check the subversion svn:eol-style state! */
+ return fModified;
+}
+
+/**
+ * Force native end of line indicator.
+ *
+ * @returns true if modifications were made, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool 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 true if modifications were made, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool 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 true if modifications were made, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ */
+bool 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 true if modifications were made, false if not.
+ * @param pIn The input stream.
+ * @param pOut The output stream.
+ * @param pSettings The settings.
+ *
+ * @remarks ASSUMES trailing white space has been removed already.
+ */
+bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ if ( !pSettings->fStripTrailingLines
+ && !pSettings->fForceTrailingLine
+ && !pSettings->fForceFinalEol)
+ return false;
+
+ size_t const cLines = ScmStreamCountLines(pIn);
+
+ /* Empty files remains empty. */
+ if (cLines <= 1)
+ return false;
+
+ /* 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 false;
+
+ /* 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 true;
+}
+
+/**
+ * Make sure there is no svn:executable keyword on the current file.
+ *
+ * @returns false - 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.
+ */
+bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ if ( !pSettings->fSetSvnExecutable
+ || !ScmSvnIsInWorkingCopy(pState))
+ return false;
+
+ 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))
+ RTMsgError("ScmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
+ }
+ return false;
+}
+
+/**
+ * Make sure the Id and Revision keywords are expanded.
+ *
+ * @returns false - 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.
+ */
+bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ if ( !pSettings->fSetSvnKeywords
+ || !ScmSvnIsInWorkingCopy(pState))
+ return false;
+
+ 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))
+ RTMsgError("ScmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
+ }
+ else
+ RTMsgError("RTStrAppend: %Rrc\n", rc); /** @todo error propagation here.. */
+ 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))
+ RTMsgError("ScmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
+ }
+ else if (RT_SUCCESS(rc))
+ RTStrFree(pszKeywords);
+
+ return false;
+}
+
+/**
+ * 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.
+ */
+bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ /* These files should be zero bytes. */
+ if (pIn->cb == 0)
+ return false;
+ ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
+ return true;
+}
+
+/**
+ * Rewrite a kBuild makefile.
+ *
+ * @returns true if modifications were made, false if not.
+ * @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.
+ */
+bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+ return false;
+}
+
+/**
+ * Rewrite a C/C++ source or header file.
+ *
+ * @returns true if modifications were made, false if not.
+ * @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.
+ */
+bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
+{
+
+ return false;
+}
+
diff --git a/src/bldprogs/scmstream.cpp b/src/bldprogs/scmstream.cpp
new file mode 100644
index 00000000..9ba78486
--- /dev/null
+++ b/src/bldprogs/scmstream.cpp
@@ -0,0 +1,1362 @@
+/* $Id: scmstream.cpp $ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager Stream Code.
+ */
+
+/*
+ * Copyright (C) 2010-2012 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+/*******************************************************************************
+* 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
+ ? pRelatedStream->cb + pRelatedStream->cb / 10
+ : _64K;
+ 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);
+ 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;
+}
+
+/**
+ * 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 false;
+ 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);
+ }
+ 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, &h);
+ if (RT_SUCCESS(rc))
+ {
+ switch (h.enmType)
+ {
+ case RTHANDLETYPE_FILE:
+ rc = RTFileWrite(h.u.hFile, pStream->pch, pStream->cb, NULL);
+ break;
+ case RTHANDLETYPE_PIPE:
+ rc = RTPipeWriteBlocking(h.u.hPipe, pStream->pch, pStream->cb, NULL);
+ 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)
+{
+ 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 (iLine < pStream->cLines)
+ {
+ pStream->off = pStream->paLines[iLine].off;
+ pStream->iLine = iLine;
+ }
+ else
+ {
+ 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;
+}
+
+/**
+ * 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.
+ *
+ * @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);
+ if (RT_FAILURE(pStream->rc))
+ return NULL;
+
+ /* Make sure it's fully delineated so we can use the index. */
+ if (RT_UNLIKELY(!pStream->fFullyLineated))
+ {
+ int rc = scmStreamLineate(pStream);
+ if (RT_FAILURE(rc))
+ return NULL;
+ }
+
+ /* End of stream? */
+ if (RT_UNLIKELY(iLine >= pStream->cLines))
+ {
+ pStream->off = pStream->cb;
+ pStream->iLine = pStream->cLines;
+ return NULL;
+ }
+
+ /* 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;
+}
+
+/**
+ * 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.
+ *
+ * @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 (!pStream->fFullyLineated)
+ return scmStreamGetLineInternal(pStream, pcchLine, penmEol);
+
+ size_t offCur = pStream->off;
+ size_t iCurLine = pStream->iLine;
+ const char *pszLine = ScmStreamGetLineByNo(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;
+}
+
+/**
+ * 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 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;
+}
+
+/**
+ * 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..cda64a8e
--- /dev/null
+++ b/src/bldprogs/scmstream.h
@@ -0,0 +1,136 @@
+/* $Id: scmstream.h $ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager Stream Code.
+ */
+
+/*
+ * Copyright (C) 2012 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef ___scmstream_h___
+#define ___scmstream_h___
+
+#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 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);
+
+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 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);
+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
+
diff --git a/src/bldprogs/scmsubversion.cpp b/src/bldprogs/scmsubversion.cpp
new file mode 100644
index 00000000..35b56d57
--- /dev/null
+++ b/src/bldprogs/scmsubversion.cpp
@@ -0,0 +1,1063 @@
+/* $Id: scmsubversion.cpp $ */
+/** @file
+ * IPRT Testcase / Tool - Source Code Massager, Subversion Access.
+ */
+
+/*
+ * Copyright (C) 2010-2012 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#define SCM_WITHOUT_LIBSVN
+
+/*******************************************************************************
+* 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"
+
+
+/*******************************************************************************
+* Global Variables *
+*******************************************************************************/
+static char g_szSvnPath[RTPATH_MAX];
+static enum
+{
+ kScmSvnVersion_Ancient = 1,
+ kScmSvnVersion_1_6,
+ kScmSvnVersion_1_7,
+ kScmSvnVersion_End
+} g_enmSvnVersion = kScmSvnVersion_Ancient;
+
+
+#ifdef SCM_WITHOUT_LIBSVN
+
+/**
+ * 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;
+}
+
+#include <iprt/handle.h>
+#include <iprt/pipe.h>
+#include <iprt/poll.h>
+
+/**
+ * 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(g_szSvnPath,
+ papszArgs,
+ RTENV_DEFAULT,
+ 0 /*fFlags*/,
+ NULL /*phStdIn*/,
+ phChildStdOut,
+ phChildStdErr,
+ NULL /*pszAsUser*/,
+ NULL /*pszPassword*/,
+ &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;
+ if (RT_SUCCESS(rc))
+ rc = RTProcCreateEx(g_szSvnPath,
+ papszArgs,
+ RTENV_DEFAULT,
+ 0 /*fFlags*/,
+ aph[0],
+ aph[1],
+ aph[2],
+ NULL /*pszAsUser*/,
+ NULL /*pszPassword*/,
+ &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;
+
+ 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",
+ 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)
+{
+ 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 RTProcExecToString. */
+ 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;
+}
+
+
+/**
+ * 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(pszVersion, "1.7") >= 0)
+ g_enmSvnVersion = kScmSvnVersion_1_7;
+ else if (RTStrVersionCompare(pszVersion, "1.6") >= 0)
+ g_enmSvnVersion = kScmSvnVersion_1_6;
+ else
+ g_enmSvnVersion = kScmSvnVersion_Ancient;
+ RTStrFree(pszVersion);
+ }
+ else
+ g_enmSvnVersion = kScmSvnVersion_Ancient;
+}
+
+
+/**
+ * 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;
+}
+
+#endif /* SCM_WITHOUT_LIBSVN */
+
+/**
+ * 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.
+ */
+bool ScmSvnIsInWorkingCopy(PSCMRWSTATE pState)
+{
+#ifdef SCM_WITHOUT_LIBSVN
+ scmSvnFindSvnBinary(pState);
+ 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))
+ return RTFileExists(szPath);
+ }
+ else
+ {
+ const char *apszArgs[] = { g_szSvnPath, "propget", "svn:no-such-property", pState->pszFilename, NULL };
+ char *pszValue;
+ int rc = scmSvnRunAndGetOutput(pState, apszArgs, true, &pszValue);
+ if (RT_SUCCESS(rc))
+ {
+ RTStrFree(pszValue);
+ return true;
+ }
+ }
+
+#else
+ NOREF(pState);
+#endif
+ 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_WITHOUT_LIBSVN
+ scmSvnFindSvnBinary(NULL);
+ 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;
+ }
+ }
+
+#else
+ NOREF(pState);
+#endif
+ return false;
+}
+
+/**
+ * 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)
+{
+ /*
+ * 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_WITHOUT_LIBSVN
+ int rc;
+ scmSvnFindSvnBinary(pState);
+ 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;
+
+#else
+ NOREF(pState);
+#endif
+ return VERR_NOT_FOUND;
+}
+
+
+/**
+ * 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_WITHOUT_LIBSVN
+ scmSvnFindSvnBinary(pState);
+
+ /*
+ * 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;
+#else
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
+
+