VirtualBox

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

Last change on this file since 96525 was 96511, checked in by vboxsync, 3 years ago

/Config.kmk,VMM,IPRT: Converted all run-at-compile-time tests and more to only use IPRT, so we can more easily transition to no-CRT static building on windows. bugref:10261

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

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