summaryrefslogtreecommitdiffstats
path: root/src/lib/kStuff/kRdr/kRdrBuffered.cpp
blob: fc589cdcc27f27ac2166b98278c27e7361316b1f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
/* $Id: kRdrBuffered.cpp 79 2016-07-27 14:25:09Z bird $ */
/** @file
 * kRdrBuffered - Buffered File Provider.
 */

/*
 * Copyright (c) 2006-2007 Knut St. Osmundsen <bird-kStuff-spamix@anduin.net>
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#include "kRdrInternal.h"
#include <k/kHlpAlloc.h>
#include <k/kHlpString.h>


/*******************************************************************************
*   Structures and Typedefs                                                    *
*******************************************************************************/
/**
 * The buffered file provier instance.
 * This is just a wrapper around another file provider.
 */
typedef struct KRDRBUF
{
    /** The file reader vtable. */
    KRDR                Core;
    /** The actual file provider that we're wrapping. */
    PKRDR               pRdr;
    /** The current file offset. */
    KFOFF               offFile;
    /** The file size. */
    KFOFF               cbFile;
    /** The offset of the buffer. */
    KFOFF               offBuf;
    /** The offset of the end of the buffer. */
    KFOFF               offBufEnd;
    /** The number of valid buffer bytes. */
    KSIZE               cbBufValid;
    /** The size of the buffer. */
    KSIZE               cbBuf;
    /** The buffer. */
    KU8                *pbBuf;
    /** Whether the pRdr instance should be closed together with us or not. */
    KBOOL               fCloseIt;
    /** Set if the buffer has been messed up by kRdrBufLineQ. */
    KBOOL               fTainedByLineQ;
} KRDRBUF, *PKRDRBUF;


/*******************************************************************************
*   Internal Functions                                                         *
*******************************************************************************/
static void     krdrBufDone(PKRDR pRdr);
static int      krdrBufUnmap(PKRDR pRdr, void *pvBase, KU32 cSegments, PCKLDRSEG paSegments);
static int      krdrBufProtect(PKRDR pRdr, void *pvBase, KU32 cSegments, PCKLDRSEG paSegments, KBOOL fUnprotectOrProtect);
static int      krdrBufRefresh(PKRDR pRdr, void *pvBase, KU32 cSegments, PCKLDRSEG paSegments);
static int      krdrBufMap(PKRDR pRdr, void **ppvBase, KU32 cSegments, PCKLDRSEG paSegments, KBOOL fFixed);
static KSIZE    krdrBufPageSize(PKRDR pRdr);
static const char *krdrBufName(PKRDR pRdr);
static KIPTR    krdrBufNativeFH(PKRDR pRdr);
static KFOFF    krdrBufTell(PKRDR pRdr);
static KFOFF    krdrBufSize(PKRDR pRdr);
static int      krdrBufAllUnmap(PKRDR pRdr, const void *pvBits);
static int      krdrBufAllMap(PKRDR pRdr, const void **ppvBits);
static int      krdrBufRead(PKRDR pRdr, void *pvBuf, KSIZE cb, KFOFF off);
static int      krdrBufDestroy(PKRDR pRdr);
static int      krdrBufCreate(PPKRDR ppRdr, const char *pszFilename);


/*******************************************************************************
*   Global Variables                                                           *
*******************************************************************************/
/** Native file provider operations.
 *
 * @remark  This is not in the file provider list as its intended for wrapping
 *          other kRdr instances.
 */
static const KRDROPS g_krdrBufOps =
{
    "Buffered kRdr",
    NULL,
    krdrBufCreate,
    krdrBufDestroy,
    krdrBufRead,
    krdrBufAllMap,
    krdrBufAllUnmap,
    krdrBufSize,
    krdrBufTell,
    krdrBufName,
    krdrBufNativeFH,
    krdrBufPageSize,
    krdrBufMap,
    krdrBufRefresh,
    krdrBufProtect,
    krdrBufUnmap,
    krdrBufDone,
    42
};


/** @copydoc KRDROPS::pfnDone */
static void     krdrBufDone(PKRDR pRdr)
{
    PKRDRBUF pThis = (PKRDRBUF)pRdr;
    return pThis->pRdr->pOps->pfnDone(pThis->pRdr);
}


