Index: /trunk/src/kObjCache/Makefile.kmk
===================================================================
--- /trunk/src/kObjCache/Makefile.kmk	(revision 1039)
+++ /trunk/src/kObjCache/Makefile.kmk	(revision 1040)
@@ -29,5 +29,7 @@
 include $(PATH_KBUILD)/subheader.kmk
 
+ifeq ($(USERNAME),bird) # disable it for now.
 PROGRAMS += kObjCache
+endif
 kObjCache_TEMPLATE = BIN
 kObjCache_SOURCES = kObjCache.c
Index: /trunk/src/kObjCache/kObjCache.c
===================================================================
--- /trunk/src/kObjCache/kObjCache.c	(revision 1039)
+++ /trunk/src/kObjCache/kObjCache.c	(revision 1040)
@@ -48,4 +48,7 @@
 #  include <unistd.h>
 # endif
+# if defined(_MSC_VER)
+   typedef intptr_t pid_t;
+# endif
 #else
 # include <unistd.h>
@@ -55,6 +58,11 @@
 # endif
 #endif
+#if defined(__WIN__)
+# include <Windows.h>
+#endif
+
 #include "crc32.h"
 #include "md5.h"
+
 
 /*******************************************************************************
@@ -75,74 +83,4 @@
 # define IS_SLASH_DRV(ch)   ((ch) == '/')
 #endif
-/** Use pipe instead of temp files when possible (speed). */
-#define USE_PIPE 1
-
-
-
-/*******************************************************************************
-*   Structures and Typedefs                                                    *
-*******************************************************************************/
-/** A checksum list entry.
- * We keep a list checksums (of precompiler output) that matches, The planned
- * matching algorithm doesn't require the precompiler output to be indentical,
- * only to produce the same object files.
- */
-typedef struct KOCSUM
-{
-    /** The next checksum. */
-    struct KOCSUM *pNext;
-    /** The crc32 checksum. */
-    uint32_t crc32;
-    /** The MD5 digest. */
-    unsigned char md5[16];
-} KOCSUM, *PKOCSUM;
-/** Pointer to a const KOCSUM. */
-typedef const KOCSUM *PCKOCSUM;
-
-/**
- * The object cache data.
- */
-typedef struct KOBJCACHE
-{
-    /** The cache dir that all other names are relative to. */
-    char *pszDir;
-    /** The name of the cache file. */
-    const char *pszName;
-    /** Set if the object needs to be (re)compiled. */
-    unsigned fNeedCompiling;
-    /** Whether the precompiler runs in piped mode. If clear it's file
-     * mode (it could be redirected stdout, but that's essentially the
-     * same from our point of view). */
-    unsigned fPiped;
-
-    /** The name of new precompiled output. */
-    const char *pszNewCppName;
-    /** Pointer to the 'mapping' of the new precompiled output. */
-    char *pszNewCppMapping;
-    /** The size of the new precompiled output 'mapping'. */
-    size_t cbNewCpp;
-    /** The new checksum. */
-    KOCSUM NewSum;
-    /** The new object filename (relative to the cache file). */
-    char *pszNewObjName;
-
-    /** The name of the precompiled output. (relative to the cache file) */
-    char *pszOldCppName;
-    /** Pointer to the 'mapping' of the old precompiled output. */
-    char *pszOldCppMapping;
-    /** The size of the old precompiled output. */
-    size_t cbOldCpp;
-
-    /** The head of the checksum list. */
-    KOCSUM SumHead;
-    /** The object filename (relative to the cache file). */
-    char *pszObjName;
-    /** The compile argument vector used to build the object. */
-    char **papszArgvCompile;
-    /** The size of the compile  */
-    unsigned cArgvCompile;
-} KOBJCACHE, *PKOBJCACHE;
-/** Pointer to a const KOBJCACHE. */
-typedef const KOBJCACHE *PCKOBJCACHE;
 
 
@@ -151,5 +89,10 @@
 *******************************************************************************/
 /** Whether verbose output is enabled. */
-static int g_fVerbose = 0;
+static unsigned g_cVerbosityLevel = 0;
+/** What to prefix the errors with. */
+static char g_szErrorPrefix[128];
+
+/** Read buffer shared by the cache components. */
+static char g_szLine[KOBJCACHE_MAX_LINE_LEN + 16];
 
 
@@ -157,6 +100,4 @@
 *   Internal Functions                                                         *
 *******************************************************************************/
-static const char *FindFilenameInPath(const char *pszPath);
-static char *AbsPath(const char *pszPath);
 static char *MakePathFromDirAndFile(const char *pszName, const char *pszDir);
 static char *CalcRelativeName(const char *pszPath, const char *pszDir);
@@ -166,7 +107,738 @@
 static int DoesFileInDirExist(const char *pszName, const char *pszDir);
 static void *ReadFileInDir(const char *pszName, const char *pszDir, size_t *pcbFile);
-static void *xmalloc(size_t);
-static void *xrealloc(void *, size_t);
-static char *xstrdup(const char *);
+
+
+void FatalMsg(const char *pszFormat, ...)
+{
+    va_list va;
+
+    if (g_szErrorPrefix[0])
+        fprintf(stderr, "%s - fatal error: ", g_szErrorPrefix);
+    else
+        fprintf(stderr, "fatal error: ");
+
+    va_start(va, pszFormat);
+    vfprintf(stderr, pszFormat, va);
+    va_end(va);
+}
+
+
+void FatalDie(const char *pszFormat, ...)
+{
+    va_list va;
+
+    if (g_szErrorPrefix[0])
+        fprintf(stderr, "%s - fatal error: ", g_szErrorPrefix);
+    else
+        fprintf(stderr, "fatal error: ");
+
+    va_start(va, pszFormat);
+    vfprintf(stderr, pszFormat, va);
+    va_end(va);
+
+    exit(1);
+}
+
+
+static void ErrorMsg(const char *pszFormat, ...)
+{
+    va_list va;
+
+    if (g_szErrorPrefix[0])
+        fprintf(stderr, "%s - error: ", g_szErrorPrefix);
+    else
+        fprintf(stderr, "error: ");
+
+    va_start(va, pszFormat);
+    vfprintf(stderr, pszFormat, va);
+    va_end(va);
+}
+
+
+static void InfoMsg(unsigned uLevel, const char *pszFormat, ...)
+{
+    if (uLevel <= g_cVerbosityLevel)
+    {
+        va_list va;
+
+        if (g_szErrorPrefix[0])
+            fprintf(stderr, "%s - info: ", g_szErrorPrefix);
+        else
+            fprintf(stderr, "info: ");
+
+        va_start(va, pszFormat);
+        vfprintf(stderr, pszFormat, va);
+        va_end(va);
+    }
+}
+
+
+static void SetErrorPrefix(const char *pszPrefix, ...)
+{
+    int cch;
+    va_list va;
+
+    va_start(va, pszPrefix);
+#if defined(_MSC_VER) || defined(__sun__)
+    cch = vsprintf(g_szErrorPrefix, pszPrefix, va);
+    if (cch >= sizeof(g_szErrorPrefix))
+        FatalDie("Buffer overflow setting error prefix!\n");
+#else
+    vsnprintf(g_szErrorPrefix, sizeof(g_szErrorPrefix), pszPrefix, va);
+#endif
+    va_end(va);
+    (void)cch;
+}
+
+
+void *xmalloc(size_t cb)
+{
+    void *pv = malloc(cb);
+    if (!pv)
+        FatalDie(NULL, "out of memory (%d)\n", (int)cb);
+    return pv;
+}
+
+
+void *xmallocz(size_t cb)
+{
+    void *pv = xmalloc(cb);
+    memset(pv, 0, cb);
+    return pv;
+}
+
+
+void *xrealloc(void *pvOld, size_t cb)
+{
+    void *pv = realloc(pvOld, cb);
+    if (!pv)
+        FatalDie(NULL, "out of memory (%d)\n", (int)cb);
+    return pv;
+}
+
+
+char *xstrdup(const char *pszIn)
+{
+    char *psz = strdup(pszIn);
+    if (!psz)
+        FatalDie(NULL, "out of memory (%d)\n", (int)strlen(pszIn));
+    return psz;
+}
+
+
+
+/**
+ * Gets the absolute path
+ *
+ * @returns A new heap buffer containing the absolute path.
+ * @param   pszPath     The path to make absolute. (Readonly)
+ */
+static char *AbsPath(const char *pszPath)
+{
+    char szTmp[PATH_MAX];
+#if defined(__OS2__) || defined(__WIN__)
+    if (!_fullpath(szTmp, *pszPath ? pszPath : ".", sizeof(szTmp)))
+        return xstrdup(pszPath);
+#else
+    if (!realpath(pszPath, szTmp))
+        return xstrdup(pszPath);
+#endif
+   return xstrdup(szTmp);
+}
+
+
+/**
+ * Utility function that finds the filename part in a path.
+ *
+ * @returns Pointer to the file name part (this may be "").
+ * @param   pszPath     The path to parse.
+ */
+static const char *FindFilenameInPath(const char *pszPath)
+{
+    const char *pszFilename = strchr(pszPath, '\0') - 1;
+    while (     pszFilename > pszPath
+           &&   !IS_SLASH_DRV(pszFilename[-1]))
+        pszFilename--;
+    return pszFilename;
+}
+
+
+/**
+ * Utility function that combines a filename and a directory into a path.
+ *
+ * @returns malloced buffer containing the result.
+ * @param   pszName     The file name.
+ * @param   pszDir      The directory path.
+ */
+static char *MakePathFromDirAndFile(const char *pszName, const char *pszDir)
+{
+    size_t cchName = strlen(pszName);
+    size_t cchDir = strlen(pszDir);
+    char *pszBuf = xmalloc(cchName + cchDir + 2);
+    memcpy(pszBuf, pszDir, cchDir);
+    if (cchDir > 0 && !IS_SLASH_DRV(pszDir[cchDir - 1]))
+        pszBuf[cchDir++] = PATH_SLASH;
+    memcpy(pszBuf + cchDir, pszName, cchName + 1);
+    return pszBuf;
+}
+
+
+/**
+ * Compares two path strings to see if they are identical.
+ *
+ * This doesn't do anything fancy, just the case ignoring and
+ * slash unification.
+ *
+ * @returns 1 if equal, 0 otherwise.
+ * @param   pszPath1    The first path.
+ * @param   pszPath2    The second path.
+ * @param   cch         The number of characters to compare.
+ */
+static int ArePathsIdentical(const char *pszPath1, const char *pszPath2, size_t cch)
+{
+#if defined(__OS2__) || defined(__WIN__)
+    if (strnicmp(pszPath1, pszPath2, cch))
+    {
+        /* Slashes may differ, compare char by char. */
+        const char *psz1 = pszPath1;
+        const char *psz2 = pszPath2;
+        for (;cch; psz1++, psz2++, cch--)
+        {
+            if (*psz1 != *psz2)
+            {
+                if (    tolower(*psz1) != tolower(*psz2)
+                    &&  toupper(*psz1) != toupper(*psz2)
+                    &&  *psz1 != '/'
+                    &&  *psz1 != '\\'
+                    &&  *psz2 != '/'
+                    &&  *psz2 != '\\')
+                    return 0;
+            }
+        }
+    }
+    return 1;
+#else
+    return !strncmp(pszPath1, pszPath2, cch);
+#endif
+}
+
+
+/**
+ * Calculate how to get to pszPath from pszDir.
+ *
+ * @returns The relative path from pszDir to path pszPath.
+ * @param   pszPath     The path to the object.
+ * @param   pszDir      The directory it shall be relative to.
+ */
+static char *CalcRelativeName(const char *pszPath, const char *pszDir)
+{
+    char *pszRet = NULL;
+    char *pszAbsPath = NULL;
+    size_t cchDir = strlen(pszDir);
+
+    /*
+     * This is indeed a bit tricky, so we'll try the easy way first...
+     */
+    if (ArePathsIdentical(pszPath, pszDir, cchDir))
+    {
+        if (pszPath[cchDir])
+            pszRet = (char *)pszPath + cchDir;
+        else
+            pszRet = "./";
+    }
+    else
+    {
+        pszAbsPath = AbsPath(pszPath);
+        if (ArePathsIdentical(pszAbsPath, pszDir, cchDir))
+        {
+            if (pszPath[cchDir])
+                pszRet = pszAbsPath + cchDir;
+            else
+                pszRet = "./";
+        }
+    }
+    if (pszRet)
+    {
+        while (IS_SLASH_DRV(*pszRet))
+            pszRet++;
+        pszRet = xstrdup(pszRet);
+        free(pszAbsPath);
+        return pszRet;
+    }
+
+    /*
+     * Damn, it's gonna be complicated. Deal with that later.
+     */
+    FatalDie("complicated relative path stuff isn't implemented yet. sorry.\n");
+    return NULL;
+}
+
+
+/**
+ * Utility function that combines a filename and directory and passes it onto fopen.
+ *
+ * @returns fopen return value.
+ * @param   pszName     The file name.
+ * @param   pszDir      The directory path.
+ * @param   pszMode     The fopen mode string.
+ */
+static FILE *FOpenFileInDir(const char *pszName, const char *pszDir, const char *pszMode)
+{
+    char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
+    FILE *pFile = fopen(pszPath, pszMode);
+    free(pszPath);
+    return pFile;
+}
+
+
+/**
+ * Utility function that combines a filename and directory and passes it onto open.
+ *
+ * @returns open return value.
+ * @param   pszName     The file name.
+ * @param   pszDir      The directory path.
+ * @param   fFlags      The open flags.
+ * @param   fCreateMode The file creation mode.
+ */
+static int OpenFileInDir(const char *pszName, const char *pszDir, int fFlags, int fCreateMode)
+{
+    char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
+    int fd = open(pszPath, fFlags, fCreateMode);
+    free(pszPath);
+    return fd;
+}
+
+
+
+/**
+ * Deletes a file in a directory.
+ *
+ * @returns whatever unlink returns.
+ * @param   pszName     The file name.
+ * @param   pszDir      The directory path.
+ */
+static int UnlinkFileInDir(const char *pszName, const char *pszDir)
+{
+    char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
+    int rc = unlink(pszPath);
+    free(pszPath);
+    return rc;
+}
+
+
+/**
+ * Renames a file in a directory.
+ *
+ * @returns whatever rename returns.
+ * @param   pszOldName  The new file name.
+ * @param   pszNewName  The old file name.
+ * @param   pszDir      The directory path.
+ */
+static int RenameFileInDir(const char *pszOldName, const char *pszNewName, const char *pszDir)
+{
+    char *pszOldPath = MakePathFromDirAndFile(pszOldName, pszDir);
+    char *pszNewPath = MakePathFromDirAndFile(pszNewName, pszDir);
+    int rc = rename(pszOldPath, pszNewPath);
+    free(pszOldPath);
+    free(pszNewPath);
+    return rc;
+}
+
+
+/**
+ * Check if a (regular) file exists in a directory.
+ *
+ * @returns 1 if it exists and is a regular file, 0 if not.
+ * @param   pszName     The file name.
+ * @param   pszDir      The directory path.
+ */
+static int DoesFileInDirExist(const char *pszName, const char *pszDir)
+{
+    char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
+    struct stat st;
+    int rc = stat(pszPath, &st);
+    free(pszPath);
+#ifdef S_ISREG
+    return !rc && S_ISREG(st.st_mode);
+#elif defined(_MSC_VER)
+    return !rc && (st.st_mode & _S_IFMT) == _S_IFREG;
+#else
+#error "Port me"
+#endif
+}
+
+
+/**
+ * Reads into memory an entire file.
+ *
+ * @returns Pointer to the heap allocation containing the file.
+ *          On failure NULL and errno is returned.
+ * @param   pszName     The file.
+ * @param   pszDir      The directory the file resides in.
+ * @param   pcbFile     Where to store the file size.
+ */
+static void *ReadFileInDir(const char *pszName, const char *pszDir, size_t *pcbFile)
+{
+    int SavedErrno;
+    char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
+    int fd = open(pszPath, O_RDONLY | O_BINARY);
+    if (fd >= 0)
+    {
+        off_t cbFile = lseek(fd, 0, SEEK_END);
+        if (    cbFile >= 0
+            &&  lseek(fd, 0, SEEK_SET) == 0)
+        {
+            char *pb = malloc(cbFile + 1);
+            if (pb)
+            {
+                if (read(fd, pb, cbFile) == cbFile)
+                {
+                    close(fd);
+                    pb[cbFile] = '\0';
+                    *pcbFile = (size_t)cbFile;
+                    return pb;
+                }
+                SavedErrno = errno;
+                free(pb);
+            }
+            else
+                SavedErrno = ENOMEM;
+        }
+        else
+            SavedErrno = errno;
+        close(fd);
+    }
+    else
+        SavedErrno = errno;
+    free(pszPath);
+    errno = SavedErrno;
+    return NULL;
+}
+
+
+/**
+ * Creates a directory including all necessary parent directories.
+ *
+ * @returns 0 on success, -1 + errno on failure.
+ * @param   pszDir      The directory.
+ */
+static int MakePath(const char *pszPath)
+{
+    /** @todo implement me */
+    return 0;
+}
+
+
+/**
+ * Adds the arguments found in the pszCmdLine string to argument vector.
+ *
+ * The parsing of the pszCmdLine string isn't very sophisticated, no
+ * escaping or quotes.
+ *
+ * @param   pcArgs      Pointer to the argument counter.
+ * @param   ppapszArgs  Pointer to the argument vector pointer.
+ * @param   pszCmdLine  The command line to parse and append.
+ * @param   pszWedgeArg Argument to put infront of anything found in pszCmdLine.
+ */
+static void AppendArgs(int *pcArgs, char ***ppapszArgs, const char *pszCmdLine, const char *pszWedgeArg)
+{
+    int i;
+    int cExtraArgs;
+    const char *psz;
+    char **papszArgs;
+
+    /*
+     * Count the new arguments.
+     */
+    cExtraArgs = 0;
+    psz = pszCmdLine;
+    while (*psz)
+    {
+        while (isspace(*psz))
+            psz++;
+        if (!psz)
+            break;
+        cExtraArgs++;
+        while (!isspace(*psz) && *psz)
+            psz++;
+    }
+    if (!cExtraArgs)
+        return;
+
+    /*
+     * Allocate a new vector that can hold the arguments.
+     * (Reallocating might not work since the argv might not be allocated
+     *  from the heap but off the stack or somewhere... )
+     */
+    i = *pcArgs;
+    *pcArgs = i + cExtraArgs + 1 + !!pszWedgeArg;
+    papszArgs = xmalloc(*pcArgs * sizeof(char *));
+    *ppapszArgs = memcpy(papszArgs, *ppapszArgs, i * sizeof(char *));
+
+    if (pszWedgeArg)
+        papszArgs[i++] = xstrdup(pszWedgeArg);
+
+    psz = pszCmdLine;
+    while (*psz)
+    {
+        const char *pszEnd;
+        while (isspace(*psz))
+            psz++;
+        if (!psz)
+            break;
+        pszEnd = psz;
+        while (!isspace(*pszEnd) && *pszEnd)
+            pszEnd++;
+
+        papszArgs[i] = xmalloc(psz - pszEnd + 1);
+        memcpy(papszArgs[i], psz, psz - pszEnd);
+        papszArgs[i][psz - pszEnd] = '\0';
+        i++;
+    }
+}
+
+
+
+
+
+/** A checksum list entry.
+ * We keep a list checksums (of precompiler output) that matches, The planned
+ * matching algorithm doesn't require the precompiler output to be indentical,
+ * only to produce the same object files.
+ */
+typedef struct KOCSUM
+{
+    /** The next checksum. */
+    struct KOCSUM *pNext;
+    /** The crc32 checksum. */
+    uint32_t crc32;
+    /** The MD5 digest. */
+    unsigned char md5[16];
+    /** Valid or not. */
+    unsigned fUsed;
+} KOCSUM;
+/** Pointer to a KOCSUM. */
+typedef KOCSUM *PKOCSUM;
+/** Pointer to a const KOCSUM. */
+typedef const KOCSUM *PCKOCSUM;
+
+
+/**
+ * Temporary context record used when calculating
+ * the checksum of some data.
+ */
+typedef struct KOCSUMCTX
+{
+    /** The MD5 context. */
+    struct MD5Context MD5Ctx;
+} KOCSUMCTX;
+/** Pointer to a check context record. */
+typedef KOCSUMCTX *PKOCSUMCTX;
+
+
+
+/**
+ * Initializes a checksum object with an associated context.
+ *
+ * @param   pSum    The checksum object.
+ * @param   pCtx    The checksum context.
+ */
+static void kOCSumInitWithCtx(PKOCSUM pSum, PKOCSUMCTX pCtx)
+{
+    memcmp(pSum, 0, sizeof(*pSum));
+    MD5Init(&pCtx->MD5Ctx);
+}
+
+
+/**
+ * Updates the checksum calculation.
+ *
+ * @param   pSum    The checksum.
+ * @param   pCtx    The checksum calcuation context.
+ * @param   pvBuf   The input data to checksum.
+ * @param   cbBuf   The size of the input data.
+ */
+static void kOCSumUpdate(PKOCSUM pSum, PKOCSUMCTX pCtx, const void *pvBuf, size_t cbBuf)
+{
+    /*
+     * Take in relativly small chunks to try keep it in the cache.
+     */
+    const unsigned char *pb = (const unsigned char *)pvBuf;
+    while (cbBuf > 0)
+    {
+        size_t cb = cbBuf >= 128*1024 ? 128*1024 : cbBuf;
+        pSum->crc32 = crc32(pSum->crc32, pb, cb);
+        MD5Update(&pCtx->MD5Ctx, pb, cb);
+        cbBuf -= cb;
+    }
+}
+
+
+/**
+ * Finalizes a checksum calculation.
+ *
+ * @param   pSum    The checksum.
+ * @param   pCtx    The checksum calcuation context.
+ */
+static void kOCSumFinalize(PKOCSUM pSum, PKOCSUMCTX pCtx)
+{
+    MD5Final(&pSum->md5[0], &pCtx->MD5Ctx);
+}
+
+
+/**
+ * Init a check sum chain head.
+ *
+ * @param   pSumHead    The checksum head to init.
+ */
+static void kOCSumInit(PKOCSUM pSumHead)
+{
+    memcmp(pSumHead, 0, sizeof(*pSumHead));
+}
+
+
+/**
+ * Parses the given string into a checksum head object.
+ *
+ * @returns 0 on success, -1 on format error.
+ * @param   pSumHead    The checksum head to init.
+ * @param   pszVal      The string to initialized it from.
+ */
+static int kOCSumInitFromString(PKOCSUM pSumHead, const char *pszVal)
+{
+    unsigned i;
+    char *pszNext;
+    char *pszMD5;
+
+    memset(pSumHead, 0, sizeof(*pSumHead));
+
+    pszMD5 = strchr(pszVal, ':');
+    if (pszMD5 == NULL)
+        return -1;
+    *pszMD5++ = '\0';
+
+    /* crc32 */
+    pSumHead->crc32 = (uint32_t)strtoul(pszVal, &pszNext, 16);
+    if (pszNext && *pszNext)
+        return -1;
+
+    /* md5 */
+    for (i = 0; i < sizeof(pSumHead->md5) * 2; i++)
+    {
+        unsigned char ch = pszMD5[i];
+        int x;
+        if ((unsigned char)(ch - '0') <= 9)
+            x = ch - '0';
+        else if ((unsigned char)(ch - 'a') <= 5)
+            x = ch - 'a' + 10;
+        else if ((unsigned char)(ch - 'A') <= 5)
+            x = ch - 'A' + 10;
+        else
+            return -1;
+        if (!(i & 1))
+            pSumHead->md5[i >> 1] = x << 4;
+        else
+            pSumHead->md5[i >> 1] |= x;
+    }
+
+    pSumHead->fUsed = 1;
+    return 0;
+}
+
+
+/**
+ * Delete a check sum chain.
+ *
+ * @param   pSumHead    The head of the checksum chain.
+ */
+static void kOCSumDeleteChain(PKOCSUM pSumHead)
+{
+    void *pv;
+    while ((pv = pSumHead->pNext))
+    {
+        pSumHead = pSumHead->pNext;
+        free(pv);
+    }
+    memcmp(pSumHead, 0, sizeof(*pSumHead));
+}
+
+
+/**
+ * Insert a check sum into the chain.
+ *
+ * @param   pSumHead    The head of the checksum list.
+ * @param   pSumAdd     The checksum to add (duplicate).
+ */
+static void kOCSumAdd(PKOCSUM pSumHead, PCKOCSUM pSumAdd)
+{
+    if (pSumHead->fUsed)
+    {
+        PKOCSUM pNew = xmalloc(sizeof(*pNew));
+        *pNew = *pSumAdd;
+        pNew->pNext = pSumHead->pNext;
+        pNew->fUsed = 1;
+        pSumHead->pNext = pNew;
+    }
+    else
+    {
+        *pSumHead = *pSumAdd;
+        pSumHead->pNext = NULL;
+        pSumHead->fUsed = 1;
+    }
+}
+
+
+/**
+ * Inserts an entrie chain into the given check sum chain.
+ *
+ * @param   pSumHead    The head of the checksum list.
+ * @param   pSumHeadAdd The head of the checksum list to be added.
+ */
+static void kOCSumAddChain(PKOCSUM pSumHead, PCKOCSUM pSumHeadAdd)
+{
+    while (pSumHeadAdd)
+    {
+        kOCSumAdd(pSumHead, pSumHeadAdd);
+        pSumHeadAdd = pSumHeadAdd->pNext;
+    }
+}
+
+
+
+/**
+ * Prints the checksum to the specified stream.
+ *
+ * @param   pSum    The checksum.
+ * @param   pFile   The output file stream
+ */
+static void kOCSumFPrintf(PCKOCSUM pSum, FILE *pFile)
+{
+    fprintf(pFile, "%#x:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
+            pSum->crc32,
+            pSum->md5[0], pSum->md5[1], pSum->md5[2], pSum->md5[3],
+            pSum->md5[4], pSum->md5[5], pSum->md5[6], pSum->md5[7],
+            pSum->md5[8], pSum->md5[9], pSum->md5[10], pSum->md5[11],
+            pSum->md5[12], pSum->md5[13], pSum->md5[14], pSum->md5[15]);
+}
+
+
+/**
+ * Displays the checksum (not chain!) using the InfoMsg() method.
+ *
+ * @param   pSum    The checksum.
+ * @param   uLevel  The info message level.
+ * @param   pszMsg  Message to prefix the info message with.
+ */
+static void kOCSumInfo(PCKOCSUM pSum, unsigned uLevel, const char *pszMsg)
+{
+    InfoMsg(uLevel,
+            "%s: crc32=%#010x md5=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
+            pszMsg,
+            pSum->crc32,
+            pSum->md5[0], pSum->md5[1], pSum->md5[2], pSum->md5[3],
+            pSum->md5[4], pSum->md5[5], pSum->md5[6], pSum->md5[7],
+            pSum->md5[8], pSum->md5[9], pSum->md5[10], pSum->md5[11],
+            pSum->md5[12], pSum->md5[13], pSum->md5[14], pSum->md5[15]);
+}
 
 
@@ -179,5 +851,5 @@
  * @param pSum2     The second checksum.
  */
