VirtualBox

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

Last change on this file since 96081 was 96077, checked in by vboxsync, 3 years ago

IPRT/RTStream: Added a RTStrmOpenFileHandle API for implementing fdopen & tmpfile[_s]. bugref:10261

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