/** @copydoc KRDROPS::pfnUnmap */
static int      krdrBufUnmap(PKRDR pRdr, void *pvBase, KU32 cSegments, PCKLDRSEG paSegments)
{
    PKRDRBUF pThis = (PKRDRBUF)pRdr;
    return pThis->pRdr->pOps->pfnUnmap(pThis->pRdr, pvBase, cSegments, paSegments);
}


/** @copydoc KRDROPS::pfnProtect */
static int      krdrBufProtect(PKRDR pRdr, void *pvBase, KU32 cSegments, PCKLDRSEG paSegments, KBOOL fUnprotectOrProtect)
{
    PKRDRBUF pThis = (PKRDRBUF)pRdr;
    return pThis->pRdr->pOps->pfnProtect(pThis->pRdr, pvBase, cSegments, paSegments, fUnprotectOrProtect);
}


/** @copydoc KRDROPS::pfnRefresh */
static int krdrBufRefresh(PKRDR pRdr, void *pvBase, KU32 cSegments, PCKLDRSEG paSegments)
{
    PKRDRBUF pThis = (PKRDRBUF)pRdr;
    return pThis->pRdr->pOps->pfnRefresh(pThis->pRdr, pvBase, cSegments, paSegments);
}


/** @copydoc KRDROPS::pfnMap */
static int krdrBufMap(PKRDR pRdr, void **ppvBase, KU32 cSegments, PCKLDRSEG paSegments, KBOOL fFixed)
{
    PKRDRBUF pThis = (PKRDRBUF)pRdr;
    return pThis->pRdr->pOps->pfnMap(pThis->pRdr, ppvBase, cSegments, paSegments, fFixed);
}


/** @copydoc KRDROPS::pfnPageSize */
static KSIZE krdrBufPageSize(PKRDR pRdr)
{
    PKRDRBUF pThis = (PKRDRBUF)pRdr;
    return pThis->pRdr->pOps->pfnPageSize(pThis->pRdr);
}


/** @copydoc KRDROPS::pfnName */
static const char *krdrBufName(PKRDR pRdr)
{
    PKRDRBUF pThis = (PKRDRBUF)pRdr;
    return pThis->pRdr->pOps->pfnName(pThis->pRdr);
}


/** @copydoc KRDROPS::pfnNativeFH */
static KIPTR krdrBufNativeFH(PKRDR pRdr)
{
    PKRDRBUF pThis = (PKRDRBUF)pRdr;
    return pThis->pRdr->pOps->pfnNativeFH(pThis->pRdr);
}


/** @copydoc KRDROPS::pfnTell */
static KFOFF krdrBufTell(PKRDR pRdr)
{
    PKRDRBUF pThis = (PKRDRBUF)pRdr;
    return pThis->offFile;
}


/** @copydoc KRDROPS::pfnSize */
static KFOFF krdrBufSize(PKRDR pRdr)
{
    PKRDRBUF pThis = (PKRDRBUF)pRdr;
    return pThis->cbFile;
}


/** @copydoc KRDROPS::pfnAllUnmap */
static int krdrBufAllUnmap(PKRDR pRdr, const void *pvBits)
{
    PKRDRBUF pThis = (PKRDRBUF)pRdr;
    return pThis->pRdr->pOps->pfnAllUnmap(pThis->pRdr, pvBits);
}


/** @copydoc KRDROPS::pfnAllMap */
static int krdrBufAllMap(PKRDR pRdr, const void **ppvBits)
{
    PKRDRBUF pThis = (PKRDRBUF)pRdr;
    return pThis->pRdr->pOps->pfnAllMap(pThis->pRdr, ppvBits);
}


/**
 * Fills the buffer with file bits starting at the specified offset.
 *
 * @returns 0 on success, pfnRead error code on failure.
 * @param   pThis   The instance.
 * @param   off     Where to start reading.
 */
static int krdrBufFillBuffer(PKRDRBUF pThis, KFOFF off)
{
    kRdrAssert(off < pThis->cbFile);

    /* Reposition the buffer if it's past the end of the file so that
       we maximize its usability. We leave one unused byte at the end
       of the buffer so kRdrBufLineQ can terminate its string properly.
       Of course, this might end up re-reading a lot of stuff for no
       future gain, but whatever... */
    kRdrAssert(pThis->cbBuf <= pThis->cbFile + 1);
    KFOFF cbLeft = pThis->cbFile - off;
    KSIZE cbRead = pThis->cbBuf;
    if ((KSSIZE)cbRead - 1 >= cbLeft)
    {
        cbRead--;
        off = pThis->cbFile - cbRead;
    }
    int rc = pThis->pRdr->pOps->pfnRead(pThis->pRdr, pThis->pbBuf, cbRead, off);
    if (!rc)
    {
        pThis->offBuf = off;
        pThis->offBufEnd = off + cbRead;
        pThis->cbBufValid = cbRead;
    }
    else
    {
        pThis->offBuf = pThis->offBufEnd = 0;
        pThis->cbBufValid = 0;
    }
    pThis->fTainedByLineQ = K_FALSE;
    return rc;
}


