summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/r3/stream.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/VBox/Runtime/r3/stream.cpp1292
1 files changed, 1292 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..85496850
--- /dev/null
+++ b/src/VBox/Runtime/r3/stream.cpp
@@ -0,0 +1,1292 @@
+/* $Id: stream.cpp $ */
+/** @file
+ * IPRT - I/O Stream.
+ */
+
+/*
+ * Copyright (C) 2006-2020 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE 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.
+ */
+
+
+
+#if defined(RT_OS_LINUX) /* PORTME: check for the _unlocked functions in stdio.h */
+#define HAVE_FWRITE_UNLOCKED
+#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/alloc.h>
+#include <iprt/err.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+
+#include "internal/alignmentchecks.h"
+#include "internal/magics.h"
+
+#include <stdio.h>
+#include <errno.h>
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+# include <io.h>
+# include <fcntl.h>
+#endif
+#ifdef RT_OS_WINDOWS
+# include <iprt/utf16.h>
+# include <iprt/win/windows.h>
+#else
+# include <termios.h>
+# include <unistd.h>
+# include <sys/ioctl.h>
+#endif
+
+#ifdef RT_OS_OS2
+# define _O_TEXT O_TEXT
+# define _O_BINARY O_BINARY
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * File stream.
+ */
+typedef struct RTSTREAM
+{
+ /** Magic value used to validate the stream. (RTSTREAM_MAGIC) */
+ uint32_t u32Magic;
+ /** File stream error. */
+ int32_t volatile i32Error;
+ /** Pointer to the LIBC file stream. */
+ FILE *pFile;
+ /** 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;
+#ifndef HAVE_FWRITE_UNLOCKED
+ /** Critical section for serializing access to the stream. */
+ PRTCRITSECT pCritSect;
+#endif
+} RTSTREAM;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The standard input stream. */
+static RTSTREAM g_StdIn =
+{
+ RTSTREAM_MAGIC,
+ 0,
+ stdin,
+ /*.fCurrentCodeSet = */ true,
+ /*.fBinary = */ false,
+ /*.fRecheckMode = */ true
+#ifndef HAVE_FWRITE_UNLOCKED
+ , NULL
+#endif
+};
+
+/** The standard error stream. */
+static RTSTREAM g_StdErr =
+{
+ RTSTREAM_MAGIC,
+ 0,
+ stderr,
+ /*.fCurrentCodeSet = */ true,
+ /*.fBinary = */ false,
+ /*.fRecheckMode = */ true
+#ifndef HAVE_FWRITE_UNLOCKED
+ , NULL
+#endif
+};
+
+/** The standard output stream. */
+static RTSTREAM g_StdOut =
+{
+ RTSTREAM_MAGIC,
+ 0,
+ stdout,
+ /*.fCurrentCodeSet = */ true,
+ /*.fBinary = */ false,
+ /*.fRecheckMode = */ true
+#ifndef HAVE_FWRITE_UNLOCKED
+ , 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;
+
+
+#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. */
+ int rc = RTCritSectInitEx(pCritSect, RTCRITSECT_FLAGS_NO_NESTING,
+ 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.
+ * @param pszMode The open mode. See fopen() standard.
+ * Format: <a|r|w>[+][b|t]
+ * @param ppStream Where to store the opened stream.
+ */
+RTR3DECL(int) RTStrmOpen(const char *pszFilename, const char *pszMode, PRTSTREAM *ppStream)
+{
+ /*
+ * Validate input.
+ */
+ if (!pszMode || !*pszMode)
+ {
+ AssertMsgFailed(("No pszMode!\n"));
+ return VERR_INVALID_PARAMETER;
+ }
+ if (!pszFilename)
+ {
+ AssertMsgFailed(("No pszFilename!\n"));
+ return VERR_INVALID_PARAMETER;
+ }
+ bool fOk = true;
+ bool fBinary = false;
+ switch (*pszMode)
+ {
+ case 'a':
+ case 'w':
+ case 'r':
+ switch (pszMode[1])
+ {
+ case '\0':
+ break;
+
+ case '+':
+ switch (pszMode[2])
+ {
+ case '\0':
+ break;
+
+ //case 't':
+ // break;
+
+ case 'b':
+ fBinary = true;
+ break;
+
+ default:
+ fOk = false;
+ break;
+ }
+ break;
+
+ //case 't':
+ // break;
+
+ case 'b':
+ fBinary = true;
+ break;
+
+ default:
+ fOk = false;
+ break;
+ }
+ break;
+ default:
+ fOk = false;
+ break;
+ }
+ if (!fOk)
+ {
+ AssertMsgFailed(("Invalid pszMode='%s', '<a|r|w>[+][b|t]'\n", pszMode));
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Allocate the stream handle and try open it.
+ */
+ PRTSTREAM pStream = (PRTSTREAM)RTMemAlloc(sizeof(*pStream));
+ if (pStream)
+ {
+ pStream->u32Magic = RTSTREAM_MAGIC;
+ pStream->i32Error = VINF_SUCCESS;
+ pStream->fCurrentCodeSet = false;
+ pStream->fBinary = fBinary;
+ pStream->fRecheckMode = false;
+#ifndef HAVE_FWRITE_UNLOCKED
+ pStream->pCritSect = NULL;
+#endif /* HAVE_FWRITE_UNLOCKED */
+ pStream->pFile = fopen(pszFilename, pszMode);
+ if (pStream->pFile)
+ {
+ *ppStream = pStream;
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pStream);
+ return RTErrConvertFromErrno(errno);
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Opens a file stream.
+ *
+ * @returns iprt status code.
+ * @param pszMode The open mode. See fopen() standard.
+ * Format: <a|r|w>[+][b|t]
+ * @param ppStream Where to store the opened stream.
+ * @param pszFilenameFmt Filename path format string.
+ * @param args Arguments to the format string.
+ */
+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;
+}
+
+
+/**
+ * Opens a file stream.
+ *
+ * @returns iprt status code.
+ * @param pszMode The open mode. See fopen() standard.
+ * Format: <a|r|w>[+][b|t]
+ * @param ppStream Where to store the opened stream.
+ * @param pszFilenameFmt Filename path format string.
+ * @param ... Arguments to the format string.
+ */
+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;
+}
+
+
+/**
+ * Closes the specified stream.
+ *
+ * @returns iprt status code.
+ * @param pStream The stream to close.
+ */
+RTR3DECL(int) RTStrmClose(PRTSTREAM pStream)
+{
+ if (!pStream)
+ return VINF_SUCCESS;
+ AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER);
+
+ if (!fclose(pStream->pFile))
+ {
+ pStream->u32Magic = 0xdeaddead;
+ pStream->pFile = NULL;
+#ifndef HAVE_FWRITE_UNLOCKED
+ if (pStream->pCritSect)
+ {
+ RTCritSectEnter(pStream->pCritSect);
+ RTCritSectLeave(pStream->pCritSect);
+ RTCritSectDelete(pStream->pCritSect);
+ RTMemFree(pStream->pCritSect);
+ pStream->pCritSect = NULL;
+ }
+#endif
+ RTMemFree(pStream);
+ return VINF_SUCCESS;
+ }
+
+ return RTErrConvertFromErrno(errno);
+}
+
+
+/**
+ * Get the pending error of the stream.
+ *
+ * @returns iprt status code. of the stream.
+ * @param pStream The stream.
+ */
+RTR3DECL(int) RTStrmError(PRTSTREAM pStream)
+{
+ AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER);
+ return pStream->i32Error;
+}
+
+
+/**
+ * Clears stream error condition.
+ *
+ * All stream operations save RTStrmClose and this will fail
+ * while an error is asserted on the stream
+ *
+ * @returns iprt status code.
+ * @param pStream The stream.
+ */
+RTR3DECL(int) RTStrmClearError(PRTSTREAM pStream)
+{
+ AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER);
+
+ clearerr(pStream->pFile);
+ 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) RTStrmInputGetEchoChars(PRTSTREAM pStream, bool *pfEchoChars)
+{
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
+ AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pfEchoChars, VERR_INVALID_POINTER);
+
+ int fh = fileno(pStream->pFile);
+ if (isatty(fh))
+ {
+#ifdef RT_OS_WINDOWS
+ DWORD dwMode;
+ HANDLE hCon = (HANDLE)_get_osfhandle(fh);
+ if (GetConsoleMode(hCon, &dwMode))
+ *pfEchoChars = RT_BOOL(dwMode & ENABLE_ECHO_INPUT);
+ else
+ rc = RTErrConvertFromWin32(GetLastError());
+#else
+ struct termios Termios;
+
+ int rcPosix = tcgetattr(fh, &Termios);
+ if (!rcPosix)
+ *pfEchoChars = RT_BOOL(Termios.c_lflag & ECHO);
+ else
+ rc = RTErrConvertFromErrno(errno);
+#endif
+ }
+ else
+ rc = VERR_INVALID_HANDLE;
+
+ return rc;
+}
+
+
+RTR3DECL(int) RTStrmInputSetEchoChars(PRTSTREAM pStream, bool fEchoChars)
+{
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
+ AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
+
+ int fh = fileno(pStream->pFile);
+ if (isatty(fh))
+ {
+#ifdef RT_OS_WINDOWS
+ DWORD dwMode;
+ HANDLE hCon = (HANDLE)_get_osfhandle(fh);
+ if (GetConsoleMode(hCon, &dwMode))
+ {
+ if (fEchoChars)
+ dwMode |= ENABLE_ECHO_INPUT;
+ else
+ dwMode &= ~ENABLE_ECHO_INPUT;
+ if (!SetConsoleMode(hCon, dwMode))
+ rc = RTErrConvertFromWin32(GetLastError());
+ }
+ else
+ rc = RTErrConvertFromWin32(GetLastError());
+#else
+ struct termios Termios;
+
+ int rcPosix = tcgetattr(fh, &Termios);
+ if (!rcPosix)
+ {
+ if (fEchoChars)
+ Termios.c_lflag |= ECHO;
+ else
+ Termios.c_lflag &= ~ECHO;
+
+ rcPosix = tcsetattr(fh, TCSAFLUSH, &Termios);
+ if (rcPosix != 0)
+ rc = RTErrConvertFromErrno(errno);
+ }
+ else
+ rc = RTErrConvertFromErrno(errno);
+#endif
+ }
+ else
+ rc = VERR_INVALID_HANDLE;
+
+ return rc;
+}
+
+
+RTR3DECL(bool) RTStrmIsTerminal(PRTSTREAM pStream)
+{
+ AssertPtrReturn(pStream, false);
+ AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, false);
+
+ if (pStream->pFile)
+ {
+ int fh = fileno(pStream->pFile);
+ if (isatty(fh))
+ {
+#ifdef RT_OS_WINDOWS
+ DWORD dwMode;
+ HANDLE hCon = (HANDLE)_get_osfhandle(fh);
+ if (GetConsoleMode(hCon, &dwMode))
+ return true;
+#else
+ return true;
+#endif
+ }
+ }
+ return false;
+}
+
+
+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 (pStream->pFile)
+ {
+ int fh = fileno(pStream->pFile);
+ if (isatty(fh))
+ {
+#ifdef RT_OS_WINDOWS
+ CONSOLE_SCREEN_BUFFER_INFO Info;
+ HANDLE hCon = (HANDLE)_get_osfhandle(fh);
+ RT_ZERO(Info);
+ if (GetConsoleScreenBufferInfo(hCon, &Info))
+ {
+ *pcchWidth = Info.dwSize.X ? Info.dwSize.X : 80;
+ return VINF_SUCCESS;
+ }
+ return RTErrConvertFromWin32(GetLastError());
+
+#elif defined(TIOCGWINSZ) || !defined(RT_OS_OS2) /* only OS/2 should currently miss this */
+ struct winsize Info;
+ RT_ZERO(Info);
+ int rc = ioctl(fh, TIOCGWINSZ, &Info);
+ if (rc >= 0)
+ {
+ *pcchWidth = Info.ws_col ? Info.ws_col : 80;
+ return VINF_SUCCESS;
+ }
+ return RTErrConvertFromErrno(errno);
+#endif
+ }
+ }
+ return VERR_INVALID_FUNCTION;
+}
+
+
+
+/**
+ * Rewinds the stream.
+ *
+ * Stream errors will be reset on success.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pStream The stream.
+ *
+ * @remarks Not all streams are rewindable and that behavior is currently
+ * undefined for those.
+ */
+RTR3DECL(int) RTStrmRewind(PRTSTREAM pStream)
+{
+ AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
+ AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
+
+ int rc;
+ clearerr(pStream->pFile);
+ errno = 0;
+ if (!fseek(pStream->pFile, 0, SEEK_SET))
+ {
+ ASMAtomicWriteS32(&pStream->i32Error, VINF_SUCCESS);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ rc = RTErrConvertFromErrno(errno);
+ ASMAtomicWriteS32(&pStream->i32Error, rc);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Recheck the stream mode.
+ *
+ * @param pStream The stream (locked).
+ */
+static void rtStreamRecheckMode(PRTSTREAM pStream)
+{
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ 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;
+}
+
+
+/**
+ * Reads from a file stream.
+ *
+ * @returns iprt status code.
+ * @param pStream The stream.
+ * @param pvBuf Where to put the read bits.
+ * Must be cbRead bytes or more.
+ * @param cbRead Number of bytes to read.
+ * @param pcbRead Where to store the number of bytes actually read.
+ * If NULL cbRead bytes are read or an error is returned.
+ */
+RTR3DECL(int) RTStrmReadEx(PRTSTREAM pStream, void *pvBuf, size_t cbRead, size_t *pcbRead)
+{
+ AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER);
+
+ int rc = pStream->i32Error;
+ if (RT_SUCCESS(rc))
+ {
+ if (pStream->fRecheckMode)
+ rtStreamRecheckMode(pStream);
+
+ if (pcbRead)
+ {
+ /*
+ * Can do with a partial read.
+ */
+ *pcbRead = fread(pvBuf, 1, cbRead, pStream->pFile);
+ if ( *pcbRead == cbRead
+ || !ferror(pStream->pFile))
+ return VINF_SUCCESS;
+ if (feof(pStream->pFile))
+ {
+ if (*pcbRead)
+ return VINF_EOF;
+ rc = 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, cbRead, 1, pStream->pFile) == 1)
+ return VINF_SUCCESS;
+
+ /* possible error/eof. */
+ 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;
+ }
+ }
+ ASMAtomicWriteS32(&pStream->i32Error, rc);
+ }
+ 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;
+}
+
+
+#ifdef RT_OS_WINDOWS
+/**
+ * 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;
+}
+#endif /* RT_OS_WINDOWS */
+
+
+/**
+ * Internal write API, stream lock already held.
+ *
+ * @returns IPRT status code.
+ * @param pStream The stream.
+ * @param pvBuf What to write.
+ * @param cbWrite 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 cbWrite, size_t *pcbWritten,
+ bool fSureIsText)
+{
+ int rc = pStream->i32Error;
+ if (RT_FAILURE(rc))
+ return rc;
+ if (pStream->fRecheckMode)
+ rtStreamRecheckMode(pStream);
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * 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))
+ {
+# 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, cbWrite, &pwszSrc, 0, &cwcSrc);
+ 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 = cbWrite;
+ }
+ 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);
+ if (RT_FAILURE(rc))
+ ASMAtomicWriteS32(&pStream->i32Error, rc);
+ return rc;
+ }
+#endif /* RT_OS_WINDOWS */
+
+ /*
+ * 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. */
+ if ( pStream->fCurrentCodeSet
+ && !pStream->fBinary
+ && ( fSureIsText
+ || rtStrmIsUtf8Text(pvBuf, cbWrite))
+ )
+ {
+ char *pszSrcFree = NULL;
+ const char *pszSrc = (const char *)pvBuf;
+ if (pszSrc[cbWrite - 1])
+ {
+ pszSrc = pszSrcFree = RTStrDupN(pszSrc, cbWrite);
+ if (pszSrc == NULL)
+ rc = VERR_NO_STR_MEMORY;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ char *pszSrcCurCP;
+ rc = RTStrUtf8ToCurrentCP(&pszSrcCurCP, pszSrc);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cchSrcCurCP = strlen(pszSrcCurCP);
+ IPRT_ALIGNMENT_CHECKS_DISABLE(); /* glibc / mempcpy again */
+#ifdef HAVE_FWRITE_UNLOCKED
+ ssize_t cbWritten = fwrite_unlocked(pszSrcCurCP, cchSrcCurCP, 1, pStream->pFile);
+#else
+ ssize_t cbWritten = fwrite(pszSrcCurCP, cchSrcCurCP, 1, pStream->pFile);
+#endif
+ IPRT_ALIGNMENT_CHECKS_ENABLE();
+ if (cbWritten == 1)
+ {
+ if (pcbWritten)
+ *pcbWritten = cbWrite;
+ }
+#ifdef HAVE_FWRITE_UNLOCKED
+ else if (!ferror_unlocked(pStream->pFile))
+#else
+ else if (!ferror(pStream->pFile))
+#endif
+ {
+ if (pcbWritten)
+ *pcbWritten = 0;
+ }
+ else
+ rc = VERR_WRITE_ERROR;
+ RTStrFree(pszSrcCurCP);
+ }
+ RTStrFree(pszSrcFree);
+ }
+
+ if (RT_FAILURE(rc))
+ ASMAtomicWriteS32(&pStream->i32Error, rc);
+ return rc;
+ }
+
+ /*
+ * Otherwise, just write it as-is.
+ */
+ if (pcbWritten)
+ {
+ IPRT_ALIGNMENT_CHECKS_DISABLE(); /* glibc / mempcpy again */
+#ifdef HAVE_FWRITE_UNLOCKED
+ *pcbWritten = fwrite_unlocked(pvBuf, 1, cbWrite, pStream->pFile);
+#else
+ *pcbWritten = fwrite(pvBuf, 1, cbWrite, pStream->pFile);
+#endif
+ IPRT_ALIGNMENT_CHECKS_ENABLE();
+ if ( *pcbWritten == cbWrite
+#ifdef HAVE_FWRITE_UNLOCKED
+ || !ferror_unlocked(pStream->pFile))
+#else
+ || !ferror(pStream->pFile))
+#endif
+ return VINF_SUCCESS;
+ rc = VERR_WRITE_ERROR;
+ }
+ else
+ {
+ /* Must write it all! */
+ IPRT_ALIGNMENT_CHECKS_DISABLE(); /* glibc / mempcpy again */
+#ifdef HAVE_FWRITE_UNLOCKED
+ size_t cbWritten = fwrite_unlocked(pvBuf, cbWrite, 1, pStream->pFile);
+#else
+ size_t cbWritten = fwrite(pvBuf, cbWrite, 1, pStream->pFile);
+#endif
+ 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... */
+
+ rc = VERR_WRITE_ERROR;
+ }
+ ASMAtomicWriteS32(&pStream->i32Error, rc);
+
+ return rc;
+}
+
+
+/**
+ * Internal write API.
+ *
+ * @returns IPRT status code.
+ * @param pStream The stream.
+ * @param pvBuf What to write.
+ * @param cbWrite 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 rtStrmWrite(PRTSTREAM pStream, const void *pvBuf, size_t cbWrite, size_t *pcbWritten, bool fSureIsText)
+{
+ rtStrmLock(pStream);
+ int rc = rtStrmWriteLocked(pStream, pvBuf, cbWrite, pcbWritten, fSureIsText);
+ rtStrmUnlock(pStream);
+ return rc;
+}
+
+
+/**
+ * Writes to a file stream.
+ *
+ * @returns iprt status code.
+ * @param pStream The stream.
+ * @param pvBuf Where to get the bits to write from.
+ * @param cbWrite Number of bytes to write.
+ * @param pcbWritten Where to store the number of bytes actually written.
+ * If NULL cbWrite bytes are written or an error is returned.
+ */
+RTR3DECL(int) RTStrmWriteEx(PRTSTREAM pStream, const void *pvBuf, size_t cbWrite, size_t *pcbWritten)
+{
+ AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER);
+ return rtStrmWrite(pStream, pvBuf, cbWrite, pcbWritten, false);
+}
+
+
+/**
+ * Reads a character from a file stream.
+ *
+ * @returns The char as an unsigned char cast to int.
+ * @returns -1 on failure.
+ * @param pStream The stream.
+ */
+RTR3DECL(int) RTStrmGetCh(PRTSTREAM pStream)
+{
+ unsigned char ch;
+ int rc = RTStrmReadEx(pStream, &ch, 1, NULL);
+ if (RT_SUCCESS(rc))
+ return ch;
+ return -1;
+}
+
+
+/**
+ * Writes a character to a file stream.
+ *
+ * @returns iprt status code.
+ * @param pStream The stream.
+ * @param ch The char to write.
+ */
+RTR3DECL(int) RTStrmPutCh(PRTSTREAM pStream, int ch)
+{
+ return rtStrmWrite(pStream, &ch, 1, NULL, true /*fSureIsText*/);
+}
+
+
+/**
+ * Writes a string to a file stream.
+ *
+ * @returns iprt status code.
+ * @param pStream The stream.
+ * @param pszString The string to write.
+ * No newlines or anything is appended or prepended.
+ * The terminating '\\0' is not written, of course.
+ */
+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)
+{
+ AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER);
+ int rc;
+ if (pszString && cbString > 1)
+ {
+ rc = pStream->i32Error;
+ if (RT_SUCCESS(rc))
+ {
+ cbString--; /* save space for the terminator. */
+ rtStrmLock(pStream);
+ for (;;)
+ {
+#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;
+ }
+ }
+ rtStrmUnlock(pStream);
+
+ *pszString = '\0';
+ if (RT_FAILURE(rc))
+ ASMAtomicWriteS32(&pStream->i32Error, rc);
+ }
+ }
+ else
+ {
+ AssertMsgFailed(("no buffer or too small buffer!\n"));
+ rc = VERR_INVALID_PARAMETER;
+ }
+ return rc;
+}
+
+
+/**
+ * Flushes a stream.
+ *
+ * @returns iprt status code.
+ * @param pStream The stream to flush.
+ */
+RTR3DECL(int) RTStrmFlush(PRTSTREAM pStream)
+{
+ if (!fflush(pStream->pFile))
+ return VINF_SUCCESS;
+ return RTErrConvertFromErrno(errno);
+}
+
+
+/**
+ * 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;
+}
+
+
+/**
+ * Prints a formatted string to the specified stream.
+ *
+ * @returns Number of bytes printed.
+ * @param pStream The stream to print to.
+ * @param pszFormat IPRT format string.
+ * @param args Arguments specified by pszFormat.
+ */
+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;
+}
+
+
+/**
+ * Prints a formatted string to the specified stream.
+ *
+ * @returns Number of bytes printed.
+ * @param pStream The stream to print to.
+ * @param pszFormat IPRT format string.
+ * @param ... Arguments specified by pszFormat.
+ */
+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;
+}
+
+
+/**
+ * Dumper vprintf-like function outputting to a stream.
+ *
+ * @param pvUser The stream to print to. NULL means standard output.
+ * @param pszFormat Runtime format string.
+ * @param va Arguments specified by pszFormat.
+ */
+RTDECL(void) RTStrmDumpPrintfV(void *pvUser, const char *pszFormat, va_list va)
+{
+ RTStrmPrintfV(pvUser ? (PRTSTREAM)pvUser : g_pStdOut, pszFormat, va);
+}
+
+
+/**
+ * Prints a formatted string to the standard output stream (g_pStdOut).
+ *
+ * @returns Number of bytes printed.
+ * @param pszFormat IPRT format string.
+ * @param args Arguments specified by pszFormat.
+ */
+RTR3DECL(int) RTPrintfV(const char *pszFormat, va_list args)
+{
+ return RTStrmPrintfV(g_pStdOut, pszFormat, args);
+}
+
+
+/**
+ * Prints a formatted string to the standard output stream (g_pStdOut).
+ *
+ * @returns Number of bytes printed.
+ * @param pszFormat IPRT format string.
+ * @param ... Arguments specified by pszFormat.
+ */
+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;
+}
+