diff options
Diffstat (limited to 'src/VBox/Runtime/r3/stream.cpp')
-rw-r--r-- | src/VBox/Runtime/r3/stream.cpp | 2698 |
1 files changed, 2698 insertions, 0 deletions
diff --git a/src/VBox/Runtime/r3/stream.cpp b/src/VBox/Runtime/r3/stream.cpp new file mode 100644 index 00000000..ead09e53 --- /dev/null +++ b/src/VBox/Runtime/r3/stream.cpp @@ -0,0 +1,2698 @@ +/* $Id: stream.cpp $ */ +/** @file + * IPRT - I/O Stream. + */ + +/* + * 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>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @def RTSTREAM_STANDALONE + * Standalone streams w/o depending on stdio.h, using our RTFile API for + * file/whatever access. */ +#if (defined(IPRT_NO_CRT) && defined(RT_OS_WINDOWS)) || defined(DOXYGEN_RUNNING) +# define RTSTREAM_STANDALONE +#endif + +#if defined(RT_OS_LINUX) /* PORTME: check for the _unlocked functions in stdio.h */ +# ifndef RTSTREAM_STANDALONE +# define HAVE_FWRITE_UNLOCKED +# endif +#endif + +/** @def RTSTREAM_WITH_TEXT_MODE + * Indicates whether we need to support the 'text' mode files and convert + * CRLF to LF while reading and writing. */ +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) || defined(DOXYGEN_RUNNING) +# define RTSTREAM_WITH_TEXT_MODE +#endif + + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/stream.h> +#include "internal/iprt.h" + +#include <iprt/asm.h> +#ifndef HAVE_FWRITE_UNLOCKED +# include <iprt/critsect.h> +#endif +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +# include <iprt/file.h> +#ifdef RTSTREAM_STANDALONE +# include <iprt/list.h> +#endif +#include <iprt/mem.h> +#ifdef RTSTREAM_STANDALONE +# include <iprt/once.h> +#endif +#include <iprt/param.h> +#include <iprt/string.h> + +#include "internal/alignmentchecks.h" +#include "internal/magics.h" +#if defined(IPRT_NO_CRT) || defined(IN_RT_STATIC) +# include "internal/initterm.h" +#endif + +#ifdef RTSTREAM_STANDALONE +# ifdef _MSC_VER +# define IPRT_COMPILER_VCC_WITH_C_INIT_TERM_SECTIONS +# include "internal/compiler-vcc.h" +# endif +#else +# include <stdio.h> +# include <errno.h> +# if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) +# include <io.h> +# include <fcntl.h> +# endif +#endif +#ifdef RT_OS_WINDOWS +# include <iprt/utf16.h> +# include <iprt/win/windows.h> +#elif !defined(RTSTREAM_STANDALONE) +# include <termios.h> +# include <unistd.h> +# include <sys/ioctl.h> +#endif + +#if defined(RT_OS_OS2) && !defined(RTSTREAM_STANDALONE) +# define _O_TEXT O_TEXT +# define _O_BINARY O_BINARY +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +#ifdef RTSTREAM_STANDALONE +/** The buffer direction. */ +typedef enum RTSTREAMBUFDIR +{ + RTSTREAMBUFDIR_NONE = 0, + RTSTREAMBUFDIR_READ, + RTSTREAMBUFDIR_WRITE +} RTSTREAMBUFDIR; + +/** The buffer style. */ +typedef enum RTSTREAMBUFSTYLE +{ + RTSTREAMBUFSTYLE_UNBUFFERED = 0, + RTSTREAMBUFSTYLE_LINE, + RTSTREAMBUFSTYLE_FULL +} RTSTREAMBUFSTYLE; + +#endif + +/** + * File stream. + */ +typedef struct RTSTREAM +{ + /** Magic value used to validate the stream. (RTSTREAM_MAGIC) */ + uint32_t u32Magic; + /** File stream error. */ + int32_t volatile i32Error; +#ifndef RTSTREAM_STANDALONE + /** Pointer to the LIBC file stream. */ + FILE *pFile; +#else + /** Indicates which standard handle this is supposed to be. + * Set to RTHANDLESTD_INVALID if not one of the tree standard streams. */ + RTHANDLESTD enmStdHandle; + /** The IPRT handle backing this stream. + * This is initialized lazily using enmStdHandle for the three standard + * streams. */ + RTFILE hFile; + /** Buffer. */ + char *pchBuf; + /** Buffer allocation size. */ + size_t cbBufAlloc; + /** Offset of the first valid byte in the buffer. */ + size_t offBufFirst; + /** Offset of the end of valid bytes in the buffer (exclusive). */ + size_t offBufEnd; + /** The stream buffer direction. */ + RTSTREAMBUFDIR enmBufDir; + /** The buffering style (unbuffered, line, full). + * @todo replace by RTSTRMBUFMODE. */ + RTSTREAMBUFSTYLE enmBufStyle; +# ifdef RTSTREAM_WITH_TEXT_MODE + /** Bitmap running parallel to each char pchBuf, indicating where a '\\r' + * character have been removed during buffer filling. This is used to implement + * RTStrmTell in non-binary mode. */ + uint32_t *pbmBuf; + /** Indicates that we've got a CR ('\\r') beyond the end of official buffer + * and need to check if there is a LF following it. This member is ignored + * in binary mode. */ + bool fPendingCr; +# endif +#endif + /** Stream is using the current process code set. */ + bool fCurrentCodeSet; + /** Whether the stream was opened in binary mode. */ + bool fBinary; + /** Whether to recheck the stream mode before writing. */ + bool fRecheckMode; +#if !defined(HAVE_FWRITE_UNLOCKED) || defined(RTSTREAM_STANDALONE) + /** Critical section for serializing access to the stream. */ + PRTCRITSECT pCritSect; +#endif +#ifdef RTSTREAM_STANDALONE + /** Entry in g_StreamList (for automatic flushing and closing at + * exit/unload). */ + RTLISTNODE ListEntry; +#endif +} RTSTREAM; + + +/** + * State for wrapped output (RTStrmWrappedPrintf, RTStrmWrappedPrintfV). + */ +typedef struct RTSTRMWRAPPEDSTATE +{ + PRTSTREAM pStream; /**< The output stream. */ + uint32_t cchWidth; /**< The line width. */ + uint32_t cchLine; /**< The current line length (valid chars in szLine). */ + uint32_t cLines; /**< Number of lines written. */ + uint32_t cchIndent; /**< The indent (determined from the first line). */ + int rcStatus; /**< The output status. */ + uint8_t cchHangingIndent; /**< Hanging indent (from fFlags). */ + char szLine[0x1000+1]; /**< We must buffer output so we can do proper word splitting. */ +} RTSTRMWRAPPEDSTATE; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The standard input stream. */ +static RTSTREAM g_StdIn = +{ + /* .u32Magic = */ RTSTREAM_MAGIC, + /* .i32Error = */ 0, +#ifndef RTSTREAM_STANDALONE + /* .pFile = */ stdin, +#else + /* .enmStdHandle = */ RTHANDLESTD_INPUT, + /* .hFile = */ NIL_RTFILE, + /* .pchBuf = */ NULL, + /* .cbBufAlloc = */ 0, + /* .offBufFirst = */ 0, + /* .offBufEnd = */ 0, + /* .enmBufDir = */ RTSTREAMBUFDIR_NONE, + /* .enmBufStyle = */ RTSTREAMBUFSTYLE_UNBUFFERED, +# ifdef RTSTREAM_WITH_TEXT_MODE + /* .pbmBuf = */ NULL, + /* .fPendingCr = */ false, +# endif +#endif + /* .fCurrentCodeSet = */ true, + /* .fBinary = */ false, + /* .fRecheckMode = */ true, +#ifndef HAVE_FWRITE_UNLOCKED + /* .pCritSect = */ NULL, +#endif +#ifdef RTSTREAM_STANDALONE + /* .ListEntry = */ { NULL, NULL }, +#endif +}; + +/** The standard error stream. */ +static RTSTREAM g_StdErr = +{ + /* .u32Magic = */ RTSTREAM_MAGIC, + /* .i32Error = */ 0, +#ifndef RTSTREAM_STANDALONE + /* .pFile = */ stderr, +#else + /* .enmStdHandle = */ RTHANDLESTD_ERROR, + /* .hFile = */ NIL_RTFILE, + /* .pchBuf = */ NULL, + /* .cbBufAlloc = */ 0, + /* .offBufFirst = */ 0, + /* .offBufEnd = */ 0, + /* .enmBufDir = */ RTSTREAMBUFDIR_NONE, + /* .enmBufStyle = */ RTSTREAMBUFSTYLE_UNBUFFERED, +# ifdef RTSTREAM_WITH_TEXT_MODE + /* .pbmBuf = */ NULL, + /* .fPendingCr = */ false, +# endif +#endif + /* .fCurrentCodeSet = */ true, + /* .fBinary = */ false, + /* .fRecheckMode = */ true, +#ifndef HAVE_FWRITE_UNLOCKED + /* .pCritSect = */ NULL, +#endif +#ifdef RTSTREAM_STANDALONE + /* .ListEntry = */ { NULL, NULL }, +#endif +}; + +/** The standard output stream. */ +static RTSTREAM g_StdOut = +{ + /* .u32Magic = */ RTSTREAM_MAGIC, + /* .i32Error = */ 0, +#ifndef RTSTREAM_STANDALONE + /* .pFile = */ stdout, +#else + /* .enmStdHandle = */ RTHANDLESTD_OUTPUT, + /* .hFile = */ NIL_RTFILE, + /* .pchBuf = */ NULL, + /* .cbBufAlloc = */ 0, + /* .offBufFirst = */ 0, + /* .offBufEnd = */ 0, + /* .enmBufDir = */ RTSTREAMBUFDIR_NONE, + /* .enmBufStyle = */ RTSTREAMBUFSTYLE_LINE, +# ifdef RTSTREAM_WITH_TEXT_MODE + /* .pbmBuf = */ NULL, + /* .fPendingCr = */ false, +# endif +#endif + /* .fCurrentCodeSet = */ true, + /* .fBinary = */ false, + /* .fRecheckMode = */ true, +#ifndef HAVE_FWRITE_UNLOCKED + /* .pCritSect = */ NULL, +#endif +#ifdef RTSTREAM_STANDALONE + /* .ListEntry = */ { NULL, NULL }, +#endif +}; + +/** Pointer to the standard input stream. */ +RTDATADECL(PRTSTREAM) g_pStdIn = &g_StdIn; + +/** Pointer to the standard output stream. */ +RTDATADECL(PRTSTREAM) g_pStdErr = &g_StdErr; + +/** Pointer to the standard output stream. */ +RTDATADECL(PRTSTREAM) g_pStdOut = &g_StdOut; + +#ifdef RTSTREAM_STANDALONE +/** Run-once initializer for the stream list (g_StreamList + g_StreamListCritSect). */ +static RTONCE g_StreamListOnce = RTONCE_INITIALIZER; +/** List of user created streams (excludes the standard streams). */ +static RTLISTANCHOR g_StreamList; +/** Critical section protecting the stream list. */ +static RTCRITSECT g_StreamListCritSect; + + +/** @callback_method_impl{FNRTONCE} */ +static DECLCALLBACK(int32_t) rtStrmListInitOnce(void *pvUser) +{ + RT_NOREF(pvUser); + RTListInit(&g_StreamList); + return RTCritSectInit(&g_StreamListCritSect); +} + +#endif + + +#ifndef HAVE_FWRITE_UNLOCKED +/** + * Allocates and acquires the lock for the stream. + * + * @returns IPRT status code. + * @param pStream The stream (valid). + */ +static int rtStrmAllocLock(PRTSTREAM pStream) +{ + Assert(pStream->pCritSect == NULL); + + PRTCRITSECT pCritSect = (PRTCRITSECT)RTMemAlloc(sizeof(*pCritSect)); + if (!pCritSect) + return VERR_NO_MEMORY; + + /* The native stream lock are normally not recursive. */ + uint32_t fFlags = RTCRITSECT_FLAGS_NO_NESTING; +# if defined(IPRT_NO_CRT) || defined(IN_RT_STATIC) + /* IPRT is often used deliberatly without initialization in no-CRT + binaries (for instance VBoxAddInstallNt3x.exe), so in order to avoid + asserting in the lock validator we add the bootstrap hack that disable + lock validation for the section. + Update: Applying this to all builds involving static linking, as it's + now going to be used for tests running at compile-time too. */ + if (!rtInitIsInitialized()) + fFlags |= RTCRITSECT_FLAGS_BOOTSTRAP_HACK; +# endif + int rc = RTCritSectInitEx(pCritSect, fFlags, NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, "RTSemSpinMutex"); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectEnter(pCritSect); + if (RT_SUCCESS(rc)) + { + if (RT_LIKELY(ASMAtomicCmpXchgPtr(&pStream->pCritSect, pCritSect, NULL))) + return VINF_SUCCESS; + + RTCritSectLeave(pCritSect); + } + RTCritSectDelete(pCritSect); + } + RTMemFree(pCritSect); + + /* Handle the lost race case... */ + pCritSect = ASMAtomicReadPtrT(&pStream->pCritSect, PRTCRITSECT); + if (pCritSect) + return RTCritSectEnter(pCritSect); + + return rc; +} +#endif /* !HAVE_FWRITE_UNLOCKED */ + + +/** + * Locks the stream. May have to allocate the lock as well. + * + * @param pStream The stream (valid). + */ +DECLINLINE(void) rtStrmLock(PRTSTREAM pStream) +{ +#ifdef HAVE_FWRITE_UNLOCKED + flockfile(pStream->pFile); +#else + if (RT_LIKELY(pStream->pCritSect)) + RTCritSectEnter(pStream->pCritSect); + else + rtStrmAllocLock(pStream); +#endif +} + + +/** + * Unlocks the stream. + * + * @param pStream The stream (valid). + */ +DECLINLINE(void) rtStrmUnlock(PRTSTREAM pStream) +{ +#ifdef HAVE_FWRITE_UNLOCKED + funlockfile(pStream->pFile); +#else + if (RT_LIKELY(pStream->pCritSect)) + RTCritSectLeave(pStream->pCritSect); +#endif +} + + +/** + * Opens a file stream. + * + * @returns iprt status code. + * @param pszFilename Path to the file to open, hFile must be NIL_RTFILE. + * NULL if a hFile is to be used instead. + * @param hFile File handle to use when called from + * RTStrmOpenFileHandle. pszFilename must be NULL. + * @param pszMode See RTStrmOpen. + * @param ppStream Where to store the opened stream. + */ +static int rtStrmOpenComon(const char *pszFilename, RTFILE hFile, const char *pszMode, PRTSTREAM *ppStream) +{ + /* + * Validate input and look for things we care for in the pszMode string. + */ + AssertReturn(pszMode && *pszMode, VERR_INVALID_FLAGS); + + /* + * Process the mode string. + */ + char chMode = '\0'; /* a|r|w */ + bool fPlus = false; /* + */ + bool fBinary = false; /* b | !t */ + bool fExclusive = false; /* x */ + bool fNoInherit = false; /* e (linux, freebsd) | N (win) | E (our for reverse) */ + const char *psz = pszMode; + char ch; + while ((ch = *psz++) != '\0') + { + switch (ch) + { + case 'a': + case 'r': + case 'w': + chMode = ch; + break; + case '+': + fPlus = true; + break; + case 'b': + fBinary = true; + break; + case 't': + fBinary = false; + break; + case 'x': + fExclusive = true; + break; + case 'e': + case 'N': + fNoInherit = true; + break; + case 'E': + fNoInherit = false; + break; + default: + AssertMsgFailedReturn(("Invalid ch='%c' in pszMode='%s', '<a|r|w>[+][b|t][x][e|N|E]'\n", ch, pszMode), + VERR_INVALID_FLAGS); + } + } + + /* + * Translate into to RTFILE_O_* flags: + */ + uint64_t fOpen; + switch (chMode) + { + case 'a': fOpen = RTFILE_O_OPEN_CREATE | RTFILE_O_WRITE | RTFILE_O_APPEND; break; + case 'w': fOpen = !fExclusive + ? RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE + : RTFILE_O_CREATE | RTFILE_O_WRITE; break; + case 'r': fOpen = RTFILE_O_OPEN | RTFILE_O_READ; break; + default: AssertMsgFailedReturn(("No main mode (a|r|w) specified in '%s'!\n", pszMode), VERR_INVALID_FLAGS); + } + AssertMsgReturn(!fExclusive || chMode == 'w', ("the 'x' flag is only allowed with 'w'! (%s)\n", pszMode), + VERR_INVALID_FLAGS); + if (fExclusive) + fOpen |= RTFILE_O_READ | RTFILE_O_WRITE; + if (fPlus) + fOpen |= RTFILE_O_READ | RTFILE_O_WRITE; + if (!fNoInherit) + fOpen |= RTFILE_O_INHERIT; + fOpen |= RTFILE_O_DENY_NONE; + fOpen |= 0666 << RTFILE_O_CREATE_MODE_SHIFT; + +#ifndef RTSTREAM_STANDALONE + /* + * Normalize mode for fdopen. + */ + char szNormalizedMode[8]; + szNormalizedMode[0] = chMode; + size_t off = 1; + if (fPlus) + szNormalizedMode[off++] = '+'; + if (fBinary) + szNormalizedMode[off++] = 'b'; + szNormalizedMode[off] = '\0'; +#endif + +#ifdef RTSTREAM_STANDALONE + /* + * Make the the stream list is initialized before we allocate anything. + */ + int rc2 = RTOnce(&g_StreamListOnce, rtStrmListInitOnce, NULL); + AssertRCReturn(rc2, rc2); +#endif + + /* + * Allocate the stream handle and try open it. + */ + int rc = VERR_NO_MEMORY; + PRTSTREAM pStream = (PRTSTREAM)RTMemAllocZ(sizeof(*pStream)); + if (pStream) + { + pStream->u32Magic = RTSTREAM_MAGIC; +#ifdef RTSTREAM_STANDALONE + pStream->enmStdHandle = RTHANDLESTD_INVALID; + pStream->hFile = NIL_RTFILE; + pStream->pchBuf = NULL; + pStream->cbBufAlloc = 0; + pStream->offBufFirst = 0; + pStream->offBufEnd = 0; + pStream->enmBufDir = RTSTREAMBUFDIR_NONE; + pStream->enmBufStyle = RTSTREAMBUFSTYLE_FULL; +# ifdef RTSTREAM_WITH_TEXT_MODE + pStream->pbmBuf = NULL; + pStream->fPendingCr = false, +# endif +#endif + pStream->i32Error = VINF_SUCCESS; + pStream->fCurrentCodeSet = false; + pStream->fBinary = fBinary; + pStream->fRecheckMode = false; +#ifndef HAVE_FWRITE_UNLOCKED + pStream->pCritSect = NULL; +#endif + RTFILEACTION enmActionTaken = RTFILEACTION_INVALID; + if (pszFilename) + rc = RTFileOpenEx(pszFilename, fOpen, &hFile, &enmActionTaken); + else + rc = VINF_SUCCESS; + if (RT_SUCCESS(rc)) + { +#ifndef RTSTREAM_STANDALONE +# ifndef _MSC_VER + int fd = (int)RTFileToNative(hFile); +# else + int fd = _open_osfhandle(RTFileToNative(hFile), + (fPlus ? _O_RDWR : chMode == 'r' ? _O_RDONLY : _O_WRONLY) + | (chMode == 'a' ? _O_APPEND : 0) + | (fBinary ? _O_BINARY : _O_TEXT) + | (fNoInherit ? _O_NOINHERIT : 0)); +# endif + if (fd >= 0) + { + pStream->pFile = fdopen(fd, szNormalizedMode); + if (pStream->pFile) +#endif + { +#ifdef RTSTREAM_STANDALONE + pStream->hFile = hFile; + + /* We keep a list of these for cleanup purposes. */ + RTCritSectEnter(&g_StreamListCritSect); + RTListAppend(&g_StreamList, &pStream->ListEntry); + RTCritSectLeave(&g_StreamListCritSect); +#endif + *ppStream = pStream; + return VINF_SUCCESS; + } + + /* + * This better not happen too often as in 'w' mode we might've + * truncated a file, and in 'w' and 'a' modes there is a chance + * that we'll race other access to the file when deleting it. + */ +#ifndef RTSTREAM_STANDALONE + rc = RTErrConvertFromErrno(errno); +# ifdef _MSC_VER + close(fd); + hFile = NIL_RTFILE; + /** @todo we're in trouble here when called from RTStrmOpenFileHandle! */ +# endif + } + else + { +# ifdef _MSC_VER + rc = RTErrConvertFromErrno(errno); +# else + AssertFailedStmt(rc = VERR_INVALID_HANDLE); +# endif + } + if (pszFilename) + { + RTFileClose(hFile); + if (enmActionTaken == RTFILEACTION_CREATED) + RTFileDelete(pszFilename); + } +#endif + } + RTMemFree(pStream); + } + return rc; +} + + +RTR3DECL(int) RTStrmOpen(const char *pszFilename, const char *pszMode, PRTSTREAM *ppStream) +{ + *ppStream = NULL; + AssertReturn(pszFilename, VERR_INVALID_PARAMETER); + return rtStrmOpenComon(pszFilename, NIL_RTFILE, pszMode, ppStream); +} + + +RTR3DECL(int) RTStrmOpenFV(const char *pszMode, PRTSTREAM *ppStream, const char *pszFilenameFmt, va_list args) +{ + int rc; + char szFilename[RTPATH_MAX]; + size_t cch = RTStrPrintfV(szFilename, sizeof(szFilename), pszFilenameFmt, args); + if (cch < sizeof(szFilename)) + rc = RTStrmOpen(szFilename, pszMode, ppStream); + else + { + AssertMsgFailed(("The filename is too long cch=%d\n", cch)); + rc = VERR_FILENAME_TOO_LONG; + } + return rc; +} + + +RTR3DECL(int) RTStrmOpenF(const char *pszMode, PRTSTREAM *ppStream, const char *pszFilenameFmt, ...) +{ + va_list args; + va_start(args, pszFilenameFmt); + int rc = RTStrmOpenFV(pszMode, ppStream, pszFilenameFmt, args); + va_end(args); + return rc; +} + + +RTR3DECL(int) RTStrmOpenFileHandle(RTFILE hFile, const char *pszMode, uint32_t fFlags, PRTSTREAM *ppStream) +{ + *ppStream = NULL; + AssertReturn(RTFileIsValid(hFile), VERR_INVALID_HANDLE); + AssertReturn(fFlags == 0, VERR_INVALID_FLAGS); + return rtStrmOpenComon(NULL, hFile, pszMode, ppStream); +} + + +RTR3DECL(int) RTStrmClose(PRTSTREAM pStream) +{ + /* + * Validate input. + */ + if (!pStream) + return VINF_SUCCESS; + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_MAGIC); + + /* We don't implement closing any of the standard handles at present. */ + AssertReturn(pStream != &g_StdIn, VERR_NOT_SUPPORTED); + AssertReturn(pStream != &g_StdOut, VERR_NOT_SUPPORTED); + AssertReturn(pStream != &g_StdErr, VERR_NOT_SUPPORTED); + + /* + * Invalidate the stream and destroy the critical section first. + */ +#ifdef RTSTREAM_STANDALONE + RTCritSectEnter(&g_StreamListCritSect); + RTListNodeRemove(&pStream->ListEntry); + RTCritSectLeave(&g_StreamListCritSect); +#endif + pStream->u32Magic = 0xdeaddead; +#ifndef HAVE_FWRITE_UNLOCKED + if (pStream->pCritSect) + { + RTCritSectEnter(pStream->pCritSect); + RTCritSectLeave(pStream->pCritSect); + RTCritSectDelete(pStream->pCritSect); + RTMemFree(pStream->pCritSect); + pStream->pCritSect = NULL; + } +#endif + + /* + * Flush and close the underlying file. + */ +#ifdef RTSTREAM_STANDALONE + int const rc1 = RTStrmFlush(pStream); + AssertRC(rc1); + int const rc2 = RTFileClose(pStream->hFile); + AssertRC(rc2); + int const rc = RT_SUCCESS(rc1) ? rc2 : rc1; +#else + int const rc = !fclose(pStream->pFile) ? VINF_SUCCESS : RTErrConvertFromErrno(errno); +#endif + + /* + * Destroy the stream. + */ +#ifdef RTSTREAM_STANDALONE + pStream->hFile = NIL_RTFILE; + RTMemFree(pStream->pchBuf); + pStream->pchBuf = NULL; + pStream->cbBufAlloc = 0; + pStream->offBufFirst = 0; + pStream->offBufEnd = 0; +# ifdef RTSTREAM_WITH_TEXT_MODE + RTMemFree(pStream->pbmBuf); + pStream->pbmBuf = NULL; +# endif +#else + pStream->pFile = NULL; +#endif + RTMemFree(pStream); + return rc; +} + + +RTR3DECL(int) RTStrmError(PRTSTREAM pStream) +{ + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_MAGIC); + return pStream->i32Error; +} + + +RTR3DECL(int) RTStrmClearError(PRTSTREAM pStream) +{ + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_MAGIC); + +#ifndef RTSTREAM_STANDALONE + clearerr(pStream->pFile); +#endif + ASMAtomicWriteS32(&pStream->i32Error, VINF_SUCCESS); + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTStrmSetMode(PRTSTREAM pStream, int fBinary, int fCurrentCodeSet) +{ + AssertPtrReturn(pStream, VERR_INVALID_HANDLE); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE); + AssertReturn((unsigned)(fBinary + 1) <= 2, VERR_INVALID_PARAMETER); + AssertReturn((unsigned)(fCurrentCodeSet + 1) <= 2, VERR_INVALID_PARAMETER); + + rtStrmLock(pStream); + + if (fBinary != -1) + { + pStream->fBinary = RT_BOOL(fBinary); + pStream->fRecheckMode = true; + } + + if (fCurrentCodeSet != -1) + pStream->fCurrentCodeSet = RT_BOOL(fCurrentCodeSet); + + rtStrmUnlock(pStream); + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTStrmSetBufferingMode(PRTSTREAM pStream, RTSTRMBUFMODE enmMode) +{ + AssertPtrReturn(pStream, VERR_INVALID_HANDLE); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(enmMode > RTSTRMBUFMODE_INVALID && enmMode < RTSTRMBUFMODE_END, VERR_INVALID_PARAMETER); + +#ifndef RTSTREAM_STANDALONE + int iCrtMode = enmMode == RTSTRMBUFMODE_FULL ? _IOFBF : enmMode == RTSTRMBUFMODE_LINE ? _IOLBF : _IONBF; + int rc = setvbuf(pStream->pFile, NULL, iCrtMode, 0); + if (rc >= 0) + return VINF_SUCCESS; + return RTErrConvertFromErrno(errno); + +#else + rtStrmLock(pStream); + pStream->enmBufStyle = enmMode == RTSTRMBUFMODE_FULL ? RTSTREAMBUFSTYLE_FULL + : enmMode == RTSTRMBUFMODE_LINE ? RTSTREAMBUFSTYLE_LINE : RTSTREAMBUFSTYLE_UNBUFFERED; + rtStrmUnlock(pStream); + return VINF_SUCCESS; +#endif +} + + +#ifdef RTSTREAM_STANDALONE + +/** + * Deals with NIL_RTFILE in rtStrmGetFile. + */ +DECL_NO_INLINE(static, RTFILE) rtStrmGetFileNil(PRTSTREAM pStream) +{ +# ifdef RT_OS_WINDOWS + DWORD dwStdHandle; + switch (pStream->enmStdHandle) + { + case RTHANDLESTD_INPUT: dwStdHandle = STD_INPUT_HANDLE; break; + case RTHANDLESTD_OUTPUT: dwStdHandle = STD_OUTPUT_HANDLE; break; + case RTHANDLESTD_ERROR: dwStdHandle = STD_ERROR_HANDLE; break; + default: return NIL_RTFILE; + } + HANDLE hHandle = GetStdHandle(dwStdHandle); + if (hHandle != INVALID_HANDLE_VALUE && hHandle != NULL) + { + int rc = RTFileFromNative(&pStream->hFile, (uintptr_t)hHandle); + if (RT_SUCCESS(rc)) + { + /* Switch to full buffering if not a console handle. */ + DWORD dwMode; + if (!GetConsoleMode(hHandle, &dwMode)) + pStream->enmBufStyle = RTSTREAMBUFSTYLE_FULL; + + return pStream->hFile; + } + } + +# else + uintptr_t uNative; + switch (pStream->enmStdHandle) + { + case RTHANDLESTD_INPUT: uNative = RTFILE_NATIVE_STDIN; break; + case RTHANDLESTD_OUTPUT: uNative = RTFILE_NATIVE_STDOUT; break; + case RTHANDLESTD_ERROR: uNative = RTFILE_NATIVE_STDERR; break; + default: return NIL_RTFILE; + } + int rc = RTFileFromNative(&pStream->hFile, uNative); + if (RT_SUCCESS(rc)) + { + /* Switch to full buffering if not a console handle. */ + if (!isatty((int)uNative)) + pStream->enmBufStyle = RTSTREAMBUFDIR_FULL; + + return pStream->hFile; + } + +# endif + return NIL_RTFILE; +} + + +/** + * For lazily resolving handles for the standard streams. + */ +DECLINLINE(RTFILE) rtStrmGetFile(PRTSTREAM pStream) +{ + RTFILE hFile = pStream->hFile; + if (hFile != NIL_RTFILE) + return hFile; + return rtStrmGetFileNil(pStream); +} + + +RTR3DECL(int) RTStrmQueryFileHandle(PRTSTREAM pStream, PRTFILE phFile) +{ + AssertPtrReturn(phFile, VERR_INVALID_POINTER); + *phFile = NIL_RTFILE; + AssertPtrReturn(pStream, VERR_INVALID_POINTER); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_MAGIC); + + rtStrmLock(pStream); + RTFILE hFile = rtStrmGetFile(pStream); + rtStrmUnlock(pStream); + if (hFile != NIL_RTFILE) + { + *phFile = hFile; + return VINF_SUCCESS; + } + return VERR_NOT_AVAILABLE; +} + +#endif /* RTSTREAM_STANDALONE */ + + +/** + * Wrapper around isatty, assumes caller takes care of stream locking/whatever + * is needed. + */ +DECLINLINE(bool) rtStrmIsTerminal(PRTSTREAM pStream) +{ +#ifdef RTSTREAM_STANDALONE + RTFILE hFile = rtStrmGetFile(pStream); + if (hFile != NIL_RTFILE) + { + HANDLE hNative = (HANDLE)RTFileToNative(hFile); + DWORD dwType = GetFileType(hNative); + if (dwType == FILE_TYPE_CHAR) + { + DWORD dwMode; + if (GetConsoleMode(hNative, &dwMode)) + return true; + } + } + return false; + +#else + if (pStream->pFile) + { + int fh = fileno(pStream->pFile); + if (isatty(fh) != 0) + { +# ifdef RT_OS_WINDOWS + DWORD dwMode; + HANDLE hCon = (HANDLE)_get_osfhandle(fh); + if (GetConsoleMode(hCon, &dwMode)) + return true; +# else + return true; +# endif + } + } + return false; +#endif +} + + +static int rtStrmInputGetEchoCharsNative(uintptr_t hNative, bool *pfEchoChars) +{ +#ifdef RT_OS_WINDOWS + DWORD dwMode; + if (GetConsoleMode((HANDLE)hNative, &dwMode)) + *pfEchoChars = RT_BOOL(dwMode & ENABLE_ECHO_INPUT); + else + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_INVALID_HANDLE) + return GetFileType((HANDLE)hNative) != FILE_TYPE_UNKNOWN ? VERR_INVALID_FUNCTION : VERR_INVALID_HANDLE; + return RTErrConvertFromWin32(dwErr); + } +#else + struct termios Termios; + int rcPosix = tcgetattr((int)hNative, &Termios); + if (!rcPosix) + *pfEchoChars = RT_BOOL(Termios.c_lflag & ECHO); + else + return errno == ENOTTY ? VERR_INVALID_FUNCTION : RTErrConvertFromErrno(errno); +#endif + return VINF_SUCCESS; +} + + + +RTR3DECL(int) RTStrmInputGetEchoChars(PRTSTREAM pStream, bool *pfEchoChars) +{ + AssertPtrReturn(pStream, VERR_INVALID_HANDLE); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pfEchoChars, VERR_INVALID_POINTER); + +#ifdef RTSTREAM_STANDALONE + return rtStrmInputGetEchoCharsNative(RTFileToNative(pStream->hFile), pfEchoChars); +#else + int rc; + int fh = fileno(pStream->pFile); + if (isatty(fh)) + { +# ifdef RT_OS_WINDOWS + rc = rtStrmInputGetEchoCharsNative(_get_osfhandle(fh), pfEchoChars); +# else + rc = rtStrmInputGetEchoCharsNative(fh, pfEchoChars); +# endif + } + else + rc = VERR_INVALID_FUNCTION; + return rc; +#endif +} + + +static int rtStrmInputSetEchoCharsNative(uintptr_t hNative, bool fEchoChars) +{ + int rc; +#ifdef RT_OS_WINDOWS + DWORD dwMode; + if (GetConsoleMode((HANDLE)hNative, &dwMode)) + { + if (fEchoChars) + dwMode |= ENABLE_ECHO_INPUT; + else + dwMode &= ~ENABLE_ECHO_INPUT; + if (SetConsoleMode((HANDLE)hNative, dwMode)) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_INVALID_HANDLE) + return GetFileType((HANDLE)hNative) != FILE_TYPE_UNKNOWN ? VERR_INVALID_FUNCTION : VERR_INVALID_HANDLE; + return RTErrConvertFromWin32(dwErr); + } +#else + struct termios Termios; + int rcPosix = tcgetattr((int)hNative, &Termios); + if (!rcPosix) + { + if (fEchoChars) + Termios.c_lflag |= ECHO; + else + Termios.c_lflag &= ~ECHO; + + rcPosix = tcsetattr((int)hNative, TCSAFLUSH, &Termios); + if (rcPosix == 0) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromErrno(errno); + } + else + rc = errno == ENOTTY ? VERR_INVALID_FUNCTION : RTErrConvertFromErrno(errno); +#endif + return rc; +} + + +RTR3DECL(int) RTStrmInputSetEchoChars(PRTSTREAM pStream, bool fEchoChars) +{ + AssertPtrReturn(pStream, VERR_INVALID_HANDLE); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE); + +#ifdef RTSTREAM_STANDALONE + return rtStrmInputSetEchoCharsNative(RTFileToNative(pStream->hFile), fEchoChars); +#else + int rc; + int fh = fileno(pStream->pFile); + if (isatty(fh)) + { +# ifdef RT_OS_WINDOWS + rc = rtStrmInputSetEchoCharsNative(_get_osfhandle(fh), fEchoChars); +# else + rc = rtStrmInputSetEchoCharsNative(fh, fEchoChars); +# endif + } + else + rc = VERR_INVALID_FUNCTION; + return rc; +#endif +} + + +RTR3DECL(bool) RTStrmIsTerminal(PRTSTREAM pStream) +{ + AssertPtrReturn(pStream, false); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, false); + + return rtStrmIsTerminal(pStream); +} + + +RTR3DECL(int) RTStrmQueryTerminalWidth(PRTSTREAM pStream, uint32_t *pcchWidth) +{ + AssertPtrReturn(pcchWidth, VERR_INVALID_HANDLE); + *pcchWidth = 80; + + AssertPtrReturn(pStream, VERR_INVALID_HANDLE); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE); + + if (rtStrmIsTerminal(pStream)) + { +#ifdef RT_OS_WINDOWS +# ifdef RTSTREAM_STANDALONE + HANDLE hCon = (HANDLE)RTFileToNative(pStream->hFile); +# else + HANDLE hCon = (HANDLE)_get_osfhandle(fileno(pStream->pFile)); +# endif + CONSOLE_SCREEN_BUFFER_INFO Info; + RT_ZERO(Info); + if (GetConsoleScreenBufferInfo(hCon, &Info)) + { + *pcchWidth = Info.dwSize.X ? Info.dwSize.X : 80; + return VINF_SUCCESS; + } + return RTErrConvertFromWin32(GetLastError()); + +#elif defined(RT_OS_OS2) && !defined(TIOCGWINSZ) /* only OS/2 should currently miss this */ + return VINF_SUCCESS; /* just pretend for now. */ + +#else + struct winsize Info; + RT_ZERO(Info); + int rc = ioctl(fileno(pStream->pFile), TIOCGWINSZ, &Info); + if (rc >= 0) + { + *pcchWidth = Info.ws_col ? Info.ws_col : 80; + return VINF_SUCCESS; + } + return RTErrConvertFromErrno(errno); +#endif + } + return VERR_INVALID_FUNCTION; +} + + +#ifdef RTSTREAM_STANDALONE + +DECLINLINE(void) rtStrmBufInvalidate(PRTSTREAM pStream) +{ + pStream->enmBufDir = RTSTREAMBUFDIR_NONE; + pStream->offBufEnd = 0; + pStream->offBufFirst = 0; +} + + +static int rtStrmBufFlushWrite(PRTSTREAM pStream, size_t cbToFlush) +{ + Assert(cbToFlush <= pStream->offBufEnd - pStream->offBufFirst); + + RTFILE const hFile = rtStrmGetFile(pStream); + if (hFile != NIL_RTFILE) + { + /** @todo do nonblocking & incomplete writes? */ + size_t offBufFirst = pStream->offBufFirst; + int rc = RTFileWrite(hFile, &pStream->pchBuf[offBufFirst], cbToFlush, NULL); + if (RT_SUCCESS(rc)) + { + offBufFirst += cbToFlush; + if (offBufFirst >= pStream->offBufEnd) + pStream->offBufEnd = 0; + else + { + /* Shift up the remaining content so the next write can take full + advantage of the buffer size. */ + size_t cbLeft = pStream->offBufEnd - offBufFirst; + memmove(pStream->pchBuf, &pStream->pchBuf[offBufFirst], cbLeft); + pStream->offBufEnd = cbLeft; + } + pStream->offBufFirst = 0; + return VINF_SUCCESS; + } + return rc; + } + return VERR_INVALID_HANDLE; +} + + +static int rtStrmBufFlushWriteMaybe(PRTSTREAM pStream, bool fInvalidate) +{ + if (pStream->enmBufDir == RTSTREAMBUFDIR_WRITE) + { + size_t cbInBuffer = pStream->offBufEnd - pStream->offBufFirst; + if (cbInBuffer > 0) + { + int rc = rtStrmBufFlushWrite(pStream, cbInBuffer); + if (fInvalidate) + pStream->enmBufDir = RTSTREAMBUFDIR_NONE; + return rc; + } + } + if (fInvalidate) + rtStrmBufInvalidate(pStream); + return VINF_SUCCESS; +} + + +/** + * Worker for rtStrmBufCheckErrorAndSwitchToReadMode and + * rtStrmBufCheckErrorAndSwitchToWriteMode that allocates a buffer. + * + * Only updates cbBufAlloc and pchBuf, callers deals with error fallout. + */ +static int rtStrmBufAlloc(PRTSTREAM pStream) +{ + size_t cbBuf = pStream->enmBufStyle == RTSTREAMBUFSTYLE_FULL ? _64K : _16K; + do + { + pStream->pchBuf = (char *)RTMemAllocZ(cbBuf); + if (RT_LIKELY(pStream->pchBuf)) + { +# ifdef RTSTREAM_WITH_TEXT_MODE + Assert(RT_ALIGN_Z(cbBuf, 64 / 8) == cbBuf); + pStream->pbmBuf = (uint32_t *)RTMemAllocZ(cbBuf / 8); + if (RT_LIKELY(pStream->pbmBuf)) +# endif + { + pStream->cbBufAlloc = cbBuf; + return VINF_SUCCESS; + } +# ifdef RTSTREAM_WITH_TEXT_MODE + RTMemFree(pStream->pchBuf); + pStream->pchBuf = NULL; +# endif + } + cbBuf /= 2; + } while (cbBuf >= 256); + return VERR_NO_MEMORY; +} + + +/** + * Checks the stream error status, flushed any pending writes, ensures there is + * a buffer allocated and switches the stream to the read direction. + * + * @returns IPRT status code (same as i32Error). + * @param pStream The stream. + */ +static int rtStrmBufCheckErrorAndSwitchToReadMode(PRTSTREAM pStream) +{ + int rc = pStream->i32Error; + if (RT_SUCCESS(rc)) + { + /* + * We're very likely already in read mode and can return without doing + * anything here. + */ + if (pStream->enmBufDir == RTSTREAMBUFDIR_READ) + return VINF_SUCCESS; + + /* + * Flush any pending writes before switching the buffer to read: + */ + rc = rtStrmBufFlushWriteMaybe(pStream, false /*fInvalidate*/); + if (RT_SUCCESS(rc)) + { + pStream->enmBufDir = RTSTREAMBUFDIR_READ; + pStream->offBufEnd = 0; + pStream->offBufFirst = 0; + pStream->fPendingCr = false; + + /* + * Read direction implies a buffer, so make sure we've got one and + * change to NONE direction if allocating one fails. + */ + if (pStream->pchBuf) + { + Assert(pStream->cbBufAlloc >= 256); + return VINF_SUCCESS; + } + + rc = rtStrmBufAlloc(pStream); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + pStream->enmBufDir = RTSTREAMBUFDIR_NONE; + } + ASMAtomicWriteS32(&pStream->i32Error, rc); + } + return rc; +} + + +/** + * Checks the stream error status, ensures there is a buffer allocated and + * switches the stream to the write direction. + * + * @returns IPRT status code (same as i32Error). + * @param pStream The stream. + */ +static int rtStrmBufCheckErrorAndSwitchToWriteMode(PRTSTREAM pStream) +{ + int rc = pStream->i32Error; + if (RT_SUCCESS(rc)) + { + /* + * We're very likely already in write mode and can return without doing + * anything here. + */ + if (pStream->enmBufDir == RTSTREAMBUFDIR_WRITE) + return VINF_SUCCESS; + + /* + * A read buffer does not need any flushing, so we just have to make + * sure there is a buffer present before switching to the write direction. + */ + pStream->enmBufDir = RTSTREAMBUFDIR_WRITE; + pStream->offBufEnd = 0; + pStream->offBufFirst = 0; + if (pStream->pchBuf) + { + Assert(pStream->cbBufAlloc >= 256); + return VINF_SUCCESS; + } + + rc = rtStrmBufAlloc(pStream); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + pStream->enmBufDir = RTSTREAMBUFDIR_NONE; + ASMAtomicWriteS32(&pStream->i32Error, rc); + } + return rc; +} + + +/** + * Reads more bytes into the buffer. + * + * @returns IPRT status code (same as i32Error). + * @param pStream The stream. + */ +static int rtStrmBufFill(PRTSTREAM pStream) +{ + /* + * Check preconditions + */ + Assert(pStream->i32Error == VINF_SUCCESS); + Assert(pStream->enmBufDir == RTSTREAMBUFDIR_READ); + AssertPtr(pStream->pchBuf); + Assert(pStream->cbBufAlloc >= 256); + Assert(RT_ALIGN_Z(pStream->cbBufAlloc, 64) == pStream->cbBufAlloc); + Assert(pStream->offBufFirst <= pStream->cbBufAlloc); + Assert(pStream->offBufEnd <= pStream->cbBufAlloc); + Assert(pStream->offBufFirst <= pStream->offBufEnd); +# ifdef RTSTREAM_WITH_TEXT_MODE + AssertPtr(pStream->pbmBuf); +# endif + /* + * If there is data in the buffer, move it up to the start. + */ + size_t cbInBuffer; + if (!pStream->offBufFirst) + cbInBuffer = pStream->offBufEnd; + else + { + cbInBuffer = pStream->offBufEnd - pStream->offBufFirst; + if (cbInBuffer) + { + memmove(pStream->pchBuf, &pStream->pchBuf[pStream->offBufFirst], cbInBuffer); +# ifdef RTSTREAM_WITH_TEXT_MODE + if (!pStream->fBinary) /** @todo this isn't very efficient, must be a better way of shifting a bitmap. */ + for (size_t off = 0; off < pStream->offBufFirst; off++) + if (ASMBitTest(pStream->pbmBuf, (int32_t)off)) + ASMBitSet(pStream->pbmBuf, (int32_t)off); + else + ASMBitClear(pStream->pbmBuf, (int32_t)off); +# endif + } + pStream->offBufFirst = 0; + pStream->offBufEnd = cbInBuffer; + } + + /* + * Add pending CR to the buffer. + */ + size_t const offCrLfConvStart = cbInBuffer; + Assert(cbInBuffer + 2 <= pStream->cbBufAlloc); + if (!pStream->fPendingCr || pStream->fBinary) + { /* likely */ } + else + { + pStream->pchBuf[cbInBuffer] = '\r'; + pStream->fPendingCr = false; + pStream->offBufEnd = ++cbInBuffer; + } + + /* + * Read data till the buffer is full. + */ + int rc = VERR_INVALID_HANDLE; + RTFILE const hFile = rtStrmGetFile(pStream); + if (hFile != NIL_RTFILE) + { + size_t cbRead = 0; + rc = RTFileRead(hFile, &pStream->pchBuf[cbInBuffer], pStream->cbBufAlloc - cbInBuffer, &cbRead); + if (RT_SUCCESS(rc)) + { + cbInBuffer += cbRead; + pStream->offBufEnd = cbInBuffer; + + if (cbInBuffer != 0) + { +# ifdef RTSTREAM_WITH_TEXT_MODE + if (pStream->fBinary) +# endif + return VINF_SUCCESS; + } + else + { + /** @todo this shouldn't be sticky, should it? */ + ASMAtomicWriteS32(&pStream->i32Error, VERR_EOF); + return VERR_EOF; + } + +# ifdef RTSTREAM_WITH_TEXT_MODE + /* + * Do CRLF -> LF conversion in the buffer. + */ + ASMBitClearRange(pStream->pbmBuf, offCrLfConvStart, RT_ALIGN_Z(cbInBuffer, 64)); + char *pchCur = &pStream->pchBuf[offCrLfConvStart]; + size_t cbLeft = cbInBuffer - offCrLfConvStart; + while (cbLeft > 0) + { + Assert(&pchCur[cbLeft] == &pStream->pchBuf[pStream->offBufEnd]); + char *pchCr = (char *)memchr(pchCur, '\r', cbLeft); + if (pchCr) + { + size_t offCur = (size_t)(pchCr - pchCur); + if (offCur + 1 < cbLeft) + { + if (pchCr[1] == '\n') + { + /* Found one '\r\n' sequence. Look for more before shifting the buffer content. */ + cbLeft -= offCur; + pchCur = pchCr; + + do + { + ASMBitSet(pStream->pbmBuf, (int32_t)(pchCur - pStream->pchBuf)); + *pchCur++ = '\n'; /* dst */ + cbLeft -= 2; + pchCr += 2; /* src */ + } while (cbLeft >= 2 && pchCr[0] == '\r' && pchCr[1] == '\n'); + + memmove(&pchCur, pchCr, cbLeft); + } + else + { + cbLeft -= offCur + 1; + pchCur = pchCr + 1; + } + } + else + { + Assert(pchCr == &pStream->pchBuf[pStream->offBufEnd - 1]); + pStream->fPendingCr = true; + pStream->offBufEnd = --cbInBuffer; + break; + } + } + else + break; + } + + return VINF_SUCCESS; +# endif + } + } + + /* + * If there is data in the buffer, don't raise the error till it has all + * been consumed, ASSUMING that another fill call will follow and that the + * error condition will reoccur then. + * + * Note! We may currently end up not converting a CRLF pair, if it's + * split over a temporary EOF condition, since we forces the caller + * to read the CR before requesting more data. However, it's not a + * very likely scenario, so we'll just leave it like that for now. + */ + if (cbInBuffer) + return VINF_SUCCESS; + ASMAtomicWriteS32(&pStream->i32Error, rc); + return rc; +} + + +/** + * Copies @a cbSrc bytes from @a pvSrc and into the buffer, flushing as needed + * to make space available. + * + * + * @returns IPRT status code (errors not assigned to i32Error). + * @param pStream The stream. + * @param pvSrc The source buffer. + * @param cbSrc Number of bytes to copy from @a pvSrc. + * @param pcbTotal A total counter to update with what was copied. + */ +static int rtStrmBufCopyTo(PRTSTREAM pStream, const void *pvSrc, size_t cbSrc, size_t *pcbTotal) +{ + Assert(cbSrc > 0); + for (;;) + { + size_t cbToCopy = RT_MIN(pStream->cbBufAlloc - pStream->offBufEnd, cbSrc); + if (cbToCopy) + { + memcpy(&pStream->pchBuf[pStream->offBufEnd], pvSrc, cbToCopy); + pStream->offBufEnd += cbToCopy; + pvSrc = (const char *)pvSrc + cbToCopy; + *pcbTotal += cbToCopy; + cbSrc -= cbToCopy; + if (!cbSrc) + break; + } + + int rc = rtStrmBufFlushWrite(pStream, pStream->offBufEnd - pStream->offBufFirst); + if (RT_FAILURE(rc)) + return rc; + } + return VINF_SUCCESS; +} + + +/** + * Worker for rtStrmFlushAndCloseAll and rtStrmFlushAndClose. + */ +static RTFILE rtStrmFlushAndCleanup(PRTSTREAM pStream) +{ + if (pStream->pchBuf) + { + if ( pStream->enmBufDir == RTSTREAMBUFDIR_WRITE + && pStream->offBufFirst < pStream->offBufEnd + && RT_SUCCESS(pStream->i32Error) ) + rtStrmBufFlushWrite(pStream, pStream->offBufEnd - pStream->offBufFirst); + RTMemFree(pStream->pchBuf); + pStream->pchBuf = NULL; + pStream->offBufFirst = 0; + pStream->offBufEnd = 0; +# ifdef RTSTREAM_WITH_TEXT_MODE + RTMemFree(pStream->pbmBuf); + pStream->pbmBuf = NULL; +# endif + } + + PRTCRITSECT pCritSect = pStream->pCritSect; + if (pCritSect) + { + pStream->pCritSect = NULL; + RTCritSectDelete(pCritSect); + RTMemFree(pCritSect); + } + + RTFILE hFile = pStream->hFile; + pStream->hFile = NIL_RTFILE; + return hFile; +} + + +/** + * Worker for rtStrmFlushAndCloseAll. + */ +static void rtStrmFlushAndClose(PRTSTREAM pStream) +{ + pStream->u32Magic = ~RTSTREAM_MAGIC; + RTFILE hFile = rtStrmFlushAndCleanup(pStream); + if (hFile != NIL_RTFILE) + RTFileClose(hFile); + RTMemFree(pStream); +} + + +/** + * Flushes and cleans up the standard streams, should flush and close all others + * too but doesn't yet... + */ +DECLCALLBACK(void) rtStrmFlushAndCloseAll(void) +{ + /* + * Flush the standard handles. + */ + rtStrmFlushAndCleanup(&g_StdOut); + rtStrmFlushAndCleanup(&g_StdErr); + rtStrmFlushAndCleanup(&g_StdIn); + + /* + * Make a list of the rest and flush+close those too. + */ + if (RTOnceWasInitialized(&g_StreamListOnce)) + { + RTCritSectDelete(&g_StreamListCritSect); + + PRTSTREAM pStream; + while ((pStream = RTListRemoveFirst(&g_StreamList, RTSTREAM, ListEntry)) != NULL) + rtStrmFlushAndClose(pStream); + + RTOnceReset(&g_StreamListOnce); + } +} + +# ifdef IPRT_COMPILER_TERM_CALLBACK +IPRT_COMPILER_TERM_CALLBACK(rtStrmFlushAndCloseAll); +# endif + +#endif /* RTSTREAM_STANDALONE */ + + +RTR3DECL(int) RTStrmRewind(PRTSTREAM pStream) +{ + AssertPtrReturn(pStream, VERR_INVALID_HANDLE); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE); + +#ifdef RTSTREAM_STANDALONE + rtStrmLock(pStream); + int const rc1 = rtStrmBufFlushWriteMaybe(pStream, true /*fInvalidate*/); + int const rc2 = RTFileSeek(rtStrmGetFile(pStream), 0, RTFILE_SEEK_BEGIN, NULL); + int rc = RT_SUCCESS(rc1) ? rc2 : rc1; + ASMAtomicWriteS32(&pStream->i32Error, rc); + rtStrmUnlock(pStream); +#else + clearerr(pStream->pFile); + errno = 0; + int rc; + if (!fseek(pStream->pFile, 0, SEEK_SET)) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromErrno(errno); + ASMAtomicWriteS32(&pStream->i32Error, rc); +#endif + return rc; +} + + +RTR3DECL(int) RTStrmSeek(PRTSTREAM pStream, RTFOFF off, uint32_t uMethod) +{ + AssertReturn(uMethod <= RTFILE_SEEK_END, VERR_INVALID_PARAMETER); +#ifdef RTSTREAM_STANDALONE + rtStrmLock(pStream); + int rc = rtStrmBufFlushWriteMaybe(pStream, true /*fInvalidate*/); + if (RT_SUCCESS(rc)) + rc = RTFileSeek(rtStrmGetFile(pStream), off, uMethod, NULL); + if (RT_FAILURE(rc)) + ASMAtomicWriteS32(&pStream->i32Error, rc); + rtStrmUnlock(pStream); +#else + int const iCrtMethod = uMethod == RTFILE_SEEK_BEGIN ? SEEK_SET : uMethod == RTFILE_SEEK_CURRENT ? SEEK_CUR : SEEK_END; + errno = 0; + int rc; +# ifdef _MSC_VER + if (!_fseeki64(pStream->pFile, off, iCrtMethod)) +# else + if (!fseeko(pStream->pFile, off, iCrtMethod)) +# endif + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromErrno(errno); + ASMAtomicWriteS32(&pStream->i32Error, rc); +#endif + return rc; +} + + +RTR3DECL(RTFOFF) RTStrmTell(PRTSTREAM pStream) +{ +#ifdef RTSTREAM_STANDALONE + uint64_t off = 0; + rtStrmLock(pStream); + int rc = pStream->i32Error; + if (RT_SUCCESS(rc)) + { + RTFILE const hFile = rtStrmGetFile(pStream); + if (hFile != NIL_RTFILE) + { + rc = RTFileSeek(hFile, 0, RTFILE_SEEK_CURRENT, &off); + if (RT_SUCCESS(rc)) + { + switch (pStream->enmBufDir) + { + case RTSTREAMBUFDIR_READ: + /* Subtract unconsumed chars and removed '\r' characters. */ + off -= pStream->offBufEnd - pStream->offBufFirst; + if (!pStream->fBinary) + for (size_t offBuf = pStream->offBufFirst; offBuf < pStream->offBufEnd; offBuf++) + off -= ASMBitTest(pStream->pbmBuf, (int32_t)offBuf); + break; + case RTSTREAMBUFDIR_WRITE: + /* Add unwrittend chars in the buffer. */ + off += pStream->offBufEnd - pStream->offBufFirst; + break; + default: + AssertFailed(); + case RTSTREAMBUFDIR_NONE: + break; + } + } + } + else + rc = VERR_INVALID_HANDLE; + } + if (RT_FAILURE(rc)) + { + ASMAtomicWriteS32(&pStream->i32Error, rc); + off = rc; + } + rtStrmUnlock(pStream); +#else +# ifdef _MSC_VER + RTFOFF off = _ftelli64(pStream->pFile); +# else + RTFOFF off = ftello(pStream->pFile); +# endif + if (off < 0) + { + int rc = RTErrConvertFromErrno(errno); + ASMAtomicWriteS32(&pStream->i32Error, rc); + off = rc; + } +#endif + return off; +} + + +/** + * Recheck the stream mode. + * + * @param pStream The stream (locked). + */ +static void rtStreamRecheckMode(PRTSTREAM pStream) +{ +#if (defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)) && !defined(RTSTREAM_STANDALONE) + int fh = fileno(pStream->pFile); + if (fh >= 0) + { + int fExpected = pStream->fBinary ? _O_BINARY : _O_TEXT; + int fActual = _setmode(fh, fExpected); + if (fActual != -1 && fExpected != (fActual & (_O_BINARY | _O_TEXT))) + { + fActual = _setmode(fh, fActual & (_O_BINARY | _O_TEXT)); + pStream->fBinary = !(fActual & _O_TEXT); + } + } +#else + NOREF(pStream); +#endif + pStream->fRecheckMode = false; +} + + +RTR3DECL(int) RTStrmReadEx(PRTSTREAM pStream, void *pvBuf, size_t cbToRead, size_t *pcbRead) +{ + AssertPtrReturn(pStream, VERR_INVALID_HANDLE); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE); + +#ifdef RTSTREAM_STANDALONE + rtStrmLock(pStream); + int rc = rtStrmBufCheckErrorAndSwitchToReadMode(pStream); +#else + int rc = pStream->i32Error; +#endif + if (RT_SUCCESS(rc)) + { + if (pStream->fRecheckMode) + rtStreamRecheckMode(pStream); + +#ifdef RTSTREAM_STANDALONE + + /* + * Copy data thru the read buffer for now as that'll handle both binary + * and text modes seamlessly. We could optimize larger reads here when + * in binary mode, that can wait till the basics work, I think. + */ + size_t cbTotal = 0; + if (cbToRead > 0) + for (;;) + { + size_t cbInBuffer = pStream->offBufEnd - pStream->offBufFirst; + if (cbInBuffer > 0) + { + size_t cbToCopy = RT_MIN(cbInBuffer, cbToRead); + memcpy(pvBuf, &pStream->pchBuf[pStream->offBufFirst], cbToCopy); + cbTotal += cbToRead; + cbToRead -= cbToCopy; + pvBuf = (char *)pvBuf + cbToCopy; + if (!cbToRead) + break; + } + rc = rtStrmBufFill(pStream); + if (RT_SUCCESS(rc)) + { /* likely */ } + else + { + if (rc == VERR_EOF && pcbRead && cbTotal > 0) + rc = VINF_EOF; + break; + } + } + if (pcbRead) + *pcbRead = cbTotal; + +#else /* !RTSTREAM_STANDALONE */ + if (pcbRead) + { + /* + * Can do with a partial read. + */ + *pcbRead = fread(pvBuf, 1, cbToRead, pStream->pFile); + if ( *pcbRead == cbToRead + || !ferror(pStream->pFile)) + rc = VINF_SUCCESS; + else if (feof(pStream->pFile)) + rc = *pcbRead ? VINF_EOF : VERR_EOF; + else if (ferror(pStream->pFile)) + rc = VERR_READ_ERROR; + else + { + AssertMsgFailed(("This shouldn't happen\n")); + rc = VERR_INTERNAL_ERROR; + } + } + else + { + /* + * Must read it all! + */ + if (fread(pvBuf, cbToRead, 1, pStream->pFile) == 1) + rc = VINF_SUCCESS; + /* possible error/eof. */ + else if (feof(pStream->pFile)) + rc = VERR_EOF; + else if (ferror(pStream->pFile)) + rc = VERR_READ_ERROR; + else + { + AssertMsgFailed(("This shouldn't happen\n")); + rc = VERR_INTERNAL_ERROR; + } + } +#endif /* !RTSTREAM_STANDALONE */ + if (RT_FAILURE(rc)) + ASMAtomicWriteS32(&pStream->i32Error, rc); + } +#ifdef RTSTREAM_STANDALONE + rtStrmUnlock(pStream); +#endif + return rc; +} + + +/** + * Check if the input text is valid UTF-8. + * + * @returns true/false. + * @param pvBuf Pointer to the buffer. + * @param cbBuf Size of the buffer. + */ +static bool rtStrmIsUtf8Text(const void *pvBuf, size_t cbBuf) +{ + NOREF(pvBuf); + NOREF(cbBuf); + /** @todo not sure this is a good idea... Better redefine RTStrmWrite. */ + return false; +} + + +#if defined(RT_OS_WINDOWS) && !defined(RTSTREAM_STANDALONE) + +/** + * Check if the stream is for a Window console. + * + * @returns true / false. + * @param pStream The stream. + * @param phCon Where to return the console handle. + */ +static bool rtStrmIsConsoleUnlocked(PRTSTREAM pStream, HANDLE *phCon) +{ + int fh = fileno(pStream->pFile); + if (isatty(fh)) + { + DWORD dwMode; + HANDLE hCon = (HANDLE)_get_osfhandle(fh); + if (GetConsoleMode(hCon, &dwMode)) + { + *phCon = hCon; + return true; + } + } + return false; +} + + +static int rtStrmWriteWinConsoleLocked(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten, HANDLE hCon) +{ + int rc; +# ifdef HAVE_FWRITE_UNLOCKED + if (!fflush_unlocked(pStream->pFile)) +# else + if (!fflush(pStream->pFile)) +# endif + { + /** @todo Consider buffering later. For now, we'd rather correct output than + * fast output. */ + DWORD cwcWritten = 0; + PRTUTF16 pwszSrc = NULL; + size_t cwcSrc = 0; + rc = RTStrToUtf16Ex((const char *)pvBuf, cbToWrite, &pwszSrc, 0, &cwcSrc); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + if (!WriteConsoleW(hCon, pwszSrc, (DWORD)cwcSrc, &cwcWritten, NULL)) + { + /* try write char-by-char to avoid heap problem. */ + cwcWritten = 0; + while (cwcWritten != cwcSrc) + { + DWORD cwcThis; + if (!WriteConsoleW(hCon, &pwszSrc[cwcWritten], 1, &cwcThis, NULL)) + { + if (!pcbWritten || cwcWritten == 0) + rc = RTErrConvertFromErrno(GetLastError()); + break; + } + if (cwcThis != 1) /* Unable to write current char (amount)? */ + break; + cwcWritten++; + } + } + if (RT_SUCCESS(rc)) + { + if (cwcWritten == cwcSrc) + { + if (pcbWritten) + *pcbWritten = cbToWrite; + } + else if (pcbWritten) + { + PCRTUTF16 pwszCur = pwszSrc; + const char *pszCur = (const char *)pvBuf; + while ((uintptr_t)(pwszCur - pwszSrc) < cwcWritten) + { + RTUNICP CpIgnored; + RTUtf16GetCpEx(&pwszCur, &CpIgnored); + RTStrGetCpEx(&pszCur, &CpIgnored); + } + *pcbWritten = pszCur - (const char *)pvBuf; + } + else + rc = VERR_WRITE_ERROR; + } + RTUtf16Free(pwszSrc); + } + } + else + rc = RTErrConvertFromErrno(errno); + return rc; +} + +#endif /* RT_OS_WINDOWS */ + +static int rtStrmWriteWorkerLocked(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten, bool fMustWriteAll) +{ +#ifdef RTSTREAM_STANDALONE + /* + * Check preconditions. + */ + Assert(pStream->enmBufDir == RTSTREAMBUFDIR_WRITE); + Assert(pStream->cbBufAlloc >= 256); + Assert(pStream->offBufFirst <= pStream->cbBufAlloc); + Assert(pStream->offBufEnd <= pStream->cbBufAlloc); + Assert(pStream->offBufFirst <= pStream->offBufEnd); + + /* + * We write everything via the buffer, letting the buffer flushing take + * care of console output hacks and similar. + */ + RT_NOREF(fMustWriteAll); + int rc = VINF_SUCCESS; + size_t cbTotal = 0; + if (cbToWrite > 0) + { +# ifdef RTSTREAM_WITH_TEXT_MODE + const char *pchLf; + if ( !pStream->fBinary + && (pchLf = (const char *)memchr(pvBuf, '\n', cbToWrite)) != NULL) + for (;;) + { + /* Deal with everything up to the newline. */ + size_t const cbToLf = (size_t)(pchLf - (const char *)pvBuf); + if (cbToLf > 0) + { + rc = rtStrmBufCopyTo(pStream, pvBuf, cbToLf, &cbTotal); + if (RT_FAILURE(rc)) + break; + } + + /* Copy the CRLF sequence into the buffer in one go to avoid complications. */ + if (pStream->cbBufAlloc - pStream->offBufEnd < 2) + { + rc = rtStrmBufFlushWrite(pStream, pStream->offBufEnd - pStream->offBufFirst); + if (RT_FAILURE(rc)) + break; + Assert(pStream->cbBufAlloc - pStream->offBufEnd >= 2); + } + pStream->pchBuf[pStream->offBufEnd++] = '\r'; + pStream->pchBuf[pStream->offBufEnd++] = '\n'; + + /* Advance past the newline. */ + pvBuf = (const char *)pvBuf + 1 + cbToLf; + cbTotal += 1 + cbToLf; + cbToWrite -= 1 + cbToLf; + if (!cbToWrite) + break; + + /* More newlines? */ + pchLf = (const char *)memchr(pvBuf, '\n', cbToWrite); + if (!pchLf) + { + rc = rtStrmBufCopyTo(pStream, pvBuf, cbToWrite, &cbTotal); + break; + } + } + else +# endif + rc = rtStrmBufCopyTo(pStream, pvBuf, cbToWrite, &cbTotal); + + /* + * If line buffered or unbuffered, we probably have to do some flushing now. + */ + if (RT_SUCCESS(rc) && pStream->enmBufStyle != RTSTREAMBUFSTYLE_FULL) + { + Assert(pStream->enmBufStyle == RTSTREAMBUFSTYLE_LINE || pStream->enmBufStyle == RTSTREAMBUFSTYLE_UNBUFFERED); + size_t cbInBuffer = pStream->offBufEnd - pStream->offBufFirst; + if (cbInBuffer > 0) + { + if ( pStream->enmBufStyle != RTSTREAMBUFSTYLE_LINE + || pStream->pchBuf[pStream->offBufEnd - 1] == '\n') + rc = rtStrmBufFlushWrite(pStream, cbInBuffer); + else + { + const char *pchToFlush = &pStream->pchBuf[pStream->offBufFirst]; + const char *pchLastLf = (const char *)memrchr(pchToFlush, '\n', cbInBuffer); + if (pchLastLf) + rc = rtStrmBufFlushWrite(pStream, (size_t)(&pchLastLf[1] - pchToFlush)); + } + } + } + } + if (pcbWritten) + *pcbWritten = cbTotal; + return rc; + + +#else + if (!fMustWriteAll) + { + IPRT_ALIGNMENT_CHECKS_DISABLE(); /* glibc / mempcpy again */ +# ifdef HAVE_FWRITE_UNLOCKED + *pcbWritten = fwrite_unlocked(pvBuf, 1, cbToWrite, pStream->pFile); +# else + *pcbWritten = fwrite(pvBuf, 1, cbToWrite, pStream->pFile); +# endif + IPRT_ALIGNMENT_CHECKS_ENABLE(); + if ( *pcbWritten == cbToWrite +# ifdef HAVE_FWRITE_UNLOCKED + || !ferror_unlocked(pStream->pFile)) +# else + || !ferror(pStream->pFile)) +# endif + return VINF_SUCCESS; + } + else + { + /* Must write it all! */ + IPRT_ALIGNMENT_CHECKS_DISABLE(); /* glibc / mempcpy again */ +# ifdef HAVE_FWRITE_UNLOCKED + size_t cbWritten = fwrite_unlocked(pvBuf, cbToWrite, 1, pStream->pFile); +# else + size_t cbWritten = fwrite(pvBuf, cbToWrite, 1, pStream->pFile); +# endif + if (pcbWritten) + *pcbWritten = cbWritten; + IPRT_ALIGNMENT_CHECKS_ENABLE(); + if (cbWritten == 1) + return VINF_SUCCESS; +# ifdef HAVE_FWRITE_UNLOCKED + if (!ferror_unlocked(pStream->pFile)) +# else + if (!ferror(pStream->pFile)) +# endif + return VINF_SUCCESS; /* WEIRD! But anyway... */ + } + return VERR_WRITE_ERROR; +#endif +} + + +/** + * Internal write API, stream lock already held. + * + * @returns IPRT status code. + * @param pStream The stream. + * @param pvBuf What to write. + * @param cbToWrite How much to write. + * @param pcbWritten Where to optionally return the number of bytes + * written. + * @param fSureIsText Set if we're sure this is UTF-8 text already. + */ +static int rtStrmWriteLocked(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten, bool fSureIsText) +{ +#ifdef RTSTREAM_STANDALONE + int rc = rtStrmBufCheckErrorAndSwitchToWriteMode(pStream); +#else + int rc = pStream->i32Error; +#endif + if (RT_FAILURE(rc)) + return rc; + if (pStream->fRecheckMode) + rtStreamRecheckMode(pStream); + +#if defined(RT_OS_WINDOWS) && !defined(RTSTREAM_STANDALONE) + /* + * Use the unicode console API when possible in order to avoid stuff + * getting lost in unnecessary code page translations. + */ + HANDLE hCon; + if (rtStrmIsConsoleUnlocked(pStream, &hCon)) + rc = rtStrmWriteWinConsoleLocked(pStream, pvBuf, cbToWrite, pcbWritten, hCon); +#else + if (0) { } +#endif /* RT_OS_WINDOWS && !RTSTREAM_STANDALONE */ + + /* + * If we're sure it's text output, convert it from UTF-8 to the current + * code page before printing it. + * + * Note! Partial writes are not supported in this scenario because we + * cannot easily report back a written length matching the input. + */ + /** @todo Skip this if the current code set is UTF-8. */ + else if ( pStream->fCurrentCodeSet + && !pStream->fBinary + && ( fSureIsText + || rtStrmIsUtf8Text(pvBuf, cbToWrite)) + ) + { + char *pszSrcFree = NULL; + const char *pszSrc = (const char *)pvBuf; + if (pszSrc[cbToWrite - 1]) + { + pszSrc = pszSrcFree = RTStrDupN(pszSrc, cbToWrite); + if (pszSrc == NULL) + rc = VERR_NO_STR_MEMORY; + } + if (RT_SUCCESS(rc)) + { + char *pszSrcCurCP; + rc = RTStrUtf8ToCurrentCP(&pszSrcCurCP, pszSrc); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + size_t cchSrcCurCP = strlen(pszSrcCurCP); + size_t cbWritten = 0; + rc = rtStrmWriteWorkerLocked(pStream, pszSrcCurCP, cchSrcCurCP, &cbWritten, true /*fMustWriteAll*/); + if (pcbWritten) + *pcbWritten = cbWritten == cchSrcCurCP ? cbToWrite : 0; + RTStrFree(pszSrcCurCP); + } + RTStrFree(pszSrcFree); + } + } + /* + * Otherwise, just write it as-is. + */ + else + rc = rtStrmWriteWorkerLocked(pStream, pvBuf, cbToWrite, pcbWritten, pcbWritten == NULL); + + /* + * Update error status on failure and return. + * + * We ignore failures from RTStrUtf8ToCurrentCP and RTStrToUtf16Ex regarding + * invalid UTF-8 encoding, as that's an input issue and shouldn't affect the + * stream state. + */ + if (RT_FAILURE(rc) && rc != VERR_INVALID_UTF8_ENCODING) + ASMAtomicWriteS32(&pStream->i32Error, rc); + return rc; +} + + +/** + * Internal write API. + * + * @returns IPRT status code. + * @param pStream The stream. + * @param pvBuf What to write. + * @param cbToWrite How much to write. + * @param pcbWritten Where to optionally return the number of bytes + * written. + * @param fSureIsText Set if we're sure this is UTF-8 text already. + */ +DECLINLINE(int) rtStrmWrite(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten, bool fSureIsText) +{ + rtStrmLock(pStream); + int rc = rtStrmWriteLocked(pStream, pvBuf, cbToWrite, pcbWritten, fSureIsText); + rtStrmUnlock(pStream); + return rc; +} + + +RTR3DECL(int) RTStrmWriteEx(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten) +{ + AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER); + return rtStrmWrite(pStream, pvBuf, cbToWrite, pcbWritten, false); +} + + +RTR3DECL(int) RTStrmGetCh(PRTSTREAM pStream) +{ + unsigned char ch; + int rc = RTStrmReadEx(pStream, &ch, 1, NULL); + if (RT_SUCCESS(rc)) + return ch; + return -1; +} + + +RTR3DECL(int) RTStrmPutCh(PRTSTREAM pStream, int ch) +{ + return rtStrmWrite(pStream, &ch, 1, NULL, true /*fSureIsText*/); +} + + +RTR3DECL(int) RTStrmPutStr(PRTSTREAM pStream, const char *pszString) +{ + size_t cch = strlen(pszString); + return rtStrmWrite(pStream, pszString, cch, NULL, true /*fSureIsText*/); +} + + +RTR3DECL(int) RTStrmGetLine(PRTSTREAM pStream, char *pszString, size_t cbString) +{ + AssertPtrReturn(pStream, VERR_INVALID_HANDLE); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE); + AssertReturn(pszString, VERR_INVALID_POINTER); + AssertReturn(cbString >= 2, VERR_INVALID_PARAMETER); + + rtStrmLock(pStream); + +#ifdef RTSTREAM_STANDALONE + int rc = rtStrmBufCheckErrorAndSwitchToReadMode(pStream); +#else + int rc = pStream->i32Error; +#endif + if (RT_SUCCESS(rc)) + { + cbString--; /* Reserve space for the terminator. */ + +#ifdef RTSTREAM_STANDALONE + char * const pszStringStart = pszString; +#endif + for (;;) + { +#ifdef RTSTREAM_STANDALONE + /* Make sure there is at least one character in the buffer: */ + size_t cbInBuffer = pStream->offBufEnd - pStream->offBufFirst; + if (cbInBuffer == 0) + { + rc = rtStrmBufFill(pStream); + if (RT_SUCCESS(rc)) + cbInBuffer = pStream->offBufEnd - pStream->offBufFirst; + else + break; + } + + /* Scan the buffer content terminating on a '\n', '\r\n' and '\0' sequence. */ + const char *pchSrc = &pStream->pchBuf[pStream->offBufFirst]; + const char *pchNewline = (const char *)memchr(pchSrc, '\n', cbInBuffer); + const char *pchTerm = (const char *)memchr(pchSrc, '\0', cbInBuffer); + size_t cbCopy; + size_t cbAdvance; + bool fStop = pchNewline || pchTerm; + if (!fStop) + cbAdvance = cbCopy = cbInBuffer; + else if (!pchTerm || (pchNewline && pchTerm && (uintptr_t)pchNewline < (uintptr_t)pchTerm)) + { + cbCopy = (size_t)(pchNewline - pchSrc); + cbAdvance = cbCopy + 1; + if (cbCopy && pchNewline[-1] == '\r') + cbCopy--; + else if (cbCopy == 0 && (uintptr_t)pszString > (uintptr_t)pszStringStart && pszString[-1] == '\r') + pszString--, cbString++; /* drop trailing '\r' that it turns out was followed by '\n' */ + } + else + { + cbCopy = (size_t)(pchTerm - pchSrc); + cbAdvance = cbCopy + 1; + } + + /* Adjust for available space in the destination buffer, copy over the string + characters and advance the buffer position (even on overflow). */ + if (cbCopy <= cbString) + pStream->offBufFirst += cbAdvance; + else + { + rc = VERR_BUFFER_OVERFLOW; + fStop = true; + cbCopy = cbString; + pStream->offBufFirst += cbString; + } + + memcpy(pszString, pchSrc, cbCopy); + pszString += cbCopy; + cbString -= cbCopy; + + if (fStop) + break; + +#else /* !RTSTREAM_STANDALONE */ +# ifdef HAVE_FWRITE_UNLOCKED /** @todo darwin + freebsd(?) has fgetc_unlocked but not fwrite_unlocked, optimize... */ + int ch = fgetc_unlocked(pStream->pFile); +# else + int ch = fgetc(pStream->pFile); +# endif + + /* Deal with \r\n sequences here. We'll return lone CR, but + treat CRLF as LF. */ + if (ch == '\r') + { +# ifdef HAVE_FWRITE_UNLOCKED /** @todo darwin + freebsd(?) has fgetc_unlocked but not fwrite_unlocked, optimize... */ + ch = fgetc_unlocked(pStream->pFile); +# else + ch = fgetc(pStream->pFile); +# endif + if (ch == '\n') + break; + + *pszString++ = '\r'; + if (--cbString <= 0) + { + /* yeah, this is an error, we dropped a character. */ + rc = VERR_BUFFER_OVERFLOW; + break; + } + } + + /* Deal with end of file. */ + if (ch == EOF) + { +# ifdef HAVE_FWRITE_UNLOCKED + if (feof_unlocked(pStream->pFile)) +# else + if (feof(pStream->pFile)) +# endif + { + rc = VERR_EOF; + break; + } +# ifdef HAVE_FWRITE_UNLOCKED + if (ferror_unlocked(pStream->pFile)) +# else + if (ferror(pStream->pFile)) +# endif + rc = VERR_READ_ERROR; + else + { + AssertMsgFailed(("This shouldn't happen\n")); + rc = VERR_INTERNAL_ERROR; + } + break; + } + + /* Deal with null terminator and (lone) new line. */ + if (ch == '\0' || ch == '\n') + break; + + /* No special character, append it to the return string. */ + *pszString++ = ch; + if (--cbString <= 0) + { + rc = VINF_BUFFER_OVERFLOW; + break; + } +#endif /* !RTSTREAM_STANDALONE */ + } + + *pszString = '\0'; + if (RT_FAILURE(rc)) + ASMAtomicWriteS32(&pStream->i32Error, rc); + } + + rtStrmUnlock(pStream); + return rc; +} + + +RTR3DECL(int) RTStrmFlush(PRTSTREAM pStream) +{ + AssertPtrReturn(pStream, VERR_INVALID_HANDLE); + AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE); + +#ifdef RTSTREAM_STANDALONE + rtStrmLock(pStream); + int rc = rtStrmBufFlushWriteMaybe(pStream, true /*fInvalidate*/); + rtStrmUnlock(pStream); + return rc; + +#else + if (!fflush(pStream->pFile)) + return VINF_SUCCESS; + return RTErrConvertFromErrno(errno); +#endif +} + + +/** + * Output callback. + * + * @returns number of bytes written. + * @param pvArg User argument. + * @param pachChars Pointer to an array of utf-8 characters. + * @param cchChars Number of bytes in the character array pointed to by pachChars. + */ +static DECLCALLBACK(size_t) rtstrmOutput(void *pvArg, const char *pachChars, size_t cchChars) +{ + if (cchChars) + rtStrmWriteLocked((PRTSTREAM)pvArg, pachChars, cchChars, NULL, true /*fSureIsText*/); + /* else: ignore termination call. */ + return cchChars; +} + + +RTR3DECL(int) RTStrmPrintfV(PRTSTREAM pStream, const char *pszFormat, va_list args) +{ + AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER); + int rc = pStream->i32Error; + if (RT_SUCCESS(rc)) + { + rtStrmLock(pStream); +// pStream->fShouldFlush = true; + rc = (int)RTStrFormatV(rtstrmOutput, pStream, NULL, NULL, pszFormat, args); + rtStrmUnlock(pStream); + Assert(rc >= 0); + } + else + rc = -1; + return rc; +} + + +RTR3DECL(int) RTStrmPrintf(PRTSTREAM pStream, const char *pszFormat, ...) +{ + va_list args; + va_start(args, pszFormat); + int rc = RTStrmPrintfV(pStream, pszFormat, args); + va_end(args); + return rc; +} + + +RTDECL(void) RTStrmDumpPrintfV(void *pvUser, const char *pszFormat, va_list va) +{ + RTStrmPrintfV(pvUser ? (PRTSTREAM)pvUser : g_pStdOut, pszFormat, va); +} + + +RTR3DECL(int) RTPrintfV(const char *pszFormat, va_list args) +{ + return RTStrmPrintfV(g_pStdOut, pszFormat, args); +} + + +RTR3DECL(int) RTPrintf(const char *pszFormat, ...) +{ + va_list args; + va_start(args, pszFormat); + int rc = RTStrmPrintfV(g_pStdOut, pszFormat, args); + va_end(args); + return rc; +} + + +/** + * Outputs @a cchIndent spaces. + */ +static void rtStrmWrapppedIndent(RTSTRMWRAPPEDSTATE *pState, uint32_t cchIndent) +{ + static const char s_szSpaces[] = " "; + while (cchIndent) + { + uint32_t cchToWrite = RT_MIN(cchIndent, sizeof(s_szSpaces) - 1); + int rc = RTStrmWrite(pState->pStream, s_szSpaces, cchToWrite); + if (RT_SUCCESS(rc)) + cchIndent -= cchToWrite; + else + { + pState->rcStatus = rc; + break; + } + } +} + + +/** + * Flushes the current line. + * + * @param pState The wrapped output state. + * @param fPartial Set if partial flush due to buffer overflow, clear when + * flushing due to '\n'. + */ +static void rtStrmWrappedFlushLine(RTSTRMWRAPPEDSTATE *pState, bool fPartial) +{ + /* + * Check indentation in case we need to split the line later. + */ + uint32_t cchIndent = pState->cchIndent; + if (cchIndent == UINT32_MAX) + { + pState->cchIndent = 0; + cchIndent = pState->cchHangingIndent; + while (RT_C_IS_BLANK(pState->szLine[cchIndent])) + cchIndent++; + } + + /* + * Do the flushing. + */ + uint32_t cchLine = pState->cchLine; + Assert(cchLine < sizeof(pState->szLine)); + while (cchLine >= pState->cchWidth || !fPartial) + { + /* + * Hopefully we don't need to do any wrapping ... + */ + uint32_t offSplit; + if (pState->cchIndent + cchLine <= pState->cchWidth) + { + if (!fPartial) + { + rtStrmWrapppedIndent(pState, pState->cchIndent); + pState->szLine[cchLine] = '\n'; + int rc = RTStrmWrite(pState->pStream, pState->szLine, cchLine + 1); + if (RT_FAILURE(rc)) + pState->rcStatus = rc; + pState->cLines += 1; + pState->cchLine = 0; + pState->cchIndent = UINT32_MAX; + return; + } + + /* + * ... no such luck. + */ + offSplit = cchLine; + } + else + offSplit = pState->cchWidth - pState->cchIndent; + + /* Find the start of the current word: */ + while (offSplit > 0 && !RT_C_IS_BLANK(pState->szLine[offSplit - 1])) + offSplit--; + + /* Skip spaces. */ + while (offSplit > 0 && RT_C_IS_BLANK(pState->szLine[offSplit - 1])) + offSplit--; + uint32_t offNextLine = offSplit; + + /* If the first word + indent is wider than the screen width, so just output it in full. */ + if (offSplit == 0) /** @todo Split words, look for hyphen... This code is currently a bit crude. */ + { + while (offSplit < cchLine && !RT_C_IS_BLANK(pState->szLine[offSplit])) + offSplit++; + offNextLine = offSplit; + } + + while (offNextLine < cchLine && RT_C_IS_BLANK(pState->szLine[offNextLine])) + offNextLine++; + + /* + * Output and advance. + */ + rtStrmWrapppedIndent(pState, pState->cchIndent); + int rc = RTStrmWrite(pState->pStream, pState->szLine, offSplit); + if (RT_SUCCESS(rc)) + rc = RTStrmPutCh(pState->pStream, '\n'); + if (RT_FAILURE(rc)) + pState->rcStatus = rc; + + cchLine -= offNextLine; + pState->cchLine = cchLine; + pState->cLines += 1; + pState->cchIndent = cchIndent; + memmove(&pState->szLine[0], &pState->szLine[offNextLine], cchLine); + } + + /* The indentation level is reset for each '\n' we process, so only save cchIndent if partial. */ + pState->cchIndent = fPartial ? cchIndent : UINT32_MAX; +} + + +/** + * @callback_method_impl{FNRTSTROUTPUT} + */ +static DECLCALLBACK(size_t) rtStrmWrappedOutput(void *pvArg, const char *pachChars, size_t cbChars) +{ + RTSTRMWRAPPEDSTATE *pState = (RTSTRMWRAPPEDSTATE *)pvArg; + size_t const cchRet = cbChars; + while (cbChars > 0) + { + if (*pachChars == '\n') + { + rtStrmWrappedFlushLine(pState, false /*fPartial*/); + pachChars++; + cbChars--; + } + else + { + const char *pszEol = (const char *)memchr(pachChars, '\n', cbChars); + size_t cchToCopy = pszEol ? (size_t)(pszEol - pachChars) : cbChars; + uint32_t cchLine = pState->cchLine; + Assert(cchLine < sizeof(pState->szLine)); + bool const fFlush = cchLine + cchToCopy >= sizeof(pState->szLine); + if (fFlush) + cchToCopy = cchToCopy - sizeof(pState->szLine) - 1; + + pState->cchLine = cchLine + (uint32_t)cchToCopy; + memcpy(&pState->szLine[cchLine], pachChars, cchToCopy); + + pachChars += cchToCopy; + cbChars -= cchToCopy; + + if (fFlush) + rtStrmWrappedFlushLine(pState, true /*fPartial*/); + } + } + return cchRet; +} + + +RTDECL(int32_t) RTStrmWrappedPrintfV(PRTSTREAM pStream, uint32_t fFlags, const char *pszFormat, va_list va) +{ + /* + * Figure the output width and set up the rest of the output state. + */ + RTSTRMWRAPPEDSTATE State; + State.pStream = pStream; + State.cchLine = fFlags & RTSTRMWRAPPED_F_LINE_OFFSET_MASK; + State.cLines = 0; + State.rcStatus = VINF_SUCCESS; + State.cchIndent = UINT32_MAX; + State.cchHangingIndent = 0; + if (fFlags & RTSTRMWRAPPED_F_HANGING_INDENT) + { + State.cchHangingIndent = (fFlags & RTSTRMWRAPPED_F_HANGING_INDENT_MASK) >> RTSTRMWRAPPED_F_HANGING_INDENT_SHIFT; + if (!State.cchHangingIndent) + State.cchHangingIndent = 4; + } + + int rc = RTStrmQueryTerminalWidth(pStream, &State.cchWidth); + if (RT_SUCCESS(rc)) + State.cchWidth = RT_MIN(State.cchWidth, RTSTRMWRAPPED_F_LINE_OFFSET_MASK + 1); + else + { + State.cchWidth = (uint32_t)fFlags & RTSTRMWRAPPED_F_NON_TERMINAL_WIDTH_MASK; + if (!State.cchWidth) + State.cchWidth = 80; + } + if (State.cchWidth < 32) + State.cchWidth = 32; + //State.cchWidth -= 1; /* necessary here? */ + + /* + * Do the formatting. + */ + RTStrFormatV(rtStrmWrappedOutput, &State, NULL, NULL, pszFormat, va); + + /* + * Returning is simple if the buffer is empty. Otherwise we'll have to + * perform a partial flush and write out whatever is left ourselves. + */ + if (RT_SUCCESS(State.rcStatus)) + { + if (State.cchLine == 0) + return State.cLines << 16; + + rtStrmWrappedFlushLine(&State, true /*fPartial*/); + if (RT_SUCCESS(State.rcStatus) && State.cchLine > 0) + { + rtStrmWrapppedIndent(&State, State.cchIndent); + State.rcStatus = RTStrmWrite(State.pStream, State.szLine, State.cchLine); + } + if (RT_SUCCESS(State.rcStatus)) + return RT_MIN(State.cchIndent + State.cchLine, RTSTRMWRAPPED_F_LINE_OFFSET_MASK) | (State.cLines << 16); + } + return State.rcStatus; +} + + +RTDECL(int32_t) RTStrmWrappedPrintf(PRTSTREAM pStream, uint32_t fFlags, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int32_t rcRet = RTStrmWrappedPrintfV(pStream, fFlags, pszFormat, va); + va_end(va); + return rcRet; +} + |