/** @copydoc KRDROPS::pfnRead */
static int krdrBufRead(PKRDR pRdr, void *pvBuf, KSIZE cb, KFOFF off)
{
    PKRDRBUF pThis = (PKRDRBUF)pRdr;

    /*
     * We need to validate and update the file offset before
     * we start making partial reads from the buffer and stuff.
     */
    KFOFF offEnd = off + cb;
    if (    off >= pThis->cbFile
        ||  offEnd > pThis->cbFile
        ||  offEnd < off)
        return KERR_OUT_OF_RANGE; /* includes EOF. */
    pThis->offFile = offEnd;
    if (!cb)
        return 0;

    /*
     * Scratch the buffer if kRdrBufLineQ has tained it.
     */
    if (pThis->fTainedByLineQ)
    {
        pThis->offBuf = pThis->offBufEnd = 0;
        pThis->cbBufValid = 0;
    }

    /*
     * Is any part of the request in the buffer?
     *
     * We will currently ignore buffer hits in the middle of the
     * request because it's annoying to implement and it's
     * questionable whether it'll benefit much performance wise.
     */
    if (pThis->cbBufValid > 0)
    {
        if (off >= pThis->offBuf)
        {
            if (off < pThis->offBufEnd)
            {
                /* head (or all) of the request is in the buffer. */
                KSIZE cbMaxChunk = (KSIZE)(pThis->offBufEnd - off);
                KSIZE cbChunk = K_MIN(cb, cbMaxChunk);
                kHlpMemCopy(pvBuf, &pThis->pbBuf[off - pThis->offBuf], cbChunk);
                if (cbChunk == cb)
                    return 0;

                cb -= cbChunk;
                pvBuf = (KU8 *)pvBuf + cbChunk;
                off += cbChunk;
            }
        }
        else if (   offEnd > pThis->offBuf
                 && offEnd <= pThis->offBufEnd)
        {
            /* the end of the request is in the buffer. */
            KSIZE cbChunk = (KSIZE)(pThis->offBufEnd - (offEnd));
            kHlpMemCopy((KU8 *)pvBuf + (pThis->offBuf - off), pThis->pbBuf, cbChunk);
            kRdrAssert(cbChunk < cb);
            cb -= cbChunk;
            offEnd -= cbChunk;
        }
    }

    /*
     * If the buffer is larger than the read request, read a full buffer
     * starting at the requested offset. Otherwise perform an unbuffered
     * read.
     */
    if (pThis->cbBuf > cb)
    {
        int rc = krdrBufFillBuffer(pThis, off);
        if (rc)
            return rc;
        if (pThis->offBuf == off)
            kHlpMemCopy(pvBuf, pThis->pbBuf, cb);
        else
        {
            kRdrAssert(off > pThis->offBuf);
            kRdrAssert(off + cb <= pThis->offBufEnd);
            kHlpMemCopy(pvBuf, pThis->pbBuf + (off - pThis->offBuf), cb);
        }
    }
    else
    {
        int rc = pThis->pRdr->pOps->pfnRead(pThis->pRdr, pvBuf, cb, off);
        if (rc)
            return rc;
    }
    return 0;
}


/** @copydoc KRDROPS::pfnDestroy */
static int krdrBufDestroy(PKRDR pRdr)
{
    PKRDRBUF pThis = (PKRDRBUF)pRdr;

    /* Close the kRdr instance that we're wrapping. */
    if (pThis->fCloseIt)
    {
        int rc = pThis->pRdr->pOps->pfnDestroy(pThis->pRdr);
        if (rc)
            return rc;
        pThis->fCloseIt = K_FALSE;
        pThis->pRdr = NULL;
    }

    kHlpFree(pThis->pbBuf);
    pThis->pbBuf = NULL;
    kHlpFree(pRdr);
    return 0;
}


