From: Jeffrey Altman Date: Sun, 1 Apr 2012 05:17:21 +0000 (-0400) Subject: Windows: Add per object per user EACCES caching X-Git-Tag: upstream/1.8.0_pre1^2~2635 X-Git-Url: https://git.michaelhowe.org/gitweb/?a=commitdiff_plain;h=7c5b8346b305911c981620fff70503008e9cf488;p=packages%2Fo%2Fopenafs.git Windows: Add per object per user EACCES caching If a cache manager is told by a file server that the user does not have permission to fetch status for an object, the cache manager must avoid requesting a fetch status a second time for that object for the same user. Doing so risks triggering the rx call abort throttling which can have a significant impact on end user usability of the Explorer Shell and other applications. The cache manager cannot make a decision on whether or not to issue an RXAFS_FetchStatus RPC based upon the type of the object because the type is unknown to the cache manager. A file server will succeed a FetchStatus request when the parent directory ACL grants lookup permission if the object in question is the directory or is a symlink/mountpoint. Only file objects require read/write permissions to obtain status information. The rx call abort throttling is broken is many ways and must be avoided. Call aborts are tracked by call channel and occur whenever ten call aborts are issued on the same call channel in a row regardless of the amount of time that has elapsed. The EACCES cache works by storing EACCES events by the FID and User for which the event occurred, when it occurred and the FID of the parent directory. By definition, the parent FID of a volume root directory is itself. Entries are removed from the cache under the following circumstances: 1. When the parent FID's callback expires or is replaced. 2. When the parent FID's cm_scache object is recycled. 3. When the user's tokens expire or are replaced. Entries are not removed when the FID's cm_scache object is recycled. This patchset also implements correct behavior if the VLF_DFSFILESET flag is set on a volume. Change-Id: I69507601f9872c9544e52a1d5e01064fa42efb81 Reviewed-on: http://gerrit.openafs.org/6996 Reviewed-by: Jeffrey Altman Tested-by: Jeffrey Altman --- diff --git a/src/WINNT/afsd/NTMakefile b/src/WINNT/afsd/NTMakefile index 6d6646c12..b9e4442d9 100644 --- a/src/WINNT/afsd/NTMakefile +++ b/src/WINNT/afsd/NTMakefile @@ -60,6 +60,7 @@ INCFILES =\ $(INCFILEDIR)\cm_volstat.h \ $(INCFILEDIR)\cm_dcache.h \ $(INCFILEDIR)\cm_access.h \ + $(INCFILEDIR)\cm_eacces.h \ $(INCFILEDIR)\cm_vnodeops.h \ $(INCFILEDIR)\cm_dir.h \ $(INCFILEDIR)\cm_utils.h \ @@ -129,6 +130,7 @@ AFSDOBJS=\ $(OUT)\cm_scache.obj \ $(OUT)\cm_dcache.obj \ $(OUT)\cm_access.obj \ + $(OUT)\cm_eacces.obj \ $(OUT)\cm_callback.obj \ $(OUT)\cm_vnodeops.obj \ $(OUT)\cm_dir.obj \ diff --git a/src/WINNT/afsd/afsd.h b/src/WINNT/afsd/afsd.h index 65f2d1811..feabae28e 100644 --- a/src/WINNT/afsd/afsd.h +++ b/src/WINNT/afsd/afsd.h @@ -48,6 +48,7 @@ BOOL APIENTRY About(HWND, unsigned int, unsigned int, long); #include "cm_volume.h" #include "cm_dcache.h" #include "cm_access.h" +#include "cm_eacces.h" #include "cm_dir.h" #include "cm_utils.h" #include "cm_vnodeops.h" diff --git a/src/WINNT/afsd/afsd_init.c b/src/WINNT/afsd/afsd_init.c index 72bf229d5..ba25a408a 100644 --- a/src/WINNT/afsd/afsd_init.c +++ b/src/WINNT/afsd/afsd_init.c @@ -1407,6 +1407,9 @@ afsd_InitCM(char **reasonP) return -1; } + /* Must be called after cm_InitMappedMemory. */ + cm_EAccesInitCache(); + #if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0500) if (cm_InitDNS(cm_dnsEnabled) == -1) cm_dnsEnabled = 0; /* init failed, so deactivate */ diff --git a/src/WINNT/afsd/cm.h b/src/WINNT/afsd/cm.h index 5d9f29387..e1711ad8a 100644 --- a/src/WINNT/afsd/cm.h +++ b/src/WINNT/afsd/cm.h @@ -82,6 +82,7 @@ #define LOCK_HIERARCHY_OTHER_GLOBAL 720 #define LOCK_HIERARCHY_ACL_GLOBAL 730 #define LOCK_HIERARCHY_USER_GLOBAL 740 +#define LOCK_HIERARCHY_EACCES_GLOBAL 750 #define LOCK_HIERARCHY_AFSDBSBMT_GLOBAL 1000 #define LOCK_HIERARCHY_TOKEN_EVENT_GLOBAL 2000 #define LOCK_HIERARCHY_SYSCFG_GLOBAL 3000 diff --git a/src/WINNT/afsd/cm_access.c b/src/WINNT/afsd/cm_access.c index 79692b1e2..17c6c7c80 100644 --- a/src/WINNT/afsd/cm_access.c +++ b/src/WINNT/afsd/cm_access.c @@ -38,37 +38,41 @@ int cm_HaveAccessRights(struct cm_scache *scp, struct cm_user *userp, cm_req_t * afs_uint32 *outRightsp) { cm_scache_t *aclScp; - long code; + long code = 0; cm_fid_t tfid; int didLock; long trights; int release = 0; /* Used to avoid a call to cm_HoldSCache in the directory case */ + cm_volume_t *volp = cm_GetVolumeByFID(&scp->fid); didLock = 0; - if (scp->fileType == CM_SCACHETYPE_DIRECTORY || cm_accessPerFileCheck) { + if (scp->fileType == CM_SCACHETYPE_DIRECTORY || + cm_accessPerFileCheck || + !volp || (volp->flags & CM_VOLUMEFLAG_DFS_VOLUME)) { aclScp = scp; /* not held, not released */ } else { cm_SetFid(&tfid, scp->fid.cell, scp->fid.volume, scp->parentVnode, scp->parentUnique); aclScp = cm_FindSCache(&tfid); - if (!aclScp) - return 0; + if (!aclScp) { + code = 0; + goto done; + } + release = 1; if (aclScp != scp) { if (aclScp->fid.vnode < scp->fid.vnode) lock_ReleaseWrite(&scp->rw); lock_ObtainRead(&aclScp->rw); + didLock = 1; if (aclScp->fid.vnode < scp->fid.vnode) lock_ObtainWrite(&scp->rw); - /* check that we have a callback, too */ + /* check that we have a callback, too */ if (!cm_HaveCallback(aclScp)) { /* can't use it */ - lock_ReleaseRead(&aclScp->rw); - cm_ReleaseSCache(aclScp); - return 0; + code = 0; + goto done; } - didLock = 1; } - release = 1; } lock_AssertAny(&aclScp->rw); @@ -133,6 +137,8 @@ int cm_HaveAccessRights(struct cm_scache *scp, struct cm_user *userp, cm_req_t * /* fall through */ done: + if (volp) + cm_PutVolume(volp); if (didLock) lock_ReleaseRead(&aclScp->rw); if (release) @@ -154,6 +160,7 @@ long cm_GetAccessRights(struct cm_scache *scp, struct cm_user *userp, cm_fid_t tfid; cm_scache_t *aclScp = NULL; int got_cb = 0; + cm_volume_t * volp = cm_GetVolumeByFID(&scp->fid); /* pretty easy: just force a pass through the fetch status code */ @@ -162,13 +169,16 @@ long cm_GetAccessRights(struct cm_scache *scp, struct cm_user *userp, /* first, start by finding out whether we have a directory or something * else, so we can find what object's ACL we need. */ - if (scp->fileType == CM_SCACHETYPE_DIRECTORY || cm_accessPerFileCheck) { + if (scp->fileType == CM_SCACHETYPE_DIRECTORY || + cm_accessPerFileCheck || + !volp || (volp->flags & CM_VOLUMEFLAG_DFS_VOLUME)) + { code = cm_SyncOp(scp, NULL, userp, reqp, 0, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS | CM_SCACHESYNC_FORCECB); if (!code) cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS); - else - osi_Log3(afsd_logp, "GetAccessRights syncop failure scp %x user %x code %x", scp, userp, code); + else + osi_Log3(afsd_logp, "GetAccessRights syncop failure scp %x user %x code %x", scp, userp, code); } else { /* not a dir, use parent dir's acl */ cm_SetFid(&tfid, scp->fid.cell, scp->fid.volume, scp->parentVnode, scp->parentUnique); @@ -194,5 +204,7 @@ long cm_GetAccessRights(struct cm_scache *scp, struct cm_user *userp, } _done: + if (volp) + cm_PutVolume(volp); return code; } diff --git a/src/WINNT/afsd/cm_aclent.c b/src/WINNT/afsd/cm_aclent.c index b9f06fc18..3c01da5cd 100644 --- a/src/WINNT/afsd/cm_aclent.c +++ b/src/WINNT/afsd/cm_aclent.c @@ -404,6 +404,8 @@ cm_ResetACLCache(cm_cell_t *cellp, cm_user_t *userp) } lock_ReleaseRead(&cm_scacheLock); + cm_EAccesClearUserEntries(userp, cellp->cellID); + if (RDR_Initialized) { lock_ObtainRead(&cm_volumeLock); for (hash = 0; hash < cm_data.volumeHashTableSize; hash++) { diff --git a/src/WINNT/afsd/cm_btree.c b/src/WINNT/afsd/cm_btree.c index eb5cbc500..4f7048d3a 100644 --- a/src/WINNT/afsd/cm_btree.c +++ b/src/WINNT/afsd/cm_btree.c @@ -2362,6 +2362,7 @@ cm_BPlusDirEnumBulkStat(cm_direnum_t *enump) goto done; } memset(bsp, 0, sizeof(cm_bulkStat_t)); + bsp->userp = userp; bs_errorCodep = malloc(sizeof(DWORD *) * AFSCBMAX); if (!bs_errorCodep) { @@ -2390,7 +2391,7 @@ cm_BPlusDirEnumBulkStat(cm_direnum_t *enump) if (tscp) { if (lock_TryWrite(&tscp->rw)) { /* we have an entry that we can look at */ - if (!(tscp->flags & CM_SCACHEFLAG_EACCESS) && cm_HaveCallback(tscp)) { + if (!cm_EAccesFindEntry(userp, &tscp->fid) && cm_HaveCallback(tscp)) { /* we have a callback on it. Don't bother * fetching this stat entry, since we're happy * with the info we have. @@ -2427,6 +2428,8 @@ cm_BPlusDirEnumBulkStat(cm_direnum_t *enump) goto done; } memset(bsp, 0, sizeof(cm_bulkStat_t)); + bsp->userp = userp; + /* * In order to prevent the directory callback from expiring * on really large directories with many symlinks to mount @@ -2497,6 +2500,7 @@ cm_BPlusDirEnumBulkStatOne(cm_direnum_t *enump, cm_scache_t *scp) goto done; } memset(bsp, 0, sizeof(cm_bulkStat_t)); + bsp->userp = userp; bs_errorCodep = malloc(sizeof(DWORD *) * AFSCBMAX); if (!bs_errorCodep) { @@ -2553,7 +2557,7 @@ cm_BPlusDirEnumBulkStatOne(cm_direnum_t *enump, cm_scache_t *scp) if (lock_TryWrite(&tscp->rw)) { /* we have an entry that we can look at */ - if (!(tscp->flags & CM_SCACHEFLAG_EACCESS) && cm_HaveCallback(tscp)) { + if (!cm_EAccesFindEntry(userp, &tscp->fid) && cm_HaveCallback(tscp)) { /* we have a callback on it. Don't bother * fetching this stat entry, since we're happy * with the info we have. @@ -2629,6 +2633,7 @@ cm_BPlusDirEnumBulkStatNext(cm_direnum_t *enump) goto done; } memset(bsp, 0, sizeof(cm_bulkStat_t)); + bsp->userp = userp; bs_errorCodep = malloc(sizeof(DWORD *) * AFSCBMAX); if (!bs_errorCodep) { @@ -2657,7 +2662,7 @@ cm_BPlusDirEnumBulkStatNext(cm_direnum_t *enump) if (tscp) { if (lock_TryWrite(&tscp->rw)) { /* we have an entry that we can look at */ - if (!(tscp->flags & CM_SCACHEFLAG_EACCESS) && cm_HaveCallback(tscp)) { + if (!cm_EAccesFindEntry(userp, &tscp->fid) && cm_HaveCallback(tscp)) { /* we have a callback on it. Don't bother * fetching this stat entry, since we're happy * with the info we have. diff --git a/src/WINNT/afsd/cm_callback.c b/src/WINNT/afsd/cm_callback.c index 5c326c427..9604ac1a1 100644 --- a/src/WINNT/afsd/cm_callback.c +++ b/src/WINNT/afsd/cm_callback.c @@ -2054,6 +2054,11 @@ void cm_CheckCBExpiration(void) cm_CallbackNotifyChange(scp); + if (scp->fileType == CM_SCACHETYPE_DIRECTORY && + !(volp && (volp->flags & CM_VOLUMEFLAG_DFS_VOLUME)) && + !cm_accessPerFileCheck) + cm_EAccesClearParentEntries(&scp->fid); + scp_complete: if (volp) cm_PutVolume(volp); diff --git a/src/WINNT/afsd/cm_daemon.c b/src/WINNT/afsd/cm_daemon.c index 2e5ef84f3..597a266fa 100644 --- a/src/WINNT/afsd/cm_daemon.c +++ b/src/WINNT/afsd/cm_daemon.c @@ -42,6 +42,7 @@ long cm_daemonPerformanceTuningInterval = 0; long cm_daemonRankServerInterval = 600; long cm_daemonRDRShakeExtentsInterval = 0; long cm_daemonAfsdHookReloadInterval = 0; +long cm_daemonEAccesCheckInterval = 1800; osi_rwlock_t *cm_daemonLockp; afs_uint64 *cm_bkgQueueCountp; /* # of queued requests */ @@ -505,6 +506,7 @@ void * cm_Daemon(void *vparm) time_t lastServerRankCheck; time_t lastRDRShakeExtents; time_t lastAfsdHookReload; + time_t lastEAccesCheck; char thostName[200]; unsigned long code; struct hostent *thp; @@ -564,6 +566,7 @@ void * cm_Daemon(void *vparm) lastRDRShakeExtents = now - cm_daemonRDRShakeExtentsInterval/2 * (rand() % cm_daemonRDRShakeExtentsInterval); if (cm_daemonAfsdHookReloadInterval) lastAfsdHookReload = now; + lastEAccesCheck = now; hHookDll = cm_LoadAfsdHookLib(); if (hHookDll) @@ -720,6 +723,16 @@ void * cm_Daemon(void *vparm) now = osi_Time(); } + if (now > lastEAccesCheck + cm_daemonEAccesCheckInterval && + daemon_ShutdownFlag == 0 && + powerStateSuspended == 0) { + lastEAccesCheck = now; + cm_EAccesClearOutdatedEntries(); + if (daemon_ShutdownFlag == 1) + break; + now = osi_Time(); + } + if (cm_daemonRDRShakeExtentsInterval && now > lastRDRShakeExtents + cm_daemonRDRShakeExtentsInterval && daemon_ShutdownFlag == 0 && diff --git a/src/WINNT/afsd/cm_eacces.c b/src/WINNT/afsd/cm_eacces.c new file mode 100644 index 000000000..603a4c8da --- /dev/null +++ b/src/WINNT/afsd/cm_eacces.c @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2012 Your File System, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of Secure Endpoints Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission from Secure Endpoints, Inc. and + * Your File System, Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "afsd.h" + +static osi_rwlock_t cm_eaccesLock; + +static struct osi_queue ** cm_eaccesFidHashTableH = NULL; +static struct osi_queue ** cm_eaccesParentHashTableH = NULL; +static struct osi_queue ** cm_eaccesUserHashTableH = NULL; + +static struct osi_queue ** cm_eaccesFidHashTableT = NULL; +static struct osi_queue ** cm_eaccesParentHashTableT = NULL; +static struct osi_queue ** cm_eaccesUserHashTableT = NULL; + +static afs_uint32 cm_eaccesFidHashTableSize = 0; +static afs_uint32 cm_eaccesParentHashTableSize = 0; +static afs_uint32 cm_eaccesUserHashTableSize = 0; + +static struct osi_queue * cm_eaccesFreeListH = NULL; + +void +cm_EAccesInitCache(void) +{ + static osi_once_t once; + + if (osi_Once(&once)) { + lock_InitializeRWLock(&cm_eaccesLock, "cm_eaccesLock", LOCK_HIERARCHY_EACCES_GLOBAL); + osi_EndOnce(&once); + } + + lock_ObtainWrite(&cm_eaccesLock); + cm_eaccesFidHashTableSize = cm_data.stats * 2; + cm_eaccesParentHashTableSize = cm_data.stats * 2; + cm_eaccesUserHashTableSize = 32; + + cm_eaccesFidHashTableH = malloc(cm_eaccesFidHashTableSize * sizeof(struct osi_queue *)); + memset(cm_eaccesFidHashTableH, 0, cm_eaccesFidHashTableSize * sizeof(struct osi_queue *)); + cm_eaccesFidHashTableT = malloc(cm_eaccesFidHashTableSize * sizeof(struct osi_queue *)); + memset(cm_eaccesFidHashTableT, 0, cm_eaccesFidHashTableSize * sizeof(struct osi_queue *)); + + cm_eaccesParentHashTableH = malloc(cm_eaccesParentHashTableSize * sizeof(struct osi_queue *)); + memset(cm_eaccesParentHashTableH, 0, cm_eaccesParentHashTableSize * sizeof(struct osi_queue *)); + cm_eaccesParentHashTableT = malloc(cm_eaccesParentHashTableSize * sizeof(struct osi_queue *)); + memset(cm_eaccesParentHashTableT, 0, cm_eaccesParentHashTableSize * sizeof(struct osi_queue *)); + + cm_eaccesUserHashTableH = malloc(cm_eaccesUserHashTableSize * sizeof(struct osi_queue *)); + memset(cm_eaccesUserHashTableH, 0, cm_eaccesUserHashTableSize * sizeof(struct osi_queue *)); + cm_eaccesUserHashTableT = malloc(cm_eaccesUserHashTableSize * sizeof(struct osi_queue *)); + memset(cm_eaccesUserHashTableT, 0, cm_eaccesUserHashTableSize * sizeof(struct osi_queue *)); + + lock_ReleaseWrite(&cm_eaccesLock); +} + +afs_uint32 +cm_EAccesAddEntry(cm_user_t* userp, cm_fid_t *fidp, cm_fid_t *parentFidp) +{ + cm_eacces_t *eaccesp = NULL; + afs_uint32 hash; + int newEntry = 0; + + hash = CM_EACCES_FID_HASH(fidp); + + lock_ObtainWrite(&cm_eaccesLock); + for (eaccesp = (cm_eacces_t *)cm_eaccesFidHashTableH[hash]; + eaccesp; + eaccesp = (cm_eacces_t *)osi_QNext(&eaccesp->q)) + { + if (eaccesp->userp == userp && + !cm_FidCmp(&eaccesp->fid, fidp)) + break; + } + + if (eaccesp == NULL) { + if (osi_QIsEmpty(&cm_eaccesFreeListH)) { + eaccesp = malloc(sizeof(cm_eacces_t)); + } else { + eaccesp = (cm_eacces_t *)cm_eaccesFreeListH; + osi_QRemove(&cm_eaccesFreeListH, &eaccesp->q); + } + + memset(eaccesp, 0, sizeof(cm_eacces_t)); + eaccesp->magic = CM_EACCES_MAGIC; + eaccesp->fid = *fidp; + eaccesp->userp = userp; + cm_HoldUser(userp); + + osi_QAddH( &cm_eaccesFidHashTableH[hash], + &cm_eaccesFidHashTableT[hash], + &eaccesp->q); + + hash = CM_EACCES_USER_HASH(userp); + osi_QAddH( &cm_eaccesUserHashTableH[hash], + &cm_eaccesUserHashTableT[hash], + &eaccesp->userq); + + newEntry = 1; + } + + if (eaccesp) { + eaccesp->errorTime = time(NULL); + + if (!newEntry && + !cm_FidCmp(parentFidp, &eaccesp->parentFid)) + { + hash = CM_EACCES_PARENT_HASH(&eaccesp->parentFid); + osi_QRemoveHT( &cm_eaccesParentHashTableH[hash], + &cm_eaccesParentHashTableT[hash], + &eaccesp->parentq); + } + + eaccesp->parentFid = *parentFidp; + hash = CM_EACCES_PARENT_HASH(&eaccesp->parentFid); + osi_QAddH( &cm_eaccesParentHashTableH[hash], + &cm_eaccesParentHashTableT[hash], + &eaccesp->parentq); + } + lock_ReleaseWrite(&cm_eaccesLock); + + return 0; +} + +cm_eacces_t * +cm_EAccesFindEntry(cm_user_t* userp, cm_fid_t *fidp) +{ + cm_eacces_t *eaccesp = NULL; + afs_uint32 hash; + + hash = CM_EACCES_FID_HASH(fidp); + + lock_ObtainRead(&cm_eaccesLock); + for (eaccesp = (cm_eacces_t *)cm_eaccesFidHashTableH[hash]; + eaccesp; + eaccesp = (cm_eacces_t *)osi_QNext(&eaccesp->q)) + { + if (eaccesp->userp == userp && + !cm_FidCmp(&eaccesp->fid, fidp)) + break; + } + lock_ReleaseRead(&cm_eaccesLock); + + return eaccesp; +} + +void +cm_EAccesClearParentEntries(cm_fid_t *parentFidp) +{ + cm_eacces_t *eaccesp = NULL; + cm_eacces_t *nextp = NULL; + afs_uint32 hash, hash2; + + hash = CM_EACCES_PARENT_HASH(parentFidp); + + lock_ObtainRead(&cm_eaccesLock); + for (eaccesp = parentq_to_cm_eacces_t(cm_eaccesParentHashTableH[hash]); + eaccesp; + eaccesp = nextp) + { + nextp = parentq_to_cm_eacces_t(osi_QNext(&eaccesp->parentq)); + + if (!cm_FidCmp(&eaccesp->parentFid, parentFidp)) + { + osi_QRemoveHT( &cm_eaccesParentHashTableH[hash], + &cm_eaccesParentHashTableT[hash], + &eaccesp->parentq); + + hash2 = CM_EACCES_FID_HASH(&eaccesp->fid); + osi_QRemoveHT( &cm_eaccesFidHashTableH[hash2], + &cm_eaccesFidHashTableT[hash2], + &eaccesp->q); + + hash2 = CM_EACCES_USER_HASH(eaccesp->userp); + osi_QRemoveHT( &cm_eaccesUserHashTableH[hash2], + &cm_eaccesUserHashTableT[hash2], + &eaccesp->userq); + + cm_ReleaseUser(eaccesp->userp); + osi_QAdd( &cm_eaccesFreeListH, &eaccesp->q); + } + } + lock_ReleaseRead(&cm_eaccesLock); +} + +void +cm_EAccesClearUserEntries(cm_user_t *userp, afs_uint32 cellID) +{ + cm_eacces_t *eaccesp = NULL; + cm_eacces_t *nextp = NULL; + afs_uint32 hash, hash2; + + hash = CM_EACCES_USER_HASH(userp); + + lock_ObtainRead(&cm_eaccesLock); + for (eaccesp = userq_to_cm_eacces_t(cm_eaccesUserHashTableH[hash]); + eaccesp; + eaccesp = nextp) + { + nextp = userq_to_cm_eacces_t(osi_QNext(&eaccesp->userq)); + + if (eaccesp->userp == userp && + (cellID == 0 || eaccesp->fid.cell == cellID)) + { + cm_ReleaseUser(userp); + osi_QRemoveHT( &cm_eaccesUserHashTableH[hash], + &cm_eaccesUserHashTableT[hash], + &eaccesp->userq); + + hash2 = CM_EACCES_FID_HASH(&eaccesp->fid); + osi_QRemoveHT( &cm_eaccesFidHashTableH[hash2], + &cm_eaccesFidHashTableT[hash2], + &eaccesp->q); + + hash2 = CM_EACCES_PARENT_HASH(&eaccesp->parentFid); + osi_QRemoveHT( &cm_eaccesParentHashTableH[hash2], + &cm_eaccesParentHashTableT[hash2], + &eaccesp->parentq); + + osi_QAdd( &cm_eaccesFreeListH, &eaccesp->q); + } + } + lock_ReleaseRead(&cm_eaccesLock); +} + +void +cm_EAccesClearOutdatedEntries(void) +{ + cm_eacces_t *eaccesp = NULL; + cm_eacces_t *nextp = NULL; + afs_uint32 hash, hash2; + time_t now = time(NULL); + + lock_ObtainRead(&cm_eaccesLock); + for (hash = 0; hash < cm_eaccesFidHashTableSize; hash++) + { + for (eaccesp = (cm_eacces_t *)(cm_eaccesFidHashTableH[hash]); + eaccesp; + eaccesp = nextp) + { + nextp = (cm_eacces_t *)(osi_QNext(&eaccesp->q)); + + if (eaccesp->errorTime + 4*60*60 < now) + { + osi_QRemoveHT( &cm_eaccesFidHashTableH[hash], + &cm_eaccesFidHashTableT[hash], + &eaccesp->q); + + hash2 = CM_EACCES_USER_HASH(eaccesp->userp); + osi_QRemoveHT( &cm_eaccesUserHashTableH[hash2], + &cm_eaccesUserHashTableT[hash2], + &eaccesp->userq); + cm_ReleaseUser(eaccesp->userp); + + hash2 = CM_EACCES_PARENT_HASH(&eaccesp->parentFid); + osi_QRemoveHT( &cm_eaccesParentHashTableH[hash2], + &cm_eaccesParentHashTableT[hash2], + &eaccesp->parentq); + + osi_QAdd( &cm_eaccesFreeListH, &eaccesp->q); + } + } + } + lock_ReleaseRead(&cm_eaccesLock); +} diff --git a/src/WINNT/afsd/cm_eacces.h b/src/WINNT/afsd/cm_eacces.h new file mode 100644 index 000000000..481b5b2d2 --- /dev/null +++ b/src/WINNT/afsd/cm_eacces.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2012 Your File System, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of Secure Endpoints Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission from Secure Endpoints, Inc. and + * Your File System, Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _CM_EACCES_H_ +#define _CM_EACCES_H_ + +#include +#include "cm_scache.h" + +#define CM_EACCES_MAGIC ('E' | 'A' <<8 | 'C'<<16 | 'C'<<24) + +/* + * Structure to hold EACCES error info for FID,User pairs + */ + +typedef struct cm_eacces { + struct osi_queue q; /* fid hash table or free list */ + afs_uint32 magic; + struct osi_queue parentq; + struct osi_queue userq; + cm_fid_t fid; + cm_fid_t parentFid; + cm_user_t *userp; + time_t errorTime; +} cm_eacces_t; + +#define parentq_to_cm_eacces_t(q) ((q) ? (cm_eacces_t *)((char *) (q) - offsetof(cm_eacces_t, parentq)) : NULL) +#define userq_to_cm_eacces_t(q) ((q) ? (cm_eacces_t *)((char *) (q) - offsetof(cm_eacces_t, userq)) : NULL) + +#define CM_EACCES_FID_HASH(fidp) (opr_jhash(&(fidp)->cell, 4, 0) & (cm_eaccesFidHashTableSize - 1)) + +#define CM_EACCES_PARENT_HASH(fidp) (opr_jhash(&(fidp)->cell, 4, 0) & (cm_eaccesParentHashTableSize - 1)) + +#define CM_EACCES_USER_HASH(userp) (opr_jhash((const uint32_t *)&userp, sizeof(cm_user_t *)/4, 0) & (cm_eaccesUserHashTableSize - 1)) + +extern void cm_EAccesInitCache(void); + +extern cm_eacces_t * cm_EAccesFindEntry(cm_user_t* userp, cm_fid_t *fidp); + +extern afs_uint32 cm_EAccesAddEntry(cm_user_t* userp, cm_fid_t *fidp, cm_fid_t *parentFidp); + +extern void cm_EAccesClearParentEntries(cm_fid_t *parentFip); + +extern void cm_EAccesClearUserEntries(cm_user_t *userp, afs_uint32 CellID); + +extern void cm_EAccesClearOutdatedEntries(void); + + +/* + * The EACCES cache works by storing EACCES events by the FID and User + * for which the event occurred, when it occurred and the FID of the parent + * directory. By definition, the parent FID of a volume root directory + * is itself. + * + * Entries are removed from the cache under the following circumstances: + * 1. When the parent FID's callback expires or is replaced. + * 2. When the parent FID's cm_scache object is recycled. + * 3. When the user's tokens expire or are replaced. + * + * Entries are not removed when the FID's cm_scache object is recycled. + */ +#endif /* _CM_EACCES_H_ */ diff --git a/src/WINNT/afsd/cm_scache.c b/src/WINNT/afsd/cm_scache.c index b94f80f76..9b65daef3 100644 --- a/src/WINNT/afsd/cm_scache.c +++ b/src/WINNT/afsd/cm_scache.c @@ -151,7 +151,6 @@ long cm_RecycleSCache(cm_scache_t *scp, afs_int32 flags) return -1; } - if (scp->flags & CM_SCACHEFLAG_SMB_FID) { osi_Log1(afsd_logp,"cm_RecycleSCache CM_SCACHEFLAG_SMB_FID detected scp 0x%p", scp); #ifdef DEBUG @@ -170,6 +169,15 @@ long cm_RecycleSCache(cm_scache_t *scp, afs_int32 flags) cm_RemoveSCacheFromHashTable(scp); + if (scp->fileType == CM_SCACHETYPE_DIRECTORY && + !cm_accessPerFileCheck) { + cm_volume_t *volp = cm_GetVolumeByFID(&scp->fid); + + if (!(volp && (volp->flags & CM_VOLUMEFLAG_DFS_VOLUME))) + cm_EAccesClearParentEntries(&fid); + cm_PutVolume(volp); + } + /* invalidate so next merge works fine; * also initialize some flags */ scp->fileType = 0; @@ -179,8 +187,7 @@ long cm_RecycleSCache(cm_scache_t *scp, afs_int32 flags) | CM_SCACHEFLAG_RO | CM_SCACHEFLAG_PURERO | CM_SCACHEFLAG_OVERQUOTA - | CM_SCACHEFLAG_OUTOFSPACE - | CM_SCACHEFLAG_EACCESS)); + | CM_SCACHEFLAG_OUTOFSPACE)); scp->serverModTime = 0; scp->dataVersion = CM_SCACHE_VERSION_BAD; scp->bufDataVersionLow = CM_SCACHE_VERSION_BAD; @@ -1324,6 +1331,10 @@ long cm_SyncOp(cm_scache_t *scp, cm_buf_t *bufp, cm_user_t *userp, cm_req_t *req if ((flags & CM_SCACHESYNC_FORCECB) || !cm_HaveCallback(scp)) { osi_Log1(afsd_logp, "CM SyncOp getting callback on scp 0x%p", scp); + + if (cm_EAccesFindEntry(userp, &scp->fid)) + return CM_ERROR_NOACCESS; + if (bufLocked) lock_ReleaseMutex(&bufp->mx); code = cm_GetCallback(scp, userp, reqp, (flags & CM_SCACHESYNC_FORCECB)?1:0); @@ -1655,7 +1666,7 @@ void cm_MergeStatus(cm_scache_t *dscp, case UAEACCES: case EPERM: case UAEPERM: - _InterlockedOr(&scp->flags, CM_SCACHEFLAG_EACCESS); + cm_EAccesAddEntry(userp, &scp->fid, &dscp->fid); } osi_Log2(afsd_logp, "Merge, Failure scp 0x%p code 0x%x", scp, statusp->errorCode); @@ -1689,8 +1700,6 @@ void cm_MergeStatus(cm_scache_t *dscp, if (RDR_Initialized) rdr_invalidate = 1; - } else { - _InterlockedAnd(&scp->flags, ~CM_SCACHEFLAG_EACCESS); } dataVersion = statusp->dataVersionHigh; @@ -1953,6 +1962,14 @@ void cm_MergeStatus(cm_scache_t *dscp, } } + /* Remove cached EACCES / EPERM errors if the file is a directory */ + if (scp->fileType == CM_SCACHETYPE_DIRECTORY && + !(volp && (volp->flags & CM_VOLUMEFLAG_DFS_VOLUME)) && + !cm_accessPerFileCheck) + { + cm_EAccesClearParentEntries(&scp->fid); + } + done: if (volp) cm_PutVolume(volp); diff --git a/src/WINNT/afsd/cm_scache.h b/src/WINNT/afsd/cm_scache.h index 15b6d2d0e..08c90ddad 100644 --- a/src/WINNT/afsd/cm_scache.h +++ b/src/WINNT/afsd/cm_scache.h @@ -291,7 +291,6 @@ typedef struct cm_scache { #define CM_SCACHEFLAG_ANYWATCH \ (CM_SCACHEFLAG_WATCHED | CM_SCACHEFLAG_WATCHEDSUBTREE) -#define CM_SCACHEFLAG_EACCESS 0x200000 /* Bulk Stat returned EACCES */ #define CM_SCACHEFLAG_SMB_FID 0x400000 #define CM_SCACHEFLAG_LOCAL 0x800000 /* Locally modified */ #define CM_SCACHEFLAG_BULKREADING 0x1000000/* Bulk read in progress */ diff --git a/src/WINNT/afsd/cm_user.c b/src/WINNT/afsd/cm_user.c index d63ec1283..9f209078e 100644 --- a/src/WINNT/afsd/cm_user.c +++ b/src/WINNT/afsd/cm_user.c @@ -188,15 +188,13 @@ void cm_CheckTokenCache(time_t now) } _InterlockedAnd(&ucellp->flags, ~CM_UCELLFLAG_RXKAD); ucellp->gen++; - bExpired=TRUE; + lock_ReleaseMutex(&userp->mx); + cm_ResetACLCache(ucellp->cellp, userp); + lock_ObtainMutex(&userp->mx); } } } lock_ReleaseMutex(&userp->mx); - if (bExpired) { - bExpired=FALSE; - cm_ResetACLCache(NULL, userp); - } } } lock_ReleaseRead(&smb_rctLock); diff --git a/src/WINNT/afsd/cm_vnodeops.c b/src/WINNT/afsd/cm_vnodeops.c index 0da77a692..411f53a4f 100644 --- a/src/WINNT/afsd/cm_vnodeops.c +++ b/src/WINNT/afsd/cm_vnodeops.c @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -2350,7 +2351,7 @@ long cm_TryBulkProc(cm_scache_t *scp, cm_dirEntry_t *dep, void *rockp, if (tscp) { if (lock_TryWrite(&tscp->rw)) { /* we have an entry that we can look at */ - if (!(tscp->flags & CM_SCACHEFLAG_EACCESS) && cm_HaveCallback(tscp)) { + if (!cm_EAccesFindEntry(bsp->userp, &tscp->fid) && cm_HaveCallback(tscp)) { /* we have a callback on it. Don't bother * fetching this stat entry, since we're happy * with the info we have. @@ -2516,6 +2517,13 @@ cm_TryBulkStatRPC(cm_scache_t *dscp, cm_bulkStat_t *bbp, cm_user_t *userp, cm_re if (inlinebulk && (&bbp->stats[j])->errorCode) { cm_req_t treq = *reqp; cm_Analyze(NULL, userp, &treq, &tfid, 0, &volSync, NULL, &cbReq, (&bbp->stats[j])->errorCode); + switch ((&bbp->stats[j])->errorCode) { + case EACCES: + case UAEACCES: + case EPERM: + case UAEPERM: + cm_EAccesAddEntry(userp, &tfid, &dscp->fid); + } } else { code = cm_GetSCache(&tfid, &dscp->fid, &scp, userp, reqp); if (code != 0) @@ -2547,7 +2555,7 @@ cm_TryBulkStatRPC(cm_scache_t *dscp, cm_bulkStat_t *bbp, cm_user_t *userp, cm_re if ((scp->cbServerp == NULL && !(scp->flags & (CM_SCACHEFLAG_FETCHING | CM_SCACHEFLAG_STORING | CM_SCACHEFLAG_SIZESTORING))) || (scp->flags & CM_SCACHEFLAG_PURERO) || - (scp->flags & CM_SCACHEFLAG_EACCESS)) + cm_EAccesFindEntry(userp, &scp->fid)) { lock_ConvertRToW(&scp->rw); lostRace = cm_EndCallbackGrantingCall(scp, &cbReq, @@ -2589,6 +2597,7 @@ cm_TryBulkStat(cm_scache_t *dscp, osi_hyper_t *offsetp, cm_user_t *userp, bbp = malloc(sizeof(cm_bulkStat_t)); memset(bbp, 0, sizeof(cm_bulkStat_t)); + bbp->userp = userp; bbp->bufOffset = *offsetp; lock_ReleaseWrite(&dscp->rw); diff --git a/src/WINNT/afsd/cm_vnodeops.h b/src/WINNT/afsd/cm_vnodeops.h index 47e4fd446..07080d285 100644 --- a/src/WINNT/afsd/cm_vnodeops.h +++ b/src/WINNT/afsd/cm_vnodeops.h @@ -254,6 +254,7 @@ extern int cm_IsSpaceAvailable(cm_fid_t * fidp, osi_hyper_t *sizep, cm_user_t *u /* rock for bulk stat calls */ typedef struct cm_bulkStat { + cm_user_t *userp; osi_hyper_t bufOffset; /* only do it for things in this buffer page */ /* info for the actual call */ diff --git a/src/WINNT/afsd/smb.c b/src/WINNT/afsd/smb.c index 6836ff907..21288b5f4 100644 --- a/src/WINNT/afsd/smb.c +++ b/src/WINNT/afsd/smb.c @@ -4660,6 +4660,7 @@ smb_ApplyDirListPatches(cm_scache_t * dscp, smb_dirListPatch_t **dirPatchespp, cm_bulkStat_t *bsp = malloc(sizeof(cm_bulkStat_t)); memset(bsp, 0, sizeof(cm_bulkStat_t)); + bsp->userp = userp; for (patchp = *dirPatchespp, count=0; patchp; @@ -4670,7 +4671,7 @@ smb_ApplyDirListPatches(cm_scache_t * dscp, smb_dirListPatch_t **dirPatchespp, if (tscp) { if (lock_TryWrite(&tscp->rw)) { /* we have an entry that we can look at */ - if (!(tscp->flags & CM_SCACHEFLAG_EACCESS) && cm_HaveCallback(tscp)) { + if (!cm_EAccesFindEntry(userp, &tscp->fid) && cm_HaveCallback(tscp)) { /* we have a callback on it. Don't bother * fetching this stat entry, since we're happy * with the info we have. @@ -4692,6 +4693,7 @@ smb_ApplyDirListPatches(cm_scache_t * dscp, smb_dirListPatch_t **dirPatchespp, if (bsp->counter == AFSCBMAX) { code = cm_TryBulkStatRPC(dscp, bsp, userp, reqp); memset(bsp, 0, sizeof(cm_bulkStat_t)); + bsp->userp = userp; } } @@ -4720,7 +4722,7 @@ smb_ApplyDirListPatches(cm_scache_t * dscp, smb_dirListPatch_t **dirPatchespp, continue; } lock_ObtainWrite(&scp->rw); - if (mustFake || (scp->flags & CM_SCACHEFLAG_EACCESS) || !cm_HaveCallback(scp)) { + if (mustFake || cm_EAccesFindEntry(userp, &scp->fid) || !cm_HaveCallback(scp)) { lock_ReleaseWrite(&scp->rw); /* set the attribute */ diff --git a/src/WINNT/afsd/smb3.c b/src/WINNT/afsd/smb3.c index e2eae0d89..aea349ed6 100644 --- a/src/WINNT/afsd/smb3.c +++ b/src/WINNT/afsd/smb3.c @@ -4577,6 +4577,7 @@ smb_ApplyV3DirListPatches(cm_scache_t *dscp, smb_dirListPatch_t **dirPatchespp, cm_bulkStat_t *bsp = malloc(sizeof(cm_bulkStat_t)); memset(bsp, 0, sizeof(cm_bulkStat_t)); + bsp->userp = userp; for (patchp = *dirPatchespp, count=0; patchp; @@ -4604,7 +4605,7 @@ smb_ApplyV3DirListPatches(cm_scache_t *dscp, smb_dirListPatch_t **dirPatchespp, continue; } #endif /* AFS_FREELANCE_CLIENT */ - if (!(tscp->flags & CM_SCACHEFLAG_EACCESS) && cm_HaveCallback(tscp)) { + if (!cm_EAccesFindEntry(userp, &tscp->fid) && cm_HaveCallback(tscp)) { /* we have a callback on it. Don't bother * fetching this stat entry, since we're happy * with the info we have. @@ -4626,6 +4627,7 @@ smb_ApplyV3DirListPatches(cm_scache_t *dscp, smb_dirListPatch_t **dirPatchespp, if (bsp->counter == AFSCBMAX) { code = cm_TryBulkStatRPC(dscp, bsp, userp, reqp); memset(bsp, 0, sizeof(cm_bulkStat_t)); + bsp->userp = userp; } } @@ -4679,7 +4681,7 @@ smb_ApplyV3DirListPatches(cm_scache_t *dscp, smb_dirListPatch_t **dirPatchespp, continue; lock_ObtainWrite(&scp->rw); - if (mustFake || (scp->flags & CM_SCACHEFLAG_EACCESS) || !cm_HaveCallback(scp)) { + if (mustFake || cm_EAccesFindEntry(userp, &scp->fid) || !cm_HaveCallback(scp)) { lock_ReleaseWrite(&scp->rw); /* Plug in fake timestamps. A time stamp of 0 causes 'invalid parameter' diff --git a/src/WINNT/afsrdr/user/RDRFunction.c b/src/WINNT/afsrdr/user/RDRFunction.c index 03475c731..cc5a4c7c5 100644 --- a/src/WINNT/afsrdr/user/RDRFunction.c +++ b/src/WINNT/afsrdr/user/RDRFunction.c @@ -404,7 +404,7 @@ RDR_PopulateCurrentEntry( IN AFSDirEnumEntry * pCurrentEntry, * status information. If not, perform a bulk status lookup of multiple * entries in order to reduce the number of RPCs issued to the file server. */ - if ((scp->flags & CM_SCACHEFLAG_EACCESS)) + if (cm_EAccesFindEntry(userp, &scp->fid)) bMustFake = TRUE; else if (!cm_HaveCallback(scp)) { lock_ReleaseWrite(&scp->rw); @@ -1759,7 +1759,7 @@ RDR_CleanupFileEntry( IN cm_user_t *userp, Fid.unique = FileId.Unique; Fid.hash = FileId.Hash; - code = cm_GetSCache(&Fid, &dscp->fid, &scp, userp, &req); + code = cm_GetSCache(&Fid, dscp ? &dscp->fid : NULL, &scp, userp, &req); if (code) { osi_Log1(afsd_logp, "RDR_CleanupFileEntry cm_GetSCache object FID failure code=0x%x", code);