Windows: Add per object per user EACCES caching
authorJeffrey Altman <jaltman@your-file-system.com>
Sun, 1 Apr 2012 05:17:21 +0000 (01:17 -0400)
committerJeffrey Altman <jaltman@secure-endpoints.com>
Fri, 6 Apr 2012 15:03:06 +0000 (08:03 -0700)
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 <jaltman@secure-endpoints.com>
Tested-by: Jeffrey Altman <jaltman@secure-endpoints.com>

19 files changed:
src/WINNT/afsd/NTMakefile
src/WINNT/afsd/afsd.h
src/WINNT/afsd/afsd_init.c
src/WINNT/afsd/cm.h
src/WINNT/afsd/cm_access.c
src/WINNT/afsd/cm_aclent.c
src/WINNT/afsd/cm_btree.c
src/WINNT/afsd/cm_callback.c
src/WINNT/afsd/cm_daemon.c
src/WINNT/afsd/cm_eacces.c [new file with mode: 0644]
src/WINNT/afsd/cm_eacces.h [new file with mode: 0644]
src/WINNT/afsd/cm_scache.c
src/WINNT/afsd/cm_scache.h
src/WINNT/afsd/cm_user.c
src/WINNT/afsd/cm_vnodeops.c
src/WINNT/afsd/cm_vnodeops.h
src/WINNT/afsd/smb.c
src/WINNT/afsd/smb3.c
src/WINNT/afsrdr/user/RDRFunction.c

index 6d6646c..b9e4442 100644 (file)
@@ -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 \
index 65f2d18..feabae2 100644 (file)
@@ -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"
index 72bf229..ba25a40 100644 (file)
@@ -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 */
index 5d9f293..e1711ad 100644 (file)
@@ -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
index 79692b1..17c6c7c 100644 (file)
@@ -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;
 }
index b9f06fc..3c01da5 100644 (file)
@@ -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++) {
index eb5cbc5..4f7048d 100644 (file)
@@ -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.
index 5c326c4..9604ac1 100644 (file)
@@ -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);
index 2e5ef84..597a266 100644 (file)
@@ -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 (file)
index 0000000..603a4c8
--- /dev/null
@@ -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 <afsconfig.h>
+#include <afs/param.h>
+#include <roken.h>
+
+#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 (file)
index 0000000..481b5b2
--- /dev/null
@@ -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 <osi.h>
+#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_ */
index b94f80f..9b65dae 100644 (file)
@@ -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);
index 15b6d2d..08c90dd 100644 (file)
@@ -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 */
index d63ec12..9f20907 100644 (file)
@@ -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);
index 0da77a6..411f53a 100644 (file)
@@ -12,6 +12,7 @@
 #include <roken.h>
 
 #include <afs/stds.h>
+#include <afs/unified_afs.h>
 
 #include <windows.h>
 #include <winsock2.h>
@@ -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);
index 47e4fd4..07080d2 100644 (file)
@@ -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 */
index 6836ff9..21288b5 100644 (file)
@@ -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 */
index e2eae0d..aea349e 100644 (file)
@@ -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'
index 03475c7..cc5a4c7 100644 (file)
@@ -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);