VirtualBox

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

Last change on this file was 103005, checked in by vboxsync, 4 months ago

iprt/asm.h,*: Split out the ASMMem* and related stuff into a separate header, asm-mem.h, so that we can get the RT_ASM_PAGE_SIZE stuff out of the way.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 82.6 KB
Line 
1/* $Id: stream.cpp 103005 2024-01-23 23:55:58Z 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-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 case RTSTREAMBUFDIR_NONE:
1687 break;
1688 }
1689 }
1690 }
1691 else
1692 rc = VERR_INVALID_HANDLE;
1693 }
1694 if (RT_FAILURE(rc))
1695 {
1696 ASMAtomicWriteS32(&pStream->i32Error, rc);
1697 off = rc;
1698 }
1699 rtStrmUnlock(pStream);
1700#else
1701# ifdef _MSC_VER
1702 RTFOFF off = _ftelli64(pStream->pFile);
1703# else
1704 RTFOFF off = ftello(pStream->pFile);
1705# endif
1706 if (off < 0)
1707 {
1708 int rc = RTErrConvertFromErrno(errno);
1709 ASMAtomicWriteS32(&pStream->i32Error, rc);
1710 off = rc;
1711 }
1712#endif
1713 return off;
1714}
1715
1716
1717/**
1718 * Recheck the stream mode.
1719 *
1720 * @param pStream The stream (locked).
1721 */
1722static void rtStreamRecheckMode(PRTSTREAM pStream)
1723{
1724#if (defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)) && !defined(RTSTREAM_STANDALONE)
1725 int fh = fileno(pStream->pFile);
1726 if (fh >= 0)
1727 {
1728 int fExpected = pStream->fBinary ? _O_BINARY : _O_TEXT;
1729 int fActual = _setmode(fh, fExpected);
1730 if (fActual != -1 && fExpected != (fActual & (_O_BINARY | _O_TEXT)))
1731 {
1732 fActual = _setmode(fh, fActual & (_O_BINARY | _O_TEXT));
1733 pStream->fBinary = !(fActual & _O_TEXT);
1734 }
1735 }
1736#else
1737 NOREF(pStream);
1738#endif
1739 pStream->fRecheckMode = false;
1740}
1741
1742
1743RTR3DECL(int) RTStrmReadEx(PRTSTREAM pStream, void *pvBuf, size_t cbToRead, size_t *pcbRead)
1744{
1745 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
1746 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
1747
1748#ifdef RTSTREAM_STANDALONE
1749 rtStrmLock(pStream);
1750 int rc = rtStrmBufCheckErrorAndSwitchToReadMode(pStream);
1751#else
1752 int rc = pStream->i32Error;
1753#endif
1754 if (RT_SUCCESS(rc))
1755 {
1756 if (pStream->fRecheckMode)
1757 rtStreamRecheckMode(pStream);
1758
1759#ifdef RTSTREAM_STANDALONE
1760
1761 /*
1762 * Copy data thru the read buffer for now as that'll handle both binary
1763 * and text modes seamlessly. We could optimize larger reads here when
1764 * in binary mode, that can wait till the basics work, I think.
1765 */
1766 size_t cbTotal = 0;
1767 if (cbToRead > 0)
1768 for (;;)
1769 {
1770 size_t cbInBuffer = pStream->offBufEnd - pStream->offBufFirst;
1771 if (cbInBuffer > 0)
1772 {
1773 size_t cbToCopy = RT_MIN(cbInBuffer, cbToRead);
1774 memcpy(pvBuf, &pStream->pchBuf[pStream->offBufFirst], cbToCopy);
1775 cbTotal += cbToRead;
1776 cbToRead -= cbToCopy;
1777 pvBuf = (char *)pvBuf + cbToCopy;
1778 if (!cbToRead)
1779 break;
1780 }
1781 rc = rtStrmBufFill(pStream);
1782 if (RT_SUCCESS(rc))
1783 { /* likely */ }
1784 else
1785 {
1786 if (rc == VERR_EOF && pcbRead && cbTotal > 0)
1787 rc = VINF_EOF;
1788 break;
1789 }
1790 }
1791 if (pcbRead)
1792 *pcbRead = cbTotal;
1793
1794#else /* !RTSTREAM_STANDALONE */
1795 if (pcbRead)
1796 {
1797 /*
1798 * Can do with a partial read.
1799 */
1800 *pcbRead = fread(pvBuf, 1, cbToRead, pStream->pFile);
1801 if ( *pcbRead == cbToRead
1802 || !ferror(pStream->pFile))
1803 rc = VINF_SUCCESS;
1804 else if (feof(pStream->pFile))
1805 rc = *pcbRead ? VINF_EOF : VERR_EOF;
1806 else if (ferror(pStream->pFile))
1807 rc = VERR_READ_ERROR;
1808 else
1809 {
1810 AssertMsgFailed(("This shouldn't happen\n"));
1811 rc = VERR_INTERNAL_ERROR;
1812 }
1813 }
1814 else
1815 {
1816 /*
1817 * Must read it all!
1818 */
1819 if (fread(pvBuf, cbToRead, 1, pStream->pFile) == 1)
1820 rc = VINF_SUCCESS;
1821 /* possible error/eof. */
1822 else if (feof(pStream->pFile))
1823 rc = VERR_EOF;
1824 else if (ferror(pStream->pFile))
1825 rc = VERR_READ_ERROR;
1826 else
1827 {
1828 AssertMsgFailed(("This shouldn't happen\n"));
1829 rc = VERR_INTERNAL_ERROR;
1830 }
1831 }
1832#endif /* !RTSTREAM_STANDALONE */
1833 if (RT_FAILURE(rc))
1834 ASMAtomicWriteS32(&pStream->i32Error, rc);
1835 }
1836#ifdef RTSTREAM_STANDALONE
1837 rtStrmUnlock(pStream);
1838#endif
1839 return rc;
1840}
1841
1842
1843/**
1844 * Check if the input text is valid UTF-8.
1845 *
1846 * @returns true/false.
1847 * @param pvBuf Pointer to the buffer.
1848 * @param cbBuf Size of the buffer.
1849 */
1850static bool rtStrmIsUtf8Text(const void *pvBuf, size_t cbBuf)
1851{
1852 NOREF(pvBuf);
1853 NOREF(cbBuf);
1854 /** @todo not sure this is a good idea... Better redefine RTStrmWrite. */
1855 return false;
1856}
1857
1858
1859#if defined(RT_OS_WINDOWS) && !defined(RTSTREAM_STANDALONE)
1860
1861/**
1862 * Check if the stream is for a Window console.
1863 *
1864 * @returns true / false.
1865 * @param pStream The stream.
1866 * @param phCon Where to return the console handle.
1867 */
1868static bool rtStrmIsConsoleUnlocked(PRTSTREAM pStream, HANDLE *phCon)
1869{
1870 int fh = fileno(pStream->pFile);
1871 if (isatty(fh))
1872 {
1873 DWORD dwMode;
1874 HANDLE hCon = (HANDLE)_get_osfhandle(fh);
1875 if (GetConsoleMode(hCon, &dwMode))
1876 {
1877 *phCon = hCon;
1878 return true;
1879 }
1880 }
1881 return false;
1882}
1883
1884
1885static int rtStrmWriteWinConsoleLocked(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten, HANDLE hCon)
1886{
1887 int rc;
1888# ifdef HAVE_FWRITE_UNLOCKED
1889 if (!fflush_unlocked(pStream->pFile))
1890# else
1891 if (!fflush(pStream->pFile))
1892# endif
1893 {
1894 /** @todo Consider buffering later. For now, we'd rather correct output than
1895 * fast output. */
1896 DWORD cwcWritten = 0;
1897 PRTUTF16 pwszSrc = NULL;
1898 size_t cwcSrc = 0;
1899 rc = RTStrToUtf16Ex((const char *)pvBuf, cbToWrite, &pwszSrc, 0, &cwcSrc);
1900 AssertRC(rc);
1901 if (RT_SUCCESS(rc))
1902 {
1903 if (!WriteConsoleW(hCon, pwszSrc, (DWORD)cwcSrc, &cwcWritten, NULL))
1904 {
1905 /* try write char-by-char to avoid heap problem. */
1906 cwcWritten = 0;
1907 while (cwcWritten != cwcSrc)
1908 {
1909 DWORD cwcThis;
1910 if (!WriteConsoleW(hCon, &pwszSrc[cwcWritten], 1, &cwcThis, NULL))
1911 {
1912 if (!pcbWritten || cwcWritten == 0)
1913 rc = RTErrConvertFromErrno(GetLastError());
1914 break;
1915 }
1916 if (cwcThis != 1) /* Unable to write current char (amount)? */
1917 break;
1918 cwcWritten++;
1919 }
1920 }
1921 if (RT_SUCCESS(rc))
1922 {
1923 if (cwcWritten == cwcSrc)
1924 {
1925 if (pcbWritten)
1926 *pcbWritten = cbToWrite;
1927 }
1928 else if (pcbWritten)
1929 {
1930 PCRTUTF16 pwszCur = pwszSrc;
1931 const char *pszCur = (const char *)pvBuf;
1932 while ((uintptr_t)(pwszCur - pwszSrc) < cwcWritten)
1933 {
1934 RTUNICP CpIgnored;
1935 RTUtf16GetCpEx(&pwszCur, &CpIgnored);
1936 RTStrGetCpEx(&pszCur, &CpIgnored);
1937 }
1938 *pcbWritten = pszCur - (const char *)pvBuf;
1939 }
1940 else
1941 rc = VERR_WRITE_ERROR;
1942 }
1943 RTUtf16Free(pwszSrc);
1944 }
1945 }
1946 else
1947 rc = RTErrConvertFromErrno(errno);
1948 return rc;
1949}
1950
1951#endif /* RT_OS_WINDOWS */
1952
1953static int rtStrmWriteWorkerLocked(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten, bool fMustWriteAll)
1954{
1955#ifdef RTSTREAM_STANDALONE
1956 /*
1957 * Check preconditions.
1958 */
1959 Assert(pStream->enmBufDir == RTSTREAMBUFDIR_WRITE);
1960 Assert(pStream->cbBufAlloc >= 256);
1961 Assert(pStream->offBufFirst <= pStream->cbBufAlloc);
1962 Assert(pStream->offBufEnd <= pStream->cbBufAlloc);
1963 Assert(pStream->offBufFirst <= pStream->offBufEnd);
1964
1965 /*
1966 * We write everything via the buffer, letting the buffer flushing take
1967 * care of console output hacks and similar.
1968 */
1969 RT_NOREF(fMustWriteAll);
1970 int rc = VINF_SUCCESS;
1971 size_t cbTotal = 0;
1972 if (cbToWrite > 0)
1973 {
1974# ifdef RTSTREAM_WITH_TEXT_MODE
1975 const char *pchLf;
1976 if ( !pStream->fBinary
1977 && (pchLf = (const char *)memchr(pvBuf, '\n', cbToWrite)) != NULL)
1978 for (;;)
1979 {
1980 /* Deal with everything up to the newline. */
1981 size_t const cbToLf = (size_t)(pchLf - (const char *)pvBuf);
1982 if (cbToLf > 0)
1983 {
1984 rc = rtStrmBufCopyTo(pStream, pvBuf, cbToLf, &cbTotal);
1985 if (RT_FAILURE(rc))
1986 break;
1987 }
1988
1989 /* Copy the CRLF sequence into the buffer in one go to avoid complications. */
1990 if (pStream->cbBufAlloc - pStream->offBufEnd < 2)
1991 {
1992 rc = rtStrmBufFlushWrite(pStream, pStream->offBufEnd - pStream->offBufFirst);
1993 if (RT_FAILURE(rc))
1994 break;
1995 Assert(pStream->cbBufAlloc - pStream->offBufEnd >= 2);
1996 }
1997 pStream->pchBuf[pStream->offBufEnd++] = '\r';
1998 pStream->pchBuf[pStream->offBufEnd++] = '\n';
1999
2000 /* Advance past the newline. */
2001 pvBuf = (const char *)pvBuf + 1 + cbToLf;
2002 cbTotal += 1 + cbToLf;
2003 cbToWrite -= 1 + cbToLf;
2004 if (!cbToWrite)
2005 break;
2006
2007 /* More newlines? */
2008 pchLf = (const char *)memchr(pvBuf, '\n', cbToWrite);
2009 if (!pchLf)
2010 {
2011 rc = rtStrmBufCopyTo(pStream, pvBuf, cbToWrite, &cbTotal);
2012 break;
2013 }
2014 }
2015 else
2016# endif
2017 rc = rtStrmBufCopyTo(pStream, pvBuf, cbToWrite, &cbTotal);
2018
2019 /*
2020 * If line buffered or unbuffered, we probably have to do some flushing now.
2021 */
2022 if (RT_SUCCESS(rc) && pStream->enmBufStyle != RTSTREAMBUFSTYLE_FULL)
2023 {
2024 Assert(pStream->enmBufStyle == RTSTREAMBUFSTYLE_LINE || pStream->enmBufStyle == RTSTREAMBUFSTYLE_UNBUFFERED);
2025 size_t cbInBuffer = pStream->offBufEnd - pStream->offBufFirst;
2026 if (cbInBuffer > 0)
2027 {
2028 if ( pStream->enmBufStyle != RTSTREAMBUFSTYLE_LINE
2029 || pStream->pchBuf[pStream->offBufEnd - 1] == '\n')
2030 rc = rtStrmBufFlushWrite(pStream, cbInBuffer);
2031 else
2032 {
2033 const char *pchToFlush = &pStream->pchBuf[pStream->offBufFirst];
2034 const char *pchLastLf = (const char *)memrchr(pchToFlush, '\n', cbInBuffer);
2035 if (pchLastLf)
2036 rc = rtStrmBufFlushWrite(pStream, (size_t)(&pchLastLf[1] - pchToFlush));
2037 }
2038 }
2039 }
2040 }
2041 if (pcbWritten)
2042 *pcbWritten = cbTotal;
2043 return rc;
2044
2045
2046#else
2047 if (!fMustWriteAll)
2048 {
2049 IPRT_ALIGNMENT_CHECKS_DISABLE(); /* glibc / mempcpy again */
2050# ifdef HAVE_FWRITE_UNLOCKED
2051 *pcbWritten = fwrite_unlocked(pvBuf, 1, cbToWrite, pStream->pFile);
2052# else
2053 *pcbWritten = fwrite(pvBuf, 1, cbToWrite, pStream->pFile);
2054# endif
2055 IPRT_ALIGNMENT_CHECKS_ENABLE();
2056 if ( *pcbWritten == cbToWrite
2057# ifdef HAVE_FWRITE_UNLOCKED
2058 || !ferror_unlocked(pStream->pFile))
2059# else
2060 || !ferror(pStream->pFile))
2061# endif
2062 return VINF_SUCCESS;
2063 }
2064 else
2065 {
2066 /* Must write it all! */
2067 IPRT_ALIGNMENT_CHECKS_DISABLE(); /* glibc / mempcpy again */
2068# ifdef HAVE_FWRITE_UNLOCKED
2069 size_t cbWritten = fwrite_unlocked(pvBuf, cbToWrite, 1, pStream->pFile);
2070# else
2071 size_t cbWritten = fwrite(pvBuf, cbToWrite, 1, pStream->pFile);
2072# endif
2073 if (pcbWritten)
2074 *pcbWritten = cbWritten;
2075 IPRT_ALIGNMENT_CHECKS_ENABLE();
2076 if (cbWritten == 1)
2077 return VINF_SUCCESS;
2078# ifdef HAVE_FWRITE_UNLOCKED
2079 if (!ferror_unlocked(pStream->pFile))
2080# else
2081 if (!ferror(pStream->pFile))
2082# endif
2083 return VINF_SUCCESS; /* WEIRD! But anyway... */
2084 }
2085 return VERR_WRITE_ERROR;
2086#endif
2087}
2088
2089
2090/**
2091 * Internal write API, stream lock already held.
2092 *
2093 * @returns IPRT status code.
2094 * @param pStream The stream.
2095 * @param pvBuf What to write.
2096 * @param cbToWrite How much to write.
2097 * @param pcbWritten Where to optionally return the number of bytes
2098 * written.
2099 * @param fSureIsText Set if we're sure this is UTF-8 text already.
2100 */
2101static int rtStrmWriteLocked(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten, bool fSureIsText)
2102{
2103#ifdef RTSTREAM_STANDALONE
2104 int rc = rtStrmBufCheckErrorAndSwitchToWriteMode(pStream);
2105#else
2106 int rc = pStream->i32Error;
2107#endif
2108 if (RT_FAILURE(rc))
2109 return rc;
2110 if (pStream->fRecheckMode)
2111 rtStreamRecheckMode(pStream);
2112
2113#if defined(RT_OS_WINDOWS) && !defined(RTSTREAM_STANDALONE)
2114 /*
2115 * Use the unicode console API when possible in order to avoid stuff
2116 * getting lost in unnecessary code page translations.
2117 */
2118 HANDLE hCon;
2119 if (rtStrmIsConsoleUnlocked(pStream, &hCon))
2120 rc = rtStrmWriteWinConsoleLocked(pStream, pvBuf, cbToWrite, pcbWritten, hCon);
2121#else
2122 if (0) { }
2123#endif /* RT_OS_WINDOWS && !RTSTREAM_STANDALONE */
2124
2125 /*
2126 * If we're sure it's text output, convert it from UTF-8 to the current
2127 * code page before printing it.
2128 *
2129 * Note! Partial writes are not supported in this scenario because we
2130 * cannot easily report back a written length matching the input.
2131 */
2132 /** @todo Skip this if the current code set is UTF-8. */
2133 else if ( pStream->fCurrentCodeSet
2134 && !pStream->fBinary
2135 && ( fSureIsText
2136 || rtStrmIsUtf8Text(pvBuf, cbToWrite))
2137 )
2138 {
2139 char *pszSrcFree = NULL;
2140 const char *pszSrc = (const char *)pvBuf;
2141 if (pszSrc[cbToWrite - 1])
2142 {
2143 pszSrc = pszSrcFree = RTStrDupN(pszSrc, cbToWrite);
2144 if (pszSrc == NULL)
2145 rc = VERR_NO_STR_MEMORY;
2146 }
2147 if (RT_SUCCESS(rc))
2148 {
2149 char *pszSrcCurCP;
2150 rc = RTStrUtf8ToCurrentCP(&pszSrcCurCP, pszSrc);
2151 AssertRC(rc);
2152 if (RT_SUCCESS(rc))
2153 {
2154 size_t cchSrcCurCP = strlen(pszSrcCurCP);
2155 size_t cbWritten = 0;
2156 rc = rtStrmWriteWorkerLocked(pStream, pszSrcCurCP, cchSrcCurCP, &cbWritten, true /*fMustWriteAll*/);
2157 if (pcbWritten)
2158 *pcbWritten = cbWritten == cchSrcCurCP ? cbToWrite : 0;
2159 RTStrFree(pszSrcCurCP);
2160 }
2161 RTStrFree(pszSrcFree);
2162 }
2163 }
2164 /*
2165 * Otherwise, just write it as-is.
2166 */
2167 else
2168 rc = rtStrmWriteWorkerLocked(pStream, pvBuf, cbToWrite, pcbWritten, pcbWritten == NULL);
2169
2170 /*
2171 * Update error status on failure and return.
2172 *
2173 * We ignore failures from RTStrUtf8ToCurrentCP and RTStrToUtf16Ex regarding
2174 * invalid UTF-8 encoding, as that's an input issue and shouldn't affect the
2175 * stream state.
2176 */
2177 if (RT_FAILURE(rc) && rc != VERR_INVALID_UTF8_ENCODING)
2178 ASMAtomicWriteS32(&pStream->i32Error, rc);
2179 return rc;
2180}
2181
2182
2183/**
2184 * Internal write API.
2185 *
2186 * @returns IPRT status code.
2187 * @param pStream The stream.
2188 * @param pvBuf What to write.
2189 * @param cbToWrite How much to write.
2190 * @param pcbWritten Where to optionally return the number of bytes
2191 * written.
2192 * @param fSureIsText Set if we're sure this is UTF-8 text already.
2193 */
2194DECLINLINE(int) rtStrmWrite(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten, bool fSureIsText)
2195{
2196 rtStrmLock(pStream);
2197 int rc = rtStrmWriteLocked(pStream, pvBuf, cbToWrite, pcbWritten, fSureIsText);
2198 rtStrmUnlock(pStream);
2199 return rc;
2200}
2201
2202
2203RTR3DECL(int) RTStrmWriteEx(PRTSTREAM pStream, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten)
2204{
2205 AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER);
2206 return rtStrmWrite(pStream, pvBuf, cbToWrite, pcbWritten, false);
2207}
2208
2209
2210RTR3DECL(int) RTStrmGetCh(PRTSTREAM pStream)
2211{
2212 unsigned char ch;
2213 int rc = RTStrmReadEx(pStream, &ch, 1, NULL);
2214 if (RT_SUCCESS(rc))
2215 return ch;
2216 return -1;
2217}
2218
2219
2220RTR3DECL(int) RTStrmPutCh(PRTSTREAM pStream, int ch)
2221{
2222 return rtStrmWrite(pStream, &ch, 1, NULL, true /*fSureIsText*/);
2223}
2224
2225
2226RTR3DECL(int) RTStrmPutStr(PRTSTREAM pStream, const char *pszString)
2227{
2228 size_t cch = strlen(pszString);
2229 return rtStrmWrite(pStream, pszString, cch, NULL, true /*fSureIsText*/);
2230}
2231
2232
2233RTR3DECL(int) RTStrmGetLine(PRTSTREAM pStream, char *pszString, size_t cbString)
2234{
2235 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
2236 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
2237 AssertReturn(pszString, VERR_INVALID_POINTER);
2238 AssertReturn(cbString >= 2, VERR_INVALID_PARAMETER);
2239
2240 rtStrmLock(pStream);
2241
2242#ifdef RTSTREAM_STANDALONE
2243 int rc = rtStrmBufCheckErrorAndSwitchToReadMode(pStream);
2244#else
2245 int rc = pStream->i32Error;
2246#endif
2247 if (RT_SUCCESS(rc))
2248 {
2249 cbString--; /* Reserve space for the terminator. */
2250
2251#ifdef RTSTREAM_STANDALONE
2252 char * const pszStringStart = pszString;
2253#endif
2254 for (;;)
2255 {
2256#ifdef RTSTREAM_STANDALONE
2257 /* Make sure there is at least one character in the buffer: */
2258 size_t cbInBuffer = pStream->offBufEnd - pStream->offBufFirst;
2259 if (cbInBuffer == 0)
2260 {
2261 rc = rtStrmBufFill(pStream);
2262 if (RT_SUCCESS(rc))
2263 cbInBuffer = pStream->offBufEnd - pStream->offBufFirst;
2264 else
2265 break;
2266 }
2267
2268 /* Scan the buffer content terminating on a '\n', '\r\n' and '\0' sequence. */
2269 const char *pchSrc = &pStream->pchBuf[pStream->offBufFirst];
2270 const char *pchNewline = (const char *)memchr(pchSrc, '\n', cbInBuffer);
2271 const char *pchTerm = (const char *)memchr(pchSrc, '\0', cbInBuffer);
2272 size_t cbCopy;
2273 size_t cbAdvance;
2274 bool fStop = pchNewline || pchTerm;
2275 if (!fStop)
2276 cbAdvance = cbCopy = cbInBuffer;
2277 else if (!pchTerm || (pchNewline && pchTerm && (uintptr_t)pchNewline < (uintptr_t)pchTerm))
2278 {
2279 cbCopy = (size_t)(pchNewline - pchSrc);
2280 cbAdvance = cbCopy + 1;
2281 if (cbCopy && pchNewline[-1] == '\r')
2282 cbCopy--;
2283 else if (cbCopy == 0 && (uintptr_t)pszString > (uintptr_t)pszStringStart && pszString[-1] == '\r')
2284 pszString--, cbString++; /* drop trailing '\r' that it turns out was followed by '\n' */
2285 }
2286 else
2287 {
2288 cbCopy = (size_t)(pchTerm - pchSrc);
2289 cbAdvance = cbCopy + 1;
2290 }
2291
2292 /* Adjust for available space in the destination buffer, copy over the string
2293 characters and advance the buffer position (even on overflow). */
2294 if (cbCopy <= cbString)
2295 pStream->offBufFirst += cbAdvance;
2296 else
2297 {
2298 rc = VERR_BUFFER_OVERFLOW;
2299 fStop = true;
2300 cbCopy = cbString;
2301 pStream->offBufFirst += cbString;
2302 }
2303
2304 memcpy(pszString, pchSrc, cbCopy);
2305 pszString += cbCopy;
2306 cbString -= cbCopy;
2307
2308 if (fStop)
2309 break;
2310
2311#else /* !RTSTREAM_STANDALONE */
2312# ifdef HAVE_FWRITE_UNLOCKED /** @todo darwin + freebsd(?) has fgetc_unlocked but not fwrite_unlocked, optimize... */
2313 int ch = fgetc_unlocked(pStream->pFile);
2314# else
2315 int ch = fgetc(pStream->pFile);
2316# endif
2317
2318 /* Deal with \r\n sequences here. We'll return lone CR, but
2319 treat CRLF as LF. */
2320 if (ch == '\r')
2321 {
2322# ifdef HAVE_FWRITE_UNLOCKED /** @todo darwin + freebsd(?) has fgetc_unlocked but not fwrite_unlocked, optimize... */
2323 ch = fgetc_unlocked(pStream->pFile);
2324# else
2325 ch = fgetc(pStream->pFile);
2326# endif
2327 if (ch == '\n')
2328 break;
2329
2330 *pszString++ = '\r';
2331 if (--cbString <= 0)
2332 {
2333 /* yeah, this is an error, we dropped a character. */
2334 rc = VERR_BUFFER_OVERFLOW;
2335 break;
2336 }
2337 }
2338
2339 /* Deal with end of file. */
2340 if (ch == EOF)
2341 {
2342# ifdef HAVE_FWRITE_UNLOCKED
2343 if (feof_unlocked(pStream->pFile))
2344# else
2345 if (feof(pStream->pFile))
2346# endif
2347 {
2348 rc = VERR_EOF;
2349 break;
2350 }
2351# ifdef HAVE_FWRITE_UNLOCKED
2352 if (ferror_unlocked(pStream->pFile))
2353# else
2354 if (ferror(pStream->pFile))
2355# endif
2356 rc = VERR_READ_ERROR;
2357 else
2358 {
2359 AssertMsgFailed(("This shouldn't happen\n"));
2360 rc = VERR_INTERNAL_ERROR;
2361 }
2362 break;
2363 }
2364
2365 /* Deal with null terminator and (lone) new line. */
2366 if (ch == '\0' || ch == '\n')
2367 break;
2368
2369 /* No special character, append it to the return string. */
2370 *pszString++ = ch;
2371 if (--cbString <= 0)
2372 {
2373 rc = VINF_BUFFER_OVERFLOW;
2374 break;
2375 }
2376#endif /* !RTSTREAM_STANDALONE */
2377 }
2378
2379 *pszString = '\0';
2380 if (RT_FAILURE(rc))
2381 ASMAtomicWriteS32(&pStream->i32Error, rc);
2382 }
2383
2384 rtStrmUnlock(pStream);
2385 return rc;
2386}
2387
2388
2389RTR3DECL(int) RTStrmFlush(PRTSTREAM pStream)
2390{
2391 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
2392 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
2393
2394#ifdef RTSTREAM_STANDALONE
2395 rtStrmLock(pStream);
2396 int rc = rtStrmBufFlushWriteMaybe(pStream, true /*fInvalidate*/);
2397 rtStrmUnlock(pStream);
2398 return rc;
2399
2400#else
2401 if (!fflush(pStream->pFile))
2402 return VINF_SUCCESS;
2403 return RTErrConvertFromErrno(errno);
2404#endif
2405}
2406
2407
2408/**
2409 * Output callback.
2410 *
2411 * @returns number of bytes written.
2412 * @param pvArg User argument.
2413 * @param pachChars Pointer to an array of utf-8 characters.
2414 * @param cchChars Number of bytes in the character array pointed to by pachChars.
2415 */
2416static DECLCALLBACK(size_t) rtstrmOutput(void *pvArg, const char *pachChars, size_t cchChars)
2417{
2418 if (cchChars)
2419 rtStrmWriteLocked((PRTSTREAM)pvArg, pachChars, cchChars, NULL, true /*fSureIsText*/);
2420 /* else: ignore termination call. */
2421 return cchChars;
2422}
2423
2424
2425RTR3DECL(int) RTStrmPrintfV(PRTSTREAM pStream, const char *pszFormat, va_list args)
2426{
2427 AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER);
2428 int rc = pStream->i32Error;
2429 if (RT_SUCCESS(rc))
2430 {
2431 rtStrmLock(pStream);
2432// pStream->fShouldFlush = true;
2433 rc = (int)RTStrFormatV(rtstrmOutput, pStream, NULL, NULL, pszFormat, args);
2434 rtStrmUnlock(pStream);
2435 Assert(rc >= 0);
2436 }
2437 else
2438 rc = -1;
2439 return rc;
2440}
2441
2442
2443RTR3DECL(int) RTStrmPrintf(PRTSTREAM pStream, const char *pszFormat, ...)
2444{
2445 va_list args;
2446 va_start(args, pszFormat);
2447 int rc = RTStrmPrintfV(pStream, pszFormat, args);
2448 va_end(args);
2449 return rc;
2450}
2451
2452
2453RTDECL(void) RTStrmDumpPrintfV(void *pvUser, const char *pszFormat, va_list va)
2454{
2455 RTStrmPrintfV(pvUser ? (PRTSTREAM)pvUser : g_pStdOut, pszFormat, va);
2456}
2457
2458
2459RTR3DECL(int) RTPrintfV(const char *pszFormat, va_list args)
2460{
2461 return RTStrmPrintfV(g_pStdOut, pszFormat, args);
2462}
2463
2464
2465RTR3DECL(int) RTPrintf(const char *pszFormat, ...)
2466{
2467 va_list args;
2468 va_start(args, pszFormat);
2469 int rc = RTStrmPrintfV(g_pStdOut, pszFormat, args);
2470 va_end(args);
2471 return rc;
2472}
2473
2474
2475/**
2476 * Outputs @a cchIndent spaces.
2477 */
2478static void rtStrmWrapppedIndent(RTSTRMWRAPPEDSTATE *pState, uint32_t cchIndent)
2479{
2480 static const char s_szSpaces[] = " ";
2481 while (cchIndent)
2482 {
2483 uint32_t cchToWrite = RT_MIN(cchIndent, sizeof(s_szSpaces) - 1);
2484 int rc = RTStrmWrite(pState->pStream, s_szSpaces, cchToWrite);
2485 if (RT_SUCCESS(rc))
2486 cchIndent -= cchToWrite;
2487 else
2488 {
2489 pState->rcStatus = rc;
2490 break;
2491 }
2492 }
2493}
2494
2495
2496/**
2497 * Flushes the current line.
2498 *
2499 * @param pState The wrapped output state.
2500 * @param fPartial Set if partial flush due to buffer overflow, clear when
2501 * flushing due to '\n'.
2502 */
2503static void rtStrmWrappedFlushLine(RTSTRMWRAPPEDSTATE *pState, bool fPartial)
2504{
2505 /*
2506 * Check indentation in case we need to split the line later.
2507 */
2508 uint32_t cchIndent = pState->cchIndent;
2509 if (cchIndent == UINT32_MAX)
2510 {
2511 pState->cchIndent = 0;
2512 cchIndent = pState->cchHangingIndent;
2513 while (RT_C_IS_BLANK(pState->szLine[cchIndent]))
2514 cchIndent++;
2515 }
2516
2517 /*
2518 * Do the flushing.
2519 */
2520 uint32_t cchLine = pState->cchLine;
2521 Assert(cchLine < sizeof(pState->szLine));
2522 while (cchLine >= pState->cchWidth || !fPartial)
2523 {
2524 /*
2525 * Hopefully we don't need to do any wrapping ...
2526 */
2527 uint32_t offSplit;
2528 if (pState->cchIndent + cchLine <= pState->cchWidth)
2529 {
2530 if (!fPartial)
2531 {
2532 rtStrmWrapppedIndent(pState, pState->cchIndent);
2533 pState->szLine[cchLine] = '\n';
2534 int rc = RTStrmWrite(pState->pStream, pState->szLine, cchLine + 1);
2535 if (RT_FAILURE(rc))
2536 pState->rcStatus = rc;
2537 pState->cLines += 1;
2538 pState->cchLine = 0;
2539 pState->cchIndent = UINT32_MAX;
2540 return;
2541 }
2542
2543 /*
2544 * ... no such luck.
2545 */
2546 offSplit = cchLine;
2547 }
2548 else
2549 offSplit = pState->cchWidth - pState->cchIndent;
2550
2551 /* Find the start of the current word: */
2552 while (offSplit > 0 && !RT_C_IS_BLANK(pState->szLine[offSplit - 1]))
2553 offSplit--;
2554
2555 /* Skip spaces. */
2556 while (offSplit > 0 && RT_C_IS_BLANK(pState->szLine[offSplit - 1]))
2557 offSplit--;
2558 uint32_t offNextLine = offSplit;
2559
2560 /* If the first word + indent is wider than the screen width, so just output it in full. */
2561 if (offSplit == 0) /** @todo Split words, look for hyphen... This code is currently a bit crude. */
2562 {
2563 while (offSplit < cchLine && !RT_C_IS_BLANK(pState->szLine[offSplit]))
2564 offSplit++;
2565 offNextLine = offSplit;
2566 }
2567
2568 while (offNextLine < cchLine && RT_C_IS_BLANK(pState->szLine[offNextLine]))
2569 offNextLine++;
2570
2571 /*
2572 * Output and advance.
2573 */
2574 rtStrmWrapppedIndent(pState, pState->cchIndent);
2575 int rc = RTStrmWrite(pState->pStream, pState->szLine, offSplit);
2576 if (RT_SUCCESS(rc))
2577 rc = RTStrmPutCh(pState->pStream, '\n');
2578 if (RT_FAILURE(rc))
2579 pState->rcStatus = rc;
2580
2581 cchLine -= offNextLine;
2582 pState->cchLine = cchLine;
2583 pState->cLines += 1;
2584 pState->cchIndent = cchIndent;
2585 memmove(&pState->szLine[0], &pState->szLine[offNextLine], cchLine);
2586 }
2587
2588 /* The indentation level is reset for each '\n' we process, so only save cchIndent if partial. */
2589 pState->cchIndent = fPartial ? cchIndent : UINT32_MAX;
2590}
2591
2592
2593/**
2594 * @callback_method_impl{FNRTSTROUTPUT}
2595 */
2596static DECLCALLBACK(size_t) rtStrmWrappedOutput(void *pvArg, const char *pachChars, size_t cbChars)
2597{
2598 RTSTRMWRAPPEDSTATE *pState = (RTSTRMWRAPPEDSTATE *)pvArg;
2599 size_t const cchRet = cbChars;
2600 while (cbChars > 0)
2601 {
2602 if (*pachChars == '\n')
2603 {
2604 rtStrmWrappedFlushLine(pState, false /*fPartial*/);
2605 pachChars++;
2606 cbChars--;
2607 }
2608 else
2609 {
2610 const char *pszEol = (const char *)memchr(pachChars, '\n', cbChars);
2611 size_t cchToCopy = pszEol ? (size_t)(pszEol - pachChars) : cbChars;
2612 uint32_t cchLine = pState->cchLine;
2613 Assert(cchLine < sizeof(pState->szLine));
2614 bool const fFlush = cchLine + cchToCopy >= sizeof(pState->szLine);
2615 if (fFlush)
2616 cchToCopy = cchToCopy - sizeof(pState->szLine) - 1;
2617
2618 pState->cchLine = cchLine + (uint32_t)cchToCopy;
2619 memcpy(&pState->szLine[cchLine], pachChars, cchToCopy);
2620
2621 pachChars += cchToCopy;
2622 cbChars -= cchToCopy;
2623
2624 if (fFlush)
2625 rtStrmWrappedFlushLine(pState, true /*fPartial*/);
2626 }
2627 }
2628 return cchRet;
2629}
2630
2631
2632RTDECL(int32_t) RTStrmWrappedPrintfV(PRTSTREAM pStream, uint32_t fFlags, const char *pszFormat, va_list va)
2633{
2634 /*
2635 * Figure the output width and set up the rest of the output state.
2636 */
2637 RTSTRMWRAPPEDSTATE State;
2638 State.pStream = pStream;
2639 State.cchLine = fFlags & RTSTRMWRAPPED_F_LINE_OFFSET_MASK;
2640 State.cLines = 0;
2641 State.rcStatus = VINF_SUCCESS;
2642 State.cchIndent = UINT32_MAX;
2643 State.cchHangingIndent = 0;
2644 if (fFlags & RTSTRMWRAPPED_F_HANGING_INDENT)
2645 {
2646 State.cchHangingIndent = (fFlags & RTSTRMWRAPPED_F_HANGING_INDENT_MASK) >> RTSTRMWRAPPED_F_HANGING_INDENT_SHIFT;
2647 if (!State.cchHangingIndent)
2648 State.cchHangingIndent = 4;
2649 }
2650
2651 int rc = RTStrmQueryTerminalWidth(pStream, &State.cchWidth);
2652 if (RT_SUCCESS(rc))
2653 State.cchWidth = RT_MIN(State.cchWidth, RTSTRMWRAPPED_F_LINE_OFFSET_MASK + 1);
2654 else
2655 {
2656 State.cchWidth = (uint32_t)fFlags & RTSTRMWRAPPED_F_NON_TERMINAL_WIDTH_MASK;
2657 if (!State.cchWidth)
2658 State.cchWidth = 80;
2659 }
2660 if (State.cchWidth < 32)
2661 State.cchWidth = 32;
2662 //State.cchWidth -= 1; /* necessary here? */
2663
2664 /*
2665 * Do the formatting.
2666 */
2667 RTStrFormatV(rtStrmWrappedOutput, &State, NULL, NULL, pszFormat, va);
2668
2669 /*
2670 * Returning is simple if the buffer is empty. Otherwise we'll have to
2671 * perform a partial flush and write out whatever is left ourselves.
2672 */
2673 if (RT_SUCCESS(State.rcStatus))
2674 {
2675 if (State.cchLine == 0)
2676 return State.cLines << 16;
2677
2678 rtStrmWrappedFlushLine(&State, true /*fPartial*/);
2679 if (RT_SUCCESS(State.rcStatus) && State.cchLine > 0)
2680 {
2681 rtStrmWrapppedIndent(&State, State.cchIndent);
2682 State.rcStatus = RTStrmWrite(State.pStream, State.szLine, State.cchLine);
2683 }
2684 if (RT_SUCCESS(State.rcStatus))
2685 return RT_MIN(State.cchIndent + State.cchLine, RTSTRMWRAPPED_F_LINE_OFFSET_MASK) | (State.cLines << 16);
2686 }
2687 return State.rcStatus;
2688}
2689
2690
2691RTDECL(int32_t) RTStrmWrappedPrintf(PRTSTREAM pStream, uint32_t fFlags, const char *pszFormat, ...)
2692{
2693 va_list va;
2694 va_start(va, pszFormat);
2695 int32_t rcRet = RTStrmWrappedPrintfV(pStream, fFlags, pszFormat, va);
2696 va_end(va);
2697 return rcRet;
2698}
2699
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use