VirtualBox

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

Last change on this file since 95685 was 95587, checked in by vboxsync, 2 years ago

IPRT: Added RTStrmWrappedPrintf and RTStrmWrappedPrintfV for some simple output wrapping accoring to the terminal size (80 columns if not terminal). [fix]

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 44.2 KB
Line 
1/* $Id: stream.cpp 95587 2022-07-11 09:52:25Z 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#if defined(RT_OS_LINUX) /* PORTME: check for the _unlocked functions in stdio.h */
30#define HAVE_FWRITE_UNLOCKED
31#endif
32
33
34/*********************************************************************************************************************************
35* Header Files *
36*********************************************************************************************************************************/
37#include <iprt/stream.h>
38#include "internal/iprt.h"
39
40#include <iprt/asm.h>
41#ifndef HAVE_FWRITE_UNLOCKED
42# include <iprt/critsect.h>
43#endif
44#include <iprt/string.h>
45#include <iprt/assert.h>
46#include <iprt/alloc.h>
47#include <iprt/ctype.h>
48#include <iprt/err.h>
49#include <iprt/param.h>
50#include <iprt/string.h>
51
52#include "internal/alignmentchecks.h"
53#include "internal/magics.h"
54
55#include <stdio.h>
56#include <errno.h>
57#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
58# include <io.h>
59# include <fcntl.h>
60#endif
61#ifdef RT_OS_WINDOWS
62# include <iprt/utf16.h>
63# include <iprt/win/windows.h>
64#else
65# include <termios.h>
66# include <unistd.h>
67# include <sys/ioctl.h>
68#endif
69
70#ifdef RT_OS_OS2
71# define _O_TEXT O_TEXT
72# define _O_BINARY O_BINARY
73#endif
74
75
76/*********************************************************************************************************************************
77* Structures and Typedefs *
78*********************************************************************************************************************************/
79/**
80 * File stream.
81 */
82typedef struct RTSTREAM
83{
84 /** Magic value used to validate the stream. (RTSTREAM_MAGIC) */
85 uint32_t u32Magic;
86 /** File stream error. */
87 int32_t volatile i32Error;
88 /** Pointer to the LIBC file stream. */
89 FILE *pFile;
90 /** Stream is using the current process code set. */
91 bool fCurrentCodeSet;
92 /** Whether the stream was opened in binary mode. */
93 bool fBinary;
94 /** Whether to recheck the stream mode before writing. */
95 bool fRecheckMode;
96#ifndef HAVE_FWRITE_UNLOCKED
97 /** Critical section for serializing access to the stream. */
98 PRTCRITSECT pCritSect;
99#endif
100} RTSTREAM;
101
102
103/**
104 * State for wrapped output (RTStrmWrappedPrintf, RTStrmWrappedPrintfV).
105 */
106typedef struct RTSTRMWRAPPEDSTATE
107{
108 PRTSTREAM pStream; /**< The output stream. */
109 uint32_t cchWidth; /**< The line width. */
110 uint32_t cchLine; /**< The current line length (valid chars in szLine). */
111 uint32_t cLines; /**< Number of lines written. */
112 uint32_t cchIndent; /**< The indent (determined from the first line). */
113 int rcStatus; /**< The output status. */
114 uint8_t cchHangingIndent; /**< Hanging indent (from fFlags). */
115 char szLine[0x1000+1]; /**< We must buffer output so we can do proper word splitting. */
116} RTSTRMWRAPPEDSTATE;
117
118
119/*********************************************************************************************************************************
120* Global Variables *
121*********************************************************************************************************************************/
122/** The standard input stream. */
123static RTSTREAM g_StdIn =
124{
125 RTSTREAM_MAGIC,
126 0,
127 stdin,
128 /*.fCurrentCodeSet = */ true,
129 /*.fBinary = */ false,
130 /*.fRecheckMode = */ true
131#ifndef HAVE_FWRITE_UNLOCKED
132 , NULL
133#endif
134};
135
136/** The standard error stream. */
137static RTSTREAM g_StdErr =
138{
139 RTSTREAM_MAGIC,
140 0,
141 stderr,
142 /*.fCurrentCodeSet = */ true,
143 /*.fBinary = */ false,
144 /*.fRecheckMode = */ true
145#ifndef HAVE_FWRITE_UNLOCKED
146 , NULL
147#endif
148};
149
150/** The standard output stream. */
151static RTSTREAM g_StdOut =
152{
153 RTSTREAM_MAGIC,
154 0,
155 stdout,
156 /*.fCurrentCodeSet = */ true,
157 /*.fBinary = */ false,
158 /*.fRecheckMode = */ true
159#ifndef HAVE_FWRITE_UNLOCKED
160 , NULL
161#endif
162};
163
164/** Pointer to the standard input stream. */
165RTDATADECL(PRTSTREAM) g_pStdIn = &g_StdIn;
166
167/** Pointer to the standard output stream. */
168RTDATADECL(PRTSTREAM) g_pStdErr = &g_StdErr;
169
170/** Pointer to the standard output stream. */
171RTDATADECL(PRTSTREAM) g_pStdOut = &g_StdOut;
172
173
174#ifndef HAVE_FWRITE_UNLOCKED
175/**
176 * Allocates and acquires the lock for the stream.
177 *
178 * @returns IPRT status code.
179 * @param pStream The stream (valid).
180 */
181static int rtStrmAllocLock(PRTSTREAM pStream)
182{
183 Assert(pStream->pCritSect == NULL);
184
185 PRTCRITSECT pCritSect = (PRTCRITSECT)RTMemAlloc(sizeof(*pCritSect));
186 if (!pCritSect)
187 return VERR_NO_MEMORY;
188
189 /* The native stream lock are normally not recursive. */
190 int rc = RTCritSectInitEx(pCritSect, RTCRITSECT_FLAGS_NO_NESTING,
191 NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, "RTSemSpinMutex");
192 if (RT_SUCCESS(rc))
193 {
194 rc = RTCritSectEnter(pCritSect);
195 if (RT_SUCCESS(rc))
196 {
197 if (RT_LIKELY(ASMAtomicCmpXchgPtr(&pStream->pCritSect, pCritSect, NULL)))
198 return VINF_SUCCESS;
199
200 RTCritSectLeave(pCritSect);
201 }
202 RTCritSectDelete(pCritSect);
203 }
204 RTMemFree(pCritSect);
205
206 /* Handle the lost race case... */
207 pCritSect = ASMAtomicReadPtrT(&pStream->pCritSect, PRTCRITSECT);
208 if (pCritSect)
209 return RTCritSectEnter(pCritSect);
210
211 return rc;
212}
213#endif /* !HAVE_FWRITE_UNLOCKED */
214
215
216/**
217 * Locks the stream. May have to allocate the lock as well.
218 *
219 * @param pStream The stream (valid).
220 */
221DECLINLINE(void) rtStrmLock(PRTSTREAM pStream)
222{
223#ifdef HAVE_FWRITE_UNLOCKED
224 flockfile(pStream->pFile);
225#else
226 if (RT_LIKELY(pStream->pCritSect))
227 RTCritSectEnter(pStream->pCritSect);
228 else
229 rtStrmAllocLock(pStream);
230#endif
231}
232
233
234/**
235 * Unlocks the stream.
236 *
237 * @param pStream The stream (valid).
238 */
239DECLINLINE(void) rtStrmUnlock(PRTSTREAM pStream)
240{
241#ifdef HAVE_FWRITE_UNLOCKED
242 funlockfile(pStream->pFile);
243#else
244 if (RT_LIKELY(pStream->pCritSect))
245 RTCritSectLeave(pStream->pCritSect);
246#endif
247}
248
249
250/**
251 * Opens a file stream.
252 *
253 * @returns iprt status code.
254 * @param pszFilename Path to the file to open.
255 * @param pszMode The open mode. See fopen() standard.
256 * Format: <a|r|w>[+][b|t]
257 * @param ppStream Where to store the opened stream.
258 */
259RTR3DECL(int) RTStrmOpen(const char *pszFilename, const char *pszMode, PRTSTREAM *ppStream)
260{
261 /*
262 * Validate input.
263 */
264 if (!pszMode || !*pszMode)
265 {
266 AssertMsgFailed(("No pszMode!\n"));
267 return VERR_INVALID_PARAMETER;
268 }
269 if (!pszFilename)
270 {
271 AssertMsgFailed(("No pszFilename!\n"));
272 return VERR_INVALID_PARAMETER;
273 }
274 bool fOk = true;
275 bool fBinary = false;
276 switch (*pszMode)
277 {
278 case 'a':
279 case 'w':
280 case 'r':
281 switch (pszMode[1])
282 {
283 case '\0':
284 break;
285
286 case '+':
287 switch (pszMode[2])
288 {
289 case '\0':
290 break;
291
292 //case 't':
293 // break;
294
295 case 'b':
296 fBinary = true;
297 break;
298
299 default:
300 fOk = false;
301 break;
302 }
303 break;
304
305 //case 't':
306 // break;
307
308 case 'b':
309 fBinary = true;
310 break;
311
312 default:
313 fOk = false;
314 break;
315 }
316 break;
317 default:
318 fOk = false;
319 break;
320 }
321 if (!fOk)
322 {
323 AssertMsgFailed(("Invalid pszMode='%s', '<a|r|w>[+][b|t]'\n", pszMode));
324 return VINF_SUCCESS;
325 }
326
327 /*
328 * Allocate the stream handle and try open it.
329 */
330 PRTSTREAM pStream = (PRTSTREAM)RTMemAlloc(sizeof(*pStream));
331 if (pStream)
332 {
333 pStream->u32Magic = RTSTREAM_MAGIC;
334 pStream->i32Error = VINF_SUCCESS;
335 pStream->fCurrentCodeSet = false;
336 pStream->fBinary = fBinary;
337 pStream->fRecheckMode = false;
338#ifndef HAVE_FWRITE_UNLOCKED
339 pStream->pCritSect = NULL;
340#endif /* HAVE_FWRITE_UNLOCKED */
341 pStream->pFile = fopen(pszFilename, pszMode);
342 if (pStream->pFile)
343 {
344 *ppStream = pStream;
345 return VINF_SUCCESS;
346 }
347 RTMemFree(pStream);
348 return RTErrConvertFromErrno(errno);
349 }
350 return VERR_NO_MEMORY;
351}
352
353
354/**
355 * Opens a file stream.
356 *
357 * @returns iprt status code.
358 * @param pszMode The open mode. See fopen() standard.
359 * Format: <a|r|w>[+][b|t]
360 * @param ppStream Where to store the opened stream.
361 * @param pszFilenameFmt Filename path format string.
362 * @param args Arguments to the format string.
363 */
364RTR3DECL(int) RTStrmOpenFV(const char *pszMode, PRTSTREAM *ppStream, const char *pszFilenameFmt, va_list args)
365{
366 int rc;
367 char szFilename[RTPATH_MAX];
368 size_t cch = RTStrPrintfV(szFilename, sizeof(szFilename), pszFilenameFmt, args);
369 if (cch < sizeof(szFilename))
370 rc = RTStrmOpen(szFilename, pszMode, ppStream);
371 else
372 {
373 AssertMsgFailed(("The filename is too long cch=%d\n", cch));
374 rc = VERR_FILENAME_TOO_LONG;
375 }
376 return rc;
377}
378
379
380/**
381 * Opens a file stream.
382 *
383 * @returns iprt status code.
384 * @param pszMode The open mode. See fopen() standard.
385 * Format: <a|r|w>[+][b|t]
386 * @param ppStream Where to store the opened stream.
387 * @param pszFilenameFmt Filename path format string.
388 * @param ... Arguments to the format string.
389 */
390RTR3DECL(int) RTStrmOpenF(const char *pszMode, PRTSTREAM *ppStream, const char *pszFilenameFmt, ...)
391{
392 va_list args;
393 va_start(args, pszFilenameFmt);
394 int rc = RTStrmOpenFV(pszMode, ppStream, pszFilenameFmt, args);
395 va_end(args);
396 return rc;
397}
398
399
400/**
401 * Closes the specified stream.
402 *
403 * @returns iprt status code.
404 * @param pStream The stream to close.
405 */
406RTR3DECL(int) RTStrmClose(PRTSTREAM pStream)
407{
408 if (!pStream)
409 return VINF_SUCCESS;
410 AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER);
411
412 if (!fclose(pStream->pFile))
413 {
414 pStream->u32Magic = 0xdeaddead;
415 pStream->pFile = NULL;
416#ifndef HAVE_FWRITE_UNLOCKED
417 if (pStream->pCritSect)
418 {
419 RTCritSectEnter(pStream->pCritSect);
420 RTCritSectLeave(pStream->pCritSect);
421 RTCritSectDelete(pStream->pCritSect);
422 RTMemFree(pStream->pCritSect);
423 pStream->pCritSect = NULL;
424 }
425#endif
426 RTMemFree(pStream);
427 return VINF_SUCCESS;
428 }
429
430 return RTErrConvertFromErrno(errno);
431}
432
433
434/**
435 * Get the pending error of the stream.
436 *
437 * @returns iprt status code. of the stream.
438 * @param pStream The stream.
439 */
440RTR3DECL(int) RTStrmError(PRTSTREAM pStream)
441{
442 AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER);
443 return pStream->i32Error;
444}
445
446
447/**
448 * Clears stream error condition.
449 *
450 * All stream operations save RTStrmClose and this will fail
451 * while an error is asserted on the stream
452 *
453 * @returns iprt status code.
454 * @param pStream The stream.
455 */
456RTR3DECL(int) RTStrmClearError(PRTSTREAM pStream)
457{
458 AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER);
459
460 clearerr(pStream->pFile);
461 ASMAtomicWriteS32(&pStream->i32Error, VINF_SUCCESS);
462 return VINF_SUCCESS;
463}
464
465
466RTR3DECL(int) RTStrmSetMode(PRTSTREAM pStream, int fBinary, int fCurrentCodeSet)
467{
468 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
469 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
470 AssertReturn((unsigned)(fBinary + 1) <= 2, VERR_INVALID_PARAMETER);
471 AssertReturn((unsigned)(fCurrentCodeSet + 1) <= 2, VERR_INVALID_PARAMETER);
472
473 rtStrmLock(pStream);
474
475 if (fBinary != -1)
476 {
477 pStream->fBinary = RT_BOOL(fBinary);
478 pStream->fRecheckMode = true;
479 }
480
481 if (fCurrentCodeSet != -1)
482 pStream->fCurrentCodeSet = RT_BOOL(fCurrentCodeSet);
483
484 rtStrmUnlock(pStream);
485
486 return VINF_SUCCESS;
487}
488
489
490RTR3DECL(int) RTStrmInputGetEchoChars(PRTSTREAM pStream, bool *pfEchoChars)
491{
492 int rc = VINF_SUCCESS;
493
494 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
495 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
496 AssertPtrReturn(pfEchoChars, VERR_INVALID_POINTER);
497
498 int fh = fileno(pStream->pFile);
499 if (isatty(fh))
500 {
501#ifdef RT_OS_WINDOWS
502 DWORD dwMode;
503 HANDLE hCon = (HANDLE)_get_osfhandle(fh);
504 if (GetConsoleMode(hCon, &dwMode))
505 *pfEchoChars = RT_BOOL(dwMode & ENABLE_ECHO_INPUT);
506 else
507 rc = RTErrConvertFromWin32(GetLastError());
508#else
509 struct termios Termios;
510
511 int rcPosix = tcgetattr(fh, &Termios);
512 if (!rcPosix)
513 *pfEchoChars = RT_BOOL(Termios.c_lflag & ECHO);
514 else
515 rc = RTErrConvertFromErrno(errno);
516#endif
517 }
518 else
519 rc = VERR_INVALID_HANDLE;
520
521 return rc;
522}
523
524
525RTR3DECL(int) RTStrmInputSetEchoChars(PRTSTREAM pStream, bool fEchoChars)
526{
527 int rc = VINF_SUCCESS;
528
529 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
530 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
531
532 int fh = fileno(pStream->pFile);
533 if (isatty(fh))
534 {
535#ifdef RT_OS_WINDOWS
536 DWORD dwMode;
537 HANDLE hCon = (HANDLE)_get_osfhandle(fh);
538 if (GetConsoleMode(hCon, &dwMode))
539 {
540 if (fEchoChars)
541 dwMode |= ENABLE_ECHO_INPUT;
542 else
543 dwMode &= ~ENABLE_ECHO_INPUT;
544 if (!SetConsoleMode(hCon, dwMode))
545 rc = RTErrConvertFromWin32(GetLastError());
546 }
547 else
548 rc = RTErrConvertFromWin32(GetLastError());
549#else
550 struct termios Termios;
551
552 int rcPosix = tcgetattr(fh, &Termios);
553 if (!rcPosix)
554 {
555 if (fEchoChars)
556 Termios.c_lflag |= ECHO;
557 else
558 Termios.c_lflag &= ~ECHO;
559
560 rcPosix = tcsetattr(fh, TCSAFLUSH, &Termios);
561 if (rcPosix != 0)
562 rc = RTErrConvertFromErrno(errno);
563 }
564 else
565 rc = RTErrConvertFromErrno(errno);
566#endif
567 }
568 else
569 rc = VERR_INVALID_HANDLE;
570
571 return rc;
572}
573
574
575RTR3DECL(bool) RTStrmIsTerminal(PRTSTREAM pStream)
576{
577 AssertPtrReturn(pStream, false);
578 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, false);
579
580 if (pStream->pFile)
581 {
582 int fh = fileno(pStream->pFile);
583 if (isatty(fh))
584 {
585#ifdef RT_OS_WINDOWS
586 DWORD dwMode;
587 HANDLE hCon = (HANDLE)_get_osfhandle(fh);
588 if (GetConsoleMode(hCon, &dwMode))
589 return true;
590#else
591 return true;
592#endif
593 }
594 }
595 return false;
596}
597
598
599RTR3DECL(int) RTStrmQueryTerminalWidth(PRTSTREAM pStream, uint32_t *pcchWidth)
600{
601 AssertPtrReturn(pcchWidth, VERR_INVALID_HANDLE);
602 *pcchWidth = 80;
603
604 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
605 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
606
607 if (pStream->pFile)
608 {
609 int fh = fileno(pStream->pFile);
610 if (isatty(fh))
611 {
612#ifdef RT_OS_WINDOWS
613 CONSOLE_SCREEN_BUFFER_INFO Info;
614 HANDLE hCon = (HANDLE)_get_osfhandle(fh);
615 RT_ZERO(Info);
616 if (GetConsoleScreenBufferInfo(hCon, &Info))
617 {
618 *pcchWidth = Info.dwSize.X ? Info.dwSize.X : 80;
619 return VINF_SUCCESS;
620 }
621 return RTErrConvertFromWin32(GetLastError());
622
623#elif defined(TIOCGWINSZ) || !defined(RT_OS_OS2) /* only OS/2 should currently miss this */
624 struct winsize Info;
625 RT_ZERO(Info);
626 int rc = ioctl(fh, TIOCGWINSZ, &Info);
627 if (rc >= 0)
628 {
629 *pcchWidth = Info.ws_col ? Info.ws_col : 80;
630 return VINF_SUCCESS;
631 }
632 return RTErrConvertFromErrno(errno);
633#endif
634 }
635 }
636 return VERR_INVALID_FUNCTION;
637}
638
639
640
641/**
642 * Rewinds the stream.
643 *
644 * Stream errors will be reset on success.
645 *
646 * @returns IPRT status code.
647 *
648 * @param pStream The stream.
649 *
650 * @remarks Not all streams are rewindable and that behavior is currently
651 * undefined for those.
652 */
653RTR3DECL(int) RTStrmRewind(PRTSTREAM pStream)
654{
655 AssertPtrReturn(pStream, VERR_INVALID_HANDLE);
656 AssertReturn(pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_HANDLE);
657
658 int rc;
659 clearerr(pStream->pFile);
660 errno = 0;
661 if (!fseek(pStream->pFile, 0, SEEK_SET))
662 {
663 ASMAtomicWriteS32(&pStream->i32Error, VINF_SUCCESS);
664 rc = VINF_SUCCESS;
665 }
666 else
667 {
668 rc = RTErrConvertFromErrno(errno);
669 ASMAtomicWriteS32(&pStream->i32Error, rc);
670 }
671
672 return rc;
673}
674
675
676/**
677 * Recheck the stream mode.
678 *
679 * @param pStream The stream (locked).
680 */
681static void rtStreamRecheckMode(PRTSTREAM pStream)
682{
683#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
684 int fh = fileno(pStream->pFile);
685 if (fh >= 0)
686 {
687 int fExpected = pStream->fBinary ? _O_BINARY : _O_TEXT;
688 int fActual = _setmode(fh, fExpected);
689 if (fActual != -1 && fExpected != (fActual & (_O_BINARY | _O_TEXT)))
690 {
691 fActual = _setmode(fh, fActual & (_O_BINARY | _O_TEXT));
692 pStream->fBinary = !(fActual & _O_TEXT);
693 }
694 }
695#else
696 NOREF(pStream);
697#endif
698 pStream->fRecheckMode = false;
699}
700
701
702/**
703 * Reads from a file stream.
704 *
705 * @returns iprt status code.
706 * @param pStream The stream.
707 * @param pvBuf Where to put the read bits.
708 * Must be cbRead bytes or more.
709 * @param cbRead Number of bytes to read.
710 * @param pcbRead Where to store the number of bytes actually read.
711 * If NULL cbRead bytes are read or an error is returned.
712 */
713RTR3DECL(int) RTStrmReadEx(PRTSTREAM pStream, void *pvBuf, size_t cbRead, size_t *pcbRead)
714{
715 AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER);
716
717 int rc = pStream->i32Error;
718 if (RT_SUCCESS(rc))
719 {
720 if (pStream->fRecheckMode)
721 rtStreamRecheckMode(pStream);
722
723 if (pcbRead)
724 {
725 /*
726 * Can do with a partial read.
727 */
728 *pcbRead = fread(pvBuf, 1, cbRead, pStream->pFile);
729 if ( *pcbRead == cbRead
730 || !ferror(pStream->pFile))
731 return VINF_SUCCESS;
732 if (feof(pStream->pFile))
733 {
734 if (*pcbRead)
735 return VINF_EOF;
736 rc = VERR_EOF;
737 }
738 else if (ferror(pStream->pFile))
739 rc = VERR_READ_ERROR;
740 else
741 {
742 AssertMsgFailed(("This shouldn't happen\n"));
743 rc = VERR_INTERNAL_ERROR;
744 }
745 }
746 else
747 {
748 /*
749 * Must read it all!
750 */
751 if (fread(pvBuf, cbRead, 1, pStream->pFile) == 1)
752 return VINF_SUCCESS;
753
754 /* possible error/eof. */
755 if (feof(pStream->pFile))
756 rc = VERR_EOF;
757 else if (ferror(pStream->pFile))
758 rc = VERR_READ_ERROR;
759 else
760 {
761 AssertMsgFailed(("This shouldn't happen\n"));
762 rc = VERR_INTERNAL_ERROR;
763 }
764 }
765 ASMAtomicWriteS32(&pStream->i32Error, rc);
766 }
767 return rc;
768}
769
770
771/**
772 * Check if the input text is valid UTF-8.
773 *
774 * @returns true/false.
775 * @param pvBuf Pointer to the buffer.
776 * @param cbBuf Size of the buffer.
777 */
778static bool rtStrmIsUtf8Text(const void *pvBuf, size_t cbBuf)
779{
780 NOREF(pvBuf);
781 NOREF(cbBuf);
782 /** @todo not sure this is a good idea... Better redefine RTStrmWrite. */
783 return false;
784}
785
786
787#ifdef RT_OS_WINDOWS
788/**
789 * Check if the stream is for a Window console.
790 *
791 * @returns true / false.
792 * @param pStream The stream.
793 * @param phCon Where to return the console handle.
794 */
795static bool rtStrmIsConsoleUnlocked(PRTSTREAM pStream, HANDLE *phCon)
796{
797 int fh = fileno(pStream->pFile);
798 if (isatty(fh))
799 {
800 DWORD dwMode;
801 HANDLE hCon = (HANDLE)_get_osfhandle(fh);
802 if (GetConsoleMode(hCon, &dwMode))
803 {
804 *phCon = hCon;
805 return true;
806 }
807 }
808 return false;
809}
810#endif /* RT_OS_WINDOWS */
811
812
813/**
814 * Internal write API, stream lock already held.
815 *
816 * @returns IPRT status code.
817 * @param pStream The stream.
818 * @param pvBuf What to write.
819 * @param cbWrite How much to write.
820 * @param pcbWritten Where to optionally return the number of bytes
821 * written.
822 * @param fSureIsText Set if we're sure this is UTF-8 text already.
823 */
824static int rtStrmWriteLocked(PRTSTREAM pStream, const void *pvBuf, size_t cbWrite, size_t *pcbWritten,
825 bool fSureIsText)
826{
827 int rc = pStream->i32Error;
828 if (RT_FAILURE(rc))
829 return rc;
830 if (pStream->fRecheckMode)
831 rtStreamRecheckMode(pStream);
832
833#ifdef RT_OS_WINDOWS
834 /*
835 * Use the unicode console API when possible in order to avoid stuff
836 * getting lost in unnecessary code page translations.
837 */
838 HANDLE hCon;
839 if (rtStrmIsConsoleUnlocked(pStream, &hCon))
840 {
841# ifdef HAVE_FWRITE_UNLOCKED
842 if (!fflush_unlocked(pStream->pFile))
843# else
844 if (!fflush(pStream->pFile))
845# endif
846 {
847 /** @todo Consider buffering later. For now, we'd rather correct output than
848 * fast output. */
849 DWORD cwcWritten = 0;
850 PRTUTF16 pwszSrc = NULL;
851 size_t cwcSrc = 0;
852 rc = RTStrToUtf16Ex((const char *)pvBuf, cbWrite, &pwszSrc, 0, &cwcSrc);
853 if (RT_SUCCESS(rc))
854 {
855 if (!WriteConsoleW(hCon, pwszSrc, (DWORD)cwcSrc, &cwcWritten, NULL))
856 {
857 /* try write char-by-char to avoid heap problem. */
858 cwcWritten = 0;
859 while (cwcWritten != cwcSrc)
860 {
861 DWORD cwcThis;
862 if (!WriteConsoleW(hCon, &pwszSrc[cwcWritten], 1, &cwcThis, NULL))
863 {
864 if (!pcbWritten || cwcWritten == 0)
865 rc = RTErrConvertFromErrno(GetLastError());
866 break;
867 }
868 if (cwcThis != 1) /* Unable to write current char (amount)? */
869 break;
870 cwcWritten++;
871 }
872 }
873 if (RT_SUCCESS(rc))
874 {
875 if (cwcWritten == cwcSrc)
876 {
877 if (pcbWritten)
878 *pcbWritten = cbWrite;
879 }
880 else if (pcbWritten)
881 {
882 PCRTUTF16 pwszCur = pwszSrc;
883 const char *pszCur = (const char *)pvBuf;
884 while ((uintptr_t)(pwszCur - pwszSrc) < cwcWritten)
885 {
886 RTUNICP CpIgnored;
887 RTUtf16GetCpEx(&pwszCur, &CpIgnored);
888 RTStrGetCpEx(&pszCur, &CpIgnored);
889 }
890 *pcbWritten = pszCur - (const char *)pvBuf;
891 }
892 else
893 rc = VERR_WRITE_ERROR;
894 }
895 RTUtf16Free(pwszSrc);
896 }
897 }
898 else
899 rc = RTErrConvertFromErrno(errno);
900 if (RT_FAILURE(rc))
901 ASMAtomicWriteS32(&pStream->i32Error, rc);
902 return rc;
903 }
904#endif /* RT_OS_WINDOWS */
905
906 /*
907 * If we're sure it's text output, convert it from UTF-8 to the current
908 * code page before printing it.
909 *
910 * Note! Partial writes are not supported in this scenario because we
911 * cannot easily report back a written length matching the input.
912 */
913 /** @todo Skip this if the current code set is UTF-8. */
914 if ( pStream->fCurrentCodeSet
915 && !pStream->fBinary
916 && ( fSureIsText
917 || rtStrmIsUtf8Text(pvBuf, cbWrite))
918 )
919 {
920 char *pszSrcFree = NULL;
921 const char *pszSrc = (const char *)pvBuf;
922 if (pszSrc[cbWrite - 1])
923 {
924 pszSrc = pszSrcFree = RTStrDupN(pszSrc, cbWrite);
925 if (pszSrc == NULL)
926 rc = VERR_NO_STR_MEMORY;
927 }
928 if (RT_SUCCESS(rc))
929 {
930 char *pszSrcCurCP;
931 rc = RTStrUtf8ToCurrentCP(&pszSrcCurCP, pszSrc);
932 if (RT_SUCCESS(rc))
933 {
934 size_t cchSrcCurCP = strlen(pszSrcCurCP);
935 IPRT_ALIGNMENT_CHECKS_DISABLE(); /* glibc / mempcpy again */
936#ifdef HAVE_FWRITE_UNLOCKED
937 ssize_t cbWritten = fwrite_unlocked(pszSrcCurCP, cchSrcCurCP, 1, pStream->pFile);
938#else
939 ssize_t cbWritten = fwrite(pszSrcCurCP, cchSrcCurCP, 1, pStream->pFile);
940#endif
941 IPRT_ALIGNMENT_CHECKS_ENABLE();
942 if (cbWritten == 1)
943 {
944 if (pcbWritten)
945 *pcbWritten = cbWrite;
946 }
947#ifdef HAVE_FWRITE_UNLOCKED
948 else if (!ferror_unlocked(pStream->pFile))
949#else
950 else if (!ferror(pStream->pFile))
951#endif
952 {
953 if (pcbWritten)
954 *pcbWritten = 0;
955 }
956 else
957 rc = VERR_WRITE_ERROR;
958 RTStrFree(pszSrcCurCP);
959 }
960 RTStrFree(pszSrcFree);
961 }
962
963 if (RT_FAILURE(rc))
964 ASMAtomicWriteS32(&pStream->i32Error, rc);
965 return rc;
966 }
967
968 /*
969 * Otherwise, just write it as-is.
970 */
971 if (pcbWritten)
972 {
973 IPRT_ALIGNMENT_CHECKS_DISABLE(); /* glibc / mempcpy again */
974#ifdef HAVE_FWRITE_UNLOCKED
975 *pcbWritten = fwrite_unlocked(pvBuf, 1, cbWrite, pStream->pFile);
976#else
977 *pcbWritten = fwrite(pvBuf, 1, cbWrite, pStream->pFile);
978#endif
979 IPRT_ALIGNMENT_CHECKS_ENABLE();
980 if ( *pcbWritten == cbWrite
981#ifdef HAVE_FWRITE_UNLOCKED
982 || !ferror_unlocked(pStream->pFile))
983#else
984 || !ferror(pStream->pFile))
985#endif
986 return VINF_SUCCESS;
987 rc = VERR_WRITE_ERROR;
988 }
989 else
990 {
991 /* Must write it all! */
992 IPRT_ALIGNMENT_CHECKS_DISABLE(); /* glibc / mempcpy again */
993#ifdef HAVE_FWRITE_UNLOCKED
994 size_t cbWritten = fwrite_unlocked(pvBuf, cbWrite, 1, pStream->pFile);
995#else
996 size_t cbWritten = fwrite(pvBuf, cbWrite, 1, pStream->pFile);
997#endif
998 IPRT_ALIGNMENT_CHECKS_ENABLE();
999 if (cbWritten == 1)
1000 return VINF_SUCCESS;
1001#ifdef HAVE_FWRITE_UNLOCKED
1002 if (!ferror_unlocked(pStream->pFile))
1003#else
1004 if (!ferror(pStream->pFile))
1005#endif
1006 return VINF_SUCCESS; /* WEIRD! But anyway... */
1007
1008 rc = VERR_WRITE_ERROR;
1009 }
1010 ASMAtomicWriteS32(&pStream->i32Error, rc);
1011
1012 return rc;
1013}
1014
1015
1016/**
1017 * Internal write API.
1018 *
1019 * @returns IPRT status code.
1020 * @param pStream The stream.
1021 * @param pvBuf What to write.
1022 * @param cbWrite How much to write.
1023 * @param pcbWritten Where to optionally return the number of bytes
1024 * written.
1025 * @param fSureIsText Set if we're sure this is UTF-8 text already.
1026 */
1027static int rtStrmWrite(PRTSTREAM pStream, const void *pvBuf, size_t cbWrite, size_t *pcbWritten, bool fSureIsText)
1028{
1029 rtStrmLock(pStream);
1030 int rc = rtStrmWriteLocked(pStream, pvBuf, cbWrite, pcbWritten, fSureIsText);
1031 rtStrmUnlock(pStream);
1032 return rc;
1033}
1034
1035
1036/**
1037 * Writes to a file stream.
1038 *
1039 * @returns iprt status code.
1040 * @param pStream The stream.
1041 * @param pvBuf Where to get the bits to write from.
1042 * @param cbWrite Number of bytes to write.
1043 * @param pcbWritten Where to store the number of bytes actually written.
1044 * If NULL cbWrite bytes are written or an error is returned.
1045 */
1046RTR3DECL(int) RTStrmWriteEx(PRTSTREAM pStream, const void *pvBuf, size_t cbWrite, size_t *pcbWritten)
1047{
1048 AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER);
1049 return rtStrmWrite(pStream, pvBuf, cbWrite, pcbWritten, false);
1050}
1051
1052
1053/**
1054 * Reads a character from a file stream.
1055 *
1056 * @returns The char as an unsigned char cast to int.
1057 * @returns -1 on failure.
1058 * @param pStream The stream.
1059 */
1060RTR3DECL(int) RTStrmGetCh(PRTSTREAM pStream)
1061{
1062 unsigned char ch;
1063 int rc = RTStrmReadEx(pStream, &ch, 1, NULL);
1064 if (RT_SUCCESS(rc))
1065 return ch;
1066 return -1;
1067}
1068
1069
1070/**
1071 * Writes a character to a file stream.
1072 *
1073 * @returns iprt status code.
1074 * @param pStream The stream.
1075 * @param ch The char to write.
1076 */
1077RTR3DECL(int) RTStrmPutCh(PRTSTREAM pStream, int ch)
1078{
1079 return rtStrmWrite(pStream, &ch, 1, NULL, true /*fSureIsText*/);
1080}
1081
1082
1083/**
1084 * Writes a string to a file stream.
1085 *
1086 * @returns iprt status code.
1087 * @param pStream The stream.
1088 * @param pszString The string to write.
1089 * No newlines or anything is appended or prepended.
1090 * The terminating '\\0' is not written, of course.
1091 */
1092RTR3DECL(int) RTStrmPutStr(PRTSTREAM pStream, const char *pszString)
1093{
1094 size_t cch = strlen(pszString);
1095 return rtStrmWrite(pStream, pszString, cch, NULL, true /*fSureIsText*/);
1096}
1097
1098
1099RTR3DECL(int) RTStrmGetLine(PRTSTREAM pStream, char *pszString, size_t cbString)
1100{
1101 AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER);
1102 int rc;
1103 if (pszString && cbString > 1)
1104 {
1105 rc = pStream->i32Error;
1106 if (RT_SUCCESS(rc))
1107 {
1108 cbString--; /* save space for the terminator. */
1109 rtStrmLock(pStream);
1110 for (;;)
1111 {
1112#ifdef HAVE_FWRITE_UNLOCKED /** @todo darwin + freebsd(?) has fgetc_unlocked but not fwrite_unlocked, optimize... */
1113 int ch = fgetc_unlocked(pStream->pFile);
1114#else
1115 int ch = fgetc(pStream->pFile);
1116#endif
1117
1118 /* Deal with \r\n sequences here. We'll return lone CR, but
1119 treat CRLF as LF. */
1120 if (ch == '\r')
1121 {
1122#ifdef HAVE_FWRITE_UNLOCKED /** @todo darwin + freebsd(?) has fgetc_unlocked but not fwrite_unlocked, optimize... */
1123 ch = fgetc_unlocked(pStream->pFile);
1124#else
1125 ch = fgetc(pStream->pFile);
1126#endif
1127 if (ch == '\n')
1128 break;
1129
1130 *pszString++ = '\r';
1131 if (--cbString <= 0)
1132 {
1133 /* yeah, this is an error, we dropped a character. */
1134 rc = VERR_BUFFER_OVERFLOW;
1135 break;
1136 }
1137 }
1138
1139 /* Deal with end of file. */
1140 if (ch == EOF)
1141 {
1142#ifdef HAVE_FWRITE_UNLOCKED
1143 if (feof_unlocked(pStream->pFile))
1144#else
1145 if (feof(pStream->pFile))
1146#endif
1147 {
1148 rc = VERR_EOF;
1149 break;
1150 }
1151#ifdef HAVE_FWRITE_UNLOCKED
1152 if (ferror_unlocked(pStream->pFile))
1153#else
1154 if (ferror(pStream->pFile))
1155#endif
1156 rc = VERR_READ_ERROR;
1157 else
1158 {
1159 AssertMsgFailed(("This shouldn't happen\n"));
1160 rc = VERR_INTERNAL_ERROR;
1161 }
1162 break;
1163 }
1164
1165 /* Deal with null terminator and (lone) new line. */
1166 if (ch == '\0' || ch == '\n')
1167 break;
1168
1169 /* No special character, append it to the return string. */
1170 *pszString++ = ch;
1171 if (--cbString <= 0)
1172 {
1173 rc = VINF_BUFFER_OVERFLOW;
1174 break;
1175 }
1176 }
1177 rtStrmUnlock(pStream);
1178
1179 *pszString = '\0';
1180 if (RT_FAILURE(rc))
1181 ASMAtomicWriteS32(&pStream->i32Error, rc);
1182 }
1183 }
1184 else
1185 {
1186 AssertMsgFailed(("no buffer or too small buffer!\n"));
1187 rc = VERR_INVALID_PARAMETER;
1188 }
1189 return rc;
1190}
1191
1192
1193/**
1194 * Flushes a stream.
1195 *
1196 * @returns iprt status code.
1197 * @param pStream The stream to flush.
1198 */
1199RTR3DECL(int) RTStrmFlush(PRTSTREAM pStream)
1200{
1201 if (!fflush(pStream->pFile))
1202 return VINF_SUCCESS;
1203 return RTErrConvertFromErrno(errno);
1204}
1205
1206
1207/**
1208 * Output callback.
1209 *
1210 * @returns number of bytes written.
1211 * @param pvArg User argument.
1212 * @param pachChars Pointer to an array of utf-8 characters.
1213 * @param cchChars Number of bytes in the character array pointed to by pachChars.
1214 */
1215static DECLCALLBACK(size_t) rtstrmOutput(void *pvArg, const char *pachChars, size_t cchChars)
1216{
1217 if (cchChars)
1218 rtStrmWriteLocked((PRTSTREAM)pvArg, pachChars, cchChars, NULL, true /*fSureIsText*/);
1219 /* else: ignore termination call. */
1220 return cchChars;
1221}
1222
1223
1224/**
1225 * Prints a formatted string to the specified stream.
1226 *
1227 * @returns Number of bytes printed.
1228 * @param pStream The stream to print to.
1229 * @param pszFormat IPRT format string.
1230 * @param args Arguments specified by pszFormat.
1231 */
1232RTR3DECL(int) RTStrmPrintfV(PRTSTREAM pStream, const char *pszFormat, va_list args)
1233{
1234 AssertReturn(RT_VALID_PTR(pStream) && pStream->u32Magic == RTSTREAM_MAGIC, VERR_INVALID_PARAMETER);
1235 int rc = pStream->i32Error;
1236 if (RT_SUCCESS(rc))
1237 {
1238 rtStrmLock(pStream);
1239// pStream->fShouldFlush = true;
1240 rc = (int)RTStrFormatV(rtstrmOutput, pStream, NULL, NULL, pszFormat, args);
1241 rtStrmUnlock(pStream);
1242 Assert(rc >= 0);
1243 }
1244 else
1245 rc = -1;
1246 return rc;
1247}
1248
1249
1250/**
1251 * Prints a formatted string to the specified stream.
1252 *
1253 * @returns Number of bytes printed.
1254 * @param pStream The stream to print to.
1255 * @param pszFormat IPRT format string.
1256 * @param ... Arguments specified by pszFormat.
1257 */
1258RTR3DECL(int) RTStrmPrintf(PRTSTREAM pStream, const char *pszFormat, ...)
1259{
1260 va_list args;
1261 va_start(args, pszFormat);
1262 int rc = RTStrmPrintfV(pStream, pszFormat, args);
1263 va_end(args);
1264 return rc;
1265}
1266
1267
1268/**
1269 * Dumper vprintf-like function outputting to a stream.
1270 *
1271 * @param pvUser The stream to print to. NULL means standard output.
1272 * @param pszFormat Runtime format string.
1273 * @param va Arguments specified by pszFormat.
1274 */
1275RTDECL(void) RTStrmDumpPrintfV(void *pvUser, const char *pszFormat, va_list va)
1276{
1277 RTStrmPrintfV(pvUser ? (PRTSTREAM)pvUser : g_pStdOut, pszFormat, va);
1278}
1279
1280
1281/**
1282 * Prints a formatted string to the standard output stream (g_pStdOut).
1283 *
1284 * @returns Number of bytes printed.
1285 * @param pszFormat IPRT format string.
1286 * @param args Arguments specified by pszFormat.
1287 */
1288RTR3DECL(int) RTPrintfV(const char *pszFormat, va_list args)
1289{
1290 return RTStrmPrintfV(g_pStdOut, pszFormat, args);
1291}
1292
1293
1294/**
1295 * Prints a formatted string to the standard output stream (g_pStdOut).
1296 *
1297 * @returns Number of bytes printed.
1298 * @param pszFormat IPRT format string.
1299 * @param ... Arguments specified by pszFormat.
1300 */
1301RTR3DECL(int) RTPrintf(const char *pszFormat, ...)
1302{
1303 va_list args;
1304 va_start(args, pszFormat);
1305 int rc = RTStrmPrintfV(g_pStdOut, pszFormat, args);
1306 va_end(args);
1307 return rc;
1308}
1309
1310
1311/**
1312 * Outputs @a cchIndent spaces.
1313 */
1314static void rtStrmWrapppedIndent(RTSTRMWRAPPEDSTATE *pState, uint32_t cchIndent)
1315{
1316 static const char s_szSpaces[] = " ";
1317 while (cchIndent)
1318 {
1319 uint32_t cchToWrite = RT_MIN(cchIndent, sizeof(s_szSpaces) - 1);
1320 int rc = RTStrmWrite(pState->pStream, s_szSpaces, cchToWrite);
1321 if (RT_SUCCESS(rc))
1322 cchIndent -= cchToWrite;
1323 else
1324 {
1325 pState->rcStatus = rc;
1326 break;
1327 }
1328 }
1329}
1330
1331
1332/**
1333 * Flushes the current line.
1334 *
1335 * @param pState The wrapped output state.
1336 * @param fPartial Set if partial flush due to buffer overflow, clear when
1337 * flushing due to '\n'.
1338 */
1339static void rtStrmWrappedFlushLine(RTSTRMWRAPPEDSTATE *pState, bool fPartial)
1340{
1341 /*
1342 * Check indentation in case we need to split the line later.
1343 */
1344 uint32_t cchIndent = pState->cchIndent;
1345 if (cchIndent == UINT32_MAX)
1346 {
1347 pState->cchIndent = 0;
1348 cchIndent = pState->cchHangingIndent;
1349 while (RT_C_IS_BLANK(pState->szLine[cchIndent]))
1350 cchIndent++;
1351 }
1352
1353 /*
1354 * Do the flushing.
1355 */
1356 uint32_t cchLine = pState->cchLine;
1357 Assert(cchLine < sizeof(pState->szLine));
1358 while (cchLine >= pState->cchWidth || !fPartial)
1359 {
1360 /*
1361 * Hopefully we don't need to do any wrapping ...
1362 */
1363 uint32_t offSplit;
1364 if (pState->cchIndent + cchLine <= pState->cchWidth)
1365 {
1366 if (!fPartial)
1367 {
1368 rtStrmWrapppedIndent(pState, pState->cchIndent);
1369 pState->szLine[cchLine] = '\n';
1370 int rc = RTStrmWrite(pState->pStream, pState->szLine, cchLine + 1);
1371 if (RT_FAILURE(rc))
1372 pState->rcStatus = rc;
1373 pState->cLines += 1;
1374 pState->cchLine = 0;
1375 pState->cchIndent = UINT32_MAX;
1376 return;
1377 }
1378
1379 /*
1380 * ... no such luck.
1381 */
1382 offSplit = cchLine;
1383 }
1384 else
1385 offSplit = pState->cchWidth - pState->cchIndent;
1386
1387 /* Find the start of the current word: */
1388 while (offSplit > 0 && !RT_C_IS_BLANK(pState->szLine[offSplit - 1]))
1389 offSplit--;
1390
1391 /* Skip spaces. */
1392 while (offSplit > 0 && RT_C_IS_BLANK(pState->szLine[offSplit - 1]))
1393 offSplit--;
1394 uint32_t offNextLine = offSplit;
1395
1396 /* If the first word + indent is wider than the screen width, so just output it in full. */
1397 if (offSplit == 0) /** @todo Split words, look for hyphen... This code is currently a bit crude. */
1398 {
1399 while (offSplit < cchLine && !RT_C_IS_BLANK(pState->szLine[offSplit]))
1400 offSplit++;
1401 offNextLine = offSplit;
1402 }
1403
1404 while (offNextLine < cchLine && RT_C_IS_BLANK(pState->szLine[offNextLine]))
1405 offNextLine++;
1406
1407 /*
1408 * Output and advance.
1409 */
1410 rtStrmWrapppedIndent(pState, pState->cchIndent);
1411 int rc = RTStrmWrite(pState->pStream, pState->szLine, offSplit);
1412 if (RT_SUCCESS(rc))
1413 rc = RTStrmPutCh(pState->pStream, '\n');
1414 if (RT_FAILURE(rc))
1415 pState->rcStatus = rc;
1416
1417 cchLine -= offNextLine;
1418 pState->cchLine = cchLine;
1419 pState->cLines += 1;
1420 pState->cchIndent = cchIndent;
1421 memmove(&pState->szLine[0], &pState->szLine[offNextLine], cchLine);
1422 }
1423
1424 /* The indentation level is reset for each '\n' we process, so only save cchIndent if partial. */
1425 pState->cchIndent = fPartial ? cchIndent : UINT32_MAX;
1426}
1427
1428
1429/**
1430 * @callback_method_impl{FNRTSTROUTPUT}
1431 */
1432static DECLCALLBACK(size_t) rtStrmWrappedOutput(void *pvArg, const char *pachChars, size_t cbChars)
1433{
1434 RTSTRMWRAPPEDSTATE *pState = (RTSTRMWRAPPEDSTATE *)pvArg;
1435 size_t const cchRet = cbChars;
1436 while (cbChars > 0)
1437 {
1438 if (*pachChars == '\n')
1439 {
1440 rtStrmWrappedFlushLine(pState, false /*fPartial*/);
1441 pachChars++;
1442 cbChars--;
1443 }
1444 else
1445 {
1446 const char *pszEol = (const char *)memchr(pachChars, '\n', cbChars);
1447 size_t cchToCopy = pszEol ? (size_t)(pszEol - pachChars) : cbChars;
1448 uint32_t cchLine = pState->cchLine;
1449 Assert(cchLine < sizeof(pState->szLine));
1450 bool const fFlush = cchLine + cchToCopy >= sizeof(pState->szLine);
1451 if (fFlush)
1452 cchToCopy = cchToCopy - sizeof(pState->szLine) - 1;
1453
1454 pState->cchLine = cchLine + (uint32_t)cchToCopy;
1455 memcpy(&pState->szLine[cchLine], pachChars, cchToCopy);
1456
1457 pachChars += cchToCopy;
1458 cbChars -= cchToCopy;
1459
1460 if (fFlush)
1461 rtStrmWrappedFlushLine(pState, true /*fPartial*/);
1462 }
1463 }
1464 return cchRet;
1465}
1466
1467
1468RTDECL(int32_t) RTStrmWrappedPrintfV(PRTSTREAM pStream, uint32_t fFlags, const char *pszFormat, va_list va)
1469{
1470 /*
1471 * Figure the output width and set up the rest of the output state.
1472 */
1473 RTSTRMWRAPPEDSTATE State;
1474 State.pStream = pStream;
1475 State.cchLine = fFlags & RTSTRMWRAPPED_F_LINE_OFFSET_MASK;
1476 State.cLines = 0;
1477 State.rcStatus = VINF_SUCCESS;
1478 State.cchIndent = UINT32_MAX;
1479 State.cchHangingIndent = 0;
1480 if (fFlags & RTSTRMWRAPPED_F_HANGING_INDENT)
1481 {
1482 State.cchHangingIndent = (fFlags & RTSTRMWRAPPED_F_HANGING_INDENT_MASK) >> RTSTRMWRAPPED_F_HANGING_INDENT_SHIFT;
1483 if (!State.cchHangingIndent)
1484 State.cchHangingIndent = 4;
1485 }
1486
1487 int rc = RTStrmQueryTerminalWidth(pStream, &State.cchWidth);
1488 if (RT_SUCCESS(rc))
1489 State.cchWidth = RT_MIN(State.cchWidth, RTSTRMWRAPPED_F_LINE_OFFSET_MASK + 1);
1490 else
1491 {
1492 State.cchWidth = (uint32_t)fFlags & RTSTRMWRAPPED_F_NON_TERMINAL_WIDTH_MASK;
1493 if (!State.cchWidth)
1494 State.cchWidth = 80;
1495 }
1496 if (State.cchWidth < 32)
1497 State.cchWidth = 32;
1498 //State.cchWidth -= 1; /* necessary here? */
1499
1500 /*
1501 * Do the formatting.
1502 */
1503 RTStrFormatV(rtStrmWrappedOutput, &State, NULL, NULL, pszFormat, va);
1504
1505 /*
1506 * Returning is simple if the buffer is empty. Otherwise we'll have to
1507 * perform a partial flush and write out whatever is left ourselves.
1508 */
1509 if (RT_SUCCESS(State.rcStatus))
1510 {
1511 if (State.cchLine == 0)
1512 return State.cLines << 16;
1513
1514 rtStrmWrappedFlushLine(&State, true /*fPartial*/);
1515 if (RT_SUCCESS(State.rcStatus) && State.cchLine > 0)
1516 {
1517 rtStrmWrapppedIndent(&State, State.cchIndent);
1518 State.rcStatus = RTStrmWrite(State.pStream, State.szLine, State.cchLine);
1519 }
1520 if (RT_SUCCESS(State.rcStatus))
1521 return RT_MIN(State.cchIndent + State.cchLine, RTSTRMWRAPPED_F_LINE_OFFSET_MASK) | (State.cLines << 16);
1522 }
1523 return State.rcStatus;
1524}
1525
1526
1527RTDECL(int32_t) RTStrmWrappedPrintf(PRTSTREAM pStream, uint32_t fFlags, const char *pszFormat, ...)
1528{
1529 va_list va;
1530 va_start(va, pszFormat);
1531 int32_t rcRet = RTStrmWrappedPrintfV(pStream, fFlags, pszFormat, va);
1532 va_end(va);
1533 return rcRet;
1534}
1535
Note: See TracBrowser for help on using the repository browser.

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