From 29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:21:29 +0200 Subject: Adding upstream version 1:0.1.9998svn3589+dfsg. Signed-off-by: Daniel Baumann --- src/lib/nt/kFsCache.c | 4840 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 4840 insertions(+) create mode 100644 src/lib/nt/kFsCache.c (limited to 'src/lib/nt/kFsCache.c') diff --git a/src/lib/nt/kFsCache.c b/src/lib/nt/kFsCache.c new file mode 100644 index 0000000..77c9655 --- /dev/null +++ b/src/lib/nt/kFsCache.c @@ -0,0 +1,4840 @@ +/* $Id: kFsCache.c 3381 2020-06-12 11:36:10Z bird $ */ +/** @file + * ntdircache.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. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include "nthlp.h" +#include "ntstat.h" + +#include +#include +#include +#ifdef _MSC_VER +# include +#endif +//#include +//#include + + +//#include +//#include + +#include "kFsCache.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @def KFSCACHE_LOG2 + * More logging. */ +#if 0 +# define KFSCACHE_LOG2(a) KFSCACHE_LOG(a) +#else +# define KFSCACHE_LOG2(a) do { } while (0) +#endif + +/** The minimum time between a directory last populated time and its + * modification time for the cache to consider it up-to-date. + * + * This helps work around races between us reading a directory and someone else + * adding / removing files and directories to /from it. Given that the + * effective time resolution typically is around 2000Hz these days, unless you + * use the new *TimePrecise API variants, there is plenty of room for a race + * here. + * + * The current value is 20ms in NT time units (100ns each), which translates + * to a 50Hz time update frequency. */ +#define KFSCACHE_MIN_LAST_POPULATED_VS_WRITE (20*1000*10) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Used by the code re-populating a directory. + */ +typedef struct KFSDIRREPOP +{ + /** The old papChildren array. */ + PKFSOBJ *papOldChildren; + /** Number of children in the array. */ + KU32 cOldChildren; + /** The index into papOldChildren we expect to find the next entry. */ + KU32 iNextOldChild; + /** Add this to iNextOldChild . */ + KI32 cNextOldChildInc; + /** Pointer to the cache (name changes). */ + PKFSCACHE pCache; +} KFSDIRREPOP; +/** Pointer to directory re-population data. */ +typedef KFSDIRREPOP *PKFSDIRREPOP; + + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static KBOOL kFsCacheRefreshObj(PKFSCACHE pCache, PKFSOBJ pObj, KFSLOOKUPERROR *penmError); + + +/** + * Retains a reference to a cache object, internal version. + * + * @returns pObj + * @param pObj The object. + */ +K_INLINE PKFSOBJ kFsCacheObjRetainInternal(PKFSOBJ pObj) +{ + KU32 cRefs = ++pObj->cRefs; + kHlpAssert(cRefs < 16384); + K_NOREF(cRefs); + return pObj; +} + + +#ifndef NDEBUG + +/** + * Debug printing. + * @param pszFormat Debug format string. + * @param ... Format argument. + */ +void kFsCacheDbgPrintfV(const char *pszFormat, va_list va) +{ + if (1) + { + DWORD const dwSavedErr = GetLastError(); + + fprintf(stderr, "debug: "); + vfprintf(stderr, pszFormat, va); + + SetLastError(dwSavedErr); + } +} + + +/** + * Debug printing. + * @param pszFormat Debug format string. + * @param ... Format argument. + */ +void kFsCacheDbgPrintf(const char *pszFormat, ...) +{ + if (1) + { + va_list va; + va_start(va, pszFormat); + kFsCacheDbgPrintfV(pszFormat, va); + va_end(va); + } +} + +#endif /* !NDEBUG */ + + + +/** + * Hashes a string. + * + * @returns 32-bit string hash. + * @param pszString String to hash. + */ +static KU32 kFsCacheStrHash(const char *pszString) +{ + /* This algorithm was created for sdbm (a public-domain reimplementation of + ndbm) database library. it was found to do well in scrambling bits, + causing better distribution of the keys and fewer splits. it also happens + to be a good general hashing function with good distribution. the actual + function is hash(i) = hash(i - 1) * 65599 + str[i]; what is included below + is the faster version used in gawk. [there is even a faster, duff-device + version] the magic constant 65599 was picked out of thin air while + experimenting with different constants, and turns out to be a prime. + this is one of the algorithms used in berkeley db (see sleepycat) and + elsewhere. */ + KU32 uHash = 0; + KU32 uChar; + while ((uChar = (unsigned char)*pszString++) != 0) + uHash = uChar + (uHash << 6) + (uHash << 16) - uHash; + return uHash; +} + + +/** + * Hashes a string. + * + * @returns The string length. + * @param pszString String to hash. + * @param puHash Where to return the 32-bit string hash. + */ +static KSIZE kFsCacheStrHashEx(const char *pszString, KU32 *puHash) +{ + const char * const pszStart = pszString; + KU32 uHash = 0; + KU32 uChar; + while ((uChar = (unsigned char)*pszString) != 0) + { + uHash = uChar + (uHash << 6) + (uHash << 16) - uHash; + pszString++; + } + *puHash = uHash; + return pszString - pszStart; +} + + +/** + * Hashes a substring. + * + * @returns 32-bit substring hash. + * @param pchString Pointer to the substring (not terminated). + * @param cchString The length of the substring. + */ +static KU32 kFsCacheStrHashN(const char *pchString, KSIZE cchString) +{ + KU32 uHash = 0; + while (cchString-- > 0) + { + KU32 uChar = (unsigned char)*pchString++; + uHash = uChar + (uHash << 6) + (uHash << 16) - uHash; + } + return uHash; +} + + +/** + * Hashes a UTF-16 string. + * + * @returns The string length in wchar_t units. + * @param pwszString String to hash. + * @param puHash Where to return the 32-bit string hash. + */ +static KSIZE kFsCacheUtf16HashEx(const wchar_t *pwszString, KU32 *puHash) +{ + const wchar_t * const pwszStart = pwszString; + KU32 uHash = 0; + KU32 uChar; + while ((uChar = *pwszString) != 0) + { + uHash = uChar + (uHash << 6) + (uHash << 16) - uHash; + pwszString++; + } + *puHash = uHash; + return pwszString - pwszStart; +} + + +/** + * Hashes a UTF-16 substring. + * + * @returns 32-bit substring hash. + * @param pwcString Pointer to the substring (not terminated). + * @param cchString The length of the substring (in wchar_t's). + */ +static KU32 kFsCacheUtf16HashN(const wchar_t *pwcString, KSIZE cwcString) +{ + KU32 uHash = 0; + while (cwcString-- > 0) + { + KU32 uChar = *pwcString++; + uHash = uChar + (uHash << 6) + (uHash << 16) - uHash; + } + return uHash; +} + + +/** + * For use when kFsCacheIAreEqualW hit's something non-trivial. + * + * @returns K_TRUE if equal, K_FALSE if different. + * @param pwcName1 The first string. + * @param pwcName2 The second string. + * @param cwcName The length of the two strings (in wchar_t's). + */ +KBOOL kFsCacheIAreEqualSlowW(const wchar_t *pwcName1, const wchar_t *pwcName2, KU16 cwcName) +{ + MY_UNICODE_STRING UniStr1 = { cwcName * sizeof(wchar_t), cwcName * sizeof(wchar_t), (wchar_t *)pwcName1 }; + MY_UNICODE_STRING UniStr2 = { cwcName * sizeof(wchar_t), cwcName * sizeof(wchar_t), (wchar_t *)pwcName2 }; + return g_pfnRtlEqualUnicodeString(&UniStr1, &UniStr2, TRUE /*fCaseInsensitive*/); +} + + +/** + * Compares two UTF-16 strings in a case-insensitive fashion. + * + * You would think we should be using _wscnicmp here instead, however it is + * locale dependent and defaults to ASCII upper/lower handling setlocale hasn't + * been called. + * + * @returns K_TRUE if equal, K_FALSE if different. + * @param pwcName1 The first string. + * @param pwcName2 The second string. + * @param cwcName The length of the two strings (in wchar_t's). + */ +K_INLINE KBOOL kFsCacheIAreEqualW(const wchar_t *pwcName1, const wchar_t *pwcName2, KU32 cwcName) +{ + while (cwcName > 0) + { + wchar_t wc1 = *pwcName1; + wchar_t wc2 = *pwcName2; + if (wc1 == wc2) + { /* not unlikely */ } + else if ( (KU16)wc1 < (KU16)0xc0 /* U+00C0 is the first upper/lower letter after 'z'. */ + && (KU16)wc2 < (KU16)0xc0) + { + /* ASCII upper case. */ + if ((KU16)wc1 - (KU16)0x61 < (KU16)26) + wc1 &= ~(wchar_t)0x20; + if ((KU16)wc2 - (KU16)0x61 < (KU16)26) + wc2 &= ~(wchar_t)0x20; + if (wc1 != wc2) + return K_FALSE; + } + else + return kFsCacheIAreEqualSlowW(pwcName1, pwcName2, (KU16)cwcName); + + pwcName2++; + pwcName1++; + cwcName--; + } + + return K_TRUE; +} + + +/** + * Looks for '..' in the path. + * + * @returns K_TRUE if '..' component found, K_FALSE if not. + * @param pszPath The path. + * @param cchPath The length of the path. + */ +static KBOOL kFsCacheHasDotDotA(const char *pszPath, KSIZE cchPath) +{ + const char *pchDot = (const char *)kHlpMemChr(pszPath, '.', cchPath); + while (pchDot) + { + if (pchDot[1] != '.') + { + pchDot++; + pchDot = (const char *)kHlpMemChr(pchDot, '.', &pszPath[cchPath] - pchDot); + } + else + { + char ch; + if ( (ch = pchDot[2]) != '\0' + && IS_SLASH(ch)) + { + if (pchDot == pszPath) + return K_TRUE; + ch = pchDot[-1]; + if ( IS_SLASH(ch) + || ch == ':') + return K_TRUE; + } + pchDot = (const char *)kHlpMemChr(pchDot + 2, '.', &pszPath[cchPath] - pchDot - 2); + } + } + + return K_FALSE; +} + + +/** + * Looks for '..' in the path. + * + * @returns K_TRUE if '..' component found, K_FALSE if not. + * @param pwszPath The path. + * @param cwcPath The length of the path (in wchar_t's). + */ +static KBOOL kFsCacheHasDotDotW(const wchar_t *pwszPath, KSIZE cwcPath) +{ + const wchar_t *pwcDot = wmemchr(pwszPath, '.', cwcPath); + while (pwcDot) + { + if (pwcDot[1] != '.') + { + pwcDot++; + pwcDot = wmemchr(pwcDot, '.', &pwszPath[cwcPath] - pwcDot); + } + else + { + wchar_t wch; + if ( (wch = pwcDot[2]) != '\0' + && IS_SLASH(wch)) + { + if (pwcDot == pwszPath) + return K_TRUE; + wch = pwcDot[-1]; + if ( IS_SLASH(wch) + || wch == ':') + return K_TRUE; + } + pwcDot = wmemchr(pwcDot + 2, '.', &pwszPath[cwcPath] - pwcDot - 2); + } + } + + return K_FALSE; +} + + +/** + * Creates an ANSI hash table entry for the given path. + * + * @returns The hash table entry or NULL if out of memory. + * @param pCache The hash + * @param pFsObj The resulting object. + * @param pszPath The path. + * @param cchPath The length of the path. + * @param uHashPath The hash of the path. + * @param fAbsolute Whether it can be refreshed using an absolute + * lookup or requires the slow treatment. + * @parma idxMissingGen The missing generation index. + * @param idxHashTab The hash table index of the path. + * @param enmError The lookup error. + */ +static PKFSHASHA kFsCacheCreatePathHashTabEntryA(PKFSCACHE pCache, PKFSOBJ pFsObj, const char *pszPath, KU32 cchPath, + KU32 uHashPath, KU32 idxHashTab, BOOL fAbsolute, KU32 idxMissingGen, + KFSLOOKUPERROR enmError) +{ + PKFSHASHA pHashEntry = (PKFSHASHA)kHlpAlloc(sizeof(*pHashEntry) + cchPath + 1); + if (pHashEntry) + { + pHashEntry->uHashPath = uHashPath; + pHashEntry->cchPath = (KU16)cchPath; + pHashEntry->fAbsolute = fAbsolute; + pHashEntry->idxMissingGen = (KU8)idxMissingGen; + pHashEntry->enmError = enmError; + pHashEntry->pszPath = (const char *)kHlpMemCopy(pHashEntry + 1, pszPath, cchPath + 1); + if (pFsObj) + { + pHashEntry->pFsObj = kFsCacheObjRetainInternal(pFsObj); + pHashEntry->uCacheGen = pFsObj->bObjType != KFSOBJ_TYPE_MISSING + ? pCache->auGenerations[ pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerationsMissing[pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + pFsObj->cPathHashRefs += 1; // for debugging + } + else + { + pHashEntry->pFsObj = NULL; + if (enmError != KFSLOOKUPERROR_UNSUPPORTED) + pHashEntry->uCacheGen = pCache->auGenerationsMissing[idxMissingGen]; + else + pHashEntry->uCacheGen = KFSOBJ_CACHE_GEN_IGNORE; + } + + pHashEntry->pNext = pCache->apAnsiPaths[idxHashTab]; + pCache->apAnsiPaths[idxHashTab] = pHashEntry; + + pCache->cbAnsiPaths += sizeof(*pHashEntry) + cchPath + 1; + pCache->cAnsiPaths++; + if (pHashEntry->pNext) + pCache->cAnsiPathCollisions++; + } + return pHashEntry; +} + + +/** + * Creates an UTF-16 hash table entry for the given path. + * + * @returns The hash table entry or NULL if out of memory. + * @param pCache The hash + * @param pFsObj The resulting object. + * @param pwszPath The path. + * @param cwcPath The length of the path (in wchar_t's). + * @param uHashPath The hash of the path. + * @param fAbsolute Whether it can be refreshed using an absolute + * lookup or requires the slow treatment. + * @parma idxMissingGen The missing generation index. + * @param idxHashTab The hash table index of the path. + * @param enmError The lookup error. + */ +static PKFSHASHW kFsCacheCreatePathHashTabEntryW(PKFSCACHE pCache, PKFSOBJ pFsObj, const wchar_t *pwszPath, KU32 cwcPath, + KU32 uHashPath, KU32 idxHashTab, BOOL fAbsolute, KU32 idxMissingGen, + KFSLOOKUPERROR enmError) +{ + PKFSHASHW pHashEntry = (PKFSHASHW)kHlpAlloc(sizeof(*pHashEntry) + (cwcPath + 1) * sizeof(wchar_t)); + if (pHashEntry) + { + pHashEntry->uHashPath = uHashPath; + pHashEntry->cwcPath = cwcPath; + pHashEntry->fAbsolute = fAbsolute; + pHashEntry->idxMissingGen = (KU8)idxMissingGen; + pHashEntry->enmError = enmError; + pHashEntry->pwszPath = (const wchar_t *)kHlpMemCopy(pHashEntry + 1, pwszPath, (cwcPath + 1) * sizeof(wchar_t)); + if (pFsObj) + { + pHashEntry->pFsObj = kFsCacheObjRetainInternal(pFsObj); + pHashEntry->uCacheGen = pFsObj->bObjType != KFSOBJ_TYPE_MISSING + ? pCache->auGenerations[ pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerationsMissing[pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + pFsObj->cPathHashRefs += 1; // for debugging + } + else + { + pHashEntry->pFsObj = NULL; + if (enmError != KFSLOOKUPERROR_UNSUPPORTED) + pHashEntry->uCacheGen = pCache->auGenerationsMissing[idxMissingGen]; + else + pHashEntry->uCacheGen = KFSOBJ_CACHE_GEN_IGNORE; + } + + pHashEntry->pNext = pCache->apUtf16Paths[idxHashTab]; + pCache->apUtf16Paths[idxHashTab] = pHashEntry; + + pCache->cbUtf16Paths += sizeof(*pHashEntry) + (cwcPath + 1) * sizeof(wchar_t); + pCache->cUtf16Paths++; + if (pHashEntry->pNext) + pCache->cAnsiPathCollisions++; + } + return pHashEntry; +} + + +/** + * Links the child in under the parent. + * + * @returns K_TRUE on success, K_FALSE if out of memory. + * @param pParent The parent node. + * @param pChild The child node. + */ +static KBOOL kFsCacheDirAddChild(PKFSCACHE pCache, PKFSDIR pParent, PKFSOBJ pChild, KFSLOOKUPERROR *penmError) +{ + if (pParent->cChildren >= pParent->cChildrenAllocated) + { + void *pvNew = kHlpRealloc(pParent->papChildren, (pParent->cChildrenAllocated + 16) * sizeof(pParent->papChildren[0])); + if (!pvNew) + return K_FALSE; + pParent->papChildren = (PKFSOBJ *)pvNew; + pParent->cChildrenAllocated += 16; + pCache->cbObjects += 16 * sizeof(pParent->papChildren[0]); + } + pParent->papChildren[pParent->cChildren++] = kFsCacheObjRetainInternal(pChild); + return K_TRUE; +} + + +/** + * Creates a new cache object. + * + * @returns Pointer (with 1 reference) to the new object. The object will not + * be linked to the parent directory yet. + * + * NULL if we're out of memory. + * + * @param pCache The cache. + * @param pParent The parent directory. + * @param pszName The ANSI name. + * @param cchName The length of the ANSI name. + * @param pwszName The UTF-16 name. + * @param cwcName The length of the UTF-16 name. + * @param pszShortName The ANSI short name, NULL if none. + * @param cchShortName The length of the ANSI short name, 0 if none. + * @param pwszShortName The UTF-16 short name, NULL if none. + * @param cwcShortName The length of the UTF-16 short name, 0 if none. + * @param bObjType The objct type. + * @param penmError Where to explain failures. + */ +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) +{ + /* + * Allocate the object. + */ + KBOOL const fDirish = bObjType != KFSOBJ_TYPE_FILE && bObjType != KFSOBJ_TYPE_OTHER; + KSIZE const cbObj = fDirish ? sizeof(KFSDIR) : sizeof(KFSOBJ); + KSIZE const cbNames = (cwcName + 1) * sizeof(wchar_t) + cchName + 1 +#ifdef KFSCACHE_CFG_SHORT_NAMES + + (cwcShortName > 0 ? (cwcShortName + 1) * sizeof(wchar_t) + cchShortName + 1 : 0) +#endif + ; + PKFSOBJ pObj; + kHlpAssert(pCache->u32Magic == KFSCACHE_MAGIC); + + pObj = (PKFSOBJ)kHlpAlloc(cbObj + cbNames); + if (pObj) + { + KU8 *pbExtra = (KU8 *)pObj + cbObj; + + KFSCACHE_LOCK(pCache); /** @todo reduce the amount of work done holding the lock */ + + pCache->cbObjects += cbObj + cbNames; + pCache->cObjects++; + + /* + * Initialize the object. + */ + pObj->u32Magic = KFSOBJ_MAGIC; + pObj->cRefs = 1; + pObj->uCacheGen = bObjType != KFSOBJ_TYPE_MISSING + ? pCache->auGenerations[pParent->Obj.fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerationsMissing[pParent->Obj.fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + pObj->bObjType = bObjType; + pObj->fHaveStats = K_FALSE; + pObj->cPathHashRefs = 0; + pObj->idxUserDataLock = KU8_MAX; + pObj->fFlags = pParent->Obj.fFlags & KFSOBJ_F_INHERITED_MASK; + pObj->pParent = pParent; + pObj->uNameHash = 0; + pObj->pNextNameHash = NULL; + pObj->pNameAlloc = NULL; + pObj->pUserDataHead = NULL; + +#ifdef KFSCACHE_CFG_UTF16 + pObj->cwcParent = pParent->Obj.cwcParent + pParent->Obj.cwcName + !!pParent->Obj.cwcName; + pObj->pwszName = (wchar_t *)kHlpMemCopy(pbExtra, pwszName, cwcName * sizeof(wchar_t)); + pObj->cwcName = cwcName; + pbExtra += cwcName * sizeof(wchar_t); + *pbExtra++ = '\0'; + *pbExtra++ = '\0'; +# ifdef KFSCACHE_CFG_SHORT_NAMES + pObj->cwcShortParent = pParent->Obj.cwcShortParent + pParent->Obj.cwcShortName + !!pParent->Obj.cwcShortName; + if (cwcShortName) + { + pObj->pwszShortName = (wchar_t *)kHlpMemCopy(pbExtra, pwszShortName, cwcShortName * sizeof(wchar_t)); + pObj->cwcShortName = cwcShortName; + pbExtra += cwcShortName * sizeof(wchar_t); + *pbExtra++ = '\0'; + *pbExtra++ = '\0'; + } + else + { + pObj->pwszShortName = pObj->pwszName; + pObj->cwcShortName = cwcName; + } +# endif +#endif + pObj->cchParent = pParent->Obj.cchParent + pParent->Obj.cchName + !!pParent->Obj.cchName; + pObj->pszName = (char *)kHlpMemCopy(pbExtra, pszName, cchName); + pObj->cchName = cchName; + pbExtra += cchName; + *pbExtra++ = '\0'; +# ifdef KFSCACHE_CFG_SHORT_NAMES + pObj->cchShortParent = pParent->Obj.cchShortParent + pParent->Obj.cchShortName + !!pParent->Obj.cchShortName; + if (cchShortName) + { + pObj->pszShortName = (char *)kHlpMemCopy(pbExtra, pszShortName, cchShortName); + pObj->cchShortName = cchShortName; + pbExtra += cchShortName; + *pbExtra++ = '\0'; + } + else + { + pObj->pszShortName = pObj->pszName; + pObj->cchShortName = cchName; + } +#endif + kHlpAssert(pbExtra - (KU8 *)pObj == cbObj); + + /* + * Type specific initialization. + */ + if (fDirish) + { + PKFSDIR pDirObj = (PKFSDIR)pObj; + pDirObj->cChildren = 0; + pDirObj->cChildrenAllocated = 0; + pDirObj->papChildren = NULL; + pDirObj->fHashTabMask = 0; + pDirObj->papHashTab = NULL; + pDirObj->hDir = INVALID_HANDLE_VALUE; + pDirObj->uDevNo = pParent->uDevNo; + pDirObj->iLastWrite = 0; + pDirObj->iLastPopulated = 0; + pDirObj->fPopulated = K_FALSE; + } + + KFSCACHE_UNLOCK(pCache); + } + else + *penmError = KFSLOOKUPERROR_OUT_OF_MEMORY; + return pObj; +} + + +/** + * Creates a new object given wide char names. + * + * This function just converts the paths and calls kFsCacheCreateObject. + * + * + * @returns Pointer (with 1 reference) to the new object. The object will not + * be linked to the parent directory yet. + * + * NULL if we're out of memory. + * + * @param pCache The cache. + * @param pParent The parent directory. + * @param pszName The ANSI name. + * @param cchName The length of the ANSI name. + * @param pwszName The UTF-16 name. + * @param cwcName The length of the UTF-16 name. + * @param pwszShortName The UTF-16 short name, NULL if none. + * @param cwcShortName The length of the UTF-16 short name, 0 if none. + * @param bObjType The objct type. + * @param penmError Where to explain failures. + */ +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) +{ + /* Convert names to ANSI first so we know their lengths. */ + char szName[KFSCACHE_CFG_MAX_ANSI_NAME]; + int cchName = WideCharToMultiByte(CP_ACP, 0, pwszName, cwcName, szName, sizeof(szName) - 1, NULL, NULL); + if (cchName >= 0) + { +#ifdef KFSCACHE_CFG_SHORT_NAMES + char szShortName[12*3 + 1]; + int cchShortName = 0; + if ( cwcShortName == 0 + || (cchShortName = WideCharToMultiByte(CP_ACP, 0, pwszShortName, cwcShortName, + szShortName, sizeof(szShortName) - 1, NULL, NULL)) > 0) +#endif + { + /* No locking needed here, kFsCacheCreateObject takes care of that. */ + return kFsCacheCreateObject(pCache, pParent, + szName, cchName, pwszName, cwcName, +#ifdef KFSCACHE_CFG_SHORT_NAMES + szShortName, cchShortName, pwszShortName, cwcShortName, +#endif + bObjType, penmError); + } + } + *penmError = KFSLOOKUPERROR_ANSI_CONVERSION_ERROR; + return NULL; +} + + +/** + * Creates a missing object. + * + * This is used for caching negative results. + * + * @returns Pointer to the newly created object on success (already linked into + * pParent). No reference. + * + * NULL on failure. + * + * @param pCache The cache. + * @param pParent The parent directory. + * @param pchName The name. + * @param cchName The length of the name. + * @param penmError Where to return failure explanations. + */ +static PKFSOBJ kFsCacheCreateMissingA(PKFSCACHE pCache, PKFSDIR pParent, const char *pchName, KU32 cchName, + KFSLOOKUPERROR *penmError) +{ + /* + * Just convert the name to UTF-16 and call kFsCacheCreateObject to do the job. + */ + wchar_t wszName[KFSCACHE_CFG_MAX_PATH]; + int cwcName = MultiByteToWideChar(CP_ACP, 0, pchName, cchName, wszName, KFSCACHE_CFG_MAX_UTF16_NAME - 1); + if (cwcName > 0) + { + /** @todo check that it actually doesn't exists before we add it. We should not + * trust the directory enumeration here, or maybe we should?? */ + + PKFSOBJ pMissing = kFsCacheCreateObject(pCache, pParent, pchName, cchName, wszName, cwcName, +#ifdef KFSCACHE_CFG_SHORT_NAMES + NULL, 0, NULL, 0, +#endif + KFSOBJ_TYPE_MISSING, penmError); + if (pMissing) + { + KBOOL fRc = kFsCacheDirAddChild(pCache, pParent, pMissing, penmError); + kFsCacheObjRelease(pCache, pMissing); + return fRc ? pMissing : NULL; + } + return NULL; + } + *penmError = KFSLOOKUPERROR_UTF16_CONVERSION_ERROR; + return NULL; +} + + +/** + * Creates a missing object, UTF-16 version. + * + * This is used for caching negative results. + * + * @returns Pointer to the newly created object on success (already linked into + * pParent). No reference. + * + * NULL on failure. + * + * @param pCache The cache. + * @param pParent The parent directory. + * @param pwcName The name. + * @param cwcName The length of the name. + * @param penmError Where to return failure explanations. + */ +static PKFSOBJ kFsCacheCreateMissingW(PKFSCACHE pCache, PKFSDIR pParent, const wchar_t *pwcName, KU32 cwcName, + KFSLOOKUPERROR *penmError) +{ + /** @todo check that it actually doesn't exists before we add it. We should not + * trust the directory enumeration here, or maybe we should?? */ + PKFSOBJ pMissing = kFsCacheCreateObjectW(pCache, pParent, pwcName, cwcName, +#ifdef KFSCACHE_CFG_SHORT_NAMES + NULL, 0, +#endif + KFSOBJ_TYPE_MISSING, penmError); + if (pMissing) + { + KBOOL fRc = kFsCacheDirAddChild(pCache, pParent, pMissing, penmError); + kFsCacheObjRelease(pCache, pMissing); + return fRc ? pMissing : NULL; + } + return NULL; +} + + +/** + * Does the growing of names. + * + * @returns pCur + * @param pCache The cache. + * @param pCur The object. + * @param pchName The name (not necessarily terminated). + * @param cchName Name length. + * @param pwcName The UTF-16 name (not necessarily terminated). + * @param cwcName The length of the UTF-16 name in wchar_t's. + * @param pchShortName The short name. + * @param cchShortName The length of the short name. This is 0 if no short + * name. + * @param pwcShortName The short UTF-16 name. + * @param cwcShortName The length of the short UTF-16 name. This is 0 if + * no short name. + */ +static PKFSOBJ kFsCacheRefreshGrowNames(PKFSCACHE pCache, PKFSOBJ pCur, + const char *pchName, KU32 cchName, + wchar_t const *pwcName, KU32 cwcName +#ifdef KFSCACHE_CFG_SHORT_NAMES + , const char *pchShortName, KU32 cchShortName, + wchar_t const *pwcShortName, KU32 cwcShortName +#endif + ) +{ + PKFSOBJNAMEALLOC pNameAlloc; + char *pch; + KU32 cbNeeded; + + pCache->cNameGrowths++; + + /* + * Figure out our requirements. + */ + cbNeeded = sizeof(KU32) + cchName + 1; +#ifdef KFSCACHE_CFG_UTF16 + cbNeeded += (cwcName + 1) * sizeof(wchar_t); +#endif +#ifdef KFSCACHE_CFG_SHORT_NAMES + cbNeeded += cchShortName + !!cchShortName; +# ifdef KFSCACHE_CFG_UTF16 + cbNeeded += (cwcShortName + !!cwcShortName) * sizeof(wchar_t); +# endif +#endif + cbNeeded = K_ALIGN_Z(cbNeeded, 8); /* Memory will likely be 8 or 16 byte aligned, so we might just claim it. */ + + /* + * Allocate memory. + */ + pNameAlloc = pCur->pNameAlloc; + if (!pNameAlloc) + { + pNameAlloc = (PKFSOBJNAMEALLOC)kHlpAlloc(cbNeeded); + if (!pNameAlloc) + return pCur; + pCache->cbObjects += cbNeeded; + pCur->pNameAlloc = pNameAlloc; + pNameAlloc->cb = cbNeeded; + } + else if (pNameAlloc->cb < cbNeeded) + { + pNameAlloc = (PKFSOBJNAMEALLOC)kHlpRealloc(pNameAlloc, cbNeeded); + if (!pNameAlloc) + return pCur; + pCache->cbObjects += cbNeeded - pNameAlloc->cb; + pCur->pNameAlloc = pNameAlloc; + pNameAlloc->cb = cbNeeded; + } + + /* + * Copy out the new names, starting with the wide char ones to avoid misaligning them. + */ + pch = &pNameAlloc->abSpace[0]; + +#ifdef KFSCACHE_CFG_UTF16 + pCur->pwszName = (wchar_t *)pch; + pCur->cwcName = cwcName; + pch = kHlpMemPCopy(pch, pwcName, cwcName * sizeof(wchar_t)); + *pch++ = '\0'; + *pch++ = '\0'; + +# ifdef KFSCACHE_CFG_SHORT_NAMES + if (cwcShortName == 0) + { + pCur->pwszShortName = pCur->pwszName; + pCur->cwcShortName = pCur->cwcName; + } + else + { + pCur->pwszShortName = (wchar_t *)pch; + pCur->cwcShortName = cwcShortName; + pch = kHlpMemPCopy(pch, pwcShortName, cwcShortName * sizeof(wchar_t)); + *pch++ = '\0'; + *pch++ = '\0'; + } +# endif +#endif + + pCur->pszName = pch; + pCur->cchName = cchName; + pch = kHlpMemPCopy(pch, pchName, cchName); + *pch++ = '\0'; + +#ifdef KFSCACHE_CFG_SHORT_NAMES + if (cchShortName == 0) + { + pCur->pszShortName = pCur->pszName; + pCur->cchShortName = pCur->cchName; + } + else + { + pCur->pszShortName = pch; + pCur->cchShortName = cchShortName; + pch = kHlpMemPCopy(pch, pchShortName, cchShortName); + *pch++ = '\0'; + } +#endif + + return pCur; +} + + +/** + * Worker for kFsCacheDirFindOldChild that refreshes the file ID value on an + * object found by name. + * + * @returns pCur. + * @param pDirRePop Repopulation data. + * @param pCur The object to check the names of. + * @param idFile The file ID. + */ +static PKFSOBJ kFsCacheDirRefreshOldChildFileId(PKFSDIRREPOP pDirRePop, PKFSOBJ pCur, KI64 idFile) +{ + KFSCACHE_LOG(("Refreshing %s/%s/ - %s changed file ID from %#llx -> %#llx...\n", + pCur->pParent->Obj.pParent->Obj.pszName, pCur->pParent->Obj.pszName, pCur->pszName, + pCur->Stats.st_ino, idFile)); + pCur->Stats.st_ino = idFile; + /** @todo inform user data items... */ + return pCur; +} + + +/** + * Worker for kFsCacheDirFindOldChild that checks the names after an old object + * has been found the file ID. + * + * @returns pCur. + * @param pDirRePop Repopulation data. + * @param pCur The object to check the names of. + * @param pwcName The file name. + * @param cwcName The length of the filename (in wchar_t's). + * @param pwcShortName The short name, if present. + * @param cwcShortName The length of the short name (in wchar_t's). + */ +static PKFSOBJ kFsCacheDirRefreshOldChildName(PKFSDIRREPOP pDirRePop, PKFSOBJ pCur, wchar_t const *pwcName, KU32 cwcName +#ifdef KFSCACHE_CFG_SHORT_NAMES + , wchar_t const *pwcShortName, KU32 cwcShortName +#endif + ) +{ + char szName[KFSCACHE_CFG_MAX_ANSI_NAME]; + int cchName; + + pDirRePop->pCache->cNameChanges++; + + /* + * Convert the names to ANSI first, that way we know all the lengths. + */ + cchName = WideCharToMultiByte(CP_ACP, 0, pwcName, cwcName, szName, sizeof(szName) - 1, NULL, NULL); + if (cchName >= 0) + { +#ifdef KFSCACHE_CFG_SHORT_NAMES + char szShortName[12*3 + 1]; + int cchShortName = 0; + if ( cwcShortName == 0 + || (cchShortName = WideCharToMultiByte(CP_ACP, 0, pwcShortName, cwcShortName, + szShortName, sizeof(szShortName) - 1, NULL, NULL)) > 0) +#endif + { + /* + * Shortening is easy for non-directory objects, for + * directory object we're only good when the length doesn't change + * on any of the components (cchParent et al). + * + * This deals with your typical xxxx.ext.tmp -> xxxx.ext renames. + */ + if ( cchName <= pCur->cchName +#ifdef KFSCACHE_CFG_UTF16 + && cwcName <= pCur->cwcName +#endif +#ifdef KFSCACHE_CFG_SHORT_NAMES + && ( cchShortName == 0 + || ( cchShortName <= pCur->cchShortName + && pCur->pszShortName != pCur->pszName +# ifdef KFSCACHE_CFG_UTF16 + && cwcShortName <= pCur->cwcShortName + && pCur->pwszShortName != pCur->pwszName +# endif + ) + ) +#endif + ) + { + if ( pCur->bObjType != KFSOBJ_TYPE_DIR + || ( cchName == pCur->cchName +#ifdef KFSCACHE_CFG_UTF16 + && cwcName == pCur->cwcName +#endif +#ifdef KFSCACHE_CFG_SHORT_NAMES + && ( cchShortName == 0 + || ( cchShortName == pCur->cchShortName +# ifdef KFSCACHE_CFG_UTF16 + && cwcShortName == pCur->cwcShortName + ) +# endif + ) +#endif + ) + ) + { + KFSCACHE_LOG(("Refreshing %ls - name changed to '%*.*ls'\n", pCur->pwszName, cwcName, cwcName, pwcName)); + *(char *)kHlpMemPCopy((void *)pCur->pszName, szName, cchName) = '\0'; + pCur->cchName = cchName; +#ifdef KFSCACHE_CFG_UTF16 + *(wchar_t *)kHlpMemPCopy((void *)pCur->pwszName, pwcName, cwcName * sizeof(wchar_t)) = '\0'; + pCur->cwcName = cwcName; +#endif +#ifdef KFSCACHE_CFG_SHORT_NAMES + *(char *)kHlpMemPCopy((void *)pCur->pszShortName, szShortName, cchShortName) = '\0'; + pCur->cchShortName = cchShortName; +# ifdef KFSCACHE_CFG_UTF16 + *(wchar_t *)kHlpMemPCopy((void *)pCur->pwszShortName, pwcShortName, cwcShortName * sizeof(wchar_t)) = '\0'; + pCur->cwcShortName = cwcShortName; +# endif +#endif + return pCur; + } + } + + return kFsCacheRefreshGrowNames(pDirRePop->pCache, pCur, szName, cchName, pwcName, cwcName, +#ifdef KFSCACHE_CFG_SHORT_NAMES + szShortName, cchShortName, pwcShortName, cwcShortName +#endif + ); + } + } + + fprintf(stderr, "kFsCacheDirRefreshOldChildName: WideCharToMultiByte error\n"); + return pCur; +} + + +/** + * Worker for kFsCacheDirFindOldChild that checks the names after an old object + * has been found by the file ID. + * + * @returns pCur. + * @param pDirRePop Repopulation data. + * @param pCur The object to check the names of. + * @param pwcName The file name. + * @param cwcName The length of the filename (in wchar_t's). + * @param pwcShortName The short name, if present. + * @param cwcShortName The length of the short name (in wchar_t's). + */ +K_INLINE PKFSOBJ kFsCacheDirCheckOldChildName(PKFSDIRREPOP pDirRePop, PKFSOBJ pCur, wchar_t const *pwcName, KU32 cwcName +#ifdef KFSCACHE_CFG_SHORT_NAMES + , wchar_t const *pwcShortName, KU32 cwcShortName +#endif + ) +{ + if ( pCur->cwcName == cwcName + && kHlpMemComp(pCur->pwszName, pwcName, cwcName * sizeof(wchar_t)) == 0) + { +#ifdef KFSCACHE_CFG_SHORT_NAMES + if (cwcShortName == 0 + ? pCur->pwszShortName == pCur->pwszName + || ( pCur->cwcShortName == cwcName + && kHlpMemComp(pCur->pwszShortName, pCur->pwszName, cwcName * sizeof(wchar_t)) == 0) + : pCur->cwcShortName == cwcShortName + && kHlpMemComp(pCur->pwszShortName, pwcShortName, cwcShortName * sizeof(wchar_t)) == 0 ) +#endif + { + return pCur; + } + } +#ifdef KFSCACHE_CFG_SHORT_NAMES + return kFsCacheDirRefreshOldChildName(pDirRePop, pCur, pwcName, cwcName, pwcShortName, cwcShortName); +#else + return kFsCacheDirRefreshOldChildName(pDirRePop, pCur, pwcName, cwcName); +#endif +} + + +/** + * Worker for kFsCachePopuplateOrRefreshDir that locates an old child object + * while re-populating a directory. + * + * @returns Pointer to the existing object if found, NULL if not. + * @param pDirRePop Repopulation data. + * @param idFile The file ID, 0 if none. + * @param pwcName The file name. + * @param cwcName The length of the filename (in wchar_t's). + * @param pwcShortName The short name, if present. + * @param cwcShortName The length of the short name (in wchar_t's). + */ +static PKFSOBJ kFsCacheDirFindOldChildSlow(PKFSDIRREPOP pDirRePop, KI64 idFile, wchar_t const *pwcName, KU32 cwcName +#ifdef KFSCACHE_CFG_SHORT_NAMES + , wchar_t const *pwcShortName, KU32 cwcShortName +#endif + ) +{ + KU32 cOldChildren = pDirRePop->cOldChildren; + KU32 const iNextOldChild = K_MIN(pDirRePop->iNextOldChild, cOldChildren - 1); + KU32 iCur; + KI32 cInc; + KI32 cDirLefts; + + kHlpAssertReturn(cOldChildren > 0, NULL); + + /* + * Search by file ID first, if we've got one. + * ASSUMES that KU32 wraps around when -1 is added to 0. + */ + if ( idFile != 0 + && idFile != KI64_MAX + && idFile != KI64_MIN) + { + cInc = pDirRePop->cNextOldChildInc; + kHlpAssert(cInc == -1 || cInc == 1); + for (cDirLefts = 2; cDirLefts > 0; cDirLefts--) + { + for (iCur = iNextOldChild; iCur < cOldChildren; iCur += cInc) + { + PKFSOBJ pCur = pDirRePop->papOldChildren[iCur]; + if (pCur->Stats.st_ino == idFile) + { + /* Remove it and check the name. */ + pDirRePop->cOldChildren = --cOldChildren; + if (iCur < cOldChildren) + pDirRePop->papOldChildren[iCur] = pDirRePop->papOldChildren[cOldChildren]; + else + cInc = -1; + pDirRePop->cNextOldChildInc = cInc; + pDirRePop->iNextOldChild = iCur + cInc; + +#ifdef KFSCACHE_CFG_SHORT_NAMES + return kFsCacheDirCheckOldChildName(pDirRePop, pCur, pwcName, cwcName, pwcShortName, cwcShortName); +#else + return kFsCacheDirCheckOldChildName(pDirRePop, pCur, pwcName, cwcName, pwcShortName, cwcShortName); +#endif + } + } + cInc = -cInc; + } + } + + /* + * Search by name. + * ASSUMES that KU32 wraps around when -1 is added to 0. + */ + cInc = pDirRePop->cNextOldChildInc; + kHlpAssert(cInc == -1 || cInc == 1); + for (cDirLefts = 2; cDirLefts > 0; cDirLefts--) + { + for (iCur = iNextOldChild; iCur < cOldChildren; iCur += cInc) + { + PKFSOBJ pCur = pDirRePop->papOldChildren[iCur]; + if ( ( pCur->cwcName == cwcName + && kFsCacheIAreEqualW(pCur->pwszName, pwcName, cwcName)) +#ifdef KFSCACHE_CFG_SHORT_NAMES + || ( pCur->cwcShortName == cwcName + && pCur->pwszShortName != pCur->pwszName + && kFsCacheIAreEqualW(pCur->pwszShortName, pwcName, cwcName)) +#endif + ) + { + /* Do this first so the compiler can share the rest with the above file ID return. */ + if (pCur->Stats.st_ino == idFile) + { /* likely */ } + else + pCur = kFsCacheDirRefreshOldChildFileId(pDirRePop, pCur, idFile); + + /* Remove it and check the name. */ + pDirRePop->cOldChildren = --cOldChildren; + if (iCur < cOldChildren) + pDirRePop->papOldChildren[iCur] = pDirRePop->papOldChildren[cOldChildren]; + else + cInc = -1; + pDirRePop->cNextOldChildInc = cInc; + pDirRePop->iNextOldChild = iCur + cInc; + +#ifdef KFSCACHE_CFG_SHORT_NAMES + return kFsCacheDirCheckOldChildName(pDirRePop, pCur, pwcName, cwcName, pwcShortName, cwcShortName); +#else + return kFsCacheDirCheckOldChildName(pDirRePop, pCur, pwcName, cwcName, pwcShortName, cwcShortName); +#endif + } + } + cInc = -cInc; + } + + return NULL; +} + + + +/** + * Worker for kFsCachePopuplateOrRefreshDir that locates an old child object + * while re-populating a directory. + * + * @returns Pointer to the existing object if found, NULL if not. + * @param pDirRePop Repopulation data. + * @param idFile The file ID, 0 if none. + * @param pwcName The file name. + * @param cwcName The length of the filename (in wchar_t's). + * @param pwcShortName The short name, if present. + * @param cwcShortName The length of the short name (in wchar_t's). + */ +K_INLINE PKFSOBJ kFsCacheDirFindOldChild(PKFSDIRREPOP pDirRePop, KI64 idFile, wchar_t const *pwcName, KU32 cwcName +#ifdef KFSCACHE_CFG_SHORT_NAMES + , wchar_t const *pwcShortName, KU32 cwcShortName +#endif + ) +{ + /* + * We only check the iNextOldChild element here, hoping that the compiler + * will actually inline this code, letting the slow version of the function + * do the rest. + */ + KU32 cOldChildren = pDirRePop->cOldChildren; + if (cOldChildren > 0) + { + KU32 const iNextOldChild = K_MIN(pDirRePop->iNextOldChild, cOldChildren - 1); + PKFSOBJ pCur = pDirRePop->papOldChildren[iNextOldChild]; + + if ( pCur->Stats.st_ino == idFile + && idFile != 0 + && idFile != KI64_MAX + && idFile != KI64_MIN) + pCur = kFsCacheDirCheckOldChildName(pDirRePop, pCur, pwcName, cwcName, pwcShortName, cwcShortName); + else if ( pCur->cwcName == cwcName + && kHlpMemComp(pCur->pwszName, pwcName, cwcName * sizeof(wchar_t)) == 0) + { + if (pCur->Stats.st_ino == idFile) + { /* likely */ } + else + pCur = kFsCacheDirRefreshOldChildFileId(pDirRePop, pCur, idFile); + +#ifdef KFSCACHE_CFG_SHORT_NAMES + if (cwcShortName == 0 + ? pCur->pwszShortName == pCur->pwszName + || ( pCur->cwcShortName == cwcName + && kHlpMemComp(pCur->pwszShortName, pCur->pwszName, cwcName * sizeof(wchar_t)) == 0) + : pCur->cwcShortName == cwcShortName + && kHlpMemComp(pCur->pwszShortName, pwcShortName, cwcShortName * sizeof(wchar_t)) == 0 ) + { /* likely */ } + else + pCur = kFsCacheDirRefreshOldChildName(pDirRePop, pCur, pwcName, cwcName, pwcShortName, cwcShortName); +#endif + } + else + pCur = NULL; + if (pCur) + { + /* + * Got a match. Remove the child from the array, replacing it with + * the last element. (This means we're reversing the second half of + * the elements, which is why we need cNextOldChildInc.) + */ + pDirRePop->cOldChildren = --cOldChildren; + if (iNextOldChild < cOldChildren) + pDirRePop->papOldChildren[iNextOldChild] = pDirRePop->papOldChildren[cOldChildren]; + pDirRePop->iNextOldChild = iNextOldChild + pDirRePop->cNextOldChildInc; + return pCur; + } + +#ifdef KFSCACHE_CFG_SHORT_NAMES + return kFsCacheDirFindOldChildSlow(pDirRePop, idFile, pwcName, cwcName, pwcShortName, cwcShortName); +#else + return kFsCacheDirFindOldChildSlow(pDirRePop, idFile, pwcName, cwcName); +#endif + } + + return NULL; +} + + + +/** + * Does the initial directory populating or refreshes it if it has been + * invalidated. + * + * This assumes the parent directory is opened. + * + * @returns K_TRUE on success, K_FALSE on error. + * @param pCache The cache. + * @param pDir The directory. + * @param penmError Where to store K_FALSE explanation. + */ +static KBOOL kFsCachePopuplateOrRefreshDir(PKFSCACHE pCache, PKFSDIR pDir, KFSLOOKUPERROR *penmError) +{ + KBOOL fRefreshing = K_FALSE; + KFSDIRREPOP DirRePop = { NULL, 0, 0, 0, NULL }; + MY_UNICODE_STRING UniStrStar = { 1 * sizeof(wchar_t), 2 * sizeof(wchar_t), L"*" }; + FILETIME Now; + + /** @todo May have to make this more flexible wrt information classes since + * older windows versions (XP, w2K) might not correctly support the + * ones with file ID on all file systems. */ +#ifdef KFSCACHE_CFG_SHORT_NAMES + MY_FILE_INFORMATION_CLASS const enmInfoClassWithId = MyFileIdBothDirectoryInformation; + MY_FILE_INFORMATION_CLASS enmInfoClass = MyFileIdBothDirectoryInformation; +#else + MY_FILE_INFORMATION_CLASS const enmInfoClassWithId = MyFileIdFullDirectoryInformation; + MY_FILE_INFORMATION_CLASS enmInfoClass = MyFileIdFullDirectoryInformation; +#endif + MY_NTSTATUS rcNt; + MY_IO_STATUS_BLOCK Ios; + union + { + /* Include the structures for better alignment. */ + MY_FILE_ID_BOTH_DIR_INFORMATION WithId; + MY_FILE_ID_FULL_DIR_INFORMATION NoId; + /** Buffer padding. We're using a 56KB buffer here to avoid size troubles + * with CIFS and such that starts at 64KB. */ + KU8 abBuf[56*1024]; + } uBuf; + + + /* + * Open the directory. + */ + if (pDir->hDir == INVALID_HANDLE_VALUE) + { + MY_OBJECT_ATTRIBUTES ObjAttr; + MY_UNICODE_STRING UniStr; + + kHlpAssert(!pDir->fPopulated); + + Ios.Information = -1; + Ios.u.Status = -1; + + UniStr.Buffer = (wchar_t *)pDir->Obj.pwszName; + UniStr.Length = (USHORT)(pDir->Obj.cwcName * sizeof(wchar_t)); + UniStr.MaximumLength = UniStr.Length + sizeof(wchar_t); + + kHlpAssertStmtReturn(pDir->Obj.pParent, *penmError = KFSLOOKUPERROR_INTERNAL_ERROR, K_FALSE); + kHlpAssertStmtReturn(pDir->Obj.pParent->hDir != INVALID_HANDLE_VALUE, *penmError = KFSLOOKUPERROR_INTERNAL_ERROR, K_FALSE); + MyInitializeObjectAttributes(&ObjAttr, &UniStr, OBJ_CASE_INSENSITIVE, pDir->Obj.pParent->hDir, NULL /*pSecAttr*/); + + /** @todo FILE_OPEN_REPARSE_POINT? */ + rcNt = g_pfnNtCreateFile(&pDir->hDir, + FILE_READ_DATA | FILE_LIST_DIRECTORY | FILE_READ_ATTRIBUTES | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL, /*cbFileInitialAlloc */ + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + NULL, /*pEaBuffer*/ + 0); /*cbEaBuffer*/ + if (MY_NT_SUCCESS(rcNt)) + { /* likely */ } + else + { + pDir->hDir = INVALID_HANDLE_VALUE; + *penmError = KFSLOOKUPERROR_DIR_OPEN_ERROR; + return K_FALSE; + } + } + /* + * When re-populating, we replace papChildren in the directory and pick + * from the old one as we go along. + */ + else if (pDir->fPopulated) + { + KU32 cAllocated; + void *pvNew; + + /* Make sure we really need to do this first. */ + if (!pDir->fNeedRePopulating) + { + if ( pDir->Obj.uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pDir->Obj.uCacheGen == pCache->auGenerations[pDir->Obj.fFlags & KFSOBJ_F_USE_CUSTOM_GEN]) + return K_TRUE; + if ( kFsCacheRefreshObj(pCache, &pDir->Obj, penmError) + && !pDir->fNeedRePopulating) + return K_TRUE; + } + + /* Yes we do need to. */ + cAllocated = K_ALIGN_Z(pDir->cChildren, 16); + pvNew = kHlpAlloc(sizeof(pDir->papChildren[0]) * cAllocated); + if (pvNew) + { + DirRePop.papOldChildren = pDir->papChildren; + DirRePop.cOldChildren = pDir->cChildren; + DirRePop.iNextOldChild = 0; + DirRePop.cNextOldChildInc = 1; + DirRePop.pCache = pCache; + + pDir->cChildren = 0; + pDir->cChildrenAllocated = cAllocated; + pDir->papChildren = (PKFSOBJ *)pvNew; + } + else + { + *penmError = KFSLOOKUPERROR_OUT_OF_MEMORY; + return K_FALSE; + } + + fRefreshing = K_TRUE; + } + if (!fRefreshing) + KFSCACHE_LOG(("Populating %s...\n", pDir->Obj.pszName)); + else + KFSCACHE_LOG(("Refreshing %s...\n", pDir->Obj.pszName)); + + /* + * Enumerate the directory content. + * + * Note! The "*" filter is necessary because kFsCacheRefreshObj may have + * previously quried a single file name and just passing NULL would + * restart that single file name query. + */ + GetSystemTimeAsFileTime(&Now); + pDir->iLastPopulated = ((KI64)Now.dwHighDateTime << 32) | Now.dwLowDateTime; + Ios.Information = -1; + Ios.u.Status = -1; + rcNt = g_pfnNtQueryDirectoryFile(pDir->hDir, + NULL, /* hEvent */ + NULL, /* pfnApcComplete */ + NULL, /* pvApcCompleteCtx */ + &Ios, + &uBuf, + sizeof(uBuf), + enmInfoClass, + FALSE, /* fReturnSingleEntry */ + &UniStrStar, /* Filter / restart pos. */ + TRUE); /* fRestartScan */ + while (MY_NT_SUCCESS(rcNt)) + { + /* + * Process the entries in the buffer. + */ + KSIZE offBuf = 0; + for (;;) + { + union + { + KU8 *pb; +#ifdef KFSCACHE_CFG_SHORT_NAMES + MY_FILE_ID_BOTH_DIR_INFORMATION *pWithId; + MY_FILE_BOTH_DIR_INFORMATION *pNoId; +#else + MY_FILE_ID_FULL_DIR_INFORMATION *pWithId; + MY_FILE_FULL_DIR_INFORMATION *pNoId; +#endif + } uPtr; + PKFSOBJ pCur; + KU32 offNext; + KU32 cbMinCur; + wchar_t *pwchFilename; + + /* ASSUME only the FileName member differs between the two structures. */ + uPtr.pb = &uBuf.abBuf[offBuf]; + if (enmInfoClass == enmInfoClassWithId) + { + pwchFilename = &uPtr.pWithId->FileName[0]; + cbMinCur = (KU32)((uintptr_t)&uPtr.pWithId->FileName[0] - (uintptr_t)uPtr.pWithId); + cbMinCur += uPtr.pNoId->FileNameLength; + } + else + { + pwchFilename = &uPtr.pNoId->FileName[0]; + cbMinCur = (KU32)((uintptr_t)&uPtr.pNoId->FileName[0] - (uintptr_t)uPtr.pNoId); + cbMinCur += uPtr.pNoId->FileNameLength; + } + + /* We need to skip the '.' and '..' entries. */ + if ( *pwchFilename != '.' + || uPtr.pNoId->FileNameLength > 4 + || !( uPtr.pNoId->FileNameLength == 2 + || ( uPtr.pNoId->FileNameLength == 4 + && pwchFilename[1] == '.') ) + ) + { + KBOOL fRc; + KU8 const bObjType = uPtr.pNoId->FileAttributes & FILE_ATTRIBUTE_DIRECTORY ? KFSOBJ_TYPE_DIR + : uPtr.pNoId->FileAttributes & (FILE_ATTRIBUTE_DEVICE | FILE_ATTRIBUTE_REPARSE_POINT) + ? KFSOBJ_TYPE_OTHER : KFSOBJ_TYPE_FILE; + + /* + * If refreshing, we must first see if this directory entry already + * exists. + */ + if (!fRefreshing) + pCur = NULL; + else + { + pCur = kFsCacheDirFindOldChild(&DirRePop, + enmInfoClass == enmInfoClassWithId ? uPtr.pWithId->FileId.QuadPart : 0, + pwchFilename, uPtr.pWithId->FileNameLength / sizeof(wchar_t) +#ifdef KFSCACHE_CFG_SHORT_NAMES + , uPtr.pWithId->ShortName, uPtr.pWithId->ShortNameLength / sizeof(wchar_t) +#endif + ); + if (pCur) + { + if (pCur->bObjType == bObjType) + { + if (pCur->bObjType == KFSOBJ_TYPE_DIR) + { + PKFSDIR pCurDir = (PKFSDIR)pCur; + if ( !pCurDir->fPopulated + || ( pCurDir->iLastWrite == uPtr.pWithId->LastWriteTime.QuadPart + && (pCur->fFlags & KFSOBJ_F_WORKING_DIR_MTIME) + && pCurDir->iLastPopulated - pCurDir->iLastWrite + >= KFSCACHE_MIN_LAST_POPULATED_VS_WRITE )) + { /* kind of likely */ } + else + { + KFSCACHE_LOG(("Refreshing %s/%s/ - %s/ needs re-populating...\n", + pDir->Obj.pParent->Obj.pszName, pDir->Obj.pszName, pCur->pszName)); + pCurDir->fNeedRePopulating = K_TRUE; + } + } + if (pCur->uCacheGen != KFSOBJ_CACHE_GEN_IGNORE) + pCur->uCacheGen = pCache->auGenerations[pCur->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + } + else if (pCur->bObjType == KFSOBJ_TYPE_MISSING) + { + KFSCACHE_LOG(("Refreshing %s/%s/ - %s appeared as %u, was missing.\n", + pDir->Obj.pParent->Obj.pszName, pDir->Obj.pszName, pCur->pszName, bObjType)); + pCur->bObjType = bObjType; + if (pCur->uCacheGen != KFSOBJ_CACHE_GEN_IGNORE) + pCur->uCacheGen = pCache->auGenerations[pCur->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + } + else + { + KFSCACHE_LOG(("Refreshing %s/%s/ - %s changed type from %u to %u! Dropping old object.\n", + pDir->Obj.pParent->Obj.pszName, pDir->Obj.pszName, pCur->pszName, + pCur->bObjType, bObjType)); + kFsCacheObjRelease(pCache, pCur); + pCur = NULL; + } + } + else + KFSCACHE_LOG(("Refreshing %s/%s/ - %*.*ls added.\n", pDir->Obj.pParent->Obj.pszName, pDir->Obj.pszName, + uPtr.pNoId->FileNameLength / sizeof(wchar_t), uPtr.pNoId->FileNameLength / sizeof(wchar_t), + pwchFilename)); + } + + if (!pCur) + { + /* + * Create the entry (not linked yet). + */ + pCur = kFsCacheCreateObjectW(pCache, pDir, pwchFilename, uPtr.pNoId->FileNameLength / sizeof(wchar_t), +#ifdef KFSCACHE_CFG_SHORT_NAMES + uPtr.pNoId->ShortName, uPtr.pNoId->ShortNameLength / sizeof(wchar_t), +#endif + bObjType, penmError); + if (!pCur) + return K_FALSE; + kHlpAssert(pCur->cRefs == 1); + } + +#ifdef KFSCACHE_CFG_SHORT_NAMES + if (enmInfoClass == enmInfoClassWithId) + birdStatFillFromFileIdBothDirInfo(&pCur->Stats, uPtr.pWithId); + else + birdStatFillFromFileBothDirInfo(&pCur->Stats, uPtr.pNoId); +#else + if (enmInfoClass == enmInfoClassWithId) + birdStatFillFromFileIdFullDirInfo(&pCur->Stats, uPtr.pWithId); + else + birdStatFillFromFileFullDirInfo(&pCur->Stats, uPtr.pNoId); +#endif + pCur->Stats.st_dev = pDir->uDevNo; + pCur->fHaveStats = K_TRUE; + + /* + * Add the entry to the directory. + */ + fRc = kFsCacheDirAddChild(pCache, pDir, pCur, penmError); + kFsCacheObjRelease(pCache, pCur); + if (fRc) + { /* likely */ } + else + { + rcNt = STATUS_NO_MEMORY; + break; + } + } + /* + * When seeing '.' we update the directory info. + */ + else if (uPtr.pNoId->FileNameLength == 2) + { + pDir->iLastWrite = uPtr.pNoId->LastWriteTime.QuadPart; +#ifdef KFSCACHE_CFG_SHORT_NAMES + if (enmInfoClass == enmInfoClassWithId) + birdStatFillFromFileIdBothDirInfo(&pDir->Obj.Stats, uPtr.pWithId); + else + birdStatFillFromFileBothDirInfo(&pDir->Obj.Stats, uPtr.pNoId); +#else + if (enmInfoClass == enmInfoClassWithId) + birdStatFillFromFileIdFullDirInfo(&pDir->Obj.Stats, uPtr.pWithId); + else + birdStatFillFromFileFullDirInfo(&pDir->Obj.Stats, uPtr.pNoId); +#endif + } + + /* + * Advance. + */ + offNext = uPtr.pNoId->NextEntryOffset; + if ( offNext >= cbMinCur + && offNext < sizeof(uBuf)) + offBuf += offNext; + else + break; + } + + /* + * Read the next chunk. + */ + rcNt = g_pfnNtQueryDirectoryFile(pDir->hDir, + NULL, /* hEvent */ + NULL, /* pfnApcComplete */ + NULL, /* pvApcCompleteCtx */ + &Ios, + &uBuf, + sizeof(uBuf), + enmInfoClass, + FALSE, /* fReturnSingleEntry */ + &UniStrStar, /* Filter / restart pos. */ + FALSE); /* fRestartScan */ + } + + if (rcNt == MY_STATUS_NO_MORE_FILES) + { + /* + * If refreshing, add missing children objects and ditch the rest. + * We ignore errors while adding missing children (lazy bird). + */ + if (!fRefreshing) + { /* more likely */ } + else + { + while (DirRePop.cOldChildren > 0) + { + KFSLOOKUPERROR enmErrorIgn; + PKFSOBJ pOldChild = DirRePop.papOldChildren[--DirRePop.cOldChildren]; + if (pOldChild->bObjType == KFSOBJ_TYPE_MISSING) + kFsCacheDirAddChild(pCache, pDir, pOldChild, &enmErrorIgn); + else + { + KFSCACHE_LOG(("Refreshing %s/%s/ - %s was removed.\n", + pDir->Obj.pParent->Obj.pszName, pDir->Obj.pszName, pOldChild->pszName)); + kHlpAssert(pOldChild->bObjType != KFSOBJ_TYPE_DIR); + /* Remove from hash table. */ + if (pOldChild->uNameHash != 0) + { + KU32 idx = pOldChild->uNameHash & pDir->fHashTabMask; + PKFSOBJ pPrev = pDir->papHashTab[idx]; + if (pPrev == pOldChild) + pDir->papHashTab[idx] = pOldChild->pNextNameHash; + else + { + while (pPrev && pPrev->pNextNameHash != pOldChild) + pPrev = pPrev->pNextNameHash; + kHlpAssert(pPrev); + if (pPrev) + pPrev->pNextNameHash = pOldChild->pNextNameHash; + } + pOldChild->uNameHash = 0; + } + } + kFsCacheObjRelease(pCache, pOldChild); + } + kHlpFree(DirRePop.papOldChildren); + } + + /* + * Mark the directory as fully populated and up to date. + */ + pDir->fPopulated = K_TRUE; + pDir->fNeedRePopulating = K_FALSE; + if (pDir->Obj.uCacheGen != KFSOBJ_CACHE_GEN_IGNORE) + pDir->Obj.uCacheGen = pCache->auGenerations[pDir->Obj.fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + return K_TRUE; + } + + /* + * If we failed during refresh, add back remaining old children. + */ + if (!fRefreshing) + { + while (DirRePop.cOldChildren > 0) + { + KFSLOOKUPERROR enmErrorIgn; + PKFSOBJ pOldChild = DirRePop.papOldChildren[--DirRePop.cOldChildren]; + kFsCacheDirAddChild(pCache, pDir, pOldChild, &enmErrorIgn); + kFsCacheObjRelease(pCache, pOldChild); + } + kHlpFree(DirRePop.papOldChildren); + } + + kHlpAssertMsgFailed(("%#x\n", rcNt)); + *penmError = KFSLOOKUPERROR_DIR_READ_ERROR; + return K_TRUE; +} + + +/** + * Does the initial directory populating or refreshes it if it has been + * invalidated. + * + * This assumes the parent directory is opened. + * + * @returns K_TRUE on success, K_FALSE on error. + * @param pCache The cache. + * @param pDir The directory. + * @param penmError Where to store K_FALSE explanation. Optional. + */ +KBOOL kFsCacheDirEnsurePopuplated(PKFSCACHE pCache, PKFSDIR pDir, KFSLOOKUPERROR *penmError) +{ + KFSLOOKUPERROR enmIgnored; + KBOOL fRet; + KFSCACHE_LOCK(pCache); + if ( pDir->fPopulated + && !pDir->fNeedRePopulating + && ( pDir->Obj.uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pDir->Obj.uCacheGen == pCache->auGenerations[pDir->Obj.fFlags & KFSOBJ_F_USE_CUSTOM_GEN]) ) + fRet = K_TRUE; + else + fRet = kFsCachePopuplateOrRefreshDir(pCache, pDir, penmError ? penmError : &enmIgnored); + KFSCACHE_UNLOCK(pCache); + return fRet; +} + + +/** + * Checks whether the modified timestamp differs on this directory. + * + * @returns K_TRUE if possibly modified, K_FALSE if definitely not modified. + * @param pDir The directory.. + */ +static KBOOL kFsCacheDirIsModified(PKFSDIR pDir) +{ + if ( pDir->hDir != INVALID_HANDLE_VALUE + && (pDir->Obj.fFlags & KFSOBJ_F_WORKING_DIR_MTIME) ) + { + if (!pDir->fNeedRePopulating) + { + MY_IO_STATUS_BLOCK Ios; + MY_FILE_BASIC_INFORMATION BasicInfo; + MY_NTSTATUS rcNt; + + Ios.Information = -1; + Ios.u.Status = -1; + + rcNt = g_pfnNtQueryInformationFile(pDir->hDir, &Ios, &BasicInfo, sizeof(BasicInfo), MyFileBasicInformation); + if (MY_NT_SUCCESS(rcNt)) + { + if ( BasicInfo.LastWriteTime.QuadPart != pDir->iLastWrite + || pDir->iLastPopulated - pDir->iLastWrite < KFSCACHE_MIN_LAST_POPULATED_VS_WRITE) + { + pDir->fNeedRePopulating = K_TRUE; + return K_TRUE; + } + return K_FALSE; + } + } + } + /* The cache root never changes. */ + else if (!pDir->Obj.pParent) + return K_FALSE; + + return K_TRUE; +} + + +static KBOOL kFsCacheRefreshMissing(PKFSCACHE pCache, PKFSOBJ pMissing, KFSLOOKUPERROR *penmError) +{ + /* + * If we can, we start by checking whether the parent directory + * has been modified. If it has, we need to check if this entry + * was added or not, most likely it wasn't added. + */ + if (!kFsCacheDirIsModified(pMissing->pParent)) + { + KFSCACHE_LOG(("Parent of missing not written to %s/%s\n", pMissing->pParent->Obj.pszName, pMissing->pszName)); + pMissing->uCacheGen = pCache->auGenerationsMissing[pMissing->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + } + else + { + MY_UNICODE_STRING UniStr; + MY_OBJECT_ATTRIBUTES ObjAttr; + MY_FILE_BASIC_INFORMATION BasicInfo; + MY_NTSTATUS rcNt; + + UniStr.Buffer = (wchar_t *)pMissing->pwszName; + UniStr.Length = (USHORT)(pMissing->cwcName * sizeof(wchar_t)); + UniStr.MaximumLength = UniStr.Length + sizeof(wchar_t); + + kHlpAssert(pMissing->pParent->hDir != INVALID_HANDLE_VALUE); + MyInitializeObjectAttributes(&ObjAttr, &UniStr, OBJ_CASE_INSENSITIVE, pMissing->pParent->hDir, NULL /*pSecAttr*/); + + rcNt = g_pfnNtQueryAttributesFile(&ObjAttr, &BasicInfo); + if (!MY_NT_SUCCESS(rcNt)) + { + /* + * Probably more likely that a missing node stays missing. + */ + pMissing->uCacheGen = pCache->auGenerationsMissing[pMissing->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + KFSCACHE_LOG(("Still missing %s/%s\n", pMissing->pParent->Obj.pszName, pMissing->pszName)); + } + else + { + /* + * We must metamorphose this node. This is tedious business + * because we need to check the file name casing. We might + * just as well update the parent directory... + */ + KU8 const bObjType = BasicInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY ? KFSOBJ_TYPE_DIR + : BasicInfo.FileAttributes & (FILE_ATTRIBUTE_DEVICE | FILE_ATTRIBUTE_REPARSE_POINT) + ? KFSOBJ_TYPE_OTHER : KFSOBJ_TYPE_FILE; + + KFSCACHE_LOG(("Birth of %s/%s as %d with attribs %#x...\n", + pMissing->pParent->Obj.pszName, pMissing->pszName, bObjType, BasicInfo.FileAttributes)); + pMissing->bObjType = bObjType; + /* (auGenerations[] - 1): make sure it's not considered up to date */ + pMissing->uCacheGen = pCache->auGenerations[pMissing->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] - 1; + /* Trigger parent directory repopulation. */ + if (pMissing->pParent->fPopulated) + pMissing->pParent->fNeedRePopulating = K_TRUE; +/** + * @todo refresh missing object names when it appears. + */ + } + } + + return K_TRUE; +} + + +static KBOOL kFsCacheRefreshMissingIntermediateDir(PKFSCACHE pCache, PKFSOBJ pMissing, KFSLOOKUPERROR *penmError) +{ + if (kFsCacheRefreshMissing(pCache, pMissing, penmError)) + { + if ( pMissing->bObjType == KFSOBJ_TYPE_DIR + || pMissing->bObjType == KFSOBJ_TYPE_MISSING) + return K_TRUE; + *penmError = KFSLOOKUPERROR_PATH_COMP_NOT_DIR; + } + + return K_FALSE; +} + + +/** + * Generic object refresh. + * + * This does not refresh the content of directories. + * + * @returns K_TRUE on success. K_FALSE and *penmError on failure. + * @param pCache The cache. + * @param pObj The object. + * @param penmError Where to return error info. + */ +static KBOOL kFsCacheRefreshObj(PKFSCACHE pCache, PKFSOBJ pObj, KFSLOOKUPERROR *penmError) +{ + KBOOL fRc; + + /* + * Since we generally assume nothing goes away in this cache, we only really + * have a hard time with negative entries. So, missing stuff goes to + * complicated land. + */ + if (pObj->bObjType == KFSOBJ_TYPE_MISSING) + fRc = kFsCacheRefreshMissing(pCache, pObj, penmError); + else + { + /* + * This object is supposed to exist, so all we need to do is query essential + * stats again. Since we've already got handles on directories, there are + * two ways to go about this. + */ + union + { + MY_FILE_NETWORK_OPEN_INFORMATION FullInfo; + MY_FILE_STANDARD_INFORMATION StdInfo; +#ifdef KFSCACHE_CFG_SHORT_NAMES + MY_FILE_ID_BOTH_DIR_INFORMATION WithId; + //MY_FILE_BOTH_DIR_INFORMATION NoId; +#else + MY_FILE_ID_FULL_DIR_INFORMATION WithId; + //MY_FILE_FULL_DIR_INFORMATION NoId; +#endif + KU8 abPadding[ sizeof(wchar_t) * KFSCACHE_CFG_MAX_UTF16_NAME + + sizeof(MY_FILE_ID_BOTH_DIR_INFORMATION)]; + } uBuf; + MY_IO_STATUS_BLOCK Ios; + MY_NTSTATUS rcNt; + if ( pObj->bObjType != KFSOBJ_TYPE_DIR + || ((PKFSDIR)pObj)->hDir == INVALID_HANDLE_VALUE) + { +#if 1 + /* This always works and doesn't mess up NtQueryDirectoryFile. */ + MY_UNICODE_STRING UniStr; + MY_OBJECT_ATTRIBUTES ObjAttr; + + UniStr.Buffer = (wchar_t *)pObj->pwszName; + UniStr.Length = (USHORT)(pObj->cwcName * sizeof(wchar_t)); + UniStr.MaximumLength = UniStr.Length + sizeof(wchar_t); + + kHlpAssert(pObj->pParent->hDir != INVALID_HANDLE_VALUE); + MyInitializeObjectAttributes(&ObjAttr, &UniStr, OBJ_CASE_INSENSITIVE, pObj->pParent->hDir, NULL /*pSecAttr*/); + + rcNt = g_pfnNtQueryFullAttributesFile(&ObjAttr, &uBuf.FullInfo); + if (MY_NT_SUCCESS(rcNt)) + { + pObj->Stats.st_size = uBuf.FullInfo.EndOfFile.QuadPart; + birdNtTimeToTimeSpec(uBuf.FullInfo.CreationTime.QuadPart, &pObj->Stats.st_birthtim); + birdNtTimeToTimeSpec(uBuf.FullInfo.ChangeTime.QuadPart, &pObj->Stats.st_ctim); + birdNtTimeToTimeSpec(uBuf.FullInfo.LastWriteTime.QuadPart, &pObj->Stats.st_mtim); + birdNtTimeToTimeSpec(uBuf.FullInfo.LastAccessTime.QuadPart, &pObj->Stats.st_atim); + pObj->Stats.st_attribs = uBuf.FullInfo.FileAttributes; + pObj->Stats.st_blksize = 65536; + pObj->Stats.st_blocks = (uBuf.FullInfo.AllocationSize.QuadPart + BIRD_STAT_BLOCK_SIZE - 1) + / BIRD_STAT_BLOCK_SIZE; + } +#else + /* This alternative lets us keep the inode number up to date and + detect name case changes. + Update: This doesn't work on windows 7, it ignores the UniStr + and continue with the "*" search. So, we're using the + above query instead for the time being. */ + MY_UNICODE_STRING UniStr; +# ifdef KFSCACHE_CFG_SHORT_NAMES + MY_FILE_INFORMATION_CLASS enmInfoClass = MyFileIdBothDirectoryInformation; +# else + MY_FILE_INFORMATION_CLASS enmInfoClass = MyFileIdFullDirectoryInformation; +# endif + + UniStr.Buffer = (wchar_t *)pObj->pwszName; + UniStr.Length = (USHORT)(pObj->cwcName * sizeof(wchar_t)); + UniStr.MaximumLength = UniStr.Length + sizeof(wchar_t); + + kHlpAssert(pObj->pParent->hDir != INVALID_HANDLE_VALUE); + + Ios.Information = -1; + Ios.u.Status = -1; + rcNt = g_pfnNtQueryDirectoryFile(pObj->pParent->hDir, + NULL, /* hEvent */ + NULL, /* pfnApcComplete */ + NULL, /* pvApcCompleteCtx */ + &Ios, + &uBuf, + sizeof(uBuf), + enmInfoClass, + TRUE, /* fReturnSingleEntry */ + &UniStr, /* Filter / restart pos. */ + TRUE); /* fRestartScan */ + + if (MY_NT_SUCCESS(rcNt)) + { + if (pObj->Stats.st_ino == uBuf.WithId.FileId.QuadPart) + KFSCACHE_LOG(("Refreshing %s/%s, no ID change...\n", pObj->pParent->Obj.pszName, pObj->pszName)); + else if ( pObj->cwcName == uBuf.WithId.FileNameLength / sizeof(wchar_t) +# ifdef KFSCACHE_CFG_SHORT_NAMES + && ( uBuf.WithId.ShortNameLength == 0 + ? pObj->pwszName == pObj->pwszShortName + || ( pObj->cwcName == pObj->cwcShortName + && memcmp(pObj->pwszName, pObj->pwszShortName, pObj->cwcName * sizeof(wchar_t)) == 0) + : pObj->cwcShortName == uBuf.WithId.ShortNameLength / sizeof(wchar_t) + && memcmp(pObj->pwszShortName, uBuf.WithId.ShortName, uBuf.WithId.ShortNameLength) == 0 + ) +# endif + && memcmp(pObj->pwszName, uBuf.WithId.FileName, uBuf.WithId.FileNameLength) == 0 + ) + { + KFSCACHE_LOG(("Refreshing %s/%s, ID changed %#llx -> %#llx...\n", + pObj->pParent->Obj.pszName, pObj->pszName, pObj->Stats.st_ino, uBuf.WithId.FileId.QuadPart)); + pObj->Stats.st_ino = uBuf.WithId.FileId.QuadPart; + } + else + { + KFSCACHE_LOG(("Refreshing %s/%s, ID changed %#llx -> %#llx and names too...\n", + pObj->pParent->Obj.pszName, pObj->pszName, pObj->Stats.st_ino, uBuf.WithId.FileId.QuadPart)); + fprintf(stderr, "kFsCacheRefreshObj - ID + name change not implemented!!\n"); + fflush(stderr); + __debugbreak(); + pObj->Stats.st_ino = uBuf.WithId.FileId.QuadPart; + /** @todo implement as needed. */ + } + + pObj->Stats.st_size = uBuf.WithId.EndOfFile.QuadPart; + birdNtTimeToTimeSpec(uBuf.WithId.CreationTime.QuadPart, &pObj->Stats.st_birthtim); + birdNtTimeToTimeSpec(uBuf.WithId.ChangeTime.QuadPart, &pObj->Stats.st_ctim); + birdNtTimeToTimeSpec(uBuf.WithId.LastWriteTime.QuadPart, &pObj->Stats.st_mtim); + birdNtTimeToTimeSpec(uBuf.WithId.LastAccessTime.QuadPart, &pObj->Stats.st_atim); + pObj->Stats.st_attribs = uBuf.WithId.FileAttributes; + pObj->Stats.st_blksize = 65536; + pObj->Stats.st_blocks = (uBuf.WithId.AllocationSize.QuadPart + BIRD_STAT_BLOCK_SIZE - 1) + / BIRD_STAT_BLOCK_SIZE; + } +#endif + if (MY_NT_SUCCESS(rcNt)) + { + pObj->uCacheGen = pCache->auGenerations[pObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + fRc = K_TRUE; + } + else + { + /* ouch! */ + kHlpAssertMsgFailed(("%#x\n", rcNt)); + fprintf(stderr, "kFsCacheRefreshObj - rcNt=%#x on non-dir - not implemented!\n", rcNt); + __debugbreak(); + fRc = K_FALSE; + } + } + else + { + /* + * An open directory. Query information via the handle, the + * file ID shouldn't have been able to change, so we can use + * NtQueryInformationFile. Right... + */ + PKFSDIR pDir = (PKFSDIR)pObj; + Ios.Information = -1; + Ios.u.Status = -1; + rcNt = g_pfnNtQueryInformationFile(pDir->hDir, &Ios, &uBuf.FullInfo, sizeof(uBuf.FullInfo), + MyFileNetworkOpenInformation); + if (MY_NT_SUCCESS(rcNt)) + rcNt = Ios.u.Status; + if (MY_NT_SUCCESS(rcNt)) + { + pObj->Stats.st_size = uBuf.FullInfo.EndOfFile.QuadPart; + birdNtTimeToTimeSpec(uBuf.FullInfo.CreationTime.QuadPart, &pObj->Stats.st_birthtim); + birdNtTimeToTimeSpec(uBuf.FullInfo.ChangeTime.QuadPart, &pObj->Stats.st_ctim); + birdNtTimeToTimeSpec(uBuf.FullInfo.LastWriteTime.QuadPart, &pObj->Stats.st_mtim); + birdNtTimeToTimeSpec(uBuf.FullInfo.LastAccessTime.QuadPart, &pObj->Stats.st_atim); + pObj->Stats.st_attribs = uBuf.FullInfo.FileAttributes; + pObj->Stats.st_blksize = 65536; + pObj->Stats.st_blocks = (uBuf.FullInfo.AllocationSize.QuadPart + BIRD_STAT_BLOCK_SIZE - 1) + / BIRD_STAT_BLOCK_SIZE; + + if ( pDir->iLastWrite == uBuf.FullInfo.LastWriteTime.QuadPart + && (pObj->fFlags & KFSOBJ_F_WORKING_DIR_MTIME) + && pDir->iLastPopulated - pDir->iLastWrite >= KFSCACHE_MIN_LAST_POPULATED_VS_WRITE) + KFSCACHE_LOG(("Refreshing %s/%s/ - no re-populating necessary.\n", + pObj->pParent->Obj.pszName, pObj->pszName)); + else + { + KFSCACHE_LOG(("Refreshing %s/%s/ - needs re-populating...\n", + pObj->pParent->Obj.pszName, pObj->pszName)); + pDir->fNeedRePopulating = K_TRUE; +#if 0 + /* Refresh the link count. */ + rcNt = g_pfnNtQueryInformationFile(pDir->hDir, &Ios, &StdInfo, sizeof(StdInfo), FileStandardInformation); + if (MY_NT_SUCCESS(rcNt)) + rcNt = Ios.s.Status; + if (MY_NT_SUCCESS(rcNt)) + pObj->Stats.st_nlink = StdInfo.NumberOfLinks; +#endif + } + } + if (MY_NT_SUCCESS(rcNt)) + { + pObj->uCacheGen = pCache->auGenerations[pObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + fRc = K_TRUE; + } + else + { + /* ouch! */ + kHlpAssertMsgFailed(("%#x\n", rcNt)); + fprintf(stderr, "kFsCacheRefreshObj - rcNt=%#x on dir - not implemented!\n", rcNt); + fflush(stderr); + __debugbreak(); + fRc = K_FALSE; + } + } + } + + return fRc; +} + + + +/** + * Looks up a drive letter. + * + * Will enter the drive if necessary. + * + * @returns Pointer to the root directory of the drive or an update-to-date + * missing node. + * @param pCache The cache. + * @param chLetter The uppercased drive letter. + * @param fFlags Lookup flags, KFSCACHE_LOOKUP_F_XXX. + * @param penmError Where to return details as to why the lookup + * failed. + */ +static PKFSOBJ kFsCacheLookupDrive(PKFSCACHE pCache, char chLetter, KU32 fFlags, KFSLOOKUPERROR *penmError) +{ + KU32 const uNameHash = chLetter - 'A'; + PKFSOBJ pCur = pCache->RootDir.papHashTab[uNameHash]; + + KU32 cLeft; + PKFSOBJ *ppCur; + MY_UNICODE_STRING NtPath; + wchar_t wszTmp[8]; + char szTmp[4]; + + /* + * Custom drive letter hashing. + */ + kHlpAssert((uNameHash & pCache->RootDir.fHashTabMask) == uNameHash); + while (pCur) + { + if ( pCur->uNameHash == uNameHash + && pCur->cchName == 2 + && pCur->pszName[0] == chLetter + && pCur->pszName[1] == ':') + { + if (pCur->bObjType == KFSOBJ_TYPE_DIR) + return pCur; + if ( (fFlags & KFSCACHE_LOOKUP_F_NO_REFRESH) + || kFsCacheRefreshMissingIntermediateDir(pCache, pCur, penmError)) + return pCur; + return NULL; + } + pCur = pCur->pNextNameHash; + } + + /* + * Make 100% sure it's not there. + */ + cLeft = pCache->RootDir.cChildren; + ppCur = pCache->RootDir.papChildren; + while (cLeft-- > 0) + { + pCur = *ppCur++; + if ( pCur->cchName == 2 + && pCur->pszName[0] == chLetter + && pCur->pszName[1] == ':') + { + if (pCur->bObjType == KFSOBJ_TYPE_DIR) + return pCur; + kHlpAssert(pCur->bObjType == KFSOBJ_TYPE_MISSING); + if ( (fFlags & KFSCACHE_LOOKUP_F_NO_REFRESH) + || kFsCacheRefreshMissingIntermediateDir(pCache, pCur, penmError)) + return pCur; + return NULL; + } + } + + if (fFlags & KFSCACHE_LOOKUP_F_NO_INSERT) + { + *penmError = KFSLOOKUPERROR_PATH_COMP_NOT_FOUND; /* close enough */ + return NULL; + } + + /* + * Need to add it. We always keep the drive letters open for the benefit + * of kFsCachePopuplateOrRefreshDir and others. + */ + wszTmp[0] = szTmp[0] = chLetter; + wszTmp[1] = szTmp[1] = ':'; + wszTmp[2] = szTmp[2] = '\\'; + wszTmp[3] = '.'; + wszTmp[4] = '\0'; + szTmp[2] = '\0'; + + NtPath.Buffer = NULL; + NtPath.Length = 0; + NtPath.MaximumLength = 0; + if (g_pfnRtlDosPathNameToNtPathName_U(wszTmp, &NtPath, NULL, NULL)) + { + HANDLE hDir; + MY_NTSTATUS rcNt; + rcNt = birdOpenFileUniStr(NULL /*hRoot*/, + &NtPath, + FILE_READ_DATA | FILE_LIST_DIRECTORY | FILE_READ_ATTRIBUTES | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + OBJ_CASE_INSENSITIVE, + &hDir); + birdFreeNtPath(&NtPath); + if (MY_NT_SUCCESS(rcNt)) + { + PKFSDIR pDir = (PKFSDIR)kFsCacheCreateObject(pCache, &pCache->RootDir, szTmp, 2, wszTmp, 2, +#ifdef KFSCACHE_CFG_SHORT_NAMES + NULL, 0, NULL, 0, +#endif + KFSOBJ_TYPE_DIR, penmError); + if (pDir) + { + /* + * We need a little bit of extra info for a drive root. These things are typically + * inherited by subdirectories down the tree, so, we do it all here for till that changes. + */ + union + { + MY_FILE_FS_VOLUME_INFORMATION VolInfo; + MY_FILE_FS_ATTRIBUTE_INFORMATION FsAttrInfo; + char abPadding[sizeof(MY_FILE_FS_VOLUME_INFORMATION) + 512]; + } uBuf; + MY_IO_STATUS_BLOCK Ios; + KBOOL fRc; + + kHlpAssert(pDir->hDir == INVALID_HANDLE_VALUE); + pDir->hDir = hDir; + + if (birdStatHandle(hDir, &pDir->Obj.Stats, pDir->Obj.pszName) == 0) + { + pDir->Obj.fHaveStats = K_TRUE; + pDir->uDevNo = pDir->Obj.Stats.st_dev; + } + else + { + /* Just in case. */ + pDir->Obj.fHaveStats = K_FALSE; + rcNt = birdQueryVolumeDeviceNumber(hDir, &uBuf.VolInfo, sizeof(uBuf), &pDir->uDevNo); + kHlpAssertMsg(MY_NT_SUCCESS(rcNt), ("%#x\n", rcNt)); + } + + /* Get the file system. */ + pDir->Obj.fFlags &= ~(KFSOBJ_F_NTFS | KFSOBJ_F_WORKING_DIR_MTIME); + Ios.Information = -1; + Ios.u.Status = -1; + rcNt = g_pfnNtQueryVolumeInformationFile(hDir, &Ios, &uBuf.FsAttrInfo, sizeof(uBuf), + MyFileFsAttributeInformation); + if (MY_NT_SUCCESS(rcNt)) + rcNt = Ios.u.Status; + if (MY_NT_SUCCESS(rcNt)) + { + if ( uBuf.FsAttrInfo.FileSystemName[0] == 'N' + && uBuf.FsAttrInfo.FileSystemName[1] == 'T' + && uBuf.FsAttrInfo.FileSystemName[2] == 'F' + && uBuf.FsAttrInfo.FileSystemName[3] == 'S' + && uBuf.FsAttrInfo.FileSystemName[4] == '\0') + { + DWORD dwDriveType = GetDriveTypeW(wszTmp); + if ( dwDriveType == DRIVE_FIXED + || dwDriveType == DRIVE_RAMDISK) + pDir->Obj.fFlags |= KFSOBJ_F_NTFS | KFSOBJ_F_WORKING_DIR_MTIME; + } + } + + /* + * Link the new drive letter into the root dir. + */ + fRc = kFsCacheDirAddChild(pCache, &pCache->RootDir, &pDir->Obj, penmError); + kFsCacheObjRelease(pCache, &pDir->Obj); + if (fRc) + { + pDir->Obj.pNextNameHash = pCache->RootDir.papHashTab[uNameHash]; + pCache->RootDir.papHashTab[uNameHash] = &pDir->Obj; + return &pDir->Obj; + } + return NULL; + } + + g_pfnNtClose(hDir); + return NULL; + } + + /* Assume it doesn't exist if this happens... This may be a little to + restrictive wrt status code checks. */ + kHlpAssertMsgStmtReturn( rcNt == MY_STATUS_OBJECT_NAME_NOT_FOUND + || rcNt == MY_STATUS_OBJECT_PATH_NOT_FOUND + || rcNt == MY_STATUS_OBJECT_PATH_INVALID + || rcNt == MY_STATUS_OBJECT_PATH_SYNTAX_BAD, + ("%#x\n", rcNt), + *penmError = KFSLOOKUPERROR_DIR_OPEN_ERROR, + NULL); + } + else + { + kHlpAssertFailed(); + *penmError = KFSLOOKUPERROR_OUT_OF_MEMORY; + return NULL; + } + + /* + * Maybe create a missing entry. + */ + if (pCache->fFlags & KFSCACHE_F_MISSING_OBJECTS) + { + PKFSOBJ pMissing = kFsCacheCreateObject(pCache, &pCache->RootDir, szTmp, 2, wszTmp, 2, +#ifdef KFSCACHE_CFG_SHORT_NAMES + NULL, 0, NULL, 0, +#endif + KFSOBJ_TYPE_MISSING, penmError); + if (pMissing) + { + KBOOL fRc = kFsCacheDirAddChild(pCache, &pCache->RootDir, pMissing, penmError); + kFsCacheObjRelease(pCache, pMissing); + return fRc ? pMissing : NULL; + } + } + else + { + /** @todo this isn't necessary correct for a root spec. */ + *penmError = KFSLOOKUPERROR_PATH_COMP_NOT_FOUND; + } + return NULL; +} + + +/** + * Slow path that allocates the child hash table and enters the given one. + * + * Allocation fialures are ignored. + * + * @param pCache The cache (for stats). + * @param pDir The directory. + * @param uNameHash The name hash to enter @a pChild under. + * @param pChild The child to enter into the hash table. + */ +static void kFsCacheDirAllocHashTabAndEnterChild(PKFSCACHE pCache, PKFSDIR pDir, KU32 uNameHash, PKFSOBJ pChild) +{ + if (uNameHash != 0) /* paranoia ^ 4! */ + { + /* + * Double the current number of children and round up to a multiple of + * two so we can avoid division. + */ + KU32 cbHashTab; + KU32 cEntries; + kHlpAssert(pDir->cChildren > 0); + if (pDir->cChildren <= KU32_MAX / 4) + { +#if defined(_MSC_VER) && 1 + KU32 cEntriesRaw = pDir->cChildren * 2; + KU32 cEntriesShift; + kHlpAssert(sizeof(cEntries) == (unsigned long)); + if (_BitScanReverse(&cEntriesShift, cEntriesRaw)) + { + if ( K_BIT32(cEntriesShift) < cEntriesRaw + && cEntriesShift < 31U) + cEntriesShift++; + cEntries = K_BIT32(cEntriesShift); + } + else + { + kHlpAssertFailed(); + cEntries = KU32_MAX / 2 + 1; + } +#else + cEntries = pDir->cChildren * 2 - 1; + cEntries |= cEntries >> 1; + cEntries |= cEntries >> 2; + cEntries |= cEntries >> 4; + cEntries |= cEntries >> 8; + cEntries |= cEntries >> 16; + cEntries++; +#endif + } + else + cEntries = KU32_MAX / 2 + 1; + kHlpAssert((cEntries & (cEntries - 1)) == 0); + + cbHashTab = cEntries * sizeof(pDir->papHashTab[0]); + pDir->papHashTab = (PKFSOBJ *)kHlpAllocZ(cbHashTab); + if (pDir->papHashTab) + { + KU32 idx; + pDir->fHashTabMask = cEntries - 1; + pCache->cbObjects += cbHashTab; + pCache->cChildHashTabs++; + pCache->cChildHashEntriesTotal += cEntries; + + /* + * Insert it. + */ + pChild->uNameHash = uNameHash; + idx = uNameHash & (pDir->fHashTabMask); + pChild->pNextNameHash = pDir->papHashTab[idx]; + pDir->papHashTab[idx] = pChild; + pCache->cChildHashed++; + } + } +} + + +/** + * Look up a child node, ANSI version. + * + * @returns Pointer to the child if found, NULL if not. + * @param pCache The cache. + * @param pParent The parent directory to search. + * @param pchName The child name to search for (not terminated). + * @param cchName The length of the child name. + */ +static PKFSOBJ kFsCacheFindChildA(PKFSCACHE pCache, PKFSDIR pParent, const char *pchName, KU32 cchName) +{ + /* + * Check for '.' first ('..' won't appear). + */ + if (cchName != 1 || *pchName != '.') + { + PKFSOBJ *ppCur; + KU32 cLeft; + KU32 uNameHash; + + /* + * Do hash table lookup. + * + * This caches previous lookups, which should be useful when looking up + * intermediate directories at least. + */ + if (pParent->papHashTab != NULL) + { + PKFSOBJ pCur; + uNameHash = kFsCacheStrHashN(pchName, cchName); + pCur = pParent->papHashTab[uNameHash & pParent->fHashTabMask]; + while (pCur) + { + if ( pCur->uNameHash == uNameHash + && ( ( pCur->cchName == cchName + && _mbsnicmp(pCur->pszName, pchName, cchName) == 0) +#ifdef KFSCACHE_CFG_SHORT_NAMES + || ( pCur->cchShortName == cchName + && pCur->pszShortName != pCur->pszName + && _mbsnicmp(pCur->pszShortName, pchName, cchName) == 0) +#endif + ) + ) + { + pCache->cChildHashHits++; + pCache->cChildSearches++; + return pCur; + } + pCur = pCur->pNextNameHash; + } + } + else + uNameHash = 0; + + /* + * Do linear search. + */ + cLeft = pParent->cChildren; + ppCur = pParent->papChildren; + while (cLeft-- > 0) + { + PKFSOBJ pCur = *ppCur++; + if ( ( pCur->cchName == cchName + && _mbsnicmp(pCur->pszName, pchName, cchName) == 0) +#ifdef KFSCACHE_CFG_SHORT_NAMES + || ( pCur->cchShortName == cchName + && pCur->pszShortName != pCur->pszName + && _mbsnicmp(pCur->pszShortName, pchName, cchName) == 0) +#endif + ) + { + /* + * Consider entering it into the parent hash table. + * Note! We hash the input, not the name we found. + */ + if ( pCur->uNameHash == 0 + && pParent->cChildren >= 2) + { + if (pParent->papHashTab) + { + if (uNameHash != 0) + { + KU32 idxNameHash = uNameHash & pParent->fHashTabMask; + pCur->uNameHash = uNameHash; + pCur->pNextNameHash = pParent->papHashTab[idxNameHash]; + pParent->papHashTab[idxNameHash] = pCur; + if (pCur->pNextNameHash) + pCache->cChildHashCollisions++; + pCache->cChildHashed++; + } + } + else + kFsCacheDirAllocHashTabAndEnterChild(pCache, pParent, kFsCacheStrHashN(pchName, cchName), pCur); + } + + pCache->cChildSearches++; + return pCur; + } + } + + pCache->cChildSearches++; + return NULL; + } + return &pParent->Obj; +} + + +/** + * Look up a child node, UTF-16 version. + * + * @returns Pointer to the child if found, NULL if not. + * @param pCache The cache. + * @param pParent The parent directory to search. + * @param pwcName The child name to search for (not terminated). + * @param cwcName The length of the child name (in wchar_t's). + */ +static PKFSOBJ kFsCacheFindChildW(PKFSCACHE pCache, PKFSDIR pParent, const wchar_t *pwcName, KU32 cwcName) +{ + /* + * Check for '.' first ('..' won't appear). + */ + if (cwcName != 1 || *pwcName != '.') + { + PKFSOBJ *ppCur; + KU32 cLeft; + KU32 uNameHash; + + /* + * Do hash table lookup. + * + * This caches previous lookups, which should be useful when looking up + * intermediate directories at least. + */ + if (pParent->papHashTab != NULL) + { + PKFSOBJ pCur; + uNameHash = kFsCacheUtf16HashN(pwcName, cwcName); + pCur = pParent->papHashTab[uNameHash & pParent->fHashTabMask]; + while (pCur) + { + if ( pCur->uNameHash == uNameHash + && ( ( pCur->cwcName == cwcName + && kFsCacheIAreEqualW(pCur->pwszName, pwcName, cwcName)) +#ifdef KFSCACHE_CFG_SHORT_NAMES + || ( pCur->cwcShortName == cwcName + && pCur->pwszShortName != pCur->pwszName + && kFsCacheIAreEqualW(pCur->pwszShortName, pwcName, cwcName)) +#endif + ) + ) + { + pCache->cChildHashHits++; + pCache->cChildSearches++; + return pCur; + } + pCur = pCur->pNextNameHash; + } + } + else + uNameHash = 0; + + /* + * Do linear search. + */ + cLeft = pParent->cChildren; + ppCur = pParent->papChildren; + while (cLeft-- > 0) + { + PKFSOBJ pCur = *ppCur++; + if ( ( pCur->cwcName == cwcName + && kFsCacheIAreEqualW(pCur->pwszName, pwcName, cwcName)) +#ifdef KFSCACHE_CFG_SHORT_NAMES + || ( pCur->cwcShortName == cwcName + && pCur->pwszShortName != pCur->pwszName + && kFsCacheIAreEqualW(pCur->pwszShortName, pwcName, cwcName)) +#endif + ) + { + /* + * Consider entering it into the parent hash table. + * Note! We hash the input, not the name we found. + */ + if ( pCur->uNameHash == 0 + && pParent->cChildren >= 4) + { + if (pParent->papHashTab) + { + if (uNameHash != 0) + { + KU32 idxNameHash = uNameHash & pParent->fHashTabMask; + pCur->uNameHash = uNameHash; + pCur->pNextNameHash = pParent->papHashTab[idxNameHash]; + pParent->papHashTab[idxNameHash] = pCur; + if (pCur->pNextNameHash) + pCache->cChildHashCollisions++; + pCache->cChildHashed++; + } + } + else + kFsCacheDirAllocHashTabAndEnterChild(pCache, pParent, kFsCacheUtf16HashN(pwcName, cwcName), pCur); + } + + pCache->cChildSearches++; + return pCur; + } + } + pCache->cChildSearches++; + return NULL; + } + return &pParent->Obj; +} + + +/** + * Looks up a UNC share, ANSI version. + * + * We keep both the server and share in the root directory entry. This means we + * have to clean up the entry name before we can insert it. + * + * @returns Pointer to the share root directory or an update-to-date missing + * node. + * @param pCache The cache. + * @param pszPath The path. + * @param fFlags Lookup flags, KFSCACHE_LOOKUP_F_XXX. + * @param poff Where to return the root dire. + * @param penmError Where to return details as to why the lookup + * failed. + */ +static PKFSOBJ kFsCacheLookupUncShareA(PKFSCACHE pCache, const char *pszPath, KU32 fFlags, + KU32 *poff, KFSLOOKUPERROR *penmError) +{ + /* + * Special case: Long path prefix w/ drive letter following it. + * Note! Must've been converted from wide char to ANSI. + */ + if ( IS_SLASH(pszPath[0]) + && IS_SLASH(pszPath[1]) + && pszPath[2] == '?' + && IS_SLASH(pszPath[3]) + && IS_ALPHA(pszPath[4]) + && pszPath[5] == ':' + && IS_SLASH(pszPath[6]) ) + { + *poff = 4 + 2; + return kFsCacheLookupDrive(pCache, pszPath[4], fFlags, penmError); + } + +#if 0 /* later */ + KU32 offStartServer; + KU32 offEndServer; + KU32 offStartShare; + + KU32 offEnd = 2; + while (IS_SLASH(pszPath[offEnd])) + offEnd++; + + offStartServer = offEnd; + while ( (ch = pszPath[offEnd]) != '\0' + && !IS_SLASH(ch)) + offEnd++; + offEndServer = offEnd; + + if (ch != '\0') + { /* likely */ } + else + { + *penmError = KFSLOOKUPERROR_NOT_FOUND; + return NULL; + } + + while (IS_SLASH(pszPath[offEnd])) + offEnd++; + offStartServer = offEnd; + while ( (ch = pszPath[offEnd]) != '\0' + && !IS_SLASH(ch)) + offEnd++; +#endif + *penmError = KFSLOOKUPERROR_UNSUPPORTED; + return NULL; +} + + +/** + * Looks up a UNC share, UTF-16 version. + * + * We keep both the server and share in the root directory entry. This means we + * have to clean up the entry name before we can insert it. + * + * @returns Pointer to the share root directory or an update-to-date missing + * node. + * @param pCache The cache. + * @param pwszPath The path. + * @param fFlags Lookup flags, KFSCACHE_LOOKUP_F_XXX. + * @param poff Where to return the root dir. + * @param penmError Where to return details as to why the lookup + * failed. + */ +static PKFSOBJ kFsCacheLookupUncShareW(PKFSCACHE pCache, const wchar_t *pwszPath, KU32 fFlags, + KU32 *poff, KFSLOOKUPERROR *penmError) +{ + /* + * Special case: Long path prefix w/ drive letter following it. + */ + if ( IS_SLASH(pwszPath[0]) + && IS_SLASH(pwszPath[1]) + && pwszPath[2] == '?' + && IS_SLASH(pwszPath[3]) + && IS_ALPHA(pwszPath[4]) + && pwszPath[5] == ':' + && IS_SLASH(pwszPath[6]) ) + { + *poff = 4 + 2; + return kFsCacheLookupDrive(pCache, (char)pwszPath[4], fFlags, penmError); + } + + +#if 0 /* later */ + KU32 offStartServer; + KU32 offEndServer; + KU32 offStartShare; + + KU32 offEnd = 2; + while (IS_SLASH(pwszPath[offEnd])) + offEnd++; + + offStartServer = offEnd; + while ( (ch = pwszPath[offEnd]) != '\0' + && !IS_SLASH(ch)) + offEnd++; + offEndServer = offEnd; + + if (ch != '\0') + { /* likely */ } + else + { + *penmError = KFSLOOKUPERROR_NOT_FOUND; + return NULL; + } + + while (IS_SLASH(pwszPath[offEnd])) + offEnd++; + offStartServer = offEnd; + while ( (ch = pwszPath[offEnd]) != '\0' + && !IS_SLASH(ch)) + offEnd++; +#endif + *penmError = KFSLOOKUPERROR_UNSUPPORTED; + return NULL; +} + + +/** + * Walks an full path relative to the given directory, ANSI version. + * + * This will create any missing nodes while walking. + * + * The caller will have to do the path hash table insertion of the result. + * + * @returns Pointer to the tree node corresponding to @a pszPath. + * NULL on lookup failure, see @a penmError for details. + * @param pCache The cache. + * @param pParent The directory to start the lookup in. + * @param pszPath The path to walk. + * @param cchPath The length of the path. + * @param fFlags Lookup flags, KFSCACHE_LOOKUP_F_XXX. + * @param penmError Where to return details as to why the lookup + * failed. + * @param ppLastAncestor Where to return the last parent element found + * (referenced) in case of error like an path/file + * not found problem. Optional. + */ +PKFSOBJ kFsCacheLookupRelativeToDirA(PKFSCACHE pCache, PKFSDIR pParent, const char *pszPath, KU32 cchPath, KU32 fFlags, + KFSLOOKUPERROR *penmError, PKFSOBJ *ppLastAncestor) +{ + /* + * Walk loop. + */ + KU32 off = 0; + if (ppLastAncestor) + *ppLastAncestor = NULL; + KFSCACHE_LOCK(pCache); + for (;;) + { + PKFSOBJ pChild; + + /* + * Find the end of the component, counting trailing slashes. + */ + char ch; + KU32 cchSlashes = 0; + KU32 offEnd = off + 1; + while ((ch = pszPath[offEnd]) != '\0') + { + if (!IS_SLASH(ch)) + offEnd++; + else + { + do + cchSlashes++; + while (IS_SLASH(pszPath[offEnd + cchSlashes])); + break; + } + } + + /* + * Do we need to populate or refresh this directory first? + */ + if ( !pParent->fNeedRePopulating + && pParent->fPopulated + && ( pParent->Obj.uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pParent->Obj.uCacheGen == pCache->auGenerations[pParent->Obj.fFlags & KFSOBJ_F_USE_CUSTOM_GEN]) ) + { /* likely */ } + else if ( (fFlags & (KFSCACHE_LOOKUP_F_NO_INSERT | KFSCACHE_LOOKUP_F_NO_REFRESH)) + || kFsCachePopuplateOrRefreshDir(pCache, pParent, penmError)) + { /* likely */ } + else + { + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + + /* + * Search the current node for the name. + * + * If we don't find it, we may insert a missing node depending on + * the cache configuration. + */ + pChild = kFsCacheFindChildA(pCache, pParent, &pszPath[off], offEnd - off); + if (pChild != NULL) + { /* probably likely */ } + else + { + if ( (pCache->fFlags & KFSCACHE_F_MISSING_OBJECTS) + && !(fFlags & KFSCACHE_LOOKUP_F_NO_INSERT)) + pChild = kFsCacheCreateMissingA(pCache, pParent, &pszPath[off], offEnd - off, penmError); + if (cchSlashes == 0 || offEnd + cchSlashes >= cchPath) + { + if (pChild) + { + kFsCacheObjRetainInternal(pChild); + KFSCACHE_UNLOCK(pCache); + return pChild; + } + *penmError = KFSLOOKUPERROR_NOT_FOUND; + } + else + *penmError = KFSLOOKUPERROR_PATH_COMP_NOT_FOUND; + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + + /* Advance off and check if we're done already. */ + off = offEnd + cchSlashes; + if ( cchSlashes == 0 + || off >= cchPath) + { + if ( pChild->bObjType != KFSOBJ_TYPE_MISSING + || pChild->uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pChild->uCacheGen == pCache->auGenerationsMissing[pChild->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + || (fFlags & KFSCACHE_LOOKUP_F_NO_REFRESH) + || kFsCacheRefreshMissing(pCache, pChild, penmError) ) + { /* likely */ } + else + { + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + kFsCacheObjRetainInternal(pChild); + KFSCACHE_UNLOCK(pCache); + return pChild; + } + + /* + * Check that it's a directory. If a missing entry, we may have to + * refresh it and re-examin it. + */ + if (pChild->bObjType == KFSOBJ_TYPE_DIR) + pParent = (PKFSDIR)pChild; + else if (pChild->bObjType != KFSOBJ_TYPE_MISSING) + { + *penmError = KFSLOOKUPERROR_PATH_COMP_NOT_DIR; + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + else if ( pChild->uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pChild->uCacheGen == pCache->auGenerationsMissing[pChild->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + || (fFlags & KFSCACHE_LOOKUP_F_NO_REFRESH)) + { + *penmError = KFSLOOKUPERROR_PATH_COMP_NOT_FOUND; + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + else if (kFsCacheRefreshMissingIntermediateDir(pCache, pChild, penmError)) + pParent = (PKFSDIR)pChild; + else + { + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + } + + /* not reached */ + KFSCACHE_UNLOCK(pCache); + return NULL; +} + + +/** + * Walks an full path relative to the given directory, UTF-16 version. + * + * This will create any missing nodes while walking. + * + * The caller will have to do the path hash table insertion of the result. + * + * @returns Pointer to the tree node corresponding to @a pszPath. + * NULL on lookup failure, see @a penmError for details. + * @param pCache The cache. + * @param pParent The directory to start the lookup in. + * @param pszPath The path to walk. No dot-dot bits allowed! + * @param cchPath The length of the path. + * @param fFlags Lookup flags, KFSCACHE_LOOKUP_F_XXX. + * @param penmError Where to return details as to why the lookup + * failed. + * @param ppLastAncestor Where to return the last parent element found + * (referenced) in case of error like an path/file + * not found problem. Optional. + */ +PKFSOBJ kFsCacheLookupRelativeToDirW(PKFSCACHE pCache, PKFSDIR pParent, const wchar_t *pwszPath, KU32 cwcPath, KU32 fFlags, + KFSLOOKUPERROR *penmError, PKFSOBJ *ppLastAncestor) +{ + /* + * Walk loop. + */ + KU32 off = 0; + if (ppLastAncestor) + *ppLastAncestor = NULL; + KFSCACHE_LOCK(pCache); + for (;;) + { + PKFSOBJ pChild; + + /* + * Find the end of the component, counting trailing slashes. + */ + wchar_t wc; + KU32 cwcSlashes = 0; + KU32 offEnd = off + 1; + while ((wc = pwszPath[offEnd]) != '\0') + { + if (!IS_SLASH(wc)) + offEnd++; + else + { + do + cwcSlashes++; + while (IS_SLASH(pwszPath[offEnd + cwcSlashes])); + break; + } + } + + /* + * Do we need to populate or refresh this directory first? + */ + if ( !pParent->fNeedRePopulating + && pParent->fPopulated + && ( pParent->Obj.uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pParent->Obj.uCacheGen == pCache->auGenerations[pParent->Obj.fFlags & KFSOBJ_F_USE_CUSTOM_GEN]) ) + { /* likely */ } + else if ( (fFlags & (KFSCACHE_LOOKUP_F_NO_INSERT | KFSCACHE_LOOKUP_F_NO_REFRESH)) + || kFsCachePopuplateOrRefreshDir(pCache, pParent, penmError)) + { /* likely */ } + else + { + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + + /* + * Search the current node for the name. + * + * If we don't find it, we may insert a missing node depending on + * the cache configuration. + */ + pChild = kFsCacheFindChildW(pCache, pParent, &pwszPath[off], offEnd - off); + if (pChild != NULL) + { /* probably likely */ } + else + { + if ( (pCache->fFlags & KFSCACHE_F_MISSING_OBJECTS) + && !(fFlags & KFSCACHE_LOOKUP_F_NO_INSERT)) + pChild = kFsCacheCreateMissingW(pCache, pParent, &pwszPath[off], offEnd - off, penmError); + if (cwcSlashes == 0 || offEnd + cwcSlashes >= cwcPath) + { + if (pChild) + { + kFsCacheObjRetainInternal(pChild); + KFSCACHE_UNLOCK(pCache); + return pChild; + } + *penmError = KFSLOOKUPERROR_NOT_FOUND; + } + else + *penmError = KFSLOOKUPERROR_PATH_COMP_NOT_FOUND; + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + + /* Advance off and check if we're done already. */ + off = offEnd + cwcSlashes; + if ( cwcSlashes == 0 + || off >= cwcPath) + { + if ( pChild->bObjType != KFSOBJ_TYPE_MISSING + || pChild->uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pChild->uCacheGen == pCache->auGenerationsMissing[pChild->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + || (fFlags & KFSCACHE_LOOKUP_F_NO_REFRESH) + || kFsCacheRefreshMissing(pCache, pChild, penmError) ) + { /* likely */ } + else + { + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + kFsCacheObjRetainInternal(pChild); + KFSCACHE_UNLOCK(pCache); + return pChild; + } + + /* + * Check that it's a directory. If a missing entry, we may have to + * refresh it and re-examin it. + */ + if (pChild->bObjType == KFSOBJ_TYPE_DIR) + pParent = (PKFSDIR)pChild; + else if (pChild->bObjType != KFSOBJ_TYPE_MISSING) + { + *penmError = KFSLOOKUPERROR_PATH_COMP_NOT_DIR; + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + else if ( pChild->uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pChild->uCacheGen == pCache->auGenerationsMissing[pChild->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + || (fFlags & KFSCACHE_LOOKUP_F_NO_REFRESH) ) + + { + *penmError = KFSLOOKUPERROR_PATH_COMP_NOT_FOUND; + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + else if (kFsCacheRefreshMissingIntermediateDir(pCache, pChild, penmError)) + pParent = (PKFSDIR)pChild; + else + { + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(&pParent->Obj); + KFSCACHE_UNLOCK(pCache); + return NULL; + } + } + + KFSCACHE_UNLOCK(pCache); + return NULL; +} + +/** + * Walk the file system tree for the given absolute path, entering it into the + * hash table. + * + * This will create any missing nodes while walking. + * + * The caller will have to do the path hash table insertion of the result. + * + * @returns Pointer to the tree node corresponding to @a pszPath. + * NULL on lookup failure, see @a penmError for details. + * @param pCache The cache. + * @param pszPath The path to walk. No dot-dot bits allowed! + * @param cchPath The length of the path. + * @param fFlags Lookup flags, KFSCACHE_LOOKUP_F_XXX. + * @param penmError Where to return details as to why the lookup + * failed. + * @param ppLastAncestor Where to return the last parent element found + * (referenced) in case of error an path/file not + * found problem. Optional. + */ +static PKFSOBJ kFsCacheLookupAbsoluteA(PKFSCACHE pCache, const char *pszPath, KU32 cchPath, KU32 fFlags, + KFSLOOKUPERROR *penmError, PKFSOBJ *ppLastAncestor) +{ + PKFSOBJ pRoot; + KU32 cchSlashes; + KU32 offEnd; + + KFSCACHE_LOG2(("kFsCacheLookupAbsoluteA(%s)\n", pszPath)); + + /* + * The root "directory" needs special handling, so we keep it outside the + * main search loop. (Special: Cannot enumerate it, UNCs, ++.) + */ + cchSlashes = 0; + if ( pszPath[1] == ':' + && IS_ALPHA(pszPath[0])) + { + /* Drive letter. */ + offEnd = 2; + kHlpAssert(IS_SLASH(pszPath[2])); + pRoot = kFsCacheLookupDrive(pCache, toupper(pszPath[0]), fFlags, penmError); + } + else if ( IS_SLASH(pszPath[0]) + && IS_SLASH(pszPath[1]) ) + pRoot = kFsCacheLookupUncShareA(pCache, pszPath, fFlags, &offEnd, penmError); + else + { + *penmError = KFSLOOKUPERROR_UNSUPPORTED; + return NULL; + } + if (pRoot) + { /* likely */ } + else + return NULL; + + /* Count slashes trailing the root spec. */ + if (offEnd < cchPath) + { + kHlpAssert(IS_SLASH(pszPath[offEnd])); + do + cchSlashes++; + while (IS_SLASH(pszPath[offEnd + cchSlashes])); + } + + /* Done already? */ + if (offEnd >= cchPath) + { + if ( pRoot->uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pRoot->uCacheGen == ( pRoot->bObjType != KFSOBJ_TYPE_MISSING + ? pCache->auGenerations[ pRoot->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerationsMissing[pRoot->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]) + || (fFlags & KFSCACHE_LOOKUP_F_NO_REFRESH) + || kFsCacheRefreshObj(pCache, pRoot, penmError)) + return kFsCacheObjRetainInternal(pRoot); + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(pRoot); + return NULL; + } + + /* Check that we've got a valid result and not a cached negative one. */ + if (pRoot->bObjType == KFSOBJ_TYPE_DIR) + { /* likely */ } + else + { + kHlpAssert(pRoot->bObjType == KFSOBJ_TYPE_MISSING); + kHlpAssert( pRoot->uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pRoot->uCacheGen == pCache->auGenerationsMissing[pRoot->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]); + return pRoot; + } + + /* + * Now that we've found a valid root directory, lookup the + * remainder of the path starting with it. + */ + return kFsCacheLookupRelativeToDirA(pCache, (PKFSDIR)pRoot, &pszPath[offEnd + cchSlashes], + cchPath - offEnd - cchSlashes, fFlags, penmError, ppLastAncestor); +} + + +/** + * Walk the file system tree for the given absolute path, UTF-16 version. + * + * This will create any missing nodes while walking. + * + * The caller will have to do the path hash table insertion of the result. + * + * @returns Pointer to the tree node corresponding to @a pszPath. + * NULL on lookup failure, see @a penmError for details. + * @param pCache The cache. + * @param pwszPath The path to walk. + * @param cwcPath The length of the path (in wchar_t's). + * @param fFlags Lookup flags, KFSCACHE_LOOKUP_F_XXX. + * @param penmError Where to return details as to why the lookup + * failed. + * @param ppLastAncestor Where to return the last parent element found + * (referenced) in case of error an path/file not + * found problem. Optional. + */ +static PKFSOBJ kFsCacheLookupAbsoluteW(PKFSCACHE pCache, const wchar_t *pwszPath, KU32 cwcPath, KU32 fFlags, + KFSLOOKUPERROR *penmError, PKFSOBJ *ppLastAncestor) +{ + PKFSDIR pParent = &pCache->RootDir; + PKFSOBJ pRoot; + KU32 off; + KU32 cwcSlashes; + KU32 offEnd; + + KFSCACHE_LOG2(("kFsCacheLookupAbsoluteW(%ls)\n", pwszPath)); + + /* + * The root "directory" needs special handling, so we keep it outside the + * main search loop. (Special: Cannot enumerate it, UNCs, ++.) + */ + cwcSlashes = 0; + off = 0; + if ( pwszPath[1] == ':' + && IS_ALPHA(pwszPath[0])) + { + /* Drive letter. */ + offEnd = 2; + kHlpAssert(IS_SLASH(pwszPath[2])); + pRoot = kFsCacheLookupDrive(pCache, toupper(pwszPath[0]), fFlags, penmError); + } + else if ( IS_SLASH(pwszPath[0]) + && IS_SLASH(pwszPath[1]) ) + pRoot = kFsCacheLookupUncShareW(pCache, pwszPath, fFlags, &offEnd, penmError); + else + { + *penmError = KFSLOOKUPERROR_UNSUPPORTED; + return NULL; + } + if (pRoot) + { /* likely */ } + else + return NULL; + + /* Count slashes trailing the root spec. */ + if (offEnd < cwcPath) + { + kHlpAssert(IS_SLASH(pwszPath[offEnd])); + do + cwcSlashes++; + while (IS_SLASH(pwszPath[offEnd + cwcSlashes])); + } + + /* Done already? */ + if (offEnd >= cwcPath) + { + if ( pRoot->uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pRoot->uCacheGen == (pRoot->bObjType != KFSOBJ_TYPE_MISSING + ? pCache->auGenerations[ pRoot->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerationsMissing[pRoot->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]) + || (fFlags & KFSCACHE_LOOKUP_F_NO_REFRESH) + || kFsCacheRefreshObj(pCache, pRoot, penmError)) + return kFsCacheObjRetainInternal(pRoot); + if (ppLastAncestor) + *ppLastAncestor = kFsCacheObjRetainInternal(pRoot); + return NULL; + } + + /* Check that we've got a valid result and not a cached negative one. */ + if (pRoot->bObjType == KFSOBJ_TYPE_DIR) + { /* likely */ } + else + { + kHlpAssert(pRoot->bObjType == KFSOBJ_TYPE_MISSING); + kHlpAssert( pRoot->uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pRoot->uCacheGen == pCache->auGenerationsMissing[pRoot->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]); + return pRoot; + } + + /* + * Now that we've found a valid root directory, lookup the + * remainder of the path starting with it. + */ + return kFsCacheLookupRelativeToDirW(pCache, (PKFSDIR)pRoot, &pwszPath[offEnd + cwcSlashes], + cwcPath - offEnd - cwcSlashes, fFlags, penmError, ppLastAncestor); +} + + +/** + * This deals with paths that are relative and paths that contains '..' + * elements, ANSI version. + * + * @returns Pointer to object corresponding to @a pszPath on success. + * NULL if this isn't a path we care to cache. + * + * @param pCache The cache. + * @param pszPath The path. + * @param cchPath The length of the path. + * @param fFlags Lookup flags, KFSCACHE_LOOKUP_F_XXX. + * @param penmError Where to return details as to why the lookup + * failed. + * @param ppLastAncestor Where to return the last parent element found + * (referenced) in case of error an path/file not + * found problem. Optional. + */ +static PKFSOBJ kFsCacheLookupSlowA(PKFSCACHE pCache, const char *pszPath, KU32 cchPath, KU32 fFlags, + KFSLOOKUPERROR *penmError, PKFSOBJ *ppLastAncestor) +{ + /* + * We just call GetFullPathNameA here to do the job as getcwd and _getdcwd + * ends up calling it anyway. + */ + char szFull[KFSCACHE_CFG_MAX_PATH]; + UINT cchFull = GetFullPathNameA(pszPath, sizeof(szFull), szFull, NULL); + if ( cchFull >= 3 + && cchFull < sizeof(szFull)) + { + KFSCACHE_LOG2(("kFsCacheLookupSlowA(%s)\n", pszPath)); + return kFsCacheLookupAbsoluteA(pCache, szFull, cchFull, fFlags, penmError, ppLastAncestor); + } + + /* The path is too long! */ + kHlpAssertMsgFailed(("'%s' -> cchFull=%u\n", pszPath, cchFull)); + *penmError = cchFull >= 3 ? KFSLOOKUPERROR_PATH_TOO_LONG : KFSLOOKUPERROR_PATH_TOO_SHORT; + return NULL; +} + + +/** + * This deals with paths that are relative and paths that contains '..' + * elements, UTF-16 version. + * + * @returns Pointer to object corresponding to @a pszPath on success. + * NULL if this isn't a path we care to cache. + * + * @param pCache The cache. + * @param pwszPath The path. + * @param cwcPath The length of the path (in wchar_t's). + * @param fFlags Lookup flags, KFSCACHE_LOOKUP_F_XXX. + * @param penmError Where to return details as to why the lookup + * failed. + * @param ppLastAncestor Where to return the last parent element found + * (referenced) in case of error an path/file not + * found problem. Optional. + */ +static PKFSOBJ kFsCacheLookupSlowW(PKFSCACHE pCache, const wchar_t *pwszPath, KU32 wcwPath, KU32 fFlags, + KFSLOOKUPERROR *penmError, PKFSOBJ *ppLastAncestor) +{ + /* + * We just call GetFullPathNameA here to do the job as getcwd and _getdcwd + * ends up calling it anyway. + */ + wchar_t wszFull[KFSCACHE_CFG_MAX_PATH]; + UINT cwcFull = GetFullPathNameW(pwszPath, KFSCACHE_CFG_MAX_PATH, wszFull, NULL); + if ( cwcFull >= 3 + && cwcFull < KFSCACHE_CFG_MAX_PATH) + { + KFSCACHE_LOG2(("kFsCacheLookupSlowA(%ls)\n", pwszPath)); + return kFsCacheLookupAbsoluteW(pCache, wszFull, cwcFull, fFlags, penmError, ppLastAncestor); + } + + /* The path is too long! */ + kHlpAssertMsgFailed(("'%ls' -> cwcFull=%u\n", pwszPath, cwcFull)); + *penmError = cwcFull >= 3 ? KFSLOOKUPERROR_PATH_TOO_LONG : KFSLOOKUPERROR_PATH_TOO_SHORT; + return NULL; +} + + +/** + * Refreshes a path hash that has expired, ANSI version. + * + * @returns pHash on success, NULL if removed. + * @param pCache The cache. + * @param pHashEntry The path hash. + * @param idxHashTab The hash table entry. + */ +static PKFSHASHA kFsCacheRefreshPathA(PKFSCACHE pCache, PKFSHASHA pHashEntry, KU32 idxHashTab) +{ + PKFSOBJ pLastAncestor = NULL; + if (!pHashEntry->pFsObj) + { + if (pHashEntry->fAbsolute) + pHashEntry->pFsObj = kFsCacheLookupAbsoluteA(pCache, pHashEntry->pszPath, pHashEntry->cchPath, 0 /*fFlags*/, + &pHashEntry->enmError, &pLastAncestor); + else + pHashEntry->pFsObj = kFsCacheLookupSlowA(pCache, pHashEntry->pszPath, pHashEntry->cchPath, 0 /*fFlags*/, + &pHashEntry->enmError, &pLastAncestor); + } + else + { + KU8 bOldType = pHashEntry->pFsObj->bObjType; + KFSLOOKUPERROR enmError; + if (kFsCacheRefreshObj(pCache, pHashEntry->pFsObj, &enmError)) + { + if (pHashEntry->pFsObj->bObjType == bOldType) + { } + else + { + pHashEntry->pFsObj->cPathHashRefs -= 1; + kFsCacheObjRelease(pCache, pHashEntry->pFsObj); + if (pHashEntry->fAbsolute) + pHashEntry->pFsObj = kFsCacheLookupAbsoluteA(pCache, pHashEntry->pszPath, pHashEntry->cchPath, 0 /*fFlags*/, + &pHashEntry->enmError, &pLastAncestor); + else + pHashEntry->pFsObj = kFsCacheLookupSlowA(pCache, pHashEntry->pszPath, pHashEntry->cchPath, 0 /*fFlags*/, + &pHashEntry->enmError, &pLastAncestor); + } + } + else + { + fprintf(stderr, "kFsCacheRefreshPathA - refresh failure handling not implemented!\n"); + __debugbreak(); + /** @todo just remove this entry. */ + return NULL; + } + } + + if (pLastAncestor && !pHashEntry->pFsObj) + pHashEntry->idxMissingGen = pLastAncestor->fFlags & KFSOBJ_F_USE_CUSTOM_GEN; + pHashEntry->uCacheGen = !pHashEntry->pFsObj + ? pCache->auGenerationsMissing[pHashEntry->idxMissingGen] + : pHashEntry->pFsObj->bObjType == KFSOBJ_TYPE_MISSING + ? pCache->auGenerationsMissing[pHashEntry->pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerations[ pHashEntry->pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + if (pLastAncestor) + kFsCacheObjRelease(pCache, pLastAncestor); + return pHashEntry; +} + + +/** + * Refreshes a path hash that has expired, UTF-16 version. + * + * @returns pHash on success, NULL if removed. + * @param pCache The cache. + * @param pHashEntry The path hash. + * @param idxHashTab The hash table entry. + */ +static PKFSHASHW kFsCacheRefreshPathW(PKFSCACHE pCache, PKFSHASHW pHashEntry, KU32 idxHashTab) +{ + PKFSOBJ pLastAncestor = NULL; + if (!pHashEntry->pFsObj) + { + if (pHashEntry->fAbsolute) + pHashEntry->pFsObj = kFsCacheLookupAbsoluteW(pCache, pHashEntry->pwszPath, pHashEntry->cwcPath, 0 /*fFlags*/, + &pHashEntry->enmError, &pLastAncestor); + else + pHashEntry->pFsObj = kFsCacheLookupSlowW(pCache, pHashEntry->pwszPath, pHashEntry->cwcPath, 0 /*fFlags*/, + &pHashEntry->enmError, &pLastAncestor); + } + else + { + KU8 bOldType = pHashEntry->pFsObj->bObjType; + KFSLOOKUPERROR enmError; + if (kFsCacheRefreshObj(pCache, pHashEntry->pFsObj, &enmError)) + { + if (pHashEntry->pFsObj->bObjType == bOldType) + { } + else + { + pHashEntry->pFsObj->cPathHashRefs -= 1; + kFsCacheObjRelease(pCache, pHashEntry->pFsObj); + if (pHashEntry->fAbsolute) + pHashEntry->pFsObj = kFsCacheLookupAbsoluteW(pCache, pHashEntry->pwszPath, pHashEntry->cwcPath, 0 /*fFlags*/, + &pHashEntry->enmError, &pLastAncestor); + else + pHashEntry->pFsObj = kFsCacheLookupSlowW(pCache, pHashEntry->pwszPath, pHashEntry->cwcPath, 0 /*fFlags*/, + &pHashEntry->enmError, &pLastAncestor); + } + } + else + { + fprintf(stderr, "kFsCacheRefreshPathW - refresh failure handling not implemented!\n"); + fflush(stderr); + __debugbreak(); + /** @todo just remove this entry. */ + return NULL; + } + } + if (pLastAncestor && !pHashEntry->pFsObj) + pHashEntry->idxMissingGen = pLastAncestor->fFlags & KFSOBJ_F_USE_CUSTOM_GEN; + pHashEntry->uCacheGen = !pHashEntry->pFsObj + ? pCache->auGenerationsMissing[pHashEntry->idxMissingGen] + : pHashEntry->pFsObj->bObjType == KFSOBJ_TYPE_MISSING + ? pCache->auGenerationsMissing[pHashEntry->pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerations[ pHashEntry->pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN]; + if (pLastAncestor) + kFsCacheObjRelease(pCache, pLastAncestor); + return pHashEntry; +} + + +/** + * Internal lookup worker that looks up a KFSOBJ for the given ANSI path with + * length and hash. + * + * This will first try the hash table. If not in the hash table, the file + * system cache tree is walked, missing bits filled in and finally a hash table + * entry is created. + * + * Only drive letter paths are cachable. We don't do any UNC paths at this + * point. + * + * @returns Reference to object corresponding to @a pszPath on success, this + * must be released by kFsCacheObjRelease. + * NULL if not a path we care to cache. + * @param pCache The cache. + * @param pchPath The path to lookup. + * @param cchPath The path length. + * @param uHashPath The hash of the path. + * @param penmError Where to return details as to why the lookup + * failed. + */ +static PKFSOBJ kFsCacheLookupHashedA(PKFSCACHE pCache, const char *pchPath, KU32 cchPath, KU32 uHashPath, + KFSLOOKUPERROR *penmError) +{ + /* + * Do hash table lookup of the path. + */ + KU32 idxHashTab = uHashPath % K_ELEMENTS(pCache->apAnsiPaths); + PKFSHASHA pHashEntry = pCache->apAnsiPaths[idxHashTab]; + kHlpAssert(pCache->u32Magic == KFSCACHE_MAGIC); + if (pHashEntry) + { + do + { + if ( pHashEntry->uHashPath == uHashPath + && pHashEntry->cchPath == cchPath + && kHlpMemComp(pHashEntry->pszPath, pchPath, cchPath) == 0) + { + PKFSOBJ pFsObj; + if ( pHashEntry->uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pHashEntry->uCacheGen == ( (pFsObj = pHashEntry->pFsObj) != NULL + ? pFsObj->bObjType != KFSOBJ_TYPE_MISSING + ? pCache->auGenerations[ pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerationsMissing[pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerationsMissing[pHashEntry->idxMissingGen]) + || (pHashEntry = kFsCacheRefreshPathA(pCache, pHashEntry, idxHashTab)) ) + { + pCache->cLookups++; + pCache->cPathHashHits++; + KFSCACHE_LOG2(("kFsCacheLookupA(%*.*s) - hit %p\n", cchPath, cchPath, pchPath, pHashEntry->pFsObj)); + *penmError = pHashEntry->enmError; + if (pHashEntry->pFsObj) + return kFsCacheObjRetainInternal(pHashEntry->pFsObj); + return NULL; + } + break; + } + pHashEntry = pHashEntry->pNext; + } while (pHashEntry); + } + + /* + * Create an entry for it by walking the file system cache and filling in the blanks. + */ + if ( cchPath > 0 + && cchPath < KFSCACHE_CFG_MAX_PATH) + { + PKFSOBJ pFsObj; + KBOOL fAbsolute; + PKFSOBJ pLastAncestor = NULL; + + /* Is absolute without any '..' bits? */ + if ( cchPath >= 3 + && ( ( pchPath[1] == ':' /* Drive letter */ + && IS_SLASH(pchPath[2]) + && IS_ALPHA(pchPath[0]) ) + || ( IS_SLASH(pchPath[0]) /* UNC */ + && IS_SLASH(pchPath[1]) ) ) + && !kFsCacheHasDotDotA(pchPath, cchPath) ) + { + pFsObj = kFsCacheLookupAbsoluteA(pCache, pchPath, cchPath, 0 /*fFlags*/, penmError, &pLastAncestor); + fAbsolute = K_TRUE; + } + else + { + pFsObj = kFsCacheLookupSlowA(pCache, pchPath, cchPath, 0 /*fFlags*/, penmError, &pLastAncestor); + fAbsolute = K_FALSE; + } + if ( pFsObj + || ( (pCache->fFlags & KFSCACHE_F_MISSING_PATHS) + && *penmError != KFSLOOKUPERROR_PATH_TOO_LONG) + || *penmError == KFSLOOKUPERROR_UNSUPPORTED ) + kFsCacheCreatePathHashTabEntryA(pCache, pFsObj, pchPath, cchPath, uHashPath, idxHashTab, fAbsolute, + pLastAncestor ? pLastAncestor->fFlags & KFSOBJ_F_USE_CUSTOM_GEN : 0, *penmError); + if (pLastAncestor) + kFsCacheObjRelease(pCache, pLastAncestor); + + pCache->cLookups++; + if (pFsObj) + pCache->cWalkHits++; + return pFsObj; + } + + *penmError = cchPath > 0 ? KFSLOOKUPERROR_PATH_TOO_LONG : KFSLOOKUPERROR_PATH_TOO_SHORT; + return NULL; +} + + +/** + * Internal lookup worker that looks up a KFSOBJ for the given UTF-16 path with + * length and hash. + * + * This will first try the hash table. If not in the hash table, the file + * system cache tree is walked, missing bits filled in and finally a hash table + * entry is created. + * + * Only drive letter paths are cachable. We don't do any UNC paths at this + * point. + * + * @returns Reference to object corresponding to @a pwcPath on success, this + * must be released by kFsCacheObjRelease. + * NULL if not a path we care to cache. + * @param pCache The cache. + * @param pwcPath The path to lookup. + * @param cwcPath The length of the path (in wchar_t's). + * @param uHashPath The hash of the path. + * @param penmError Where to return details as to why the lookup + * failed. + */ +static PKFSOBJ kFsCacheLookupHashedW(PKFSCACHE pCache, const wchar_t *pwcPath, KU32 cwcPath, KU32 uHashPath, + KFSLOOKUPERROR *penmError) +{ + /* + * Do hash table lookup of the path. + */ + KU32 idxHashTab = uHashPath % K_ELEMENTS(pCache->apAnsiPaths); + PKFSHASHW pHashEntry = pCache->apUtf16Paths[idxHashTab]; + kHlpAssert(pCache->u32Magic == KFSCACHE_MAGIC); + if (pHashEntry) + { + do + { + if ( pHashEntry->uHashPath == uHashPath + && pHashEntry->cwcPath == cwcPath + && kHlpMemComp(pHashEntry->pwszPath, pwcPath, cwcPath) == 0) + { + PKFSOBJ pFsObj; + if ( pHashEntry->uCacheGen == KFSOBJ_CACHE_GEN_IGNORE + || pHashEntry->uCacheGen == ((pFsObj = pHashEntry->pFsObj) != NULL + ? pFsObj->bObjType != KFSOBJ_TYPE_MISSING + ? pCache->auGenerations[ pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerationsMissing[pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN] + : pCache->auGenerationsMissing[pHashEntry->idxMissingGen]) + || (pHashEntry = kFsCacheRefreshPathW(pCache, pHashEntry, idxHashTab)) ) + { + pCache->cLookups++; + pCache->cPathHashHits++; + KFSCACHE_LOG2(("kFsCacheLookupW(%*.*ls) - hit %p\n", cwcPath, cwcPath, pwcPath, pHashEntry->pFsObj)); + *penmError = pHashEntry->enmError; + if (pHashEntry->pFsObj) + return kFsCacheObjRetainInternal(pHashEntry->pFsObj); + return NULL; + } + break; + } + pHashEntry = pHashEntry->pNext; + } while (pHashEntry); + } + + /* + * Create an entry for it by walking the file system cache and filling in the blanks. + */ + if ( cwcPath > 0 + && cwcPath < KFSCACHE_CFG_MAX_PATH) + { + PKFSOBJ pFsObj; + KBOOL fAbsolute; + PKFSOBJ pLastAncestor = NULL; + + /* Is absolute without any '..' bits? */ + if ( cwcPath >= 3 + && ( ( pwcPath[1] == ':' /* Drive letter */ + && IS_SLASH(pwcPath[2]) + && IS_ALPHA(pwcPath[0]) ) + || ( IS_SLASH(pwcPath[0]) /* UNC */ + && IS_SLASH(pwcPath[1]) ) ) + && !kFsCacheHasDotDotW(pwcPath, cwcPath) ) + { + pFsObj = kFsCacheLookupAbsoluteW(pCache, pwcPath, cwcPath, 0 /*fFlags*/, penmError, &pLastAncestor); + fAbsolute = K_TRUE; + } + else + { + pFsObj = kFsCacheLookupSlowW(pCache, pwcPath, cwcPath, 0 /*fFlags*/, penmError, &pLastAncestor); + fAbsolute = K_FALSE; + } + if ( pFsObj + || ( (pCache->fFlags & KFSCACHE_F_MISSING_PATHS) + && *penmError != KFSLOOKUPERROR_PATH_TOO_LONG) + || *penmError == KFSLOOKUPERROR_UNSUPPORTED ) + kFsCacheCreatePathHashTabEntryW(pCache, pFsObj, pwcPath, cwcPath, uHashPath, idxHashTab, fAbsolute, + pLastAncestor ? pLastAncestor->fFlags & KFSOBJ_F_USE_CUSTOM_GEN : 0, *penmError); + if (pLastAncestor) + kFsCacheObjRelease(pCache, pLastAncestor); + + pCache->cLookups++; + if (pFsObj) + pCache->cWalkHits++; + return pFsObj; + } + + *penmError = cwcPath > 0 ? KFSLOOKUPERROR_PATH_TOO_LONG : KFSLOOKUPERROR_PATH_TOO_SHORT; + return NULL; +} + + + +/** + * Looks up a KFSOBJ for the given ANSI path. + * + * This will first try the hash table. If not in the hash table, the file + * system cache tree is walked, missing bits filled in and finally a hash table + * entry is created. + * + * Only drive letter paths are cachable. We don't do any UNC paths at this + * point. + * + * @returns Reference to object corresponding to @a pszPath on success, this + * must be released by kFsCacheObjRelease. + * NULL if not a path we care to cache. + * @param pCache The cache. + * @param pszPath The path to lookup. + * @param penmError Where to return details as to why the lookup + * failed. + */ +PKFSOBJ kFsCacheLookupA(PKFSCACHE pCache, const char *pszPath, KFSLOOKUPERROR *penmError) +{ + KU32 uHashPath; + KU32 cchPath = (KU32)kFsCacheStrHashEx(pszPath, &uHashPath); + PKFSOBJ pObj; + KFSCACHE_LOCK(pCache); + pObj = kFsCacheLookupHashedA(pCache, pszPath, cchPath, uHashPath, penmError); + KFSCACHE_UNLOCK(pCache); + return pObj; +} + + +/** + * Looks up a KFSOBJ for the given UTF-16 path. + * + * This will first try the hash table. If not in the hash table, the file + * system cache tree is walked, missing bits filled in and finally a hash table + * entry is created. + * + * Only drive letter paths are cachable. We don't do any UNC paths at this + * point. + * + * @returns Reference to object corresponding to @a pwszPath on success, this + * must be released by kFsCacheObjRelease. + * NULL if not a path we care to cache. + * @param pCache The cache. + * @param pwszPath The path to lookup. + * @param penmError Where to return details as to why the lookup + * failed. + */ +PKFSOBJ kFsCacheLookupW(PKFSCACHE pCache, const wchar_t *pwszPath, KFSLOOKUPERROR *penmError) +{ + KU32 uHashPath; + KU32 cwcPath = (KU32)kFsCacheUtf16HashEx(pwszPath, &uHashPath); + PKFSOBJ pObj; + KFSCACHE_LOCK(pCache); + pObj = kFsCacheLookupHashedW(pCache, pwszPath, cwcPath, uHashPath, penmError); + KFSCACHE_UNLOCK(pCache); + return pObj; +} + + +/** + * Looks up a KFSOBJ for the given ANSI path. + * + * This will first try the hash table. If not in the hash table, the file + * system cache tree is walked, missing bits filled in and finally a hash table + * entry is created. + * + * Only drive letter paths are cachable. We don't do any UNC paths at this + * point. + * + * @returns Reference to object corresponding to @a pchPath on success, this + * must be released by kFsCacheObjRelease. + * NULL if not a path we care to cache. + * @param pCache The cache. + * @param pchPath The path to lookup (does not need to be nul + * terminated). + * @param cchPath The path length. + * @param penmError Where to return details as to why the lookup + * failed. + */ +PKFSOBJ kFsCacheLookupWithLengthA(PKFSCACHE pCache, const char *pchPath, KSIZE cchPath, KFSLOOKUPERROR *penmError) +{ + KU32 uHashPath = kFsCacheStrHashN(pchPath, cchPath); + PKFSOBJ pObj; + KFSCACHE_LOCK(pCache); + pObj = kFsCacheLookupHashedA(pCache, pchPath, (KU32)cchPath, uHashPath, penmError); + KFSCACHE_UNLOCK(pCache); + return pObj; +} + + +/** + * Looks up a KFSOBJ for the given UTF-16 path. + * + * This will first try the hash table. If not in the hash table, the file + * system cache tree is walked, missing bits filled in and finally a hash table + * entry is created. + * + * Only drive letter paths are cachable. We don't do any UNC paths at this + * point. + * + * @returns Reference to object corresponding to @a pwchPath on success, this + * must be released by kFsCacheObjRelease. + * NULL if not a path we care to cache. + * @param pCache The cache. + * @param pwcPath The path to lookup (does not need to be nul + * terminated). + * @param cwcPath The path length (in wchar_t's). + * @param penmError Where to return details as to why the lookup + * failed. + */ +PKFSOBJ kFsCacheLookupWithLengthW(PKFSCACHE pCache, const wchar_t *pwcPath, KSIZE cwcPath, KFSLOOKUPERROR *penmError) +{ + KU32 uHashPath = kFsCacheUtf16HashN(pwcPath, cwcPath); + PKFSOBJ pObj; + KFSCACHE_LOCK(pCache); + pObj = kFsCacheLookupHashedW(pCache, pwcPath, (KU32)cwcPath, uHashPath, penmError); + KFSCACHE_UNLOCK(pCache); + return pObj; +} + + +/** + * Wrapper around kFsCacheLookupA that drops KFSOBJ_TYPE_MISSING and returns + * KFSLOOKUPERROR_NOT_FOUND instead. + * + * @returns Reference to object corresponding to @a pszPath on success, this + * must be released by kFsCacheObjRelease. + * NULL if not a path we care to cache. + * @param pCache The cache. + * @param pszPath The path to lookup. + * @param penmError Where to return details as to why the lookup + * failed. + */ +PKFSOBJ kFsCacheLookupNoMissingA(PKFSCACHE pCache, const char *pszPath, KFSLOOKUPERROR *penmError) +{ + PKFSOBJ pObj; + KFSCACHE_LOCK(pCache); /* probably not necessary */ + pObj = kFsCacheLookupA(pCache, pszPath, penmError); + if (pObj) + { + if (pObj->bObjType != KFSOBJ_TYPE_MISSING) + { + KFSCACHE_UNLOCK(pCache); + return pObj; + } + + kFsCacheObjRelease(pCache, pObj); + *penmError = KFSLOOKUPERROR_NOT_FOUND; + } + KFSCACHE_UNLOCK(pCache); + return NULL; +} + + +/** + * Wrapper around kFsCacheLookupW that drops KFSOBJ_TYPE_MISSING and returns + * KFSLOOKUPERROR_NOT_FOUND instead. + * + * @returns Reference to object corresponding to @a pszPath on success, this + * must be released by kFsCacheObjRelease. + * NULL if not a path we care to cache. + * @param pCache The cache. + * @param pwszPath The path to lookup. + * @param penmError Where to return details as to why the lookup + * failed. + */ +PKFSOBJ kFsCacheLookupNoMissingW(PKFSCACHE pCache, const wchar_t *pwszPath, KFSLOOKUPERROR *penmError) +{ + PKFSOBJ pObj; + KFSCACHE_LOCK(pCache); /* probably not necessary */ + pObj = kFsCacheLookupW(pCache, pwszPath, penmError); + if (pObj) + { + if (pObj->bObjType != KFSOBJ_TYPE_MISSING) + { + KFSCACHE_UNLOCK(pCache); + return pObj; + } + + kFsCacheObjRelease(pCache, pObj); + *penmError = KFSLOOKUPERROR_NOT_FOUND; + } + KFSCACHE_UNLOCK(pCache); + return NULL; +} + + +/** + * Destroys a cache object which has a zero reference count. + * + * @returns 0 + * @param pCache The cache. + * @param pObj The object. + * @param pszWhere Where it was released from. + */ +KU32 kFsCacheObjDestroy(PKFSCACHE pCache, PKFSOBJ pObj, const char *pszWhere) +{ + kHlpAssert(pObj->cRefs == 0); + kHlpAssert(pObj->pParent == NULL); + kHlpAssert(pObj->u32Magic == KFSOBJ_MAGIC); + KFSCACHE_LOCK(pCache); + + KFSCACHE_LOG(("Destroying %s/%s, type=%d, pObj=%p, pszWhere=%s\n", + pObj->pParent ? pObj->pParent->Obj.pszName : "", pObj->pszName, pObj->bObjType, pObj, pszWhere)); + if (pObj->cPathHashRefs != 0) + { + fprintf(stderr, "Destroying %s/%s, type=%d, path hash entries: %d!\n", pObj->pParent ? pObj->pParent->Obj.pszName : "", + pObj->pszName, pObj->bObjType, pObj->cPathHashRefs); + fflush(stderr); + __debugbreak(); + } + + /* + * Invalidate the structure. + */ + pObj->u32Magic = ~KFSOBJ_MAGIC; + + /* + * Destroy any user data first. + */ + while (pObj->pUserDataHead != NULL) + { + PKFSUSERDATA pUserData = pObj->pUserDataHead; + pObj->pUserDataHead = pUserData->pNext; + if (pUserData->pfnDestructor) + pUserData->pfnDestructor(pCache, pObj, pUserData); + kHlpFree(pUserData); + } + + /* + * Do type specific destruction + */ + switch (pObj->bObjType) + { + case KFSOBJ_TYPE_MISSING: + /* nothing else to do here */ + pCache->cbObjects -= sizeof(KFSDIR); + break; + + case KFSOBJ_TYPE_DIR: + { + PKFSDIR pDir = (PKFSDIR)pObj; + KU32 cChildren = pDir->cChildren; + pCache->cbObjects -= sizeof(*pDir) + + K_ALIGN_Z(cChildren, 16) * sizeof(pDir->papChildren) + + (pDir->fHashTabMask + !!pDir->fHashTabMask) * sizeof(pDir->papHashTab[0]); + + pDir->cChildren = 0; + while (cChildren-- > 0) + kFsCacheObjRelease(pCache, pDir->papChildren[cChildren]); + kHlpFree(pDir->papChildren); + pDir->papChildren = NULL; + + kHlpFree(pDir->papHashTab); + pDir->papHashTab = NULL; + break; + } + + case KFSOBJ_TYPE_FILE: + case KFSOBJ_TYPE_OTHER: + pCache->cbObjects -= sizeof(*pObj); + break; + + default: + KFSCACHE_UNLOCK(pCache); + return 0; + } + + /* + * Common bits. + */ + pCache->cbObjects -= pObj->cchName + 1; +#ifdef KFSCACHE_CFG_UTF16 + pCache->cbObjects -= (pObj->cwcName + 1) * sizeof(wchar_t); +#endif +#ifdef KFSCACHE_CFG_SHORT_NAMES + if (pObj->pszName != pObj->pszShortName) + { + pCache->cbObjects -= pObj->cchShortName + 1; +# ifdef KFSCACHE_CFG_UTF16 + pCache->cbObjects -= (pObj->cwcShortName + 1) * sizeof(wchar_t); +# endif + } +#endif + pCache->cObjects--; + + if (pObj->pNameAlloc) + { + pCache->cbObjects -= pObj->pNameAlloc->cb; + kHlpFree(pObj->pNameAlloc); + } + + KFSCACHE_UNLOCK(pCache); + + kHlpFree(pObj); + return 0; +} + + +/** + * Releases a reference to a cache object. + * + * @returns New reference count. + * @param pCache The cache. + * @param pObj The object. + */ +#undef kFsCacheObjRelease +KU32 kFsCacheObjRelease(PKFSCACHE pCache, PKFSOBJ pObj) +{ + if (pObj) + { + KU32 cRefs; + kHlpAssert(pCache->u32Magic == KFSCACHE_MAGIC); + kHlpAssert(pObj->u32Magic == KFSOBJ_MAGIC); + + cRefs = _InterlockedDecrement(&pObj->cRefs); + if (cRefs) + return cRefs; + return kFsCacheObjDestroy(pCache, pObj, "kFsCacheObjRelease"); + } + return 0; +} + + +/** + * Debug version of kFsCacheObjRelease + * + * @returns New reference count. + * @param pCache The cache. + * @param pObj The object. + * @param pszWhere Where it's invoked from. + */ +KU32 kFsCacheObjReleaseTagged(PKFSCACHE pCache, PKFSOBJ pObj, const char *pszWhere) +{ + if (pObj) + { + KU32 cRefs; + kHlpAssert(pCache->u32Magic == KFSCACHE_MAGIC); + kHlpAssert(pObj->u32Magic == KFSOBJ_MAGIC); + + cRefs = _InterlockedDecrement(&pObj->cRefs); + if (cRefs) + return cRefs; + return kFsCacheObjDestroy(pCache, pObj, pszWhere); + } + return 0; +} + + +/** + * Retains a reference to a cahce object. + * + * @returns New reference count. + * @param pObj The object. + */ +KU32 kFsCacheObjRetain(PKFSOBJ pObj) +{ + KU32 cRefs; + kHlpAssert(pCache->u32Magic == KFSCACHE_MAGIC); + kHlpAssert(pObj->u32Magic == KFSOBJ_MAGIC); + + cRefs = _InterlockedIncrement(&pObj->cRefs); + kHlpAssert(cRefs < 16384); + return cRefs; +} + + +/** + * Associates an item of user data with the given object. + * + * If the data needs cleaning up before being free, set the + * PKFSUSERDATA::pfnDestructor member of the returned structure. + * + * @returns Pointer to the user data on success. + * NULL if out of memory or key already in use. + * + * @param pCache The cache. + * @param pObj The object. + * @param uKey The user data key. + * @param cbUserData The size of the user data. + */ +PKFSUSERDATA kFsCacheObjAddUserData(PKFSCACHE pCache, PKFSOBJ pObj, KUPTR uKey, KSIZE cbUserData) +{ + kHlpAssert(cbUserData >= sizeof(*pNew)); + KFSCACHE_OBJUSERDATA_LOCK(pCache, pObj); + + if (kFsCacheObjGetUserData(pCache, pObj, uKey) == NULL) + { + PKFSUSERDATA pNew = (PKFSUSERDATA)kHlpAllocZ(cbUserData); + if (pNew) + { + pNew->uKey = uKey; + pNew->pfnDestructor = NULL; + pNew->pNext = pObj->pUserDataHead; + pObj->pUserDataHead = pNew; + KFSCACHE_OBJUSERDATA_UNLOCK(pCache, pObj); + return pNew; + } + } + + KFSCACHE_OBJUSERDATA_UNLOCK(pCache, pObj); + return NULL; +} + + +/** + * Retrieves an item of user data associated with the given object. + * + * @returns Pointer to the associated user data if found, otherwise NULL. + * @param pCache The cache. + * @param pObj The object. + * @param uKey The user data key. + */ +PKFSUSERDATA kFsCacheObjGetUserData(PKFSCACHE pCache, PKFSOBJ pObj, KUPTR uKey) +{ + PKFSUSERDATA pCur; + + kHlpAssert(pCache->u32Magic == KFSCACHE_MAGIC); + kHlpAssert(pObj->u32Magic == KFSOBJ_MAGIC); + KFSCACHE_OBJUSERDATA_LOCK(pCache, pObj); + + for (pCur = pObj->pUserDataHead; pCur; pCur = pCur->pNext) + if (pCur->uKey == uKey) + { + KFSCACHE_OBJUSERDATA_UNLOCK(pCache, pObj); + return pCur; + } + + KFSCACHE_OBJUSERDATA_UNLOCK(pCache, pObj); + return NULL; +} + + +/** + * Determins the idxUserDataLock value. + * + * Called by KFSCACHE_OBJUSERDATA_LOCK when idxUserDataLock is set to KU8_MAX. + * + * @returns The proper idxUserDataLock value. + * @param pCache The cache. + * @param pObj The object. + */ +KU8 kFsCacheObjGetUserDataLockIndex(PKFSCACHE pCache, PKFSOBJ pObj) +{ + KU8 idxUserDataLock = pObj->idxUserDataLock; + if (idxUserDataLock == KU8_MAX) + { + KFSCACHE_LOCK(pCache); + idxUserDataLock = pObj->idxUserDataLock; + if (idxUserDataLock == KU8_MAX) + { + idxUserDataLock = pCache->idxUserDataNext++; + idxUserDataLock %= K_ELEMENTS(pCache->auUserDataLocks); + pObj->idxUserDataLock = idxUserDataLock; + } + KFSCACHE_UNLOCK(pCache); + } + return idxUserDataLock; +} + +/** + * Gets the full path to @a pObj, ANSI version. + * + * @returns K_TRUE on success, K_FALSE on buffer overflow (nothing stored). + * @param pObj The object to get the full path to. + * @param pszPath Where to return the path + * @param cbPath The size of the output buffer. + * @param chSlash The slash to use. + */ +KBOOL kFsCacheObjGetFullPathA(PKFSOBJ pObj, char *pszPath, KSIZE cbPath, char chSlash) +{ + /** @todo No way of to do locking here w/o pCache parameter; need to verify + * that we're only access static data! */ + KSIZE off = pObj->cchParent; + kHlpAssert(pObj->u32Magic == KFSOBJ_MAGIC); + if (off > 0) + { + KSIZE offEnd = off + pObj->cchName; + if (offEnd < cbPath) + { + PKFSDIR pAncestor; + + pszPath[off + pObj->cchName] = '\0'; + memcpy(&pszPath[off], pObj->pszName, pObj->cchName); + + for (pAncestor = pObj->pParent; off > 0; pAncestor = pAncestor->Obj.pParent) + { + kHlpAssert(off > 1); + kHlpAssert(pAncestor != NULL); + kHlpAssert(pAncestor->Obj.cchName > 0); + pszPath[--off] = chSlash; + off -= pAncestor->Obj.cchName; + kHlpAssert(pAncestor->Obj.cchParent == off); + memcpy(&pszPath[off], pAncestor->Obj.pszName, pAncestor->Obj.cchName); + } + return K_TRUE; + } + } + else + { + KBOOL const fDriveLetter = pObj->cchName == 2 && pObj->pszName[2] == ':'; + off = pObj->cchName; + if (off + fDriveLetter < cbPath) + { + memcpy(pszPath, pObj->pszName, off); + if (fDriveLetter) + pszPath[off++] = chSlash; + pszPath[off] = '\0'; + return K_TRUE; + } + } + + return K_FALSE; +} + + +/** + * Gets the full path to @a pObj, UTF-16 version. + * + * @returns K_TRUE on success, K_FALSE on buffer overflow (nothing stored). + * @param pObj The object to get the full path to. + * @param pszPath Where to return the path + * @param cbPath The size of the output buffer. + * @param wcSlash The slash to use. + */ +KBOOL kFsCacheObjGetFullPathW(PKFSOBJ pObj, wchar_t *pwszPath, KSIZE cwcPath, wchar_t wcSlash) +{ + /** @todo No way of to do locking here w/o pCache parameter; need to verify + * that we're only access static data! */ + KSIZE off = pObj->cwcParent; + kHlpAssert(pObj->u32Magic == KFSOBJ_MAGIC); + if (off > 0) + { + KSIZE offEnd = off + pObj->cwcName; + if (offEnd < cwcPath) + { + PKFSDIR pAncestor; + + pwszPath[off + pObj->cwcName] = '\0'; + memcpy(&pwszPath[off], pObj->pwszName, pObj->cwcName * sizeof(wchar_t)); + + for (pAncestor = pObj->pParent; off > 0; pAncestor = pAncestor->Obj.pParent) + { + kHlpAssert(off > 1); + kHlpAssert(pAncestor != NULL); + kHlpAssert(pAncestor->Obj.cwcName > 0); + pwszPath[--off] = wcSlash; + off -= pAncestor->Obj.cwcName; + kHlpAssert(pAncestor->Obj.cwcParent == off); + memcpy(&pwszPath[off], pAncestor->Obj.pwszName, pAncestor->Obj.cwcName * sizeof(wchar_t)); + } + return K_TRUE; + } + } + else + { + KBOOL const fDriveLetter = pObj->cchName == 2 && pObj->pszName[2] == ':'; + off = pObj->cwcName; + if (off + fDriveLetter < cwcPath) + { + memcpy(pwszPath, pObj->pwszName, off * sizeof(wchar_t)); + if (fDriveLetter) + pwszPath[off++] = wcSlash; + pwszPath[off] = '\0'; + return K_TRUE; + } + } + + return K_FALSE; +} + + +#ifdef KFSCACHE_CFG_SHORT_NAMES + +/** + * Gets the full short path to @a pObj, ANSI version. + * + * @returns K_TRUE on success, K_FALSE on buffer overflow (nothing stored). + * @param pObj The object to get the full path to. + * @param pszPath Where to return the path + * @param cbPath The size of the output buffer. + * @param chSlash The slash to use. + */ +KBOOL kFsCacheObjGetFullShortPathA(PKFSOBJ pObj, char *pszPath, KSIZE cbPath, char chSlash) +{ + /** @todo No way of to do locking here w/o pCache parameter; need to verify + * that we're only access static data! */ + KSIZE off = pObj->cchShortParent; + kHlpAssert(pObj->u32Magic == KFSOBJ_MAGIC); + if (off > 0) + { + KSIZE offEnd = off + pObj->cchShortName; + if (offEnd < cbPath) + { + PKFSDIR pAncestor; + + pszPath[off + pObj->cchShortName] = '\0'; + memcpy(&pszPath[off], pObj->pszShortName, pObj->cchShortName); + + for (pAncestor = pObj->pParent; off > 0; pAncestor = pAncestor->Obj.pParent) + { + kHlpAssert(off > 1); + kHlpAssert(pAncestor != NULL); + kHlpAssert(pAncestor->Obj.cchShortName > 0); + pszPath[--off] = chSlash; + off -= pAncestor->Obj.cchShortName; + kHlpAssert(pAncestor->Obj.cchShortParent == off); + memcpy(&pszPath[off], pAncestor->Obj.pszShortName, pAncestor->Obj.cchShortName); + } + return K_TRUE; + } + } + else + { + KBOOL const fDriveLetter = pObj->cchShortName == 2 && pObj->pszShortName[2] == ':'; + off = pObj->cchShortName; + if (off + fDriveLetter < cbPath) + { + memcpy(pszPath, pObj->pszShortName, off); + if (fDriveLetter) + pszPath[off++] = chSlash; + pszPath[off] = '\0'; + return K_TRUE; + } + } + + return K_FALSE; +} + + +/** + * Gets the full short path to @a pObj, UTF-16 version. + * + * @returns K_TRUE on success, K_FALSE on buffer overflow (nothing stored). + * @param pObj The object to get the full path to. + * @param pszPath Where to return the path + * @param cbPath The size of the output buffer. + * @param wcSlash The slash to use. + */ +KBOOL kFsCacheObjGetFullShortPathW(PKFSOBJ pObj, wchar_t *pwszPath, KSIZE cwcPath, wchar_t wcSlash) +{ + /** @todo No way of to do locking here w/o pCache parameter; need to verify + * that we're only access static data! */ + KSIZE off = pObj->cwcShortParent; + kHlpAssert(pObj->u32Magic == KFSOBJ_MAGIC); + if (off > 0) + { + KSIZE offEnd = off + pObj->cwcShortName; + if (offEnd < cwcPath) + { + PKFSDIR pAncestor; + + pwszPath[off + pObj->cwcShortName] = '\0'; + memcpy(&pwszPath[off], pObj->pwszShortName, pObj->cwcShortName * sizeof(wchar_t)); + + for (pAncestor = pObj->pParent; off > 0; pAncestor = pAncestor->Obj.pParent) + { + kHlpAssert(off > 1); + kHlpAssert(pAncestor != NULL); + kHlpAssert(pAncestor->Obj.cwcShortName > 0); + pwszPath[--off] = wcSlash; + off -= pAncestor->Obj.cwcShortName; + kHlpAssert(pAncestor->Obj.cwcShortParent == off); + memcpy(&pwszPath[off], pAncestor->Obj.pwszShortName, pAncestor->Obj.cwcShortName * sizeof(wchar_t)); + } + return K_TRUE; + } + } + else + { + KBOOL const fDriveLetter = pObj->cchShortName == 2 && pObj->pszShortName[2] == ':'; + off = pObj->cwcShortName; + if (off + fDriveLetter < cwcPath) + { + memcpy(pwszPath, pObj->pwszShortName, off * sizeof(wchar_t)); + if (fDriveLetter) + pwszPath[off++] = wcSlash; + pwszPath[off] = '\0'; + return K_TRUE; + } + } + + return K_FALSE; +} + +#endif /* KFSCACHE_CFG_SHORT_NAMES */ + + + +/** + * Read the specified bits from the files into the given buffer, simple version. + * + * @returns K_TRUE on success (all requested bytes read), + * K_FALSE on any kind of failure. + * + * @param pCache The cache. + * @param pFileObj The file object. + * @param offStart Where to start reading. + * @param pvBuf Where to store what we read. + * @param cbToRead How much to read (exact). + */ +KBOOL kFsCacheFileSimpleOpenReadClose(PKFSCACHE pCache, PKFSOBJ pFileObj, KU64 offStart, void *pvBuf, KSIZE cbToRead) +{ + /* + * Open the file relative to the parent directory. + */ + MY_NTSTATUS rcNt; + HANDLE hFile; + MY_IO_STATUS_BLOCK Ios; + MY_OBJECT_ATTRIBUTES ObjAttr; + MY_UNICODE_STRING UniStr; + + kHlpAssertReturn(pFileObj->bObjType == KFSOBJ_TYPE_FILE, K_FALSE); + kHlpAssert(pFileObj->pParent); + kHlpAssertReturn(pFileObj->pParent->hDir != INVALID_HANDLE_VALUE, K_FALSE); + kHlpAssertReturn(offStart == 0, K_FALSE); /** @todo when needed */ + + Ios.Information = -1; + Ios.u.Status = -1; + + UniStr.Buffer = (wchar_t *)pFileObj->pwszName; + UniStr.Length = (USHORT)(pFileObj->cwcName * sizeof(wchar_t)); + UniStr.MaximumLength = UniStr.Length + sizeof(wchar_t); + +/** @todo potential race against kFsCacheInvalidateDeletedDirectoryA */ + MyInitializeObjectAttributes(&ObjAttr, &UniStr, OBJ_CASE_INSENSITIVE, pFileObj->pParent->hDir, NULL /*pSecAttr*/); + + rcNt = g_pfnNtCreateFile(&hFile, + GENERIC_READ | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL, /*cbFileInitialAlloc */ + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, + NULL, /*pEaBuffer*/ + 0); /*cbEaBuffer*/ + if (MY_NT_SUCCESS(rcNt)) + { + LARGE_INTEGER offFile; + offFile.QuadPart = offStart; + + Ios.Information = -1; + Ios.u.Status = -1; + rcNt = g_pfnNtReadFile(hFile, NULL /*hEvent*/, NULL /*pfnApcComplete*/, NULL /*pvApcCtx*/, &Ios, + pvBuf, (KU32)cbToRead, !offStart ? &offFile : NULL, NULL /*puKey*/); + if (MY_NT_SUCCESS(rcNt)) + rcNt = Ios.u.Status; + if (MY_NT_SUCCESS(rcNt)) + { + if (Ios.Information == cbToRead) + { + g_pfnNtClose(hFile); + return K_TRUE; + } + KFSCACHE_LOG(("Error reading %#x bytes from '%ls': Information=%p\n", pFileObj->pwszName, Ios.Information)); + } + else + KFSCACHE_LOG(("Error reading %#x bytes from '%ls': %#x\n", pFileObj->pwszName, rcNt)); + g_pfnNtClose(hFile); + } + else + KFSCACHE_LOG(("Error opening '%ls' for caching: %#x\n", pFileObj->pwszName, rcNt)); + return K_FALSE; +} + + +/** + * Invalidate all cache entries of missing files. + * + * @param pCache The cache. + */ +void kFsCacheInvalidateMissing(PKFSCACHE pCache) +{ + kHlpAssert(pCache->u32Magic == KFSOBJ_MAGIC); + KFSCACHE_LOCK(pCache); + + pCache->auGenerationsMissing[0]++; + kHlpAssert(pCache->uGenerationMissing < KU32_MAX); + + KFSCACHE_LOG(("Invalidate missing %#x\n", pCache->auGenerationsMissing[0])); + KFSCACHE_UNLOCK(pCache); +} + + +/** + * Recursively close directories. + */ +static void kFsCacheCloseDirs(PKFSOBJ *papChildren, KU32 cChildren) +{ + while (cChildren-- > 0) + { + PKFSDIR pDir = (PKFSDIR)papChildren[cChildren]; + if (pDir && pDir->Obj.bObjType == KFSOBJ_TYPE_DIR) + { + if (pDir->hDir != INVALID_HANDLE_VALUE) + { + g_pfnNtClose(pDir->hDir); + pDir->hDir = INVALID_HANDLE_VALUE; + } + kFsCacheCloseDirs(pDir->papChildren, pDir->cChildren); + } + } +} + + +/** + * Worker for kFsCacheInvalidateAll and kFsCacheInvalidateAllAndCloseDirs + */ +static void kFsCacheInvalidateAllWorker(PKFSCACHE pCache, KBOOL fCloseDirs, KBOOL fIncludingRoot) +{ + kHlpAssert(pCache->u32Magic == KFSOBJ_MAGIC); + KFSCACHE_LOCK(pCache); + + pCache->auGenerationsMissing[0]++; + kHlpAssert(pCache->auGenerationsMissing[0] < KU32_MAX); + pCache->auGenerationsMissing[1]++; + kHlpAssert(pCache->auGenerationsMissing[1] < KU32_MAX); + + pCache->auGenerations[0]++; + kHlpAssert(pCache->auGenerations[0] < KU32_MAX); + pCache->auGenerations[1]++; + kHlpAssert(pCache->auGenerations[1] < KU32_MAX); + + if (fCloseDirs) + { + kFsCacheCloseDirs(pCache->RootDir.papChildren, pCache->RootDir.cChildren); + if (fCloseDirs && pCache->RootDir.hDir != INVALID_HANDLE_VALUE) + { + g_pfnNtClose(pCache->RootDir.hDir); + pCache->RootDir.hDir = INVALID_HANDLE_VALUE; + } + } + + KFSCACHE_LOG(("Invalidate all - default: %#x/%#x, custom: %#x/%#x\n", + pCache->auGenerationsMissing[0], pCache->auGenerations[0], + pCache->auGenerationsMissing[1], pCache->auGenerations[1])); + KFSCACHE_UNLOCK(pCache); +} + + +/** + * Invalidate all cache entries (regular, custom & missing). + * + * @param pCache The cache. + */ +void kFsCacheInvalidateAll(PKFSCACHE pCache) +{ + kHlpAssert(pCache->u32Magic == KFSOBJ_MAGIC); + kFsCacheInvalidateAllWorker(pCache, K_FALSE, K_FALSE); +} + + +/** + * Invalidate all cache entries (regular, custom & missing) and close all the + * directory handles. + * + * @param pCache The cache. + * @param fIncludingRoot Close the root directory handle too. + */ +void kFsCacheInvalidateAllAndCloseDirs(PKFSCACHE pCache, KBOOL fIncludingRoot) +{ + kHlpAssert(pCache->u32Magic == KFSOBJ_MAGIC); + kFsCacheInvalidateAllWorker(pCache, K_TRUE, fIncludingRoot); +} + + +/** + * Invalidate all cache entries with custom generation handling set. + * + * @see kFsCacheSetupCustomRevisionForTree, KFSOBJ_F_USE_CUSTOM_GEN + * @param pCache The cache. + */ +void kFsCacheInvalidateCustomMissing(PKFSCACHE pCache) +{ + kHlpAssert(pCache->u32Magic == KFSOBJ_MAGIC); + KFSCACHE_LOCK(pCache); + + pCache->auGenerationsMissing[1]++; + kHlpAssert(pCache->auGenerationsMissing[1] < KU32_MAX); + + KFSCACHE_LOG(("Invalidate missing custom %#x\n", pCache->auGenerationsMissing[1])); + KFSCACHE_UNLOCK(pCache); +} + + +/** + * Invalidate all cache entries with custom generation handling set, both + * missing and regular present entries. + * + * @see kFsCacheSetupCustomRevisionForTree, KFSOBJ_F_USE_CUSTOM_GEN + * @param pCache The cache. + */ +void kFsCacheInvalidateCustomBoth(PKFSCACHE pCache) +{ + kHlpAssert(pCache->u32Magic == KFSOBJ_MAGIC); + KFSCACHE_LOCK(pCache); + + pCache->auGenerations[1]++; + kHlpAssert(pCache->auGenerations[1] < KU32_MAX); + pCache->auGenerationsMissing[1]++; + kHlpAssert(pCache->auGenerationsMissing[1] < KU32_MAX); + + KFSCACHE_LOG(("Invalidate both custom %#x/%#x\n", pCache->auGenerationsMissing[1], pCache->auGenerations[1])); + KFSCACHE_UNLOCK(pCache); +} + + + +/** + * Applies the given flags to all the objects in a tree. + * + * @param pRoot Where to start applying the flag changes. + * @param fAndMask The AND mask. + * @param fOrMask The OR mask. + */ +static void kFsCacheApplyFlagsToTree(PKFSDIR pRoot, KU32 fAndMask, KU32 fOrMask) +{ + PKFSOBJ *ppCur = ((PKFSDIR)pRoot)->papChildren; + KU32 cLeft = ((PKFSDIR)pRoot)->cChildren; + while (cLeft-- > 0) + { + PKFSOBJ pCur = *ppCur++; + if (pCur->bObjType != KFSOBJ_TYPE_DIR) + pCur->fFlags = (fAndMask & pCur->fFlags) | fOrMask; + else + kFsCacheApplyFlagsToTree((PKFSDIR)pCur, fAndMask, fOrMask); + } + + pRoot->Obj.fFlags = (fAndMask & pRoot->Obj.fFlags) | fOrMask; +} + + +/** + * Sets up using custom revisioning for the specified directory tree or file. + * + * There are some restrictions of the current implementation: + * - If the root of the sub-tree is ever deleted from the cache (i.e. + * deleted in real life and reflected in the cache), the setting is lost. + * - It is not automatically applied to the lookup paths caches. + * + * @returns K_TRUE on success, K_FALSE on failure. + * @param pCache The cache. + * @param pRoot The root of the subtree. A non-directory is + * fine, like a missing node. + */ +KBOOL kFsCacheSetupCustomRevisionForTree(PKFSCACHE pCache, PKFSOBJ pRoot) +{ + if (pRoot) + { + KFSCACHE_LOCK(pCache); + if (pRoot->bObjType == KFSOBJ_TYPE_DIR) + kFsCacheApplyFlagsToTree((PKFSDIR)pRoot, KU32_MAX, KFSOBJ_F_USE_CUSTOM_GEN); + else + pRoot->fFlags |= KFSOBJ_F_USE_CUSTOM_GEN; + KFSCACHE_UNLOCK(pCache); + return K_TRUE; + } + return K_FALSE; +} + + +/** + * Invalidates a deleted directory, ANSI version. + * + * @returns K_TRUE if found and is a non-root directory. Otherwise K_FALSE. + * @param pCache The cache. + * @param pszDir The directory. + */ +KBOOL kFsCacheInvalidateDeletedDirectoryA(PKFSCACHE pCache, const char *pszDir) +{ + KU32 cchDir = (KU32)kHlpStrLen(pszDir); + KFSLOOKUPERROR enmError; + PKFSOBJ pFsObj; + + KFSCACHE_LOCK(pCache); + + /* Is absolute without any '..' bits? */ + if ( cchDir >= 3 + && ( ( pszDir[1] == ':' /* Drive letter */ + && IS_SLASH(pszDir[2]) + && IS_ALPHA(pszDir[0]) ) + || ( IS_SLASH(pszDir[0]) /* UNC */ + && IS_SLASH(pszDir[1]) ) ) + && !kFsCacheHasDotDotA(pszDir, cchDir) ) + pFsObj = kFsCacheLookupAbsoluteA(pCache, pszDir, cchDir, KFSCACHE_LOOKUP_F_NO_INSERT | KFSCACHE_LOOKUP_F_NO_REFRESH, + &enmError, NULL); + else + pFsObj = kFsCacheLookupSlowA(pCache, pszDir, cchDir, KFSCACHE_LOOKUP_F_NO_INSERT | KFSCACHE_LOOKUP_F_NO_REFRESH, + &enmError, NULL); + if (pFsObj) + { + /* Is directory? */ + if (pFsObj->bObjType == KFSOBJ_TYPE_DIR) + { + if (pFsObj->pParent != &pCache->RootDir) + { + PKFSDIR pDir = (PKFSDIR)pFsObj; + KFSCACHE_LOG(("kFsCacheInvalidateDeletedDirectoryA: %s hDir=%p\n", pszDir, pDir->hDir)); + if (pDir->hDir != INVALID_HANDLE_VALUE) + { + g_pfnNtClose(pDir->hDir); + pDir->hDir = INVALID_HANDLE_VALUE; + } + pDir->fNeedRePopulating = K_TRUE; + pDir->Obj.uCacheGen = pCache->auGenerations[pDir->Obj.fFlags & KFSOBJ_F_USE_CUSTOM_GEN] - 1; + kFsCacheObjRelease(pCache, &pDir->Obj); + KFSCACHE_UNLOCK(pCache); + return K_TRUE; + } + KFSCACHE_LOG(("kFsCacheInvalidateDeletedDirectoryA: Trying to invalidate a root directory was deleted! %s\n", pszDir)); + } + else + KFSCACHE_LOG(("kFsCacheInvalidateDeletedDirectoryA: Trying to invalidate a non-directory: bObjType=%d %s\n", + pFsObj->bObjType, pszDir)); + kFsCacheObjRelease(pCache, pFsObj); + } + else + KFSCACHE_LOG(("kFsCacheInvalidateDeletedDirectoryA: '%s' was not found\n", pszDir)); + KFSCACHE_UNLOCK(pCache); + return K_FALSE; +} + + +PKFSCACHE kFsCacheCreate(KU32 fFlags) +{ + PKFSCACHE pCache; + birdResolveImports(); + + pCache = (PKFSCACHE)kHlpAllocZ(sizeof(*pCache)); + if (pCache) + { + /* Dummy root dir entry. */ + pCache->RootDir.Obj.u32Magic = KFSOBJ_MAGIC; + pCache->RootDir.Obj.cRefs = 1; + pCache->RootDir.Obj.uCacheGen = KFSOBJ_CACHE_GEN_IGNORE; + pCache->RootDir.Obj.bObjType = KFSOBJ_TYPE_DIR; + pCache->RootDir.Obj.fHaveStats = K_FALSE; + pCache->RootDir.Obj.pParent = NULL; + pCache->RootDir.Obj.pszName = ""; + pCache->RootDir.Obj.cchName = 0; + pCache->RootDir.Obj.cchParent = 0; +#ifdef KFSCACHE_CFG_UTF16 + pCache->RootDir.Obj.cwcName = 0; + pCache->RootDir.Obj.cwcParent = 0; + pCache->RootDir.Obj.pwszName = L""; +#endif + +#ifdef KFSCACHE_CFG_SHORT_NAMES + pCache->RootDir.Obj.pszShortName = NULL; + pCache->RootDir.Obj.cchShortName = 0; + pCache->RootDir.Obj.cchShortParent = 0; +# ifdef KFSCACHE_CFG_UTF16 + pCache->RootDir.Obj.cwcShortName; + pCache->RootDir.Obj.cwcShortParent; + pCache->RootDir.Obj.pwszShortName; +# endif +#endif + pCache->RootDir.cChildren = 0; + pCache->RootDir.cChildrenAllocated = 0; + pCache->RootDir.papChildren = NULL; + pCache->RootDir.hDir = INVALID_HANDLE_VALUE; + pCache->RootDir.fHashTabMask = 255; /* 256: 26 drive letters and 102 UNCs before we're half ways. */ + pCache->RootDir.papHashTab = (PKFSOBJ *)kHlpAllocZ(256 * sizeof(pCache->RootDir.papHashTab[0])); + if (pCache->RootDir.papHashTab) + { + /* The cache itself. */ + pCache->u32Magic = KFSCACHE_MAGIC; + pCache->fFlags = fFlags; + pCache->auGenerations[0] = KU32_MAX / 4; + pCache->auGenerations[1] = KU32_MAX / 32; + pCache->auGenerationsMissing[0] = KU32_MAX / 256; + pCache->auGenerationsMissing[1] = 1; + pCache->cObjects = 1; + pCache->cbObjects = sizeof(pCache->RootDir) + + (pCache->RootDir.fHashTabMask + 1) * sizeof(pCache->RootDir.papHashTab[0]); + pCache->cPathHashHits = 0; + pCache->cWalkHits = 0; + pCache->cChildSearches = 0; + pCache->cChildHashHits = 0; + pCache->cChildHashed = 0; + pCache->cChildHashTabs = 1; + pCache->cChildHashEntriesTotal = pCache->RootDir.fHashTabMask + 1; + pCache->cChildHashCollisions = 0; + pCache->cNameChanges = 0; + pCache->cNameGrowths = 0; + pCache->cAnsiPaths = 0; + pCache->cAnsiPathCollisions = 0; + pCache->cbAnsiPaths = 0; +#ifdef KFSCACHE_CFG_UTF16 + pCache->cUtf16Paths = 0; + pCache->cUtf16PathCollisions = 0; + pCache->cbUtf16Paths = 0; +#endif + +#ifdef KFSCACHE_CFG_LOCKING + { + KSIZE idx = K_ELEMENTS(pCache->auUserDataLocks); + while (idx-- > 0) + InitializeCriticalSection(&pCache->auUserDataLocks[idx].CritSect); + InitializeCriticalSection(&pCache->u.CritSect); + } +#endif + return pCache; + } + + kHlpFree(pCache); + } + return NULL; +} + -- cgit v1.2.3