VirtualBox

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

Last change on this file since 96860 was 96407, checked in by vboxsync, 22 months ago

scm copyright and license note update

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

© 2023 Oracle
ContactPrivacy policyTerms of Use