diff options
author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2012-10-26 16:25:44 +0000 |
---|---|---|
committer | <> | 2012-11-12 12:15:52 +0000 |
commit | 58ed4748338f9466599adfc8a9171280ed99e23f (patch) | |
tree | 02027d99ded4fb56a64aa9489ac2eb487e7858ab /src/bldprogs | |
download | VirtualBox-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.kmk | 65 | ||||
-rw-r--r-- | src/bldprogs/VBoxCPP.cpp | 5501 | ||||
-rw-r--r-- | src/bldprogs/VBoxCmp.cpp | 131 | ||||
-rw-r--r-- | src/bldprogs/VBoxPeSetVersion.cpp | 82 | ||||
-rw-r--r-- | src/bldprogs/VBoxTpG.cpp | 2471 | ||||
-rw-r--r-- | src/bldprogs/bin2c.c | 249 | ||||
-rw-r--r-- | src/bldprogs/biossums.c | 229 | ||||
-rwxr-xr-x | src/bldprogs/checkUndefined.sh | 85 | ||||
-rw-r--r-- | src/bldprogs/deftoimp.sed | 57 | ||||
-rw-r--r-- | src/bldprogs/filesplitter.cpp | 369 | ||||
-rw-r--r-- | src/bldprogs/preload.cpp | 211 | ||||
-rw-r--r-- | src/bldprogs/scm.cpp | 1613 | ||||
-rw-r--r-- | src/bldprogs/scm.h | 226 | ||||
-rw-r--r-- | src/bldprogs/scmdiff.cpp | 439 | ||||
-rw-r--r-- | src/bldprogs/scmdiff.h | 61 | ||||
-rw-r--r-- | src/bldprogs/scmrw.cpp | 460 | ||||
-rw-r--r-- | src/bldprogs/scmstream.cpp | 1362 | ||||
-rw-r--r-- | src/bldprogs/scmstream.h | 136 | ||||
-rw-r--r-- | src/bldprogs/scmsubversion.cpp | 1063 |
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 +} + + + |