/** @copydoc KRDROPS::pfnCreate */
static int krdrBufCreate(PPKRDR ppRdr, const char *pszFilename)
{
    K_NOREF(ppRdr);
    K_NOREF(pszFilename);
    return KERR_NOT_IMPLEMENTED;
}


/**
 * Worker for kRdrBufOpen and kRdrBufWrap.
 *
 * It's essentially kRdrBufWrap without error checking.
 *
 * @returns 0 on success, one of the kErrors status code on failure.
 * @param   ppRdr           Where to store the new file provider instance.
 * @param   pRdrWrapped     The file provider instance to buffer.
 * @param   fCloseIt        Whether it the pRdrWrapped instance should be closed
 *                          when the new instance is closed.
 */
static int krdrBufWrapIt(PPKRDR ppRdr, PKRDR pRdrWrapped, KBOOL fCloseIt)
{
    PKRDRBUF pThis = (PKRDRBUF)kHlpAlloc(sizeof(*pThis));
    if (pThis)
    {
        pThis->Core.u32Magic = KRDR_MAGIC;
        pThis->Core.pOps = &g_krdrBufOps;
        pThis->pRdr = pRdrWrapped;
        pThis->offFile = pRdrWrapped->pOps->pfnTell(pRdrWrapped);
        pThis->cbFile = pRdrWrapped->pOps->pfnSize(pRdrWrapped);
        pThis->offBuf = pThis->offBufEnd = 0;
        pThis->cbBufValid = 0;
        pThis->fCloseIt = fCloseIt;
        pThis->fTainedByLineQ = K_FALSE;
        if (pThis->cbFile < 128*1024)
            pThis->cbBuf = (KSIZE)pThis->cbFile + 1; /* need space for the kRdrBufLineQ terminator. */
        else
            pThis->cbBuf = 64*1024;
        pThis->pbBuf = (KU8 *)kHlpAlloc(pThis->cbBuf);
        if (pThis->pbBuf)
        {
            *ppRdr = &pThis->Core;
            return 0;
        }

        pThis->Core.u32Magic = 0;
        kHlpFree(pThis);
    }
    return KERR_NO_MEMORY;
}


/**
 * Opens a file provider with a buffered wrapper.
 *
 * @returns 0 on success, KERR_* on failure.
 * @param   ppRdr       Where to store the buffered file reader instance on success.
 * @param   pszFilename The name of the file that should be opened.
 */
KRDR_DECL(int) kRdrBufOpen(PPKRDR ppRdr, const char *pszFilename)
{
    kRdrAssertPtrReturn(ppRdr, KERR_INVALID_POINTER);
    *ppRdr = NULL;

    PKRDR pRdrWrapped;
    int rc = kRdrOpen(&pRdrWrapped, pszFilename);
    if (!rc)
    {
        rc = krdrBufWrapIt(ppRdr, pRdrWrapped, K_TRUE);
        if (rc)
            kRdrClose(pRdrWrapped);
    }
    return rc;
}


/**
 * Creates a buffered file provider instance for an existing one.
 *
 * @returns 0 on success, KERR_* on failure.
 * @param   ppRdr           Where to store the new file provider pointer.
 * @param   pRdr            The file provider instance to wrap.
 * @param   fCLoseIt        Whether it the wrapped reader should be automatically
 *                          closed when the wrapper closes.
 */
KRDR_DECL(int) kRdrBufWrap(PPKRDR ppRdr, PKRDR pRdr, KBOOL fCloseIt)
{
    KRDR_VALIDATE(pRdr);
    return krdrBufWrapIt(ppRdr, pRdr, fCloseIt);
}


/**
 * Checks whether the file provider instance is of the buffered type or not.
 *
 * @returns K_TRUE if it is, otherwise K_FALSE.
 * @param   pRdr            The file provider instance to check.
 */
KRDR_DECL(KBOOL) kRdrBufIsBuffered(PKRDR pRdr)
{
    KRDR_VALIDATE_EX(pRdr, K_FALSE);
    return pRdr->pOps == &g_krdrBufOps;
}


/**
 * Reads a line from a buffered file provider.
 *
 * The trailing '\n' or '\r\n' is stripped.
 *
 * @returns 0 on success. KERR_* on failure.
 * @retval  KRDR_ERR_LINE_TOO_LONG if the line is too long to fit in the passed in buffer.
 * @retval  KRDR_ERR_NOT_BUFFERED_RDR if pRdr isn't a buffered reader.
 * @param   pRdr        The buffered file reader.
 * @param   pszLine     Where to store the line.
 * @param   cbLine      The size of the the line buffer.
 */
KRDR_DECL(int) kRdrBufLine(PKRDR pRdr, char *pszLine, KSIZE cbLine)
{
    return kRdrBufLineEx(pRdr, pszLine, &cbLine);
}


/**
 * Reads a line from a buffered file provider.
 *
 * The trailing '\n' or '\r\n' is stripped.
 *
 * @returns 0 on success. KERR_* on failure.
 * @retval  KRDR_ERR_LINE_TOO_LONG if the line is too long to fit in the passed in buffer.
 * @retval  KRDR_ERR_NOT_BUFFERED_RDR if pRdr isn't a buffered reader.
 * @param   pRdr        The buffered file reader.
 * @param   pszLine     Where to store the line.
 * @param   pcbLine     The size of the the line buffer on input, the length of the
 *                      returned line on output.
 */
KRDR_DECL(int) kRdrBufLineEx(PKRDR pRdr, char *pszLine, KSIZE *pcbLine)
{
    /*
     * Validate input.
     */
    kRdrAssertPtrReturn(pcbLine, KERR_INVALID_POINTER);
    KSIZE cbLeft = *pcbLine;
    *pcbLine = 0;
    kRdrAssertReturn(cbLeft > 0, KERR_INVALID_PARAMETER);
    KRDR_VALIDATE(pRdr);
    kRdrAssertReturn(pRdr->pOps != &g_krdrBufOps, KRDR_ERR_NOT_BUFFERED_RDR);
    kRdrAssertPtrReturn(pszLine, KERR_INVALID_POINTER);

    /* check for EOF */
    PKRDRBUF pThis = (PKRDRBUF)pRdr;
    if (pThis->offFile >= pThis->cbFile)
    {
        kRdrAssert(pThis->offFile == pThis->cbFile);
        *pszLine = '\0';
        *pcbLine = 0;
        return KERR_EOF;
    }

    /*
     * Scratch the buffer if kRdrBufLineQ has tained it.
     */
    if (pThis->fTainedByLineQ)
    {
        pThis->offBuf = pThis->offBufEnd = 0;
        pThis->cbBufValid = 0;
    }

    /*
     * Buffered read loop.
     *
     * The overflow logic is a bit fishy wrt to overflowing at an "\r\n"
     * that arrives at a buffer boundrary. The current policy is to try
     * our best to not to fail with overflow in the EOL sequence or EOF.
     * If it's the end of the buffer, it will not be refilled just to
     * check for this because that's too much work.
     */
    cbLeft--; /* reserve space for the terminator. */
    char *pszOut = pszLine;
    for (;;)
    {
        /*
         * Do we need to (re-)fill the buffer or does it contain something
         * that we can work on already?
         */
        if (    !pThis->cbBufValid
            ||  pThis->offFile >= pThis->offBufEnd
            ||  pThis->offFile < pThis->offBuf)
        {
            int rc = krdrBufFillBuffer(pThis, pThis->offFile);
            if (rc)
            {
                *pszOut = '\0';
                return rc;
            }
        }

        /*
         * Parse the buffer looking for the EOL indicator.
         */
        kRdrAssert(pThis->offFile >= pThis->offBuf && pThis->offFile < pThis->offBufEnd);
        kRdrAssert(sizeof(char) == sizeof(*pThis->pbBuf));
        const char * const pszStart = (const char *)&pThis->pbBuf[pThis->offFile - pThis->offBuf];
        const char * const pszEnd   = (const char *)&pThis->pbBuf[pThis->cbBufValid];
        const char *psz = pszStart;
        while (psz < pszEnd)
        {
            const char ch = *psz;
            if (ch == '\n')
            {
                /* found the EOL, update file position and line length. */
                pThis->offFile += psz - pszStart + 1;
                *pcbLine += psz - pszStart;

                /* terminate the string, checking for "\r\n" first. */
                if (    *pcbLine
                    &&  pszOut[-1] == '\r')
                {
                    *pcbLine -= 1;
                    pszOut--;
                }
                *pszOut = '\0';
                return 0;
            }
            if (!cbLeft)
            {
                /* the line is *probably* too long. */
                pThis->offFile += psz - pszStart;
                *pcbLine += psz - pszStart;
                *pszOut = '\0';

                /* The only possible case where the line actually isn't too long
                   is if we're at a "\r\n" sequence. We will re-fill the buffer
                   if necessary to check for the '\n' as it's not that much work. */
                if (    ch == '\r'
                    &&  pThis->offFile + 2 <= pThis->cbFile)
                {
                    if (psz + 1 >= pszEnd)
                    {
                        int rc = krdrBufFillBuffer(pThis, pThis->offFile);
                        if (rc)
                        {
                            *pszOut = '\0';
                            return rc;
                        }
                    }
                    psz = (const char *)&pThis->pbBuf[pThis->offFile - pThis->offBuf];
                    kRdrAssert(*psz == '\r');
                    if (psz[1] == '\n')
                    {
                        *pcbLine -= 1;
                        pszOut[-1] = '\0';
                        pThis->offFile += 2;
                        return 0;
                    }
                }
                return KRDR_ERR_LINE_TOO_LONG;
            }

            /* copy and advance */
            *pszOut++ = ch;
            cbLeft--;
            psz++;
        }

        /* advance past the buffer and check for EOF. */
        *pcbLine += pszEnd - pszStart;
        pThis->offFile = pThis->offBufEnd;
        if (pThis->offFile >= pThis->cbFile)
        {
            kRdrAssert(pThis->offFile == pThis->cbFile);
            *pszOut = '\0';
            return 0;
        }
    }
}


/**
 * Worker for kRdrBufLineQ that searches the current buffer for EOL or EOF.
 *
 * When a EOF marker is found
 *
 *
 * @returns NULL if EOL/EOF isn't found the buffer.
 * @param   pThis   The buffered reader instance.
 */
static const char * krdrBufLineQWorker(PKRDRBUF pThis)
{
    kRdrAssert(pThis->offFile >= pThis->offBuf && pThis->offFile < pThis->offBufEnd);

    /*
     * Search the buffer.
     */
    kRdrAssert(sizeof(char) == sizeof(*pThis->pbBuf));
    const char * const pszStart = (const char *)&pThis->pbBuf[pThis->offFile - pThis->offBuf];
    const char * const pszEnd   = (const char *)&pThis->pbBuf[pThis->cbBufValid];
    char *psz = (char *)pszStart;
    while (psz < pszEnd)
    {
        char ch = *psz;
        if (ch == '\n')
        {
            pThis->offFile += psz - pszStart;
            pThis->fTainedByLineQ = K_TRUE;
            *psz = '\0';
            if (    psz > pszStart
                &&  psz[-1] == '\r')
                *--psz = '\0';
            return pszStart;
        }
        psz++;
    }

    /*
     * Check for EOF. There must be room for a terminator char here.
     */
    if (    pThis->offBufEnd >= pThis->cbFile
        &&  (pThis->offBufEnd - pThis->offBuf) < (KSSIZE)pThis->cbBuf)
    {
        pThis->offFile = pThis->cbFile;
        pThis->pbBuf[pThis->cbBufValid] = '\0';
        return pszStart;
    }

    return NULL;
}


/**
 * Get the pointer to the next next line in the buffer.
 * The returned line is zero terminated.
 *
 * @returns A pointer to the line on success. This becomes invalid
 *          upon the next call to this kRdr instance.
 * @returns NULL on EOF, read error of if the line was too long.
 * @param   pRdr        The buffered file reader.
 */
KRDR_DECL(const char *) kRdrBufLineQ(PKRDR pRdr)
{
    /*
     * Validate input.
     */
    KRDR_VALIDATE_EX(pRdr, NULL);
    kRdrAssertReturn(pRdr->pOps != &g_krdrBufOps, NULL);

    /* check for EOF */
    PKRDRBUF pThis = (PKRDRBUF)pRdr;
    if (pThis->offFile >= pThis->cbFile)
    {
        kRdrAssert(pThis->offFile == pThis->cbFile);
        return NULL;
    }

    /*
     * Search the current buffer if possible
     */
    if (    pThis->cbBufValid
        &&  pThis->offFile >= pThis->offBuf
        &&  pThis->offFile < pThis->offBufEnd)
    {
        const char *psz = krdrBufLineQWorker(pThis);
        if (psz)
            return psz;
    }

    /*
     * Fill the buffer in an optimal way and look for the EOL/EOF (again).
     */
    int rc = krdrBufFillBuffer(pThis, pThis->offFile);
    if (rc)
        return NULL;
    return krdrBufLineQWorker(pThis);
}