VirtualBox

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

Last change on this file since 99828 was 99758, checked in by vboxsync, 13 months ago

IPRT: Make doxygen 1.9.6 happy. Mostly removing duplicate docs (iprt is documented in the header files). bugref:10442

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 82.6 KB
Line 
1/* $Id: stream.cpp 99758 2023-05-11 21:37:59Z vboxsync $ */
2/** @file
3 * IPRT - I/O Stream.
4 */
5
6/*
7 * Copyright (C) 2006-2023 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
644RTR3DECL(int) RTStrmOpen(const char *pszFilename, const char *pszMode, PRTSTREAM *ppStream)
645{
646 *ppStream = NULL;
647 AssertReturn(pszFilename, VERR_INVALID_PARAMETER);
648 return rtStrmOpenComon(pszFilename, NIL_RTFILE, pszMode, ppStream);
649}
650
651
652RTR3DECL(int) RTStrmOpenFV(const char *pszMode, PRTSTREAM *ppStream, const char *pszFilenameFmt, va_list args)
653{
654 int rc;
655 char szFilename[RTPATH_MAX];
656 size_t cch = RTStrPrintfV(szFilename, sizeof(szFilename), pszFilenameFmt, args);
657 if (cch < sizeof(szFilename))
658 rc = RTStrmOpen(szFilename, pszMode, ppStream);
659 else
660 {
661 AssertMsgFailed(("The filename is too long cch=%d\n", cch));
662 rc = VERR_FILENAME_TOO_LONG;
663 }
664 return rc;
665}
666
667
668RTR3DECL(int) RTStrmOpenF(const char *pszMode, PRTSTREAM *ppStream, const char *pszFilenameFmt, ...)
669{
670 va_list args;
671 va_start(args, pszFilenameFmt);
672 int rc = RTStrmOpenFV(pszMode, ppStream, pszFilenameFmt, args);
673 va_end(args);
674 return rc;
675}
676
677
678RTR3DECL(int) RTStrmOpenFileHandle(RTFILE hFile, const char *pszMode, uint32_t fFlags, PRTSTREAM *ppStream)
679{
680 *ppStream = NULL;
681 AssertReturn(RTFileIsValid(hFile), VERR_INVALID_HANDLE);
682 AssertReturn(fFlags == 0, VERR_INVALID_FLAGS);
683 return rtStrmOpenComon(NULL, hFile, pszMode, ppStream);
684}
685
686
687RTR3DECL(int) RTStrmClose(PRTSTREAM pStream)
688{
689 /*
690 * Validate input.
691 */
692 if (!pStream)
693 return VINF_SUCCESS;
694 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
695 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_MAGIC);
696
697 /* We don't implement closing any of the standard handles at present. */
698 AssertReturn(pStream != &g_StdIn, VERR_NOT_SUPPORTED);
699 AssertReturn(pStream != &g_StdOut, VERR_NOT_SUPPORTED);
700 AssertReturn(pStream != &g_StdErr, VERR_NOT_SUPPORTED);
701
702 /*
703 * Invalidate the stream and destroy the critical section first.
704 */
705#ifdef RTSTREAM_STANDALONE
706 RTCritSectEnter(&g_StreamListCritSect);
707 RTListNodeRemove(&pStream->ListEntry);
708 RTCritSectLeave(&g_StreamListCritSect);
709#endif
710 pStream->u32Magic = 0xdeaddead;
711#ifndef HAVE_FWRITE_UNLOCKED
712 if (pStream->pCritSect)
713 {
714 RTCritSectEnter(pStream->pCritSect);
715 RTCritSectLeave(pStream->pCritSect);
716 RTCritSectDelete(pStream->pCritSect);
717 RTMemFree(pStream->pCritSect);
718 pStream->pCritSect = NULL;
719 }
720#endif
721
722 /*
723 * Flush and close the underlying file.
724 */
725#ifdef RTSTREAM_STANDALONE
726 int const rc1 = RTStrmFlush(pStream);
727 AssertRC(rc1);
728 int const rc2 = RTFileClose(pStream->hFile);
729 AssertRC(rc2);
730 int const rc = RT_SUCCESS(rc1) ? rc2 : rc1;
731#else
732 int const rc = !fclose(pStream->pFile) ? VINF_SUCCESS : RTErrConvertFromErrno(errno);
733#endif
734
735 /*
736 * Destroy the stream.
737 */
738#ifdef RTSTREAM_STANDALONE
739 pStream->hFile = NIL_RTFILE;
740 RTMemFree(pStream->pchBuf);
741 pStream->pchBuf = NULL;
742 pStream->cbBufAlloc = 0;
743 pStream->offBufFirst = 0;
744 pStream->offBufEnd = 0;
745# ifdef RTSTREAM_WITH_TEXT_MODE
746 RTMemFree(pStream->pbmBuf);
747 pStream->pbmBuf = NULL;
748# endif
749#else
750 pStream->pFile = NULL;
751#endif
752 RTMemFree(pStream);
753 return rc;
754}
755
756
757RTR3DECL(int) RTStrmError(PRTSTREAM pStream)
758{
759 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
760 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_MAGIC);
761 return pStream->i32Error;
762}
763
764
765RTR3DECL(int) RTStrmClearError(PRTSTREAM pStream)
766{
767 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
768 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_MAGIC);
769
770#ifndef RTSTREAM_STANDALONE
771 clearerr(pStream->pFile);
772#endif
773 ASMAtomicWriteS32(&pStream->i32Error, VINF_SUCCESS);
774 return VINF_SUCCESS;
775}
776
777
778RTR3DECL(int) RTStrmSetMode(PRTSTREAM pStream, int fBinary, int fCurrentCodeSet)
779{
780 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
781 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
782 AssertReturn((unsigned)(fBinary + 1) <= 2, VERR_INVALID_PARAMETER);
783 AssertReturn((unsigned)(fCurrentCodeSet + 1) <= 2, VERR_INVALID_PARAMETER);
784
785 rtStrmLock(pStream);
786
787 if (fBinary != -1)
788 {
789 pStream->fBinary = RT_BOOL(fBinary);
790 pStream->fRecheckMode = true;
791 }
792
793 if (fCurrentCodeSet != -1)
794 pStream->fCurrentCodeSet = RT_BOOL(fCurrentCodeSet);
795
796 rtStrmUnlock(pStream);
797
798 return VINF_SUCCESS;
799}
800
801
802RTR3DECL(int) RTStrmSetBufferingMode(PRTSTREAM pStream, RTSTRMBUFMODE enmMode)
803{
804 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
805 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
806 AssertReturn(enmMode > RTSTRMBUFMODE_INVALID && enmMode < RTSTRMBUFMODE_END, VERR_INVALID_PARAMETER);
807
808#ifndef RTSTREAM_STANDALONE
809 int iCrtMode = enmMode == RTSTRMBUFMODE_FULL ? _IOFBF : enmMode == RTSTRMBUFMODE_LINE ? _IOLBF : _IONBF;
810 int rc = setvbuf(pStream->pFile, NULL, iCrtMode, 0);
811 if (rc >= 0)
812 return VINF_SUCCESS;
813 return RTErrConvertFromErrno(errno);
814
815#else
816 rtStrmLock(pStream);
817 pStream->enmBufStyle = enmMode == RTSTRMBUFMODE_FULL ? RTSTREAMBUFSTYLE_FULL
818 : enmMode == RTSTRMBUFMODE_LINE ? RTSTREAMBUFSTYLE_LINE : RTSTREAMBUFSTYLE_UNBUFFERED;
819 rtStrmUnlock(pStream);
820 return VINF_SUCCESS;
821#endif
822}
823
824
825#ifdef RTSTREAM_STANDALONE
826
827/**
828 * Deals with NIL_RTFILE in rtStrmGetFile.
829 */
830DECL_NO_INLINE(static, RTFILE) rtStrmGetFileNil(PRTSTREAM pStream)
831{
832# ifdef RT_OS_WINDOWS
833 DWORD dwStdHandle;
834 switch (pStream->enmStdHandle)
835 {
836 case RTHANDLESTD_INPUT: dwStdHandle = STD_INPUT_HANDLE; break;
837 case RTHANDLESTD_OUTPUT: dwStdHandle = STD_OUTPUT_HANDLE; break;
838 case RTHANDLESTD_ERROR: dwStdHandle = STD_ERROR_HANDLE; break;
839 default: return NIL_RTFILE;
840 }
841 HANDLE hHandle = GetStdHandle(dwStdHandle);
842 if (hHandle != INVALID_HANDLE_VALUE && hHandle != NULL)
843 {
844 int rc = RTFileFromNative(&pStream->hFile, (uintptr_t)hHandle);
845 if (RT_SUCCESS(rc))
846 {
847 /* Switch to full buffering if not a console handle. */
848 DWORD dwMode;
849 if (!GetConsoleMode(hHandle, &dwMode))
850 pStream->enmBufStyle = RTSTREAMBUFSTYLE_FULL;
851
852 return pStream->hFile;
853 }
854 }
855
856# else
857 uintptr_t uNative;
858 switch (pStream->enmStdHandle)
859 {
860 case RTHANDLESTD_INPUT: uNative = RTFILE_NATIVE_STDIN; break;
861 case RTHANDLESTD_OUTPUT: uNative = RTFILE_NATIVE_STDOUT; break;
862 case RTHANDLESTD_ERROR: uNative = RTFILE_NATIVE_STDERR; break;
863 default: return NIL_RTFILE;
864 }
865 int rc = RTFileFromNative(&pStream->hFile, uNative);
866 if (RT_SUCCESS(rc))
867 {
868 /* Switch to full buffering if not a console handle. */
869 if (!isatty((int)uNative))
870 pStream->enmBufStyle = RTSTREAMBUFDIR_FULL;
871
872 return pStream->hFile;
873 }
874
875# endif
876 return NIL_RTFILE;
877}
878
879
880/**
881 * For lazily resolving handles for the standard streams.
882 */
883DECLINLINE(RTFILE) rtStrmGetFile(PRTSTREAM pStream)
884{
885 RTFILE hFile = pStream->hFile;
886 if (hFile != NIL_RTFILE)
887 return hFile;
888 return rtStrmGetFileNil(pStream);
889}
890
891
892RTR3DECL(int) RTStrmQueryFileHandle(PRTSTREAM pStream, PRTFILE phFile)
893{
894 AssertPtrReturn(phFile, VERR_INVALID_POINTER);
895 *phFile = NIL_RTFILE;
896 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
897 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_MAGIC);
898
899 rtStrmLock(pStream);
900 RTFILE hFile = rtStrmGetFile(pStream);
901 rtStrmUnlock(pStream);
902 if (hFile != NIL_RTFILE)
903 {
904 *phFile = hFile;
905 return VINF_SUCCESS;
906 }
907 return VERR_NOT_AVAILABLE;
908}
909
910#endif /* RTSTREAM_STANDALONE */
911
912
913/**
914 * Wrapper around isatty, assumes caller takes care of stream locking/whatever
915 * is needed.
916 */
917DECLINLINE(bool) rtStrmIsTerminal(PRTSTREAM pStream)
918{
919#ifdef RTSTREAM_STANDALONE
920 RTFILE hFile = rtStrmGetFile(pStream);
921 if (hFile != NIL_RTFILE)
922 {
923 HANDLE hNative = (HANDLE)RTFileToNative(hFile);
924 DWORD dwType = GetFileType(hNative);
925 if (dwType == FILE_TYPE_CHAR)
926 {
927 DWORD dwMode;
928 if (GetConsoleMode(hNative, &dwMode))
929 return true;
930 }
931 }
932 return false;
933
934#else
935 if (pStream->pFile)
936 {
937 int fh = fileno(pStream->pFile);
938 if (isatty(fh) != 0)
939 {
940# ifdef RT_OS_WINDOWS
941 DWORD dwMode;
942 HANDLE hCon = (HANDLE)_get_osfhandle(fh);
943 if (GetConsoleMode(hCon, &dwMode))
944 return true;
945# else
946 return true;
947# endif
948 }
949 }
950 return false;
951#endif
952}
953
954
955static int rtStrmInputGetEchoCharsNative(uintptr_t hNative, bool *pfEchoChars)
956{
957#ifdef RT_OS_WINDOWS
958 DWORD dwMode;
959 if (GetConsoleMode((HANDLE)hNative, &dwMode))
960 *pfEchoChars = RT_BOOL(dwMode & ENABLE_ECHO_INPUT);
961 else
962 {
963 DWORD dwErr = GetLastError();
964 if (dwErr == ERROR_INVALID_HANDLE)
965 return GetFileType((HANDLE)hNative) != FILE_TYPE_UNKNOWN ? VERR_INVALID_FUNCTION : VERR_INVALID_HANDLE;
966 return RTErrConvertFromWin32(dwErr);
967 }
968#else
969 struct termios Termios;
970 int rcPosix = tcgetattr((int)hNative, &Termios);
971 if (!rcPosix)
972 *pfEchoChars = RT_BOOL(Termios.c_lflag & ECHO);
973 else
974 return errno == ENOTTY ? VERR_INVALID_FUNCTION : RTErrConvertFromErrno(errno);
975#endif
976 return VINF_SUCCESS;
977}
978
979
980
981RTR3DECL(int) RTStrmInputGetEchoChars(PRTSTREAM pStream, bool *pfEchoChars)
982{
983 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
984 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
985 AssertPtrReturn(pfEchoChars, VERR_INVALID_POINTER);
986
987#ifdef RTSTREAM_STANDALONE
988 return rtStrmInputGetEchoCharsNative(RTFileToNative(pStream->hFile), pfEchoChars);
989#else
990 int rc;
991 int fh = fileno(pStream->pFile);
992 if (isatty(fh))
993 {
994# ifdef RT_OS_WINDOWS
995 rc = rtStrmInputGetEchoCharsNative(_get_osfhandle(fh), pfEchoChars);
996# else
997 rc = rtStrmInputGetEchoCharsNative(fh, pfEchoChars);
998# endif
999 }
1000 else
1001 rc = VERR_INVALID_FUNCTION;
1002 return rc;
1003#endif
1004}
1005
1006
1007static int rtStrmInputSetEchoCharsNative(uintptr_t hNative, bool fEchoChars)
1008{
1009 int rc;
1010#ifdef RT_OS_WINDOWS
1011 DWORD dwMode;
1012 if (GetConsoleMode((HANDLE)hNative, &dwMode))
1013 {
1014 if (fEchoChars)
1015 dwMode |= ENABLE_ECHO_INPUT;
1016 else
1017 dwMode &= ~ENABLE_ECHO_INPUT;
1018 if (SetConsoleMode((HANDLE)hNative, dwMode))
1019 rc = VINF_SUCCESS;
1020 else
1021 rc = RTErrConvertFromWin32(GetLastError());
1022 }
1023 else
1024 {
1025 DWORD dwErr = GetLastError();
1026 if (dwErr == ERROR_INVALID_HANDLE)
1027 return GetFileType((HANDLE)hNative) != FILE_TYPE_UNKNOWN ? VERR_INVALID_FUNCTION : VERR_INVALID_HANDLE;
1028 return RTErrConvertFromWin32(dwErr);
1029 }
1030#else
1031 struct termios Termios;
1032 int rcPosix = tcgetattr((int)hNative, &Termios);
1033 if (!rcPosix)
1034 {
1035 if (fEchoChars)
1036 Termios.c_lflag |= ECHO;
1037 else
1038 Termios.c_lflag &= ~ECHO;
1039
1040 rcPosix = tcsetattr((int)hNative, TCSAFLUSH, &Termios);
1041 if (rcPosix == 0)
1042 rc = VINF_SUCCESS;
1043 else
1044 rc = RTErrConvertFromErrno(errno);
1045 }
1046 else
1047 rc = errno == ENOTTY ? VERR_INVALID_FUNCTION : RTErrConvertFromErrno(errno);
1048#endif
1049 return rc;
1050}
1051
1052
1053RTR3DECL(int) RTStrmInputSetEchoChars(PRTSTREAM pStream, bool fEchoChars)
1054{
1055 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
1056 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
1057
1058#ifdef RTSTREAM_STANDALONE
1059 return rtStrmInputSetEchoCharsNative(RTFileToNative(pStream->hFile), fEchoChars);
1060#else
1061 int rc;
1062 int fh = fileno(pStream->pFile);
1063 if (isatty(fh))
1064 {
1065# ifdef RT_OS_WINDOWS
1066 rc = rtStrmInputSetEchoCharsNative(_get_osfhandle(fh), fEchoChars);
1067# else
1068 rc = rtStrmInputSetEchoCharsNative(fh, fEchoChars);
1069# endif
1070 }
1071 else
1072 rc = VERR_INVALID_FUNCTION;
1073 return rc;
1074#endif
1075}
1076
1077
1078RTR3DECL(bool) RTStrmIsTerminal(PRTSTREAM pStream)
1079{
1080 AssertPtrReturn(pStream, false);
1081 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, false);
1082
1083 return rtStrmIsTerminal(pStream);
1084}
1085
1086
1087RTR3DECL(int) RTStrmQueryTerminalWidth(PRTSTREAM pStream, uint32_t *pcchWidth)
1088{
1089 AssertPtrReturn(pcchWidth, VERR_INVALID_HANDLE);
1090 *pcchWidth = 80;
1091
1092 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
1093 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
1094
1095 if (rtStrmIsTerminal(pStream))
1096 {
1097#ifdef RT_OS_WINDOWS
1098# ifdef RTSTREAM_STANDALONE
1099 HANDLE hCon = (HANDLE)RTFileToNative(pStream->hFile);
1100# else
1101 HANDLE hCon = (HANDLE)_get_osfhandle(fileno(pStream->pFile));
1102# endif
1103 CONSOLE_SCREEN_BUFFER_INFO Info;
1104 RT_ZERO(Info);
1105 if (GetConsoleScreenBufferInfo(hCon, &Info))
1106 {
1107 *pcchWidth = Info.dwSize.X ? Info.dwSize.X : 80;
1108 return VINF_SUCCESS;
1109 }
1110 return RTErrConvertFromWin32(GetLastError());
1111
1112#elif defined(RT_OS_OS2) && !defined(TIOCGWINSZ) /* only OS/2 should currently miss this */
1113 return VINF_SUCCESS; /* just pretend for now. */
1114
1115#else
1116 struct winsize Info;
1117 RT_ZERO(Info);
1118 int rc = ioctl(fileno(pStream->pFile), TIOCGWINSZ, &Info);
1119 if (rc >= 0)
1120 {
1121 *pcchWidth = Info.ws_col ? Info.ws_col : 80;
1122 return VINF_SUCCESS;
1123 }
1124 return RTErrConvertFromErrno(errno);
1125#endif
1126 }
1127 return VERR_INVALID_FUNCTION;
1128}
1129
1130
1131#ifdef RTSTREAM_STANDALONE
1132
1133DECLINLINE(void) rtStrmBufInvalidate(PRTSTREAM pStream)
1134{
1135 pStream->enmBufDir = RTSTREAMBUFDIR_NONE;
1136 pStream->offBufEnd = 0;
1137 pStream->offBufFirst = 0;
1138}
1139
1140
1141static int rtStrmBufFlushWrite(PRTSTREAM pStream, size_t cbToFlush)
1142{
1143 Assert(cbToFlush <= pStream->offBufEnd - pStream->offBufFirst);
1144
1145 RTFILE const hFile = rtStrmGetFile(pStream);
1146 if (hFile != NIL_RTFILE)
1147 {
1148 /** @todo do nonblocking & incomplete writes? */
1149 size_t offBufFirst = pStream->offBufFirst;
1150 int rc = RTFileWrite(hFile, &pStream->pchBuf[offBufFirst], cbToFlush, NULL);
1151 if (RT_SUCCESS(rc))
1152 {
1153 offBufFirst += cbToFlush;
1154 if (offBufFirst >= pStream->offBufEnd)
1155 pStream->offBufEnd = 0;
1156 else
1157 {
1158 /* Shift up the remaining content so the next write can take full
1159 advantage of the buffer size. */
1160 size_t cbLeft = pStream->offBufEnd - offBufFirst;
1161 memmove(pStream->pchBuf, &pStream->pchBuf[offBufFirst], cbLeft);
1162 pStream->offBufEnd = cbLeft;
1163 }
1164 pStream->offBufFirst = 0;
1165 return VINF_SUCCESS;
1166 }
1167 return rc;
1168 }
1169 return VERR_INVALID_HANDLE;
1170}
1171
1172
1173static int rtStrmBufFlushWriteMaybe(PRTSTREAM pStream, bool fInvalidate)
1174{
1175 if (pStream->enmBufDir == RTSTREAMBUFDIR_WRITE)
1176 {
1177 size_t cbInBuffer = pStream->offBufEnd - pStream->offBufFirst;
1178 if (cbInBuffer > 0)
1179 {
1180 int rc = rtStrmBufFlushWrite(pStream, cbInBuffer);
1181 if (fInvalidate)
1182 pStream->enmBufDir = RTSTREAMBUFDIR_NONE;
1183 return rc;
1184 }
1185 }
1186 if (fInvalidate)
1187 rtStrmBufInvalidate(pStream);
1188 return VINF_SUCCESS;
1189}
1190
1191
1192/**
1193 * Worker for rtStrmBufCheckErrorAndSwitchToReadMode and
1194 * rtStrmBufCheckErrorAndSwitchToWriteMode that allocates a buffer.
1195 *
1196 * Only updates cbBufAlloc and pchBuf, callers deals with error fallout.
1197 */
1198static int rtStrmBufAlloc(PRTSTREAM pStream)
1199{
1200 size_t cbBuf = pStream->enmBufStyle == RTSTREAMBUFSTYLE_FULL ? _64K : _16K;
1201 do
1202 {
1203 pStream->pchBuf = (char *)RTMemAllocZ(cbBuf);
1204 if (RT_LIKELY(pStream->pchBuf))
1205 {
1206# ifdef RTSTREAM_WITH_TEXT_MODE
1207 Assert(RT_ALIGN_Z(cbBuf, 64 / 8) == cbBuf);
1208 pStream->pbmBuf = (uint32_t *)RTMemAllocZ(cbBuf / 8);
1209 if (RT_LIKELY(pStream->pbmBuf))
1210# endif
1211 {
1212 pStream->cbBufAlloc = cbBuf;
1213 return VINF_SUCCESS;
1214 }
1215# ifdef RTSTREAM_WITH_TEXT_MODE
1216 RTMemFree(pStream->pchBuf);
1217 pStream->pchBuf = NULL;
1218# endif
1219 }
1220 cbBuf /= 2;
1221 } while (cbBuf >= 256);
1222 return VERR_NO_MEMORY;
1223}
1224
1225
1226/**
1227 * Checks the stream error status, flushed any pending writes, ensures there is
1228 * a buffer allocated and switches the stream to the read direction.
1229 *
1230 * @returns IPRT status code (same as i32Error).
1231 * @param pStream The stream.
1232 */
1233static int rtStrmBufCheckErrorAndSwitchToReadMode(PRTSTREAM pStream)
1234{
1235 int rc = pStream->i32Error;
1236 if (RT_SUCCESS(rc))
1237 {
1238 /*
1239 * We're very likely already in read mode and can return without doing
1240 * anything here.
1241 */
1242 if (pStream->enmBufDir == RTSTREAMBUFDIR_READ)
1243 return VINF_SUCCESS;
1244
1245 /*
1246 * Flush any pending writes before switching the buffer to read:
1247 */
1248 rc = rtStrmBufFlushWriteMaybe(pStream, false /*fInvalidate*/);
1249 if (RT_SUCCESS(rc))
1250 {
1251 pStream->enmBufDir = RTSTREAMBUFDIR_READ;
1252 pStream->offBufEnd = 0;
1253 pStream->offBufFirst = 0;
1254 pStream->fPendingCr = false;
1255
1256 /*
1257 * Read direction implies a buffer, so make sure we've got one and
1258 * change to NONE direction if allocating one fails.
1259 */
1260 if (pStream->pchBuf)
1261 {
1262 Assert(pStream->cbBufAlloc >= 256);
1263 return VINF_SUCCESS;
1264 }
1265
1266 rc = rtStrmBufAlloc(pStream);
1267 if (RT_SUCCESS(rc))
1268 return VINF_SUCCESS;
1269
1270 pStream->enmBufDir = RTSTREAMBUFDIR_NONE;
1271 }
1272 ASMAtomicWriteS32(&pStream->i32Error, rc);
1273 }
1274 return rc;
1275}
1276
1277
1278/**
1279 * Checks the stream error status, ensures there is a buffer allocated and
1280 * switches the stream to the write direction.
1281 *
1282 * @returns IPRT status code (same as i32Error).
1283 * @param pStream The stream.
1284 */
1285static int rtStrmBufCheckErrorAndSwitchToWriteMode(PRTSTREAM pStream)
1286{
1287 int rc = pStream->i32Error;
1288 if (RT_SUCCESS(rc))
1289 {
1290 /*
1291 * We're very likely already in write mode and can return without doing
1292 * anything here.
1293 */
1294 if (pStream->enmBufDir == RTSTREAMBUFDIR_WRITE)
1295 return VINF_SUCCESS;
1296
1297 /*
1298 * A read buffer does not need any flushing, so we just have to make
1299 * sure there is a buffer present before switching to the write direction.
1300 */
1301 pStream->enmBufDir = RTSTREAMBUFDIR_WRITE;
1302 pStream->offBufEnd = 0;
1303 pStream->offBufFirst = 0;
1304 if (pStream->pchBuf)
1305 {
1306 Assert(pStream->cbBufAlloc >= 256);
1307 return VINF_SUCCESS;
1308 }
1309
1310 rc = rtStrmBufAlloc(pStream);
1311 if (RT_SUCCESS(rc))
1312 return VINF_SUCCESS;
1313
1314 pStream->enmBufDir = RTSTREAMBUFDIR_NONE;
1315 ASMAtomicWriteS32(&pStream->i32Error, rc);
1316 }
1317 return rc;
1318}
1319
1320
1321/**
1322 * Reads more bytes into the buffer.
1323 *
1324 * @returns IPRT status code (same as i32Error).
1325 * @param pStream The stream.
1326 */
1327static int rtStrmBufFill(PRTSTREAM pStream)
1328{
1329 /*
1330 * Check preconditions
1331 */
1332 Assert(pStream->i32Error == VINF_SUCCESS);
1333 Assert(pStream->enmBufDir == RTSTREAMBUFDIR_READ);
1334 AssertPtr(pStream->pchBuf);
1335 Assert(pStream->cbBufAlloc >= 256);
1336 Assert(RT_ALIGN_Z(pStream->cbBufAlloc, 64) == pStream->cbBufAlloc);
1337 Assert(pStream->offBufFirst <= pStream->cbBufAlloc);
1338 Assert(pStream->offBufEnd <= pStream->cbBufAlloc);
1339 Assert(pStream->offBufFirst <= pStream->offBufEnd);
1340# ifdef RTSTREAM_WITH_TEXT_MODE
1341 AssertPtr(pStream->pbmBuf);
1342# endif
1343 /*
1344 * If there is data in the buffer, move it up to the start.
1345 */
1346 size_t cbInBuffer;
1347 if (!pStream->offBufFirst)
1348 cbInBuffer = pStream->offBufEnd;
1349 else
1350 {
1351 cbInBuffer = pStream->offBufEnd - pStream->offBufFirst;
1352 if (cbInBuffer)
1353 {
1354 memmove(pStream->pchBuf, &pStream->pchBuf[pStream->offBufFirst], cbInBuffer);
1355# ifdef RTSTREAM_WITH_TEXT_MODE
1356 if (!pStream->fBinary) /** @todo this isn't very efficient, must be a better way of shifting a bitmap. */
1357 for (size_t off = 0; off < pStream->offBufFirst; off++)
1358 if (ASMBitTest(pStream->pbmBuf, (int32_t)off))
1359 ASMBitSet(pStream->pbmBuf, (int32_t)off);
1360 else
1361 ASMBitClear(pStream->pbmBuf, (int32_t)off);
1362# endif
1363 }
1364 pStream->offBufFirst = 0;
1365 pStream->offBufEnd = cbInBuffer;
1366 }
1367
1368 /*
1369 * Add pending CR to the buffer.
1370 */
1371 size_t const offCrLfConvStart = cbInBuffer;
1372 Assert(cbInBuffer + 2 <= pStream->cbBufAlloc);
1373 if (!pStream->fPendingCr || pStream->fBinary)
1374 { /* likely */ }
1375 else
1376 {
1377 pStream->pchBuf[cbInBuffer] = '\r';
1378 pStream->fPendingCr = false;
1379 pStream->offBufEnd = ++cbInBuffer;
1380 }
1381
1382 /*
1383 * Read data till the buffer is full.
1384 */
1385 int rc = VERR_INVALID_HANDLE;
1386 RTFILE const hFile = rtStrmGetFile(pStream);
1387 if (hFile != NIL_RTFILE)
1388 {
1389 size_t cbRead = 0;
1390 rc = RTFileRead(hFile, &pStream->pchBuf[cbInBuffer], pStream->cbBufAlloc - cbInBuffer, &cbRead);
1391 if (RT_SUCCESS(rc))
1392 {
1393 cbInBuffer += cbRead;
1394 pStream->offBufEnd = cbInBuffer;
1395
1396 if (cbInBuffer != 0)
1397 {
1398# ifdef RTSTREAM_WITH_TEXT_MODE
1399 if (pStream->fBinary)
1400# endif
1401 return VINF_SUCCESS;
1402 }
1403 else
1404 {
1405 /** @todo this shouldn't be sticky, should it? */
1406 ASMAtomicWriteS32(&pStream->i32Error, VERR_EOF);
1407 return VERR_EOF;
1408 }
1409
1410# ifdef RTSTREAM_WITH_TEXT_MODE
1411 /*
1412 * Do CRLF -> LF conversion in the buffer.
1413 */
1414 ASMBitClearRange(pStream->pbmBuf, offCrLfConvStart, RT_ALIGN_Z(cbInBuffer, 64));
1415 char *pchCur = &pStream->pchBuf[offCrLfConvStart];
1416 size_t cbLeft = cbInBuffer - offCrLfConvStart;
1417 while (cbLeft > 0)
1418 {
1419 Assert(&pchCur[cbLeft] == &pStream->pchBuf[pStream->offBufEnd]);
1420 char *pchCr = (char *)memchr(pchCur, '\r', cbLeft);
1421 if (pchCr)
1422 {
1423 size_t offCur = (size_t)(pchCr - pchCur);
1424 if (offCur + 1 < cbLeft)
1425 {
1426 if (pchCr[1] == '\n')
1427 {
1428 /* Found one '\r\n' sequence. Look for more before shifting the buffer content. */
1429 cbLeft -= offCur;
1430 pchCur = pchCr;
1431
1432 do
1433 {
1434 ASMBitSet(pStream->pbmBuf, (int32_t)(pchCur - pStream->pchBuf));
1435 *pchCur++ = '\n'; /* dst */
1436 cbLeft -= 2;
1437 pchCr += 2; /* src */
1438 } while (cbLeft >= 2 && pchCr[0] == '\r' && pchCr[1] == '\n');
1439
1440 memmove(&pchCur, pchCr, cbLeft);
1441 }
1442 else
1443 {
1444 cbLeft -= offCur + 1;
1445 pchCur = pchCr + 1;
1446 }
1447 }
1448 else
1449 {
1450 Assert(pchCr == &pStream->pchBuf[pStream->offBufEnd - 1]);
1451 pStream->fPendingCr = true;
1452 pStream->offBufEnd = --cbInBuffer;
1453 break;
1454 }
1455 }
1456 else
1457 break;
1458 }
1459
1460 return VINF_SUCCESS;
1461# endif
1462 }
1463 }
1464
1465 /*
1466 * If there is data in the buffer, don't raise the error till it has all
1467 * been consumed, ASSUMING that another fill call will follow and that the
1468 * error condition will reoccur then.
1469 *
1470 * Note! We may currently end up not converting a CRLF pair, if it's
1471 * split over a temporary EOF condition, since we forces the caller
1472 * to read the CR before requesting more data. However, it's not a
1473 * very likely scenario, so we'll just leave it like that for now.
1474 */
1475 if (cbInBuffer)
1476 return VINF_SUCCESS;
1477 ASMAtomicWriteS32(&pStream->i32Error, rc);
1478 return rc;
1479}
1480
1481
1482/**
1483 * Copies @a cbSrc bytes from @a pvSrc and into the buffer, flushing as needed
1484 * to make space available.
1485 *
1486 *
1487 * @returns IPRT status code (errors not assigned to i32Error).
1488 * @param pStream The stream.
1489 * @param pvSrc The source buffer.
1490 * @param cbSrc Number of bytes to copy from @a pvSrc.
1491 * @param pcbTotal A total counter to update with what was copied.
1492 */
1493static int rtStrmBufCopyTo(PRTSTREAM pStream, const void *pvSrc, size_t cbSrc, size_t *pcbTotal)
1494{
1495 Assert(cbSrc > 0);
1496 for (;;)
1497 {
1498 size_t cbToCopy = RT_MIN(pStream->cbBufAlloc - pStream->offBufEnd, cbSrc);
1499 if (cbToCopy)
1500 {
1501 memcpy(&pStream->pchBuf[pStream->offBufEnd], pvSrc, cbToCopy);
1502 pStream->offBufEnd += cbToCopy;
1503 pvSrc = (const char *)pvSrc + cbToCopy;
1504 *pcbTotal += cbToCopy;
1505 cbSrc -= cbToCopy;
1506 if (!cbSrc)
1507 break;
1508 }
1509
1510 int rc = rtStrmBufFlushWrite(pStream, pStream->offBufEnd - pStream->offBufFirst);
1511 if (RT_FAILURE(rc))
1512 return rc;
1513 }
1514 return VINF_SUCCESS;
1515}
1516
1517
1518/**
1519 * Worker for rtStrmFlushAndCloseAll and rtStrmFlushAndClose.
1520 */
1521static RTFILE rtStrmFlushAndCleanup(PRTSTREAM pStream)
1522{
1523 if (pStream->pchBuf)
1524 {
1525 if ( pStream->enmBufDir == RTSTREAMBUFDIR_WRITE
1526 && pStream->offBufFirst < pStream->offBufEnd
1527 && RT_SUCCESS(pStream->i32Error) )
1528 rtStrmBufFlushWrite(pStream, pStream->offBufEnd - pStream->offBufFirst);
1529 RTMemFree(pStream->pchBuf);
1530 pStream->pchBuf = NULL;
1531 pStream->offBufFirst = 0;
1532 pStream->offBufEnd = 0;
1533# ifdef RTSTREAM_WITH_TEXT_MODE
1534 RTMemFree(pStream->pbmBuf);
1535 pStream->pbmBuf = NULL;
1536# endif
1537 }
1538
1539 PRTCRITSECT pCritSect = pStream->pCritSect;
1540 if (pCritSect)
1541 {
1542 pStream->pCritSect = NULL;
1543 RTCritSectDelete(pCritSect);
1544 RTMemFree(pCritSect);
1545 }
1546
1547 RTFILE hFile = pStream->hFile;
1548 pStream->hFile = NIL_RTFILE;
1549 return hFile;
1550}
1551
1552
1553/**
1554 * Worker for rtStrmFlushAndCloseAll.
1555 */
1556static void rtStrmFlushAndClose(PRTSTREAM pStream)
1557{
1558 pStream->u32Magic = ~RTSTREAM_MAGIC;
1559 RTFILE hFile = rtStrmFlushAndCleanup(pStream);
1560 if (hFile != NIL_RTFILE)
1561 RTFileClose(hFile);
1562 RTMemFree(pStream);
1563}
1564
1565
1566/**
1567 * Flushes and cleans up the standard streams, should flush and close all others
1568 * too but doesn't yet...
1569 */
1570DECLCALLBACK(void) rtStrmFlushAndCloseAll(void)
1571{
1572 /*
1573 * Flush the standard handles.
1574 */
1575 rtStrmFlushAndCleanup(&g_StdOut);
1576 rtStrmFlushAndCleanup(&g_StdErr);
1577 rtStrmFlushAndCleanup(&g_StdIn);
1578
1579 /*
1580 * Make a list of the rest and flush+close those too.
1581 */
1582 if (RTOnceWasInitialized(&g_StreamListOnce))
1583 {
1584 RTCritSectDelete(&g_StreamListCritSect);
1585
1586 PRTSTREAM pStream;
1587 while ((pStream = RTListRemoveFirst(&g_StreamList, RTSTREAM, ListEntry)) != NULL)
1588 rtStrmFlushAndClose(pStream);
1589
1590 RTOnceReset(&g_StreamListOnce);
1591 }
1592}
1593
1594# ifdef IPRT_COMPILER_TERM_CALLBACK
1595IPRT_COMPILER_TERM_CALLBACK(rtStrmFlushAndCloseAll);
1596# endif
1597
1598#endif /* RTSTREAM_STANDALONE */
1599
1600
1601RTR3DECL(int) RTStrmRewind(PRTSTREAM pStream)
1602{
1603 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
1604 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
1605
1606#ifdef RTSTREAM_STANDALONE
1607 rtStrmLock(pStream);
1608 int const rc1 = rtStrmBufFlushWriteMaybe(pStream, true /*fInvalidate*/);
1609 int const rc2 = RTFileSeek(rtStrmGetFile(pStream), 0, RTFILE_SEEK_BEGIN, NULL);
1610 int rc = RT_SUCCESS(rc1) ? rc2 : rc1;
1611 ASMAtomicWriteS32(&pStream->i32Error, rc);
1612 rtStrmUnlock(pStream);
1613#else
1614 clearerr(pStream->pFile);
1615 errno = 0;
1616 int rc;
1617 if (!fseek(pStream->pFile, 0, SEEK_SET))
1618 rc = VINF_SUCCESS;
1619 else
1620 rc = RTErrConvertFromErrno(errno);
1621 ASMAtomicWriteS32(&pStream->i32Error, rc);
1622#endif
1623 return rc;
1624}
1625
1626
1627RTR3DECL(int) RTStrmSeek(PRTSTREAM pStream, RTFOFF off, uint32_t uMethod)
1628{
1629 AssertReturn(uMethod <= RTFILE_SEEK_END, VERR_INVALID_PARAMETER);
1630#ifdef RTSTREAM_STANDALONE
1631 rtStrmLock(pStream);
1632 int rc = rtStrmBufFlushWriteMaybe(pStream, true /*fInvalidate*/);
1633 if (RT_SUCCESS(rc))
1634 rc = RTFileSeek(rtStrmGetFile(pStream), off, uMethod, NULL);
1635 if (RT_FAILURE(rc))
1636 ASMAtomicWriteS32(&pStream->i32Error, rc);
1637 rtStrmUnlock(pStream);
1638#else
1639 int const iCrtMethod = uMethod == RTFILE_SEEK_BEGIN ? SEEK_SET : uMethod == RTFILE_SEEK_CURRENT ? SEEK_CUR : SEEK_END;
1640 errno = 0;
1641 int rc;
1642# ifdef _MSC_VER
1643 if (!_fseeki64(pStream->pFile, off, iCrtMethod))
1644# else
1645 if (!fseeko(pStream->pFile, off, iCrtMethod))
1646# endif
1647 rc = VINF_SUCCESS;
1648 else
1649 rc = RTErrConvertFromErrno(errno);
1650 ASMAtomicWriteS32(&pStream->i32Error, rc);
1651#endif
1652 return rc;
1653}
1654
1655
1656RTR3DECL(RTFOFF) RTStrmTell(PRTSTREAM pStream)
1657{
1658#ifdef RTSTREAM_STANDALONE
1659 uint64_t off = 0;
1660 rtStrmLock(pStream);
1661 int rc = pStream->i32Error;
1662 if (RT_SUCCESS(rc))
1663 {
1664 RTFILE const hFile = rtStrmGetFile(pStream);
1665 if (hFile != NIL_RTFILE)
1666 {
1667 rc = RTFileSeek(hFile, 0, RTFILE_SEEK_CURRENT, &off);
1668 if (RT_SUCCESS(rc))
1669 {
1670 switch (pStream->enmBufDir)
1671 {
1672 case RTSTREAMBUFDIR_READ:
1673 /* Subtract unconsumed chars and removed '\r' characters. */
1674 off -= pStream->offBufEnd - pStream->offBufFirst;
1675 if (!pStream->fBinary)
1676 for (size_t offBuf = pStream->offBufFirst; offBuf < pStream->offBufEnd; offBuf++)
1677 off -= ASMBitTest(pStream->pbmBuf, (int32_t)offBuf);
1678 break;
1679 case RTSTREAMBUFDIR_WRITE:
1680 /* Add unwrittend chars in the buffer. */
1681 off += pStream->offBufEnd - pStream->offBufFirst;
1682 break;
1683 default:
1684 AssertFailed();
1685 case RTSTREAMBUFDIR_NONE:
1686 break;
1687 }
1688 }
1689 }
1690 else
1691 rc = VERR_INVALID_HANDLE;
1692 }
1693 if (RT_FAILURE(rc))
1694 {
1695 ASMAtomicWriteS32(&pStream->i32Error, rc);
1696 off = rc;
1697 }
1698 rtStrmUnlock(pStream);
1699#else
1700# ifdef _MSC_VER
1701 RTFOFF off = _ftelli64(pStream->pFile);
1702# else
1703 RTFOFF off = ftello(pStream->pFile);
1704# endif
1705 if (off < 0)
1706 {
1707 int rc = RTErrConvertFromErrno(errno);
1708 ASMAtomicWriteS32(&pStream->i32Error, rc);
1709 off = rc;
1710 }
1711#endif
1712 return off;
1713}
1714
1715
1716/**
1717 * Recheck the stream mode.
1718 *
1719 * @param pStream The stream (locked).
1720 */
1721static void rtStreamRecheckMode(PRTSTREAM pStream)
1722{
1723#if (defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)) && !defined(RTSTREAM_STANDALONE)
1724 int fh = fileno(pStream->pFile);
1725 if (fh >= 0)
1726 {
1727 int fExpected = pStream->fBinary ? _O_BINARY : _O_TEXT;
1728 int fActual = _setmode(fh, fExpected);
1729 if (fActual != -1 && fExpected != (fActual & (_O_BINARY | _O_TEXT)))
1730 {
1731 fActual = _setmode(fh, fActual & (_O_BINARY | _O_TEXT));
1732 pStream->fBinary = !(fActual & _O_TEXT);
1733 }
1734 }
1735#else
1736 NOREF(pStream);
1737#endif
1738 pStream->fRecheckMode = false;
1739}
1740
1741
1742RTR3DECL(int) RTStrmReadEx(PRTSTREAM pStream, void *pvBuf, size_t cbToRead, size_t *pcbRead)
1743{
1744 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
1745 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
1746
1747#ifdef RTSTREAM_STANDALONE
1748 rtStrmLock(pStream);
1749 int rc = rtStrmBufCheckErrorAndSwitchToReadMode(pStream);
1750#else
1751 int rc = pStream->i32Error;
1752#endif
1753 if (RT_SUCCESS(rc))
1754 {
1755 if (pStream->fRecheckMode)
1756 rtStreamRecheckMode(pStream);
1757
1758#ifdef RTSTREAM_STANDALONE
1759
1760 /*
1761 * Copy data thru the read buffer for now as that'll handle both binary
1762 * and text modes seamlessly. We could optimize larger reads here when
1763 * in binary mode, that can wait till the basics work, I think.
1764 */
1765 size_t cbTotal = 0;
1766 if (cbToRead > 0)
1767 for (;;)
1768 {
1769 size_t cbInBuffer = pStream->offBufEnd - pStream->offBufFirst;
1770 if (cbInBuffer > 0)
1771 {
1772 size_t cbToCopy = RT_MIN(cbInBuffer, cbToRead);
1773 memcpy(pvBuf, &pStream->pchBuf[pStream->offBufFirst], cbToCopy);
1774 cbTotal += cbToRead;
1775 cbToRead -= cbToCopy;
1776 pvBuf = (char *)pvBuf + cbToCopy;
1777 if (!cbToRead)
1778 break;
1779 }
1780 rc = rtStrmBufFill(pStream);
1781 if (RT_SUCCESS(rc))
1782 { /* likely */ }
1783 else
1784 {
1785 if (rc == VERR_EOF && pcbRead && cbTotal > 0)
1786 rc = VINF_EOF;
1787 break;
1788 }
1789 }
1790 if (pcbRead)
1791 *pcbRead = cbTotal;
1792
1793#else /* !RTSTREAM_STANDALONE */
1794 if (pcbRead)
1795 {
1796 /*
1797 * Can do with a partial read.
1798 */
1799 *pcbRead = fread(pvBuf, 1, cbToRead, pStream->pFile);
1800 if ( *pcbRead == cbToRead
1801 || !ferror(pStream->pFile))
1802 rc = VINF_SUCCESS;
1803 else if (feof(pStream->pFile))
1804 rc = *pcbRead ? VINF_EOF : VERR_EOF;
1805 else if (ferror(pStream->pFile))
1806 rc = VERR_READ_ERROR;
1807 else
1808 {
1809 AssertMsgFailed(("This shouldn't happen\n"));
1810 rc = VERR_INTERNAL_ERROR;
1811 }
1812 }
1813 else
1814 {
1815 /*
1816 * Must read it all!
1817 */
1818 if (fread(pvBuf, cbToRead, 1, pStream->pFile) == 1)
1819 rc = VINF_SUCCESS;
1820 /* possible error/eof. */
1821 else if (feof(pStream->pFile))
1822 rc = VERR_EOF;
1823 else if (ferror(pStream->pFile))
1824 rc = VERR_READ_ERROR;
1825 else
1826 {
1827 AssertMsgFailed(("This shouldn't happen\n"));
1828 rc = VERR_INTERNAL_ERROR;
1829 }
1830 }
1831#endif /* !RTSTREAM_STANDALONE */
1832 if (RT_FAILURE(rc))
1833 ASMAtomicWriteS32(&pStream->i32Error, rc);
1834 }
1835#ifdef RTSTREAM_STANDALONE
1836 rtStrmUnlock(pStream);
1837#endif
1838 return rc;
1839}
1840
1841
1842/**
1843 * Check if the input text is valid UTF-8.
1844 *
1845 * @returns true/false.
1846 * @param pvBuf Pointer to the buffer.
1847 * @param cbBuf Size of the buffer.
1848 */
1849static bool rtStrmIsUtf8Text(const void *pvBuf, size_t cbBuf)
1850{
1851 NOREF(pvBuf);
1852 NOREF(cbBuf);
1853 /** @todo not sure this is a good idea... Better redefine RTStrmWrite. */
1854 return false;
1855}
1856
1857
1858#if defined(RT_OS_WINDOWS) && !defined(RTSTREAM_STANDALONE)
1859
1860/**
1861 * Check if the stream is for a Window console.
1862 *
1863 * @returns true / false.
1864 * @param pStream The stream.
1865 * @param phCon Where to return the console handle.
1866 */
1867static bool rtStrmIsConsoleUnlocked(PRTSTREAM pStream, HANDLE *phCon)
1868{
1869 int fh = fileno(pStream->pFile);
1870 if (isatty(fh))
1871 {
1872 DWORD dwMode;
1873 HANDLE hCon = (HANDLE)_get_osfhandle(fh);
1874 if (GetConsoleMode(hCon, &dwMode))
1875 {
1876 *phCon = hCon;
1877 return true;
1878 }
1879 }
1880 return false;
1881}
1882
1883
1884static int rtStrmWriteWinConsoleLocked(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten, HANDLE hCon)
1885{
1886 int rc;
1887# ifdef HAVE_FWRITE_UNLOCKED
1888 if (!fflush_unlocked(pStream->pFile))
1889# else
1890 if (!fflush(pStream->pFile))
1891# endif
1892 {
1893 /** @todo Consider buffering later. For now, we'd rather correct output than
1894 * fast output. */
1895 DWORD cwcWritten = 0;
1896 PRTUTF16 pwszSrc = NULL;
1897 size_t cwcSrc = 0;
1898 rc = RTStrToUtf16Ex((const char *)pvBuf, cbToWrite, &pwszSrc, 0, &cwcSrc);
1899 AssertRC(rc);
1900 if (RT_SUCCESS(rc))
1901 {
1902 if (!WriteConsoleW(hCon, pwszSrc, (DWORD)cwcSrc, &cwcWritten, NULL))
1903 {
1904 /* try write char-by-char to avoid heap problem. */
1905 cwcWritten = 0;
1906 while (cwcWritten != cwcSrc)
1907 {
1908 DWORD cwcThis;
1909 if (!WriteConsoleW(hCon, &pwszSrc[cwcWritten], 1, &cwcThis, NULL))
1910 {
1911 if (!pcbWritten || cwcWritten == 0)
1912 rc = RTErrConvertFromErrno(GetLastError());
1913 break;
1914 }
1915 if (cwcThis != 1) /* Unable to write current char (amount)? */
1916 break;
1917 cwcWritten++;
1918 }
1919 }
1920 if (RT_SUCCESS(rc))
1921 {
1922 if (cwcWritten == cwcSrc)
1923 {
1924 if (pcbWritten)
1925 *pcbWritten = cbToWrite;
1926 }
1927 else if (pcbWritten)
1928 {
1929 PCRTUTF16 pwszCur = pwszSrc;
1930 const char *pszCur = (const char *)pvBuf;
1931 while ((uintptr_t)(pwszCur - pwszSrc) < cwcWritten)
1932 {
1933 RTUNICP CpIgnored;
1934 RTUtf16GetCpEx(&pwszCur, &CpIgnored);
1935 RTStrGetCpEx(&pszCur, &CpIgnored);
1936 }
1937 *pcbWritten = pszCur - (const char *)pvBuf;
1938 }
1939 else
1940 rc = VERR_WRITE_ERROR;
1941 }
1942 RTUtf16Free(pwszSrc);
1943 }
1944 }
1945 else
1946 rc = RTErrConvertFromErrno(errno);
1947 return rc;
1948}
1949
1950#endif /* RT_OS_WINDOWS */
1951
1952static int rtStrmWriteWorkerLocked(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten, bool fMustWriteAll)
1953{
1954#ifdef RTSTREAM_STANDALONE
1955 /*
1956 * Check preconditions.
1957 */
1958 Assert(pStream->enmBufDir == RTSTREAMBUFDIR_WRITE);
1959 Assert(pStream->cbBufAlloc >= 256);
1960 Assert(pStream->offBufFirst <= pStream->cbBufAlloc);
1961 Assert(pStream->offBufEnd <= pStream->cbBufAlloc);
1962 Assert(pStream->offBufFirst <= pStream->offBufEnd);
1963
1964 /*
1965 * We write everything via the buffer, letting the buffer flushing take
1966 * care of console output hacks and similar.
1967 */
1968 RT_NOREF(fMustWriteAll);
1969 int rc = VINF_SUCCESS;
1970 size_t cbTotal = 0;
1971 if (cbToWrite > 0)
1972 {
1973# ifdef RTSTREAM_WITH_TEXT_MODE
1974 const char *pchLf;
1975 if ( !pStream->fBinary
1976 && (pchLf = (const char *)memchr(pvBuf, '\n', cbToWrite)) != NULL)
1977 for (;;)
1978 {
1979 /* Deal with everything up to the newline. */
1980 size_t const cbToLf = (size_t)(pchLf - (const char *)pvBuf);
1981 if (cbToLf > 0)
1982 {
1983 rc = rtStrmBufCopyTo(pStream, pvBuf, cbToLf, &cbTotal);
1984 if (RT_FAILURE(rc))
1985 break;
1986 }
1987
1988 /* Copy the CRLF sequence into the buffer in one go to avoid complications. */
1989 if (pStream->cbBufAlloc - pStream->offBufEnd < 2)
1990 {
1991 rc = rtStrmBufFlushWrite(pStream, pStream->offBufEnd - pStream->offBufFirst);
1992 if (RT_FAILURE(rc))
1993 break;
1994 Assert(pStream->cbBufAlloc - pStream->offBufEnd >= 2);
1995 }
1996 pStream->pchBuf[pStream->offBufEnd++] = '\r';
1997 pStream->pchBuf[pStream->offBufEnd++] = '\n';
1998
1999 /* Advance past the newline. */
2000 pvBuf = (const char *)pvBuf + 1 + cbToLf;
2001 cbTotal += 1 + cbToLf;
2002 cbToWrite -= 1 + cbToLf;
2003 if (!cbToWrite)
2004 break;
2005
2006 /* More newlines? */
2007 pchLf = (const char *)memchr(pvBuf, '\n', cbToWrite);
2008 if (!pchLf)
2009 {
2010 rc = rtStrmBufCopyTo(pStream, pvBuf, cbToWrite, &cbTotal);
2011 break;
2012 }
2013 }
2014 else
2015# endif
2016 rc = rtStrmBufCopyTo(pStream, pvBuf, cbToWrite, &cbTotal);
2017
2018 /*
2019 * If line buffered or unbuffered, we probably have to do some flushing now.
2020 */
2021 if (RT_SUCCESS(rc) && pStream->enmBufStyle != RTSTREAMBUFSTYLE_FULL)
2022 {
2023 Assert(pStream->enmBufStyle == RTSTREAMBUFSTYLE_LINE || pStream->enmBufStyle == RTSTREAMBUFSTYLE_UNBUFFERED);
2024 size_t cbInBuffer = pStream->offBufEnd - pStream->offBufFirst;
2025 if (cbInBuffer > 0)
2026 {
2027 if ( pStream->enmBufStyle != RTSTREAMBUFSTYLE_LINE
2028 || pStream->pchBuf[pStream->offBufEnd - 1] == '\n')
2029 rc = rtStrmBufFlushWrite(pStream, cbInBuffer);
2030 else
2031 {
2032 const char *pchToFlush = &pStream->pchBuf[pStream->offBufFirst];
2033 const char *pchLastLf = (const char *)memrchr(pchToFlush, '\n', cbInBuffer);
2034 if (pchLastLf)
2035 rc = rtStrmBufFlushWrite(pStream, (size_t)(&pchLastLf[1] - pchToFlush));
2036 }
2037 }
2038 }
2039 }
2040 if (pcbWritten)
2041 *pcbWritten = cbTotal;
2042 return rc;
2043
2044
2045#else
2046 if (!fMustWriteAll)
2047 {
2048 IPRT_ALIGNMENT_CHECKS_DISABLE(); /* glibc / mempcpy again */
2049# ifdef HAVE_FWRITE_UNLOCKED
2050 *pcbWritten = fwrite_unlocked(pvBuf, 1, cbToWrite, pStream->pFile);
2051# else
2052 *pcbWritten = fwrite(pvBuf, 1, cbToWrite, pStream->pFile);
2053# endif
2054 IPRT_ALIGNMENT_CHECKS_ENABLE();
2055 if ( *pcbWritten == cbToWrite
2056# ifdef HAVE_FWRITE_UNLOCKED
2057 || !ferror_unlocked(pStream->pFile))
2058# else
2059 || !ferror(pStream->pFile))
2060# endif
2061 return VINF_SUCCESS;
2062 }
2063 else
2064 {
2065 /* Must write it all! */
2066 IPRT_ALIGNMENT_CHECKS_DISABLE(); /* glibc / mempcpy again */
2067# ifdef HAVE_FWRITE_UNLOCKED
2068 size_t cbWritten = fwrite_unlocked(pvBuf, cbToWrite, 1, pStream->pFile);
2069# else
2070 size_t cbWritten = fwrite(pvBuf, cbToWrite, 1, pStream->pFile);
2071# endif
2072 if (pcbWritten)
2073 *pcbWritten = cbWritten;
2074 IPRT_ALIGNMENT_CHECKS_ENABLE();
2075 if (cbWritten == 1)
2076 return VINF_SUCCESS;
2077# ifdef HAVE_FWRITE_UNLOCKED
2078 if (!ferror_unlocked(pStream->pFile))
2079# else
2080 if (!ferror(pStream->pFile))
2081# endif
2082 return VINF_SUCCESS; /* WEIRD! But anyway... */
2083 }
2084 return VERR_WRITE_ERROR;
2085#endif
2086}
2087
2088
2089/**
2090 * Internal write API, stream lock already held.
2091 *
2092 * @returns IPRT status code.
2093 * @param pStream The stream.
2094 * @param pvBuf What to write.
2095 * @param cbToWrite How much to write.
2096 * @param pcbWritten Where to optionally return the number of bytes
2097 * written.
2098 * @param fSureIsText Set if we're sure this is UTF-8 text already.
2099 */
2100static int rtStrmWriteLocked(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten, bool fSureIsText)
2101{
2102#ifdef RTSTREAM_STANDALONE
2103 int rc = rtStrmBufCheckErrorAndSwitchToWriteMode(pStream);
2104#else
2105 int rc = pStream->i32Error;
2106#endif
2107 if (RT_FAILURE(rc))
2108 return rc;
2109 if (pStream->fRecheckMode)
2110 rtStreamRecheckMode(pStream);
2111
2112#if defined(RT_OS_WINDOWS) && !defined(RTSTREAM_STANDALONE)
2113 /*
2114 * Use the unicode console API when possible in order to avoid stuff
2115 * getting lost in unnecessary code page translations.
2116 */
2117 HANDLE hCon;
2118 if (rtStrmIsConsoleUnlocked(pStream, &hCon))
2119 rc = rtStrmWriteWinConsoleLocked(pStream, pvBuf, cbToWrite, pcbWritten, hCon);
2120#else
2121 if (0) { }
2122#endif /* RT_OS_WINDOWS && !RTSTREAM_STANDALONE */
2123
2124 /*
2125 * If we're sure it's text output, convert it from UTF-8 to the current
2126 * code page before printing it.
2127 *
2128 * Note! Partial writes are not supported in this scenario because we
2129 * cannot easily report back a written length matching the input.
2130 */
2131 /** @todo Skip this if the current code set is UTF-8. */
2132 else if ( pStream->fCurrentCodeSet
2133 && !pStream->fBinary
2134 && ( fSureIsText
2135 || rtStrmIsUtf8Text(pvBuf, cbToWrite))
2136 )
2137 {
2138 char *pszSrcFree = NULL;
2139 const char *pszSrc = (const char *)pvBuf;
2140 if (pszSrc[cbToWrite - 1])
2141 {
2142 pszSrc = pszSrcFree = RTStrDupN(pszSrc, cbToWrite);
2143 if (pszSrc == NULL)
2144 rc = VERR_NO_STR_MEMORY;
2145 }
2146 if (RT_SUCCESS(rc))
2147 {
2148 char *pszSrcCurCP;
2149 rc = RTStrUtf8ToCurrentCP(&pszSrcCurCP, pszSrc);
2150 AssertRC(rc);
2151 if (RT_SUCCESS(rc))
2152 {
2153 size_t cchSrcCurCP = strlen(pszSrcCurCP);
2154 size_t cbWritten = 0;
2155 rc = rtStrmWriteWorkerLocked(pStream, pszSrcCurCP, cchSrcCurCP, &cbWritten, true /*fMustWriteAll*/);
2156 if (pcbWritten)
2157 *pcbWritten = cbWritten == cchSrcCurCP ? cbToWrite : 0;
2158 RTStrFree(pszSrcCurCP);
2159 }
2160 RTStrFree(pszSrcFree);
2161 }
2162 }
2163 /*
2164 * Otherwise, just write it as-is.
2165 */
2166 else
2167 rc = rtStrmWriteWorkerLocked(pStream, pvBuf, cbToWrite, pcbWritten, pcbWritten == NULL);
2168
2169 /*
2170 * Update error status on failure and return.
2171 *
2172 * We ignore failures from RTStrUtf8ToCurrentCP and RTStrToUtf16Ex regarding
2173 * invalid UTF-8 encoding, as that's an input issue and shouldn't affect the
2174 * stream state.
2175 */
2176 if (RT_FAILURE(rc) && rc != VERR_INVALID_UTF8_ENCODING)
2177 ASMAtomicWriteS32(&pStream->i32Error, rc);
2178 return rc;
2179}
2180
2181
2182/**
2183 * Internal write API.
2184 *
2185 * @returns IPRT status code.
2186 * @param pStream The stream.
2187 * @param pvBuf What to write.
2188 * @param cbToWrite How much to write.
2189 * @param pcbWritten Where to optionally return the number of bytes
2190 * written.
2191 * @param fSureIsText Set if we're sure this is UTF-8 text already.
2192 */
2193DECLINLINE(int) rtStrmWrite(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten, bool fSureIsText)
2194{
2195 rtStrmLock(pStream);
2196 int rc = rtStrmWriteLocked(pStream, pvBuf, cbToWrite, pcbWritten, fSureIsText);
2197 rtStrmUnlock(pStream);
2198 return rc;
2199}
2200
2201
2202RTR3DECL(int) RTStrmWriteEx(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten)
2203{
2204 AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER);
2205 return rtStrmWrite(pStream, pvBuf, cbToWrite, pcbWritten, false);
2206}
2207
2208
2209RTR3DECL(int) RTStrmGetCh(PRTSTREAM pStream)
2210{
2211 unsigned char ch;
2212 int rc = RTStrmReadEx(pStream, &ch, 1, NULL);
2213 if (RT_SUCCESS(rc))
2214 return ch;
2215 return -1;
2216}
2217
2218
2219RTR3DECL(int) RTStrmPutCh(PRTSTREAM pStream, int ch)
2220{
2221 return rtStrmWrite(pStream, &ch, 1, NULL, true /*fSureIsText*/);
2222}
2223
2224
2225RTR3DECL(int) RTStrmPutStr(PRTSTREAM pStream, const char *pszString)
2226{
2227 size_t cch = strlen(pszString);
2228 return rtStrmWrite(pStream, pszString, cch, NULL, true /*fSureIsText*/);
2229}
2230
2231
2232RTR3DECL(int) RTStrmGetLine(PRTSTREAM pStream, char *pszString, size_t cbString)
2233{
2234 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
2235 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
2236 AssertReturn(pszString, VERR_INVALID_POINTER);
2237 AssertReturn(cbString >= 2, VERR_INVALID_PARAMETER);
2238
2239 rtStrmLock(pStream);
2240
2241#ifdef RTSTREAM_STANDALONE
2242 int rc = rtStrmBufCheckErrorAndSwitchToReadMode(pStream);
2243#else
2244 int rc = pStream->i32Error;
2245#endif
2246 if (RT_SUCCESS(rc))
2247 {
2248 cbString--; /* Reserve space for the terminator. */
2249
2250#ifdef RTSTREAM_STANDALONE
2251 char * const pszStringStart = pszString;
2252#endif
2253 for (;;)
2254 {
2255#ifdef RTSTREAM_STANDALONE
2256 /* Make sure there is at least one character in the buffer: */
2257 size_t cbInBuffer = pStream->offBufEnd - pStream->offBufFirst;
2258 if (cbInBuffer == 0)
2259 {
2260 rc = rtStrmBufFill(pStream);
2261 if (RT_SUCCESS(rc))
2262 cbInBuffer = pStream->offBufEnd - pStream->offBufFirst;
2263 else
2264 break;
2265 }
2266
2267 /* Scan the buffer content terminating on a '\n', '\r\n' and '\0' sequence. */
2268 const char *pchSrc = &pStream->pchBuf[pStream->offBufFirst];
2269 const char *pchNewline = (const char *)memchr(pchSrc, '\n', cbInBuffer);
2270 const char *pchTerm = (const char *)memchr(pchSrc, '\0', cbInBuffer);
2271 size_t cbCopy;
2272 size_t cbAdvance;
2273 bool fStop = pchNewline || pchTerm;
2274 if (!fStop)
2275 cbAdvance = cbCopy = cbInBuffer;
2276 else if (!pchTerm || (pchNewline && pchTerm && (uintptr_t)pchNewline < (uintptr_t)pchTerm))
2277 {
2278 cbCopy = (size_t)(pchNewline - pchSrc);
2279 cbAdvance = cbCopy + 1;
2280 if (cbCopy && pchNewline[-1] == '\r')
2281 cbCopy--;
2282 else if (cbCopy == 0 && (uintptr_t)pszString > (uintptr_t)pszStringStart && pszString[-1] == '\r')
2283 pszString--, cbString++; /* drop trailing '\r' that it turns out was followed by '\n' */
2284 }
2285 else
2286 {
2287 cbCopy = (size_t)(pchTerm - pchSrc);
2288 cbAdvance = cbCopy + 1;
2289 }
2290
2291 /* Adjust for available space in the destination buffer, copy over the string
2292 characters and advance the buffer position (even on overflow). */
2293 if (cbCopy <= cbString)
2294 pStream->offBufFirst += cbAdvance;
2295 else
2296 {
2297 rc = VERR_BUFFER_OVERFLOW;
2298 fStop = true;
2299 cbCopy = cbString;
2300 pStream->offBufFirst += cbString;
2301 }
2302
2303 memcpy(pszString, pchSrc, cbCopy);
2304 pszString += cbCopy;
2305 cbString -= cbCopy;
2306
2307 if (fStop)
2308 break;
2309
2310#else /* !RTSTREAM_STANDALONE */
2311# ifdef HAVE_FWRITE_UNLOCKED /** @todo darwin + freebsd(?) has fgetc_unlocked but not fwrite_unlocked, optimize... */
2312 int ch = fgetc_unlocked(pStream->pFile);
2313# else
2314 int ch = fgetc(pStream->pFile);
2315# endif
2316
2317 /* Deal with \r\n sequences here. We'll return lone CR, but
2318 treat CRLF as LF. */
2319 if (ch == '\r')
2320 {
2321# ifdef HAVE_FWRITE_UNLOCKED /** @todo darwin + freebsd(?) has fgetc_unlocked but not fwrite_unlocked, optimize... */
2322 ch = fgetc_unlocked(pStream->pFile);
2323# else
2324 ch = fgetc(pStream->pFile);
2325# endif
2326 if (ch == '\n')
2327 break;
2328
2329 *pszString++ = '\r';
2330 if (--cbString <= 0)
2331 {
2332 /* yeah, this is an error, we dropped a character. */
2333 rc = VERR_BUFFER_OVERFLOW;
2334 break;
2335 }
2336 }
2337
2338 /* Deal with end of file. */
2339 if (ch == EOF)
2340 {
2341# ifdef HAVE_FWRITE_UNLOCKED
2342 if (feof_unlocked(pStream->pFile))
2343# else
2344 if (feof(pStream->pFile))
2345# endif
2346 {
2347 rc = VERR_EOF;
2348 break;
2349 }
2350# ifdef HAVE_FWRITE_UNLOCKED
2351 if (ferror_unlocked(pStream->pFile))
2352# else
2353 if (ferror(pStream->pFile))
2354# endif
2355 rc = VERR_READ_ERROR;
2356 else
2357 {
2358 AssertMsgFailed(("This shouldn't happen\n"));
2359 rc = VERR_INTERNAL_ERROR;
2360 }
2361 break;
2362 }
2363
2364 /* Deal with null terminator and (lone) new line. */
2365 if (ch == '\0' || ch == '\n')
2366 break;
2367
2368 /* No special character, append it to the return string. */
2369 *pszString++ = ch;
2370 if (--cbString <= 0)
2371 {
2372 rc = VINF_BUFFER_OVERFLOW;
2373 break;
2374 }
2375#endif /* !RTSTREAM_STANDALONE */
2376 }
2377
2378 *pszString = '\0';
2379 if (RT_FAILURE(rc))
2380 ASMAtomicWriteS32(&pStream->i32Error, rc);
2381 }
2382
2383 rtStrmUnlock(pStream);
2384 return rc;
2385}
2386
2387
2388RTR3DECL(int) RTStrmFlush(PRTSTREAM pStream)
2389{
2390 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
2391 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
2392
2393#ifdef RTSTREAM_STANDALONE
2394 rtStrmLock(pStream);
2395 int rc = rtStrmBufFlushWriteMaybe(pStream, true /*fInvalidate*/);
2396 rtStrmUnlock(pStream);
2397 return rc;
2398
2399#else
2400 if (!fflush(pStream->pFile))
2401 return VINF_SUCCESS;
2402 return RTErrConvertFromErrno(errno);
2403#endif
2404}
2405
2406
2407/**
2408 * Output callback.
2409 *
2410 * @returns number of bytes written.
2411 * @param pvArg User argument.
2412 * @param pachChars Pointer to an array of utf-8 characters.
2413 * @param cchChars Number of bytes in the character array pointed to by pachChars.
2414 */
2415static DECLCALLBACK(size_t) rtstrmOutput(void *pvArg, const char *pachChars, size_t cchChars)
2416{
2417 if (cchChars)
2418 rtStrmWriteLocked((PRTSTREAM)pvArg, pachChars, cchChars, NULL, true /*fSureIsText*/);
2419 /* else: ignore termination call. */
2420 return cchChars;
2421}
2422
2423
2424RTR3DECL(int) RTStrmPrintfV(PRTSTREAM pStream, const char *pszFormat, va_list args)
2425{
2426 AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER);
2427 int rc = pStream->i32Error;
2428 if (RT_SUCCESS(rc))
2429 {
2430 rtStrmLock(pStream);
2431// pStream->fShouldFlush = true;
2432 rc = (int)RTStrFormatV(rtstrmOutput, pStream, NULL, NULL, pszFormat, args);
2433 rtStrmUnlock(pStream);
2434 Assert(rc >= 0);
2435 }
2436 else
2437 rc = -1;
2438 return rc;
2439}
2440
2441
2442RTR3DECL(int) RTStrmPrintf(PRTSTREAM pStream, const char *pszFormat, ...)
2443{
2444 va_list args;
2445 va_start(args, pszFormat);
2446 int rc = RTStrmPrintfV(pStream, pszFormat, args);
2447 va_end(args);
2448 return rc;
2449}
2450
2451
2452RTDECL(void) RTStrmDumpPrintfV(void *pvUser, const char *pszFormat, va_list va)
2453{
2454 RTStrmPrintfV(pvUser ? (PRTSTREAM)pvUser : g_pStdOut, pszFormat, va);
2455}
2456
2457
2458RTR3DECL(int) RTPrintfV(const char *pszFormat, va_list args)
2459{
2460 return RTStrmPrintfV(g_pStdOut, pszFormat, args);
2461}
2462
2463
2464RTR3DECL(int) RTPrintf(const char *pszFormat, ...)
2465{
2466 va_list args;
2467 va_start(args, pszFormat);
2468 int rc = RTStrmPrintfV(g_pStdOut, pszFormat, args);
2469 va_end(args);
2470 return rc;
2471}
2472
2473
2474/**
2475 * Outputs @a cchIndent spaces.
2476 */
2477static void rtStrmWrapppedIndent(RTSTRMWRAPPEDSTATE *pState, uint32_t cchIndent)
2478{
2479 static const char s_szSpaces[] = " ";
2480 while (cchIndent)
2481 {
2482 uint32_t cchToWrite = RT_MIN(cchIndent, sizeof(s_szSpaces) - 1);
2483 int rc = RTStrmWrite(pState->pStream, s_szSpaces, cchToWrite);
2484 if (RT_SUCCESS(rc))
2485 cchIndent -= cchToWrite;
2486 else
2487 {
2488 pState->rcStatus = rc;
2489 break;
2490 }
2491 }
2492}
2493
2494
2495/**
2496 * Flushes the current line.
2497 *
2498 * @param pState The wrapped output state.
2499 * @param fPartial Set if partial flush due to buffer overflow, clear when
2500 * flushing due to '\n'.
2501 */
2502static void rtStrmWrappedFlushLine(RTSTRMWRAPPEDSTATE *pState, bool fPartial)
2503{
2504 /*
2505 * Check indentation in case we need to split the line later.
2506 */
2507 uint32_t cchIndent = pState->cchIndent;
2508 if (cchIndent == UINT32_MAX)
2509 {
2510 pState->cchIndent = 0;
2511 cchIndent = pState->cchHangingIndent;
2512 while (RT_C_IS_BLANK(pState->szLine[cchIndent]))
2513 cchIndent++;
2514 }
2515
2516 /*
2517 * Do the flushing.
2518 */
2519 uint32_t cchLine = pState->cchLine;
2520 Assert(cchLine < sizeof(pState->szLine));
2521 while (cchLine >= pState->cchWidth || !fPartial)
2522 {
2523 /*
2524 * Hopefully we don't need to do any wrapping ...
2525 */
2526 uint32_t offSplit;
2527 if (pState->cchIndent + cchLine <= pState->cchWidth)
2528 {
2529 if (!fPartial)
2530 {
2531 rtStrmWrapppedIndent(pState, pState->cchIndent);
2532 pState->szLine[cchLine] = '\n';
2533 int rc = RTStrmWrite(pState->pStream, pState->szLine, cchLine + 1);
2534 if (RT_FAILURE(rc))
2535 pState->rcStatus = rc;
2536 pState->cLines += 1;
2537 pState->cchLine = 0;
2538 pState->cchIndent = UINT32_MAX;
2539 return;
2540 }
2541
2542 /*
2543 * ... no such luck.
2544 */
2545 offSplit = cchLine;
2546 }
2547 else
2548 offSplit = pState->cchWidth - pState->cchIndent;
2549
2550 /* Find the start of the current word: */
2551 while (offSplit > 0 && !RT_C_IS_BLANK(pState->szLine[offSplit - 1]))
2552 offSplit--;
2553
2554 /* Skip spaces. */
2555 while (offSplit > 0 && RT_C_IS_BLANK(pState->szLine[offSplit - 1]))
2556 offSplit--;
2557 uint32_t offNextLine = offSplit;
2558
2559 /* If the first word + indent is wider than the screen width, so just output it in full. */
2560 if (offSplit == 0) /** @todo Split words, look for hyphen... This code is currently a bit crude. */
2561 {
2562 while (offSplit < cchLine && !RT_C_IS_BLANK(pState->szLine[offSplit]))
2563 offSplit++;
2564 offNextLine = offSplit;
2565 }
2566
2567 while (offNextLine < cchLine && RT_C_IS_BLANK(pState->szLine[offNextLine]))
2568 offNextLine++;
2569
2570 /*
2571 * Output and advance.
2572 */
2573 rtStrmWrapppedIndent(pState, pState->cchIndent);
2574 int rc = RTStrmWrite(pState->pStream, pState->szLine, offSplit);
2575 if (RT_SUCCESS(rc))
2576 rc = RTStrmPutCh(pState->pStream, '\n');
2577 if (RT_FAILURE(rc))
2578 pState->rcStatus = rc;
2579
2580 cchLine -= offNextLine;
2581 pState->cchLine = cchLine;
2582 pState->cLines += 1;
2583 pState->cchIndent = cchIndent;
2584 memmove(&pState->szLine[0], &pState->szLine[offNextLine], cchLine);
2585 }
2586
2587 /* The indentation level is reset for each '\n' we process, so only save cchIndent if partial. */
2588 pState->cchIndent = fPartial ? cchIndent : UINT32_MAX;
2589}
2590
2591
2592/**
2593 * @callback_method_impl{FNRTSTROUTPUT}
2594 */
2595static DECLCALLBACK(size_t) rtStrmWrappedOutput(void *pvArg, const char *pachChars, size_t cbChars)
2596{
2597 RTSTRMWRAPPEDSTATE *pState = (RTSTRMWRAPPEDSTATE *)pvArg;
2598 size_t const cchRet = cbChars;
2599 while (cbChars > 0)
2600 {
2601 if (*pachChars == '\n')
2602 {
2603 rtStrmWrappedFlushLine(pState, false /*fPartial*/);
2604 pachChars++;
2605 cbChars--;
2606 }
2607 else
2608 {
2609 const char *pszEol = (const char *)memchr(pachChars, '\n', cbChars);
2610 size_t cchToCopy = pszEol ? (size_t)(pszEol - pachChars) : cbChars;
2611 uint32_t cchLine = pState->cchLine;
2612 Assert(cchLine < sizeof(pState->szLine));
2613 bool const fFlush = cchLine + cchToCopy >= sizeof(pState->szLine);
2614 if (fFlush)
2615 cchToCopy = cchToCopy - sizeof(pState->szLine) - 1;
2616
2617 pState->cchLine = cchLine + (uint32_t)cchToCopy;
2618 memcpy(&pState->szLine[cchLine], pachChars, cchToCopy);
2619
2620 pachChars += cchToCopy;
2621 cbChars -= cchToCopy;
2622
2623 if (fFlush)
2624 rtStrmWrappedFlushLine(pState, true /*fPartial*/);
2625 }
2626 }
2627 return cchRet;
2628}
2629
2630
2631RTDECL(int32_t) RTStrmWrappedPrintfV(PRTSTREAM pStream, uint32_t fFlags, const char *pszFormat, va_list va)
2632{
2633 /*
2634 * Figure the output width and set up the rest of the output state.
2635 */
2636 RTSTRMWRAPPEDSTATE State;
2637 State.pStream = pStream;
2638 State.cchLine = fFlags & RTSTRMWRAPPED_F_LINE_OFFSET_MASK;
2639 State.cLines = 0;
2640 State.rcStatus = VINF_SUCCESS;
2641 State.cchIndent = UINT32_MAX;
2642 State.cchHangingIndent = 0;
2643 if (fFlags & RTSTRMWRAPPED_F_HANGING_INDENT)
2644 {
2645 State.cchHangingIndent = (fFlags & RTSTRMWRAPPED_F_HANGING_INDENT_MASK) >> RTSTRMWRAPPED_F_HANGING_INDENT_SHIFT;
2646 if (!State.cchHangingIndent)
2647 State.cchHangingIndent = 4;
2648 }
2649
2650 int rc = RTStrmQueryTerminalWidth(pStream, &State.cchWidth);
2651 if (RT_SUCCESS(rc))
2652 State.cchWidth = RT_MIN(State.cchWidth, RTSTRMWRAPPED_F_LINE_OFFSET_MASK + 1);
2653 else
2654 {
2655 State.cchWidth = (uint32_t)fFlags & RTSTRMWRAPPED_F_NON_TERMINAL_WIDTH_MASK;
2656 if (!State.cchWidth)
2657 State.cchWidth = 80;
2658 }
2659 if (State.cchWidth < 32)
2660 State.cchWidth = 32;
2661 //State.cchWidth -= 1; /* necessary here? */
2662
2663 /*
2664 * Do the formatting.
2665 */
2666 RTStrFormatV(rtStrmWrappedOutput, &State, NULL, NULL, pszFormat, va);
2667
2668 /*
2669 * Returning is simple if the buffer is empty. Otherwise we'll have to
2670 * perform a partial flush and write out whatever is left ourselves.
2671 */
2672 if (RT_SUCCESS(State.rcStatus))
2673 {
2674 if (State.cchLine == 0)
2675 return State.cLines << 16;
2676
2677 rtStrmWrappedFlushLine(&State, true /*fPartial*/);
2678 if (RT_SUCCESS(State.rcStatus) && State.cchLine > 0)
2679 {
2680 rtStrmWrapppedIndent(&State, State.cchIndent);
2681 State.rcStatus = RTStrmWrite(State.pStream, State.szLine, State.cchLine);
2682 }
2683 if (RT_SUCCESS(State.rcStatus))
2684 return RT_MIN(State.cchIndent + State.cchLine, RTSTRMWRAPPED_F_LINE_OFFSET_MASK) | (State.cLines << 16);
2685 }
2686 return State.rcStatus;
2687}
2688
2689
2690RTDECL(int32_t) RTStrmWrappedPrintf(PRTSTREAM pStream, uint32_t fFlags, const char *pszFormat, ...)
2691{
2692 va_list va;
2693 va_start(va, pszFormat);
2694 int32_t rcRet = RTStrmWrappedPrintfV(pStream, fFlags, pszFormat, va);
2695 va_end(va);
2696 return rcRet;
2697}
2698
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use