VirtualBox

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

Last change on this file since 96401 was 96401, checked in by vboxsync, 2 years ago

bldprogs/scm: Teach it to replace the old copyright and license notices with the updated ones (e.g. GPlv2 to GPLv3).

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