diff options
Diffstat (limited to 'src/bldprogs/filesplitter.cpp')
-rw-r--r-- | src/bldprogs/filesplitter.cpp | 388 |
1 files changed, 388 insertions, 0 deletions
diff --git a/src/bldprogs/filesplitter.cpp b/src/bldprogs/filesplitter.cpp new file mode 100644 index 00000000..90c7dbe7 --- /dev/null +++ b/src/bldprogs/filesplitter.cpp @@ -0,0 +1,388 @@ +/* $Id: filesplitter.cpp $ */ +/** @file + * File splitter - Splits a text file according to ###### markers in it. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <sys/types.h> +#include <sys/stat.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#include <iprt/string.h> +#include <iprt/stdarg.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#ifndef S_ISDIR +# define S_ISDIR(a_fMode) ( (S_IFMT & (a_fMode)) == S_IFDIR ) +#endif + + +/** + * Calculates the line number for a file position. + * + * @returns Line number. + * @param pcszContent The file content. + * @param pcszPos The current position. + */ +static unsigned long lineNumber(const char *pcszContent, const char *pcszPos) +{ + unsigned long cLine = 0; + while ( *pcszContent + && (uintptr_t)pcszContent < (uintptr_t)pcszPos) + { + pcszContent = strchr(pcszContent, '\n'); + if (!pcszContent) + break; + ++cLine; + ++pcszContent; + } + + return cLine; +} + + +/** + * Writes an error message. + * + * @returns RTEXITCODE_FAILURE. + * @param pcszFormat Error message. + * @param ... Format argument referenced in the message. + */ +static int printErr(const char *pcszFormat, ...) +{ + va_list va; + + fprintf(stderr, "filesplitter: "); + va_start(va, pcszFormat); + vfprintf(stderr, pcszFormat, va); + va_end(va); + + return RTEXITCODE_FAILURE; +} + + +/** + * Opens the makefile list for writing. + * + * @returns Exit code. + * @param pcszPath The path to the file. + * @param pcszVariableName The make variable name. + * @param ppFile Where to return the file stream. + */ +static int openMakefileList(const char *pcszPath, const char *pcszVariableName, FILE **ppFile) +{ + *ppFile = NULL; + + FILE *pFile= fopen(pcszPath, "w"); + if (!pFile) +#ifdef _MSC_VER + return printErr("Failed to open \"%s\" for writing the file list: %s (win32: %d)\n", + pcszPath, strerror(errno), _doserrno); +#else + return printErr("Failed to open \"%s\" for writing the file list: %s\n", pcszPath, strerror(errno)); +#endif + + if (fprintf(pFile, "%s := \\\n", pcszVariableName) <= 0) + { + fclose(pFile); + return printErr("Error writing to the makefile list.\n"); + } + + *ppFile = pFile; + return 0; +} + + +/** + * Adds the given file to the makefile list. + * + * @returns Exit code. + * @param pFile The file stream of the makefile list. + * @param pszFilename The file name to add. + */ +static int addFileToMakefileList(FILE *pFile, char *pszFilename) +{ + if (pFile) + { + char *pszSlash = pszFilename; + while ((pszSlash = strchr(pszSlash, '\\')) != NULL) + *pszSlash++ = '/'; + + if (fprintf(pFile, "\t%s \\\n", pszFilename) <= 0) + return printErr("Error adding file to makefile list.\n"); + } + return 0; +} + + +/** + * Closes the makefile list. + * + * @returns Exit code derived from @a rc. + * @param pFile The file stream of the makefile list. + * @param rc The current exit code. + */ +static int closeMakefileList(FILE *pFile, int rc) +{ + fprintf(pFile, "\n\n"); + if (fclose(pFile)) + return printErr("Error closing the file list file: %s\n", strerror(errno)); + return rc; +} + + +/** + * Reads in a file. + * + * @returns Exit code. + * @param pcszFile The path to the file. + * @param ppszFile Where to return the buffer. + * @param pcchFile Where to return the file size. + */ +static int readFile(const char *pcszFile, char **ppszFile, size_t *pcchFile) +{ + FILE *pFile; + struct stat FileStat; + int rc; + + if (stat(pcszFile, &FileStat)) + return printErr("Failed to stat \"%s\": %s\n", pcszFile, strerror(errno)); + + pFile = fopen(pcszFile, "r"); + if (!pFile) + return printErr("Failed to open \"%s\": %s\n", pcszFile, strerror(errno)); + + *ppszFile = (char *)malloc(FileStat.st_size + 1); + if (*ppszFile) + { + errno = 0; + size_t cbRead = fread(*ppszFile, 1, FileStat.st_size, pFile); + if ( cbRead <= (size_t)FileStat.st_size + && (cbRead > 0 || !ferror(pFile)) ) + { + if (ftell(pFile) == FileStat.st_size) /* (\r\n vs \n in the DOS world) */ + { + (*ppszFile)[cbRead] = '\0'; + if (pcchFile) + *pcchFile = (size_t)cbRead; + + fclose(pFile); + return 0; + } + } + + rc = printErr("Error reading \"%s\": %s\n", pcszFile, strerror(errno)); + free(*ppszFile); + *ppszFile = NULL; + } + else + rc = printErr("Failed to allocate %lu bytes\n", (unsigned long)(FileStat.st_size + 1)); + fclose(pFile); + return rc; +} + + +/** + * Checks whether the sub-file already exists and has the exact + * same content. + * + * @returns @c true if the existing file matches exactly, otherwise @c false. + * @param pcszFilename The path to the file. + * @param pcszSubContent The content to write. + * @param cchSubContent The length of the content. + */ +static bool compareSubFile(const char *pcszFilename, const char *pcszSubContent, size_t cchSubContent) +{ + struct stat FileStat; + if (stat(pcszFilename, &FileStat)) + return false; + if ((size_t)FileStat.st_size < cchSubContent) + return false; + + size_t cchExisting; + char *pszExisting; + int rc = readFile(pcszFilename, &pszExisting, &cchExisting); + if (rc) + return false; + + bool fRc = cchExisting == cchSubContent + && !memcmp(pcszSubContent, pszExisting, cchSubContent); + free(pszExisting); + + return fRc; +} + + +/** + * Writes out a sub-file. + * + * @returns exit code. + * @param pcszFilename The path to the sub-file. + * @param pcszSubContent The content of the file. + * @param cchSubContent The size of the content. + */ +static int writeSubFile(const char *pcszFilename, const char *pcszSubContent, size_t cchSubContent) +{ + FILE *pFile = fopen(pcszFilename, "w"); + if (!pFile) +#ifdef _MSC_VER + return printErr("Failed to open \"%s\" for writing: %s (win32: %d)\n", pcszFilename, strerror(errno), _doserrno); +#else + return printErr("Failed to open \"%s\" for writing: %s\n", pcszFilename, strerror(errno)); +#endif + + errno = 0; + int rc = 0; + if (fwrite(pcszSubContent, cchSubContent, 1, pFile) != 1) + rc = printErr("Error writing \"%s\": %s\n", pcszFilename, strerror(errno)); + + errno = 0; + int rc2 = fclose(pFile); + if (rc2 == EOF) + rc = printErr("Error closing \"%s\": %s\n", pcszFilename, strerror(errno)); + return rc; +} + + +/** + * Does the actual file splitting. + * + * @returns exit code. + * @param pcszOutDir Path to the output directory. + * @param pcszContent The content to split up. + * @param pFileList The file stream of the makefile list. Can be NULL. + */ +static int splitFile(const char *pcszOutDir, const char *pcszContent, FILE *pFileList) +{ + static char const s_szBeginMarker[] = "\n// ##### BEGINFILE \""; + static char const s_szEndMarker[] = "\n// ##### ENDFILE"; + const size_t cchBeginMarker = sizeof(s_szBeginMarker) - 1; + const char *pcszSearch = pcszContent; + size_t const cchOutDir = strlen(pcszOutDir); + unsigned long cFilesWritten = 0; + unsigned long cFilesUnchanged = 0; + int rc = 0; + + do + { + /* find begin marker */ + const char *pcszBegin = strstr(pcszSearch, s_szBeginMarker); + if (!pcszBegin) + break; + + /* find line after begin marker */ + const char *pcszLineAfterBegin = strchr(pcszBegin + cchBeginMarker, '\n'); + if (!pcszLineAfterBegin) + return printErr("No newline after begin-file marker found.\n"); + ++pcszLineAfterBegin; + + /* find filename end quote in begin marker line */ + const char *pcszStartFilename = pcszBegin + cchBeginMarker; + const char *pcszEndQuote = (const char *)memchr(pcszStartFilename, '\"', pcszLineAfterBegin - pcszStartFilename); + if (!pcszEndQuote) + return printErr("Can't parse filename after begin-file marker (line %lu).\n", + lineNumber(pcszContent, s_szBeginMarker)); + + /* find end marker */ + const char *pcszEnd = strstr(pcszLineAfterBegin, s_szEndMarker); + if (!pcszEnd) + return printErr("No matching end-line marker for begin-file marker found (line %lu).\n", + lineNumber(pcszContent, s_szBeginMarker)); + + /* construct output filename */ + size_t cchFilename = pcszEndQuote - pcszStartFilename; + char *pszFilename = (char *)malloc(cchOutDir + 1 + cchFilename + 1); + if (!pszFilename) + return printErr("Can't allocate memory for filename.\n"); + + memcpy(pszFilename, pcszOutDir, cchOutDir); + pszFilename[cchOutDir] = '/'; + memcpy(pszFilename + cchOutDir + 1, pcszStartFilename, cchFilename); + pszFilename[cchFilename + 1 + cchOutDir] = '\0'; + + /* Write the file only if necessary. */ + if (compareSubFile(pszFilename, pcszLineAfterBegin, pcszEnd - pcszLineAfterBegin)) + cFilesUnchanged++; + else + { + rc = writeSubFile(pszFilename, pcszLineAfterBegin, pcszEnd - pcszLineAfterBegin); + cFilesWritten++; + } + + if (!rc) + rc = addFileToMakefileList(pFileList, pszFilename); + + free(pszFilename); + + pcszSearch = pcszEnd; + } while (rc == 0 && pcszSearch); + + printf("filesplitter: Out of %lu files: %lu rewritten, %lu unchanged. (%s)\n", + cFilesWritten + cFilesUnchanged, cFilesWritten, cFilesUnchanged, pcszOutDir); + return rc; +} + + +int main(int argc, char *argv[]) +{ + int rc = 0; + + if (argc == 3 || argc == 5) + { + struct stat DirStat; + if ( stat(argv[2], &DirStat) == 0 + && S_ISDIR(DirStat.st_mode)) + { + char *pszContent; + rc = readFile(argv[1], &pszContent, NULL); + if (!rc) + { + FILE *pFileList = NULL; + if (argc == 5) + rc = openMakefileList(argv[3], argv[4], &pFileList); + + if (argc < 4 || pFileList) + rc = splitFile(argv[2], pszContent, pFileList); + + if (pFileList) + rc = closeMakefileList(pFileList, rc); + free(pszContent); + } + } + else + rc = printErr("Given argument \"%s\" is not a valid directory.\n", argv[2]); + } + else + rc = printErr("Syntax error: usage: filesplitter <infile> <outdir> [<list.kmk> <kmkvar>]\n"); + return rc; +} |