VirtualBox

source: vbox/trunk/src/bldprogs/scm.cpp@ 33000

Last change on this file since 33000 was 30320, checked in by vboxsync, 14 years ago

*: Replaced memchr(psz, '\0', cb) with RTStrEnd(psz, cb) and worked around memchr( RTSTR_MAX) issue in RTStrEnd. Here (linux.amd64 / glibc-2.10.1-r1) memchr fails for cb > ~(size_t)11. Since RTSTR_MAX is ~(size_t)0, this behavior breaks several IPRT string APIs.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 138.5 KB
Line 
1/* $Id: scm.cpp 30320 2010-06-21 08:35:09Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager.
4 */
5
6/*
7 * Copyright (C) 2010 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
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>
36
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. */
49typedef struct SCMSETTINGSBASE const *PCSCMSETTINGSBASE;
50
51/** End of line marker type. */
52typedef 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. */
59typedef SCMEOL *PSCMEOL;
60
61/**
62 * Line record.
63 */
64typedef 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. */
76typedef SCMSTREAMLINE *PSCMSTREAMLINE;
77
78/**
79 * Source code massager stream.
80 */
81typedef 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. */
112typedef SCMSTREAM *PSCMSTREAM;
113/** Pointer to a const SCM stream. */
114typedef SCMSTREAM const *PCSCMSTREAM;
115
116
117/**
118 * SVN property.
119 */
120typedef 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. */
130typedef SCMSVNPROP *PSCMSVNPROP;
131/** Pointer to a const SVN property. */
132typedef SCMSVNPROP const *PCSCMSVNPROP;
133
134
135/**
136 * Rewriter state.
137 */
138typedef 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. */
151typedef 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 */
164typedef bool (*PFNSCMREWRITER)(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
165
166
167/**
168 * Configuration entry.
169 */
170typedef 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;
179typedef SCMCFGENTRY *PSCMCFGENTRY;
180typedef SCMCFGENTRY const *PCSCMCFGENTRY;
181
182
183/**
184 * Diff state.
185 */
186typedef 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. */
208typedef SCMDIFFSTATE *PSCMDIFFSTATE;
209
210/**
211 * Source Code Massager Settings.
212 */
213typedef 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 (unually 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 matcihng 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 aboslute paths. */
238 char *pszFilterOutFiles;
239 /** Filter out directories matching the following patterns. This is applied
240 * to base names as well as the aboslute paths. All absolute paths ends with a
241 * slash and dot ("/."). */
242 char *pszFilterOutDirs;
243} SCMSETTINGSBASE;
244/** Pointer to massager settings. */
245typedef 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 */
255typedef 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 */
302typedef struct SCMPATRNOPTPAIR
303{
304 char *pszPattern;
305 char *pszOptions;
306} SCMPATRNOPTPAIR;
307/** Pointer to a pattern + option pair. */
308typedef SCMPATRNOPTPAIR *PSCMPATRNOPTPAIR;
309
310
311/** Pointer to a settings set. */
312typedef 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 recusing 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 preceeded 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 */
330typedef 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. */
344typedef SCMSETTINGS const *PCSCMSETTINGS;
345
346
347/*******************************************************************************
348* Internal Functions *
349*******************************************************************************/
350static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
351static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
352static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
353static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
354static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
355static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
356static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
357static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
358static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
359static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
360static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings);
361
362
363/*******************************************************************************
364* Global Variables *
365*******************************************************************************/
366static const char g_szProgName[] = "scm";
367static const char *g_pszChangedSuff = "";
368static const char g_szTabSpaces[16+1] = " ";
369static bool g_fDryRun = true;
370static bool g_fDiffSpecialChars = true;
371static bool g_fDiffIgnoreEol = false;
372static bool g_fDiffIgnoreLeadingWS = false;
373static bool g_fDiffIgnoreTrailingWS = false;
374static int g_iVerbosity = 2;//99; //0;
375
376/** The global settings. */
377static 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. */
397static 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). */
428static const char *g_pszFileFilter = NULL;
429
430static PFNSCMREWRITER const g_aRewritersFor_Makefile_kup[] =
431{
432 rewrite_SvnNoExecutable,
433 rewrite_Makefile_kup
434};
435
436static 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
446static 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
457static 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
467static 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
477static PFNSCMREWRITER const g_aRewritersFor_ShellScripts[] =
478{
479 rewrite_ForceLF,
480 rewrite_ExpandTabs,
481 rewrite_StripTrailingBlanks
482};
483
484static PFNSCMREWRITER const g_aRewritersFor_BatchFiles[] =
485{
486 rewrite_ForceCRLF,
487 rewrite_ExpandTabs,
488 rewrite_StripTrailingBlanks
489};
490
491static 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 */
512static 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 */
538int 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 */
562int 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 */
602void 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 */
628int 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 */
641static 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 */
674static 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 */
693void 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 */
706void 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 */
724bool 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 */
739int 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 */
776int 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 */
813static 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 lineates a stream.
866 *
867 * @returns IPRT status code.
868 * @param pStream The stream. Caller must check that it is in
869 * read mode.
870 */
871static 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 */
897size_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 */
908size_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 */
919size_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 */
930size_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 */
949int 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 lineated. (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 begining and
1001 * end of the stream.
1002 */
1003int 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 */
1024int 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 lineated. (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 * occured.
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 */
1068static 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 lineated 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 * occured.
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 */
1117static 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 */
1140static 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 */
1162static 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 */
1180SCMEOL 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 */
1213SCMEOL 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 */
1236int 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 */
1331int 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 */
1434int 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 * annyoing 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 destionation 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 */
1469int 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 */
1507static 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 propells 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 */
1581static 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 */
1659static 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 */
1691DECLINLINE(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 */
1718static 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 */
1748static 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 */
1834size_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 */
1912static 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 */
1943static 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 */
1953static 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 */
1983static 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 */
2126static 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 */
2160static 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 */
2176static 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 */
2196static 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 */
2209static 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 settgins structure to destroy. NULL is OK.
2232 */
2233static 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 */
2259static 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 */
2323static 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 */
2378static 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 */
2412static 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 */
2466static 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 */
2487static 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 */
2519static 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 */
2540static 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 */
2556static 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 */
2609static 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 */
2642static 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 */
2670static 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 */
2702static 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 */
2735static 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 */
2758static 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 */
2788static 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 */
2946static 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 */
3007static 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 */
3020static 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 */
3043static 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 */
3116static 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 */
3155static 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 */
3215static 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 */
3273static 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 */
3290static 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 */
3303static 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 */
3319static 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 */
3387static 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 */
3413static 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 */
3463static 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 preceeded by one space.
3485 */
3486static 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 */
3521static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
3522{
3523
3524 return false;
3525}
3526
3527/* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */
3528
3529/**
3530 * Processes a file.
3531 *
3532 * @returns IPRT status code.
3533 * @param pState The rewriter state.
3534 * @param pszFilename The file name.
3535 * @param pszBasename The base name (pointer within @a pszFilename).
3536 * @param cchBasename The length of the base name. (For passing to
3537 * RTStrSimplePatternMultiMatch.)
3538 * @param pBaseSettings The base settings to use. It's OK to modify
3539 * these.
3540 */
3541static int scmProcessFileInner(PSCMRWSTATE pState, const char *pszFilename, const char *pszBasename, size_t cchBasename,
3542 PSCMSETTINGSBASE pBaseSettings)
3543{
3544 /*
3545 * Do the file level filtering.
3546 */
3547 if ( pBaseSettings->pszFilterFiles
3548 && *pBaseSettings->pszFilterFiles
3549 && !RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterFiles, RTSTR_MAX, pszBasename, cchBasename, NULL))
3550 {
3551 ScmVerbose(NULL, 5, "skipping '%s': file filter mismatch\n", pszFilename);
3552 return VINF_SUCCESS;
3553 }
3554 if ( pBaseSettings->pszFilterOutFiles
3555 && *pBaseSettings->pszFilterOutFiles
3556 && ( RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszBasename, cchBasename, NULL)
3557 || RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszFilename, RTSTR_MAX, NULL)) )
3558 {
3559 ScmVerbose(NULL, 5, "skipping '%s': filterd out\n", pszFilename);
3560 return VINF_SUCCESS;
3561 }
3562 if ( pBaseSettings->fOnlySvnFiles
3563 && !scmSvnIsInWorkingCopy(pState))
3564 {
3565 ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename);
3566 return VINF_SUCCESS;
3567 }
3568
3569 /*
3570 * Try find a matching rewrite config for this filename.
3571 */
3572 PCSCMCFGENTRY pCfg = NULL;
3573 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
3574 if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, pszBasename, cchBasename, NULL))
3575 {
3576 pCfg = &g_aConfigs[iCfg];
3577 break;
3578 }
3579 if (!pCfg)
3580 {
3581 ScmVerbose(NULL, 4, "skipping '%s': no rewriters configured\n", pszFilename);
3582 return VINF_SUCCESS;
3583 }
3584 ScmVerbose(pState, 4, "matched \"%s\"\n", pCfg->pszFilePattern);
3585
3586 /*
3587 * Create an input stream from the file and check that it's text.
3588 */
3589 SCMSTREAM Stream1;
3590 int rc = ScmStreamInitForReading(&Stream1, pszFilename);
3591 if (RT_FAILURE(rc))
3592 {
3593 RTMsgError("Failed to read '%s': %Rrc\n", pszFilename, rc);
3594 return rc;
3595 }
3596 if (ScmStreamIsText(&Stream1))
3597 {
3598 ScmVerbose(pState, 3, NULL);
3599
3600 /*
3601 * Gather SCM and editor settings from the stream.
3602 */
3603 rc = scmSettingsBaseLoadFromDocument(pBaseSettings, &Stream1);
3604 if (RT_SUCCESS(rc))
3605 {
3606 ScmStreamRewindForReading(&Stream1);
3607
3608 /*
3609 * Create two more streams for output and push the text thru all the
3610 * rewriters, switching the two streams around when something is
3611 * actually rewritten. Stream1 remains unchanged.
3612 */
3613 SCMSTREAM Stream2;
3614 rc = ScmStreamInitForWriting(&Stream2, &Stream1);
3615 if (RT_SUCCESS(rc))
3616 {
3617 SCMSTREAM Stream3;
3618 rc = ScmStreamInitForWriting(&Stream3, &Stream1);
3619 if (RT_SUCCESS(rc))
3620 {
3621 bool fModified = false;
3622 PSCMSTREAM pIn = &Stream1;
3623 PSCMSTREAM pOut = &Stream2;
3624 for (size_t iRw = 0; iRw < pCfg->cRewriters; iRw++)
3625 {
3626 bool fRc = pCfg->papfnRewriter[iRw](pState, pIn, pOut, pBaseSettings);
3627 if (fRc)
3628 {
3629 PSCMSTREAM pTmp = pOut;
3630 pOut = pIn == &Stream1 ? &Stream3 : pIn;
3631 pIn = pTmp;
3632 fModified = true;
3633 }
3634 ScmStreamRewindForReading(pIn);
3635 ScmStreamRewindForWriting(pOut);
3636 }
3637
3638 rc = ScmStreamGetStatus(&Stream1);
3639 if (RT_SUCCESS(rc))
3640 rc = ScmStreamGetStatus(&Stream2);
3641 if (RT_SUCCESS(rc))
3642 rc = ScmStreamGetStatus(&Stream3);
3643 if (RT_SUCCESS(rc))
3644 {
3645 /*
3646 * If rewritten, write it back to disk.
3647 */
3648 if (fModified)
3649 {
3650 if (!g_fDryRun)
3651 {
3652 ScmVerbose(pState, 1, "writing modified file to \"%s%s\"\n", pszFilename, g_pszChangedSuff);
3653 rc = ScmStreamWriteToFile(pIn, "%s%s", pszFilename, g_pszChangedSuff);
3654 if (RT_FAILURE(rc))
3655 RTMsgError("Error writing '%s%s': %Rrc\n", pszFilename, g_pszChangedSuff, rc);
3656 }
3657 else
3658 {
3659 ScmVerbose(pState, 1, NULL);
3660 ScmDiffStreams(pszFilename, &Stream1, pIn, g_fDiffIgnoreEol, g_fDiffIgnoreLeadingWS,
3661 g_fDiffIgnoreTrailingWS, g_fDiffSpecialChars, pBaseSettings->cchTab, g_pStdOut);
3662 ScmVerbose(pState, 2, "would have modified the file \"%s%s\"\n", pszFilename, g_pszChangedSuff);
3663 }
3664 }
3665
3666 /*
3667 * If pending SVN property changes, apply them.
3668 */
3669 if (pState->cSvnPropChanges && RT_SUCCESS(rc))
3670 {
3671 if (!g_fDryRun)
3672 {
3673 rc = scmSvnApplyChanges(pState);
3674 if (RT_FAILURE(rc))
3675 RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc);
3676 }
3677 else
3678 scmSvnDisplayChanges(pState);
3679 }
3680
3681 if (!fModified && !pState->cSvnPropChanges)
3682 ScmVerbose(pState, 3, "no change\n", pszFilename);
3683 }
3684 else
3685 RTMsgError("%s: stream error %Rrc\n", pszFilename);
3686 ScmStreamDelete(&Stream3);
3687 }
3688 else
3689 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
3690 ScmStreamDelete(&Stream2);
3691 }
3692 else
3693 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
3694 }
3695 else
3696 RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);
3697 }
3698 else
3699 ScmVerbose(pState, 4, "not text file: \"%s\"\n", pszFilename);
3700 ScmStreamDelete(&Stream1);
3701
3702 return rc;
3703}
3704
3705/**
3706 * Processes a file.
3707 *
3708 * This is just a wrapper for scmProcessFileInner for avoid wasting stack in the
3709 * directory recursion method.
3710 *
3711 * @returns IPRT status code.
3712 * @param pszFilename The file name.
3713 * @param pszBasename The base name (pointer within @a pszFilename).
3714 * @param cchBasename The length of the base name. (For passing to
3715 * RTStrSimplePatternMultiMatch.)
3716 * @param pSettingsStack The settings stack (pointer to the top element).
3717 */
3718static int scmProcessFile(const char *pszFilename, const char *pszBasename, size_t cchBasename,
3719 PSCMSETTINGS pSettingsStack)
3720{
3721 SCMSETTINGSBASE Base;
3722 int rc = scmSettingsStackMakeFileBase(pSettingsStack, pszFilename, pszBasename, cchBasename, &Base);
3723 if (RT_SUCCESS(rc))
3724 {
3725 SCMRWSTATE State;
3726 State.fFirst = false;
3727 State.pszFilename = pszFilename;
3728 State.cSvnPropChanges = 0;
3729 State.paSvnPropChanges = NULL;
3730
3731 rc = scmProcessFileInner(&State, pszFilename, pszBasename, cchBasename, &Base);
3732
3733 size_t i = State.cSvnPropChanges;
3734 while (i-- > 0)
3735 {
3736 RTStrFree(State.paSvnPropChanges[i].pszName);
3737 RTStrFree(State.paSvnPropChanges[i].pszValue);
3738 }
3739 RTMemFree(State.paSvnPropChanges);
3740
3741 scmSettingsBaseDelete(&Base);
3742 }
3743 return rc;
3744}
3745
3746
3747/**
3748 * Tries to correct RTDIRENTRY_UNKNOWN.
3749 *
3750 * @returns Corrected type.
3751 * @param pszPath The path to the object in question.
3752 */
3753static RTDIRENTRYTYPE scmFigureUnknownType(const char *pszPath)
3754{
3755 RTFSOBJINFO Info;
3756 int rc = RTPathQueryInfo(pszPath, &Info, RTFSOBJATTRADD_NOTHING);
3757 if (RT_FAILURE(rc))
3758 return RTDIRENTRYTYPE_UNKNOWN;
3759 if (RTFS_IS_DIRECTORY(Info.Attr.fMode))
3760 return RTDIRENTRYTYPE_DIRECTORY;
3761 if (RTFS_IS_FILE(Info.Attr.fMode))
3762 return RTDIRENTRYTYPE_FILE;
3763 return RTDIRENTRYTYPE_UNKNOWN;
3764}
3765
3766/**
3767 * Recurse into a sub-directory and process all the files and directories.
3768 *
3769 * @returns IPRT status code.
3770 * @param pszBuf Path buffer containing the directory path on
3771 * entry. This ends with a dot. This is passed
3772 * along when recusing in order to save stack space
3773 * and avoid needless copying.
3774 * @param cchDir Length of our path in pszbuf.
3775 * @param pEntry Directory entry buffer. This is also passed
3776 * along when recursing to save stack space.
3777 * @param pSettingsStack The settings stack (pointer to the top element).
3778 * @param iRecursion The recursion depth. This is used to restrict
3779 * the recursions.
3780 */
3781static int scmProcessDirTreeRecursion(char *pszBuf, size_t cchDir, PRTDIRENTRY pEntry,
3782 PSCMSETTINGS pSettingsStack, unsigned iRecursion)
3783{
3784 int rc;
3785 Assert(cchDir > 1 && pszBuf[cchDir - 1] == '.');
3786
3787 /*
3788 * Make sure we stop somewhere.
3789 */
3790 if (iRecursion > 128)
3791 {
3792 RTMsgError("recursion too deep: %d\n", iRecursion);
3793 return VINF_SUCCESS; /* ignore */
3794 }
3795
3796 /*
3797 * Check if it's excluded by --only-svn-dir.
3798 */
3799 if (pSettingsStack->Base.fOnlySvnDirs)
3800 {
3801 rc = RTPathAppend(pszBuf, RTPATH_MAX, ".svn");
3802 if (RT_FAILURE(rc))
3803 {
3804 RTMsgError("RTPathAppend: %Rrc\n", rc);
3805 return rc;
3806 }
3807 if (!RTDirExists(pszBuf))
3808 return VINF_SUCCESS;
3809
3810 Assert(RTPATH_IS_SLASH(pszBuf[cchDir]));
3811 pszBuf[cchDir] = '\0';
3812 pszBuf[cchDir - 1] = '.';
3813 }
3814
3815 /*
3816 * Try open and read the directory.
3817 */
3818 PRTDIR pDir;
3819 rc = RTDirOpenFiltered(&pDir, pszBuf, RTDIRFILTER_NONE);
3820 if (RT_FAILURE(rc))
3821 {
3822 RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc);
3823 return rc;
3824 }
3825 for (;;)
3826 {
3827 /* Read the next entry. */
3828 rc = RTDirRead(pDir, pEntry, NULL);
3829 if (RT_FAILURE(rc))
3830 {
3831 if (rc == VERR_NO_MORE_FILES)
3832 rc = VINF_SUCCESS;
3833 else
3834 RTMsgError("RTDirRead -> %Rrc\n", rc);
3835 break;
3836 }
3837
3838 /* Skip '.' and '..'. */
3839 if ( pEntry->szName[0] == '.'
3840 && ( pEntry->cbName == 1
3841 || ( pEntry->cbName == 2
3842 && pEntry->szName[1] == '.')))
3843 continue;
3844
3845 /* Enter it into the buffer so we've got a full name to work
3846 with when needed. */
3847 if (pEntry->cbName + cchDir >= RTPATH_MAX)
3848 {
3849 RTMsgError("Skipping too long entry: %s", pEntry->szName);
3850 continue;
3851 }
3852 memcpy(&pszBuf[cchDir - 1], pEntry->szName, pEntry->cbName + 1);
3853
3854 /* Figure the type. */
3855 RTDIRENTRYTYPE enmType = pEntry->enmType;
3856 if (enmType == RTDIRENTRYTYPE_UNKNOWN)
3857 enmType = scmFigureUnknownType(pszBuf);
3858
3859 /* Process the file or directory, skip the rest. */
3860 if (enmType == RTDIRENTRYTYPE_FILE)
3861 rc = scmProcessFile(pszBuf, pEntry->szName, pEntry->cbName, pSettingsStack);
3862 else if (enmType == RTDIRENTRYTYPE_DIRECTORY)
3863 {
3864 /* Append the dot for the benefit of the pattern matching. */
3865 if (pEntry->cbName + cchDir + 5 >= RTPATH_MAX)
3866 {
3867 RTMsgError("Skipping too deep dir entry: %s", pEntry->szName);
3868 continue;
3869 }
3870 memcpy(&pszBuf[cchDir - 1 + pEntry->cbName], "/.", sizeof("/."));
3871 size_t cchSubDir = cchDir - 1 + pEntry->cbName + sizeof("/.") - 1;
3872
3873 if ( !pSettingsStack->Base.pszFilterOutDirs
3874 || !*pSettingsStack->Base.pszFilterOutDirs
3875 || ( !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
3876 pEntry->szName, pEntry->cbName, NULL)
3877 && !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
3878 pszBuf, cchSubDir, NULL)
3879 )
3880 )
3881 {
3882 rc = scmSettingsStackPushDir(&pSettingsStack, pszBuf);
3883 if (RT_SUCCESS(rc))
3884 {
3885 rc = scmProcessDirTreeRecursion(pszBuf, cchSubDir, pEntry, pSettingsStack, iRecursion + 1);
3886 scmSettingsStackPopAndDestroy(&pSettingsStack);
3887 }
3888 }
3889 }
3890 if (RT_FAILURE(rc))
3891 break;
3892 }
3893 RTDirClose(pDir);
3894 return rc;
3895
3896}
3897
3898/**
3899 * Process a directory tree.
3900 *
3901 * @returns IPRT status code.
3902 * @param pszDir The directory to start with. This is pointer to
3903 * a RTPATH_MAX sized buffer.
3904 */
3905static int scmProcessDirTree(char *pszDir, PSCMSETTINGS pSettingsStack)
3906{
3907 /*
3908 * Setup the recursion.
3909 */
3910 int rc = RTPathAppend(pszDir, RTPATH_MAX, ".");
3911 if (RT_SUCCESS(rc))
3912 {
3913 RTDIRENTRY Entry;
3914 rc = scmProcessDirTreeRecursion(pszDir, strlen(pszDir), &Entry, pSettingsStack, 0);
3915 }
3916 else
3917 RTMsgError("RTPathAppend: %Rrc\n", rc);
3918 return rc;
3919}
3920
3921
3922/**
3923 * Processes a file or directory specified as an command line argument.
3924 *
3925 * @returns IPRT status code
3926 * @param pszSomething What we found in the commad line arguments.
3927 * @param pSettingsStack The settings stack (pointer to the top element).
3928 */
3929static int scmProcessSomething(const char *pszSomething, PSCMSETTINGS pSettingsStack)
3930{
3931 char szBuf[RTPATH_MAX];
3932 int rc = RTPathAbs(pszSomething, szBuf, sizeof(szBuf));
3933 if (RT_SUCCESS(rc))
3934 {
3935 RTPathChangeToUnixSlashes(szBuf, false /*fForce*/);
3936
3937 PSCMSETTINGS pSettings;
3938 rc = scmSettingsCreateForPath(&pSettings, &pSettingsStack->Base, szBuf);
3939 if (RT_SUCCESS(rc))
3940 {
3941 scmSettingsStackPush(&pSettingsStack, pSettings);
3942
3943 if (RTFileExists(szBuf))
3944 {
3945 const char *pszBasename = RTPathFilename(szBuf);
3946 if (pszBasename)
3947 {
3948 size_t cchBasename = strlen(pszBasename);
3949 rc = scmProcessFile(szBuf, pszBasename, cchBasename, pSettingsStack);
3950 }
3951 else
3952 {
3953 RTMsgError("RTPathFilename: NULL\n");
3954 rc = VERR_IS_A_DIRECTORY;
3955 }
3956 }
3957 else
3958 rc = scmProcessDirTree(szBuf, pSettingsStack);
3959
3960 PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);
3961 Assert(pPopped == pSettings);
3962 scmSettingsDestroy(pSettings);
3963 }
3964 else
3965 RTMsgError("scmSettingsInitStack: %Rrc\n", rc);
3966 }
3967 else
3968 RTMsgError("RTPathAbs: %Rrc\n", rc);
3969 return rc;
3970}
3971
3972int main(int argc, char **argv)
3973{
3974 int rc = RTR3Init();
3975 if (RT_FAILURE(rc))
3976 return 1;
3977
3978 /*
3979 * Init the settings.
3980 */
3981 PSCMSETTINGS pSettings;
3982 rc = scmSettingsCreate(&pSettings, &g_Defaults);
3983 if (RT_FAILURE(rc))
3984 {
3985 RTMsgError("scmSettingsCreate: %Rrc\n", rc);
3986 return 1;
3987 }
3988
3989 /*
3990 * Parse arguments and process input in order (because this is the only
3991 * thing that works at the moment).
3992 */
3993 static RTGETOPTDEF s_aOpts[14 + RT_ELEMENTS(g_aScmOpts)] =
3994 {
3995 { "--dry-run", 'd', RTGETOPT_REQ_NOTHING },
3996 { "--real-run", 'D', RTGETOPT_REQ_NOTHING },
3997 { "--file-filter", 'f', RTGETOPT_REQ_STRING },
3998 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
3999 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
4000 { "--diff-ignore-eol", SCMOPT_DIFF_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
4001 { "--diff-no-ignore-eol", SCMOPT_DIFF_NO_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
4002 { "--diff-ignore-space", SCMOPT_DIFF_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
4003 { "--diff-no-ignore-space", SCMOPT_DIFF_NO_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
4004 { "--diff-ignore-leading-space", SCMOPT_DIFF_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
4005 { "--diff-no-ignore-leading-space", SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
4006 { "--diff-ignore-trailing-space", SCMOPT_DIFF_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
4007 { "--diff-no-ignore-trailing-space", SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
4008 { "--diff-special-chars", SCMOPT_DIFF_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
4009 { "--diff-no-special-chars", SCMOPT_DIFF_NO_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
4010 };
4011 memcpy(&s_aOpts[RT_ELEMENTS(s_aOpts) - RT_ELEMENTS(g_aScmOpts)], &g_aScmOpts[0], sizeof(g_aScmOpts));
4012
4013 RTGETOPTUNION ValueUnion;
4014 RTGETOPTSTATE GetOptState;
4015 rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
4016 AssertReleaseRCReturn(rc, 1);
4017 size_t cProcessed = 0;
4018
4019 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
4020 {
4021 switch (rc)
4022 {
4023 case 'd':
4024 g_fDryRun = true;
4025 break;
4026 case 'D':
4027 g_fDryRun = false;
4028 break;
4029
4030 case 'f':
4031 g_pszFileFilter = ValueUnion.psz;
4032 break;
4033
4034 case 'h':
4035 RTPrintf("VirtualBox Source Code Massager\n"
4036 "\n"
4037 "Usage: %s [options] <files & dirs>\n"
4038 "\n"
4039 "Options:\n", g_szProgName);
4040 for (size_t i = 0; i < RT_ELEMENTS(s_aOpts); i++)
4041 {
4042 bool fAdvanceTwo = false;
4043 if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING)
4044 {
4045 fAdvanceTwo = i + 1 < RT_ELEMENTS(s_aOpts)
4046 && ( strstr(s_aOpts[i+1].pszLong, "-no-") != NULL
4047 || strstr(s_aOpts[i+1].pszLong, "-not-") != NULL
4048 || strstr(s_aOpts[i+1].pszLong, "-dont-") != NULL
4049 );
4050 if (fAdvanceTwo)
4051 RTPrintf(" %s, %s\n", s_aOpts[i].pszLong, s_aOpts[i + 1].pszLong);
4052 else
4053 RTPrintf(" %s\n", s_aOpts[i].pszLong);
4054 }
4055 else if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING)
4056 RTPrintf(" %s string\n", s_aOpts[i].pszLong);
4057 else
4058 RTPrintf(" %s value\n", s_aOpts[i].pszLong);
4059 switch (s_aOpts[i].iShort)
4060 {
4061 case SCMOPT_CONVERT_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertEol); break;
4062 case SCMOPT_CONVERT_TABS: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertTabs); break;
4063 case SCMOPT_FORCE_FINAL_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceFinalEol); break;
4064 case SCMOPT_FORCE_TRAILING_LINE: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceTrailingLine); break;
4065 case SCMOPT_STRIP_TRAILING_BLANKS: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingBlanks); break;
4066 case SCMOPT_STRIP_TRAILING_LINES: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingLines); break;
4067 case SCMOPT_ONLY_SVN_DIRS: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnDirs); break;
4068 case SCMOPT_ONLY_SVN_FILES: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnFiles); break;
4069 case SCMOPT_SET_SVN_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnEol); break;
4070 case SCMOPT_SET_SVN_EXECUTABLE: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnExecutable); break;
4071 case SCMOPT_SET_SVN_KEYWORDS: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnKeywords); break;
4072 case SCMOPT_TAB_SIZE: RTPrintf(" Default: %u\n", g_Defaults.cchTab); break;
4073 case SCMOPT_FILTER_OUT_DIRS: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutDirs); break;
4074 case SCMOPT_FILTER_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterFiles); break;
4075 case SCMOPT_FILTER_OUT_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutFiles); break;
4076 }
4077 i += fAdvanceTwo;
4078 }
4079 return 1;
4080
4081 case 'q':
4082 g_iVerbosity = 0;
4083 break;
4084
4085 case 'v':
4086 g_iVerbosity++;
4087 break;
4088
4089 case 'V':
4090 {
4091 /* The following is assuming that svn does it's job here. */
4092 static const char s_szRev[] = "$Revision: 30320 $";
4093 const char *psz = RTStrStripL(strchr(s_szRev, ' '));
4094 RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);
4095 return 0;
4096 }
4097
4098 case SCMOPT_DIFF_IGNORE_EOL:
4099 g_fDiffIgnoreEol = true;
4100 break;
4101 case SCMOPT_DIFF_NO_IGNORE_EOL:
4102 g_fDiffIgnoreEol = false;
4103 break;
4104
4105 case SCMOPT_DIFF_IGNORE_SPACE:
4106 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = true;
4107 break;
4108 case SCMOPT_DIFF_NO_IGNORE_SPACE:
4109 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = false;
4110 break;
4111
4112 case SCMOPT_DIFF_IGNORE_LEADING_SPACE:
4113 g_fDiffIgnoreLeadingWS = true;
4114 break;
4115 case SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE:
4116 g_fDiffIgnoreLeadingWS = false;
4117 break;
4118
4119 case SCMOPT_DIFF_IGNORE_TRAILING_SPACE:
4120 g_fDiffIgnoreTrailingWS = true;
4121 break;
4122 case SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE:
4123 g_fDiffIgnoreTrailingWS = false;
4124 break;
4125
4126 case SCMOPT_DIFF_SPECIAL_CHARS:
4127 g_fDiffSpecialChars = true;
4128 break;
4129 case SCMOPT_DIFF_NO_SPECIAL_CHARS:
4130 g_fDiffSpecialChars = false;
4131 break;
4132
4133 case VINF_GETOPT_NOT_OPTION:
4134 {
4135 if (!g_fDryRun)
4136 {
4137 if (!cProcessed)
4138 {
4139 RTPrintf("%s: Warning! This program will make changes to your source files and\n"
4140 "%s: there is a slight risk that bugs or a full disk may cause\n"
4141 "%s: LOSS OF DATA. So, please make sure you have checked in\n"
4142 "%s: all your changes already. If you didn't, then don't blame\n"
4143 "%s: anyone for not warning you!\n"
4144 "%s:\n"
4145 "%s: Press any key to continue...\n",
4146 g_szProgName, g_szProgName, g_szProgName, g_szProgName, g_szProgName,
4147 g_szProgName, g_szProgName);
4148 RTStrmGetCh(g_pStdIn);
4149 }
4150 cProcessed++;
4151 }
4152 rc = scmProcessSomething(ValueUnion.psz, pSettings);
4153 if (RT_FAILURE(rc))
4154 return rc;
4155 break;
4156 }
4157
4158 default:
4159 {
4160 int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion);
4161 if (RT_SUCCESS(rc2))
4162 break;
4163 if (rc2 != VERR_GETOPT_UNKNOWN_OPTION)
4164 return 2;
4165 return RTGetOptPrintError(rc, &ValueUnion);
4166 }
4167 }
4168 }
4169
4170 scmSettingsDestroy(pSettings);
4171 return 0;
4172}
4173
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use