/* Directory caching code for Netdrive plugins. Copyright (C) netlabs.org 2010-2016 */ #include #include #include #include #include #include "dircache.h" PLUGINHELPERTABLE2L *ph; /* * An entry holds file name and a pointer to custom data */ typedef struct DirectoryCacheEntryData { const char fname[PATH_MAX]; const void* customData; } DirectoryCacheEntryData; /* * An entry in the directory cache contains one directory listing. */ typedef struct DirectoryCacheEntry { struct DirectoryCacheEntry *pNext; struct DirectoryCacheEntry *pPrev; DirectoryCacheEntryData *aInfos; int cInfos; int cInfosAllocated; char *pszPath; ULONG ulHash; ULONG ulLastUpdateTime; int fInvalid; } DirectoryCacheEntry; typedef struct DirectoryCache { NDMUTEX mutex; DirectoryCacheEntry *pEntriesHead; DirectoryCacheEntry *pEntriesTail; int cEntries; int fEnabled; unsigned long ulExpirationTime; int cMaxEntries; // resource handle, used only for per-share logging void* resource; // callback called to release data structures PFNFREEDIRENTRY release; } DirectoryCache; enum { CacheFault = 0, CacheOk = 1 }; /* * Static helpers. */ static int lockCreate (NDMUTEX *pMutex) { return ph->fsphCreateMutex (pMutex); } static void lockDelete (NDMUTEX mutex) { ph->fsphCloseMutex (mutex); } static int lockRequest (NDMUTEX mutex) { return ph->fsphRequestMutex (mutex, 10000); } void lockRelease (NDMUTEX mutex) { ph->fsphReleaseMutex (mutex); } static ULONG Hash (const char *pData, ULONG ulLen) { ULONG hash = 0ul, g; static ULONG ulHashModule = 30031ul; const char *p; int i; for( p = pData, i = 0; i < ulLen; i++, p++ ) { hash = ( hash << 4 ) + (*p); g = hash & 0xf0000000ul; if ( g ) { hash ^= ( g >> 24 ); hash ^= g; } } return ( hash % ulHashModule ); } static unsigned long timesec (void) { ULONG ul = 0; DosQuerySysInfo (QSV_TIME_LOW, QSV_TIME_LOW, &ul, sizeof (ul)); return ul; } static unsigned long computehash (const char *s) { return s? Hash ((char *)s, strlen (s)): 0; } static int dceCreate (DirectoryCacheEntry **ppdce, DirectoryCache* pdc, const char *path, int cFiles) { DirectoryCacheEntry *pdce = (DirectoryCacheEntry *)malloc(sizeof(DirectoryCacheEntry)); if (!pdce) { return ERROR_NOT_ENOUGH_MEMORY; } pdce->aInfos = (DirectoryCacheEntryData *) malloc(cFiles * sizeof (DirectoryCacheEntryData)); if (!pdce->aInfos) { free (pdce); return ERROR_NOT_ENOUGH_MEMORY; } pdce->cInfos = 0; pdce->cInfosAllocated = cFiles; pdce->ulHash = computehash (path); pdce->ulLastUpdateTime = 0; pdce->fInvalid = 0; int l = strlen (path); pdce->pszPath = (char *)malloc (l + 1); memcpy (pdce->pszPath, path, l + 1); pdce->pNext = NULL; pdce->pPrev = NULL; *ppdce = pdce; return NO_ERROR; } static void dceDelete (DirectoryCache *pdc, DirectoryCacheEntry *pdce) { int i; debuglocal(pdc->resource, 9, "dceDelete: pdc %p, pdce %p\n", pdc, pdce); /* use plugin callback to deallocate memory stored into aInfos */ if (pdc->release) for (i = 0; i < pdce->cInfos; i++) { debuglocal(pdc->resource, 9, "dceDelete: #%d->%s->%p\n", i, pdce->aInfos[i].fname, pdce->aInfos[i].customData); pdc->release( (void*)pdce->aInfos[i].customData); } /* now free dce local data */ free(pdce->aInfos); free(pdce->pszPath); free(pdce); } static void dceClear (DirectoryCacheEntry *pdce) { pdce->cInfos = 0; } static void dceWriteEntry (DirectoryCache *pdc, DirectoryCacheEntry *pdce, const char *fname, const void *finfo) { if (pdce->cInfos >= pdce->cInfosAllocated) { /* 50% increase of the buffer, but at least 1 more. */ int cNewAllocated = pdce->cInfosAllocated + pdce->cInfosAllocated / 2 + 1; DirectoryCacheEntryData *pNewInfos = (DirectoryCacheEntryData *) realloc(pdce->aInfos, cNewAllocated * sizeof (DirectoryCacheEntryData)); debuglocal(pdc->resource, 9, "dceWriteEntry: [%s] realloc %d -> %d\n", pdce->pszPath, pdce->cInfosAllocated, cNewAllocated); if (pNewInfos) { pdce->cInfosAllocated = cNewAllocated; pdce->aInfos = pNewInfos; } else { /* Mark the entry as invalid. The entry will be deleted in dircache_write_end */ pdce->fInvalid = 1; return; } } // store entry name and custom data strcpy( (char*)pdce->aInfos[pdce->cInfos].fname, fname); pdce->aInfos[pdce->cInfos].customData = finfo; pdce->cInfos++; } static void dceAdjust (DirectoryCache *pdc, DirectoryCacheEntry *pdce) { /* If the entry has too many preallocated info structures, adjust the memory allocation */ int cFree = pdce->cInfosAllocated - pdce->cInfos; if (cFree > pdce->cInfos / 2) { /* More than 50% is free. Make the free space 2 times smaller. */ int cNewAllocated = pdce->cInfos + cFree / 2; DirectoryCacheEntryData *pNewInfos = (DirectoryCacheEntryData *) realloc(pdce->aInfos, cNewAllocated * sizeof (DirectoryCacheEntryData)); debuglocal(pdc->resource, 9, "dceAdjust: [%s] realloc %d -> %d\n", pdce->pszPath, pdce->cInfosAllocated, cNewAllocated); if (pNewInfos) { pdce->cInfosAllocated = cNewAllocated; pdce->aInfos = pNewInfos; } else { /* Ignore. The old buffer remains. */ } } } static int dcePathEqualsTo (DirectoryCache *pdc, DirectoryCacheEntry *pdce, const char *path) { debuglocal(pdc->resource, 9, "dcePathEqualTo: [%s] [%s]\n", path, pdce->pszPath); return ph->fsphStrICmp (path, pdce->pszPath) == 0; } static int dcePathPrefixedWith (DirectoryCacheEntry *pdce, const char *path) { return ph->fsphStrNICmp (path, pdce->pszPath, strlen (path)) == 0; } static int dceExpired (DirectoryCacheEntry *pdce, unsigned long ulExpirationTime) { return (timesec () - pdce->ulLastUpdateTime >= ulExpirationTime); } static struct DirectoryCacheEntry *dcFindDirCache (DirectoryCache *pdc, const char *path, int fPrefix) { unsigned long hash = computehash (path); DirectoryCacheEntry *iter = pdc->pEntriesHead; debuglocal(pdc->resource, 9, "dcFindDirCache: [%s]\n", path); debuglocal(pdc->resource, 9, "dcFindDirCache: iter [%p]\n", iter); while (iter != NULL) { debuglocal(pdc->resource, 9, "dcFindDirCache: entry [%s]\n", iter->pszPath); if (fPrefix) { if (dcePathPrefixedWith (iter, path)) { break; } } else { if ( iter->ulHash == hash && dcePathEqualsTo (pdc, iter, path) ) { break; } } iter = iter->pNext; } debuglocal(pdc->resource, 9, "dcFindDirCache: %p\n", iter); return iter; } static int dcCreate (struct DirectoryCache **ppdc, void* pRes) { int rc; DirectoryCache *pdc = (DirectoryCache *)malloc(sizeof (DirectoryCache)); if (!pdc) { return ERROR_NOT_ENOUGH_MEMORY; } rc = lockCreate (&pdc->mutex); if (rc != NO_ERROR) { free(pdc); return rc; } pdc->pEntriesHead = NULL; pdc->pEntriesTail = NULL; pdc->cEntries = 0; pdc->fEnabled = 0; pdc->ulExpirationTime = -1; pdc->resource = pRes; *ppdc = pdc; return NO_ERROR; } static void dcDelete (struct DirectoryCache *pdc) { DirectoryCacheEntry *iter = pdc->pEntriesHead; debuglocal(pdc->resource, 9, "dcDelete: pdc %p, iter %p\n", pdc, iter); while (iter != NULL) { DirectoryCacheEntry *next = iter->pNext; dceDelete (pdc, iter); iter = next; } lockDelete (pdc->mutex); free (pdc); } static void dcRemoveEntry (DirectoryCache *pdc, DirectoryCacheEntry *pdce) { if (pdce->pNext) { pdce->pNext->pPrev = pdce->pPrev; } else { pdc->pEntriesTail = pdce->pPrev; } if (pdce->pPrev) { pdce->pPrev->pNext = pdce->pNext; } else { pdc->pEntriesHead = pdce->pNext; } pdce->pNext = NULL; pdce->pPrev = NULL; pdc->cEntries--; } static void dcInsertEntry (DirectoryCache *pdc, DirectoryCacheEntry *pdce) { pdce->pNext = pdc->pEntriesHead; if (pdc->pEntriesHead) { pdc->pEntriesHead->pPrev = pdce; } else { pdc->pEntriesTail = pdce; } pdc->pEntriesHead = pdce; pdc->cEntries++; } static void dcEnable (DirectoryCache *pdc, int fEnable, unsigned long ulExpirationTime, int cMaxEntries) { pdc->fEnabled = fEnable; pdc->ulExpirationTime = ulExpirationTime; pdc->cMaxEntries = cMaxEntries; }; static int dcExistsInCache (DirectoryCache *pdc, const char *path) { int rc = CacheFault; if (pdc->fEnabled) { if (dcFindDirCache (pdc, path, 0) != NULL) { rc = CacheOk; } debuglocal(pdc->resource, 9, "ExistsInCache: [%s], %d\n", path, rc); } return rc; } static int dcRead (DirectoryCache *pdc, const char *path, PFNADDDIRENTRY fn, void* plist, int *ptotal_received, int fForceCache) { int i; int rc = CacheFault; if (pdc->fEnabled) { DirectoryCacheEntry *pdce; pdce = dcFindDirCache (pdc, path, 0); if (pdce) { debuglocal(pdc->resource, 9, "dcRead: entry %p found for [%s]\n", pdce, path); if (fForceCache) { for (i = 0; i < pdce->cInfos; i++) { fn (plist, (void*)pdce->aInfos[i].customData); } rc = CacheOk; } else { if (dceExpired (pdce, pdc->ulExpirationTime)) { debuglocal(pdc->resource, 9, "dcRead: expired\n"); dcRemoveEntry (pdc, pdce); dceDelete (pdc, pdce); } else { debuglocal(pdc->resource, 9, "dcRead: adding %d entries from cache\n", pdce->cInfos); for (i = 0; i < pdce->cInfos; i++) { debuglocal(pdc->resource, 9, "dcRead: #%d->%s->%p\n", i, pdce->aInfos[i].fname, pdce->aInfos[i].customData); fn (plist, (void*)pdce->aInfos[i].customData); } rc = CacheOk; } } if (rc == CacheOk) { *ptotal_received = (int)pdce->cInfos; } } debuglocal(pdc->resource, 9, "dcRead: [%s], %d\n", path, rc); } return rc; } static DirectoryCacheEntry *dcWriteBegin (DirectoryCache *pdc, const char *path, int cFiles) { DirectoryCacheEntry *pdce = NULL; if (pdc->fEnabled) { pdce = dcFindDirCache (pdc, path, 0); if (!pdce) { /* Does not exist in the cache yet. */ dceCreate (&pdce, pdc, path, cFiles); } else { /* Discard the listing. */ dceClear (pdce); /* Remove the entry from list. It will be added again in dircache_write_end. */ dcRemoveEntry (pdc, pdce); } if (pdce) { pdce->ulLastUpdateTime = timesec (); } debuglocal(pdc->resource, 9, "CacheWriteBegin: %s\n", path); } return pdce; } static void dcInvalidate (DirectoryCache *pdc, const char *path, int fPrefix) { DirectoryCacheEntry *pdce; debuglocal(pdc->resource, 9, "CacheInvalidate: [%s]\n", path); pdce = dcFindDirCache (pdc, path, fPrefix); while (pdce) { dcRemoveEntry (pdc, pdce); dceDelete (pdc, pdce); pdce = dcFindDirCache (pdc, path, fPrefix); } } static int dcFindPath (DirectoryCache *pdc, const char *path, const char *parent, const char *name, void **finfo, unsigned long *pulAge) { DirectoryCacheEntry *pdce = pdc->pEntriesHead; unsigned long hash = computehash (parent); debuglocal(pdc->resource, 9, "dcFindPath: [%s][%s]\n", parent, name); while (pdce != NULL) { debuglocal(pdc->resource, 9, "dcFindPath: entry [%s]\n", pdce->pszPath); if ( pdce->ulHash == hash && dcePathEqualsTo (pdc, pdce, parent)) { /* This entry should contain the path. */ int i; for (i = 0; i < pdce->cInfos; i++) { if (ph->fsphStrICmp (pdce->aInfos[i].fname, name) == 0) { *finfo = (void*) pdce->aInfos[i].customData; *pulAge = timesec () - pdce->ulLastUpdateTime; debuglocal(pdc->resource, 9, "dcFindPath: FindPath %s found, age %d\n", path, *pulAge); return 1; } } } pdce = pdce->pNext; } debuglocal(pdc->resource, 9, "dcFindPath: FindPath %s not found\n", path); return 0; } static int dcCreateDirPath(char *path, int cbPath, char* dir_mask, char* fullpath) { /* State contains the original path passed to the plugin (fullpath) and * the actual filename mask (dir_mask), which should be used to filter * appropriate filenames from the full listing. * So the directory name can be constructed by removing the mask from the fullpath. */ int cbDir; int cbMask = strlen(dir_mask) + 1; if (cbMask > cbPath) { /* This actually should never happen, because mask is a part of path. * But still return a failure, better no dircache than a crash. */ return 0; } cbDir = cbPath - cbMask; if (cbDir > 0) { cbDir--; /* Exclude the slash. */ memcpy(path, fullpath, cbDir); } path[cbDir] = 0; return 1; } /* * Public API. */ int dircache_create( DirectoryCache **ppdc, void* pRes, int cachetimeout, int cachedepth, PFNFREEDIRENTRY _release, PLUGINHELPERTABLE2L *_ph) { unsigned long ulExpirationTime = cachetimeout; int cMaxEntries = cachedepth; int rc; debuglocal( pRes, 9, "dircache_create: %u seconds, %d entries\n", ulExpirationTime, cMaxEntries); // save pointer to helpers ph = _ph; rc = dcCreate(ppdc, pRes); if (rc == NO_ERROR) { (*ppdc)->release = _release; dcEnable (*ppdc, 1, ulExpirationTime, cMaxEntries); } debuglocal( (*ppdc)->resource, 9, "dircache_create: %p, rc = %d\n", *ppdc, rc); return rc; } void dircache_delete(DirectoryCache *pdc) { debuglocal(pdc->resource, 9, "dircache_delete: %p\n", pdc); if (pdc) { dcDelete(pdc); } } int dircache_list_files(DirectoryCache *pdc, PFNADDDIRENTRY fn, void* plist, char* dir_mask, char* fullpath, int *ptotal_received) { int rc; int cbPath = strlen(fullpath) + 1; char *path = (char*) alloca(cbPath); debuglocal(pdc->resource, 9, "dircache_list_files pdc %p, dir_mask [%s], fullpath [%s]\n", pdc, dir_mask, fullpath); if (!dcCreateDirPath(path, cbPath, dir_mask, fullpath)) { debuglocal(pdc->resource, 9, "dircache_list_files pdc %p, dcCreateDirPath failed\n", pdc); return 0; } debuglocal(pdc->resource, 9, "dircache_list_files [%s]\n", path); if (!pdc) { return 0; } lockRequest (pdc->mutex); rc = dcRead (pdc, path, fn, plist, ptotal_received, 0); lockRelease (pdc->mutex); debuglocal(pdc->resource, 9, "dircache_list_files rc=%d\n", (rc == CacheOk)); return (rc == CacheOk); } void *dircache_write_begin(DirectoryCache *pdc, char* dir_mask, char* fullpath, int cFiles) { DirectoryCacheEntry *pdce = NULL; debuglocal(pdc->resource, 9, "dircache_write_begin pdc %p path [%s]\n", pdc, fullpath); int cbPath = strlen(fullpath) + 1; char *path = (char*) alloca(cbPath); if (!dcCreateDirPath(path, cbPath, dir_mask, fullpath)) { debuglocal(pdc->resource, 9, "dircache_write_begin dcCreateDirPath failed\n"); return NULL; } lockRequest (pdc->mutex); if (pdc->cEntries >= pdc->cMaxEntries) { /* Remove oldest entry. */ pdce = pdc->pEntriesTail; dcRemoveEntry (pdc, pdce); dceDelete (pdc, pdce); pdce = NULL; } pdce = dcWriteBegin (pdc, path, cFiles); lockRelease (pdc->mutex); debuglocal(pdc->resource, 9, "dircache_write_begin returning ctx %p\n", pdce); return (void *)pdce; } void dircache_write_entry(DirectoryCache *pdc, void *dircachectx, const char *fname, const void *finfo) { DirectoryCacheEntry *pdce = (DirectoryCacheEntry *)dircachectx; debuglocal(pdc->resource, 9, "dircache_write_entry %p\n", pdce); if (!pdce) { return; } lockRequest (pdc->mutex); dceWriteEntry (pdc, pdce, fname, finfo); lockRelease (pdc->mutex); } void dircache_write_end(DirectoryCache *pdc, void *dircachectx) { DirectoryCacheEntry *pdce = (DirectoryCacheEntry *)dircachectx; debuglocal(pdc->resource, 9, "dircache_write_end: pdce %p\n", pdce); if (!pdce) { return; } lockRequest (pdc->mutex); if (pdce->fInvalid) { /* Something happened during writing to the entry. Delete it. */ dceDelete (pdc, pdce); } else { dceAdjust (pdc, pdce); dcInsertEntry (pdc, pdce); } lockRelease (pdc->mutex); } void dircache_invalidate(DirectoryCache *pdc, const char *path, int fParent) { debuglocal(pdc->resource, 9, "dircache_invalidate [%s], parent %d\n", path, fParent); if (!pdc) { return; } lockRequest (pdc->mutex); if (fParent) { int cb = strlen (path) + 1; char *p = (char *)alloca(cb); memcpy(p, path, cb); char *lastSlash = ph->fsphStrRChr(p, '\\'); if (!lastSlash) lastSlash = ph->fsphStrRChr(p, '/'); if (lastSlash) { *lastSlash = 0; dcInvalidate (pdc, p, 0); } else { dcInvalidate (pdc, "", 0); } } else { dcInvalidate (pdc, path, 0); } lockRelease (pdc->mutex); return; } int dircache_find_path(DirectoryCache *pdc, const char *path, void **finfo, unsigned long *pulAge) { int cb; char *p; char *lastSlash; int fFound = 0; debuglocal(pdc->resource, 9, "dircache_find_path [%s]\n", path); if (!pdc) { return 0; } if (ph->fsphStrChr(path, '*') || ph->fsphStrChr(path, '?')) { /* Wildcards are not allowed in the input path. */ return 0; } lockRequest (pdc->mutex); /* Prepare the parent path. */ cb = strlen (path) + 1; p = (char *)alloca(cb); memcpy(p, path, cb); lastSlash = ph->fsphStrRChr(p, '\\'); if (!lastSlash) lastSlash = ph->fsphStrRChr(p, '/'); if (lastSlash) { *lastSlash = 0; fFound = dcFindPath (pdc, path, p, lastSlash + 1, finfo, pulAge); } else { /* Find in the root directory. p is the name. */ fFound = dcFindPath (pdc, path, "", p, finfo, pulAge); } lockRelease (pdc->mutex); debuglocal(pdc->resource, 9, "dircache_find_path fFound %d\n", fFound); return fFound; }