/* $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 . * * 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 #include "internal/iprt.h" #include #ifndef HAVE_FWRITE_UNLOCKED # include #endif #include #include #include #include # include #ifdef RTSTREAM_STANDALONE # include #endif #include #ifdef RTSTREAM_STANDALONE # include #endif #include #include #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 # include # if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) # include # include # endif #endif #ifdef RT_OS_WINDOWS # include # include #elif !defined(RTSTREAM_STANDALONE) # include # include # include #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', '[+][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; }