VirtualBox

source: vbox/trunk/src/bldprogs/scmstream.cpp@ 40530

Last change on this file since 40530 was 40530, checked in by vboxsync, 13 years ago

scm: Splitting out the diff code while I'm at it.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 29.4 KB
Line 
1/* $Id: scmstream.cpp 40530 2012-03-19 11:13:17Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager Stream Code.
4 */
5
6/*
7 * Copyright (C) 2010-2012 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
18/*******************************************************************************
19* Header Files *
20*******************************************************************************/
21#include <iprt/assert.h>
22#include <iprt/ctype.h>
23#include <iprt/file.h>
24#include <iprt/err.h>
25#include <iprt/mem.h>
26#include <iprt/string.h>
27
28#include "scmstream.h"
29
30
31/**
32 * Initializes the stream structure.
33 *
34 * @param pStream The stream structure.
35 * @param fWriteOrRead The value of the fWriteOrRead stream member.
36 */
37static void scmStreamInitInternal(PSCMSTREAM pStream, bool fWriteOrRead)
38{
39 pStream->pch = NULL;
40 pStream->off = 0;
41 pStream->cb = 0;
42 pStream->cbAllocated = 0;
43
44 pStream->paLines = NULL;
45 pStream->iLine = 0;
46 pStream->cLines = 0;
47 pStream->cLinesAllocated = 0;
48
49 pStream->fWriteOrRead = fWriteOrRead;
50 pStream->fFileMemory = false;
51 pStream->fFullyLineated = false;
52
53 pStream->rc = VINF_SUCCESS;
54}
55
56/**
57 * Initialize an input stream.
58 *
59 * @returns IPRT status code.
60 * @param pStream The stream to initialize.
61 * @param pszFilename The file to take the stream content from.
62 */
63int ScmStreamInitForReading(PSCMSTREAM pStream, const char *pszFilename)
64{
65 scmStreamInitInternal(pStream, false /*fWriteOrRead*/);
66
67 void *pvFile;
68 size_t cbFile;
69 int rc = pStream->rc = RTFileReadAll(pszFilename, &pvFile, &cbFile);
70 if (RT_SUCCESS(rc))
71 {
72 pStream->pch = (char *)pvFile;
73 pStream->cb = cbFile;
74 pStream->cbAllocated = cbFile;
75 pStream->fFileMemory = true;
76 }
77 return rc;
78}
79
80/**
81 * Initialize an output stream.
82 *
83 * @returns IPRT status code
84 * @param pStream The stream to initialize.
85 * @param pRelatedStream Pointer to a related stream. NULL is fine.
86 */
87int ScmStreamInitForWriting(PSCMSTREAM pStream, PCSCMSTREAM pRelatedStream)
88{
89 scmStreamInitInternal(pStream, true /*fWriteOrRead*/);
90
91 /* allocate stuff */
92 size_t cbEstimate = pRelatedStream
93 ? pRelatedStream->cb + pRelatedStream->cb / 10
94 : _64K;
95 cbEstimate = RT_ALIGN(cbEstimate, _4K);
96 pStream->pch = (char *)RTMemAlloc(cbEstimate);
97 if (pStream->pch)
98 {
99 size_t cLinesEstimate = pRelatedStream && pRelatedStream->fFullyLineated
100 ? pRelatedStream->cLines + pRelatedStream->cLines / 10
101 : cbEstimate / 24;
102 cLinesEstimate = RT_ALIGN(cLinesEstimate, 512);
103 pStream->paLines = (PSCMSTREAMLINE)RTMemAlloc(cLinesEstimate * sizeof(SCMSTREAMLINE));
104 if (pStream->paLines)
105 {
106 pStream->paLines[0].off = 0;
107 pStream->paLines[0].cch = 0;
108 pStream->paLines[0].enmEol = SCMEOL_NONE;
109 pStream->cbAllocated = cbEstimate;
110 pStream->cLinesAllocated = cLinesEstimate;
111 return VINF_SUCCESS;
112 }
113
114 RTMemFree(pStream->pch);
115 pStream->pch = NULL;
116 }
117 return pStream->rc = VERR_NO_MEMORY;
118}
119
120/**
121 * Frees the resources associated with the stream.
122 *
123 * Nothing is happens to whatever the stream was initialized from or dumped to.
124 *
125 * @param pStream The stream to delete.
126 */
127void ScmStreamDelete(PSCMSTREAM pStream)
128{
129 if (pStream->pch)
130 {
131 if (pStream->fFileMemory)
132 RTFileReadAllFree(pStream->pch, pStream->cbAllocated);
133 else
134 RTMemFree(pStream->pch);
135 pStream->pch = NULL;
136 }
137 pStream->cbAllocated = 0;
138
139 if (pStream->paLines)
140 {
141 RTMemFree(pStream->paLines);
142 pStream->paLines = NULL;
143 }
144 pStream->cLinesAllocated = 0;
145}
146
147/**
148 * Get the stream status code.
149 *
150 * @returns IPRT status code.
151 * @param pStream The stream.
152 */
153int ScmStreamGetStatus(PCSCMSTREAM pStream)
154{
155 return pStream->rc;
156}
157
158/**
159 * Grows the buffer of a write stream.
160 *
161 * @returns IPRT status code.
162 * @param pStream The stream. Must be in write mode.
163 * @param cbAppending The minimum number of bytes to grow the buffer
164 * with.
165 */
166static int scmStreamGrowBuffer(PSCMSTREAM pStream, size_t cbAppending)
167{
168 size_t cbAllocated = pStream->cbAllocated;
169 cbAllocated += RT_MAX(0x1000 + cbAppending, cbAllocated);
170 cbAllocated = RT_ALIGN(cbAllocated, 0x1000);
171 void *pvNew;
172 if (!pStream->fFileMemory)
173 {
174 pvNew = RTMemRealloc(pStream->pch, cbAllocated);
175 if (!pvNew)
176 return pStream->rc = VERR_NO_MEMORY;
177 }
178 else
179 {
180 pvNew = RTMemDupEx(pStream->pch, pStream->off, cbAllocated - pStream->off);
181 if (!pvNew)
182 return pStream->rc = VERR_NO_MEMORY;
183 RTFileReadAllFree(pStream->pch, pStream->cbAllocated);
184 pStream->fFileMemory = false;
185 }
186 pStream->pch = (char *)pvNew;
187 pStream->cbAllocated = cbAllocated;
188
189 return VINF_SUCCESS;
190}
191
192/**
193 * Grows the line array of a stream.
194 *
195 * @returns IPRT status code.
196 * @param pStream The stream.
197 * @param iMinLine Minimum line number.
198 */
199static int scmStreamGrowLines(PSCMSTREAM pStream, size_t iMinLine)
200{
201 size_t cLinesAllocated = pStream->cLinesAllocated;
202 cLinesAllocated += RT_MAX(512 + iMinLine, cLinesAllocated);
203 cLinesAllocated = RT_ALIGN(cLinesAllocated, 512);
204 void *pvNew = RTMemRealloc(pStream->paLines, cLinesAllocated * sizeof(SCMSTREAMLINE));
205 if (!pvNew)
206 return pStream->rc = VERR_NO_MEMORY;
207
208 pStream->paLines = (PSCMSTREAMLINE)pvNew;
209 pStream->cLinesAllocated = cLinesAllocated;
210 return VINF_SUCCESS;
211}
212
213/**
214 * Rewinds the stream and sets the mode to read.
215 *
216 * @param pStream The stream.
217 */
218void ScmStreamRewindForReading(PSCMSTREAM pStream)
219{
220 pStream->off = 0;
221 pStream->iLine = 0;
222 pStream->fWriteOrRead = false;
223 pStream->rc = VINF_SUCCESS;
224}
225
226/**
227 * Rewinds the stream and sets the mode to write.
228 *
229 * @param pStream The stream.
230 */
231void ScmStreamRewindForWriting(PSCMSTREAM pStream)
232{
233 pStream->off = 0;
234 pStream->iLine = 0;
235 pStream->cLines = 0;
236 pStream->fWriteOrRead = true;
237 pStream->fFullyLineated = true;
238 pStream->rc = VINF_SUCCESS;
239}
240
241/**
242 * Checks if it's a text stream.
243 *
244 * Not 100% proof.
245 *
246 * @returns true if it probably is a text file, false if not.
247 * @param pStream The stream. Write or read, doesn't matter.
248 */
249bool ScmStreamIsText(PSCMSTREAM pStream)
250{
251 if (RTStrEnd(pStream->pch, pStream->cb))
252 return false;
253 if (!pStream->cb)
254 return false;
255 return true;
256}
257
258/**
259 * Performs an integrity check of the stream.
260 *
261 * @returns IPRT status code.
262 * @param pStream The stream.
263 */
264int ScmStreamCheckItegrity(PSCMSTREAM pStream)
265{
266 /*
267 * Perform sanity checks.
268 */
269 size_t const cbFile = pStream->cb;
270 for (size_t iLine = 0; iLine < pStream->cLines; iLine++)
271 {
272 size_t offEol = pStream->paLines[iLine].off + pStream->paLines[iLine].cch;
273 AssertReturn(offEol + pStream->paLines[iLine].enmEol <= cbFile, VERR_INTERNAL_ERROR_2);
274 switch (pStream->paLines[iLine].enmEol)
275 {
276 case SCMEOL_LF:
277 AssertReturn(pStream->pch[offEol] == '\n', VERR_INTERNAL_ERROR_3);
278 break;
279 case SCMEOL_CRLF:
280 AssertReturn(pStream->pch[offEol] == '\r', VERR_INTERNAL_ERROR_3);
281 AssertReturn(pStream->pch[offEol + 1] == '\n', VERR_INTERNAL_ERROR_3);
282 break;
283 case SCMEOL_NONE:
284 AssertReturn(iLine + 1 >= pStream->cLines, VERR_INTERNAL_ERROR_4);
285 break;
286 default:
287 AssertReturn(iLine + 1 >= pStream->cLines, VERR_INTERNAL_ERROR_5);
288 }
289 }
290 return VINF_SUCCESS;
291}
292
293/**
294 * Writes the stream to a file.
295 *
296 * @returns IPRT status code
297 * @param pStream The stream.
298 * @param pszFilenameFmt The filename format string.
299 * @param ... Format arguments.
300 */
301int ScmStreamWriteToFile(PSCMSTREAM pStream, const char *pszFilenameFmt, ...)
302{
303 int rc;
304
305#ifdef RT_STRICT
306 /*
307 * Check that what we're going to write makes sense first.
308 */
309 rc = ScmStreamCheckItegrity(pStream);
310 if (RT_FAILURE(rc))
311 return rc;
312#endif
313
314 /*
315 * Do the actual writing.
316 */
317 RTFILE hFile;
318 va_list va;
319 va_start(va, pszFilenameFmt);
320 rc = RTFileOpenV(&hFile, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_WRITE, pszFilenameFmt, va);
321 if (RT_SUCCESS(rc))
322 {
323 rc = RTFileWrite(hFile, pStream->pch, pStream->cb, NULL);
324 RTFileClose(hFile);
325 }
326 return rc;
327}
328
329/**
330 * Worker for ScmStreamGetLine that builds the line number index while parsing
331 * the stream.
332 *
333 * @returns Same as SCMStreamGetLine.
334 * @param pStream The stream. Must be in read mode.
335 * @param pcchLine Where to return the line length.
336 * @param penmEol Where to return the kind of end of line marker.
337 */
338static const char *scmStreamGetLineInternal(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol)
339{
340 AssertReturn(!pStream->fWriteOrRead, NULL);
341 if (RT_FAILURE(pStream->rc))
342 return NULL;
343
344 size_t off = pStream->off;
345 size_t cb = pStream->cb;
346 if (RT_UNLIKELY(off >= cb))
347 {
348 pStream->fFullyLineated = true;
349 return NULL;
350 }
351
352 size_t iLine = pStream->iLine;
353 if (RT_UNLIKELY(iLine >= pStream->cLinesAllocated))
354 {
355 int rc = scmStreamGrowLines(pStream, iLine);
356 if (RT_FAILURE(rc))
357 return NULL;
358 }
359 pStream->paLines[iLine].off = off;
360
361 cb -= off;
362 const char *pchRet = &pStream->pch[off];
363 const char *pch = (const char *)memchr(pchRet, '\n', cb);
364 if (RT_LIKELY(pch))
365 {
366 cb = pch - pchRet;
367 pStream->off = off + cb + 1;
368 if ( cb < 1
369 || pch[-1] != '\r')
370 pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_LF;
371 else
372 {
373 pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_CRLF;
374 cb--;
375 }
376 }
377 else
378 {
379 pStream->off = off + cb;
380 pStream->paLines[iLine].enmEol = *penmEol = SCMEOL_NONE;
381 }
382 *pcchLine = cb;
383 pStream->paLines[iLine].cch = cb;
384 pStream->cLines = pStream->iLine = ++iLine;
385
386 return pchRet;
387}
388
389/**
390 * Internal worker that delineates a stream.
391 *
392 * @returns IPRT status code.
393 * @param pStream The stream. Caller must check that it is in
394 * read mode.
395 */
396static int scmStreamLineate(PSCMSTREAM pStream)
397{
398 /* Save the stream position. */
399 size_t const offSaved = pStream->off;
400 size_t const iLineSaved = pStream->iLine;
401
402 /* Get each line. */
403 size_t cchLine;
404 SCMEOL enmEol;
405 while (scmStreamGetLineInternal(pStream, &cchLine, &enmEol))
406 /* nothing */;
407 Assert(RT_FAILURE(pStream->rc) || pStream->fFullyLineated);
408
409 /* Restore the position */
410 pStream->off = offSaved;
411 pStream->iLine = iLineSaved;
412
413 return pStream->rc;
414}
415
416/**
417 * Get the current stream position as an byte offset.
418 *
419 * @returns The current byte offset
420 * @param pStream The stream.
421 */
422size_t ScmStreamTell(PSCMSTREAM pStream)
423{
424 return pStream->off;
425}
426
427/**
428 * Get the current stream position as a line number.
429 *
430 * @returns The current line (0-based).
431 * @param pStream The stream.
432 */
433size_t ScmStreamTellLine(PSCMSTREAM pStream)
434{
435 return pStream->iLine;
436}
437
438/**
439 * Get the current stream size in bytes.
440 *
441 * @returns Count of bytes.
442 * @param pStream The stream.
443 */
444size_t ScmStreamSize(PSCMSTREAM pStream)
445{
446 return pStream->cb;
447}
448
449/**
450 * Gets the number of lines in the stream.
451 *
452 * @returns The number of lines.
453 * @param pStream The stream.
454 */
455size_t ScmStreamCountLines(PSCMSTREAM pStream)
456{
457 if (!pStream->fFullyLineated)
458 scmStreamLineate(pStream);
459 return pStream->cLines;
460}
461
462/**
463 * Seeks to a given byte offset in the stream.
464 *
465 * @returns IPRT status code.
466 * @retval VERR_SEEK if the new stream position is the middle of an EOL marker.
467 * This is a temporary restriction.
468 *
469 * @param pStream The stream. Must be in read mode.
470 * @param offAbsolute The offset to seek to. If this is beyond the
471 * end of the stream, the position is set to the
472 * end.
473 */
474int ScmStreamSeekAbsolute(PSCMSTREAM pStream, size_t offAbsolute)
475{
476 AssertReturn(!pStream->fWriteOrRead, VERR_ACCESS_DENIED);
477 if (RT_FAILURE(pStream->rc))
478 return pStream->rc;
479
480 /* Must be fully delineated. (lazy bird) */
481 if (RT_UNLIKELY(!pStream->fFullyLineated))
482 {
483 int rc = scmStreamLineate(pStream);
484 if (RT_FAILURE(rc))
485 return rc;
486 }
487
488 /* Ok, do the job. */
489 if (offAbsolute < pStream->cb)
490 {
491 /** @todo Should do a binary search here, but I'm too darn lazy tonight. */
492 pStream->off = ~(size_t)0;
493 for (size_t i = 0; i < pStream->cLines; i++)
494 {
495 if (offAbsolute < pStream->paLines[i].off + pStream->paLines[i].cch + pStream->paLines[i].enmEol)
496 {
497 pStream->off = offAbsolute;
498 pStream->iLine = i;
499 if (offAbsolute > pStream->paLines[i].off + pStream->paLines[i].cch)
500 return pStream->rc = VERR_SEEK;
501 break;
502 }
503 }
504 AssertReturn(pStream->off != ~(size_t)0, pStream->rc = VERR_INTERNAL_ERROR_3);
505 }
506 else
507 {
508 pStream->off = pStream->cb;
509 pStream->iLine = pStream->cLines;
510 }
511 return VINF_SUCCESS;
512}
513
514
515/**
516 * Seeks a number of bytes relative to the current stream position.
517 *
518 * @returns IPRT status code.
519 * @retval VERR_SEEK if the new stream position is the middle of an EOL marker.
520 * This is a temporary restriction.
521 *
522 * @param pStream The stream. Must be in read mode.
523 * @param offRelative The offset to seek to. A negative offset
524 * rewinds and positive one fast forwards the
525 * stream. Will quietly stop at the beginning and
526 * end of the stream.
527 */
528int ScmStreamSeekRelative(PSCMSTREAM pStream, ssize_t offRelative)
529{
530 size_t offAbsolute;
531 if (offRelative >= 0)
532 offAbsolute = pStream->off + offRelative;
533 else if ((size_t)-offRelative <= pStream->off)
534 offAbsolute = pStream->off + offRelative;
535 else
536 offAbsolute = 0;
537 return ScmStreamSeekAbsolute(pStream, offAbsolute);
538}
539
540/**
541 * Seeks to a given line in the stream.
542 *
543 * @returns IPRT status code.
544 *
545 * @param pStream The stream. Must be in read mode.
546 * @param iLine The line to seek to. If this is beyond the end
547 * of the stream, the position is set to the end.
548 */
549int ScmStreamSeekByLine(PSCMSTREAM pStream, size_t iLine)
550{
551 AssertReturn(!pStream->fWriteOrRead, VERR_ACCESS_DENIED);
552 if (RT_FAILURE(pStream->rc))
553 return pStream->rc;
554
555 /* Must be fully delineated. (lazy bird) */
556 if (RT_UNLIKELY(!pStream->fFullyLineated))
557 {
558 int rc = scmStreamLineate(pStream);
559 if (RT_FAILURE(rc))
560 return rc;
561 }
562
563 /* Ok, do the job. */
564 if (iLine < pStream->cLines)
565 {
566 pStream->off = pStream->paLines[iLine].off;
567 pStream->iLine = iLine;
568 }
569 else
570 {
571 pStream->off = pStream->cb;
572 pStream->iLine = pStream->cLines;
573 }
574 return VINF_SUCCESS;
575}
576
577/**
578 * Get a numbered line from the stream (changes the position).
579 *
580 * A line is always delimited by a LF character or the end of the stream. The
581 * delimiter is not included in returned line length, but instead returned via
582 * the @a penmEol indicator.
583 *
584 * @returns Pointer to the first character in the line, not NULL terminated.
585 * NULL if the end of the stream has been reached or some problem
586 * occurred.
587 *
588 * @param pStream The stream. Must be in read mode.
589 * @param iLine The line to get (0-based).
590 * @param pcchLine The length.
591 * @param penmEol Where to return the end of line type indicator.
592 */
593const char *ScmStreamGetLineByNo(PSCMSTREAM pStream, size_t iLine, size_t *pcchLine, PSCMEOL penmEol)
594{
595 AssertReturn(!pStream->fWriteOrRead, NULL);
596 if (RT_FAILURE(pStream->rc))
597 return NULL;
598
599 /* Make sure it's fully delineated so we can use the index. */
600 if (RT_UNLIKELY(!pStream->fFullyLineated))
601 {
602 int rc = scmStreamLineate(pStream);
603 if (RT_FAILURE(rc))
604 return NULL;
605 }
606
607 /* End of stream? */
608 if (RT_UNLIKELY(iLine >= pStream->cLines))
609 {
610 pStream->off = pStream->cb;
611 pStream->iLine = pStream->cLines;
612 return NULL;
613 }
614
615 /* Get the data. */
616 const char *pchRet = &pStream->pch[pStream->paLines[iLine].off];
617 *pcchLine = pStream->paLines[iLine].cch;
618 *penmEol = pStream->paLines[iLine].enmEol;
619
620 /* update the stream position. */
621 pStream->off = pStream->paLines[iLine].off + pStream->paLines[iLine].cch + pStream->paLines[iLine].enmEol;
622 pStream->iLine = iLine + 1;
623
624 return pchRet;
625}
626
627/**
628 * Get a line from the stream.
629 *
630 * A line is always delimited by a LF character or the end of the stream. The
631 * delimiter is not included in returned line length, but instead returned via
632 * the @a penmEol indicator.
633 *
634 * @returns Pointer to the first character in the line, not NULL terminated.
635 * NULL if the end of the stream has been reached or some problem
636 * occurred.
637 *
638 * @param pStream The stream. Must be in read mode.
639 * @param pcchLine The length.
640 * @param penmEol Where to return the end of line type indicator.
641 */
642const char *ScmStreamGetLine(PSCMSTREAM pStream, size_t *pcchLine, PSCMEOL penmEol)
643{
644 /** @todo this doesn't work when pStream->off !=
645 * pStream->paLines[pStream->iLine-1].pff. */
646 if (!pStream->fFullyLineated)
647 return scmStreamGetLineInternal(pStream, pcchLine, penmEol);
648 return ScmStreamGetLineByNo(pStream, pStream->iLine, pcchLine, penmEol);
649}
650
651/**
652 * Reads @a cbToRead bytes into @a pvBuf.
653 *
654 * Will fail if end of stream is encountered before the entire read has been
655 * completed.
656 *
657 * @returns IPRT status code.
658 * @retval VERR_EOF if there isn't @a cbToRead bytes left to read. Stream
659 * position will be unchanged.
660 *
661 * @param pStream The stream. Must be in read mode.
662 * @param pvBuf The buffer to read into.
663 * @param cbToRead The number of bytes to read.
664 */
665int ScmStreamRead(PSCMSTREAM pStream, void *pvBuf, size_t cbToRead)
666{
667 AssertReturn(!pStream->fWriteOrRead, VERR_PERMISSION_DENIED);
668 if (RT_FAILURE(pStream->rc))
669 return pStream->rc;
670
671 /* If there isn't enough stream left, fail already. */
672 if (RT_UNLIKELY(pStream->cb - pStream->cb < cbToRead))
673 return VERR_EOF;
674
675 /* Copy the data and simply seek to the new stream position. */
676 memcpy(pvBuf, &pStream->pch[pStream->off], cbToRead);
677 return ScmStreamSeekAbsolute(pStream, pStream->off + cbToRead);
678}
679
680/**
681 * Checks if the given line is empty or full of white space.
682 *
683 * @returns true if white space only, false if not (or if non-existant).
684 * @param pStream The stream. Must be in read mode.
685 * @param iLine The line in question.
686 */
687bool ScmStreamIsWhiteLine(PSCMSTREAM pStream, size_t iLine)
688{
689 SCMEOL enmEol;
690 size_t cchLine;
691 const char *pchLine = ScmStreamGetLineByNo(pStream, iLine, &cchLine, &enmEol);
692 if (!pchLine)
693 return false;
694 while (cchLine && RT_C_IS_SPACE(*pchLine))
695 pchLine++, cchLine--;
696 return cchLine == 0;
697}
698
699/**
700 * Try figure out the end of line style of the give stream.
701 *
702 * @returns Most likely end of line style.
703 * @param pStream The stream.
704 */
705SCMEOL ScmStreamGetEol(PSCMSTREAM pStream)
706{
707 SCMEOL enmEol;
708 if (pStream->cLines > 0)
709 enmEol = pStream->paLines[0].enmEol;
710 else if (pStream->cb == 0)
711 enmEol = SCMEOL_NONE;
712 else
713 {
714 const char *pchLF = (const char *)memchr(pStream->pch, '\n', pStream->cb);
715 if (pchLF && pchLF != pStream->pch && pchLF[-1] == '\r')
716 enmEol = SCMEOL_CRLF;
717 else
718 enmEol = SCMEOL_LF;
719 }
720
721 if (enmEol == SCMEOL_NONE)
722#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
723 enmEol = SCMEOL_CRLF;
724#else
725 enmEol = SCMEOL_LF;
726#endif
727 return enmEol;
728}
729
730/**
731 * Get the end of line indicator type for a line.
732 *
733 * @returns The EOL indicator. If the line isn't found, the default EOL
734 * indicator is return.
735 * @param pStream The stream.
736 * @param iLine The line (0-base).
737 */
738SCMEOL ScmStreamGetEolByLine(PSCMSTREAM pStream, size_t iLine)
739{
740 SCMEOL enmEol;
741 if (iLine < pStream->cLines)
742 enmEol = pStream->paLines[iLine].enmEol;
743 else
744#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
745 enmEol = SCMEOL_CRLF;
746#else
747 enmEol = SCMEOL_LF;
748#endif
749 return enmEol;
750}
751
752/**
753 * Appends a line to the stream.
754 *
755 * @returns IPRT status code.
756 * @param pStream The stream. Must be in write mode.
757 * @param pchLine Pointer to the line.
758 * @param cchLine Line length.
759 * @param enmEol Which end of line indicator to use.
760 */
761int ScmStreamPutLine(PSCMSTREAM pStream, const char *pchLine, size_t cchLine, SCMEOL enmEol)
762{
763 AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);
764 if (RT_FAILURE(pStream->rc))
765 return pStream->rc;
766
767 /*
768 * Make sure the previous line has a new-line indicator.
769 */
770 size_t off = pStream->off;
771 size_t iLine = pStream->iLine;
772 if (RT_UNLIKELY( iLine != 0
773 && pStream->paLines[iLine - 1].enmEol == SCMEOL_NONE))
774 {
775 AssertReturn(pStream->paLines[iLine].cch == 0, VERR_INTERNAL_ERROR_3);
776 SCMEOL enmEol2 = enmEol != SCMEOL_NONE ? enmEol : ScmStreamGetEol(pStream);
777 if (RT_UNLIKELY(off + cchLine + enmEol + enmEol2 > pStream->cbAllocated))
778 {
779 int rc = scmStreamGrowBuffer(pStream, cchLine + enmEol + enmEol2);
780 if (RT_FAILURE(rc))
781 return rc;
782 }
783 if (enmEol2 == SCMEOL_LF)
784 pStream->pch[off++] = '\n';
785 else
786 {
787 pStream->pch[off++] = '\r';
788 pStream->pch[off++] = '\n';
789 }
790 pStream->paLines[iLine - 1].enmEol = enmEol2;
791 pStream->paLines[iLine].off = off;
792 pStream->off = off;
793 pStream->cb = off;
794 }
795
796 /*
797 * Ensure we've got sufficient buffer space.
798 */
799 if (RT_UNLIKELY(off + cchLine + enmEol > pStream->cbAllocated))
800 {
801 int rc = scmStreamGrowBuffer(pStream, cchLine + enmEol);
802 if (RT_FAILURE(rc))
803 return rc;
804 }
805
806 /*
807 * Add a line record.
808 */
809 if (RT_UNLIKELY(iLine + 1 >= pStream->cLinesAllocated))
810 {
811 int rc = scmStreamGrowLines(pStream, iLine);
812 if (RT_FAILURE(rc))
813 return rc;
814 }
815
816 pStream->paLines[iLine].cch = off - pStream->paLines[iLine].off + cchLine;
817 pStream->paLines[iLine].enmEol = enmEol;
818
819 iLine++;
820 pStream->cLines = iLine;
821 pStream->iLine = iLine;
822
823 /*
824 * Copy the line
825 */
826 memcpy(&pStream->pch[off], pchLine, cchLine);
827 off += cchLine;
828 if (enmEol == SCMEOL_LF)
829 pStream->pch[off++] = '\n';
830 else if (enmEol == SCMEOL_CRLF)
831 {
832 pStream->pch[off++] = '\r';
833 pStream->pch[off++] = '\n';
834 }
835 pStream->off = off;
836 pStream->cb = off;
837
838 /*
839 * Start a new line.
840 */
841 pStream->paLines[iLine].off = off;
842 pStream->paLines[iLine].cch = 0;
843 pStream->paLines[iLine].enmEol = SCMEOL_NONE;
844
845 return VINF_SUCCESS;
846}
847
848/**
849 * Writes to the stream.
850 *
851 * @returns IPRT status code
852 * @param pStream The stream. Must be in write mode.
853 * @param pchBuf What to write.
854 * @param cchBuf How much to write.
855 */
856int ScmStreamWrite(PSCMSTREAM pStream, const char *pchBuf, size_t cchBuf)
857{
858 AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);
859 if (RT_FAILURE(pStream->rc))
860 return pStream->rc;
861
862 /*
863 * Ensure we've got sufficient buffer space.
864 */
865 size_t off = pStream->off;
866 if (RT_UNLIKELY(off + cchBuf > pStream->cbAllocated))
867 {
868 int rc = scmStreamGrowBuffer(pStream, cchBuf);
869 if (RT_FAILURE(rc))
870 return rc;
871 }
872
873 /*
874 * Deal with the odd case where we've already pushed a line with SCMEOL_NONE.
875 */
876 size_t iLine = pStream->iLine;
877 if (RT_UNLIKELY( iLine > 0
878 && pStream->paLines[iLine - 1].enmEol == SCMEOL_NONE))
879 {
880 iLine--;
881 pStream->cLines = iLine;
882 pStream->iLine = iLine;
883 }
884
885 /*
886 * Deal with lines.
887 */
888 const char *pchLF = (const char *)memchr(pchBuf, '\n', cchBuf);
889 if (!pchLF)
890 pStream->paLines[iLine].cch += cchBuf;
891 else
892 {
893 const char *pchLine = pchBuf;
894 for (;;)
895 {
896 if (RT_UNLIKELY(iLine + 1 >= pStream->cLinesAllocated))
897 {
898 int rc = scmStreamGrowLines(pStream, iLine);
899 if (RT_FAILURE(rc))
900 {
901 iLine = pStream->iLine;
902 pStream->paLines[iLine].cch = off - pStream->paLines[iLine].off;
903 pStream->paLines[iLine].enmEol = SCMEOL_NONE;
904 return rc;
905 }
906 }
907
908 size_t cchLine = pchLF - pchLine;
909 if ( cchLine
910 ? pchLF[-1] != '\r'
911 : !pStream->paLines[iLine].cch
912 || pStream->pch[pStream->paLines[iLine].off + pStream->paLines[iLine].cch - 1] != '\r')
913 pStream->paLines[iLine].enmEol = SCMEOL_LF;
914 else
915 {
916 pStream->paLines[iLine].enmEol = SCMEOL_CRLF;
917 cchLine--;
918 }
919 pStream->paLines[iLine].cch += cchLine;
920
921 iLine++;
922 size_t offBuf = pchLF + 1 - pchBuf;
923 pStream->paLines[iLine].off = off + offBuf;
924 pStream->paLines[iLine].cch = 0;
925 pStream->paLines[iLine].enmEol = SCMEOL_NONE;
926
927 size_t cchLeft = cchBuf - offBuf;
928 pchLF = (const char *)memchr(pchLF + 1, '\n', cchLeft);
929 if (!pchLF)
930 {
931 pStream->paLines[iLine].cch = cchLeft;
932 break;
933 }
934 }
935
936 pStream->iLine = iLine;
937 pStream->cLines = iLine;
938 }
939
940 /*
941 * Copy the data and update position and size.
942 */
943 memcpy(&pStream->pch[off], pchBuf, cchBuf);
944 off += cchBuf;
945 pStream->off = off;
946 pStream->cb = off;
947
948 return VINF_SUCCESS;
949}
950
951/**
952 * Write a character to the stream.
953 *
954 * @returns IPRT status code
955 * @param pStream The stream. Must be in write mode.
956 * @param pchBuf What to write.
957 * @param cchBuf How much to write.
958 */
959int ScmStreamPutCh(PSCMSTREAM pStream, char ch)
960{
961 AssertReturn(pStream->fWriteOrRead, VERR_ACCESS_DENIED);
962 if (RT_FAILURE(pStream->rc))
963 return pStream->rc;
964
965 /*
966 * Only deal with the simple cases here, use ScmStreamWrite for the
967 * annoying stuff.
968 */
969 size_t off = pStream->off;
970 if ( ch == '\n'
971 || RT_UNLIKELY(off + 1 > pStream->cbAllocated))
972 return ScmStreamWrite(pStream, &ch, 1);
973
974 /*
975 * Just append it.
976 */
977 pStream->pch[off] = ch;
978 pStream->off = off + 1;
979 pStream->paLines[pStream->iLine].cch++;
980
981 return VINF_SUCCESS;
982}
983
984/**
985 * Copies @a cLines from the @a pSrc stream onto the @a pDst stream.
986 *
987 * The stream positions will be used and changed in both streams.
988 *
989 * @returns IPRT status code.
990 * @param pDst The destination stream. Must be in write mode.
991 * @param cLines The number of lines. (0 is accepted.)
992 * @param pSrc The source stream. Must be in read mode.
993 */
994int ScmStreamCopyLines(PSCMSTREAM pDst, PSCMSTREAM pSrc, size_t cLines)
995{
996 AssertReturn(pDst->fWriteOrRead, VERR_ACCESS_DENIED);
997 if (RT_FAILURE(pDst->rc))
998 return pDst->rc;
999
1000 AssertReturn(!pSrc->fWriteOrRead, VERR_ACCESS_DENIED);
1001 if (RT_FAILURE(pSrc->rc))
1002 return pSrc->rc;
1003
1004 while (cLines-- > 0)
1005 {
1006 SCMEOL enmEol;
1007 size_t cchLine;
1008 const char *pchLine = ScmStreamGetLine(pSrc, &cchLine, &enmEol);
1009 if (!pchLine)
1010 return pDst->rc = (RT_FAILURE(pSrc->rc) ? pSrc->rc : VERR_EOF);
1011
1012 int rc = ScmStreamPutLine(pDst, pchLine, cchLine, enmEol);
1013 if (RT_FAILURE(rc))
1014 return rc;
1015 }
1016
1017 return VINF_SUCCESS;
1018}
1019
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