summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/r3/stream.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Runtime/r3/stream.cpp')
-rw-r--r--src/VBox/Runtime/r3/stream.cpp2698
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;
+}
+