summaryrefslogtreecommitdiffstats
path: root/src/kmk/dir-nt-bird.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/kmk/dir-nt-bird.c')
-rw-r--r--src/kmk/dir-nt-bird.c794
1 files changed, 794 insertions, 0 deletions
diff --git a/src/kmk/dir-nt-bird.c b/src/kmk/dir-nt-bird.c
new file mode 100644
index 0000000..d690751
--- /dev/null
+++ b/src/kmk/dir-nt-bird.c
@@ -0,0 +1,794 @@
+/* $Id: dir-nt-bird.c 3359 2020-06-05 16:17:17Z bird $ */
+/** @file
+ * Reimplementation of dir.c for NT using kFsCache.
+ *
+ * This should perform better on NT, especially on machines "infected"
+ * by antivirus programs.
+ */
+
+/*
+ * Copyright (c) 2016 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <Windows.h> /* locking */
+#include "nt/kFsCache.h"
+#include "makeint.h"
+#if defined(KMK) && !defined(__OS2__)
+# include "glob/glob.h"
+#else
+# include <glob.h>
+#endif
+#include <assert.h>
+#include "kmkbuiltin.h"
+#include "kmkbuiltin/err.h"
+
+#include "nt_fullpath.h" /* for the time being - will be implemented here later on. */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** User data key indicating that it's an impossible file to make.
+ * See file_impossible() and file_impossible_p(). */
+#define KMK_DIR_NT_IMPOSSIBLE_KEY (~(KUPTR)7)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * glob directory stream.
+ */
+typedef struct KMKNTOPENDIR
+{
+ /** Reference to the directory. */
+ PKFSDIR pDir;
+ /** Index of the next directory entry (child) to return. */
+ KU32 idxNext;
+ /** The structure in which to return the directory entry. */
+ struct dirent DirEnt;
+} KMKNTOPENDIR;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The cache.*/
+PKFSCACHE g_pFsCache = NULL;
+/** Number of times dir_cache_invalid_missing was called. */
+static KU32 volatile g_cInvalidates = 0;
+/** Set by dir_cache_volatile_dir to indicate that the user has marked the
+ * volatile parts of the file system with custom revisioning and we only need to
+ * flush these. This is very handy when using a separate output directory
+ * from the sources. */
+static KBOOL g_fFsCacheIsUsingCustomRevision = K_FALSE;
+/** The ID of the main thread. We currently only let it access the cache. */
+static DWORD g_idMainThread = 0;
+
+
+void hash_init_directories(void)
+{
+ g_idMainThread = GetCurrentThreadId();
+ g_pFsCache = kFsCacheCreate(0);
+ if (g_pFsCache)
+ return;
+ fputs("kFsCacheCreate failed!", stderr);
+ exit(9);
+}
+
+
+/**
+ * Checks if @a pszName exists in directory @a pszDir.
+ *
+ * @returns 1 if it does, 0 if it doesn't.
+ *
+ * @param pszDir The directory.
+ * @param pszName The name.
+ *
+ * If empty string, just check if the directory exists.
+ *
+ * If NULL, just read the whole cache the directory into
+ * the cache (we always do that).
+ */
+int dir_file_exists_p(const char *pszDir, const char *pszName)
+{
+ int fRc = 0;
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pDirObj = kFsCacheLookupA(g_pFsCache, pszDir, &enmError);
+ assert(GetCurrentThreadId() == g_idMainThread);
+ if (pDirObj)
+ {
+
+ if (pDirObj->bObjType == KFSOBJ_TYPE_DIR)
+ {
+ if (pszName != 0)
+ {
+ /* Empty filename is just checking out the directory. */
+ if (*pszName == '\0')
+ fRc = 1;
+ else
+ {
+ PKFSOBJ pNameObj = kFsCacheLookupRelativeToDirA(g_pFsCache, (PKFSDIR)pDirObj, pszName, strlen(pszName),
+ 0/*fFlags*/, &enmError, NULL);
+ if (pNameObj)
+ {
+ fRc = pNameObj->bObjType == KFSOBJ_TYPE_MISSING;
+ kFsCacheObjRelease(g_pFsCache, pNameObj);
+ }
+ }
+ }
+ }
+ kFsCacheObjRelease(g_pFsCache, pDirObj);
+ }
+ return fRc;
+}
+
+
+/**
+ * Checks if a file exists.
+ *
+ * @returns 1 if it does exist, 0 if it doesn't.
+ * @param pszPath The path to check out.
+ * @note Multi-thread safe.
+ */
+int file_exists_p(const char *pszPath)
+{
+ int fRc;
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
+ if (pPathObj)
+ {
+ fRc = pPathObj->bObjType != KFSOBJ_TYPE_MISSING;
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ }
+ else
+ fRc = 0;
+ return fRc;
+}
+
+
+/**
+ * Checks if a file exists and is a regular file, given a UTF-16 string.
+ *
+ * @returns 1 if it regular file, 0 if doesn't exist or isn't a file
+ * @param pwszPath The UTF-16 path to check out.
+ * @note Multi-thread safe.
+ */
+int utf16_regular_file_p(const wchar_t *pwszPath)
+{
+ int fRc;
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pPathObj = kFsCacheLookupW(g_pFsCache, pwszPath, &enmError);
+ if (pPathObj)
+ {
+ fRc = pPathObj->bObjType == KFSOBJ_TYPE_FILE;
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ }
+ else
+ fRc = 0;
+ return fRc;
+}
+
+
+/**
+ * Just a way for vpath.c to get a correctly cased path, I think.
+ *
+ * @returns Directory path in string cache.
+ * @param pszDir The directory.
+ */
+const char *dir_name(const char *pszDir)
+{
+ char szTmp[MAX_PATH];
+ assert(GetCurrentThreadId() == g_idMainThread);
+ nt_fullpath(pszDir, szTmp, sizeof(szTmp));
+ return strcache_add(szTmp);
+}
+
+
+/**
+ * Makes future file_impossible_p calls return 1 for pszPath.
+ */
+void file_impossible(const char *pszPath)
+{
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
+ assert(GetCurrentThreadId() == g_idMainThread);
+ if (pPathObj)
+ {
+ kFsCacheObjAddUserData(g_pFsCache, pPathObj, KMK_DIR_NT_IMPOSSIBLE_KEY, sizeof(KFSUSERDATA));
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ }
+}
+
+/**
+ * Makes future file_impossible_p calls return 1 for pszPath.
+ */
+int file_impossible_p(const char *pszPath)
+{
+ int fRc;
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
+ assert(GetCurrentThreadId() == g_idMainThread);
+ if (pPathObj)
+ {
+ fRc = kFsCacheObjGetUserData(g_pFsCache, pPathObj, KMK_DIR_NT_IMPOSSIBLE_KEY) != NULL;
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ }
+ else
+ fRc = 0;
+ return fRc;
+}
+
+
+/**
+ * opendir for glob.
+ *
+ * @returns Pointer to DIR like handle, NULL if directory not found.
+ * @param pszDir The directory to enumerate.
+ */
+static __ptr_t dir_glob_opendir(const char *pszDir)
+{
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pDirObj = kFsCacheLookupA(g_pFsCache, pszDir, &enmError);
+ assert(GetCurrentThreadId() == g_idMainThread);
+ if (pDirObj)
+ {
+ if (pDirObj->bObjType == KFSOBJ_TYPE_DIR)
+ {
+ if (kFsCacheDirEnsurePopuplated(g_pFsCache, (PKFSDIR)pDirObj, NULL))
+ {
+ KMKNTOPENDIR *pDir = xmalloc(sizeof(*pDir));
+ pDir->pDir = (PKFSDIR)pDirObj;
+ pDir->idxNext = 0;
+ return pDir;
+ }
+ }
+ kFsCacheObjRelease(g_pFsCache, pDirObj);
+ }
+ return NULL;
+}
+
+
+/**
+ * readdir for glob.
+ *
+ * @returns Pointer to DIR like handle, NULL if directory not found.
+ * @param pDir Directory enum handle by dir_glob_opendir.
+ */
+static struct dirent *dir_glob_readdir(__ptr_t pvDir)
+{
+ KMKNTOPENDIR *pDir = (KMKNTOPENDIR *)pvDir;
+ KU32 const cChildren = pDir->pDir->cChildren;
+ assert(GetCurrentThreadId() == g_idMainThread);
+ while (pDir->idxNext < cChildren)
+ {
+ PKFSOBJ pEntry = pDir->pDir->papChildren[pDir->idxNext++];
+
+ /* Don't return missing objects. */
+ if (pEntry->bObjType != KFSOBJ_TYPE_MISSING)
+ {
+ /* Copy the name that fits, trying to avoid names with spaces.
+ If neither fits, skip the name. */
+ if ( pEntry->cchName < sizeof(pDir->DirEnt.d_name)
+ && ( pEntry->pszShortName == pEntry->pszName
+ || memchr(pEntry->pszName, ' ', pEntry->cchName) == NULL))
+ {
+ pDir->DirEnt.d_namlen = pEntry->cchName;
+ memcpy(pDir->DirEnt.d_name, pEntry->pszName, pEntry->cchName + 1);
+ }
+ else if (pEntry->cchShortName < sizeof(pDir->DirEnt.d_name))
+ {
+ pDir->DirEnt.d_namlen = pEntry->cchShortName;
+ memcpy(pDir->DirEnt.d_name, pEntry->pszShortName, pEntry->cchShortName + 1);
+ }
+ else
+ continue;
+
+ pDir->DirEnt.d_reclen = offsetof(struct dirent, d_name) + pDir->DirEnt.d_namlen;
+ if (pEntry->bObjType == KFSOBJ_TYPE_DIR)
+ pDir->DirEnt.d_type = DT_DIR;
+ else if (pEntry->bObjType == KFSOBJ_TYPE_FILE)
+ pDir->DirEnt.d_type = DT_REG;
+ else
+ pDir->DirEnt.d_type = DT_UNKNOWN;
+
+ return &pDir->DirEnt;
+ }
+ }
+
+ /*
+ * Fake the '.' and '..' directories because they're not part of papChildren above.
+ */
+ if (pDir->idxNext < cChildren + 2)
+ {
+ pDir->idxNext++;
+ pDir->DirEnt.d_type = DT_DIR;
+ pDir->DirEnt.d_namlen = pDir->idxNext - cChildren;
+ pDir->DirEnt.d_reclen = offsetof(struct dirent, d_name) + pDir->DirEnt.d_namlen;
+ pDir->DirEnt.d_name[0] = '.';
+ pDir->DirEnt.d_name[1] = '.';
+ pDir->DirEnt.d_name[pDir->DirEnt.d_namlen] = '\0';
+ return &pDir->DirEnt;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * closedir for glob.
+ *
+ * @param pDir Directory enum handle by dir_glob_opendir.
+ */
+static void dir_glob_closedir(__ptr_t pvDir)
+{
+ KMKNTOPENDIR *pDir = (KMKNTOPENDIR *)pvDir;
+ assert(GetCurrentThreadId() == g_idMainThread);
+ kFsCacheObjRelease(g_pFsCache, &pDir->pDir->Obj);
+ pDir->pDir = NULL;
+ free(pDir);
+}
+
+
+/**
+ * stat for glob.
+ *
+ * @returns 0 on success, -1 + errno on failure.
+ * @param pszPath The path to stat.
+ * @param pStat Where to return the info.
+ */
+static int dir_glob_stat(const char *pszPath, struct stat *pStat)
+{
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
+ assert(GetCurrentThreadId() == g_idMainThread);
+/** @todo follow symlinks vs. on symlink! */
+ if (pPathObj)
+ {
+ if (pPathObj->bObjType != KFSOBJ_TYPE_MISSING)
+ {
+ kHlpAssert(pPathObj->fHaveStats); /* currently always true. */
+ *pStat = pPathObj->Stats;
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ return 0;
+ }
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ }
+ errno = ENOENT;
+ return -1;
+}
+
+
+/**
+ * lstat for glob.
+ *
+ * @returns 0 on success, -1 + errno on failure.
+ * @param pszPath The path to stat.
+ * @param pStat Where to return the info.
+ */
+static int dir_glob_lstat(const char *pszPath, struct stat *pStat)
+{
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
+ assert(GetCurrentThreadId() == g_idMainThread);
+ if (pPathObj)
+ {
+ if (pPathObj->bObjType != KFSOBJ_TYPE_MISSING)
+ {
+ kHlpAssert(pPathObj->fHaveStats); /* currently always true. */
+ *pStat = pPathObj->Stats;
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ return 0;
+ }
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ errno = ENOENT;
+ }
+ else
+ errno = enmError == KFSLOOKUPERROR_NOT_DIR
+ || enmError == KFSLOOKUPERROR_PATH_COMP_NOT_DIR
+ ? ENOTDIR : ENOENT;
+
+ return -1;
+}
+
+
+/**
+ * Checks if @a pszDir exists and is a directory.
+ *
+ * @returns 1 if is directory, 0 if isn't or doesn't exists.
+ * @param pszDir The alleged directory.
+ */
+static int dir_globl_dir_exists_p(const char *pszDir)
+{
+ int fRc;
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pDirObj = kFsCacheLookupA(g_pFsCache, pszDir, &enmError);
+ assert(GetCurrentThreadId() == g_idMainThread);
+ if (pDirObj)
+ {
+ fRc = pDirObj->bObjType == KFSOBJ_TYPE_DIR;
+ kFsCacheObjRelease(g_pFsCache, pDirObj);
+ }
+ else
+ fRc = 0;
+ return fRc;
+
+}
+
+
+/**
+ * Sets up pGlob with the necessary callbacks.
+ *
+ * @param pGlob Structure to populate.
+ */
+void dir_setup_glob(glob_t *pGlob)
+{
+ assert(GetCurrentThreadId() == g_idMainThread);
+ pGlob->gl_opendir = dir_glob_opendir;
+ pGlob->gl_readdir = dir_glob_readdir;
+ pGlob->gl_closedir = dir_glob_closedir;
+ pGlob->gl_stat = dir_glob_stat;
+#ifdef __EMX__ /* The FreeBSD implementation actually uses gl_lstat!! */
+ pGlob->gl_lstat = dir_glob_lstat;
+#else
+ pGlob->gl_exists = file_exists_p;
+ pGlob->gl_isdir = dir_globl_dir_exists_p;
+#endif
+}
+
+
+/**
+ * Print statitstics.
+ */
+void print_dir_stats(void)
+{
+ FILE *pOut = stdout;
+ KU32 cMisses;
+
+ fputs("\n"
+ "# NT dir cache stats:\n", pOut);
+ fprintf(pOut, "# %u objects, taking up %u (%#x) bytes, avg %u bytes\n",
+ g_pFsCache->cObjects, g_pFsCache->cbObjects, g_pFsCache->cbObjects, g_pFsCache->cbObjects / g_pFsCache->cObjects);
+ fprintf(pOut, "# %u A path hashes, taking up %u (%#x) bytes, avg %u bytes, %u collision\n",
+ g_pFsCache->cAnsiPaths, g_pFsCache->cbAnsiPaths, g_pFsCache->cbAnsiPaths,
+ g_pFsCache->cbAnsiPaths / K_MAX(g_pFsCache->cAnsiPaths, 1), g_pFsCache->cAnsiPathCollisions);
+#ifdef KFSCACHE_CFG_UTF16
+ fprintf(pOut, "# %u W path hashes, taking up %u (%#x) bytes, avg %u bytes, %u collisions\n",
+ g_pFsCache->cUtf16Paths, g_pFsCache->cbUtf16Paths, g_pFsCache->cbUtf16Paths,
+ g_pFsCache->cbUtf16Paths / K_MAX(g_pFsCache->cUtf16Paths, 1), g_pFsCache->cUtf16PathCollisions);
+#endif
+ fprintf(pOut, "# %u child hash tables, total of %u entries, %u children inserted, %u collisions\n",
+ g_pFsCache->cChildHashTabs, g_pFsCache->cChildHashEntriesTotal,
+ g_pFsCache->cChildHashed, g_pFsCache->cChildHashCollisions);
+
+ cMisses = g_pFsCache->cLookups - g_pFsCache->cPathHashHits - g_pFsCache->cWalkHits;
+ fprintf(pOut, "# %u lookups: %u (%" KU64_PRI " %%) path hash hits, %u (%" KU64_PRI "%%) walks hits, %u (%" KU64_PRI "%%) misses\n",
+ g_pFsCache->cLookups,
+ g_pFsCache->cPathHashHits, g_pFsCache->cPathHashHits * (KU64)100 / K_MAX(g_pFsCache->cLookups, 1),
+ g_pFsCache->cWalkHits, g_pFsCache->cWalkHits * (KU64)100 / K_MAX(g_pFsCache->cLookups, 1),
+ cMisses, cMisses * (KU64)100 / K_MAX(g_pFsCache->cLookups, 1));
+ fprintf(pOut, "# %u child searches, %u (%" KU64_PRI "%%) hash hits\n",
+ g_pFsCache->cChildSearches,
+ g_pFsCache->cChildHashHits, g_pFsCache->cChildHashHits * (KU64)100 / K_MAX(g_pFsCache->cChildSearches, 1));
+}
+
+
+void print_dir_data_base(void)
+{
+ /** @todo. */
+
+}
+
+
+/* duplicated in kWorker.c
+ * Note! Tries avoid to produce a result with spaces since they aren't supported by makefiles. */
+void nt_fullpath_cached(const char *pszPath, char *pszFull, size_t cbFull)
+{
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pPathObj;
+
+ KFSCACHE_LOCK(g_pFsCache); /* let's start out being careful. */
+
+ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
+ if (pPathObj)
+ {
+ KSIZE off = pPathObj->cchParent;
+ if (off > 0)
+ {
+ KSIZE offEnd = off + pPathObj->cchName;
+ if (offEnd < cbFull)
+ {
+ PKFSDIR pAncestor;
+
+ pszFull[offEnd] = '\0';
+ memcpy(&pszFull[off], pPathObj->pszName, pPathObj->cchName);
+
+ for (pAncestor = pPathObj->pParent; off > 0; pAncestor = pAncestor->Obj.pParent)
+ {
+ kHlpAssert(off > 1);
+ kHlpAssert(pAncestor != NULL);
+ kHlpAssert(pAncestor->Obj.cchName > 0);
+ pszFull[--off] = '/';
+#ifdef KFSCACHE_CFG_SHORT_NAMES
+ if ( pAncestor->Obj.pszName == pAncestor->Obj.pszShortName
+ || memchr(pAncestor->Obj.pszName, ' ', pAncestor->Obj.cchName) == NULL)
+#endif
+ {
+ off -= pAncestor->Obj.cchName;
+ kHlpAssert(pAncestor->Obj.cchParent == off);
+ memcpy(&pszFull[off], pAncestor->Obj.pszName, pAncestor->Obj.cchName);
+ }
+#ifdef KFSCACHE_CFG_SHORT_NAMES
+ else
+ {
+ /*
+ * The long name constains a space, so use the alternative name instead.
+ * Most likely the alternative name differs in length, usually it's shorter,
+ * so we have to shift the part of the path we've already assembled
+ * accordingly.
+ */
+ KSSIZE cchDelta = (KSSIZE)pAncestor->Obj.cchShortName - (KSSIZE)pAncestor->Obj.cchName;
+ if (cchDelta != 0)
+ {
+ if ((KSIZE)(offEnd + cchDelta) >= cbFull)
+ goto l_fallback;
+ memmove(&pszFull[off + cchDelta], &pszFull[off], offEnd + 1 - off);
+ off += cchDelta;
+ offEnd += cchDelta;
+ }
+ off -= pAncestor->Obj.cchShortName;
+ kHlpAssert(pAncestor->Obj.cchParent == off);
+ memcpy(&pszFull[off], pAncestor->Obj.pszShortName, pAncestor->Obj.cchShortName);
+ }
+#endif
+ }
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ KFSCACHE_UNLOCK(g_pFsCache);
+ return;
+ }
+ }
+ else
+ {
+ if ((size_t)pPathObj->cchName + 1 < cbFull)
+ {
+ /* Assume no spaces here. */
+ memcpy(pszFull, pPathObj->pszName, pPathObj->cchName);
+ pszFull[pPathObj->cchName] = '/';
+ pszFull[pPathObj->cchName + 1] = '\0';
+
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ KFSCACHE_UNLOCK(g_pFsCache);
+ return;
+ }
+ }
+
+ /* do fallback. */
+#ifdef KFSCACHE_CFG_SHORT_NAMES
+l_fallback:
+#endif
+ kHlpAssertFailed();
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ }
+ KFSCACHE_UNLOCK(g_pFsCache);
+
+ nt_fullpath(pszPath, pszFull, cbFull);
+}
+
+
+/**
+ * Special stat call used by remake.c
+ *
+ * @returns 0 on success, -1 + errno on failure.
+ * @param pszPath The path to stat.
+ * @param pStat Where to return the mtime field only.
+ */
+int stat_only_mtime(const char *pszPath, struct stat *pStat)
+{
+ /* Currently a little expensive, so just hit the file system once the
+ jobs starts comming in. */
+ assert(GetCurrentThreadId() == g_idMainThread);
+ if (g_cInvalidates == 0)
+ {
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
+ if (pPathObj)
+ {
+ if (pPathObj->bObjType != KFSOBJ_TYPE_MISSING)
+ {
+ kHlpAssert(pPathObj->fHaveStats); /* currently always true. */
+ pStat->st_mtime = pPathObj->Stats.st_mtime;
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ return 0;
+ }
+
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ errno = ENOENT;
+ }
+ else
+ errno = enmError == KFSLOOKUPERROR_NOT_DIR
+ || enmError == KFSLOOKUPERROR_PATH_COMP_NOT_DIR
+ ? ENOTDIR : ENOENT;
+ return -1;
+ }
+ return birdStatModTimeOnly(pszPath, &pStat->st_mtim, 1 /*fFollowLink*/);
+}
+
+/**
+ * Do cache invalidation after a job completes.
+ */
+void dir_cache_invalid_after_job(void)
+{
+ assert(GetCurrentThreadId() == g_idMainThread);
+ g_cInvalidates++;
+ if (g_fFsCacheIsUsingCustomRevision)
+ kFsCacheInvalidateCustomBoth(g_pFsCache);
+ else
+ kFsCacheInvalidateAll(g_pFsCache);
+}
+
+/**
+ * Invalidate the whole directory cache
+ *
+ * Used by $(dircache-ctl invalidate)
+ * @note Multi-thread safe.
+ */
+void dir_cache_invalid_all(void)
+{
+ g_cInvalidates++;
+ kFsCacheInvalidateAll(g_pFsCache);
+}
+
+/**
+ * Invalidate the whole directory cache and closes all open handles.
+ *
+ * Used by $(dircache-ctl invalidate-and-close-dirs)
+ * @param including_root Also close the root directory.
+ * @note Multi-thread safe.
+ */
+void dir_cache_invalid_all_and_close_dirs(int including_root)
+{
+ g_cInvalidates++;
+ kFsCacheInvalidateAllAndCloseDirs(g_pFsCache, !!including_root);
+}
+
+/**
+ * Invalidate missing bits of the directory cache.
+ *
+ * Used by $(dircache-ctl invalidate-missing)
+ * @note Multi-thread safe.
+ */
+void dir_cache_invalid_missing(void)
+{
+ g_cInvalidates++;
+ kFsCacheInvalidateAll(g_pFsCache);
+}
+
+/**
+ * Invalidate the volatile bits of the directory cache.
+ *
+ * Used by $(dircache-ctl invalidate-missing)
+ * @note Multi-thread safe.
+ */
+void dir_cache_invalid_volatile(void)
+{
+ g_cInvalidates++;
+ if (g_fFsCacheIsUsingCustomRevision)
+ kFsCacheInvalidateCustomBoth(g_pFsCache);
+ else
+ kFsCacheInvalidateAll(g_pFsCache);
+}
+
+/**
+ * Used by $(dircache-ctl ) to mark a directory subtree or file as volatile.
+ *
+ * The first call changes the rest of the cache to be considered non-volatile.
+ *
+ * @returns 0 on success, -1 on failure.
+ * @param pszDir The directory (or file for what that is worth).
+ */
+int dir_cache_volatile_dir(const char *pszDir)
+{
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pObj = kFsCacheLookupA(g_pFsCache, pszDir, &enmError);
+ assert(GetCurrentThreadId() == g_idMainThread);
+ if (pObj)
+ {
+ KBOOL fRc = kFsCacheSetupCustomRevisionForTree(g_pFsCache, pObj);
+ kFsCacheObjRelease(g_pFsCache, pObj);
+ if (fRc)
+ {
+ g_fFsCacheIsUsingCustomRevision = K_TRUE;
+ return 0;
+ }
+ OS(error, reading_file, "failed to mark '%s' as volatile", pszDir);
+ }
+ else
+ OS(error, reading_file, "failed to mark '%s' as volatile (not found)", pszDir);
+ return -1;
+}
+
+/**
+ * Invalidates a deleted directory so the cache can close handles to it.
+ *
+ * Used by kmk_builtin_rm and kmk_builtin_rmdir.
+ *
+ * @returns 0 on success, -1 on failure.
+ * @param pszDir The directory to invalidate as deleted.
+ */
+int dir_cache_deleted_directory(const char *pszDir)
+{
+ assert(GetCurrentThreadId() == g_idMainThread);
+ if (kFsCacheInvalidateDeletedDirectoryA(g_pFsCache, pszDir))
+ return 0;
+ return -1;
+}
+
+
+int kmk_builtin_dircache(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ assert(GetCurrentThreadId() == g_idMainThread);
+ if (argc >= 2)
+ {
+ const char *pszCmd = argv[1];
+ if (strcmp(pszCmd, "invalidate") == 0)
+ {
+ if (argc == 2)
+ {
+ dir_cache_invalid_all();
+ return 0;
+ }
+ errx(pCtx, 2, "the 'invalidate' command takes no arguments!\n");
+ }
+ else if (strcmp(pszCmd, "invalidate-missing") == 0)
+ {
+ if (argc == 2)
+ {
+ dir_cache_invalid_missing ();
+ return 0;
+ }
+ errx(pCtx, 2, "the 'invalidate-missing' command takes no arguments!\n");
+ }
+ else if (strcmp(pszCmd, "volatile") == 0)
+ {
+ int i;
+ for (i = 2; i < argc; i++)
+ dir_cache_volatile_dir(argv[i]);
+ return 0;
+ }
+ else if (strcmp(pszCmd, "deleted") == 0)
+ {
+ int i;
+ for (i = 2; i < argc; i++)
+ dir_cache_deleted_directory(argv[i]);
+ return 0;
+ }
+ else
+ errx(pCtx, 2, "Invalid command '%s'!\n", pszCmd);
+ }
+ else
+ errx(pCtx, 2, "No command given!\n");
+
+ K_NOREF(envp);
+ return 2;
+}
+