VirtualBox

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

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

Automated rebranding to Oracle copyright/license strings via filemuncher

  • 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 28800 2010-04-27 08:22:32Z 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 (memchr(pStream->pch, '\0', 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;
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 /*
2415 * We'll be working with a stack copy of the path.
2416 */
2417 char szFile[RTPATH_MAX];
2418 size_t cchDir = strlen(pszPath);
2419 if (cchDir >= sizeof(szFile) - sizeof(SCM_SETTINGS_FILENAME))
2420 return VERR_FILENAME_TOO_LONG;
2421
2422 /*
2423 * Create the bottom-most settings.
2424 */
2425 PSCMSETTINGS pSettings;
2426 int rc = scmSettingsCreate(&pSettings, pBaseSettings);
2427 if (RT_FAILURE(rc))
2428 return rc;
2429
2430 /*
2431 * Enumerate the path components from the root and down. Load any setting
2432 * files we find.
2433 */
2434 size_t cComponents = RTPathCountComponents(pszPath);
2435 for (size_t i = 1; i <= cComponents; i++)
2436 {
2437 rc = RTPathCopyComponents(szFile, sizeof(szFile), pszPath, i);
2438 if (RT_SUCCESS(rc))
2439 rc = RTPathAppend(szFile, sizeof(szFile), SCM_SETTINGS_FILENAME);
2440 if (RT_FAILURE(rc))
2441 break;
2442 if (RTFileExists(szFile))
2443 {
2444 rc = scmSettingsLoadFile(pSettings, szFile);
2445 if (RT_FAILURE(rc))
2446 break;
2447 }
2448 }
2449
2450 if (RT_SUCCESS(rc))
2451 *ppSettings = pSettings;
2452 else
2453 scmSettingsDestroy(pSettings);
2454 return rc;
2455}
2456
2457/**
2458 * Pushes a new settings set onto the stack.
2459 *
2460 * @param ppSettingsStack The pointer to the pointer to the top stack
2461 * element. This will be used as input and output.
2462 * @param pSettings The settings to push onto the stack.
2463 */
2464static void scmSettingsStackPush(PSCMSETTINGS *ppSettingsStack, PSCMSETTINGS pSettings)
2465{
2466 PSCMSETTINGS pOld = *ppSettingsStack;
2467 pSettings->pDown = pOld;
2468 pSettings->pUp = NULL;
2469 if (pOld)
2470 pOld->pUp = pSettings;
2471 *ppSettingsStack = pSettings;
2472}
2473
2474/**
2475 * Pushes the settings of the specified directory onto the stack.
2476 *
2477 * We will load any .scm-settings in the directory. A stack entry is added even
2478 * if no settings file was found.
2479 *
2480 * @returns IPRT status code.
2481 * @param ppSettingsStack The pointer to the pointer to the top stack
2482 * element. This will be used as input and output.
2483 * @param pszDir The directory to do this for.
2484 */
2485static int scmSettingsStackPushDir(PSCMSETTINGS *ppSettingsStack, const char *pszDir)
2486{
2487 char szFile[RTPATH_MAX];
2488 int rc = RTPathJoin(szFile, sizeof(szFile), pszDir, SCM_SETTINGS_FILENAME);
2489 if (RT_SUCCESS(rc))
2490 {
2491 PSCMSETTINGS pSettings;
2492 rc = scmSettingsCreate(&pSettings, &(*ppSettingsStack)->Base);
2493 if (RT_SUCCESS(rc))
2494 {
2495 if (RTFileExists(szFile))
2496 rc = scmSettingsLoadFile(pSettings, szFile);
2497 if (RT_SUCCESS(rc))
2498 {
2499 scmSettingsStackPush(ppSettingsStack, pSettings);
2500 return VINF_SUCCESS;
2501 }
2502
2503 scmSettingsDestroy(pSettings);
2504 }
2505 }
2506 return rc;
2507}
2508
2509
2510/**
2511 * Pops a settings set off the stack.
2512 *
2513 * @returns The popped setttings.
2514 * @param ppSettingsStack The pointer to the pointer to the top stack
2515 * element. This will be used as input and output.
2516 */
2517static PSCMSETTINGS scmSettingsStackPop(PSCMSETTINGS *ppSettingsStack)
2518{
2519 PSCMSETTINGS pRet = *ppSettingsStack;
2520 PSCMSETTINGS pNew = pRet ? pRet->pDown : NULL;
2521 *ppSettingsStack = pNew;
2522 if (pNew)
2523 pNew->pUp = NULL;
2524 if (pRet)
2525 {
2526 pRet->pUp = NULL;
2527 pRet->pDown = NULL;
2528 }
2529 return pRet;
2530}
2531
2532/**
2533 * Pops and destroys the top entry of the stack.
2534 *
2535 * @param ppSettingsStack The pointer to the pointer to the top stack
2536 * element. This will be used as input and output.
2537 */
2538static void scmSettingsStackPopAndDestroy(PSCMSETTINGS *ppSettingsStack)
2539{
2540 scmSettingsDestroy(scmSettingsStackPop(ppSettingsStack));
2541}
2542
2543/**
2544 * Constructs the base settings for the specified file name.
2545 *
2546 * @returns IPRT status code.
2547 * @param pSettingsStack The top element on the settings stack.
2548 * @param pszFilename The file name.
2549 * @param pszBasename The base name (pointer within @a pszFilename).
2550 * @param cchBasename The length of the base name. (For passing to
2551 * RTStrSimplePatternMultiMatch.)
2552 * @param pBase Base settings to initialize.
2553 */
2554static int scmSettingsStackMakeFileBase(PCSCMSETTINGS pSettingsStack, const char *pszFilename,
2555 const char *pszBasename, size_t cchBasename, PSCMSETTINGSBASE pBase)
2556{
2557 int rc = scmSettingsBaseInitAndCopy(pBase, &pSettingsStack->Base);
2558 if (RT_SUCCESS(rc))
2559 {
2560 /* find the bottom entry in the stack. */
2561 PCSCMSETTINGS pCur = pSettingsStack;
2562 while (pCur->pDown)
2563 pCur = pCur->pDown;
2564
2565 /* Work our way up thru the stack and look for matching pairs. */
2566 while (pCur)
2567 {
2568 size_t const cPairs = pCur->cPairs;
2569 if (cPairs)
2570 {
2571 for (size_t i = 0; i < cPairs; i++)
2572 if ( RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
2573 pszBasename, cchBasename, NULL)
2574 || RTStrSimplePatternMultiMatch(pCur->paPairs[i].pszPattern, RTSTR_MAX,
2575 pszFilename, RTSTR_MAX, NULL))
2576 {
2577 rc = scmSettingsBaseParseString(pBase, pCur->paPairs[i].pszOptions);
2578 if (RT_FAILURE(rc))
2579 break;
2580 }
2581 if (RT_FAILURE(rc))
2582 break;
2583 }
2584
2585 /* advance */
2586 pCur = pCur->pUp;
2587 }
2588 }
2589 if (RT_FAILURE(rc))
2590 scmSettingsBaseDelete(pBase);
2591 return rc;
2592}
2593
2594
2595/* -=-=-=-=-=- misc -=-=-=-=-=- */
2596
2597
2598/**
2599 * Prints a verbose message if the level is high enough.
2600 *
2601 * @param pState The rewrite state. Optional.
2602 * @param iLevel The required verbosity level.
2603 * @param pszFormat The message format string. Can be NULL if we
2604 * only want to trigger the per file message.
2605 * @param ... Format arguments.
2606 */
2607static void ScmVerbose(PSCMRWSTATE pState, int iLevel, const char *pszFormat, ...)
2608{
2609 if (iLevel <= g_iVerbosity)
2610 {
2611 if (pState && !pState->fFirst)
2612 {
2613 RTPrintf("%s: info: --= Rewriting '%s' =--\n", g_szProgName, pState->pszFilename);
2614 pState->fFirst = true;
2615 }
2616 if (pszFormat)
2617 {
2618 RTPrintf(pState
2619 ? "%s: info: "
2620 : "%s: info: ",
2621 g_szProgName);
2622 va_list va;
2623 va_start(va, pszFormat);
2624 RTPrintfV(pszFormat, va);
2625 va_end(va);
2626 }
2627 }
2628}
2629
2630
2631/* -=-=-=-=-=- subversion -=-=-=-=-=- */
2632
2633#define SCM_WITHOUT_LIBSVN
2634
2635#ifdef SCM_WITHOUT_LIBSVN
2636
2637/**
2638 * Callback that is call for each path to search.
2639 */
2640static DECLCALLBACK(int) scmSvnFindSvnBinaryCallback(char const *pchPath, size_t cchPath, void *pvUser1, void *pvUser2)
2641{
2642 char *pszDst = (char *)pvUser1;
2643 size_t cchDst = (size_t)pvUser2;
2644 if (cchDst > cchPath)
2645 {
2646 memcpy(pszDst, pchPath, cchPath);
2647 pszDst[cchPath] = '\0';
2648#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
2649 int rc = RTPathAppend(pszDst, cchDst, "svn.exe");
2650#else
2651 int rc = RTPathAppend(pszDst, cchDst, "svn");
2652#endif
2653 if ( RT_SUCCESS(rc)
2654 && RTFileExists(pszDst))
2655 return VINF_SUCCESS;
2656 }
2657 return VERR_TRY_AGAIN;
2658}
2659
2660
2661/**
2662 * Finds the svn binary.
2663 *
2664 * @param pszPath Where to store it. Worst case, we'll return
2665 * "svn" here.
2666 * @param cchPath The size of the buffer pointed to by @a pszPath.
2667 */
2668static void scmSvnFindSvnBinary(char *pszPath, size_t cchPath)
2669{
2670 /** @todo code page fun... */
2671 Assert(cchPath >= sizeof("svn"));
2672#ifdef RT_OS_WINDOWS
2673 const char *pszEnvVar = RTEnvGet("Path");
2674#else
2675 const char *pszEnvVar = RTEnvGet("PATH");
2676#endif
2677 if (pszPath)
2678 {
2679#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
2680 int rc = RTPathTraverseList(pszEnvVar, ';', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);
2681#else
2682 int rc = RTPathTraverseList(pszEnvVar, ':', scmSvnFindSvnBinaryCallback, pszPath, (void *)cchPath);
2683#endif
2684 if (RT_SUCCESS(rc))
2685 return;
2686 }
2687 strcpy(pszPath, "svn");
2688}
2689
2690
2691/**
2692 * Construct a dot svn filename for the file being rewritten.
2693 *
2694 * @returns IPRT status code.
2695 * @param pState The rewrite state (for the name).
2696 * @param pszDir The directory, including ".svn/".
2697 * @param pszSuff The filename suffix.
2698 * @param pszDst The output buffer. RTPATH_MAX in size.
2699 */
2700static int scmSvnConstructName(PSCMRWSTATE pState, const char *pszDir, const char *pszSuff, char *pszDst)
2701{
2702 strcpy(pszDst, pState->pszFilename); /* ASSUMES sizeof(szBuf) <= sizeof(szPath) */
2703 RTPathStripFilename(pszDst);
2704
2705 int rc = RTPathAppend(pszDst, RTPATH_MAX, pszDir);
2706 if (RT_SUCCESS(rc))
2707 {
2708 rc = RTPathAppend(pszDst, RTPATH_MAX, RTPathFilename(pState->pszFilename));
2709 if (RT_SUCCESS(rc))
2710 {
2711 size_t cchDst = strlen(pszDst);
2712 size_t cchSuff = strlen(pszSuff);
2713 if (cchDst + cchSuff < RTPATH_MAX)
2714 {
2715 memcpy(&pszDst[cchDst], pszSuff, cchSuff + 1);
2716 return VINF_SUCCESS;
2717 }
2718 else
2719 rc = VERR_BUFFER_OVERFLOW;
2720 }
2721 }
2722 return rc;
2723}
2724
2725/**
2726 * Interprets the specified string as decimal numbers.
2727 *
2728 * @returns true if parsed successfully, false if not.
2729 * @param pch The string (not terminated).
2730 * @param cch The string length.
2731 * @param pu Where to return the value.
2732 */
2733static bool scmSvnReadNumber(const char *pch, size_t cch, size_t *pu)
2734{
2735 size_t u = 0;
2736 while (cch-- > 0)
2737 {
2738 char ch = *pch++;
2739 if (ch < '0' || ch > '9')
2740 return false;
2741 u *= 10;
2742 u += ch - '0';
2743 }
2744 *pu = u;
2745 return true;
2746}
2747
2748#endif /* SCM_WITHOUT_LIBSVN */
2749
2750/**
2751 * Checks if the file we're operating on is part of a SVN working copy.
2752 *
2753 * @returns true if it is, false if it isn't or we cannot tell.
2754 * @param pState The rewrite state to work on.
2755 */
2756static bool scmSvnIsInWorkingCopy(PSCMRWSTATE pState)
2757{
2758#ifdef SCM_WITHOUT_LIBSVN
2759 /*
2760 * Hack: check if the .svn/text-base/<file>.svn-base file exists.
2761 */
2762 char szPath[RTPATH_MAX];
2763 int rc = scmSvnConstructName(pState, ".svn/text-base/", ".svn-base", szPath);
2764 if (RT_SUCCESS(rc))
2765 return RTFileExists(szPath);
2766
2767#else
2768 NOREF(pState);
2769#endif
2770 return false;
2771}
2772
2773/**
2774 * Queries the value of an SVN property.
2775 *
2776 * This will automatically adjust for scheduled changes.
2777 *
2778 * @returns IPRT status code.
2779 * @retval VERR_INVALID_STATE if not a SVN WC file.
2780 * @retval VERR_NOT_FOUND if the property wasn't found.
2781 * @param pState The rewrite state to work on.
2782 * @param pszName The property name.
2783 * @param ppszValue Where to return the property value. Free this
2784 * using RTStrFree. Optional.
2785 */
2786static int scmSvnQueryProperty(PSCMRWSTATE pState, const char *pszName, char **ppszValue)
2787{
2788 /*
2789 * Look it up in the scheduled changes.
2790 */
2791 uint32_t i = pState->cSvnPropChanges;
2792 while (i-- > 0)
2793 if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
2794 {
2795 const char *pszValue = pState->paSvnPropChanges[i].pszValue;
2796 if (!pszValue)
2797 return VERR_NOT_FOUND;
2798 if (ppszValue)
2799 return RTStrDupEx(ppszValue, pszValue);
2800 return VINF_SUCCESS;
2801 }
2802
2803#ifdef SCM_WITHOUT_LIBSVN
2804 /*
2805 * Hack: Read the .svn/props/<file>.svn-work file exists.
2806 */
2807 char szPath[RTPATH_MAX];
2808 int rc = scmSvnConstructName(pState, ".svn/props/", ".svn-work", szPath);
2809 if (RT_SUCCESS(rc) && !RTFileExists(szPath))
2810 rc = scmSvnConstructName(pState, ".svn/prop-base/", ".svn-base", szPath);
2811 if (RT_SUCCESS(rc))
2812 {
2813 SCMSTREAM Stream;
2814 rc = ScmStreamInitForReading(&Stream, szPath);
2815 if (RT_SUCCESS(rc))
2816 {
2817 /*
2818 * The current format is K len\n<name>\nV len\n<value>\n" ... END.
2819 */
2820 rc = VERR_NOT_FOUND;
2821 size_t const cchName = strlen(pszName);
2822 SCMEOL enmEol;
2823 size_t cchLine;
2824 const char *pchLine;
2825 while ((pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol)) != NULL)
2826 {
2827 /*
2828 * Parse the 'K num' / 'END' line.
2829 */
2830 if ( cchLine == 3
2831 && !memcmp(pchLine, "END", 3))
2832 break;
2833 size_t cchKey;
2834 if ( cchLine < 3
2835 || pchLine[0] != 'K'
2836 || pchLine[1] != ' '
2837 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchKey)
2838 || cchKey == 0
2839 || cchKey > 4096)
2840 {
2841 RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
2842 rc = VERR_PARSE_ERROR;
2843 break;
2844 }
2845
2846 /*
2847 * Match the key and skip to the value line. Don't bother with
2848 * names containing EOL markers.
2849 */
2850 size_t const offKey = ScmStreamTell(&Stream);
2851 bool fMatch = cchName == cchKey;
2852 if (fMatch)
2853 {
2854 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
2855 if (!pchLine)
2856 break;
2857 fMatch = cchLine == cchName
2858 && !memcmp(pchLine, pszName, cchName);
2859 }
2860
2861 if (RT_FAILURE(ScmStreamSeekAbsolute(&Stream, offKey + cchKey)))
2862 break;
2863 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
2864 break;
2865
2866 /*
2867 * Read and Parse the 'V num' line.
2868 */
2869 pchLine = ScmStreamGetLine(&Stream, &cchLine, &enmEol);
2870 if (!pchLine)
2871 break;
2872 size_t cchValue;
2873 if ( cchLine < 3
2874 || pchLine[0] != 'V'
2875 || pchLine[1] != ' '
2876 || !scmSvnReadNumber(&pchLine[2], cchLine - 2, &cchValue)
2877 || cchValue > _1M)
2878 {
2879 RTMsgError("%s:%u: Unexpected data '%.*s'\n", szPath, ScmStreamTellLine(&Stream), cchLine, pchLine);
2880 rc = VERR_PARSE_ERROR;
2881 break;
2882 }
2883
2884 /*
2885 * If we have a match, allocate a return buffer and read the
2886 * value into it. Otherwise skip this value and continue
2887 * searching.
2888 */
2889 if (fMatch)
2890 {
2891 if (!ppszValue)
2892 rc = VINF_SUCCESS;
2893 else
2894 {
2895 char *pszValue;
2896 rc = RTStrAllocEx(&pszValue, cchValue + 1);
2897 if (RT_SUCCESS(rc))
2898 {
2899 rc = ScmStreamRead(&Stream, pszValue, cchValue);
2900 if (RT_SUCCESS(rc))
2901 *ppszValue = pszValue;
2902 else
2903 RTStrFree(pszValue);
2904 }
2905 }
2906 break;
2907 }
2908
2909 if (RT_FAILURE(ScmStreamSeekRelative(&Stream, cchValue)))
2910 break;
2911 if (RT_FAILURE(ScmStreamSeekByLine(&Stream, ScmStreamTellLine(&Stream) + 1)))
2912 break;
2913 }
2914
2915 if (RT_FAILURE(ScmStreamGetStatus(&Stream)))
2916 {
2917 rc = ScmStreamGetStatus(&Stream);
2918 RTMsgError("%s: stream error %Rrc\n", szPath, rc);
2919 }
2920 ScmStreamDelete(&Stream);
2921 }
2922 }
2923
2924 if (rc == VERR_FILE_NOT_FOUND)
2925 rc = VERR_NOT_FOUND;
2926 return rc;
2927
2928#else
2929 NOREF(pState);
2930#endif
2931 return VERR_NOT_FOUND;
2932}
2933
2934
2935/**
2936 * Schedules the setting of a property.
2937 *
2938 * @returns IPRT status code.
2939 * @retval VERR_INVALID_STATE if not a SVN WC file.
2940 * @param pState The rewrite state to work on.
2941 * @param pszName The name of the property to set.
2942 * @param pszValue The value. NULL means deleting it.
2943 */
2944static int scmSvnSetProperty(PSCMRWSTATE pState, const char *pszName, const char *pszValue)
2945{
2946 /*
2947 * Update any existing entry first.
2948 */
2949 size_t i = pState->cSvnPropChanges;
2950 while (i-- > 0)
2951 if (!strcmp(pState->paSvnPropChanges[i].pszName, pszName))
2952 {
2953 if (!pszValue)
2954 {
2955 RTStrFree(pState->paSvnPropChanges[i].pszValue);
2956 pState->paSvnPropChanges[i].pszValue = NULL;
2957 }
2958 else
2959 {
2960 char *pszCopy;
2961 int rc = RTStrDupEx(&pszCopy, pszValue);
2962 if (RT_FAILURE(rc))
2963 return rc;
2964 pState->paSvnPropChanges[i].pszValue = pszCopy;
2965 }
2966 return VINF_SUCCESS;
2967 }
2968
2969 /*
2970 * Insert a new entry.
2971 */
2972 i = pState->cSvnPropChanges;
2973 if ((i % 32) == 0)
2974 {
2975 void *pvNew = RTMemRealloc(pState->paSvnPropChanges, (i + 32) * sizeof(SCMSVNPROP));
2976 if (!pvNew)
2977 return VERR_NO_MEMORY;
2978 pState->paSvnPropChanges = (PSCMSVNPROP)pvNew;
2979 }
2980
2981 pState->paSvnPropChanges[i].pszName = RTStrDup(pszName);
2982 pState->paSvnPropChanges[i].pszValue = pszValue ? RTStrDup(pszValue) : NULL;
2983 if ( pState->paSvnPropChanges[i].pszName
2984 && (pState->paSvnPropChanges[i].pszValue || !pszValue) )
2985 pState->cSvnPropChanges = i + 1;
2986 else
2987 {
2988 RTStrFree(pState->paSvnPropChanges[i].pszName);
2989 pState->paSvnPropChanges[i].pszName = NULL;
2990 RTStrFree(pState->paSvnPropChanges[i].pszValue);
2991 pState->paSvnPropChanges[i].pszValue = NULL;
2992 return VERR_NO_MEMORY;
2993 }
2994 return VINF_SUCCESS;
2995}
2996
2997
2998/**
2999 * Schedules a property deletion.
3000 *
3001 * @returns IPRT status code.
3002 * @param pState The rewrite state to work on.
3003 * @param pszName The name of the property to delete.
3004 */
3005static int scmSvnDelProperty(PSCMRWSTATE pState, const char *pszName)
3006{
3007 return scmSvnSetProperty(pState, pszName, NULL);
3008}
3009
3010
3011/**
3012 * Applies any SVN property changes to the work copy of the file.
3013 *
3014 * @returns IPRT status code.
3015 * @param pState The rewrite state which SVN property changes
3016 * should be applied.
3017 */
3018static int scmSvnDisplayChanges(PSCMRWSTATE pState)
3019{
3020 size_t i = pState->cSvnPropChanges;
3021 while (i-- > 0)
3022 {
3023 const char *pszName = pState->paSvnPropChanges[i].pszName;
3024 const char *pszValue = pState->paSvnPropChanges[i].pszValue;
3025 if (pszValue)
3026 ScmVerbose(pState, 0, "svn ps '%s' '%s' %s\n", pszName, pszValue, pState->pszFilename);
3027 else
3028 ScmVerbose(pState, 0, "svn pd '%s' %s\n", pszName, pszValue, pState->pszFilename);
3029 }
3030
3031 return VINF_SUCCESS;
3032}
3033
3034/**
3035 * Applies any SVN property changes to the work copy of the file.
3036 *
3037 * @returns IPRT status code.
3038 * @param pState The rewrite state which SVN property changes
3039 * should be applied.
3040 */
3041static int scmSvnApplyChanges(PSCMRWSTATE pState)
3042{
3043#ifdef SCM_WITHOUT_LIBSVN
3044 /*
3045 * This sucks. We gotta find svn(.exe).
3046 */
3047 static char s_szSvnPath[RTPATH_MAX];
3048 if (s_szSvnPath[0] == '\0')
3049 scmSvnFindSvnBinary(s_szSvnPath, sizeof(s_szSvnPath));
3050
3051 /*
3052 * Iterate thru the changes and apply them by starting the svn client.
3053 */
3054 for (size_t i = 0; i <pState->cSvnPropChanges; i++)
3055 {
3056 const char *apszArgv[6];
3057 apszArgv[0] = s_szSvnPath;
3058 apszArgv[1] = pState->paSvnPropChanges[i].pszValue ? "ps" : "pd";
3059 apszArgv[2] = pState->paSvnPropChanges[i].pszName;
3060 int iArg = 3;
3061 if (pState->paSvnPropChanges[i].pszValue)
3062 apszArgv[iArg++] = pState->paSvnPropChanges[i].pszValue;
3063 apszArgv[iArg++] = pState->pszFilename;
3064 apszArgv[iArg++] = NULL;
3065 ScmVerbose(pState, 2, "executing: %s %s %s %s %s\n",
3066 apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4]);
3067
3068 RTPROCESS pid;
3069 int rc = RTProcCreate(s_szSvnPath, apszArgv, RTENV_DEFAULT, 0 /*fFlags*/, &pid);
3070 if (RT_SUCCESS(rc))
3071 {
3072 RTPROCSTATUS Status;
3073 rc = RTProcWait(pid, RTPROCWAIT_FLAGS_BLOCK, &Status);
3074 if ( RT_SUCCESS(rc)
3075 && ( Status.enmReason != RTPROCEXITREASON_NORMAL
3076 || Status.iStatus != 0) )
3077 {
3078 RTMsgError("%s: %s %s %s %s %s -> %s %u\n",
3079 pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4],
3080 Status.enmReason == RTPROCEXITREASON_NORMAL ? "exit code"
3081 : Status.enmReason == RTPROCEXITREASON_SIGNAL ? "signal"
3082 : Status.enmReason == RTPROCEXITREASON_ABEND ? "abnormal end"
3083 : "abducted by alien",
3084 Status.iStatus);
3085 return VERR_GENERAL_FAILURE;
3086 }
3087 }
3088 if (RT_FAILURE(rc))
3089 {
3090 RTMsgError("%s: error executing %s %s %s %s %s: %Rrc\n",
3091 pState->pszFilename, apszArgv[0], apszArgv[1], apszArgv[2], apszArgv[3], apszArgv[4], rc);
3092 return rc;
3093 }
3094 }
3095
3096 return VINF_SUCCESS;
3097#else
3098 return VERR_NOT_IMPLEMENTED;
3099#endif
3100}
3101
3102
3103/* -=-=-=-=-=- rewriters -=-=-=-=-=- */
3104
3105
3106/**
3107 * Strip trailing blanks (space & tab).
3108 *
3109 * @returns True if modified, false if not.
3110 * @param pIn The input stream.
3111 * @param pOut The output stream.
3112 * @param pSettings The settings.
3113 */
3114static bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
3115{
3116 if (!pSettings->fStripTrailingBlanks)
3117 return false;
3118
3119 bool fModified = false;
3120 SCMEOL enmEol;
3121 size_t cchLine;
3122 const char *pchLine;
3123 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
3124 {
3125 int rc;
3126 if ( cchLine == 0
3127 || !RT_C_IS_BLANK(pchLine[cchLine - 1]) )
3128 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
3129 else
3130 {
3131 cchLine--;
3132 while (cchLine > 0 && RT_C_IS_BLANK(pchLine[cchLine - 1]))
3133 cchLine--;
3134 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
3135 fModified = true;
3136 }
3137 if (RT_FAILURE(rc))
3138 return false;
3139 }
3140 if (fModified)
3141 ScmVerbose(pState, 2, " * Stripped trailing blanks\n");
3142 return fModified;
3143}
3144
3145/**
3146 * Expand tabs.
3147 *
3148 * @returns True if modified, false if not.
3149 * @param pIn The input stream.
3150 * @param pOut The output stream.
3151 * @param pSettings The settings.
3152 */
3153static bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
3154{
3155 if (!pSettings->fConvertTabs)
3156 return false;
3157
3158 size_t const cchTab = pSettings->cchTab;
3159 bool fModified = false;
3160 SCMEOL enmEol;
3161 size_t cchLine;
3162 const char *pchLine;
3163 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
3164 {
3165 int rc;
3166 const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);
3167 if (!pchTab)
3168 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
3169 else
3170 {
3171 size_t offTab = 0;
3172 const char *pchChunk = pchLine;
3173 for (;;)
3174 {
3175 size_t cchChunk = pchTab - pchChunk;
3176 offTab += cchChunk;
3177 ScmStreamWrite(pOut, pchChunk, cchChunk);
3178
3179 size_t cchToTab = cchTab - offTab % cchTab;
3180 ScmStreamWrite(pOut, g_szTabSpaces, cchToTab);
3181 offTab += cchToTab;
3182
3183 pchChunk = pchTab + 1;
3184 size_t cchLeft = cchLine - (pchChunk - pchLine);
3185 pchTab = (const char *)memchr(pchChunk, '\t', cchLeft);
3186 if (!pchTab)
3187 {
3188 rc = ScmStreamPutLine(pOut, pchChunk, cchLeft, enmEol);
3189 break;
3190 }
3191 }
3192
3193 fModified = true;
3194 }
3195 if (RT_FAILURE(rc))
3196 return false;
3197 }
3198 if (fModified)
3199 ScmVerbose(pState, 2, " * Expanded tabs\n");
3200 return fModified;
3201}
3202
3203/**
3204 * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF.
3205 *
3206 * @returns true if modifications were made, false if not.
3207 * @param pIn The input stream.
3208 * @param pOut The output stream.
3209 * @param pSettings The settings.
3210 * @param enmDesiredEol The desired end of line indicator type.
3211 * @param pszDesiredSvnEol The desired svn:eol-style.
3212 */
3213static bool rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
3214 SCMEOL enmDesiredEol, const char *pszDesiredSvnEol)
3215{
3216 if (!pSettings->fConvertEol)
3217 return false;
3218
3219 bool fModified = false;
3220 SCMEOL enmEol;
3221 size_t cchLine;
3222 const char *pchLine;
3223 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
3224 {
3225 if ( enmEol != enmDesiredEol
3226 && enmEol != SCMEOL_NONE)
3227 {
3228 fModified = true;
3229 enmEol = enmDesiredEol;
3230 }
3231 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
3232 if (RT_FAILURE(rc))
3233 return false;
3234 }
3235 if (fModified)
3236 ScmVerbose(pState, 2, " * Converted EOL markers\n");
3237
3238 /* Check svn:eol-style if appropriate */
3239 if ( pSettings->fSetSvnEol
3240 && scmSvnIsInWorkingCopy(pState))
3241 {
3242 char *pszEol;
3243 int rc = scmSvnQueryProperty(pState, "svn:eol-style", &pszEol);
3244 if ( (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol))
3245 || rc == VERR_NOT_FOUND)
3246 {
3247 if (rc == VERR_NOT_FOUND)
3248 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (missing)\n", pszDesiredSvnEol);
3249 else
3250 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol);
3251 int rc2 = scmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol);
3252 if (RT_FAILURE(rc2))
3253 RTMsgError("scmSvnSetProperty: %Rrc\n", rc2); /** @todo propagate the error somehow... */
3254 }
3255 if (RT_SUCCESS(rc))
3256 RTStrFree(pszEol);
3257 }
3258
3259 /** @todo also check the subversion svn:eol-style state! */
3260 return fModified;
3261}
3262
3263/**
3264 * Force native end of line indicator.
3265 *
3266 * @returns true if modifications were made, false if not.
3267 * @param pIn The input stream.
3268 * @param pOut The output stream.
3269 * @param pSettings The settings.
3270 */
3271static bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
3272{
3273#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
3274 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "native");
3275#else
3276 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "native");
3277#endif
3278}
3279
3280/**
3281 * Force the stream to use LF as the end of line indicator.
3282 *
3283 * @returns true if modifications were made, false if not.
3284 * @param pIn The input stream.
3285 * @param pOut The output stream.
3286 * @param pSettings The settings.
3287 */
3288static bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
3289{
3290 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "LF");
3291}
3292
3293/**
3294 * Force the stream to use CRLF as the end of line indicator.
3295 *
3296 * @returns true if modifications were made, false if not.
3297 * @param pIn The input stream.
3298 * @param pOut The output stream.
3299 * @param pSettings The settings.
3300 */
3301static bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
3302{
3303 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "CRLF");
3304}
3305
3306/**
3307 * Strip trailing blank lines and/or make sure there is exactly one blank line
3308 * at the end of the file.
3309 *
3310 * @returns true if modifications were made, false if not.
3311 * @param pIn The input stream.
3312 * @param pOut The output stream.
3313 * @param pSettings The settings.
3314 *
3315 * @remarks ASSUMES trailing white space has been removed already.
3316 */
3317static bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
3318{
3319 if ( !pSettings->fStripTrailingLines
3320 && !pSettings->fForceTrailingLine
3321 && !pSettings->fForceFinalEol)
3322 return false;
3323
3324 size_t const cLines = ScmStreamCountLines(pIn);
3325
3326 /* Empty files remains empty. */
3327 if (cLines <= 1)
3328 return false;
3329
3330 /* Figure out if we need to adjust the number of lines or not. */
3331 size_t cLinesNew = cLines;
3332
3333 if ( pSettings->fStripTrailingLines
3334 && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
3335 {
3336 while ( cLinesNew > 1
3337 && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))
3338 cLinesNew--;
3339 }
3340
3341 if ( pSettings->fForceTrailingLine
3342 && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
3343 cLinesNew++;
3344
3345 bool fFixMissingEol = pSettings->fForceFinalEol
3346 && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;
3347
3348 if ( !fFixMissingEol
3349 && cLines == cLinesNew)
3350 return false;
3351
3352 /* Copy the number of lines we've arrived at. */
3353 ScmStreamRewindForReading(pIn);
3354
3355 size_t cCopied = RT_MIN(cLinesNew, cLines);
3356 ScmStreamCopyLines(pOut, pIn, cCopied);
3357
3358 if (cCopied != cLinesNew)
3359 {
3360 while (cCopied++ < cLinesNew)
3361 ScmStreamPutLine(pOut, "", 0, ScmStreamGetEol(pIn));
3362 }
3363 /* Fix missing EOL if required. */
3364 else if (fFixMissingEol)
3365 {
3366 if (ScmStreamGetEol(pIn) == SCMEOL_LF)
3367 ScmStreamWrite(pOut, "\n", 1);
3368 else
3369 ScmStreamWrite(pOut, "\r\n", 2);
3370 }
3371
3372 ScmVerbose(pState, 2, " * Adjusted trailing blank lines\n");
3373 return true;
3374}
3375
3376/**
3377 * Make sure there is no svn:executable keyword on the current file.
3378 *
3379 * @returns false - the state carries these kinds of changes.
3380 * @param pState The rewriter state.
3381 * @param pIn The input stream.
3382 * @param pOut The output stream.
3383 * @param pSettings The settings.
3384 */
3385static bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
3386{
3387 if ( !pSettings->fSetSvnExecutable
3388 || !scmSvnIsInWorkingCopy(pState))
3389 return false;
3390
3391 int rc = scmSvnQueryProperty(pState, "svn:executable", NULL);
3392 if (RT_SUCCESS(rc))
3393 {
3394 ScmVerbose(pState, 2, " * removing svn:executable\n");
3395 rc = scmSvnDelProperty(pState, "svn:executable");
3396 if (RT_FAILURE(rc))
3397 RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
3398 }
3399 return false;
3400}
3401
3402/**
3403 * Make sure the Id and Revision keywords are expanded.
3404 *
3405 * @returns false - the state carries these kinds of changes.
3406 * @param pState The rewriter state.
3407 * @param pIn The input stream.
3408 * @param pOut The output stream.
3409 * @param pSettings The settings.
3410 */
3411static bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
3412{
3413 if ( !pSettings->fSetSvnKeywords
3414 || !scmSvnIsInWorkingCopy(pState))
3415 return false;
3416
3417 char *pszKeywords;
3418 int rc = scmSvnQueryProperty(pState, "svn:keywords", &pszKeywords);
3419 if ( RT_SUCCESS(rc)
3420 && ( !strstr(pszKeywords, "Id") /** @todo need some function for finding a word in a string. */
3421 || !strstr(pszKeywords, "Revision")) )
3422 {
3423 if (!strstr(pszKeywords, "Id") && !strstr(pszKeywords, "Revision"))
3424 rc = RTStrAAppend(&pszKeywords, " Id Revision");
3425 else if (!strstr(pszKeywords, "Id"))
3426 rc = RTStrAAppend(&pszKeywords, " Id");
3427 else
3428 rc = RTStrAAppend(&pszKeywords, " Revision");
3429 if (RT_SUCCESS(rc))
3430 {
3431 ScmVerbose(pState, 2, " * changing svn:keywords to '%s'\n", pszKeywords);
3432 rc = scmSvnSetProperty(pState, "svn:keywords", pszKeywords);
3433 if (RT_FAILURE(rc))
3434 RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
3435 }
3436 else
3437 RTMsgError("RTStrAppend: %Rrc\n", rc); /** @todo error propagation here.. */
3438 RTStrFree(pszKeywords);
3439 }
3440 else if (rc == VERR_NOT_FOUND)
3441 {
3442 ScmVerbose(pState, 2, " * setting svn:keywords to 'Id Revision'\n");
3443 rc = scmSvnSetProperty(pState, "svn:keywords", "Id Revision");
3444 if (RT_FAILURE(rc))
3445 RTMsgError("scmSvnSetProperty: %Rrc\n", rc); /** @todo error propagation here.. */
3446 }
3447 else if (RT_SUCCESS(rc))
3448 RTStrFree(pszKeywords);
3449
3450 return false;
3451}
3452
3453/**
3454 * Makefile.kup are empty files, enforce this.
3455 *
3456 * @returns true if modifications were made, false if not.
3457 * @param pIn The input stream.
3458 * @param pOut The output stream.
3459 * @param pSettings The settings.
3460 */
3461static bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
3462{
3463 /* These files should be zero bytes. */
3464 if (pIn->cb == 0)
3465 return false;
3466 ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
3467 return true;
3468}
3469
3470/**
3471 * Rewrite a kBuild makefile.
3472 *
3473 * @returns true if modifications were made, false if not.
3474 * @param pIn The input stream.
3475 * @param pOut The output stream.
3476 * @param pSettings The settings.
3477 *
3478 * @todo
3479 *
3480 * Ideas for Makefile.kmk and Config.kmk:
3481 * - sort if1of/ifn1of sets.
3482 * - line continuation slashes should only be preceeded by one space.
3483 */
3484static bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
3485{
3486 return false;
3487}
3488
3489/**
3490 * Rewrite a C/C++ source or header file.
3491 *
3492 * @returns true if modifications were made, false if not.
3493 * @param pIn The input stream.
3494 * @param pOut The output stream.
3495 * @param pSettings The settings.
3496 *
3497 * @todo
3498 *
3499 * Ideas for C/C++:
3500 * - space after if, while, for, switch
3501 * - spaces in for (i=0;i<x;i++)
3502 * - complex conditional, bird style.
3503 * - remove unnecessary parentheses.
3504 * - sort defined RT_OS_*|| and RT_ARCH
3505 * - sizeof without parenthesis.
3506 * - defined without parenthesis.
3507 * - trailing spaces.
3508 * - parameter indentation.
3509 * - space after comma.
3510 * - while (x--); -> multi line + comment.
3511 * - else statement;
3512 * - space between function and left parenthesis.
3513 * - TODO, XXX, @todo cleanup.
3514 * - Space before/after '*'.
3515 * - ensure new line at end of file.
3516 * - Indentation of precompiler statements (#ifdef, #defines).
3517 * - space between functions.
3518 */
3519static bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
3520{
3521
3522 return false;
3523}
3524
3525/* -=-=-=-=-=- file and directory processing -=-=-=-=-=- */
3526
3527/**
3528 * Processes a file.
3529 *
3530 * @returns IPRT status code.
3531 * @param pState The rewriter state.
3532 * @param pszFilename The file name.
3533 * @param pszBasename The base name (pointer within @a pszFilename).
3534 * @param cchBasename The length of the base name. (For passing to
3535 * RTStrSimplePatternMultiMatch.)
3536 * @param pBaseSettings The base settings to use. It's OK to modify
3537 * these.
3538 */
3539static int scmProcessFileInner(PSCMRWSTATE pState, const char *pszFilename, const char *pszBasename, size_t cchBasename,
3540 PSCMSETTINGSBASE pBaseSettings)
3541{
3542 /*
3543 * Do the file level filtering.
3544 */
3545 if ( pBaseSettings->pszFilterFiles
3546 && *pBaseSettings->pszFilterFiles
3547 && !RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterFiles, RTSTR_MAX, pszBasename, cchBasename, NULL))
3548 {
3549 ScmVerbose(NULL, 5, "skipping '%s': file filter mismatch\n", pszFilename);
3550 return VINF_SUCCESS;
3551 }
3552 if ( pBaseSettings->pszFilterOutFiles
3553 && *pBaseSettings->pszFilterOutFiles
3554 && ( RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszBasename, cchBasename, NULL)
3555 || RTStrSimplePatternMultiMatch(pBaseSettings->pszFilterOutFiles, RTSTR_MAX, pszFilename, RTSTR_MAX, NULL)) )
3556 {
3557 ScmVerbose(NULL, 5, "skipping '%s': filterd out\n", pszFilename);
3558 return VINF_SUCCESS;
3559 }
3560 if ( pBaseSettings->fOnlySvnFiles
3561 && !scmSvnIsInWorkingCopy(pState))
3562 {
3563 ScmVerbose(NULL, 5, "skipping '%s': not in SVN WC\n", pszFilename);
3564 return VINF_SUCCESS;
3565 }
3566
3567 /*
3568 * Try find a matching rewrite config for this filename.
3569 */
3570 PCSCMCFGENTRY pCfg = NULL;
3571 for (size_t iCfg = 0; iCfg < RT_ELEMENTS(g_aConfigs); iCfg++)
3572 if (RTStrSimplePatternMultiMatch(g_aConfigs[iCfg].pszFilePattern, RTSTR_MAX, pszBasename, cchBasename, NULL))
3573 {
3574 pCfg = &g_aConfigs[iCfg];
3575 break;
3576 }
3577 if (!pCfg)
3578 {
3579 ScmVerbose(NULL, 4, "skipping '%s': no rewriters configured\n", pszFilename);
3580 return VINF_SUCCESS;
3581 }
3582 ScmVerbose(pState, 4, "matched \"%s\"\n", pCfg->pszFilePattern);
3583
3584 /*
3585 * Create an input stream from the file and check that it's text.
3586 */
3587 SCMSTREAM Stream1;
3588 int rc = ScmStreamInitForReading(&Stream1, pszFilename);
3589 if (RT_FAILURE(rc))
3590 {
3591 RTMsgError("Failed to read '%s': %Rrc\n", pszFilename, rc);
3592 return rc;
3593 }
3594 if (ScmStreamIsText(&Stream1))
3595 {
3596 ScmVerbose(pState, 3, NULL);
3597
3598 /*
3599 * Gather SCM and editor settings from the stream.
3600 */
3601 rc = scmSettingsBaseLoadFromDocument(pBaseSettings, &Stream1);
3602 if (RT_SUCCESS(rc))
3603 {
3604 ScmStreamRewindForReading(&Stream1);
3605
3606 /*
3607 * Create two more streams for output and push the text thru all the
3608 * rewriters, switching the two streams around when something is
3609 * actually rewritten. Stream1 remains unchanged.
3610 */
3611 SCMSTREAM Stream2;
3612 rc = ScmStreamInitForWriting(&Stream2, &Stream1);
3613 if (RT_SUCCESS(rc))
3614 {
3615 SCMSTREAM Stream3;
3616 rc = ScmStreamInitForWriting(&Stream3, &Stream1);
3617 if (RT_SUCCESS(rc))
3618 {
3619 bool fModified = false;
3620 PSCMSTREAM pIn = &Stream1;
3621 PSCMSTREAM pOut = &Stream2;
3622 for (size_t iRw = 0; iRw < pCfg->cRewriters; iRw++)
3623 {
3624 bool fRc = pCfg->papfnRewriter[iRw](pState, pIn, pOut, pBaseSettings);
3625 if (fRc)
3626 {
3627 PSCMSTREAM pTmp = pOut;
3628 pOut = pIn == &Stream1 ? &Stream3 : pIn;
3629 pIn = pTmp;
3630 fModified = true;
3631 }
3632 ScmStreamRewindForReading(pIn);
3633 ScmStreamRewindForWriting(pOut);
3634 }
3635
3636 rc = ScmStreamGetStatus(&Stream1);
3637 if (RT_SUCCESS(rc))
3638 rc = ScmStreamGetStatus(&Stream2);
3639 if (RT_SUCCESS(rc))
3640 rc = ScmStreamGetStatus(&Stream3);
3641 if (RT_SUCCESS(rc))
3642 {
3643 /*
3644 * If rewritten, write it back to disk.
3645 */
3646 if (fModified)
3647 {
3648 if (!g_fDryRun)
3649 {
3650 ScmVerbose(pState, 1, "writing modified file to \"%s%s\"\n", pszFilename, g_pszChangedSuff);
3651 rc = ScmStreamWriteToFile(pIn, "%s%s", pszFilename, g_pszChangedSuff);
3652 if (RT_FAILURE(rc))
3653 RTMsgError("Error writing '%s%s': %Rrc\n", pszFilename, g_pszChangedSuff, rc);
3654 }
3655 else
3656 {
3657 ScmVerbose(pState, 1, NULL);
3658 ScmDiffStreams(pszFilename, &Stream1, pIn, g_fDiffIgnoreEol, g_fDiffIgnoreLeadingWS,
3659 g_fDiffIgnoreTrailingWS, g_fDiffSpecialChars, pBaseSettings->cchTab, g_pStdOut);
3660 ScmVerbose(pState, 2, "would have modified the file \"%s%s\"\n", pszFilename, g_pszChangedSuff);
3661 }
3662 }
3663
3664 /*
3665 * If pending SVN property changes, apply them.
3666 */
3667 if (pState->cSvnPropChanges && RT_SUCCESS(rc))
3668 {
3669 if (!g_fDryRun)
3670 {
3671 rc = scmSvnApplyChanges(pState);
3672 if (RT_FAILURE(rc))
3673 RTMsgError("%s: failed to apply SVN property changes (%Rrc)\n", pszFilename, rc);
3674 }
3675 else
3676 scmSvnDisplayChanges(pState);
3677 }
3678
3679 if (!fModified && !pState->cSvnPropChanges)
3680 ScmVerbose(pState, 3, "no change\n", pszFilename);
3681 }
3682 else
3683 RTMsgError("%s: stream error %Rrc\n", pszFilename);
3684 ScmStreamDelete(&Stream3);
3685 }
3686 else
3687 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
3688 ScmStreamDelete(&Stream2);
3689 }
3690 else
3691 RTMsgError("Failed to init stream for writing: %Rrc\n", rc);
3692 }
3693 else
3694 RTMsgError("scmSettingsBaseLoadFromDocument: %Rrc\n", rc);
3695 }
3696 else
3697 ScmVerbose(pState, 4, "not text file: \"%s\"\n", pszFilename);
3698 ScmStreamDelete(&Stream1);
3699
3700 return rc;
3701}
3702
3703/**
3704 * Processes a file.
3705 *
3706 * This is just a wrapper for scmProcessFileInner for avoid wasting stack in the
3707 * directory recursion method.
3708 *
3709 * @returns IPRT status code.
3710 * @param pszFilename The file name.
3711 * @param pszBasename The base name (pointer within @a pszFilename).
3712 * @param cchBasename The length of the base name. (For passing to
3713 * RTStrSimplePatternMultiMatch.)
3714 * @param pSettingsStack The settings stack (pointer to the top element).
3715 */
3716static int scmProcessFile(const char *pszFilename, const char *pszBasename, size_t cchBasename,
3717 PSCMSETTINGS pSettingsStack)
3718{
3719 SCMSETTINGSBASE Base;
3720 int rc = scmSettingsStackMakeFileBase(pSettingsStack, pszFilename, pszBasename, cchBasename, &Base);
3721 if (RT_SUCCESS(rc))
3722 {
3723 SCMRWSTATE State;
3724 State.fFirst = false;
3725 State.pszFilename = pszFilename;
3726 State.cSvnPropChanges = 0;
3727 State.paSvnPropChanges = NULL;
3728
3729 rc = scmProcessFileInner(&State, pszFilename, pszBasename, cchBasename, &Base);
3730
3731 size_t i = State.cSvnPropChanges;
3732 while (i-- > 0)
3733 {
3734 RTStrFree(State.paSvnPropChanges[i].pszName);
3735 RTStrFree(State.paSvnPropChanges[i].pszValue);
3736 }
3737 RTMemFree(State.paSvnPropChanges);
3738
3739 scmSettingsBaseDelete(&Base);
3740 }
3741 return rc;
3742}
3743
3744
3745/**
3746 * Tries to correct RTDIRENTRY_UNKNOWN.
3747 *
3748 * @returns Corrected type.
3749 * @param pszPath The path to the object in question.
3750 */
3751static RTDIRENTRYTYPE scmFigureUnknownType(const char *pszPath)
3752{
3753 RTFSOBJINFO Info;
3754 int rc = RTPathQueryInfo(pszPath, &Info, RTFSOBJATTRADD_NOTHING);
3755 if (RT_FAILURE(rc))
3756 return RTDIRENTRYTYPE_UNKNOWN;
3757 if (RTFS_IS_DIRECTORY(Info.Attr.fMode))
3758 return RTDIRENTRYTYPE_DIRECTORY;
3759 if (RTFS_IS_FILE(Info.Attr.fMode))
3760 return RTDIRENTRYTYPE_FILE;
3761 return RTDIRENTRYTYPE_UNKNOWN;
3762}
3763
3764/**
3765 * Recurse into a sub-directory and process all the files and directories.
3766 *
3767 * @returns IPRT status code.
3768 * @param pszBuf Path buffer containing the directory path on
3769 * entry. This ends with a dot. This is passed
3770 * along when recusing in order to save stack space
3771 * and avoid needless copying.
3772 * @param cchDir Length of our path in pszbuf.
3773 * @param pEntry Directory entry buffer. This is also passed
3774 * along when recursing to save stack space.
3775 * @param pSettingsStack The settings stack (pointer to the top element).
3776 * @param iRecursion The recursion depth. This is used to restrict
3777 * the recursions.
3778 */
3779static int scmProcessDirTreeRecursion(char *pszBuf, size_t cchDir, PRTDIRENTRY pEntry,
3780 PSCMSETTINGS pSettingsStack, unsigned iRecursion)
3781{
3782 int rc;
3783 Assert(cchDir > 1 && pszBuf[cchDir - 1] == '.');
3784
3785 /*
3786 * Make sure we stop somewhere.
3787 */
3788 if (iRecursion > 128)
3789 {
3790 RTMsgError("recursion too deep: %d\n", iRecursion);
3791 return VINF_SUCCESS; /* ignore */
3792 }
3793
3794 /*
3795 * Check if it's excluded by --only-svn-dir.
3796 */
3797 if (pSettingsStack->Base.fOnlySvnDirs)
3798 {
3799 rc = RTPathAppend(pszBuf, RTPATH_MAX, ".svn");
3800 if (RT_FAILURE(rc))
3801 {
3802 RTMsgError("RTPathAppend: %Rrc\n", rc);
3803 return rc;
3804 }
3805 if (!RTDirExists(pszBuf))
3806 return VINF_SUCCESS;
3807
3808 Assert(RTPATH_IS_SLASH(pszBuf[cchDir]));
3809 pszBuf[cchDir] = '\0';
3810 pszBuf[cchDir - 1] = '.';
3811 }
3812
3813 /*
3814 * Try open and read the directory.
3815 */
3816 PRTDIR pDir;
3817 rc = RTDirOpenFiltered(&pDir, pszBuf, RTDIRFILTER_NONE);
3818 if (RT_FAILURE(rc))
3819 {
3820 RTMsgError("Failed to enumerate directory '%s': %Rrc", pszBuf, rc);
3821 return rc;
3822 }
3823 for (;;)
3824 {
3825 /* Read the next entry. */
3826 rc = RTDirRead(pDir, pEntry, NULL);
3827 if (RT_FAILURE(rc))
3828 {
3829 if (rc == VERR_NO_MORE_FILES)
3830 rc = VINF_SUCCESS;
3831 else
3832 RTMsgError("RTDirRead -> %Rrc\n", rc);
3833 break;
3834 }
3835
3836 /* Skip '.' and '..'. */
3837 if ( pEntry->szName[0] == '.'
3838 && ( pEntry->cbName == 1
3839 || ( pEntry->cbName == 2
3840 && pEntry->szName[1] == '.')))
3841 continue;
3842
3843 /* Enter it into the buffer so we've got a full name to work
3844 with when needed. */
3845 if (pEntry->cbName + cchDir >= RTPATH_MAX)
3846 {
3847 RTMsgError("Skipping too long entry: %s", pEntry->szName);
3848 continue;
3849 }
3850 memcpy(&pszBuf[cchDir - 1], pEntry->szName, pEntry->cbName + 1);
3851
3852 /* Figure the type. */
3853 RTDIRENTRYTYPE enmType = pEntry->enmType;
3854 if (enmType == RTDIRENTRYTYPE_UNKNOWN)
3855 enmType = scmFigureUnknownType(pszBuf);
3856
3857 /* Process the file or directory, skip the rest. */
3858 if (enmType == RTDIRENTRYTYPE_FILE)
3859 rc = scmProcessFile(pszBuf, pEntry->szName, pEntry->cbName, pSettingsStack);
3860 else if (enmType == RTDIRENTRYTYPE_DIRECTORY)
3861 {
3862 /* Append the dot for the benefit of the pattern matching. */
3863 if (pEntry->cbName + cchDir + 5 >= RTPATH_MAX)
3864 {
3865 RTMsgError("Skipping too deep dir entry: %s", pEntry->szName);
3866 continue;
3867 }
3868 memcpy(&pszBuf[cchDir - 1 + pEntry->cbName], "/.", sizeof("/."));
3869 size_t cchSubDir = cchDir - 1 + pEntry->cbName + sizeof("/.") - 1;
3870
3871 if ( !pSettingsStack->Base.pszFilterOutDirs
3872 || !*pSettingsStack->Base.pszFilterOutDirs
3873 || ( !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
3874 pEntry->szName, pEntry->cbName, NULL)
3875 && !RTStrSimplePatternMultiMatch(pSettingsStack->Base.pszFilterOutDirs, RTSTR_MAX,
3876 pszBuf, cchSubDir, NULL)
3877 )
3878 )
3879 {
3880 rc = scmSettingsStackPushDir(&pSettingsStack, pszBuf);
3881 if (RT_SUCCESS(rc))
3882 {
3883 rc = scmProcessDirTreeRecursion(pszBuf, cchSubDir, pEntry, pSettingsStack, iRecursion + 1);
3884 scmSettingsStackPopAndDestroy(&pSettingsStack);
3885 }
3886 }
3887 }
3888 if (RT_FAILURE(rc))
3889 break;
3890 }
3891 RTDirClose(pDir);
3892 return rc;
3893
3894}
3895
3896/**
3897 * Process a directory tree.
3898 *
3899 * @returns IPRT status code.
3900 * @param pszDir The directory to start with. This is pointer to
3901 * a RTPATH_MAX sized buffer.
3902 */
3903static int scmProcessDirTree(char *pszDir, PSCMSETTINGS pSettingsStack)
3904{
3905 /*
3906 * Setup the recursion.
3907 */
3908 int rc = RTPathAppend(pszDir, RTPATH_MAX, ".");
3909 if (RT_SUCCESS(rc))
3910 {
3911 RTDIRENTRY Entry;
3912 rc = scmProcessDirTreeRecursion(pszDir, strlen(pszDir), &Entry, pSettingsStack, 0);
3913 }
3914 else
3915 RTMsgError("RTPathAppend: %Rrc\n", rc);
3916 return rc;
3917}
3918
3919
3920/**
3921 * Processes a file or directory specified as an command line argument.
3922 *
3923 * @returns IPRT status code
3924 * @param pszSomething What we found in the commad line arguments.
3925 * @param pSettingsStack The settings stack (pointer to the top element).
3926 */
3927static int scmProcessSomething(const char *pszSomething, PSCMSETTINGS pSettingsStack)
3928{
3929 char szBuf[RTPATH_MAX];
3930 int rc = RTPathAbs(pszSomething, szBuf, sizeof(szBuf));
3931 if (RT_SUCCESS(rc))
3932 {
3933 RTPathChangeToUnixSlashes(szBuf, false /*fForce*/);
3934
3935 PSCMSETTINGS pSettings;
3936 rc = scmSettingsCreateForPath(&pSettings, &pSettingsStack->Base, szBuf);
3937 if (RT_SUCCESS(rc))
3938 {
3939 scmSettingsStackPush(&pSettingsStack, pSettings);
3940
3941 if (RTFileExists(szBuf))
3942 {
3943 const char *pszBasename = RTPathFilename(szBuf);
3944 if (pszBasename)
3945 {
3946 size_t cchBasename = strlen(pszBasename);
3947 rc = scmProcessFile(szBuf, pszBasename, cchBasename, pSettingsStack);
3948 }
3949 else
3950 {
3951 RTMsgError("RTPathFilename: NULL\n");
3952 rc = VERR_IS_A_DIRECTORY;
3953 }
3954 }
3955 else
3956 rc = scmProcessDirTree(szBuf, pSettingsStack);
3957
3958 PSCMSETTINGS pPopped = scmSettingsStackPop(&pSettingsStack);
3959 Assert(pPopped == pSettings);
3960 scmSettingsDestroy(pSettings);
3961 }
3962 else
3963 RTMsgError("scmSettingsInitStack: %Rrc\n", rc);
3964 }
3965 else
3966 RTMsgError("RTPathAbs: %Rrc\n", rc);
3967 return rc;
3968}
3969
3970int main(int argc, char **argv)
3971{
3972 int rc = RTR3Init();
3973 if (RT_FAILURE(rc))
3974 return 1;
3975
3976 /*
3977 * Init the settings.
3978 */
3979 PSCMSETTINGS pSettings;
3980 rc = scmSettingsCreate(&pSettings, &g_Defaults);
3981 if (RT_FAILURE(rc))
3982 {
3983 RTMsgError("scmSettingsCreate: %Rrc\n", rc);
3984 return 1;
3985 }
3986
3987 /*
3988 * Parse arguments and process input in order (because this is the only
3989 * thing that works at the moment).
3990 */
3991 static RTGETOPTDEF s_aOpts[14 + RT_ELEMENTS(g_aScmOpts)] =
3992 {
3993 { "--dry-run", 'd', RTGETOPT_REQ_NOTHING },
3994 { "--real-run", 'D', RTGETOPT_REQ_NOTHING },
3995 { "--file-filter", 'f', RTGETOPT_REQ_STRING },
3996 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
3997 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
3998 { "--diff-ignore-eol", SCMOPT_DIFF_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
3999 { "--diff-no-ignore-eol", SCMOPT_DIFF_NO_IGNORE_EOL, RTGETOPT_REQ_NOTHING },
4000 { "--diff-ignore-space", SCMOPT_DIFF_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
4001 { "--diff-no-ignore-space", SCMOPT_DIFF_NO_IGNORE_SPACE, RTGETOPT_REQ_NOTHING },
4002 { "--diff-ignore-leading-space", SCMOPT_DIFF_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
4003 { "--diff-no-ignore-leading-space", SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE, RTGETOPT_REQ_NOTHING },
4004 { "--diff-ignore-trailing-space", SCMOPT_DIFF_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
4005 { "--diff-no-ignore-trailing-space", SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE, RTGETOPT_REQ_NOTHING },
4006 { "--diff-special-chars", SCMOPT_DIFF_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
4007 { "--diff-no-special-chars", SCMOPT_DIFF_NO_SPECIAL_CHARS, RTGETOPT_REQ_NOTHING },
4008 };
4009 memcpy(&s_aOpts[RT_ELEMENTS(s_aOpts) - RT_ELEMENTS(g_aScmOpts)], &g_aScmOpts[0], sizeof(g_aScmOpts));
4010
4011 RTGETOPTUNION ValueUnion;
4012 RTGETOPTSTATE GetOptState;
4013 rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
4014 AssertReleaseRCReturn(rc, 1);
4015 size_t cProcessed = 0;
4016
4017 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
4018 {
4019 switch (rc)
4020 {
4021 case 'd':
4022 g_fDryRun = true;
4023 break;
4024 case 'D':
4025 g_fDryRun = false;
4026 break;
4027
4028 case 'f':
4029 g_pszFileFilter = ValueUnion.psz;
4030 break;
4031
4032 case 'h':
4033 RTPrintf("VirtualBox Source Code Massager\n"
4034 "\n"
4035 "Usage: %s [options] <files & dirs>\n"
4036 "\n"
4037 "Options:\n", g_szProgName);
4038 for (size_t i = 0; i < RT_ELEMENTS(s_aOpts); i++)
4039 {
4040 bool fAdvanceTwo = false;
4041 if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_NOTHING)
4042 {
4043 fAdvanceTwo = i + 1 < RT_ELEMENTS(s_aOpts)
4044 && ( strstr(s_aOpts[i+1].pszLong, "-no-") != NULL
4045 || strstr(s_aOpts[i+1].pszLong, "-not-") != NULL
4046 || strstr(s_aOpts[i+1].pszLong, "-dont-") != NULL
4047 );
4048 if (fAdvanceTwo)
4049 RTPrintf(" %s, %s\n", s_aOpts[i].pszLong, s_aOpts[i + 1].pszLong);
4050 else
4051 RTPrintf(" %s\n", s_aOpts[i].pszLong);
4052 }
4053 else if ((s_aOpts[i].fFlags & RTGETOPT_REQ_MASK) == RTGETOPT_REQ_STRING)
4054 RTPrintf(" %s string\n", s_aOpts[i].pszLong);
4055 else
4056 RTPrintf(" %s value\n", s_aOpts[i].pszLong);
4057 switch (s_aOpts[i].iShort)
4058 {
4059 case SCMOPT_CONVERT_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertEol); break;
4060 case SCMOPT_CONVERT_TABS: RTPrintf(" Default: %RTbool\n", g_Defaults.fConvertTabs); break;
4061 case SCMOPT_FORCE_FINAL_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceFinalEol); break;
4062 case SCMOPT_FORCE_TRAILING_LINE: RTPrintf(" Default: %RTbool\n", g_Defaults.fForceTrailingLine); break;
4063 case SCMOPT_STRIP_TRAILING_BLANKS: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingBlanks); break;
4064 case SCMOPT_STRIP_TRAILING_LINES: RTPrintf(" Default: %RTbool\n", g_Defaults.fStripTrailingLines); break;
4065 case SCMOPT_ONLY_SVN_DIRS: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnDirs); break;
4066 case SCMOPT_ONLY_SVN_FILES: RTPrintf(" Default: %RTbool\n", g_Defaults.fOnlySvnFiles); break;
4067 case SCMOPT_SET_SVN_EOL: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnEol); break;
4068 case SCMOPT_SET_SVN_EXECUTABLE: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnExecutable); break;
4069 case SCMOPT_SET_SVN_KEYWORDS: RTPrintf(" Default: %RTbool\n", g_Defaults.fSetSvnKeywords); break;
4070 case SCMOPT_TAB_SIZE: RTPrintf(" Default: %u\n", g_Defaults.cchTab); break;
4071 case SCMOPT_FILTER_OUT_DIRS: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutDirs); break;
4072 case SCMOPT_FILTER_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterFiles); break;
4073 case SCMOPT_FILTER_OUT_FILES: RTPrintf(" Default: %s\n", g_Defaults.pszFilterOutFiles); break;
4074 }
4075 i += fAdvanceTwo;
4076 }
4077 return 1;
4078
4079 case 'q':
4080 g_iVerbosity = 0;
4081 break;
4082
4083 case 'v':
4084 g_iVerbosity++;
4085 break;
4086
4087 case 'V':
4088 {
4089 /* The following is assuming that svn does it's job here. */
4090 static const char s_szRev[] = "$Revision: 28800 $";
4091 const char *psz = RTStrStripL(strchr(s_szRev, ' '));
4092 RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);
4093 return 0;
4094 }
4095
4096 case SCMOPT_DIFF_IGNORE_EOL:
4097 g_fDiffIgnoreEol = true;
4098 break;
4099 case SCMOPT_DIFF_NO_IGNORE_EOL:
4100 g_fDiffIgnoreEol = false;
4101 break;
4102
4103 case SCMOPT_DIFF_IGNORE_SPACE:
4104 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = true;
4105 break;
4106 case SCMOPT_DIFF_NO_IGNORE_SPACE:
4107 g_fDiffIgnoreTrailingWS = g_fDiffIgnoreLeadingWS = false;
4108 break;
4109
4110 case SCMOPT_DIFF_IGNORE_LEADING_SPACE:
4111 g_fDiffIgnoreLeadingWS = true;
4112 break;
4113 case SCMOPT_DIFF_NO_IGNORE_LEADING_SPACE:
4114 g_fDiffIgnoreLeadingWS = false;
4115 break;
4116
4117 case SCMOPT_DIFF_IGNORE_TRAILING_SPACE:
4118 g_fDiffIgnoreTrailingWS = true;
4119 break;
4120 case SCMOPT_DIFF_NO_IGNORE_TRAILING_SPACE:
4121 g_fDiffIgnoreTrailingWS = false;
4122 break;
4123
4124 case SCMOPT_DIFF_SPECIAL_CHARS:
4125 g_fDiffSpecialChars = true;
4126 break;
4127 case SCMOPT_DIFF_NO_SPECIAL_CHARS:
4128 g_fDiffSpecialChars = false;
4129 break;
4130
4131 case VINF_GETOPT_NOT_OPTION:
4132 {
4133 if (!g_fDryRun)
4134 {
4135 if (!cProcessed)
4136 {
4137 RTPrintf("%s: Warning! This program will make changes to your source files and\n"
4138 "%s: there is a slight risk that bugs or a full disk may cause\n"
4139 "%s: LOSS OF DATA. So, please make sure you have checked in\n"
4140 "%s: all your changes already. If you didn't, then don't blame\n"
4141 "%s: anyone for not warning you!\n"
4142 "%s:\n"
4143 "%s: Press any key to continue...\n",
4144 g_szProgName, g_szProgName, g_szProgName, g_szProgName, g_szProgName,
4145 g_szProgName, g_szProgName);
4146 RTStrmGetCh(g_pStdIn);
4147 }
4148 cProcessed++;
4149 }
4150 rc = scmProcessSomething(ValueUnion.psz, pSettings);
4151 if (RT_FAILURE(rc))
4152 return rc;
4153 break;
4154 }
4155
4156 default:
4157 {
4158 int rc2 = scmSettingsBaseHandleOpt(&pSettings->Base, rc, &ValueUnion);
4159 if (RT_SUCCESS(rc2))
4160 break;
4161 if (rc2 != VERR_GETOPT_UNKNOWN_OPTION)
4162 return 2;
4163 return RTGetOptPrintError(rc, &ValueUnion);
4164 }
4165 }
4166 }
4167
4168 scmSettingsDestroy(pSettings);
4169 return 0;
4170}
4171
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use