-static int kObjCacheSumIsEqual(PCKOCSUM pSum1, PCKOCSUM pSum2)
+static int kOCSumIsEqual(PCKOCSUM pSum1, PCKOCSUM pSum2)
 {
     if (pSum1 == pSum2)
@@ -194,42 +866,98 @@
 
 /**
- * Print a fatal error message and exit with rc=1.
- *
- * @param   pEntry      The cache entry.
- * @param   pszFormat   The message to print.
- * @param   ...         Format arguments.
- */
-static void kObjCacheFatal(PCKOBJCACHE pEntry, const char *pszFormat, ...)
-{
-    va_list va;
-
-    fprintf(stderr, "kObjCache %s - fatal error: ", pEntry->pszName);
-    va_start(va, pszFormat);
-    vfprintf(stderr, pszFormat, va);
-    va_end(va);
-
-    exit(1);
-}
-
-
-/**
- * Print a verbose message if verbosisty is enabled.
- *
- * @param   pEntry      The cache entry.
- * @param   pszFormat   The message to print.
- * @param   ...         Format arguments.
- */
-static void kObjCacheVerbose(PCKOBJCACHE pEntry, const char *pszFormat, ...)
-{
-    if (g_fVerbose)
-    {
-        va_list va;
-
-        fprintf(stdout, "kObjCache %s - info: ", pEntry->pszName);
-        va_start(va, pszFormat);
-        vfprintf(stdout, pszFormat, va);
-        va_end(va);
-    }
-}
+ * Checks if the specified checksum equals one of the
+ * checksums in the chain.
+ *
+ * @returns 1 if equals one of them, 0 if not.
+ *
+ * @param pSumHead  The checksum chain too look in.
+ * @param pSum      The checksum to look for.
+ * @todo ugly name. fix.
+ */
+static int kOCSumHasEqualInChain(PCKOCSUM pSumHead, PCKOCSUM pSum)
+{
+    for (; pSumHead; pSumHead = pSumHead->pNext)
+    {
+        if (pSumHead == pSum)
+            return 1;
+        if (pSumHead->crc32 != pSum->crc32)
+            continue;
+        if (memcmp(&pSumHead->md5[0], &pSum->md5[0], sizeof(pSumHead->md5)))
+            continue;
+        return 1;
+    }
+    return 0;
+}
+
+
+/**
+ * Checks if the checksum (chain) empty.
+ *
+ * @returns 1 if empty, 0 if it there is one or more checksums.
+ * @param   pSum    The checksum to test.
+ */
+static int kOCSumIsEmpty(PCKOCSUM pSum)
+{
+    return !pSum->fUsed;
+}
+
+
+
+
+
+
+/**
+ * The representation of a cache entry.
+ */
+typedef struct KOCENTRY
+{
+    /** The name of the cache entry. */
+    const char *pszName;
+    /** The dir that all other names are relative to. */
+    char *pszDir;
+    /** The absolute path. */
+    char *pszAbsPath;
+    /** Set if the object needs to be (re)compiled. */
+    unsigned fNeedCompiling;
+    /** Whether the precompiler runs in piped mode. If clear it's file
+     * mode (it could be redirected stdout, but that's essentially the
+     * same from our point of view). */
+    unsigned fPipedPreComp;
+    /** Whether the compiler runs in piped mode (precompiler output on stdin). */
+    unsigned fPipedCompile;
+    /** Cache entry key that's used for some quick digest validation. */
+    uint32_t uKey;
+
+    /** The file data. */
+    struct KOCENTRYDATA
+    {
+        /** The name of file containing the precompiler output. */
+        char *pszCppName;
+        /** Pointer to the precompiler output. */
+        char *pszCppMapping;
+        /** The size of the precompiler output. 0 if not determined. */
+        size_t cbCpp;
+        /** The precompiler output checksums that will produce the cached object. */
+        KOCSUM SumHead;
+        /** The object filename (relative to the cache file). */
+        char *pszObjName;
+        /** The compile argument vector used to build the object. */
+        char **papszArgvCompile;
+        /** The size of the compile  */
+        unsigned cArgvCompile;
+        /** The checksum of the compiler argument vector. */
+        KOCSUM SumCompArgv;
+        /** The target os/arch identifier. */
+        char *pszTarget;
+    }
+    /** The old data.*/
+            Old,
+    /** The new data. */
+            New;
+} KOCENTRY;
+/** Pointer to a KOCENTRY. */
+typedef KOCENTRY *PKOCENTRY;
+/** Pointer to a const KOCENTRY. */
+typedef const KOCENTRY *PCKOCENTRY;
 
 
@@ -240,22 +968,31 @@
  * @param   pszFilename     The cache file name.
  */
-static PKOBJCACHE kObjCacheCreate(const char *pszFilename)
-{
-    PKOBJCACHE pEntry;
+static PKOCENTRY kOCEntryCreate(const char *pszFilename)
+{
+    PKOCENTRY pEntry;
+    size_t off;
 
     /*
      * Allocate an empty entry.
      */
-    pEntry = xmalloc(sizeof(*pEntry));
-    memset(pEntry, 0, sizeof(*pEntry));
+    pEntry = xmallocz(sizeof(*pEntry));
+
+    kOCSumInit(&pEntry->New.SumHead);
+    kOCSumInit(&pEntry->Old.SumHead);
+
+    kOCSumInit(&pEntry->New.SumCompArgv);
+    kOCSumInit(&pEntry->Old.SumCompArgv);
 
     /*
      * Setup the directory and cache file name.
      */
-    pEntry->pszDir = AbsPath(pszFilename);
-    pEntry->pszName = FindFilenameInPath(pEntry->pszDir);
-    if (pEntry->pszDir == pEntry->pszName)
-        kObjCacheFatal(pEntry, "Failed to find abs path for '%s'!\n", pszFilename);
-    ((char *)pEntry->pszName)[-1] = '\0';
+    pEntry->pszAbsPath = AbsPath(pszFilename);
+    pEntry->pszName = FindFilenameInPath(pEntry->pszAbsPath);
+    off = pEntry->pszName - pEntry->pszAbsPath;
+    if (!off)
+        FatalDie("Failed to find abs path for '%s'!\n", pszFilename);
+    pEntry->pszDir = xmalloc(off - 1);
+    memcpy(pEntry->pszDir, pEntry->pszAbsPath, off);
+    pEntry->pszDir[off - 1] = '\0';
 
     return pEntry;
@@ -263,5 +1000,4 @@
 
 
-#if 0 /* don't bother. */
 /**
  * Destroys the cache entry freeing up all it's resources.
@@ -269,18 +1005,34 @@
  * @param   pEntry      The entry to free.
  */
-static void kObjCacheDestroy(PKOBJCACHE pEntry)
+static void kOCEntryDestroy(PKOCENTRY pEntry)
 {
     free(pEntry->pszDir);
-    free(pEntry->pszName);
-    while (pEntry->SumHead.pNext)
-    {
-        void *pv = pEntry->SumHead.pNext;
-        pEntry->SumHead.pNext = pEntry->SumHead.pNext->pNext;
-        if (pv != &pEntry->NewSum)
-            free(pv);
-    }
+    free(pEntry->pszAbsPath);
+
+    kOCSumDeleteChain(&pEntry->New.SumHead);
+    kOCSumDeleteChain(&pEntry->Old.SumHead);
+
+    kOCSumDeleteChain(&pEntry->New.SumCompArgv);
+    kOCSumDeleteChain(&pEntry->Old.SumCompArgv);
+
+    free(pEntry->New.pszCppName);
+    free(pEntry->Old.pszCppName);
+
+    free(pEntry->New.pszCppMapping);
+    free(pEntry->Old.pszCppMapping);
+
+    free(pEntry->New.pszObjName);
+    free(pEntry->Old.pszObjName);
+
+    while (pEntry->New.cArgvCompile > 0)
+        free(pEntry->New.papszArgvCompile[--pEntry->New.cArgvCompile]);
+    while (pEntry->Old.cArgvCompile > 0)
+        free(pEntry->Old.papszArgvCompile[--pEntry->Old.cArgvCompile]);
+
+    free(pEntry->New.papszArgvCompile);
+    free(pEntry->Old.papszArgvCompile);
+
     free(pEntry);
 }
-#endif
 
 
@@ -290,20 +1042,19 @@
  * @param   pEntry      The entry to read it into.
  */
-static void kObjCacheRead(PKOBJCACHE pEntry)
-{
-    static char s_szLine[KOBJCACHE_MAX_LINE_LEN + 16];
+static void kOCEntryRead(PKOCENTRY pEntry)
+{
     FILE *pFile;
     pFile = FOpenFileInDir(pEntry->pszName, pEntry->pszDir, "rb");
     if (pFile)
     {
-        kObjCacheVerbose(pEntry, "reading cache file...\n");
+        InfoMsg(1, "reading cache file...\n");
 
         /*
          * Check the magic.
          */
-        if (    !fgets(s_szLine, sizeof(s_szLine), pFile)
-            ||  strcmp(s_szLine, "magic=kObjCache-1\n"))
-        {
-            kObjCacheVerbose(pEntry, "bad cache file (magic)\n");
+        if (    !fgets(g_szLine, sizeof(g_szLine), pFile)
+            ||  strcmp(g_szLine, "magic=kObjCache-1\n"))
+        {
+            InfoMsg(1, "bad cache file (magic)\n");
             pEntry->fNeedCompiling = 1;
         }
@@ -315,106 +1066,75 @@
             unsigned i;
             int fBad = 0;
-            int fBadBeforeMissing;
+            int fBadBeforeMissing = 1;
             int fFirstSum = 1;
-            while (fgets(s_szLine, sizeof(s_szLine), pFile))
+            while (fgets(g_szLine, sizeof(g_szLine), pFile))
             {
+                char *pszNl;
+                char *pszVal;
+
                 /* Split the line and drop the trailing newline. */
-                char *pszNl = strchr(s_szLine, '\n');
-                char *pszVal = strchr(s_szLine, '=');
+                pszVal = strchr(g_szLine, '=');
                 if ((fBad = pszVal == NULL))
                     break;
+                *pszVal++ = '\0';
+
+                pszNl = strchr(pszVal, '\n');
                 if (pszNl)
                     *pszNl = '\0';
-                *pszVal++ = '\0';
 
                 /* string case on variable name */
-                if (!strcmp(s_szLine, "obj"))
+                if (!strcmp(g_szLine, "obj"))
                 {
-                    if ((fBad = pEntry->pszObjName != NULL))
+                    if ((fBad = pEntry->Old.pszObjName != NULL))
                         break;
-                    pEntry->pszObjName = xstrdup(pszVal);
+                    pEntry->Old.pszObjName = xstrdup(pszVal);
                 }
-                else if (!strcmp(s_szLine, "cpp"))
+                else if (!strcmp(g_szLine, "cpp"))
                 {
-                    if ((fBad = pEntry->pszOldCppName != NULL))
+                    if ((fBad = pEntry->Old.pszCppName != NULL))
                         break;
-                    pEntry->pszOldCppName = xstrdup(pszVal);
+                    pEntry->Old.pszCppName = xstrdup(pszVal);
                 }
-                else if (!strcmp(s_szLine, "cpp-size"))
+                else if (!strcmp(g_szLine, "cpp-size"))
                 {
                     char *pszNext;
-                    if ((fBad = pEntry->cbOldCpp != 0))
+                    if ((fBad = pEntry->Old.cbCpp != 0))
                         break;
-                    pEntry->cbOldCpp = strtoul(pszVal, &pszNext, 0);
+                    pEntry->Old.cbCpp = strtoul(pszVal, &pszNext, 0);
                     if ((fBad = pszNext && *pszNext))
                         break;
                 }
-                else if (!strcmp(s_szLine, "cc-argc"))
+                else if (!strcmp(g_szLine, "cc-argc"))
                 {
-                    if ((fBad = pEntry->papszArgvCompile != NULL))
+                    if ((fBad = pEntry->Old.papszArgvCompile != NULL))
                         break;
-                    pEntry->cArgvCompile = atoi(pszVal); /* if wrong, we'll fail below. */
-                    pEntry->papszArgvCompile = xmalloc((pEntry->cArgvCompile + 1) * sizeof(pEntry->papszArgvCompile[0]));
-                    memset(pEntry->papszArgvCompile, 0, (pEntry->cArgvCompile + 1) * sizeof(pEntry->papszArgvCompile[0]));
+                    pEntry->Old.cArgvCompile = atoi(pszVal); /* if wrong, we'll fail below. */
+                    pEntry->Old.papszArgvCompile = xmallocz((pEntry->Old.cArgvCompile + 1) * sizeof(pEntry->Old.papszArgvCompile[0]));
                 }
-                else if (!strncmp(s_szLine, "cc-argv-#", sizeof("cc-argv-#") - 1))
+                else if (!strncmp(g_szLine, "cc-argv-#", sizeof("cc-argv-#") - 1))
                 {
                     char *pszNext;
-                    unsigned i = strtoul(&s_szLine[sizeof("cc-argv-#") - 1], &pszNext, 0);
-                    if ((fBad = i >= pEntry->cArgvCompile || pEntry->papszArgvCompile[i] || (pszNext && *pszNext)))
+                    unsigned i = strtoul(&g_szLine[sizeof("cc-argv-#") - 1], &pszNext, 0);
+                    if ((fBad = i >= pEntry->Old.cArgvCompile || pEntry->Old.papszArgvCompile[i] || (pszNext && *pszNext)))
                         break;
-                    pEntry->papszArgvCompile[i] = xstrdup(pszVal);
+                    pEntry->Old.papszArgvCompile[i] = xstrdup(pszVal);
                 }
-                else if (!strcmp(s_szLine, "sum"))
+                else if (!strcmp(g_szLine, "sum"))
                 {
                     KOCSUM Sum;
-                    unsigned i;
-                    char *pszNext;
-                    char *pszMD5 = strchr(pszVal, ':');
-                    if ((fBad = pszMD5 == NULL))
+                    if ((fBad = kOCSumInitFromString(&Sum, pszVal)))
                         break;
-                    *pszMD5++ = '\0';
-
-                    /* crc32 */
-                    Sum.crc32 = (uint32_t)strtoul(pszVal, &pszNext, 16);
-                    if ((fBad = (pszNext && *pszNext)))
+                    kOCSumAdd(&pEntry->Old.SumHead, &Sum);
+                }
+                else if (!strcmp(g_szLine, "target"))
+                {
+                    if ((fBad = pEntry->Old.pszTarget != NULL))
                         break;
-
-                    /* md5 */
-                    for (i = 0; i < sizeof(Sum.md5) * 2; i++)
-                    {
-                        unsigned char ch = pszMD5[i];
-                        int x;
-                        if ((unsigned char)(ch - '0') <= 9)
-                            x = ch - '0';
-                        else if ((unsigned char)(ch - 'a') <= 5)
-                            x = ch - 'a' + 10;
-                        else if ((unsigned char)(ch - 'A') <= 5)
-                            x = ch - 'A' + 10;
-                        else
-                        {
-                            fBad = 1;
-                            break;
-                        }
-                        if (!(i & 1))
-                            Sum.md5[i >> 1] = x << 4;
-                        else
-                            Sum.md5[i >> 1] |= x;
-                    }
-                    if (fBad)
-                        break;
-
-                    if (fFirstSum)
-                    {
-                        pEntry->SumHead = Sum;
-                        pEntry->SumHead.pNext = NULL;
-                        fFirstSum = 0;
-                    }
-                    else
-                    {
-                        Sum.pNext = pEntry->SumHead.pNext;
-                        pEntry->SumHead.pNext = xmalloc(sizeof(Sum));
-                        *pEntry->SumHead.pNext = Sum;
-                    }
+                    pEntry->Old.pszTarget = xstrdup(pszVal);
+                }
+                else if (!strcmp(g_szLine, "the-end"))
+                {
+                    fBadBeforeMissing = fBad = !strcmp(pszVal, "fine");
+                    break;
                 }
                 else
@@ -426,21 +1146,62 @@
 
             /*
-             * Did we find everything?
+             * Did we find everything and does it add up correctly?
              */
-            fBadBeforeMissing = fBad;
-            if (    !fBad
-                &&  (   !pEntry->papszArgvCompile
-                     || !pEntry->pszObjName
-                     || !pEntry->pszOldCppName
-                     || fFirstSum))
+            if (!fBad && fBadBeforeMissing)
+            {
+                InfoMsg(1, "bad cache file (no end)\n");
                 fBad = 1;
-            if (!fBad)
-                for (i = 0; i < pEntry->cArgvCompile; i++)
-                    if ((fBad = !pEntry->papszArgvCompile[i]))
-                        break;
-            if (fBad)
-                kObjCacheVerbose(pEntry, "bad cache file (%s)\n", fBadBeforeMissing ? s_szLine : "missing stuff");
-            else if (ferror(pFile))
-                kObjCacheVerbose(pEntry, "cache file read error\n");
+            }
+            else
+            {
+                fBadBeforeMissing = fBad;
+                if (    !fBad
+                    &&  (   !pEntry->Old.papszArgvCompile
+                         || !pEntry->Old.pszObjName
+                         || !pEntry->Old.pszCppName
+                         || fFirstSum))
+                    fBad = 1;
+                if (!fBad)
+                {
+                    KOCSUMCTX Ctx;
+                    KOCSUM Sum;
+
+                    kOCSumInitWithCtx(&Sum, &Ctx);
+                    for (i = 0; i < pEntry->Old.cArgvCompile; i++)
+                    {
+                        if ((fBad = !pEntry->Old.papszArgvCompile[i]))
+                            break;
+                        kOCSumUpdate(&Sum, &Ctx, pEntry->Old.papszArgvCompile[i], strlen(pEntry->Old.papszArgvCompile[i]) + 1);
+                    }
+                    kOCSumFinalize(&Sum, &Ctx);
+                    if (!fBad)
+                        fBad = !kOCSumIsEqual(&pEntry->Old.SumCompArgv, &Sum);
+                }
+                if (fBad)
+                    InfoMsg(1, "bad cache file (%s)\n", fBadBeforeMissing ? g_szLine : "missing stuff");
+                else if (ferror(pFile))
+                {
+                    InfoMsg(1, "cache file read error\n");
+                    fBad = 1;
+                }
+
+                /*
+                 * Verify the existance of the object file.
+                 */
+                if (!fBad)
+                {
+                    struct stat st;
+                    char *pszPath = MakePathFromDirAndFile(pEntry->Old.pszObjName, pEntry->pszDir);
+                    if (stat(pszPath, &st) != 0)
+                    {
+                        InfoMsg(1, "failed to stat object file: %s\n", strerror(errno));
+                        fBad = 1;
+                    }
+                    else
+                    {
+                        /** @todo verify size and the timestamp. */
+                    }
+                }
+            }
             pEntry->fNeedCompiling = fBad;
         }
@@ -449,5 +1210,5 @@
     else
     {
-        kObjCacheVerbose(pEntry, "no cache file\n");
+        InfoMsg(1, "no cache file\n");
         pEntry->fNeedCompiling = 1;
     }
@@ -460,5 +1221,5 @@
  * @param   pEntry      The entry to write.
  */
-static void kObjCacheWrite(PKOBJCACHE pEntry)
+static void kOCEntryWrite(PKOCENTRY pEntry)
 {
     FILE *pFile;
@@ -466,30 +1227,54 @@
     unsigned i;
 
-    kObjCacheVerbose(pEntry, "writing cache file...\n");
+    InfoMsg(1, "writing cache file...\n");
     pFile = FOpenFileInDir(pEntry->pszName, pEntry->pszDir, "wb");
     if (!pFile)
-        kObjCacheFatal(pEntry, "Failed to open '%s' in '%s': %s\n",
-                       pEntry->pszName, pEntry->pszDir, strerror(errno));
+        FatalDie("Failed to open '%s' in '%s': %s\n",
+                 pEntry->pszName, pEntry->pszDir, strerror(errno));
 
 #define CHECK_LEN(expr) \
-        do { int cch = expr; if (cch >= KOBJCACHE_MAX_LINE_LEN) kObjCacheFatal(pEntry, "Line too long: %d (max %d)\nexpr: %s\n", cch, KOBJCACHE_MAX_LINE_LEN, #expr); } while (0)
+        do { int cch = expr; if (cch >= KOBJCACHE_MAX_LINE_LEN) FatalDie("Line too long: %d (max %d)\nexpr: %s\n", cch, KOBJCACHE_MAX_LINE_LEN, #expr); } while (0)
 
     fprintf(pFile, "magic=kObjCache-1\n");
-    CHECK_LEN(fprintf(pFile, "obj=%s\n", pEntry->pszNewObjName ? pEntry->pszNewObjName : pEntry->pszObjName));
-    CHECK_LEN(fprintf(pFile, "cpp=%s\n", pEntry->pszNewCppName ? pEntry->pszNewCppName : pEntry->pszOldCppName));
-    CHECK_LEN(fprintf(pFile, "cpp-size=%lu\n", pEntry->pszNewCppName ? pEntry->cbNewCpp : pEntry->cbOldCpp));
-    CHECK_LEN(fprintf(pFile, "cc-argc=%u\n", pEntry->cArgvCompile));
-    for (i = 0; i < pEntry->cArgvCompile; i++)
-        CHECK_LEN(fprintf(pFile, "cc-argv-#%u=%s\n", i, pEntry->papszArgvCompile[i]));
-    for (pSum = pEntry->fNeedCompiling ? &pEntry->NewSum : &pEntry->SumHead;
+    CHECK_LEN(fprintf(pFile, "target=%s\n", pEntry->New.pszTarget ? pEntry->New.pszTarget : pEntry->Old.pszTarget));
+    CHECK_LEN(fprintf(pFile, "obj=%s\n", pEntry->New.pszObjName ? pEntry->New.pszObjName : pEntry->Old.pszObjName));
+    CHECK_LEN(fprintf(pFile, "cpp=%s\n", pEntry->New.pszCppName ? pEntry->New.pszCppName : pEntry->Old.pszCppName));
+    CHECK_LEN(fprintf(pFile, "cpp-size=%lu\n", pEntry->New.pszCppName ? pEntry->New.cbCpp : pEntry->Old.cbCpp));
+
+    if (!kOCSumIsEmpty(&pEntry->New.SumCompArgv))
+    {
+        CHECK_LEN(fprintf(pFile, "cc-argc=%u\n", pEntry->New.cArgvCompile));
+        for (i = 0; i < pEntry->New.cArgvCompile; i++)
+            CHECK_LEN(fprintf(pFile, "cc-argv-#%u=%s\n", i, pEntry->New.papszArgvCompile[i]));
+        fprintf(pFile, "cc-argv-sum=");
+        kOCSumFPrintf(&pEntry->New.SumCompArgv, pFile);
+    }
+    else
+    {
+        CHECK_LEN(fprintf(pFile, "cc-argc=%u\n", pEntry->Old.cArgvCompile));
+        for (i = 0; i < pEntry->Old.cArgvCompile; i++)
+            CHECK_LEN(fprintf(pFile, "cc-argv-#%u=%s\n", i, pEntry->Old.papszArgvCompile[i]));
+        fprintf(pFile, "cc-argv-sum=");
+        kOCSumFPrintf(&pEntry->Old.SumCompArgv, pFile);
+    }
+
+
+    for (pSum = kOCSumIsEmpty(&pEntry->New.SumHead) ? &pEntry->New.SumHead : &pEntry->Old.SumHead;
          pSum;
          pSum = pSum->pNext)
-        fprintf(pFile, "sum=%#x:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
-                pSum->crc32,
-                pSum->md5[0], pSum->md5[1], pSum->md5[2], pSum->md5[3],
-                pSum->md5[4], pSum->md5[5], pSum->md5[6], pSum->md5[7],
-                pSum->md5[8], pSum->md5[9], pSum->md5[10], pSum->md5[11],
-                pSum->md5[12], pSum->md5[13], pSum->md5[14], pSum->md5[15]);
-
+    {
+        fprintf(pFile, "sum=");
+        kOCSumFPrintf(pSum, pFile);
+    }
+
+    fprintf(pFile, "the-end=fine\n");
+
+#undef CHECK_LEN
+
+    /*
+     * Flush the file and check for errors.
+     * On failure delete the file so we won't be seeing any invalid
+     * files the next time or upset make with new timestamps.
+     */
     if (    fflush(pFile) < 0
         ||  ferror(pFile))
@@ -498,8 +1283,139 @@
         fclose(pFile);
         UnlinkFileInDir(pEntry->pszName, pEntry->pszDir);
-        kObjCacheFatal(pEntry, "Stream error occured while writing '%s' in '%s': %d (?)\n",
-                       pEntry->pszName, pEntry->pszDir, strerror(iErr));
+        FatalDie("Stream error occured while writing '%s' in '%s': %s\n",
+                 pEntry->pszName, pEntry->pszDir, strerror(iErr));
     }
     fclose(pFile);
+}
+
+
+/**
+ * Checks that the read cache entry is valid.
+ * It sets fNeedCompiling if it isn't.
+ *
+ * @returns 1 valid, 0 invalid.
+ * @param   pEntry      The cache entry.
+ */
+static int kOCEntryCheck(PKOCENTRY pEntry)
+{
+    return pEntry->fNeedCompiling;
+}
+
+
+/**
+ * Set the new compiler args, calc their checksum, and comparing them with any old ones.
+ *
+ * @param   pEntry              The cache entry.
+ * @param   papszArgvCompile    The new argument vector for compilation.
+ * @param   cArgvCompile        The number of arguments in the vector.
+ */
+static void kOCEntrySetCompileArgv(PKOCENTRY pEntry, const char * const *papszArgvCompile, unsigned cArgvCompile)
+{
+    KOCSUMCTX Ctx;
+    unsigned i;
+
+    /* call me only once! */
+    assert(!pEntry->New.cArgvCompile);
+
+    /*
+     * Copy the argument vector and calculate the checksum while doing so.
+     */
+    pEntry->New.cArgvCompile = cArgvCompile;
+    pEntry->New.papszArgvCompile = xmalloc(cArgvCompile + 1);
+    kOCSumInitWithCtx(&pEntry->New.SumCompArgv, &Ctx);
+    for (i = 0; i < cArgvCompile; i++)
+    {
+        pEntry->New.papszArgvCompile[i] = xstrdup(papszArgvCompile[i]);
+        kOCSumUpdate(&pEntry->New.SumCompArgv, &Ctx, papszArgvCompile[i], strlen(papszArgvCompile[i]));
+    }
+    kOCSumFinalize(&pEntry->New.SumCompArgv, &Ctx);
+    pEntry->New.papszArgvCompile[i] = NULL; /* for exev/spawnv */
+
+    /*
+     * Compare with the old argument vector.
+     */
+    if (    !pEntry->fNeedCompiling
+        &&  !kOCSumIsEqual(&pEntry->New.SumCompArgv, &pEntry->Old.SumCompArgv))
+    {
+        InfoMsg(1, "compiler args differs\n");
+        pEntry->fNeedCompiling = 1;
+    }
+}
+
+
+/**
+ * Sets the object name and compares it with the old name if present.
+ *
+ * @param   pEntry      The cache entry.
+ * @param   pszObjName  The new object name.
+ */
+static void kOCEntrySetCompileObjName(PKOCENTRY pEntry, const char *pszObjName)
+{
+    assert(!pEntry->New.pszObjName);
+    pEntry->New.pszObjName = CalcRelativeName(pszObjName, pEntry->pszDir);
+
+    if (    !pEntry->fNeedCompiling
+        &&  (   !pEntry->Old.pszObjName
+             || strcmp(pEntry->New.pszObjName, pEntry->Old.pszObjName)))
+    {
+        InfoMsg(1, "object file name differs\n");
+        pEntry->fNeedCompiling = 1;
+    }
+
+    if (    !pEntry->fNeedCompiling
+        &&  !DoesFileInDirExist(pEntry->New.pszObjName, pEntry->pszDir))
+    {
+        InfoMsg(1, "object file doesn't exist\n");
+        pEntry->fNeedCompiling = 1;
+    }
+}
+
+
+/**
+ * Sets the arch/os target and compares it with the old name if present.
+ *
+ * @param   pEntry      The cache entry.
+ * @param   pszObjName  The new object name.
+ */
+static void kOCEntrySetTarget(PKOCENTRY pEntry, const char *pszTarget)
+{
+    assert(!pEntry->New.pszTarget);
+    pEntry->New.pszTarget = xstrdup(pszTarget);
+
+    if (    !pEntry->fNeedCompiling
+        &&  (   !pEntry->Old.pszTarget
+             || strcmp(pEntry->New.pszTarget, pEntry->Old.pszTarget)))
+    {
+        InfoMsg(1, "target differs\n");
+        pEntry->fNeedCompiling = 1;
+    }
+}
+
+
+/**
+ * Sets the precompiler output filename.
+ * We don't generally care if this matches the old name or not.
+ *
+ * @param   pEntry      The cache entry.
+ * @param   pszCppName  The precompiler output filename.
+ */
+static void kOCEntrySetCppName(PKOCENTRY pEntry, const char *pszCppName)
+{
+    assert(!pEntry->New.pszCppName);
+    pEntry->New.pszCppName = CalcRelativeName(pszCppName, pEntry->pszDir);
+}
+
+
+/**
+ * Sets the piped mode of the precompiler and compiler.
+ *
+ * @param   pEntry                  The cache entry.
+ * @param   fRedirPreCompStdOut     Whether the precompiler is in piped mode.
+ * @param   fRedirCompileStdIn      Whether the compiler is in piped mode.
+ */
+static void kOCEntrySetPipedMode(PKOCENTRY pEntry, int fRedirPreCompStdOut, int fRedirCompileStdIn)
+{
+    pEntry->fPipedPreComp = fRedirPreCompStdOut;
+    pEntry->fPipedCompile = fRedirCompileStdIn;
 }
 
@@ -512,5 +1428,5 @@
  * @param   cArgv           The number of arguments in the vector.
  */
-static void kObjCacheSpawn(PCKOBJCACHE pEntry, const char **papszArgv, unsigned cArgv, const char *pszMsg, const char *pszStdOut)
+static void kOCEntrySpawn(PCKOCENTRY pEntry, const char **papszArgv, unsigned cArgv, const char *pszMsg, const char *pszStdOut)
 {
 #if defined(__OS2__) || defined(__WIN__)
@@ -522,13 +1438,13 @@
         fdStdOut = dup(1); /* dup2(1,-1) doesn't work right on windows */
         close(1);
-        fdReDir = open(pszStdOut, O_CREAT | O_TRUNC | O_WRONLY, 0777);
+        fdReDir = open(pszStdOut, O_CREAT | O_TRUNC | O_WRONLY, 0666);
         if (fdReDir < 0)
-            kObjCacheFatal(pEntry, "%s - failed to create stdout redirection file '%s': %s\n",
-                           pszMsg, pszStdOut, strerror(errno));
+            FatalDie("%s - failed to create stdout redirection file '%s': %s\n",
+                     pszMsg, pszStdOut, strerror(errno));
 
         if (fdReDir != 1)
         {
             if (dup2(fdReDir, 1) < 0)
-                kObjCacheFatal(pEntry, "%s - dup2 failed: %s\n", pszMsg, strerror(errno));
+                FatalDie("%s - dup2 failed: %s\n", pszMsg, strerror(errno));
             close(fdReDir);
         }
@@ -538,7 +1454,7 @@
     rc = _spawnvp(_P_WAIT, papszArgv[0], papszArgv);
     if (rc < 0)
-        kObjCacheFatal(pEntry, "%s - _spawnvp failed (rc=0x%p): %s\n", pszMsg, rc, strerror(errno));
+        FatalDie("%s - _spawnvp failed (rc=0x%p): %s\n", pszMsg, rc, strerror(errno));
     if (rc > 0)
-        kObjCacheFatal(pEntry, "%s - failed rc=%d\n", pszMsg, (int)rc);
+        FatalDie("%s - failed rc=%d\n", pszMsg, (int)rc);
     if (fdStdOut)
     {
@@ -559,12 +1475,12 @@
 
             close(1);
-            fdReDir = open(pszStdOut, O_CREAT | O_TRUNC | O_WRONLY, 0777);
+            fdReDir = open(pszStdOut, O_CREAT | O_TRUNC | O_WRONLY, 0666);
             if (fdReDir < 0)
-                kObjCacheFatal(pEntry, "%s - failed to create stdout redirection file '%s': %s\n",
-                               pszMsg, pszStdOut, strerror(errno));
+                FatalDie("%s - failed to create stdout redirection file '%s': %s\n",
+                         pszMsg, pszStdOut, strerror(errno));
             if (fdReDir != 1)
             {
                 if (dup2(fdReDir, 1) < 0)
-                    kObjCacheFatal(pEntry, "%s - dup2 failed: %s\n", pszMsg, strerror(errno));
+                    FatalDie("%s - dup2 failed: %s\n", pszMsg, strerror(errno));
                 close(fdReDir);
             }
@@ -572,9 +1488,9 @@
 
         execvp(papszArgv[0], (char **)papszArgv);
-        kObjCacheFatal(pEntry, "%s - execvp failed: %s\n",
-                       pszMsg, strerror(errno));
+        FatalDie("%s - execvp failed: %s\n",
+                 pszMsg, strerror(errno));
     }
     if (pid == -1)
-        kObjCacheFatal(pEntry, "%s - fork() failed: %s\n", pszMsg, strerror(errno));
+        FatalDie("%s - fork() failed: %s\n", pszMsg, strerror(errno));
 
     pidWait = waitpid(pid, &iStatus, 0);
@@ -582,10 +1498,10 @@
         pidWait = waitpid(pid, &iStatus, 0);
     if (pidWait != pid)
-        kObjCacheFatal(pEntry, "%s - waitpid failed rc=%d: %s\n",
-                       pszMsg, pidWait, strerror(errno));
+        FatalDie("%s - waitpid failed rc=%d: %s\n",
+                 pszMsg, pidWait, strerror(errno));
     if (!WIFEXITED(iStatus))
-        kObjCacheFatal(pEntry, "%s - abended (iStatus=%#x)\n", pszMsg, iStatus);
+        FatalDie("%s - abended (iStatus=%#x)\n", pszMsg, iStatus);
     if (WEXITSTATUS(iStatus))
-        kObjCacheFatal(pEntry, "%s - failed with rc %d\n", pszMsg, WEXITSTATUS(iStatus));
+        FatalDie("%s - failed with rc %d\n", pszMsg, WEXITSTATUS(iStatus));
 #endif
     (void)cArgv;
@@ -593,44 +1509,43 @@
 
 
-#ifdef USE_PIPE
-/**
- * Spawns a child in a synchronous fashion.
- * Terminating on failure.
- *
+/**
+ * Spawns child with optional redirection of stdin and stdout.
+ *
+ * @param   pEntry          The cache entry.
  * @param   papszArgv       Argument vector. The cArgv element is NULL.
  * @param   cArgv           The number of arguments in the vector.
- */
-static void kObjCacheSpawnPipe(PCKOBJCACHE pEntry, const char **papszArgv, unsigned cArgv, const char *pszMsg, char **ppszOutput, size_t *pcchOutput)
-{
-    int fds[2];
-    int iStatus;
-#if defined(__WIN__)
-    intptr_t pid, pidWait;
-#else
-    pid_t pid, pidWait;
+ * @param   fdStdIn         Child stdin, -1 if it should inherit our stdin. Will be closed.
+ * @param   fdStdOut        Child stdout, -1 if it should inherit our stdout. Will be closed.
+ * @param   pszMsg          Message to start the info/error messages with.
+ */
+static pid_t kOCEntrySpawnChild(PCKOCENTRY pEntry, const char **papszArgv, unsigned cArgv, int fdStdIn, int fdStdOut, const char *pszMsg)
+{
+    pid_t pid;
+    int fdSavedStdOut = -1;
+    int fdSavedStdIn = -1;
+
+    /*
+     * Setup redirection.
+     */
+    if (fdStdOut != -1)
+    {
+        fdSavedStdOut = dup(1 /* stdout */);
+        if (dup2(fdStdOut, 1 /* stdout */) < 0)
+            FatalDie("%s - dup2(,1) failed: %s\n", pszMsg, strerror(errno));
+        close(fdStdOut);
+#ifndef __WIN__
+        fcntl(fdSavedStdOut, F_SETFD, FD_CLOEXEC);
 #endif
-    int fdStdOut;
-    size_t cbAlloc;
-    size_t cbLeft;
-    char *psz;
-
-    /*
-     * Setup the pipe.
-     */
-#if defined(__WIN__)
-    if (_pipe(fds, 0, _O_NOINHERIT | _O_BINARY) < 0)
-#else
-    if (pipe(fds) < 0)
+    }
+    if (fdStdIn != -1)
+    {
+        fdSavedStdIn = dup(0 /* stdin */);
+        if (dup2(fdStdOut, 0 /* stdin */) < 0)
+            FatalDie("%s - dup2(,0) failed: %s\n", pszMsg, strerror(errno));
+        close(fdStdIn);
+#ifndef __WIN__
+        fcntl(fdSavedStdIn, F_SETFD, FD_CLOEXEC);
 #endif
-        kObjCacheFatal(pEntry, "pipe failed: %s\n", strerror(errno));
-    fdStdOut = dup(1);
-    if (dup2(fds[1 /* write */], 1) < 0)
-        kObjCacheFatal(pEntry, "dup2(,1) failed: %s\n", strerror(errno));
-    close(fds[1]);
-    fds[1] = -1;
-#ifndef __WIN__
-    fcntl(fds[0], F_SETFD, FD_CLOEXEC);
-    fcntl(fdStdOut, F_SETFD, FD_CLOEXEC);
-#endif
+    }
 
     /*
@@ -641,5 +1556,5 @@
     pid = _spawnvp(_P_NOWAIT, papszArgv[0], papszArgv);
     if (pid == -1)
-        kObjCacheFatal(pEntry, "%s - _spawnvp failed: %s\n", pszMsg, strerror(errno));
+        FatalDie("precompile - _spawnvp failed: %s\n", strerror(errno));
 
 #else
@@ -648,57 +1563,49 @@
     {
         execvp(papszArgv[0], (char **)papszArgv);
-        kObjCacheFatal(pEntry, "%s - execvp failed: %s\n",
-                       pszMsg, strerror(errno));
+        FatalDie("precompile - execvp failed: %s\n", strerror(errno));
     }
     if (pid == -1)
-        kObjCacheFatal(pEntry, "%s - fork() failed: %s\n", pszMsg, strerror(errno));
+        FatalDie("precompile - fork() failed: %s\n", strerror(errno));
 #endif
 
     /*
-     * Restore stdout.
-     */
-    close(1);
-    fdStdOut = dup2(fdStdOut, 1);
-
-    /*
-     * Read data from the child.
-     */
-    cbAlloc = pEntry->cbOldCpp ? (pEntry->cbOldCpp + 4*1024*1024 + 4096) & ~(4*1024*1024 - 1) : 4*1024*1024;
-    cbLeft = cbAlloc;
-    *ppszOutput = psz = xmalloc(cbAlloc);
-    for (;;)
-    {
-        long cbRead = read(fds[0], psz, cbLeft - 1);
-        if (!cbRead)
-            break;
-        if (cbRead < 0 && errno != EINTR)
-            kObjCacheFatal(pEntry, "%s - read(%d,,%ld) failed: %s\n", pszMsg, fds[0], (long)cbLeft, strerror(errno));
-        psz += cbRead;
-        *psz = '\0';
-        cbLeft -= cbRead;
-
-        /* expand the buffer? */
-        if (cbLeft <= 1)
-        {
-            size_t off = psz - *ppszOutput;
-            cbLeft = 4*1024*1024;
-            cbAlloc += cbLeft;
-            *ppszOutput = xrealloc(*ppszOutput, cbAlloc);
-            psz = *ppszOutput + off;
-        }
-    }
-    close(fds[0]);
-    *pcchOutput = cbAlloc - cbLeft;
-
-    /*
-     * Reap the child.
-     */
+     * Restore stdout & stdin.
+     */
+    if (fdSavedStdIn)
+    {
+        close(0 /* stdin */);
+        dup2(fdStdOut, 0 /* stdin */);
+        close(fdSavedStdIn);
+    }
+    if (fdSavedStdOut)
+    {
+        close(1 /* stdout */);
+        dup2(fdSavedStdOut, 1 /* stdout */);
+        close(fdSavedStdOut);
+    }
+
+    (void)cArgv;
+    (void)pEntry;
+    return pid;
+}
+
+
+/**
+ * Waits for a child and exits fatally if the child failed in any way.
+ *
+ * @param   pEntry      The cache entry.
+ * @param   pid         The child to wait for.
+ * @param   pszMsg      Message to start the info/error messages with.
+ */
+static void kOCEntryWaitChild(PCKOCENTRY pEntry, pid_t pid, const char *pszMsg)
+{
+    int iStatus = -1;
+    pid_t pidWait;
 #ifdef __WIN__
     pidWait = _cwait(&iStatus, pid, _WAIT_CHILD);
     if (pidWait == -1)
-        kObjCacheFatal(pEntry, "%s - waitpid failed: %s\n",
-                       pszMsg, strerror(errno));
+        FatalDie("%s - waitpid failed: %s\n", pszMsg, strerror(errno));
     if (iStatus)
-        kObjCacheFatal(pEntry, "%s - failed with rc %d\n", pszMsg, iStatus);
+        FatalDie("%s - failed with rc %d\n", pszMsg, iStatus);
 #else
     pidWait = waitpid(pid, &iStatus, 0);
@@ -706,56 +1613,232 @@
         pidWait = waitpid(pid, &iStatus, 0);
     if (pidWait != pid)
-        kObjCacheFatal(pEntry, "%s - waitpid failed rc=%d: %s\n",
-                       pszMsg, pidWait, strerror(errno));
+        FatalDie("%s - waitpid failed rc=%d: %s\n", pidWait, strerror(errno));
     if (!WIFEXITED(iStatus))
-        kObjCacheFatal(pEntry, "%s - abended (iStatus=%#x)\n", pszMsg, iStatus);
+        FatalDie("%s - abended (iStatus=%#x)\n", pszMsg, iStatus);
     if (WEXITSTATUS(iStatus))
-        kObjCacheFatal(pEntry, "%s - failed with rc %d\n", pszMsg, WEXITSTATUS(iStatus));
+        FatalDie("%s - failed with rc %d\n", pszMsg, WEXITSTATUS(iStatus));
 #endif
-    (void)cArgv;
-}
-#endif /* USE_PIPE */
-
-
-/**
- * Reads the (new) output of the precompiler.
- *
- * Not used when using pipes.
- *
- * @param   pEntry      The cache entry. cbNewCpp and pszNewCppMapping will be updated.
- */
-static void kObjCacheReadPrecompileOutput(PKOBJCACHE pEntry)
-{
-    pEntry->pszNewCppMapping = ReadFileInDir(pEntry->pszNewCppName, pEntry->pszDir, &pEntry->cbNewCpp);
-    if (!pEntry->pszNewCppMapping)
-        kObjCacheFatal(pEntry, "failed to open/read '%s' in '%s': %s\n",
-                       pEntry->pszNewCppName, pEntry->pszDir, strerror(errno));
-    kObjCacheVerbose(pEntry, "precompiled file is %lu bytes long\n", (unsigned long)pEntry->cbNewCpp);
-}
-
-
-/**
- * Worker for kObjCachePreCompile and calculates the checksum of
+    (void)pEntry;
+}
+
+
+/**
+ * Creates a pipe for setting up redirected stdin/stdout.
+ *
+ * @param   pEntry          The cache entry.
+ * @param   pFDs            Where to store the two file descriptors.
+ * @param   pszMsg          The operation message for info/error messages.
+ */
+static void kOCEntryCreatePipe(PKOCENTRY pEntry, int *pFDs, const char *pszMsg)
+{
+#if defined(__WIN__)
+    if (_pipe(pFDs, 0, _O_NOINHERIT | _O_BINARY) < 0)
+#else
+    if (pipe(pFDs) < 0)
+#endif
+        FatalDie("%s - pipe failed: %s\n", pszMsg, strerror(errno));
+#if !defined(__WIN__)
+    fcntl(pFDs[0], F_SETFD, FD_CLOEXEC);
+    fcntl(pFDs[1], F_SETFD, FD_CLOEXEC);
+#endif
+}
+
+
+/**
+ * Spawns a child that produces output to stdout.
+ *
+ * @param   papszArgv       Argument vector. The cArgv element is NULL.
+ * @param   cArgv           The number of arguments in the vector.
+ * @param   pszMsg          The operation message for info/error messages.
+ * @param   pfnConsumer     Pointer to a consumer callback function that is responsible
+ *                          for servicing the child output and closing the pipe.
+ */
+static void kOCEntrySpawnProducer(PKOCENTRY pEntry, const char **papszArgv, unsigned cArgv, const char *pszMsg,
+                                  void (*pfnConsumer)(PKOCENTRY, int))
+{
+    int fds[2];
+    pid_t pid;
+
+    kOCEntryCreatePipe(pEntry, fds, pszMsg);
+    pid = kOCEntrySpawnChild(pEntry, papszArgv, cArgv, -1, fds[1 /* write */], pszMsg);
+
+    pfnConsumer(pEntry, fds[0 /* read */]);
+
+    kOCEntryWaitChild(pEntry, pid, pszMsg);
+}
+
+
+/**
+ * Spawns a child that consumes input on stdin.
+ *
+ * @param   papszArgv       Argument vector. The cArgv element is NULL.
+ * @param   cArgv           The number of arguments in the vector.
+ * @param   pszMsg          The operation message for info/error messages.
+ * @param   pfnProducer     Pointer to a producer callback function that is responsible
+ *                          for serving the child input and closing the pipe.
+ */
+static void kOCEntrySpawnConsumer(PKOCENTRY pEntry, const char **papszArgv, unsigned cArgv, const char *pszMsg,
+                                  void (*pfnProducer)(PKOCENTRY, int))
+{
+    int fds[2];
+    pid_t pid;
+
+    kOCEntryCreatePipe(pEntry, fds, pszMsg);
+    pid = kOCEntrySpawnChild(pEntry, papszArgv, cArgv, fds[0 /* read */], -1, pszMsg);
+
+    pfnProducer(pEntry, fds[1 /* write */]);
+
+    kOCEntryWaitChild(pEntry, pid, pszMsg);
+}
+
+
+/**
+ * Spawns two child processes, one producing output and one consuming.
+ * Terminating on failure.
+ *
+ * @param   papszArgv       Argument vector. The cArgv element is NULL.
+ * @param   cArgv           The number of arguments in the vector.
+ * @param   pszMsg          The operation message for info/error messages.
+ * @param   pfnConsumer     Pointer to a consumer callback function that is responsible
+ *                          for servicing the child output and closing the pipe.
+ */
+static void kOCEntrySpawnTee(PKOCENTRY pEntry, const char **papszProdArgv, unsigned cProdArgv,
+                             const char **papszConsArgv, unsigned cConsArgv,
+                             const char *pszMsg, void (*pfnTeeConsumer)(PKOCENTRY, int, int))
+{
+    int fds[2];
+    int fdIn, fdOut;
+    pid_t pidProducer, pidConsumer;
+
+    /*
+     * The producer.
+     */
+    kOCEntryCreatePipe(pEntry, fds, pszMsg);
+    pidConsumer = kOCEntrySpawnChild(pEntry, papszProdArgv, cProdArgv, -1, fds[1 /* write */], pszMsg);
+    fdIn = fds[0 /* read */];
+
+    /*
+     * The consumer.
+     */
+    kOCEntryCreatePipe(pEntry, fds, pszMsg);
+    pidProducer = kOCEntrySpawnChild(pEntry, papszProdArgv, cProdArgv, fds[0 /* read */], -1, pszMsg);
+    fdOut = fds[1 /* write */];
+
+    /*
+     * Hand it on to the tee consumer.
+     */
+    pfnTeeConsumer(pEntry, fdIn, fdOut);
+
+    /*
+     * Reap the children.
+     */
+    kOCEntryWaitChild(pEntry, pidProducer, pszMsg);
+    kOCEntryWaitChild(pEntry, pidConsumer, pszMsg);
+}
+
+
+/**
+ * Reads the output from the precompiler.
+ *
+ * @param   pEntry      The cache entry. New.cbCpp and New.pszCppMapping will be updated.
+ * @param   pWhich      Specifies what to read (old/new).
+ * @param   fNonFatal   Whether failure is fatal or not.
+ */
+static int kOCEntryReadCppOutput(PKOCENTRY pEntry, struct KOCENTRYDATA *pWhich, int fNonFatal)
+{
+    pWhich->pszCppMapping = ReadFileInDir(pWhich->pszCppName, pEntry->pszDir, &pWhich->cbCpp);
+    if (!pWhich->pszCppMapping)
+    {
+        if (!fNonFatal)
+            FatalDie("failed to open/read '%s' in '%s': %s\n",
+                     pWhich->pszCppName, pEntry->pszDir, strerror(errno));
+        InfoMsg(1, "failed to open/read '%s' in '%s': %s\n",
+                pWhich->pszCppName, pEntry->pszDir, strerror(errno));
+        return -1;
+    }
+
+    InfoMsg(1, "precompiled file is %lu bytes long\n", (unsigned long)pWhich->cbCpp);
+    return 0;
+}
+
+
+/**
+ * Worker for kOCEntryPreCompile and calculates the checksum of
  * the precompiler output.
  *
  * @param   pEntry      The cache entry. NewSum will be updated.
  */
-static void kObjCacheCalcChecksum(PKOBJCACHE pEntry)
-{
-    struct MD5Context MD5Ctx;
-
-    memset(&pEntry->NewSum, 0, sizeof(pEntry->NewSum));
-    pEntry->NewSum.crc32 = crc32(0, pEntry->pszNewCppMapping, pEntry->cbNewCpp);
-    MD5Init(&MD5Ctx);
-    MD5Update(&MD5Ctx, (unsigned char *)pEntry->pszNewCppMapping, pEntry->cbNewCpp);
-    MD5Final(&pEntry->NewSum.md5[0], &MD5Ctx);
-    kObjCacheVerbose(pEntry, "crc32=%#lx md5=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
-                     pEntry->NewSum.crc32,
-                     pEntry->NewSum.md5[0], pEntry->NewSum.md5[1], pEntry->NewSum.md5[2], pEntry->NewSum.md5[3],
-                     pEntry->NewSum.md5[4], pEntry->NewSum.md5[5], pEntry->NewSum.md5[6], pEntry->NewSum.md5[7],
-                     pEntry->NewSum.md5[8], pEntry->NewSum.md5[9], pEntry->NewSum.md5[10], pEntry->NewSum.md5[11],
-                     pEntry->NewSum.md5[12], pEntry->NewSum.md5[13], pEntry->NewSum.md5[14], pEntry->NewSum.md5[15]);
-
-}
+static void kOCEntryCalcChecksum(PKOCENTRY pEntry)
+{
+    KOCSUMCTX Ctx;
+    kOCSumInitWithCtx(&pEntry->New.SumHead, &Ctx);
+    kOCSumUpdate(&pEntry->New.SumHead, &Ctx, pEntry->New.pszCppMapping, pEntry->New.cbCpp);
+    kOCSumFinalize(&pEntry->New.SumHead, &Ctx);
+    kOCSumInfo(&pEntry->New.SumHead, 1, "");
+}
+
+
+/**
+ * This consumes the precompiler output and checksums it.
+ *
+ * @param   pEntry  The cache entry.
+ * @param   fdIn    The precompiler output pipe.
+ * @param   fdOut   The compiler input pipe, -1 if no compiler.
+ */
+static void kOCEntryPreCompileConsumer(PKOCENTRY pEntry, int fdIn)
+{
+    KOCSUMCTX Ctx;
+    long cbLeft;
+    long cbAlloc;
+    char *psz;
+
+    kOCSumInitWithCtx(&pEntry->New.SumHead, &Ctx);
+    cbAlloc = pEntry->Old.cbCpp ? (pEntry->Old.cbCpp + 4*1024*1024 + 4096) & ~(4*1024*1024 - 1) : 4*1024*1024;
+    cbLeft = cbAlloc;
+    pEntry->New.pszCppMapping = psz = xmalloc(cbAlloc);
+    for (;;)
+    {
+        /*
+         * Read data from the pipe.
+         */
+        long cbRead = read(fdIn, psz, cbLeft - 1);
+        if (!cbRead)
+            break;
+        if (cbRead < 0)
+        {
+            if (errno == EINTR)
+                continue;
+            FatalDie("precompile - read(%d,,%ld) failed: %s\n",
+                     fdIn, (long)cbLeft, strerror(errno));
+        }
+
+        /*
+         * Process the data.
+         */
+        psz[cbRead] = '\0';
+        kOCSumUpdate(&pEntry->New.SumHead, &Ctx, psz, cbRead);
+
+        /*
+         * Advance.
+         */
+        psz += cbRead;
+        cbLeft -= cbRead;
+        if (cbLeft <= 1)
+        {
+            size_t off = psz - pEntry->New.pszCppMapping;
+            assert(off == cbAlloc);
+            cbLeft = 4*1024*1024;
+            cbAlloc += cbLeft;
+            pEntry->New.pszCppMapping = xrealloc(pEntry->New.pszCppMapping, cbAlloc);
+            psz = pEntry->New.pszCppMapping + off;
+        }
+    }
+
+    close(fdIn);
+    pEntry->New.cbCpp = cbAlloc - cbLeft;
+    kOCSumFinalize(&pEntry->New.SumHead, &Ctx);
+}
+
+
 
 
@@ -766,61 +1849,297 @@
  * @param   papszArgvPreComp    The argument vector for executing precompiler. The cArgvPreComp'th argument must be NULL.
  * @param   cArgvPreComp        The number of arguments.
- * @param   pszPreCompName      Precompile output name. (must kick around)
- * @param   fRedirStdOut        Whether stdout needs to be redirected or not.
- */
-static void kObjCachePreCompile(PKOBJCACHE pEntry, const char **papszArgvPreComp, unsigned cArgvPreComp, const char *pszPreCompName, int fRedirStdOut)
-{
-#ifdef USE_PIPE
-    /*
-     * Flag it as piped or non-piped.
-     */
-    if (fRedirStdOut)
-        pEntry->fPiped = 1;
+ */
+static void kOCEntryPreCompile(PKOCENTRY pEntry, const char **papszArgvPreComp, unsigned cArgvPreComp)
+{
+    /*
+     * If we're executing the precompiler in piped mode, it's relatively simple.
+     */
+    if (pEntry->fPipedPreComp)
+        kOCEntrySpawnProducer(pEntry, papszArgvPreComp, cArgvPreComp, "precompile",
+                              kOCEntryPreCompileConsumer);
     else
-#endif
-        pEntry->fPiped = 0;
-
-    /*
-     * Rename the old precompiled output to '-old'.
-     * We'll discard the old output and keep the new output, but because
-     * we might with to do a quick matchup later we can't remove it just now.
-     * If we're using the pipe strategy, we will not do any renaming.
-     */
-    if (    pEntry->pszOldCppName
-        &&  !pEntry->fPiped
-        &&  DoesFileInDirExist(pEntry->pszOldCppName, pEntry->pszDir))
-    {
-        size_t cch = strlen(pEntry->pszOldCppName);
-        char *psz = xmalloc(cch + sizeof("-old"));
-        memcpy(psz, pEntry->pszOldCppName, cch);
-        memcpy(psz + cch, "-old", sizeof("-old"));
-
-        kObjCacheVerbose(pEntry, "renaming '%s' to '%s' in '%s'\n", pEntry->pszOldCppName, psz, pEntry->pszDir);
-        UnlinkFileInDir(psz, pEntry->pszDir);
-        if (RenameFileInDir(pEntry->pszOldCppName, psz, pEntry->pszDir))
-            kObjCacheFatal(pEntry, "failed to rename '%s' -> '%s' in '%s': %s\n",
-                           pEntry->pszOldCppName, psz, pEntry->pszDir, strerror(errno));
-        free(pEntry->pszOldCppName);
-        pEntry->pszOldCppName = psz;
-    }
-    pEntry->pszNewCppName = CalcRelativeName(pszPreCompName, pEntry->pszDir);
-
-    /*
-     * Precompile it and calculate the checksum on the output.
-     */
-    kObjCacheVerbose(pEntry, "precompiling -> '%s'...\n", pEntry->pszNewCppName);
-#ifdef USE_PIPE
-    if (pEntry->fPiped)
-        kObjCacheSpawnPipe(pEntry, papszArgvPreComp, cArgvPreComp, "precompile", &pEntry->pszNewCppMapping, &pEntry->cbNewCpp);
+    {
+        /*
+         * Rename the old precompiled output to '-old' so the precompiler won't
+         * overwrite it when we execute it.
+         */
+        if (    pEntry->Old.pszCppName
+            &&  DoesFileInDirExist(pEntry->Old.pszCppName, pEntry->pszDir))
+        {
+            size_t cch = strlen(pEntry->Old.pszCppName);
+            char *psz = xmalloc(cch + sizeof("-old"));
+            memcpy(psz, pEntry->Old.pszCppName, cch);
+            memcpy(psz + cch, "-old", sizeof("-old"));
+
+            InfoMsg(1, "renaming '%s' to '%s' in '%s'\n", pEntry->Old.pszCppName, psz, pEntry->pszDir);
+            UnlinkFileInDir(psz, pEntry->pszDir);
+            if (RenameFileInDir(pEntry->Old.pszCppName, psz, pEntry->pszDir))
+                FatalDie("failed to rename '%s' -> '%s' in '%s': %s\n",
+                         pEntry->Old.pszCppName, psz, pEntry->pszDir, strerror(errno));
+            free(pEntry->Old.pszCppName);
+            pEntry->Old.pszCppName = psz;
+        }
+
+        /*
+         * Precompile it and calculate the checksum on the output.
+         */
+        InfoMsg(1, "precompiling -> '%s'...\n", pEntry->New.pszCppName);
+        kOCEntrySpawn(pEntry, papszArgvPreComp, cArgvPreComp, "precompile", NULL);
+        kOCEntryReadCppOutput(pEntry, &pEntry->New, 0 /* fatal */);
+        kOCEntryCalcChecksum(pEntry);
+    }
+}
+
+
+/**
+ * Worker function for kOCEntryTeeConsumer and kOCEntryCompileIt that
+ * writes the precompiler output to disk.
+ *
+ * @param   pEntry      The cache entry.
+ * @param   fFreeIt     Whether we can free it after writing it or not.
+ */
+static void kOCEntryWriteCppOutput(PKOCENTRY pEntry, int fFreeIt)
+{
+    /*
+     * Remove old files.
+     */
+    if (pEntry->Old.pszCppName)
+        UnlinkFileInDir(pEntry->Old.pszCppName, pEntry->pszDir);
+    if (pEntry->New.pszCppName)
+        UnlinkFileInDir(pEntry->New.pszCppName, pEntry->pszDir);
+
+    /*
+     * Write it to disk if we've got a file name.
+     */
+    if (pEntry->New.pszCppName)
+    {
+        long cbLeft;
+        char *psz;
+        int fd = OpenFileInDir(pEntry->New.pszCppName, pEntry->pszDir, O_WRONLY | O_TRUNC | O_BINARY, 0666);
+        if (fd == -1)
+            FatalDie("Failed to create '%s' in '%s': %s\n",
+                     pEntry->New.pszCppName, pEntry->pszDir, strerror(errno));
+        psz = pEntry->New.pszCppMapping;
+        cbLeft = pEntry->New.cbCpp;
+        while (cbLeft > 0)
+        {
+            long cbWritten = write(fd, psz, cbLeft);
+            if (cbWritten < 0)
+            {
+                int iErr = errno;
+                if (iErr == EINTR)
+                    continue;
+                close(fd);
+                UnlinkFileInDir(pEntry->New.pszCppName, pEntry->pszDir);
+                FatalDie("error writing '%s' in '%s': %s\n",
+                         pEntry->New.pszCppName, pEntry->pszDir, strerror(iErr));
+            }
+        }
+        close(fd);
+    }
+
+    /*
+     * Free it.
+     */
+    if (fFreeIt)
+    {
+        free(pEntry->New.pszCppMapping);
+        pEntry->New.pszCppMapping = NULL;
+    }
+}
+
+
+/**
+ * kOCEntrySpawnConsumer callback that passes the precompiler
+ * output to the compiler and writes it to the disk (latter only when necesary).
+ *
+ * @param   pEntry      The cache entry.
+ * @param   fdOut       The pipe handle connected to the childs stdin.
+ */
+static void kOCEntryCompileProducer(PKOCENTRY pEntry, int fdOut)
+{
+    const char *psz = pEntry->New.pszCppMapping;
+    long cbLeft = pEntry->New.cbCpp;
+    while (cbLeft > 0)
+    {
+        long cbWritten = write(fdOut, psz, cbLeft);
+        if (cbWritten < 0)
+        {
+            if (errno == EINTR)
+                continue;
+            FatalDie("compile - write(%d,,%ld) failed: %s\n", fdOut, cbLeft, strerror(errno));
+        }
+        psz += cbWritten;
+        cbLeft -= cbWritten;
+    }
+
+    if (pEntry->fPipedPreComp)
+        kOCEntryWriteCppOutput(pEntry, 1 /* free it */);
+}
+
+
+/**
+ * Does the actual compiling.
+ *
+ * @param   pEntry      The cache entry.
+ */
+static void kOCEntryCompileIt(PKOCENTRY pEntry)
+{
+    /*
+     * Delete the object files and free old cpp output that's no longer needed.
+     */
+    if (pEntry->Old.pszObjName)
+        UnlinkFileInDir(pEntry->Old.pszObjName, pEntry->pszDir);
+    UnlinkFileInDir(pEntry->New.pszObjName, pEntry->pszDir);
+
+    free(pEntry->Old.pszCppMapping);
+    pEntry->Old.pszCppMapping = NULL;
+    if (!pEntry->fPipedPreComp && !pEntry->fPipedCompile)
+    {
+        free(pEntry->New.pszCppMapping);
+        pEntry->New.pszCppMapping = NULL;
+    }
+
+    /*
+     * Do the (re-)compile job.
+     */
+    if (pEntry->fPipedCompile)
+    {
+        if (    !pEntry->fPipedPreComp
+            &&  !pEntry->New.pszCppMapping)
+            kOCEntryReadCppOutput(pEntry, &pEntry->New, 0 /* fatal */);
+        InfoMsg(1, "compiling -> '%s'...\n", pEntry->New.pszObjName);
+        kOCEntrySpawnConsumer(pEntry, pEntry->New.papszArgvCompile, pEntry->New.cArgvCompile,
+                              "compile", kOCEntryCompileProducer);
+    }
     else
-#endif
-    {
-        if (fRedirStdOut)
-            kObjCacheSpawn(pEntry, papszArgvPreComp, cArgvPreComp, "precompile", pszPreCompName);
-        else
-            kObjCacheSpawn(pEntry, papszArgvPreComp, cArgvPreComp, "precompile", NULL);
-        kObjCacheReadPrecompileOutput(pEntry);
-    }
-    kObjCacheCalcChecksum(pEntry);
+    {
+        if (pEntry->fPipedPreComp)
+            kOCEntryWriteCppOutput(pEntry, 1 /* free it */);
+        InfoMsg(1, "compiling -> '%s'...\n", pEntry->New.pszObjName);
+        kOCEntrySpawn(pEntry, pEntry->New.papszArgvCompile, pEntry->New.cArgvCompile, "compile", NULL);
+    }
+}
+
+
+/**
+ * kOCEntrySpawnTee callback that works sort of like 'tee'.
+ *
+ * It will calculate the precompiled output checksum and
+ * write it to disk while the compiler is busy compiling it.
+ *
+ * @param   pEntry  The cache entry.
+ * @param   fdIn    The input handle (connected to the precompiler).
+ * @param   fdOut   The output handle (connected to the compiler).
+ */
+static void kOCEntryTeeConsumer(PKOCENTRY pEntry, int fdIn, int fdOut)
+{
+    KOCSUMCTX Ctx;
+    long cbLeft;
+    long cbAlloc;
+    char *psz;
+
+    kOCSumInitWithCtx(&pEntry->New.SumHead, &Ctx);
+    cbAlloc = pEntry->Old.cbCpp ? (pEntry->Old.cbCpp + 4*1024*1024 + 4096) & ~(4*1024*1024 - 1) : 4*1024*1024;
+    cbLeft = cbAlloc;
+    pEntry->New.pszCppMapping = psz = xmalloc(cbAlloc);
+    for (;;)
+    {
+        /*
+         * Read data from the pipe.
+         */
+        long cbRead = read(fdIn, psz, cbLeft - 1);
+        if (!cbRead)
+            break;
+        if (cbRead < 0)
+        {
+            if (errno == EINTR)
+                continue;
+            FatalDie("precompile|compile - read(%d,,%ld) failed: %s\n",
+                     fdIn, (long)cbLeft, strerror(errno));
+        }
+
+        /*
+         * Process the data.
+         */
+        psz[cbRead] = '\0';
+        kOCSumUpdate(&pEntry->New.SumHead, &Ctx, psz, cbRead);
+        do
+        {
+            long cbWritten = write(fdOut, psz, cbRead);
+            if (cbWritten < 0)
+            {
+                if (errno == EINTR)
+                    continue;
+                FatalDie("precompile|compile - write(%d,,%ld) failed: %s\n", fdOut, cbRead, strerror(errno));
+            }
+            psz += cbWritten;
+            cbRead -= cbWritten;
+            cbLeft -= cbWritten;
+        } while (cbRead > 0);
+
+        /*
+         * Expand the buffer?
+         */
+        if (cbLeft <= 1)
+        {
+            size_t off = psz - pEntry->New.pszCppMapping;
+            assert(off == cbAlloc);
+            cbLeft = 4*1024*1024;
+            cbAlloc += cbLeft;
+            pEntry->New.pszCppMapping = xrealloc(pEntry->New.pszCppMapping, cbAlloc);
+            psz = pEntry->New.pszCppMapping + off;
+        }
+    }
+
+    close(fdIn);
+    close(fdOut);
+    pEntry->New.cbCpp = cbAlloc - cbLeft;
+    kOCSumFinalize(&pEntry->New.SumHead, &Ctx);
+
+    /*
+     * Write the precompiler output to disk and free the memory it
+     * occupies while the compiler is busy compiling.
+     */
+    kOCEntryWriteCppOutput(pEntry, 1 /* free it */);
+}
+
+
+/**
+ * Performs pre-compile and compile in one go (typical clean build scenario).
+ *
+ * @param   pEntry              The cache entry.
+ * @param   papszArgvPreComp    The argument vector for executing precompiler. The cArgvPreComp'th argument must be NULL.
+ * @param   cArgvPreComp        The number of arguments.
+ */
+static void kOCEntryPreCompileAndCompile(PKOCENTRY pEntry, const char **papszArgvPreComp, unsigned cArgvPreComp)
+{
+    if (    pEntry->fPipedCompile
+        &&  pEntry->fPipedPreComp)
+    {
+        /*
+         * Clean up old stuff first.
+         */
+        if (pEntry->Old.pszObjName)
+            UnlinkFileInDir(pEntry->Old.pszObjName, pEntry->pszDir);
+        if (pEntry->New.pszObjName)
+            UnlinkFileInDir(pEntry->New.pszObjName, pEntry->pszDir);
+        if (pEntry->Old.pszCppName)
+            UnlinkFileInDir(pEntry->Old.pszCppName, pEntry->pszDir);
+        if (pEntry->New.pszCppName)
+            UnlinkFileInDir(pEntry->New.pszCppName, pEntry->pszDir);
+
+        /*
+         * Do the actual compile and write the precompiler output to disk.
+         */
+        kOCEntrySpawnTee(pEntry, papszArgvPreComp, cArgvPreComp,
+                         pEntry->New.papszArgvCompile, pEntry->New.cArgvCompile,
+                         "precompile|compile", kOCEntryTeeConsumer);
+    }
+    else
+    {
+        kOCEntryPreCompile(pEntry, papszArgvPreComp, cArgvPreComp);
+        kOCEntryCompileIt(pEntry);
+    }
 }
 
@@ -834,5 +2153,5 @@
  * @parma   ppszFile    Where to store the start of the filename.
  */
-static int IsLineStatement(const char *psz, unsigned *piLine, const char **ppszFile)
+static int kOCEntryIsLineStatement(const char *psz, unsigned *piLine, const char **ppszFile)
 {
     unsigned iLine;
@@ -883,5 +2202,5 @@
  * @param   piLine          The line number count to adjust.
  */
-static const char *FindFileStatement(const char *pszStart, const char *pszStop, unsigned *piLine)
+static const char *kOCEntryFindFileStatement(const char *pszStart, const char *pszStop, unsigned *piLine)
 {
     unsigned iLine = *piLine;
@@ -899,5 +2218,5 @@
                 psz--;
             if (    (psz < pszStop || *psz == '\n')
-                &&  IsLineStatement(pszStart, &iLineTmp, &pszFile))
+                &&  kOCEntryIsLineStatement(pszStart, &iLineTmp, &pszFile))
             {
                 *piLine = iLine + iLineTmp - 1;
@@ -912,5 +2231,5 @@
 
 /**
- * Worker for kObjCacheCompareOldAndNewOutput() that compares the
+ * Worker for kOCEntryCompareOldAndNewOutput() that compares the
  * precompiled output using a fast but not very good method.
  *
@@ -919,10 +2238,10 @@
  *                      The entry is not updated in any way.
  */
-static int kObjCacheCompareFast(PCKOBJCACHE pEntry)
-{
-    const char *        psz1 = pEntry->pszNewCppMapping;
-    const char * const  pszEnd1 = psz1 + pEntry->cbNewCpp;
-    const char *        psz2 = pEntry->pszOldCppMapping;
-    const char * const  pszEnd2 = psz2 + pEntry->cbOldCpp;
+static int kOCEntryCompareFast(PCKOCENTRY pEntry)
+{
+    const char *        psz1 = pEntry->New.pszCppMapping;
+    const char * const  pszEnd1 = psz1 + pEntry->New.cbCpp;
+    const char *        psz2 = pEntry->Old.pszCppMapping;
+    const char * const  pszEnd2 = psz2 + pEntry->Old.cbCpp;
 
     assert(*pszEnd1 == '\0');
@@ -986,5 +2305,5 @@
             /* locate the start of that line. */
             psz = psz1;
-            while (     psz > pEntry->pszNewCppMapping
+            while (     psz > pEntry->New.pszCppMapping
                    &&   psz[-1] != '\n')
                 psz--;
@@ -1011,5 +2330,5 @@
                         iLine1++;
                     }
-                    else if (*psz == '#' && IsLineStatement(psz, &iLine1, &pszFile1))
+                    else if (*psz == '#' && kOCEntryIsLineStatement(psz, &iLine1, &pszFile1))
                     {
                         psz1 = memchr(psz, '\n', pszEnd1 - psz);
@@ -1053,5 +2372,5 @@
                         iLine2++;
                     }
-                    else if (*psz == '#' && IsLineStatement(psz, &iLine2, &pszFile2))
+                    else if (*psz == '#' && kOCEntryIsLineStatement(psz, &iLine2, &pszFile2))
                     {
                         psz2 = memchr(psz, '\n', pszEnd2 - psz);
@@ -1098,7 +2417,7 @@
             /* Check that we're at the same location now. */
             if (!pszFile1)
-                pszFile1 = FindFileStatement(pszMismatch1, pEntry->pszNewCppMapping, &iLine1);
+                pszFile1 = kOCEntryFindFileStatement(pszMismatch1, pEntry->New.pszCppMapping, &iLine1);
             if (!pszFile2)
-                pszFile2 = FindFileStatement(pszMismatch2, pEntry->pszOldCppMapping, &iLine2);
+                pszFile2 = kOCEntryFindFileStatement(pszMismatch2, pEntry->Old.pszCppMapping, &iLine2);
             if (pszFile1 && pszFile2)
             {
@@ -1138,341 +2457,930 @@
 
 /**
- * Worker for kObjCacheCompileIfNeeded that compares the
+ * Worker for kOCEntryCompileIfNeeded that compares the
  * precompiled output.
  *
  * @returns 1 if matching, 0 if not matching.
  * @param   pEntry      The entry containing the names of the files to compare.
- *                      This will load the old cpp output (changing pszOldCppName and cbOldCpp).
- */
-static int kObjCacheCompareOldAndNewOutput(PKOBJCACHE pEntry)
-{
-    /** @todo do some quick but fancy comparing that determins whether code
-     * has actually changed or moved. We can ignore declarations and typedefs that
-     * has just been moved up/down a bit. The typical case is adding a new error
-     * #define that doesn't influence the current compile job. */
-
-    /*
-     * Load the old output.
-     */
-    pEntry->pszOldCppMapping = ReadFileInDir(pEntry->pszOldCppName, pEntry->pszDir, &pEntry->cbOldCpp);
-    if (!pEntry->pszOldCppMapping)
-    {
-        kObjCacheVerbose(pEntry, "failed to read old cpp file ('%s' in '%s'): %s\n",
-                         pEntry->pszOldCppName, pEntry->pszDir, strerror(errno));
+ *                      This will load the old cpp output (changing pszOldCppName and Old.cbCpp).
+ */
+static int kOCEntryCompareOldAndNewOutput(PKOCENTRY pEntry)
+{
+    /*
+     * I may implement a more sophisticated alternative method later... maybe.
+     */
+    if (kOCEntryReadCppOutput(pEntry, &pEntry->Old, 1 /* nonfatal */) == -1)
         return 0;
-    }
-
-    /*
-     * I may implement a more sophisticated alternative method later... maybe.
-     */
     //if ()
-    //    return kObjCacheCompareBest(pEntry);
-    return kObjCacheCompareFast(pEntry);
-}
-
-
-/**
- * Worker for kObjCacheCompileIfNeeded that does the actual (re)compilation.
- *
- * @returns 1 if matching, 0 if not matching.
+    //    return kOCEntryCompareBest(pEntry);
+    return kOCEntryCompareFast(pEntry);
+}
+
+
+/**
+ * Check if re-compilation is required.
+ * This sets the fNeedCompile flag.
+ *
  * @param   pEntry              The cache entry.
- * @param   papszArgvCompile    The argument vector for invoking the compiler. The cArgvCompile'th entry must be NULL.
- * @param   cArgvCompile        The number of arguments in the vector.
- * @param   pszObjName          The name of the object file.
- */
-static void kObjCacheCompileIt(PKOBJCACHE pEntry, const char **papszArgvCompile, unsigned cArgvCompile, const char *pszObjName)
-{
-    /*
-     * Delete the old object file and precompiler output.
-     */
-    if (pEntry->pszObjName)
-    {
-        UnlinkFileInDir(pEntry->pszObjName, pEntry->pszDir);
-        pEntry->pszObjName = NULL;
-    }
-    pEntry->pszNewObjName = CalcRelativeName(pszObjName, pEntry->pszDir);
-
-    /*
-     * If we executed the precompiled in piped mode we'll have to write the
-     * precompiler output to disk so the compile has some thing to chew on.
-     */
-    if (pEntry->fPiped)
-    {
-        FILE *pFile = FOpenFileInDir(pEntry->pszNewCppName, pEntry->pszDir, "wb");
-        if (!pFile)
-            kObjCacheFatal(pEntry, "failed to create file '%s' in '%s': %s\n",
-                           pEntry->pszNewCppName, pEntry->pszDir, strerror(errno));
-        if (fwrite(pEntry->pszNewCppMapping, pEntry->cbNewCpp, 1, pFile) != 1)
-            kObjCacheFatal(pEntry, "fwrite failed: %s\n", strerror(errno));
-        if (fclose(pFile))
-            kObjCacheFatal(pEntry, "fclose failed: %s\n", strerror(errno));
-    }
-
-    /*
-     * Release buffers we no longer need before starting the compile.
-     */
-    free(pEntry->pszNewCppMapping);
-    pEntry->pszNewCppMapping = NULL;
-    free(pEntry->pszOldCppMapping);
-    pEntry->pszOldCppMapping = NULL;
-
-    /*
-     * Do the recompilation.
-     */
-    kObjCacheVerbose(pEntry, "compiling -> '%s'...\n", pEntry->pszNewObjName);
-    pEntry->papszArgvCompile = (char **)papszArgvCompile; /* LEAK */
-    pEntry->cArgvCompile = cArgvCompile;
-    kObjCacheSpawn(pEntry, papszArgvCompile, cArgvCompile, "compile", NULL);
-}
-
-
-/**
- * Check if (re-)compilation is required and do it.
- *
- * @returns 1 if matching, 0 if not matching.
- * @param   pEntry              The cache entry.
- * @param   papszArgvCompile    The argument vector for invoking the compiler. The cArgvCompile'th entry must be NULL.
- * @param   cArgvCompile        The number of arguments in the vector.
- * @param   pszObjName          The name of the object file.
- */
-static void kObjCacheCompileIfNeeded(PKOBJCACHE pEntry, const char **papszArgvCompile, unsigned cArgvCompile, const char *pszObjName)
-{
-    /*
-     * Does the object name differ?
-     */
-    if (!pEntry->fNeedCompiling)
-    {
-        char *pszTmp = CalcRelativeName(pszObjName, pEntry->pszDir);
-        if (strcmp(pEntry->pszObjName, pszTmp))
-        {
+ */
+static void kOCEntryCalcRecompile(PKOCENTRY pEntry)
+{
+    if (pEntry->fNeedCompiling)
+        return;
+
+    /*
+     * Check if the precompiler output differ in any significant way?
+     */
+    if (!kOCSumHasEqualInChain(&pEntry->Old.SumHead, &pEntry->New.SumHead))
+    {
+        InfoMsg(1, "no checksum match - comparing output\n");
+        if (!kOCEntryCompareOldAndNewOutput(pEntry))
             pEntry->fNeedCompiling = 1;
-            kObjCacheVerbose(pEntry, "object name changed '%s' -> '%s'\n", pEntry->pszObjName, pszTmp);
+        else
+            kOCSumAddChain(&pEntry->New.SumHead, &pEntry->Old.SumHead);
+    }
+}
+
+
+/**
+ * Does this cache entry need compiling or what?
+ *
+ * @returns 1 if it does, 0 if it doesn't.
+ * @param   pEntry      The cache entry in question.
+ */
+static int kOCEntryNeedsCompiling(PCKOCENTRY pEntry)
+{
+    return pEntry->fNeedCompiling;
+}
+
+
+/**
+ * Worker function for kOCEntryCopy.
+ *
+ * @param   pEntry      The entry we're coping to, which pszTo is relative to.
+ * @param   pszTo       The destination.
+ * @param   pszFrom     The source. This path will be freed.
+ */
+static void kOCEntryCopyFile(PCKOCENTRY pEntry, const char *pszTo, char *pszSrc)
+{
+    char *pszDst = MakePathFromDirAndFile(pszTo, pEntry->pszDir);
+    char *pszBuf = xmalloc(256 * 1024);
+    char *psz;
+    int fdSrc;
+    int fdDst;
+
+    /*
+     * Open the files.
+     */
+    fdSrc = open(pszSrc, O_RDONLY | O_BINARY);
+    if (fdSrc == -1)
+        FatalDie("failed to open '%s': %s\n", pszSrc, strerror(errno));
+
+    unlink(pszDst);
+    fdDst = open(pszDst, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666);
+    if (fdDst == -1)
+        FatalDie("failed to create '%s': %s\n", pszDst, strerror(errno));
+
+    /*
+     * Copy them.
+     */
+    for (;;)
+    {
+        /* read a chunk. */
+        long cbRead = read(fdSrc, pszBuf, 256*1024);
+        if (cbRead < 0)
+        {
+            if (errno == EINTR)
+                continue;
+            if (eof(fdSrc))
+                break;
+            FatalDie("read '%s' failed: %s\n", pszSrc, strerror(errno));
         }
-        free(pszTmp);
-    }
-
-    /*
-     * Does the compile command differ?
-     * TODO: Ignore irrelevant options here (like warning level).
-     */
-    if (    !pEntry->fNeedCompiling
-        &&  pEntry->cArgvCompile != cArgvCompile)
-    {
-        pEntry->fNeedCompiling = 1;
-        kObjCacheVerbose(pEntry, "compile argument count changed\n");
-    }
-    if (!pEntry->fNeedCompiling)
-    {
-        unsigned i;
-        for (i = 0; i < cArgvCompile; i++)
-            if (strcmp(papszArgvCompile[i], pEntry->papszArgvCompile[i]))
+
+        /* write the chunk. */
+        psz = pszBuf;
+        do
+        {
+            long cbWritten = write(fdDst, psz, cbRead);
+            if (cbWritten < 0)
             {
-                pEntry->fNeedCompiling = 1;
-                kObjCacheVerbose(pEntry, "compile argument differs (%#d)\n", i);
+                if (errno == EINTR)
+                    continue;
+                FatalDie("write '%s' failed: %s\n", pszSrc, strerror(errno));
+            }
+            psz += cbWritten;
+            cbRead -= cbWritten;
+        } while (cbRead > 0);
+    }
+
+    /* cleanup */
+    if (close(fdDst) != 0)
+        FatalDie("closing '%s' failed: %s\n", pszDst, strerror(errno));
+    close(fdSrc);
+    free(pszBuf);
+    free(pszDst);
+    free(pszSrc);
+}
+
+
+/**
+ * Copies the object (and whatever else) from one cache entry to another.
+ *
+ * This is called when a matching cache entry has been found and we don't
+ * need to recompile anything.
+ *
+ * @param   pEntry      The entry to copy to.
+ * @param   pFrom       The entry to copy from.
+ */
+static void kOCEntryCopy(PKOCENTRY pEntry, PCKOCENTRY pFrom)
+{
+    kOCEntryCopyFile(pEntry, pEntry->New.pszObjName,
+                     MakePathFromDirAndFile(pFrom->New.pszObjName
+                                            ? pFrom->New.pszObjName : pFrom->Old.pszObjName,
+                                            pFrom->pszDir));
+}
+
+
+/**
+ * Gets the absolute path to the cache entry.
+ *
+ * @returns absolute path to the cache entry.
+ * @param   pEntry      The cache entry in question.
+ */
+static const char *kOCEntryAbsPath(PCKOCENTRY pEntry)
+{
+    return pEntry->pszAbsPath;
+}
+
+
+
+
+
+
+/**
+ * Digest of one cache entry.
+ *
+ * This contains all the information required to find a matching
+ * cache entry without having to open each of the files.
+ */
+typedef struct KOCDIGEST
+{
+    /** The relative path to the entry. Optional if pszAbsPath is set. */
+    char *pszRelPath;
+    /** The absolute path to the entry. Optional if pszRelPath is set. */
+    char *pszAbsPath;
+    /** The target os/arch identifier. */
+    char *pszTarget;
+    /** A unique number assigned to the entry when it's (re)-inserted
+     * into the cache. This is used for simple consitency checking. */
+    uint32_t uKey;
+    /** The checksum of the compile argument vector. */
+    KOCSUM SumCompArgv;
+    /** The list of precompiler output checksums that's . */
+    KOCSUM SumHead;
+} KOCDIGEST;
+/** Pointer to a file digest. */
+typedef KOCDIGEST *PKOCDIGEST;
+/** Pointer to a const file digest. */
+typedef KOCDIGEST *PCKOCDIGEST;
+
+
+/**
+ * Initializes the specified digest.
+ *
+ * @param   pDigest     The digest.
+ */
+static void kOCDigestInit(PKOCDIGEST pDigest)
+{
+    memset(pDigest, 0, sizeof(*pDigest));
+    kOCSumInit(&pDigest->SumHead);
+}
+
+
+/**
+ * Initializes the digest for the specified entry.
+ *
+ * @param   pDigest     The (uninitialized) digest.
+ * @param   pEntry      The entry.
+ */
+static void kOCDigestInitFromEntry(PKOCDIGEST pDigest, PCKOCENTRY pEntry)
+{
+    kOCDigestInit(pDigest);
+
+    pDigest->uKey = pEntry->uKey;
+
+    kOCSumInit(&pDigest->SumCompArgv);
+    if (!kOCSumIsEmpty(&pEntry->New.SumCompArgv))
+        kOCSumAdd(&pDigest->SumCompArgv, &pEntry->New.SumCompArgv);
+    else
+        kOCSumAdd(&pDigest->SumCompArgv, &pEntry->Old.SumCompArgv);
+
+    kOCSumInit(&pDigest->SumHead);
+    if (!kOCSumIsEmpty(&pEntry->New.SumHead))
+        kOCSumAddChain(&pDigest->SumHead, &pEntry->New.SumHead);
+    else
+        kOCSumAddChain(&pDigest->SumHead, &pEntry->Old.SumHead);
+
+    /** @todo implement selective relative path support. */
+    pDigest->pszRelPath = NULL;
+    pDigest->pszAbsPath = xstrdup(kOCEntryAbsPath(pEntry));
+}
+
+
+/**
+ * Purges a digest, freeing all resources and returning
+ * it to the initial state.
+ *
+ * @param   pDigest     The digest.
+ */
+static void kOCDigestPurge(PKOCDIGEST pDigest)
+{
+    free(pDigest->pszRelPath);
+    free(pDigest->pszAbsPath);
+    free(pDigest->pszTarget);
+    pDigest->pszTarget = pDigest->pszAbsPath = pDigest->pszRelPath = NULL;
+    pDigest->uKey = 0;
+    kOCSumDeleteChain(&pDigest->SumCompArgv);
+    kOCSumDeleteChain(&pDigest->SumHead);
+}
+
+
+/**
+ * Returns the absolute path to the entry, calculating
+ * the path if necessary.
+ *
+ * @returns absolute path.
+ * @param   pDigest     The digest.
+ * @param   pszDir      The cache directory that it might be relative to.
+ */
+static const char *kOCDigestAbsPath(PCKOCDIGEST pDigest, const char *pszDir)
+{
+    if (!pDigest->pszAbsPath)
+    {
+        char *pszPath = MakePathFromDirAndFile(pDigest->pszRelPath, pszDir);
+        ((PKOCDIGEST)pDigest)->pszAbsPath = AbsPath(pszPath);
+        free(pszPath);
+    }
+    return pDigest->pszAbsPath;
+}
+
+
+/**
+ * Checks that the digest matches the
+ *
+ * @returns 1 if valid, 0 if invalid in some way.
+ *
+ * @param   pDigest     The digest to validate.
+ * @param   pEntry      What to validate it against.
+ */
+static int kOCDigestIsValid(PCKOCDIGEST pDigest, PCKOCENTRY pEntry)
+{
+    PCKOCSUM pSum;
+    PCKOCSUM pSumEntry;
+
+    if (pDigest->uKey != pEntry->uKey)
+        return 0;
+
+    if (!kOCSumIsEqual(&pDigest->SumCompArgv,
+                       kOCSumIsEmpty(&pEntry->New.SumCompArgv)
+                       ? &pEntry->Old.SumCompArgv : &pEntry->New.SumCompArgv))
+        return 0;
+
+    if (strcmp(pDigest->pszTarget, pEntry->New.pszTarget ? pEntry->New.pszTarget : pEntry->Old.pszTarget))
+        return 0;
+
+    /* match the checksums */
+    pSumEntry = kOCSumIsEmpty(&pEntry->New.SumHead)
+              ? &pEntry->Old.SumHead : &pEntry->New.SumHead;
+    for (pSum = &pDigest->SumHead; pSum; pSum = pSum->pNext)
+        if (!kOCSumHasEqualInChain(pSumEntry, pSum))
+            return 0;
+
+    return 1;
+}
+
+
+
+
+
+/**
+ * The structure for the central cache entry.
+ */
+typedef struct KOBJCACHE
+{
+    /** The entry name. */
+    const char *pszName;
+    /** The dir that relative names in the digest are relative to. */
+    char *pszDir;
+    /** The absolute path. */
+    char *pszAbsPath;
+
+    /** The cache file descriptor. */
+    int fd;
+    /** Whether it's currently locked or not. */
+    unsigned fLocked;
+    /** Whether the cache file is dirty and needs writing back. */
+    unsigned fDirty;
+    /** Whether this is a new cache or not. */
+    unsigned fNewCache;
+
+    /** The cache file generation. */
+    uint32_t uGeneration;
+    /** The next valid key. (Determin at load time.) */
+    uint32_t uNextKey;
+
+    /** Number of digests in paDigests. */
+    unsigned cDigests;
+    /** Array of digests for the KOCENTRY objects in the cache. */
+    PKOCDIGEST paDigests;
+
+} KOBJCACHE;
+/** Pointer to a cache. */
+typedef KOBJCACHE *PKOBJCACHE;
+/** Pointer to a const cache. */
+typedef KOBJCACHE const *PCKOBJCACHE;
+
+
+/**
+ * Creates an empty cache.
+ *
+ * This doesn't touch the file system, it just create the data structure.
+ *
+ * @returns Pointer to a cache.
+ * @param   pszCacheFile        The cache file.
+ */
+static PKOBJCACHE kObjCacheCreate(const char *pszCacheFile)
+{
+    PKOBJCACHE pCache;
+    size_t off;
+
+    /*
+     * Allocate an empty entry.
+     */
+    pCache = xmallocz(sizeof(*pCache));
+    pCache->fd = -1;
+
+    /*
+     * Setup the directory and cache file name.
+     */
+    pCache->pszAbsPath = AbsPath(pszCacheFile);
+    pCache->pszName = FindFilenameInPath(pCache->pszAbsPath);
+    off = pCache->pszName - pCache->pszAbsPath;
+    if (!off)
+        FatalDie("Failed to find abs path for '%s'!\n", pszCacheFile);
+    pCache->pszDir = xmalloc(off - 1);
+    memcpy(pCache->pszDir, pCache->pszAbsPath, off);
+    pCache->pszDir[off - 1] = '\0';
+
+    return pCache;
+}
+
+
+/**
+ * Purges the data in the cache object.
+ *
+ * @param   pCache      The cache object.
+ */
+static void kObjCachePurge(PKOBJCACHE pCache)
+{
+    while (pCache->cDigests > 0)
+        kOCDigestPurge(&pCache->paDigests[--pCache->cDigests]);
+    free(pCache->paDigests);
+    pCache->paDigests = NULL;
+    pCache->uGeneration = 0;
+    pCache->uNextKey = 0;
+}
+
+
+/**
+ * (Re-)reads the file.
+ *
+ * @param   pCache      The cache to (re)-read.
+ */
+static void kObjCacheRead(PKOBJCACHE pCache)
+{
+    FILE *pFile;
+    unsigned i;
+    int fBad = 0;
+
+    /*
+     * Rewind the file and associate it with a buffered file stream.
+     */
+    if (lseek(pCache->fd, 0, SEEK_SET) == -1)
+        FatalDie("lseek(cache-fd) failed: %s\n", strerror(errno));
+    pFile = fdopen(pCache->fd, "rb");
+    if (!pFile)
+        FatalDie("fdopen(cache-fd,rb) failed: %s\n", strerror(errno));
+
+    /*
+     * Read magic and generation.
+     */
+    if (    !fgets(g_szLine, sizeof(g_szLine), pFile)
+        ||  strcmp(g_szLine, "magic=kObjCache-v0.1.0\n"))
+    {
+        InfoMsg(1, "bad cache file (magic)\n");
+        fBad = 1;
+    }
+    else if (    !fgets(g_szLine, sizeof(g_szLine), pFile)
+             ||  strncmp(g_szLine, "generation=", sizeof("generation=") - 1))
+    {
+        InfoMsg(1, "bad cache file (generation)\n");
+        fBad = 1;
+    }
+    else if (   pCache->uGeneration
+             && pCache->uGeneration == atol(&g_szLine[sizeof("generation=") - 1]))
+    {
+        InfoMsg(1, "cache file unchanged\n");
+        fBad = 0;
+    }
+    else
+    {
+        int fBadBeforeMissing;
+
+        /*
+         * Read everything (anew).
+         */
+        kObjCachePurge(pCache);
+        do
+        {
+            PKOCDIGEST pDigest;
+            char *pszNl;
+            char *pszVal;
+            char *psz;
+
+            /* Split the line and drop the trailing newline. */
+            pszVal = strchr(g_szLine, '=');
+            if ((fBad = pszVal == NULL))
+                break;
+            *pszVal++ = '\0';
+
+            pszNl = strchr(pszVal, '\n');
+            if (pszNl)
+                *pszNl = '\0';
+
+            /* digest '#'? */
+            psz = strchr(g_szLine, '#');
+            if (psz)
+            {
+                i = strtoul(psz, &psz, 0);
+                if ((fBad = psz && *psz))
+                    break;
+                if ((fBad = i >= pCache->cDigests))
+                    break;
+                pDigest = &pCache->paDigests[i];
+            }
+            else
+                pDigest = NULL;
+
+
+            /* string case on value name. */
+            if (!strncmp(g_szLine, "sum-#", sizeof("sum-sum-#") - 1))
+            {
+                KOCSUM Sum;
+                if ((fBad = kOCSumInitFromString(&Sum, pszVal) != 0))
+                    break;
+                kOCSumAdd(&pDigest->SumHead, &Sum);
+            }
+            else if (!strncmp(g_szLine, "digest-abs-#", sizeof("digest-abs-#") - 1))
+            {
+                if ((fBad = pDigest->pszAbsPath != NULL))
+                    break;
+                pDigest->pszAbsPath = xstrdup(pszVal);
+            }
+            else if (!strncmp(g_szLine, "digest-rel-#", sizeof("digest-rel-#") - 1))
+            {
+                if ((fBad = pDigest->pszRelPath != NULL))
+                    break;
+                pDigest->pszRelPath = xstrdup(pszVal);
+            }
+            else if (!strncmp(g_szLine, "key-#", sizeof("key-#") - 1))
+            {
+                if ((fBad = pDigest->uKey != 0))
+                    break;
+                pDigest->uKey = strtoul(pszVal, &psz, 0);
+                if ((fBad = psz && *psz))
+                    break;
+                if (pDigest->uKey >= pCache->uNextKey)
+                    pCache->uNextKey = pDigest->uKey + 1;
+            }
+            else if (!strncmp(g_szLine, "comp-argv-sum-#", sizeof("comp-argv-sum-#") - 1))
+            {
+                if ((fBad = !kOCSumIsEmpty(&pDigest->SumCompArgv)))
+                    break;
+                if ((fBad = kOCSumInitFromString(&pDigest->SumCompArgv, pszVal) != 0))
+                    break;
+            }
+            else if (!strncmp(g_szLine, "target-#", sizeof("target-#") - 1))
+            {
+                if ((fBad = pDigest->pszTarget != NULL))
+                    break;
+                pDigest->pszTarget = xstrdup(pszVal);
+            }
+            else if (!strcmp(g_szLine, "digests"))
+            {
+                if ((fBad = pCache->paDigests != NULL))
+                    break;
+                pCache->cDigests = strtoul(pszVal, &psz, 0);
+                if ((fBad = psz && *psz))
+                    break;
+                i = (pCache->cDigests + 4) & ~3;
+                pCache->paDigests = xmalloc(i * sizeof(pCache->paDigests[0]));
+                for (i = 0; i < pCache->cDigests; i++)
+                    kOCDigestInit(&pCache->paDigests[i]);
+            }
+            else if (!strcmp(g_szLine, "generation"))
+            {
+                if ((fBad = pCache->uGeneration != 0))
+                    break;
+                pCache->uGeneration = strtoul(pszVal, &psz, 0);
+                if ((fBad = psz && *psz))
+                    break;
+            }
+            else if (!strcmp(g_szLine, "the-end"))
+            {
+                fBad = strcmp(pszVal, "fine");
                 break;
             }
-    }
-
-    /*
-     * Does the object file exist?
-     */
-    if (    !pEntry->fNeedCompiling
-        &&  !DoesFileInDirExist(pEntry->pszObjName, pEntry->pszDir))
-    {
-        pEntry->fNeedCompiling = 1;
-        kObjCacheVerbose(pEntry, "object file doesn't exist\n");
-    }
-
-    /*
-     * Does the precompiled output differ in any significant way?
-     */
-    if (!pEntry->fNeedCompiling)
-    {
-        int fFound = 0;
-        PCKOCSUM pSum;
-        for (pSum = &pEntry->SumHead; pSum; pSum = pSum->pNext)
-            if (kObjCacheSumIsEqual(pSum, &pEntry->NewSum))
+            else
             {
-                fFound = 1;
+                fBad = 1;
                 break;
             }
-        if (!fFound)
-        {
-            kObjCacheVerbose(pEntry, "no checksum match - comparing output\n");
-            if (!kObjCacheCompareOldAndNewOutput(pEntry))
-                pEntry->fNeedCompiling = 1;
-            else
+        } while (fgets(g_szLine, sizeof(g_szLine), pFile));
+
+        /*
+         * Did we find everything?
+         */
+        fBadBeforeMissing = fBad;
+        if (    !fBad
+            &&  !pCache->uGeneration)
+            fBad = 1;
+        if (!fBad)
+            for (i = 0; i < pCache->cDigests; i++)
             {
-                /* insert the sum into the list. */
-                pEntry->NewSum.pNext = pEntry->SumHead.pNext;
-                pEntry->SumHead.pNext = &pEntry->NewSum;
+                if ((fBad = kOCSumIsEmpty(&pCache->paDigests[i].SumCompArgv)))
+                    break;
+                if ((fBad = kOCSumIsEmpty(&pCache->paDigests[i].SumHead)))
+                    break;
+                if ((fBad = pCache->paDigests[i].uKey == 0))
+                    break;
+                if ((fBad = pCache->paDigests[i].pszAbsPath == NULL
+                         && pCache->paDigests[i].pszRelPath == NULL))
+                    break;
+                if ((fBad = pCache->paDigests[i].pszTarget == NULL))
+                    break;
             }
+        if (fBad)
+            InfoMsg(1, "bad cache file (%s)\n", fBadBeforeMissing ? g_szLine : "missing stuff");
+        else if (ferror(pFile))
+        {
+            InfoMsg(1, "cache file read error\n");
+            fBad = 1;
         }
     }
-
-    /*
-     * Discard the old precompiled output if it's no longer needed.
-     */
-    if (    pEntry->pszOldCppName
-        &&  (   !pEntry->fPiped
-             || pEntry->fNeedCompiling))
-    {
-        UnlinkFileInDir(pEntry->pszOldCppName, pEntry->pszDir);
-        free(pEntry->pszOldCppName);
-        pEntry->pszOldCppName = NULL;
-    }
-
-    /*
-     * Do the compliation if found necessary.
-     */
-    if (pEntry->fNeedCompiling)
-        kObjCacheCompileIt(pEntry, papszArgvCompile, cArgvCompile, pszObjName);
-}
-
-
-/**
- * Gets the absolute path
- *
- * @returns A new heap buffer containing the absolute path.
- * @param   pszPath     The path to make absolute. (Readonly)
- */
-static char *AbsPath(const char *pszPath)
-{
-    char szTmp[PATH_MAX];
-#if defined(__OS2__) || defined(__WIN__)
-    if (!_fullpath(szTmp, *pszPath ? pszPath : ".", sizeof(szTmp)))
-        return xstrdup(pszPath);
+    if (fBad)
+    {
+        kObjCachePurge(pCache);
+        pCache->fNewCache = 1;
+    }
+
+    /*
+     * Close the stream.
+     */
+    fclose(pFile);
+}
+
+
+/**
+ * Re-writes the cache file.
+ *
+ * @param   pCache      The cache to commit and unlock.
+ */
+static void kObjCacheWrite(PKOBJCACHE pCache)
+{
+    FILE *pFile;
+    unsigned i;
+    off_t cb;
+    assert(pCache->fLocked);
+    assert(pCache->fDirty);
+
+    /*
+     * Rewind the file and associate it with a buffered file stream.
+     */
+    if (lseek(pCache->fd, 0, SEEK_SET) == -1)
+        FatalDie("lseek(cache-fd) failed: %s\n", strerror(errno));
+    pFile = fdopen(pCache->fd, "w+b");
+    if (!pFile)
+        FatalDie("fdopen(cache-fd,w+b) failed: %s\n", strerror(errno));
+
+    /*
+     * Write the header.
+     */
+    pCache->uGeneration++;
+    fprintf(pFile,
+            "magic=kObjCache-v0.1.0\n"
+            "generation=%d\n"
+            "digests=%d\n",
+            pCache->uGeneration,
+            pCache->cDigests);
+
+    /*
+     * Write the digests.
+     */
+    for (i = 0; pCache->cDigests; i++)
+    {
+        PCKOCDIGEST pDigest = &pCache->paDigests[i];
+        PKOCSUM pSum;
+
+        if (pDigest->pszAbsPath)
+            fprintf(pFile, "digest-abs-#%u=%s\n", i, pDigest->pszAbsPath);
+        if (pDigest->pszRelPath)
+            fprintf(pFile, "digest-rel-#%u=%s\n", i, pDigest->pszRelPath);
+        fprintf(pFile, "key-#%u=%u\n", i, pDigest->uKey);
+        fprintf(pFile, "target-#%u=%s\n", i, pDigest->pszTarget);
+        fprintf(pFile, "comp-argv-sum-#%u=", i);
+        kOCSumFPrintf(&pDigest->SumCompArgv, pFile);
+        for (pSum = &pDigest->SumHead; pSum; pSum = pSum->pNext)
+        {
+            fprintf(pFile, "sum-#%u=", i);
+            kOCSumFPrintf(pSum, pFile);
+        }
+    }
+
+    /*
+     * Close the stream and unlock fhe file.
+     * (Closing the stream shouldn't close the file handle IIRC...)
+     */
+    fprintf(pFile, "the-end=fine\n");
+    fflush(pFile);
+    if (    fflush(pFile) < 0
+        ||  ferror(pFile))
+    {
+        int iErr = errno;
+        fclose(pFile);
+        UnlinkFileInDir(pCache->pszName, pCache->pszDir);
+        FatalDie("Stream error occured while writing '%s' in '%s': %s\n",
+                 pCache->pszName, pCache->pszDir, strerror(iErr));
+    }
+    fclose(pFile);
+
+    cb = lseek(pCache->fd, 0, SEEK_CUR);
+    if (cb == -1)
+        FatalDie("lseek(cache-file,0,CUR) failed: %s\n", strerror(errno));
+#if defined(__WIN__)
+    if (_chsize(pCache->fd, cb) == -1)
 #else
-    if (!realpath(pszPath, szTmp))
-        return xstrdup(pszPath);
+    if (ftruncate(pCache->fd, cb) == -1)
 #endif
-   return xstrdup(szTmp);
-}
-
-
-/**
- * Utility function that finds the filename part in a path.
- *
- * @returns Pointer to the file name part (this may be "").
- * @param   pszPath     The path to parse.
- */
-static const char *FindFilenameInPath(const char *pszPath)
-{
-    const char *pszFilename = strchr(pszPath, '\0') - 1;
-    while (     pszFilename > pszPath
-           &&   !IS_SLASH_DRV(pszFilename[-1]))
-        pszFilename--;
-    return pszFilename;
-}
-
-
-/**
- * Utility function that combines a filename and a directory into a path.
- *
- * @returns malloced buffer containing the result.
- * @param   pszName     The file name.
- * @param   pszDir      The directory path.
- */
-static char *MakePathFromDirAndFile(const char *pszName, const char *pszDir)
-{
-    size_t cchName = strlen(pszName);
-    size_t cchDir = strlen(pszDir);
-    char *pszBuf = xmalloc(cchName + cchDir + 2);
-    memcpy(pszBuf, pszDir, cchDir);
-    if (cchDir > 0 && !IS_SLASH_DRV(pszDir[cchDir - 1]))
-        pszBuf[cchDir++] = PATH_SLASH;
-    memcpy(pszBuf + cchDir, pszName, cchName + 1);
-    return pszBuf;
-}
-
-
-/**
- * Compares two path strings to see if they are identical.
- *
- * This doesn't do anything fancy, just the case ignoring and
- * slash unification.
- *
- * @returns 1 if equal, 0 otherwise.
- * @param   pszPath1    The first path.
- * @param   pszPath2    The second path.
- * @param   cch         The number of characters to compare.
- */
-static int ArePathsIdentical(const char *pszPath1, const char *pszPath2, size_t cch)
-{
-#if defined(__OS2__) || defined(__WIN__)
-    if (strnicmp(pszPath1, pszPath2, cch))
-    {
-        /* Slashes may differ, compare char by char. */
-        const char *psz1 = pszPath1;
-        const char *psz2 = pszPath2;
-        for (;cch; psz1++, psz2++, cch--)
-        {
-            if (*psz1 != *psz2)
-            {
-                if (    tolower(*psz1) != tolower(*psz2)
-                    &&  toupper(*psz1) != toupper(*psz2)
-                    &&  *psz1 != '/'
-                    &&  *psz1 != '\\'
-                    &&  *psz2 != '/'
-                    &&  *psz2 != '\\')
-                    return 0;
-            }
+        FatalDie("file truncation failed: %s\n", strerror(errno));
+    InfoMsg(1, "Wrote '%s' in '%s', %d bytes\n", pCache->pszName, pCache->pszDir, cb);
+}
+
+
+/**
+ * Cleans out all invalid digests.s
+ *
+ * This is done periodically from the unlock routine to make
+ * sure we don't accidentally accumulate stale digests.
+ *
+ * @param   pCache      The cache to chek.
+ */
+static void kObjCacheClean(PKOBJCACHE pCache)
+{
+    unsigned i = pCache->cDigests;
+    while (i-- > 0)
+    {
+        /*
+         * Try open it and purge it if it's bad.
+         * (We don't kill the entry file because that's kmk clean's job.)
+         */
+        PCKOCDIGEST pDigest = &pCache->paDigests[i];
+        PKOCENTRY pEntry = kOCEntryCreate(kOCDigestAbsPath(pDigest, pCache->pszDir));
+        kOCEntryRead(pEntry);
+        if (    !kOCEntryCheck(pEntry)
+            ||  !kOCDigestIsValid(pDigest, pEntry))
+        {
+            unsigned cLeft;
+            kOCDigestPurge(pDigest);
+
+            pCache->cDigests--;
+            cLeft = pCache->cDigests - i;
+            if (cLeft)
+                memmove(pDigest, pDigest + 1, cLeft * sizeof(*pDigest));
+
+            pCache->fDirty = 1;
         }
-    }
-    return 1;
+        kOCEntryDestroy(pEntry);
+    }
+}
+
+
+/**
+ * Locks the cache for exclusive access.
+ *
+ * This will open the file if necessary and lock the entire file
+ * using the best suitable platform API (tricky).
+ *
+ * @param   pCache      The cache to lock.
+ */
+static void kObjCacheLock(PKOBJCACHE pCache)
+{
+    struct stat st;
+    assert(!pCache->fLocked);
+
+    /*
+     * Open it?
+     */
+    if (pCache->fd < 0)
+    {
+        pCache->fd = OpenFileInDir(pCache->pszName, pCache->pszDir, O_CREAT | O_RDWR | O_BINARY, 0666);
+        if (pCache->fd == -1)
+        {
+            MakePath(pCache->pszDir);
+            pCache->fd = OpenFileInDir(pCache->pszName, pCache->pszDir, O_CREAT | O_RDWR | O_BINARY, 0666);
+            if (pCache->fd == -1)
+                FatalDie("Failed to create '%s' in '%s': %s\n", pCache->pszName, pCache->pszDir, strerror(errno));
+        }
+    }
+
+    /*
+     * Lock it.
+     */
+#if defined(__WIN__)
+    if (!LockFileEx((HANDLE)_get_osfhandle(pCache->fd), LOCKFILE_EXCLUSIVE_LOCK, 0, ~0, 0, NULL))
+        FatalDie("Failed to lock the cache file: Windows Error %d\n", GetLastError());
 #else
-    return !strncmp(pszPath1, pszPath2, cch);
+# error port me....
 #endif
-}
-
-
-/**
- * Calculate how to get to pszPath from pszDir.
- *
- * @returns The relative path from pszDir to path pszPath.
- * @param   pszPath     The path to the object.
- * @param   pszDir      The directory it shall be relative to.
- */
-static char *CalcRelativeName(const char *pszPath, const char *pszDir)
-{
-    char *pszRet = NULL;
-    char *pszAbsPath = NULL;
-    size_t cchDir = strlen(pszDir);
-
-    /*
-     * This is indeed a bit tricky, so we'll try the easy way first...
-     */
-    if (ArePathsIdentical(pszPath, pszDir, cchDir))
-    {
-        if (pszPath[cchDir])
-            pszRet = (char *)pszPath + cchDir;
-        else
-            pszRet = "./";
-    }
+    pCache->fLocked = 1;
+
+    /*
+     * Check for new cache and read it it's an existing cache.
+     *
+     * There is no point in initializing a new cache until we've finished
+     * compiling and has something to put into it, so we'll leave it as a
+     * 0 byte file.
+     */
+    if (fstat(pCache->fd, &st) == -1)
+        FatalDie("fstat(cache-fd) failed: %s\n", strerror(errno));
+    if (st.st_size)
+        kObjCacheRead(pCache);
     else
-    {
-        pszAbsPath = AbsPath(pszPath);
-        if (ArePathsIdentical(pszAbsPath, pszDir, cchDir))
-        {
-            if (pszPath[cchDir])
-                pszRet = pszAbsPath + cchDir;
-            else
-                pszRet = "./";
+        pCache->fNewCache = 1;
+}
+
+
+/**
+ * Unlocks the cache (without writing anything back).
+ *
+ * @param   pCache      The cache to unlock.
+ */
+static void kObjCacheUnlock(PKOBJCACHE pCache)
+{
+    assert(pCache->fLocked);
+
+    /*
+     * Write it back if it's dirty.
+     */
+    if (pCache->fDirty)
+    {
+        if (    pCache->cDigests >= 16
+            &&  (pCache->uGeneration % 19) == 19)
+            kObjCacheClean(pCache);
+        kObjCacheWrite(pCache);
+        pCache->fDirty = 0;
+    }
+
+    /*
+     * Lock it.
+     */
+#if defined(__WIN__)
+    if (!UnlockFileEx((HANDLE)_get_osfhandle(pCache->fd), 0, ~0U, 0, NULL))
+        FatalDie("Failed to unlock the cache file: Windows Error %d\n", GetLastError());
+#else
+# error port me....
+#endif
+    pCache->fLocked = 0;
+}
+
+
+/**
+ * Removes the entry from the cache.
+ *
+ * The entry doesn't need to be in the cache.
+ * The cache entry (file) itself is not touched.
+ *
+ * @param   pCache      The cache.
+ * @param   pEntry      The entry.
+ */
+static void kObjCacheRemoveEntry(PKOBJCACHE pCache, PCKOCENTRY pEntry)
+{
+    unsigned i = pCache->cDigests;
+    while (i-- > 0)
+    {
+        PKOCDIGEST pDigest = &pCache->paDigests[i];
+        if (ArePathsIdentical(kOCDigestAbsPath(pDigest, pCache->pszDir),
+                              kOCEntryAbsPath(pEntry), ~0U))
+        {
+            unsigned cLeft;
+            kOCDigestPurge(pDigest);
+
+            pCache->cDigests--;
+            cLeft = pCache->cDigests - i;
+            if (cLeft)
+                memmove(pDigest, pDigest + 1, cLeft * sizeof(*pDigest));
+
+            pCache->fDirty = 1;
         }
     }
-    if (pszRet)
-    {
-        while (IS_SLASH_DRV(*pszRet))
-            pszRet++;
-        pszRet = xstrdup(pszRet);
-        free(pszAbsPath);
-        return pszRet;
-    }
-
-    /*
-     * Damn, it's gonna be complicated. Deal with that later.
-     */
-    fprintf(stderr, "kObjCache: complicated relative path stuff isn't implemented yet. sorry.\n");
-    exit(1);
+}
+
+
+/**
+ * Inserts the entry into the cache.
+ *
+ * The cache entry (file) itself is not touched by this operation,
+ * the pEntry object otoh is.
+ *
+ * @param   pCache      The cache.
+ * @param   pEntry      The entry.
+ */
+static void kObjCacheInsertEntry(PKOBJCACHE pCache, PKOCENTRY pEntry)
+{
+    unsigned i;
+
+    /*
+     * Find a new key.
+     */
+    pEntry->uKey = pCache->uNextKey++;
+    i = pCache->cDigests;
+    while (i-- > 0)
+        if (pCache->paDigests[i].uKey == pEntry->uKey)
+        {
+            pEntry->uKey = pCache->uNextKey++;
+            if (!pEntry->uKey)
+                pEntry->uKey = pCache->uNextKey++;
+            i = pCache->cDigests;
+        }
+
+    /*
+     * Reallocate the digest array?
+     */
+    if (    !(pCache->cDigests & 3)
+        &&  (pCache->cDigests || !pCache->paDigests))
+        pCache->paDigests = xrealloc(pCache->paDigests, sizeof(pCache->paDigests[0]) * pCache->cDigests + 4);
+
+    /*
+     * Create a new digest.
+     */
+    i = pCache->cDigests++;
+    kOCDigestInitFromEntry(&pCache->paDigests[i], pEntry);
+
+    pCache->fDirty = 1;
+}
+
+
+/**
+ * Find a matching cache entry.
+ */
+static PKOCENTRY kObjCacheFindMatchingEntry(PKOBJCACHE pCache, PCKOCENTRY pEntry)
+{
+    unsigned i = pCache->cDigests;
+
+    assert(pEntry->fNeedCompiling);
+    assert(!kOCSumIsEmpty(&pEntry->New.SumCompArgv));
+    assert(!kOCSumIsEmpty(&pEntry->New.SumHead));
+
+    while (i-- > 0)
+    {
+        /*
+         * Matching?
+         */
+        PCKOCDIGEST pDigest = &pCache->paDigests[i];
+        if (    kOCSumIsEqual(&pDigest->SumCompArgv, &pEntry->New.SumCompArgv)
+            &&  kOCSumHasEqualInChain(&pDigest->SumHead, &pEntry->New.SumHead))
+        {
+            /*
+             * Try open it.
+             */
+            unsigned cLeft;
+            PKOCENTRY pRetEntry = kOCEntryCreate(kOCDigestAbsPath(pDigest, pCache->pszDir));
+            kOCEntryRead(pRetEntry);
+            if (    kOCEntryCheck(pRetEntry)
+                &&  kOCDigestIsValid(pDigest, pRetEntry))
+                return pRetEntry;
+            kOCEntryDestroy(pRetEntry);
+
+            /* bad entry, purge it. */
+            kOCDigestPurge(pDigest);
+
+            pCache->cDigests--;
+            cLeft = pCache->cDigests - i;
+            if (cLeft)
+                memmove(pDigest, pDigest + 1, cLeft * sizeof(*pDigest));
+
+            pCache->fDirty = 1;
+        }
+    }
+
     return NULL;
 }
@@ -1480,150 +3388,12 @@
 
 /**
- * Utility function that combines a filename and directory and passes it onto fopen.
- *
- * @returns fopen return value.
- * @param   pszName     The file name.
- * @param   pszDir      The directory path.
- * @param   pszMode     The fopen mode string.
- */
-static FILE *FOpenFileInDir(const char *pszName, const char *pszDir, const char *pszMode)
-{
-    char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
-    FILE *pFile = fopen(pszPath, pszMode);
-    free(pszPath);
-    return pFile;
-}
-
-
-/**
- * Deletes a file in a directory.
- *
- * @returns whatever unlink returns.
- * @param   pszName     The file name.
- * @param   pszDir      The directory path.
- */
-static int UnlinkFileInDir(const char *pszName, const char *pszDir)
-{
-    char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
-    int rc = unlink(pszPath);
-    free(pszPath);
-    return rc;
-}
-
-
-/**
- * Renames a file in a directory.
- *
- * @returns whatever unlink returns.
- * @param   pszOldName  The new file name.
- * @param   pszNewName  The old file name.
- * @param   pszDir      The directory path.
- */
-static int RenameFileInDir(const char *pszOldName, const char *pszNewName, const char *pszDir)
-{
-    char *pszOldPath = MakePathFromDirAndFile(pszOldName, pszDir);
-    char *pszNewPath = MakePathFromDirAndFile(pszNewName, pszDir);
-    int rc = rename(pszOldPath, pszNewPath);
-    free(pszOldPath);
-    free(pszNewPath);
-    return rc;
-}
-
-
-/**
- * Check if a (regular) file exists in a directory.
- *
- * @returns 1 if it exists and is a regular file, 0 if not.
- * @param   pszName     The file name.
- * @param   pszDir      The directory path.
- */
-static int DoesFileInDirExist(const char *pszName, const char *pszDir)
-{
-    char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
-    struct stat st;
-    int rc = stat(pszPath, &st);
-    free(pszPath);
-#ifdef S_ISREG
-    return !rc && S_ISREG(st.st_mode);
-#elif defined(_MSC_VER)
-    return !rc && (st.st_mode & _S_IFMT) == _S_IFREG;
-#else
-#error "Port me"
-#endif
-}
-
-
-/**
- * Reads into memory an entire file.
- *
- * @returns Pointer to the heap allocation containing the file.
- *          On failure NULL and errno is returned.
- * @param   pszName     The file.
- * @param   pszDir      The directory the file resides in.
- * @param   pcbFile     Where to store the file size.
- */
-static void *ReadFileInDir(const char *pszName, const char *pszDir, size_t *pcbFile)
-{
-    int SavedErrno;
-    char *pszPath = MakePathFromDirAndFile(pszName, pszDir);
-    int fd = open(pszPath, O_RDONLY | O_BINARY);
-    if (fd >= 0)
-    {
-        off_t cbFile = lseek(fd, 0, SEEK_END);
-        if (    cbFile >= 0
-            &&  lseek(fd, 0, SEEK_SET) == 0)
-        {
-            char *pb = malloc(cbFile + 1);
-            if (pb)
-            {
-                if (read(fd, pb, cbFile) == cbFile)
-                {
-                    close(fd);
-                    pb[cbFile] = '\0';
-                    *pcbFile = (size_t)cbFile;
-                    return pb;
-                }
-                SavedErrno = errno;
-                free(pb);
-            }
-            else
-                SavedErrno = ENOMEM;
-        }
-        else
-            SavedErrno = errno;
-        close(fd);
-    }
-    else
-        SavedErrno = errno;
-    free(pszPath);
-    errno = SavedErrno;
-    return NULL;
-}
-
-
-static void *xmalloc(size_t cb)
-{
-    void *pv = malloc(cb);
-    if (!pv)
-        kObjCacheFatal(NULL, "out of memory (%d)\n", (int)cb);
-    return pv;
-}
-
-
-static void *xrealloc(void *pvOld, size_t cb)
-{
-    void *pv = realloc(pvOld, cb);
-    if (!pv)
-        kObjCacheFatal(NULL, "out of memory (%d)\n", (int)cb);
-    return pv;
-}
-
-
-static char *xstrdup(const char *pszIn)
-{
-    char *psz = strdup(pszIn);
-    if (!psz)
-        kObjCacheFatal(NULL, "out of memory (%d)\n", (int)strlen(pszIn));
-    return psz;
+ * Is this a new cache?
+ *
+ * @returns 1 if new, 0 if not new.
+ * @param   pEntry      The entry.
+ */
+static int kObjCacheIsNew(PKOBJCACHE pCache)
+{
+    return pCache->fNewCache;
 }
 
@@ -1653,9 +3423,19 @@
 static int usage(void)
 {
-    printf("syntax: kObjCache [-v|--verbose] [-f|--file] <cache-file> [-V|--version] [-r|--redir-stdout]\n"
-           "                  --kObjCache-cpp <filename> <precompiler + args> \n"
-           "                  --kObjCache-cc <object> <compiler + args>\n"
-           "                  [--kObjCache-both [args]]\n"
-           "                  [--kObjCache-cpp|--kObjCache-cc [more args]]\n"
+    printf("syntax: kObjCache [--kObjCache-options] [-v|--verbose]\n"
+           "            [-d|--cache-dir <cache-dir>] [-n|--name <name-in-cache>]\n"
+           "            [-f|--file <local-cache-file>] [-t|--target <target-name>\n"
+           "            [-r|--redir-stdout] [-p|--passthru]\n"
+           "            --kObjCache-cpp <filename> <precompiler + args>\n"
+           "            --kObjCache-cc <object> <compiler + args>\n"
+           "            [--kObjCache-both [args]]\n"
+           "            [--kObjCache-cpp|--kObjCache-cc [more args]]\n"
+           "        kObjCache <-V|--version>\n"
+           "        kObjCache [-?|-h|--help]\n"
+           "\n"
+           "The env.var. KOBJCACHE_DIR sets the default cache diretory (-d).\n"
+           "The env.var. KOBJCACHE_OPTS allow you to specifie additional options\n"
+           "without having to mess with the makefiles. These are appended with "
+           "a --kObjCache-options between them and the command args.\n"
            "\n");
     return 0;
@@ -1665,20 +3445,37 @@
 int main(int argc, char **argv)
 {
-    PKOBJCACHE pEntry;
-
-    const char *pszCacheFile;
+    PKOBJCACHE pCache;
+    PKOCENTRY pEntry;
+
+    const char *pszCacheDir = getenv("KOBJCACHE_DIR");
+    const char *pszCacheName = NULL;
+    const char *pszCacheFile = NULL;
+    const char *pszEntryFile = NULL;
 
     const char **papszArgvPreComp = NULL;
     unsigned cArgvPreComp = 0;
     const char *pszPreCompName = NULL;
-    int fRedirStdOut = 0;
+    int fRedirPreCompStdOut = 0;
 
     const char **papszArgvCompile = NULL;
     unsigned cArgvCompile = 0;
     const char *pszObjName = NULL;
+    int fRedirCompileStdIn = 0;
+
+    const char *pszTarget = NULL;
 
     enum { kOC_Options, kOC_CppArgv, kOC_CcArgv, kOC_BothArgv } enmMode = kOC_Options;
 
+    char *psz;
     int i;
+
+    SetErrorPrefix("kObjCache");
+
+    /*
+     * Arguments passed in the environmnet?
+     */
+    psz = getenv("KOBJCACHE_OPTS");
+    if (psz)
+        AppendArgs(&argc, &argv, psz, "--kObjCache-options");
 
     /*
@@ -1709,4 +3506,6 @@
         else if (!strcmp(argv[i], "--kObjCache-both"))
             enmMode = kOC_BothArgv;
+        else if (!strcmp(argv[i], "--kObjCache-options"))
+            enmMode = kOC_Options;
         else if (!strcmp(argv[i], "--help"))
             return usage();
@@ -1728,5 +3527,11 @@
             }
         }
-        else if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "--file"))
+        else if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "--entry-file"))
+        {
+            if (i + 1 >= argc)
+                return SyntaxError("%s requires a cache entry filename!\n", argv[i]);
+            pszEntryFile = argv[++i];
+        }
+        else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--cache-file"))
         {
             if (i + 1 >= argc)
@@ -1734,10 +3539,30 @@
             pszCacheFile = argv[++i];
         }
+        else if (!strcmp(argv[i], "-n") || !strcmp(argv[i], "--name"))
+        {
+            if (i + 1 >= argc)
+                return SyntaxError("%s requires a cache name!\n", argv[i]);
+            pszCacheName = argv[++i];
+        }
+        else if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--cache-dir"))
+        {
+            if (i + 1 >= argc)
+                return SyntaxError("%s requires a cache directory!\n", argv[i]);
+            pszCacheDir = argv[++i];
+        }
+        else if (!strcmp(argv[i], "-t") || !strcmp(argv[i], "--target"))
+        {
+            if (i + 1 >= argc)
+                return SyntaxError("%s requires a target platform/arch name!\n", argv[i]);
+            pszTarget = argv[++i];
+        }
+        else if (!strcmp(argv[i], "-p") || !strcmp(argv[i], "--passthru"))
+            fRedirPreCompStdOut = fRedirCompileStdIn = 1;
         else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--redir-stdout"))
-            fRedirStdOut = 1;
+            fRedirPreCompStdOut = 1;
         else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))
-            g_fVerbose = 1;
+            g_cVerbosityLevel++;
         else if (!strcmp(argv[i], "-q") || !strcmp(argv[i], "--quiet"))
-            g_fVerbose = 0;
+            g_cVerbosityLevel = 0;
         else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-?"))
             return usage();
@@ -1750,28 +3575,100 @@
             return SyntaxError("Doesn't grok '%s'!\n", argv[i]);
     }
+    if (!pszEntryFile)
+        return SyntaxError("No cache entry filename (-f)!\n");
+    if (!cArgvCompile)
+        return SyntaxError("No compiler arguments (--kObjCache-cc)!\n");
+    if (!cArgvPreComp)
+        return SyntaxError("No precompiler arguments (--kObjCache-cc)!\n");
+
+    /*
+     * Calc the cache file name.
+     */
     if (!pszCacheFile)
-        return SyntaxError("No cache file name (-f)\n");
-    if (!cArgvCompile)
-        return SyntaxError("No compiler arguments (--kObjCache-cc)\n");
-    if (!cArgvPreComp)
-        return SyntaxError("No precompiler arguments (--kObjCache-cc)\n");
-
-    /*
-     * Create a cache entry from the cache file (if found).
-     */
-    pEntry = kObjCacheCreate(pszCacheFile);
-    kObjCacheRead(pEntry);
-
-    /*
-     * Do the compiling.
-     */
-    kObjCachePreCompile(pEntry, papszArgvPreComp, cArgvPreComp, pszPreCompName, fRedirStdOut);
-    kObjCacheCompileIfNeeded(pEntry, papszArgvCompile, cArgvCompile, pszObjName);
-
-    /*
-     * Write the cache file.
-     */
-    kObjCacheWrite(pEntry);
-    /* kObjCacheDestroy(pEntry); - don't bother */
+    {
+        if (!pszCacheDir)
+            return SyntaxError("No cache dir (-d / KOBJCACHE_DIR) and no cache filename!\n");
+        if (!pszCacheName)
+        {
+            pszCacheName = FindFilenameInPath(pszCacheFile);
+            if (!*pszCacheName)
+                return SyntaxError("The cache file (-f) specifies a directory / nothing!\n");
+            pszCacheName = xstrdup(pszCacheName);
+            psz = strrchr(pszCacheName, '.');
+            if (psz > pszCacheName)
+                *psz = '\0';
+        }
+        pszCacheFile = MakePathFromDirAndFile(pszCacheDir, pszCacheName); /* harmless leak. */
+    }
+
+    /*
+     * Create and initialize the two bojects we'll be working on.
+     *
+     * We're supposed to be the only ones actually writing to the local file,
+     * so it's perfectly fine to read it here before we lock it. This simplifies
+     * the detection of object name and compiler argument changes.
+     */
+    SetErrorPrefix("kObjCache - %s", FindFilenameInPath(pszCacheFile));
+    pCache = kObjCacheCreate(pszCacheFile);
+
+    pEntry = kOCEntryCreate(pszCacheFile);
+    kOCEntryRead(pEntry);
+    kOCEntrySetCompileArgv(pEntry, papszArgvCompile, cArgvCompile);
+    kOCEntrySetCompileObjName(pEntry, pszObjName);
+    kOCEntrySetTarget(pEntry, pszTarget);
+    kOCEntrySetCppName(pEntry, pszPreCompName);
+    kOCEntrySetPipedMode(pEntry, fRedirPreCompStdOut, fRedirCompileStdIn);
+
+    /*
+     * Open (& lock) the two files and do validity checks and such.
+     */
+    kObjCacheLock(pCache);
+    if (    kObjCacheIsNew(pCache)
+        &&  kOCEntryNeedsCompiling(pEntry))
+    {
+        /*
+         * Both files are missing/invalid.
+         * Optimize this path as it is frequently used when making a clean build.
+         */
+        kOCEntryPreCompileAndCompile(pEntry, papszArgvPreComp, cArgvPreComp);
+    }
+    else
+    {
+        /*
+         * Do the precompile (don't need to lock the cache file for this).
+         */
+        kObjCacheUnlock(pCache);
+        kOCEntryPreCompile(pEntry, papszArgvPreComp, cArgvPreComp);
+
+        /*
+         * Check if we need to recompile. If we do, try see if the is a cache entry first.
+         */
+        kOCEntryCalcRecompile(pEntry);
+        if (kOCEntryNeedsCompiling(pEntry))
+        {
+            PKOCENTRY pUseEntry;
+            kObjCacheLock(pCache);
+            kObjCacheRemoveEntry(pCache, pEntry);
+            pUseEntry = kObjCacheFindMatchingEntry(pCache, pEntry);
+            if (pUseEntry)
+            {
+                kOCEntryCopy(pEntry, pUseEntry);
+                kOCEntryDestroy(pUseEntry);
+            }
+            else
+            {
+                kObjCacheUnlock(pCache);
+                kOCEntryCompileIt(pEntry);
+                kObjCacheLock(pCache);
+            }
+        }
+    }
+
+    /*
+     * Update the cache files.
+     */
+    kObjCacheInsertEntry(pCache, pEntry);
+    kOCEntryWrite(pEntry);
+    kObjCacheUnlock(pCache);
     return 0;
 }
