VirtualBox

source: vbox/trunk/src/bldprogs/scmrw.cpp@ 76556

Last change on this file since 76556 was 76556, checked in by vboxsync, 6 years ago

scm: Header guard fixes and more helpful output.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 126.3 KB
Line 
1/* $Id: scmrw.cpp 76556 2019-01-01 02:35:28Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager.
4 */
5
6/*
7 * Copyright (C) 2010-2019 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/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#include <iprt/assert.h>
23#include <iprt/ctype.h>
24#include <iprt/dir.h>
25#include <iprt/env.h>
26#include <iprt/file.h>
27#include <iprt/err.h>
28#include <iprt/getopt.h>
29#include <iprt/initterm.h>
30#include <iprt/mem.h>
31#include <iprt/message.h>
32#include <iprt/param.h>
33#include <iprt/path.h>
34#include <iprt/process.h>
35#include <iprt/stream.h>
36#include <iprt/string.h>
37
38#include "scm.h"
39
40
41/*********************************************************************************************************************************
42* Structures and Typedefs *
43*********************************************************************************************************************************/
44/** License types. */
45typedef enum SCMLICENSETYPE
46{
47 kScmLicenseType_Invalid = 0,
48 kScmLicenseType_OseGpl,
49 kScmLicenseType_OseDualGplCddl,
50 kScmLicenseType_OseCddl,
51 kScmLicenseType_VBoxLgpl,
52 kScmLicenseType_Mit,
53 kScmLicenseType_Confidential
54} SCMLICENSETYPE;
55
56/** A license. */
57typedef struct SCMLICENSETEXT
58{
59 /** The license type. */
60 SCMLICENSETYPE enmType;
61 /** The license option. */
62 SCMLICENSE enmOpt;
63 /** The license text. */
64 const char *psz;
65 /** The license text length. */
66 size_t cch;
67} SCMLICENSETEXT;
68/** Pointer to a license. */
69typedef SCMLICENSETEXT const *PCSCMLICENSETEXT;
70
71/**
72 * Copyright + license rewriter state.
73 */
74typedef struct SCMCOPYRIGHTINFO
75{
76 /** State. */
77 PSCMRWSTATE pState; /**< input */
78 /** The comment style (neede for C/C++). */
79 SCMCOMMENTSTYLE enmCommentStyle; /**< input */
80
81 /** Number of comments we've parsed. */
82 uint32_t cComments;
83
84 /** Copy of the contributed-by line if present. */
85 char *pszContributedBy;
86
87 /** @name Common info
88 * @{ */
89 uint32_t iLineComment;
90 uint32_t cLinesComment; /**< This excludes any external license lines. */
91 /** @} */
92
93 /** @name Copyright info
94 * @{ */
95 uint32_t iLineCopyright;
96 uint32_t uFirstYear;
97 uint32_t uLastYear;
98 bool fWellFormedCopyright;
99 bool fUpToDateCopyright;
100 /** @} */
101
102 /** @name License info
103 * @{ */
104 bool fOpenSource; /**< input */
105 PCSCMLICENSETEXT pExpectedLicense; /**< input */
106 PCSCMLICENSETEXT paLicenses; /**< input */
107 SCMLICENSE enmLicenceOpt; /**< input */
108 uint32_t iLineLicense;
109 uint32_t cLinesLicense;
110 PCSCMLICENSETEXT pCurrentLicense;
111 bool fIsCorrectLicense;
112 bool fWellFormedLicense;
113 bool fExternalLicense;
114 /** @} */
115
116 /** @name LGPL licence notice and disclaimer info
117 * @{ */
118 /** Wheter to check for LGPL license notices and disclaimers. */
119 bool fCheckforLgpl;
120 /** The approximate line we found the (first) LGPL licence notice on. */
121 uint32_t iLineLgplNotice;
122 /** The line number after the LGPL notice comment. */
123 uint32_t iLineAfterLgplComment;
124 /** The LGPL disclaimer line. */
125 uint32_t iLineLgplDisclaimer;
126 /** @} */
127
128} SCMCOPYRIGHTINFO;
129typedef SCMCOPYRIGHTINFO *PSCMCOPYRIGHTINFO;
130
131
132/*********************************************************************************************************************************
133* Global Variables *
134*********************************************************************************************************************************/
135/** --license-ose-gpl */
136static const char g_szVBoxOseGpl[] =
137 "This file is part of VirtualBox Open Source Edition (OSE), as\n"
138 "available from http://www.virtualbox.org. This file is free software;\n"
139 "you can redistribute it and/or modify it under the terms of the GNU\n"
140 "General Public License (GPL) as published by the Free Software\n"
141 "Foundation, in version 2 as it comes in the \"COPYING\" file of the\n"
142 "VirtualBox OSE distribution. VirtualBox OSE is distributed in the\n"
143 "hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.\n";
144
145/** --license-ose-dual */
146static const char g_szVBoxOseDualGplCddl[] =
147 "This file is part of VirtualBox Open Source Edition (OSE), as\n"
148 "available from http://www.virtualbox.org. This file is free software;\n"
149 "you can redistribute it and/or modify it under the terms of the GNU\n"
150 "General Public License (GPL) as published by the Free Software\n"
151 "Foundation, in version 2 as it comes in the \"COPYING\" file of the\n"
152 "VirtualBox OSE distribution. VirtualBox OSE is distributed in the\n"
153 "hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.\n"
154 "\n"
155 "The contents of this file may alternatively be used under the terms\n"
156 "of the Common Development and Distribution License Version 1.0\n"
157 "(CDDL) only, as it comes in the \"COPYING.CDDL\" file of the\n"
158 "VirtualBox OSE distribution, in which case the provisions of the\n"
159 "CDDL are applicable instead of those of the GPL.\n"
160 "\n"
161 "You may elect to license modified versions of this file under the\n"
162 "terms and conditions of either the GPL or the CDDL or both.\n";
163
164/** --license-ose-cddl */
165static const char g_szVBoxOseCddl[] =
166 "This file is part of VirtualBox Open Source Edition (OSE), as\n"
167 "available from http://www.virtualbox.org. This file is free software;\n"
168 "you can redistribute it and/or modify it under the terms of the Common\n"
169 "Development and Distribution License Version 1.0 (CDDL) only, as it\n"
170 "comes in the \"COPYING.CDDL\" file of the VirtualBox OSE distribution.\n"
171 "VirtualBox OSE is distributed in the hope that it will be useful, but\n"
172 "WITHOUT ANY WARRANTY of any kind.\n";
173
174/** --license-lgpl */
175static const char g_szVBoxLgpl[] =
176 "This file is part of a free software library; you can redistribute\n"
177 "it and/or modify it under the terms of the GNU Lesser General\n"
178 "Public License version 2.1 as published by the Free Software\n"
179 "Foundation and shipped in the \"COPYING\" file with this library.\n"
180 "The library is distributed in the hope that it will be useful,\n"
181 "but WITHOUT ANY WARRANTY of any kind.\n"
182 "\n"
183 "Oracle LGPL Disclaimer: For the avoidance of doubt, except that if\n"
184 "any license choice other than GPL or LGPL is available it will\n"
185 "apply instead, Oracle elects to use only the Lesser General Public\n"
186 "License version 2.1 (LGPLv2) at this time for any software where\n"
187 "a choice of LGPL license versions is made available with the\n"
188 "language indicating that LGPLv2 or any later version may be used,\n"
189 "or where a choice of which version of the LGPL is applied is\n"
190 "otherwise unspecified.\n";
191
192/** --license-mit
193 * @note This isn't detectable as VirtualBox or Oracle specific.
194 */
195static const char g_szMit[] =
196 "Permission is hereby granted, free of charge, to any person\n"
197 "obtaining a copy of this software and associated documentation\n"
198 "files (the \"Software\"), to deal in the Software without\n"
199 "restriction, including without limitation the rights to use,\n"
200 "copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
201 "copies of the Software, and to permit persons to whom the\n"
202 "Software is furnished to do so, subject to the following\n"
203 "conditions:\n"
204 "\n"
205 "The above copyright notice and this permission notice shall be\n"
206 "included in all copies or substantial portions of the Software.\n"
207 "\n"
208 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n"
209 "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\n"
210 "OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n"
211 "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n"
212 "HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\n"
213 "WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n"
214 "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n"
215 "OTHER DEALINGS IN THE SOFTWARE.\n";
216
217/** --license-mit, alternative wording \#1.
218 * @note This differes from g_szMit in "AUTHORS OR COPYRIGHT HOLDERS" is written
219 * "COPYRIGHT HOLDER(S) OR AUTHOR(S)". Its layout is wider, so it is a
220 * couple of lines shorter. */
221static const char g_szMitAlt1[] =
222 "Permission is hereby granted, free of charge, to any person obtaining a\n"
223 "copy of this software and associated documentation files (the \"Software\"),\n"
224 "to deal in the Software without restriction, including without limitation\n"
225 "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
226 "and/or sell copies of the Software, and to permit persons to whom the\n"
227 "Software is furnished to do so, subject to the following conditions:\n"
228 "\n"
229 "The above copyright notice and this permission notice shall be included in\n"
230 "all copies or substantial portions of the Software.\n"
231 "\n"
232 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
233 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
234 "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n"
235 "THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR\n"
236 "OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n"
237 "ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n"
238 "OTHER DEALINGS IN THE SOFTWARE.\n";
239
240/** --license-mit, alternative wording \#2.
241 * @note This differes from g_szMit in that "AUTHORS OR COPYRIGHT HOLDERS" is
242 * replaced with "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS".
243 * Its layout is wider, so it is a couple of lines shorter. */
244static const char g_szMitAlt2[] =
245 "Permission is hereby granted, free of charge, to any person obtaining a\n"
246 "copy of this software and associated documentation files (the \"Software\"),\n"
247 "to deal in the Software without restriction, including without limitation\n"
248 "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
249 "and/or sell copies of the Software, and to permit persons to whom the\n"
250 "Software is furnished to do so, subject to the following conditions:\n"
251 "\n"
252 "The above copyright notice and this permission notice shall be included in\n"
253 "all copies or substantial portions of the Software.\n"
254 "\n"
255 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
256 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
257 "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
258 "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
259 "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
260 "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
261 "USE OR OTHER DEALINGS IN THE SOFTWARE.\n";
262
263/** --license-mit, alternative wording \#3.
264 * @note This differes from g_szMitAlt2 in that the second and third sections
265 * have been switch. */
266static const char g_szMitAlt3[] =
267 "Permission is hereby granted, free of charge, to any person obtaining a\n"
268 "copy of this software and associated documentation files (the \"Software\"),\n"
269 "to deal in the Software without restriction, including without limitation\n"
270 "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
271 "and/or sell copies of the Software, and to permit persons to whom the\n"
272 "Software is furnished to do so, subject to the following conditions:\n"
273 "\n"
274 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
275 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
276 "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
277 "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
278 "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
279 "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
280 "USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
281 "\n"
282 "The above copyright notice and this permission notice shall be included in\n"
283 "all copies or substantial portions of the Software.\n";
284
285/** --license-(based-on)mit, alternative wording \#4.
286 * @note This differs from g_szMitAlt2 in injecting "(including the next
287 * paragraph)". */
288static const char g_szMitAlt4[] =
289 "Permission is hereby granted, free of charge, to any person obtaining a\n"
290 "copy of this software and associated documentation files (the \"Software\"),\n"
291 "to deal in the Software without restriction, including without limitation\n"
292 "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
293 "and/or sell copies of the Software, and to permit persons to whom the\n"
294 "Software is furnished to do so, subject to the following conditions:\n"
295 "\n"
296 "The above copyright notice and this permission notice (including the next\n"
297 "paragraph) shall be included in all copies or substantial portions of the\n"
298 "Software.\n"
299 "\n"
300 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
301 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
302 "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n"
303 "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
304 "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n"
305 "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n"
306 "DEALINGS IN THE SOFTWARE.\n";
307
308/** --license-(based-on)mit, alternative wording \#5.
309 * @note This differs from g_szMitAlt3 in using "sub license" instead of
310 * "sublicense" and adding an illogical "(including the next
311 * paragraph)" remark to the final paragraph. (vbox_ttm.c) */
312static const char g_szMitAlt5[] =
313 "Permission is hereby granted, free of charge, to any person obtaining a\n"
314 "copy of this software and associated documentation files (the\n"
315 "\"Software\"), to deal in the Software without restriction, including\n"
316 "without limitation the rights to use, copy, modify, merge, publish,\n"
317 "distribute, sub license, and/or sell copies of the Software, and to\n"
318 "permit persons to whom the Software is furnished to do so, subject to\n"
319 "the following conditions:\n"
320 "\n"
321 "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
322 "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
323 "FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL\n"
324 "THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,\n"
325 "DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n"
326 "OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n"
327 "USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
328 "\n"
329 "The above copyright notice and this permission notice (including the\n"
330 "next paragraph) shall be included in all copies or substantial portions\n"
331 "of the Software.\n";
332
333/** Oracle confidential. */
334static const char g_szOracleConfidential[] =
335 "Oracle Corporation confidential\n"
336 "All rights reserved\n";
337
338/** Licenses to detect when --license-mit isn't used. */
339static const SCMLICENSETEXT g_aLicenses[] =
340{
341 { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseGpl)},
342 { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseDualGplCddl) },
343 { kScmLicenseType_OseCddl, kScmLicense_OseCddl, RT_STR_TUPLE(g_szVBoxOseCddl) },
344 { kScmLicenseType_VBoxLgpl, kScmLicense_Lgpl, RT_STR_TUPLE(g_szVBoxLgpl)},
345 { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidential) },
346 { kScmLicenseType_Invalid, kScmLicense_End, NULL, 0 },
347};
348
349/** Licenses to detect when --license-mit or --license-based-on-mit are used. */
350static const SCMLICENSETEXT g_aLicensesWithMit[] =
351{
352 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMit) },
353 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt1) },
354 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt2) },
355 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt3) },
356 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt4) },
357 { kScmLicenseType_Mit, kScmLicense_Mit, RT_STR_TUPLE(g_szMitAlt5) },
358 { kScmLicenseType_OseGpl, kScmLicense_OseGpl, RT_STR_TUPLE(g_szVBoxOseGpl)},
359 { kScmLicenseType_OseDualGplCddl, kScmLicense_OseDualGplCddl, RT_STR_TUPLE(g_szVBoxOseDualGplCddl) },
360 { kScmLicenseType_VBoxLgpl, kScmLicense_Lgpl, RT_STR_TUPLE(g_szVBoxLgpl)},
361 { kScmLicenseType_Confidential, kScmLicense_End, RT_STR_TUPLE(g_szOracleConfidential) },
362 { kScmLicenseType_Invalid, kScmLicense_End, NULL, 0 },
363};
364
365/** Copyright holder. */
366static const char g_szCopyrightHolder[] = "Oracle Corporation";
367
368/** LGPL disclaimer. */
369static const char g_szLgplDisclaimer[] =
370 "Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice\n"
371 "other than GPL or LGPL is available it will apply instead, Oracle elects to use only\n"
372 "the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where\n"
373 "a choice of LGPL license versions is made available with the language indicating\n"
374 "that LGPLv2 or any later version may be used, or where a choice of which version\n"
375 "of the LGPL is applied is otherwise unspecified.\n";
376
377/** Copyright+license comment start for each SCMCOMMENTSTYLE. */
378static RTSTRTUPLE const g_aCopyrightCommentStart[] =
379{
380 { RT_STR_TUPLE("<invalid> ") },
381 { RT_STR_TUPLE("/*") },
382 { RT_STR_TUPLE("#") },
383 { RT_STR_TUPLE("\"\"\"") },
384 { RT_STR_TUPLE(";") },
385 { RT_STR_TUPLE("REM") },
386 { RT_STR_TUPLE("rem") },
387 { RT_STR_TUPLE("Rem") },
388 { RT_STR_TUPLE("--") },
389 { RT_STR_TUPLE("'") },
390 { RT_STR_TUPLE("<end>") },
391};
392
393/** Copyright+license line prefix for each SCMCOMMENTSTYLE. */
394static RTSTRTUPLE const g_aCopyrightCommentPrefix[] =
395{
396 { RT_STR_TUPLE("<invalid> ") },
397 { RT_STR_TUPLE(" * ") },
398 { RT_STR_TUPLE("# ") },
399 { RT_STR_TUPLE("") },
400 { RT_STR_TUPLE("; ") },
401 { RT_STR_TUPLE("REM ") },
402 { RT_STR_TUPLE("rem ") },
403 { RT_STR_TUPLE("Rem ") },
404 { RT_STR_TUPLE("-- ") },
405 { RT_STR_TUPLE("' ") },
406 { RT_STR_TUPLE("<end>") },
407};
408
409/** Copyright+license empty line for each SCMCOMMENTSTYLE. */
410static RTSTRTUPLE const g_aCopyrightCommentEmpty[] =
411{
412 { RT_STR_TUPLE("<invalid>") },
413 { RT_STR_TUPLE(" *") },
414 { RT_STR_TUPLE("#") },
415 { RT_STR_TUPLE("") },
416 { RT_STR_TUPLE(";") },
417 { RT_STR_TUPLE("REM") },
418 { RT_STR_TUPLE("rem") },
419 { RT_STR_TUPLE("Rem") },
420 { RT_STR_TUPLE("--") },
421 { RT_STR_TUPLE("'") },
422 { RT_STR_TUPLE("<end>") },
423};
424
425/** Copyright+license end of comment for each SCMCOMMENTSTYLE. */
426static RTSTRTUPLE const g_aCopyrightCommentEnd[] =
427{
428 { RT_STR_TUPLE("<invalid> ") },
429 { RT_STR_TUPLE(" */") },
430 { RT_STR_TUPLE("#") },
431 { RT_STR_TUPLE("\"\"\"") },
432 { RT_STR_TUPLE(";") },
433 { RT_STR_TUPLE("REM") },
434 { RT_STR_TUPLE("rem") },
435 { RT_STR_TUPLE("Rem") },
436 { RT_STR_TUPLE("--") },
437 { RT_STR_TUPLE("'") },
438 { RT_STR_TUPLE("<end>") },
439};
440
441
442/**
443 * Figures out the predominant casing of the "REM" keyword in a batch file.
444 *
445 * @returns Predominant comment style.
446 * @param pIn The file to scan. Will be rewound.
447 */
448static SCMCOMMENTSTYLE determinBatchFileCommentStyle(PSCMSTREAM pIn)
449{
450 /*
451 * Figure out whether it's using upper or lower case REM comments before
452 * doing the work.
453 */
454 uint32_t cUpper = 0;
455 uint32_t cLower = 0;
456 uint32_t cCamel = 0;
457 SCMEOL enmEol;
458 size_t cchLine;
459 const char *pchLine;
460 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
461 {
462 while ( cchLine > 2
463 && RT_C_IS_SPACE(*pchLine))
464 {
465 pchLine++;
466 cchLine--;
467 }
468 if ( ( cchLine > 3
469 && RT_C_IS_SPACE(pchLine[2]))
470 || cchLine == 3)
471 {
472 if ( pchLine[0] == 'R'
473 && pchLine[1] == 'E'
474 && pchLine[2] == 'M')
475 cUpper++;
476 else if ( pchLine[0] == 'r'
477 && pchLine[1] == 'e'
478 && pchLine[2] == 'm')
479 cLower++;
480 else if ( pchLine[0] == 'R'
481 && pchLine[1] == 'e'
482 && pchLine[2] == 'm')
483 cCamel++;
484 }
485 }
486
487 ScmStreamRewindForReading(pIn);
488
489 if (cLower >= cUpper && cLower >= cCamel)
490 return kScmCommentStyle_Rem_Lower;
491 if (cCamel >= cLower && cCamel >= cUpper)
492 return kScmCommentStyle_Rem_Camel;
493 return kScmCommentStyle_Rem_Upper;
494}
495
496
497/**
498 * Worker for isBlankLine.
499 *
500 * @returns true if blank, false if not.
501 * @param pchLine Pointer to the start of the line.
502 * @param cchLine The (encoded) length of the line, excluding EOL char.
503 */
504static bool isBlankLineSlow(const char *pchLine, size_t cchLine)
505{
506 /*
507 * From the end, more likely to hit a non-blank char there.
508 */
509 while (cchLine-- > 0)
510 if (!RT_C_IS_BLANK(pchLine[cchLine]))
511 return false;
512 return true;
513}
514
515/**
516 * Helper for checking whether a line is blank.
517 *
518 * @returns true if blank, false if not.
519 * @param pchLine Pointer to the start of the line.
520 * @param cchLine The (encoded) length of the line, excluding EOL char.
521 */
522DECLINLINE(bool) isBlankLine(const char *pchLine, size_t cchLine)
523{
524 if (cchLine == 0)
525 return true;
526 /*
527 * We're more likely to fine a non-space char at the end of the line than
528 * at the start, due to source code indentation.
529 */
530 if (pchLine[cchLine - 1])
531 return false;
532
533 /*
534 * Don't bother inlining loop code.
535 */
536 return isBlankLineSlow(pchLine, cchLine);
537}
538
539
540/**
541 * Checks if there are @a cch blanks at @a pch.
542 *
543 * @returns true if span of @a cch blanks, false if not.
544 * @param pch The start of the span to check.
545 * @param cch The length of the span.
546 */
547DECLINLINE(bool) isSpanOfBlanks(const char *pch, size_t cch)
548{
549 while (cch-- > 0)
550 {
551 char const ch = *pch++;
552 if (!RT_C_IS_BLANK(ch))
553 return false;
554 }
555 return true;
556}
557
558
559/**
560 * Strip trailing blanks (space & tab).
561 *
562 * @returns True if modified, false if not.
563 * @param pIn The input stream.
564 * @param pOut The output stream.
565 * @param pSettings The settings.
566 */
567bool rewrite_StripTrailingBlanks(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
568{
569 if (!pSettings->fStripTrailingBlanks)
570 return false;
571
572 bool fModified = false;
573 SCMEOL enmEol;
574 size_t cchLine;
575 const char *pchLine;
576 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
577 {
578 int rc;
579 if ( cchLine == 0
580 || !RT_C_IS_BLANK(pchLine[cchLine - 1]) )
581 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
582 else
583 {
584 cchLine--;
585 while (cchLine > 0 && RT_C_IS_BLANK(pchLine[cchLine - 1]))
586 cchLine--;
587 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
588 fModified = true;
589 }
590 if (RT_FAILURE(rc))
591 return false;
592 }
593 if (fModified)
594 ScmVerbose(pState, 2, " * Stripped trailing blanks\n");
595 return fModified;
596}
597
598/**
599 * Expand tabs.
600 *
601 * @returns True if modified, false if not.
602 * @param pIn The input stream.
603 * @param pOut The output stream.
604 * @param pSettings The settings.
605 */
606bool rewrite_ExpandTabs(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
607{
608 if (!pSettings->fConvertTabs)
609 return false;
610
611 size_t const cchTab = pSettings->cchTab;
612 bool fModified = false;
613 SCMEOL enmEol;
614 size_t cchLine;
615 const char *pchLine;
616 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
617 {
618 int rc;
619 const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);
620 if (!pchTab)
621 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
622 else
623 {
624 size_t offTab = 0;
625 const char *pchChunk = pchLine;
626 for (;;)
627 {
628 size_t cchChunk = pchTab - pchChunk;
629 offTab += cchChunk;
630 ScmStreamWrite(pOut, pchChunk, cchChunk);
631
632 size_t cchToTab = cchTab - offTab % cchTab;
633 ScmStreamWrite(pOut, g_szTabSpaces, cchToTab);
634 offTab += cchToTab;
635
636 pchChunk = pchTab + 1;
637 size_t cchLeft = cchLine - (pchChunk - pchLine);
638 pchTab = (const char *)memchr(pchChunk, '\t', cchLeft);
639 if (!pchTab)
640 {
641 rc = ScmStreamPutLine(pOut, pchChunk, cchLeft, enmEol);
642 break;
643 }
644 }
645
646 fModified = true;
647 }
648 if (RT_FAILURE(rc))
649 return false;
650 }
651 if (fModified)
652 ScmVerbose(pState, 2, " * Expanded tabs\n");
653 return fModified;
654}
655
656/**
657 * Worker for rewrite_ForceNativeEol, rewrite_ForceLF and rewrite_ForceCRLF.
658 *
659 * @returns true if modifications were made, false if not.
660 * @param pIn The input stream.
661 * @param pOut The output stream.
662 * @param pSettings The settings.
663 * @param enmDesiredEol The desired end of line indicator type.
664 * @param pszDesiredSvnEol The desired svn:eol-style.
665 */
666static bool rewrite_ForceEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
667 SCMEOL enmDesiredEol, const char *pszDesiredSvnEol)
668{
669 if (!pSettings->fConvertEol)
670 return false;
671
672 bool fModified = false;
673 SCMEOL enmEol;
674 size_t cchLine;
675 const char *pchLine;
676 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
677 {
678 if ( enmEol != enmDesiredEol
679 && enmEol != SCMEOL_NONE)
680 {
681 fModified = true;
682 enmEol = enmDesiredEol;
683 }
684 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
685 if (RT_FAILURE(rc))
686 return false;
687 }
688 if (fModified)
689 ScmVerbose(pState, 2, " * Converted EOL markers\n");
690
691 /* Check svn:eol-style if appropriate */
692 if ( pSettings->fSetSvnEol
693 && ScmSvnIsInWorkingCopy(pState))
694 {
695 char *pszEol;
696 int rc = ScmSvnQueryProperty(pState, "svn:eol-style", &pszEol);
697 if ( (RT_SUCCESS(rc) && strcmp(pszEol, pszDesiredSvnEol))
698 || rc == VERR_NOT_FOUND)
699 {
700 if (rc == VERR_NOT_FOUND)
701 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (missing)\n", pszDesiredSvnEol);
702 else
703 ScmVerbose(pState, 2, " * Setting svn:eol-style to %s (was: %s)\n", pszDesiredSvnEol, pszEol);
704 int rc2 = ScmSvnSetProperty(pState, "svn:eol-style", pszDesiredSvnEol);
705 if (RT_FAILURE(rc2))
706 ScmError(pState, rc2, "ScmSvnSetProperty: %Rrc\n", rc2);
707 }
708 if (RT_SUCCESS(rc))
709 RTStrFree(pszEol);
710 }
711
712 /** @todo also check the subversion svn:eol-style state! */
713 return fModified;
714}
715
716/**
717 * Force native end of line indicator.
718 *
719 * @returns true if modifications were made, false if not.
720 * @param pIn The input stream.
721 * @param pOut The output stream.
722 * @param pSettings The settings.
723 */
724bool rewrite_ForceNativeEol(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
725{
726#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
727 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "native");
728#else
729 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "native");
730#endif
731}
732
733/**
734 * Force the stream to use LF as the end of line indicator.
735 *
736 * @returns true if modifications were made, false if not.
737 * @param pIn The input stream.
738 * @param pOut The output stream.
739 * @param pSettings The settings.
740 */
741bool rewrite_ForceLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
742{
743 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_LF, "LF");
744}
745
746/**
747 * Force the stream to use CRLF as the end of line indicator.
748 *
749 * @returns true if modifications were made, false if not.
750 * @param pIn The input stream.
751 * @param pOut The output stream.
752 * @param pSettings The settings.
753 */
754bool rewrite_ForceCRLF(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
755{
756 return rewrite_ForceEol(pState, pIn, pOut, pSettings, SCMEOL_CRLF, "CRLF");
757}
758
759/**
760 * Strip trailing blank lines and/or make sure there is exactly one blank line
761 * at the end of the file.
762 *
763 * @returns true if modifications were made, false if not.
764 * @param pIn The input stream.
765 * @param pOut The output stream.
766 * @param pSettings The settings.
767 *
768 * @remarks ASSUMES trailing white space has been removed already.
769 */
770bool rewrite_AdjustTrailingLines(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
771{
772 if ( !pSettings->fStripTrailingLines
773 && !pSettings->fForceTrailingLine
774 && !pSettings->fForceFinalEol)
775 return false;
776
777 size_t const cLines = ScmStreamCountLines(pIn);
778
779 /* Empty files remains empty. */
780 if (cLines <= 1)
781 return false;
782
783 /* Figure out if we need to adjust the number of lines or not. */
784 size_t cLinesNew = cLines;
785
786 if ( pSettings->fStripTrailingLines
787 && ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
788 {
789 while ( cLinesNew > 1
790 && ScmStreamIsWhiteLine(pIn, cLinesNew - 2))
791 cLinesNew--;
792 }
793
794 if ( pSettings->fForceTrailingLine
795 && !ScmStreamIsWhiteLine(pIn, cLinesNew - 1))
796 cLinesNew++;
797
798 bool fFixMissingEol = pSettings->fForceFinalEol
799 && ScmStreamGetEolByLine(pIn, cLinesNew - 1) == SCMEOL_NONE;
800
801 if ( !fFixMissingEol
802 && cLines == cLinesNew)
803 return false;
804
805 /* Copy the number of lines we've arrived at. */
806 ScmStreamRewindForReading(pIn);
807
808 size_t cCopied = RT_MIN(cLinesNew, cLines);
809 ScmStreamCopyLines(pOut, pIn, cCopied);
810
811 if (cCopied != cLinesNew)
812 {
813 while (cCopied++ < cLinesNew)
814 ScmStreamPutLine(pOut, "", 0, ScmStreamGetEol(pIn));
815 }
816 /* Fix missing EOL if required. */
817 else if (fFixMissingEol)
818 {
819 if (ScmStreamGetEol(pIn) == SCMEOL_LF)
820 ScmStreamWrite(pOut, "\n", 1);
821 else
822 ScmStreamWrite(pOut, "\r\n", 2);
823 }
824
825 ScmVerbose(pState, 2, " * Adjusted trailing blank lines\n");
826 return true;
827}
828
829/**
830 * Make sure there is no svn:executable property on the current file.
831 *
832 * @returns false - the state carries these kinds of changes.
833 * @param pState The rewriter state.
834 * @param pIn The input stream.
835 * @param pOut The output stream.
836 * @param pSettings The settings.
837 */
838bool rewrite_SvnNoExecutable(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
839{
840 RT_NOREF2(pIn, pOut);
841 if ( !pSettings->fSetSvnExecutable
842 || !ScmSvnIsInWorkingCopy(pState))
843 return false;
844
845 int rc = ScmSvnQueryProperty(pState, "svn:executable", NULL);
846 if (RT_SUCCESS(rc))
847 {
848 ScmVerbose(pState, 2, " * removing svn:executable\n");
849 rc = ScmSvnDelProperty(pState, "svn:executable");
850 if (RT_FAILURE(rc))
851 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
852 }
853 return false;
854}
855
856/**
857 * Make sure there is no svn:keywords property on the current file.
858 *
859 * @returns false - the state carries these kinds of changes.
860 * @param pState The rewriter state.
861 * @param pIn The input stream.
862 * @param pOut The output stream.
863 * @param pSettings The settings.
864 */
865bool rewrite_SvnNoKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
866{
867 RT_NOREF2(pIn, pOut);
868 if ( !pSettings->fSetSvnExecutable
869 || !ScmSvnIsInWorkingCopy(pState))
870 return false;
871
872 int rc = ScmSvnQueryProperty(pState, "svn:keywords", NULL);
873 if (RT_SUCCESS(rc))
874 {
875 ScmVerbose(pState, 2, " * removing svn:keywords\n");
876 rc = ScmSvnDelProperty(pState, "svn:keywords");
877 if (RT_FAILURE(rc))
878 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
879 }
880 return false;
881}
882
883/**
884 * Make sure there is no svn:eol-style property on the current file.
885 *
886 * @returns false - the state carries these kinds of changes.
887 * @param pState The rewriter state.
888 * @param pIn The input stream.
889 * @param pOut The output stream.
890 * @param pSettings The settings.
891 */
892bool rewrite_SvnNoEolStyle(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
893{
894 RT_NOREF2(pIn, pOut);
895 if ( !pSettings->fSetSvnExecutable
896 || !ScmSvnIsInWorkingCopy(pState))
897 return false;
898
899 int rc = ScmSvnQueryProperty(pState, "svn:eol-style", NULL);
900 if (RT_SUCCESS(rc))
901 {
902 ScmVerbose(pState, 2, " * removing svn:eol-style\n");
903 rc = ScmSvnDelProperty(pState, "svn:eol-style");
904 if (RT_FAILURE(rc))
905 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
906 }
907 return false;
908}
909
910/**
911 * Makes sure the svn properties are appropriate for a binary.
912 *
913 * @returns false - the state carries these kinds of changes.
914 * @param pState The rewriter state.
915 * @param pIn The input stream.
916 * @param pOut The output stream.
917 * @param pSettings The settings.
918 */
919bool rewrite_SvnBinary(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
920{
921 RT_NOREF2(pIn, pOut);
922 if ( !pSettings->fSetSvnExecutable
923 || !ScmSvnIsInWorkingCopy(pState))
924 return false;
925
926 /* remove svn:eol-style and svn:keywords */
927 static const char * const s_apszRemove[] = { "svn:eol-style", "svn:keywords" };
928 for (uint32_t i = 0; i < RT_ELEMENTS(s_apszRemove); i++)
929 {
930 char *pszValue;
931 int rc = ScmSvnQueryProperty(pState, s_apszRemove[i], &pszValue);
932 if (RT_SUCCESS(rc))
933 {
934 ScmVerbose(pState, 2, " * removing %s=%s\n", s_apszRemove[i], pszValue);
935 RTStrFree(pszValue);
936 rc = ScmSvnDelProperty(pState, s_apszRemove[i]);
937 if (RT_FAILURE(rc))
938 ScmError(pState, rc, "ScmSvnSetProperty(,%s): %Rrc\n", s_apszRemove[i], rc);
939 }
940 else if (rc != VERR_NOT_FOUND)
941 ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
942 }
943
944 /* Make sure there is a svn:mime-type set. */
945 int rc = ScmSvnQueryProperty(pState, "svn:mime-type", NULL);
946 if (rc == VERR_NOT_FOUND)
947 {
948 ScmVerbose(pState, 2, " * settings svn:mime-type\n");
949 rc = ScmSvnSetProperty(pState, "svn:mime-type", "application/octet-stream");
950 if (RT_FAILURE(rc))
951 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
952 }
953 else if (RT_FAILURE(rc))
954 ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
955
956 return false;
957}
958
959/**
960 * Make sure the Id and Revision keywords are expanded.
961 *
962 * @returns false - the state carries these kinds of changes.
963 * @param pState The rewriter state.
964 * @param pIn The input stream.
965 * @param pOut The output stream.
966 * @param pSettings The settings.
967 */
968bool rewrite_SvnKeywords(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
969{
970 RT_NOREF2(pIn, pOut);
971 if ( !pSettings->fSetSvnKeywords
972 || !ScmSvnIsInWorkingCopy(pState))
973 return false;
974
975 char *pszKeywords;
976 int rc = ScmSvnQueryProperty(pState, "svn:keywords", &pszKeywords);
977 if ( RT_SUCCESS(rc)
978 && ( !strstr(pszKeywords, "Id") /** @todo need some function for finding a word in a string. */
979 || !strstr(pszKeywords, "Revision")) )
980 {
981 if (!strstr(pszKeywords, "Id") && !strstr(pszKeywords, "Revision"))
982 rc = RTStrAAppend(&pszKeywords, " Id Revision");
983 else if (!strstr(pszKeywords, "Id"))
984 rc = RTStrAAppend(&pszKeywords, " Id");
985 else
986 rc = RTStrAAppend(&pszKeywords, " Revision");
987 if (RT_SUCCESS(rc))
988 {
989 ScmVerbose(pState, 2, " * changing svn:keywords to '%s'\n", pszKeywords);
990 rc = ScmSvnSetProperty(pState, "svn:keywords", pszKeywords);
991 if (RT_FAILURE(rc))
992 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
993 }
994 else
995 ScmError(pState, rc, "RTStrAppend: %Rrc\n", rc);
996 RTStrFree(pszKeywords);
997 }
998 else if (rc == VERR_NOT_FOUND)
999 {
1000 ScmVerbose(pState, 2, " * setting svn:keywords to 'Id Revision'\n");
1001 rc = ScmSvnSetProperty(pState, "svn:keywords", "Id Revision");
1002 if (RT_FAILURE(rc))
1003 ScmError(pState, rc, "ScmSvnSetProperty: %Rrc\n", rc);
1004 }
1005 else if (RT_SUCCESS(rc))
1006 RTStrFree(pszKeywords);
1007
1008 return false;
1009}
1010
1011/**
1012 * Checks the svn:sync-process value and that parent is exported too.
1013 *
1014 * @returns false - the state carries these kinds of changes.
1015 * @param pState The rewriter state.
1016 * @param pIn The input stream.
1017 * @param pOut The output stream.
1018 * @param pSettings The settings.
1019 */
1020bool rewrite_SvnSyncProcess(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1021{
1022 RT_NOREF2(pIn, pOut);
1023 if ( pSettings->fSkipSvnSyncProcess
1024 || !ScmSvnIsInWorkingCopy(pState))
1025 return false;
1026
1027 char *pszSyncProcess;
1028 int rc = ScmSvnQueryProperty(pState, "svn:sync-process", &pszSyncProcess);
1029 if (RT_SUCCESS(rc))
1030 {
1031 if (strcmp(pszSyncProcess, "export") == 0)
1032 {
1033 char *pszParentSyncProcess;
1034 rc = ScmSvnQueryParentProperty(pState, "svn:sync-process", &pszParentSyncProcess);
1035 if (RT_SUCCESS(rc))
1036 {
1037 if (strcmp(pszSyncProcess, "export") != 0)
1038 ScmError(pState, VERR_INVALID_STATE,
1039 "svn:sync-process=export, but parent directory differs: %s\n"
1040 "WARNING! Make sure to unexport everything inside the directory first!\n"
1041 " Then you may export the directory and stuff inside it if you want.\n"
1042 " (Just exporting the directory will not make anything inside it externally visible.)\n"
1043 , pszParentSyncProcess);
1044 RTStrFree(pszParentSyncProcess);
1045 }
1046 else if (rc == VERR_NOT_FOUND)
1047 ScmError(pState, VERR_NOT_FOUND,
1048 "svn:sync-process=export, but parent directory is not exported!\n"
1049 "WARNING! Make sure to unexport everything inside the directory first!\n"
1050 " Then you may export the directory and stuff inside it if you want.\n"
1051 " (Just exporting the directory will not make anything inside it externally visible.)\n");
1052 else
1053 ScmError(pState, rc, "ScmSvnQueryParentProperty: %Rrc\n", rc);
1054 }
1055 else if (strcmp(pszSyncProcess, "ignore") != 0)
1056 ScmError(pState, VERR_INVALID_NAME, "Bad sync-process value: %s\n", pszSyncProcess);
1057 RTStrFree(pszSyncProcess);
1058 }
1059 else if (rc != VERR_NOT_FOUND)
1060 ScmError(pState, rc, "ScmSvnQueryProperty: %Rrc\n", rc);
1061
1062 return false;
1063}
1064
1065/**
1066 * Compares two strings word-by-word, ignoring spaces, punctuation and case.
1067 *
1068 * Assumes ASCII strings.
1069 *
1070 * @returns true if they match, false if not.
1071 * @param psz1 The first string. This is typically the known one.
1072 * @param psz2 The second string. This is typically the unknown one,
1073 * which is why we return a next pointer for this one.
1074 * @param ppsz2Next Where to return the next part of the 2nd string. If
1075 * this is NULL, the whole string must match.
1076 */
1077static bool IsEqualWordByWordIgnoreCase(const char *psz1, const char *psz2, const char **ppsz2Next)
1078{
1079 for (;;)
1080 {
1081 /* Try compare raw strings first. */
1082 char ch1 = *psz1;
1083 char ch2 = *psz2;
1084 if ( ch1 == ch2
1085 || RT_C_TO_LOWER(ch1) == RT_C_TO_LOWER(ch2))
1086 {
1087 if (ch1)
1088 {
1089 psz1++;
1090 psz2++;
1091 }
1092 else
1093 {
1094 if (ppsz2Next)
1095 *ppsz2Next = psz2;
1096 return true;
1097 }
1098 }
1099 else
1100 {
1101 /* Try skip spaces an punctuation. */
1102 while ( RT_C_IS_SPACE(ch1)
1103 || RT_C_IS_PUNCT(ch1))
1104 ch1 = *++psz1;
1105
1106 if (ch1 == '\0' && ppsz2Next)
1107 {
1108 *ppsz2Next = psz2;
1109 return true;
1110 }
1111
1112 while ( RT_C_IS_SPACE(ch2)
1113 || RT_C_IS_PUNCT(ch2))
1114 ch2 = *++psz2;
1115
1116 if ( ch1 != ch2
1117 && RT_C_TO_LOWER(ch1) != RT_C_TO_LOWER(ch2))
1118 {
1119 if (ppsz2Next)
1120 *ppsz2Next = psz2;
1121 return false;
1122 }
1123 }
1124 }
1125}
1126
1127/**
1128 * Looks for @a pszFragment anywhere in @a pszText, ignoring spaces, punctuation
1129 * and case.
1130 *
1131 * @returns true if found, false if not.
1132 * @param pszText The haystack to search in.
1133 * @param cchText The length @a pszText.
1134 * @param pszFragment The needle to search for.
1135 * @param ppszStart Where to return the address in @a pszText where
1136 * the fragment was found. Optional.
1137 * @param ppszNext Where to return the pointer to the first char in
1138 * @a pszText after the fragment. Optional.
1139 *
1140 * @remarks First character of @a pszFragment must be an 7-bit ASCII character!
1141 * This character must not be space or punctuation.
1142 */
1143static bool scmContainsWordByWordIgnoreCase(const char *pszText, size_t cchText, const char *pszFragment,
1144 const char **ppszStart, const char **ppszNext)
1145{
1146 Assert(!((unsigned)*pszFragment & 0x80));
1147 Assert(pszText[cchText] == '\0');
1148 Assert(!RT_C_IS_BLANK(*pszFragment));
1149 Assert(!RT_C_IS_PUNCT(*pszFragment));
1150
1151 char chLower = RT_C_TO_LOWER(*pszFragment);
1152 char chUpper = RT_C_TO_UPPER(*pszFragment);
1153 for (;;)
1154 {
1155 const char *pszHit = (const char *)memchr(pszText, chLower, cchText);
1156 const char *pszHit2 = (const char *)memchr(pszText, chUpper, cchText);
1157 if (!pszHit && !pszHit2)
1158 {
1159 if (ppszStart)
1160 *ppszStart = NULL;
1161 if (ppszNext)
1162 *ppszNext = NULL;
1163 return false;
1164 }
1165
1166 if ( pszHit == NULL
1167 || ( pszHit2 != NULL
1168 && ((uintptr_t)pszHit2 < (uintptr_t)pszHit)) )
1169 pszHit = pszHit2;
1170
1171 const char *pszNext;
1172 if (IsEqualWordByWordIgnoreCase(pszFragment, pszHit, &pszNext))
1173 {
1174 if (ppszStart)
1175 *ppszStart = pszHit;
1176 if (ppszNext)
1177 *ppszNext = pszNext;
1178 return true;
1179 }
1180
1181 cchText -= pszHit - pszText + 1;
1182 pszText = pszHit + 1;
1183 }
1184}
1185
1186
1187/**
1188 * Counts the number of lines in the given substring.
1189 *
1190 * @returns The number of lines.
1191 * @param psz The start of the substring.
1192 * @param cch The length of the substring.
1193 */
1194static uint32_t CountLinesInSubstring(const char *psz, size_t cch)
1195{
1196 uint32_t cLines = 0;
1197 for (;;)
1198 {
1199 const char *pszEol = (const char *)memchr(psz, '\n', cch);
1200 if (pszEol)
1201 cLines++;
1202 else
1203 return cLines + (*psz != '\0');
1204 cch -= pszEol + 1 - psz;
1205 if (!cch)
1206 return cLines;
1207 psz = pszEol + 1;
1208 }
1209}
1210
1211
1212/**
1213 * Comment parser callback for locating copyright and license.
1214 */
1215static DECLCALLBACK(int)
1216rewrite_Copyright_CommentCallback(PCSCMCOMMENTINFO pInfo, const char *pszBody, size_t cchBody, void *pvUser)
1217{
1218 PSCMCOPYRIGHTINFO pState = (PSCMCOPYRIGHTINFO)pvUser;
1219 Assert(strlen(pszBody) == cchBody);
1220 //RTPrintf("--- comment at %u, type %u ---\n%s\n--- end ---\n", pInfo->iLineStart, pInfo->enmType, pszBody);
1221 ScmVerbose(pState->pState, 5,
1222 "--- comment at %u col %u, %u lines, type %u, %u lines before body, %u lines after body\n",
1223 pInfo->iLineStart, pInfo->offStart, pInfo->iLineEnd - pInfo->iLineStart + 1, pInfo->enmType,
1224 pInfo->cBlankLinesBefore, pInfo->cBlankLinesAfter);
1225
1226 pState->cComments++;
1227
1228 uint32_t iLine = pInfo->iLineStart + pInfo->cBlankLinesBefore;
1229
1230 /*
1231 * Look for a 'contributed by' or 'includes contributions from' line, these
1232 * comes first when present.
1233 */
1234 const char *pchContributedBy = NULL;
1235 size_t cchContributedBy = 0;
1236 size_t cBlankLinesAfterContributedBy = 0;
1237 if ( pState->pszContributedBy == NULL
1238 && ( pState->iLineCopyright == UINT32_MAX
1239 || pState->iLineLicense == UINT32_MAX)
1240 && ( ( cchBody > sizeof("Contributed by")
1241 && RTStrNICmp(pszBody, RT_STR_TUPLE("contributed by")) == 0)
1242 || ( cchBody > sizeof("Includes contributions from")
1243 && RTStrNICmp(pszBody, RT_STR_TUPLE("Includes contributions from")) == 0) ) )
1244 {
1245 const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
1246 while (pszNextLine && pszNextLine[1] != '\n')
1247 pszNextLine = (const char *)memchr(pszNextLine + 1, '\n', cchBody);
1248 if (pszNextLine)
1249 {
1250 pchContributedBy = pszBody;
1251 cchContributedBy = pszNextLine - pszBody;
1252
1253 /* Skip the copyright line and any blank lines following it. */
1254 cchBody -= cchContributedBy + 1;
1255 pszBody = pszNextLine + 1;
1256 iLine += 1;
1257 while (*pszBody == '\n')
1258 {
1259 pszBody++;
1260 cchBody--;
1261 iLine++;
1262 cBlankLinesAfterContributedBy++;
1263 }
1264 }
1265 }
1266
1267 /*
1268 * Look for the copyright line.
1269 */
1270 bool fFoundCopyright = false;
1271 uint32_t cBlankLinesAfterCopyright = 0;
1272 if ( pState->iLineCopyright == UINT32_MAX
1273 && cchBody > sizeof("Copyright") + sizeof(g_szCopyrightHolder)
1274 && RTStrNICmp(pszBody, RT_STR_TUPLE("copyright")) == 0)
1275 {
1276 const char *pszNextLine = (const char *)memchr(pszBody, '\n', cchBody);
1277
1278 /* Oracle copyright? */
1279 const char *pszEnd = pszNextLine ? pszNextLine : &pszBody[cchBody];
1280 while (RT_C_IS_SPACE(pszEnd[-1]))
1281 pszEnd--;
1282 if ( (uintptr_t)(pszEnd - pszBody) > sizeof(g_szCopyrightHolder)
1283 && (*(unsigned char *)(pszEnd - sizeof(g_szCopyrightHolder) + 1) & 0x80) == 0 /* to avoid annoying assertion */
1284 && RTStrNICmp(pszEnd - sizeof(g_szCopyrightHolder) + 1, RT_STR_TUPLE(g_szCopyrightHolder)) == 0)
1285 {
1286 /* Parse out the year(s). */
1287 const char *psz = pszBody + sizeof("copyright");
1288 while ((uintptr_t)psz < (uintptr_t)pszEnd && !RT_C_IS_DIGIT(*psz))
1289 psz++;
1290 if (RT_C_IS_DIGIT(*psz))
1291 {
1292 char *pszNext;
1293 int rc = RTStrToUInt32Ex(psz, &pszNext, 10, &pState->uFirstYear);
1294 if ( RT_SUCCESS(rc)
1295 && rc != VWRN_NUMBER_TOO_BIG
1296 && rc != VWRN_NEGATIVE_UNSIGNED)
1297 {
1298 if ( pState->uFirstYear < 1975
1299 || pState->uFirstYear > 3000)
1300 {
1301 ScmError(pState->pState, VERR_OUT_OF_RANGE, "Copyright year is out of range: %u ('%.*s')\n",
1302 pState->uFirstYear, pszEnd - pszBody, pszBody);
1303 pState->uFirstYear = UINT32_MAX;
1304 }
1305
1306 while (RT_C_IS_SPACE(*pszNext))
1307 pszNext++;
1308 if (*pszNext == '-')
1309 {
1310 do
1311 pszNext++;
1312 while (RT_C_IS_SPACE(*pszNext));
1313 rc = RTStrToUInt32Ex(pszNext, &pszNext, 10, &pState->uLastYear);
1314 if ( RT_SUCCESS(rc)
1315 && rc != VWRN_NUMBER_TOO_BIG
1316 && rc != VWRN_NEGATIVE_UNSIGNED)
1317 {
1318 if ( pState->uLastYear < 1975
1319 || pState->uLastYear > 3000)
1320 {
1321 ScmError(pState->pState, VERR_OUT_OF_RANGE, "Second copyright year is out of range: %u ('%.*s')\n",
1322 pState->uLastYear, pszEnd - pszBody, pszBody);
1323 pState->uLastYear = UINT32_MAX;
1324 }
1325 else if (pState->uFirstYear > pState->uLastYear)
1326 {
1327 RTMsgWarning("Copyright years switched(?): '%.*s'\n", pszEnd - pszBody, pszBody);
1328 uint32_t iTmp = pState->uLastYear;
1329 pState->uLastYear = pState->uFirstYear;
1330 pState->uFirstYear = iTmp;
1331 }
1332 }
1333 else
1334 {
1335 pState->uLastYear = UINT32_MAX;
1336 ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
1337 "Failed to parse second copyright year: '%.*s'\n", pszEnd - pszBody, pszBody);
1338 }
1339 }
1340 else if (*pszNext != g_szCopyrightHolder[0])
1341 ScmError(pState->pState, VERR_PARSE_ERROR,
1342 "Failed to parse copyright: '%.*s'\n", pszEnd - pszBody, pszBody);
1343 else
1344 pState->uLastYear = pState->uFirstYear;
1345 }
1346 else
1347 {
1348 pState->uFirstYear = UINT32_MAX;
1349 ScmError(pState->pState, RT_SUCCESS(rc) ? -rc : rc,
1350 "Failed to parse copyright year: '%.*s'\n", pszEnd - pszBody, pszBody);
1351 }
1352 }
1353
1354 /* The copyright comment must come before the license. */
1355 if (pState->iLineLicense != UINT32_MAX)
1356 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright (line %u) must come before the license (line %u)!\n",
1357 iLine, pState->iLineLicense);
1358
1359 /* In C/C++ code, this must be a multiline comment. While in python it
1360 must be a */
1361 if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
1362 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a multiline comment (no doxygen stuff)\n");
1363 else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
1364 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright must appear in a doc-string\n");
1365
1366 /* The copyright must be followed by the license. */
1367 if (!pszNextLine)
1368 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
1369
1370 /* Quit if we've flagged a failure. */
1371 if (RT_FAILURE(pState->pState->rc))
1372 return VERR_CALLBACK_RETURN;
1373
1374 /* Check if it's well formed and up to date. */
1375 char szWellFormed[256];
1376 size_t cchWellFormed;
1377 if (pState->uFirstYear == pState->uLastYear)
1378 cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u %s",
1379 pState->uFirstYear, g_szCopyrightHolder);
1380 else
1381 cchWellFormed = RTStrPrintf(szWellFormed, sizeof(szWellFormed), "Copyright (C) %u-%u %s",
1382 pState->uFirstYear, pState->uLastYear, g_szCopyrightHolder);
1383 pState->fUpToDateCopyright = pState->uLastYear == g_uYear;
1384 pState->iLineCopyright = iLine;
1385 pState->fWellFormedCopyright = cchWellFormed == (uintptr_t)(pszEnd - pszBody)
1386 && memcmp(pszBody, szWellFormed, cchWellFormed) == 0;
1387 if (!pState->fWellFormedCopyright)
1388 ScmVerbose(pState->pState, 1, "* copyright isn't well formed\n");
1389
1390 /* If there wasn't exactly one blank line before the comment, trigger a rewrite. */
1391 if (pInfo->cBlankLinesBefore != 1)
1392 {
1393 ScmVerbose(pState->pState, 1, "* copyright comment is preceeded by %u blank lines instead of 1\n",
1394 pInfo->cBlankLinesBefore);
1395 pState->fWellFormedCopyright = false;
1396 }
1397
1398 /* If the comment doesn't start in column 1, trigger rewrite. */
1399 if (pInfo->offStart != 0)
1400 {
1401 ScmVerbose(pState->pState, 1, "* copyright comment starts in column %u instead of 1\n", pInfo->offStart + 1);
1402 pState->fWellFormedCopyright = false;
1403 /** @todo check that there isn't any code preceeding the comment. */
1404 }
1405
1406 if (pchContributedBy)
1407 {
1408 pState->pszContributedBy = RTStrDupN(pchContributedBy, cchContributedBy);
1409 if (cBlankLinesAfterContributedBy != 1)
1410 {
1411 ScmVerbose(pState->pState, 1, "* %u blank lines between contributed by and copyright, should be 1\n",
1412 cBlankLinesAfterContributedBy);
1413 pState->fWellFormedCopyright = false;
1414 }
1415 }
1416
1417 fFoundCopyright = true;
1418 ScmVerbose(pState->pState, 3, "oracle copyright %u-%u: up-to-date=%RTbool well-formed=%RTbool\n",
1419 pState->uFirstYear, pState->uLastYear, pState->fUpToDateCopyright, pState->fWellFormedCopyright);
1420 }
1421 else
1422 ScmVerbose(pState->pState, 3, "not oracle copyright: '%.*s'\n", pszEnd - pszBody, pszBody);
1423
1424 if (!pszNextLine)
1425 return VINF_SUCCESS;
1426
1427 /* Skip the copyright line and any blank lines following it. */
1428 cchBody -= pszNextLine - pszBody + 1;
1429 pszBody = pszNextLine + 1;
1430 iLine += 1;
1431 while (*pszBody == '\n')
1432 {
1433 pszBody++;
1434 cchBody--;
1435 iLine++;
1436 cBlankLinesAfterCopyright++;
1437 }
1438
1439 /*
1440 * If we have a based-on-mit scenario, check for the lead in now and
1441 * complain if not found.
1442 */
1443 if ( fFoundCopyright
1444 && pState->enmLicenceOpt == kScmLicense_BasedOnMit
1445 && pState->iLineLicense == UINT32_MAX)
1446 {
1447 if (RTStrNICmp(pszBody, RT_STR_TUPLE("This file is based on ")) == 0)
1448 {
1449 /* Take down a comment area which goes up to 'this file is based on'.
1450 The license line and length isn't used but gets set to cover the current line. */
1451 pState->iLineComment = pInfo->iLineStart;
1452 pState->cLinesComment = iLine - pInfo->iLineStart;
1453 pState->iLineLicense = iLine;
1454 pState->cLinesLicense = 1;
1455 pState->fExternalLicense = true;
1456 pState->fIsCorrectLicense = true;
1457 pState->fWellFormedLicense = true;
1458
1459 /* Check if we've got a MIT a license here or not. */
1460 pState->pCurrentLicense = NULL;
1461 do
1462 {
1463 const char *pszEol = (const char *)memchr(pszBody, '\n', cchBody);
1464 if (!pszEol || pszEol[1] == '\0')
1465 {
1466 pszBody += cchBody;
1467 cchBody = 0;
1468 break;
1469 }
1470 cchBody -= pszEol - pszBody + 1;
1471 pszBody = pszEol + 1;
1472 iLine++;
1473
1474 for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++)
1475 {
1476 const char *pszNext;
1477 if ( pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */
1478 && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext))
1479 {
1480 pState->pCurrentLicense = pCur;
1481 break;
1482 }
1483 }
1484 } while (!pState->pCurrentLicense);
1485 if (!pState->pCurrentLicense)
1486 ScmError(pState->pState, VERR_NOT_FOUND, "Could not find the based-on license!\n");
1487 else if (pState->pCurrentLicense->enmType != kScmLicenseType_Mit)
1488 ScmError(pState->pState, VERR_NOT_FOUND, "The based-on license is not MIT (%.32s...)\n",
1489 pState->pCurrentLicense->psz);
1490 }
1491 else
1492 ScmError(pState->pState, VERR_WRONG_ORDER, "Expected 'This file is based on ...' after our copyright!\n");
1493 return VINF_SUCCESS;
1494 }
1495 }
1496
1497 /*
1498 * Look for LGPL like text in the comment.
1499 */
1500 if (pState->fCheckforLgpl && cchBody > 128)
1501 {
1502 /* We look for typical LGPL notices. */
1503 if (pState->iLineLgplNotice == UINT32_MAX)
1504 {
1505 static const char * const s_apszFragments[] =
1506 {
1507 "under the terms of the GNU Lesser General Public License",
1508 };
1509 for (unsigned i = 0; i < RT_ELEMENTS(s_apszFragments); i++)
1510 if (scmContainsWordByWordIgnoreCase(pszBody, cchBody, s_apszFragments[i], NULL, NULL))
1511 {
1512 pState->iLineLgplNotice = iLine;
1513 pState->iLineAfterLgplComment = pInfo->iLineEnd + 1;
1514 ScmVerbose(pState->pState, 3, "Found LGPL notice at %u\n", iLine);
1515 break;
1516 }
1517 }
1518
1519 if ( pState->iLineLgplDisclaimer == UINT32_MAX
1520 && scmContainsWordByWordIgnoreCase(pszBody, cchBody, g_szLgplDisclaimer, NULL, NULL))
1521 {
1522 pState->iLineLgplDisclaimer = iLine;
1523 ScmVerbose(pState->pState, 3, "Found LGPL disclaimer at %u\n", iLine);
1524 }
1525 }
1526
1527 /*
1528 * Look for the license text.
1529 */
1530 if (pState->iLineLicense == UINT32_MAX)
1531 {
1532 for (PCSCMLICENSETEXT pCur = pState->paLicenses; pCur->cch > 0; pCur++)
1533 {
1534 const char *pszNext;
1535 if ( pCur->cch <= cchBody + 32 /* (+ 32 since we don't compare spaces and punctuation) */
1536 && IsEqualWordByWordIgnoreCase(pCur->psz, pszBody, &pszNext))
1537 {
1538 while ( RT_C_IS_SPACE(*pszNext)
1539 || (RT_C_IS_PUNCT(*pszNext) && *pszNext != '-'))
1540 pszNext++;
1541
1542 uint32_t cDashes = 0;
1543 while (*pszNext == '-')
1544 cDashes++, pszNext++;
1545 bool fExternal = cDashes > 10;
1546
1547 if ( *pszNext == '\0'
1548 || fExternal)
1549 {
1550 /* In C/C++ code, this must be a multiline comment. While in python it
1551 must be a */
1552 if (pState->enmCommentStyle == kScmCommentStyle_C && pInfo->enmType != kScmCommentType_MultiLine)
1553 ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a multiline comment (no doxygen stuff)\n");
1554 else if (pState->enmCommentStyle == kScmCommentStyle_Python && pInfo->enmType != kScmCommentType_DocString)
1555 ScmError(pState->pState, VERR_WRONG_ORDER, "License must appear in a doc-string\n");
1556
1557 /* Quit if we've flagged a failure. */
1558 if (RT_FAILURE(pState->pState->rc))
1559 return VERR_CALLBACK_RETURN;
1560
1561 /* Record it. */
1562 pState->iLineLicense = iLine;
1563 pState->cLinesLicense = CountLinesInSubstring(pszBody, pszNext - pszBody) - fExternal;
1564 pState->pCurrentLicense = pCur;
1565 pState->fExternalLicense = fExternal;
1566 pState->fIsCorrectLicense = pState->fOpenSource
1567 ? pCur == pState->pExpectedLicense
1568 : pCur->enmType == kScmLicenseType_Confidential;
1569 pState->fWellFormedLicense = memcmp(pszBody, pCur->psz, pCur->cch - 1) == 0;
1570 if (!pState->fWellFormedLicense)
1571 ScmVerbose(pState->pState, 1, "* license text isn't well-formed\n");
1572
1573 /* If there was more than one blank line between the copyright and the
1574 license text, extend the license text area and force a rewrite of it. */
1575 if (cBlankLinesAfterCopyright > 1)
1576 {
1577 ScmVerbose(pState->pState, 1, "* %u blank lines between copyright and license text, instead of 1\n",
1578 cBlankLinesAfterCopyright);
1579 pState->iLineLicense -= cBlankLinesAfterCopyright - 1;
1580 pState->cLinesLicense += cBlankLinesAfterCopyright - 1;
1581 pState->fWellFormedLicense = false;
1582 }
1583
1584 /* If there was more than one blank line after the license, trigger a rewrite. */
1585 if (!fExternal && pInfo->cBlankLinesAfter != 1)
1586 {
1587 ScmVerbose(pState->pState, 1, "* copyright comment is followed by %u blank lines instead of 1\n",
1588 pInfo->cBlankLinesAfter);
1589 pState->fWellFormedLicense = false;
1590 }
1591
1592 /** @todo Check that the last comment line doesn't have any code on it. */
1593 /** @todo Check that column 2 contains '*' for C/C++ files. */
1594
1595 ScmVerbose(pState->pState, 3,
1596 "Found license %d/%d at %u..%u: is-correct=%RTbool well-formed=%RTbool external-part=%RTbool open-source=%RTbool\n",
1597 pCur->enmType, pCur->enmOpt, pState->iLineLicense, pState->iLineLicense + pState->cLinesLicense,
1598 pState->fIsCorrectLicense, pState->fWellFormedLicense,
1599 pState->fExternalLicense, pState->fOpenSource);
1600
1601 if (fFoundCopyright)
1602 {
1603 pState->iLineComment = pInfo->iLineStart;
1604 pState->cLinesComment = (fExternal ? pState->iLineLicense + pState->cLinesLicense : pInfo->iLineEnd + 1)
1605 - pInfo->iLineStart;
1606 }
1607 else
1608 ScmError(pState->pState, VERR_WRONG_ORDER, "License should be preceeded by the copyright!\n");
1609 break;
1610 }
1611 }
1612 }
1613 }
1614
1615 if (fFoundCopyright && pState->iLineLicense == UINT32_MAX)
1616 ScmError(pState->pState, VERR_WRONG_ORDER, "Copyright should be followed by the license text!\n");
1617
1618 /*
1619 * Stop looking for stuff after 100 comments.
1620 */
1621 if (pState->cComments > 100)
1622 return VERR_CALLBACK_RETURN;
1623 return VINF_SUCCESS;
1624}
1625
1626/**
1627 * Writes comment body text.
1628 *
1629 * @returns Stream status.
1630 * @param pOut The output stream.
1631 * @param pszText The text to write.
1632 * @param cchText The length of the text.
1633 * @param enmCommentStyle The comment style.
1634 * @param enmEol The EOL style.
1635 */
1636static int scmWriteCommentBody(PSCMSTREAM pOut, const char *pszText, size_t cchText,
1637 SCMCOMMENTSTYLE enmCommentStyle, SCMEOL enmEol)
1638{
1639 Assert(pszText[cchText - 1] == '\n');
1640 Assert(pszText[cchText - 2] != '\n');
1641 NOREF(cchText);
1642 do
1643 {
1644 const char *pszEol = strchr(pszText, '\n');
1645 if (pszEol != pszText)
1646 {
1647 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1648 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1649 ScmStreamWrite(pOut, pszText, pszEol - pszText);
1650 ScmStreamPutEol(pOut, enmEol);
1651 }
1652 else
1653 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1654 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1655 pszText = pszEol + 1;
1656 } while (*pszText != '\0');
1657 return ScmStreamGetStatus(pOut);
1658}
1659
1660
1661/**
1662 * Updates the copyright year and/or license text.
1663 *
1664 * @returns true if modifications were made, false if not.
1665 * @param pState The rewriter state.
1666 * @param pIn The input stream.
1667 * @param pOut The output stream.
1668 * @param pSettings The settings.
1669 * @param enmCommentStyle The comment style used by the file.
1670 */
1671static bool rewrite_Copyright_Common(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings,
1672 SCMCOMMENTSTYLE enmCommentStyle)
1673{
1674 if ( !pSettings->fUpdateCopyrightYear
1675 && pSettings->enmUpdateLicense == kScmLicense_LeaveAlone)
1676 return false;
1677
1678 /*
1679 * Try locate the relevant comments.
1680 */
1681 SCMCOPYRIGHTINFO Info =
1682 {
1683 /*.pState = */ pState,
1684 /*.enmCommentStyle = */ enmCommentStyle,
1685
1686 /*.cComments = */ 0,
1687
1688 /*.pszContributedBy = */ NULL,
1689
1690 /*.iLineComment = */ UINT32_MAX,
1691 /*.cLinesComment = */ 0,
1692
1693 /*.iLineCopyright = */ UINT32_MAX,
1694 /*.uFirstYear = */ UINT32_MAX,
1695 /*.uLastYear = */ UINT32_MAX,
1696 /*.fWellFormedCopyright = */ false,
1697 /*.fUpToDateCopyright = */ false,
1698
1699 /*.fOpenSource = */ true,
1700 /*.pExpectedLicense = */ NULL,
1701 /*.paLicenses = */ pSettings->enmUpdateLicense != kScmLicense_Mit
1702 && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit
1703 ? &g_aLicenses[0] : &g_aLicensesWithMit[0],
1704 /*.enmLicenceOpt = */ pSettings->enmUpdateLicense,
1705 /*.iLineLicense = */ UINT32_MAX,
1706 /*.cLinesLicense = */ 0,
1707 /*.pCurrentLicense = */ NULL,
1708 /*.fIsCorrectLicense = */ false,
1709 /*.fWellFormedLicense = */ false,
1710 /*.fExternalLicense = */ false,
1711
1712 /*.fCheckForLgpl = */ true,
1713 /*.iLineLgplNotice = */ UINT32_MAX,
1714 /*.iLineAfterLgplComment = */ UINT32_MAX,
1715 /*.iLineLgplDisclaimer = */ UINT32_MAX,
1716 };
1717
1718 /* Figure Info.fOpenSource and the desired license: */
1719 char *pszSyncProcess;
1720 int rc = ScmSvnQueryProperty(pState, "svn:sync-process", &pszSyncProcess);
1721 if (RT_SUCCESS(rc))
1722 {
1723 Info.fOpenSource = strcmp(RTStrStrip(pszSyncProcess), "export") == 0;
1724 RTStrFree(pszSyncProcess);
1725 }
1726 else if (rc == VERR_NOT_FOUND)
1727 Info.fOpenSource = false;
1728 else
1729 return ScmError(pState, rc, "ScmSvnQueryProperty(svn:sync-process): %Rrc\n", rc);
1730
1731 Info.pExpectedLicense = Info.paLicenses;
1732 if (Info.fOpenSource)
1733 {
1734 if ( pSettings->enmUpdateLicense != kScmLicense_Mit
1735 && pSettings->enmUpdateLicense != kScmLicense_BasedOnMit)
1736 while (Info.pExpectedLicense->enmOpt != pSettings->enmUpdateLicense)
1737 Info.pExpectedLicense++;
1738 else
1739 Assert(Info.pExpectedLicense->enmOpt == kScmLicense_Mit);
1740 }
1741 else
1742 while (Info.pExpectedLicense->enmType != kScmLicenseType_Confidential)
1743 Info.pExpectedLicense++;
1744
1745 /* Scan the comments. */
1746 rc = ScmEnumerateComments(pIn, enmCommentStyle, rewrite_Copyright_CommentCallback, &Info);
1747 if ( (rc == VERR_CALLBACK_RETURN || RT_SUCCESS(rc))
1748 && RT_SUCCESS(pState->rc))
1749 {
1750 /*
1751 * Do conformity checks.
1752 */
1753 bool fAddLgplDisclaimer = false;
1754 if (Info.fCheckforLgpl)
1755 {
1756 if ( Info.iLineLgplNotice != UINT32_MAX
1757 && Info.iLineLgplDisclaimer == UINT32_MAX)
1758 {
1759 if (!pSettings->fLgplDisclaimer) /** @todo reconcile options with common sense. */
1760 ScmError(pState, VERR_NOT_FOUND, "LGPL licence notice on line %u, but no LGPL disclaimer was found!\n",
1761 Info.iLineLgplNotice + 1);
1762 else
1763 {
1764 ScmVerbose(pState, 1, "* Need to add LGPL disclaimer\n");
1765 fAddLgplDisclaimer = true;
1766 }
1767 }
1768 else if ( Info.iLineLgplNotice == UINT32_MAX
1769 && Info.iLineLgplDisclaimer != UINT32_MAX)
1770 ScmError(pState, VERR_NOT_FOUND, "LGPL disclaimer on line %u, but no LGPL copyright notice!\n",
1771 Info.iLineLgplDisclaimer + 1);
1772 }
1773
1774 if (!pSettings->fExternalCopyright)
1775 {
1776 if (Info.iLineCopyright == UINT32_MAX)
1777 ScmError(pState, VERR_NOT_FOUND, "Missing copyright!\n");
1778 if (Info.iLineLicense == UINT32_MAX)
1779 ScmError(pState, VERR_NOT_FOUND, "Missing license!\n");
1780 }
1781 else if (Info.iLineCopyright != UINT32_MAX)
1782 ScmError(pState, VERR_NOT_FOUND,
1783 "Marked as external copyright only, but found non-external copyright statement at line %u!\n",
1784 Info.iLineCopyright + 1);
1785
1786
1787 if (RT_SUCCESS(pState->rc))
1788 {
1789 /*
1790 * Do we need to make any changes?
1791 */
1792 bool fUpdateCopyright = !pSettings->fExternalCopyright
1793 && ( !Info.fWellFormedCopyright
1794 || (!Info.fUpToDateCopyright && pSettings->fUpdateCopyrightYear));
1795 bool fUpdateLicense = !pSettings->fExternalCopyright
1796 && Info.enmLicenceOpt != kScmLicense_LeaveAlone
1797 && ( !Info.fWellFormedLicense
1798 || !Info.fIsCorrectLicense);
1799 if ( fUpdateCopyright
1800 || fUpdateLicense
1801 || fAddLgplDisclaimer)
1802 {
1803 Assert(Info.iLineComment != UINT32_MAX);
1804 Assert(Info.cLinesComment > 0);
1805
1806 /*
1807 * Okay, do the work.
1808 */
1809 ScmStreamRewindForReading(pIn);
1810
1811 if (pSettings->fUpdateCopyrightYear)
1812 Info.uLastYear = g_uYear;
1813
1814 uint32_t iLine = 0;
1815 SCMEOL enmEol;
1816 size_t cchLine;
1817 const char *pchLine;
1818 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
1819 {
1820 if ( iLine == Info.iLineComment
1821 && (fUpdateCopyright || fUpdateLicense) )
1822 {
1823 /* Leading blank line. */
1824 ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz,
1825 g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol);
1826
1827 /* Contributed by someone? */
1828 if (Info.pszContributedBy)
1829 {
1830 const char *psz = Info.pszContributedBy;
1831 for (;;)
1832 {
1833 const char *pszEol = strchr(psz, '\n');
1834 size_t cchContribLine = pszEol ? pszEol - psz : strlen(psz);
1835 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1836 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1837 ScmStreamWrite(pOut, psz, cchContribLine);
1838 ScmStreamPutEol(pOut, enmEol);
1839 if (!pszEol)
1840 break;
1841 psz = pszEol + 1;
1842 }
1843
1844 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1845 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1846 }
1847
1848 /* Write the copyright comment line. */
1849 ScmStreamWrite(pOut, g_aCopyrightCommentPrefix[enmCommentStyle].psz,
1850 g_aCopyrightCommentPrefix[enmCommentStyle].cch);
1851
1852 char szCopyright[256];
1853 size_t cchCopyright;
1854 if (Info.uFirstYear == Info.uLastYear)
1855 cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u %s",
1856 Info.uFirstYear, g_szCopyrightHolder);
1857 else
1858 cchCopyright = RTStrPrintf(szCopyright, sizeof(szCopyright), "Copyright (C) %u-%u %s",
1859 Info.uFirstYear, Info.uLastYear, g_szCopyrightHolder);
1860
1861 ScmStreamWrite(pOut, szCopyright, cchCopyright);
1862 ScmStreamPutEol(pOut, enmEol);
1863
1864 if (pSettings->enmUpdateLicense != kScmLicense_BasedOnMit)
1865 {
1866 /* Blank line separating the two. */
1867 ScmStreamPutLine(pOut, g_aCopyrightCommentEmpty[enmCommentStyle].psz,
1868 g_aCopyrightCommentEmpty[enmCommentStyle].cch, enmEol);
1869
1870 /* Write the license text. */
1871 scmWriteCommentBody(pOut, Info.pExpectedLicense->psz, Info.pExpectedLicense->cch,
1872 enmCommentStyle, enmEol);
1873
1874 /* Final comment line. */
1875 if (!Info.fExternalLicense)
1876 ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz,
1877 g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol);
1878 }
1879 else
1880 Assert(Info.fExternalLicense);
1881
1882 /* Skip the copyright and license text in the input file. */
1883 rc = ScmStreamGetStatus(pOut);
1884 if (RT_SUCCESS(rc))
1885 {
1886 iLine = Info.iLineComment + Info.cLinesComment;
1887 rc = ScmStreamSeekByLine(pIn, iLine);
1888 }
1889 }
1890 /*
1891 * Add LGPL disclaimer?
1892 */
1893 else if ( iLine == Info.iLineAfterLgplComment
1894 && fAddLgplDisclaimer)
1895 {
1896 ScmStreamPutEol(pOut, enmEol);
1897 ScmStreamPutLine(pOut, g_aCopyrightCommentStart[enmCommentStyle].psz,
1898 g_aCopyrightCommentStart[enmCommentStyle].cch, enmEol);
1899 scmWriteCommentBody(pOut, g_szLgplDisclaimer, sizeof(g_szLgplDisclaimer) - 1,
1900 enmCommentStyle, enmEol);
1901 ScmStreamPutLine(pOut, g_aCopyrightCommentEnd[enmCommentStyle].psz,
1902 g_aCopyrightCommentEnd[enmCommentStyle].cch, enmEol);
1903
1904 /* put the actual line */
1905 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
1906 iLine++;
1907 }
1908 else
1909 {
1910 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
1911 iLine++;
1912 }
1913 if (RT_FAILURE(rc))
1914 {
1915 RTStrFree(Info.pszContributedBy);
1916 return false;
1917 }
1918 } /* for each source line */
1919
1920 RTStrFree(Info.pszContributedBy);
1921 return true;
1922 }
1923 }
1924 }
1925 else
1926 ScmError(pState, rc, "ScmEnumerateComments: %Rrc\n", rc);
1927 NOREF(pState); NOREF(pOut);
1928 RTStrFree(Info.pszContributedBy);
1929 return false;
1930}
1931
1932
1933/** Copyright updater for C-style comments. */
1934bool rewrite_Copyright_CstyleComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1935{
1936 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_C);
1937}
1938
1939/** Copyright updater for hash-prefixed comments. */
1940bool rewrite_Copyright_HashComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1941{
1942 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Hash);
1943}
1944
1945/** Copyright updater for REM-prefixed comments. */
1946bool rewrite_Copyright_RemComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1947{
1948 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, determinBatchFileCommentStyle(pIn));
1949}
1950
1951/** Copyright updater for python comments. */
1952bool rewrite_Copyright_PythonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1953{
1954 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Python);
1955}
1956
1957/** Copyright updater for semicolon-prefixed comments. */
1958bool rewrite_Copyright_SemicolonComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1959{
1960 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Semicolon);
1961}
1962
1963/** Copyright updater for sql comments. */
1964bool rewrite_Copyright_SqlComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1965{
1966 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Sql);
1967}
1968
1969/** Copyright updater for tick-prefixed comments. */
1970bool rewrite_Copyright_TickComment(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1971{
1972 return rewrite_Copyright_Common(pState, pIn, pOut, pSettings, kScmCommentStyle_Tick);
1973}
1974
1975
1976/**
1977 * Makefile.kup are empty files, enforce this.
1978 *
1979 * @returns true if modifications were made, false if not.
1980 * @param pIn The input stream.
1981 * @param pOut The output stream.
1982 * @param pSettings The settings.
1983 */
1984bool rewrite_Makefile_kup(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
1985{
1986 RT_NOREF2(pOut, pSettings);
1987
1988 /* These files should be zero bytes. */
1989 if (pIn->cb == 0)
1990 return false;
1991 ScmVerbose(pState, 2, " * Truncated file to zero bytes\n");
1992 return true;
1993}
1994
1995/**
1996 * Rewrite a kBuild makefile.
1997 *
1998 * @returns true if modifications were made, false if not.
1999 * @param pIn The input stream.
2000 * @param pOut The output stream.
2001 * @param pSettings The settings.
2002 *
2003 * @todo
2004 *
2005 * Ideas for Makefile.kmk and Config.kmk:
2006 * - sort if1of/ifn1of sets.
2007 * - line continuation slashes should only be preceded by one space.
2008 */
2009bool rewrite_Makefile_kmk(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2010{
2011 RT_NOREF4(pState, pIn, pOut, pSettings);
2012 return false;
2013}
2014
2015
2016static bool isFlowerBoxSectionMarker(PSCMSTREAM pIn, const char *pchLine, size_t cchLine, uint32_t cchWidth,
2017 const char **ppchText, size_t *pcchText, bool *pfNeedFixing)
2018{
2019 *ppchText = NULL;
2020 *pcchText = 0;
2021 *pfNeedFixing = false;
2022
2023 /*
2024 * The first line.
2025 */
2026 if (pchLine[0] != '/')
2027 return false;
2028 size_t offLine = 1;
2029 while (offLine < cchLine && pchLine[offLine] == '*')
2030 offLine++;
2031 if (offLine < 20) /* (Code below depend on a reasonable minimum here.) */
2032 return false;
2033 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2034 offLine++;
2035 if (offLine != cchLine)
2036 return false;
2037
2038 size_t const cchBox = cchLine;
2039 *pfNeedFixing = cchBox != cchWidth;
2040
2041 /*
2042 * The next line, extracting the text.
2043 */
2044 SCMEOL enmEol;
2045 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
2046 if (cchLine < cchBox - 3)
2047 return false;
2048
2049 offLine = 0;
2050 if (RT_C_IS_BLANK(pchLine[0]))
2051 {
2052 *pfNeedFixing = true;
2053 offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
2054 }
2055
2056 if (pchLine[offLine] != '*')
2057 return false;
2058 offLine++;
2059
2060 if (!RT_C_IS_BLANK(pchLine[offLine + 1]))
2061 return false;
2062 offLine++;
2063
2064 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2065 offLine++;
2066 if (offLine >= cchLine)
2067 return false;
2068 if (!RT_C_IS_UPPER(pchLine[offLine]))
2069 return false;
2070
2071 if (offLine != 4 || cchLine != cchBox)
2072 *pfNeedFixing = true;
2073
2074 *ppchText = &pchLine[offLine];
2075 size_t const offText = offLine;
2076
2077 /* From the end now. */
2078 offLine = cchLine - 1;
2079 while (RT_C_IS_BLANK(pchLine[offLine]))
2080 offLine--;
2081
2082 if (pchLine[offLine] != '*')
2083 return false;
2084 offLine--;
2085 if (!RT_C_IS_BLANK(pchLine[offLine]))
2086 return false;
2087 offLine--;
2088 while (RT_C_IS_BLANK(pchLine[offLine]))
2089 offLine--;
2090 *pcchText = offLine - offText + 1;
2091
2092 /*
2093 * Third line closes the box.
2094 */
2095 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
2096 if (cchLine < cchBox - 3)
2097 return false;
2098
2099 offLine = 0;
2100 if (RT_C_IS_BLANK(pchLine[0]))
2101 {
2102 *pfNeedFixing = true;
2103 offLine = RT_C_IS_BLANK(pchLine[1]) ? 2 : 1;
2104 }
2105 while (offLine < cchLine && pchLine[offLine] == '*')
2106 offLine++;
2107 if (offLine < cchBox - 4)
2108 return false;
2109
2110 if (pchLine[offLine] != '/')
2111 return false;
2112 offLine++;
2113
2114 if (offLine != cchBox)
2115 *pfNeedFixing = true;
2116
2117 while (offLine < cchLine && RT_C_IS_BLANK(pchLine[offLine]))
2118 offLine++;
2119 if (offLine != cchLine)
2120 return false;
2121
2122 return true;
2123}
2124
2125
2126/**
2127 * Flower box marker comments in C and C++ code.
2128 *
2129 * @returns true if modifications were made, false if not.
2130 * @param pIn The input stream.
2131 * @param pOut The output stream.
2132 * @param pSettings The settings.
2133 */
2134bool rewrite_FixFlowerBoxMarkers(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2135{
2136 if (!pSettings->fFixFlowerBoxMarkers)
2137 return false;
2138
2139 /*
2140 * Work thru the file line by line looking for flower box markers.
2141 */
2142 size_t cChanges = 0;
2143 size_t cBlankLines = 0;
2144 SCMEOL enmEol;
2145 size_t cchLine;
2146 const char *pchLine;
2147 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
2148 {
2149 /*
2150 * Get a likely match for a first line.
2151 */
2152 if ( pchLine[0] == '/'
2153 && cchLine > 20
2154 && pchLine[1] == '*'
2155 && pchLine[2] == '*'
2156 && pchLine[3] == '*')
2157 {
2158 size_t const offSaved = ScmStreamTell(pIn);
2159 char const *pchText;
2160 size_t cchText;
2161 bool fNeedFixing;
2162 bool fIsFlowerBoxSection = isFlowerBoxSectionMarker(pIn, pchLine, cchLine, pSettings->cchWidth,
2163 &pchText, &cchText, &fNeedFixing);
2164 if ( fIsFlowerBoxSection
2165 && ( fNeedFixing
2166 || cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers) )
2167 {
2168 while (cBlankLines < pSettings->cMinBlankLinesBeforeFlowerBoxMakers)
2169 {
2170 ScmStreamPutEol(pOut, enmEol);
2171 cBlankLines++;
2172 }
2173
2174 ScmStreamPutCh(pOut, '/');
2175 ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
2176 ScmStreamPutEol(pOut, enmEol);
2177
2178 static const char s_szLead[] = "* ";
2179 ScmStreamWrite(pOut, s_szLead, sizeof(s_szLead) - 1);
2180 ScmStreamWrite(pOut, pchText, cchText);
2181 size_t offCurPlus1 = sizeof(s_szLead) - 1 + cchText + 1;
2182 ScmStreamWrite(pOut, g_szSpaces, offCurPlus1 < pSettings->cchWidth ? pSettings->cchWidth - offCurPlus1 : 1);
2183 ScmStreamPutCh(pOut, '*');
2184 ScmStreamPutEol(pOut, enmEol);
2185
2186 ScmStreamWrite(pOut, g_szAsterisks, pSettings->cchWidth - 1);
2187 ScmStreamPutCh(pOut, '/');
2188 ScmStreamPutEol(pOut, enmEol);
2189
2190 cChanges++;
2191 cBlankLines = 0;
2192 continue;
2193 }
2194
2195 int rc = ScmStreamSeekAbsolute(pIn, offSaved);
2196 if (RT_FAILURE(rc))
2197 return false;
2198 }
2199
2200 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
2201 if (RT_FAILURE(rc))
2202 return false;
2203
2204 /* Do blank line accounting so we can ensure at least two blank lines
2205 before each section marker. */
2206 if (!isBlankLine(pchLine, cchLine))
2207 cBlankLines = 0;
2208 else
2209 cBlankLines++;
2210 }
2211 if (cChanges > 0)
2212 ScmVerbose(pState, 2, " * Converted %zu flower boxer markers\n", cChanges);
2213 return cChanges != 0;
2214}
2215
2216
2217/**
2218 * Looks for the start of a todo comment.
2219 *
2220 * @returns Offset into the line of the comment start sequence.
2221 * @param pchLine The line to search.
2222 * @param cchLineBeforeTodo The length of the line before the todo.
2223 * @param pfSameLine Indicates whether it's refering to a statemtn on
2224 * the same line comment (true), or the next
2225 * statement (false).
2226 */
2227static size_t findTodoCommentStart(char const *pchLine, size_t cchLineBeforeTodo, bool *pfSameLine)
2228{
2229 *pfSameLine = false;
2230
2231 /* Skip one '@' or '\\'. */
2232 char ch;
2233 if ( cchLineBeforeTodo > 2
2234 && ( ((ch = pchLine[cchLineBeforeTodo - 1]) == '@')
2235 || ch == '\\' ) )
2236 cchLineBeforeTodo--;
2237
2238 /* Skip blanks. */
2239 while ( cchLineBeforeTodo > 2
2240 && RT_C_IS_BLANK(pchLine[cchLineBeforeTodo - 1]))
2241 cchLineBeforeTodo--;
2242
2243 /* Look for same line indicator. */
2244 if ( cchLineBeforeTodo > 0
2245 && pchLine[cchLineBeforeTodo - 1] == '<')
2246 {
2247 *pfSameLine = true;
2248 cchLineBeforeTodo--;
2249 }
2250
2251 /* Skip *s */
2252 while ( cchLineBeforeTodo > 1
2253 && pchLine[cchLineBeforeTodo - 1] == '*')
2254 cchLineBeforeTodo--;
2255
2256 /* Do we have a comment opening sequence. */
2257 if ( cchLineBeforeTodo > 0
2258 && pchLine[cchLineBeforeTodo - 1] == '/'
2259 && ( ( cchLineBeforeTodo >= 2
2260 && pchLine[cchLineBeforeTodo - 2] == '/')
2261 || pchLine[cchLineBeforeTodo] == '*'))
2262 {
2263 /* Skip slashes at the start. */
2264 while ( cchLineBeforeTodo > 0
2265 && pchLine[cchLineBeforeTodo - 1] == '/')
2266 cchLineBeforeTodo--;
2267
2268 return cchLineBeforeTodo;
2269 }
2270
2271 return ~(size_t)0;
2272}
2273
2274
2275/**
2276 * Looks for a TODO or todo in the given line.
2277 *
2278 * @returns Offset into the line of found, ~(size_t)0 if not.
2279 * @param pchLine The line to search.
2280 * @param cchLine The length of the line.
2281 */
2282static size_t findTodo(char const *pchLine, size_t cchLine)
2283{
2284 if (cchLine >= 4 + 2)
2285 {
2286 /* We don't search the first to chars because we need the start of a comment.
2287 Also, skip the last three chars since we need at least four for a match. */
2288 size_t const cchLineT = cchLine - 3;
2289 if ( memchr(pchLine + 2, 't', cchLineT - 2) != NULL
2290 || memchr(pchLine + 2, 'T', cchLineT - 2) != NULL)
2291 {
2292 for (size_t off = 2; off < cchLineT; off++)
2293 {
2294 char ch = pchLine[off];
2295 if ( ( ch != 't'
2296 && ch != 'T')
2297 || ( (ch = pchLine[off + 1]) != 'o'
2298 && ch != 'O')
2299 || ( (ch = pchLine[off + 2]) != 'd'
2300 && ch != 'D')
2301 || ( (ch = pchLine[off + 3]) != 'o'
2302 && ch != 'O')
2303 || ( off + 4 != cchLine
2304 && (ch = pchLine[off + 4]) != ' '
2305 && ch != '\t'
2306 && ch != ':' /** @todo */
2307 && (ch != '*' || off + 5 > cchLine || pchLine[off + 5] != '/') /** @todo */
2308 ) )
2309 { /* not a hit - likely */ }
2310 else
2311 return off;
2312 }
2313 }
2314 }
2315 return ~(size_t)0;
2316}
2317
2318
2319/**
2320 * Doxygen todos in C and C++ code.
2321 *
2322 * @returns true if modifications were made, false if not.
2323 * @param pState The rewriter state.
2324 * @param pIn The input stream.
2325 * @param pOut The output stream.
2326 * @param pSettings The settings.
2327 */
2328bool rewrite_Fix_C_and_CPP_Todos(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2329{
2330 if (!pSettings->fFixTodos)
2331 return false;
2332
2333 /*
2334 * Work thru the file line by line looking for the start of todo comments.
2335 */
2336 size_t cChanges = 0;
2337 SCMEOL enmEol;
2338 size_t cchLine;
2339 const char *pchLine;
2340 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
2341 {
2342 /*
2343 * Look for the word 'todo' in the line. We're currently only trying
2344 * to catch comments starting with the word todo and adjust the start of
2345 * the doxygen statement.
2346 */
2347 size_t offTodo = findTodo(pchLine, cchLine);
2348 if ( offTodo != ~(size_t)0
2349 && offTodo >= 2)
2350 {
2351 /* Work backwards to find the start of the comment. */
2352 bool fSameLine = false;
2353 size_t offCommentStart = findTodoCommentStart(pchLine, offTodo, &fSameLine);
2354 if (offCommentStart != ~(size_t)0)
2355 {
2356 char szNew[64];
2357 size_t cchNew = 0;
2358 szNew[cchNew++] = '/';
2359 szNew[cchNew++] = pchLine[offCommentStart + 1];
2360 szNew[cchNew++] = pchLine[offCommentStart + 1];
2361 if (fSameLine)
2362 szNew[cchNew++] = '<';
2363 szNew[cchNew++] = ' ';
2364 szNew[cchNew++] = '@';
2365 szNew[cchNew++] = 't';
2366 szNew[cchNew++] = 'o';
2367 szNew[cchNew++] = 'd';
2368 szNew[cchNew++] = 'o';
2369
2370 /* Figure out wheter to continue after the @todo statement opening, we'll strip ':'
2371 but need to take into account that we might be at the end of the line before
2372 adding the space. */
2373 size_t offTodoAfter = offTodo + 4;
2374 if ( offTodoAfter < cchLine
2375 && pchLine[offTodoAfter] == ':')
2376 offTodoAfter++;
2377 if ( offTodoAfter < cchLine
2378 && RT_C_IS_BLANK(pchLine[offTodoAfter]))
2379 offTodoAfter++;
2380 if (offTodoAfter < cchLine)
2381 szNew[cchNew++] = ' ';
2382
2383 /* Write it out. */
2384 ScmStreamWrite(pOut, pchLine, offCommentStart);
2385 ScmStreamWrite(pOut, szNew, cchNew);
2386 if (offTodoAfter < cchLine)
2387 ScmStreamWrite(pOut, &pchLine[offTodoAfter], cchLine - offTodoAfter);
2388 ScmStreamPutEol(pOut, enmEol);
2389
2390 /* Check whether we actually made any changes. */
2391 if ( cchNew != offTodoAfter - offCommentStart
2392 || memcmp(szNew, &pchLine[offCommentStart], cchNew))
2393 cChanges++;
2394 continue;
2395 }
2396 }
2397
2398 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
2399 if (RT_FAILURE(rc))
2400 return false;
2401 }
2402 if (cChanges > 0)
2403 ScmVerbose(pState, 2, " * Converted %zu todo statements.\n", cChanges);
2404 return cChanges != 0;
2405}
2406
2407
2408/**
2409 * Tries to parse a C/C++ preprocessor include directive.
2410 *
2411 * This is resonably forgiving and expects sane input.
2412 *
2413 * @retval kScmIncludeDir_Invalid if not a valid include directive.
2414 * @retval kScmIncludeDir_Quoted
2415 * @retval kScmIncludeDir_Bracketed
2416 * @retval kScmIncludeDir_Macro
2417 *
2418 * @param pState The rewriter state (for repording malformed
2419 * directives).
2420 * @param pchLine The line to try parse as an include statement.
2421 * @param cchLine The line length.
2422 * @param ppchFilename Where to return the pointer to the filename part.
2423 * @param pcchFilename Where to return the length of the filename.
2424 */
2425SCMINCLUDEDIR ScmMaybeParseCIncludeLine(PSCMRWSTATE pState, const char *pchLine, size_t cchLine,
2426 const char **ppchFilename, size_t *pcchFilename)
2427{
2428 /* Skip leading spaces: */
2429 while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
2430 cchLine--, pchLine++;
2431
2432 /* Check for '#': */
2433 if (cchLine > 0 && *pchLine == '#')
2434 {
2435 cchLine--;
2436 pchLine++;
2437
2438 /* Skip spaces after '#' (optional): */
2439 while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
2440 cchLine--, pchLine++;
2441
2442 /* Check for 'include': */
2443 static char const s_szInclude[] = "include";
2444 if ( cchLine >= sizeof(s_szInclude)
2445 && memcmp(pchLine, RT_STR_TUPLE(s_szInclude)) == 0)
2446 {
2447 cchLine -= sizeof(s_szInclude) - 1;
2448 pchLine += sizeof(s_szInclude) - 1;
2449
2450 /* Skip spaces after 'include' word (optional): */
2451 while (cchLine > 0 && RT_C_IS_BLANK(*pchLine))
2452 cchLine--, pchLine++;
2453 if (cchLine > 0)
2454 {
2455 /* Quoted or bracketed? */
2456 char const chFirst = *pchLine;
2457 if (chFirst == '"' || chFirst == '<')
2458 {
2459 cchLine--;
2460 pchLine++;
2461 const char *pchEnd = (const char *)memchr(pchLine, chFirst == '"' ? '"' : '>', cchLine);
2462 if (pchEnd)
2463 {
2464 if (ppchFilename)
2465 *ppchFilename = pchLine;
2466 if (pcchFilename)
2467 *pcchFilename = pchEnd - pchLine;
2468 return chFirst == '"' ? kScmIncludeDir_Quoted : kScmIncludeDir_Bracketed;
2469 }
2470 ScmError(pState, VERR_PARSE_ERROR, "Unbalanced #include filename %s: %.*s\n",
2471 chFirst == '"' ? "quotes" : "brackets" , cchLine, pchLine);
2472 }
2473 /* C prepreprocessor macro? */
2474 else if (ScmIsCIdentifierLeadChar(chFirst))
2475 {
2476 size_t cchFilename = 1;
2477 while ( cchFilename < cchLine
2478 && ScmIsCIdentifierChar(pchLine[cchFilename]))
2479 cchFilename++;
2480 if (ppchFilename)
2481 *ppchFilename = pchLine;
2482 if (pcchFilename)
2483 *pcchFilename = cchFilename;
2484 return kScmIncludeDir_Macro;
2485 }
2486 else
2487 ScmError(pState, VERR_PARSE_ERROR, "Malformed #include filename part: %.*s\n", cchLine, pchLine);
2488 }
2489 else
2490 ScmError(pState, VERR_PARSE_ERROR, "Missing #include filename!\n");
2491 }
2492 }
2493
2494 if (ppchFilename)
2495 *ppchFilename = NULL;
2496 if (pcchFilename)
2497 *pcchFilename = 0;
2498 return kScmIncludeDir_Invalid;
2499}
2500
2501
2502/**
2503 * Fix err.h/errcore.h usage.
2504 *
2505 * @returns true if modifications were made, false if not.
2506 * @param pIn The input stream.
2507 * @param pOut The output stream.
2508 * @param pSettings The settings.
2509 */
2510bool rewrite_Fix_Err_H(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2511{
2512 if (!pSettings->fFixErrH)
2513 return false;
2514
2515 static struct
2516 {
2517 const char *pszHeader;
2518 unsigned cchHeader;
2519 int iLevel;
2520 } const s_aHeaders[] =
2521 {
2522 { RT_STR_TUPLE("iprt/errcore.h"), 1 },
2523 { RT_STR_TUPLE("iprt/err.h"), 2 },
2524 { RT_STR_TUPLE("VBox/err.h"), 3 },
2525 };
2526 static RTSTRTUPLE const g_aLevel1Statuses[] = /* Note! Keep in sync with errcore.h content! */
2527 {
2528 { RT_STR_TUPLE("VINF_SUCCESS") },
2529 { RT_STR_TUPLE("VERR_GENERAL_FAILURE") },
2530 { RT_STR_TUPLE("VERR_INVALID_PARAMETER") },
2531 { RT_STR_TUPLE("VWRN_INVALID_PARAMETER") },
2532 { RT_STR_TUPLE("VERR_INVALID_MAGIC") },
2533 { RT_STR_TUPLE("VWRN_INVALID_MAGIC") },
2534 { RT_STR_TUPLE("VERR_INVALID_HANDLE") },
2535 { RT_STR_TUPLE("VWRN_INVALID_HANDLE") },
2536 { RT_STR_TUPLE("VERR_INVALID_POINTER") },
2537 { RT_STR_TUPLE("VERR_NO_MEMORY") },
2538 { RT_STR_TUPLE("VERR_PERMISSION_DENIED") },
2539 { RT_STR_TUPLE("VINF_PERMISSION_DENIED") },
2540 { RT_STR_TUPLE("VERR_VERSION_MISMATCH") },
2541 { RT_STR_TUPLE("VERR_NOT_IMPLEMENTED") },
2542 { RT_STR_TUPLE("VERR_INVALID_FLAGS") },
2543 { RT_STR_TUPLE("VERR_WRONG_ORDER") },
2544 { RT_STR_TUPLE("VERR_INVALID_FUNCTION") },
2545 { RT_STR_TUPLE("VERR_NOT_SUPPORTED") },
2546 { RT_STR_TUPLE("VINF_NOT_SUPPORTED") },
2547 { RT_STR_TUPLE("VERR_ACCESS_DENIED") },
2548 { RT_STR_TUPLE("VERR_INTERRUPTED") },
2549 { RT_STR_TUPLE("VINF_INTERRUPTED") },
2550 { RT_STR_TUPLE("VERR_TIMEOUT") },
2551 { RT_STR_TUPLE("VINF_TIMEOUT") },
2552 { RT_STR_TUPLE("VERR_BUFFER_OVERFLOW") },
2553 { RT_STR_TUPLE("VINF_BUFFER_OVERFLOW") },
2554 { RT_STR_TUPLE("VERR_TOO_MUCH_DATA") },
2555 { RT_STR_TUPLE("VERR_TRY_AGAIN") },
2556 { RT_STR_TUPLE("VINF_TRY_AGAIN") },
2557 { RT_STR_TUPLE("VERR_PARSE_ERROR") },
2558 { RT_STR_TUPLE("VERR_OUT_OF_RANGE") },
2559 { RT_STR_TUPLE("VERR_NUMBER_TOO_BIG") },
2560 { RT_STR_TUPLE("VWRN_NUMBER_TOO_BIG") },
2561 { RT_STR_TUPLE("VERR_CANCELLED") },
2562 { RT_STR_TUPLE("VERR_TRAILING_CHARS") },
2563 { RT_STR_TUPLE("VWRN_TRAILING_CHARS") },
2564 { RT_STR_TUPLE("VERR_TRAILING_SPACES") },
2565 { RT_STR_TUPLE("VWRN_TRAILING_SPACES") },
2566 { RT_STR_TUPLE("VERR_NOT_FOUND") },
2567 { RT_STR_TUPLE("VWRN_NOT_FOUND") },
2568 { RT_STR_TUPLE("VERR_INVALID_STATE") },
2569 { RT_STR_TUPLE("VWRN_INVALID_STATE") },
2570 { RT_STR_TUPLE("VERR_OUT_OF_RESOURCES") },
2571 { RT_STR_TUPLE("VWRN_OUT_OF_RESOURCES") },
2572 { RT_STR_TUPLE("VERR_END_OF_STRING") },
2573 { RT_STR_TUPLE("VERR_CALLBACK_RETURN") },
2574 { RT_STR_TUPLE("VINF_CALLBACK_RETURN") },
2575 { RT_STR_TUPLE("VERR_DUPLICATE") },
2576 { RT_STR_TUPLE("VERR_MISSING") },
2577 { RT_STR_TUPLE("VERR_BUFFER_UNDERFLOW") },
2578 { RT_STR_TUPLE("VINF_BUFFER_UNDERFLOW") },
2579 { RT_STR_TUPLE("VERR_NOT_AVAILABLE") },
2580 { RT_STR_TUPLE("VERR_MISMATCH") },
2581 { RT_STR_TUPLE("VERR_WRONG_TYPE") },
2582 { RT_STR_TUPLE("VWRN_WRONG_TYPE") },
2583 { RT_STR_TUPLE("VERR_WRONG_PARAMETER_COUNT") },
2584 { RT_STR_TUPLE("VERR_WRONG_PARAMETER_TYPE") },
2585 { RT_STR_TUPLE("VERR_INVALID_CLIENT_ID") },
2586 { RT_STR_TUPLE("VERR_INVALID_SESSION_ID") },
2587 { RT_STR_TUPLE("VERR_INCOMPATIBLE_CONFIG") },
2588 { RT_STR_TUPLE("VERR_INTERNAL_ERROR") },
2589 { RT_STR_TUPLE("VINF_GETOPT_NOT_OPTION") },
2590 { RT_STR_TUPLE("VERR_GETOPT_UNKNOWN_OPTION") },
2591 };
2592
2593 /*
2594 * First pass: Scout #include err.h/errcore.h locations and usage.
2595 *
2596 * Note! This isn't entirely optimal since it's also parsing comments and
2597 * strings, not just code. However it does a decent job for now.
2598 */
2599 int iIncludeLevel = 0;
2600 int iUsageLevel = 0;
2601 uint32_t iLine = 0;
2602 SCMEOL enmEol;
2603 size_t cchLine;
2604 const char *pchLine;
2605 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
2606 {
2607 iLine++;
2608 if (cchLine < 6)
2609 continue;
2610
2611 /*
2612 * Look for #includes.
2613 */
2614 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
2615 if ( pchHash
2616 && isSpanOfBlanks(pchLine, pchHash - pchLine))
2617 {
2618 const char *pchFilename;
2619 size_t cchFilename;
2620 SCMINCLUDEDIR enmIncDir = ScmMaybeParseCIncludeLine(pState, pchLine, cchLine, &pchFilename, &cchFilename);
2621 if ( enmIncDir == kScmIncludeDir_Bracketed
2622 || enmIncDir == kScmIncludeDir_Quoted)
2623 {
2624 unsigned i = RT_ELEMENTS(s_aHeaders);
2625 while (i-- > 0)
2626 if ( s_aHeaders[i].cchHeader == cchFilename
2627 && RTStrNICmpAscii(pchFilename, s_aHeaders[i].pszHeader, cchFilename) == 0)
2628 {
2629 if (iIncludeLevel < s_aHeaders[i].iLevel)
2630 iIncludeLevel = s_aHeaders[i].iLevel;
2631 break;
2632 }
2633
2634 /* Special hack for error info. */
2635 if (cchFilename == sizeof("errmsgdata.h") - 1 && memcmp(pchFilename, RT_STR_TUPLE("errmsgdata.h")) == 0)
2636 iUsageLevel = 4;
2637
2638 /* Special hack for code templates. */
2639 if ( cchFilename >= sizeof(".cpp.h")
2640 && memcmp(&pchFilename[cchFilename - sizeof(".cpp.h") + 1], RT_STR_TUPLE(".cpp.h")) == 0)
2641 iUsageLevel = 4;
2642 continue;
2643 }
2644 }
2645 /*
2646 * Look for VERR_, VWRN_, VINF_ prefixed identifiers in the current line.
2647 */
2648 const char *pchHit = (const char *)memchr(pchLine, 'V', cchLine);
2649 if (pchHit)
2650 {
2651 const char *pchLeft = pchLine;
2652 size_t cchLeft = cchLine;
2653 do
2654 {
2655 size_t cchLeftHit = &pchLeft[cchLeft] - pchHit;
2656 if (cchLeftHit < 6)
2657 break;
2658 if ( pchHit[4] == '_'
2659 && ( pchHit == pchLine
2660 || !ScmIsCIdentifierChar(pchHit[-1]))
2661 && ( (pchHit[1] == 'E' && pchHit[2] == 'R' && pchHit[3] == 'R')
2662 || (pchHit[1] == 'W' && pchHit[2] == 'R' && pchHit[3] == 'N')
2663 || (pchHit[1] == 'I' && pchHit[2] == 'N' && pchHit[3] == 'F') ) )
2664 {
2665 size_t cchIdentifier = 5;
2666 while (cchIdentifier < cchLeftHit && ScmIsCIdentifierChar(pchHit[cchIdentifier]))
2667 cchIdentifier++;
2668 ScmVerbose(pState, 4, "--- status code at %u col %zu: %.*s\n",
2669 iLine, pchHit - pchLine, cchIdentifier, pchHit);
2670
2671 if (iUsageLevel <= 1)
2672 {
2673 iUsageLevel = 3; /* Cannot distingish between iprt/err.h and VBox/err.h, so pick the latter for now. */
2674 for (unsigned i = 0; i < RT_ELEMENTS(g_aLevel1Statuses); i++)
2675 if ( cchIdentifier == g_aLevel1Statuses[i].cch
2676 && memcmp(pchHit, g_aLevel1Statuses[i].psz, cchIdentifier) == 0)
2677 {
2678 iUsageLevel = 1;
2679 break;
2680 }
2681 }
2682
2683 pchLeft = pchHit + cchIdentifier;
2684 cchLeft = cchLeftHit - cchIdentifier;
2685 }
2686 else
2687 {
2688 pchLeft = pchHit + 1;
2689 cchLeft = cchLeftHit - 1;
2690 }
2691 pchHit = (const char *)memchr(pchLeft, 'V', cchLeft);
2692 } while (pchHit != NULL);
2693 }
2694 }
2695 ScmVerbose(pState, 3, "--- iIncludeLevel=%d iUsageLevel=%d\n", iIncludeLevel, iUsageLevel);
2696
2697 /*
2698 * Second pass: Change err.h to errcore.h if we detected a need for change.
2699 */
2700 if ( iIncludeLevel <= iUsageLevel
2701 || iIncludeLevel <= 1 /* we cannot safely eliminate errcore.h includes atm. */)
2702 return false;
2703
2704 unsigned cChanges = 0;
2705 ScmStreamRewindForReading(pIn);
2706 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
2707 {
2708 /*
2709 * Look for #includes to modify.
2710 */
2711 if (cchLine >= 6)
2712 {
2713 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
2714 if ( pchHash
2715 && isSpanOfBlanks(pchLine, pchHash - pchLine))
2716 {
2717 const char *pchFilename;
2718 size_t cchFilename;
2719 SCMINCLUDEDIR enmIncDir = ScmMaybeParseCIncludeLine(pState, pchLine, cchLine, &pchFilename, &cchFilename);
2720 if ( enmIncDir == kScmIncludeDir_Bracketed
2721 || enmIncDir == kScmIncludeDir_Quoted)
2722 {
2723 unsigned i = RT_ELEMENTS(s_aHeaders);
2724 while (i-- > 0)
2725 if ( s_aHeaders[i].cchHeader == cchFilename
2726 && RTStrNICmpAscii(pchFilename, s_aHeaders[i].pszHeader, cchFilename) == 0)
2727 {
2728 ScmStreamWrite(pOut, pchLine, pchFilename - pchLine - 1);
2729 ScmStreamWrite(pOut, RT_STR_TUPLE("<iprt/errcore.h>"));
2730 size_t cchTrailing = &pchLine[cchLine] - &pchFilename[cchFilename + 1];
2731 if (cchTrailing > 0)
2732 ScmStreamWrite(pOut, &pchFilename[cchFilename + 1], cchTrailing);
2733 ScmStreamPutEol(pOut, enmEol);
2734 cChanges++;
2735 pchLine = NULL;
2736 break;
2737 }
2738 if (!pchLine)
2739 continue;
2740 }
2741 }
2742 }
2743
2744 int rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
2745 if (RT_FAILURE(rc))
2746 return false;
2747 }
2748 ScmVerbose(pState, 2, " * Converted %zu err.h/errcore.h include statements.\n", cChanges);
2749 return true;
2750}
2751
2752typedef struct
2753{
2754 const char *pch;
2755 uint8_t cch;
2756 uint8_t cchSpaces; /**< Number of expected spaces before the word. */
2757 bool fSpacesBefore : 1; /**< Whether there may be spaces or tabs before the word. */
2758 bool fIdentifier : 1; /**< Whether we're to expect a C/C++ identifier rather than pch/cch. */
2759} SCMMATCHWORD;
2760
2761
2762int ScmMatchWords(const char *pchLine, size_t cchLine, SCMMATCHWORD const *paWords, size_t cWords,
2763 size_t *poffNext, PRTSTRTUPLE paIdentifiers, PRTERRINFO pErrInfo)
2764{
2765 int rc = VINF_SUCCESS;
2766
2767 size_t offLine = 0;
2768 for (size_t i = 0; i < cWords; i++)
2769 {
2770 SCMMATCHWORD const *pWord = &paWords[i];
2771
2772 /*
2773 * Deal with spaces preceeding the word first:
2774 */
2775 if (pWord->fSpacesBefore)
2776 {
2777 size_t cchSpaces = 0;
2778 size_t cchTabs = 0;
2779 while (offLine < cchLine)
2780 {
2781 const char ch = pchLine[offLine];
2782 if (ch == ' ')
2783 cchSpaces++;
2784 else if (ch == '\t')
2785 cchTabs++;
2786 else
2787 break;
2788 offLine++;
2789 }
2790
2791 if (cchSpaces == pWord->cchSpaces && cchTabs == 0)
2792 { /* likely */ }
2793 else if (cchSpaces == 0 && cchTabs == 0)
2794 return RTErrInfoSetF(pErrInfo, VERR_PARSE_ERROR, "expected space at offset %u", offLine);
2795 else
2796 rc = VWRN_TRAILING_SPACES;
2797 }
2798 else
2799 Assert(pWord->cchSpaces == 0);
2800
2801 /*
2802 * C/C++ identifier?
2803 */
2804 if (pWord->fIdentifier)
2805 {
2806 if (offLine >= cchLine)
2807 return RTErrInfoSetF(pErrInfo, VERR_END_OF_STRING,
2808 "expected '%.*s' (C/C++ identifier) at offset %u, not end of string",
2809 pWord->cch, pWord->pch, offLine);
2810 if (!ScmIsCIdentifierLeadChar(pchLine[offLine]))
2811 return RTErrInfoSetF(pErrInfo, VERR_MISMATCH, "expected '%.*s' (C/C++ identifier) at offset %u",
2812 pWord->cch, pWord->pch, offLine);
2813 size_t const offStart = offLine++;
2814 while (offLine < cchLine && ScmIsCIdentifierChar(pchLine[offLine]))
2815 offLine++;
2816 if (paIdentifiers)
2817 {
2818 paIdentifiers->cch = offLine - offStart;
2819 paIdentifiers->psz = &pchLine[offStart];
2820 paIdentifiers++;
2821 }
2822 }
2823 /*
2824 * Match the exact word.
2825 */
2826 else if ( pWord->cch == 0
2827 || ( pWord->cch <= cchLine - offLine
2828 && !memcmp(pWord->pch, &pchLine[offLine], pWord->cch)))
2829 offLine += pWord->cch;
2830 else
2831 return RTErrInfoSetF(pErrInfo, VERR_MISMATCH, "expected '%.*s' at offset %u", pWord->cch, pWord->pch, offLine);
2832 }
2833
2834 /*
2835 * Check for trailing characters/whatnot.
2836 */
2837 if (poffNext)
2838 *poffNext = offLine;
2839 else if (offLine != cchLine)
2840 rc = RTErrInfoSetF(pErrInfo, VERR_TRAILING_CHARS, "unexpected trailing characters at offset %u", offLine);
2841 return rc;
2842}
2843
2844
2845/**
2846 * Fix header file include guards and \#pragma once.
2847 *
2848 * @returns true if modifications were made, false if not.
2849 * @param pIn The input stream.
2850 * @param pOut The output stream.
2851 * @param pSettings The settings.
2852 */
2853bool rewrite_FixHeaderGuards(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
2854{
2855 if (!pSettings->fFixHeaderGuards)
2856 return false;
2857
2858 /* always skip .cpp.h files */
2859 size_t cchFilename = strlen(pState->pszFilename);
2860 if ( cchFilename > sizeof(".cpp.h")
2861 && RTStrICmpAscii(&pState->pszFilename[cchFilename - sizeof(".cpp.h") + 1], ".cpp.h") == 0)
2862 return false;
2863
2864 RTERRINFOSTATIC ErrInfo;
2865 char szNormalized[168];
2866 size_t cchNormalized = 0;
2867 int rc;
2868 bool fRet = false;
2869
2870 /*
2871 * Calculate the expected guard for this file, if so tasked.
2872 * ASSUMES pState->pszFilename is absolute as is pSettings->pszGuardRelativeToDir.
2873 */
2874 szNormalized[0] = '\0';
2875 if (pSettings->pszGuardRelativeToDir)
2876 {
2877 rc = RTStrCopy(szNormalized, sizeof(szNormalized), pSettings->pszGuardPrefix);
2878 if (RT_FAILURE(rc))
2879 return ScmError(pState, rc, "Guard prefix too long (or something): %s\n", pSettings->pszGuardPrefix);
2880 cchNormalized = strlen(szNormalized);
2881 rc = RTPathCalcRelative(&szNormalized[cchNormalized], sizeof(szNormalized) - cchNormalized,
2882 pSettings->pszGuardRelativeToDir, pState->pszFilename);
2883 if (RT_FAILURE(rc))
2884 return ScmError(pState, rc, "Error calculating guard prefix (RTPathCalcRelative): %Rrc\n", rc);
2885 char ch;
2886 while ((ch = szNormalized[cchNormalized]) != '\0')
2887 {
2888 if (!ScmIsCIdentifierChar(ch))
2889 szNormalized[cchNormalized] = '_';
2890 cchNormalized++;
2891 }
2892 }
2893
2894 /*
2895 * First part looks for the #ifndef xxxx paired with #define xxxx.
2896 *
2897 * We blindly assume the first preprocessor directive in the file is the guard
2898 * and will be upset if this isn't the case.
2899 */
2900 RTSTRTUPLE Guard = { NULL, 0 };
2901 uint32_t cBlankLines = 0;
2902 SCMEOL enmEol;
2903 size_t cchLine;
2904 const char *pchLine;
2905 for (;;)
2906 {
2907 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
2908 if (pchLine == NULL)
2909 return ScmError(pState, VERR_PARSE_ERROR, "Did not find any include guards!\n");
2910 if (cchLine >= 2)
2911 {
2912 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
2913 if ( pchHash
2914 && isSpanOfBlanks(pchLine, pchHash - pchLine))
2915 {
2916 /* #ifndef xxxx */
2917 static const SCMMATCHWORD s_aIfndefGuard[] =
2918 {
2919 { RT_STR_TUPLE("#"), 0, true, false },
2920 { RT_STR_TUPLE("ifndef"), 0, true, false },
2921 { RT_STR_TUPLE("IDENTIFIER"), 1, true, true },
2922 { RT_STR_TUPLE(""), 0, true, false },
2923 };
2924 rc = ScmMatchWords(pchLine, cchLine, s_aIfndefGuard, RT_ELEMENTS(s_aIfndefGuard),
2925 NULL /*poffNext*/, &Guard, RTErrInfoInitStatic(&ErrInfo));
2926 if (RT_FAILURE(rc))
2927 return ScmError(pState, rc, "%u: Expected first preprocessor directive to be '#ifndef xxxx'. %s (%.*s)\n",
2928 ScmStreamTellLine(pIn) - 1, ErrInfo.Core.pszMsg, cchLine, pchLine);
2929 fRet |= rc != VINF_SUCCESS;
2930 ScmVerbose(pState, 3, "line %u in %s: #ifndef %.*s\n",
2931 ScmStreamTellLine(pIn) - 1, pState->pszFilename, Guard.cch, Guard.psz);
2932
2933 /* #define xxxx */
2934 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
2935 if (!pchLine)
2936 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef %.*s'\n",
2937 ScmStreamTellLine(pIn) - 1, Guard.cch, Guard.psz);
2938 const SCMMATCHWORD aDefineGuard[] =
2939 {
2940 { RT_STR_TUPLE("#"), 0, true, false },
2941 { RT_STR_TUPLE("define"), 0, true, false },
2942 { Guard.psz, (uint8_t)Guard.cch, 1, true, false },
2943 { RT_STR_TUPLE(""), 0, true, false },
2944 };
2945 rc = ScmMatchWords(pchLine, cchLine, aDefineGuard, RT_ELEMENTS(aDefineGuard),
2946 NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
2947 if (RT_FAILURE(rc))
2948 return ScmError(pState, rc, "%u: Expected '#define %.*s' to follow '#ifndef %.*s'. %s (%.*s)\n",
2949 ScmStreamTellLine(pIn) - 1, Guard.cch, Guard.psz, Guard.cch, Guard.psz,
2950 ErrInfo.Core.pszMsg, cchLine, pchLine);
2951 fRet |= rc != VINF_SUCCESS;
2952
2953 if (Guard.cch >= sizeof(szNormalized))
2954 return ScmError(pState, VERR_BUFFER_OVERFLOW, "%u: Guard macro too long! %.*s\n",
2955 ScmStreamTellLine(pIn) - 2, Guard.cch, Guard.psz);
2956
2957 if (szNormalized[0] != '\0')
2958 {
2959 if ( Guard.cch != cchNormalized
2960 || memcmp(Guard.psz, szNormalized, cchNormalized) != 0)
2961 {
2962 ScmVerbose(pState, 2, "guard changed from %.*s to %s\n", Guard.cch, Guard.psz, szNormalized);
2963 ScmVerbose(pState, 2, "grep -rw %.*s ${WCROOT} | grep -Fv %s\n",
2964 Guard.cch, Guard.psz, pState->pszFilename);
2965 fRet = true;
2966 }
2967 Guard.psz = szNormalized;
2968 Guard.cch = cchNormalized;
2969 }
2970
2971 /*
2972 * Write guard, making sure we've got a single blank line preceeding it.
2973 */
2974 ScmStreamPutEol(pOut, enmEol);
2975 ScmStreamWrite(pOut, RT_STR_TUPLE("#ifndef "));
2976 ScmStreamWrite(pOut, Guard.psz, Guard.cch);
2977 ScmStreamPutEol(pOut, enmEol);
2978 ScmStreamWrite(pOut, RT_STR_TUPLE("#define "));
2979 ScmStreamWrite(pOut, Guard.psz, Guard.cch);
2980 rc = ScmStreamPutEol(pOut, enmEol);
2981 if (RT_FAILURE(rc))
2982 return false;
2983 break;
2984 }
2985 }
2986
2987 if (!isBlankLine(pchLine, cchLine))
2988 {
2989 while (cBlankLines-- > 0)
2990 ScmStreamPutEol(pOut, enmEol);
2991 cBlankLines = 0;
2992 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
2993 if (RT_FAILURE(rc))
2994 return false;
2995 }
2996 else
2997 cBlankLines++;
2998 }
2999
3000 /*
3001 * Look for pragma once wrapped in #ifndef RT_WITHOUT_PRAGMA_ONCE.
3002 */
3003 size_t const iPragmaOnce = ScmStreamTellLine(pIn);
3004 static const SCMMATCHWORD s_aIfndefRtWithoutPragmaOnce[] =
3005 {
3006 { RT_STR_TUPLE("#"), 0, true, false },
3007 { RT_STR_TUPLE("ifndef"), 0, true, false },
3008 { RT_STR_TUPLE("RT_WITHOUT_PRAGMA_ONCE"), 1, true, false },
3009 { RT_STR_TUPLE(""), 0, true, false },
3010 };
3011 static const SCMMATCHWORD s_aPragmaOnce[] =
3012 {
3013 { RT_STR_TUPLE("#"), 0, true, false },
3014 { RT_STR_TUPLE("pragma"), 1, true, false },
3015 { RT_STR_TUPLE("once"), 1, true, false},
3016 { RT_STR_TUPLE(""), 0, true, false },
3017 };
3018 static const SCMMATCHWORD s_aEndif[] =
3019 {
3020 { RT_STR_TUPLE("#"), 0, true, false },
3021 { RT_STR_TUPLE("endif"), 0, true, false },
3022 { RT_STR_TUPLE(""), 0, true, false },
3023 };
3024
3025 /* #ifndef RT_WITHOUT_PRAGMA_ONCE */
3026 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
3027 if (!pchLine)
3028 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after header guard!\n", iPragmaOnce + 1);
3029 size_t offNext;
3030 rc = ScmMatchWords(pchLine, cchLine, s_aIfndefRtWithoutPragmaOnce, RT_ELEMENTS(s_aIfndefRtWithoutPragmaOnce),
3031 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
3032 if (RT_SUCCESS(rc))
3033 {
3034 fRet |= rc != VINF_SUCCESS;
3035 if (offNext != cchLine)
3036 return ScmError(pState, VERR_PARSE_ERROR, "%u: Characters trailing '#ifndef RT_WITHOUT_PRAGMA_ONCE' (%.*s)\n",
3037 iPragmaOnce + 1, cchLine, pchLine);
3038
3039 /* # pragma once */
3040 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
3041 if (!pchLine)
3042 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef RT_WITHOUT_PRAGMA_ONCE'\n",
3043 iPragmaOnce + 2);
3044 rc = ScmMatchWords(pchLine, cchLine, s_aPragmaOnce, RT_ELEMENTS(s_aPragmaOnce),
3045 NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
3046 if (RT_SUCCESS(rc))
3047 fRet |= rc != VINF_SUCCESS;
3048 else
3049 return ScmError(pState, rc, "%u: Expected '# pragma once' to follow '#ifndef RT_WITHOUT_PRAGMA_ONCE'! %s (%.*s)\n",
3050 iPragmaOnce + 2, ErrInfo.Core.pszMsg, cchLine, pchLine);
3051
3052 /* #endif */
3053 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
3054 if (!pchLine)
3055 return ScmError(pState, VERR_PARSE_ERROR, "%u: Unexpected end of file after '#ifndef RT_WITHOUT_PRAGMA_ONCE' and '#pragma once'\n",
3056 iPragmaOnce + 3);
3057 rc = ScmMatchWords(pchLine, cchLine, s_aEndif, RT_ELEMENTS(s_aEndif),
3058 NULL /*poffNext*/, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
3059 if (RT_SUCCESS(rc))
3060 fRet |= rc != VINF_SUCCESS;
3061 else
3062 return ScmError(pState, rc,
3063 "%u: Expected '#endif' to follow '#ifndef RT_WITHOUT_PRAGMA_ONCE' and '# pragma once'! %s (%.*s)\n",
3064 iPragmaOnce + 3, ErrInfo.Core.pszMsg, cchLine, pchLine);
3065 ScmVerbose(pState, 3, "Found pragma once\n");
3066 fRet |= !pSettings->fPragmaOnce;
3067 }
3068 else
3069 {
3070 rc = ScmStreamSeekByLine(pIn, iPragmaOnce);
3071 if (RT_FAILURE(rc))
3072 return ScmError(pState, rc, "seek error\n");
3073 fRet |= pSettings->fPragmaOnce;
3074 ScmVerbose(pState, 2, "Missing #pragma once\n");
3075 }
3076
3077 /*
3078 * Write the pragma once stuff.
3079 */
3080 if (pSettings->fPragmaOnce)
3081 {
3082 ScmStreamPutLine(pOut, RT_STR_TUPLE("#ifndef RT_WITHOUT_PRAGMA_ONCE"), enmEol);
3083 ScmStreamPutLine(pOut, RT_STR_TUPLE("# pragma once"), enmEol);
3084 rc = ScmStreamPutLine(pOut, RT_STR_TUPLE("#endif"), enmEol);
3085 if (RT_FAILURE(rc))
3086 return false;
3087 }
3088
3089 /*
3090 * Copy the rest of the file and remove pragma once statements, while
3091 * looking for the last #endif in the file.
3092 */
3093 size_t iEndIfIn = 0;
3094 size_t iEndIfOut = 0;
3095 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
3096 {
3097 if (cchLine > 2)
3098 {
3099 const char *pchHash = (const char *)memchr(pchLine, '#', cchLine);
3100 if ( pchHash
3101 && isSpanOfBlanks(pchLine, pchHash - pchLine))
3102 {
3103 size_t off = pchHash - pchLine + 1;
3104 while (off < cchLine && RT_C_IS_BLANK(pchLine[off]))
3105 off++;
3106 /* #pragma once */
3107 if ( off + sizeof("pragma") - 1 <= cchLine
3108 && !memcmp(&pchLine[off], RT_STR_TUPLE("pragma")))
3109 {
3110 rc = ScmMatchWords(pchLine, cchLine, s_aPragmaOnce, RT_ELEMENTS(s_aPragmaOnce),
3111 &offNext, NULL /*paIdentifiers*/, RTErrInfoInitStatic(&ErrInfo));
3112 if (RT_SUCCESS(rc))
3113 {
3114 fRet = true;
3115 continue;
3116 }
3117 }
3118 /* #endif */
3119 else if ( off + sizeof("endif") - 1 <= cchLine
3120 && !memcmp(&pchLine[off], RT_STR_TUPLE("endif")))
3121 {
3122 iEndIfIn = ScmStreamTellLine(pIn) - 1;
3123 iEndIfOut = ScmStreamTellLine(pOut);
3124 }
3125 }
3126 }
3127
3128 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
3129 if (RT_FAILURE(rc))
3130 return false;
3131 }
3132
3133 /*
3134 * Check out the last endif, making sure it's well formed and make sure it has the
3135 * right kind of comment following it.
3136 */
3137 if (pSettings->fFixHeaderGuardEndif)
3138 {
3139 if (iEndIfOut == 0)
3140 return ScmError(pState, VERR_PARSE_ERROR, "Expected '#endif' at the end of the file...\n");
3141 rc = ScmStreamSeekByLine(pIn, iEndIfIn);
3142 if (RT_FAILURE(rc))
3143 return false;
3144 rc = ScmStreamSeekByLine(pOut, iEndIfOut);
3145 if (RT_FAILURE(rc))
3146 return false;
3147
3148 pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol);
3149 if (!pchLine)
3150 return ScmError(pState, VERR_INTERNAL_ERROR, "ScmStreamGetLine failed re-reading #endif!\n");
3151
3152 char szTmp[64 + sizeof(szNormalized)];
3153 size_t cchTmp;
3154 if (pSettings->fEndifGuardComment)
3155 cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "#endif /* !%.*s */", Guard.cch, Guard.psz);
3156 else
3157 cchTmp = RTStrPrintf(szTmp, sizeof(szTmp), "#endif"); /* lazy bird */
3158 fRet |= cchTmp != cchLine || memcmp(szTmp, pchLine, cchTmp) != 0;
3159 rc = ScmStreamPutLine(pOut, szTmp, cchTmp, enmEol);
3160 if (RT_FAILURE(rc))
3161 return false;
3162
3163 /* Copy out the remaining lines (assumes no #pragma once here). */
3164 while ((pchLine = ScmStreamGetLine(pIn, &cchLine, &enmEol)) != NULL)
3165 {
3166 rc = ScmStreamPutLine(pOut, pchLine, cchLine, enmEol);
3167 if (RT_FAILURE(rc))
3168 return false;
3169 }
3170 }
3171
3172 return fRet;
3173}
3174
3175
3176/**
3177 * Rewrite a C/C++ source or header file.
3178 *
3179 * @returns true if modifications were made, false if not.
3180 * @param pIn The input stream.
3181 * @param pOut The output stream.
3182 * @param pSettings The settings.
3183 *
3184 * @todo
3185 *
3186 * Ideas for C/C++:
3187 * - space after if, while, for, switch
3188 * - spaces in for (i=0;i<x;i++)
3189 * - complex conditional, bird style.
3190 * - remove unnecessary parentheses.
3191 * - sort defined RT_OS_*|| and RT_ARCH
3192 * - sizeof without parenthesis.
3193 * - defined without parenthesis.
3194 * - trailing spaces.
3195 * - parameter indentation.
3196 * - space after comma.
3197 * - while (x--); -> multi line + comment.
3198 * - else statement;
3199 * - space between function and left parenthesis.
3200 * - TODO, XXX, @todo cleanup.
3201 * - Space before/after '*'.
3202 * - ensure new line at end of file.
3203 * - Indentation of precompiler statements (#ifdef, #defines).
3204 * - space between functions.
3205 * - string.h -> iprt/string.h, stdarg.h -> iprt/stdarg.h, etc.
3206 */
3207bool rewrite_C_and_CPP(PSCMRWSTATE pState, PSCMSTREAM pIn, PSCMSTREAM pOut, PCSCMSETTINGSBASE pSettings)
3208{
3209
3210 RT_NOREF4(pState, pIn, pOut, pSettings);
3211 return false;
3212}
3213
Note: See TracBrowser for help on using the repository browser.

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