VirtualBox

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

Last change on this file since 95924 was 95903, checked in by vboxsync, 3 years ago

IPRT/stream: Flush & cleanup of non-standard streams on exit/unload like regular crt does (IIRC). bugref:10261

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