diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
commit | 16f504a9dca3fe3b70568f67b7d41241ae485288 (patch) | |
tree | c60f36ada0496ba928b7161059ba5ab1ab224f9d /src/bldprogs/scmstream.cpp | |
parent | Initial commit. (diff) | |
download | virtualbox-16f504a9dca3fe3b70568f67b7d41241ae485288.tar.xz virtualbox-16f504a9dca3fe3b70568f67b7d41241ae485288.zip |
Adding upstream version 7.0.6-dfsg.upstream/7.0.6-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/bldprogs/scmstream.cpp')
-rw-r--r-- | src/bldprogs/scmstream.cpp | 1447 |
1 files changed, 1447 insertions, 0 deletions
diff --git a/src/bldprogs/scmstream.cpp b/src/bldprogs/scmstream.cpp new file mode 100644 index 00000000..8fa37da5 --- /dev/null +++ b/src/bldprogs/scmstream.cpp @@ -0,0 +1,1447 @@ +/* $Id: scmstream.cpp $ */ +/** @file + * IPRT Testcase / Tool - Source Code Massager Stream Code. + */ + +/* + * Copyright (C) 2010-2022 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 <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/handle.h> +#include <iprt/mem.h> +#include <iprt/pipe.h> +#include <iprt/string.h> + +#include "scmstream.h" + + +/** + * Initializes the stream structure. + * + * @param pStream The stream structure. + * @param fWriteOrRead The value of the fWriteOrRead stream member. + */ +static void scmStreamInitInternal(PSCMSTREAM pStream, bool fWriteOrRead) +{ + pStream->pch = NULL; + pStream->off = 0; + pStream->cb = 0; + pStream->cbAllocated = 0; + + pStream->paLines = NULL; + pStream->iLine = 0; + pStream->cLines = 0; + pStream->cLinesAllocated = 0; + + pStream->fWriteOrRead = fWriteOrRead; + pStream->fFileMemory = false; + pStream->fFullyLineated = false; + + pStream->rc = VINF_SUCCESS; +} + +/** + * Initialize an input stream. + * + * @returns IPRT status code. + * @param pStream The stream to initialize. + * @param pszFilename The file to take the stream content from. + */ +int ScmStreamInitForReading(PSCMSTREAM pStream, const char *pszFilename) +{ + scmStreamInitInternal(pStream, false /*fWriteOrRead*/); + + void *pvFile; + size_t cbFile; + int rc = pStream->rc = RTFileReadAll(pszFilename, &pvFile, &cbFile); + if (RT_SUCCESS(rc)) + { + pStream->pch = (char *)pvFile; + pStream->cb = cbFile; + pStream->cbAllocated = cbFile; + pStream->fFileMemory = true; + } + return rc; +} + +/** + * Initialize an output stream. + * + * @returns IPRT status code + * @param pStream The stream to initialize. + * @param pRelatedStream Pointer to a related stream. NULL is fine. + */ +int ScmStreamInitForWriting(PSCMSTREAM pStream, PCSCMSTREAM pRelatedStream) +{ + scmStreamInitInternal(pStream, true /*fWriteOrRead*/); + + /* allocate stuff */ + size_t cbEstimate = !pRelatedStream ? _64K + : pRelatedStream->cb > 0 ? pRelatedStream->cb + pRelatedStream->cb / 10 : 64; + cbEstimate = RT_ALIGN(cbEstimate, _4K); + pStream->pch = (char *)RTMemAlloc(cbEstimate); + if (pStream->pch) + { + size_t cLinesEstimate = pRelatedStream && pRelatedStream->fFullyLineated + ? pRelatedStream->cLines + pRelatedStream->cLines / 10 + : cbEstimate / 24; + cLinesEstimate = RT_ALIGN(cLinesEstimate, 512); + if (cLinesEstimate == 0) + cLinesEstimate = 16; + pStream->paLines = (PSCMSTREAMLINE)RTMemAlloc(cLinesEstimate * sizeof(SCMSTREAMLINE)); + if (pStream->paLines) + { + pStream->paLines[0].off = 0; + pStream->paLines[0].cch = 0; + pStream->paLines[0].enmEol = SCMEOL_NONE; + pStream->cbAllocated = cbEstimate; + pStream->cLinesAllocated = cLinesEstimate; + return VINF_SUCCESS; + } + + RTMemFree(pStream->pch); + pStream->pch = NULL; + } + return pStream->rc = VERR_NO_MEMORY; +} + +/** + * Frees the resources associated with the stream. + * + * Nothing is happens to whatever the stream was initialized from or dumped to. + * + * @param pStream The stream to delete. + */ +void ScmStreamDelete(PSCMSTREAM pStream) +{ + if (pStream->pch) + { + if (pStream->fFileMemory) + RTFileReadAllFree(pStream->pch, pStream->cbAllocated); + else + RTMemFree(pStream->pch); + pStream->pch = NULL; + } + pStream->cbAllocated = 0; + + if (pStream->paLines) + { + RTMemFree(pStream->paLines); + pStream->paLines = NULL; + } + pStream->cLinesAllocated = 0; +} + +/** + * Get the stream status code. + * + * @returns IPRT status code. + * @param pStream The stream. + */ +int ScmStreamGetStatus(PCSCMSTREAM pStream) +{ + return pStream->rc; +} + +/** + * Grows the buffer of a write stream. + * + * @returns IPRT status code. + * @param pStream The stream. Must be in write mode. + * @param cbAppending The minimum number of bytes to grow the buffer + * with. + */ +static int scmStreamGrowBuffer(PSCMSTREAM pStream, size_t cbAppending) +{ + size_t cbAllocated = pStream->cbAllocated; + cbAllocated += RT_MAX(0x1000 + cbAppending, cbAllocated); + cbAllocated = RT_ALIGN(cbAllocated, 0x1000); + void *pvNew; + if (!pStream->fFileMemory) + { + pvNew = RTMemRealloc(pStream->pch, cbAllocated); + if (!pvNew) + return pStream->rc = VERR_NO_MEMORY; + } + else + { + pvNew = RTMemDupEx(pStream->pch, pStream->off, cbAllocated - pStream->off); + if (!pvNew) + return pStream->rc = VERR_NO_MEMORY; + RTFileReadAllFree(pStream->pch, pStream->cbAllocated); + pStream->fFileMemory = false; + } + pStream->pch = (char *)pvNew; + pStream->cbAllocated = cbAllocated; + + return VINF_SUCCESS; +} + +/** + * Grows the line array of a stream. + * + * @returns IPRT status code. + * @param pStream The stream. + * @param iMinLine Minimum line number. + */ +static int scmStreamGrowLines(PSCMSTREAM pStream, size_t iMinLine) +{ + size_t cLinesAllocated = pStream->cLinesAllocated; + cLinesAllocated += RT_MAX(512 + iMinLine, cLinesAllocated); + cLinesAllocated = RT_ALIGN(cLinesAllocated, 512); + void *pvNew = RTMemRealloc(pStream->paLines, cLinesAllocated * sizeof(SCMSTREAMLINE)); + if (!pvNew) + return pStream->rc = VERR_NO_MEMORY; + + pStream->paLines = (PSCMSTREAMLINE)pvNew; + pStream->cLinesAllocated = cLinesAllocated; + return VINF_SUCCESS; +} + +/** + * Rewinds the stream and sets the mode to read. + * + * @param pStream The stream. + */ +void ScmStreamRewindForReading(PSCMSTREAM pStream) +{ + pStream->off = 0; + pStream->iLine = 0; + pStream->fWriteOrRead = false; + pStream->rc = VINF_SUCCESS; +} + +/** + * Rewinds the stream and sets the mode to write. + * + * @param pStream The stream. + */ +void ScmStreamRewindForWriting(PSCMSTREAM pStream) +{ + pStream->off = 0; + pStream->iLine = 0; + pStream->cLines = 0; + pStream->fWriteOrRead = true; + pStream->fFullyLineated = true; + pStream->rc = VINF_SUCCESS; + + /* Initialize the first line with a zero length so ScmStreamWrite won't misbehave. */ + if (pStream->cLinesAllocated == 0) + scmStreamGrowLines(pStream, 1); + if (pStream->cLinesAllocated > 0) + { + pStream->paLines[0].off = 0; + pStream->paLines[0].cch = 0; + pStream->paLines[0].enmEol = SCMEOL_NONE; + } +} + +/** + * Checks if it's a text stream. + * + * Not 100% proof. + * + * @returns true if it probably is a text file, false if not. + * @param pStream The stream. Write or read, doesn't matter. + */ +bool ScmStreamIsText(PSCMSTREAM pStream) +{ + if (RTStrEnd(pStream->pch, pStream->cb)) + return false; + if (!pStream->cb) + return true; + return true; +} + +/** + * Performs an integrity check of the stream. + * + * @returns IPRT status code. + * @param pStream The stream. + */ +int ScmStreamCheckItegrity(PSCMSTREAM pStream) +{ + /* + * Perform sanity checks. + */ + size_t const cbFile = pStream->cb; + for (size_t iLine = 0; iLine < pStream->cLines; iLine++) + { + size_t offEol = pStream->paLines[iLine].off + pStream->paLines[iLine].cch; + AssertReturn(offEol + pStream->paLines[iLine].enmEol <= cbFile, VERR_INTERNAL_ERROR_2); + switch (pStream->paLines[iLine].enmEol) + { + case SCMEOL_LF: + AssertReturn(pStream->pch[offEol] == '\n', VERR_INTERNAL_ERROR_3); + break; + case SCMEOL_CRLF: + AssertReturn(pStream->pch[offEol] == '\r', VERR_INTERNAL_ERROR_3); + AssertReturn(pStream->pch[offEol + 1] == '\n', VERR_INTERNAL_ERROR_3); + break; + case SCMEOL_NONE: + AssertReturn(iLine + 1 >= pStream->cLines, VERR_INTERNAL_ERROR_4); + break; + default: + AssertReturn(iLine + 1 >= pStream->cLines, VERR_INTERNAL_ERROR_5); + } + } + return VINF_SUCCESS; +} + +/** + * Writes the stream to a file. + * + * @returns IPRT status code + * @param pStream The stream. + * @param pszFilenameFmt The filename format string. + * @param ... Format arguments. + */ +int ScmStreamWriteToFile(PSCMSTREAM pStream, const char *pszFilenameFmt, ...) +{ + int rc; + +#ifdef RT_STRICT + /* + * Check that what we're going to write makes sense first. + */ + rc = ScmStreamCheckItegrity(pStream); + if (RT_FAILURE(rc)) + return rc; +#endif + + /* + * Do the actual writing. + */ + RTFILE hFile; + va_list va; + va_start(va, pszFilenameFmt); + rc = RTFileOpenV(&hFile, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_WRITE, pszFilenameFmt, va); + if (RT_SUCCESS(rc)) + { + rc = RTFileWrite(hFile, pStream->pch, pStream->cb, NULL); + RTFileClose(hFile); + } + va_end(va); + return rc; +} + +/** + * Writes the stream to standard output. + * + * @returns IPRT status code + * @param pStream The stream. + */ +int ScmStreamWriteToStdOut(PSCMSTREAM pStream) +{ + int rc; + +#ifdef RT_STRICT + /* + * Check that what we're going to write makes sense first. + */ + rc = ScmStreamCheckItegrity(pStream); + if (RT_FAILURE(rc)) + return rc; +#endif + + /* + * Do the actual writing. + */ + RTHANDLE h; + rc = RTHandleGetStandard(RTHANDLESTD_OUTPUT, true /*fLeaveOpen*/, &h); + if (RT_SUCCESS(rc)) + { + switch (h.enmType) + { + case RTHANDLETYPE_FILE: + rc = RTFileWrite(h.u.hFile, pStream->pch, pStream->cb, NULL); + /** @todo RTFileClose */ + break; + case RTHANDLETYPE_PIPE: + rc = RTPipeWriteBlocking(h.u.hPipe, pStream->pch, pStream->cb, NULL); + RTPipeClose(h.u.hPipe); + break; + default: + rc = VERR_INVALID_HANDLE; + break; + } + } + return rc; +} + +/** + * Worker for ScmStreamGetLine that builds the line number index while parsing + * the stream. + * + * @returns Same as SCMStreamGetLine. + * @param pStream The stream. Must be in read mode. + * @param pcchLine Where to return the line length. + * @param penmEol Where to return the kind of end of line marker. + */ +static const char *scmStreamGetLineInternal(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol) +{ + AssertReturn(!pStream->fWriteOrRead, NULL); + if (RT_FAILURE(pStream->rc)) + return NULL; + + size_t off = pStream->off; + size_t cb = pStream->cb; + if (RT_UNLIKELY(off >= cb)) + { + pStream->fFullyLineated = true; + return NULL; + } + + size_t iLine = pStream->iLine; + if (RT_UNLIKELY(iLine >= pStream->cLinesAllocated)) + { + int rc = scmStreamGrowLines(pStream, iLine); + if (RT_FAILURE(rc)) + return NULL; + } + pStream->paLines[iLine].off = off; + + cb -= off; + const char *pchRet = &pStream->pch[off]; + const char *pch = (const char *)memchr(pchRet, '\n', cb); + if (RT_LIKELY(pch)) + { + cb = pch - pchRet; + pStream->off = off + cb + 1; + if ( cb < 1 + || pch[-1] != '\r') + pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_LF; + else + { + pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_CRLF; + cb--; + } + } + else + { + pStream->off = off + cb; + pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_NONE; + } + *pcchLine = cb; + pStream->paLines[iLine].cch = cb; + pStream->cLines = pStream->iLine = ++iLine; + + return pchRet; +} + +/** + * Internal worker that delineates a stream. + * + * @returns IPRT status code. + * @param pStream The stream. Caller must check that it is in + * read mode. + */ +static int scmStreamLineate(PSCMSTREAM pStream) +{ + /* Save the stream position. */ + size_t const offSaved = pStream->off; + size_t const iLineSaved = pStream->iLine; + + /* Get each line. */ + size_t cchLine; + SCMEOL enmEol; + while (scmStreamGetLineInternal(pStream, &cchLine, &enmEol)) + /* nothing */; + Assert(RT_FAILURE(pStream->rc) || pStream->fFullyLineated); + + /* Restore the position */ + pStream->off = offSaved; + pStream->iLine = iLineSaved; + + return pStream->rc; +} + +/** + * Get the current stream position as an byte offset. + * + * @returns The current byte offset + * @param pStream The stream. + */ +size_t ScmStreamTell(PSCMSTREAM pStream) +{ + return pStream->off; +} + +/** + * Get the current stream position as a line number. + * + * @returns The current line (0-based). + * @param pStream The stream. + */ +size_t ScmStreamTellLine(PSCMSTREAM pStream) +{ + return pStream->iLine; +} + + +/** + * Gets the stream offset of a given line. + * + * @returns The offset of the line, or the stream size if the line number is too + * high. + * @param pStream The stream. Must be in read mode. + * @param iLine The line we're asking about. + */ +size_t ScmStreamTellOffsetOfLine(PSCMSTREAM pStream, size_t iLine) +{ + AssertReturn(!pStream->fWriteOrRead, pStream->cb); + if (!pStream->fFullyLineated) + { + int rc = scmStreamLineate(pStream); + AssertRCReturn(rc, pStream->cb); + } + if (iLine >= pStream->cLines) + return pStream->cb; + return pStream->paLines[iLine].off; +} + + +/** + * Get the current stream size in bytes. + * + * @returns Count of bytes. + * @param pStream The stream. + */ +size_t ScmStreamSize(PSCMSTREAM pStream) +{ + return pStream->cb; +} + +/** + * Gets the number of lines in the stream. + * + * @returns The number of lines. + * @param pStream The stream. + */ +size_t ScmStreamCountLines(PSCMSTREAM pStream) +{ + if (!pStream->fFullyLineated) + scmStreamLineate(pStream); + return pStream->cLines; +} + +/** + * Seeks to a given byte offset in the stream. + * + * @returns IPRT status code. + * @retval VERR_SEEK if the new stream position is the middle of an EOL marker. + * This is a temporary restriction. + * + * @param pStream The stream. Must be in read mode. + * @param offAbsolute The offset to seek to. If this is beyond the + * end of the stream, the position is set to the + * end. + */ +int ScmStreamSeekAbsolute(PSCMSTREAM pStream, size_t offAbsolute) +{ + AssertReturn(!pStream->fWriteOrRead, VERR_ACCESS_DENIED); + if (RT_FAILURE(pStream->rc)) + return pStream->rc; + + /* Must be fully delineated. (lazy bird) */ + if (RT_UNLIKELY(!pStream->fFullyLineated)) + { + int rc = scmStreamLineate(pStream); + if (RT_FAILURE(rc)) + return rc; + } + + /* Ok, do the job. */ + if (offAbsolute < pStream->cb) + { + /** @todo Should do a binary search here, but I'm too darn lazy tonight. */ + pStream->off = ~(size_t)0; + for (size_t i = 0; i < pStream->cLines; i++) + { + if (offAbsolute < pStream->paLines[i].off + pStream->paLines[i].cch + pStream->paLines[i].enmEol) + { + pStream->off = offAbsolute; + pStream->iLine = i; + if (offAbsolute > pStream->paLines[i].off + pStream->paLines[i].cch) + return pStream->rc = VERR_SEEK; + break; + } + } + AssertReturn(pStream->off != ~(size_t)0, pStream->rc = VERR_INTERNAL_ERROR_3); + } + else + { + pStream->off = pStream->cb; + pStream->iLine = pStream->cLines; + } + return VINF_SUCCESS; +} + + +/** + * Seeks a number of bytes relative to the current stream position. + * + * @returns IPRT status code. + * @retval VERR_SEEK if the new stream position is the middle of an EOL marker. + * This is a temporary restriction. + * + * @param pStream The stream. Must be in read mode. + * @param offRelative The offset to seek to. A negative offset + * rewinds and positive one fast forwards the + * stream. Will quietly stop at the beginning and + * end of the stream. + */ +int ScmStreamSeekRelative(PSCMSTREAM pStream, ssize_t offRelative) +{ + size_t offAbsolute; + if (offRelative >= 0) + offAbsolute = pStream->off + offRelative; + else if ((size_t)-offRelative <= pStream->off) + offAbsolute = pStream->off + offRelative; + else + offAbsolute = 0; + return ScmStreamSeekAbsolute(pStream, offAbsolute); +} + +/** + * Seeks to a given line in the stream. + * + * @returns IPRT status code. + * + * @param pStream The stream. Must be in read mode. + * @param iLine The line to seek to. If this is beyond the end + * of the stream, the position is set to the end. + */ +int ScmStreamSeekByLine(PSCMSTREAM pStream, size_t iLine) +{ + if (RT_FAILURE(pStream->rc)) + return pStream->rc; + + /* Must be fully delineated. (lazy bird) */ + if (RT_UNLIKELY(!pStream->fFullyLineated)) + { + AssertReturn(!pStream->fWriteOrRead, VERR_ACCESS_DENIED); + int rc = scmStreamLineate(pStream); + if (RT_FAILURE(rc)) + return rc; + } + + /* Ok, do the job. */ + if (iLine < pStream->cLines) + { + pStream->iLine = iLine; + pStream->off = pStream->paLines[iLine].off; + if (pStream->fWriteOrRead) + { + pStream->cb = pStream->paLines[iLine].off; + pStream->cLines = iLine; + pStream->paLines[iLine].cch = 0; + pStream->paLines[iLine].enmEol = SCMEOL_NONE; + } + } + else + { + AssertReturn(!pStream->fWriteOrRead, VERR_ACCESS_DENIED); + pStream->off = pStream->cb; + pStream->iLine = pStream->cLines; + } + return VINF_SUCCESS; +} + +/** + * Checks if the stream position is at the start of a line. + * + * @returns @c true if at the start, @c false if not. + * @param pStream The stream. + */ +bool ScmStreamIsAtStartOfLine(PSCMSTREAM pStream) +{ + if ( !pStream->fFullyLineated + && !pStream->fWriteOrRead) + { + int rc = scmStreamLineate(pStream); + if (RT_FAILURE(rc)) + return false; + } + return pStream->off == pStream->paLines[pStream->iLine].off; +} + +/** + * Worker for ScmStreamGetLineByNo and ScmStreamGetLine. + * + * Works on a fully lineated stream. + * + * @returns Pointer to the first character in the line, not NULL terminated. + * NULL if the end of the stream has been reached or some problem + * occurred. + * + * @param pStream The stream. Must be in read mode. + * @param iLine The line to get (0-based). + * @param pcchLine The length. + * @param penmEol Where to return the end of line type indicator. + */ +DECLINLINE(const char *) scmStreamGetLineByNoCommon(PSCMSTREAM pStream, size_t iLine, size_t *pcchLine, PSCMEOL penmEol) +{ + Assert(!pStream->fWriteOrRead); + Assert(pStream->fFullyLineated); + + /* Check stream status. */ + if (RT_SUCCESS(pStream->rc)) + { + /* Not at the end of the stream yet? */ + if (RT_LIKELY(iLine < pStream->cLines)) + { + /* Get the data. */ + const char *pchRet = &pStream->pch[pStream->paLines[iLine].off]; + *pcchLine = pStream->paLines[iLine].cch; + *penmEol = pStream->paLines[iLine].enmEol; + + /* update the stream position. */ + pStream->off = pStream->paLines[iLine].off + pStream->paLines[iLine].cch + pStream->paLines[iLine].enmEol; + pStream->iLine = iLine + 1; + return pchRet; + } + pStream->off = pStream->cb; + pStream->iLine = pStream->cLines; + } + *pcchLine = 0; + *penmEol = SCMEOL_NONE; + return NULL; +} + + +/** + * Get a numbered line from the stream (changes the position). + * + * A line is always delimited by a LF character or the end of the stream. The + * delimiter is not included in returned line length, but instead returned via + * the @a penmEol indicator. + * + * @returns Pointer to the first character in the line, not NULL terminated. + * NULL if the end of the stream has been reached or some problem + * occurred (*pcchLine set to zero and *penmEol to SCMEOL_NONE). + * + * @param pStream The stream. Must be in read mode. + * @param iLine The line to get (0-based). + * @param pcchLine The length. + * @param penmEol Where to return the end of line type indicator. + */ +const char *ScmStreamGetLineByNo(PSCMSTREAM pStream, size_t iLine, size_t *pcchLine, PSCMEOL penmEol) +{ + AssertReturn(!pStream->fWriteOrRead, NULL); + + /* Make sure it's fully delineated so we can use the index. */ + if (RT_LIKELY(pStream->fFullyLineated)) + return scmStreamGetLineByNoCommon(pStream, iLine, pcchLine, penmEol); + + int rc = pStream->rc; + if (RT_SUCCESS(rc)) + { + rc = scmStreamLineate(pStream); + if (RT_SUCCESS(rc)) + return scmStreamGetLineByNoCommon(pStream, iLine, pcchLine, penmEol); + } + + *pcchLine = 0; + *penmEol = SCMEOL_NONE; + return NULL; +} + +/** + * Get a line from the stream. + * + * A line is always delimited by a LF character or the end of the stream. The + * delimiter is not included in returned line length, but instead returned via + * the @a penmEol indicator. + * + * @returns Pointer to the first character in the line, not NULL terminated. + * NULL if the end of the stream has been reached or some problem + * occurred (*pcchLine set to zero and *penmEol to SCMEOL_NONE). + * + * @param pStream The stream. Must be in read mode. + * @param pcchLine The length. + * @param penmEol Where to return the end of line type indicator. + */ +const char *ScmStreamGetLine(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol) +{ + if (RT_LIKELY(pStream->fFullyLineated)) + { + size_t offCur = pStream->off; + size_t iCurLine = pStream->iLine; + const char *pszLine = scmStreamGetLineByNoCommon(pStream, iCurLine, pcchLine, penmEol); + if ( pszLine + && offCur > pStream->paLines[iCurLine].off) + { + offCur -= pStream->paLines[iCurLine].off; + Assert(offCur <= pStream->paLines[iCurLine].cch + pStream->paLines[iCurLine].enmEol); + if (offCur < pStream->paLines[iCurLine].cch) + *pcchLine -= offCur; + else + *pcchLine = 0; + pszLine += offCur; + } + return pszLine; + } + return scmStreamGetLineInternal(pStream, pcchLine, penmEol); +} + +/** + * Get the current buffer pointer. + * + * @returns Buffer pointer on success, NULL on failure (asserted). + * @param pStream The stream. Must be in read mode. + */ +const char *ScmStreamGetCur(PSCMSTREAM pStream) +{ + AssertReturn(!pStream->fWriteOrRead, NULL); + return pStream->pch + pStream->off; +} + +/** + * Gets a character from the stream. + * + * @returns The next unsigned character in the stream. + * ~(unsigned)0 on failure. + * @param pStream The stream. Must be in read mode. + */ +unsigned ScmStreamGetCh(PSCMSTREAM pStream) +{ + /* Check stream state. */ + AssertReturn(!pStream->fWriteOrRead, ~(unsigned)0); + if (RT_FAILURE(pStream->rc)) + return ~(unsigned)0; + if (RT_UNLIKELY(!pStream->fFullyLineated)) + { + int rc = scmStreamLineate(pStream); + if (RT_FAILURE(rc)) + return ~(unsigned)0; + } + + /* If there isn't enough stream left, fail already. */ + if (RT_UNLIKELY(pStream->off >= pStream->cb)) + return ~(unsigned)0; + + /* Read a character. */ + char ch = pStream->pch[pStream->off++]; + + /* Advance the line indicator. */ + size_t iLine = pStream->iLine; + if (pStream->off >= pStream->paLines[iLine].off + pStream->paLines[iLine].cch + pStream->paLines[iLine].enmEol) + pStream->iLine++; + + return (unsigned)ch; +} + + +/** + * Peeks at the next character from the stream. + * + * @returns The next unsigned character in the stream. + * ~(unsigned)0 on failure. + * @param pStream The stream. Must be in read mode. + */ +unsigned ScmStreamPeekCh(PSCMSTREAM pStream) +{ + /* Check stream state. */ + AssertReturn(!pStream->fWriteOrRead, ~(unsigned)0); + if (RT_FAILURE(pStream->rc)) + return ~(unsigned)0; + if (RT_UNLIKELY(!pStream->fFullyLineated)) + { + int rc = scmStreamLineate(pStream); + if (RT_FAILURE(rc)) + return ~(unsigned)0; + } + + /* If there isn't enough stream left, fail already. */ + if (RT_UNLIKELY(pStream->off >= pStream->cb)) + return ~(unsigned)0; + + /* Peek at the next character. */ + char ch = pStream->pch[pStream->off]; + return (unsigned)ch; +} + + +/** + * Reads @a cbToRead bytes into @a pvBuf. + * + * Will fail if end of stream is encountered before the entire read has been + * completed. + * + * @returns IPRT status code. + * @retval VERR_EOF if there isn't @a cbToRead bytes left to read. Stream + * position will be unchanged. + * + * @param pStream The stream. Must be in read mode. + * @param pvBuf The buffer to read into. + * @param cbToRead The number of bytes to read. + */ +int ScmStreamRead(PSCMSTREAM pStream, void *pvBuf, size_t cbToRead) +{ + AssertReturn(!pStream->fWriteOrRead, VERR_PERMISSION_DENIED); + if (RT_FAILURE(pStream->rc)) + return pStream->rc; + + /* If there isn't enough stream left, fail already. */ + if (RT_UNLIKELY(pStream->cb - pStream->off < cbToRead)) + return VERR_EOF; + + /* Copy the data and simply seek to the new stream position. */ + memcpy(pvBuf, &pStream->pch[pStream->off], cbToRead); + return ScmStreamSeekAbsolute(pStream, pStream->off + cbToRead); +} + + +/** + * Checks if the given line is empty or full of white space. + * + * @returns true if white space only, false if not (or if non-existant). + * @param pStream The stream. Must be in read mode. + * @param iLine The line in question. + */ +bool ScmStreamIsWhiteLine(PSCMSTREAM pStream, size_t iLine) +{ + SCMEOL enmEol; + size_t cchLine; + const char *pchLine = ScmStreamGetLineByNo(pStream, iLine, &cchLine, &enmEol); + if (!pchLine) + return false; + while (cchLine && RT_C_IS_SPACE(*pchLine)) + pchLine++, cchLine--; + return cchLine == 0; +} + + +/** + * Try figure out the end of line style of the give stream. + * + * @returns Most likely end of line style. + * @param pStream The stream. + */ +SCMEOL ScmStreamGetEol(PSCMSTREAM pStream) +{ + SCMEOL enmEol; + if (pStream->cLines > 0) + enmEol = pStream->paLines[0].enmEol; + else if (pStream->cb == 0) + enmEol = SCMEOL_NONE; + else + { + const char *pchLF = (const char *)memchr(pStream->pch, '\n', pStream->cb); + if (pchLF && pchLF != pStream->pch && pchLF[-1] == '\r') + enmEol = SCMEOL_CRLF; + else + enmEol = SCMEOL_LF; + } + + if (enmEol == SCMEOL_NONE) +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + enmEol = SCMEOL_CRLF; +#else + enmEol = SCMEOL_LF; +#endif + return enmEol; +} + + +/** + * Get the end of line indicator type for a line. + * + * @returns The EOL indicator. If the line isn't found, the default EOL + * indicator is return. + * @param pStream The stream. + * @param iLine The line (0-base). + */ +SCMEOL ScmStreamGetEolByLine(PSCMSTREAM pStream, size_t iLine) +{ + SCMEOL enmEol; + if (iLine < pStream->cLines) + enmEol = pStream->paLines[iLine].enmEol; + else +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + enmEol = SCMEOL_CRLF; +#else + enmEol = SCMEOL_LF; +#endif + return enmEol; +} + + +/** + * Appends a line to the stream. + * + * @returns IPRT status code. + * @param pStream The stream. Must be in write mode. + * @param pchLine Pointer to the line. + * @param cchLine Line length. + * @param enmEol Which end of line indicator to use. + */ +int ScmStreamPutLine(PSCMSTREAM pStream, const char *pchLine, size_t cchLine, SCMEOL enmEol) +{ + AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED); + if (RT_FAILURE(pStream->rc)) + return pStream->rc; + + /* + * Make sure the previous line has a new-line indicator. + */ + size_t off = pStream->off; + size_t iLine = pStream->iLine; + if (RT_UNLIKELY( iLine != 0 + && pStream->paLines[iLine - 1].enmEol == SCMEOL_NONE)) + { + AssertReturn(pStream->paLines[iLine].cch == 0, VERR_INTERNAL_ERROR_3); + SCMEOL enmEol2 = enmEol != SCMEOL_NONE ? enmEol : ScmStreamGetEol(pStream); + if (RT_UNLIKELY(off + cchLine + enmEol + enmEol2 > pStream->cbAllocated)) + { + int rc = scmStreamGrowBuffer(pStream, cchLine + enmEol + enmEol2); + if (RT_FAILURE(rc)) + return rc; + } + if (enmEol2 == SCMEOL_LF) + pStream->pch[off++] = '\n'; + else + { + pStream->pch[off++] = '\r'; + pStream->pch[off++] = '\n'; + } + pStream->paLines[iLine - 1].enmEol = enmEol2; + pStream->paLines[iLine].off = off; + pStream->off = off; + pStream->cb = off; + } + + /* + * Ensure we've got sufficient buffer space. + */ + if (RT_UNLIKELY(off + cchLine + enmEol > pStream->cbAllocated)) + { + int rc = scmStreamGrowBuffer(pStream, cchLine + enmEol); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Add a line record. + */ + if (RT_UNLIKELY(iLine + 1 >= pStream->cLinesAllocated)) + { + int rc = scmStreamGrowLines(pStream, iLine); + if (RT_FAILURE(rc)) + return rc; + } + + pStream->paLines[iLine].cch = off - pStream->paLines[iLine].off + cchLine; + pStream->paLines[iLine].enmEol = enmEol; + + iLine++; + pStream->cLines = iLine; + pStream->iLine = iLine; + + /* + * Copy the line + */ + memcpy(&pStream->pch[off], pchLine, cchLine); + off += cchLine; + if (enmEol == SCMEOL_LF) + pStream->pch[off++] = '\n'; + else if (enmEol == SCMEOL_CRLF) + { + pStream->pch[off++] = '\r'; + pStream->pch[off++] = '\n'; + } + pStream->off = off; + pStream->cb = off; + + /* + * Start a new line. + */ + pStream->paLines[iLine].off = off; + pStream->paLines[iLine].cch = 0; + pStream->paLines[iLine].enmEol = SCMEOL_NONE; + + return VINF_SUCCESS; +} + +/** + * Writes to the stream. + * + * @returns IPRT status code + * @param pStream The stream. Must be in write mode. + * @param pchBuf What to write. + * @param cchBuf How much to write. + */ +int ScmStreamWrite(PSCMSTREAM pStream, const char *pchBuf, size_t cchBuf) +{ + AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED); + if (RT_FAILURE(pStream->rc)) + return pStream->rc; + + /* + * Ensure we've got sufficient buffer space. + */ + size_t off = pStream->off; + if (RT_UNLIKELY(off + cchBuf > pStream->cbAllocated)) + { + int rc = scmStreamGrowBuffer(pStream, cchBuf); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Deal with the odd case where we've already pushed a line with SCMEOL_NONE. + */ + size_t iLine = pStream->iLine; + if (RT_UNLIKELY( iLine > 0 + && pStream->paLines[iLine - 1].enmEol == SCMEOL_NONE)) + { + iLine--; + pStream->cLines = iLine; + pStream->iLine = iLine; + } + + /* + * Deal with lines. + */ + const char *pchLF = (const char *)memchr(pchBuf, '\n', cchBuf); + if (!pchLF) + pStream->paLines[iLine].cch += cchBuf; + else + { + const char *pchLine = pchBuf; + for (;;) + { + if (RT_UNLIKELY(iLine + 1 >= pStream->cLinesAllocated)) + { + int rc = scmStreamGrowLines(pStream, iLine); + if (RT_FAILURE(rc)) + { + iLine = pStream->iLine; + pStream->paLines[iLine].cch = off - pStream->paLines[iLine].off; + pStream->paLines[iLine].enmEol = SCMEOL_NONE; + return rc; + } + } + + size_t cchLine = pchLF - pchLine; + if ( cchLine + ? pchLF[-1] != '\r' + : !pStream->paLines[iLine].cch + || pStream->pch[pStream->paLines[iLine].off + pStream->paLines[iLine].cch - 1] != '\r') + pStream->paLines[iLine].enmEol = SCMEOL_LF; + else + { + pStream->paLines[iLine].enmEol = SCMEOL_CRLF; + cchLine--; + } + pStream->paLines[iLine].cch += cchLine; + + iLine++; + size_t offBuf = pchLF + 1 - pchBuf; + pStream->paLines[iLine].off = off + offBuf; + pStream->paLines[iLine].cch = 0; + pStream->paLines[iLine].enmEol = SCMEOL_NONE; + + size_t cchLeft = cchBuf - offBuf; + pchLine = pchLF + 1; + pchLF = (const char *)memchr(pchLine, '\n', cchLeft); + if (!pchLF) + { + pStream->paLines[iLine].cch = cchLeft; + break; + } + } + + pStream->iLine = iLine; + pStream->cLines = iLine; + } + + /* + * Copy the data and update position and size. + */ + memcpy(&pStream->pch[off], pchBuf, cchBuf); + off += cchBuf; + pStream->off = off; + pStream->cb = off; + + return VINF_SUCCESS; +} + +/** + * Write a character to the stream. + * + * @returns IPRT status code + * @param pStream The stream. Must be in write mode. + * @param pchBuf What to write. + * @param cchBuf How much to write. + */ +int ScmStreamPutCh(PSCMSTREAM pStream, char ch) +{ + AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED); + if (RT_FAILURE(pStream->rc)) + return pStream->rc; + + /* + * Only deal with the simple cases here, use ScmStreamWrite for the + * annoying stuff. + */ + size_t off = pStream->off; + if ( ch == '\n' + || RT_UNLIKELY(off + 1 > pStream->cbAllocated)) + return ScmStreamWrite(pStream, &ch, 1); + + /* + * Just append it. + */ + pStream->pch[off] = ch; + pStream->off = off + 1; + pStream->paLines[pStream->iLine].cch++; + + return VINF_SUCCESS; +} + +/** + * Puts an EOL marker to the stream. + * + * @returns IPRt status code. + * @param pStream The stream. Must be in write mode. + * @param enmEol The end-of-line marker to write. + */ +int ScmStreamPutEol(PSCMSTREAM pStream, SCMEOL enmEol) +{ + if (enmEol == SCMEOL_LF) + return ScmStreamWrite(pStream, "\n", 1); + if (enmEol == SCMEOL_CRLF) + return ScmStreamWrite(pStream, "\r\n", 2); + if (enmEol == SCMEOL_NONE) + return VINF_SUCCESS; + AssertFailedReturn(VERR_INVALID_PARAMETER); +} + +/** + * Formats a string and writes it to the SCM stream. + * + * @returns The number of bytes written (>= 0). Negative value are IPRT error + * status codes. + * @param pStream The stream to write to. + * @param pszFormat The format string. + * @param va The arguments to format. + */ +ssize_t ScmStreamPrintfV(PSCMSTREAM pStream, const char *pszFormat, va_list va) +{ + char *psz; + ssize_t cch = RTStrAPrintfV(&psz, pszFormat, va); + if (cch) + { + int rc = ScmStreamWrite(pStream, psz, cch); + RTStrFree(psz); + if (RT_FAILURE(rc)) + cch = rc; + } + return cch; +} + +/** + * Formats a string and writes it to the SCM stream. + * + * @returns The number of bytes written (>= 0). Negative value are IPRT error + * status codes. + * @param pStream The stream to write to. + * @param pszFormat The format string. + * @param ... The arguments to format. + */ +ssize_t ScmStreamPrintf(PSCMSTREAM pStream, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + ssize_t cch = ScmStreamPrintfV(pStream, pszFormat, va); + va_end(va); + return cch; +} + +/** + * Copies @a cLines from the @a pSrc stream onto the @a pDst stream. + * + * The stream positions will be used and changed in both streams. + * + * @returns IPRT status code. + * @param pDst The destination stream. Must be in write mode. + * @param cLines The number of lines. (0 is accepted.) + * @param pSrc The source stream. Must be in read mode. + */ +int ScmStreamCopyLines(PSCMSTREAM pDst, PSCMSTREAM pSrc, size_t cLines) +{ + AssertReturn(pDst->fWriteOrRead, VERR_ACCESS_DENIED); + if (RT_FAILURE(pDst->rc)) + return pDst->rc; + + AssertReturn(!pSrc->fWriteOrRead, VERR_ACCESS_DENIED); + if (RT_FAILURE(pSrc->rc)) + return pSrc->rc; + + while (cLines-- > 0) + { + SCMEOL enmEol; + size_t cchLine; + const char *pchLine = ScmStreamGetLine(pSrc, &cchLine, &enmEol); + if (!pchLine) + return pDst->rc = RT_FAILURE(pSrc->rc) ? pSrc->rc : VERR_EOF; + + int rc = ScmStreamPutLine(pDst, pchLine, cchLine, enmEol); + if (RT_FAILURE(rc)) + return rc; + } + + return VINF_SUCCESS; +} + + +/** + * If the given C word is at off - 1, return @c true and skip beyond it, + * otherwise return @c false. + * + * @retval true if the given C-word is at the current position minus one char. + * The stream position changes. + * @retval false if not. The stream position is unchanged. + * + * @param pStream The stream. + * @param cchWord The length of the word. + * @param pszWord The word. + */ +bool ScmStreamCMatchingWordM1(PSCMSTREAM pStream, const char *pszWord, size_t cchWord) +{ + /* Check stream state. */ + AssertReturn(!pStream->fWriteOrRead, false); + AssertReturn(RT_SUCCESS(pStream->rc), false); + AssertReturn(pStream->fFullyLineated, false); + + /* Sufficient chars left on the line? */ + size_t const iLine = pStream->iLine; + AssertReturn(pStream->off > pStream->paLines[iLine].off, false); + size_t const cchLeft = pStream->paLines[iLine].cch + pStream->paLines[iLine].off - (pStream->off - 1); + if (cchWord > cchLeft) + return false; + + /* Do they match? */ + const char *psz = &pStream->pch[pStream->off - 1]; + if (memcmp(psz, pszWord, cchWord)) + return false; + + /* Is it the end of a C word? */ + if (cchWord < cchLeft) + { + psz += cchWord; + if (RT_C_IS_ALNUM(*psz) || *psz == '_') + return false; + } + + /* Skip ahead. */ + pStream->off += cchWord - 1; + return true; +} + + +/** + * Get's the C word starting at the current position. + * + * @returns Pointer to the word on success and the stream position advanced to + * the end of it. + * NULL on failure, stream position normally unchanged. + * @param pStream The stream to get the C word from. + * @param pcchWord Where to return the word length. + */ +const char *ScmStreamCGetWord(PSCMSTREAM pStream, size_t *pcchWord) +{ + /* Check stream state. */ + AssertReturn(!pStream->fWriteOrRead, NULL); + AssertReturn(RT_SUCCESS(pStream->rc), NULL); + AssertReturn(pStream->fFullyLineated, NULL); + + /* Get the number of chars left on the line and locate the current char. */ + size_t const iLine = pStream->iLine; + size_t const cchLeft = pStream->paLines[iLine].cch + pStream->paLines[iLine].off - pStream->off; + const char *psz = &pStream->pch[pStream->off]; + + /* Is it a leading C character. */ + if (!RT_C_IS_ALPHA(*psz) && *psz != '_') + return NULL; + + /* Find the end of the word. */ + char ch; + size_t off = 1; + while ( off < cchLeft + && ( (ch = psz[off]) == '_' + || RT_C_IS_ALNUM(ch))) + off++; + + pStream->off += off; + *pcchWord = off; + return psz; +} + + +/** + * Get's the C word starting at the current position minus one. + * + * @returns Pointer to the word on success and the stream position advanced to + * the end of it. + * NULL on failure, stream position normally unchanged. + * @param pStream The stream to get the C word from. + * @param pcchWord Where to return the word length. + */ +const char *ScmStreamCGetWordM1(PSCMSTREAM pStream, size_t *pcchWord) +{ + /* Check stream state. */ + AssertReturn(!pStream->fWriteOrRead, NULL); + AssertReturn(RT_SUCCESS(pStream->rc), NULL); + AssertReturn(pStream->fFullyLineated, NULL); + + /* Get the number of chars left on the line and locate the current char. */ + size_t const iLine = pStream->iLine; + size_t const cchLeft = pStream->paLines[iLine].cch + pStream->paLines[iLine].off - (pStream->off - 1); + const char *psz = &pStream->pch[pStream->off - 1]; + + /* Is it a leading C character. */ + if (!RT_C_IS_ALPHA(*psz) && *psz != '_') + return NULL; + + /* Find the end of the word. */ + char ch; + size_t off = 1; + while ( off < cchLeft + && ( (ch = psz[off]) == '_' + || RT_C_IS_ALNUM(ch))) + off++; + + pStream->off += off - 1; + *pcchWord = off; + return psz; +} + |