VirtualBox

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

Last change on this file since 106842 was 106497, checked in by vboxsync, 2 months ago

iprt/r3: switch fall thru and LARGE_INTEGER init. jiraref:VBP-1171

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