/* $Id: kFsCache.h 3381 2020-06-12 11:36:10Z bird $ */ /** @file * kFsCache.c - NT directory content cache. */ /* * Copyright (c) 2016 knut st. osmundsen * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * * Alternatively, the content of this file may be used under the terms of the * GPL version 2 or later, or LGPL version 2.1 or later. */ #ifndef ___lib_nt_kFsCache_h___ #define ___lib_nt_kFsCache_h___ #include #include "ntstat.h" #ifndef NDEBUG # include #endif /** @def KFSCACHE_CFG_UTF16 * Whether to compile in the UTF-16 names support. */ #define KFSCACHE_CFG_UTF16 1 /** @def KFSCACHE_CFG_SHORT_NAMES * Whether to compile in the short name support. */ #define KFSCACHE_CFG_SHORT_NAMES 1 /** @def KFSCACHE_CFG_PATH_HASH_TAB_SIZE * Size of the path hash table. */ #define KFSCACHE_CFG_PATH_HASH_TAB_SIZE 99991 /** The max length paths we consider. */ #define KFSCACHE_CFG_MAX_PATH 1024 /** The max ANSI name length. */ #define KFSCACHE_CFG_MAX_ANSI_NAME (256*3 + 16) /** The max UTF-16 name length. */ #define KFSCACHE_CFG_MAX_UTF16_NAME (256*2 + 16) /** Enables locking of the cache and thereby making it thread safe. */ #define KFSCACHE_CFG_LOCKING 1 /** Special KFSOBJ::uCacheGen number indicating that it does not apply. */ #define KFSOBJ_CACHE_GEN_IGNORE KU32_MAX /** @name KFSOBJ_TYPE_XXX - KFSOBJ::bObjType * @{ */ /** Directory, type KFSDIR. */ #define KFSOBJ_TYPE_DIR KU8_C(0x01) /** Regular file - type KFSOBJ. */ #define KFSOBJ_TYPE_FILE KU8_C(0x02) /** Other file - type KFSOBJ. */ #define KFSOBJ_TYPE_OTHER KU8_C(0x03) /** Caching of a negative result - type KFSOBJ. * @remarks We will allocate enough space for the largest cache node, so this * can metamorph into any other object should it actually turn up. */ #define KFSOBJ_TYPE_MISSING KU8_C(0x04) ///** Invalidated entry flag. */ //#define KFSOBJ_TYPE_F_INVALID KU8_C(0x20) /** @} */ /** @name KFSOBJ_F_XXX - KFSOBJ::fFlags * @{ */ /** Use custom generation. * @remarks This is given the value 1, as we use it as an index into * KFSCACHE::auGenerations, 0 being the default. */ #define KFSOBJ_F_USE_CUSTOM_GEN KU32_C(0x00000001) /** Whether the file system update the modified timestamp of directories * when something is removed from it or added to it. * @remarks They say NTFS is the only windows filesystem doing this. */ #define KFSOBJ_F_WORKING_DIR_MTIME KU32_C(0x00000002) /** NTFS file system volume. */ #define KFSOBJ_F_NTFS KU32_C(0x80000000) /** Flags that are automatically inherited. */ #define KFSOBJ_F_INHERITED_MASK KU32_C(0xffffffff) /** @} */ #define IS_ALPHA(ch) ( ((ch) >= 'A' && (ch) <= 'Z') || ((ch) >= 'a' && (ch) <= 'z') ) #define IS_SLASH(ch) ((ch) == '\\' || (ch) == '/') /** Pointer to a cache. */ typedef struct KFSCACHE *PKFSCACHE; /** Pointer to a core object. */ typedef struct KFSOBJ *PKFSOBJ; /** Pointer to a directory object. */ typedef struct KFSDIR *PKFSDIR; /** Pointer to a directory hash table entry. */ typedef struct KFSOBJHASH *PKFSOBJHASH; /** Pointer to a user data item. */ typedef struct KFSUSERDATA *PKFSUSERDATA; /** * User data item associated with a cache node. */ typedef struct KFSUSERDATA { /** Pointer to the next piece of user data. */ PKFSUSERDATA pNext; /** The key identifying this user. */ KUPTR uKey; /** The destructor. */ void (*pfnDestructor)(PKFSCACHE pCache, PKFSOBJ pObj, PKFSUSERDATA pData); } KFSUSERDATA; /** * Storage for name strings for the unlikely event that they should grow in * length after the KFSOBJ was created. */ typedef struct KFSOBJNAMEALLOC { /** Size of the allocation. */ KU32 cb; /** The space for names. */ char abSpace[1]; } KFSOBJNAMEALLOC; /** Name growth allocation. */ typedef KFSOBJNAMEALLOC *PKFSOBJNAMEALLOC; /** * Base cache node. */ typedef struct KFSOBJ { /** Magic value (KFSOBJ_MAGIC). */ KU32 u32Magic; /** Number of references. */ KU32 volatile cRefs; /** The cache generation, see KFSOBJ_CACHE_GEN_IGNORE. */ KU32 uCacheGen; /** The object type, KFSOBJ_TYPE_XXX. */ KU8 bObjType; /** Set if the Stats member is valid, clear if not. */ KBOOL fHaveStats; /** Internal debug field for counting path hash references. * @internal */ KU8 cPathHashRefs; /** Index into KFSCACHE::auUserData. */ KU8 idxUserDataLock; /** Flags, KFSOBJ_F_XXX. */ KU32 fFlags; /** Hash value of the name inserted into the parent hash table. * This is 0 if not inserted. Names are only hashed and inserted as they are * first found thru linear searching of its siblings, and which name it is * dependens on the lookup function (W or A) and whether the normal name or * short name seems to have matched. * * @note It was ruled out as too much work to hash and track all four names, * so instead this minimalist approach was choosen in stead. */ KU32 uNameHash; /** Pointer to the next child with the same name hash value. */ PKFSOBJ pNextNameHash; /** Pointer to the parent (directory). * This is only NULL for a root. */ PKFSDIR pParent; /** The directory name. (Allocated after the structure.) */ const char *pszName; /** The length of pszName. */ KU16 cchName; /** The length of the parent path (up to where pszName starts). * @note This is valuable when constructing an absolute path to this node by * means of the parent pointer (no need for recursion). */ KU16 cchParent; #ifdef KFSCACHE_CFG_UTF16 /** The length of pwszName (in wchar_t's). */ KU16 cwcName; /** The length of the parent UTF-16 path (in wchar_t's). * @note This is valuable when constructing an absolute path to this node by * means of the parent pointer (no need for recursion). */ KU16 cwcParent; /** The UTF-16 object name. (Allocated after the structure.) */ const wchar_t *pwszName; #endif #ifdef KFSCACHE_CFG_SHORT_NAMES /** The short object name. (Allocated after the structure, could be same * as pszName.) */ const char *pszShortName; /** The length of pszShortName. */ KU16 cchShortName; /** The length of the short parent path (up to where pszShortName starts). */ KU16 cchShortParent; # ifdef KFSCACHE_CFG_UTF16 /** The length of pwszShortName (in wchar_t's). */ KU16 cwcShortName; /** The length of the short parent UTF-16 path (in wchar_t's). */ KU16 cwcShortParent; /** The UTF-16 short object name. (Allocated after the structure, possibly * same as pwszName.) */ const wchar_t *pwszShortName; # endif #endif /** Allocation for handling name length increases. */ PKFSOBJNAMEALLOC pNameAlloc; /** Pointer to the first user data item */ PKFSUSERDATA pUserDataHead; /** Stats - only valid when fHaveStats is set. */ BirdStat_T Stats; } KFSOBJ; /** The magic for a KFSOBJ structure (Thelonious Sphere Monk). */ #define KFSOBJ_MAGIC KU32_C(0x19171010) /** * Directory node in the cache. */ typedef struct KFSDIR { /** The core object information. */ KFSOBJ Obj; /** Child objects. */ PKFSOBJ *papChildren; /** The number of child objects. */ KU32 cChildren; /** The allocated size of papChildren. */ KU32 cChildrenAllocated; /** Pointer to the child hash table. */ PKFSOBJ *papHashTab; /** The mask shift of the hash table. * Hash table size is a power of two, this is the size minus one. * * @remarks The hash table is optional and populated by lookup hits. The * assumption being that a lookup is repeated and will choose a good * name to hash on. We've got up to 4 different hashes, so this * was the easy way out. */ KU32 fHashTabMask; /** Handle to the directory (we generally keep it open). */ #ifndef DECLARE_HANDLE KUPTR hDir; #else HANDLE hDir; #endif /** The device number we queried/inherited when opening it. */ KU64 uDevNo; /** The last write time sampled the last time the directory was refreshed. * @remarks May differ from st_mtim because it will be updated when the * parent directory is refreshed. */ KI64 iLastWrite; /** The time that iLastWrite was read. */ KI64 iLastPopulated; /** Set if populated. */ KBOOL fPopulated; /** Set if it needs re-populated. */ KBOOL fNeedRePopulating; } KFSDIR; /** * Lookup errors. */ typedef enum KFSLOOKUPERROR { /** Lookup was a success. */ KFSLOOKUPERROR_SUCCESS = 0, /** A path component was not found. */ KFSLOOKUPERROR_PATH_COMP_NOT_FOUND, /** A path component is not a directory. */ KFSLOOKUPERROR_PATH_COMP_NOT_DIR, /** The final path entry is not a directory (trailing slash). */ KFSLOOKUPERROR_NOT_DIR, /** Not found. */ KFSLOOKUPERROR_NOT_FOUND, /** The path is too long. */ KFSLOOKUPERROR_PATH_TOO_LONG, /** The path is too short. */ KFSLOOKUPERROR_PATH_TOO_SHORT, /** Unsupported path type. */ KFSLOOKUPERROR_UNSUPPORTED, /** We're out of memory. */ KFSLOOKUPERROR_OUT_OF_MEMORY, /** Error opening directory. */ KFSLOOKUPERROR_DIR_OPEN_ERROR, /** Error reading directory. */ KFSLOOKUPERROR_DIR_READ_ERROR, /** UTF-16 to ANSI conversion error. */ KFSLOOKUPERROR_ANSI_CONVERSION_ERROR, /** ANSI to UTF-16 conversion error. */ KFSLOOKUPERROR_UTF16_CONVERSION_ERROR, /** Internal error. */ KFSLOOKUPERROR_INTERNAL_ERROR } KFSLOOKUPERROR; /** Pointer to an ANSI path hash table entry. */ typedef struct KFSHASHA *PKFSHASHA; /** * ANSI file system path hash table entry. * The path hash table allows us to skip parsing and walking a path. */ typedef struct KFSHASHA { /** Next entry with the same hash table slot. */ PKFSHASHA pNext; /** Path hash value. */ KU32 uHashPath; /** The path length. */ KU16 cchPath; /** Set if aboslute path. */ KBOOL fAbsolute; /** Index into KFSCACHE:auGenerationsMissing when pFsObj is NULL. */ KU8 idxMissingGen; /** The cache generation ID. */ KU32 uCacheGen; /** The lookup error (when pFsObj is NULL). */ KFSLOOKUPERROR enmError; /** The path. (Allocated after the structure.) */ const char *pszPath; /** Pointer to the matching FS object. * This is NULL for negative path entries? */ PKFSOBJ pFsObj; } KFSHASHA; #ifdef KFSCACHE_CFG_UTF16 /** Pointer to an UTF-16 path hash table entry. */ typedef struct KFSHASHW *PKFSHASHW; /** * UTF-16 file system path hash table entry. The path hash table allows us * to skip parsing and walking a path. */ typedef struct KFSHASHW { /** Next entry with the same hash table slot. */ PKFSHASHW pNext; /** Path hash value. */ KU32 uHashPath; /** The path length (in wchar_t units). */ KU16 cwcPath; /** Set if aboslute path. */ KBOOL fAbsolute; /** Index into KFSCACHE:auGenerationsMissing when pFsObj is NULL. */ KU8 idxMissingGen; /** The cache generation ID. */ KU32 uCacheGen; /** The lookup error (when pFsObj is NULL). */ KFSLOOKUPERROR enmError; /** The path. (Allocated after the structure.) */ const wchar_t *pwszPath; /** Pointer to the matching FS object. * This is NULL for negative path entries? */ PKFSOBJ pFsObj; } KFSHASHW; #endif /** @def KFSCACHE_LOCK * Locks the cache exclusively. */ /** @def KFSCACHE_UNLOCK * Counterpart to KFSCACHE_LOCK. */ /** @def KFSCACHE_OBJUSERDATA_LOCK * Locks the user data list of an object exclusively. */ /** @def KFSCACHE_OBJUSERDATA_UNLOCK * Counterpart to KFSCACHE_OBJUSERDATA_LOCK. */ #ifdef KFSCACHE_CFG_LOCKING # define KFSCACHE_LOCK(a_pCache) EnterCriticalSection(&(a_pCache)->u.CritSect) # define KFSCACHE_UNLOCK(a_pCache) LeaveCriticalSection(&(a_pCache)->u.CritSect) # define KFSCACHE_OBJUSERDATA_LOCK(a_pCache, a_pObj) do { \ KU8 idxUserDataLock = (a_pObj)->idxUserDataLock; \ if (idxUserDataLock != KU8_MAX) \ { /* likely */ } \ else \ idxUserDataLock = kFsCacheObjGetUserDataLockIndex(a_pCache, a_pObj); \ idxUserDataLock &= (KU8)(K_ELEMENTS((a_pCache)->auUserDataLocks) - 1); \ EnterCriticalSection(&(a_pCache)->auUserDataLocks[idxUserDataLock].CritSect); \ } while (0) # define KFSCACHE_OBJUSERDATA_UNLOCK(a_pCache, a_pObj) \ LeaveCriticalSection(&(a_pCache)->auUserDataLocks[(a_pObj)->idxUserDataLock & (K_ELEMENTS((a_pCache)->auUserDataLocks) - 1)].CritSect) #else # define KFSCACHE_LOCK(a_pCache) do { } while (0) # define KFSCACHE_UNLOCK(a_pCache) do { } while (0) # define KFSCACHE_OBJUSERDATA_LOCK(a_pCache, a_pObj) do { } while (0) # define KFSCACHE_OBJUSERDATA_UNLOCK(a_pCache, a_pObj) do { } while (0) #endif /** @name KFSCACHE_F_XXX * @{ */ /** Whether to cache missing directory entries (KFSOBJ_TYPE_MISSING). */ #define KFSCACHE_F_MISSING_OBJECTS KU32_C(0x00000001) /** Whether to cache missing paths. */ #define KFSCACHE_F_MISSING_PATHS KU32_C(0x00000002) /** @} */ /** * Directory cache instance. */ typedef struct KFSCACHE { /** Magic value (KFSCACHE_MAGIC). */ KU32 u32Magic; /** Cache flags. */ KU32 fFlags; /** The default and custom cache generations for stuff that exists, indexed by * KFSOBJ_F_USE_CUSTOM_GEN. * * The custom generation can be used to invalidate parts of the file system that * are known to be volatile without triggering refreshing of the more static * parts. Like the 'out' directory in a kBuild setup or a 'TEMP' directory are * expected to change and you need to invalidate the caching of these frequently * to stay on top of things. Whereas the sources, headers, compilers, sdk, * ddks, windows directory and such generally doesn't change all that often. */ KU32 auGenerations[2]; /** The current cache generation for missing objects, negative results, ++. * This comes with a custom variant too. Indexed by KFSOBJ_F_USE_CUSTOM_GEN. */ KU32 auGenerationsMissing[2]; /** Number of cache objects. */ KSIZE cObjects; /** Memory occupied by the cache object structures. */ KSIZE cbObjects; /** Number of lookups. */ KSIZE cLookups; /** Number of hits in the path hash tables. */ KSIZE cPathHashHits; /** Number of hits walking the file system hierarchy. */ KSIZE cWalkHits; /** Number of child searches. */ KSIZE cChildSearches; /** Number of cChildLookups resolved thru hash table hits. */ KSIZE cChildHashHits; /** The number of child hash tables. */ KSIZE cChildHashTabs; /** The sum of all child hash table sizes. */ KSIZE cChildHashEntriesTotal; /** Number of children inserted into the hash tables. */ KSIZE cChildHashed; /** Number of collisions in the child hash tables. */ KSIZE cChildHashCollisions; /** Number times a object name changed. */ KSIZE cNameChanges; /** Number times a object name grew and needed KFSOBJNAMEALLOC. * (Subset of cNameChanges) */ KSIZE cNameGrowths; /** The root directory. */ KFSDIR RootDir; #ifdef KFSCACHE_CFG_LOCKING union { # ifdef _WINBASE_ CRITICAL_SECTION CritSect; # endif KU64 abPadding[2 * 4 + 4 * sizeof(void *)]; } /** Critical section protecting the cache. */ u, /** Critical section protecting the pUserDataHead of objects. * @note Array size must be a power of two. */ auUserDataLocks[8]; /** The next auUserData index. */ KU8 idxUserDataNext; #endif /** File system hash table for ANSI filename strings. */ PKFSHASHA apAnsiPaths[KFSCACHE_CFG_PATH_HASH_TAB_SIZE]; /** Number of paths in the apAnsiPaths hash table. */ KSIZE cAnsiPaths; /** Number of collisions in the apAnsiPaths hash table. */ KSIZE cAnsiPathCollisions; /** Amount of memory used by the path entries. */ KSIZE cbAnsiPaths; #ifdef KFSCACHE_CFG_UTF16 /** Number of paths in the apUtf16Paths hash table. */ KSIZE cUtf16Paths; /** Number of collisions in the apUtf16Paths hash table. */ KSIZE cUtf16PathCollisions; /** Amount of memory used by the UTF-16 path entries. */ KSIZE cbUtf16Paths; /** File system hash table for UTF-16 filename strings. */ PKFSHASHW apUtf16Paths[KFSCACHE_CFG_PATH_HASH_TAB_SIZE]; #endif } KFSCACHE; /** Magic value for KFSCACHE::u32Magic (Jon Batiste). */ #define KFSCACHE_MAGIC KU32_C(0x19861111) /** @def KW_LOG * Generic logging. * @param a Argument list for kFsCacheDbgPrintf */ #if 1 /*def NDEBUG - enable when needed! */ # define KFSCACHE_LOG(a) do { } while (0) #else # define KFSCACHE_LOG(a) kFsCacheDbgPrintf a void kFsCacheDbgPrintfV(const char *pszFormat, va_list va); void kFsCacheDbgPrintf(const char *pszFormat, ...); #endif KBOOL kFsCacheDirEnsurePopuplated(PKFSCACHE pCache, PKFSDIR pDir, KFSLOOKUPERROR *penmError); KBOOL kFsCacheDirAddChild(PKFSCACHE pCache, PKFSDIR pParent, PKFSOBJ pChild, KFSLOOKUPERROR *penmError); PKFSOBJ kFsCacheCreateObject(PKFSCACHE pCache, PKFSDIR pParent, char const *pszName, KU16 cchName, wchar_t const *pwszName, KU16 cwcName, #ifdef KFSCACHE_CFG_SHORT_NAMES char const *pszShortName, KU16 cchShortName, wchar_t const *pwszShortName, KU16 cwcShortName, #endif KU8 bObjType, KFSLOOKUPERROR *penmError); PKFSOBJ kFsCacheCreateObjectW(PKFSCACHE pCache, PKFSDIR pParent, wchar_t const *pwszName, KU32 cwcName, #ifdef KFSCACHE_CFG_SHORT_NAMES wchar_t const *pwszShortName, KU32 cwcShortName, #endif KU8 bObjType, KFSLOOKUPERROR *penmError); PKFSOBJ kFsCacheLookupA(PKFSCACHE pCache, const char *pszPath, KFSLOOKUPERROR *penmError); PKFSOBJ kFsCacheLookupW(PKFSCACHE pCache, const wchar_t *pwszPath, KFSLOOKUPERROR *penmError); PKFSOBJ kFsCacheLookupRelativeToDirA(PKFSCACHE pCache, PKFSDIR pParent, const char *pszPath, KU32 cchPath, KU32 fFlags, KFSLOOKUPERROR *penmError, PKFSOBJ *ppLastAncestor); PKFSOBJ kFsCacheLookupRelativeToDirW(PKFSCACHE pCache, PKFSDIR pParent, const wchar_t *pwszPath, KU32 cwcPath, KU32 fFlags, KFSLOOKUPERROR *penmError, PKFSOBJ *ppLastAncestor); PKFSOBJ kFsCacheLookupWithLengthA(PKFSCACHE pCache, const char *pchPath, KSIZE cchPath, KFSLOOKUPERROR *penmError); PKFSOBJ kFsCacheLookupWithLengthW(PKFSCACHE pCache, const wchar_t *pwcPath, KSIZE cwcPath, KFSLOOKUPERROR *penmError); PKFSOBJ kFsCacheLookupNoMissingA(PKFSCACHE pCache, const char *pszPath, KFSLOOKUPERROR *penmError); PKFSOBJ kFsCacheLookupNoMissingW(PKFSCACHE pCache, const wchar_t *pwszPath, KFSLOOKUPERROR *penmError); /** @name KFSCACHE_LOOKUP_F_XXX - lookup flags * @{ */ /** No inserting new cache entries. * This effectively prevent directories from being repopulated too. */ #define KFSCACHE_LOOKUP_F_NO_INSERT KU32_C(1) /** No refreshing cache entries. */ #define KFSCACHE_LOOKUP_F_NO_REFRESH KU32_C(2) /** @} */ KU32 kFsCacheObjRelease(PKFSCACHE pCache, PKFSOBJ pObj); KU32 kFsCacheObjReleaseTagged(PKFSCACHE pCache, PKFSOBJ pObj, const char *pszWhere); #ifndef NDEBUG /* enable to debug object release. */ # define kFsCacheObjRelease(a_pCache, a_pObj) kFsCacheObjReleaseTagged(a_pCache, a_pObj, __FUNCTION__) #endif KU32 kFsCacheObjRetain(PKFSOBJ pObj); PKFSUSERDATA kFsCacheObjAddUserData(PKFSCACHE pCache, PKFSOBJ pObj, KUPTR uKey, KSIZE cbUserData); PKFSUSERDATA kFsCacheObjGetUserData(PKFSCACHE pCache, PKFSOBJ pObj, KUPTR uKey); KU8 kFsCacheObjGetUserDataLockIndex(PKFSCACHE pCache, PKFSOBJ pObj); KBOOL kFsCacheObjGetFullPathA(PKFSOBJ pObj, char *pszPath, KSIZE cbPath, char chSlash); KBOOL kFsCacheObjGetFullPathW(PKFSOBJ pObj, wchar_t *pwszPath, KSIZE cwcPath, wchar_t wcSlash); KBOOL kFsCacheObjGetFullShortPathA(PKFSOBJ pObj, char *pszPath, KSIZE cbPath, char chSlash); KBOOL kFsCacheObjGetFullShortPathW(PKFSOBJ pObj, wchar_t *pwszPath, KSIZE cwcPath, wchar_t wcSlash); KBOOL kFsCacheFileSimpleOpenReadClose(PKFSCACHE pCache, PKFSOBJ pFileObj, KU64 offStart, void *pvBuf, KSIZE cbToRead); PKFSCACHE kFsCacheCreate(KU32 fFlags); void kFsCacheDestroy(PKFSCACHE); void kFsCacheInvalidateMissing(PKFSCACHE pCache); void kFsCacheInvalidateAll(PKFSCACHE pCache); void kFsCacheInvalidateAllAndCloseDirs(PKFSCACHE pCache, KBOOL fIncludingRoot); void kFsCacheInvalidateCustomMissing(PKFSCACHE pCache); void kFsCacheInvalidateCustomBoth(PKFSCACHE pCache); KBOOL kFsCacheSetupCustomRevisionForTree(PKFSCACHE pCache, PKFSOBJ pRoot); KBOOL kFsCacheInvalidateDeletedDirectoryA(PKFSCACHE pCache, const char *pszDir); #endif