VirtualBox

source: vbox/trunk/src/VBox/Runtime/r3/stream.cpp@ 96763

Last change on this file since 96763 was 96710, checked in by vboxsync, 2 years ago

IPRT/stream: Check that rtStrmGetFile doesn't return NIL_RTFILE before passing it along to RTFileRead or RTFileWrite, as these will assert when NIL and all the standard streams can have NIL handles (especially in GUI apps). bugref:10261

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 89.2 KB
Line 
1/* $Id: stream.cpp 96710 2022-09-12 19:47:16Z vboxsync $ */
2/** @file
3 * IPRT - I/O Stream.
4 */
5
6/*
7 * Copyright (C) 2006-2022 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * The contents of this file may alternatively be used under the terms
26 * of the Common Development and Distribution License Version 1.0
27 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
28 * in the VirtualBox distribution, in which case the provisions of the
29 * CDDL are applicable instead of those of the GPL.
30 *
31 * You may elect to license modified versions of this file under the
32 * terms and conditions of either the GPL or the CDDL or both.
33 *
34 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
35 */
36
37
38
39/*********************************************************************************************************************************
40* Defined Constants And Macros *
41*********************************************************************************************************************************/
42/** @def RTSTREAM_STANDALONE
43 * Standalone streams w/o depending on stdio.h, using our RTFile API for
44 * file/whatever access. */
45#if (defined(IPRT_NO_CRT) && defined(RT_OS_WINDOWS)) || defined(DOXYGEN_RUNNING)
46# define RTSTREAM_STANDALONE
47#endif
48
49#if defined(RT_OS_LINUX) /* PORTME: check for the _unlocked functions in stdio.h */
50# ifndef RTSTREAM_STANDALONE
51# define HAVE_FWRITE_UNLOCKED
52# endif
53#endif
54
55/** @def RTSTREAM_WITH_TEXT_MODE
56 * Indicates whether we need to support the 'text' mode files and convert
57 * CRLF to LF while reading and writing. */
58#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) || defined(DOXYGEN_RUNNING)
59# define RTSTREAM_WITH_TEXT_MODE
60#endif
61
62
63
64/*********************************************************************************************************************************
65* Header Files *
66*********************************************************************************************************************************/
67#include <iprt/stream.h>
68#include "internal/iprt.h"
69
70#include <iprt/asm.h>
71#ifndef HAVE_FWRITE_UNLOCKED
72# include <iprt/critsect.h>
73#endif
74#include <iprt/string.h>
75#include <iprt/assert.h>
76#include <iprt/ctype.h>
77#include <iprt/err.h>
78# include <iprt/file.h>
79#ifdef RTSTREAM_STANDALONE
80# include <iprt/list.h>
81#endif
82#include <iprt/mem.h>
83#ifdef RTSTREAM_STANDALONE
84# include <iprt/once.h>
85#endif
86#include <iprt/param.h>
87#include <iprt/string.h>
88
89#include "internal/alignmentchecks.h"
90#include "internal/magics.h"
91#if defined(IPRT_NO_CRT) || defined(IN_RT_STATIC)
92# include "internal/initterm.h"
93#endif
94
95#ifdef RTSTREAM_STANDALONE
96# ifdef _MSC_VER
97# define IPRT_COMPILER_VCC_WITH_C_INIT_TERM_SECTIONS
98# include "internal/compiler-vcc.h"
99# endif
100#else
101# include <stdio.h>
102# include <errno.h>
103# if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
104# include <io.h>
105# include <fcntl.h>
106# endif
107#endif
108#ifdef RT_OS_WINDOWS
109# include <iprt/utf16.h>
110# include <iprt/win/windows.h>
111#elif !defined(RTSTREAM_STANDALONE)
112# include <termios.h>
113# include <unistd.h>
114# include <sys/ioctl.h>
115#endif
116
117#if defined(RT_OS_OS2) && !defined(RTSTREAM_STANDALONE)
118# define _O_TEXT O_TEXT
119# define _O_BINARY O_BINARY
120#endif
121
122
123/*********************************************************************************************************************************
124* Structures and Typedefs *
125*********************************************************************************************************************************/
126#ifdef RTSTREAM_STANDALONE
127/** The buffer direction. */
128typedef enum RTSTREAMBUFDIR
129{
130 RTSTREAMBUFDIR_NONE = 0,
131 RTSTREAMBUFDIR_READ,
132 RTSTREAMBUFDIR_WRITE
133} RTSTREAMBUFDIR;
134
135/** The buffer style. */
136typedef enum RTSTREAMBUFSTYLE
137{
138 RTSTREAMBUFSTYLE_UNBUFFERED = 0,
139 RTSTREAMBUFSTYLE_LINE,
140 RTSTREAMBUFSTYLE_FULL
141} RTSTREAMBUFSTYLE;
142
143#endif
144
145/**
146 * File stream.
147 */
148typedef struct RTSTREAM
149{
150 /** Magic value used to validate the stream. (RTSTREAM_MAGIC) */
151 uint32_t u32Magic;
152 /** File stream error. */
153 int32_t volatile i32Error;
154#ifndef RTSTREAM_STANDALONE
155 /** Pointer to the LIBC file stream. */
156 FILE *pFile;
157#else
158 /** Indicates which standard handle this is supposed to be.
159 * Set to RTHANDLESTD_INVALID if not one of the tree standard streams. */
160 RTHANDLESTD enmStdHandle;
161 /** The IPRT handle backing this stream.
162 * This is initialized lazily using enmStdHandle for the three standard
163 * streams. */
164 RTFILE hFile;
165 /** Buffer. */
166 char *pchBuf;
167 /** Buffer allocation size. */
168 size_t cbBufAlloc;
169 /** Offset of the first valid byte in the buffer. */
170 size_t offBufFirst;
171 /** Offset of the end of valid bytes in the buffer (exclusive). */
172 size_t offBufEnd;
173 /** The stream buffer direction. */
174 RTSTREAMBUFDIR enmBufDir;
175 /** The buffering style (unbuffered, line, full).
176 * @todo replace by RTSTRMBUFMODE. */
177 RTSTREAMBUFSTYLE enmBufStyle;
178# ifdef RTSTREAM_WITH_TEXT_MODE
179 /** Bitmap running parallel to each char pchBuf, indicating where a '\\r'
180 * character have been removed during buffer filling. This is used to implement
181 * RTStrmTell in non-binary mode. */
182 uint32_t *pbmBuf;
183 /** Indicates that we've got a CR ('\\r') beyond the end of official buffer
184 * and need to check if there is a LF following it. This member is ignored
185 * in binary mode. */
186 bool fPendingCr;
187# endif
188#endif
189 /** Stream is using the current process code set. */
190 bool fCurrentCodeSet;
191 /** Whether the stream was opened in binary mode. */
192 bool fBinary;
193 /** Whether to recheck the stream mode before writing. */
194 bool fRecheckMode;
195#if !defined(HAVE_FWRITE_UNLOCKED) || defined(RTSTREAM_STANDALONE)
196 /** Critical section for serializing access to the stream. */
197 PRTCRITSECT pCritSect;
198#endif
199#ifdef RTSTREAM_STANDALONE
200 /** Entry in g_StreamList (for automatic flushing and closing at
201 * exit/unload). */
202 RTLISTNODE ListEntry;
203#endif
204} RTSTREAM;
205
206
207/**
208 * State for wrapped output (RTStrmWrappedPrintf, RTStrmWrappedPrintfV).
209 */
210typedef struct RTSTRMWRAPPEDSTATE
211{
212 PRTSTREAM pStream; /**< The output stream. */
213 uint32_t cchWidth; /**< The line width. */
214 uint32_t cchLine; /**< The current line length (valid chars in szLine). */
215 uint32_t cLines; /**< Number of lines written. */
216 uint32_t cchIndent; /**< The indent (determined from the first line). */
217 int rcStatus; /**< The output status. */
218 uint8_t cchHangingIndent; /**< Hanging indent (from fFlags). */
219 char szLine[0x1000+1]; /**< We must buffer output so we can do proper word splitting. */
220} RTSTRMWRAPPEDSTATE;
221
222
223/*********************************************************************************************************************************
224* Global Variables *
225*********************************************************************************************************************************/
226/** The standard input stream. */
227static RTSTREAM g_StdIn =
228{
229 /* .u32Magic = */ RTSTREAM_MAGIC,
230 /* .i32Error = */ 0,
231#ifndef RTSTREAM_STANDALONE
232 /* .pFile = */ stdin,
233#else
234 /* .enmStdHandle = */ RTHANDLESTD_INPUT,
235 /* .hFile = */ NIL_RTFILE,
236 /* .pchBuf = */ NULL,
237 /* .cbBufAlloc = */ 0,
238 /* .offBufFirst = */ 0,
239 /* .offBufEnd = */ 0,
240 /* .enmBufDir = */ RTSTREAMBUFDIR_NONE,
241 /* .enmBufStyle = */ RTSTREAMBUFSTYLE_UNBUFFERED,
242# ifdef RTSTREAM_WITH_TEXT_MODE
243 /* .pbmBuf = */ NULL,
244 /* .fPendingCr = */ false,
245# endif
246#endif
247 /* .fCurrentCodeSet = */ true,
248 /* .fBinary = */ false,
249 /* .fRecheckMode = */ true,
250#ifndef HAVE_FWRITE_UNLOCKED
251 /* .pCritSect = */ NULL,
252#endif
253#ifdef RTSTREAM_STANDALONE
254 /* .ListEntry = */ { NULL, NULL },
255#endif
256};
257
258/** The standard error stream. */
259static RTSTREAM g_StdErr =
260{
261 /* .u32Magic = */ RTSTREAM_MAGIC,
262 /* .i32Error = */ 0,
263#ifndef RTSTREAM_STANDALONE
264 /* .pFile = */ stderr,
265#else
266 /* .enmStdHandle = */ RTHANDLESTD_ERROR,
267 /* .hFile = */ NIL_RTFILE,
268 /* .pchBuf = */ NULL,
269 /* .cbBufAlloc = */ 0,
270 /* .offBufFirst = */ 0,
271 /* .offBufEnd = */ 0,
272 /* .enmBufDir = */ RTSTREAMBUFDIR_NONE,
273 /* .enmBufStyle = */ RTSTREAMBUFSTYLE_UNBUFFERED,
274# ifdef RTSTREAM_WITH_TEXT_MODE
275 /* .pbmBuf = */ NULL,
276 /* .fPendingCr = */ false,
277# endif
278#endif
279 /* .fCurrentCodeSet = */ true,
280 /* .fBinary = */ false,
281 /* .fRecheckMode = */ true,
282#ifndef HAVE_FWRITE_UNLOCKED
283 /* .pCritSect = */ NULL,
284#endif
285#ifdef RTSTREAM_STANDALONE
286 /* .ListEntry = */ { NULL, NULL },
287#endif
288};
289
290/** The standard output stream. */
291static RTSTREAM g_StdOut =
292{
293 /* .u32Magic = */ RTSTREAM_MAGIC,
294 /* .i32Error = */ 0,
295#ifndef RTSTREAM_STANDALONE
296 /* .pFile = */ stdout,
297#else
298 /* .enmStdHandle = */ RTHANDLESTD_OUTPUT,
299 /* .hFile = */ NIL_RTFILE,
300 /* .pchBuf = */ NULL,
301 /* .cbBufAlloc = */ 0,
302 /* .offBufFirst = */ 0,
303 /* .offBufEnd = */ 0,
304 /* .enmBufDir = */ RTSTREAMBUFDIR_NONE,
305 /* .enmBufStyle = */ RTSTREAMBUFSTYLE_LINE,
306# ifdef RTSTREAM_WITH_TEXT_MODE
307 /* .pbmBuf = */ NULL,
308 /* .fPendingCr = */ false,
309# endif
310#endif
311 /* .fCurrentCodeSet = */ true,
312 /* .fBinary = */ false,
313 /* .fRecheckMode = */ true,
314#ifndef HAVE_FWRITE_UNLOCKED
315 /* .pCritSect = */ NULL,
316#endif
317#ifdef RTSTREAM_STANDALONE
318 /* .ListEntry = */ { NULL, NULL },
319#endif
320};
321
322/** Pointer to the standard input stream. */
323RTDATADECL(PRTSTREAM) g_pStdIn = &g_StdIn;
324
325/** Pointer to the standard output stream. */
326RTDATADECL(PRTSTREAM) g_pStdErr = &g_StdErr;
327
328/** Pointer to the standard output stream. */
329RTDATADECL(PRTSTREAM) g_pStdOut = &g_StdOut;
330
331#ifdef RTSTREAM_STANDALONE
332/** Run-once initializer for the stream list (g_StreamList + g_StreamListCritSect). */
333static RTONCE g_StreamListOnce = RTONCE_INITIALIZER;
334/** List of user created streams (excludes the standard streams). */
335static RTLISTANCHOR g_StreamList;
336/** Critical section protecting the stream list. */
337static RTCRITSECT g_StreamListCritSect;
338
339
340/** @callback_method_impl{FNRTONCE} */
341static DECLCALLBACK(int32_t) rtStrmListInitOnce(void *pvUser)
342{
343 RT_NOREF(pvUser);
344 RTListInit(&g_StreamList);
345 return RTCritSectInit(&g_StreamListCritSect);
346}
347
348#endif
349
350
351#ifndef HAVE_FWRITE_UNLOCKED
352/**
353 * Allocates and acquires the lock for the stream.
354 *
355 * @returns IPRT status code.
356 * @param pStream The stream (valid).
357 */
358static int rtStrmAllocLock(PRTSTREAM pStream)
359{
360 Assert(pStream->pCritSect == NULL);
361
362 PRTCRITSECT pCritSect = (PRTCRITSECT)RTMemAlloc(sizeof(*pCritSect));
363 if (!pCritSect)
364 return VERR_NO_MEMORY;
365
366 /* The native stream lock are normally not recursive. */
367 uint32_t fFlags = RTCRITSECT_FLAGS_NO_NESTING;
368# if defined(IPRT_NO_CRT) || defined(IN_RT_STATIC)
369 /* IPRT is often used deliberatly without initialization in no-CRT
370 binaries (for instance VBoxAddInstallNt3x.exe), so in order to avoid
371 asserting in the lock validator we add the bootstrap hack that disable
372 lock validation for the section.
373 Update: Applying this to all builds involving static linking, as it's
374 now going to be used for tests running at compile-time too. */
375 if (!rtInitIsInitialized())
376 fFlags |= RTCRITSECT_FLAGS_BOOTSTRAP_HACK;
377# endif
378 int rc = RTCritSectInitEx(pCritSect, fFlags, NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, "RTSemSpinMutex");
379 if (RT_SUCCESS(rc))
380 {
381 rc = RTCritSectEnter(pCritSect);
382 if (RT_SUCCESS(rc))
383 {
384 if (RT_LIKELY(ASMAtomicCmpXchgPtr(&pStream->pCritSect, pCritSect, NULL)))
385 return VINF_SUCCESS;
386
387 RTCritSectLeave(pCritSect);
388 }
389 RTCritSectDelete(pCritSect);
390 }
391 RTMemFree(pCritSect);
392
393 /* Handle the lost race case... */
394 pCritSect = ASMAtomicReadPtrT(&pStream->pCritSect, PRTCRITSECT);
395 if (pCritSect)
396 return RTCritSectEnter(pCritSect);
397
398 return rc;
399}
400#endif /* !HAVE_FWRITE_UNLOCKED */
401
402
403/**
404 * Locks the stream. May have to allocate the lock as well.
405 *
406 * @param pStream The stream (valid).
407 */
408DECLINLINE(void) rtStrmLock(PRTSTREAM pStream)
409{
410#ifdef HAVE_FWRITE_UNLOCKED
411 flockfile(pStream->pFile);
412#else
413 if (RT_LIKELY(pStream->pCritSect))
414 RTCritSectEnter(pStream->pCritSect);
415 else
416 rtStrmAllocLock(pStream);
417#endif
418}
419
420
421/**
422 * Unlocks the stream.
423 *
424 * @param pStream The stream (valid).
425 */
426DECLINLINE(void) rtStrmUnlock(PRTSTREAM pStream)
427{
428#ifdef HAVE_FWRITE_UNLOCKED
429 funlockfile(pStream->pFile);
430#else
431 if (RT_LIKELY(pStream->pCritSect))
432 RTCritSectLeave(pStream->pCritSect);
433#endif
434}
435
436
437/**
438 * Opens a file stream.
439 *
440 * @returns iprt status code.
441 * @param pszFilename Path to the file to open, hFile must be NIL_RTFILE.
442 * NULL if a hFile is to be used instead.
443 * @param hFile File handle to use when called from
444 * RTStrmOpenFileHandle. pszFilename must be NULL.
445 * @param pszMode See RTStrmOpen.
446 * @param ppStream Where to store the opened stream.
447 */
448static int rtStrmOpenComon(const char *pszFilename, RTFILE hFile, const char *pszMode, PRTSTREAM *ppStream)
449{
450 /*
451 * Validate input and look for things we care for in the pszMode string.
452 */
453 AssertReturn(pszMode && *pszMode, VERR_INVALID_FLAGS);
454
455 /*
456 * Process the mode string.
457 */
458 char chMode = '\0'; /* a|r|w */
459 bool fPlus = false; /* + */
460 bool fBinary = false; /* b | !t */
461 bool fExclusive = false; /* x */
462 bool fNoInherit = false; /* e (linux, freebsd) | N (win) | E (our for reverse) */
463 const char *psz = pszMode;
464 char ch;
465 while ((ch = *psz++) != '\0')
466 {
467 switch (ch)
468 {
469 case 'a':
470 case 'r':
471 case 'w':
472 chMode = ch;
473 break;
474 case '+':
475 fPlus = true;
476 break;
477 case 'b':
478 fBinary = true;
479 break;
480 case 't':
481 fBinary = false;
482 break;
483 case 'x':
484 fExclusive = true;
485 break;
486 case 'e':
487 case 'N':
488 fNoInherit = true;
489 break;
490 case 'E':
491 fNoInherit = false;
492 break;
493 default:
494 AssertMsgFailedReturn(("Invalid ch='%c' in pszMode='%s', '<a|r|w>[+][b|t][x][e|N|E]'\n", ch, pszMode),
495 VERR_INVALID_FLAGS);
496 }
497 }
498
499 /*
500 * Translate into to RTFILE_O_* flags:
501 */
502 uint64_t fOpen;
503 switch (chMode)
504 {
505 case 'a': fOpen = RTFILE_O_OPEN_CREATE | RTFILE_O_WRITE | RTFILE_O_APPEND; break;
506 case 'w': fOpen = !fExclusive
507 ? RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE
508 : RTFILE_O_CREATE | RTFILE_O_WRITE; break;
509 case 'r': fOpen = RTFILE_O_OPEN | RTFILE_O_READ; break;
510 default: AssertMsgFailedReturn(("No main mode (a|r|w) specified in '%s'!\n", pszMode), VERR_INVALID_FLAGS);
511 }
512 AssertMsgReturn(!fExclusive || chMode == 'w', ("the 'x' flag is only allowed with 'w'! (%s)\n", pszMode),
513 VERR_INVALID_FLAGS);
514 if (fExclusive)
515 fOpen |= RTFILE_O_READ | RTFILE_O_WRITE;
516 if (fPlus)
517 fOpen |= RTFILE_O_READ | RTFILE_O_WRITE;
518 if (!fNoInherit)
519 fOpen |= RTFILE_O_INHERIT;
520 fOpen |= RTFILE_O_DENY_NONE;
521 fOpen |= 0666 << RTFILE_O_CREATE_MODE_SHIFT;
522
523#ifndef RTSTREAM_STANDALONE
524 /*
525 * Normalize mode for fdopen.
526 */
527 char szNormalizedMode[8];
528 szNormalizedMode[0] = chMode;
529 size_t off = 1;
530 if (fPlus)
531 szNormalizedMode[off++] = '+';
532 if (fBinary)
533 szNormalizedMode[off++] = 'b';
534 szNormalizedMode[off] = '\0';
535#endif
536
537#ifdef RTSTREAM_STANDALONE
538 /*
539 * Make the the stream list is initialized before we allocate anything.
540 */
541 int rc2 = RTOnce(&g_StreamListOnce, rtStrmListInitOnce, NULL);
542 AssertRCReturn(rc2, rc2);
543#endif
544
545 /*
546 * Allocate the stream handle and try open it.
547 */
548 int rc = VERR_NO_MEMORY;
549 PRTSTREAM pStream = (PRTSTREAM)RTMemAllocZ(sizeof(*pStream));
550 if (pStream)
551 {
552 pStream->u32Magic = RTSTREAM_MAGIC;
553#ifdef RTSTREAM_STANDALONE
554 pStream->enmStdHandle = RTHANDLESTD_INVALID;
555 pStream->hFile = NIL_RTFILE;
556 pStream->pchBuf = NULL;
557 pStream->cbBufAlloc = 0;
558 pStream->offBufFirst = 0;
559 pStream->offBufEnd = 0;
560 pStream->enmBufDir = RTSTREAMBUFDIR_NONE;
561 pStream->enmBufStyle = RTSTREAMBUFSTYLE_FULL;
562# ifdef RTSTREAM_WITH_TEXT_MODE
563 pStream->pbmBuf = NULL;
564 pStream->fPendingCr = false,
565# endif
566#endif
567 pStream->i32Error = VINF_SUCCESS;
568 pStream->fCurrentCodeSet = false;
569 pStream->fBinary = fBinary;
570 pStream->fRecheckMode = false;
571#ifndef HAVE_FWRITE_UNLOCKED
572 pStream->pCritSect = NULL;
573#endif
574 RTFILEACTION enmActionTaken = RTFILEACTION_INVALID;
575 if (pszFilename)
576 rc = RTFileOpenEx(pszFilename, fOpen, &hFile, &enmActionTaken);
577 else
578 rc = VINF_SUCCESS;
579 if (RT_SUCCESS(rc))
580 {
581#ifndef RTSTREAM_STANDALONE
582# ifndef _MSC_VER
583 int fd = (int)RTFileToNative(hFile);
584# else
585 int fd = _open_osfhandle(RTFileToNative(hFile),
586 (fPlus ? _O_RDWR : chMode == 'r' ? _O_RDONLY : _O_WRONLY)
587 | (chMode == 'a' ? _O_APPEND : 0)
588 | (fBinary ? _O_BINARY : _O_TEXT)
589 | (fNoInherit ? _O_NOINHERIT : 0));
590# endif
591 if (fd >= 0)
592 {
593 pStream->pFile = fdopen(fd, szNormalizedMode);
594 if (pStream->pFile)
595#endif
596 {
597#ifdef RTSTREAM_STANDALONE
598 pStream->hFile = hFile;
599
600 /* We keep a list of these for cleanup purposes. */
601 RTCritSectEnter(&g_StreamListCritSect);
602 RTListAppend(&g_StreamList, &pStream->ListEntry);
603 RTCritSectLeave(&g_StreamListCritSect);
604#endif
605 *ppStream = pStream;
606 return VINF_SUCCESS;
607 }
608
609 /*
610 * This better not happen too often as in 'w' mode we might've
611 * truncated a file, and in 'w' and 'a' modes there is a chance
612 * that we'll race other access to the file when deleting it.
613 */
614#ifndef RTSTREAM_STANDALONE
615 rc = RTErrConvertFromErrno(errno);
616# ifdef _MSC_VER
617 close(fd);
618 hFile = NIL_RTFILE;
619 /** @todo we're in trouble here when called from RTStrmOpenFileHandle! */
620# endif
621 }
622 else
623 {
624# ifdef _MSC_VER
625 rc = RTErrConvertFromErrno(errno);
626# else
627 AssertFailedStmt(rc = VERR_INVALID_HANDLE);
628# endif
629 }
630 if (pszFilename)
631 {
632 RTFileClose(hFile);
633 if (enmActionTaken == RTFILEACTION_CREATED)
634 RTFileDelete(pszFilename);
635 }
636#endif
637 }
638 RTMemFree(pStream);
639 }
640 return rc;
641}
642
643
644/**
645 * Opens a file stream.
646 *
647 * @returns iprt status code.
648 * @param pszFilename Path to the file to open.
649 * @param pszMode The open mode. See fopen() standard.
650 * Format: <a|r|w>[+][b|t][x][e|N|E]
651 * - 'a': Open or create file and writes
652 * append tos it.
653 * - 'r': Open existing file and read from it.
654 * - 'w': Open or truncate existing file and write
655 * to it.
656 * - '+': Open for both read and write access.
657 * - 'b' / 't': binary / text
658 * - 'x': exclusively create, no open. Only
659 * possible with 'w'.
660 * - 'e' / 'N': No inherit on exec. (The 'e' is
661 * how Linux and FreeBSD expresses this, the
662 * latter is Visual C++).
663 * @param ppStream Where to store the opened stream.
664 */
665RTR3DECL(int) RTStrmOpen(const char *pszFilename, const char *pszMode, PRTSTREAM *ppStream)
666{
667 *ppStream = NULL;
668 AssertReturn(pszFilename, VERR_INVALID_PARAMETER);
669 return rtStrmOpenComon(pszFilename, NIL_RTFILE, pszMode, ppStream);
670}
671
672
673/**
674 * Opens a file stream.
675 *
676 * @returns iprt status code.
677 * @param pszMode The open mode. See fopen() standard.
678 * Format: <a|r|w>[+][b]
679 * @param ppStream Where to store the opened stream.
680 * @param pszFilenameFmt Filename path format string.
681 * @param args Arguments to the format string.
682 */
683RTR3DECL(int) RTStrmOpenFV(const char *pszMode, PRTSTREAM *ppStream, const char *pszFilenameFmt, va_list args)
684{
685 int rc;
686 char szFilename[RTPATH_MAX];
687 size_t cch = RTStrPrintfV(szFilename, sizeof(szFilename), pszFilenameFmt, args);
688 if (cch < sizeof(szFilename))
689 rc = RTStrmOpen(szFilename, pszMode, ppStream);
690 else
691 {
692 AssertMsgFailed(("The filename is too long cch=%d\n", cch));
693 rc = VERR_FILENAME_TOO_LONG;
694 }
695 return rc;
696}
697
698
699/**
700 * Opens a file stream.
701 *
702 * @returns iprt status code.
703 * @param pszMode The open mode. See fopen() standard.
704 * Format: <a|r|w>[+][b]
705 * @param ppStream Where to store the opened stream.
706 * @param pszFilenameFmt Filename path format string.
707 * @param ... Arguments to the format string.
708 */
709RTR3DECL(int) RTStrmOpenF(const char *pszMode, PRTSTREAM *ppStream, const char *pszFilenameFmt, ...)
710{
711 va_list args;
712 va_start(args, pszFilenameFmt);
713 int rc = RTStrmOpenFV(pszMode, ppStream, pszFilenameFmt, args);
714 va_end(args);
715 return rc;
716}
717
718
719/**
720 * Opens a file stream for a RTFILE handle, taking ownership of the handle.
721 *
722 * @returns iprt status code.
723 * @param hFile The file handle to use. On success, handle
724 * ownership is transfered to the stream and it will be
725 * closed when the stream closes.
726 * @param pszMode The open mode, accept the same as RTStrOpen and
727 * friends however it is only used to figure out what
728 * we can do with the handle.
729 * @param fFlags Reserved, must be zero.
730 * @param ppStream Where to store the opened stream.
731 */
732RTR3DECL(int) RTStrmOpenFileHandle(RTFILE hFile, const char *pszMode, uint32_t fFlags, PRTSTREAM *ppStream)
733{
734 *ppStream = NULL;
735 AssertReturn(RTFileIsValid(hFile), VERR_INVALID_HANDLE);
736 AssertReturn(fFlags == 0, VERR_INVALID_FLAGS);
737 return rtStrmOpenComon(NULL, hFile, pszMode, ppStream);
738}
739
740
741/**
742 * Closes the specified stream.
743 *
744 * @returns iprt status code.
745 * @param pStream The stream to close.
746 */
747RTR3DECL(int) RTStrmClose(PRTSTREAM pStream)
748{
749 /*
750 * Validate input.
751 */
752 if (!pStream)
753 return VINF_SUCCESS;
754 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
755 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_MAGIC);
756
757 /* We don't implement closing any of the standard handles at present. */
758 AssertReturn(pStream != &g_StdIn, VERR_NOT_SUPPORTED);
759 AssertReturn(pStream != &g_StdOut, VERR_NOT_SUPPORTED);
760 AssertReturn(pStream != &g_StdErr, VERR_NOT_SUPPORTED);
761
762 /*
763 * Invalidate the stream and destroy the critical section first.
764 */
765#ifdef RTSTREAM_STANDALONE
766 RTCritSectEnter(&g_StreamListCritSect);
767 RTListNodeRemove(&pStream->ListEntry);
768 RTCritSectLeave(&g_StreamListCritSect);
769#endif
770 pStream->u32Magic = 0xdeaddead;
771#ifndef HAVE_FWRITE_UNLOCKED
772 if (pStream->pCritSect)
773 {
774 RTCritSectEnter(pStream->pCritSect);
775 RTCritSectLeave(pStream->pCritSect);
776 RTCritSectDelete(pStream->pCritSect);
777 RTMemFree(pStream->pCritSect);
778 pStream->pCritSect = NULL;
779 }
780#endif
781
782 /*
783 * Flush and close the underlying file.
784 */
785#ifdef RTSTREAM_STANDALONE
786 int const rc1 = RTStrmFlush(pStream);
787 AssertRC(rc1);
788 int const rc2 = RTFileClose(pStream->hFile);
789 AssertRC(rc2);
790 int const rc = RT_SUCCESS(rc1) ? rc2 : rc1;
791#else
792 int const rc = !fclose(pStream->pFile) ? VINF_SUCCESS : RTErrConvertFromErrno(errno);
793#endif
794
795 /*
796 * Destroy the stream.
797 */
798#ifdef RTSTREAM_STANDALONE
799 pStream->hFile = NIL_RTFILE;
800 RTMemFree(pStream->pchBuf);
801 pStream->pchBuf = NULL;
802 pStream->cbBufAlloc = 0;
803 pStream->offBufFirst = 0;
804 pStream->offBufEnd = 0;
805# ifdef RTSTREAM_WITH_TEXT_MODE
806 RTMemFree(pStream->pbmBuf);
807 pStream->pbmBuf = NULL;
808# endif
809#else
810 pStream->pFile = NULL;
811#endif
812 RTMemFree(pStream);
813 return rc;
814}
815
816
817/**
818 * Get the pending error of the stream.
819 *
820 * @returns iprt status code. of the stream.
821 * @param pStream The stream.
822 */
823RTR3DECL(int) RTStrmError(PRTSTREAM pStream)
824{
825 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
826 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_MAGIC);
827 return pStream->i32Error;
828}
829
830
831/**
832 * Clears stream error condition.
833 *
834 * All stream operations save RTStrmClose and this will fail
835 * while an error is asserted on the stream
836 *
837 * @returns iprt status code.
838 * @param pStream The stream.
839 */
840RTR3DECL(int) RTStrmClearError(PRTSTREAM pStream)
841{
842 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
843 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_MAGIC);
844
845#ifndef RTSTREAM_STANDALONE
846 clearerr(pStream->pFile);
847#endif
848 ASMAtomicWriteS32(&pStream->i32Error, VINF_SUCCESS);
849 return VINF_SUCCESS;
850}
851
852
853RTR3DECL(int) RTStrmSetMode(PRTSTREAM pStream, int fBinary, int fCurrentCodeSet)
854{
855 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
856 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
857 AssertReturn((unsigned)(fBinary + 1) <= 2, VERR_INVALID_PARAMETER);
858 AssertReturn((unsigned)(fCurrentCodeSet + 1) <= 2, VERR_INVALID_PARAMETER);
859
860 rtStrmLock(pStream);
861
862 if (fBinary != -1)
863 {
864 pStream->fBinary = RT_BOOL(fBinary);
865 pStream->fRecheckMode = true;
866 }
867
868 if (fCurrentCodeSet != -1)
869 pStream->fCurrentCodeSet = RT_BOOL(fCurrentCodeSet);
870
871 rtStrmUnlock(pStream);
872
873 return VINF_SUCCESS;
874}
875
876
877RTR3DECL(int) RTStrmSetBufferingMode(PRTSTREAM pStream, RTSTRMBUFMODE enmMode)
878{
879 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
880 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
881 AssertReturn(enmMode > RTSTRMBUFMODE_INVALID && enmMode < RTSTRMBUFMODE_END, VERR_INVALID_PARAMETER);
882
883#ifndef RTSTREAM_STANDALONE
884 int iCrtMode = enmMode == RTSTRMBUFMODE_FULL ? _IOFBF : enmMode == RTSTRMBUFMODE_LINE ? _IOLBF : _IONBF;
885 int rc = setvbuf(pStream->pFile, NULL, iCrtMode, 0);
886 if (rc >= 0)
887 return VINF_SUCCESS;
888 return RTErrConvertFromErrno(errno);
889
890#else
891 rtStrmLock(pStream);
892 pStream->enmBufStyle = enmMode == RTSTRMBUFMODE_FULL ? RTSTREAMBUFSTYLE_FULL
893 : enmMode == RTSTRMBUFMODE_LINE ? RTSTREAMBUFSTYLE_LINE : RTSTREAMBUFSTYLE_UNBUFFERED;
894 rtStrmUnlock(pStream);
895 return VINF_SUCCESS;
896#endif
897}
898
899
900#ifdef RTSTREAM_STANDALONE
901
902/**
903 * Deals with NIL_RTFILE in rtStrmGetFile.
904 */
905DECL_NO_INLINE(static, RTFILE) rtStrmGetFileNil(PRTSTREAM pStream)
906{
907# ifdef RT_OS_WINDOWS
908 DWORD dwStdHandle;
909 switch (pStream->enmStdHandle)
910 {
911 case RTHANDLESTD_INPUT: dwStdHandle = STD_INPUT_HANDLE; break;
912 case RTHANDLESTD_OUTPUT: dwStdHandle = STD_OUTPUT_HANDLE; break;
913 case RTHANDLESTD_ERROR: dwStdHandle = STD_ERROR_HANDLE; break;
914 default: return NIL_RTFILE;
915 }
916 HANDLE hHandle = GetStdHandle(dwStdHandle);
917 if (hHandle != INVALID_HANDLE_VALUE && hHandle != NULL)
918 {
919 int rc = RTFileFromNative(&pStream->hFile, (uintptr_t)hHandle);
920 if (RT_SUCCESS(rc))
921 {
922 /* Switch to full buffering if not a console handle. */
923 DWORD dwMode;
924 if (!GetConsoleMode(hHandle, &dwMode))
925 pStream->enmBufStyle = RTSTREAMBUFSTYLE_FULL;
926
927 return pStream->hFile;
928 }
929 }
930
931# else
932 uintptr_t uNative;
933 switch (pStream->enmStdHandle)
934 {
935 case RTHANDLESTD_INPUT: uNative = RTFILE_NATIVE_STDIN; break;
936 case RTHANDLESTD_OUTPUT: uNative = RTFILE_NATIVE_STDOUT; break;
937 case RTHANDLESTD_ERROR: uNative = RTFILE_NATIVE_STDERR; break;
938 default: return NIL_RTFILE;
939 }
940 int rc = RTFileFromNative(&pStream->hFile, uNative);
941 if (RT_SUCCESS(rc))
942 {
943 /* Switch to full buffering if not a console handle. */
944 if (!isatty((int)uNative))
945 pStream->enmBufStyle = RTSTREAMBUFDIR_FULL;
946
947 return pStream->hFile;
948 }
949
950# endif
951 return NIL_RTFILE;
952}
953
954
955/**
956 * For lazily resolving handles for the standard streams.
957 */
958DECLINLINE(RTFILE) rtStrmGetFile(PRTSTREAM pStream)
959{
960 RTFILE hFile = pStream->hFile;
961 if (hFile != NIL_RTFILE)
962 return hFile;
963 return rtStrmGetFileNil(pStream);
964}
965
966
967RTR3DECL(int) RTStrmQueryFileHandle(PRTSTREAM pStream, PRTFILE phFile)
968{
969 AssertPtrReturn(phFile, VERR_INVALID_POINTER);
970 *phFile = NIL_RTFILE;
971 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
972 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_MAGIC);
973
974 rtStrmLock(pStream);
975 RTFILE hFile = rtStrmGetFile(pStream);
976 rtStrmUnlock(pStream);
977 if (hFile != NIL_RTFILE)
978 {
979 *phFile = hFile;
980 return VINF_SUCCESS;
981 }
982 return VERR_NOT_AVAILABLE;
983}
984
985#endif /* RTSTREAM_STANDALONE */
986
987
988/**
989 * Wrapper around isatty, assumes caller takes care of stream locking/whatever
990 * is needed.
991 */
992DECLINLINE(bool) rtStrmIsTerminal(PRTSTREAM pStream)
993{
994#ifdef RTSTREAM_STANDALONE
995 RTFILE hFile = rtStrmGetFile(pStream);
996 if (hFile != NIL_RTFILE)
997 {
998 HANDLE hNative = (HANDLE)RTFileToNative(hFile);
999 DWORD dwType = GetFileType(hNative);
1000 if (dwType == FILE_TYPE_CHAR)
1001 {
1002 DWORD dwMode;
1003 if (GetConsoleMode(hNative, &dwMode))
1004 return true;
1005 }
1006 }
1007 return false;
1008
1009#else
1010 if (pStream->pFile)
1011 {
1012 int fh = fileno(pStream->pFile);
1013 if (isatty(fh) != 0)
1014 {
1015# ifdef RT_OS_WINDOWS
1016 DWORD dwMode;
1017 HANDLE hCon = (HANDLE)_get_osfhandle(fh);
1018 if (GetConsoleMode(hCon, &dwMode))
1019 return true;
1020# else
1021 return true;
1022# endif
1023 }
1024 }
1025 return false;
1026#endif
1027}
1028
1029
1030static int rtStrmInputGetEchoCharsNative(uintptr_t hNative, bool *pfEchoChars)
1031{
1032#ifdef RT_OS_WINDOWS
1033 DWORD dwMode;
1034 if (GetConsoleMode((HANDLE)hNative, &dwMode))
1035 *pfEchoChars = RT_BOOL(dwMode & ENABLE_ECHO_INPUT);
1036 else
1037 {
1038 DWORD dwErr = GetLastError();
1039 if (dwErr == ERROR_INVALID_HANDLE)
1040 return GetFileType((HANDLE)hNative) != FILE_TYPE_UNKNOWN ? VERR_INVALID_FUNCTION : VERR_INVALID_HANDLE;
1041 return RTErrConvertFromWin32(dwErr);
1042 }
1043#else
1044 struct termios Termios;
1045 int rcPosix = tcgetattr((int)hNative, &Termios);
1046 if (!rcPosix)
1047 *pfEchoChars = RT_BOOL(Termios.c_lflag & ECHO);
1048 else
1049 return errno == ENOTTY ? VERR_INVALID_FUNCTION : RTErrConvertFromErrno(errno);
1050#endif
1051 return VINF_SUCCESS;
1052}
1053
1054
1055
1056RTR3DECL(int) RTStrmInputGetEchoChars(PRTSTREAM pStream, bool *pfEchoChars)
1057{
1058 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
1059 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
1060 AssertPtrReturn(pfEchoChars, VERR_INVALID_POINTER);
1061
1062#ifdef RTSTREAM_STANDALONE
1063 return rtStrmInputGetEchoCharsNative(RTFileToNative(pStream->hFile), pfEchoChars);
1064#else
1065 int rc;
1066 int fh = fileno(pStream->pFile);
1067 if (isatty(fh))
1068 {
1069# ifdef RT_OS_WINDOWS
1070 rc = rtStrmInputGetEchoCharsNative(_get_osfhandle(fh), pfEchoChars);
1071# else
1072 rc = rtStrmInputGetEchoCharsNative(fh, pfEchoChars);
1073# endif
1074 }
1075 else
1076 rc = VERR_INVALID_FUNCTION;
1077 return rc;
1078#endif
1079}
1080
1081
1082static int rtStrmInputSetEchoCharsNative(uintptr_t hNative, bool fEchoChars)
1083{
1084 int rc;
1085#ifdef RT_OS_WINDOWS
1086 DWORD dwMode;
1087 if (GetConsoleMode((HANDLE)hNative, &dwMode))
1088 {
1089 if (fEchoChars)
1090 dwMode |= ENABLE_ECHO_INPUT;
1091 else
1092 dwMode &= ~ENABLE_ECHO_INPUT;
1093 if (SetConsoleMode((HANDLE)hNative, dwMode))
1094 rc = VINF_SUCCESS;
1095 else
1096 rc = RTErrConvertFromWin32(GetLastError());
1097 }
1098 else
1099 {
1100 DWORD dwErr = GetLastError();
1101 if (dwErr == ERROR_INVALID_HANDLE)
1102 return GetFileType((HANDLE)hNative) != FILE_TYPE_UNKNOWN ? VERR_INVALID_FUNCTION : VERR_INVALID_HANDLE;
1103 return RTErrConvertFromWin32(dwErr);
1104 }
1105#else
1106 struct termios Termios;
1107 int rcPosix = tcgetattr((int)hNative, &Termios);
1108 if (!rcPosix)
1109 {
1110 if (fEchoChars)
1111 Termios.c_lflag |= ECHO;
1112 else
1113 Termios.c_lflag &= ~ECHO;
1114
1115 rcPosix = tcsetattr((int)hNative, TCSAFLUSH, &Termios);
1116 if (rcPosix == 0)
1117 rc = VINF_SUCCESS;
1118 else
1119 rc = RTErrConvertFromErrno(errno);
1120 }
1121 else
1122 rc = errno == ENOTTY ? VERR_INVALID_FUNCTION : RTErrConvertFromErrno(errno);
1123#endif
1124 return rc;
1125}
1126
1127
1128RTR3DECL(int) RTStrmInputSetEchoChars(PRTSTREAM pStream, bool fEchoChars)
1129{
1130 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
1131 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
1132
1133#ifdef RTSTREAM_STANDALONE
1134 return rtStrmInputSetEchoCharsNative(RTFileToNative(pStream->hFile), fEchoChars);
1135#else
1136 int rc;
1137 int fh = fileno(pStream->pFile);
1138 if (isatty(fh))
1139 {
1140# ifdef RT_OS_WINDOWS
1141 rc = rtStrmInputSetEchoCharsNative(_get_osfhandle(fh), fEchoChars);
1142# else
1143 rc = rtStrmInputSetEchoCharsNative(fh, fEchoChars);
1144# endif
1145 }
1146 else
1147 rc = VERR_INVALID_FUNCTION;
1148 return rc;
1149#endif
1150}
1151
1152
1153RTR3DECL(bool) RTStrmIsTerminal(PRTSTREAM pStream)
1154{
1155 AssertPtrReturn(pStream, false);
1156 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, false);
1157
1158 return rtStrmIsTerminal(pStream);
1159}
1160
1161
1162RTR3DECL(int) RTStrmQueryTerminalWidth(PRTSTREAM pStream, uint32_t *pcchWidth)
1163{
1164 AssertPtrReturn(pcchWidth, VERR_INVALID_HANDLE);
1165 *pcchWidth = 80;
1166
1167 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
1168 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
1169
1170 if (rtStrmIsTerminal(pStream))
1171 {
1172#ifdef RT_OS_WINDOWS
1173# ifdef RTSTREAM_STANDALONE
1174 HANDLE hCon = (HANDLE)RTFileToNative(pStream->hFile);
1175# else
1176 HANDLE hCon = (HANDLE)_get_osfhandle(fileno(pStream->pFile));
1177# endif
1178 CONSOLE_SCREEN_BUFFER_INFO Info;
1179 RT_ZERO(Info);
1180 if (GetConsoleScreenBufferInfo(hCon, &Info))
1181 {
1182 *pcchWidth = Info.dwSize.X ? Info.dwSize.X : 80;
1183 return VINF_SUCCESS;
1184 }
1185 return RTErrConvertFromWin32(GetLastError());
1186
1187#elif defined(RT_OS_OS2) && !defined(TIOCGWINSZ) /* only OS/2 should currently miss this */
1188 return VINF_SUCCESS; /* just pretend for now. */
1189
1190#else
1191 struct winsize Info;
1192 RT_ZERO(Info);
1193 int rc = ioctl(fileno(pStream->pFile), TIOCGWINSZ, &Info);
1194 if (rc >= 0)
1195 {
1196 *pcchWidth = Info.ws_col ? Info.ws_col : 80;
1197 return VINF_SUCCESS;
1198 }
1199 return RTErrConvertFromErrno(errno);
1200#endif
1201 }
1202 return VERR_INVALID_FUNCTION;
1203}
1204
1205
1206#ifdef RTSTREAM_STANDALONE
1207
1208DECLINLINE(void) rtStrmBufInvalidate(PRTSTREAM pStream)
1209{
1210 pStream->enmBufDir = RTSTREAMBUFDIR_NONE;
1211 pStream->offBufEnd = 0;
1212 pStream->offBufFirst = 0;
1213}
1214
1215
1216static int rtStrmBufFlushWrite(PRTSTREAM pStream, size_t cbToFlush)
1217{
1218 Assert(cbToFlush <= pStream->offBufEnd - pStream->offBufFirst);
1219
1220 RTFILE const hFile = rtStrmGetFile(pStream);
1221 if (hFile != NIL_RTFILE)
1222 {
1223 /** @todo do nonblocking & incomplete writes? */
1224 size_t offBufFirst = pStream->offBufFirst;
1225 int rc = RTFileWrite(hFile, &pStream->pchBuf[offBufFirst], cbToFlush, NULL);
1226 if (RT_SUCCESS(rc))
1227 {
1228 offBufFirst += cbToFlush;
1229 if (offBufFirst >= pStream->offBufEnd)
1230 pStream->offBufEnd = 0;
1231 else
1232 {
1233 /* Shift up the remaining content so the next write can take full
1234 advantage of the buffer size. */
1235 size_t cbLeft = pStream->offBufEnd - offBufFirst;
1236 memmove(pStream->pchBuf, &pStream->pchBuf[offBufFirst], cbLeft);
1237 pStream->offBufEnd = cbLeft;
1238 }
1239 pStream->offBufFirst = 0;
1240 return VINF_SUCCESS;
1241 }
1242 return rc;
1243 }
1244 return VERR_INVALID_HANDLE;
1245}
1246
1247
1248static int rtStrmBufFlushWriteMaybe(PRTSTREAM pStream, bool fInvalidate)
1249{
1250 if (pStream->enmBufDir == RTSTREAMBUFDIR_WRITE)
1251 {
1252 size_t cbInBuffer = pStream->offBufEnd - pStream->offBufFirst;
1253 if (cbInBuffer > 0)
1254 {
1255 int rc = rtStrmBufFlushWrite(pStream, cbInBuffer);
1256 if (fInvalidate)
1257 pStream->enmBufDir = RTSTREAMBUFDIR_NONE;
1258 return rc;
1259 }
1260 }
1261 if (fInvalidate)
1262 rtStrmBufInvalidate(pStream);
1263 return VINF_SUCCESS;
1264}
1265
1266
1267/**
1268 * Worker for rtStrmBufCheckErrorAndSwitchToReadMode and
1269 * rtStrmBufCheckErrorAndSwitchToWriteMode that allocates a buffer.
1270 *
1271 * Only updates cbBufAlloc and pchBuf, callers deals with error fallout.
1272 */
1273static int rtStrmBufAlloc(PRTSTREAM pStream)
1274{
1275 size_t cbBuf = pStream->enmBufStyle == RTSTREAMBUFSTYLE_FULL ? _64K : _16K;
1276 do
1277 {
1278 pStream->pchBuf = (char *)RTMemAllocZ(cbBuf);
1279 if (RT_LIKELY(pStream->pchBuf))
1280 {
1281# ifdef RTSTREAM_WITH_TEXT_MODE
1282 Assert(RT_ALIGN_Z(cbBuf, 64 / 8) == cbBuf);
1283 pStream->pbmBuf = (uint32_t *)RTMemAllocZ(cbBuf / 8);
1284 if (RT_LIKELY(pStream->pbmBuf))
1285# endif
1286 {
1287 pStream->cbBufAlloc = cbBuf;
1288 return VINF_SUCCESS;
1289 }
1290# ifdef RTSTREAM_WITH_TEXT_MODE
1291 RTMemFree(pStream->pchBuf);
1292 pStream->pchBuf = NULL;
1293# endif
1294 }
1295 cbBuf /= 2;
1296 } while (cbBuf >= 256);
1297 return VERR_NO_MEMORY;
1298}
1299
1300
1301/**
1302 * Checks the stream error status, flushed any pending writes, ensures there is
1303 * a buffer allocated and switches the stream to the read direction.
1304 *
1305 * @returns IPRT status code (same as i32Error).
1306 * @param pStream The stream.
1307 */
1308static int rtStrmBufCheckErrorAndSwitchToReadMode(PRTSTREAM pStream)
1309{
1310 int rc = pStream->i32Error;
1311 if (RT_SUCCESS(rc))
1312 {
1313 /*
1314 * We're very likely already in read mode and can return without doing
1315 * anything here.
1316 */
1317 if (pStream->enmBufDir == RTSTREAMBUFDIR_READ)
1318 return VINF_SUCCESS;
1319
1320 /*
1321 * Flush any pending writes before switching the buffer to read:
1322 */
1323 rc = rtStrmBufFlushWriteMaybe(pStream, false /*fInvalidate*/);
1324 if (RT_SUCCESS(rc))
1325 {
1326 pStream->enmBufDir = RTSTREAMBUFDIR_READ;
1327 pStream->offBufEnd = 0;
1328 pStream->offBufFirst = 0;
1329 pStream->fPendingCr = false;
1330
1331 /*
1332 * Read direction implies a buffer, so make sure we've got one and
1333 * change to NONE direction if allocating one fails.
1334 */
1335 if (pStream->pchBuf)
1336 {
1337 Assert(pStream->cbBufAlloc >= 256);
1338 return VINF_SUCCESS;
1339 }
1340
1341 rc = rtStrmBufAlloc(pStream);
1342 if (RT_SUCCESS(rc))
1343 return VINF_SUCCESS;
1344
1345 pStream->enmBufDir = RTSTREAMBUFDIR_NONE;
1346 }
1347 ASMAtomicWriteS32(&pStream->i32Error, rc);
1348 }
1349 return rc;
1350}
1351
1352
1353/**
1354 * Checks the stream error status, ensures there is a buffer allocated and
1355 * switches the stream to the write direction.
1356 *
1357 * @returns IPRT status code (same as i32Error).
1358 * @param pStream The stream.
1359 */
1360static int rtStrmBufCheckErrorAndSwitchToWriteMode(PRTSTREAM pStream)
1361{
1362 int rc = pStream->i32Error;
1363 if (RT_SUCCESS(rc))
1364 {
1365 /*
1366 * We're very likely already in write mode and can return without doing
1367 * anything here.
1368 */
1369 if (pStream->enmBufDir == RTSTREAMBUFDIR_WRITE)
1370 return VINF_SUCCESS;
1371
1372 /*
1373 * A read buffer does not need any flushing, so we just have to make
1374 * sure there is a buffer present before switching to the write direction.
1375 */
1376 pStream->enmBufDir = RTSTREAMBUFDIR_WRITE;
1377 pStream->offBufEnd = 0;
1378 pStream->offBufFirst = 0;
1379 if (pStream->pchBuf)
1380 {
1381 Assert(pStream->cbBufAlloc >= 256);
1382 return VINF_SUCCESS;
1383 }
1384
1385 rc = rtStrmBufAlloc(pStream);
1386 if (RT_SUCCESS(rc))
1387 return VINF_SUCCESS;
1388
1389 pStream->enmBufDir = RTSTREAMBUFDIR_NONE;
1390 ASMAtomicWriteS32(&pStream->i32Error, rc);
1391 }
1392 return rc;
1393}
1394
1395
1396/**
1397 * Reads more bytes into the buffer.
1398 *
1399 * @returns IPRT status code (same as i32Error).
1400 * @param pStream The stream.
1401 */
1402static int rtStrmBufFill(PRTSTREAM pStream)
1403{
1404 /*
1405 * Check preconditions
1406 */
1407 Assert(pStream->i32Error == VINF_SUCCESS);
1408 Assert(pStream->enmBufDir == RTSTREAMBUFDIR_READ);
1409 AssertPtr(pStream->pchBuf);
1410 Assert(pStream->cbBufAlloc >= 256);
1411 Assert(RT_ALIGN_Z(pStream->cbBufAlloc, 64) == pStream->cbBufAlloc);
1412 Assert(pStream->offBufFirst <= pStream->cbBufAlloc);
1413 Assert(pStream->offBufEnd <= pStream->cbBufAlloc);
1414 Assert(pStream->offBufFirst <= pStream->offBufEnd);
1415# ifdef RTSTREAM_WITH_TEXT_MODE
1416 AssertPtr(pStream->pbmBuf);
1417# endif
1418 /*
1419 * If there is data in the buffer, move it up to the start.
1420 */
1421 size_t cbInBuffer;
1422 if (!pStream->offBufFirst)
1423 cbInBuffer = pStream->offBufEnd;
1424 else
1425 {
1426 cbInBuffer = pStream->offBufEnd - pStream->offBufFirst;
1427 if (cbInBuffer)
1428 {
1429 memmove(pStream->pchBuf, &pStream->pchBuf[pStream->offBufFirst], cbInBuffer);
1430# ifdef RTSTREAM_WITH_TEXT_MODE
1431 if (!pStream->fBinary) /** @todo this isn't very efficient, must be a better way of shifting a bitmap. */
1432 for (size_t off = 0; off < pStream->offBufFirst; off++)
1433 if (ASMBitTest(pStream->pbmBuf, (int32_t)off))
1434 ASMBitSet(pStream->pbmBuf, (int32_t)off);
1435 else
1436 ASMBitClear(pStream->pbmBuf, (int32_t)off);
1437# endif
1438 }
1439 pStream->offBufFirst = 0;
1440 pStream->offBufEnd = cbInBuffer;
1441 }
1442
1443 /*
1444 * Add pending CR to the buffer.
1445 */
1446 size_t const offCrLfConvStart = cbInBuffer;
1447 Assert(cbInBuffer + 2 <= pStream->cbBufAlloc);
1448 if (!pStream->fPendingCr || pStream->fBinary)
1449 { /* likely */ }
1450 else
1451 {
1452 pStream->pchBuf[cbInBuffer] = '\r';
1453 pStream->fPendingCr = false;
1454 pStream->offBufEnd = ++cbInBuffer;
1455 }
1456
1457 /*
1458 * Read data till the buffer is full.
1459 */
1460 int rc = VERR_INVALID_HANDLE;
1461 RTFILE const hFile = rtStrmGetFile(pStream);
1462 if (hFile != NIL_RTFILE)
1463 {
1464 size_t cbRead = 0;
1465 rc = RTFileRead(hFile, &pStream->pchBuf[cbInBuffer], pStream->cbBufAlloc - cbInBuffer, &cbRead);
1466 if (RT_SUCCESS(rc))
1467 {
1468 cbInBuffer += cbRead;
1469 pStream->offBufEnd = cbInBuffer;
1470
1471 if (cbInBuffer != 0)
1472 {
1473# ifdef RTSTREAM_WITH_TEXT_MODE
1474 if (pStream->fBinary)
1475# endif
1476 return VINF_SUCCESS;
1477 }
1478 else
1479 {
1480 /** @todo this shouldn't be sticky, should it? */
1481 ASMAtomicWriteS32(&pStream->i32Error, VERR_EOF);
1482 return VERR_EOF;
1483 }
1484
1485# ifdef RTSTREAM_WITH_TEXT_MODE
1486 /*
1487 * Do CRLF -> LF conversion in the buffer.
1488 */
1489 ASMBitClearRange(pStream->pbmBuf, offCrLfConvStart, RT_ALIGN_Z(cbInBuffer, 64));
1490 char *pchCur = &pStream->pchBuf[offCrLfConvStart];
1491 size_t cbLeft = cbInBuffer - offCrLfConvStart;
1492 while (cbLeft > 0)
1493 {
1494 Assert(&pchCur[cbLeft] == &pStream->pchBuf[pStream->offBufEnd]);
1495 char *pchCr = (char *)memchr(pchCur, '\r', cbLeft);
1496 if (pchCr)
1497 {
1498 size_t offCur = (size_t)(pchCr - pchCur);
1499 if (offCur + 1 < cbLeft)
1500 {
1501 if (pchCr[1] == '\n')
1502 {
1503 /* Found one '\r\n' sequence. Look for more before shifting the buffer content. */
1504 cbLeft -= offCur;
1505 pchCur = pchCr;
1506
1507 do
1508 {
1509 ASMBitSet(pStream->pbmBuf, (int32_t)(pchCur - pStream->pchBuf));
1510 *pchCur++ = '\n'; /* dst */
1511 cbLeft -= 2;
1512 pchCr += 2; /* src */
1513 } while (cbLeft >= 2 && pchCr[0] == '\r' && pchCr[1] == '\n');
1514
1515 memmove(&pchCur, pchCr, cbLeft);
1516 }
1517 else
1518 {
1519 cbLeft -= offCur + 1;
1520 pchCur = pchCr + 1;
1521 }
1522 }
1523 else
1524 {
1525 Assert(pchCr == &pStream->pchBuf[pStream->offBufEnd - 1]);
1526 pStream->fPendingCr = true;
1527 pStream->offBufEnd = --cbInBuffer;
1528 break;
1529 }
1530 }
1531 else
1532 break;
1533 }
1534
1535 return VINF_SUCCESS;
1536# endif
1537 }
1538 }
1539
1540 /*
1541 * If there is data in the buffer, don't raise the error till it has all
1542 * been consumed, ASSUMING that another fill call will follow and that the
1543 * error condition will reoccur then.
1544 *
1545 * Note! We may currently end up not converting a CRLF pair, if it's
1546 * split over a temporary EOF condition, since we forces the caller
1547 * to read the CR before requesting more data. However, it's not a
1548 * very likely scenario, so we'll just leave it like that for now.
1549 */
1550 if (cbInBuffer)
1551 return VINF_SUCCESS;
1552 ASMAtomicWriteS32(&pStream->i32Error, rc);
1553 return rc;
1554}
1555
1556
1557/**
1558 * Copies @a cbSrc bytes from @a pvSrc and into the buffer, flushing as needed
1559 * to make space available.
1560 *
1561 *
1562 * @returns IPRT status code (errors not assigned to i32Error).
1563 * @param pStream The stream.
1564 * @param pvSrc The source buffer.
1565 * @param cbSrc Number of bytes to copy from @a pvSrc.
1566 * @param pcbTotal A total counter to update with what was copied.
1567 */
1568static int rtStrmBufCopyTo(PRTSTREAM pStream, const void *pvSrc, size_t cbSrc, size_t *pcbTotal)
1569{
1570 Assert(cbSrc > 0);
1571 for (;;)
1572 {
1573 size_t cbToCopy = RT_MIN(pStream->cbBufAlloc - pStream->offBufEnd, cbSrc);
1574 if (cbToCopy)
1575 {
1576 memcpy(&pStream->pchBuf[pStream->offBufEnd], pvSrc, cbToCopy);
1577 pStream->offBufEnd += cbToCopy;
1578 pvSrc = (const char *)pvSrc + cbToCopy;
1579 *pcbTotal += cbToCopy;
1580 cbSrc -= cbToCopy;
1581 if (!cbSrc)
1582 break;
1583 }
1584
1585 int rc = rtStrmBufFlushWrite(pStream, pStream->offBufEnd - pStream->offBufFirst);
1586 if (RT_FAILURE(rc))
1587 return rc;
1588 }
1589 return VINF_SUCCESS;
1590}
1591
1592
1593/**
1594 * Worker for rtStrmFlushAndCloseAll and rtStrmFlushAndClose.
1595 */
1596static RTFILE rtStrmFlushAndCleanup(PRTSTREAM pStream)
1597{
1598 if (pStream->pchBuf)
1599 {
1600 if ( pStream->enmBufDir == RTSTREAMBUFDIR_WRITE
1601 && pStream->offBufFirst < pStream->offBufEnd
1602 && RT_SUCCESS(pStream->i32Error) )
1603 rtStrmBufFlushWrite(pStream, pStream->offBufEnd - pStream->offBufFirst);
1604 RTMemFree(pStream->pchBuf);
1605 pStream->pchBuf = NULL;
1606 pStream->offBufFirst = 0;
1607 pStream->offBufEnd = 0;
1608# ifdef RTSTREAM_WITH_TEXT_MODE
1609 RTMemFree(pStream->pbmBuf);
1610 pStream->pbmBuf = NULL;
1611# endif
1612 }
1613
1614 PRTCRITSECT pCritSect = pStream->pCritSect;
1615 if (pCritSect)
1616 {
1617 pStream->pCritSect = NULL;
1618 RTCritSectDelete(pCritSect);
1619 RTMemFree(pCritSect);
1620 }
1621
1622 RTFILE hFile = pStream->hFile;
1623 pStream->hFile = NIL_RTFILE;
1624 return hFile;
1625}
1626
1627
1628/**
1629 * Worker for rtStrmFlushAndCloseAll.
1630 */
1631static void rtStrmFlushAndClose(PRTSTREAM pStream)
1632{
1633 pStream->u32Magic = ~RTSTREAM_MAGIC;
1634 RTFILE hFile = rtStrmFlushAndCleanup(pStream);
1635 if (hFile != NIL_RTFILE)
1636 RTFileClose(hFile);
1637 RTMemFree(pStream);
1638}
1639
1640
1641/**
1642 * Flushes and cleans up the standard streams, should flush and close all others
1643 * too but doesn't yet...
1644 */
1645DECLCALLBACK(void) rtStrmFlushAndCloseAll(void)
1646{
1647 /*
1648 * Flush the standard handles.
1649 */
1650 rtStrmFlushAndCleanup(&g_StdOut);
1651 rtStrmFlushAndCleanup(&g_StdErr);
1652 rtStrmFlushAndCleanup(&g_StdIn);
1653
1654 /*
1655 * Make a list of the rest and flush+close those too.
1656 */
1657 if (RTOnceWasInitialized(&g_StreamListOnce))
1658 {
1659 RTCritSectDelete(&g_StreamListCritSect);
1660
1661 PRTSTREAM pStream;
1662 while ((pStream = RTListRemoveFirst(&g_StreamList, RTSTREAM, ListEntry)) != NULL)
1663 rtStrmFlushAndClose(pStream);
1664
1665 RTOnceReset(&g_StreamListOnce);
1666 }
1667}
1668
1669# ifdef IPRT_COMPILER_TERM_CALLBACK
1670IPRT_COMPILER_TERM_CALLBACK(rtStrmFlushAndCloseAll);
1671# endif
1672
1673#endif /* RTSTREAM_STANDALONE */
1674
1675
1676/**
1677 * Rewinds the stream.
1678 *
1679 * Stream errors will be reset on success.
1680 *
1681 * @returns IPRT status code.
1682 *
1683 * @param pStream The stream.
1684 *
1685 * @remarks Not all streams are rewindable and that behavior is currently
1686 * undefined for those.
1687 */
1688RTR3DECL(int) RTStrmRewind(PRTSTREAM pStream)
1689{
1690 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
1691 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
1692
1693#ifdef RTSTREAM_STANDALONE
1694 rtStrmLock(pStream);
1695 int const rc1 = rtStrmBufFlushWriteMaybe(pStream, true /*fInvalidate*/);
1696 int const rc2 = RTFileSeek(rtStrmGetFile(pStream), 0, RTFILE_SEEK_BEGIN, NULL);
1697 int rc = RT_SUCCESS(rc1) ? rc2 : rc1;
1698 ASMAtomicWriteS32(&pStream->i32Error, rc);
1699 rtStrmUnlock(pStream);
1700#else
1701 clearerr(pStream->pFile);
1702 errno = 0;
1703 int rc;
1704 if (!fseek(pStream->pFile, 0, SEEK_SET))
1705 rc = VINF_SUCCESS;
1706 else
1707 rc = RTErrConvertFromErrno(errno);
1708 ASMAtomicWriteS32(&pStream->i32Error, rc);
1709#endif
1710 return rc;
1711}
1712
1713
1714/**
1715 * Changes the file position.
1716 *
1717 * @returns IPRT status code.
1718 *
1719 * @param pStream The stream.
1720 * @param off The seek offset.
1721 * @param uMethod Seek method, i.e. one of the RTFILE_SEEK_* defines.
1722 *
1723 * @remarks Not all streams are seekable and that behavior is currently
1724 * undefined for those.
1725 */
1726RTR3DECL(int) RTStrmSeek(PRTSTREAM pStream, RTFOFF off, uint32_t uMethod)
1727{
1728 AssertReturn(uMethod <= RTFILE_SEEK_END, VERR_INVALID_PARAMETER);
1729#ifdef RTSTREAM_STANDALONE
1730 rtStrmLock(pStream);
1731 int rc = rtStrmBufFlushWriteMaybe(pStream, true /*fInvalidate*/);
1732 if (RT_SUCCESS(rc))
1733 rc = RTFileSeek(rtStrmGetFile(pStream), off, uMethod, NULL);
1734 if (RT_FAILURE(rc))
1735 ASMAtomicWriteS32(&pStream->i32Error, rc);
1736 rtStrmUnlock(pStream);
1737#else
1738 int const iCrtMethod = uMethod == RTFILE_SEEK_BEGIN ? SEEK_SET : uMethod == RTFILE_SEEK_CURRENT ? SEEK_CUR : SEEK_END;
1739 errno = 0;
1740 int rc;
1741# ifdef _MSC_VER
1742 if (!_fseeki64(pStream->pFile, off, iCrtMethod))
1743# else
1744 if (!fseeko(pStream->pFile, off, iCrtMethod))
1745# endif
1746 rc = VINF_SUCCESS;
1747 else
1748 rc = RTErrConvertFromErrno(errno);
1749 ASMAtomicWriteS32(&pStream->i32Error, rc);
1750#endif
1751 return rc;
1752}
1753
1754
1755/**
1756 * Tells the stream position.
1757 *
1758 * @returns Stream position or IPRT error status. Non-negative numbers are
1759 * stream positions, while negative numbers are IPRT error stauses.
1760 *
1761 * @param pStream The stream.
1762 *
1763 * @remarks Not all streams have a position and that behavior is currently
1764 * undefined for those.
1765 */
1766RTR3DECL(RTFOFF) RTStrmTell(PRTSTREAM pStream)
1767{
1768#ifdef RTSTREAM_STANDALONE
1769 uint64_t off = 0;
1770 rtStrmLock(pStream);
1771 int rc = pStream->i32Error;
1772 if (RT_SUCCESS(rc))
1773 {
1774 RTFILE const hFile = rtStrmGetFile(pStream);
1775 if (hFile != NIL_RTFILE)
1776 {
1777 rc = RTFileSeek(hFile, 0, RTFILE_SEEK_CURRENT, &off);
1778 if (RT_SUCCESS(rc))
1779 {
1780 switch (pStream->enmBufDir)
1781 {
1782 case RTSTREAMBUFDIR_READ:
1783 /* Subtract unconsumed chars and removed '\r' characters. */
1784 off -= pStream->offBufEnd - pStream->offBufFirst;
1785 if (!pStream->fBinary)
1786 for (size_t offBuf = pStream->offBufFirst; offBuf < pStream->offBufEnd; offBuf++)
1787 off -= ASMBitTest(pStream->pbmBuf, (int32_t)offBuf);
1788 break;
1789 case RTSTREAMBUFDIR_WRITE:
1790 /* Add unwrittend chars in the buffer. */
1791 off += pStream->offBufEnd - pStream->offBufFirst;
1792 break;
1793 default:
1794 AssertFailed();
1795 case RTSTREAMBUFDIR_NONE:
1796 break;
1797 }
1798 }
1799 }
1800 else
1801 rc = VERR_INVALID_HANDLE;
1802 }
1803 if (RT_FAILURE(rc))
1804 {
1805 ASMAtomicWriteS32(&pStream->i32Error, rc);
1806 off = rc;
1807 }
1808 rtStrmUnlock(pStream);
1809#else
1810# ifdef _MSC_VER
1811 RTFOFF off = _ftelli64(pStream->pFile);
1812# else
1813 RTFOFF off = ftello(pStream->pFile);
1814# endif
1815 if (off < 0)
1816 {
1817 int rc = RTErrConvertFromErrno(errno);
1818 ASMAtomicWriteS32(&pStream->i32Error, rc);
1819 off = rc;
1820 }
1821#endif
1822 return off;
1823}
1824
1825
1826/**
1827 * Recheck the stream mode.
1828 *
1829 * @param pStream The stream (locked).
1830 */
1831static void rtStreamRecheckMode(PRTSTREAM pStream)
1832{
1833#if (defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)) && !defined(RTSTREAM_STANDALONE)
1834 int fh = fileno(pStream->pFile);
1835 if (fh >= 0)
1836 {
1837 int fExpected = pStream->fBinary ? _O_BINARY : _O_TEXT;
1838 int fActual = _setmode(fh, fExpected);
1839 if (fActual != -1 && fExpected != (fActual & (_O_BINARY | _O_TEXT)))
1840 {
1841 fActual = _setmode(fh, fActual & (_O_BINARY | _O_TEXT));
1842 pStream->fBinary = !(fActual & _O_TEXT);
1843 }
1844 }
1845#else
1846 NOREF(pStream);
1847#endif
1848 pStream->fRecheckMode = false;
1849}
1850
1851
1852/**
1853 * Reads from a file stream.
1854 *
1855 * @returns iprt status code.
1856 * @param pStream The stream.
1857 * @param pvBuf Where to put the read bits.
1858 * Must be cbRead bytes or more.
1859 * @param cbToRead Number of bytes to read.
1860 * @param pcbRead Where to store the number of bytes actually read.
1861 * If NULL cbRead bytes are read or an error is returned.
1862 */
1863RTR3DECL(int) RTStrmReadEx(PRTSTREAM pStream, void *pvBuf, size_t cbToRead, size_t *pcbRead)
1864{
1865 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
1866 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
1867
1868#ifdef RTSTREAM_STANDALONE
1869 rtStrmLock(pStream);
1870 int rc = rtStrmBufCheckErrorAndSwitchToReadMode(pStream);
1871#else
1872 int rc = pStream->i32Error;
1873#endif
1874 if (RT_SUCCESS(rc))
1875 {
1876 if (pStream->fRecheckMode)
1877 rtStreamRecheckMode(pStream);
1878
1879#ifdef RTSTREAM_STANDALONE
1880
1881 /*
1882 * Copy data thru the read buffer for now as that'll handle both binary
1883 * and text modes seamlessly. We could optimize larger reads here when
1884 * in binary mode, that can wait till the basics work, I think.
1885 */
1886 size_t cbTotal = 0;
1887 if (cbToRead > 0)
1888 for (;;)
1889 {
1890 size_t cbInBuffer = pStream->offBufEnd - pStream->offBufFirst;
1891 if (cbInBuffer > 0)
1892 {
1893 size_t cbToCopy = RT_MIN(cbInBuffer, cbToRead);
1894 memcpy(pvBuf, &pStream->pchBuf[pStream->offBufFirst], cbToCopy);
1895 cbTotal += cbToRead;
1896 cbToRead -= cbToCopy;
1897 pvBuf = (char *)pvBuf + cbToCopy;
1898 if (!cbToRead)
1899 break;
1900 }
1901 rc = rtStrmBufFill(pStream);
1902 if (RT_SUCCESS(rc))
1903 { /* likely */ }
1904 else
1905 {
1906 if (rc == VERR_EOF && pcbRead && cbTotal > 0)
1907 rc = VINF_EOF;
1908 break;
1909 }
1910 }
1911 if (pcbRead)
1912 *pcbRead = cbTotal;
1913
1914#else /* !RTSTREAM_STANDALONE */
1915 if (pcbRead)
1916 {
1917 /*
1918 * Can do with a partial read.
1919 */
1920 *pcbRead = fread(pvBuf, 1, cbToRead, pStream->pFile);
1921 if ( *pcbRead == cbToRead
1922 || !ferror(pStream->pFile))
1923 rc = VINF_SUCCESS;
1924 else if (feof(pStream->pFile))
1925 rc = *pcbRead ? VINF_EOF : VERR_EOF;
1926 else if (ferror(pStream->pFile))
1927 rc = VERR_READ_ERROR;
1928 else
1929 {
1930 AssertMsgFailed(("This shouldn't happen\n"));
1931 rc = VERR_INTERNAL_ERROR;
1932 }
1933 }
1934 else
1935 {
1936 /*
1937 * Must read it all!
1938 */
1939 if (fread(pvBuf, cbToRead, 1, pStream->pFile) == 1)
1940 rc = VINF_SUCCESS;
1941 /* possible error/eof. */
1942 else if (feof(pStream->pFile))
1943 rc = VERR_EOF;
1944 else if (ferror(pStream->pFile))
1945 rc = VERR_READ_ERROR;
1946 else
1947 {
1948 AssertMsgFailed(("This shouldn't happen\n"));
1949 rc = VERR_INTERNAL_ERROR;
1950 }
1951 }
1952#endif /* !RTSTREAM_STANDALONE */
1953 if (RT_FAILURE(rc))
1954 ASMAtomicWriteS32(&pStream->i32Error, rc);
1955 }
1956#ifdef RTSTREAM_STANDALONE
1957 rtStrmUnlock(pStream);
1958#endif
1959 return rc;
1960}
1961
1962
1963/**
1964 * Check if the input text is valid UTF-8.
1965 *
1966 * @returns true/false.
1967 * @param pvBuf Pointer to the buffer.
1968 * @param cbBuf Size of the buffer.
1969 */
1970static bool rtStrmIsUtf8Text(const void *pvBuf, size_t cbBuf)
1971{
1972 NOREF(pvBuf);
1973 NOREF(cbBuf);
1974 /** @todo not sure this is a good idea... Better redefine RTStrmWrite. */
1975 return false;
1976}
1977
1978
1979#if defined(RT_OS_WINDOWS) && !defined(RTSTREAM_STANDALONE)
1980
1981/**
1982 * Check if the stream is for a Window console.
1983 *
1984 * @returns true / false.
1985 * @param pStream The stream.
1986 * @param phCon Where to return the console handle.
1987 */
1988static bool rtStrmIsConsoleUnlocked(PRTSTREAM pStream, HANDLE *phCon)
1989{
1990 int fh = fileno(pStream->pFile);
1991 if (isatty(fh))
1992 {
1993 DWORD dwMode;
1994 HANDLE hCon = (HANDLE)_get_osfhandle(fh);
1995 if (GetConsoleMode(hCon, &dwMode))
1996 {
1997 *phCon = hCon;
1998 return true;
1999 }
2000 }
2001 return false;
2002}
2003
2004
2005static int rtStrmWriteWinConsoleLocked(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten, HANDLE hCon)
2006{
2007 int rc;
2008# ifdef HAVE_FWRITE_UNLOCKED
2009 if (!fflush_unlocked(pStream->pFile))
2010# else
2011 if (!fflush(pStream->pFile))
2012# endif
2013 {
2014 /** @todo Consider buffering later. For now, we'd rather correct output than
2015 * fast output. */
2016 DWORD cwcWritten = 0;
2017 PRTUTF16 pwszSrc = NULL;
2018 size_t cwcSrc = 0;
2019 rc = RTStrToUtf16Ex((const char *)pvBuf, cbToWrite, &pwszSrc, 0, &cwcSrc);
2020 AssertRC(rc);
2021 if (RT_SUCCESS(rc))
2022 {
2023 if (!WriteConsoleW(hCon, pwszSrc, (DWORD)cwcSrc, &cwcWritten, NULL))
2024 {
2025 /* try write char-by-char to avoid heap problem. */
2026 cwcWritten = 0;
2027 while (cwcWritten != cwcSrc)
2028 {
2029 DWORD cwcThis;
2030 if (!WriteConsoleW(hCon, &pwszSrc[cwcWritten], 1, &cwcThis, NULL))
2031 {
2032 if (!pcbWritten || cwcWritten == 0)
2033 rc = RTErrConvertFromErrno(GetLastError());
2034 break;
2035 }
2036 if (cwcThis != 1) /* Unable to write current char (amount)? */
2037 break;
2038 cwcWritten++;
2039 }
2040 }
2041 if (RT_SUCCESS(rc))
2042 {
2043 if (cwcWritten == cwcSrc)
2044 {
2045 if (pcbWritten)
2046 *pcbWritten = cbToWrite;
2047 }
2048 else if (pcbWritten)
2049 {
2050 PCRTUTF16 pwszCur = pwszSrc;
2051 const char *pszCur = (const char *)pvBuf;
2052 while ((uintptr_t)(pwszCur - pwszSrc) < cwcWritten)
2053 {
2054 RTUNICP CpIgnored;
2055 RTUtf16GetCpEx(&pwszCur, &CpIgnored);
2056 RTStrGetCpEx(&pszCur, &CpIgnored);
2057 }
2058 *pcbWritten = pszCur - (const char *)pvBuf;
2059 }
2060 else
2061 rc = VERR_WRITE_ERROR;
2062 }
2063 RTUtf16Free(pwszSrc);
2064 }
2065 }
2066 else
2067 rc = RTErrConvertFromErrno(errno);
2068 return rc;
2069}
2070
2071#endif /* RT_OS_WINDOWS */
2072
2073static int rtStrmWriteWorkerLocked(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten, bool fMustWriteAll)
2074{
2075#ifdef RTSTREAM_STANDALONE
2076 /*
2077 * Check preconditions.
2078 */
2079 Assert(pStream->enmBufDir == RTSTREAMBUFDIR_WRITE);
2080 Assert(pStream->cbBufAlloc >= 256);
2081 Assert(pStream->offBufFirst <= pStream->cbBufAlloc);
2082 Assert(pStream->offBufEnd <= pStream->cbBufAlloc);
2083 Assert(pStream->offBufFirst <= pStream->offBufEnd);
2084
2085 /*
2086 * We write everything via the buffer, letting the buffer flushing take
2087 * care of console output hacks and similar.
2088 */
2089 RT_NOREF(fMustWriteAll);
2090 int rc = VINF_SUCCESS;
2091 size_t cbTotal = 0;
2092 if (cbToWrite > 0)
2093 {
2094# ifdef RTSTREAM_WITH_TEXT_MODE
2095 const char *pchLf;
2096 if ( !pStream->fBinary
2097 && (pchLf = (const char *)memchr(pvBuf, '\n', cbToWrite)) != NULL)
2098 for (;;)
2099 {
2100 /* Deal with everything up to the newline. */
2101 size_t const cbToLf = (size_t)(pchLf - (const char *)pvBuf);
2102 if (cbToLf > 0)
2103 {
2104 rc = rtStrmBufCopyTo(pStream, pvBuf, cbToLf, &cbTotal);
2105 if (RT_FAILURE(rc))
2106 break;
2107 }
2108
2109 /* Copy the CRLF sequence into the buffer in one go to avoid complications. */
2110 if (pStream->cbBufAlloc - pStream->offBufEnd < 2)
2111 {
2112 rc = rtStrmBufFlushWrite(pStream, pStream->offBufEnd - pStream->offBufFirst);
2113 if (RT_FAILURE(rc))
2114 break;
2115 Assert(pStream->cbBufAlloc - pStream->offBufEnd >= 2);
2116 }
2117 pStream->pchBuf[pStream->offBufEnd++] = '\r';
2118 pStream->pchBuf[pStream->offBufEnd++] = '\n';
2119
2120 /* Advance past the newline. */
2121 pvBuf = (const char *)pvBuf + 1 + cbToLf;
2122 cbTotal += 1 + cbToLf;
2123 cbToWrite -= 1 + cbToLf;
2124 if (!cbToWrite)
2125 break;
2126
2127 /* More newlines? */
2128 pchLf = (const char *)memchr(pvBuf, '\n', cbToWrite);
2129 if (!pchLf)
2130 {
2131 rc = rtStrmBufCopyTo(pStream, pvBuf, cbToWrite, &cbTotal);
2132 break;
2133 }
2134 }
2135 else
2136# endif
2137 rc = rtStrmBufCopyTo(pStream, pvBuf, cbToWrite, &cbTotal);
2138
2139 /*
2140 * If line buffered or unbuffered, we probably have to do some flushing now.
2141 */
2142 if (RT_SUCCESS(rc) && pStream->enmBufStyle != RTSTREAMBUFSTYLE_FULL)
2143 {
2144 Assert(pStream->enmBufStyle == RTSTREAMBUFSTYLE_LINE || pStream->enmBufStyle == RTSTREAMBUFSTYLE_UNBUFFERED);
2145 size_t cbInBuffer = pStream->offBufEnd - pStream->offBufFirst;
2146 if (cbInBuffer > 0)
2147 {
2148 if ( pStream->enmBufStyle != RTSTREAMBUFSTYLE_LINE
2149 || pStream->pchBuf[pStream->offBufEnd - 1] == '\n')
2150 rc = rtStrmBufFlushWrite(pStream, cbInBuffer);
2151 else
2152 {
2153 const char *pchToFlush = &pStream->pchBuf[pStream->offBufFirst];
2154 const char *pchLastLf = (const char *)memrchr(pchToFlush, '\n', cbInBuffer);
2155 if (pchLastLf)
2156 rc = rtStrmBufFlushWrite(pStream, (size_t)(&pchLastLf[1] - pchToFlush));
2157 }
2158 }
2159 }
2160 }
2161 if (pcbWritten)
2162 *pcbWritten = cbTotal;
2163 return rc;
2164
2165
2166#else
2167 if (!fMustWriteAll)
2168 {
2169 IPRT_ALIGNMENT_CHECKS_DISABLE(); /* glibc / mempcpy again */
2170# ifdef HAVE_FWRITE_UNLOCKED
2171 *pcbWritten = fwrite_unlocked(pvBuf, 1, cbToWrite, pStream->pFile);
2172# else
2173 *pcbWritten = fwrite(pvBuf, 1, cbToWrite, pStream->pFile);
2174# endif
2175 IPRT_ALIGNMENT_CHECKS_ENABLE();
2176 if ( *pcbWritten == cbToWrite
2177# ifdef HAVE_FWRITE_UNLOCKED
2178 || !ferror_unlocked(pStream->pFile))
2179# else
2180 || !ferror(pStream->pFile))
2181# endif
2182 return VINF_SUCCESS;
2183 }
2184 else
2185 {
2186 /* Must write it all! */
2187 IPRT_ALIGNMENT_CHECKS_DISABLE(); /* glibc / mempcpy again */
2188# ifdef HAVE_FWRITE_UNLOCKED
2189 size_t cbWritten = fwrite_unlocked(pvBuf, cbToWrite, 1, pStream->pFile);
2190# else
2191 size_t cbWritten = fwrite(pvBuf, cbToWrite, 1, pStream->pFile);
2192# endif
2193 if (pcbWritten)
2194 *pcbWritten = cbWritten;
2195 IPRT_ALIGNMENT_CHECKS_ENABLE();
2196 if (cbWritten == 1)
2197 return VINF_SUCCESS;
2198# ifdef HAVE_FWRITE_UNLOCKED
2199 if (!ferror_unlocked(pStream->pFile))
2200# else
2201 if (!ferror(pStream->pFile))
2202# endif
2203 return VINF_SUCCESS; /* WEIRD! But anyway... */
2204 }
2205 return VERR_WRITE_ERROR;
2206#endif
2207}
2208
2209
2210/**
2211 * Internal write API, stream lock already held.
2212 *
2213 * @returns IPRT status code.
2214 * @param pStream The stream.
2215 * @param pvBuf What to write.
2216 * @param cbToWrite How much to write.
2217 * @param pcbWritten Where to optionally return the number of bytes
2218 * written.
2219 * @param fSureIsText Set if we're sure this is UTF-8 text already.
2220 */
2221static int rtStrmWriteLocked(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten, bool fSureIsText)
2222{
2223#ifdef RTSTREAM_STANDALONE
2224 int rc = rtStrmBufCheckErrorAndSwitchToWriteMode(pStream);
2225#else
2226 int rc = pStream->i32Error;
2227#endif
2228 if (RT_FAILURE(rc))
2229 return rc;
2230 if (pStream->fRecheckMode)
2231 rtStreamRecheckMode(pStream);
2232
2233#if defined(RT_OS_WINDOWS) && !defined(RTSTREAM_STANDALONE)
2234 /*
2235 * Use the unicode console API when possible in order to avoid stuff
2236 * getting lost in unnecessary code page translations.
2237 */
2238 HANDLE hCon;
2239 if (rtStrmIsConsoleUnlocked(pStream, &hCon))
2240 rc = rtStrmWriteWinConsoleLocked(pStream, pvBuf, cbToWrite, pcbWritten, hCon);
2241#else
2242 if (0) { }
2243#endif /* RT_OS_WINDOWS && !RTSTREAM_STANDALONE */
2244
2245 /*
2246 * If we're sure it's text output, convert it from UTF-8 to the current
2247 * code page before printing it.
2248 *
2249 * Note! Partial writes are not supported in this scenario because we
2250 * cannot easily report back a written length matching the input.
2251 */
2252 /** @todo Skip this if the current code set is UTF-8. */
2253 else if ( pStream->fCurrentCodeSet
2254 && !pStream->fBinary
2255 && ( fSureIsText
2256 || rtStrmIsUtf8Text(pvBuf, cbToWrite))
2257 )
2258 {
2259 char *pszSrcFree = NULL;
2260 const char *pszSrc = (const char *)pvBuf;
2261 if (pszSrc[cbToWrite - 1])
2262 {
2263 pszSrc = pszSrcFree = RTStrDupN(pszSrc, cbToWrite);
2264 if (pszSrc == NULL)
2265 rc = VERR_NO_STR_MEMORY;
2266 }
2267 if (RT_SUCCESS(rc))
2268 {
2269 char *pszSrcCurCP;
2270 rc = RTStrUtf8ToCurrentCP(&pszSrcCurCP, pszSrc);
2271 AssertRC(rc);
2272 if (RT_SUCCESS(rc))
2273 {
2274 size_t cchSrcCurCP = strlen(pszSrcCurCP);
2275 size_t cbWritten = 0;
2276 rc = rtStrmWriteWorkerLocked(pStream, pszSrcCurCP, cchSrcCurCP, &cbWritten, true /*fMustWriteAll*/);
2277 if (pcbWritten)
2278 *pcbWritten = cbWritten == cchSrcCurCP ? cbToWrite : 0;
2279 RTStrFree(pszSrcCurCP);
2280 }
2281 RTStrFree(pszSrcFree);
2282 }
2283 }
2284 /*
2285 * Otherwise, just write it as-is.
2286 */
2287 else
2288 rc = rtStrmWriteWorkerLocked(pStream, pvBuf, cbToWrite, pcbWritten, pcbWritten == NULL);
2289
2290 /*
2291 * Update error status on failure and return.
2292 *
2293 * We ignore failures from RTStrUtf8ToCurrentCP and RTStrToUtf16Ex regarding
2294 * invalid UTF-8 encoding, as that's an input issue and shouldn't affect the
2295 * stream state.
2296 */
2297 if (RT_FAILURE(rc) && rc != VERR_INVALID_UTF8_ENCODING)
2298 ASMAtomicWriteS32(&pStream->i32Error, rc);
2299 return rc;
2300}
2301
2302
2303/**
2304 * Internal write API.
2305 *
2306 * @returns IPRT status code.
2307 * @param pStream The stream.
2308 * @param pvBuf What to write.
2309 * @param cbToWrite How much to write.
2310 * @param pcbWritten Where to optionally return the number of bytes
2311 * written.
2312 * @param fSureIsText Set if we're sure this is UTF-8 text already.
2313 */
2314DECLINLINE(int) rtStrmWrite(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten, bool fSureIsText)
2315{
2316 rtStrmLock(pStream);
2317 int rc = rtStrmWriteLocked(pStream, pvBuf, cbToWrite, pcbWritten, fSureIsText);
2318 rtStrmUnlock(pStream);
2319 return rc;
2320}
2321
2322
2323/**
2324 * Writes to a file stream.
2325 *
2326 * @returns iprt status code.
2327 * @param pStream The stream.
2328 * @param pvBuf Where to get the bits to write from.
2329 * @param cbToWrite Number of bytes to write.
2330 * @param pcbWritten Where to store the number of bytes actually written.
2331 * If NULL cbToWrite bytes are written or an error is
2332 * returned.
2333 */
2334RTR3DECL(int) RTStrmWriteEx(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten)
2335{
2336 AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER);
2337 return rtStrmWrite(pStream, pvBuf, cbToWrite, pcbWritten, false);
2338}
2339
2340
2341/**
2342 * Reads a character from a file stream.
2343 *
2344 * @returns The char as an unsigned char cast to int.
2345 * @returns -1 on failure.
2346 * @param pStream The stream.
2347 */
2348RTR3DECL(int) RTStrmGetCh(PRTSTREAM pStream)
2349{
2350 unsigned char ch;
2351 int rc = RTStrmReadEx(pStream, &ch, 1, NULL);
2352 if (RT_SUCCESS(rc))
2353 return ch;
2354 return -1;
2355}
2356
2357
2358/**
2359 * Writes a character to a file stream.
2360 *
2361 * @returns iprt status code.
2362 * @param pStream The stream.
2363 * @param ch The char to write.
2364 */
2365RTR3DECL(int) RTStrmPutCh(PRTSTREAM pStream, int ch)
2366{
2367 return rtStrmWrite(pStream, &ch, 1, NULL, true /*fSureIsText*/);
2368}
2369
2370
2371/**
2372 * Writes a string to a file stream.
2373 *
2374 * @returns iprt status code.
2375 * @param pStream The stream.
2376 * @param pszString The string to write.
2377 * No newlines or anything is appended or prepended.
2378 * The terminating '\\0' is not written, of course.
2379 */
2380RTR3DECL(int) RTStrmPutStr(PRTSTREAM pStream, const char *pszString)
2381{
2382 size_t cch = strlen(pszString);
2383 return rtStrmWrite(pStream, pszString, cch, NULL, true /*fSureIsText*/);
2384}
2385
2386
2387RTR3DECL(int) RTStrmGetLine(PRTSTREAM pStream, char *pszString, size_t cbString)
2388{
2389 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
2390 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
2391 AssertReturn(pszString, VERR_INVALID_POINTER);
2392 AssertReturn(cbString >= 2, VERR_INVALID_PARAMETER);
2393
2394 rtStrmLock(pStream);
2395
2396#ifdef RTSTREAM_STANDALONE
2397 int rc = rtStrmBufCheckErrorAndSwitchToReadMode(pStream);
2398#else
2399 int rc = pStream->i32Error;
2400#endif
2401 if (RT_SUCCESS(rc))
2402 {
2403 cbString--; /* Reserve space for the terminator. */
2404
2405#ifdef RTSTREAM_STANDALONE
2406 char * const pszStringStart = pszString;
2407#endif
2408 for (;;)
2409 {
2410#ifdef RTSTREAM_STANDALONE
2411 /* Make sure there is at least one character in the buffer: */
2412 size_t cbInBuffer = pStream->offBufEnd - pStream->offBufFirst;
2413 if (cbInBuffer == 0)
2414 {
2415 rc = rtStrmBufFill(pStream);
2416 if (RT_SUCCESS(rc))
2417 cbInBuffer = pStream->offBufEnd - pStream->offBufFirst;
2418 else
2419 break;
2420 }
2421
2422 /* Scan the buffer content terminating on a '\n', '\r\n' and '\0' sequence. */
2423 const char *pchSrc = &pStream->pchBuf[pStream->offBufFirst];
2424 const char *pchNewline = (const char *)memchr(pchSrc, '\n', cbInBuffer);
2425 const char *pchTerm = (const char *)memchr(pchSrc, '\0', cbInBuffer);
2426 size_t cbCopy;
2427 size_t cbAdvance;
2428 bool fStop = pchNewline || pchTerm;
2429 if (!fStop)
2430 cbAdvance = cbCopy = cbInBuffer;
2431 else if (!pchTerm || (pchNewline && pchTerm && (uintptr_t)pchNewline < (uintptr_t)pchTerm))
2432 {
2433 cbCopy = (size_t)(pchNewline - pchSrc);
2434 cbAdvance = cbCopy + 1;
2435 if (cbCopy && pchNewline[-1] == '\r')
2436 cbCopy--;
2437 else if (cbCopy == 0 && (uintptr_t)pszString > (uintptr_t)pszStringStart && pszString[-1] == '\r')
2438 pszString--, cbString++; /* drop trailing '\r' that it turns out was followed by '\n' */
2439 }
2440 else
2441 {
2442 cbCopy = (size_t)(pchTerm - pchSrc);
2443 cbAdvance = cbCopy + 1;
2444 }
2445
2446 /* Adjust for available space in the destination buffer, copy over the string
2447 characters and advance the buffer position (even on overflow). */
2448 if (cbCopy <= cbString)
2449 pStream->offBufFirst += cbAdvance;
2450 else
2451 {
2452 rc = VERR_BUFFER_OVERFLOW;
2453 fStop = true;
2454 cbCopy = cbString;
2455 pStream->offBufFirst += cbString;
2456 }
2457
2458 memcpy(pszString, pchSrc, cbCopy);
2459 pszString += cbCopy;
2460 cbString -= cbCopy;
2461
2462 if (fStop)
2463 break;
2464
2465#else /* !RTSTREAM_STANDALONE */
2466# ifdef HAVE_FWRITE_UNLOCKED /** @todo darwin + freebsd(?) has fgetc_unlocked but not fwrite_unlocked, optimize... */
2467 int ch = fgetc_unlocked(pStream->pFile);
2468# else
2469 int ch = fgetc(pStream->pFile);
2470# endif
2471
2472 /* Deal with \r\n sequences here. We'll return lone CR, but
2473 treat CRLF as LF. */
2474 if (ch == '\r')
2475 {
2476# ifdef HAVE_FWRITE_UNLOCKED /** @todo darwin + freebsd(?) has fgetc_unlocked but not fwrite_unlocked, optimize... */
2477 ch = fgetc_unlocked(pStream->pFile);
2478# else
2479 ch = fgetc(pStream->pFile);
2480# endif
2481 if (ch == '\n')
2482 break;
2483
2484 *pszString++ = '\r';
2485 if (--cbString <= 0)
2486 {
2487 /* yeah, this is an error, we dropped a character. */
2488 rc = VERR_BUFFER_OVERFLOW;
2489 break;
2490 }
2491 }
2492
2493 /* Deal with end of file. */
2494 if (ch == EOF)
2495 {
2496# ifdef HAVE_FWRITE_UNLOCKED
2497 if (feof_unlocked(pStream->pFile))
2498# else
2499 if (feof(pStream->pFile))
2500# endif
2501 {
2502 rc = VERR_EOF;
2503 break;
2504 }
2505# ifdef HAVE_FWRITE_UNLOCKED
2506 if (ferror_unlocked(pStream->pFile))
2507# else
2508 if (ferror(pStream->pFile))
2509# endif
2510 rc = VERR_READ_ERROR;
2511 else
2512 {
2513 AssertMsgFailed(("This shouldn't happen\n"));
2514 rc = VERR_INTERNAL_ERROR;
2515 }
2516 break;
2517 }
2518
2519 /* Deal with null terminator and (lone) new line. */
2520 if (ch == '\0' || ch == '\n')
2521 break;
2522
2523 /* No special character, append it to the return string. */
2524 *pszString++ = ch;
2525 if (--cbString <= 0)
2526 {
2527 rc = VINF_BUFFER_OVERFLOW;
2528 break;
2529 }
2530#endif /* !RTSTREAM_STANDALONE */
2531 }
2532
2533 *pszString = '\0';
2534 if (RT_FAILURE(rc))
2535 ASMAtomicWriteS32(&pStream->i32Error, rc);
2536 }
2537
2538 rtStrmUnlock(pStream);
2539 return rc;
2540}
2541
2542
2543/**
2544 * Flushes a stream.
2545 *
2546 * @returns iprt status code.
2547 * @param pStream The stream to flush.
2548 */
2549RTR3DECL(int) RTStrmFlush(PRTSTREAM pStream)
2550{
2551 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
2552 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
2553
2554#ifdef RTSTREAM_STANDALONE
2555 rtStrmLock(pStream);
2556 int rc = rtStrmBufFlushWriteMaybe(pStream, true /*fInvalidate*/);
2557 rtStrmUnlock(pStream);
2558 return rc;
2559
2560#else
2561 if (!fflush(pStream->pFile))
2562 return VINF_SUCCESS;
2563 return RTErrConvertFromErrno(errno);
2564#endif
2565}
2566
2567
2568/**
2569 * Output callback.
2570 *
2571 * @returns number of bytes written.
2572 * @param pvArg User argument.
2573 * @param pachChars Pointer to an array of utf-8 characters.
2574 * @param cchChars Number of bytes in the character array pointed to by pachChars.
2575 */
2576static DECLCALLBACK(size_t) rtstrmOutput(void *pvArg, const char *pachChars, size_t cchChars)
2577{
2578 if (cchChars)
2579 rtStrmWriteLocked((PRTSTREAM)pvArg, pachChars, cchChars, NULL, true /*fSureIsText*/);
2580 /* else: ignore termination call. */
2581 return cchChars;
2582}
2583
2584
2585/**
2586 * Prints a formatted string to the specified stream.
2587 *
2588 * @returns Number of bytes printed.
2589 * @param pStream The stream to print to.
2590 * @param pszFormat IPRT format string.
2591 * @param args Arguments specified by pszFormat.
2592 */
2593RTR3DECL(int) RTStrmPrintfV(PRTSTREAM pStream, const char *pszFormat, va_list args)
2594{
2595 AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER);
2596 int rc = pStream->i32Error;
2597 if (RT_SUCCESS(rc))
2598 {
2599 rtStrmLock(pStream);
2600// pStream->fShouldFlush = true;
2601 rc = (int)RTStrFormatV(rtstrmOutput, pStream, NULL, NULL, pszFormat, args);
2602 rtStrmUnlock(pStream);
2603 Assert(rc >= 0);
2604 }
2605 else
2606 rc = -1;
2607 return rc;
2608}
2609
2610
2611/**
2612 * Prints a formatted string to the specified stream.
2613 *
2614 * @returns Number of bytes printed.
2615 * @param pStream The stream to print to.
2616 * @param pszFormat IPRT format string.
2617 * @param ... Arguments specified by pszFormat.
2618 */
2619RTR3DECL(int) RTStrmPrintf(PRTSTREAM pStream, const char *pszFormat, ...)
2620{
2621 va_list args;
2622 va_start(args, pszFormat);
2623 int rc = RTStrmPrintfV(pStream, pszFormat, args);
2624 va_end(args);
2625 return rc;
2626}
2627
2628
2629/**
2630 * Dumper vprintf-like function outputting to a stream.
2631 *
2632 * @param pvUser The stream to print to. NULL means standard output.
2633 * @param pszFormat Runtime format string.
2634 * @param va Arguments specified by pszFormat.
2635 */
2636RTDECL(void) RTStrmDumpPrintfV(void *pvUser, const char *pszFormat, va_list va)
2637{
2638 RTStrmPrintfV(pvUser ? (PRTSTREAM)pvUser : g_pStdOut, pszFormat, va);
2639}
2640
2641
2642/**
2643 * Prints a formatted string to the standard output stream (g_pStdOut).
2644 *
2645 * @returns Number of bytes printed.
2646 * @param pszFormat IPRT format string.
2647 * @param args Arguments specified by pszFormat.
2648 */
2649RTR3DECL(int) RTPrintfV(const char *pszFormat, va_list args)
2650{
2651 return RTStrmPrintfV(g_pStdOut, pszFormat, args);
2652}
2653
2654
2655/**
2656 * Prints a formatted string to the standard output stream (g_pStdOut).
2657 *
2658 * @returns Number of bytes printed.
2659 * @param pszFormat IPRT format string.
2660 * @param ... Arguments specified by pszFormat.
2661 */
2662RTR3DECL(int) RTPrintf(const char *pszFormat, ...)
2663{
2664 va_list args;
2665 va_start(args, pszFormat);
2666 int rc = RTStrmPrintfV(g_pStdOut, pszFormat, args);
2667 va_end(args);
2668 return rc;
2669}
2670
2671
2672/**
2673 * Outputs @a cchIndent spaces.
2674 */
2675static void rtStrmWrapppedIndent(RTSTRMWRAPPEDSTATE *pState, uint32_t cchIndent)
2676{
2677 static const char s_szSpaces[] = " ";
2678 while (cchIndent)
2679 {
2680 uint32_t cchToWrite = RT_MIN(cchIndent, sizeof(s_szSpaces) - 1);
2681 int rc = RTStrmWrite(pState->pStream, s_szSpaces, cchToWrite);
2682 if (RT_SUCCESS(rc))
2683 cchIndent -= cchToWrite;
2684 else
2685 {
2686 pState->rcStatus = rc;
2687 break;
2688 }
2689 }
2690}
2691
2692
2693/**
2694 * Flushes the current line.
2695 *
2696 * @param pState The wrapped output state.
2697 * @param fPartial Set if partial flush due to buffer overflow, clear when
2698 * flushing due to '\n'.
2699 */
2700static void rtStrmWrappedFlushLine(RTSTRMWRAPPEDSTATE *pState, bool fPartial)
2701{
2702 /*
2703 * Check indentation in case we need to split the line later.
2704 */
2705 uint32_t cchIndent = pState->cchIndent;
2706 if (cchIndent == UINT32_MAX)
2707 {
2708 pState->cchIndent = 0;
2709 cchIndent = pState->cchHangingIndent;
2710 while (RT_C_IS_BLANK(pState->szLine[cchIndent]))
2711 cchIndent++;
2712 }
2713
2714 /*
2715 * Do the flushing.
2716 */
2717 uint32_t cchLine = pState->cchLine;
2718 Assert(cchLine < sizeof(pState->szLine));
2719 while (cchLine >= pState->cchWidth || !fPartial)
2720 {
2721 /*
2722 * Hopefully we don't need to do any wrapping ...
2723 */
2724 uint32_t offSplit;
2725 if (pState->cchIndent + cchLine <= pState->cchWidth)
2726 {
2727 if (!fPartial)
2728 {
2729 rtStrmWrapppedIndent(pState, pState->cchIndent);
2730 pState->szLine[cchLine] = '\n';
2731 int rc = RTStrmWrite(pState->pStream, pState->szLine, cchLine + 1);
2732 if (RT_FAILURE(rc))
2733 pState->rcStatus = rc;
2734 pState->cLines += 1;
2735 pState->cchLine = 0;
2736 pState->cchIndent = UINT32_MAX;
2737 return;
2738 }
2739
2740 /*
2741 * ... no such luck.
2742 */
2743 offSplit = cchLine;
2744 }
2745 else
2746 offSplit = pState->cchWidth - pState->cchIndent;
2747
2748 /* Find the start of the current word: */
2749 while (offSplit > 0 && !RT_C_IS_BLANK(pState->szLine[offSplit - 1]))
2750 offSplit--;
2751
2752 /* Skip spaces. */
2753 while (offSplit > 0 && RT_C_IS_BLANK(pState->szLine[offSplit - 1]))
2754 offSplit--;
2755 uint32_t offNextLine = offSplit;
2756
2757 /* If the first word + indent is wider than the screen width, so just output it in full. */
2758 if (offSplit == 0) /** @todo Split words, look for hyphen... This code is currently a bit crude. */
2759 {
2760 while (offSplit < cchLine && !RT_C_IS_BLANK(pState->szLine[offSplit]))
2761 offSplit++;
2762 offNextLine = offSplit;
2763 }
2764
2765 while (offNextLine < cchLine && RT_C_IS_BLANK(pState->szLine[offNextLine]))
2766 offNextLine++;
2767
2768 /*
2769 * Output and advance.
2770 */
2771 rtStrmWrapppedIndent(pState, pState->cchIndent);
2772 int rc = RTStrmWrite(pState->pStream, pState->szLine, offSplit);
2773 if (RT_SUCCESS(rc))
2774 rc = RTStrmPutCh(pState->pStream, '\n');
2775 if (RT_FAILURE(rc))
2776 pState->rcStatus = rc;
2777
2778 cchLine -= offNextLine;
2779 pState->cchLine = cchLine;
2780 pState->cLines += 1;
2781 pState->cchIndent = cchIndent;
2782 memmove(&pState->szLine[0], &pState->szLine[offNextLine], cchLine);
2783 }
2784
2785 /* The indentation level is reset for each '\n' we process, so only save cchIndent if partial. */
2786 pState->cchIndent = fPartial ? cchIndent : UINT32_MAX;
2787}
2788
2789
2790/**
2791 * @callback_method_impl{FNRTSTROUTPUT}
2792 */
2793static DECLCALLBACK(size_t) rtStrmWrappedOutput(void *pvArg, const char *pachChars, size_t cbChars)
2794{
2795 RTSTRMWRAPPEDSTATE *pState = (RTSTRMWRAPPEDSTATE *)pvArg;
2796 size_t const cchRet = cbChars;
2797 while (cbChars > 0)
2798 {
2799 if (*pachChars == '\n')
2800 {
2801 rtStrmWrappedFlushLine(pState, false /*fPartial*/);
2802 pachChars++;
2803 cbChars--;
2804 }
2805 else
2806 {
2807 const char *pszEol = (const char *)memchr(pachChars, '\n', cbChars);
2808 size_t cchToCopy = pszEol ? (size_t)(pszEol - pachChars) : cbChars;
2809 uint32_t cchLine = pState->cchLine;
2810 Assert(cchLine < sizeof(pState->szLine));
2811 bool const fFlush = cchLine + cchToCopy >= sizeof(pState->szLine);
2812 if (fFlush)
2813 cchToCopy = cchToCopy - sizeof(pState->szLine) - 1;
2814
2815 pState->cchLine = cchLine + (uint32_t)cchToCopy;
2816 memcpy(&pState->szLine[cchLine], pachChars, cchToCopy);
2817
2818 pachChars += cchToCopy;
2819 cbChars -= cchToCopy;
2820
2821 if (fFlush)
2822 rtStrmWrappedFlushLine(pState, true /*fPartial*/);
2823 }
2824 }
2825 return cchRet;
2826}
2827
2828
2829RTDECL(int32_t) RTStrmWrappedPrintfV(PRTSTREAM pStream, uint32_t fFlags, const char *pszFormat, va_list va)
2830{
2831 /*
2832 * Figure the output width and set up the rest of the output state.
2833 */
2834 RTSTRMWRAPPEDSTATE State;
2835 State.pStream = pStream;
2836 State.cchLine = fFlags & RTSTRMWRAPPED_F_LINE_OFFSET_MASK;
2837 State.cLines = 0;
2838 State.rcStatus = VINF_SUCCESS;
2839 State.cchIndent = UINT32_MAX;
2840 State.cchHangingIndent = 0;
2841 if (fFlags & RTSTRMWRAPPED_F_HANGING_INDENT)
2842 {
2843 State.cchHangingIndent = (fFlags & RTSTRMWRAPPED_F_HANGING_INDENT_MASK) >> RTSTRMWRAPPED_F_HANGING_INDENT_SHIFT;
2844 if (!State.cchHangingIndent)
2845 State.cchHangingIndent = 4;
2846 }
2847
2848 int rc = RTStrmQueryTerminalWidth(pStream, &State.cchWidth);
2849 if (RT_SUCCESS(rc))
2850 State.cchWidth = RT_MIN(State.cchWidth, RTSTRMWRAPPED_F_LINE_OFFSET_MASK + 1);
2851 else
2852 {
2853 State.cchWidth = (uint32_t)fFlags & RTSTRMWRAPPED_F_NON_TERMINAL_WIDTH_MASK;
2854 if (!State.cchWidth)
2855 State.cchWidth = 80;
2856 }
2857 if (State.cchWidth < 32)
2858 State.cchWidth = 32;
2859 //State.cchWidth -= 1; /* necessary here? */
2860
2861 /*
2862 * Do the formatting.
2863 */
2864 RTStrFormatV(rtStrmWrappedOutput, &State, NULL, NULL, pszFormat, va);
2865
2866 /*
2867 * Returning is simple if the buffer is empty. Otherwise we'll have to
2868 * perform a partial flush and write out whatever is left ourselves.
2869 */
2870 if (RT_SUCCESS(State.rcStatus))
2871 {
2872 if (State.cchLine == 0)
2873 return State.cLines << 16;
2874
2875 rtStrmWrappedFlushLine(&State, true /*fPartial*/);
2876 if (RT_SUCCESS(State.rcStatus) && State.cchLine > 0)
2877 {
2878 rtStrmWrapppedIndent(&State, State.cchIndent);
2879 State.rcStatus = RTStrmWrite(State.pStream, State.szLine, State.cchLine);
2880 }
2881 if (RT_SUCCESS(State.rcStatus))
2882 return RT_MIN(State.cchIndent + State.cchLine, RTSTRMWRAPPED_F_LINE_OFFSET_MASK) | (State.cLines << 16);
2883 }
2884 return State.rcStatus;
2885}
2886
2887
2888RTDECL(int32_t) RTStrmWrappedPrintf(PRTSTREAM pStream, uint32_t fFlags, const char *pszFormat, ...)
2889{
2890 va_list va;
2891 va_start(va, pszFormat);
2892 int32_t rcRet = RTStrmWrappedPrintfV(pStream, fFlags, pszFormat, va);
2893 va_end(va);
2894 return rcRet;
2895}
2896
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette