Index: /trunk/kStuff/include/k/kErrors.h
===================================================================
--- /trunk/kStuff/include/k/kErrors.h	(revision 3547)
+++ /trunk/kStuff/include/k/kErrors.h	(revision 3548)
@@ -74,4 +74,6 @@
 /** The specified file was not found. */
 #define KERR_FILE_NOT_FOUND                             (KERR_FILE_SYSTEM_AND_IO_BASE + 0)
+/** End of file. */
+#define KERR_EOF                                        (KERR_FILE_SYSTEM_AND_IO_BASE + 1)
 /** @} */
 
@@ -80,5 +82,5 @@
  * @{ */
 /** The base of the kDbg specific status codes. */
-#define KDBG_ERR_BASE                                   (KERR_FILE_SYSTEM_AND_IO_BASE + 1)
+#define KDBG_ERR_BASE                                   (KERR_FILE_SYSTEM_AND_IO_BASE + 2)
 /** The (module) format isn't known to use. */
 #define KDBG_ERR_UNKOWN_FORMAT                          (KDBG_ERR_BASE + 0)
@@ -96,5 +98,9 @@
 #define KRDR_ERR_BASE                                   (KDBG_ERR_BASE + 4)
 /** The file reader can't take more concurrent mappings. */
-#define KRDR_ERR_TOO_MANY_MAPPINGS                      (KRDR_ERR_BASE + 59)
+#define KRDR_ERR_TOO_MANY_MAPPINGS                      (KRDR_ERR_BASE + 0)
+/** The pRdr instance passed to a kRdrBuf* API isn't a buffered instance. */
+#define KRDR_ERR_NOT_BUFFERED_RDR                       (KRDR_ERR_BASE + 1)
+/** The line is too long to fit in the buffer passed to kRdrBufLine or kRdrBufLineEx. */
+#define KRDR_ERR_LINE_TOO_LONG                          (KRDR_ERR_BASE + 2)
 /** @} */
 
Index: /trunk/kStuff/include/k/kRdr.h
===================================================================
--- /trunk/kStuff/include/k/kRdr.h	(revision 3547)
+++ /trunk/kStuff/include/k/kRdr.h	(revision 3548)
@@ -34,4 +34,16 @@
  * @{ */
 
+/** @def KRDR_DECL
+ * Declares a kRdr function according to build context.
+ * @param type          The return type.
+ */
+#if defined(KRDR_BUILDING_DYNAMIC)
+# define KRDR_DECL(type)    K_DECL_EXPORT(type)
+#elif defined(KRDR_BUILT_DYNAMIC)
+# define KRDR_DECL(type)    K_DECL_IMPORT(type)
+#else
+# define KRDR_DECL(type)    type
+#endif
+
 /** Pointer to a file provider instance. */
 typedef struct KRDR *PKRDR;
@@ -43,22 +55,27 @@
 #endif
 
-int     kRdrOpen(PPKRDR ppRdr, const char *pszFilename);
-int     kRdrOpenBuffered(PPKRDR ppRdr, const char *pszFilename);
-int     kRdrClose(   PKRDR pRdr);
-int     kRdrRead(    PKRDR pRdr, void *pvBuf, KSIZE cb, KFOFF off);
-int     kRdrAllMap(  PKRDR pRdr, const void **ppvBits);
-int     kRdrAllUnmap(PKRDR pRdr, const void *pvBits);
-KFOFF   kRdrSize(    PKRDR pRdr);
-KFOFF   kRdrTell(    PKRDR pRdr);
-const char *kRdrName(PKRDR pRdr);
-KIPTR   kRdrNativeFH(PKRDR pRdr);
-KSIZE   kRdrPageSize(PKRDR pRdr);
+KRDR_DECL(int)      kRdrOpen(   PPKRDR ppRdr, const char *pszFilename);
+KRDR_DECL(int)      kRdrClose(    PKRDR pRdr);
+KRDR_DECL(int)      kRdrRead(     PKRDR pRdr, void *pvBuf, KSIZE cb, KFOFF off);
+KRDR_DECL(int)      kRdrAllMap(   PKRDR pRdr, const void **ppvBits);
+KRDR_DECL(int)      kRdrAllUnmap( PKRDR pRdr, const void *pvBits);
+KRDR_DECL(KFOFF)    kRdrSize(     PKRDR pRdr);
+KRDR_DECL(KFOFF)    kRdrTell(     PKRDR pRdr);
+KRDR_DECL(const char *) kRdrName( PKRDR pRdr);
+KRDR_DECL(KIPTR)    kRdrNativeFH( PKRDR pRdr);
+KRDR_DECL(KSIZE)    kRdrPageSize( PKRDR pRdr);
 #ifndef ___k_kLdr___
-int     kRdrMap(     PKRDR pRdr, void **ppvBase, KU32 cSegments, PCKLDRSEG paSegments, KBOOL fFixed);
-int     kRdrRefresh( PKRDR pRdr, void *pvBase, KU32 cSegments, PCKLDRSEG paSegments);
-int     kRdrProtect( PKRDR pRdr, void *pvBase, KU32 cSegments, PCKLDRSEG paSegments, KBOOL fUnprotectOrProtect);
-int     kRdrUnmap(   PKRDR pRdr, void *pvBase, KU32 cSegments, PCKLDRSEG paSegments);
+KRDR_DECL(int)      kRdrMap(      PKRDR pRdr, void **ppvBase, KU32 cSegments, PCKLDRSEG paSegments, KBOOL fFixed);
+KRDR_DECL(int)      kRdrRefresh(  PKRDR pRdr, void *pvBase, KU32 cSegments, PCKLDRSEG paSegments);
+KRDR_DECL(int)      kRdrProtect(  PKRDR pRdr, void *pvBase, KU32 cSegments, PCKLDRSEG paSegments, KBOOL fUnprotectOrProtect);
+KRDR_DECL(int)      kRdrUnmap(    PKRDR pRdr, void *pvBase, KU32 cSegments, PCKLDRSEG paSegments);
 #endif /* !___k_kLdr___ */
-void    kRdrDone(    PKRDR pRdr);
+KRDR_DECL(void)     kRdrDone(PKRDR pRdr);
+
+KRDR_DECL(int)      kRdrBufOpen(PPKRDR ppRdr, const char *pszFilename);
+KRDR_DECL(int)      kRdrBufWrap(PPKRDR ppRdr, PKRDR pRdr);
+KRDR_DECL(int)      kRdrBufLine(  PKRDR pRdr, char *pszLine, KSIZE cbLine);
+KRDR_DECL(int)      kRdrBufLineEx(PKRDR pRdr, char *pszLine, KSIZE *pcbLine);
+KRDR_DECL(const char *) kRdrBufLineQ(PKRDR pRdr);
 
 #ifdef __cplusplus
Index: /trunk/kStuff/kRdr/Makefile.kmk
===================================================================
--- /trunk/kStuff/kRdr/Makefile.kmk	(revision 3547)
+++ /trunk/kStuff/kRdr/Makefile.kmk	(revision 3548)
@@ -37,5 +37,6 @@
 kRdrStatic_SOURCES = \
 	kRdr.cpp \
-	kRdrFile.cpp
+	kRdrFile.cpp \
+	kRdrBuffered.cpp
 
 # Generate the rules
Index: /trunk/kStuff/kRdr/kRdrBuffered.cpp
===================================================================
--- /trunk/kStuff/kRdr/kRdrBuffered.cpp	(revision 3548)
+++ /trunk/kStuff/kRdr/kRdrBuffered.cpp	(revision 3548)
@@ -0,0 +1,729 @@
+/* $Id$ */
+/** @file
+ * kRdrBuffered - Buffered File Provider.
+ */
+
+/*
+ * Copyright (c) 2007 knut st. osmundsen <bird-src-spam@anduin.net>
+ *
+ * This file is part of kStuff.
+ *
+ * kStuff is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * kStuff is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with kStuff; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+/*******************************************************************************
+*   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;
+    /** Did we open the pRdr instance or was it handed to us? */
+    KBOOL               fOpenedRdr;
+    /** 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 (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->fOpenedRdr)
+    {
+        int rc = pThis->pRdr->pOps->pfnDestroy(pThis->pRdr);
+        if (rc)
+            return rc;
+        pThis->fOpenedRdr = 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)
+{
+    return KERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * Worker for kRdrBufOpen and kRdrBufWrap.
+ *
+ * It's essentially kRdrBufWrap without some error checking and an extra argument.
+ *
+ * @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   fOpenedIt       Whether it was opened or not. If set we'll close
+ *                          pRdrWrapped on failure.
+ */
+static int krdrBufWrapIt(PPKRDR ppRdr, PKRDR pRdrWrapped, KBOOL fOpenedIt)
+{
+    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->fOpenedRdr = fOpenedIt;
+        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);
+    }
+    if (fOpenedIt)
+        kRdrClose(pRdrWrapped);
+    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)
+        return rc;
+
+    return krdrBufWrapIt(ppRdr, pRdrWrapped, K_TRUE);
+}
+
+
+/**
+ * Creates a buffered file provider instance for an existing one.
+ *
+ * @returns 0 on success, KERR_* on failure.
+ * @param   ppRdr
+ * @param   pRdr
+ */
+KRDR_DECL(int) kRdrBufWrap(PPKRDR ppRdr, PKRDR pRdr)
+{
+    KRDR_VALIDATE(pRdr);
+    return krdrBufWrapIt(ppRdr, pRdr, K_FALSE);
+}
+
+
+/**
+ * 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;
+        }
+    }
+    return -1;
+}
+
+
+/**
+ * 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) < 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);
+}
+
Index: /trunk/kStuff/kRdr/kRdrInternal.h
===================================================================
--- /trunk/kStuff/kRdr/kRdrInternal.h	(revision 3547)
+++ /trunk/kStuff/kRdr/kRdrInternal.h	(revision 3548)
@@ -93,5 +93,6 @@
 #define KRDR_VALIDATE_VOID(pRdr) \
     do  { \
-        if (    (pRdr)->u32Magic != KRDR_MAGIC \
+        if (    !K_VALID_PTR(pRdr) \
+            ||  (pRdr)->u32Magic != KRDR_MAGIC \
             ||  (pRdr)->pOps == NULL \
            )\
