[41624] | 1 | /* $Id: filesplitter.cpp 103385 2024-02-15 12:30:38Z vboxsync $ */
|
---|
[23269] | 2 | /** @file
|
---|
[41624] | 3 | * File splitter - Splits a text file according to ###### markers in it.
|
---|
[23269] | 4 | */
|
---|
| 5 |
|
---|
| 6 | /*
|
---|
[98103] | 7 | * Copyright (C) 2006-2023 Oracle and/or its affiliates.
|
---|
[23269] | 8 | *
|
---|
[96407] | 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
|
---|
[23269] | 26 | */
|
---|
| 27 |
|
---|
[41624] | 28 |
|
---|
[57353] | 29 | /*********************************************************************************************************************************
|
---|
| 30 | * Header Files *
|
---|
| 31 | *********************************************************************************************************************************/
|
---|
[23269] | 32 | #include <sys/types.h>
|
---|
| 33 | #include <sys/stat.h>
|
---|
| 34 | #include <stdio.h>
|
---|
| 35 | #include <stdlib.h>
|
---|
[41624] | 36 | #include <errno.h>
|
---|
[23269] | 37 |
|
---|
[41624] | 38 | #include <iprt/string.h>
|
---|
| 39 | #include <iprt/stdarg.h>
|
---|
| 40 |
|
---|
| 41 |
|
---|
[57353] | 42 | /*********************************************************************************************************************************
|
---|
| 43 | * Defined Constants And Macros *
|
---|
| 44 | *********************************************************************************************************************************/
|
---|
[41630] | 45 | #ifndef S_ISDIR
|
---|
| 46 | # define S_ISDIR(a_fMode) ( (S_IFMT & (a_fMode)) == S_IFDIR )
|
---|
| 47 | #endif
|
---|
| 48 |
|
---|
| 49 |
|
---|
[41624] | 50 | /**
|
---|
| 51 | * Calculates the line number for a file position.
|
---|
[48959] | 52 | *
|
---|
[41624] | 53 | * @returns Line number.
|
---|
| 54 | * @param pcszContent The file content.
|
---|
| 55 | * @param pcszPos The current position.
|
---|
| 56 | */
|
---|
| 57 | static unsigned long lineNumber(const char *pcszContent, const char *pcszPos)
|
---|
[23269] | 58 | {
|
---|
| 59 | unsigned long cLine = 0;
|
---|
[48959] | 60 | while ( *pcszContent
|
---|
[41624] | 61 | && (uintptr_t)pcszContent < (uintptr_t)pcszPos)
|
---|
[23269] | 62 | {
|
---|
[41624] | 63 | pcszContent = strchr(pcszContent, '\n');
|
---|
| 64 | if (!pcszContent)
|
---|
[23269] | 65 | break;
|
---|
| 66 | ++cLine;
|
---|
[41624] | 67 | ++pcszContent;
|
---|
[23269] | 68 | }
|
---|
| 69 |
|
---|
| 70 | return cLine;
|
---|
| 71 | }
|
---|
| 72 |
|
---|
[41624] | 73 |
|
---|
| 74 | /**
|
---|
[48959] | 75 | * Writes an error message.
|
---|
| 76 | *
|
---|
[41624] | 77 | * @returns RTEXITCODE_FAILURE.
|
---|
| 78 | * @param pcszFormat Error message.
|
---|
| 79 | * @param ... Format argument referenced in the message.
|
---|
| 80 | */
|
---|
| 81 | static int printErr(const char *pcszFormat, ...)
|
---|
[23269] | 82 | {
|
---|
[41624] | 83 | va_list va;
|
---|
[23269] | 84 |
|
---|
[41624] | 85 | fprintf(stderr, "filesplitter: ");
|
---|
| 86 | va_start(va, pcszFormat);
|
---|
| 87 | vfprintf(stderr, pcszFormat, va);
|
---|
| 88 | va_end(va);
|
---|
| 89 |
|
---|
| 90 | return RTEXITCODE_FAILURE;
|
---|
| 91 | }
|
---|
| 92 |
|
---|
| 93 |
|
---|
| 94 | /**
|
---|
[48959] | 95 | * Opens the makefile list for writing.
|
---|
| 96 | *
|
---|
[41630] | 97 | * @returns Exit code.
|
---|
| 98 | * @param pcszPath The path to the file.
|
---|
| 99 | * @param pcszVariableName The make variable name.
|
---|
| 100 | * @param ppFile Where to return the file stream.
|
---|
| 101 | */
|
---|
| 102 | static int openMakefileList(const char *pcszPath, const char *pcszVariableName, FILE **ppFile)
|
---|
| 103 | {
|
---|
| 104 | *ppFile = NULL;
|
---|
| 105 |
|
---|
| 106 | FILE *pFile= fopen(pcszPath, "w");
|
---|
| 107 | if (!pFile)
|
---|
[65237] | 108 | #ifdef _MSC_VER
|
---|
| 109 | return printErr("Failed to open \"%s\" for writing the file list: %s (win32: %d)\n",
|
---|
| 110 | pcszPath, strerror(errno), _doserrno);
|
---|
| 111 | #else
|
---|
| 112 | return printErr("Failed to open \"%s\" for writing the file list: %s\n", pcszPath, strerror(errno));
|
---|
| 113 | #endif
|
---|
[41630] | 114 |
|
---|
| 115 | if (fprintf(pFile, "%s := \\\n", pcszVariableName) <= 0)
|
---|
| 116 | {
|
---|
| 117 | fclose(pFile);
|
---|
| 118 | return printErr("Error writing to the makefile list.\n");
|
---|
| 119 | }
|
---|
| 120 |
|
---|
| 121 | *ppFile = pFile;
|
---|
| 122 | return 0;
|
---|
| 123 | }
|
---|
| 124 |
|
---|
| 125 |
|
---|
| 126 | /**
|
---|
[48959] | 127 | * Adds the given file to the makefile list.
|
---|
| 128 | *
|
---|
[41630] | 129 | * @returns Exit code.
|
---|
| 130 | * @param pFile The file stream of the makefile list.
|
---|
| 131 | * @param pszFilename The file name to add.
|
---|
| 132 | */
|
---|
| 133 | static int addFileToMakefileList(FILE *pFile, char *pszFilename)
|
---|
| 134 | {
|
---|
| 135 | if (pFile)
|
---|
| 136 | {
|
---|
| 137 | char *pszSlash = pszFilename;
|
---|
| 138 | while ((pszSlash = strchr(pszSlash, '\\')) != NULL)
|
---|
| 139 | *pszSlash++ = '/';
|
---|
| 140 |
|
---|
| 141 | if (fprintf(pFile, "\t%s \\\n", pszFilename) <= 0)
|
---|
| 142 | return printErr("Error adding file to makefile list.\n");
|
---|
| 143 | }
|
---|
| 144 | return 0;
|
---|
| 145 | }
|
---|
| 146 |
|
---|
| 147 |
|
---|
| 148 | /**
|
---|
[48959] | 149 | * Closes the makefile list.
|
---|
| 150 | *
|
---|
[41630] | 151 | * @returns Exit code derived from @a rc.
|
---|
| 152 | * @param pFile The file stream of the makefile list.
|
---|
| 153 | * @param rc The current exit code.
|
---|
| 154 | */
|
---|
| 155 | static int closeMakefileList(FILE *pFile, int rc)
|
---|
| 156 | {
|
---|
| 157 | fprintf(pFile, "\n\n");
|
---|
| 158 | if (fclose(pFile))
|
---|
| 159 | return printErr("Error closing the file list file: %s\n", strerror(errno));
|
---|
| 160 | return rc;
|
---|
| 161 | }
|
---|
| 162 |
|
---|
| 163 |
|
---|
| 164 | /**
|
---|
[48959] | 165 | * Reads in a file.
|
---|
| 166 | *
|
---|
[41624] | 167 | * @returns Exit code.
|
---|
| 168 | * @param pcszFile The path to the file.
|
---|
| 169 | * @param ppszFile Where to return the buffer.
|
---|
| 170 | * @param pcchFile Where to return the file size.
|
---|
| 171 | */
|
---|
| 172 | static int readFile(const char *pcszFile, char **ppszFile, size_t *pcchFile)
|
---|
| 173 | {
|
---|
| 174 | FILE *pFile;
|
---|
| 175 | struct stat FileStat;
|
---|
| 176 | int rc;
|
---|
| 177 |
|
---|
| 178 | if (stat(pcszFile, &FileStat))
|
---|
| 179 | return printErr("Failed to stat \"%s\": %s\n", pcszFile, strerror(errno));
|
---|
| 180 |
|
---|
| 181 | pFile = fopen(pcszFile, "r");
|
---|
| 182 | if (!pFile)
|
---|
| 183 | return printErr("Failed to open \"%s\": %s\n", pcszFile, strerror(errno));
|
---|
| 184 |
|
---|
| 185 | *ppszFile = (char *)malloc(FileStat.st_size + 1);
|
---|
| 186 | if (*ppszFile)
|
---|
[29822] | 187 | {
|
---|
[41624] | 188 | errno = 0;
|
---|
| 189 | size_t cbRead = fread(*ppszFile, 1, FileStat.st_size, pFile);
|
---|
| 190 | if ( cbRead <= (size_t)FileStat.st_size
|
---|
| 191 | && (cbRead > 0 || !ferror(pFile)) )
|
---|
[23269] | 192 | {
|
---|
[41624] | 193 | if (ftell(pFile) == FileStat.st_size) /* (\r\n vs \n in the DOS world) */
|
---|
| 194 | {
|
---|
| 195 | (*ppszFile)[cbRead] = '\0';
|
---|
| 196 | if (pcchFile)
|
---|
| 197 | *pcchFile = (size_t)cbRead;
|
---|
[23269] | 198 |
|
---|
[41624] | 199 | fclose(pFile);
|
---|
| 200 | return 0;
|
---|
| 201 | }
|
---|
[23269] | 202 | }
|
---|
| 203 |
|
---|
[41624] | 204 | rc = printErr("Error reading \"%s\": %s\n", pcszFile, strerror(errno));
|
---|
| 205 | free(*ppszFile);
|
---|
| 206 | *ppszFile = NULL;
|
---|
| 207 | }
|
---|
| 208 | else
|
---|
| 209 | rc = printErr("Failed to allocate %lu bytes\n", (unsigned long)(FileStat.st_size + 1));
|
---|
| 210 | fclose(pFile);
|
---|
| 211 | return rc;
|
---|
| 212 | }
|
---|
[23269] | 213 |
|
---|
| 214 |
|
---|
[41624] | 215 | /**
|
---|
[48959] | 216 | * Checks whether the sub-file already exists and has the exact
|
---|
| 217 | * same content.
|
---|
| 218 | *
|
---|
[41624] | 219 | * @returns @c true if the existing file matches exactly, otherwise @c false.
|
---|
| 220 | * @param pcszFilename The path to the file.
|
---|
| 221 | * @param pcszSubContent The content to write.
|
---|
| 222 | * @param cchSubContent The length of the content.
|
---|
| 223 | */
|
---|
| 224 | static bool compareSubFile(const char *pcszFilename, const char *pcszSubContent, size_t cchSubContent)
|
---|
| 225 | {
|
---|
[48959] | 226 | struct stat FileStat;
|
---|
[41624] | 227 | if (stat(pcszFilename, &FileStat))
|
---|
| 228 | return false;
|
---|
| 229 | if ((size_t)FileStat.st_size < cchSubContent)
|
---|
| 230 | return false;
|
---|
[23269] | 231 |
|
---|
[41624] | 232 | size_t cchExisting;
|
---|
| 233 | char *pszExisting;
|
---|
| 234 | int rc = readFile(pcszFilename, &pszExisting, &cchExisting);
|
---|
| 235 | if (rc)
|
---|
| 236 | return false;
|
---|
[23269] | 237 |
|
---|
[41624] | 238 | bool fRc = cchExisting == cchSubContent
|
---|
| 239 | && !memcmp(pcszSubContent, pszExisting, cchSubContent);
|
---|
| 240 | free(pszExisting);
|
---|
[23269] | 241 |
|
---|
[41624] | 242 | return fRc;
|
---|
| 243 | }
|
---|
[23269] | 244 |
|
---|
| 245 |
|
---|
[41624] | 246 | /**
|
---|
[48959] | 247 | * Writes out a sub-file.
|
---|
| 248 | *
|
---|
[41624] | 249 | * @returns exit code.
|
---|
| 250 | * @param pcszFilename The path to the sub-file.
|
---|
| 251 | * @param pcszSubContent The content of the file.
|
---|
| 252 | * @param cchSubContent The size of the content.
|
---|
| 253 | */
|
---|
| 254 | static int writeSubFile(const char *pcszFilename, const char *pcszSubContent, size_t cchSubContent)
|
---|
| 255 | {
|
---|
| 256 | FILE *pFile = fopen(pcszFilename, "w");
|
---|
| 257 | if (!pFile)
|
---|
[65237] | 258 | #ifdef _MSC_VER
|
---|
| 259 | return printErr("Failed to open \"%s\" for writing: %s (win32: %d)\n", pcszFilename, strerror(errno), _doserrno);
|
---|
| 260 | #else
|
---|
[41624] | 261 | return printErr("Failed to open \"%s\" for writing: %s\n", pcszFilename, strerror(errno));
|
---|
[65237] | 262 | #endif
|
---|
[23269] | 263 |
|
---|
[41624] | 264 | errno = 0;
|
---|
| 265 | int rc = 0;
|
---|
| 266 | if (fwrite(pcszSubContent, cchSubContent, 1, pFile) != 1)
|
---|
| 267 | rc = printErr("Error writing \"%s\": %s\n", pcszFilename, strerror(errno));
|
---|
[23269] | 268 |
|
---|
[41624] | 269 | errno = 0;
|
---|
| 270 | int rc2 = fclose(pFile);
|
---|
| 271 | if (rc2 == EOF)
|
---|
| 272 | rc = printErr("Error closing \"%s\": %s\n", pcszFilename, strerror(errno));
|
---|
| 273 | return rc;
|
---|
| 274 | }
|
---|
[23269] | 275 |
|
---|
| 276 |
|
---|
[41624] | 277 | /**
|
---|
| 278 | * Does the actual file splitting.
|
---|
[48959] | 279 | *
|
---|
[41624] | 280 | * @returns exit code.
|
---|
| 281 | * @param pcszOutDir Path to the output directory.
|
---|
[48959] | 282 | * @param pcszContent The content to split up.
|
---|
[41630] | 283 | * @param pFileList The file stream of the makefile list. Can be NULL.
|
---|
[41624] | 284 | */
|
---|
[41630] | 285 | static int splitFile(const char *pcszOutDir, const char *pcszContent, FILE *pFileList)
|
---|
[41624] | 286 | {
|
---|
| 287 | static char const s_szBeginMarker[] = "\n// ##### BEGINFILE \"";
|
---|
| 288 | static char const s_szEndMarker[] = "\n// ##### ENDFILE";
|
---|
| 289 | const size_t cchBeginMarker = sizeof(s_szBeginMarker) - 1;
|
---|
| 290 | const char *pcszSearch = pcszContent;
|
---|
| 291 | size_t const cchOutDir = strlen(pcszOutDir);
|
---|
| 292 | unsigned long cFilesWritten = 0;
|
---|
| 293 | unsigned long cFilesUnchanged = 0;
|
---|
| 294 | int rc = 0;
|
---|
[23269] | 295 |
|
---|
[41624] | 296 | do
|
---|
| 297 | {
|
---|
| 298 | /* find begin marker */
|
---|
| 299 | const char *pcszBegin = strstr(pcszSearch, s_szBeginMarker);
|
---|
| 300 | if (!pcszBegin)
|
---|
| 301 | break;
|
---|
[23269] | 302 |
|
---|
[41624] | 303 | /* find line after begin marker */
|
---|
| 304 | const char *pcszLineAfterBegin = strchr(pcszBegin + cchBeginMarker, '\n');
|
---|
| 305 | if (!pcszLineAfterBegin)
|
---|
| 306 | return printErr("No newline after begin-file marker found.\n");
|
---|
| 307 | ++pcszLineAfterBegin;
|
---|
[23269] | 308 |
|
---|
[41624] | 309 | /* find filename end quote in begin marker line */
|
---|
| 310 | const char *pcszStartFilename = pcszBegin + cchBeginMarker;
|
---|
| 311 | const char *pcszEndQuote = (const char *)memchr(pcszStartFilename, '\"', pcszLineAfterBegin - pcszStartFilename);
|
---|
| 312 | if (!pcszEndQuote)
|
---|
[48959] | 313 | return printErr("Can't parse filename after begin-file marker (line %lu).\n",
|
---|
[41624] | 314 | lineNumber(pcszContent, s_szBeginMarker));
|
---|
[23269] | 315 |
|
---|
[41624] | 316 | /* find end marker */
|
---|
| 317 | const char *pcszEnd = strstr(pcszLineAfterBegin, s_szEndMarker);
|
---|
| 318 | if (!pcszEnd)
|
---|
[48959] | 319 | return printErr("No matching end-line marker for begin-file marker found (line %lu).\n",
|
---|
[41624] | 320 | lineNumber(pcszContent, s_szBeginMarker));
|
---|
[23269] | 321 |
|
---|
[41624] | 322 | /* construct output filename */
|
---|
| 323 | size_t cchFilename = pcszEndQuote - pcszStartFilename;
|
---|
| 324 | char *pszFilename = (char *)malloc(cchOutDir + 1 + cchFilename + 1);
|
---|
| 325 | if (!pszFilename)
|
---|
| 326 | return printErr("Can't allocate memory for filename.\n");
|
---|
[23269] | 327 |
|
---|
[41624] | 328 | memcpy(pszFilename, pcszOutDir, cchOutDir);
|
---|
| 329 | pszFilename[cchOutDir] = '/';
|
---|
| 330 | memcpy(pszFilename + cchOutDir + 1, pcszStartFilename, cchFilename);
|
---|
| 331 | pszFilename[cchFilename + 1 + cchOutDir] = '\0';
|
---|
| 332 |
|
---|
| 333 | /* Write the file only if necessary. */
|
---|
| 334 | if (compareSubFile(pszFilename, pcszLineAfterBegin, pcszEnd - pcszLineAfterBegin))
|
---|
| 335 | cFilesUnchanged++;
|
---|
| 336 | else
|
---|
| 337 | {
|
---|
| 338 | rc = writeSubFile(pszFilename, pcszLineAfterBegin, pcszEnd - pcszLineAfterBegin);
|
---|
| 339 | cFilesWritten++;
|
---|
| 340 | }
|
---|
| 341 |
|
---|
[41630] | 342 | if (!rc)
|
---|
| 343 | rc = addFileToMakefileList(pFileList, pszFilename);
|
---|
| 344 |
|
---|
[41624] | 345 | free(pszFilename);
|
---|
| 346 |
|
---|
| 347 | pcszSearch = pcszEnd;
|
---|
[103385] | 348 | } while (rc == 0);
|
---|
[41624] | 349 |
|
---|
[48959] | 350 | printf("filesplitter: Out of %lu files: %lu rewritten, %lu unchanged. (%s)\n",
|
---|
[41624] | 351 | cFilesWritten + cFilesUnchanged, cFilesWritten, cFilesUnchanged, pcszOutDir);
|
---|
[23269] | 352 | return rc;
|
---|
| 353 | }
|
---|
[41624] | 354 |
|
---|
| 355 |
|
---|
| 356 | int main(int argc, char *argv[])
|
---|
| 357 | {
|
---|
| 358 | int rc = 0;
|
---|
| 359 |
|
---|
[41630] | 360 | if (argc == 3 || argc == 5)
|
---|
[41624] | 361 | {
|
---|
| 362 | struct stat DirStat;
|
---|
[41630] | 363 | if ( stat(argv[2], &DirStat) == 0
|
---|
[41624] | 364 | && S_ISDIR(DirStat.st_mode))
|
---|
| 365 | {
|
---|
| 366 | char *pszContent;
|
---|
| 367 | rc = readFile(argv[1], &pszContent, NULL);
|
---|
| 368 | if (!rc)
|
---|
| 369 | {
|
---|
[41630] | 370 | FILE *pFileList = NULL;
|
---|
| 371 | if (argc == 5)
|
---|
| 372 | rc = openMakefileList(argv[3], argv[4], &pFileList);
|
---|
| 373 |
|
---|
| 374 | if (argc < 4 || pFileList)
|
---|
| 375 | rc = splitFile(argv[2], pszContent, pFileList);
|
---|
| 376 |
|
---|
| 377 | if (pFileList)
|
---|
| 378 | rc = closeMakefileList(pFileList, rc);
|
---|
[41624] | 379 | free(pszContent);
|
---|
| 380 | }
|
---|
| 381 | }
|
---|
| 382 | else
|
---|
| 383 | rc = printErr("Given argument \"%s\" is not a valid directory.\n", argv[2]);
|
---|
| 384 | }
|
---|
| 385 | else
|
---|
[41630] | 386 | rc = printErr("Syntax error: usage: filesplitter <infile> <outdir> [<list.kmk> <kmkvar>]\n");
|
---|
[41624] | 387 | return rc;
|
---|
| 388 | }
|
---|