VirtualBox

Changeset 40528 in vbox for trunk/src/bldprogs


Ignore:
Timestamp:
Mar 19, 2012 11:04:34 AM (13 years ago)
Author:
vboxsync
Message:

scm: Splitting out the SCMSTREAM bits so it can be used by others.

Location:
trunk/src/bldprogs
Files:
2 edited
2 copied

Legend:

Unmodified
Added
Removed
  • trunk/src/bldprogs/Makefile.kmk

    r34484 r40528  
    3636 PROGRAMS += scm
    3737 scm_TEMPLATE = VBOXR3EXE
    38  scm_SOURCES = scm.cpp
     38 scm_SOURCES = \
     39        scm.cpp \
     40        scmstream.cpp
    3941 scm_LIBS = \
    4042        $(LIB_RUNTIME)
  • trunk/src/bldprogs/scm.cpp

    r39612 r40528  
    55
    66/*
    7  * Copyright (C) 2010 Oracle Corporation
     7 * Copyright (C) 2010-2012 Oracle Corporation
    88 *
    99 * This file is part of VirtualBox Open Source Edition (OSE), as
     
    3535#include <iprt/string.h>
    3636
     37#include "scmstream.h"
     38
    3739
    3840/*******************************************************************************
     
    4850/** Pointer to const massager settings. */
    4951typedef struct SCMSETTINGSBASE const *PCSCMSETTINGSBASE;
    50 
    51 /** End of line marker type. */
    52 typedef enum SCMEOL
    53 {
    54     SCMEOL_NONE = 0,
    55     SCMEOL_LF = 1,
    56     SCMEOL_CRLF = 2
    57 } SCMEOL;
    58 /** Pointer to an end of line marker type. */
    59 typedef SCMEOL *PSCMEOL;
    60 
    61 /**
    62  * Line record.
    63  */
    64 typedef struct SCMSTREAMLINE
    65 {
    66     /** The offset of the line. */
    67     size_t          off;
    68     /** The line length, excluding the LF character.
    69      * @todo This could be derived from the offset of the next line if that wasn't
    70      *       so tedious. */
    71     size_t          cch;
    72     /** The end of line marker type. */
    73     SCMEOL          enmEol;
    74 } SCMSTREAMLINE;
    75 /** Pointer to a line record. */
    76 typedef SCMSTREAMLINE *PSCMSTREAMLINE;
    77 
    78 /**
    79  * Source code massager stream.
    80  */
    81 typedef struct SCMSTREAM
    82 {
    83     /** Pointer to the file memory. */
    84     char           *pch;
    85     /** The current stream position. */
    86     size_t          off;
    87     /** The current stream size. */
    88     size_t          cb;
    89     /** The size of the memory pb points to. */
    90     size_t          cbAllocated;
    91 
    92     /** Line records. */
    93     PSCMSTREAMLINE  paLines;
    94     /** The current line. */
    95     size_t          iLine;
    96     /** The current stream size given in lines.   */
    97     size_t          cLines;
    98     /** The sizeof the the memory backing paLines.   */
    99     size_t          cLinesAllocated;
    100 
    101     /** Set if write-only, clear if read-only. */
    102     bool            fWriteOrRead;
    103     /** Set if the memory pb points to is from RTFileReadAll. */
    104     bool            fFileMemory;
    105     /** Set if fully broken into lines. */
    106     bool            fFullyLineated;
    107 
    108     /** Stream status code (IPRT). */
    109     int             rc;
    110 } SCMSTREAM;
    111 /** Pointer to a SCM stream. */
    112 typedef SCMSTREAM *PSCMSTREAM;
    113 /** Pointer to a const SCM stream. */
    114 typedef SCMSTREAM const *PCSCMSTREAM;
    115 
    11652
    11753/**
     
    500436};
    501437
    502 
    503 /* -=-=-=-=-=- memory streams -=-=-=-=-=- */
    504 
    505 
    506 /**
    507  * Initializes the stream structure.
    508  *
    509  * @param   pStream             The stream structure.
    510  * @param   fWriteOrRead        The value of the fWriteOrRead stream member.
    511  */
    512 static void scmStreamInitInternal(PSCMSTREAM pStream, bool fWriteOrRead)
    513 {
    514     pStream->pch                = NULL;
    515     pStream->off                = 0;
    516     pStream->cb                 = 0;
    517     pStream->cbAllocated        = 0;
    518 
    519     pStream->paLines            = NULL;
    520     pStream->iLine              = 0;
    521     pStream->cLines             = 0;
    522     pStream->cLinesAllocated    = 0;
    523 
    524     pStream->fWriteOrRead       = fWriteOrRead;
    525     pStream->fFileMemory        = false;
    526     pStream->fFullyLineated     = false;
    527 
    528     pStream->rc                 = VINF_SUCCESS;
    529 }
    530 
    531 /**
    532  * Initialize an input stream.
    533  *
    534  * @returns IPRT status code.
    535  * @param   pStream             The stream to initialize.
    536  * @param   pszFilename         The file to take the stream content from.
    537  */
    538 int ScmStreamInitForReading(PSCMSTREAM pStream, const char *pszFilename)
    539 {
    540     scmStreamInitInternal(pStream, false /*fWriteOrRead*/);
    541 
    542     void *pvFile;
    543     size_t cbFile;
    544     int rc = pStream->rc = RTFileReadAll(pszFilename, &pvFile, &cbFile);
    545     if (RT_SUCCESS(rc))
    546     {
    547         pStream->pch            = (char *)pvFile;
    548         pStream->cb             = cbFile;
    549         pStream->cbAllocated    = cbFile;
    550         pStream->fFileMemory    = true;
    551     }
    552     return rc;
    553 }
    554 
    555 /**
    556  * Initialize an output stream.
    557  *
    558  * @returns IPRT status code
    559  * @param   pStream             The stream to initialize.
    560  * @param   pRelatedStream      Pointer to a related stream.  NULL is fine.
    561  */
    562 int ScmStreamInitForWriting(PSCMSTREAM pStream, PCSCMSTREAM pRelatedStream)
    563 {
    564     scmStreamInitInternal(pStream, true /*fWriteOrRead*/);
    565 
    566     /* allocate stuff */
    567     size_t cbEstimate = pRelatedStream
    568                       ? pRelatedStream->cb + pRelatedStream->cb / 10
    569                       : _64K;
    570     cbEstimate = RT_ALIGN(cbEstimate, _4K);
    571     pStream->pch = (char *)RTMemAlloc(cbEstimate);
    572     if (pStream->pch)
    573     {
    574         size_t cLinesEstimate = pRelatedStream && pRelatedStream->fFullyLineated
    575                               ? pRelatedStream->cLines + pRelatedStream->cLines / 10
    576                               : cbEstimate / 24;
    577         cLinesEstimate = RT_ALIGN(cLinesEstimate, 512);
    578         pStream->paLines = (PSCMSTREAMLINE)RTMemAlloc(cLinesEstimate * sizeof(SCMSTREAMLINE));
    579         if (pStream->paLines)
    580         {
    581             pStream->paLines[0].off     = 0;
    582             pStream->paLines[0].cch     = 0;
    583             pStream->paLines[0].enmEol  = SCMEOL_NONE;
    584             pStream->cbAllocated        = cbEstimate;
    585             pStream->cLinesAllocated    = cLinesEstimate;
    586             return VINF_SUCCESS;
    587         }
    588 
    589         RTMemFree(pStream->pch);
    590         pStream->pch = NULL;
    591     }
    592     return pStream->rc = VERR_NO_MEMORY;
    593 }
    594 
    595 /**
    596  * Frees the resources associated with the stream.
    597  *
    598  * Nothing is happens to whatever the stream was initialized from or dumped to.
    599  *
    600  * @param   pStream             The stream to delete.
    601  */
    602 void ScmStreamDelete(PSCMSTREAM pStream)
    603 {
    604     if (pStream->pch)
    605     {
    606         if (pStream->fFileMemory)
    607             RTFileReadAllFree(pStream->pch, pStream->cbAllocated);
    608         else
    609             RTMemFree(pStream->pch);
    610         pStream->pch = NULL;
    611     }
    612     pStream->cbAllocated = 0;
    613 
    614     if (pStream->paLines)
    615     {
    616         RTMemFree(pStream->paLines);
    617         pStream->paLines = NULL;
    618     }
    619     pStream->cLinesAllocated = 0;
    620 }
    621 
    622 /**
    623  * Get the stream status code.
    624  *
    625  * @returns IPRT status code.
    626  * @param   pStream             The stream.
    627  */
    628 int ScmStreamGetStatus(PCSCMSTREAM pStream)
    629 {
    630     return pStream->rc;
    631 }
    632 
    633 /**
    634  * Grows the buffer of a write stream.
    635  *
    636  * @returns IPRT status code.
    637  * @param   pStream             The stream.  Must be in write mode.
    638  * @param   cbAppending         The minimum number of bytes to grow the buffer
    639  *                              with.
    640  */
    641 static int scmStreamGrowBuffer(PSCMSTREAM pStream, size_t cbAppending)
    642 {
    643     size_t cbAllocated = pStream->cbAllocated;
    644     cbAllocated += RT_MAX(0x1000 + cbAppending, cbAllocated);
    645     cbAllocated = RT_ALIGN(cbAllocated, 0x1000);
    646     void *pvNew;
    647     if (!pStream->fFileMemory)
    648     {
    649         pvNew = RTMemRealloc(pStream->pch, cbAllocated);
    650         if (!pvNew)
    651             return pStream->rc = VERR_NO_MEMORY;
    652     }
    653     else
    654     {
    655         pvNew = RTMemDupEx(pStream->pch, pStream->off, cbAllocated - pStream->off);
    656         if (!pvNew)
    657             return pStream->rc = VERR_NO_MEMORY;
    658         RTFileReadAllFree(pStream->pch, pStream->cbAllocated);
    659         pStream->fFileMemory = false;
    660     }
    661     pStream->pch = (char *)pvNew;
    662     pStream->cbAllocated = cbAllocated;
    663 
    664     return VINF_SUCCESS;
    665 }
    666 
    667 /**
    668  * Grows the line array of a stream.
    669  *
    670  * @returns IPRT status code.
    671  * @param   pStream             The stream.
    672  * @param   iMinLine            Minimum line number.
    673  */
    674 static int scmStreamGrowLines(PSCMSTREAM pStream, size_t iMinLine)
    675 {
    676     size_t cLinesAllocated = pStream->cLinesAllocated;
    677     cLinesAllocated += RT_MAX(512 + iMinLine, cLinesAllocated);
    678     cLinesAllocated = RT_ALIGN(cLinesAllocated, 512);
    679     void *pvNew = RTMemRealloc(pStream->paLines, cLinesAllocated * sizeof(SCMSTREAMLINE));
    680     if (!pvNew)
    681         return pStream->rc = VERR_NO_MEMORY;
    682 
    683     pStream->paLines = (PSCMSTREAMLINE)pvNew;
    684     pStream->cLinesAllocated = cLinesAllocated;
    685     return VINF_SUCCESS;
    686 }
    687 
    688 /**
    689  * Rewinds the stream and sets the mode to read.
    690  *
    691  * @param   pStream             The stream.
    692  */
    693 void ScmStreamRewindForReading(PSCMSTREAM pStream)
    694 {
    695     pStream->off          = 0;
    696     pStream->iLine        = 0;
    697     pStream->fWriteOrRead = false;
    698     pStream->rc           = VINF_SUCCESS;
    699 }
    700 
    701 /**
    702  * Rewinds the stream and sets the mode to write.
    703  *
    704  * @param   pStream             The stream.
    705  */
    706 void ScmStreamRewindForWriting(PSCMSTREAM pStream)
    707 {
    708     pStream->off            = 0;
    709     pStream->iLine          = 0;
    710     pStream->cLines         = 0;
    711     pStream->fWriteOrRead   = true;
    712     pStream->fFullyLineated = true;
    713     pStream->rc             = VINF_SUCCESS;
    714 }
    715 
    716 /**
    717  * Checks if it's a text stream.
    718  *
    719  * Not 100% proof.
    720  *
    721  * @returns true if it probably is a text file, false if not.
    722  * @param   pStream             The stream. Write or read, doesn't matter.
    723  */
    724 bool ScmStreamIsText(PSCMSTREAM pStream)
    725 {
    726     if (RTStrEnd(pStream->pch, pStream->cb))
    727         return false;
    728     if (!pStream->cb)
    729         return false;
    730     return true;
    731 }
    732 
    733 /**
    734  * Performs an integrity check of the stream.
    735  *
    736  * @returns IPRT status code.
    737  * @param   pStream             The stream.
    738  */
    739 int ScmStreamCheckItegrity(PSCMSTREAM pStream)
    740 {
    741     /*
    742      * Perform sanity checks.
    743      */
    744     size_t const cbFile = pStream->cb;
    745     for (size_t iLine = 0; iLine < pStream->cLines; iLine++)
    746     {
    747         size_t offEol = pStream->paLines[iLine].off + pStream->paLines[iLine].cch;
    748         AssertReturn(offEol + pStream->paLines[iLine].enmEol <= cbFile, VERR_INTERNAL_ERROR_2);
    749         switch (pStream->paLines[iLine].enmEol)
    750         {
    751             case SCMEOL_LF:
    752                 AssertReturn(pStream->pch[offEol] == '\n', VERR_INTERNAL_ERROR_3);
    753                 break;
    754             case SCMEOL_CRLF:
    755                 AssertReturn(pStream->pch[offEol] == '\r', VERR_INTERNAL_ERROR_3);
    756                 AssertReturn(pStream->pch[offEol + 1] == '\n', VERR_INTERNAL_ERROR_3);
    757                 break;
    758             case SCMEOL_NONE:
    759                 AssertReturn(iLine + 1 >= pStream->cLines, VERR_INTERNAL_ERROR_4);
    760                 break;
    761             default:
    762                 AssertReturn(iLine + 1 >= pStream->cLines, VERR_INTERNAL_ERROR_5);
    763         }
    764     }
    765     return VINF_SUCCESS;
    766 }
    767 
    768 /**
    769  * Writes the stream to a file.
    770  *
    771  * @returns IPRT status code
    772  * @param   pStream             The stream.
    773  * @param   pszFilenameFmt      The filename format string.
    774  * @param   ...                 Format arguments.
    775  */
    776 int ScmStreamWriteToFile(PSCMSTREAM pStream, const char *pszFilenameFmt, ...)
    777 {
    778     int rc;
    779 
    780 #ifdef RT_STRICT
    781     /*
    782      * Check that what we're going to write makes sense first.
    783      */
    784     rc = ScmStreamCheckItegrity(pStream);
    785     if (RT_FAILURE(rc))
    786         return rc;
    787 #endif
    788 
    789     /*
    790      * Do the actual writing.
    791      */
    792     RTFILE hFile;
    793     va_list va;
    794     va_start(va, pszFilenameFmt);
    795     rc = RTFileOpenV(&hFile, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_WRITE, pszFilenameFmt, va);
    796     if (RT_SUCCESS(rc))
    797     {
    798         rc = RTFileWrite(hFile, pStream->pch, pStream->cb, NULL);
    799         RTFileClose(hFile);
    800     }
    801     return rc;
    802 }
    803 
    804 /**
    805  * Worker for ScmStreamGetLine that builds the line number index while parsing
    806  * the stream.
    807  *
    808  * @returns Same as SCMStreamGetLine.
    809  * @param   pStream             The stream.  Must be in read mode.
    810  * @param   pcchLine            Where to return the line length.
    811  * @param   penmEol             Where to return the kind of end of line marker.
    812  */
    813 static const char *scmStreamGetLineInternal(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol)
    814 {
    815     AssertReturn(!pStream->fWriteOrRead, NULL);
    816     if (RT_FAILURE(pStream->rc))
    817         return NULL;
    818 
    819     size_t off = pStream->off;
    820     size_t cb  = pStream->cb;
    821     if (RT_UNLIKELY(off >= cb))
    822     {
    823         pStream->fFullyLineated = true;
    824         return NULL;
    825     }
    826 
    827     size_t iLine = pStream->iLine;
    828     if (RT_UNLIKELY(iLine >= pStream->cLinesAllocated))
    829     {
    830         int rc = scmStreamGrowLines(pStream, iLine);
    831         if (RT_FAILURE(rc))
    832             return NULL;
    833     }
    834     pStream->paLines[iLine].off = off;
    835 
    836     cb -= off;
    837     const char *pchRet = &pStream->pch[off];
    838     const char *pch = (const char *)memchr(pchRet, '\n', cb);
    839     if (RT_LIKELY(pch))
    840     {
    841         cb = pch - pchRet;
    842         pStream->off = off + cb + 1;
    843         if (   cb < 1
    844             || pch[-1] != '\r')
    845             pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_LF;
    846         else
    847         {
    848             pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_CRLF;
    849             cb--;
    850         }
    851     }
    852     else
    853     {
    854         pStream->off = off + cb;
    855         pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_NONE;
    856     }
    857     *pcchLine = cb;
    858     pStream->paLines[iLine].cch = cb;
    859     pStream->cLines = pStream->iLine = ++iLine;
    860 
    861     return pchRet;
    862 }
    863 
    864 /**
    865  * Internal worker that delineates a stream.
    866  *
    867  * @returns IPRT status code.
    868  * @param   pStream             The stream.  Caller must check that it is in
    869  *                              read mode.
    870  */
    871 static int scmStreamLineate(PSCMSTREAM pStream)
    872 {
    873     /* Save the stream position. */
    874     size_t const offSaved   = pStream->off;
    875     size_t const iLineSaved = pStream->iLine;
    876 
    877     /* Get each line. */
    878     size_t cchLine;
    879     SCMEOL enmEol;
    880     while (scmStreamGetLineInternal(pStream, &cchLine, &enmEol))
    881         /* nothing */;
    882     Assert(RT_FAILURE(pStream->rc) || pStream->fFullyLineated);
    883 
    884     /* Restore the position */
    885     pStream->off   = offSaved;
    886     pStream->iLine = iLineSaved;
    887 
    888     return pStream->rc;
    889 }
    890 
    891 /**
    892  * Get the current stream position as an byte offset.
    893  *
    894  * @returns The current byte offset
    895  * @param   pStream             The stream.
    896  */
    897 size_t ScmStreamTell(PSCMSTREAM pStream)
    898 {
    899     return pStream->off;
    900 }
    901 
    902 /**
    903  * Get the current stream position as a line number.
    904  *
    905  * @returns The current line (0-based).
    906  * @param   pStream             The stream.
    907  */
    908 size_t ScmStreamTellLine(PSCMSTREAM pStream)
    909 {
    910     return pStream->iLine;
    911 }
    912 
    913 /**
    914  * Get the current stream size in bytes.
    915  *
    916  * @returns Count of bytes.
    917  * @param   pStream             The stream.
    918  */
    919 size_t ScmStreamSize(PSCMSTREAM pStream)
    920 {
    921     return pStream->cb;
    922 }
    923 
    924 /**
    925  * Gets the number of lines in the stream.
    926  *
    927  * @returns The number of lines.
    928  * @param   pStream             The stream.
    929  */
    930 size_t ScmStreamCountLines(PSCMSTREAM pStream)
    931 {
    932     if (!pStream->fFullyLineated)
    933         scmStreamLineate(pStream);
    934     return pStream->cLines;
    935 }
    936 
    937 /**
    938  * Seeks to a given byte offset in the stream.
    939  *
    940  * @returns IPRT status code.
    941  * @retval  VERR_SEEK if the new stream position is the middle of an EOL marker.
    942  *          This is a temporary restriction.
    943  *
    944  * @param   pStream             The stream.  Must be in read mode.
    945  * @param   offAbsolute         The offset to seek to.  If this is beyond the
    946  *                              end of the stream, the position is set to the
    947  *                              end.
    948  */
    949 int ScmStreamSeekAbsolute(PSCMSTREAM pStream, size_t offAbsolute)
    950 {
    951     AssertReturn(!pStream->fWriteOrRead, VERR_ACCESS_DENIED);
    952     if (RT_FAILURE(pStream->rc))
    953         return pStream->rc;
    954 
    955     /* Must be fully delineated. (lazy bird) */
    956     if (RT_UNLIKELY(!pStream->fFullyLineated))
    957     {
    958         int rc = scmStreamLineate(pStream);
    959         if (RT_FAILURE(rc))
    960             return rc;
    961     }
    962 
    963     /* Ok, do the job. */
    964     if (offAbsolute < pStream->cb)
    965     {
    966         /** @todo Should do a binary search here, but I'm too darn lazy tonight. */
    967         pStream->off = ~(size_t)0;
    968         for (size_t i = 0; i < pStream->cLines; i++)
    969         {
    970             if (offAbsolute < pStream->paLines[i].off + pStream->paLines[i].cch + pStream->paLines[i].enmEol)
    971             {
    972                 pStream->off   = offAbsolute;
    973                 pStream->iLine = i;
    974                 if (offAbsolute > pStream->paLines[i].off + pStream->paLines[i].cch)
    975                     return pStream->rc = VERR_SEEK;
    976                 break;
    977             }
    978         }
    979         AssertReturn(pStream->off != ~(size_t)0, pStream->rc = VERR_INTERNAL_ERROR_3);
    980     }
    981     else
    982     {
    983         pStream->off   = pStream->cb;
    984         pStream->iLine = pStream->cLines;
    985     }
    986     return VINF_SUCCESS;
    987 }
    988 
    989 
    990 /**
    991  * Seeks a number of bytes relative to the current stream position.
    992  *
    993  * @returns IPRT status code.
    994  * @retval  VERR_SEEK if the new stream position is the middle of an EOL marker.
    995  *          This is a temporary restriction.
    996  *
    997  * @param   pStream             The stream.  Must be in read mode.
    998  * @param   offRelative         The offset to seek to.  A negative offset
    999  *                              rewinds and positive one fast forwards the
    1000  *                              stream.  Will quietly stop at the beginning and
    1001  *                              end of the stream.
    1002  */
    1003 int ScmStreamSeekRelative(PSCMSTREAM pStream, ssize_t offRelative)
    1004 {
    1005     size_t offAbsolute;
    1006     if (offRelative >= 0)
    1007         offAbsolute = pStream->off + offRelative;
    1008     else if ((size_t)-offRelative <= pStream->off)
    1009         offAbsolute = pStream->off + offRelative;
    1010     else
    1011         offAbsolute = 0;
    1012     return ScmStreamSeekAbsolute(pStream, offAbsolute);
    1013 }
    1014 
    1015 /**
    1016  * Seeks to a given line in the stream.
    1017  *
    1018  * @returns IPRT status code.
    1019  *
    1020  * @param   pStream             The stream.  Must be in read mode.
    1021  * @param   iLine               The line to seek to.  If this is beyond the end
    1022  *                              of the stream, the position is set to the end.
    1023  */
    1024 int ScmStreamSeekByLine(PSCMSTREAM pStream, size_t iLine)
    1025 {
    1026     AssertReturn(!pStream->fWriteOrRead, VERR_ACCESS_DENIED);
    1027     if (RT_FAILURE(pStream->rc))
    1028         return pStream->rc;
    1029 
    1030     /* Must be fully delineated. (lazy bird) */
    1031     if (RT_UNLIKELY(!pStream->fFullyLineated))
    1032     {
    1033         int rc = scmStreamLineate(pStream);
    1034         if (RT_FAILURE(rc))
    1035             return rc;
    1036     }
    1037 
    1038     /* Ok, do the job. */
    1039     if (iLine < pStream->cLines)
    1040     {
    1041         pStream->off   = pStream->paLines[iLine].off;
    1042         pStream->iLine = iLine;
    1043     }
    1044     else
    1045     {
    1046         pStream->off   = pStream->cb;
    1047         pStream->iLine = pStream->cLines;
    1048     }
    1049     return VINF_SUCCESS;
    1050 }
    1051 
    1052 /**
    1053  * Get a numbered line from the stream (changes the position).
    1054  *
    1055  * A line is always delimited by a LF character or the end of the stream.  The
    1056  * delimiter is not included in returned line length, but instead returned via
    1057  * the @a penmEol indicator.
    1058  *
    1059  * @returns Pointer to the first character in the line, not NULL terminated.
    1060  *          NULL if the end of the stream has been reached or some problem
    1061  *          occurred.
    1062  *
    1063  * @param   pStream             The stream.  Must be in read mode.
    1064  * @param   iLine               The line to get (0-based).
    1065  * @param   pcchLine            The length.
    1066  * @param   penmEol             Where to return the end of line type indicator.
    1067  */
    1068 static const char *ScmStreamGetLineByNo(PSCMSTREAM pStream, size_t iLine, size_t *pcchLine, PSCMEOL penmEol)
    1069 {
    1070     AssertReturn(!pStream->fWriteOrRead, NULL);
    1071     if (RT_FAILURE(pStream->rc))
    1072         return NULL;
    1073 
    1074     /* Make sure it's fully delineated so we can use the index. */
    1075     if (RT_UNLIKELY(!pStream->fFullyLineated))
    1076     {
    1077         int rc = scmStreamLineate(pStream);
    1078         if (RT_FAILURE(rc))
    1079             return NULL;
    1080     }
    1081 
    1082     /* End of stream? */
    1083     if (RT_UNLIKELY(iLine >= pStream->cLines))
    1084     {
    1085         pStream->off   = pStream->cb;
    1086         pStream->iLine = pStream->cLines;
    1087         return NULL;
    1088     }
    1089 
    1090     /* Get the data. */
    1091     const char *pchRet = &pStream->pch[pStream->paLines[iLine].off];
    1092     *pcchLine          = pStream->paLines[iLine].cch;
    1093     *penmEol           = pStream->paLines[iLine].enmEol;
    1094 
    1095     /* update the stream position. */
    1096     pStream->off       = pStream->paLines[iLine].off + pStream->paLines[iLine].cch + pStream->paLines[iLine].enmEol;
    1097     pStream->iLine     = iLine + 1;
    1098 
    1099     return pchRet;
    1100 }
    1101 
    1102 /**
    1103  * Get a line from the stream.
    1104  *
    1105  * A line is always delimited by a LF character or the end of the stream.  The
    1106  * delimiter is not included in returned line length, but instead returned via
    1107  * the @a penmEol indicator.
    1108  *
    1109  * @returns Pointer to the first character in the line, not NULL terminated.
    1110  *          NULL if the end of the stream has been reached or some problem
    1111  *          occurred.
    1112  *
    1113  * @param   pStream             The stream.  Must be in read mode.
    1114  * @param   pcchLine            The length.
    1115  * @param   penmEol             Where to return the end of line type indicator.
    1116  */
    1117 static const char *ScmStreamGetLine(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol)
    1118 {
    1119     /** @todo this doesn't work when pStream->off !=
    1120      *        pStream->paLines[pStream->iLine-1].pff. */
    1121     if (!pStream->fFullyLineated)
    1122         return scmStreamGetLineInternal(pStream, pcchLine, penmEol);
    1123     return ScmStreamGetLineByNo(pStream, pStream->iLine, pcchLine, penmEol);
    1124 }
    1125 
    1126 /**
    1127  * Reads @a cbToRead bytes into @a pvBuf.
    1128  *
    1129  * Will fail if end of stream is encountered before the entire read has been
    1130  * completed.
    1131  *
    1132  * @returns IPRT status code.
    1133  * @retval  VERR_EOF if there isn't @a cbToRead bytes left to read.  Stream
    1134  *          position will be unchanged.
    1135  *
    1136  * @param   pStream             The stream.  Must be in read mode.
    1137  * @param   pvBuf               The buffer to read into.
    1138  * @param   cbToRead            The number of bytes to read.
    1139  */
    1140 static int ScmStreamRead(PSCMSTREAM pStream, void *pvBuf, size_t cbToRead)
    1141 {
    1142     AssertReturn(!pStream->fWriteOrRead, VERR_PERMISSION_DENIED);
    1143     if (RT_FAILURE(pStream->rc))
    1144         return pStream->rc;
    1145 
    1146     /* If there isn't enough stream left, fail already. */
    1147     if (RT_UNLIKELY(pStream->cb - pStream->cb < cbToRead))
    1148         return VERR_EOF;
    1149 
    1150     /* Copy the data and simply seek to the new stream position. */
    1151     memcpy(pvBuf, &pStream->pch[pStream->off], cbToRead);
    1152     return ScmStreamSeekAbsolute(pStream, pStream->off + cbToRead);
    1153 }
    1154 
    1155 /**
    1156  * Checks if the given line is empty or full of white space.
    1157  *
    1158  * @returns true if white space only, false if not (or if non-existant).
    1159  * @param   pStream             The stream.  Must be in read mode.
    1160  * @param   iLine               The line in question.
    1161  */
    1162 static bool ScmStreamIsWhiteLine(PSCMSTREAM pStream, size_t iLine)
    1163 {
    1164     SCMEOL      enmEol;
    1165     size_t      cchLine;
    1166     const char *pchLine = ScmStreamGetLineByNo(pStream, iLine, &cchLine, &enmEol);
    1167     if (!pchLine)
    1168         return false;
    1169     while (cchLine && RT_C_IS_SPACE(*pchLine))
    1170         pchLine++, cchLine--;
    1171     return cchLine == 0;
    1172 }
    1173 
    1174 /**
    1175  * Try figure out the end of line style of the give stream.
    1176  *
    1177  * @returns Most likely end of line style.
    1178  * @param   pStream             The stream.
    1179  */
    1180 SCMEOL ScmStreamGetEol(PSCMSTREAM pStream)
    1181 {
    1182     SCMEOL enmEol;
    1183     if (pStream->cLines > 0)
    1184         enmEol = pStream->paLines[0].enmEol;
    1185     else if (pStream->cb == 0)
    1186         enmEol = SCMEOL_NONE;
    1187     else
    1188     {
    1189         const char *pchLF = (const char *)memchr(pStream->pch, '\n', pStream->cb);
    1190         if (pchLF && pchLF != pStream->pch && pchLF[-1] == '\r')
    1191             enmEol = SCMEOL_CRLF;
    1192         else
    1193             enmEol = SCMEOL_LF;
    1194     }
    1195 
    1196     if (enmEol == SCMEOL_NONE)
    1197 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
    1198         enmEol = SCMEOL_CRLF;
    1199 #else
    1200         enmEol = SCMEOL_LF;
    1201 #endif
    1202     return enmEol;
    1203 }
    1204 
    1205 /**
    1206  * Get the end of line indicator type for a line.
    1207  *
    1208  * @returns The EOL indicator.  If the line isn't found, the default EOL
    1209  *          indicator is return.
    1210  * @param   pStream             The stream.
    1211  * @param   iLine               The line (0-base).
    1212  */
    1213 SCMEOL ScmStreamGetEolByLine(PSCMSTREAM pStream, size_t iLine)
    1214 {
    1215     SCMEOL enmEol;
    1216     if (iLine < pStream->cLines)
    1217         enmEol = pStream->paLines[iLine].enmEol;
    1218     else
    1219 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
    1220         enmEol = SCMEOL_CRLF;
    1221 #else
    1222         enmEol = SCMEOL_LF;
    1223 #endif
    1224     return enmEol;
    1225 }
    1226 
    1227 /**
    1228  * Appends a line to the stream.
    1229  *
    1230  * @returns IPRT status code.
    1231  * @param   pStream             The stream.  Must be in write mode.
    1232  * @param   pchLine             Pointer to the line.
    1233  * @param   cchLine             Line length.
    1234  * @param   enmEol              Which end of line indicator to use.
    1235  */
    1236 int ScmStreamPutLine(PSCMSTREAM pStream, const char *pchLine, size_t cchLine, SCMEOL enmEol)
    1237 {
    1238     AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);
    1239     if (RT_FAILURE(pStream->rc))
    1240         return pStream->rc;
    1241 
    1242     /*
    1243      * Make sure the previous line has a new-line indicator.
    1244      */
    1245     size_t off   = pStream->off;
    1246     size_t iLine = pStream->iLine;
    1247     if (RT_UNLIKELY(   iLine != 0
    1248                     && pStream->paLines[iLine - 1].enmEol == SCMEOL_NONE))
    1249     {
    1250         AssertReturn(pStream->paLines[iLine].cch == 0, VERR_INTERNAL_ERROR_3);
    1251         SCMEOL enmEol2 = enmEol != SCMEOL_NONE ? enmEol : ScmStreamGetEol(pStream);
    1252         if (RT_UNLIKELY(off + cchLine + enmEol + enmEol2 > pStream->cbAllocated))
    1253         {
    1254             int rc = scmStreamGrowBuffer(pStream, cchLine + enmEol + enmEol2);
    1255             if (RT_FAILURE(rc))
    1256                 return rc;
    1257         }
    1258         if (enmEol2 == SCMEOL_LF)
    1259             pStream->pch[off++] = '\n';
    1260         else
    1261         {
    1262             pStream->pch[off++] = '\r';
    1263             pStream->pch[off++] = '\n';
    1264         }
    1265         pStream->paLines[iLine - 1].enmEol = enmEol2;
    1266         pStream->paLines[iLine].off = off;
    1267         pStream->off = off;
    1268         pStream->cb  = off;
    1269     }
    1270 
    1271     /*
    1272      * Ensure we've got sufficient buffer space.
    1273      */
    1274     if (RT_UNLIKELY(off + cchLine + enmEol > pStream->cbAllocated))
    1275     {
    1276         int rc = scmStreamGrowBuffer(pStream, cchLine + enmEol);
    1277         if (RT_FAILURE(rc))
    1278             return rc;
    1279     }
    1280 
    1281     /*
    1282      * Add a line record.
    1283      */
    1284     if (RT_UNLIKELY(iLine + 1 >= pStream->cLinesAllocated))
    1285     {
    1286         int rc = scmStreamGrowLines(pStream, iLine);
    1287         if (RT_FAILURE(rc))
    1288             return rc;
    1289     }
    1290 
    1291     pStream->paLines[iLine].cch    = off - pStream->paLines[iLine].off + cchLine;
    1292     pStream->paLines[iLine].enmEol = enmEol;
    1293 
    1294     iLine++;
    1295     pStream->cLines = iLine;
    1296     pStream->iLine  = iLine;
    1297 
    1298     /*
    1299      * Copy the line
    1300      */
    1301     memcpy(&pStream->pch[off], pchLine, cchLine);
    1302     off += cchLine;
    1303     if (enmEol == SCMEOL_LF)
    1304         pStream->pch[off++] = '\n';
    1305     else if (enmEol == SCMEOL_CRLF)
    1306     {
    1307         pStream->pch[off++] = '\r';
    1308         pStream->pch[off++] = '\n';
    1309     }
    1310     pStream->off = off;
    1311     pStream->cb  = off;
    1312 
    1313     /*
    1314      * Start a new line.
    1315      */
    1316     pStream->paLines[iLine].off    = off;
    1317     pStream->paLines[iLine].cch    = 0;
    1318     pStream->paLines[iLine].enmEol = SCMEOL_NONE;
    1319 
    1320     return VINF_SUCCESS;
    1321 }
    1322 
    1323 /**
    1324  * Writes to the stream.
    1325  *
    1326  * @returns IPRT status code
    1327  * @param   pStream             The stream.  Must be in write mode.
    1328  * @param   pchBuf              What to write.
    1329  * @param   cchBuf              How much to write.
    1330  */
    1331 int ScmStreamWrite(PSCMSTREAM pStream, const char *pchBuf, size_t cchBuf)
    1332 {
    1333     AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);
    1334     if (RT_FAILURE(pStream->rc))
    1335         return pStream->rc;
    1336 
    1337     /*
    1338      * Ensure we've got sufficient buffer space.
    1339      */
    1340     size_t off = pStream->off;
    1341     if (RT_UNLIKELY(off + cchBuf > pStream->cbAllocated))
    1342     {
    1343         int rc = scmStreamGrowBuffer(pStream, cchBuf);
    1344         if (RT_FAILURE(rc))
    1345             return rc;
    1346     }
    1347 
    1348     /*
    1349      * Deal with the odd case where we've already pushed a line with SCMEOL_NONE.
    1350      */
    1351     size_t iLine = pStream->iLine;
    1352     if (RT_UNLIKELY(   iLine > 0
    1353                     && pStream->paLines[iLine - 1].enmEol == SCMEOL_NONE))
    1354     {
    1355         iLine--;
    1356         pStream->cLines = iLine;
    1357         pStream->iLine  = iLine;
    1358     }
    1359 
    1360     /*
    1361      * Deal with lines.
    1362      */
    1363     const char *pchLF = (const char *)memchr(pchBuf, '\n', cchBuf);
    1364     if (!pchLF)
    1365         pStream->paLines[iLine].cch += cchBuf;
    1366     else
    1367     {
    1368         const char *pchLine = pchBuf;
    1369         for (;;)
    1370         {
    1371             if (RT_UNLIKELY(iLine + 1 >= pStream->cLinesAllocated))
    1372             {
    1373                 int rc = scmStreamGrowLines(pStream, iLine);
    1374                 if (RT_FAILURE(rc))
    1375                 {
    1376                     iLine = pStream->iLine;
    1377                     pStream->paLines[iLine].cch    = off - pStream->paLines[iLine].off;
    1378                     pStream->paLines[iLine].enmEol = SCMEOL_NONE;
    1379                     return rc;
    1380                 }
    1381             }
    1382 
    1383             size_t cchLine = pchLF - pchLine;
    1384             if (   cchLine
    1385                 ?  pchLF[-1] != '\r'
    1386                 :     !pStream->paLines[iLine].cch
    1387                    || pStream->pch[pStream->paLines[iLine].off + pStream->paLines[iLine].cch - 1] != '\r')
    1388                 pStream->paLines[iLine].enmEol = SCMEOL_LF;
    1389             else
    1390             {
    1391                 pStream->paLines[iLine].enmEol = SCMEOL_CRLF;
    1392                 cchLine--;
    1393             }
    1394             pStream->paLines[iLine].cch += cchLine;
    1395 
    1396             iLine++;
    1397             size_t offBuf = pchLF + 1 - pchBuf;
    1398             pStream->paLines[iLine].off    = off + offBuf;
    1399             pStream->paLines[iLine].cch    = 0;
    1400             pStream->paLines[iLine].enmEol = SCMEOL_NONE;
    1401 
    1402             size_t cchLeft = cchBuf - offBuf;
    1403             pchLF = (const char *)memchr(pchLF + 1, '\n', cchLeft);
    1404             if (!pchLF)
    1405             {
    1406                 pStream->paLines[iLine].cch = cchLeft;
    1407                 break;
    1408             }
    1409         }
    1410 
    1411         pStream->iLine  = iLine;
    1412         pStream->cLines = iLine;
    1413     }
    1414 
    1415     /*
    1416      * Copy the data and update position and size.
    1417      */
    1418     memcpy(&pStream->pch[off], pchBuf, cchBuf);
    1419     off += cchBuf;
    1420     pStream->off = off;
    1421     pStream->cb  = off;
    1422 
    1423     return VINF_SUCCESS;
    1424 }
    1425 
    1426 /**
    1427  * Write a character to the stream.
    1428  *
    1429  * @returns IPRT status code
    1430  * @param   pStream             The stream.  Must be in write mode.
    1431  * @param   pchBuf              What to write.
    1432  * @param   cchBuf              How much to write.
    1433  */
    1434 int ScmStreamPutCh(PSCMSTREAM pStream, char ch)
    1435 {
    1436     AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);
    1437     if (RT_FAILURE(pStream->rc))
    1438         return pStream->rc;
    1439 
    1440     /*
    1441      * Only deal with the simple cases here, use ScmStreamWrite for the
    1442      * annoying stuff.
    1443      */
    1444     size_t off = pStream->off;
    1445     if (   ch == '\n'
    1446         || RT_UNLIKELY(off + 1 > pStream->cbAllocated))
    1447         return ScmStreamWrite(pStream, &ch, 1);
    1448 
    1449     /*
    1450      * Just append it.
    1451      */
    1452     pStream->pch[off] = ch;
    1453     pStream->off = off + 1;
    1454     pStream->paLines[pStream->iLine].cch++;
    1455 
    1456     return VINF_SUCCESS;
    1457 }
    1458 
    1459 /**
    1460  * Copies @a cLines from the @a pSrc stream onto the @a pDst stream.
    1461  *
    1462  * The stream positions will be used and changed in both streams.
    1463  *
    1464  * @returns IPRT status code.
    1465  * @param   pDst                The destination stream.  Must be in write mode.
    1466  * @param   cLines              The number of lines.  (0 is accepted.)
    1467  * @param   pSrc                The source stream.  Must be in read mode.
    1468  */
    1469 int ScmStreamCopyLines(PSCMSTREAM pDst, PSCMSTREAM pSrc, size_t cLines)
    1470 {
    1471     AssertReturn(pDst->fWriteOrRead, VERR_ACCESS_DENIED);
    1472     if (RT_FAILURE(pDst->rc))
    1473         return pDst->rc;
    1474 
    1475     AssertReturn(!pSrc->fWriteOrRead, VERR_ACCESS_DENIED);
    1476     if (RT_FAILURE(pSrc->rc))
    1477         return pSrc->rc;
    1478 
    1479     while (cLines-- > 0)
    1480     {
    1481         SCMEOL      enmEol;
    1482         size_t      cchLine;
    1483         const char *pchLine = ScmStreamGetLine(pSrc, &cchLine, &enmEol);
    1484         if (!pchLine)
    1485             return pDst->rc = (RT_FAILURE(pSrc->rc) ? pSrc->rc : VERR_EOF);
    1486 
    1487         int rc = ScmStreamPutLine(pDst, pchLine, cchLine, enmEol);
    1488         if (RT_FAILURE(rc))
    1489             return rc;
    1490     }
    1491 
    1492     return VINF_SUCCESS;
    1493 }
    1494438
    1495439/* -=-=-=-=-=- diff -=-=-=-=-=- */
  • trunk/src/bldprogs/scmstream.cpp

    r40523 r40528  
    11/* $Id$ */
    22/** @file
    3  * IPRT Testcase / Tool - Source Code Massager.
     3 * IPRT Testcase / Tool - Source Code Massager Stream Code.
    44 */
    55
    66/*
    7  * Copyright (C) 2010 Oracle Corporation
     7 * Copyright (C) 2010-2012 Oracle Corporation
    88 *
    99 * This file is part of VirtualBox Open Source Edition (OSE), as
     
    2121#include <iprt/assert.h>
    2222#include <iprt/ctype.h>
    23 #include <iprt/dir.h>
    24 #include <iprt/env.h>
     23//#include <iprt/dir.h>
     24//#include <iprt/env.h>
    2525#include <iprt/file.h>
    2626#include <iprt/err.h>
    27 #include <iprt/getopt.h>
    28 #include <iprt/initterm.h>
     27//#include <iprt/getopt.h>
     28//#include <iprt/initterm.h>
    2929#include <iprt/mem.h>
    30 #include <iprt/message.h>
    31 #include <iprt/param.h>
    32 #include <iprt/path.h>
    33 #include <iprt/process.h>
    34 #include <iprt/stream.h>
     30//#include <iprt/message.h>
     31//#include <iprt/param.h>
     32//#include <iprt/path.h>
     33//#include <iprt/process.h>
     34//#include <iprt/stream.h>
    3535#include <iprt/string.h>
    3636
    37 
    38 /*******************************************************************************
    39 *   Defined Constants And Macros                                               *
    40 *******************************************************************************/
    41 /** The name of the settings files. */
    42 #define SCM_SETTINGS_FILENAME           ".scm-settings"
    43 
    44 
    45 /*******************************************************************************
    46 *   Structures and Typedefs                                                    *
    47 *******************************************************************************/
    48 /** Pointer to const massager settings. */
    49 typedef struct SCMSETTINGSBASE const *PCSCMSETTINGSBASE;
    50 
    51 /** End of line marker type. */
    52 typedef enum SCMEOL
    53 {
    54     SCMEOL_NONE = 0,
    55     SCMEOL_LF = 1,
    56     SCMEOL_CRLF = 2
    57 } SCMEOL;
    58 /** Pointer to an end of line marker type. */
    59 typedef SCMEOL *PSCMEOL;
    60 
    61 /**
    62  * Line record.
    63  */
    64 typedef struct SCMSTREAMLINE
    65 {
    66     /** The offset of the line. */
    67     size_t          off;
    68     /** The line length, excluding the LF character.
    69      * @todo This could be derived from the offset of the next line if that wasn't
    70      *       so tedious. */
    71     size_t          cch;
    72     /** The end of line marker type. */
    73     SCMEOL          enmEol;
    74 } SCMSTREAMLINE;
    75 /** Pointer to a line record. */
    76 typedef SCMSTREAMLINE *PSCMSTREAMLINE;
    77 
    78 /**
    79  * Source code massager stream.
    80  */
    81 typedef struct SCMSTREAM
    82 {
    83     /** Pointer to the file memory. */
    84     char           *pch;
    85     /** The current stream position. */
    86     size_t          off;
    87     /** The current stream size. */
    88     size_t          cb;
    89     /** The size of the memory pb points to. */
    90     size_t          cbAllocated;
    91 
    92     /** Line records. */
    93     PSCMSTREAMLINE  paLines;
    94     /** The current line. */
    95     size_t          iLine;
    96     /** The current stream size given in lines.   */
    97     size_t          cLines;
    98     /** The sizeof the the memory backing paLines.   */
    99     size_t          cLinesAllocated;
    100 
    101     /** Set if write-only, clear if read-only. */
    102     bool            fWriteOrRead;
    103     /** Set if the memory pb points to is from RTFileReadAll. */
    104     bool            fFileMemory;
    105     /** Set if fully broken into lines. */
    106     bool            fFullyLineated;
    107 
    108     /** Stream status code (IPRT). */
    109     int             rc;
    110 } SCMSTREAM;
    111 /** Pointer to a SCM stream. */
    112 typedef SCMSTREAM *PSCMSTREAM;
    113 /** Pointer to a const SCM stream. */
    114 typedef SCMSTREAM const *PCSCMSTREAM;
    115 
    116 
    117 /**
    118  * SVN property.
    119  */
    120 typedef struct SCMSVNPROP
    121 {
    122     /** The property. */
    123     char           *pszName;
    124     /** The value.
    125      * When used to record updates, this can be set to NULL to trigger the
    126      * deletion of the property. */
    127     char           *pszValue;
    128 } SCMSVNPROP;
    129 /** Pointer to a SVN property. */
    130 typedef SCMSVNPROP *PSCMSVNPROP;
    131 /** Pointer to a const  SVN property. */
    132 typedef SCMSVNPROP const *PCSCMSVNPROP;
    133 
    134 
    135 /**
    136  * Rewriter state.
    137  */
    138 typedef struct SCMRWSTATE
    139 {
    140     /** The filename.  */
    141     const char     *pszFilename;
    142     /** Set after the printing the first verbose message about a file under
    143      *  rewrite. */
    144     bool            fFirst;
    145     /** The number of SVN property changes. */
    146     size_t          cSvnPropChanges;
    147     /** Pointer to an array of SVN property changes. */
    148     PSCMSVNPROP     paSvnPropChanges;
    149 } SCMRWSTATE;
    150 /** Pointer to the rewriter state. */
    151 typedef SCMRWSTATE *PSCMRWSTATE;
    152 
    153 /**
    154  * A rewriter.
    155  *
    156  * This works like a stream editor, reading @a pIn, modifying it and writing it
    157  * to @a pOut.
    158  *
    159  * @returns true if any changes were made, false if not.
    160  * @param   pIn                 The input stream.
    161  * @param   pOut                The output stream.
    162  * @param   pSettings           The settings.
    163  */
    164 typedef bool (*PFNSCMREWRITER)(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    165 
    166 
    167 /**
    168  * Configuration entry.
    169  */
    170 typedef struct SCMCFGENTRY
    171 {
    172     /** Number of rewriters. */
    173     size_t          cRewriters;
    174     /** Pointer to an array of rewriters. */
    175     PFNSCMREWRITER const  *papfnRewriter;
    176     /** File pattern (simple).  */
    177     const char     *pszFilePattern;
    178 } SCMCFGENTRY;
    179 typedef SCMCFGENTRY *PSCMCFGENTRY;
    180 typedef SCMCFGENTRY const *PCSCMCFGENTRY;
    181 
    182 
    183 /**
    184  * Diff state.
    185  */
    186 typedef struct SCMDIFFSTATE
    187 {
    188     size_t          cDiffs;
    189     const char     *pszFilename;
    190 
    191     PSCMSTREAM      pLeft;
    192     PSCMSTREAM      pRight;
    193 
    194     /** Whether to ignore end of line markers when diffing. */
    195     bool            fIgnoreEol;
    196     /** Whether to ignore trailing whitespace. */
    197     bool            fIgnoreTrailingWhite;
    198     /** Whether to ignore leading whitespace. */
    199     bool            fIgnoreLeadingWhite;
    200     /** Whether to print special characters in human readable form or not. */
    201     bool            fSpecialChars;
    202     /** The tab size. */
    203     size_t          cchTab;
    204     /** Where to push the diff. */
    205     PRTSTREAM       pDiff;
    206 } SCMDIFFSTATE;
    207 /** Pointer to a diff state. */
    208 typedef SCMDIFFSTATE *PSCMDIFFSTATE;
    209 
    210 /**
    211  * Source Code Massager Settings.
    212  */
    213 typedef struct SCMSETTINGSBASE
    214 {
    215     bool            fConvertEol;
    216     bool            fConvertTabs;
    217     bool            fForceFinalEol;
    218     bool            fForceTrailingLine;
    219     bool            fStripTrailingBlanks;
    220     bool            fStripTrailingLines;
    221     /** Only process files that are part of a SVN working copy. */
    222     bool            fOnlySvnFiles;
    223     /** Only recurse into directories containing an .svn dir.  */
    224     bool            fOnlySvnDirs;
    225     /** Set svn:eol-style if missing or incorrect. */
    226     bool            fSetSvnEol;
    227     /** Set svn:executable according to type (unusually this means deleting it). */
    228     bool            fSetSvnExecutable;
    229     /** Set svn:keyword if completely or partially missing. */
    230     bool            fSetSvnKeywords;
    231     /**  */
    232     unsigned        cchTab;
    233     /** Only consider files matching these patterns.  This is only applied to the
    234      *  base names. */
    235     char           *pszFilterFiles;
    236     /** Filter out files matching the following patterns.  This is applied to base
    237      *  names as well as the absolute paths.  */
    238     char           *pszFilterOutFiles;
    239     /** Filter out directories matching the following patterns.  This is applied
    240      *  to base names as well as the absolute paths.  All absolute paths ends with a
    241      *  slash and dot ("/.").  */
    242     char           *pszFilterOutDirs;
    243 } SCMSETTINGSBASE;
    244 /** Pointer to massager settings. */
    245 typedef SCMSETTINGSBASE *PSCMSETTINGSBASE;
    246 
    247 /**
    248  * Option identifiers.
    249  *
    250  * @note    The first chunk, down to SCMOPT_TAB_SIZE, are alternately set &
    251  *          clear.  So, the option setting a flag (boolean) will have an even
    252  *          number and the one clearing it will have an odd number.
    253  * @note    Down to SCMOPT_LAST_SETTINGS corresponds exactly to SCMSETTINGSBASE.
    254  */
    255 typedef enum SCMOPT
    256 {
    257     SCMOPT_CONVERT_EOL = 10000,
    258     SCMOPT_NO_CONVERT_EOL,
    259     SCMOPT_CONVERT_TABS,
    260     SCMOPT_NO_CONVERT_TABS,
    261     SCMOPT_FORCE_FINAL_EOL,
    262     SCMOPT_NO_FORCE_FINAL_EOL,
    263     SCMOPT_FORCE_TRAILING_LINE,
    264     SCMOPT_NO_FORCE_TRAILING_LINE,
    265     SCMOPT_STRIP_TRAILING_BLANKS,
    266     SCMOPT_NO_STRIP_TRAILING_BLANKS,
    267     SCMOPT_STRIP_TRAILING_LINES,
    268     SCMOPT_NO_STRIP_TRAILING_LINES,
    269     SCMOPT_ONLY_SVN_DIRS,
    270     SCMOPT_NOT_ONLY_SVN_DIRS,
    271     SCMOPT_ONLY_SVN_FILES,
    272     SCMOPT_NOT_ONLY_SVN_FILES,
    273     SCMOPT_SET_SVN_EOL,
    274     SCMOPT_DONT_SET_SVN_EOL,
    275     SCMOPT_SET_SVN_EXECUTABLE,
    276     SCMOPT_DONT_SET_SVN_EXECUTABLE,
    277     SCMOPT_SET_SVN_KEYWORDS,
    278     SCMOPT_DONT_SET_SVN_KEYWORDS,
    279     SCMOPT_TAB_SIZE,
    280     SCMOPT_FILTER_OUT_DIRS,
    281     SCMOPT_FILTER_FILES,
    282     SCMOPT_FILTER_OUT_FILES,
    283     SCMOPT_LAST_SETTINGS = SCMOPT_FILTER_OUT_FILES,
    284     //
    285     SCMOPT_DIFF_IGNORE_EOL,
    286     SCMOPT_DIFF_NO_IGNORE_EOL,
    287     SCMOPT_DIFF_IGNORE_SPACE,
    288     SCMOPT_DIFF_NO_IGNORE_SPACE,
    289     SCMOPT_DIFF_IGNORE_LEADING_SPACE,
    290     SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE,
    291     SCMOPT_DIFF_IGNORE_TRAILING_SPACE,
    292     SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE,
    293     SCMOPT_DIFF_SPECIAL_CHARS,
    294     SCMOPT_DIFF_NO_SPECIAL_CHARS,
    295     SCMOPT_END
    296 } SCMOPT;
    297 
    298 
    299 /**
    300  * File/dir pattern + options.
    301  */
    302 typedef struct SCMPATRNOPTPAIR
    303 {
    304     char *pszPattern;
    305     char *pszOptions;
    306 } SCMPATRNOPTPAIR;
    307 /** Pointer to a pattern + option pair. */
    308 typedef SCMPATRNOPTPAIR *PSCMPATRNOPTPAIR;
    309 
    310 
    311 /** Pointer to a settings set. */
    312 typedef struct SCMSETTINGS *PSCMSETTINGS;
    313 /**
    314  * Settings set.
    315  *
    316  * This structure is constructed from the command line arguments or any
    317  * .scm-settings file found in a directory we recurse into.  When recursing in
    318  * and out of a directory, we push and pop a settings set for it.
    319  *
    320  * The .scm-settings file has two kinds of setttings, first there are the
    321  * unqualified base settings and then there are the settings which applies to a
    322  * set of files or directories.  The former are lines with command line options.
    323  * For the latter, the options are preceded by a string pattern and a colon.
    324  * The pattern specifies which files (and/or directories) the options applies
    325  * to.
    326  *
    327  * We parse the base options into the Base member and put the others into the
    328  * paPairs array.
    329  */
    330 typedef struct SCMSETTINGS
    331 {
    332     /** Pointer to the setting file below us in the stack. */
    333     PSCMSETTINGS        pDown;
    334     /** Pointer to the setting file above us in the stack. */
    335     PSCMSETTINGS        pUp;
    336     /** File/dir patterns and their options. */
    337     PSCMPATRNOPTPAIR    paPairs;
    338     /** The number of entires in paPairs. */
    339     uint32_t            cPairs;
    340     /** The base settings that was read out of the file. */
    341     SCMSETTINGSBASE     Base;
    342 } SCMSETTINGS;
    343 /** Pointer to a const settings set. */
    344 typedef SCMSETTINGS const *PCSCMSETTINGS;
    345 
    346 
    347 /*******************************************************************************
    348 *   Internal Functions                                                         *
    349 *******************************************************************************/
    350 static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    351 static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    352 static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    353 static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    354 static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    355 static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    356 static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    357 static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    358 static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    359 static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    360 static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    361 
    362 
    363 /*******************************************************************************
    364 *   Global Variables                                                           *
    365 *******************************************************************************/
    366 static const char   g_szProgName[]          = "scm";
    367 static const char  *g_pszChangedSuff        = "";
    368 static const char   g_szTabSpaces[16+1]     = "                ";
    369 static bool         g_fDryRun               = true;
    370 static bool         g_fDiffSpecialChars     = true;
    371 static bool         g_fDiffIgnoreEol        = false;
    372 static bool         g_fDiffIgnoreLeadingWS  = false;
    373 static bool         g_fDiffIgnoreTrailingWS = false;
    374 static int          g_iVerbosity            = 2;//99; //0;
    375 
    376 /** The global settings. */
    377 static SCMSETTINGSBASE const g_Defaults =
    378 {
    379     /* .fConvertEol = */            true,
    380     /* .fConvertTabs = */           true,
    381     /* .fForceFinalEol = */         true,
    382     /* .fForceTrailingLine = */     false,
    383     /* .fStripTrailingBlanks = */   true,
    384     /* .fStripTrailingLines = */    true,
    385     /* .fOnlySvnFiles = */          false,
    386     /* .fOnlySvnDirs = */           false,
    387     /* .fSetSvnEol = */             false,
    388     /* .fSetSvnExecutable = */      false,
    389     /* .fSetSvnKeywords = */        false,
    390     /* .cchTab = */                 8,
    391     /* .pszFilterFiles = */         (char *)"",
    392     /* .pszFilterOutFiles = */      (char *)"*.exe|*.com|20*-*-*.log",
    393     /* .pszFilterOutDirs = */       (char *)".svn|.hg|.git|CVS",
    394 };
    395 
    396 /** Option definitions for the base settings. */
    397 static RTGETOPTDEF  g_aScmOpts[] =
    398 {
    399     { "--convert-eol",                      SCMOPT_CONVERT_EOL,                     RTGETOPT_REQ_NOTHING },
    400     { "--no-convert-eol",                   SCMOPT_NO_CONVERT_EOL,                  RTGETOPT_REQ_NOTHING },
    401     { "--convert-tabs",                     SCMOPT_CONVERT_TABS,                    RTGETOPT_REQ_NOTHING },
    402     { "--no-convert-tabs",                  SCMOPT_NO_CONVERT_TABS,                 RTGETOPT_REQ_NOTHING },
    403     { "--force-final-eol",                  SCMOPT_FORCE_FINAL_EOL,                 RTGETOPT_REQ_NOTHING },
    404     { "--no-force-final-eol",               SCMOPT_NO_FORCE_FINAL_EOL,              RTGETOPT_REQ_NOTHING },
    405     { "--force-trailing-line",              SCMOPT_FORCE_TRAILING_LINE,             RTGETOPT_REQ_NOTHING },
    406     { "--no-force-trailing-line",           SCMOPT_NO_FORCE_TRAILING_LINE,          RTGETOPT_REQ_NOTHING },
    407     { "--strip-trailing-blanks",            SCMOPT_STRIP_TRAILING_BLANKS,           RTGETOPT_REQ_NOTHING },
    408     { "--no-strip-trailing-blanks",         SCMOPT_NO_STRIP_TRAILING_BLANKS,        RTGETOPT_REQ_NOTHING },
    409     { "--strip-trailing-lines",             SCMOPT_STRIP_TRAILING_LINES,            RTGETOPT_REQ_NOTHING },
    410     { "--strip-no-trailing-lines",          SCMOPT_NO_STRIP_TRAILING_LINES,         RTGETOPT_REQ_NOTHING },
    411     { "--only-svn-dirs",                    SCMOPT_ONLY_SVN_DIRS,                   RTGETOPT_REQ_NOTHING },
    412     { "--not-only-svn-dirs",                SCMOPT_NOT_ONLY_SVN_DIRS,               RTGETOPT_REQ_NOTHING },
    413     { "--only-svn-files",                   SCMOPT_ONLY_SVN_FILES,                  RTGETOPT_REQ_NOTHING },
    414     { "--not-only-svn-files",               SCMOPT_NOT_ONLY_SVN_FILES,              RTGETOPT_REQ_NOTHING },
    415     { "--set-svn-eol",                      SCMOPT_SET_SVN_EOL,                     RTGETOPT_REQ_NOTHING },
    416     { "--dont-set-svn-eol",                 SCMOPT_DONT_SET_SVN_EOL,                RTGETOPT_REQ_NOTHING },
    417     { "--set-svn-executable",               SCMOPT_SET_SVN_EXECUTABLE,              RTGETOPT_REQ_NOTHING },
    418     { "--dont-set-svn-executable",          SCMOPT_DONT_SET_SVN_EXECUTABLE,         RTGETOPT_REQ_NOTHING },
    419     { "--set-svn-keywords",                 SCMOPT_SET_SVN_KEYWORDS,                RTGETOPT_REQ_NOTHING },
    420     { "--dont-set-svn-keywords",            SCMOPT_DONT_SET_SVN_KEYWORDS,           RTGETOPT_REQ_NOTHING },
    421     { "--tab-size",                         SCMOPT_TAB_SIZE,                        RTGETOPT_REQ_UINT8   },
    422     { "--filter-out-dirs",                  SCMOPT_FILTER_OUT_DIRS,                 RTGETOPT_REQ_STRING  },
    423     { "--filter-files",                     SCMOPT_FILTER_FILES,                    RTGETOPT_REQ_STRING  },
    424     { "--filter-out-files",                 SCMOPT_FILTER_OUT_FILES,                RTGETOPT_REQ_STRING  },
    425 };
    426 
    427 /** Consider files matching the following patterns (base names only). */
    428 static const char  *g_pszFileFilter         = NULL;
    429 
    430 static PFNSCMREWRITER const g_aRewritersFor_Makefile_kup[] =
    431 {
    432     rewrite_SvnNoExecutable,
    433     rewrite_Makefile_kup
    434 };
    435 
    436 static PFNSCMREWRITER const g_aRewritersFor_Makefile_kmk[] =
    437 {
    438     rewrite_ForceNativeEol,
    439     rewrite_StripTrailingBlanks,
    440     rewrite_AdjustTrailingLines,
    441     rewrite_SvnNoExecutable,
    442     rewrite_SvnKeywords,
    443     rewrite_Makefile_kmk
    444 };
    445 
    446 static PFNSCMREWRITER const g_aRewritersFor_C_and_CPP[] =
    447 {
    448     rewrite_ForceNativeEol,
    449     rewrite_ExpandTabs,
    450     rewrite_StripTrailingBlanks,
    451     rewrite_AdjustTrailingLines,
    452     rewrite_SvnNoExecutable,
    453     rewrite_SvnKeywords,
    454     rewrite_C_and_CPP
    455 };
    456 
    457 static PFNSCMREWRITER const g_aRewritersFor_H_and_HPP[] =
    458 {
    459     rewrite_ForceNativeEol,
    460     rewrite_ExpandTabs,
    461     rewrite_StripTrailingBlanks,
    462     rewrite_AdjustTrailingLines,
    463     rewrite_SvnNoExecutable,
    464     rewrite_C_and_CPP
    465 };
    466 
    467 static PFNSCMREWRITER const g_aRewritersFor_RC[] =
    468 {
    469     rewrite_ForceNativeEol,
    470     rewrite_ExpandTabs,
    471     rewrite_StripTrailingBlanks,
    472     rewrite_AdjustTrailingLines,
    473     rewrite_SvnNoExecutable,
    474     rewrite_SvnKeywords
    475 };
    476 
    477 static PFNSCMREWRITER const g_aRewritersFor_ShellScripts[] =
    478 {
    479     rewrite_ForceLF,
    480     rewrite_ExpandTabs,
    481     rewrite_StripTrailingBlanks
    482 };
    483 
    484 static PFNSCMREWRITER const g_aRewritersFor_BatchFiles[] =
    485 {
    486     rewrite_ForceCRLF,
    487     rewrite_ExpandTabs,
    488     rewrite_StripTrailingBlanks
    489 };
    490 
    491 static SCMCFGENTRY const g_aConfigs[] =
    492 {
    493     { RT_ELEMENTS(g_aRewritersFor_Makefile_kup), &g_aRewritersFor_Makefile_kup[0], "Makefile.kup" },
    494     { RT_ELEMENTS(g_aRewritersFor_Makefile_kmk), &g_aRewritersFor_Makefile_kmk[0], "Makefile.kmk|Config.kmk" },
    495     { RT_ELEMENTS(g_aRewritersFor_C_and_CPP),    &g_aRewritersFor_C_and_CPP[0],    "*.c|*.cpp|*.C|*.CPP|*.cxx|*.cc" },
    496     { RT_ELEMENTS(g_aRewritersFor_H_and_HPP),    &g_aRewritersFor_H_and_HPP[0],    "*.h|*.hpp" },
    497     { RT_ELEMENTS(g_aRewritersFor_RC),           &g_aRewritersFor_RC[0],           "*.rc" },
    498     { RT_ELEMENTS(g_aRewritersFor_ShellScripts), &g_aRewritersFor_ShellScripts[0], "*.sh|configure" },
    499     { RT_ELEMENTS(g_aRewritersFor_BatchFiles),   &g_aRewritersFor_BatchFiles[0],   "*.bat|*.cmd|*.btm|*.vbs|*.ps1" },
    500 };
    501 
    502 
    503 /* -=-=-=-=-=- memory streams -=-=-=-=-=- */
     37#include "scmstream.h"
    50438
    50539
     
    1066600 * @param   penmEol             Where to return the end of line type indicator.
    1067601 */
    1068 static const char *ScmStreamGetLineByNo(PSCMSTREAM pStream, size_t iLine, size_t *pcchLine, PSCMEOL penmEol)
     602const char *ScmStreamGetLineByNo(PSCMSTREAM pStream, size_t iLine, size_t *pcchLine, PSCMEOL penmEol)
    1069603{
    1070604    AssertReturn(!pStream->fWriteOrRead, NULL);
     
    1115649 * @param   penmEol             Where to return the end of line type indicator.
    1116650 */
    1117 static const char *ScmStreamGetLine(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol)
     651const char *ScmStreamGetLine(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol)
    1118652{
    1119653    /** @todo this doesn't work when pStream->off !=
     
    1138672 * @param   cbToRead            The number of bytes to read.
    1139673 */
    1140 static int ScmStreamRead(PSCMSTREAM pStream, void *pvBuf, size_t cbToRead)
     674int ScmStreamRead(PSCMSTREAM pStream, void *pvBuf, size_t cbToRead)
    1141675{
    1142676    AssertReturn(!pStream->fWriteOrRead, VERR_PERMISSION_DENIED);
     
    1160694 * @param   iLine               The line in question.
    1161695 */
    1162 static bool ScmStreamIsWhiteLine(PSCMSTREAM pStream, size_t iLine)
     696bool ScmStreamIsWhiteLine(PSCMSTREAM pStream, size_t iLine)
    1163697{
    1164698    SCMEOL      enmEol;
     
    14931027}
    14941028
    1495 /* -=-=-=-=-=- diff -=-=-=-=-=- */
    1496 
    1497 
    1498 /**
    1499  * Prints a range of lines with a prefix.
    1500  *
    1501  * @param   pState              The diff state.
    1502  * @param   chPrefix            The prefix.
    1503  * @param   pStream             The stream to get the lines from.
    1504  * @param   iLine               The first line.
    1505  * @param   cLines              The number of lines.
    1506  */
    1507 static void scmDiffPrintLines(PSCMDIFFSTATE pState, char chPrefix, PSCMSTREAM pStream, size_t iLine, size_t cLines)
    1508 {
    1509     while (cLines-- > 0)
    1510     {
    1511         SCMEOL      enmEol;
    1512         size_t      cchLine;
    1513         const char *pchLine = ScmStreamGetLineByNo(pStream, iLine, &cchLine, &enmEol);
    1514 
    1515         RTStrmPutCh(pState->pDiff, chPrefix);
    1516         if (pchLine && cchLine)
    1517         {
    1518             if (!pState->fSpecialChars)
    1519                 RTStrmWrite(pState->pDiff, pchLine, cchLine);
    1520             else
    1521             {
    1522                 size_t      offVir   = 0;
    1523                 const char *pchStart = pchLine;
    1524                 const char *pchTab   = (const char *)memchr(pchLine, '\t', cchLine);
    1525                 while (pchTab)
    1526                 {
    1527                     RTStrmWrite(pState->pDiff, pchStart, pchTab - pchStart);
    1528                     offVir += pchTab - pchStart;
    1529 
    1530                     size_t cchTab = pState->cchTab - offVir % pState->cchTab;
    1531                     switch (cchTab)
    1532                     {
    1533                         case 1: RTStrmPutStr(pState->pDiff, "."); break;
    1534                         case 2: RTStrmPutStr(pState->pDiff, ".."); break;
    1535                         case 3: RTStrmPutStr(pState->pDiff, "[T]"); break;
    1536                         case 4: RTStrmPutStr(pState->pDiff, "[TA]"); break;
    1537                         case 5: RTStrmPutStr(pState->pDiff, "[TAB]"); break;
    1538                         default: RTStrmPrintf(pState->pDiff, "[TAB%.*s]", cchTab - 5, g_szTabSpaces); break;
    1539                     }
    1540                     offVir += cchTab;
    1541 
    1542                     /* next */
    1543                     pchStart = pchTab + 1;
    1544                     pchTab = (const char *)memchr(pchStart, '\t', cchLine - (pchStart - pchLine));
    1545                 }
    1546                 size_t cchLeft = cchLine - (pchStart - pchLine);
    1547                 if (cchLeft)
    1548                     RTStrmWrite(pState->pDiff, pchStart, cchLeft);
    1549             }
    1550         }
    1551 
    1552         if (!pState->fSpecialChars)
    1553             RTStrmPutCh(pState->pDiff, '\n');
    1554         else if (enmEol == SCMEOL_LF)
    1555             RTStrmPutStr(pState->pDiff, "[LF]\n");
    1556         else if (enmEol == SCMEOL_CRLF)
    1557             RTStrmPutStr(pState->pDiff, "[CRLF]\n");
    1558         else
    1559             RTStrmPutStr(pState->pDiff, "[NONE]\n");
    1560 
    1561         iLine++;
    1562     }
    1563 }
    1564 
    1565 
    1566 /**
    1567  * Reports a difference and propels the streams to the lines following the
    1568  * resync.
    1569  *
    1570  *
    1571  * @returns New pState->cDiff value (just to return something).
    1572  * @param   pState              The diff state.  The cDiffs member will be
    1573  *                              incremented.
    1574  * @param   cMatches            The resync length.
    1575  * @param   iLeft               Where the difference starts on the left side.
    1576  * @param   cLeft               How long it is on this side.  ~(size_t)0 is used
    1577  *                              to indicate that it goes all the way to the end.
    1578  * @param   iRight              Where the difference starts on the right side.
    1579  * @param   cRight              How long it is.
    1580  */
    1581 static size_t scmDiffReport(PSCMDIFFSTATE pState, size_t cMatches,
    1582                             size_t iLeft, size_t cLeft,
    1583                             size_t iRight, size_t cRight)
    1584 {
    1585     /*
    1586      * Adjust the input.
    1587      */
    1588     if (cLeft == ~(size_t)0)
    1589     {
    1590         size_t c = ScmStreamCountLines(pState->pLeft);
    1591         if (c >= iLeft)
    1592             cLeft = c - iLeft;
    1593         else
    1594         {
    1595             iLeft = c;
    1596             cLeft = 0;
    1597         }
    1598     }
    1599 
    1600     if (cRight == ~(size_t)0)
    1601     {
    1602         size_t c = ScmStreamCountLines(pState->pRight);
    1603         if (c >= iRight)
    1604             cRight = c - iRight;
    1605         else
    1606         {
    1607             iRight = c;
    1608             cRight = 0;
    1609         }
    1610     }
    1611 
    1612     /*
    1613      * Print header if it's the first difference
    1614      */
    1615     if (!pState->cDiffs)
    1616         RTStrmPrintf(pState->pDiff, "diff %s %s\n", pState->pszFilename, pState->pszFilename);
    1617 
    1618     /*
    1619      * Emit the change description.
    1620      */
    1621     char ch = cLeft == 0
    1622             ? 'a'
    1623             : cRight == 0
    1624             ? 'd'
    1625             : 'c';
    1626     if (cLeft > 1 && cRight > 1)
    1627         RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu,%zu\n", iLeft + 1, iLeft + cLeft, ch, iRight + 1, iRight + cRight);
    1628     else if (cLeft > 1)
    1629         RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu\n",     iLeft + 1, iLeft + cLeft, ch, iRight + 1);
    1630     else if (cRight > 1)
    1631         RTStrmPrintf(pState->pDiff, "%zu%c%zu,%zu\n",     iLeft + 1,                ch, iRight + 1, iRight + cRight);
    1632     else
    1633         RTStrmPrintf(pState->pDiff, "%zu%c%zu\n",         iLeft + 1,                ch, iRight + 1);
    1634 
    1635     /*
    1636      * And the lines.
    1637      */
    1638     if (cLeft)
    1639         scmDiffPrintLines(pState, '<', pState->pLeft, iLeft, cLeft);
    1640     if (cLeft && cRight)
    1641         RTStrmPrintf(pState->pDiff, "---\n");
    1642     if (cRight)
    1643         scmDiffPrintLines(pState, '>', pState->pRight, iRight, cRight);
    1644 
    1645     /*
    1646      * Reposition the streams (safely ignores return value).
    1647      */
    1648     ScmStreamSeekByLine(pState->pLeft,  iLeft  + cLeft  + cMatches);
    1649     ScmStreamSeekByLine(pState->pRight, iRight + cRight + cMatches);
    1650 
    1651     pState->cDiffs++;
    1652     return pState->cDiffs;
    1653 }
    1654 
    1655 /**
    1656  * Helper for scmDiffCompare that takes care of trailing spaces and stuff
    1657  * like that.
    1658  */
    1659 static bool scmDiffCompareSlow(PSCMDIFFSTATE pState,
    1660                                const char *pchLeft,  size_t cchLeft,  SCMEOL enmEolLeft,
    1661                                const char *pchRight, size_t cchRight, SCMEOL enmEolRight)
    1662 {
    1663     if (pState->fIgnoreTrailingWhite)
    1664     {
    1665         while (cchLeft > 0 && RT_C_IS_SPACE(pchLeft[cchLeft - 1]))
    1666             cchLeft--;
    1667         while (cchRight > 0 && RT_C_IS_SPACE(pchRight[cchRight - 1]))
    1668             cchRight--;
    1669     }
    1670 
    1671     if (pState->fIgnoreLeadingWhite)
    1672     {
    1673         while (cchLeft > 0 && RT_C_IS_SPACE(*pchLeft))
    1674             pchLeft++, cchLeft--;
    1675         while (cchRight > 0 && RT_C_IS_SPACE(*pchRight))
    1676             pchRight++, cchRight--;
    1677     }
    1678 
    1679     if (   cchLeft != cchRight
    1680         || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)
    1681         || memcmp(pchLeft, pchRight, cchLeft))
    1682         return false;
    1683     return true;
    1684 }
    1685 
    1686 /**
    1687  * Compare two lines.
    1688  *
    1689  * @returns true if the are equal, false if not.
    1690  */
    1691 DECLINLINE(bool) scmDiffCompare(PSCMDIFFSTATE pState,
    1692                                 const char *pchLeft,  size_t cchLeft,  SCMEOL enmEolLeft,
    1693                                 const char *pchRight, size_t cchRight, SCMEOL enmEolRight)
    1694 {
    1695     if (   cchLeft != cchRight
    1696         || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)
    1697         || memcmp(pchLeft, pchRight, cchLeft))
    1698     {
    1699         if (   pState->fIgnoreTrailingWhite
    1700             || pState->fIgnoreTrailingWhite)
    1701             return scmDiffCompareSlow(pState,
    1702                                       pchLeft, cchLeft, enmEolLeft,
    1703                                       pchRight, cchRight, enmEolRight);
    1704         return false;
    1705     }
    1706     return true;
    1707 }
    1708 
    1709 /**
    1710  * Compares two sets of lines from the two files.
    1711  *
    1712  * @returns true if they matches, false if they don't.
    1713  * @param   pState              The diff state.
    1714  * @param   iLeft               Where to start in the left stream.
    1715  * @param   iRight              Where to start in the right stream.
    1716  * @param   cLines              How many lines to compare.
    1717  */
    1718 static bool scmDiffCompareLines(PSCMDIFFSTATE pState, size_t iLeft, size_t iRight, size_t cLines)
    1719 {
    1720     for (size_t iLine = 0; iLine < cLines; iLine++)
    1721     {
    1722         SCMEOL      enmEolLeft;
    1723         size_t      cchLeft;
    1724         const char *pchLeft  = ScmStreamGetLineByNo(pState->pLeft,  iLeft + iLine,  &cchLeft,  &enmEolLeft);
    1725 
    1726         SCMEOL      enmEolRight;
    1727         size_t      cchRight;
    1728         const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iRight + iLine, &cchRight, &enmEolRight);
    1729 
    1730         if (!scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight))
    1731             return false;
    1732     }
    1733     return true;
    1734 }
    1735 
    1736 
    1737 /**
    1738  * Resynchronize the two streams and reports the difference.
    1739  *
    1740  * Upon return, the streams will be positioned after the block of @a cMatches
    1741  * lines where it resynchronized them.
    1742  *
    1743  * @returns pState->cDiffs (just so we can use it in a return statement).
    1744  * @param   pState              The state.
    1745  * @param   cMatches            The number of lines that needs to match for the
    1746  *                              stream to be considered synchronized again.
    1747  */
    1748 static size_t scmDiffSynchronize(PSCMDIFFSTATE pState, size_t cMatches)
    1749 {
    1750     size_t const iStartLeft  = ScmStreamTellLine(pState->pLeft)  - 1;
    1751     size_t const iStartRight = ScmStreamTellLine(pState->pRight) - 1;
    1752     Assert(cMatches > 0);
    1753 
    1754     /*
    1755      * Compare each new line from each of the streams will all the preceding
    1756      * ones, including iStartLeft/Right.
    1757      */
    1758     for (size_t iRange = 1; ; iRange++)
    1759     {
    1760         /*
    1761          * Get the next line in the left stream and compare it against all the
    1762          * preceding lines on the right side.
    1763          */
    1764         SCMEOL      enmEol;
    1765         size_t      cchLine;
    1766         const char *pchLine = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iRange, &cchLine, &enmEol);
    1767         if (!pchLine)
    1768             return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0);
    1769 
    1770         for (size_t iRight = cMatches - 1; iRight < iRange; iRight++)
    1771         {
    1772             SCMEOL      enmEolRight;
    1773             size_t      cchRight;
    1774             const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRight,
    1775                                                         &cchRight, &enmEolRight);
    1776             if (   scmDiffCompare(pState, pchLine, cchLine, enmEol, pchRight, cchRight, enmEolRight)
    1777                 && scmDiffCompareLines(pState,
    1778                                        iStartLeft  + iRange + 1 - cMatches,
    1779                                        iStartRight + iRight + 1 - cMatches,
    1780                                        cMatches - 1)
    1781                )
    1782                 return scmDiffReport(pState, cMatches,
    1783                                      iStartLeft,  iRange + 1 - cMatches,
    1784                                      iStartRight, iRight + 1 - cMatches);
    1785         }
    1786 
    1787         /*
    1788          * Get the next line in the right stream and compare it against all the
    1789          * lines on the right side.
    1790          */
    1791         pchLine = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRange, &cchLine, &enmEol);
    1792         if (!pchLine)
    1793             return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0);
    1794 
    1795         for (size_t iLeft = cMatches - 1; iLeft <= iRange; iLeft++)
    1796         {
    1797             SCMEOL      enmEolLeft;
    1798             size_t      cchLeft;
    1799             const char *pchLeft = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iLeft,
    1800                                                        &cchLeft, &enmEolLeft);
    1801             if (    scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchLine, cchLine, enmEol)
    1802                 && scmDiffCompareLines(pState,
    1803                                        iStartLeft  + iLeft  + 1 - cMatches,
    1804                                        iStartRight + iRange + 1 - cMatches,
    1805                                        cMatches - 1)
    1806                )
    1807                 return scmDiffReport(pState, cMatches,
    1808                                      iStartLeft,  iLeft  + 1 - cMatches,
    1809                                      iStartRight, iRange + 1 - cMatches);
    1810         }
    1811     }
    1812 }
    1813 
    1814 /**
    1815  * Creates a diff of the changes between the streams @a pLeft and @a pRight.
    1816  *
    1817  * This currently only implements the simplest diff format, so no contexts.
    1818  *
    1819  * Also, note that we won't detect differences in the final newline of the
    1820  * streams.
    1821  *
    1822  * @returns The number of differences.
    1823  * @param   pszFilename         The filename.
    1824  * @param   pLeft               The left side stream.
    1825  * @param   pRight              The right side stream.
    1826  * @param   fIgnoreEol          Whether to ignore end of line markers.
    1827  * @param   fIgnoreLeadingWhite Set if leading white space should be ignored.
    1828  * @param   fIgnoreTrailingWhite  Set if trailing white space should be ignored.
    1829  * @param   fSpecialChars       Whether to print special chars in a human
    1830  *                              readable form or not.
    1831  * @param   cchTab              The tab size.
    1832  * @param   pDiff               Where to write the diff.
    1833  */
    1834 size_t ScmDiffStreams(const char *pszFilename, PSCMSTREAM pLeft, PSCMSTREAM pRight, bool fIgnoreEol,
    1835                       bool fIgnoreLeadingWhite, bool fIgnoreTrailingWhite, bool fSpecialChars,
    1836                       size_t cchTab, PRTSTREAM pDiff)
    1837 {
    1838 #ifdef RT_STRICT
    1839     ScmStreamCheckItegrity(pLeft);
    1840     ScmStreamCheckItegrity(pRight);
    1841 #endif
    1842 
    1843     /*
    1844      * Set up the diff state.
    1845      */
    1846     SCMDIFFSTATE State;
    1847     State.cDiffs                = 0;
    1848     State.pszFilename           = pszFilename;
    1849     State.pLeft                 = pLeft;
    1850     State.pRight                = pRight;
    1851     State.fIgnoreEol            = fIgnoreEol;
    1852     State.fIgnoreLeadingWhite   = fIgnoreLeadingWhite;
    1853     State.fIgnoreTrailingWhite  = fIgnoreTrailingWhite;
    1854     State.fSpecialChars         = fSpecialChars;
    1855     State.cchTab                = cchTab;
    1856     State.pDiff                 = pDiff;
    1857 
    1858     /*
    1859      * Compare them line by line.
    1860      */
    1861     ScmStreamRewindForReading(pLeft);
    1862     ScmStreamRewindForReading(pRight);
    1863     const char *pchLeft;
    1864     const char *pchRight;
    1865 
    1866     for (;;)
    1867     {
    1868         SCMEOL  enmEolLeft;
    1869         size_t  cchLeft;
    1870         pchLeft  = ScmStreamGetLine(pLeft,  &cchLeft,  &enmEolLeft);
    1871 
    1872         SCMEOL  enmEolRight;
    1873         size_t  cchRight;
    1874         pchRight = ScmStreamGetLine(pRight, &cchRight, &enmEolRight);
    1875         if (!pchLeft || !pchRight)
    1876             break;
    1877 
    1878         if (!scmDiffCompare(&State, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight))
    1879             scmDiffSynchronize(&State, 3);
    1880     }
    1881 
    1882     /*
    1883      * Deal with any remaining differences.
    1884      */
    1885     if (pchLeft)
    1886         scmDiffReport(&State, 0, ScmStreamTellLine(pLeft) - 1, ~(size_t)0, ScmStreamTellLine(pRight), 0);
    1887     else if (pchRight)
    1888         scmDiffReport(&State, 0, ScmStreamTellLine(pLeft), 0, ScmStreamTellLine(pRight) - 1, ~(size_t)0);
    1889 
    1890     /*
    1891      * Report any errors.
    1892      */
    1893     if (RT_FAILURE(ScmStreamGetStatus(pLeft)))
    1894         RTMsgError("Left diff stream error: %Rrc\n", ScmStreamGetStatus(pLeft));
    1895     if (RT_FAILURE(ScmStreamGetStatus(pRight)))
    1896         RTMsgError("Right diff stream error: %Rrc\n", ScmStreamGetStatus(pRight));
    1897 
    1898     return State.cDiffs;
    1899 }
    1900 
    1901 
    1902 
    1903 /* -=-=-=-=-=- settings -=-=-=-=-=- */
    1904 
    1905 /**
    1906  * Init a settings structure with settings from @a pSrc.
    1907  *
    1908  * @returns IPRT status code
    1909  * @param   pSettings           The settings.
    1910  * @param   pSrc                The source settings.
    1911  */
    1912 static int scmSettingsBaseInitAndCopy(PSCMSETTINGSBASE pSettings, PCSCMSETTINGSBASE pSrc)
    1913 {
    1914     *pSettings = *pSrc;
    1915 
    1916     int rc = RTStrDupEx(&pSettings->pszFilterFiles, pSrc->pszFilterFiles);
    1917     if (RT_SUCCESS(rc))
    1918     {
    1919         rc = RTStrDupEx(&pSettings->pszFilterOutFiles, pSrc->pszFilterOutFiles);
    1920         if (RT_SUCCESS(rc))
    1921         {
    1922             rc = RTStrDupEx(&pSettings->pszFilterOutDirs, pSrc->pszFilterOutDirs);
    1923             if (RT_SUCCESS(rc))
    1924                 return VINF_SUCCESS;
    1925 
    1926             RTStrFree(pSettings->pszFilterOutFiles);
    1927         }
    1928         RTStrFree(pSettings->pszFilterFiles);
    1929     }
    1930 
    1931     pSettings->pszFilterFiles = NULL;
    1932     pSettings->pszFilterOutFiles = NULL;
    1933     pSettings->pszFilterOutDirs = NULL;
    1934     return rc;
    1935 }
    1936 
    1937 /**
    1938  * Init a settings structure.
    1939  *
    1940  * @returns IPRT status code
    1941  * @param   pSettings           The settings.
    1942  */
    1943 static int scmSettingsBaseInit(PSCMSETTINGSBASE pSettings)
    1944 {
    1945     return scmSettingsBaseInitAndCopy(pSettings, &g_Defaults);
    1946 }
    1947 
    1948 /**
    1949  * Deletes the settings, i.e. free any dynamically allocated content.
    1950  *
    1951  * @param   pSettings           The settings.
    1952  */
    1953 static void scmSettingsBaseDelete(PSCMSETTINGSBASE pSettings)
    1954 {
    1955     if (pSettings)
    1956     {
    1957         Assert(pSettings->cchTab != ~(unsigned)0);
    1958         pSettings->cchTab = ~(unsigned)0;
    1959 
    1960         RTStrFree(pSettings->pszFilterFiles);
    1961         pSettings->pszFilterFiles = NULL;
    1962 
    1963         RTStrFree(pSettings->pszFilterOutFiles);
    1964         pSettings->pszFilterOutFiles = NULL;
    1965 
    1966         RTStrFree(pSettings->pszFilterOutDirs);
    1967         pSettings->pszFilterOutDirs = NULL;
    1968     }
    1969 }
    1970 
    1971 
    1972 /**
    1973  * Processes a RTGetOpt result.
    1974  *
    1975  * @retval  VINF_SUCCESS if handled.
    1976  * @retval  VERR_OUT_OF_RANGE if the option value was out of range.
    1977  * @retval  VERR_GETOPT_UNKNOWN_OPTION if the option was not recognized.
    1978  *
    1979  * @param   pSettings           The settings to change.
    1980  * @param   rc                  The RTGetOpt return value.
    1981  * @param   pValueUnion         The RTGetOpt value union.
    1982  */
    1983 static int scmSettingsBaseHandleOpt(PSCMSETTINGSBASE pSettings, int rc, PRTGETOPTUNION pValueUnion)
    1984 {
    1985     switch (rc)
    1986     {
    1987         case SCMOPT_CONVERT_EOL:
    1988             pSettings->fConvertEol = true;
    1989             return VINF_SUCCESS;
    1990         case SCMOPT_NO_CONVERT_EOL:
    1991             pSettings->fConvertEol = false;
    1992             return VINF_SUCCESS;
    1993 
    1994         case SCMOPT_CONVERT_TABS:
    1995             pSettings->fConvertTabs = true;
    1996             return VINF_SUCCESS;
    1997         case SCMOPT_NO_CONVERT_TABS:
    1998             pSettings->fConvertTabs = false;
    1999             return VINF_SUCCESS;
    2000 
    2001         case SCMOPT_FORCE_FINAL_EOL:
    2002             pSettings->fForceFinalEol = true;
    2003             return VINF_SUCCESS;
    2004         case SCMOPT_NO_FORCE_FINAL_EOL:
    2005             pSettings->fForceFinalEol = false;
    2006             return VINF_SUCCESS;
    2007 
    2008         case SCMOPT_FORCE_TRAILING_LINE:
    2009             pSettings->fForceTrailingLine = true;
    2010             return VINF_SUCCESS;
    2011         case SCMOPT_NO_FORCE_TRAILING_LINE:
    2012             pSettings->fForceTrailingLine = false;
    2013             return VINF_SUCCESS;
    2014 
    2015         case SCMOPT_STRIP_TRAILING_BLANKS:
    2016             pSettings->fStripTrailingBlanks = true;
    2017             return VINF_SUCCESS;
    2018         case SCMOPT_NO_STRIP_TRAILING_BLANKS:
    2019             pSettings->fStripTrailingBlanks = false;
    2020             return VINF_SUCCESS;
    2021 
    2022         case SCMOPT_STRIP_TRAILING_LINES:
    2023             pSettings->fStripTrailingLines = true;
    2024             return VINF_SUCCESS;
    2025         case SCMOPT_NO_STRIP_TRAILING_LINES:
    2026             pSettings->fStripTrailingLines = false;
    2027             return VINF_SUCCESS;
    2028 
    2029         case SCMOPT_ONLY_SVN_DIRS:
    2030             pSettings->fOnlySvnDirs = true;
    2031             return VINF_SUCCESS;
    2032         case SCMOPT_NOT_ONLY_SVN_DIRS:
    2033             pSettings->fOnlySvnDirs = false;
    2034             return VINF_SUCCESS;
    2035 
    2036         case SCMOPT_ONLY_SVN_FILES:
    2037             pSettings->fOnlySvnFiles = true;
    2038             return VINF_SUCCESS;
    2039         case SCMOPT_NOT_ONLY_SVN_FILES:
    2040             pSettings->fOnlySvnFiles = false;
    2041             return VINF_SUCCESS;
    2042 
    2043         case SCMOPT_SET_SVN_EOL:
    2044             pSettings->fSetSvnEol = true;
    2045             return VINF_SUCCESS;
    2046         case SCMOPT_DONT_SET_SVN_EOL:
    2047             pSettings->fSetSvnEol = false;
    2048             return VINF_SUCCESS;
    2049 
    2050         case SCMOPT_SET_SVN_EXECUTABLE:
    2051             pSettings->fSetSvnExecutable = true;
    2052             return VINF_SUCCESS;
    2053         case SCMOPT_DONT_SET_SVN_EXECUTABLE:
    2054             pSettings->fSetSvnExecutable = false;
    2055             return VINF_SUCCESS;
    2056 
    2057         case SCMOPT_SET_SVN_KEYWORDS:
    2058             pSettings->fSetSvnKeywords = true;
    2059             return VINF_SUCCESS;
    2060         case SCMOPT_DONT_SET_SVN_KEYWORDS:
    2061             pSettings->fSetSvnKeywords = false;
    2062             return VINF_SUCCESS;
    2063 
    2064         case SCMOPT_TAB_SIZE:
    2065             if (   pValueUnion->u8 < 1
    2066                 || pValueUnion->u8 >= RT_ELEMENTS(g_szTabSpaces))
    2067             {
    2068                 RTMsgError("Invalid tab size: %u - must be in {1..%u}\n",
    2069                            pValueUnion->u8, RT_ELEMENTS(g_szTabSpaces) - 1);
    2070                 return VERR_OUT_OF_RANGE;
    2071             }
    2072             pSettings->cchTab = pValueUnion->u8;
    2073             return VINF_SUCCESS;
    2074 
    2075         case SCMOPT_FILTER_OUT_DIRS:
    2076         case SCMOPT_FILTER_FILES:
    2077         case SCMOPT_FILTER_OUT_FILES:
    2078         {
    2079             char **ppsz = NULL;
    2080             switch (rc)
    2081             {
    2082                 case SCMOPT_FILTER_OUT_DIRS:    ppsz = &pSettings->pszFilterOutDirs; break;
    2083                 case SCMOPT_FILTER_FILES:       ppsz = &pSettings->pszFilterFiles; break;
    2084                 case SCMOPT_FILTER_OUT_FILES:   ppsz = &pSettings->pszFilterOutFiles; break;
    2085             }
    2086 
    2087             /*
    2088              * An empty string zaps the current list.
    2089              */
    2090             if (!*pValueUnion->psz)
    2091                 return RTStrATruncate(ppsz, 0);
    2092 
    2093             /*
    2094              * Non-empty strings are appended to the pattern list.
    2095              *
    2096              * Strip leading and trailing pattern separators before attempting
    2097              * to append it.  If it's just separators, don't do anything.
    2098              */
    2099             const char *pszSrc = pValueUnion->psz;
    2100             while (*pszSrc == '|')
    2101                 pszSrc++;
    2102             size_t cchSrc = strlen(pszSrc);
    2103             while (cchSrc > 0 && pszSrc[cchSrc - 1] == '|')
    2104                 cchSrc--;
    2105             if (!cchSrc)
    2106                 return VINF_SUCCESS;
    2107 
    2108             return RTStrAAppendExN(ppsz, 2,
    2109                                    "|", *ppsz && **ppsz ? 1 : 0,
    2110                                    pszSrc, cchSrc);
    2111         }
    2112 
    2113         default:
    2114             return VERR_GETOPT_UNKNOWN_OPTION;
    2115     }
    2116 }
    2117 
    2118 /**
    2119  * Parses an option string.
    2120  *
    2121  * @returns IPRT status code.
    2122  * @param   pBase               The base settings structure to apply the options
    2123  *                              to.
    2124  * @param   pszOptions          The options to parse.
    2125  */
    2126 static int scmSettingsBaseParseString(PSCMSETTINGSBASE pBase, const char *pszLine)
    2127 {
    2128     int    cArgs;
    2129     char **papszArgs;
    2130     int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, NULL);
    2131     if (RT_SUCCESS(rc))
    2132     {
    2133         RTGETOPTUNION   ValueUnion;
    2134         RTGETOPTSTATE   GetOptState;
    2135         rc = RTGetOptInit(&GetOptState, cArgs, papszArgs, &g_aScmOpts[0], RT_ELEMENTS(g_aScmOpts), 0, 0 /*fFlags*/);
    2136         if (RT_SUCCESS(rc))
    2137         {
    2138             while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
    2139             {
    2140                 rc = scmSettingsBaseHandleOpt(pBase, rc, &ValueUnion);
    2141                 if (RT_FAILURE(rc))
    2142                     break;
    2143             }
    2144         }
    2145         RTGetOptArgvFree(papszArgs);
    2146     }
    2147 
    2148     return rc;
    2149 }
    2150 
    2151 /**
    2152  * Parses an unterminated option string.
    2153  *
    2154  * @returns IPRT status code.
    2155  * @param   pBase               The base settings structure to apply the options
    2156  *                              to.
    2157  * @param   pchLine             The line.
    2158  * @param   cchLine             The line length.
    2159  */
    2160 static int scmSettingsBaseParseStringN(PSCMSETTINGSBASE pBase, const char *pchLine, size_t cchLine)
    2161 {
    2162     char *pszLine = RTStrDupN(pchLine, cchLine);
    2163     if (!pszLine)
    2164         return VERR_NO_MEMORY;
    2165     int rc = scmSettingsBaseParseString(pBase, pszLine);
    2166     RTStrFree(pszLine);
    2167     return rc;
    2168 }
    2169 
    2170 /**
    2171  * Verifies the options string.
    2172  *
    2173  * @returns IPRT status code.
    2174  * @param   pszOptions          The options to verify .
    2175  */
    2176 static int scmSettingsBaseVerifyString(const char *pszOptions)
    2177 {
    2178     SCMSETTINGSBASE Base;
    2179     int rc = scmSettingsBaseInit(&Base);
    2180     if (RT_SUCCESS(rc))
    2181     {
    2182         rc = scmSettingsBaseParseString(&Base, pszOptions);
    2183         scmSettingsBaseDelete(&Base);
    2184     }
    2185     return rc;
    2186 }
    2187 
    2188 /**
    2189  * Loads settings found in editor and SCM settings directives within the
    2190  * document (@a pStream).
    2191  *
    2192  * @returns IPRT status code.
    2193  * @param   pBase               The settings base to load settings into.
    2194  * @param   pStream             The stream to scan for settings directives.
    2195  */
    2196 static int scmSettingsBaseLoadFromDocument(PSCMSETTINGSBASE pBase, PSCMSTREAM pStream)
    2197 {
    2198     /** @todo Editor and SCM settings directives in documents.  */
    2199     return VINF_SUCCESS;
    2200 }
    2201 
    2202 /**
    2203  * Creates a new settings file struct, cloning @a pSettings.
    2204  *
    2205  * @returns IPRT status code.
    2206  * @param   ppSettings          Where to return the new struct.
    2207  * @param   pSettingsBase       The settings to inherit from.
    2208  */
    2209 static int scmSettingsCreate(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pSettingsBase)
    2210 {
    2211     PSCMSETTINGS pSettings = (PSCMSETTINGS)RTMemAlloc(sizeof(*pSettings));
    2212     if (!pSettings)
    2213         return VERR_NO_MEMORY;
    2214     int rc = scmSettingsBaseInitAndCopy(&pSettings->Base, pSettingsBase);
    2215     if (RT_SUCCESS(rc))
    2216     {
    2217         pSettings->pDown   = NULL;
    2218         pSettings->pUp     = NULL;
    2219         pSettings->paPairs = NULL;
    2220         pSettings->cPairs  = 0;
    2221         *ppSettings = pSettings;
    2222         return VINF_SUCCESS;
    2223     }
    2224     RTMemFree(pSettings);
    2225     return rc;
    2226 }
    2227 
    2228 /**
    2229  * Destroys a settings structure.
    2230  *
    2231  * @param   pSettings           The settings structure to destroy.  NULL is OK.
    2232  */
    2233 static void scmSettingsDestroy(PSCMSETTINGS pSettings)
    2234 {
    2235     if (pSettings)
    2236     {
    2237         scmSettingsBaseDelete(&pSettings->Base);
    2238         for (size_t i = 0; i < pSettings->cPairs; i++)
    2239         {
    2240             RTStrFree(pSettings->paPairs[i].pszPattern);
    2241             RTStrFree(pSettings->paPairs[i].pszOptions);
    2242             pSettings->paPairs[i].pszPattern = NULL;
    2243             pSettings->paPairs[i].pszOptions = NULL;
    2244         }
    2245         RTMemFree(pSettings->paPairs);
    2246         pSettings->paPairs = NULL;
    2247         RTMemFree(pSettings);
    2248     }
    2249 }
    2250 
    2251 /**
    2252  * Adds a pattern/options pair to the settings structure.
    2253  *
    2254  * @returns IPRT status code.
    2255  * @param   pSettings           The settings.
    2256  * @param   pchLine             The line containing the unparsed pair.
    2257  * @param   cchLine             The length of the line.
    2258  */
    2259 static int scmSettingsAddPair(PSCMSETTINGS pSettings, const char *pchLine, size_t cchLine)
    2260 {
    2261     /*
    2262      * Split the string.
    2263      */
    2264     const char *pchOptions = (const char *)memchr(pchLine, ':', cchLine);
    2265     if (!pchOptions)
    2266         return VERR_INVALID_PARAMETER;
    2267     size_t cchPattern = pchOptions - pchLine;
    2268     size_t cchOptions = cchLine - cchPattern - 1;
    2269     pchOptions++;
    2270 
    2271     /* strip spaces everywhere */
    2272     while (cchPattern > 0 && RT_C_IS_SPACE(pchLine[cchPattern - 1]))
    2273         cchPattern--;
    2274     while (cchPattern > 0 && RT_C_IS_SPACE(*pchLine))
    2275         cchPattern--, pchLine++;
    2276 
    2277     while (cchOptions > 0 && RT_C_IS_SPACE(pchOptions[cchOptions - 1]))
    2278         cchOptions--;
    2279     while (cchOptions > 0 && RT_C_IS_SPACE(*pchOptions))
    2280         cchOptions--, pchOptions++;
    2281 
    2282     /* Quietly ignore empty patterns and empty options. */
    2283     if (!cchOptions || !cchPattern)
    2284         return VINF_SUCCESS;
    2285 
    2286     /*
    2287      * Add the pair and verify the option string.
    2288      */
    2289     uint32_t iPair = pSettings->cPairs;
    2290     if ((iPair % 32) == 0)
    2291     {
    2292         void *pvNew = RTMemRealloc(pSettings->paPairs, (iPair + 32) * sizeof(pSettings->paPairs[0]));
    2293         if (!pvNew)
    2294             return VERR_NO_MEMORY;
    2295         pSettings->paPairs = (PSCMPATRNOPTPAIR)pvNew;
    2296     }
    2297 
    2298     pSettings->paPairs[iPair].pszPattern = RTStrDupN(pchLine, cchPattern);
    2299     pSettings->paPairs[iPair].pszOptions = RTStrDupN(pchOptions, cchOptions);
    2300     int rc;
    2301     if (   pSettings->paPairs[iPair].pszPattern
    2302         && pSettings->paPairs[iPair].pszOptions)
    2303         rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions);
    2304     else
    2305         rc = VERR_NO_MEMORY;
    2306     if (RT_SUCCESS(rc))
    2307         pSettings->cPairs = iPair + 1;
    2308     else
    2309     {
    2310         RTStrFree(pSettings->paPairs[iPair].pszPattern);
    2311         RTStrFree(pSettings->paPairs[iPair].pszOptions);
    2312     }
    2313     return rc;
    2314 }
    2315 
    2316 /**
    2317  * Loads in the settings from @a pszFilename.
    2318  *
    2319  * @returns IPRT status code.
    2320  * @param   pSettings           Where to load the settings file.
    2321  * @param   pszFilename         The file to load.
    2322  */
    2323 static int scmSettingsLoadFile(PSCMSETTINGS pSettings, const char *pszFilename)
    2324 {
    2325     SCMSTREAM Stream;
    2326     int rc = ScmStreamInitForReading(&Stream, pszFilename);
    2327     if (RT_FAILURE(rc))
    2328     {
    2329         RTMsgError("%s: ScmStreamInitForReading -> %Rrc\n", pszFilename, rc);
    2330         return rc;
    2331     }
    2332 
    2333     SCMEOL      enmEol;
    2334     const char *pchLine;
    2335     size_t      cchLine;
    2336     while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
    2337     {
    2338         /* Ignore leading spaces. */
    2339         while (cchLine > 0 && RT_C_IS_SPACE(*pchLine))
    2340             pchLine++, cchLine--;
    2341 
    2342         /* Ignore empty lines and comment lines. */
    2343         if (cchLine < 1 || *pchLine == '#')
    2344             continue;
    2345 
    2346         /* What kind of line is it? */
    2347         const char *pchColon = (const char *)memchr(pchLine, ':', cchLine);
    2348         if (pchColon)
    2349             rc = scmSettingsAddPair(pSettings, pchLine, cchLine);
    2350         else
    2351             rc = scmSettingsBaseParseStringN(&pSettings->Base, pchLine, cchLine);
    2352         if (RT_FAILURE(rc))
    2353         {
    2354             RTMsgError("%s:%d: %Rrc\n", pszFilename, ScmStreamTellLine(&Stream), rc);
    2355             break;
    2356         }
    2357     }
    2358 
    2359     if (RT_SUCCESS(rc))
    2360     {
    2361         rc = ScmStreamGetStatus(&Stream);
    2362         if (RT_FAILURE(rc))
    2363             RTMsgError("%s: ScmStreamGetStatus- > %Rrc\n", pszFilename, rc);
    2364     }
    2365 
    2366     ScmStreamDelete(&Stream);
    2367     return rc;
    2368 }
    2369 
    2370 /**
    2371  * Parse the specified settings file creating a new settings struct from it.
    2372  *
    2373  * @returns IPRT status code
    2374  * @param   ppSettings          Where to return the new settings.
    2375  * @param   pszFilename         The file to parse.
    2376  * @param   pSettingsBase       The base settings we inherit from.
    2377  */
    2378 static int scmSettingsCreateFromFile(PSCMSETTINGS *ppSettings, const char *pszFilename, PCSCMSETTINGSBASE pSettingsBase)
    2379 {
    2380     PSCMSETTINGS pSettings;
    2381     int rc = scmSettingsCreate(&pSettings, pSettingsBase);
    2382     if (RT_SUCCESS(rc))
    2383     {
    2384         rc = scmSettingsLoadFile(pSettings, pszFilename);
    2385         if (RT_SUCCESS(rc))
    2386         {
    2387             *ppSettings = pSettings;
    2388             return VINF_SUCCESS;
    2389         }
    2390 
    2391         scmSettingsDestroy(pSettings);
    2392     }
    2393     *ppSettings = NULL;
    2394     return rc;
    2395 }
    2396 
    2397 
    2398 /**
    2399  * Create an initial settings structure when starting processing a new file or
    2400  * directory.
    2401  *
    2402  * This will look for .scm-settings files from the root and down to the
    2403  * specified directory, combining them into the returned settings structure.
    2404  *
    2405  * @returns IPRT status code.
    2406  * @param   ppSettings          Where to return the pointer to the top stack
    2407  *                              object.
    2408  * @param   pBaseSettings       The base settings we inherit from (globals
    2409  *                              typically).
    2410  * @param   pszPath             The absolute path to the new directory or file.
    2411  */
    2412 static int scmSettingsCreateForPath(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pBaseSettings, const char *pszPath)
    2413 {
    2414     *ppSettings = NULL;                 /* try shut up gcc. */
    2415 
    2416     /*
    2417      * We'll be working with a stack copy of the path.
    2418      */
    2419     char    szFile[RTPATH_MAX];
    2420     size_t  cchDir = strlen(pszPath);
    2421     if (cchDir >= sizeof(szFile) - sizeof(SCM_SETTINGS_FILENAME))
    2422         return VERR_FILENAME_TOO_LONG;
    2423 
    2424     /*
    2425      * Create the bottom-most settings.
    2426      */
    2427     PSCMSETTINGS pSettings;
    2428     int rc = scmSettingsCreate(&pSettings, pBaseSettings);
    2429     if (RT_FAILURE(rc))
    2430         return rc;
    2431 
    2432     /*
    2433      * Enumerate the path components from the root and down. Load any setting
    2434      * files we find.
    2435      */
    2436     size_t cComponents = RTPathCountComponents(pszPath);
    2437     for (size_t i = 1; i <= cComponents; i++)
    2438     {
    2439         rc = RTPathCopyComponents(szFile, sizeof(szFile), pszPath, i);
    2440         if (RT_SUCCESS(rc))
    2441             rc = RTPathAppend(szFile, sizeof(szFile), SCM_SETTINGS_FILENAME);
    2442         if (RT_FAILURE(rc))
    2443             break;
    2444         if (RTFileExists(szFile))
    2445         {
    2446             rc = scmSettingsLoadFile(pSettings, szFile);
    2447             if (RT_FAILURE(rc))
    2448                 break;
    2449         }
    2450     }
    2451 
    2452     if (RT_SUCCESS(rc))
    2453         *ppSettings = pSettings;
    2454     else
    2455         scmSettingsDestroy(pSettings);
    2456     return rc;
    2457 }
    2458 
    2459 /**
    2460  * Pushes a new settings set onto the stack.
    2461  *
    2462  * @param   ppSettingsStack     The pointer to the pointer to the top stack
    2463  *                              element.  This will be used as input and output.
    2464  * @param   pSettings           The settings to push onto the stack.
    2465  */
    2466 static void scmSettingsStackPush(PSCMSETTINGS *ppSettingsStack, PSCMSETTINGS pSettings)
    2467 {
    2468     PSCMSETTINGS pOld = *ppSettingsStack;
    2469     pSettings->pDown  = pOld;
    2470     pSettings->pUp    = NULL;
    2471     if (pOld)
    2472         pOld->pUp = pSettings;
    2473     *ppSettingsStack = pSettings;
    2474 }
    2475 
    2476 /**
    2477  * Pushes the settings of the specified directory onto the stack.
    2478  *
    2479  * We will load any .scm-settings in the directory.  A stack entry is added even
    2480  * if no settings file was found.
    2481  *
    2482  * @returns IPRT status code.
    2483  * @param   ppSettingsStack     The pointer to the pointer to the top stack
    2484  *                              element.  This will be used as input and output.
    2485  * @param   pszDir              The directory to do this for.
    2486  */
    2487 static int scmSettingsStackPushDir(PSCMSETTINGS *ppSettingsStack, const char *pszDir)
    2488 {
    2489     char szFile[RTPATH_MAX];
    2490     int rc = RTPathJoin(szFile, sizeof(szFile), pszDir, SCM_SETTINGS_FILENAME);
    2491     if (RT_SUCCESS(rc))
    2492     {
    2493         PSCMSETTINGS pSettings;
    2494         rc = scmSettingsCreate(&pSettings, &(*ppSettingsStack)->Base);
    2495         if (RT_SUCCESS(rc))
    2496         {
    2497             if (RTFileExists(szFile))
    2498                 rc = scmSettingsLoadFile(pSettings, szFile);
    2499             if (RT_SUCCESS(rc))
    2500             {
    2501                 scmSettingsStackPush(ppSettingsStack, pSettings);
    2502                 return VINF_SUCCESS;
    2503             }
    2504 
    2505             scmSettingsDestroy(pSettings);
    2506         }
    2507     }
    2508     return rc;
    2509 }
    2510 
    2511 
    2512 /**
    2513  * Pops a settings set off the stack.
    2514  *
    2515  * @returns The popped setttings.
    2516  * @param   ppSettingsStack     The pointer to the pointer to the top stack
    2517  *                              element.  This will be used as input and output.
    2518  */
    2519 static PSCMSETTINGS scmSettingsStackPop(PSCMSETTINGS *ppSettingsStack)
    2520 {
    2521     PSCMSETTINGS pRet = *ppSettingsStack;
    2522     PSCMSETTINGS pNew = pRet ? pRet->pDown : NULL;
    2523     *ppSettingsStack = pNew;
    2524     if (pNew)
    2525         pNew->pUp    = NULL;
    2526     if (pRet)
    2527     {
    2528         pRet->pUp    = NULL;
    2529         pRet->pDown  = NULL;
    2530     }
    2531     return pRet;
    2532 }
    2533 
    2534 /**
    2535  * Pops and destroys the top entry of the stack.
    2536  *
    2537  * @param   ppSettingsStack     The pointer to the pointer to the top stack
    2538  *                              element.  This will be used as input and output.
    2539  */
    2540 static void scmSettingsStackPopAndDestroy(PSCMSETTINGS *ppSettingsStack)
    2541 {
    2542     scmSettingsDestroy(scmSettingsStackPop(ppSettingsStack));
    2543 }
    2544 
    2545 /**
    2546  * Constructs the base settings for the specified file name.
    2547  *
    2548  * @returns IPRT status code.
    2549  * @param   pSettingsStack      The top element on the settings stack.
    2550  * @param   pszFilename         The file name.
    2551  * @param   pszBasename         The base name (pointer within @a pszFilename).
    2552  * @param   cchBasename         The length of the base name.  (For passing to
    2553  *                              RTStrSimplePatternMultiMatch.)
    2554  * @param   pBase               Base settings to initialize.
    2555  */
    2556 static int scmSettingsStackMakeFileBase(PCSCMSETTINGS pSettingsStack, const char *pszFilename,
    2557                                         const char *pszBasename, size_t cchBasename, PSCMSETTINGSBASE pBase)
    2558 {
    2559     int rc = scmSettingsBaseInitAndCopy(pBase, &pSettingsStack->Base);
    2560     if (RT_SUCCESS(rc))
    2561     {
    2562         /* find the bottom entry in the stack. */
    2563         PCSCMSETTINGS pCur = pSettingsStack;
    2564         while (pCur->pDown)
    2565             pCur = pCur->pDown;
    2566 
    2567         /* Work our way up thru the stack and look for matching pairs. */
    2568         while (pCur)
    2569         {
    2570             size_t const cPairs = pCur->cPairs;
    2571             if (cPairs)
    2572             {
    2573                 for (size_t i = 0; i < cPairs; i++)
    2574                     if (   RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
    2575                                                         pszBasename,  cchBasename, NULL)
    2576                         || RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
    2577                                                         pszFilename,  RTSTR_MAX, NULL))
    2578                     {
    2579                         rc = scmSettingsBaseParseString(pBase, pCur->paPairs[i].pszOptions);
    2580                         if (RT_FAILURE(rc))
    2581                             break;
    2582                     }
    2583                 if (RT_FAILURE(rc))
    2584                     break;
    2585             }
    2586 
    2587             /* advance */
    2588             pCur = pCur->pUp;
    2589         }
    2590     }
    2591     if (RT_FAILURE(rc))
    2592         scmSettingsBaseDelete(pBase);
    2593     return rc;
    2594 }
    2595 
    2596 
    2597 /* -=-=-=-=-=- misc -=-=-=-=-=- */
    2598 
    2599 
    2600 /**
    2601  * Prints a verbose message if the level is high enough.
    2602  *
    2603  * @param   pState              The rewrite state.  Optional.
    2604  * @param   iLevel              The required verbosity level.
    2605  * @param   pszFormat           The message format string.  Can be NULL if we
    2606  *                              only want to trigger the per file message.
    2607  * @param   ...                 Format arguments.
    2608  */
    2609 static void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)
    2610 {
    2611     if (iLevel <= g_iVerbosity)
    2612     {
    2613         if (pState && !pState->fFirst)
    2614         {
    2615             RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
    2616             pState->fFirst = true;
    2617         }
    2618         if (pszFormat)
    2619         {
    2620             RTPrintf(pState
    2621                      ? "%s: info:   "
    2622                      : "%s: info: ",
    2623                      g_szProgName);
    2624             va_list va;
    2625             va_start(va, pszFormat);
    2626             RTPrintfV(pszFormat, va);
    2627             va_end(va);
    2628         }
    2629     }
    2630 }
    2631 
    2632 
    2633 /* -=-=-=-=-=- subversion -=-=-=-=-=- */
    2634 
    2635 #define SCM_WITHOUT_LIBSVN
    2636 
    2637 #ifdef SCM_WITHOUT_LIBSVN
    2638 
    2639 /**
    2640  * Callback that is call for each path to search.
    2641  */
    2642 static DECLCALLBACK(int) scmSvnFindSvnBinaryCallback(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
    2643 {
    2644     char   *pszDst = (char *)pvUser1;
    2645     size_t  cchDst = (size_t)pvUser2;
    2646     if (cchDst > cchPath)
    2647     {
    2648         memcpy(pszDst, pchPath, cchPath);
    2649         pszDst[cchPath] = '\0';
    2650 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
    2651         int rc = RTPathAppend(pszDst, cchDst, "svn.exe");
    2652 #else
    2653         int rc = RTPathAppend(pszDst, cchDst, "svn");
    2654 #endif
    2655         if (   RT_SUCCESS(rc)
    2656             && RTFileExists(pszDst))
    2657             return VINF_SUCCESS;
    2658     }
    2659     return VERR_TRY_AGAIN;
    2660 }
    2661 
    2662 
    2663 /**
    2664  * Finds the svn binary.
    2665  *
    2666  * @param   pszPath             Where to store it.  Worst case, we'll return
    2667  *                              "svn" here.
    2668  * @param   cchPath             The size of the buffer pointed to by @a pszPath.
    2669  */
    2670 static void scmSvnFindSvnBinary(char *pszPath, size_t cchPath)
    2671 {
    2672     /** @todo code page fun... */
    2673     Assert(cchPath >= sizeof("svn"));
    2674 #ifdef RT_OS_WINDOWS
    2675     const char *pszEnvVar = RTEnvGet("Path");
    2676 #else
    2677     const char *pszEnvVar = RTEnvGet("PATH");
    2678 #endif
    2679     if (pszPath)
    2680     {
    2681 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
    2682         int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);
    2683 #else
    2684         int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);
    2685 #endif
    2686         if (RT_SUCCESS(rc))
    2687             return;
    2688     }
    2689     strcpy(pszPath, "svn");
    2690 }
    2691 
    2692 
    2693 /**
    2694  * Construct a dot svn filename for the file being rewritten.
    2695  *
    2696  * @returns IPRT status code.
    2697  * @param   pState              The rewrite state (for the name).
    2698  * @param   pszDir              The directory, including ".svn/".
    2699  * @param   pszSuff             The filename suffix.
    2700  * @param   pszDst              The output buffer.  RTPATH_MAX in size.
    2701  */
    2702 static int scmSvnConstructName(PSCMRWSTATE pState, const char *pszDir, const char *pszSuff, char *pszDst)
    2703 {
    2704     strcpy(pszDst, pState->pszFilename); /* ASSUMES sizeof(szBuf) <= sizeof(szPath) */
    2705     RTPathStripFilename(pszDst);
    2706 
    2707     int rc = RTPathAppend(pszDst, RTPATH_MAX, pszDir);
    2708     if (RT_SUCCESS(rc))
    2709     {
    2710         rc = RTPathAppend(pszDst, RTPATH_MAX, RTPathFilename(pState->pszFilename));
    2711         if (RT_SUCCESS(rc))
    2712         {
    2713             size_t cchDst  = strlen(pszDst);
    2714             size_t cchSuff = strlen(pszSuff);
    2715             if (cchDst + cchSuff < RTPATH_MAX)
    2716             {
    2717                 memcpy(&pszDst[cchDst], pszSuff, cchSuff + 1);
    2718                 return VINF_SUCCESS;
    2719             }
    2720             else
    2721                 rc = VERR_BUFFER_OVERFLOW;
    2722         }
    2723     }
    2724     return rc;
    2725 }
    2726 
    2727 /**
    2728  * Interprets the specified string as decimal numbers.
    2729  *
    2730  * @returns true if parsed successfully, false if not.
    2731  * @param   pch                 The string (not terminated).
    2732  * @param   cch                 The string length.
    2733  * @param   pu                  Where to return the value.
    2734  */
    2735 static bool scmSvnReadNumber(const char *pch, size_t cch, size_t *pu)
    2736 {
    2737     size_t u = 0;
    2738     while (cch-- > 0)
    2739     {
    2740         char ch = *pch++;
    2741         if (ch < '0' || ch > '9')
    2742             return false;
    2743         u *= 10;
    2744         u += ch - '0';
    2745     }
    2746     *pu = u;
    2747     return true;
    2748 }
    2749 
    2750 #endif /* SCM_WITHOUT_LIBSVN */
    2751 
    2752 /**
    2753  * Checks if the file we're operating on is part of a SVN working copy.
    2754  *
    2755  * @returns true if it is, false if it isn't or we cannot tell.
    2756  * @param   pState              The rewrite state to work on.
    2757  */
    2758 static bool scmSvnIsInWorkingCopy(PSCMRWSTATE pState)
    2759 {
    2760 #ifdef SCM_WITHOUT_LIBSVN
    2761     /*
    2762      * Hack: check if the .svn/text-base/<file>.svn-base file exists.
    2763      */
    2764     char szPath[RTPATH_MAX];
    2765     int rc = scmSvnConstructName(pState, ".svn/text-base/", ".svn-base", szPath);
    2766     if (RT_SUCCESS(rc))
    2767         return RTFileExists(szPath);
    2768 
    2769 #else
    2770     NOREF(pState);
    2771 #endif
    2772     return false;
    2773 }
    2774 
    2775 /**
    2776  * Queries the value of an SVN property.
    2777  *
    2778  * This will automatically adjust for scheduled changes.
    2779  *
    2780  * @returns IPRT status code.
    2781  * @retval  VERR_INVALID_STATE if not a SVN WC file.
    2782  * @retval  VERR_NOT_FOUND if the property wasn't found.
    2783  * @param   pState              The rewrite state to work on.
    2784  * @param   pszName             The property name.
    2785  * @param   ppszValue           Where to return the property value.  Free this
    2786  *                              using RTStrFree.  Optional.
    2787  */
    2788 static int scmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)
    2789 {
    2790     /*
    2791      * Look it up in the scheduled changes.
    2792      */
    2793     uint32_t i = pState->cSvnPropChanges;
    2794     while (i-- > 0)
    2795         if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
    2796         {
    2797             const char *pszValue = pState->paSvnPropChanges[i].pszValue;
    2798             if (!pszValue)
    2799                 return VERR_NOT_FOUND;
    2800             if (ppszValue)
    2801                 return RTStrDupEx(ppszValue, pszValue);
    2802             return VINF_SUCCESS;
    2803         }
    2804 
    2805 #ifdef SCM_WITHOUT_LIBSVN
    2806     /*
    2807      * Hack: Read the .svn/props/<file>.svn-work file exists.
    2808      */
    2809     char szPath[RTPATH_MAX];
    2810     int rc = scmSvnConstructName(pState, ".svn/props/", ".svn-work", szPath);
    2811     if (RT_SUCCESS(rc) && !RTFileExists(szPath))
    2812         rc = scmSvnConstructName(pState, ".svn/prop-base/", ".svn-base", szPath);
    2813     if (RT_SUCCESS(rc))
    2814     {
    2815         SCMSTREAM Stream;
    2816         rc = ScmStreamInitForReading(&Stream, szPath);
    2817         if (RT_SUCCESS(rc))
    2818         {
    2819             /*
    2820              * The current format is K len\n<name>\nV len\n<value>\n" ... END.
    2821              */
    2822             rc = VERR_NOT_FOUND;
    2823             size_t const    cchName = strlen(pszName);
    2824             SCMEOL          enmEol;
    2825             size_t          cchLine;
    2826             const char     *pchLine;
    2827             while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
    2828             {
    2829                 /*
    2830                  * Parse the 'K num' / 'END' line.
    2831                  */
    2832                 if (   cchLine == 3
    2833                     && !memcmp(pchLine, "END", 3))
    2834                     break;
    2835                 size_t cchKey;
    2836                 if (   cchLine < 3
    2837                     || pchLine[0] != 'K'
    2838                     || pchLine[1] != ' '
    2839                     || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey)
    2840                     || cchKey == 0
    2841                     || cchKey > 4096)
    2842                 {
    2843                     RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
    2844                     rc = VERR_PARSE_ERROR;
    2845                     break;
    2846                 }
    2847 
    2848                 /*
    2849                  * Match the key and skip to the value line.  Don't bother with
    2850                  * names containing EOL markers.
    2851                  */
    2852                 size_t const offKey = ScmStreamTell(&Stream);
    2853                 bool fMatch = cchName == cchKey;
    2854                 if (fMatch)
    2855                 {
    2856                     pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
    2857                     if (!pchLine)
    2858                         break;
    2859                     fMatch = cchLine == cchName
    2860                           && !memcmp(pchLine, pszName, cchName);
    2861                 }
    2862 
    2863                 if (RT_FAILURE(ScmStreamSeekAbsolute(&Stream, offKey + cchKey)))
    2864                     break;
    2865                 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
    2866                     break;
    2867 
    2868                 /*
    2869                  * Read and Parse the 'V num' line.
    2870                  */
    2871                 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
    2872                 if (!pchLine)
    2873                     break;
    2874                 size_t cchValue;
    2875                 if (   cchLine < 3
    2876                     || pchLine[0] != 'V'
    2877                     || pchLine[1] != ' '
    2878                     || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchValue)
    2879                     || cchValue > _1M)
    2880                 {
    2881                     RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
    2882                     rc = VERR_PARSE_ERROR;
    2883                     break;
    2884                 }
    2885 
    2886                 /*
    2887                  * If we have a match, allocate a return buffer and read the
    2888                  * value into it.  Otherwise skip this value and continue
    2889                  * searching.
    2890                  */
    2891                 if (fMatch)
    2892                 {
    2893                     if (!ppszValue)
    2894                         rc = VINF_SUCCESS;
    2895                     else
    2896                     {
    2897                         char *pszValue;
    2898                         rc = RTStrAllocEx(&pszValue, cchValue + 1);
    2899                         if (RT_SUCCESS(rc))
    2900                         {
    2901                             rc = ScmStreamRead(&Stream, pszValue, cchValue);
    2902                             if (RT_SUCCESS(rc))
    2903                                 *ppszValue = pszValue;
    2904                             else
    2905                                 RTStrFree(pszValue);
    2906                         }
    2907                     }
    2908                     break;
    2909                 }
    2910 
    2911                 if (RT_FAILURE(ScmStreamSeekRelative(&Stream, cchValue)))
    2912                     break;
    2913                 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
    2914                     break;
    2915             }
    2916 
    2917             if (RT_FAILURE(ScmStreamGetStatus(&Stream)))
    2918             {
    2919                 rc = ScmStreamGetStatus(&Stream);
    2920                 RTMsgError("%s: stream error %Rrc\n", szPath, rc);
    2921             }
    2922             ScmStreamDelete(&Stream);
    2923         }
    2924     }
    2925 
    2926     if (rc == VERR_FILE_NOT_FOUND)
    2927         rc = VERR_NOT_FOUND;
    2928     return rc;
    2929 
    2930 #else
    2931     NOREF(pState);
    2932 #endif
    2933     return VERR_NOT_FOUND;
    2934 }
    2935 
    2936 
    2937 /**
    2938  * Schedules the setting of a property.
    2939  *
    2940  * @returns IPRT status code.
    2941  * @retval  VERR_INVALID_STATE if not a SVN WC file.
    2942  * @param   pState              The rewrite state to work on.
    2943  * @param   pszName             The name of the property to set.
    2944  * @param   pszValue            The value.  NULL means deleting it.
    2945  */
    2946 static int scmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue)
    2947 {
    2948     /*
    2949      * Update any existing entry first.
    2950      */
    2951     size_t i = pState->cSvnPropChanges;
    2952     while (i-- > 0)
    2953         if (!strcmp(pState->paSvnPropChanges[i].pszName,  pszName))
    2954         {
    2955             if (!pszValue)
    2956             {
    2957                 RTStrFree(pState->paSvnPropChanges[i].pszValue);
    2958                 pState->paSvnPropChanges[i].pszValue = NULL;
    2959             }
    2960             else
    2961             {
    2962                 char *pszCopy;
    2963                 int rc = RTStrDupEx(&pszCopy, pszValue);
    2964                 if (RT_FAILURE(rc))
    2965                     return rc;
    2966                 pState->paSvnPropChanges[i].pszValue = pszCopy;
    2967             }
    2968             return VINF_SUCCESS;
    2969         }
    2970 
    2971     /*
    2972      * Insert a new entry.
    2973      */
    2974     i = pState->cSvnPropChanges;
    2975     if ((i % 32) == 0)
    2976     {
    2977         void *pvNew = RTMemRealloc(pState->paSvnPropChanges, (i + 32) * sizeof(SCMSVNPROP));
    2978         if (!pvNew)
    2979             return VERR_NO_MEMORY;
    2980         pState->paSvnPropChanges = (PSCMSVNPROP)pvNew;
    2981     }
    2982 
    2983     pState->paSvnPropChanges[i].pszName  = RTStrDup(pszName);
    2984     pState->paSvnPropChanges[i].pszValue = pszValue ? RTStrDup(pszValue) : NULL;
    2985     if (   pState->paSvnPropChanges[i].pszName
    2986         && (pState->paSvnPropChanges[i].pszValue || !pszValue) )
    2987         pState->cSvnPropChanges = i + 1;
    2988     else
    2989     {
    2990         RTStrFree(pState->paSvnPropChanges[i].pszName);
    2991         pState->paSvnPropChanges[i].pszName = NULL;
    2992         RTStrFree(pState->paSvnPropChanges[i].pszValue);
    2993         pState->paSvnPropChanges[i].pszValue = NULL;
    2994         return VERR_NO_MEMORY;
    2995     }
    2996     return VINF_SUCCESS;
    2997 }
    2998 
    2999 
    3000 /**
    3001  * Schedules a property deletion.
    3002  *
    3003  * @returns IPRT status code.
    3004  * @param   pState              The rewrite state to work on.
    3005  * @param   pszName             The name of the property to delete.
    3006  */
    3007 static int scmSvnDelProperty(PSCMRWSTATE pState, const char *pszName)
    3008 {
    3009     return scmSvnSetProperty(pState, pszName, NULL);
    3010 }
    3011 
    3012 
    3013 /**
    3014  * Applies any SVN property changes to the work copy of the file.
    3015  *
    3016  * @returns IPRT status code.
    3017  * @param   pState              The rewrite state which SVN property changes
    3018  *                              should be applied.
    3019  */
    3020 static int scmSvnDisplayChanges(PSCMRWSTATE pState)
    3021 {
    3022     size_t i = pState->cSvnPropChanges;
    3023     while (i-- > 0)
    3024     {
    3025         const char *pszName  = pState->paSvnPropChanges[i].pszName;
    3026         const char *pszValue = pState->paSvnPropChanges[i].pszValue;
    3027         if (pszValue)
    3028             ScmVerbose(pState, 0, "svn ps '%s' '%s'  %s\n", pszName, pszValue, pState->pszFilename);
    3029         else
    3030             ScmVerbose(pState, 0, "svn pd '%s'  %s\n", pszName, pszValue, pState->pszFilename);
    3031     }
    3032 
    3033     return VINF_SUCCESS;
    3034 }
    3035 
    3036 /**
    3037  * Applies any SVN property changes to the work copy of the file.
    3038  *
    3039  * @returns IPRT status code.
    3040  * @param   pState              The rewrite state which SVN property changes
    3041  *                              should be applied.
    3042  */
    3043 static int scmSvnApplyChanges(PSCMRWSTATE pState)
    3044 {
    3045 #ifdef SCM_WITHOUT_LIBSVN
    3046     /*
    3047      * This sucks. We gotta find svn(.exe).
    3048      */
    3049     static char s_szSvnPath[RTPATH_MAX];
    3050     if (s_szSvnPath[0] == '\0')
    3051         scmSvnFindSvnBinary(s_szSvnPath, sizeof(s_szSvnPath));
    3052 
    3053     /*
    3054      * Iterate thru the changes and apply them by starting the svn client.
    3055      */
    3056     for (size_t i = 0; i <pState->cSvnPropChanges; i++)
    3057     {
    3058         const char *apszArgv[6];
    3059         apszArgv[0] = s_szSvnPath;
    3060         apszArgv[1] = pState->paSvnPropChanges[i].pszValue ? "ps" : "pd";
    3061         apszArgv[2] = pState->paSvnPropChanges[i].pszName;
    3062         int iArg = 3;
    3063         if (pState->paSvnPropChanges[i].pszValue)
    3064             apszArgv[iArg++] = pState->paSvnPropChanges[i].pszValue;
    3065         apszArgv[iArg++] = pState->pszFilename;
    3066         apszArgv[iArg++] = NULL;
    3067         ScmVerbose(pState, 2, "executing: %s %s %s %s %s\n",
    3068                    apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4]);
    3069 
    3070         RTPROCESS pid;
    3071         int rc = RTProcCreate(s_szSvnPath, apszArgv, RTENV_DEFAULT, 0 /*fFlags*/, &pid);
    3072         if (RT_SUCCESS(rc))
    3073         {
    3074             RTPROCSTATUS Status;
    3075             rc = RTProcWait(pid, RTPROCWAIT_FLAGS_BLOCK, &Status);
    3076             if (    RT_SUCCESS(rc)
    3077                 &&  (   Status.enmReason != RTPROCEXITREASON_NORMAL
    3078                      || Status.iStatus != 0) )
    3079             {
    3080                 RTMsgError("%s: %s %s %s %s %s -> %s %u\n",
    3081                            pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4],
    3082                            Status.enmReason == RTPROCEXITREASON_NORMAL   ? "exit code"
    3083                            : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"
    3084                            : Status.enmReason == RTPROCEXITREASON_ABEND  ? "abnormal end"
    3085                            : "abducted by alien",
    3086                            Status.iStatus);
    3087                 return VERR_GENERAL_FAILURE;
    3088             }
    3089         }
    3090         if (RT_FAILURE(rc))
    3091         {
    3092             RTMsgError("%s: error executing %s %s %s %s %s: %Rrc\n",
    3093                        pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4], rc);
    3094             return rc;
    3095         }
    3096     }
    3097 
    3098     return VINF_SUCCESS;
    3099 #else
    3100     return VERR_NOT_IMPLEMENTED;
    3101 #endif
    3102 }
    3103 
    3104 
    3105 /* -=-=-=-=-=- rewriters -=-=-=-=-=- */
    3106 
    3107 
    3108 /**
    3109  * Strip trailing blanks (space & tab).
    3110  *
    3111  * @returns True if modified, false if not.
    3112  * @param   pIn                 The input stream.
    3113  * @param   pOut                The output stream.
    3114  * @param   pSettings           The settings.
    3115  */
    3116 static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3117 {
    3118     if (!pSettings->fStripTrailingBlanks)
    3119         return false;
    3120 
    3121     bool        fModified = false;
    3122     SCMEOL      enmEol;
    3123     size_t      cchLine;
    3124     const char *pchLine;
    3125     while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
    3126     {
    3127         int rc;
    3128         if (    cchLine == 0
    3129             ||  !RT_C_IS_BLANK(pchLine[cchLine - 1]) )
    3130             rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    3131         else
    3132         {
    3133             cchLine--;
    3134             while (cchLine > 0 && RT_C_IS_BLANK(pchLine[cchLine - 1]))
    3135                 cchLine--;
    3136             rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    3137             fModified = true;
    3138         }
    3139         if (RT_FAILURE(rc))
    3140             return false;
    3141     }
    3142     if (fModified)
    3143         ScmVerbose(pState, 2, " * Stripped trailing blanks\n");
    3144     return fModified;
    3145 }
    3146 
    3147 /**
    3148  * Expand tabs.
    3149  *
    3150  * @returns True if modified, false if not.
    3151  * @param   pIn                 The input stream.
    3152  * @param   pOut                The output stream.
    3153  * @param   pSettings           The settings.
    3154  */
    3155 static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3156 {
    3157     if (!pSettings->fConvertTabs)
    3158         return false;
    3159 
    3160     size_t const    cchTab = pSettings->cchTab;
    3161     bool            fModified = false;
    3162     SCMEOL          enmEol;
    3163     size_t          cchLine;
    3164     const char     *pchLine;
    3165     while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
    3166     {
    3167         int rc;
    3168         const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);
    3169         if (!pchTab)
    3170             rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    3171         else
    3172         {
    3173             size_t      offTab   = 0;
    3174             const char *pchChunk = pchLine;
    3175             for (;;)
    3176             {
    3177                 size_t  cchChunk = pchTab - pchChunk;
    3178                 offTab += cchChunk;
    3179                 ScmStreamWrite(pOut, pchChunk, cchChunk);
    3180 
    3181                 size_t  cchToTab = cchTab - offTab % cchTab;
    3182                 ScmStreamWrite(pOut, g_szTabSpaces, cchToTab);
    3183                 offTab += cchToTab;
    3184 
    3185                 pchChunk = pchTab + 1;
    3186                 size_t  cchLeft  = cchLine - (pchChunk - pchLine);
    3187                 pchTab = (const char *)memchr(pchChunk, '\t', cchLeft);
    3188                 if (!pchTab)
    3189                 {
    3190                     rc = ScmStreamPutLine(pOut, pchChunk, cchLeft, enmEol);
    3191                     break;
    3192                 }
    3193             }
    3194 
    3195             fModified = true;
    3196         }
    3197         if (RT_FAILURE(rc))
    3198             return false;
    3199     }
    3200     if (fModified)
    3201         ScmVerbose(pState, 2, " * Expanded tabs\n");
    3202     return fModified;
    3203 }
    3204 
    3205 /**
    3206  * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF.
    3207  *
    3208  * @returns true if modifications were made, false if not.
    3209  * @param   pIn                 The input stream.
    3210  * @param   pOut                The output stream.
    3211  * @param   pSettings           The settings.
    3212  * @param   enmDesiredEol       The desired end of line indicator type.
    3213  * @param   pszDesiredSvnEol    The desired svn:eol-style.
    3214  */
    3215 static bool rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
    3216                              SCMEOL enmDesiredEol, const char *pszDesiredSvnEol)
    3217 {
    3218     if (!pSettings->fConvertEol)
    3219         return false;
    3220 
    3221     bool        fModified = false;
    3222     SCMEOL      enmEol;
    3223     size_t      cchLine;
    3224     const char *pchLine;
    3225     while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
    3226     {
    3227         if (   enmEol != enmDesiredEol
    3228             && enmEol != SCMEOL_NONE)
    3229         {
    3230             fModified = true;
    3231             enmEol = enmDesiredEol;
    3232         }
    3233         int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    3234         if (RT_FAILURE(rc))
    3235             return false;
    3236     }
    3237     if (fModified)
    3238         ScmVerbose(pState, 2, " * Converted EOL markers\n");
    3239 
    3240     /* Check svn:eol-style if appropriate */
    3241     if (   pSettings->fSetSvnEol
    3242         && scmSvnIsInWorkingCopy(pState))
    3243     {
    3244         char *pszEol;
    3245         int rc = scmSvnQueryProperty(pState, "svn:eol-style", &pszEol);
    3246         if (   (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol))
    3247             || rc == VERR_NOT_FOUND)
    3248         {
    3249             if (rc == VERR_NOT_FOUND)
    3250                 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (missing)\n", pszDesiredSvnEol);
    3251             else
    3252                 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol);
    3253             int rc2 = scmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol);
    3254             if (RT_FAILURE(rc2))
    3255                 RTMsgError("scmSvnSetProperty: %Rrc\n", rc2); /** @todo propagate the error somehow... */
    3256         }
    3257         if (RT_SUCCESS(rc))
    3258             RTStrFree(pszEol);
    3259     }
    3260 
    3261     /** @todo also check the subversion svn:eol-style state! */
    3262     return fModified;
    3263 }
    3264 
    3265 /**
    3266  * Force native end of line indicator.
    3267  *
    3268  * @returns true if modifications were made, false if not.
    3269  * @param   pIn                 The input stream.
    3270  * @param   pOut                The output stream.
    3271  * @param   pSettings           The settings.
    3272  */
    3273 static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3274 {
    3275 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
    3276     return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "native");
    3277 #else
    3278     return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF,   "native");
    3279 #endif
    3280 }
    3281 
    3282 /**
    3283  * Force the stream to use LF as the end of line indicator.
    3284  *
    3285  * @returns true if modifications were made, false if not.
    3286  * @param   pIn                 The input stream.
    3287  * @param   pOut                The output stream.
    3288  * @param   pSettings           The settings.
    3289  */
    3290 static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3291 {
    3292     return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "LF");
    3293 }
    3294 
    3295 /**
    3296  * Force the stream to use CRLF as the end of line indicator.
    3297  *
    3298  * @returns true if modifications were made, false if not.
    3299  * @param   pIn                 The input stream.
    3300  * @param   pOut                The output stream.
    3301  * @param   pSettings           The settings.
    3302  */
    3303 static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3304 {
    3305     return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "CRLF");
    3306 }
    3307 
    3308 /**
    3309  * Strip trailing blank lines and/or make sure there is exactly one blank line
    3310  * at the end of the file.
    3311  *
    3312  * @returns true if modifications were made, false if not.
    3313  * @param   pIn                 The input stream.
    3314  * @param   pOut                The output stream.
    3315  * @param   pSettings           The settings.
    3316  *
    3317  * @remarks ASSUMES trailing white space has been removed already.
    3318  */
    3319 static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3320 {
    3321     if (   !pSettings->fStripTrailingLines
    3322         && !pSettings->fForceTrailingLine
    3323         && !pSettings->fForceFinalEol)
    3324         return false;
    3325 
    3326     size_t const cLines = ScmStreamCountLines(pIn);
    3327 
    3328     /* Empty files remains empty. */
    3329     if (cLines <= 1)
    3330         return false;
    3331 
    3332     /* Figure out if we need to adjust the number of lines or not. */
    3333     size_t cLinesNew = cLines;
    3334 
    3335     if (   pSettings->fStripTrailingLines
    3336         && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
    3337     {
    3338         while (   cLinesNew > 1
    3339                && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))
    3340             cLinesNew--;
    3341     }
    3342 
    3343     if (    pSettings->fForceTrailingLine
    3344         && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
    3345         cLinesNew++;
    3346 
    3347     bool fFixMissingEol = pSettings->fForceFinalEol
    3348                        && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;
    3349 
    3350     if (   !fFixMissingEol
    3351         && cLines == cLinesNew)
    3352         return false;
    3353 
    3354     /* Copy the number of lines we've arrived at. */
    3355     ScmStreamRewindForReading(pIn);
    3356 
    3357     size_t cCopied = RT_MIN(cLinesNew, cLines);
    3358     ScmStreamCopyLines(pOut, pIn, cCopied);
    3359 
    3360     if (cCopied != cLinesNew)
    3361     {
    3362         while (cCopied++ < cLinesNew)
    3363             ScmStreamPutLine(pOut, "", 0, ScmStreamGetEol(pIn));
    3364     }
    3365     /* Fix missing EOL if required. */
    3366     else if (fFixMissingEol)
    3367     {
    3368         if (ScmStreamGetEol(pIn) == SCMEOL_LF)
    3369             ScmStreamWrite(pOut, "\n", 1);
    3370         else
    3371             ScmStreamWrite(pOut, "\r\n", 2);
    3372     }
    3373 
    3374     ScmVerbose(pState, 2, " * Adjusted trailing blank lines\n");
    3375     return true;
    3376 }
    3377 
    3378 /**
    3379  * Make sure there is no svn:executable keyword on the current file.
    3380  *
    3381  * @returns false - the state carries these kinds of changes.
    3382  * @param   pState              The rewriter state.
    3383  * @param   pIn                 The input stream.
    3384  * @param   pOut                The output stream.
    3385  * @param   pSettings           The settings.
    3386  */
    3387 static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3388 {
    3389     if (   !pSettings->fSetSvnExecutable
    3390         || !scmSvnIsInWorkingCopy(pState))
    3391         return false;
    3392 
    3393     int rc = scmSvnQueryProperty(pState, "svn:executable", NULL);
    3394     if (RT_SUCCESS(rc))
    3395     {
    3396         ScmVerbose(pState, 2, " * removing svn:executable\n");
    3397         rc = scmSvnDelProperty(pState, "svn:executable");
    3398         if (RT_FAILURE(rc))
    3399             RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
    3400     }
    3401     return false;
    3402 }
    3403 
    3404 /**
    3405  * Make sure the Id and Revision keywords are expanded.
    3406  *
    3407  * @returns false - the state carries these kinds of changes.
    3408  * @param   pState              The rewriter state.
    3409  * @param   pIn                 The input stream.
    3410  * @param   pOut                The output stream.
    3411  * @param   pSettings           The settings.
    3412  */
    3413 static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3414 {
    3415     if (   !pSettings->fSetSvnKeywords
    3416         || !scmSvnIsInWorkingCopy(pState))
    3417         return false;
    3418 
    3419     char *pszKeywords;
    3420     int rc = scmSvnQueryProperty(pState, "svn:keywords", &pszKeywords);
    3421     if (    RT_SUCCESS(rc)
    3422         && (   !strstr(pszKeywords, "Id") /** @todo need some function for finding a word in a string.  */
    3423             || !strstr(pszKeywords, "Revision")) )
    3424     {
    3425         if (!strstr(pszKeywords, "Id") && !strstr(pszKeywords, "Revision"))
    3426             rc = RTStrAAppend(&pszKeywords, " Id Revision");
    3427         else if (!strstr(pszKeywords, "Id"))
    3428             rc = RTStrAAppend(&pszKeywords, " Id");
    3429         else
    3430             rc = RTStrAAppend(&pszKeywords, " Revision");
    3431         if (RT_SUCCESS(rc))
    3432         {
    3433             ScmVerbose(pState, 2, " * changing svn:keywords to '%s'\n", pszKeywords);
    3434             rc = scmSvnSetProperty(pState, "svn:keywords", pszKeywords);
    3435             if (RT_FAILURE(rc))
    3436                 RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
    3437         }
    3438         else
    3439             RTMsgError("RTStrAppend: %Rrc\n", rc); /** @todo error propagation here.. */
    3440         RTStrFree(pszKeywords);
    3441     }
    3442     else if (rc == VERR_NOT_FOUND)
    3443     {
    3444         ScmVerbose(pState, 2, " * setting svn:keywords to 'Id Revision'\n");
    3445         rc = scmSvnSetProperty(pState, "svn:keywords", "Id Revision");
    3446         if (RT_FAILURE(rc))
    3447             RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
    3448     }
    3449     else if (RT_SUCCESS(rc))
    3450         RTStrFree(pszKeywords);
    3451 
    3452     return false;
    3453 }
    3454 
    3455 /**
    3456  * Makefile.kup are empty files, enforce this.
    3457  *
    3458  * @returns true if modifications were made, false if not.
    3459  * @param   pIn                 The input stream.
    3460  * @param   pOut                The output stream.
    3461  * @param   pSettings           The settings.
    3462  */
    3463 static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3464 {
    3465     /* These files should be zero bytes. */
    3466     if (pIn->cb == 0)
    3467         return false;
    3468     ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
    3469     return true;
    3470 }
    3471 
    3472 /**
    3473  * Rewrite a kBuild makefile.
    3474  *
    3475  * @returns true if modifications were made, false if not.
    3476  * @param   pIn                 The input stream.
    3477  * @param   pOut                The output stream.
    3478  * @param   pSettings           The settings.
    3479  *
    3480  * @todo
    3481  *
    3482  * Ideas for Makefile.kmk and Config.kmk:
    3483  *      - sort if1of/ifn1of sets.
    3484  *      - line continuation slashes should only be preceded by one space.
    3485  */
    3486 static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3487 {
    3488     return false;
    3489 }
    3490 
    3491 /**
    3492  * Rewrite a C/C++ source or header file.
    3493  *
    3494  * @returns true if modifications were made, false if not.
    3495  * @param   pIn                 The input stream.
    3496  * @param   pOut                The output stream.
    3497  * @param   pSettings           The settings.
    3498  *
    3499  * @todo
    3500  *
    3501  * Ideas for C/C++:
    3502  *      - space after if, while, for, switch
    3503  *      - spaces in for (i=0;i<x;i++)
    3504  *      - complex conditional, bird style.
    3505  *      - remove unnecessary parentheses.
    3506  *      - sort defined RT_OS_*||  and RT_ARCH
    3507  *      - sizeof without parenthesis.
    3508  *      - defined without parenthesis.
    3509  *      - trailing spaces.
    3510  *      - parameter indentation.
    3511  *      - space after comma.
    3512  *      - while (x--); -> multi line + comment.
    3513  *      - else statement;
    3514  *      - space between function and left parenthesis.
    3515  *      - TODO, XXX, @todo cleanup.
    3516  *      - Space before/after '*'.
    3517  *      - ensure new line at end of file.
    3518  *      - Indentation of precompiler statements (#ifdef, #defines).
    3519  *      - space between functions.
    3520  *      - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
    3521  */
    3522 static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3523 {
    3524 
    3525     return false;
    3526 }
    3527 
    3528 /* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */
    3529 
    3530 /**
    3531  * Processes a file.
    3532  *
    3533  * @returns IPRT status code.
    3534  * @param   pState              The rewriter state.
    3535  * @param   pszFilename         The file name.
    3536  * @param   pszBasename         The base name (pointer within @a pszFilename).
    3537  * @param   cchBasename         The length of the base name.  (For passing to
    3538  *                              RTStrSimplePatternMultiMatch.)
    3539  * @param   pBaseSettings       The base settings to use.  It's OK to modify
    3540  *                              these.
    3541  */
    3542 static int scmProcessFileInner(PSCMRWSTATE pState, const char *pszFilename, const char *pszBasename, size_t cchBasename,
    3543                                PSCMSETTINGSBASE pBaseSettings)
    3544 {
    3545     /*
    3546      * Do the file level filtering.
    3547      */
    3548     if (   pBaseSettings->pszFilterFiles
    3549         && *pBaseSettings->pszFilterFiles
    3550         && !RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterFiles, RTSTR_MAX, pszBasename, cchBasename, NULL))
    3551     {
    3552         ScmVerbose(NULL, 5, "skipping '%s': file filter mismatch\n", pszFilename);
    3553         return VINF_SUCCESS;
    3554     }
    3555     if (   pBaseSettings->pszFilterOutFiles
    3556         && *pBaseSettings->pszFilterOutFiles
    3557         && (   RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszBasename, cchBasename, NULL)
    3558             || RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszFilename, RTSTR_MAX, NULL)) )
    3559     {
    3560         ScmVerbose(NULL, 5, "skipping '%s': filterd out\n", pszFilename);
    3561         return VINF_SUCCESS;
    3562     }
    3563     if (   pBaseSettings->fOnlySvnFiles
    3564         && !scmSvnIsInWorkingCopy(pState))
    3565     {
    3566         ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename);
    3567         return VINF_SUCCESS;
    3568     }
    3569 
    3570     /*
    3571      * Try find a matching rewrite config for this filename.
    3572      */
    3573     PCSCMCFGENTRY pCfg = NULL;
    3574     for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
    3575         if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, pszBasename, cchBasename, NULL))
    3576         {
    3577             pCfg = &g_aConfigs[iCfg];
    3578             break;
    3579         }
    3580     if (!pCfg)
    3581     {
    3582         ScmVerbose(NULL, 4, "skipping '%s': no rewriters configured\n", pszFilename);
    3583         return VINF_SUCCESS;
    3584     }
    3585     ScmVerbose(pState, 4, "matched \"%s\"\n", pCfg->pszFilePattern);
    3586 
    3587     /*
    3588      * Create an input stream from the file and check that it's text.
    3589      */
    3590     SCMSTREAM Stream1;
    3591     int rc = ScmStreamInitForReading(&Stream1, pszFilename);
    3592     if (RT_FAILURE(rc))
    3593     {
    3594         RTMsgError("Failed to read '%s': %Rrc\n", pszFilename, rc);
    3595         return rc;
    3596     }
    3597     if (ScmStreamIsText(&Stream1))
    3598     {
    3599         ScmVerbose(pState, 3, NULL);
    3600 
    3601         /*
    3602          * Gather SCM and editor settings from the stream.
    3603          */
    3604         rc = scmSettingsBaseLoadFromDocument(pBaseSettings, &Stream1);
    3605         if (RT_SUCCESS(rc))
    3606         {
    3607             ScmStreamRewindForReading(&Stream1);
    3608 
    3609             /*
    3610              * Create two more streams for output and push the text thru all the
    3611              * rewriters, switching the two streams around when something is
    3612              * actually rewritten.  Stream1 remains unchanged.
    3613              */
    3614             SCMSTREAM Stream2;
    3615             rc = ScmStreamInitForWriting(&Stream2, &Stream1);
    3616             if (RT_SUCCESS(rc))
    3617             {
    3618                 SCMSTREAM Stream3;
    3619                 rc = ScmStreamInitForWriting(&Stream3, &Stream1);
    3620                 if (RT_SUCCESS(rc))
    3621                 {
    3622                     bool        fModified = false;
    3623                     PSCMSTREAM  pIn       = &Stream1;
    3624                     PSCMSTREAM  pOut      = &Stream2;
    3625                     for (size_t iRw = 0; iRw < pCfg->cRewriters; iRw++)
    3626                     {
    3627                         bool fRc = pCfg->papfnRewriter[iRw](pState, pIn, pOut, pBaseSettings);
    3628                         if (fRc)
    3629                         {
    3630                             PSCMSTREAM pTmp = pOut;
    3631                             pOut = pIn == &Stream1 ? &Stream3 : pIn;
    3632                             pIn  = pTmp;
    3633                             fModified = true;
    3634                         }
    3635                         ScmStreamRewindForReading(pIn);
    3636                         ScmStreamRewindForWriting(pOut);
    3637                     }
    3638 
    3639                     rc = ScmStreamGetStatus(&Stream1);
    3640                     if (RT_SUCCESS(rc))
    3641                         rc = ScmStreamGetStatus(&Stream2);
    3642                     if (RT_SUCCESS(rc))
    3643                         rc = ScmStreamGetStatus(&Stream3);
    3644                     if (RT_SUCCESS(rc))
    3645                     {
    3646                         /*
    3647                          * If rewritten, write it back to disk.
    3648                          */
    3649                         if (fModified)
    3650                         {
    3651                             if (!g_fDryRun)
    3652                             {
    3653                                 ScmVerbose(pState, 1, "writing modified file to \"%s%s\"\n", pszFilename, g_pszChangedSuff);
    3654                                 rc = ScmStreamWriteToFile(pIn, "%s%s", pszFilename, g_pszChangedSuff);
    3655                                 if (RT_FAILURE(rc))
    3656                                     RTMsgError("Error writing '%s%s': %Rrc\n", pszFilename, g_pszChangedSuff, rc);
    3657                             }
    3658                             else
    3659                             {
    3660                                 ScmVerbose(pState, 1, NULL);
    3661                                 ScmDiffStreams(pszFilename, &Stream1, pIn, g_fDiffIgnoreEol, g_fDiffIgnoreLeadingWS,
    3662                                                g_fDiffIgnoreTrailingWS, g_fDiffSpecialChars, pBaseSettings->cchTab, g_pStdOut);
    3663                                 ScmVerbose(pState, 2, "would have modified the file \"%s%s\"\n", pszFilename, g_pszChangedSuff);
    3664                             }
    3665                         }
    3666 
    3667                         /*
    3668                          * If pending SVN property changes, apply them.
    3669                          */
    3670                         if (pState->cSvnPropChanges && RT_SUCCESS(rc))
    3671                         {
    3672                             if (!g_fDryRun)
    3673                             {
    3674                                 rc = scmSvnApplyChanges(pState);
    3675                                 if (RT_FAILURE(rc))
    3676                                     RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc);
    3677                             }
    3678                             else
    3679                                 scmSvnDisplayChanges(pState);
    3680                         }
    3681 
    3682                         if (!fModified && !pState->cSvnPropChanges)
    3683                             ScmVerbose(pState, 3, "no change\n", pszFilename);
    3684                     }
    3685                     else
    3686                         RTMsgError("%s: stream error %Rrc\n", pszFilename);
    3687                     ScmStreamDelete(&Stream3);
    3688                 }
    3689                 else
    3690                     RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
    3691                 ScmStreamDelete(&Stream2);
    3692             }
    3693             else
    3694                 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
    3695         }
    3696         else
    3697             RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);
    3698     }
    3699     else
    3700         ScmVerbose(pState, 4, "not text file: \"%s\"\n", pszFilename);
    3701     ScmStreamDelete(&Stream1);
    3702 
    3703     return rc;
    3704 }
    3705 
    3706 /**
    3707  * Processes a file.
    3708  *
    3709  * This is just a wrapper for scmProcessFileInner for avoid wasting stack in the
    3710  * directory recursion method.
    3711  *
    3712  * @returns IPRT status code.
    3713  * @param   pszFilename         The file name.
    3714  * @param   pszBasename         The base name (pointer within @a pszFilename).
    3715  * @param   cchBasename         The length of the base name.  (For passing to
    3716  *                              RTStrSimplePatternMultiMatch.)
    3717  * @param   pSettingsStack      The settings stack (pointer to the top element).
    3718  */
    3719 static int scmProcessFile(const char *pszFilename, const char *pszBasename, size_t cchBasename,
    3720                           PSCMSETTINGS pSettingsStack)
    3721 {
    3722     SCMSETTINGSBASE Base;
    3723     int rc = scmSettingsStackMakeFileBase(pSettingsStack, pszFilename, pszBasename, cchBasename, &Base);
    3724     if (RT_SUCCESS(rc))
    3725     {
    3726         SCMRWSTATE State;
    3727         State.fFirst           = false;
    3728         State.pszFilename      = pszFilename;
    3729         State.cSvnPropChanges  = 0;
    3730         State.paSvnPropChanges = NULL;
    3731 
    3732         rc = scmProcessFileInner(&State, pszFilename, pszBasename, cchBasename, &Base);
    3733 
    3734         size_t i = State.cSvnPropChanges;
    3735         while (i-- > 0)
    3736         {
    3737             RTStrFree(State.paSvnPropChanges[i].pszName);
    3738             RTStrFree(State.paSvnPropChanges[i].pszValue);
    3739         }
    3740         RTMemFree(State.paSvnPropChanges);
    3741 
    3742         scmSettingsBaseDelete(&Base);
    3743     }
    3744     return rc;
    3745 }
    3746 
    3747 
    3748 /**
    3749  * Tries to correct RTDIRENTRY_UNKNOWN.
    3750  *
    3751  * @returns Corrected type.
    3752  * @param   pszPath             The path to the object in question.
    3753  */
    3754 static RTDIRENTRYTYPE scmFigureUnknownType(const char *pszPath)
    3755 {
    3756     RTFSOBJINFO Info;
    3757     int rc = RTPathQueryInfo(pszPath, &Info, RTFSOBJATTRADD_NOTHING);
    3758     if (RT_FAILURE(rc))
    3759         return RTDIRENTRYTYPE_UNKNOWN;
    3760     if (RTFS_IS_DIRECTORY(Info.Attr.fMode))
    3761         return RTDIRENTRYTYPE_DIRECTORY;
    3762     if (RTFS_IS_FILE(Info.Attr.fMode))
    3763         return RTDIRENTRYTYPE_FILE;
    3764     return RTDIRENTRYTYPE_UNKNOWN;
    3765 }
    3766 
    3767 /**
    3768  * Recurse into a sub-directory and process all the files and directories.
    3769  *
    3770  * @returns IPRT status code.
    3771  * @param   pszBuf              Path buffer containing the directory path on
    3772  *                              entry.  This ends with a dot.  This is passed
    3773  *                              along when recursing in order to save stack space
    3774  *                              and avoid needless copying.
    3775  * @param   cchDir              Length of our path in pszbuf.
    3776  * @param   pEntry              Directory entry buffer.  This is also passed
    3777  *                              along when recursing to save stack space.
    3778  * @param   pSettingsStack      The settings stack (pointer to the top element).
    3779  * @param   iRecursion          The recursion depth.  This is used to restrict
    3780  *                              the recursions.
    3781  */
    3782 static int scmProcessDirTreeRecursion(char *pszBuf, size_t cchDir, PRTDIRENTRY pEntry,
    3783                                       PSCMSETTINGS pSettingsStack, unsigned iRecursion)
    3784 {
    3785     int rc;
    3786     Assert(cchDir > 1 && pszBuf[cchDir - 1] == '.');
    3787 
    3788     /*
    3789      * Make sure we stop somewhere.
    3790      */
    3791     if (iRecursion > 128)
    3792     {
    3793         RTMsgError("recursion too deep: %d\n", iRecursion);
    3794         return VINF_SUCCESS; /* ignore */
    3795     }
    3796 
    3797     /*
    3798      * Check if it's excluded by --only-svn-dir.
    3799      */
    3800     if (pSettingsStack->Base.fOnlySvnDirs)
    3801     {
    3802         rc = RTPathAppend(pszBuf, RTPATH_MAX, ".svn");
    3803         if (RT_FAILURE(rc))
    3804         {
    3805             RTMsgError("RTPathAppend: %Rrc\n", rc);
    3806             return rc;
    3807         }
    3808         if (!RTDirExists(pszBuf))
    3809             return VINF_SUCCESS;
    3810 
    3811         Assert(RTPATH_IS_SLASH(pszBuf[cchDir]));
    3812         pszBuf[cchDir]     = '\0';
    3813         pszBuf[cchDir - 1] = '.';
    3814     }
    3815 
    3816     /*
    3817      * Try open and read the directory.
    3818      */
    3819     PRTDIR pDir;
    3820     rc = RTDirOpenFiltered(&pDir, pszBuf, RTDIRFILTER_NONE, 0);
    3821     if (RT_FAILURE(rc))
    3822     {
    3823         RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc);
    3824         return rc;
    3825     }
    3826     for (;;)
    3827     {
    3828         /* Read the next entry. */
    3829         rc = RTDirRead(pDir, pEntry, NULL);
    3830         if (RT_FAILURE(rc))
    3831         {
    3832             if (rc == VERR_NO_MORE_FILES)
    3833                 rc = VINF_SUCCESS;
    3834             else
    3835                 RTMsgError("RTDirRead -> %Rrc\n", rc);
    3836             break;
    3837         }
    3838 
    3839         /* Skip '.' and '..'. */
    3840         if (    pEntry->szName[0] == '.'
    3841             &&  (   pEntry->cbName == 1
    3842                  || (   pEntry->cbName == 2
    3843                      && pEntry->szName[1] == '.')))
    3844             continue;
    3845 
    3846         /* Enter it into the buffer so we've got a full name to work
    3847            with when needed. */
    3848         if (pEntry->cbName + cchDir >= RTPATH_MAX)
    3849         {
    3850             RTMsgError("Skipping too long entry: %s", pEntry->szName);
    3851             continue;
    3852         }
    3853         memcpy(&pszBuf[cchDir - 1], pEntry->szName, pEntry->cbName + 1);
    3854 
    3855         /* Figure the type. */
    3856         RTDIRENTRYTYPE enmType = pEntry->enmType;
    3857         if (enmType == RTDIRENTRYTYPE_UNKNOWN)
    3858             enmType = scmFigureUnknownType(pszBuf);
    3859 
    3860         /* Process the file or directory, skip the rest. */
    3861         if (enmType == RTDIRENTRYTYPE_FILE)
    3862             rc = scmProcessFile(pszBuf, pEntry->szName, pEntry->cbName, pSettingsStack);
    3863         else if (enmType == RTDIRENTRYTYPE_DIRECTORY)
    3864         {
    3865             /* Append the dot for the benefit of the pattern matching. */
    3866             if (pEntry->cbName + cchDir + 5 >= RTPATH_MAX)
    3867             {
    3868                 RTMsgError("Skipping too deep dir entry: %s", pEntry->szName);
    3869                 continue;
    3870             }
    3871             memcpy(&pszBuf[cchDir - 1 + pEntry->cbName], "/.", sizeof("/."));
    3872             size_t cchSubDir = cchDir - 1 + pEntry->cbName + sizeof("/.") - 1;
    3873 
    3874             if (   !pSettingsStack->Base.pszFilterOutDirs
    3875                 || !*pSettingsStack->Base.pszFilterOutDirs
    3876                 || (   !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
    3877                                                      pEntry->szName, pEntry->cbName, NULL)
    3878                     && !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
    3879                                                      pszBuf, cchSubDir, NULL)
    3880                    )
    3881                )
    3882             {
    3883                 rc = scmSettingsStackPushDir(&pSettingsStack, pszBuf);
    3884                 if (RT_SUCCESS(rc))
    3885                 {
    3886                     rc = scmProcessDirTreeRecursion(pszBuf, cchSubDir, pEntry, pSettingsStack, iRecursion + 1);
    3887                     scmSettingsStackPopAndDestroy(&pSettingsStack);
    3888                 }
    3889             }
    3890         }
    3891         if (RT_FAILURE(rc))
    3892             break;
    3893     }
    3894     RTDirClose(pDir);
    3895     return rc;
    3896 
    3897 }
    3898 
    3899 /**
    3900  * Process a directory tree.
    3901  *
    3902  * @returns IPRT status code.
    3903  * @param   pszDir              The directory to start with.  This is pointer to
    3904  *                              a RTPATH_MAX sized buffer.
    3905  */
    3906 static int scmProcessDirTree(char *pszDir, PSCMSETTINGS pSettingsStack)
    3907 {
    3908     /*
    3909      * Setup the recursion.
    3910      */
    3911     int rc = RTPathAppend(pszDir, RTPATH_MAX, ".");
    3912     if (RT_SUCCESS(rc))
    3913     {
    3914         RTDIRENTRY Entry;
    3915         rc = scmProcessDirTreeRecursion(pszDir, strlen(pszDir), &Entry, pSettingsStack, 0);
    3916     }
    3917     else
    3918         RTMsgError("RTPathAppend: %Rrc\n", rc);
    3919     return rc;
    3920 }
    3921 
    3922 
    3923 /**
    3924  * Processes a file or directory specified as an command line argument.
    3925  *
    3926  * @returns IPRT status code
    3927  * @param   pszSomething        What we found in the command line arguments.
    3928  * @param   pSettingsStack      The settings stack (pointer to the top element).
    3929  */
    3930 static int scmProcessSomething(const char *pszSomething, PSCMSETTINGS pSettingsStack)
    3931 {
    3932     char szBuf[RTPATH_MAX];
    3933     int rc = RTPathAbs(pszSomething, szBuf, sizeof(szBuf));
    3934     if (RT_SUCCESS(rc))
    3935     {
    3936         RTPathChangeToUnixSlashes(szBuf, false /*fForce*/);
    3937 
    3938         PSCMSETTINGS pSettings;
    3939         rc = scmSettingsCreateForPath(&pSettings, &pSettingsStack->Base, szBuf);
    3940         if (RT_SUCCESS(rc))
    3941         {
    3942             scmSettingsStackPush(&pSettingsStack, pSettings);
    3943 
    3944             if (RTFileExists(szBuf))
    3945             {
    3946                 const char *pszBasename = RTPathFilename(szBuf);
    3947                 if (pszBasename)
    3948                 {
    3949                     size_t cchBasename = strlen(pszBasename);
    3950                     rc = scmProcessFile(szBuf, pszBasename, cchBasename, pSettingsStack);
    3951                 }
    3952                 else
    3953                 {
    3954                     RTMsgError("RTPathFilename: NULL\n");
    3955                     rc = VERR_IS_A_DIRECTORY;
    3956                 }
    3957             }
    3958             else
    3959                 rc = scmProcessDirTree(szBuf, pSettingsStack);
    3960 
    3961             PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);
    3962             Assert(pPopped == pSettings);
    3963             scmSettingsDestroy(pSettings);
    3964         }
    3965         else
    3966             RTMsgError("scmSettingsInitStack: %Rrc\n", rc);
    3967     }
    3968     else
    3969         RTMsgError("RTPathAbs: %Rrc\n", rc);
    3970     return rc;
    3971 }
    3972 
    3973 int main(int argc, char **argv)
    3974 {
    3975     int rc = RTR3InitExe(argc, &argv, 0);
    3976     if (RT_FAILURE(rc))
    3977         return 1;
    3978 
    3979     /*
    3980      * Init the settings.
    3981      */
    3982     PSCMSETTINGS pSettings;
    3983     rc = scmSettingsCreate(&pSettings, &g_Defaults);
    3984     if (RT_FAILURE(rc))
    3985     {
    3986         RTMsgError("scmSettingsCreate: %Rrc\n", rc);
    3987         return 1;
    3988     }
    3989 
    3990     /*
    3991      * Parse arguments and process input in order (because this is the only
    3992      * thing that works at the moment).
    3993      */
    3994     static RTGETOPTDEF s_aOpts[14 + RT_ELEMENTS(g_aScmOpts)] =
    3995     {
    3996         { "--dry-run",                          'd',                                    RTGETOPT_REQ_NOTHING },
    3997         { "--real-run",                         'D',                                    RTGETOPT_REQ_NOTHING },
    3998         { "--file-filter",                      'f',                                    RTGETOPT_REQ_STRING  },
    3999         { "--quiet",                            'q',                                    RTGETOPT_REQ_NOTHING },
    4000         { "--verbose",                          'v',                                    RTGETOPT_REQ_NOTHING },
    4001         { "--diff-ignore-eol",                  SCMOPT_DIFF_IGNORE_EOL,                 RTGETOPT_REQ_NOTHING },
    4002         { "--diff-no-ignore-eol",               SCMOPT_DIFF_NO_IGNORE_EOL,              RTGETOPT_REQ_NOTHING },
    4003         { "--diff-ignore-space",                SCMOPT_DIFF_IGNORE_SPACE,               RTGETOPT_REQ_NOTHING },
    4004         { "--diff-no-ignore-space",             SCMOPT_DIFF_NO_IGNORE_SPACE,            RTGETOPT_REQ_NOTHING },
    4005         { "--diff-ignore-leading-space",        SCMOPT_DIFF_IGNORE_LEADING_SPACE,       RTGETOPT_REQ_NOTHING },
    4006         { "--diff-no-ignore-leading-space",     SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE,    RTGETOPT_REQ_NOTHING },
    4007         { "--diff-ignore-trailing-space",       SCMOPT_DIFF_IGNORE_TRAILING_SPACE,      RTGETOPT_REQ_NOTHING },
    4008         { "--diff-no-ignore-trailing-space",    SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE,   RTGETOPT_REQ_NOTHING },
    4009         { "--diff-special-chars",               SCMOPT_DIFF_SPECIAL_CHARS,              RTGETOPT_REQ_NOTHING },
    4010         { "--diff-no-special-chars",            SCMOPT_DIFF_NO_SPECIAL_CHARS,           RTGETOPT_REQ_NOTHING },
    4011     };
    4012     memcpy(&s_aOpts[RT_ELEMENTS(s_aOpts) - RT_ELEMENTS(g_aScmOpts)], &g_aScmOpts[0], sizeof(g_aScmOpts));
    4013 
    4014     RTGETOPTUNION   ValueUnion;
    4015     RTGETOPTSTATE   GetOptState;
    4016     rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
    4017     AssertReleaseRCReturn(rc, 1);
    4018     size_t          cProcessed = 0;
    4019 
    4020     while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
    4021     {
    4022         switch (rc)
    4023         {
    4024             case 'd':
    4025                 g_fDryRun = true;
    4026                 break;
    4027             case 'D':
    4028                 g_fDryRun = false;
    4029                 break;
    4030 
    4031             case 'f':
    4032                 g_pszFileFilter = ValueUnion.psz;
    4033                 break;
    4034 
    4035             case 'h':
    4036                 RTPrintf("VirtualBox Source Code Massager\n"
    4037                          "\n"
    4038                          "Usage: %s [options] <files & dirs>\n"
    4039                          "\n"
    4040                          "Options:\n", g_szProgName);
    4041                 for (size_t i = 0; i < RT_ELEMENTS(s_aOpts); i++)
    4042                 {
    4043                     bool fAdvanceTwo = false;
    4044                     if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING)
    4045                     {
    4046                         fAdvanceTwo = i + 1 < RT_ELEMENTS(s_aOpts)
    4047                                    && (   strstr(s_aOpts[i+1].pszLong, "-no-") != NULL
    4048                                        || strstr(s_aOpts[i+1].pszLong, "-not-") != NULL
    4049                                        || strstr(s_aOpts[i+1].pszLong, "-dont-") != NULL
    4050                                       );
    4051                         if (fAdvanceTwo)
    4052                             RTPrintf("  %s, %s\n", s_aOpts[i].pszLong, s_aOpts[i + 1].pszLong);
    4053                         else
    4054                             RTPrintf("  %s\n", s_aOpts[i].pszLong);
    4055                     }
    4056                     else if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING)
    4057                         RTPrintf("  %s string\n", s_aOpts[i].pszLong);
    4058                     else
    4059                         RTPrintf("  %s value\n", s_aOpts[i].pszLong);
    4060                     switch (s_aOpts[i].iShort)
    4061                     {
    4062                         case SCMOPT_CONVERT_EOL:            RTPrintf("      Default: %RTbool\n", g_Defaults.fConvertEol); break;
    4063                         case SCMOPT_CONVERT_TABS:           RTPrintf("      Default: %RTbool\n", g_Defaults.fConvertTabs); break;
    4064                         case SCMOPT_FORCE_FINAL_EOL:        RTPrintf("      Default: %RTbool\n", g_Defaults.fForceFinalEol); break;
    4065                         case SCMOPT_FORCE_TRAILING_LINE:    RTPrintf("      Default: %RTbool\n", g_Defaults.fForceTrailingLine); break;
    4066                         case SCMOPT_STRIP_TRAILING_BLANKS:  RTPrintf("      Default: %RTbool\n", g_Defaults.fStripTrailingBlanks); break;
    4067                         case SCMOPT_STRIP_TRAILING_LINES:   RTPrintf("      Default: %RTbool\n", g_Defaults.fStripTrailingLines); break;
    4068                         case SCMOPT_ONLY_SVN_DIRS:          RTPrintf("      Default: %RTbool\n", g_Defaults.fOnlySvnDirs); break;
    4069                         case SCMOPT_ONLY_SVN_FILES:         RTPrintf("      Default: %RTbool\n", g_Defaults.fOnlySvnFiles); break;
    4070                         case SCMOPT_SET_SVN_EOL:            RTPrintf("      Default: %RTbool\n", g_Defaults.fSetSvnEol); break;
    4071                         case SCMOPT_SET_SVN_EXECUTABLE:     RTPrintf("      Default: %RTbool\n", g_Defaults.fSetSvnExecutable); break;
    4072                         case SCMOPT_SET_SVN_KEYWORDS:       RTPrintf("      Default: %RTbool\n", g_Defaults.fSetSvnKeywords); break;
    4073                         case SCMOPT_TAB_SIZE:               RTPrintf("      Default: %u\n", g_Defaults.cchTab); break;
    4074                         case SCMOPT_FILTER_OUT_DIRS:        RTPrintf("      Default: %s\n", g_Defaults.pszFilterOutDirs); break;
    4075                         case SCMOPT_FILTER_FILES:           RTPrintf("      Default: %s\n", g_Defaults.pszFilterFiles); break;
    4076                         case SCMOPT_FILTER_OUT_FILES:       RTPrintf("      Default: %s\n", g_Defaults.pszFilterOutFiles); break;
    4077                     }
    4078                     i += fAdvanceTwo;
    4079                 }
    4080                 return 1;
    4081 
    4082             case 'q':
    4083                 g_iVerbosity = 0;
    4084                 break;
    4085 
    4086             case 'v':
    4087                 g_iVerbosity++;
    4088                 break;
    4089 
    4090             case 'V':
    4091             {
    4092                 /* The following is assuming that svn does it's job here. */
    4093                 static const char s_szRev[] = "$Revision$";
    4094                 const char *psz = RTStrStripL(strchr(s_szRev, ' '));
    4095                 RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);
    4096                 return 0;
    4097             }
    4098 
    4099             case SCMOPT_DIFF_IGNORE_EOL:
    4100                 g_fDiffIgnoreEol = true;
    4101                 break;
    4102             case SCMOPT_DIFF_NO_IGNORE_EOL:
    4103                 g_fDiffIgnoreEol = false;
    4104                 break;
    4105 
    4106             case SCMOPT_DIFF_IGNORE_SPACE:
    4107                 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = true;
    4108                 break;
    4109             case SCMOPT_DIFF_NO_IGNORE_SPACE:
    4110                 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = false;
    4111                 break;
    4112 
    4113             case SCMOPT_DIFF_IGNORE_LEADING_SPACE:
    4114                 g_fDiffIgnoreLeadingWS = true;
    4115                 break;
    4116             case SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE:
    4117                 g_fDiffIgnoreLeadingWS = false;
    4118                 break;
    4119 
    4120             case SCMOPT_DIFF_IGNORE_TRAILING_SPACE:
    4121                 g_fDiffIgnoreTrailingWS = true;
    4122                 break;
    4123             case SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE:
    4124                 g_fDiffIgnoreTrailingWS = false;
    4125                 break;
    4126 
    4127             case SCMOPT_DIFF_SPECIAL_CHARS:
    4128                 g_fDiffSpecialChars = true;
    4129                 break;
    4130             case SCMOPT_DIFF_NO_SPECIAL_CHARS:
    4131                 g_fDiffSpecialChars = false;
    4132                 break;
    4133 
    4134             case VINF_GETOPT_NOT_OPTION:
    4135             {
    4136                 if (!g_fDryRun)
    4137                 {
    4138                     if (!cProcessed)
    4139                     {
    4140                         RTPrintf("%s: Warning! This program will make changes to your source files and\n"
    4141                                  "%s:          there is a slight risk that bugs or a full disk may cause\n"
    4142                                  "%s:          LOSS OF DATA.   So, please make sure you have checked in\n"
    4143                                  "%s:          all your changes already.  If you didn't, then don't blame\n"
    4144                                  "%s:          anyone for not warning you!\n"
    4145                                  "%s:\n"
    4146                                  "%s:          Press any key to continue...\n",
    4147                                  g_szProgName, g_szProgName, g_szProgName, g_szProgName, g_szProgName,
    4148                                  g_szProgName, g_szProgName);
    4149                         RTStrmGetCh(g_pStdIn);
    4150                     }
    4151                     cProcessed++;
    4152                 }
    4153                 rc = scmProcessSomething(ValueUnion.psz, pSettings);
    4154                 if (RT_FAILURE(rc))
    4155                     return rc;
    4156                 break;
    4157             }
    4158 
    4159             default:
    4160             {
    4161                 int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion);
    4162                 if (RT_SUCCESS(rc2))
    4163                     break;
    4164                 if (rc2 != VERR_GETOPT_UNKNOWN_OPTION)
    4165                     return 2;
    4166                 return RTGetOptPrintError(rc, &ValueUnion);
    4167             }
    4168         }
    4169     }
    4170 
    4171     scmSettingsDestroy(pSettings);
    4172     return 0;
    4173 }
    4174 
  • trunk/src/bldprogs/scmstream.h

    r40523 r40528  
    11/* $Id$ */
    22/** @file
    3  * IPRT Testcase / Tool - Source Code Massager.
     3 * IPRT Testcase / Tool - Source Code Massager Stream Code.
    44 */
    55
    66/*
    7  * Copyright (C) 2010 Oracle Corporation
     7 * Copyright (C) 2012 Oracle Corporation
    88 *
    99 * This file is part of VirtualBox Open Source Edition (OSE), as
     
    1616 */
    1717
    18 /*******************************************************************************
    19 *   Header Files                                                               *
    20 *******************************************************************************/
    21 #include <iprt/assert.h>
    22 #include <iprt/ctype.h>
    23 #include <iprt/dir.h>
    24 #include <iprt/env.h>
    25 #include <iprt/file.h>
    26 #include <iprt/err.h>
    27 #include <iprt/getopt.h>
    28 #include <iprt/initterm.h>
    29 #include <iprt/mem.h>
    30 #include <iprt/message.h>
    31 #include <iprt/param.h>
    32 #include <iprt/path.h>
    33 #include <iprt/process.h>
    34 #include <iprt/stream.h>
    35 #include <iprt/string.h>
     18#ifndef ___scmstream_h___
     19#define ___scmstream_h___
    3620
     21#include <iprt/types.h>
    3722
    38 /*******************************************************************************
    39 *   Defined Constants And Macros                                               *
    40 *******************************************************************************/
    41 /** The name of the settings files. */
    42 #define SCM_SETTINGS_FILENAME           ".scm-settings"
    43 
    44 
    45 /*******************************************************************************
    46 *   Structures and Typedefs                                                    *
    47 *******************************************************************************/
    48 /** Pointer to const massager settings. */
    49 typedef struct SCMSETTINGSBASE const *PCSCMSETTINGSBASE;
     23RT_C_DECLS_BEGIN
    5024
    5125/** End of line marker type. */
     
    11589
    11690
    117 /**
    118  * SVN property.
    119  */
    120 typedef struct SCMSVNPROP
    121 {
    122     /** The property. */
    123     char           *pszName;
    124     /** The value.
    125      * When used to record updates, this can be set to NULL to trigger the
    126      * deletion of the property. */
    127     char           *pszValue;
    128 } SCMSVNPROP;
    129 /** Pointer to a SVN property. */
    130 typedef SCMSVNPROP *PSCMSVNPROP;
    131 /** Pointer to a const  SVN property. */
    132 typedef SCMSVNPROP const *PCSCMSVNPROP;
     91int         ScmStreamInitForReading(PSCMSTREAM pStream, const char *pszFilename);
     92int         ScmStreamInitForWriting(PSCMSTREAM pStream, PCSCMSTREAM pRelatedStream);
     93void        ScmStreamDelete(PSCMSTREAM pStream);
     94int         ScmStreamGetStatus(PCSCMSTREAM pStream);
     95void        ScmStreamRewindForReading(PSCMSTREAM pStream);
     96void        ScmStreamRewindForWriting(PSCMSTREAM pStream);
     97bool        ScmStreamIsText(PSCMSTREAM pStream);
     98int         ScmStreamCheckItegrity(PSCMSTREAM pStream);
     99int         ScmStreamWriteToFile(PSCMSTREAM pStream, const char *pszFilenameFmt, ...);
     100size_t      ScmStreamTell(PSCMSTREAM pStream);
     101size_t      ScmStreamTellLine(PSCMSTREAM pStream);
     102size_t      ScmStreamSize(PSCMSTREAM pStream);
     103size_t      ScmStreamCountLines(PSCMSTREAM pStream);
     104int         ScmStreamSeekAbsolute(PSCMSTREAM pStream, size_t offAbsolute);
     105int         ScmStreamSeekRelative(PSCMSTREAM pStream, ssize_t offRelative);
     106int         ScmStreamSeekByLine(PSCMSTREAM pStream, size_t iLine);
     107const char *ScmStreamGetLineByNo(PSCMSTREAM pStream, size_t iLine, size_t *pcchLine, PSCMEOL penmEol);
     108const char *ScmStreamGetLine(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol);
     109int         ScmStreamRead(PSCMSTREAM pStream, void *pvBuf, size_t cbToRead);
     110bool        ScmStreamIsWhiteLine(PSCMSTREAM pStream, size_t iLine);
     111SCMEOL      ScmStreamGetEol(PSCMSTREAM pStream);
     112SCMEOL      ScmStreamGetEolByLine(PSCMSTREAM pStream, size_t iLine);
     113int         ScmStreamPutLine(PSCMSTREAM pStream, const char *pchLine, size_t cchLine, SCMEOL enmEol);
     114int         ScmStreamWrite(PSCMSTREAM pStream, const char *pchBuf, size_t cchBuf);
     115int         ScmStreamPutCh(PSCMSTREAM pStream, char ch);
     116int         ScmStreamCopyLines(PSCMSTREAM pDst, PSCMSTREAM pSrc, size_t cLines);
    133117
     118RT_C_DECLS_END
    134119
    135 /**
    136  * Rewriter state.
    137  */
    138 typedef struct SCMRWSTATE
    139 {
    140     /** The filename.  */
    141     const char     *pszFilename;
    142     /** Set after the printing the first verbose message about a file under
    143      *  rewrite. */
    144     bool            fFirst;
    145     /** The number of SVN property changes. */
    146     size_t          cSvnPropChanges;
    147     /** Pointer to an array of SVN property changes. */
    148     PSCMSVNPROP     paSvnPropChanges;
    149 } SCMRWSTATE;
    150 /** Pointer to the rewriter state. */
    151 typedef SCMRWSTATE *PSCMRWSTATE;
    152 
    153 /**
    154  * A rewriter.
    155  *
    156  * This works like a stream editor, reading @a pIn, modifying it and writing it
    157  * to @a pOut.
    158  *
    159  * @returns true if any changes were made, false if not.
    160  * @param   pIn                 The input stream.
    161  * @param   pOut                The output stream.
    162  * @param   pSettings           The settings.
    163  */
    164 typedef bool (*PFNSCMREWRITER)(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    165 
    166 
    167 /**
    168  * Configuration entry.
    169  */
    170 typedef struct SCMCFGENTRY
    171 {
    172     /** Number of rewriters. */
    173     size_t          cRewriters;
    174     /** Pointer to an array of rewriters. */
    175     PFNSCMREWRITER const  *papfnRewriter;
    176     /** File pattern (simple).  */
    177     const char     *pszFilePattern;
    178 } SCMCFGENTRY;
    179 typedef SCMCFGENTRY *PSCMCFGENTRY;
    180 typedef SCMCFGENTRY const *PCSCMCFGENTRY;
    181 
    182 
    183 /**
    184  * Diff state.
    185  */
    186 typedef struct SCMDIFFSTATE
    187 {
    188     size_t          cDiffs;
    189     const char     *pszFilename;
    190 
    191     PSCMSTREAM      pLeft;
    192     PSCMSTREAM      pRight;
    193 
    194     /** Whether to ignore end of line markers when diffing. */
    195     bool            fIgnoreEol;
    196     /** Whether to ignore trailing whitespace. */
    197     bool            fIgnoreTrailingWhite;
    198     /** Whether to ignore leading whitespace. */
    199     bool            fIgnoreLeadingWhite;
    200     /** Whether to print special characters in human readable form or not. */
    201     bool            fSpecialChars;
    202     /** The tab size. */
    203     size_t          cchTab;
    204     /** Where to push the diff. */
    205     PRTSTREAM       pDiff;
    206 } SCMDIFFSTATE;
    207 /** Pointer to a diff state. */
    208 typedef SCMDIFFSTATE *PSCMDIFFSTATE;
    209 
    210 /**
    211  * Source Code Massager Settings.
    212  */
    213 typedef struct SCMSETTINGSBASE
    214 {
    215     bool            fConvertEol;
    216     bool            fConvertTabs;
    217     bool            fForceFinalEol;
    218     bool            fForceTrailingLine;
    219     bool            fStripTrailingBlanks;
    220     bool            fStripTrailingLines;
    221     /** Only process files that are part of a SVN working copy. */
    222     bool            fOnlySvnFiles;
    223     /** Only recurse into directories containing an .svn dir.  */
    224     bool            fOnlySvnDirs;
    225     /** Set svn:eol-style if missing or incorrect. */
    226     bool            fSetSvnEol;
    227     /** Set svn:executable according to type (unusually this means deleting it). */
    228     bool            fSetSvnExecutable;
    229     /** Set svn:keyword if completely or partially missing. */
    230     bool            fSetSvnKeywords;
    231     /**  */
    232     unsigned        cchTab;
    233     /** Only consider files matching these patterns.  This is only applied to the
    234      *  base names. */
    235     char           *pszFilterFiles;
    236     /** Filter out files matching the following patterns.  This is applied to base
    237      *  names as well as the absolute paths.  */
    238     char           *pszFilterOutFiles;
    239     /** Filter out directories matching the following patterns.  This is applied
    240      *  to base names as well as the absolute paths.  All absolute paths ends with a
    241      *  slash and dot ("/.").  */
    242     char           *pszFilterOutDirs;
    243 } SCMSETTINGSBASE;
    244 /** Pointer to massager settings. */
    245 typedef SCMSETTINGSBASE *PSCMSETTINGSBASE;
    246 
    247 /**
    248  * Option identifiers.
    249  *
    250  * @note    The first chunk, down to SCMOPT_TAB_SIZE, are alternately set &
    251  *          clear.  So, the option setting a flag (boolean) will have an even
    252  *          number and the one clearing it will have an odd number.
    253  * @note    Down to SCMOPT_LAST_SETTINGS corresponds exactly to SCMSETTINGSBASE.
    254  */
    255 typedef enum SCMOPT
    256 {
    257     SCMOPT_CONVERT_EOL = 10000,
    258     SCMOPT_NO_CONVERT_EOL,
    259     SCMOPT_CONVERT_TABS,
    260     SCMOPT_NO_CONVERT_TABS,
    261     SCMOPT_FORCE_FINAL_EOL,
    262     SCMOPT_NO_FORCE_FINAL_EOL,
    263     SCMOPT_FORCE_TRAILING_LINE,
    264     SCMOPT_NO_FORCE_TRAILING_LINE,
    265     SCMOPT_STRIP_TRAILING_BLANKS,
    266     SCMOPT_NO_STRIP_TRAILING_BLANKS,
    267     SCMOPT_STRIP_TRAILING_LINES,
    268     SCMOPT_NO_STRIP_TRAILING_LINES,
    269     SCMOPT_ONLY_SVN_DIRS,
    270     SCMOPT_NOT_ONLY_SVN_DIRS,
    271     SCMOPT_ONLY_SVN_FILES,
    272     SCMOPT_NOT_ONLY_SVN_FILES,
    273     SCMOPT_SET_SVN_EOL,
    274     SCMOPT_DONT_SET_SVN_EOL,
    275     SCMOPT_SET_SVN_EXECUTABLE,
    276     SCMOPT_DONT_SET_SVN_EXECUTABLE,
    277     SCMOPT_SET_SVN_KEYWORDS,
    278     SCMOPT_DONT_SET_SVN_KEYWORDS,
    279     SCMOPT_TAB_SIZE,
    280     SCMOPT_FILTER_OUT_DIRS,
    281     SCMOPT_FILTER_FILES,
    282     SCMOPT_FILTER_OUT_FILES,
    283     SCMOPT_LAST_SETTINGS = SCMOPT_FILTER_OUT_FILES,
    284     //
    285     SCMOPT_DIFF_IGNORE_EOL,
    286     SCMOPT_DIFF_NO_IGNORE_EOL,
    287     SCMOPT_DIFF_IGNORE_SPACE,
    288     SCMOPT_DIFF_NO_IGNORE_SPACE,
    289     SCMOPT_DIFF_IGNORE_LEADING_SPACE,
    290     SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE,
    291     SCMOPT_DIFF_IGNORE_TRAILING_SPACE,
    292     SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE,
    293     SCMOPT_DIFF_SPECIAL_CHARS,
    294     SCMOPT_DIFF_NO_SPECIAL_CHARS,
    295     SCMOPT_END
    296 } SCMOPT;
    297 
    298 
    299 /**
    300  * File/dir pattern + options.
    301  */
    302 typedef struct SCMPATRNOPTPAIR
    303 {
    304     char *pszPattern;
    305     char *pszOptions;
    306 } SCMPATRNOPTPAIR;
    307 /** Pointer to a pattern + option pair. */
    308 typedef SCMPATRNOPTPAIR *PSCMPATRNOPTPAIR;
    309 
    310 
    311 /** Pointer to a settings set. */
    312 typedef struct SCMSETTINGS *PSCMSETTINGS;
    313 /**
    314  * Settings set.
    315  *
    316  * This structure is constructed from the command line arguments or any
    317  * .scm-settings file found in a directory we recurse into.  When recursing in
    318  * and out of a directory, we push and pop a settings set for it.
    319  *
    320  * The .scm-settings file has two kinds of setttings, first there are the
    321  * unqualified base settings and then there are the settings which applies to a
    322  * set of files or directories.  The former are lines with command line options.
    323  * For the latter, the options are preceded by a string pattern and a colon.
    324  * The pattern specifies which files (and/or directories) the options applies
    325  * to.
    326  *
    327  * We parse the base options into the Base member and put the others into the
    328  * paPairs array.
    329  */
    330 typedef struct SCMSETTINGS
    331 {
    332     /** Pointer to the setting file below us in the stack. */
    333     PSCMSETTINGS        pDown;
    334     /** Pointer to the setting file above us in the stack. */
    335     PSCMSETTINGS        pUp;
    336     /** File/dir patterns and their options. */
    337     PSCMPATRNOPTPAIR    paPairs;
    338     /** The number of entires in paPairs. */
    339     uint32_t            cPairs;
    340     /** The base settings that was read out of the file. */
    341     SCMSETTINGSBASE     Base;
    342 } SCMSETTINGS;
    343 /** Pointer to a const settings set. */
    344 typedef SCMSETTINGS const *PCSCMSETTINGS;
    345 
    346 
    347 /*******************************************************************************
    348 *   Internal Functions                                                         *
    349 *******************************************************************************/
    350 static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    351 static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    352 static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    353 static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    354 static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    355 static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    356 static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    357 static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    358 static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    359 static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    360 static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
    361 
    362 
    363 /*******************************************************************************
    364 *   Global Variables                                                           *
    365 *******************************************************************************/
    366 static const char   g_szProgName[]          = "scm";
    367 static const char  *g_pszChangedSuff        = "";
    368 static const char   g_szTabSpaces[16+1]     = "                ";
    369 static bool         g_fDryRun               = true;
    370 static bool         g_fDiffSpecialChars     = true;
    371 static bool         g_fDiffIgnoreEol        = false;
    372 static bool         g_fDiffIgnoreLeadingWS  = false;
    373 static bool         g_fDiffIgnoreTrailingWS = false;
    374 static int          g_iVerbosity            = 2;//99; //0;
    375 
    376 /** The global settings. */
    377 static SCMSETTINGSBASE const g_Defaults =
    378 {
    379     /* .fConvertEol = */            true,
    380     /* .fConvertTabs = */           true,
    381     /* .fForceFinalEol = */         true,
    382     /* .fForceTrailingLine = */     false,
    383     /* .fStripTrailingBlanks = */   true,
    384     /* .fStripTrailingLines = */    true,
    385     /* .fOnlySvnFiles = */          false,
    386     /* .fOnlySvnDirs = */           false,
    387     /* .fSetSvnEol = */             false,
    388     /* .fSetSvnExecutable = */      false,
    389     /* .fSetSvnKeywords = */        false,
    390     /* .cchTab = */                 8,
    391     /* .pszFilterFiles = */         (char *)"",
    392     /* .pszFilterOutFiles = */      (char *)"*.exe|*.com|20*-*-*.log",
    393     /* .pszFilterOutDirs = */       (char *)".svn|.hg|.git|CVS",
    394 };
    395 
    396 /** Option definitions for the base settings. */
    397 static RTGETOPTDEF  g_aScmOpts[] =
    398 {
    399     { "--convert-eol",                      SCMOPT_CONVERT_EOL,                     RTGETOPT_REQ_NOTHING },
    400     { "--no-convert-eol",                   SCMOPT_NO_CONVERT_EOL,                  RTGETOPT_REQ_NOTHING },
    401     { "--convert-tabs",                     SCMOPT_CONVERT_TABS,                    RTGETOPT_REQ_NOTHING },
    402     { "--no-convert-tabs",                  SCMOPT_NO_CONVERT_TABS,                 RTGETOPT_REQ_NOTHING },
    403     { "--force-final-eol",                  SCMOPT_FORCE_FINAL_EOL,                 RTGETOPT_REQ_NOTHING },
    404     { "--no-force-final-eol",               SCMOPT_NO_FORCE_FINAL_EOL,              RTGETOPT_REQ_NOTHING },
    405     { "--force-trailing-line",              SCMOPT_FORCE_TRAILING_LINE,             RTGETOPT_REQ_NOTHING },
    406     { "--no-force-trailing-line",           SCMOPT_NO_FORCE_TRAILING_LINE,          RTGETOPT_REQ_NOTHING },
    407     { "--strip-trailing-blanks",            SCMOPT_STRIP_TRAILING_BLANKS,           RTGETOPT_REQ_NOTHING },
    408     { "--no-strip-trailing-blanks",         SCMOPT_NO_STRIP_TRAILING_BLANKS,        RTGETOPT_REQ_NOTHING },
    409     { "--strip-trailing-lines",             SCMOPT_STRIP_TRAILING_LINES,            RTGETOPT_REQ_NOTHING },
    410     { "--strip-no-trailing-lines",          SCMOPT_NO_STRIP_TRAILING_LINES,         RTGETOPT_REQ_NOTHING },
    411     { "--only-svn-dirs",                    SCMOPT_ONLY_SVN_DIRS,                   RTGETOPT_REQ_NOTHING },
    412     { "--not-only-svn-dirs",                SCMOPT_NOT_ONLY_SVN_DIRS,               RTGETOPT_REQ_NOTHING },
    413     { "--only-svn-files",                   SCMOPT_ONLY_SVN_FILES,                  RTGETOPT_REQ_NOTHING },
    414     { "--not-only-svn-files",               SCMOPT_NOT_ONLY_SVN_FILES,              RTGETOPT_REQ_NOTHING },
    415     { "--set-svn-eol",                      SCMOPT_SET_SVN_EOL,                     RTGETOPT_REQ_NOTHING },
    416     { "--dont-set-svn-eol",                 SCMOPT_DONT_SET_SVN_EOL,                RTGETOPT_REQ_NOTHING },
    417     { "--set-svn-executable",               SCMOPT_SET_SVN_EXECUTABLE,              RTGETOPT_REQ_NOTHING },
    418     { "--dont-set-svn-executable",          SCMOPT_DONT_SET_SVN_EXECUTABLE,         RTGETOPT_REQ_NOTHING },
    419     { "--set-svn-keywords",                 SCMOPT_SET_SVN_KEYWORDS,                RTGETOPT_REQ_NOTHING },
    420     { "--dont-set-svn-keywords",            SCMOPT_DONT_SET_SVN_KEYWORDS,           RTGETOPT_REQ_NOTHING },
    421     { "--tab-size",                         SCMOPT_TAB_SIZE,                        RTGETOPT_REQ_UINT8   },
    422     { "--filter-out-dirs",                  SCMOPT_FILTER_OUT_DIRS,                 RTGETOPT_REQ_STRING  },
    423     { "--filter-files",                     SCMOPT_FILTER_FILES,                    RTGETOPT_REQ_STRING  },
    424     { "--filter-out-files",                 SCMOPT_FILTER_OUT_FILES,                RTGETOPT_REQ_STRING  },
    425 };
    426 
    427 /** Consider files matching the following patterns (base names only). */
    428 static const char  *g_pszFileFilter         = NULL;
    429 
    430 static PFNSCMREWRITER const g_aRewritersFor_Makefile_kup[] =
    431 {
    432     rewrite_SvnNoExecutable,
    433     rewrite_Makefile_kup
    434 };
    435 
    436 static PFNSCMREWRITER const g_aRewritersFor_Makefile_kmk[] =
    437 {
    438     rewrite_ForceNativeEol,
    439     rewrite_StripTrailingBlanks,
    440     rewrite_AdjustTrailingLines,
    441     rewrite_SvnNoExecutable,
    442     rewrite_SvnKeywords,
    443     rewrite_Makefile_kmk
    444 };
    445 
    446 static PFNSCMREWRITER const g_aRewritersFor_C_and_CPP[] =
    447 {
    448     rewrite_ForceNativeEol,
    449     rewrite_ExpandTabs,
    450     rewrite_StripTrailingBlanks,
    451     rewrite_AdjustTrailingLines,
    452     rewrite_SvnNoExecutable,
    453     rewrite_SvnKeywords,
    454     rewrite_C_and_CPP
    455 };
    456 
    457 static PFNSCMREWRITER const g_aRewritersFor_H_and_HPP[] =
    458 {
    459     rewrite_ForceNativeEol,
    460     rewrite_ExpandTabs,
    461     rewrite_StripTrailingBlanks,
    462     rewrite_AdjustTrailingLines,
    463     rewrite_SvnNoExecutable,
    464     rewrite_C_and_CPP
    465 };
    466 
    467 static PFNSCMREWRITER const g_aRewritersFor_RC[] =
    468 {
    469     rewrite_ForceNativeEol,
    470     rewrite_ExpandTabs,
    471     rewrite_StripTrailingBlanks,
    472     rewrite_AdjustTrailingLines,
    473     rewrite_SvnNoExecutable,
    474     rewrite_SvnKeywords
    475 };
    476 
    477 static PFNSCMREWRITER const g_aRewritersFor_ShellScripts[] =
    478 {
    479     rewrite_ForceLF,
    480     rewrite_ExpandTabs,
    481     rewrite_StripTrailingBlanks
    482 };
    483 
    484 static PFNSCMREWRITER const g_aRewritersFor_BatchFiles[] =
    485 {
    486     rewrite_ForceCRLF,
    487     rewrite_ExpandTabs,
    488     rewrite_StripTrailingBlanks
    489 };
    490 
    491 static SCMCFGENTRY const g_aConfigs[] =
    492 {
    493     { RT_ELEMENTS(g_aRewritersFor_Makefile_kup), &g_aRewritersFor_Makefile_kup[0], "Makefile.kup" },
    494     { RT_ELEMENTS(g_aRewritersFor_Makefile_kmk), &g_aRewritersFor_Makefile_kmk[0], "Makefile.kmk|Config.kmk" },
    495     { RT_ELEMENTS(g_aRewritersFor_C_and_CPP),    &g_aRewritersFor_C_and_CPP[0],    "*.c|*.cpp|*.C|*.CPP|*.cxx|*.cc" },
    496     { RT_ELEMENTS(g_aRewritersFor_H_and_HPP),    &g_aRewritersFor_H_and_HPP[0],    "*.h|*.hpp" },
    497     { RT_ELEMENTS(g_aRewritersFor_RC),           &g_aRewritersFor_RC[0],           "*.rc" },
    498     { RT_ELEMENTS(g_aRewritersFor_ShellScripts), &g_aRewritersFor_ShellScripts[0], "*.sh|configure" },
    499     { RT_ELEMENTS(g_aRewritersFor_BatchFiles),   &g_aRewritersFor_BatchFiles[0],   "*.bat|*.cmd|*.btm|*.vbs|*.ps1" },
    500 };
    501 
    502 
    503 /* -=-=-=-=-=- memory streams -=-=-=-=-=- */
    504 
    505 
    506 /**
    507  * Initializes the stream structure.
    508  *
    509  * @param   pStream             The stream structure.
    510  * @param   fWriteOrRead        The value of the fWriteOrRead stream member.
    511  */
    512 static void scmStreamInitInternal(PSCMSTREAM pStream, bool fWriteOrRead)
    513 {
    514     pStream->pch                = NULL;
    515     pStream->off                = 0;
    516     pStream->cb                 = 0;
    517     pStream->cbAllocated        = 0;
    518 
    519     pStream->paLines            = NULL;
    520     pStream->iLine              = 0;
    521     pStream->cLines             = 0;
    522     pStream->cLinesAllocated    = 0;
    523 
    524     pStream->fWriteOrRead       = fWriteOrRead;
    525     pStream->fFileMemory        = false;
    526     pStream->fFullyLineated     = false;
    527 
    528     pStream->rc                 = VINF_SUCCESS;
    529 }
    530 
    531 /**
    532  * Initialize an input stream.
    533  *
    534  * @returns IPRT status code.
    535  * @param   pStream             The stream to initialize.
    536  * @param   pszFilename         The file to take the stream content from.
    537  */
    538 int ScmStreamInitForReading(PSCMSTREAM pStream, const char *pszFilename)
    539 {
    540     scmStreamInitInternal(pStream, false /*fWriteOrRead*/);
    541 
    542     void *pvFile;
    543     size_t cbFile;
    544     int rc = pStream->rc = RTFileReadAll(pszFilename, &pvFile, &cbFile);
    545     if (RT_SUCCESS(rc))
    546     {
    547         pStream->pch            = (char *)pvFile;
    548         pStream->cb             = cbFile;
    549         pStream->cbAllocated    = cbFile;
    550         pStream->fFileMemory    = true;
    551     }
    552     return rc;
    553 }
    554 
    555 /**
    556  * Initialize an output stream.
    557  *
    558  * @returns IPRT status code
    559  * @param   pStream             The stream to initialize.
    560  * @param   pRelatedStream      Pointer to a related stream.  NULL is fine.
    561  */
    562 int ScmStreamInitForWriting(PSCMSTREAM pStream, PCSCMSTREAM pRelatedStream)
    563 {
    564     scmStreamInitInternal(pStream, true /*fWriteOrRead*/);
    565 
    566     /* allocate stuff */
    567     size_t cbEstimate = pRelatedStream
    568                       ? pRelatedStream->cb + pRelatedStream->cb / 10
    569                       : _64K;
    570     cbEstimate = RT_ALIGN(cbEstimate, _4K);
    571     pStream->pch = (char *)RTMemAlloc(cbEstimate);
    572     if (pStream->pch)
    573     {
    574         size_t cLinesEstimate = pRelatedStream && pRelatedStream->fFullyLineated
    575                               ? pRelatedStream->cLines + pRelatedStream->cLines / 10
    576                               : cbEstimate / 24;
    577         cLinesEstimate = RT_ALIGN(cLinesEstimate, 512);
    578         pStream->paLines = (PSCMSTREAMLINE)RTMemAlloc(cLinesEstimate * sizeof(SCMSTREAMLINE));
    579         if (pStream->paLines)
    580         {
    581             pStream->paLines[0].off     = 0;
    582             pStream->paLines[0].cch     = 0;
    583             pStream->paLines[0].enmEol  = SCMEOL_NONE;
    584             pStream->cbAllocated        = cbEstimate;
    585             pStream->cLinesAllocated    = cLinesEstimate;
    586             return VINF_SUCCESS;
    587         }
    588 
    589         RTMemFree(pStream->pch);
    590         pStream->pch = NULL;
    591     }
    592     return pStream->rc = VERR_NO_MEMORY;
    593 }
    594 
    595 /**
    596  * Frees the resources associated with the stream.
    597  *
    598  * Nothing is happens to whatever the stream was initialized from or dumped to.
    599  *
    600  * @param   pStream             The stream to delete.
    601  */
    602 void ScmStreamDelete(PSCMSTREAM pStream)
    603 {
    604     if (pStream->pch)
    605     {
    606         if (pStream->fFileMemory)
    607             RTFileReadAllFree(pStream->pch, pStream->cbAllocated);
    608         else
    609             RTMemFree(pStream->pch);
    610         pStream->pch = NULL;
    611     }
    612     pStream->cbAllocated = 0;
    613 
    614     if (pStream->paLines)
    615     {
    616         RTMemFree(pStream->paLines);
    617         pStream->paLines = NULL;
    618     }
    619     pStream->cLinesAllocated = 0;
    620 }
    621 
    622 /**
    623  * Get the stream status code.
    624  *
    625  * @returns IPRT status code.
    626  * @param   pStream             The stream.
    627  */
    628 int ScmStreamGetStatus(PCSCMSTREAM pStream)
    629 {
    630     return pStream->rc;
    631 }
    632 
    633 /**
    634  * Grows the buffer of a write stream.
    635  *
    636  * @returns IPRT status code.
    637  * @param   pStream             The stream.  Must be in write mode.
    638  * @param   cbAppending         The minimum number of bytes to grow the buffer
    639  *                              with.
    640  */
    641 static int scmStreamGrowBuffer(PSCMSTREAM pStream, size_t cbAppending)
    642 {
    643     size_t cbAllocated = pStream->cbAllocated;
    644     cbAllocated += RT_MAX(0x1000 + cbAppending, cbAllocated);
    645     cbAllocated = RT_ALIGN(cbAllocated, 0x1000);
    646     void *pvNew;
    647     if (!pStream->fFileMemory)
    648     {
    649         pvNew = RTMemRealloc(pStream->pch, cbAllocated);
    650         if (!pvNew)
    651             return pStream->rc = VERR_NO_MEMORY;
    652     }
    653     else
    654     {
    655         pvNew = RTMemDupEx(pStream->pch, pStream->off, cbAllocated - pStream->off);
    656         if (!pvNew)
    657             return pStream->rc = VERR_NO_MEMORY;
    658         RTFileReadAllFree(pStream->pch, pStream->cbAllocated);
    659         pStream->fFileMemory = false;
    660     }
    661     pStream->pch = (char *)pvNew;
    662     pStream->cbAllocated = cbAllocated;
    663 
    664     return VINF_SUCCESS;
    665 }
    666 
    667 /**
    668  * Grows the line array of a stream.
    669  *
    670  * @returns IPRT status code.
    671  * @param   pStream             The stream.
    672  * @param   iMinLine            Minimum line number.
    673  */
    674 static int scmStreamGrowLines(PSCMSTREAM pStream, size_t iMinLine)
    675 {
    676     size_t cLinesAllocated = pStream->cLinesAllocated;
    677     cLinesAllocated += RT_MAX(512 + iMinLine, cLinesAllocated);
    678     cLinesAllocated = RT_ALIGN(cLinesAllocated, 512);
    679     void *pvNew = RTMemRealloc(pStream->paLines, cLinesAllocated * sizeof(SCMSTREAMLINE));
    680     if (!pvNew)
    681         return pStream->rc = VERR_NO_MEMORY;
    682 
    683     pStream->paLines = (PSCMSTREAMLINE)pvNew;
    684     pStream->cLinesAllocated = cLinesAllocated;
    685     return VINF_SUCCESS;
    686 }
    687 
    688 /**
    689  * Rewinds the stream and sets the mode to read.
    690  *
    691  * @param   pStream             The stream.
    692  */
    693 void ScmStreamRewindForReading(PSCMSTREAM pStream)
    694 {
    695     pStream->off          = 0;
    696     pStream->iLine        = 0;
    697     pStream->fWriteOrRead = false;
    698     pStream->rc           = VINF_SUCCESS;
    699 }
    700 
    701 /**
    702  * Rewinds the stream and sets the mode to write.
    703  *
    704  * @param   pStream             The stream.
    705  */
    706 void ScmStreamRewindForWriting(PSCMSTREAM pStream)
    707 {
    708     pStream->off            = 0;
    709     pStream->iLine          = 0;
    710     pStream->cLines         = 0;
    711     pStream->fWriteOrRead   = true;
    712     pStream->fFullyLineated = true;
    713     pStream->rc             = VINF_SUCCESS;
    714 }
    715 
    716 /**
    717  * Checks if it's a text stream.
    718  *
    719  * Not 100% proof.
    720  *
    721  * @returns true if it probably is a text file, false if not.
    722  * @param   pStream             The stream. Write or read, doesn't matter.
    723  */
    724 bool ScmStreamIsText(PSCMSTREAM pStream)
    725 {
    726     if (RTStrEnd(pStream->pch, pStream->cb))
    727         return false;
    728     if (!pStream->cb)
    729         return false;
    730     return true;
    731 }
    732 
    733 /**
    734  * Performs an integrity check of the stream.
    735  *
    736  * @returns IPRT status code.
    737  * @param   pStream             The stream.
    738  */
    739 int ScmStreamCheckItegrity(PSCMSTREAM pStream)
    740 {
    741     /*
    742      * Perform sanity checks.
    743      */
    744     size_t const cbFile = pStream->cb;
    745     for (size_t iLine = 0; iLine < pStream->cLines; iLine++)
    746     {
    747         size_t offEol = pStream->paLines[iLine].off + pStream->paLines[iLine].cch;
    748         AssertReturn(offEol + pStream->paLines[iLine].enmEol <= cbFile, VERR_INTERNAL_ERROR_2);
    749         switch (pStream->paLines[iLine].enmEol)
    750         {
    751             case SCMEOL_LF:
    752                 AssertReturn(pStream->pch[offEol] == '\n', VERR_INTERNAL_ERROR_3);
    753                 break;
    754             case SCMEOL_CRLF:
    755                 AssertReturn(pStream->pch[offEol] == '\r', VERR_INTERNAL_ERROR_3);
    756                 AssertReturn(pStream->pch[offEol + 1] == '\n', VERR_INTERNAL_ERROR_3);
    757                 break;
    758             case SCMEOL_NONE:
    759                 AssertReturn(iLine + 1 >= pStream->cLines, VERR_INTERNAL_ERROR_4);
    760                 break;
    761             default:
    762                 AssertReturn(iLine + 1 >= pStream->cLines, VERR_INTERNAL_ERROR_5);
    763         }
    764     }
    765     return VINF_SUCCESS;
    766 }
    767 
    768 /**
    769  * Writes the stream to a file.
    770  *
    771  * @returns IPRT status code
    772  * @param   pStream             The stream.
    773  * @param   pszFilenameFmt      The filename format string.
    774  * @param   ...                 Format arguments.
    775  */
    776 int ScmStreamWriteToFile(PSCMSTREAM pStream, const char *pszFilenameFmt, ...)
    777 {
    778     int rc;
    779 
    780 #ifdef RT_STRICT
    781     /*
    782      * Check that what we're going to write makes sense first.
    783      */
    784     rc = ScmStreamCheckItegrity(pStream);
    785     if (RT_FAILURE(rc))
    786         return rc;
    787120#endif
    788121
    789     /*
    790      * Do the actual writing.
    791      */
    792     RTFILE hFile;
    793     va_list va;
    794     va_start(va, pszFilenameFmt);
    795     rc = RTFileOpenV(&hFile, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_WRITE, pszFilenameFmt, va);
    796     if (RT_SUCCESS(rc))
    797     {
    798         rc = RTFileWrite(hFile, pStream->pch, pStream->cb, NULL);
    799         RTFileClose(hFile);
    800     }
    801     return rc;
    802 }
    803 
    804 /**
    805  * Worker for ScmStreamGetLine that builds the line number index while parsing
    806  * the stream.
    807  *
    808  * @returns Same as SCMStreamGetLine.
    809  * @param   pStream             The stream.  Must be in read mode.
    810  * @param   pcchLine            Where to return the line length.
    811  * @param   penmEol             Where to return the kind of end of line marker.
    812  */
    813 static const char *scmStreamGetLineInternal(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol)
    814 {
    815     AssertReturn(!pStream->fWriteOrRead, NULL);
    816     if (RT_FAILURE(pStream->rc))
    817         return NULL;
    818 
    819     size_t off = pStream->off;
    820     size_t cb  = pStream->cb;
    821     if (RT_UNLIKELY(off >= cb))
    822     {
    823         pStream->fFullyLineated = true;
    824         return NULL;
    825     }
    826 
    827     size_t iLine = pStream->iLine;
    828     if (RT_UNLIKELY(iLine >= pStream->cLinesAllocated))
    829     {
    830         int rc = scmStreamGrowLines(pStream, iLine);
    831         if (RT_FAILURE(rc))
    832             return NULL;
    833     }
    834     pStream->paLines[iLine].off = off;
    835 
    836     cb -= off;
    837     const char *pchRet = &pStream->pch[off];
    838     const char *pch = (const char *)memchr(pchRet, '\n', cb);
    839     if (RT_LIKELY(pch))
    840     {
    841         cb = pch - pchRet;
    842         pStream->off = off + cb + 1;
    843         if (   cb < 1
    844             || pch[-1] != '\r')
    845             pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_LF;
    846         else
    847         {
    848             pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_CRLF;
    849             cb--;
    850         }
    851     }
    852     else
    853     {
    854         pStream->off = off + cb;
    855         pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_NONE;
    856     }
    857     *pcchLine = cb;
    858     pStream->paLines[iLine].cch = cb;
    859     pStream->cLines = pStream->iLine = ++iLine;
    860 
    861     return pchRet;
    862 }
    863 
    864 /**
    865  * Internal worker that delineates a stream.
    866  *
    867  * @returns IPRT status code.
    868  * @param   pStream             The stream.  Caller must check that it is in
    869  *                              read mode.
    870  */
    871 static int scmStreamLineate(PSCMSTREAM pStream)
    872 {
    873     /* Save the stream position. */
    874     size_t const offSaved   = pStream->off;
    875     size_t const iLineSaved = pStream->iLine;
    876 
    877     /* Get each line. */
    878     size_t cchLine;
    879     SCMEOL enmEol;
    880     while (scmStreamGetLineInternal(pStream, &cchLine, &enmEol))
    881         /* nothing */;
    882     Assert(RT_FAILURE(pStream->rc) || pStream->fFullyLineated);
    883 
    884     /* Restore the position */
    885     pStream->off   = offSaved;
    886     pStream->iLine = iLineSaved;
    887 
    888     return pStream->rc;
    889 }
    890 
    891 /**
    892  * Get the current stream position as an byte offset.
    893  *
    894  * @returns The current byte offset
    895  * @param   pStream             The stream.
    896  */
    897 size_t ScmStreamTell(PSCMSTREAM pStream)
    898 {
    899     return pStream->off;
    900 }
    901 
    902 /**
    903  * Get the current stream position as a line number.
    904  *
    905  * @returns The current line (0-based).
    906  * @param   pStream             The stream.
    907  */
    908 size_t ScmStreamTellLine(PSCMSTREAM pStream)
    909 {
    910     return pStream->iLine;
    911 }
    912 
    913 /**
    914  * Get the current stream size in bytes.
    915  *
    916  * @returns Count of bytes.
    917  * @param   pStream             The stream.
    918  */
    919 size_t ScmStreamSize(PSCMSTREAM pStream)
    920 {
    921     return pStream->cb;
    922 }
    923 
    924 /**
    925  * Gets the number of lines in the stream.
    926  *
    927  * @returns The number of lines.
    928  * @param   pStream             The stream.
    929  */
    930 size_t ScmStreamCountLines(PSCMSTREAM pStream)
    931 {
    932     if (!pStream->fFullyLineated)
    933         scmStreamLineate(pStream);
    934     return pStream->cLines;
    935 }
    936 
    937 /**
    938  * Seeks to a given byte offset in the stream.
    939  *
    940  * @returns IPRT status code.
    941  * @retval  VERR_SEEK if the new stream position is the middle of an EOL marker.
    942  *          This is a temporary restriction.
    943  *
    944  * @param   pStream             The stream.  Must be in read mode.
    945  * @param   offAbsolute         The offset to seek to.  If this is beyond the
    946  *                              end of the stream, the position is set to the
    947  *                              end.
    948  */
    949 int ScmStreamSeekAbsolute(PSCMSTREAM pStream, size_t offAbsolute)
    950 {
    951     AssertReturn(!pStream->fWriteOrRead, VERR_ACCESS_DENIED);
    952     if (RT_FAILURE(pStream->rc))
    953         return pStream->rc;
    954 
    955     /* Must be fully delineated. (lazy bird) */
    956     if (RT_UNLIKELY(!pStream->fFullyLineated))
    957     {
    958         int rc = scmStreamLineate(pStream);
    959         if (RT_FAILURE(rc))
    960             return rc;
    961     }
    962 
    963     /* Ok, do the job. */
    964     if (offAbsolute < pStream->cb)
    965     {
    966         /** @todo Should do a binary search here, but I'm too darn lazy tonight. */
    967         pStream->off = ~(size_t)0;
    968         for (size_t i = 0; i < pStream->cLines; i++)
    969         {
    970             if (offAbsolute < pStream->paLines[i].off + pStream->paLines[i].cch + pStream->paLines[i].enmEol)
    971             {
    972                 pStream->off   = offAbsolute;
    973                 pStream->iLine = i;
    974                 if (offAbsolute > pStream->paLines[i].off + pStream->paLines[i].cch)
    975                     return pStream->rc = VERR_SEEK;
    976                 break;
    977             }
    978         }
    979         AssertReturn(pStream->off != ~(size_t)0, pStream->rc = VERR_INTERNAL_ERROR_3);
    980     }
    981     else
    982     {
    983         pStream->off   = pStream->cb;
    984         pStream->iLine = pStream->cLines;
    985     }
    986     return VINF_SUCCESS;
    987 }
    988 
    989 
    990 /**
    991  * Seeks a number of bytes relative to the current stream position.
    992  *
    993  * @returns IPRT status code.
    994  * @retval  VERR_SEEK if the new stream position is the middle of an EOL marker.
    995  *          This is a temporary restriction.
    996  *
    997  * @param   pStream             The stream.  Must be in read mode.
    998  * @param   offRelative         The offset to seek to.  A negative offset
    999  *                              rewinds and positive one fast forwards the
    1000  *                              stream.  Will quietly stop at the beginning and
    1001  *                              end of the stream.
    1002  */
    1003 int ScmStreamSeekRelative(PSCMSTREAM pStream, ssize_t offRelative)
    1004 {
    1005     size_t offAbsolute;
    1006     if (offRelative >= 0)
    1007         offAbsolute = pStream->off + offRelative;
    1008     else if ((size_t)-offRelative <= pStream->off)
    1009         offAbsolute = pStream->off + offRelative;
    1010     else
    1011         offAbsolute = 0;
    1012     return ScmStreamSeekAbsolute(pStream, offAbsolute);
    1013 }
    1014 
    1015 /**
    1016  * Seeks to a given line in the stream.
    1017  *
    1018  * @returns IPRT status code.
    1019  *
    1020  * @param   pStream             The stream.  Must be in read mode.
    1021  * @param   iLine               The line to seek to.  If this is beyond the end
    1022  *                              of the stream, the position is set to the end.
    1023  */
    1024 int ScmStreamSeekByLine(PSCMSTREAM pStream, size_t iLine)
    1025 {
    1026     AssertReturn(!pStream->fWriteOrRead, VERR_ACCESS_DENIED);
    1027     if (RT_FAILURE(pStream->rc))
    1028         return pStream->rc;
    1029 
    1030     /* Must be fully delineated. (lazy bird) */
    1031     if (RT_UNLIKELY(!pStream->fFullyLineated))
    1032     {
    1033         int rc = scmStreamLineate(pStream);
    1034         if (RT_FAILURE(rc))
    1035             return rc;
    1036     }
    1037 
    1038     /* Ok, do the job. */
    1039     if (iLine < pStream->cLines)
    1040     {
    1041         pStream->off   = pStream->paLines[iLine].off;
    1042         pStream->iLine = iLine;
    1043     }
    1044     else
    1045     {
    1046         pStream->off   = pStream->cb;
    1047         pStream->iLine = pStream->cLines;
    1048     }
    1049     return VINF_SUCCESS;
    1050 }
    1051 
    1052 /**
    1053  * Get a numbered line from the stream (changes the position).
    1054  *
    1055  * A line is always delimited by a LF character or the end of the stream.  The
    1056  * delimiter is not included in returned line length, but instead returned via
    1057  * the @a penmEol indicator.
    1058  *
    1059  * @returns Pointer to the first character in the line, not NULL terminated.
    1060  *          NULL if the end of the stream has been reached or some problem
    1061  *          occurred.
    1062  *
    1063  * @param   pStream             The stream.  Must be in read mode.
    1064  * @param   iLine               The line to get (0-based).
    1065  * @param   pcchLine            The length.
    1066  * @param   penmEol             Where to return the end of line type indicator.
    1067  */
    1068 static const char *ScmStreamGetLineByNo(PSCMSTREAM pStream, size_t iLine, size_t *pcchLine, PSCMEOL penmEol)
    1069 {
    1070     AssertReturn(!pStream->fWriteOrRead, NULL);
    1071     if (RT_FAILURE(pStream->rc))
    1072         return NULL;
    1073 
    1074     /* Make sure it's fully delineated so we can use the index. */
    1075     if (RT_UNLIKELY(!pStream->fFullyLineated))
    1076     {
    1077         int rc = scmStreamLineate(pStream);
    1078         if (RT_FAILURE(rc))
    1079             return NULL;
    1080     }
    1081 
    1082     /* End of stream? */
    1083     if (RT_UNLIKELY(iLine >= pStream->cLines))
    1084     {
    1085         pStream->off   = pStream->cb;
    1086         pStream->iLine = pStream->cLines;
    1087         return NULL;
    1088     }
    1089 
    1090     /* Get the data. */
    1091     const char *pchRet = &pStream->pch[pStream->paLines[iLine].off];
    1092     *pcchLine          = pStream->paLines[iLine].cch;
    1093     *penmEol           = pStream->paLines[iLine].enmEol;
    1094 
    1095     /* update the stream position. */
    1096     pStream->off       = pStream->paLines[iLine].off + pStream->paLines[iLine].cch + pStream->paLines[iLine].enmEol;
    1097     pStream->iLine     = iLine + 1;
    1098 
    1099     return pchRet;
    1100 }
    1101 
    1102 /**
    1103  * Get a line from the stream.
    1104  *
    1105  * A line is always delimited by a LF character or the end of the stream.  The
    1106  * delimiter is not included in returned line length, but instead returned via
    1107  * the @a penmEol indicator.
    1108  *
    1109  * @returns Pointer to the first character in the line, not NULL terminated.
    1110  *          NULL if the end of the stream has been reached or some problem
    1111  *          occurred.
    1112  *
    1113  * @param   pStream             The stream.  Must be in read mode.
    1114  * @param   pcchLine            The length.
    1115  * @param   penmEol             Where to return the end of line type indicator.
    1116  */
    1117 static const char *ScmStreamGetLine(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol)
    1118 {
    1119     /** @todo this doesn't work when pStream->off !=
    1120      *        pStream->paLines[pStream->iLine-1].pff. */
    1121     if (!pStream->fFullyLineated)
    1122         return scmStreamGetLineInternal(pStream, pcchLine, penmEol);
    1123     return ScmStreamGetLineByNo(pStream, pStream->iLine, pcchLine, penmEol);
    1124 }
    1125 
    1126 /**
    1127  * Reads @a cbToRead bytes into @a pvBuf.
    1128  *
    1129  * Will fail if end of stream is encountered before the entire read has been
    1130  * completed.
    1131  *
    1132  * @returns IPRT status code.
    1133  * @retval  VERR_EOF if there isn't @a cbToRead bytes left to read.  Stream
    1134  *          position will be unchanged.
    1135  *
    1136  * @param   pStream             The stream.  Must be in read mode.
    1137  * @param   pvBuf               The buffer to read into.
    1138  * @param   cbToRead            The number of bytes to read.
    1139  */
    1140 static int ScmStreamRead(PSCMSTREAM pStream, void *pvBuf, size_t cbToRead)
    1141 {
    1142     AssertReturn(!pStream->fWriteOrRead, VERR_PERMISSION_DENIED);
    1143     if (RT_FAILURE(pStream->rc))
    1144         return pStream->rc;
    1145 
    1146     /* If there isn't enough stream left, fail already. */
    1147     if (RT_UNLIKELY(pStream->cb - pStream->cb < cbToRead))
    1148         return VERR_EOF;
    1149 
    1150     /* Copy the data and simply seek to the new stream position. */
    1151     memcpy(pvBuf, &pStream->pch[pStream->off], cbToRead);
    1152     return ScmStreamSeekAbsolute(pStream, pStream->off + cbToRead);
    1153 }
    1154 
    1155 /**
    1156  * Checks if the given line is empty or full of white space.
    1157  *
    1158  * @returns true if white space only, false if not (or if non-existant).
    1159  * @param   pStream             The stream.  Must be in read mode.
    1160  * @param   iLine               The line in question.
    1161  */
    1162 static bool ScmStreamIsWhiteLine(PSCMSTREAM pStream, size_t iLine)
    1163 {
    1164     SCMEOL      enmEol;
    1165     size_t      cchLine;
    1166     const char *pchLine = ScmStreamGetLineByNo(pStream, iLine, &cchLine, &enmEol);
    1167     if (!pchLine)
    1168         return false;
    1169     while (cchLine && RT_C_IS_SPACE(*pchLine))
    1170         pchLine++, cchLine--;
    1171     return cchLine == 0;
    1172 }
    1173 
    1174 /**
    1175  * Try figure out the end of line style of the give stream.
    1176  *
    1177  * @returns Most likely end of line style.
    1178  * @param   pStream             The stream.
    1179  */
    1180 SCMEOL ScmStreamGetEol(PSCMSTREAM pStream)
    1181 {
    1182     SCMEOL enmEol;
    1183     if (pStream->cLines > 0)
    1184         enmEol = pStream->paLines[0].enmEol;
    1185     else if (pStream->cb == 0)
    1186         enmEol = SCMEOL_NONE;
    1187     else
    1188     {
    1189         const char *pchLF = (const char *)memchr(pStream->pch, '\n', pStream->cb);
    1190         if (pchLF && pchLF != pStream->pch && pchLF[-1] == '\r')
    1191             enmEol = SCMEOL_CRLF;
    1192         else
    1193             enmEol = SCMEOL_LF;
    1194     }
    1195 
    1196     if (enmEol == SCMEOL_NONE)
    1197 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
    1198         enmEol = SCMEOL_CRLF;
    1199 #else
    1200         enmEol = SCMEOL_LF;
    1201 #endif
    1202     return enmEol;
    1203 }
    1204 
    1205 /**
    1206  * Get the end of line indicator type for a line.
    1207  *
    1208  * @returns The EOL indicator.  If the line isn't found, the default EOL
    1209  *          indicator is return.
    1210  * @param   pStream             The stream.
    1211  * @param   iLine               The line (0-base).
    1212  */
    1213 SCMEOL ScmStreamGetEolByLine(PSCMSTREAM pStream, size_t iLine)
    1214 {
    1215     SCMEOL enmEol;
    1216     if (iLine < pStream->cLines)
    1217         enmEol = pStream->paLines[iLine].enmEol;
    1218     else
    1219 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
    1220         enmEol = SCMEOL_CRLF;
    1221 #else
    1222         enmEol = SCMEOL_LF;
    1223 #endif
    1224     return enmEol;
    1225 }
    1226 
    1227 /**
    1228  * Appends a line to the stream.
    1229  *
    1230  * @returns IPRT status code.
    1231  * @param   pStream             The stream.  Must be in write mode.
    1232  * @param   pchLine             Pointer to the line.
    1233  * @param   cchLine             Line length.
    1234  * @param   enmEol              Which end of line indicator to use.
    1235  */
    1236 int ScmStreamPutLine(PSCMSTREAM pStream, const char *pchLine, size_t cchLine, SCMEOL enmEol)
    1237 {
    1238     AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);
    1239     if (RT_FAILURE(pStream->rc))
    1240         return pStream->rc;
    1241 
    1242     /*
    1243      * Make sure the previous line has a new-line indicator.
    1244      */
    1245     size_t off   = pStream->off;
    1246     size_t iLine = pStream->iLine;
    1247     if (RT_UNLIKELY(   iLine != 0
    1248                     && pStream->paLines[iLine - 1].enmEol == SCMEOL_NONE))
    1249     {
    1250         AssertReturn(pStream->paLines[iLine].cch == 0, VERR_INTERNAL_ERROR_3);
    1251         SCMEOL enmEol2 = enmEol != SCMEOL_NONE ? enmEol : ScmStreamGetEol(pStream);
    1252         if (RT_UNLIKELY(off + cchLine + enmEol + enmEol2 > pStream->cbAllocated))
    1253         {
    1254             int rc = scmStreamGrowBuffer(pStream, cchLine + enmEol + enmEol2);
    1255             if (RT_FAILURE(rc))
    1256                 return rc;
    1257         }
    1258         if (enmEol2 == SCMEOL_LF)
    1259             pStream->pch[off++] = '\n';
    1260         else
    1261         {
    1262             pStream->pch[off++] = '\r';
    1263             pStream->pch[off++] = '\n';
    1264         }
    1265         pStream->paLines[iLine - 1].enmEol = enmEol2;
    1266         pStream->paLines[iLine].off = off;
    1267         pStream->off = off;
    1268         pStream->cb  = off;
    1269     }
    1270 
    1271     /*
    1272      * Ensure we've got sufficient buffer space.
    1273      */
    1274     if (RT_UNLIKELY(off + cchLine + enmEol > pStream->cbAllocated))
    1275     {
    1276         int rc = scmStreamGrowBuffer(pStream, cchLine + enmEol);
    1277         if (RT_FAILURE(rc))
    1278             return rc;
    1279     }
    1280 
    1281     /*
    1282      * Add a line record.
    1283      */
    1284     if (RT_UNLIKELY(iLine + 1 >= pStream->cLinesAllocated))
    1285     {
    1286         int rc = scmStreamGrowLines(pStream, iLine);
    1287         if (RT_FAILURE(rc))
    1288             return rc;
    1289     }
    1290 
    1291     pStream->paLines[iLine].cch    = off - pStream->paLines[iLine].off + cchLine;
    1292     pStream->paLines[iLine].enmEol = enmEol;
    1293 
    1294     iLine++;
    1295     pStream->cLines = iLine;
    1296     pStream->iLine  = iLine;
    1297 
    1298     /*
    1299      * Copy the line
    1300      */
    1301     memcpy(&pStream->pch[off], pchLine, cchLine);
    1302     off += cchLine;
    1303     if (enmEol == SCMEOL_LF)
    1304         pStream->pch[off++] = '\n';
    1305     else if (enmEol == SCMEOL_CRLF)
    1306     {
    1307         pStream->pch[off++] = '\r';
    1308         pStream->pch[off++] = '\n';
    1309     }
    1310     pStream->off = off;
    1311     pStream->cb  = off;
    1312 
    1313     /*
    1314      * Start a new line.
    1315      */
    1316     pStream->paLines[iLine].off    = off;
    1317     pStream->paLines[iLine].cch    = 0;
    1318     pStream->paLines[iLine].enmEol = SCMEOL_NONE;
    1319 
    1320     return VINF_SUCCESS;
    1321 }
    1322 
    1323 /**
    1324  * Writes to the stream.
    1325  *
    1326  * @returns IPRT status code
    1327  * @param   pStream             The stream.  Must be in write mode.
    1328  * @param   pchBuf              What to write.
    1329  * @param   cchBuf              How much to write.
    1330  */
    1331 int ScmStreamWrite(PSCMSTREAM pStream, const char *pchBuf, size_t cchBuf)
    1332 {
    1333     AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);
    1334     if (RT_FAILURE(pStream->rc))
    1335         return pStream->rc;
    1336 
    1337     /*
    1338      * Ensure we've got sufficient buffer space.
    1339      */
    1340     size_t off = pStream->off;
    1341     if (RT_UNLIKELY(off + cchBuf > pStream->cbAllocated))
    1342     {
    1343         int rc = scmStreamGrowBuffer(pStream, cchBuf);
    1344         if (RT_FAILURE(rc))
    1345             return rc;
    1346     }
    1347 
    1348     /*
    1349      * Deal with the odd case where we've already pushed a line with SCMEOL_NONE.
    1350      */
    1351     size_t iLine = pStream->iLine;
    1352     if (RT_UNLIKELY(   iLine > 0
    1353                     && pStream->paLines[iLine - 1].enmEol == SCMEOL_NONE))
    1354     {
    1355         iLine--;
    1356         pStream->cLines = iLine;
    1357         pStream->iLine  = iLine;
    1358     }
    1359 
    1360     /*
    1361      * Deal with lines.
    1362      */
    1363     const char *pchLF = (const char *)memchr(pchBuf, '\n', cchBuf);
    1364     if (!pchLF)
    1365         pStream->paLines[iLine].cch += cchBuf;
    1366     else
    1367     {
    1368         const char *pchLine = pchBuf;
    1369         for (;;)
    1370         {
    1371             if (RT_UNLIKELY(iLine + 1 >= pStream->cLinesAllocated))
    1372             {
    1373                 int rc = scmStreamGrowLines(pStream, iLine);
    1374                 if (RT_FAILURE(rc))
    1375                 {
    1376                     iLine = pStream->iLine;
    1377                     pStream->paLines[iLine].cch    = off - pStream->paLines[iLine].off;
    1378                     pStream->paLines[iLine].enmEol = SCMEOL_NONE;
    1379                     return rc;
    1380                 }
    1381             }
    1382 
    1383             size_t cchLine = pchLF - pchLine;
    1384             if (   cchLine
    1385                 ?  pchLF[-1] != '\r'
    1386                 :     !pStream->paLines[iLine].cch
    1387                    || pStream->pch[pStream->paLines[iLine].off + pStream->paLines[iLine].cch - 1] != '\r')
    1388                 pStream->paLines[iLine].enmEol = SCMEOL_LF;
    1389             else
    1390             {
    1391                 pStream->paLines[iLine].enmEol = SCMEOL_CRLF;
    1392                 cchLine--;
    1393             }
    1394             pStream->paLines[iLine].cch += cchLine;
    1395 
    1396             iLine++;
    1397             size_t offBuf = pchLF + 1 - pchBuf;
    1398             pStream->paLines[iLine].off    = off + offBuf;
    1399             pStream->paLines[iLine].cch    = 0;
    1400             pStream->paLines[iLine].enmEol = SCMEOL_NONE;
    1401 
    1402             size_t cchLeft = cchBuf - offBuf;
    1403             pchLF = (const char *)memchr(pchLF + 1, '\n', cchLeft);
    1404             if (!pchLF)
    1405             {
    1406                 pStream->paLines[iLine].cch = cchLeft;
    1407                 break;
    1408             }
    1409         }
    1410 
    1411         pStream->iLine  = iLine;
    1412         pStream->cLines = iLine;
    1413     }
    1414 
    1415     /*
    1416      * Copy the data and update position and size.
    1417      */
    1418     memcpy(&pStream->pch[off], pchBuf, cchBuf);
    1419     off += cchBuf;
    1420     pStream->off = off;
    1421     pStream->cb  = off;
    1422 
    1423     return VINF_SUCCESS;
    1424 }
    1425 
    1426 /**
    1427  * Write a character to the stream.
    1428  *
    1429  * @returns IPRT status code
    1430  * @param   pStream             The stream.  Must be in write mode.
    1431  * @param   pchBuf              What to write.
    1432  * @param   cchBuf              How much to write.
    1433  */
    1434 int ScmStreamPutCh(PSCMSTREAM pStream, char ch)
    1435 {
    1436     AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);
    1437     if (RT_FAILURE(pStream->rc))
    1438         return pStream->rc;
    1439 
    1440     /*
    1441      * Only deal with the simple cases here, use ScmStreamWrite for the
    1442      * annoying stuff.
    1443      */
    1444     size_t off = pStream->off;
    1445     if (   ch == '\n'
    1446         || RT_UNLIKELY(off + 1 > pStream->cbAllocated))
    1447         return ScmStreamWrite(pStream, &ch, 1);
    1448 
    1449     /*
    1450      * Just append it.
    1451      */
    1452     pStream->pch[off] = ch;
    1453     pStream->off = off + 1;
    1454     pStream->paLines[pStream->iLine].cch++;
    1455 
    1456     return VINF_SUCCESS;
    1457 }
    1458 
    1459 /**
    1460  * Copies @a cLines from the @a pSrc stream onto the @a pDst stream.
    1461  *
    1462  * The stream positions will be used and changed in both streams.
    1463  *
    1464  * @returns IPRT status code.
    1465  * @param   pDst                The destination stream.  Must be in write mode.
    1466  * @param   cLines              The number of lines.  (0 is accepted.)
    1467  * @param   pSrc                The source stream.  Must be in read mode.
    1468  */
    1469 int ScmStreamCopyLines(PSCMSTREAM pDst, PSCMSTREAM pSrc, size_t cLines)
    1470 {
    1471     AssertReturn(pDst->fWriteOrRead, VERR_ACCESS_DENIED);
    1472     if (RT_FAILURE(pDst->rc))
    1473         return pDst->rc;
    1474 
    1475     AssertReturn(!pSrc->fWriteOrRead, VERR_ACCESS_DENIED);
    1476     if (RT_FAILURE(pSrc->rc))
    1477         return pSrc->rc;
    1478 
    1479     while (cLines-- > 0)
    1480     {
    1481         SCMEOL      enmEol;
    1482         size_t      cchLine;
    1483         const char *pchLine = ScmStreamGetLine(pSrc, &cchLine, &enmEol);
    1484         if (!pchLine)
    1485             return pDst->rc = (RT_FAILURE(pSrc->rc) ? pSrc->rc : VERR_EOF);
    1486 
    1487         int rc = ScmStreamPutLine(pDst, pchLine, cchLine, enmEol);
    1488         if (RT_FAILURE(rc))
    1489             return rc;
    1490     }
    1491 
    1492     return VINF_SUCCESS;
    1493 }
    1494 
    1495 /* -=-=-=-=-=- diff -=-=-=-=-=- */
    1496 
    1497 
    1498 /**
    1499  * Prints a range of lines with a prefix.
    1500  *
    1501  * @param   pState              The diff state.
    1502  * @param   chPrefix            The prefix.
    1503  * @param   pStream             The stream to get the lines from.
    1504  * @param   iLine               The first line.
    1505  * @param   cLines              The number of lines.
    1506  */
    1507 static void scmDiffPrintLines(PSCMDIFFSTATE pState, char chPrefix, PSCMSTREAM pStream, size_t iLine, size_t cLines)
    1508 {
    1509     while (cLines-- > 0)
    1510     {
    1511         SCMEOL      enmEol;
    1512         size_t      cchLine;
    1513         const char *pchLine = ScmStreamGetLineByNo(pStream, iLine, &cchLine, &enmEol);
    1514 
    1515         RTStrmPutCh(pState->pDiff, chPrefix);
    1516         if (pchLine && cchLine)
    1517         {
    1518             if (!pState->fSpecialChars)
    1519                 RTStrmWrite(pState->pDiff, pchLine, cchLine);
    1520             else
    1521             {
    1522                 size_t      offVir   = 0;
    1523                 const char *pchStart = pchLine;
    1524                 const char *pchTab   = (const char *)memchr(pchLine, '\t', cchLine);
    1525                 while (pchTab)
    1526                 {
    1527                     RTStrmWrite(pState->pDiff, pchStart, pchTab - pchStart);
    1528                     offVir += pchTab - pchStart;
    1529 
    1530                     size_t cchTab = pState->cchTab - offVir % pState->cchTab;
    1531                     switch (cchTab)
    1532                     {
    1533                         case 1: RTStrmPutStr(pState->pDiff, "."); break;
    1534                         case 2: RTStrmPutStr(pState->pDiff, ".."); break;
    1535                         case 3: RTStrmPutStr(pState->pDiff, "[T]"); break;
    1536                         case 4: RTStrmPutStr(pState->pDiff, "[TA]"); break;
    1537                         case 5: RTStrmPutStr(pState->pDiff, "[TAB]"); break;
    1538                         default: RTStrmPrintf(pState->pDiff, "[TAB%.*s]", cchTab - 5, g_szTabSpaces); break;
    1539                     }
    1540                     offVir += cchTab;
    1541 
    1542                     /* next */
    1543                     pchStart = pchTab + 1;
    1544                     pchTab = (const char *)memchr(pchStart, '\t', cchLine - (pchStart - pchLine));
    1545                 }
    1546                 size_t cchLeft = cchLine - (pchStart - pchLine);
    1547                 if (cchLeft)
    1548                     RTStrmWrite(pState->pDiff, pchStart, cchLeft);
    1549             }
    1550         }
    1551 
    1552         if (!pState->fSpecialChars)
    1553             RTStrmPutCh(pState->pDiff, '\n');
    1554         else if (enmEol == SCMEOL_LF)
    1555             RTStrmPutStr(pState->pDiff, "[LF]\n");
    1556         else if (enmEol == SCMEOL_CRLF)
    1557             RTStrmPutStr(pState->pDiff, "[CRLF]\n");
    1558         else
    1559             RTStrmPutStr(pState->pDiff, "[NONE]\n");
    1560 
    1561         iLine++;
    1562     }
    1563 }
    1564 
    1565 
    1566 /**
    1567  * Reports a difference and propels the streams to the lines following the
    1568  * resync.
    1569  *
    1570  *
    1571  * @returns New pState->cDiff value (just to return something).
    1572  * @param   pState              The diff state.  The cDiffs member will be
    1573  *                              incremented.
    1574  * @param   cMatches            The resync length.
    1575  * @param   iLeft               Where the difference starts on the left side.
    1576  * @param   cLeft               How long it is on this side.  ~(size_t)0 is used
    1577  *                              to indicate that it goes all the way to the end.
    1578  * @param   iRight              Where the difference starts on the right side.
    1579  * @param   cRight              How long it is.
    1580  */
    1581 static size_t scmDiffReport(PSCMDIFFSTATE pState, size_t cMatches,
    1582                             size_t iLeft, size_t cLeft,
    1583                             size_t iRight, size_t cRight)
    1584 {
    1585     /*
    1586      * Adjust the input.
    1587      */
    1588     if (cLeft == ~(size_t)0)
    1589     {
    1590         size_t c = ScmStreamCountLines(pState->pLeft);
    1591         if (c >= iLeft)
    1592             cLeft = c - iLeft;
    1593         else
    1594         {
    1595             iLeft = c;
    1596             cLeft = 0;
    1597         }
    1598     }
    1599 
    1600     if (cRight == ~(size_t)0)
    1601     {
    1602         size_t c = ScmStreamCountLines(pState->pRight);
    1603         if (c >= iRight)
    1604             cRight = c - iRight;
    1605         else
    1606         {
    1607             iRight = c;
    1608             cRight = 0;
    1609         }
    1610     }
    1611 
    1612     /*
    1613      * Print header if it's the first difference
    1614      */
    1615     if (!pState->cDiffs)
    1616         RTStrmPrintf(pState->pDiff, "diff %s %s\n", pState->pszFilename, pState->pszFilename);
    1617 
    1618     /*
    1619      * Emit the change description.
    1620      */
    1621     char ch = cLeft == 0
    1622             ? 'a'
    1623             : cRight == 0
    1624             ? 'd'
    1625             : 'c';
    1626     if (cLeft > 1 && cRight > 1)
    1627         RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu,%zu\n", iLeft + 1, iLeft + cLeft, ch, iRight + 1, iRight + cRight);
    1628     else if (cLeft > 1)
    1629         RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu\n",     iLeft + 1, iLeft + cLeft, ch, iRight + 1);
    1630     else if (cRight > 1)
    1631         RTStrmPrintf(pState->pDiff, "%zu%c%zu,%zu\n",     iLeft + 1,                ch, iRight + 1, iRight + cRight);
    1632     else
    1633         RTStrmPrintf(pState->pDiff, "%zu%c%zu\n",         iLeft + 1,                ch, iRight + 1);
    1634 
    1635     /*
    1636      * And the lines.
    1637      */
    1638     if (cLeft)
    1639         scmDiffPrintLines(pState, '<', pState->pLeft, iLeft, cLeft);
    1640     if (cLeft && cRight)
    1641         RTStrmPrintf(pState->pDiff, "---\n");
    1642     if (cRight)
    1643         scmDiffPrintLines(pState, '>', pState->pRight, iRight, cRight);
    1644 
    1645     /*
    1646      * Reposition the streams (safely ignores return value).
    1647      */
    1648     ScmStreamSeekByLine(pState->pLeft,  iLeft  + cLeft  + cMatches);
    1649     ScmStreamSeekByLine(pState->pRight, iRight + cRight + cMatches);
    1650 
    1651     pState->cDiffs++;
    1652     return pState->cDiffs;
    1653 }
    1654 
    1655 /**
    1656  * Helper for scmDiffCompare that takes care of trailing spaces and stuff
    1657  * like that.
    1658  */
    1659 static bool scmDiffCompareSlow(PSCMDIFFSTATE pState,
    1660                                const char *pchLeft,  size_t cchLeft,  SCMEOL enmEolLeft,
    1661                                const char *pchRight, size_t cchRight, SCMEOL enmEolRight)
    1662 {
    1663     if (pState->fIgnoreTrailingWhite)
    1664     {
    1665         while (cchLeft > 0 && RT_C_IS_SPACE(pchLeft[cchLeft - 1]))
    1666             cchLeft--;
    1667         while (cchRight > 0 && RT_C_IS_SPACE(pchRight[cchRight - 1]))
    1668             cchRight--;
    1669     }
    1670 
    1671     if (pState->fIgnoreLeadingWhite)
    1672     {
    1673         while (cchLeft > 0 && RT_C_IS_SPACE(*pchLeft))
    1674             pchLeft++, cchLeft--;
    1675         while (cchRight > 0 && RT_C_IS_SPACE(*pchRight))
    1676             pchRight++, cchRight--;
    1677     }
    1678 
    1679     if (   cchLeft != cchRight
    1680         || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)
    1681         || memcmp(pchLeft, pchRight, cchLeft))
    1682         return false;
    1683     return true;
    1684 }
    1685 
    1686 /**
    1687  * Compare two lines.
    1688  *
    1689  * @returns true if the are equal, false if not.
    1690  */
    1691 DECLINLINE(bool) scmDiffCompare(PSCMDIFFSTATE pState,
    1692                                 const char *pchLeft,  size_t cchLeft,  SCMEOL enmEolLeft,
    1693                                 const char *pchRight, size_t cchRight, SCMEOL enmEolRight)
    1694 {
    1695     if (   cchLeft != cchRight
    1696         || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)
    1697         || memcmp(pchLeft, pchRight, cchLeft))
    1698     {
    1699         if (   pState->fIgnoreTrailingWhite
    1700             || pState->fIgnoreTrailingWhite)
    1701             return scmDiffCompareSlow(pState,
    1702                                       pchLeft, cchLeft, enmEolLeft,
    1703                                       pchRight, cchRight, enmEolRight);
    1704         return false;
    1705     }
    1706     return true;
    1707 }
    1708 
    1709 /**
    1710  * Compares two sets of lines from the two files.
    1711  *
    1712  * @returns true if they matches, false if they don't.
    1713  * @param   pState              The diff state.
    1714  * @param   iLeft               Where to start in the left stream.
    1715  * @param   iRight              Where to start in the right stream.
    1716  * @param   cLines              How many lines to compare.
    1717  */
    1718 static bool scmDiffCompareLines(PSCMDIFFSTATE pState, size_t iLeft, size_t iRight, size_t cLines)
    1719 {
    1720     for (size_t iLine = 0; iLine < cLines; iLine++)
    1721     {
    1722         SCMEOL      enmEolLeft;
    1723         size_t      cchLeft;
    1724         const char *pchLeft  = ScmStreamGetLineByNo(pState->pLeft,  iLeft + iLine,  &cchLeft,  &enmEolLeft);
    1725 
    1726         SCMEOL      enmEolRight;
    1727         size_t      cchRight;
    1728         const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iRight + iLine, &cchRight, &enmEolRight);
    1729 
    1730         if (!scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight))
    1731             return false;
    1732     }
    1733     return true;
    1734 }
    1735 
    1736 
    1737 /**
    1738  * Resynchronize the two streams and reports the difference.
    1739  *
    1740  * Upon return, the streams will be positioned after the block of @a cMatches
    1741  * lines where it resynchronized them.
    1742  *
    1743  * @returns pState->cDiffs (just so we can use it in a return statement).
    1744  * @param   pState              The state.
    1745  * @param   cMatches            The number of lines that needs to match for the
    1746  *                              stream to be considered synchronized again.
    1747  */
    1748 static size_t scmDiffSynchronize(PSCMDIFFSTATE pState, size_t cMatches)
    1749 {
    1750     size_t const iStartLeft  = ScmStreamTellLine(pState->pLeft)  - 1;
    1751     size_t const iStartRight = ScmStreamTellLine(pState->pRight) - 1;
    1752     Assert(cMatches > 0);
    1753 
    1754     /*
    1755      * Compare each new line from each of the streams will all the preceding
    1756      * ones, including iStartLeft/Right.
    1757      */
    1758     for (size_t iRange = 1; ; iRange++)
    1759     {
    1760         /*
    1761          * Get the next line in the left stream and compare it against all the
    1762          * preceding lines on the right side.
    1763          */
    1764         SCMEOL      enmEol;
    1765         size_t      cchLine;
    1766         const char *pchLine = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iRange, &cchLine, &enmEol);
    1767         if (!pchLine)
    1768             return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0);
    1769 
    1770         for (size_t iRight = cMatches - 1; iRight < iRange; iRight++)
    1771         {
    1772             SCMEOL      enmEolRight;
    1773             size_t      cchRight;
    1774             const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRight,
    1775                                                         &cchRight, &enmEolRight);
    1776             if (   scmDiffCompare(pState, pchLine, cchLine, enmEol, pchRight, cchRight, enmEolRight)
    1777                 && scmDiffCompareLines(pState,
    1778                                        iStartLeft  + iRange + 1 - cMatches,
    1779                                        iStartRight + iRight + 1 - cMatches,
    1780                                        cMatches - 1)
    1781                )
    1782                 return scmDiffReport(pState, cMatches,
    1783                                      iStartLeft,  iRange + 1 - cMatches,
    1784                                      iStartRight, iRight + 1 - cMatches);
    1785         }
    1786 
    1787         /*
    1788          * Get the next line in the right stream and compare it against all the
    1789          * lines on the right side.
    1790          */
    1791         pchLine = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRange, &cchLine, &enmEol);
    1792         if (!pchLine)
    1793             return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0);
    1794 
    1795         for (size_t iLeft = cMatches - 1; iLeft <= iRange; iLeft++)
    1796         {
    1797             SCMEOL      enmEolLeft;
    1798             size_t      cchLeft;
    1799             const char *pchLeft = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iLeft,
    1800                                                        &cchLeft, &enmEolLeft);
    1801             if (    scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchLine, cchLine, enmEol)
    1802                 && scmDiffCompareLines(pState,
    1803                                        iStartLeft  + iLeft  + 1 - cMatches,
    1804                                        iStartRight + iRange + 1 - cMatches,
    1805                                        cMatches - 1)
    1806                )
    1807                 return scmDiffReport(pState, cMatches,
    1808                                      iStartLeft,  iLeft  + 1 - cMatches,
    1809                                      iStartRight, iRange + 1 - cMatches);
    1810         }
    1811     }
    1812 }
    1813 
    1814 /**
    1815  * Creates a diff of the changes between the streams @a pLeft and @a pRight.
    1816  *
    1817  * This currently only implements the simplest diff format, so no contexts.
    1818  *
    1819  * Also, note that we won't detect differences in the final newline of the
    1820  * streams.
    1821  *
    1822  * @returns The number of differences.
    1823  * @param   pszFilename         The filename.
    1824  * @param   pLeft               The left side stream.
    1825  * @param   pRight              The right side stream.
    1826  * @param   fIgnoreEol          Whether to ignore end of line markers.
    1827  * @param   fIgnoreLeadingWhite Set if leading white space should be ignored.
    1828  * @param   fIgnoreTrailingWhite  Set if trailing white space should be ignored.
    1829  * @param   fSpecialChars       Whether to print special chars in a human
    1830  *                              readable form or not.
    1831  * @param   cchTab              The tab size.
    1832  * @param   pDiff               Where to write the diff.
    1833  */
    1834 size_t ScmDiffStreams(const char *pszFilename, PSCMSTREAM pLeft, PSCMSTREAM pRight, bool fIgnoreEol,
    1835                       bool fIgnoreLeadingWhite, bool fIgnoreTrailingWhite, bool fSpecialChars,
    1836                       size_t cchTab, PRTSTREAM pDiff)
    1837 {
    1838 #ifdef RT_STRICT
    1839     ScmStreamCheckItegrity(pLeft);
    1840     ScmStreamCheckItegrity(pRight);
    1841 #endif
    1842 
    1843     /*
    1844      * Set up the diff state.
    1845      */
    1846     SCMDIFFSTATE State;
    1847     State.cDiffs                = 0;
    1848     State.pszFilename           = pszFilename;
    1849     State.pLeft                 = pLeft;
    1850     State.pRight                = pRight;
    1851     State.fIgnoreEol            = fIgnoreEol;
    1852     State.fIgnoreLeadingWhite   = fIgnoreLeadingWhite;
    1853     State.fIgnoreTrailingWhite  = fIgnoreTrailingWhite;
    1854     State.fSpecialChars         = fSpecialChars;
    1855     State.cchTab                = cchTab;
    1856     State.pDiff                 = pDiff;
    1857 
    1858     /*
    1859      * Compare them line by line.
    1860      */
    1861     ScmStreamRewindForReading(pLeft);
    1862     ScmStreamRewindForReading(pRight);
    1863     const char *pchLeft;
    1864     const char *pchRight;
    1865 
    1866     for (;;)
    1867     {
    1868         SCMEOL  enmEolLeft;
    1869         size_t  cchLeft;
    1870         pchLeft  = ScmStreamGetLine(pLeft,  &cchLeft,  &enmEolLeft);
    1871 
    1872         SCMEOL  enmEolRight;
    1873         size_t  cchRight;
    1874         pchRight = ScmStreamGetLine(pRight, &cchRight, &enmEolRight);
    1875         if (!pchLeft || !pchRight)
    1876             break;
    1877 
    1878         if (!scmDiffCompare(&State, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight))
    1879             scmDiffSynchronize(&State, 3);
    1880     }
    1881 
    1882     /*
    1883      * Deal with any remaining differences.
    1884      */
    1885     if (pchLeft)
    1886         scmDiffReport(&State, 0, ScmStreamTellLine(pLeft) - 1, ~(size_t)0, ScmStreamTellLine(pRight), 0);
    1887     else if (pchRight)
    1888         scmDiffReport(&State, 0, ScmStreamTellLine(pLeft), 0, ScmStreamTellLine(pRight) - 1, ~(size_t)0);
    1889 
    1890     /*
    1891      * Report any errors.
    1892      */
    1893     if (RT_FAILURE(ScmStreamGetStatus(pLeft)))
    1894         RTMsgError("Left diff stream error: %Rrc\n", ScmStreamGetStatus(pLeft));
    1895     if (RT_FAILURE(ScmStreamGetStatus(pRight)))
    1896         RTMsgError("Right diff stream error: %Rrc\n", ScmStreamGetStatus(pRight));
    1897 
    1898     return State.cDiffs;
    1899 }
    1900 
    1901 
    1902 
    1903 /* -=-=-=-=-=- settings -=-=-=-=-=- */
    1904 
    1905 /**
    1906  * Init a settings structure with settings from @a pSrc.
    1907  *
    1908  * @returns IPRT status code
    1909  * @param   pSettings           The settings.
    1910  * @param   pSrc                The source settings.
    1911  */
    1912 static int scmSettingsBaseInitAndCopy(PSCMSETTINGSBASE pSettings, PCSCMSETTINGSBASE pSrc)
    1913 {
    1914     *pSettings = *pSrc;
    1915 
    1916     int rc = RTStrDupEx(&pSettings->pszFilterFiles, pSrc->pszFilterFiles);
    1917     if (RT_SUCCESS(rc))
    1918     {
    1919         rc = RTStrDupEx(&pSettings->pszFilterOutFiles, pSrc->pszFilterOutFiles);
    1920         if (RT_SUCCESS(rc))
    1921         {
    1922             rc = RTStrDupEx(&pSettings->pszFilterOutDirs, pSrc->pszFilterOutDirs);
    1923             if (RT_SUCCESS(rc))
    1924                 return VINF_SUCCESS;
    1925 
    1926             RTStrFree(pSettings->pszFilterOutFiles);
    1927         }
    1928         RTStrFree(pSettings->pszFilterFiles);
    1929     }
    1930 
    1931     pSettings->pszFilterFiles = NULL;
    1932     pSettings->pszFilterOutFiles = NULL;
    1933     pSettings->pszFilterOutDirs = NULL;
    1934     return rc;
    1935 }
    1936 
    1937 /**
    1938  * Init a settings structure.
    1939  *
    1940  * @returns IPRT status code
    1941  * @param   pSettings           The settings.
    1942  */
    1943 static int scmSettingsBaseInit(PSCMSETTINGSBASE pSettings)
    1944 {
    1945     return scmSettingsBaseInitAndCopy(pSettings, &g_Defaults);
    1946 }
    1947 
    1948 /**
    1949  * Deletes the settings, i.e. free any dynamically allocated content.
    1950  *
    1951  * @param   pSettings           The settings.
    1952  */
    1953 static void scmSettingsBaseDelete(PSCMSETTINGSBASE pSettings)
    1954 {
    1955     if (pSettings)
    1956     {
    1957         Assert(pSettings->cchTab != ~(unsigned)0);
    1958         pSettings->cchTab = ~(unsigned)0;
    1959 
    1960         RTStrFree(pSettings->pszFilterFiles);
    1961         pSettings->pszFilterFiles = NULL;
    1962 
    1963         RTStrFree(pSettings->pszFilterOutFiles);
    1964         pSettings->pszFilterOutFiles = NULL;
    1965 
    1966         RTStrFree(pSettings->pszFilterOutDirs);
    1967         pSettings->pszFilterOutDirs = NULL;
    1968     }
    1969 }
    1970 
    1971 
    1972 /**
    1973  * Processes a RTGetOpt result.
    1974  *
    1975  * @retval  VINF_SUCCESS if handled.
    1976  * @retval  VERR_OUT_OF_RANGE if the option value was out of range.
    1977  * @retval  VERR_GETOPT_UNKNOWN_OPTION if the option was not recognized.
    1978  *
    1979  * @param   pSettings           The settings to change.
    1980  * @param   rc                  The RTGetOpt return value.
    1981  * @param   pValueUnion         The RTGetOpt value union.
    1982  */
    1983 static int scmSettingsBaseHandleOpt(PSCMSETTINGSBASE pSettings, int rc, PRTGETOPTUNION pValueUnion)
    1984 {
    1985     switch (rc)
    1986     {
    1987         case SCMOPT_CONVERT_EOL:
    1988             pSettings->fConvertEol = true;
    1989             return VINF_SUCCESS;
    1990         case SCMOPT_NO_CONVERT_EOL:
    1991             pSettings->fConvertEol = false;
    1992             return VINF_SUCCESS;
    1993 
    1994         case SCMOPT_CONVERT_TABS:
    1995             pSettings->fConvertTabs = true;
    1996             return VINF_SUCCESS;
    1997         case SCMOPT_NO_CONVERT_TABS:
    1998             pSettings->fConvertTabs = false;
    1999             return VINF_SUCCESS;
    2000 
    2001         case SCMOPT_FORCE_FINAL_EOL:
    2002             pSettings->fForceFinalEol = true;
    2003             return VINF_SUCCESS;
    2004         case SCMOPT_NO_FORCE_FINAL_EOL:
    2005             pSettings->fForceFinalEol = false;
    2006             return VINF_SUCCESS;
    2007 
    2008         case SCMOPT_FORCE_TRAILING_LINE:
    2009             pSettings->fForceTrailingLine = true;
    2010             return VINF_SUCCESS;
    2011         case SCMOPT_NO_FORCE_TRAILING_LINE:
    2012             pSettings->fForceTrailingLine = false;
    2013             return VINF_SUCCESS;
    2014 
    2015         case SCMOPT_STRIP_TRAILING_BLANKS:
    2016             pSettings->fStripTrailingBlanks = true;
    2017             return VINF_SUCCESS;
    2018         case SCMOPT_NO_STRIP_TRAILING_BLANKS:
    2019             pSettings->fStripTrailingBlanks = false;
    2020             return VINF_SUCCESS;
    2021 
    2022         case SCMOPT_STRIP_TRAILING_LINES:
    2023             pSettings->fStripTrailingLines = true;
    2024             return VINF_SUCCESS;
    2025         case SCMOPT_NO_STRIP_TRAILING_LINES:
    2026             pSettings->fStripTrailingLines = false;
    2027             return VINF_SUCCESS;
    2028 
    2029         case SCMOPT_ONLY_SVN_DIRS:
    2030             pSettings->fOnlySvnDirs = true;
    2031             return VINF_SUCCESS;
    2032         case SCMOPT_NOT_ONLY_SVN_DIRS:
    2033             pSettings->fOnlySvnDirs = false;
    2034             return VINF_SUCCESS;
    2035 
    2036         case SCMOPT_ONLY_SVN_FILES:
    2037             pSettings->fOnlySvnFiles = true;
    2038             return VINF_SUCCESS;
    2039         case SCMOPT_NOT_ONLY_SVN_FILES:
    2040             pSettings->fOnlySvnFiles = false;
    2041             return VINF_SUCCESS;
    2042 
    2043         case SCMOPT_SET_SVN_EOL:
    2044             pSettings->fSetSvnEol = true;
    2045             return VINF_SUCCESS;
    2046         case SCMOPT_DONT_SET_SVN_EOL:
    2047             pSettings->fSetSvnEol = false;
    2048             return VINF_SUCCESS;
    2049 
    2050         case SCMOPT_SET_SVN_EXECUTABLE:
    2051             pSettings->fSetSvnExecutable = true;
    2052             return VINF_SUCCESS;
    2053         case SCMOPT_DONT_SET_SVN_EXECUTABLE:
    2054             pSettings->fSetSvnExecutable = false;
    2055             return VINF_SUCCESS;
    2056 
    2057         case SCMOPT_SET_SVN_KEYWORDS:
    2058             pSettings->fSetSvnKeywords = true;
    2059             return VINF_SUCCESS;
    2060         case SCMOPT_DONT_SET_SVN_KEYWORDS:
    2061             pSettings->fSetSvnKeywords = false;
    2062             return VINF_SUCCESS;
    2063 
    2064         case SCMOPT_TAB_SIZE:
    2065             if (   pValueUnion->u8 < 1
    2066                 || pValueUnion->u8 >= RT_ELEMENTS(g_szTabSpaces))
    2067             {
    2068                 RTMsgError("Invalid tab size: %u - must be in {1..%u}\n",
    2069                            pValueUnion->u8, RT_ELEMENTS(g_szTabSpaces) - 1);
    2070                 return VERR_OUT_OF_RANGE;
    2071             }
    2072             pSettings->cchTab = pValueUnion->u8;
    2073             return VINF_SUCCESS;
    2074 
    2075         case SCMOPT_FILTER_OUT_DIRS:
    2076         case SCMOPT_FILTER_FILES:
    2077         case SCMOPT_FILTER_OUT_FILES:
    2078         {
    2079             char **ppsz = NULL;
    2080             switch (rc)
    2081             {
    2082                 case SCMOPT_FILTER_OUT_DIRS:    ppsz = &pSettings->pszFilterOutDirs; break;
    2083                 case SCMOPT_FILTER_FILES:       ppsz = &pSettings->pszFilterFiles; break;
    2084                 case SCMOPT_FILTER_OUT_FILES:   ppsz = &pSettings->pszFilterOutFiles; break;
    2085             }
    2086 
    2087             /*
    2088              * An empty string zaps the current list.
    2089              */
    2090             if (!*pValueUnion->psz)
    2091                 return RTStrATruncate(ppsz, 0);
    2092 
    2093             /*
    2094              * Non-empty strings are appended to the pattern list.
    2095              *
    2096              * Strip leading and trailing pattern separators before attempting
    2097              * to append it.  If it's just separators, don't do anything.
    2098              */
    2099             const char *pszSrc = pValueUnion->psz;
    2100             while (*pszSrc == '|')
    2101                 pszSrc++;
    2102             size_t cchSrc = strlen(pszSrc);
    2103             while (cchSrc > 0 && pszSrc[cchSrc - 1] == '|')
    2104                 cchSrc--;
    2105             if (!cchSrc)
    2106                 return VINF_SUCCESS;
    2107 
    2108             return RTStrAAppendExN(ppsz, 2,
    2109                                    "|", *ppsz && **ppsz ? 1 : 0,
    2110                                    pszSrc, cchSrc);
    2111         }
    2112 
    2113         default:
    2114             return VERR_GETOPT_UNKNOWN_OPTION;
    2115     }
    2116 }
    2117 
    2118 /**
    2119  * Parses an option string.
    2120  *
    2121  * @returns IPRT status code.
    2122  * @param   pBase               The base settings structure to apply the options
    2123  *                              to.
    2124  * @param   pszOptions          The options to parse.
    2125  */
    2126 static int scmSettingsBaseParseString(PSCMSETTINGSBASE pBase, const char *pszLine)
    2127 {
    2128     int    cArgs;
    2129     char **papszArgs;
    2130     int rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszLine, NULL);
    2131     if (RT_SUCCESS(rc))
    2132     {
    2133         RTGETOPTUNION   ValueUnion;
    2134         RTGETOPTSTATE   GetOptState;
    2135         rc = RTGetOptInit(&GetOptState, cArgs, papszArgs, &g_aScmOpts[0], RT_ELEMENTS(g_aScmOpts), 0, 0 /*fFlags*/);
    2136         if (RT_SUCCESS(rc))
    2137         {
    2138             while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
    2139             {
    2140                 rc = scmSettingsBaseHandleOpt(pBase, rc, &ValueUnion);
    2141                 if (RT_FAILURE(rc))
    2142                     break;
    2143             }
    2144         }
    2145         RTGetOptArgvFree(papszArgs);
    2146     }
    2147 
    2148     return rc;
    2149 }
    2150 
    2151 /**
    2152  * Parses an unterminated option string.
    2153  *
    2154  * @returns IPRT status code.
    2155  * @param   pBase               The base settings structure to apply the options
    2156  *                              to.
    2157  * @param   pchLine             The line.
    2158  * @param   cchLine             The line length.
    2159  */
    2160 static int scmSettingsBaseParseStringN(PSCMSETTINGSBASE pBase, const char *pchLine, size_t cchLine)
    2161 {
    2162     char *pszLine = RTStrDupN(pchLine, cchLine);
    2163     if (!pszLine)
    2164         return VERR_NO_MEMORY;
    2165     int rc = scmSettingsBaseParseString(pBase, pszLine);
    2166     RTStrFree(pszLine);
    2167     return rc;
    2168 }
    2169 
    2170 /**
    2171  * Verifies the options string.
    2172  *
    2173  * @returns IPRT status code.
    2174  * @param   pszOptions          The options to verify .
    2175  */
    2176 static int scmSettingsBaseVerifyString(const char *pszOptions)
    2177 {
    2178     SCMSETTINGSBASE Base;
    2179     int rc = scmSettingsBaseInit(&Base);
    2180     if (RT_SUCCESS(rc))
    2181     {
    2182         rc = scmSettingsBaseParseString(&Base, pszOptions);
    2183         scmSettingsBaseDelete(&Base);
    2184     }
    2185     return rc;
    2186 }
    2187 
    2188 /**
    2189  * Loads settings found in editor and SCM settings directives within the
    2190  * document (@a pStream).
    2191  *
    2192  * @returns IPRT status code.
    2193  * @param   pBase               The settings base to load settings into.
    2194  * @param   pStream             The stream to scan for settings directives.
    2195  */
    2196 static int scmSettingsBaseLoadFromDocument(PSCMSETTINGSBASE pBase, PSCMSTREAM pStream)
    2197 {
    2198     /** @todo Editor and SCM settings directives in documents.  */
    2199     return VINF_SUCCESS;
    2200 }
    2201 
    2202 /**
    2203  * Creates a new settings file struct, cloning @a pSettings.
    2204  *
    2205  * @returns IPRT status code.
    2206  * @param   ppSettings          Where to return the new struct.
    2207  * @param   pSettingsBase       The settings to inherit from.
    2208  */
    2209 static int scmSettingsCreate(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pSettingsBase)
    2210 {
    2211     PSCMSETTINGS pSettings = (PSCMSETTINGS)RTMemAlloc(sizeof(*pSettings));
    2212     if (!pSettings)
    2213         return VERR_NO_MEMORY;
    2214     int rc = scmSettingsBaseInitAndCopy(&pSettings->Base, pSettingsBase);
    2215     if (RT_SUCCESS(rc))
    2216     {
    2217         pSettings->pDown   = NULL;
    2218         pSettings->pUp     = NULL;
    2219         pSettings->paPairs = NULL;
    2220         pSettings->cPairs  = 0;
    2221         *ppSettings = pSettings;
    2222         return VINF_SUCCESS;
    2223     }
    2224     RTMemFree(pSettings);
    2225     return rc;
    2226 }
    2227 
    2228 /**
    2229  * Destroys a settings structure.
    2230  *
    2231  * @param   pSettings           The settings structure to destroy.  NULL is OK.
    2232  */
    2233 static void scmSettingsDestroy(PSCMSETTINGS pSettings)
    2234 {
    2235     if (pSettings)
    2236     {
    2237         scmSettingsBaseDelete(&pSettings->Base);
    2238         for (size_t i = 0; i < pSettings->cPairs; i++)
    2239         {
    2240             RTStrFree(pSettings->paPairs[i].pszPattern);
    2241             RTStrFree(pSettings->paPairs[i].pszOptions);
    2242             pSettings->paPairs[i].pszPattern = NULL;
    2243             pSettings->paPairs[i].pszOptions = NULL;
    2244         }
    2245         RTMemFree(pSettings->paPairs);
    2246         pSettings->paPairs = NULL;
    2247         RTMemFree(pSettings);
    2248     }
    2249 }
    2250 
    2251 /**
    2252  * Adds a pattern/options pair to the settings structure.
    2253  *
    2254  * @returns IPRT status code.
    2255  * @param   pSettings           The settings.
    2256  * @param   pchLine             The line containing the unparsed pair.
    2257  * @param   cchLine             The length of the line.
    2258  */
    2259 static int scmSettingsAddPair(PSCMSETTINGS pSettings, const char *pchLine, size_t cchLine)
    2260 {
    2261     /*
    2262      * Split the string.
    2263      */
    2264     const char *pchOptions = (const char *)memchr(pchLine, ':', cchLine);
    2265     if (!pchOptions)
    2266         return VERR_INVALID_PARAMETER;
    2267     size_t cchPattern = pchOptions - pchLine;
    2268     size_t cchOptions = cchLine - cchPattern - 1;
    2269     pchOptions++;
    2270 
    2271     /* strip spaces everywhere */
    2272     while (cchPattern > 0 && RT_C_IS_SPACE(pchLine[cchPattern - 1]))
    2273         cchPattern--;
    2274     while (cchPattern > 0 && RT_C_IS_SPACE(*pchLine))
    2275         cchPattern--, pchLine++;
    2276 
    2277     while (cchOptions > 0 && RT_C_IS_SPACE(pchOptions[cchOptions - 1]))
    2278         cchOptions--;
    2279     while (cchOptions > 0 && RT_C_IS_SPACE(*pchOptions))
    2280         cchOptions--, pchOptions++;
    2281 
    2282     /* Quietly ignore empty patterns and empty options. */
    2283     if (!cchOptions || !cchPattern)
    2284         return VINF_SUCCESS;
    2285 
    2286     /*
    2287      * Add the pair and verify the option string.
    2288      */
    2289     uint32_t iPair = pSettings->cPairs;
    2290     if ((iPair % 32) == 0)
    2291     {
    2292         void *pvNew = RTMemRealloc(pSettings->paPairs, (iPair + 32) * sizeof(pSettings->paPairs[0]));
    2293         if (!pvNew)
    2294             return VERR_NO_MEMORY;
    2295         pSettings->paPairs = (PSCMPATRNOPTPAIR)pvNew;
    2296     }
    2297 
    2298     pSettings->paPairs[iPair].pszPattern = RTStrDupN(pchLine, cchPattern);
    2299     pSettings->paPairs[iPair].pszOptions = RTStrDupN(pchOptions, cchOptions);
    2300     int rc;
    2301     if (   pSettings->paPairs[iPair].pszPattern
    2302         && pSettings->paPairs[iPair].pszOptions)
    2303         rc = scmSettingsBaseVerifyString(pSettings->paPairs[iPair].pszOptions);
    2304     else
    2305         rc = VERR_NO_MEMORY;
    2306     if (RT_SUCCESS(rc))
    2307         pSettings->cPairs = iPair + 1;
    2308     else
    2309     {
    2310         RTStrFree(pSettings->paPairs[iPair].pszPattern);
    2311         RTStrFree(pSettings->paPairs[iPair].pszOptions);
    2312     }
    2313     return rc;
    2314 }
    2315 
    2316 /**
    2317  * Loads in the settings from @a pszFilename.
    2318  *
    2319  * @returns IPRT status code.
    2320  * @param   pSettings           Where to load the settings file.
    2321  * @param   pszFilename         The file to load.
    2322  */
    2323 static int scmSettingsLoadFile(PSCMSETTINGS pSettings, const char *pszFilename)
    2324 {
    2325     SCMSTREAM Stream;
    2326     int rc = ScmStreamInitForReading(&Stream, pszFilename);
    2327     if (RT_FAILURE(rc))
    2328     {
    2329         RTMsgError("%s: ScmStreamInitForReading -> %Rrc\n", pszFilename, rc);
    2330         return rc;
    2331     }
    2332 
    2333     SCMEOL      enmEol;
    2334     const char *pchLine;
    2335     size_t      cchLine;
    2336     while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
    2337     {
    2338         /* Ignore leading spaces. */
    2339         while (cchLine > 0 && RT_C_IS_SPACE(*pchLine))
    2340             pchLine++, cchLine--;
    2341 
    2342         /* Ignore empty lines and comment lines. */
    2343         if (cchLine < 1 || *pchLine == '#')
    2344             continue;
    2345 
    2346         /* What kind of line is it? */
    2347         const char *pchColon = (const char *)memchr(pchLine, ':', cchLine);
    2348         if (pchColon)
    2349             rc = scmSettingsAddPair(pSettings, pchLine, cchLine);
    2350         else
    2351             rc = scmSettingsBaseParseStringN(&pSettings->Base, pchLine, cchLine);
    2352         if (RT_FAILURE(rc))
    2353         {
    2354             RTMsgError("%s:%d: %Rrc\n", pszFilename, ScmStreamTellLine(&Stream), rc);
    2355             break;
    2356         }
    2357     }
    2358 
    2359     if (RT_SUCCESS(rc))
    2360     {
    2361         rc = ScmStreamGetStatus(&Stream);
    2362         if (RT_FAILURE(rc))
    2363             RTMsgError("%s: ScmStreamGetStatus- > %Rrc\n", pszFilename, rc);
    2364     }
    2365 
    2366     ScmStreamDelete(&Stream);
    2367     return rc;
    2368 }
    2369 
    2370 /**
    2371  * Parse the specified settings file creating a new settings struct from it.
    2372  *
    2373  * @returns IPRT status code
    2374  * @param   ppSettings          Where to return the new settings.
    2375  * @param   pszFilename         The file to parse.
    2376  * @param   pSettingsBase       The base settings we inherit from.
    2377  */
    2378 static int scmSettingsCreateFromFile(PSCMSETTINGS *ppSettings, const char *pszFilename, PCSCMSETTINGSBASE pSettingsBase)
    2379 {
    2380     PSCMSETTINGS pSettings;
    2381     int rc = scmSettingsCreate(&pSettings, pSettingsBase);
    2382     if (RT_SUCCESS(rc))
    2383     {
    2384         rc = scmSettingsLoadFile(pSettings, pszFilename);
    2385         if (RT_SUCCESS(rc))
    2386         {
    2387             *ppSettings = pSettings;
    2388             return VINF_SUCCESS;
    2389         }
    2390 
    2391         scmSettingsDestroy(pSettings);
    2392     }
    2393     *ppSettings = NULL;
    2394     return rc;
    2395 }
    2396 
    2397 
    2398 /**
    2399  * Create an initial settings structure when starting processing a new file or
    2400  * directory.
    2401  *
    2402  * This will look for .scm-settings files from the root and down to the
    2403  * specified directory, combining them into the returned settings structure.
    2404  *
    2405  * @returns IPRT status code.
    2406  * @param   ppSettings          Where to return the pointer to the top stack
    2407  *                              object.
    2408  * @param   pBaseSettings       The base settings we inherit from (globals
    2409  *                              typically).
    2410  * @param   pszPath             The absolute path to the new directory or file.
    2411  */
    2412 static int scmSettingsCreateForPath(PSCMSETTINGS *ppSettings, PCSCMSETTINGSBASE pBaseSettings, const char *pszPath)
    2413 {
    2414     *ppSettings = NULL;                 /* try shut up gcc. */
    2415 
    2416     /*
    2417      * We'll be working with a stack copy of the path.
    2418      */
    2419     char    szFile[RTPATH_MAX];
    2420     size_t  cchDir = strlen(pszPath);
    2421     if (cchDir >= sizeof(szFile) - sizeof(SCM_SETTINGS_FILENAME))
    2422         return VERR_FILENAME_TOO_LONG;
    2423 
    2424     /*
    2425      * Create the bottom-most settings.
    2426      */
    2427     PSCMSETTINGS pSettings;
    2428     int rc = scmSettingsCreate(&pSettings, pBaseSettings);
    2429     if (RT_FAILURE(rc))
    2430         return rc;
    2431 
    2432     /*
    2433      * Enumerate the path components from the root and down. Load any setting
    2434      * files we find.
    2435      */
    2436     size_t cComponents = RTPathCountComponents(pszPath);
    2437     for (size_t i = 1; i <= cComponents; i++)
    2438     {
    2439         rc = RTPathCopyComponents(szFile, sizeof(szFile), pszPath, i);
    2440         if (RT_SUCCESS(rc))
    2441             rc = RTPathAppend(szFile, sizeof(szFile), SCM_SETTINGS_FILENAME);
    2442         if (RT_FAILURE(rc))
    2443             break;
    2444         if (RTFileExists(szFile))
    2445         {
    2446             rc = scmSettingsLoadFile(pSettings, szFile);
    2447             if (RT_FAILURE(rc))
    2448                 break;
    2449         }
    2450     }
    2451 
    2452     if (RT_SUCCESS(rc))
    2453         *ppSettings = pSettings;
    2454     else
    2455         scmSettingsDestroy(pSettings);
    2456     return rc;
    2457 }
    2458 
    2459 /**
    2460  * Pushes a new settings set onto the stack.
    2461  *
    2462  * @param   ppSettingsStack     The pointer to the pointer to the top stack
    2463  *                              element.  This will be used as input and output.
    2464  * @param   pSettings           The settings to push onto the stack.
    2465  */
    2466 static void scmSettingsStackPush(PSCMSETTINGS *ppSettingsStack, PSCMSETTINGS pSettings)
    2467 {
    2468     PSCMSETTINGS pOld = *ppSettingsStack;
    2469     pSettings->pDown  = pOld;
    2470     pSettings->pUp    = NULL;
    2471     if (pOld)
    2472         pOld->pUp = pSettings;
    2473     *ppSettingsStack = pSettings;
    2474 }
    2475 
    2476 /**
    2477  * Pushes the settings of the specified directory onto the stack.
    2478  *
    2479  * We will load any .scm-settings in the directory.  A stack entry is added even
    2480  * if no settings file was found.
    2481  *
    2482  * @returns IPRT status code.
    2483  * @param   ppSettingsStack     The pointer to the pointer to the top stack
    2484  *                              element.  This will be used as input and output.
    2485  * @param   pszDir              The directory to do this for.
    2486  */
    2487 static int scmSettingsStackPushDir(PSCMSETTINGS *ppSettingsStack, const char *pszDir)
    2488 {
    2489     char szFile[RTPATH_MAX];
    2490     int rc = RTPathJoin(szFile, sizeof(szFile), pszDir, SCM_SETTINGS_FILENAME);
    2491     if (RT_SUCCESS(rc))
    2492     {
    2493         PSCMSETTINGS pSettings;
    2494         rc = scmSettingsCreate(&pSettings, &(*ppSettingsStack)->Base);
    2495         if (RT_SUCCESS(rc))
    2496         {
    2497             if (RTFileExists(szFile))
    2498                 rc = scmSettingsLoadFile(pSettings, szFile);
    2499             if (RT_SUCCESS(rc))
    2500             {
    2501                 scmSettingsStackPush(ppSettingsStack, pSettings);
    2502                 return VINF_SUCCESS;
    2503             }
    2504 
    2505             scmSettingsDestroy(pSettings);
    2506         }
    2507     }
    2508     return rc;
    2509 }
    2510 
    2511 
    2512 /**
    2513  * Pops a settings set off the stack.
    2514  *
    2515  * @returns The popped setttings.
    2516  * @param   ppSettingsStack     The pointer to the pointer to the top stack
    2517  *                              element.  This will be used as input and output.
    2518  */
    2519 static PSCMSETTINGS scmSettingsStackPop(PSCMSETTINGS *ppSettingsStack)
    2520 {
    2521     PSCMSETTINGS pRet = *ppSettingsStack;
    2522     PSCMSETTINGS pNew = pRet ? pRet->pDown : NULL;
    2523     *ppSettingsStack = pNew;
    2524     if (pNew)
    2525         pNew->pUp    = NULL;
    2526     if (pRet)
    2527     {
    2528         pRet->pUp    = NULL;
    2529         pRet->pDown  = NULL;
    2530     }
    2531     return pRet;
    2532 }
    2533 
    2534 /**
    2535  * Pops and destroys the top entry of the stack.
    2536  *
    2537  * @param   ppSettingsStack     The pointer to the pointer to the top stack
    2538  *                              element.  This will be used as input and output.
    2539  */
    2540 static void scmSettingsStackPopAndDestroy(PSCMSETTINGS *ppSettingsStack)
    2541 {
    2542     scmSettingsDestroy(scmSettingsStackPop(ppSettingsStack));
    2543 }
    2544 
    2545 /**
    2546  * Constructs the base settings for the specified file name.
    2547  *
    2548  * @returns IPRT status code.
    2549  * @param   pSettingsStack      The top element on the settings stack.
    2550  * @param   pszFilename         The file name.
    2551  * @param   pszBasename         The base name (pointer within @a pszFilename).
    2552  * @param   cchBasename         The length of the base name.  (For passing to
    2553  *                              RTStrSimplePatternMultiMatch.)
    2554  * @param   pBase               Base settings to initialize.
    2555  */
    2556 static int scmSettingsStackMakeFileBase(PCSCMSETTINGS pSettingsStack, const char *pszFilename,
    2557                                         const char *pszBasename, size_t cchBasename, PSCMSETTINGSBASE pBase)
    2558 {
    2559     int rc = scmSettingsBaseInitAndCopy(pBase, &pSettingsStack->Base);
    2560     if (RT_SUCCESS(rc))
    2561     {
    2562         /* find the bottom entry in the stack. */
    2563         PCSCMSETTINGS pCur = pSettingsStack;
    2564         while (pCur->pDown)
    2565             pCur = pCur->pDown;
    2566 
    2567         /* Work our way up thru the stack and look for matching pairs. */
    2568         while (pCur)
    2569         {
    2570             size_t const cPairs = pCur->cPairs;
    2571             if (cPairs)
    2572             {
    2573                 for (size_t i = 0; i < cPairs; i++)
    2574                     if (   RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
    2575                                                         pszBasename,  cchBasename, NULL)
    2576                         || RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
    2577                                                         pszFilename,  RTSTR_MAX, NULL))
    2578                     {
    2579                         rc = scmSettingsBaseParseString(pBase, pCur->paPairs[i].pszOptions);
    2580                         if (RT_FAILURE(rc))
    2581                             break;
    2582                     }
    2583                 if (RT_FAILURE(rc))
    2584                     break;
    2585             }
    2586 
    2587             /* advance */
    2588             pCur = pCur->pUp;
    2589         }
    2590     }
    2591     if (RT_FAILURE(rc))
    2592         scmSettingsBaseDelete(pBase);
    2593     return rc;
    2594 }
    2595 
    2596 
    2597 /* -=-=-=-=-=- misc -=-=-=-=-=- */
    2598 
    2599 
    2600 /**
    2601  * Prints a verbose message if the level is high enough.
    2602  *
    2603  * @param   pState              The rewrite state.  Optional.
    2604  * @param   iLevel              The required verbosity level.
    2605  * @param   pszFormat           The message format string.  Can be NULL if we
    2606  *                              only want to trigger the per file message.
    2607  * @param   ...                 Format arguments.
    2608  */
    2609 static void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)
    2610 {
    2611     if (iLevel <= g_iVerbosity)
    2612     {
    2613         if (pState && !pState->fFirst)
    2614         {
    2615             RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
    2616             pState->fFirst = true;
    2617         }
    2618         if (pszFormat)
    2619         {
    2620             RTPrintf(pState
    2621                      ? "%s: info:   "
    2622                      : "%s: info: ",
    2623                      g_szProgName);
    2624             va_list va;
    2625             va_start(va, pszFormat);
    2626             RTPrintfV(pszFormat, va);
    2627             va_end(va);
    2628         }
    2629     }
    2630 }
    2631 
    2632 
    2633 /* -=-=-=-=-=- subversion -=-=-=-=-=- */
    2634 
    2635 #define SCM_WITHOUT_LIBSVN
    2636 
    2637 #ifdef SCM_WITHOUT_LIBSVN
    2638 
    2639 /**
    2640  * Callback that is call for each path to search.
    2641  */
    2642 static DECLCALLBACK(int) scmSvnFindSvnBinaryCallback(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
    2643 {
    2644     char   *pszDst = (char *)pvUser1;
    2645     size_t  cchDst = (size_t)pvUser2;
    2646     if (cchDst > cchPath)
    2647     {
    2648         memcpy(pszDst, pchPath, cchPath);
    2649         pszDst[cchPath] = '\0';
    2650 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
    2651         int rc = RTPathAppend(pszDst, cchDst, "svn.exe");
    2652 #else
    2653         int rc = RTPathAppend(pszDst, cchDst, "svn");
    2654 #endif
    2655         if (   RT_SUCCESS(rc)
    2656             && RTFileExists(pszDst))
    2657             return VINF_SUCCESS;
    2658     }
    2659     return VERR_TRY_AGAIN;
    2660 }
    2661 
    2662 
    2663 /**
    2664  * Finds the svn binary.
    2665  *
    2666  * @param   pszPath             Where to store it.  Worst case, we'll return
    2667  *                              "svn" here.
    2668  * @param   cchPath             The size of the buffer pointed to by @a pszPath.
    2669  */
    2670 static void scmSvnFindSvnBinary(char *pszPath, size_t cchPath)
    2671 {
    2672     /** @todo code page fun... */
    2673     Assert(cchPath >= sizeof("svn"));
    2674 #ifdef RT_OS_WINDOWS
    2675     const char *pszEnvVar = RTEnvGet("Path");
    2676 #else
    2677     const char *pszEnvVar = RTEnvGet("PATH");
    2678 #endif
    2679     if (pszPath)
    2680     {
    2681 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
    2682         int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);
    2683 #else
    2684         int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);
    2685 #endif
    2686         if (RT_SUCCESS(rc))
    2687             return;
    2688     }
    2689     strcpy(pszPath, "svn");
    2690 }
    2691 
    2692 
    2693 /**
    2694  * Construct a dot svn filename for the file being rewritten.
    2695  *
    2696  * @returns IPRT status code.
    2697  * @param   pState              The rewrite state (for the name).
    2698  * @param   pszDir              The directory, including ".svn/".
    2699  * @param   pszSuff             The filename suffix.
    2700  * @param   pszDst              The output buffer.  RTPATH_MAX in size.
    2701  */
    2702 static int scmSvnConstructName(PSCMRWSTATE pState, const char *pszDir, const char *pszSuff, char *pszDst)
    2703 {
    2704     strcpy(pszDst, pState->pszFilename); /* ASSUMES sizeof(szBuf) <= sizeof(szPath) */
    2705     RTPathStripFilename(pszDst);
    2706 
    2707     int rc = RTPathAppend(pszDst, RTPATH_MAX, pszDir);
    2708     if (RT_SUCCESS(rc))
    2709     {
    2710         rc = RTPathAppend(pszDst, RTPATH_MAX, RTPathFilename(pState->pszFilename));
    2711         if (RT_SUCCESS(rc))
    2712         {
    2713             size_t cchDst  = strlen(pszDst);
    2714             size_t cchSuff = strlen(pszSuff);
    2715             if (cchDst + cchSuff < RTPATH_MAX)
    2716             {
    2717                 memcpy(&pszDst[cchDst], pszSuff, cchSuff + 1);
    2718                 return VINF_SUCCESS;
    2719             }
    2720             else
    2721                 rc = VERR_BUFFER_OVERFLOW;
    2722         }
    2723     }
    2724     return rc;
    2725 }
    2726 
    2727 /**
    2728  * Interprets the specified string as decimal numbers.
    2729  *
    2730  * @returns true if parsed successfully, false if not.
    2731  * @param   pch                 The string (not terminated).
    2732  * @param   cch                 The string length.
    2733  * @param   pu                  Where to return the value.
    2734  */
    2735 static bool scmSvnReadNumber(const char *pch, size_t cch, size_t *pu)
    2736 {
    2737     size_t u = 0;
    2738     while (cch-- > 0)
    2739     {
    2740         char ch = *pch++;
    2741         if (ch < '0' || ch > '9')
    2742             return false;
    2743         u *= 10;
    2744         u += ch - '0';
    2745     }
    2746     *pu = u;
    2747     return true;
    2748 }
    2749 
    2750 #endif /* SCM_WITHOUT_LIBSVN */
    2751 
    2752 /**
    2753  * Checks if the file we're operating on is part of a SVN working copy.
    2754  *
    2755  * @returns true if it is, false if it isn't or we cannot tell.
    2756  * @param   pState              The rewrite state to work on.
    2757  */
    2758 static bool scmSvnIsInWorkingCopy(PSCMRWSTATE pState)
    2759 {
    2760 #ifdef SCM_WITHOUT_LIBSVN
    2761     /*
    2762      * Hack: check if the .svn/text-base/<file>.svn-base file exists.
    2763      */
    2764     char szPath[RTPATH_MAX];
    2765     int rc = scmSvnConstructName(pState, ".svn/text-base/", ".svn-base", szPath);
    2766     if (RT_SUCCESS(rc))
    2767         return RTFileExists(szPath);
    2768 
    2769 #else
    2770     NOREF(pState);
    2771 #endif
    2772     return false;
    2773 }
    2774 
    2775 /**
    2776  * Queries the value of an SVN property.
    2777  *
    2778  * This will automatically adjust for scheduled changes.
    2779  *
    2780  * @returns IPRT status code.
    2781  * @retval  VERR_INVALID_STATE if not a SVN WC file.
    2782  * @retval  VERR_NOT_FOUND if the property wasn't found.
    2783  * @param   pState              The rewrite state to work on.
    2784  * @param   pszName             The property name.
    2785  * @param   ppszValue           Where to return the property value.  Free this
    2786  *                              using RTStrFree.  Optional.
    2787  */
    2788 static int scmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)
    2789 {
    2790     /*
    2791      * Look it up in the scheduled changes.
    2792      */
    2793     uint32_t i = pState->cSvnPropChanges;
    2794     while (i-- > 0)
    2795         if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
    2796         {
    2797             const char *pszValue = pState->paSvnPropChanges[i].pszValue;
    2798             if (!pszValue)
    2799                 return VERR_NOT_FOUND;
    2800             if (ppszValue)
    2801                 return RTStrDupEx(ppszValue, pszValue);
    2802             return VINF_SUCCESS;
    2803         }
    2804 
    2805 #ifdef SCM_WITHOUT_LIBSVN
    2806     /*
    2807      * Hack: Read the .svn/props/<file>.svn-work file exists.
    2808      */
    2809     char szPath[RTPATH_MAX];
    2810     int rc = scmSvnConstructName(pState, ".svn/props/", ".svn-work", szPath);
    2811     if (RT_SUCCESS(rc) && !RTFileExists(szPath))
    2812         rc = scmSvnConstructName(pState, ".svn/prop-base/", ".svn-base", szPath);
    2813     if (RT_SUCCESS(rc))
    2814     {
    2815         SCMSTREAM Stream;
    2816         rc = ScmStreamInitForReading(&Stream, szPath);
    2817         if (RT_SUCCESS(rc))
    2818         {
    2819             /*
    2820              * The current format is K len\n<name>\nV len\n<value>\n" ... END.
    2821              */
    2822             rc = VERR_NOT_FOUND;
    2823             size_t const    cchName = strlen(pszName);
    2824             SCMEOL          enmEol;
    2825             size_t          cchLine;
    2826             const char     *pchLine;
    2827             while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
    2828             {
    2829                 /*
    2830                  * Parse the 'K num' / 'END' line.
    2831                  */
    2832                 if (   cchLine == 3
    2833                     && !memcmp(pchLine, "END", 3))
    2834                     break;
    2835                 size_t cchKey;
    2836                 if (   cchLine < 3
    2837                     || pchLine[0] != 'K'
    2838                     || pchLine[1] != ' '
    2839                     || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey)
    2840                     || cchKey == 0
    2841                     || cchKey > 4096)
    2842                 {
    2843                     RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
    2844                     rc = VERR_PARSE_ERROR;
    2845                     break;
    2846                 }
    2847 
    2848                 /*
    2849                  * Match the key and skip to the value line.  Don't bother with
    2850                  * names containing EOL markers.
    2851                  */
    2852                 size_t const offKey = ScmStreamTell(&Stream);
    2853                 bool fMatch = cchName == cchKey;
    2854                 if (fMatch)
    2855                 {
    2856                     pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
    2857                     if (!pchLine)
    2858                         break;
    2859                     fMatch = cchLine == cchName
    2860                           && !memcmp(pchLine, pszName, cchName);
    2861                 }
    2862 
    2863                 if (RT_FAILURE(ScmStreamSeekAbsolute(&Stream, offKey + cchKey)))
    2864                     break;
    2865                 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
    2866                     break;
    2867 
    2868                 /*
    2869                  * Read and Parse the 'V num' line.
    2870                  */
    2871                 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
    2872                 if (!pchLine)
    2873                     break;
    2874                 size_t cchValue;
    2875                 if (   cchLine < 3
    2876                     || pchLine[0] != 'V'
    2877                     || pchLine[1] != ' '
    2878                     || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchValue)
    2879                     || cchValue > _1M)
    2880                 {
    2881                     RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
    2882                     rc = VERR_PARSE_ERROR;
    2883                     break;
    2884                 }
    2885 
    2886                 /*
    2887                  * If we have a match, allocate a return buffer and read the
    2888                  * value into it.  Otherwise skip this value and continue
    2889                  * searching.
    2890                  */
    2891                 if (fMatch)
    2892                 {
    2893                     if (!ppszValue)
    2894                         rc = VINF_SUCCESS;
    2895                     else
    2896                     {
    2897                         char *pszValue;
    2898                         rc = RTStrAllocEx(&pszValue, cchValue + 1);
    2899                         if (RT_SUCCESS(rc))
    2900                         {
    2901                             rc = ScmStreamRead(&Stream, pszValue, cchValue);
    2902                             if (RT_SUCCESS(rc))
    2903                                 *ppszValue = pszValue;
    2904                             else
    2905                                 RTStrFree(pszValue);
    2906                         }
    2907                     }
    2908                     break;
    2909                 }
    2910 
    2911                 if (RT_FAILURE(ScmStreamSeekRelative(&Stream, cchValue)))
    2912                     break;
    2913                 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
    2914                     break;
    2915             }
    2916 
    2917             if (RT_FAILURE(ScmStreamGetStatus(&Stream)))
    2918             {
    2919                 rc = ScmStreamGetStatus(&Stream);
    2920                 RTMsgError("%s: stream error %Rrc\n", szPath, rc);
    2921             }
    2922             ScmStreamDelete(&Stream);
    2923         }
    2924     }
    2925 
    2926     if (rc == VERR_FILE_NOT_FOUND)
    2927         rc = VERR_NOT_FOUND;
    2928     return rc;
    2929 
    2930 #else
    2931     NOREF(pState);
    2932 #endif
    2933     return VERR_NOT_FOUND;
    2934 }
    2935 
    2936 
    2937 /**
    2938  * Schedules the setting of a property.
    2939  *
    2940  * @returns IPRT status code.
    2941  * @retval  VERR_INVALID_STATE if not a SVN WC file.
    2942  * @param   pState              The rewrite state to work on.
    2943  * @param   pszName             The name of the property to set.
    2944  * @param   pszValue            The value.  NULL means deleting it.
    2945  */
    2946 static int scmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue)
    2947 {
    2948     /*
    2949      * Update any existing entry first.
    2950      */
    2951     size_t i = pState->cSvnPropChanges;
    2952     while (i-- > 0)
    2953         if (!strcmp(pState->paSvnPropChanges[i].pszName,  pszName))
    2954         {
    2955             if (!pszValue)
    2956             {
    2957                 RTStrFree(pState->paSvnPropChanges[i].pszValue);
    2958                 pState->paSvnPropChanges[i].pszValue = NULL;
    2959             }
    2960             else
    2961             {
    2962                 char *pszCopy;
    2963                 int rc = RTStrDupEx(&pszCopy, pszValue);
    2964                 if (RT_FAILURE(rc))
    2965                     return rc;
    2966                 pState->paSvnPropChanges[i].pszValue = pszCopy;
    2967             }
    2968             return VINF_SUCCESS;
    2969         }
    2970 
    2971     /*
    2972      * Insert a new entry.
    2973      */
    2974     i = pState->cSvnPropChanges;
    2975     if ((i % 32) == 0)
    2976     {
    2977         void *pvNew = RTMemRealloc(pState->paSvnPropChanges, (i + 32) * sizeof(SCMSVNPROP));
    2978         if (!pvNew)
    2979             return VERR_NO_MEMORY;
    2980         pState->paSvnPropChanges = (PSCMSVNPROP)pvNew;
    2981     }
    2982 
    2983     pState->paSvnPropChanges[i].pszName  = RTStrDup(pszName);
    2984     pState->paSvnPropChanges[i].pszValue = pszValue ? RTStrDup(pszValue) : NULL;
    2985     if (   pState->paSvnPropChanges[i].pszName
    2986         && (pState->paSvnPropChanges[i].pszValue || !pszValue) )
    2987         pState->cSvnPropChanges = i + 1;
    2988     else
    2989     {
    2990         RTStrFree(pState->paSvnPropChanges[i].pszName);
    2991         pState->paSvnPropChanges[i].pszName = NULL;
    2992         RTStrFree(pState->paSvnPropChanges[i].pszValue);
    2993         pState->paSvnPropChanges[i].pszValue = NULL;
    2994         return VERR_NO_MEMORY;
    2995     }
    2996     return VINF_SUCCESS;
    2997 }
    2998 
    2999 
    3000 /**
    3001  * Schedules a property deletion.
    3002  *
    3003  * @returns IPRT status code.
    3004  * @param   pState              The rewrite state to work on.
    3005  * @param   pszName             The name of the property to delete.
    3006  */
    3007 static int scmSvnDelProperty(PSCMRWSTATE pState, const char *pszName)
    3008 {
    3009     return scmSvnSetProperty(pState, pszName, NULL);
    3010 }
    3011 
    3012 
    3013 /**
    3014  * Applies any SVN property changes to the work copy of the file.
    3015  *
    3016  * @returns IPRT status code.
    3017  * @param   pState              The rewrite state which SVN property changes
    3018  *                              should be applied.
    3019  */
    3020 static int scmSvnDisplayChanges(PSCMRWSTATE pState)
    3021 {
    3022     size_t i = pState->cSvnPropChanges;
    3023     while (i-- > 0)
    3024     {
    3025         const char *pszName  = pState->paSvnPropChanges[i].pszName;
    3026         const char *pszValue = pState->paSvnPropChanges[i].pszValue;
    3027         if (pszValue)
    3028             ScmVerbose(pState, 0, "svn ps '%s' '%s'  %s\n", pszName, pszValue, pState->pszFilename);
    3029         else
    3030             ScmVerbose(pState, 0, "svn pd '%s'  %s\n", pszName, pszValue, pState->pszFilename);
    3031     }
    3032 
    3033     return VINF_SUCCESS;
    3034 }
    3035 
    3036 /**
    3037  * Applies any SVN property changes to the work copy of the file.
    3038  *
    3039  * @returns IPRT status code.
    3040  * @param   pState              The rewrite state which SVN property changes
    3041  *                              should be applied.
    3042  */
    3043 static int scmSvnApplyChanges(PSCMRWSTATE pState)
    3044 {
    3045 #ifdef SCM_WITHOUT_LIBSVN
    3046     /*
    3047      * This sucks. We gotta find svn(.exe).
    3048      */
    3049     static char s_szSvnPath[RTPATH_MAX];
    3050     if (s_szSvnPath[0] == '\0')
    3051         scmSvnFindSvnBinary(s_szSvnPath, sizeof(s_szSvnPath));
    3052 
    3053     /*
    3054      * Iterate thru the changes and apply them by starting the svn client.
    3055      */
    3056     for (size_t i = 0; i <pState->cSvnPropChanges; i++)
    3057     {
    3058         const char *apszArgv[6];
    3059         apszArgv[0] = s_szSvnPath;
    3060         apszArgv[1] = pState->paSvnPropChanges[i].pszValue ? "ps" : "pd";
    3061         apszArgv[2] = pState->paSvnPropChanges[i].pszName;
    3062         int iArg = 3;
    3063         if (pState->paSvnPropChanges[i].pszValue)
    3064             apszArgv[iArg++] = pState->paSvnPropChanges[i].pszValue;
    3065         apszArgv[iArg++] = pState->pszFilename;
    3066         apszArgv[iArg++] = NULL;
    3067         ScmVerbose(pState, 2, "executing: %s %s %s %s %s\n",
    3068                    apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4]);
    3069 
    3070         RTPROCESS pid;
    3071         int rc = RTProcCreate(s_szSvnPath, apszArgv, RTENV_DEFAULT, 0 /*fFlags*/, &pid);
    3072         if (RT_SUCCESS(rc))
    3073         {
    3074             RTPROCSTATUS Status;
    3075             rc = RTProcWait(pid, RTPROCWAIT_FLAGS_BLOCK, &Status);
    3076             if (    RT_SUCCESS(rc)
    3077                 &&  (   Status.enmReason != RTPROCEXITREASON_NORMAL
    3078                      || Status.iStatus != 0) )
    3079             {
    3080                 RTMsgError("%s: %s %s %s %s %s -> %s %u\n",
    3081                            pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4],
    3082                            Status.enmReason == RTPROCEXITREASON_NORMAL   ? "exit code"
    3083                            : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"
    3084                            : Status.enmReason == RTPROCEXITREASON_ABEND  ? "abnormal end"
    3085                            : "abducted by alien",
    3086                            Status.iStatus);
    3087                 return VERR_GENERAL_FAILURE;
    3088             }
    3089         }
    3090         if (RT_FAILURE(rc))
    3091         {
    3092             RTMsgError("%s: error executing %s %s %s %s %s: %Rrc\n",
    3093                        pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4], rc);
    3094             return rc;
    3095         }
    3096     }
    3097 
    3098     return VINF_SUCCESS;
    3099 #else
    3100     return VERR_NOT_IMPLEMENTED;
    3101 #endif
    3102 }
    3103 
    3104 
    3105 /* -=-=-=-=-=- rewriters -=-=-=-=-=- */
    3106 
    3107 
    3108 /**
    3109  * Strip trailing blanks (space & tab).
    3110  *
    3111  * @returns True if modified, false if not.
    3112  * @param   pIn                 The input stream.
    3113  * @param   pOut                The output stream.
    3114  * @param   pSettings           The settings.
    3115  */
    3116 static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3117 {
    3118     if (!pSettings->fStripTrailingBlanks)
    3119         return false;
    3120 
    3121     bool        fModified = false;
    3122     SCMEOL      enmEol;
    3123     size_t      cchLine;
    3124     const char *pchLine;
    3125     while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
    3126     {
    3127         int rc;
    3128         if (    cchLine == 0
    3129             ||  !RT_C_IS_BLANK(pchLine[cchLine - 1]) )
    3130             rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    3131         else
    3132         {
    3133             cchLine--;
    3134             while (cchLine > 0 && RT_C_IS_BLANK(pchLine[cchLine - 1]))
    3135                 cchLine--;
    3136             rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    3137             fModified = true;
    3138         }
    3139         if (RT_FAILURE(rc))
    3140             return false;
    3141     }
    3142     if (fModified)
    3143         ScmVerbose(pState, 2, " * Stripped trailing blanks\n");
    3144     return fModified;
    3145 }
    3146 
    3147 /**
    3148  * Expand tabs.
    3149  *
    3150  * @returns True if modified, false if not.
    3151  * @param   pIn                 The input stream.
    3152  * @param   pOut                The output stream.
    3153  * @param   pSettings           The settings.
    3154  */
    3155 static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3156 {
    3157     if (!pSettings->fConvertTabs)
    3158         return false;
    3159 
    3160     size_t const    cchTab = pSettings->cchTab;
    3161     bool            fModified = false;
    3162     SCMEOL          enmEol;
    3163     size_t          cchLine;
    3164     const char     *pchLine;
    3165     while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
    3166     {
    3167         int rc;
    3168         const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);
    3169         if (!pchTab)
    3170             rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    3171         else
    3172         {
    3173             size_t      offTab   = 0;
    3174             const char *pchChunk = pchLine;
    3175             for (;;)
    3176             {
    3177                 size_t  cchChunk = pchTab - pchChunk;
    3178                 offTab += cchChunk;
    3179                 ScmStreamWrite(pOut, pchChunk, cchChunk);
    3180 
    3181                 size_t  cchToTab = cchTab - offTab % cchTab;
    3182                 ScmStreamWrite(pOut, g_szTabSpaces, cchToTab);
    3183                 offTab += cchToTab;
    3184 
    3185                 pchChunk = pchTab + 1;
    3186                 size_t  cchLeft  = cchLine - (pchChunk - pchLine);
    3187                 pchTab = (const char *)memchr(pchChunk, '\t', cchLeft);
    3188                 if (!pchTab)
    3189                 {
    3190                     rc = ScmStreamPutLine(pOut, pchChunk, cchLeft, enmEol);
    3191                     break;
    3192                 }
    3193             }
    3194 
    3195             fModified = true;
    3196         }
    3197         if (RT_FAILURE(rc))
    3198             return false;
    3199     }
    3200     if (fModified)
    3201         ScmVerbose(pState, 2, " * Expanded tabs\n");
    3202     return fModified;
    3203 }
    3204 
    3205 /**
    3206  * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF.
    3207  *
    3208  * @returns true if modifications were made, false if not.
    3209  * @param   pIn                 The input stream.
    3210  * @param   pOut                The output stream.
    3211  * @param   pSettings           The settings.
    3212  * @param   enmDesiredEol       The desired end of line indicator type.
    3213  * @param   pszDesiredSvnEol    The desired svn:eol-style.
    3214  */
    3215 static bool rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
    3216                              SCMEOL enmDesiredEol, const char *pszDesiredSvnEol)
    3217 {
    3218     if (!pSettings->fConvertEol)
    3219         return false;
    3220 
    3221     bool        fModified = false;
    3222     SCMEOL      enmEol;
    3223     size_t      cchLine;
    3224     const char *pchLine;
    3225     while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
    3226     {
    3227         if (   enmEol != enmDesiredEol
    3228             && enmEol != SCMEOL_NONE)
    3229         {
    3230             fModified = true;
    3231             enmEol = enmDesiredEol;
    3232         }
    3233         int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
    3234         if (RT_FAILURE(rc))
    3235             return false;
    3236     }
    3237     if (fModified)
    3238         ScmVerbose(pState, 2, " * Converted EOL markers\n");
    3239 
    3240     /* Check svn:eol-style if appropriate */
    3241     if (   pSettings->fSetSvnEol
    3242         && scmSvnIsInWorkingCopy(pState))
    3243     {
    3244         char *pszEol;
    3245         int rc = scmSvnQueryProperty(pState, "svn:eol-style", &pszEol);
    3246         if (   (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol))
    3247             || rc == VERR_NOT_FOUND)
    3248         {
    3249             if (rc == VERR_NOT_FOUND)
    3250                 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (missing)\n", pszDesiredSvnEol);
    3251             else
    3252                 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol);
    3253             int rc2 = scmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol);
    3254             if (RT_FAILURE(rc2))
    3255                 RTMsgError("scmSvnSetProperty: %Rrc\n", rc2); /** @todo propagate the error somehow... */
    3256         }
    3257         if (RT_SUCCESS(rc))
    3258             RTStrFree(pszEol);
    3259     }
    3260 
    3261     /** @todo also check the subversion svn:eol-style state! */
    3262     return fModified;
    3263 }
    3264 
    3265 /**
    3266  * Force native end of line indicator.
    3267  *
    3268  * @returns true if modifications were made, false if not.
    3269  * @param   pIn                 The input stream.
    3270  * @param   pOut                The output stream.
    3271  * @param   pSettings           The settings.
    3272  */
    3273 static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3274 {
    3275 #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
    3276     return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "native");
    3277 #else
    3278     return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF,   "native");
    3279 #endif
    3280 }
    3281 
    3282 /**
    3283  * Force the stream to use LF as the end of line indicator.
    3284  *
    3285  * @returns true if modifications were made, false if not.
    3286  * @param   pIn                 The input stream.
    3287  * @param   pOut                The output stream.
    3288  * @param   pSettings           The settings.
    3289  */
    3290 static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3291 {
    3292     return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "LF");
    3293 }
    3294 
    3295 /**
    3296  * Force the stream to use CRLF as the end of line indicator.
    3297  *
    3298  * @returns true if modifications were made, false if not.
    3299  * @param   pIn                 The input stream.
    3300  * @param   pOut                The output stream.
    3301  * @param   pSettings           The settings.
    3302  */
    3303 static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3304 {
    3305     return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "CRLF");
    3306 }
    3307 
    3308 /**
    3309  * Strip trailing blank lines and/or make sure there is exactly one blank line
    3310  * at the end of the file.
    3311  *
    3312  * @returns true if modifications were made, false if not.
    3313  * @param   pIn                 The input stream.
    3314  * @param   pOut                The output stream.
    3315  * @param   pSettings           The settings.
    3316  *
    3317  * @remarks ASSUMES trailing white space has been removed already.
    3318  */
    3319 static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3320 {
    3321     if (   !pSettings->fStripTrailingLines
    3322         && !pSettings->fForceTrailingLine
    3323         && !pSettings->fForceFinalEol)
    3324         return false;
    3325 
    3326     size_t const cLines = ScmStreamCountLines(pIn);
    3327 
    3328     /* Empty files remains empty. */
    3329     if (cLines <= 1)
    3330         return false;
    3331 
    3332     /* Figure out if we need to adjust the number of lines or not. */
    3333     size_t cLinesNew = cLines;
    3334 
    3335     if (   pSettings->fStripTrailingLines
    3336         && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
    3337     {
    3338         while (   cLinesNew > 1
    3339                && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))
    3340             cLinesNew--;
    3341     }
    3342 
    3343     if (    pSettings->fForceTrailingLine
    3344         && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
    3345         cLinesNew++;
    3346 
    3347     bool fFixMissingEol = pSettings->fForceFinalEol
    3348                        && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;
    3349 
    3350     if (   !fFixMissingEol
    3351         && cLines == cLinesNew)
    3352         return false;
    3353 
    3354     /* Copy the number of lines we've arrived at. */
    3355     ScmStreamRewindForReading(pIn);
    3356 
    3357     size_t cCopied = RT_MIN(cLinesNew, cLines);
    3358     ScmStreamCopyLines(pOut, pIn, cCopied);
    3359 
    3360     if (cCopied != cLinesNew)
    3361     {
    3362         while (cCopied++ < cLinesNew)
    3363             ScmStreamPutLine(pOut, "", 0, ScmStreamGetEol(pIn));
    3364     }
    3365     /* Fix missing EOL if required. */
    3366     else if (fFixMissingEol)
    3367     {
    3368         if (ScmStreamGetEol(pIn) == SCMEOL_LF)
    3369             ScmStreamWrite(pOut, "\n", 1);
    3370         else
    3371             ScmStreamWrite(pOut, "\r\n", 2);
    3372     }
    3373 
    3374     ScmVerbose(pState, 2, " * Adjusted trailing blank lines\n");
    3375     return true;
    3376 }
    3377 
    3378 /**
    3379  * Make sure there is no svn:executable keyword on the current file.
    3380  *
    3381  * @returns false - the state carries these kinds of changes.
    3382  * @param   pState              The rewriter state.
    3383  * @param   pIn                 The input stream.
    3384  * @param   pOut                The output stream.
    3385  * @param   pSettings           The settings.
    3386  */
    3387 static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3388 {
    3389     if (   !pSettings->fSetSvnExecutable
    3390         || !scmSvnIsInWorkingCopy(pState))
    3391         return false;
    3392 
    3393     int rc = scmSvnQueryProperty(pState, "svn:executable", NULL);
    3394     if (RT_SUCCESS(rc))
    3395     {
    3396         ScmVerbose(pState, 2, " * removing svn:executable\n");
    3397         rc = scmSvnDelProperty(pState, "svn:executable");
    3398         if (RT_FAILURE(rc))
    3399             RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
    3400     }
    3401     return false;
    3402 }
    3403 
    3404 /**
    3405  * Make sure the Id and Revision keywords are expanded.
    3406  *
    3407  * @returns false - the state carries these kinds of changes.
    3408  * @param   pState              The rewriter state.
    3409  * @param   pIn                 The input stream.
    3410  * @param   pOut                The output stream.
    3411  * @param   pSettings           The settings.
    3412  */
    3413 static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3414 {
    3415     if (   !pSettings->fSetSvnKeywords
    3416         || !scmSvnIsInWorkingCopy(pState))
    3417         return false;
    3418 
    3419     char *pszKeywords;
    3420     int rc = scmSvnQueryProperty(pState, "svn:keywords", &pszKeywords);
    3421     if (    RT_SUCCESS(rc)
    3422         && (   !strstr(pszKeywords, "Id") /** @todo need some function for finding a word in a string.  */
    3423             || !strstr(pszKeywords, "Revision")) )
    3424     {
    3425         if (!strstr(pszKeywords, "Id") && !strstr(pszKeywords, "Revision"))
    3426             rc = RTStrAAppend(&pszKeywords, " Id Revision");
    3427         else if (!strstr(pszKeywords, "Id"))
    3428             rc = RTStrAAppend(&pszKeywords, " Id");
    3429         else
    3430             rc = RTStrAAppend(&pszKeywords, " Revision");
    3431         if (RT_SUCCESS(rc))
    3432         {
    3433             ScmVerbose(pState, 2, " * changing svn:keywords to '%s'\n", pszKeywords);
    3434             rc = scmSvnSetProperty(pState, "svn:keywords", pszKeywords);
    3435             if (RT_FAILURE(rc))
    3436                 RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
    3437         }
    3438         else
    3439             RTMsgError("RTStrAppend: %Rrc\n", rc); /** @todo error propagation here.. */
    3440         RTStrFree(pszKeywords);
    3441     }
    3442     else if (rc == VERR_NOT_FOUND)
    3443     {
    3444         ScmVerbose(pState, 2, " * setting svn:keywords to 'Id Revision'\n");
    3445         rc = scmSvnSetProperty(pState, "svn:keywords", "Id Revision");
    3446         if (RT_FAILURE(rc))
    3447             RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
    3448     }
    3449     else if (RT_SUCCESS(rc))
    3450         RTStrFree(pszKeywords);
    3451 
    3452     return false;
    3453 }
    3454 
    3455 /**
    3456  * Makefile.kup are empty files, enforce this.
    3457  *
    3458  * @returns true if modifications were made, false if not.
    3459  * @param   pIn                 The input stream.
    3460  * @param   pOut                The output stream.
    3461  * @param   pSettings           The settings.
    3462  */
    3463 static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3464 {
    3465     /* These files should be zero bytes. */
    3466     if (pIn->cb == 0)
    3467         return false;
    3468     ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
    3469     return true;
    3470 }
    3471 
    3472 /**
    3473  * Rewrite a kBuild makefile.
    3474  *
    3475  * @returns true if modifications were made, false if not.
    3476  * @param   pIn                 The input stream.
    3477  * @param   pOut                The output stream.
    3478  * @param   pSettings           The settings.
    3479  *
    3480  * @todo
    3481  *
    3482  * Ideas for Makefile.kmk and Config.kmk:
    3483  *      - sort if1of/ifn1of sets.
    3484  *      - line continuation slashes should only be preceded by one space.
    3485  */
    3486 static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3487 {
    3488     return false;
    3489 }
    3490 
    3491 /**
    3492  * Rewrite a C/C++ source or header file.
    3493  *
    3494  * @returns true if modifications were made, false if not.
    3495  * @param   pIn                 The input stream.
    3496  * @param   pOut                The output stream.
    3497  * @param   pSettings           The settings.
    3498  *
    3499  * @todo
    3500  *
    3501  * Ideas for C/C++:
    3502  *      - space after if, while, for, switch
    3503  *      - spaces in for (i=0;i<x;i++)
    3504  *      - complex conditional, bird style.
    3505  *      - remove unnecessary parentheses.
    3506  *      - sort defined RT_OS_*||  and RT_ARCH
    3507  *      - sizeof without parenthesis.
    3508  *      - defined without parenthesis.
    3509  *      - trailing spaces.
    3510  *      - parameter indentation.
    3511  *      - space after comma.
    3512  *      - while (x--); -> multi line + comment.
    3513  *      - else statement;
    3514  *      - space between function and left parenthesis.
    3515  *      - TODO, XXX, @todo cleanup.
    3516  *      - Space before/after '*'.
    3517  *      - ensure new line at end of file.
    3518  *      - Indentation of precompiler statements (#ifdef, #defines).
    3519  *      - space between functions.
    3520  *      - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
    3521  */
    3522 static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
    3523 {
    3524 
    3525     return false;
    3526 }
    3527 
    3528 /* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */
    3529 
    3530 /**
    3531  * Processes a file.
    3532  *
    3533  * @returns IPRT status code.
    3534  * @param   pState              The rewriter state.
    3535  * @param   pszFilename         The file name.
    3536  * @param   pszBasename         The base name (pointer within @a pszFilename).
    3537  * @param   cchBasename         The length of the base name.  (For passing to
    3538  *                              RTStrSimplePatternMultiMatch.)
    3539  * @param   pBaseSettings       The base settings to use.  It's OK to modify
    3540  *                              these.
    3541  */
    3542 static int scmProcessFileInner(PSCMRWSTATE pState, const char *pszFilename, const char *pszBasename, size_t cchBasename,
    3543                                PSCMSETTINGSBASE pBaseSettings)
    3544 {
    3545     /*
    3546      * Do the file level filtering.
    3547      */
    3548     if (   pBaseSettings->pszFilterFiles
    3549         && *pBaseSettings->pszFilterFiles
    3550         && !RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterFiles, RTSTR_MAX, pszBasename, cchBasename, NULL))
    3551     {
    3552         ScmVerbose(NULL, 5, "skipping '%s': file filter mismatch\n", pszFilename);
    3553         return VINF_SUCCESS;
    3554     }
    3555     if (   pBaseSettings->pszFilterOutFiles
    3556         && *pBaseSettings->pszFilterOutFiles
    3557         && (   RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszBasename, cchBasename, NULL)
    3558             || RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszFilename, RTSTR_MAX, NULL)) )
    3559     {
    3560         ScmVerbose(NULL, 5, "skipping '%s': filterd out\n", pszFilename);
    3561         return VINF_SUCCESS;
    3562     }
    3563     if (   pBaseSettings->fOnlySvnFiles
    3564         && !scmSvnIsInWorkingCopy(pState))
    3565     {
    3566         ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename);
    3567         return VINF_SUCCESS;
    3568     }
    3569 
    3570     /*
    3571      * Try find a matching rewrite config for this filename.
    3572      */
    3573     PCSCMCFGENTRY pCfg = NULL;
    3574     for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
    3575         if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, pszBasename, cchBasename, NULL))
    3576         {
    3577             pCfg = &g_aConfigs[iCfg];
    3578             break;
    3579         }
    3580     if (!pCfg)
    3581     {
    3582         ScmVerbose(NULL, 4, "skipping '%s': no rewriters configured\n", pszFilename);
    3583         return VINF_SUCCESS;
    3584     }
    3585     ScmVerbose(pState, 4, "matched \"%s\"\n", pCfg->pszFilePattern);
    3586 
    3587     /*
    3588      * Create an input stream from the file and check that it's text.
    3589      */
    3590     SCMSTREAM Stream1;
    3591     int rc = ScmStreamInitForReading(&Stream1, pszFilename);
    3592     if (RT_FAILURE(rc))
    3593     {
    3594         RTMsgError("Failed to read '%s': %Rrc\n", pszFilename, rc);
    3595         return rc;
    3596     }
    3597     if (ScmStreamIsText(&Stream1))
    3598     {
    3599         ScmVerbose(pState, 3, NULL);
    3600 
    3601         /*
    3602          * Gather SCM and editor settings from the stream.
    3603          */
    3604         rc = scmSettingsBaseLoadFromDocument(pBaseSettings, &Stream1);
    3605         if (RT_SUCCESS(rc))
    3606         {
    3607             ScmStreamRewindForReading(&Stream1);
    3608 
    3609             /*
    3610              * Create two more streams for output and push the text thru all the
    3611              * rewriters, switching the two streams around when something is
    3612              * actually rewritten.  Stream1 remains unchanged.
    3613              */
    3614             SCMSTREAM Stream2;
    3615             rc = ScmStreamInitForWriting(&Stream2, &Stream1);
    3616             if (RT_SUCCESS(rc))
    3617             {
    3618                 SCMSTREAM Stream3;
    3619                 rc = ScmStreamInitForWriting(&Stream3, &Stream1);
    3620                 if (RT_SUCCESS(rc))
    3621                 {
    3622                     bool        fModified = false;
    3623                     PSCMSTREAM  pIn       = &Stream1;
    3624                     PSCMSTREAM  pOut      = &Stream2;
    3625                     for (size_t iRw = 0; iRw < pCfg->cRewriters; iRw++)
    3626                     {
    3627                         bool fRc = pCfg->papfnRewriter[iRw](pState, pIn, pOut, pBaseSettings);
    3628                         if (fRc)
    3629                         {
    3630                             PSCMSTREAM pTmp = pOut;
    3631                             pOut = pIn == &Stream1 ? &Stream3 : pIn;
    3632                             pIn  = pTmp;
    3633                             fModified = true;
    3634                         }
    3635                         ScmStreamRewindForReading(pIn);
    3636                         ScmStreamRewindForWriting(pOut);
    3637                     }
    3638 
    3639                     rc = ScmStreamGetStatus(&Stream1);
    3640                     if (RT_SUCCESS(rc))
    3641                         rc = ScmStreamGetStatus(&Stream2);
    3642                     if (RT_SUCCESS(rc))
    3643                         rc = ScmStreamGetStatus(&Stream3);
    3644                     if (RT_SUCCESS(rc))
    3645                     {
    3646                         /*
    3647                          * If rewritten, write it back to disk.
    3648                          */
    3649                         if (fModified)
    3650                         {
    3651                             if (!g_fDryRun)
    3652                             {
    3653                                 ScmVerbose(pState, 1, "writing modified file to \"%s%s\"\n", pszFilename, g_pszChangedSuff);
    3654                                 rc = ScmStreamWriteToFile(pIn, "%s%s", pszFilename, g_pszChangedSuff);
    3655                                 if (RT_FAILURE(rc))
    3656                                     RTMsgError("Error writing '%s%s': %Rrc\n", pszFilename, g_pszChangedSuff, rc);
    3657                             }
    3658                             else
    3659                             {
    3660                                 ScmVerbose(pState, 1, NULL);
    3661                                 ScmDiffStreams(pszFilename, &Stream1, pIn, g_fDiffIgnoreEol, g_fDiffIgnoreLeadingWS,
    3662                                                g_fDiffIgnoreTrailingWS, g_fDiffSpecialChars, pBaseSettings->cchTab, g_pStdOut);
    3663                                 ScmVerbose(pState, 2, "would have modified the file \"%s%s\"\n", pszFilename, g_pszChangedSuff);
    3664                             }
    3665                         }
    3666 
    3667                         /*
    3668                          * If pending SVN property changes, apply them.
    3669                          */
    3670                         if (pState->cSvnPropChanges && RT_SUCCESS(rc))
    3671                         {
    3672                             if (!g_fDryRun)
    3673                             {
    3674                                 rc = scmSvnApplyChanges(pState);
    3675                                 if (RT_FAILURE(rc))
    3676                                     RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc);
    3677                             }
    3678                             else
    3679                                 scmSvnDisplayChanges(pState);
    3680                         }
    3681 
    3682                         if (!fModified && !pState->cSvnPropChanges)
    3683                             ScmVerbose(pState, 3, "no change\n", pszFilename);
    3684                     }
    3685                     else
    3686                         RTMsgError("%s: stream error %Rrc\n", pszFilename);
    3687                     ScmStreamDelete(&Stream3);
    3688                 }
    3689                 else
    3690                     RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
    3691                 ScmStreamDelete(&Stream2);
    3692             }
    3693             else
    3694                 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
    3695         }
    3696         else
    3697             RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);
    3698     }
    3699     else
    3700         ScmVerbose(pState, 4, "not text file: \"%s\"\n", pszFilename);
    3701     ScmStreamDelete(&Stream1);
    3702 
    3703     return rc;
    3704 }
    3705 
    3706 /**
    3707  * Processes a file.
    3708  *
    3709  * This is just a wrapper for scmProcessFileInner for avoid wasting stack in the
    3710  * directory recursion method.
    3711  *
    3712  * @returns IPRT status code.
    3713  * @param   pszFilename         The file name.
    3714  * @param   pszBasename         The base name (pointer within @a pszFilename).
    3715  * @param   cchBasename         The length of the base name.  (For passing to
    3716  *                              RTStrSimplePatternMultiMatch.)
    3717  * @param   pSettingsStack      The settings stack (pointer to the top element).
    3718  */
    3719 static int scmProcessFile(const char *pszFilename, const char *pszBasename, size_t cchBasename,
    3720                           PSCMSETTINGS pSettingsStack)
    3721 {
    3722     SCMSETTINGSBASE Base;
    3723     int rc = scmSettingsStackMakeFileBase(pSettingsStack, pszFilename, pszBasename, cchBasename, &Base);
    3724     if (RT_SUCCESS(rc))
    3725     {
    3726         SCMRWSTATE State;
    3727         State.fFirst           = false;
    3728         State.pszFilename      = pszFilename;
    3729         State.cSvnPropChanges  = 0;
    3730         State.paSvnPropChanges = NULL;
    3731 
    3732         rc = scmProcessFileInner(&State, pszFilename, pszBasename, cchBasename, &Base);
    3733 
    3734         size_t i = State.cSvnPropChanges;
    3735         while (i-- > 0)
    3736         {
    3737             RTStrFree(State.paSvnPropChanges[i].pszName);
    3738             RTStrFree(State.paSvnPropChanges[i].pszValue);
    3739         }
    3740         RTMemFree(State.paSvnPropChanges);
    3741 
    3742         scmSettingsBaseDelete(&Base);
    3743     }
    3744     return rc;
    3745 }
    3746 
    3747 
    3748 /**
    3749  * Tries to correct RTDIRENTRY_UNKNOWN.
    3750  *
    3751  * @returns Corrected type.
    3752  * @param   pszPath             The path to the object in question.
    3753  */
    3754 static RTDIRENTRYTYPE scmFigureUnknownType(const char *pszPath)
    3755 {
    3756     RTFSOBJINFO Info;
    3757     int rc = RTPathQueryInfo(pszPath, &Info, RTFSOBJATTRADD_NOTHING);
    3758     if (RT_FAILURE(rc))
    3759         return RTDIRENTRYTYPE_UNKNOWN;
    3760     if (RTFS_IS_DIRECTORY(Info.Attr.fMode))
    3761         return RTDIRENTRYTYPE_DIRECTORY;
    3762     if (RTFS_IS_FILE(Info.Attr.fMode))
    3763         return RTDIRENTRYTYPE_FILE;
    3764     return RTDIRENTRYTYPE_UNKNOWN;
    3765 }
    3766 
    3767 /**
    3768  * Recurse into a sub-directory and process all the files and directories.
    3769  *
    3770  * @returns IPRT status code.
    3771  * @param   pszBuf              Path buffer containing the directory path on
    3772  *                              entry.  This ends with a dot.  This is passed
    3773  *                              along when recursing in order to save stack space
    3774  *                              and avoid needless copying.
    3775  * @param   cchDir              Length of our path in pszbuf.
    3776  * @param   pEntry              Directory entry buffer.  This is also passed
    3777  *                              along when recursing to save stack space.
    3778  * @param   pSettingsStack      The settings stack (pointer to the top element).
    3779  * @param   iRecursion          The recursion depth.  This is used to restrict
    3780  *                              the recursions.
    3781  */
    3782 static int scmProcessDirTreeRecursion(char *pszBuf, size_t cchDir, PRTDIRENTRY pEntry,
    3783                                       PSCMSETTINGS pSettingsStack, unsigned iRecursion)
    3784 {
    3785     int rc;
    3786     Assert(cchDir > 1 && pszBuf[cchDir - 1] == '.');
    3787 
    3788     /*
    3789      * Make sure we stop somewhere.
    3790      */
    3791     if (iRecursion > 128)
    3792     {
    3793         RTMsgError("recursion too deep: %d\n", iRecursion);
    3794         return VINF_SUCCESS; /* ignore */
    3795     }
    3796 
    3797     /*
    3798      * Check if it's excluded by --only-svn-dir.
    3799      */
    3800     if (pSettingsStack->Base.fOnlySvnDirs)
    3801     {
    3802         rc = RTPathAppend(pszBuf, RTPATH_MAX, ".svn");
    3803         if (RT_FAILURE(rc))
    3804         {
    3805             RTMsgError("RTPathAppend: %Rrc\n", rc);
    3806             return rc;
    3807         }
    3808         if (!RTDirExists(pszBuf))
    3809             return VINF_SUCCESS;
    3810 
    3811         Assert(RTPATH_IS_SLASH(pszBuf[cchDir]));
    3812         pszBuf[cchDir]     = '\0';
    3813         pszBuf[cchDir - 1] = '.';
    3814     }
    3815 
    3816     /*
    3817      * Try open and read the directory.
    3818      */
    3819     PRTDIR pDir;
    3820     rc = RTDirOpenFiltered(&pDir, pszBuf, RTDIRFILTER_NONE, 0);
    3821     if (RT_FAILURE(rc))
    3822     {
    3823         RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc);
    3824         return rc;
    3825     }
    3826     for (;;)
    3827     {
    3828         /* Read the next entry. */
    3829         rc = RTDirRead(pDir, pEntry, NULL);
    3830         if (RT_FAILURE(rc))
    3831         {
    3832             if (rc == VERR_NO_MORE_FILES)
    3833                 rc = VINF_SUCCESS;
    3834             else
    3835                 RTMsgError("RTDirRead -> %Rrc\n", rc);
    3836             break;
    3837         }
    3838 
    3839         /* Skip '.' and '..'. */
    3840         if (    pEntry->szName[0] == '.'
    3841             &&  (   pEntry->cbName == 1
    3842                  || (   pEntry->cbName == 2
    3843                      && pEntry->szName[1] == '.')))
    3844             continue;
    3845 
    3846         /* Enter it into the buffer so we've got a full name to work
    3847            with when needed. */
    3848         if (pEntry->cbName + cchDir >= RTPATH_MAX)
    3849         {
    3850             RTMsgError("Skipping too long entry: %s", pEntry->szName);
    3851             continue;
    3852         }
    3853         memcpy(&pszBuf[cchDir - 1], pEntry->szName, pEntry->cbName + 1);
    3854 
    3855         /* Figure the type. */
    3856         RTDIRENTRYTYPE enmType = pEntry->enmType;
    3857         if (enmType == RTDIRENTRYTYPE_UNKNOWN)
    3858             enmType = scmFigureUnknownType(pszBuf);
    3859 
    3860         /* Process the file or directory, skip the rest. */
    3861         if (enmType == RTDIRENTRYTYPE_FILE)
    3862             rc = scmProcessFile(pszBuf, pEntry->szName, pEntry->cbName, pSettingsStack);
    3863         else if (enmType == RTDIRENTRYTYPE_DIRECTORY)
    3864         {
    3865             /* Append the dot for the benefit of the pattern matching. */
    3866             if (pEntry->cbName + cchDir + 5 >= RTPATH_MAX)
    3867             {
    3868                 RTMsgError("Skipping too deep dir entry: %s", pEntry->szName);
    3869                 continue;
    3870             }
    3871             memcpy(&pszBuf[cchDir - 1 + pEntry->cbName], "/.", sizeof("/."));
    3872             size_t cchSubDir = cchDir - 1 + pEntry->cbName + sizeof("/.") - 1;
    3873 
    3874             if (   !pSettingsStack->Base.pszFilterOutDirs
    3875                 || !*pSettingsStack->Base.pszFilterOutDirs
    3876                 || (   !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
    3877                                                      pEntry->szName, pEntry->cbName, NULL)
    3878                     && !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
    3879                                                      pszBuf, cchSubDir, NULL)
    3880                    )
    3881                )
    3882             {
    3883                 rc = scmSettingsStackPushDir(&pSettingsStack, pszBuf);
    3884                 if (RT_SUCCESS(rc))
    3885                 {
    3886                     rc = scmProcessDirTreeRecursion(pszBuf, cchSubDir, pEntry, pSettingsStack, iRecursion + 1);
    3887                     scmSettingsStackPopAndDestroy(&pSettingsStack);
    3888                 }
    3889             }
    3890         }
    3891         if (RT_FAILURE(rc))
    3892             break;
    3893     }
    3894     RTDirClose(pDir);
    3895     return rc;
    3896 
    3897 }
    3898 
    3899 /**
    3900  * Process a directory tree.
    3901  *
    3902  * @returns IPRT status code.
    3903  * @param   pszDir              The directory to start with.  This is pointer to
    3904  *                              a RTPATH_MAX sized buffer.
    3905  */
    3906 static int scmProcessDirTree(char *pszDir, PSCMSETTINGS pSettingsStack)
    3907 {
    3908     /*
    3909      * Setup the recursion.
    3910      */
    3911     int rc = RTPathAppend(pszDir, RTPATH_MAX, ".");
    3912     if (RT_SUCCESS(rc))
    3913     {
    3914         RTDIRENTRY Entry;
    3915         rc = scmProcessDirTreeRecursion(pszDir, strlen(pszDir), &Entry, pSettingsStack, 0);
    3916     }
    3917     else
    3918         RTMsgError("RTPathAppend: %Rrc\n", rc);
    3919     return rc;
    3920 }
    3921 
    3922 
    3923 /**
    3924  * Processes a file or directory specified as an command line argument.
    3925  *
    3926  * @returns IPRT status code
    3927  * @param   pszSomething        What we found in the command line arguments.
    3928  * @param   pSettingsStack      The settings stack (pointer to the top element).
    3929  */
    3930 static int scmProcessSomething(const char *pszSomething, PSCMSETTINGS pSettingsStack)
    3931 {
    3932     char szBuf[RTPATH_MAX];
    3933     int rc = RTPathAbs(pszSomething, szBuf, sizeof(szBuf));
    3934     if (RT_SUCCESS(rc))
    3935     {
    3936         RTPathChangeToUnixSlashes(szBuf, false /*fForce*/);
    3937 
    3938         PSCMSETTINGS pSettings;
    3939         rc = scmSettingsCreateForPath(&pSettings, &pSettingsStack->Base, szBuf);
    3940         if (RT_SUCCESS(rc))
    3941         {
    3942             scmSettingsStackPush(&pSettingsStack, pSettings);
    3943 
    3944             if (RTFileExists(szBuf))
    3945             {
    3946                 const char *pszBasename = RTPathFilename(szBuf);
    3947                 if (pszBasename)
    3948                 {
    3949                     size_t cchBasename = strlen(pszBasename);
    3950                     rc = scmProcessFile(szBuf, pszBasename, cchBasename, pSettingsStack);
    3951                 }
    3952                 else
    3953                 {
    3954                     RTMsgError("RTPathFilename: NULL\n");
    3955                     rc = VERR_IS_A_DIRECTORY;
    3956                 }
    3957             }
    3958             else
    3959                 rc = scmProcessDirTree(szBuf, pSettingsStack);
    3960 
    3961             PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);
    3962             Assert(pPopped == pSettings);
    3963             scmSettingsDestroy(pSettings);
    3964         }
    3965         else
    3966             RTMsgError("scmSettingsInitStack: %Rrc\n", rc);
    3967     }
    3968     else
    3969         RTMsgError("RTPathAbs: %Rrc\n", rc);
    3970     return rc;
    3971 }
    3972 
    3973 int main(int argc, char **argv)
    3974 {
    3975     int rc = RTR3InitExe(argc, &argv, 0);
    3976     if (RT_FAILURE(rc))
    3977         return 1;
    3978 
    3979     /*
    3980      * Init the settings.
    3981      */
    3982     PSCMSETTINGS pSettings;
    3983     rc = scmSettingsCreate(&pSettings, &g_Defaults);
    3984     if (RT_FAILURE(rc))
    3985     {
    3986         RTMsgError("scmSettingsCreate: %Rrc\n", rc);
    3987         return 1;
    3988     }
    3989 
    3990     /*
    3991      * Parse arguments and process input in order (because this is the only
    3992      * thing that works at the moment).
    3993      */
    3994     static RTGETOPTDEF s_aOpts[14 + RT_ELEMENTS(g_aScmOpts)] =
    3995     {
    3996         { "--dry-run",                          'd',                                    RTGETOPT_REQ_NOTHING },
    3997         { "--real-run",                         'D',                                    RTGETOPT_REQ_NOTHING },
    3998         { "--file-filter",                      'f',                                    RTGETOPT_REQ_STRING  },
    3999         { "--quiet",                            'q',                                    RTGETOPT_REQ_NOTHING },
    4000         { "--verbose",                          'v',                                    RTGETOPT_REQ_NOTHING },
    4001         { "--diff-ignore-eol",                  SCMOPT_DIFF_IGNORE_EOL,                 RTGETOPT_REQ_NOTHING },
    4002         { "--diff-no-ignore-eol",               SCMOPT_DIFF_NO_IGNORE_EOL,              RTGETOPT_REQ_NOTHING },
    4003         { "--diff-ignore-space",                SCMOPT_DIFF_IGNORE_SPACE,               RTGETOPT_REQ_NOTHING },
    4004         { "--diff-no-ignore-space",             SCMOPT_DIFF_NO_IGNORE_SPACE,            RTGETOPT_REQ_NOTHING },
    4005         { "--diff-ignore-leading-space",        SCMOPT_DIFF_IGNORE_LEADING_SPACE,       RTGETOPT_REQ_NOTHING },
    4006         { "--diff-no-ignore-leading-space",     SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE,    RTGETOPT_REQ_NOTHING },
    4007         { "--diff-ignore-trailing-space",       SCMOPT_DIFF_IGNORE_TRAILING_SPACE,      RTGETOPT_REQ_NOTHING },
    4008         { "--diff-no-ignore-trailing-space",    SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE,   RTGETOPT_REQ_NOTHING },
    4009         { "--diff-special-chars",               SCMOPT_DIFF_SPECIAL_CHARS,              RTGETOPT_REQ_NOTHING },
    4010         { "--diff-no-special-chars",            SCMOPT_DIFF_NO_SPECIAL_CHARS,           RTGETOPT_REQ_NOTHING },
    4011     };
    4012     memcpy(&s_aOpts[RT_ELEMENTS(s_aOpts) - RT_ELEMENTS(g_aScmOpts)], &g_aScmOpts[0], sizeof(g_aScmOpts));
    4013 
    4014     RTGETOPTUNION   ValueUnion;
    4015     RTGETOPTSTATE   GetOptState;
    4016     rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
    4017     AssertReleaseRCReturn(rc, 1);
    4018     size_t          cProcessed = 0;
    4019 
    4020     while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
    4021     {
    4022         switch (rc)
    4023         {
    4024             case 'd':
    4025                 g_fDryRun = true;
    4026                 break;
    4027             case 'D':
    4028                 g_fDryRun = false;
    4029                 break;
    4030 
    4031             case 'f':
    4032                 g_pszFileFilter = ValueUnion.psz;
    4033                 break;
    4034 
    4035             case 'h':
    4036                 RTPrintf("VirtualBox Source Code Massager\n"
    4037                          "\n"
    4038                          "Usage: %s [options] <files & dirs>\n"
    4039                          "\n"
    4040                          "Options:\n", g_szProgName);
    4041                 for (size_t i = 0; i < RT_ELEMENTS(s_aOpts); i++)
    4042                 {
    4043                     bool fAdvanceTwo = false;
    4044                     if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING)
    4045                     {
    4046                         fAdvanceTwo = i + 1 < RT_ELEMENTS(s_aOpts)
    4047                                    && (   strstr(s_aOpts[i+1].pszLong, "-no-") != NULL
    4048                                        || strstr(s_aOpts[i+1].pszLong, "-not-") != NULL
    4049                                        || strstr(s_aOpts[i+1].pszLong, "-dont-") != NULL
    4050                                       );
    4051                         if (fAdvanceTwo)
    4052                             RTPrintf("  %s, %s\n", s_aOpts[i].pszLong, s_aOpts[i + 1].pszLong);
    4053                         else
    4054                             RTPrintf("  %s\n", s_aOpts[i].pszLong);
    4055                     }
    4056                     else if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING)
    4057                         RTPrintf("  %s string\n", s_aOpts[i].pszLong);
    4058                     else
    4059                         RTPrintf("  %s value\n", s_aOpts[i].pszLong);
    4060                     switch (s_aOpts[i].iShort)
    4061                     {
    4062                         case SCMOPT_CONVERT_EOL:            RTPrintf("      Default: %RTbool\n", g_Defaults.fConvertEol); break;
    4063                         case SCMOPT_CONVERT_TABS:           RTPrintf("      Default: %RTbool\n", g_Defaults.fConvertTabs); break;
    4064                         case SCMOPT_FORCE_FINAL_EOL:        RTPrintf("      Default: %RTbool\n", g_Defaults.fForceFinalEol); break;
    4065                         case SCMOPT_FORCE_TRAILING_LINE:    RTPrintf("      Default: %RTbool\n", g_Defaults.fForceTrailingLine); break;
    4066                         case SCMOPT_STRIP_TRAILING_BLANKS:  RTPrintf("      Default: %RTbool\n", g_Defaults.fStripTrailingBlanks); break;
    4067                         case SCMOPT_STRIP_TRAILING_LINES:   RTPrintf("      Default: %RTbool\n", g_Defaults.fStripTrailingLines); break;
    4068                         case SCMOPT_ONLY_SVN_DIRS:          RTPrintf("      Default: %RTbool\n", g_Defaults.fOnlySvnDirs); break;
    4069                         case SCMOPT_ONLY_SVN_FILES:         RTPrintf("      Default: %RTbool\n", g_Defaults.fOnlySvnFiles); break;
    4070                         case SCMOPT_SET_SVN_EOL:            RTPrintf("      Default: %RTbool\n", g_Defaults.fSetSvnEol); break;
    4071                         case SCMOPT_SET_SVN_EXECUTABLE:     RTPrintf("      Default: %RTbool\n", g_Defaults.fSetSvnExecutable); break;
    4072                         case SCMOPT_SET_SVN_KEYWORDS:       RTPrintf("      Default: %RTbool\n", g_Defaults.fSetSvnKeywords); break;
    4073                         case SCMOPT_TAB_SIZE:               RTPrintf("      Default: %u\n", g_Defaults.cchTab); break;
    4074                         case SCMOPT_FILTER_OUT_DIRS:        RTPrintf("      Default: %s\n", g_Defaults.pszFilterOutDirs); break;
    4075                         case SCMOPT_FILTER_FILES:           RTPrintf("      Default: %s\n", g_Defaults.pszFilterFiles); break;
    4076                         case SCMOPT_FILTER_OUT_FILES:       RTPrintf("      Default: %s\n", g_Defaults.pszFilterOutFiles); break;
    4077                     }
    4078                     i += fAdvanceTwo;
    4079                 }
    4080                 return 1;
    4081 
    4082             case 'q':
    4083                 g_iVerbosity = 0;
    4084                 break;
    4085 
    4086             case 'v':
    4087                 g_iVerbosity++;
    4088                 break;
    4089 
    4090             case 'V':
    4091             {
    4092                 /* The following is assuming that svn does it's job here. */
    4093                 static const char s_szRev[] = "$Revision$";
    4094                 const char *psz = RTStrStripL(strchr(s_szRev, ' '));
    4095                 RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);
    4096                 return 0;
    4097             }
    4098 
    4099             case SCMOPT_DIFF_IGNORE_EOL:
    4100                 g_fDiffIgnoreEol = true;
    4101                 break;
    4102             case SCMOPT_DIFF_NO_IGNORE_EOL:
    4103                 g_fDiffIgnoreEol = false;
    4104                 break;
    4105 
    4106             case SCMOPT_DIFF_IGNORE_SPACE:
    4107                 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = true;
    4108                 break;
    4109             case SCMOPT_DIFF_NO_IGNORE_SPACE:
    4110                 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = false;
    4111                 break;
    4112 
    4113             case SCMOPT_DIFF_IGNORE_LEADING_SPACE:
    4114                 g_fDiffIgnoreLeadingWS = true;
    4115                 break;
    4116             case SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE:
    4117                 g_fDiffIgnoreLeadingWS = false;
    4118                 break;
    4119 
    4120             case SCMOPT_DIFF_IGNORE_TRAILING_SPACE:
    4121                 g_fDiffIgnoreTrailingWS = true;
    4122                 break;
    4123             case SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE:
    4124                 g_fDiffIgnoreTrailingWS = false;
    4125                 break;
    4126 
    4127             case SCMOPT_DIFF_SPECIAL_CHARS:
    4128                 g_fDiffSpecialChars = true;
    4129                 break;
    4130             case SCMOPT_DIFF_NO_SPECIAL_CHARS:
    4131                 g_fDiffSpecialChars = false;
    4132                 break;
    4133 
    4134             case VINF_GETOPT_NOT_OPTION:
    4135             {
    4136                 if (!g_fDryRun)
    4137                 {
    4138                     if (!cProcessed)
    4139                     {
    4140                         RTPrintf("%s: Warning! This program will make changes to your source files and\n"
    4141                                  "%s:          there is a slight risk that bugs or a full disk may cause\n"
    4142                                  "%s:          LOSS OF DATA.   So, please make sure you have checked in\n"
    4143                                  "%s:          all your changes already.  If you didn't, then don't blame\n"
    4144                                  "%s:          anyone for not warning you!\n"
    4145                                  "%s:\n"
    4146                                  "%s:          Press any key to continue...\n",
    4147                                  g_szProgName, g_szProgName, g_szProgName, g_szProgName, g_szProgName,
    4148                                  g_szProgName, g_szProgName);
    4149                         RTStrmGetCh(g_pStdIn);
    4150                     }
    4151                     cProcessed++;
    4152                 }
    4153                 rc = scmProcessSomething(ValueUnion.psz, pSettings);
    4154                 if (RT_FAILURE(rc))
    4155                     return rc;
    4156                 break;
    4157             }
    4158 
    4159             default:
    4160             {
    4161                 int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion);
    4162                 if (RT_SUCCESS(rc2))
    4163                     break;
    4164                 if (rc2 != VERR_GETOPT_UNKNOWN_OPTION)
    4165                     return 2;
    4166                 return RTGetOptPrintError(rc, &ValueUnion);
    4167             }
    4168         }
    4169     }
    4170 
    4171     scmSettingsDestroy(pSettings);
    4172     return 0;
    4173 }
    4174 
Note: See TracChangeset for help on using the changeset viewer.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette