afs: Avoid incorrect size when fetching beyond EOF
[openafs.git] / src / afs / afs_dcache.c
index 3fc0bb5..9ccadae 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright 2000, International Business Machines Corporation and others.
- *$All Rights Reserved.
+ * All Rights Reserved.
  *
  * This software has been released under the terms of the IBM Public
  * License.  For details, see the LICENSE file in the top-level source
 #include "afs/afs_cbqueue.h"
 #include "afs/afs_osidnlc.h"
 
+#include <opr/ffs.h>
+
 /* Forward declarations. */
 static void afs_GetDownD(int anumber, int *aneedSpace, afs_int32 buckethint);
-static void afs_FreeDiscardedDCache(void);
+static int afs_FreeDiscardedDCache(void);
 static void afs_DiscardDCache(struct dcache *);
 static void afs_FreeDCache(struct dcache *);
 /* For split cache */
@@ -154,6 +156,29 @@ struct afs_cacheOps afs_MemCacheOps = {
 int cacheDiskType;             /*Type of backing disk for cache */
 struct afs_cacheOps *afs_cacheType;
 
+
+/*
+ * The PFlush algorithm makes use of the fact that Fid.Unique is not used in
+ * below hash algorithms.  Change it if need be so that flushing algorithm
+ * doesn't move things from one hash chain to another.
+ */
+/*Vnode, Chunk -> Hash table index */
+int DCHash(struct VenusFid *fid, afs_int32 chunk)
+{
+    afs_uint32 buf[3];
+
+    buf[0] = fid->Fid.Volume;
+    buf[1] = fid->Fid.Vnode;
+    buf[2] = chunk;
+    return opr_jhash(buf, 3, 0) & (afs_dhashsize - 1);
+}
+/*Vnode -> Other hash table index */
+int DVHash(struct VenusFid *fid)
+{
+    return opr_jhash_int2(fid->Fid.Volume, fid->Fid.Vnode, 0) &
+       (afs_dhashsize - 1);
+}
+
 /*!
  * Where is this vcache's entry associated dcache located/
  * \param avc The vcache entry.
@@ -396,6 +421,27 @@ static struct CTD_stats {
 u_int afs_min_cache = 0;
 
 /*!
+ * If there are waiters for the cache to drain, wake them if
+ * the number of free or discarded cache blocks reaches the
+ * CM_CACHESIZEDDRAINEDPCT limit.
+ *
+ * \note Environment:
+ *     This routine must be called with the afs_xdcache lock held
+ *     (in write mode).
+ */
+static void
+afs_WakeCacheWaitersIfDrained(void)
+{
+    if (afs_WaitForCacheDrain) {
+       if ((afs_blocksUsed - afs_blocksDiscarded) <=
+           PERCENT(CM_CACHESIZEDRAINEDPCT, afs_cacheBlocks)) {
+           afs_WaitForCacheDrain = 0;
+           afs_osi_Wakeup(&afs_WaitForCacheDrain);
+       }
+    }
+}
+
+/*!
  * Keeps the cache clean and free by truncating uneeded files, when used.
  * \param
  * \return
@@ -416,7 +462,7 @@ afs_CacheTruncateDaemon(void)
     while (1) {
        cb_lowat = PERCENT((CM_DCACHESPACEFREEPCT - CM_DCACHEEXTRAPCT), afs_cacheBlocks);
        ObtainWriteLock(&afs_xdcache, 266);
-       if (afs_CacheTooFull) {
+       if (afs_CacheTooFull || afs_WaitForCacheDrain) {
            int space_needed, slots_needed;
            /* if we get woken up, we should try to clean something out */
            for (counter = 0; counter < 10; counter++) {
@@ -431,14 +477,15 @@ afs_CacheTruncateDaemon(void)
                if (slots_needed || space_needed)
                    afs_GetDownD(slots_needed, &space_needed, 0);
                if ((space_needed <= 0) && (slots_needed <= 0)) {
-                   afs_CacheTooFull = 0;
                    break;
                }
                if (afs_termState == AFSOP_STOP_TRUNCDAEMON)
                    break;
            }
-           if (!afs_CacheIsTooFull())
+           if (!afs_CacheIsTooFull()) {
                afs_CacheTooFull = 0;
+               afs_WakeCacheWaitersIfDrained();
+           }
        }       /* end of cache cleanup */
        ReleaseWriteLock(&afs_xdcache);
 
@@ -459,7 +506,16 @@ afs_CacheTruncateDaemon(void)
         */
        while (afs_blocksDiscarded && !afs_WaitForCacheDrain
               && (afs_termState != AFSOP_STOP_TRUNCDAEMON)) {
-           afs_FreeDiscardedDCache();
+           int code = afs_FreeDiscardedDCache();
+           if (code) {
+               /* If we can't free any discarded dcache entries, that's okay.
+                * We're just doing this in the background; if someone needs
+                * discarded entries freed, they will try it themselves and/or
+                * signal us that the cache is too full. In any case, we'll
+                * try doing this again the next time we run through the loop.
+                */
+               break;
+           }
        }
 
        /* See if we need to continue to run. Someone may have
@@ -978,14 +1034,6 @@ afs_FlushDCache(struct dcache *adc)
     } else {
        afs_FreeDCache(adc);
     }
-
-    if (afs_WaitForCacheDrain) {
-       if (afs_blocksUsed <=
-           PERCENT(CM_CACHESIZEDRAINEDPCT, afs_cacheBlocks)) {
-           afs_WaitForCacheDrain = 0;
-           afs_osi_Wakeup(&afs_WaitForCacheDrain);
-       }
-    }
 }                              /*afs_FlushDCache */
 
 
@@ -1010,13 +1058,7 @@ afs_FreeDCache(struct dcache *adc)
     afs_indexFlags[adc->index] |= IFFree;
     adc->dflags |= DFEntryMod;
 
-    if (afs_WaitForCacheDrain) {
-       if ((afs_blocksUsed - afs_blocksDiscarded) <=
-           PERCENT(CM_CACHESIZEDRAINEDPCT, afs_cacheBlocks)) {
-           afs_WaitForCacheDrain = 0;
-           afs_osi_Wakeup(&afs_WaitForCacheDrain);
-       }
-    }
+    afs_WakeCacheWaitersIfDrained();
 }                              /* afs_FreeDCache */
 
 /*!
@@ -1056,20 +1098,44 @@ afs_DiscardDCache(struct dcache *adc)
     adc->dflags |= DFEntryMod;
     afs_indexFlags[adc->index] |= IFDiscarded;
 
-    if (afs_WaitForCacheDrain) {
-       if ((afs_blocksUsed - afs_blocksDiscarded) <=
-           PERCENT(CM_CACHESIZEDRAINEDPCT, afs_cacheBlocks)) {
-           afs_WaitForCacheDrain = 0;
-           afs_osi_Wakeup(&afs_WaitForCacheDrain);
+    afs_WakeCacheWaitersIfDrained();
+}                              /*afs_DiscardDCache */
+
+/**
+ * Get a dcache entry from the discard or free list
+ *
+ * @param[in] indexp  A pointer to the head of the dcache free list or discard
+ *                    list (afs_freeDCList, or afs_discardDCList)
+ *
+ * @return A dcache from that list, or NULL if none could be retrieved.
+ *
+ * @pre afs_xdcache is write-locked
+ */
+static struct dcache *
+afs_GetDSlotFromList(afs_int32 *indexp)
+{
+    struct dcache *tdc;
+
+    for ( ; *indexp != NULLIDX; indexp = &afs_dvnextTbl[*indexp]) {
+       tdc = afs_GetUnusedDSlot(*indexp);
+       if (tdc) {
+           osi_Assert(tdc->refCount == 1);
+           ReleaseReadLock(&tdc->tlock);
+           *indexp = afs_dvnextTbl[tdc->index];
+           afs_dvnextTbl[tdc->index] = NULLIDX;
+           return tdc;
        }
     }
-
-}                              /*afs_DiscardDCache */
+    return NULL;
+}
 
 /*!
  * Free the next element on the list of discarded cache elements.
+ *
+ * Returns -1 if we encountered an error preventing us from freeing a
+ * discarded dcache, or 0 on success.
  */
-static void
+static int
 afs_FreeDiscardedDCache(void)
 {
     struct dcache *tdc;
@@ -1081,18 +1147,18 @@ afs_FreeDiscardedDCache(void)
     ObtainWriteLock(&afs_xdcache, 510);
     if (!afs_blocksDiscarded) {
        ReleaseWriteLock(&afs_xdcache);
-       return;
+       return 0;
     }
 
     /*
      * Get an entry from the list of discarded cache elements
      */
-    tdc = afs_GetNewDSlot(afs_discardDCList);
-    osi_Assert(tdc->refCount == 1);
-    ReleaseReadLock(&tdc->tlock);
+    tdc = afs_GetDSlotFromList(&afs_discardDCList);
+    if (!tdc) {
+       ReleaseWriteLock(&afs_xdcache);
+       return -1;
+    }
 
-    afs_discardDCList = afs_dvnextTbl[tdc->index];
-    afs_dvnextTbl[tdc->index] = NULLIDX;
     afs_discardDCCount--;
     size = ((tdc->f.chunkBytes + afs_fsfragsize) ^ afs_fsfragsize) >> 10;      /* round up */
     afs_blocksDiscarded -= size;
@@ -1120,6 +1186,8 @@ afs_FreeDiscardedDCache(void)
     ReleaseWriteLock(&tdc->lock);
     afs_PutDCache(tdc);
     ReleaseWriteLock(&afs_xdcache);
+
+    return 0;
 }
 
 /*!
@@ -1137,7 +1205,14 @@ afs_MaybeFreeDiscardedDCache(void)
     while (afs_blocksDiscarded
           && (afs_blocksUsed >
               PERCENT(CM_WAITFORDRAINPCT, afs_cacheBlocks))) {
-       afs_FreeDiscardedDCache();
+       int code = afs_FreeDiscardedDCache();
+       if (code) {
+           /* Callers depend on us to get the afs_blocksDiscarded count down.
+            * If we cannot do that, the callers can spin by calling us over
+            * and over. Panic for now until we can figure out something
+            * better. */
+           osi_Panic("Error freeing discarded dcache");
+       }
     }
     return 0;
 }
@@ -1314,7 +1389,12 @@ afs_TryToSmush(struct vcache *avc, afs_ucred_t *acred, int sync)
        if (afs_indexUnique[index] == avc->f.fid.Fid.Unique) {
            int releaseTlock = 1;
            tdc = afs_GetValidDSlot(index);
-           if (!tdc) osi_Panic("afs_TryToSmush tdc");
+           if (!tdc) {
+               /* afs_TryToSmush is best-effort; we may not actually discard
+                * everything, so failure to discard a dcache due to an i/o
+                * error is okay. */
+               continue;
+           }
            if (!FidCmp(&tdc->f.fid, &avc->f.fid)) {
                if (sync) {
                    if ((afs_indexFlags[index] & IFDataMod) == 0
@@ -1456,17 +1536,21 @@ afs_FindDCache(struct vcache *avc, afs_size_t abyte)
      */
     i = DCHash(&avc->f.fid, chunk);
     ObtainWriteLock(&afs_xdcache, 278);
-    for (index = afs_dchashTbl[i]; index != NULLIDX;) {
+    for (index = afs_dchashTbl[i]; index != NULLIDX; index = afs_dcnextTbl[index]) {
        if (afs_indexUnique[index] == avc->f.fid.Fid.Unique) {
            tdc = afs_GetValidDSlot(index);
-           if (!tdc) osi_Panic("afs_FindDCache tdc");
+           if (!tdc) {
+               /* afs_FindDCache is best-effort; we may not find the given
+                * file/offset, so if we cannot find the given dcache due to
+                * i/o errors, that is okay. */
+               continue;
+           }
            ReleaseReadLock(&tdc->tlock);
            if (!FidCmp(&tdc->f.fid, &avc->f.fid) && chunk == tdc->f.chunk) {
                break;          /* leaving refCount high for caller */
            }
            afs_PutDCache(tdc);
        }
-       index = afs_dcnextTbl[index];
     }
     if (index != NULLIDX) {
        hset(afs_indexTimes[tdc->index], afs_indexCounter);
@@ -1478,6 +1562,53 @@ afs_FindDCache(struct vcache *avc, afs_size_t abyte)
     return NULL;
 }                              /*afs_FindDCache */
 
+/* only call these from afs_AllocDCache() */
+static struct dcache *
+afs_AllocFreeDSlot(void)
+{
+    struct dcache *tdc;
+
+    tdc = afs_GetDSlotFromList(&afs_freeDCList);
+    if (!tdc) {
+       return NULL;
+    }
+    afs_indexFlags[tdc->index] &= ~IFFree;
+    ObtainWriteLock(&tdc->lock, 604);
+    afs_freeDCCount--;
+
+    return tdc;
+}
+static struct dcache *
+afs_AllocDiscardDSlot(afs_int32 lock)
+{
+    struct dcache *tdc;
+    afs_uint32 size = 0;
+    struct osi_file *file;
+
+    tdc = afs_GetDSlotFromList(&afs_discardDCList);
+    if (!tdc) {
+       return NULL;
+    }
+    afs_indexFlags[tdc->index] &= ~IFDiscarded;
+    ObtainWriteLock(&tdc->lock, 605);
+    afs_discardDCCount--;
+    size =
+       ((tdc->f.chunkBytes +
+         afs_fsfragsize) ^ afs_fsfragsize) >> 10;
+    tdc->f.states &= ~(DRO|DBackup|DRW);
+    afs_DCMoveBucket(tdc, size, 0);
+    afs_blocksDiscarded -= size;
+    afs_stats_cmperf.cacheBlocksDiscarded = afs_blocksDiscarded;
+    if ((lock & 2)) {
+       /* Truncate the chunk so zeroes get filled properly */
+       file = afs_CFileOpen(&tdc->f.inode);
+       afs_CFileTruncate(file, 0);
+       afs_CFileClose(file);
+       afs_AdjustSize(tdc, 0);
+    }
+
+    return tdc;
+}
 
 /*!
  * Get a fresh dcache from the free or discarded list.
@@ -1500,42 +1631,23 @@ afs_AllocDCache(struct vcache *avc, afs_int32 chunk, afs_int32 lock,
                struct VenusFid *ashFid)
 {
     struct dcache *tdc = NULL;
-    afs_uint32 size = 0;
-    struct osi_file *file;
 
-    if (afs_discardDCList == NULLIDX
-       || ((lock & 2) && afs_freeDCList != NULLIDX)) {
-
-       afs_indexFlags[afs_freeDCList] &= ~IFFree;
-       tdc = afs_GetNewDSlot(afs_freeDCList);
-       osi_Assert(tdc->refCount == 1);
-       ReleaseReadLock(&tdc->tlock);
-       ObtainWriteLock(&tdc->lock, 604);
-       afs_freeDCList = afs_dvnextTbl[tdc->index];
-       afs_freeDCCount--;
+    /* if (lock & 2), prefer 'free' dcaches; otherwise, prefer 'discard'
+     * dcaches. In either case, try both if our first choice doesn't work. */
+    if ((lock & 2)) {
+       tdc = afs_AllocFreeDSlot();
+       if (!tdc) {
+           tdc = afs_AllocDiscardDSlot(lock);
+       }
     } else {
-       afs_indexFlags[afs_discardDCList] &= ~IFDiscarded;
-       tdc = afs_GetNewDSlot(afs_discardDCList);
-       osi_Assert(tdc->refCount == 1);
-       ReleaseReadLock(&tdc->tlock);
-       ObtainWriteLock(&tdc->lock, 605);
-       afs_discardDCList = afs_dvnextTbl[tdc->index];
-       afs_discardDCCount--;
-       size =
-           ((tdc->f.chunkBytes +
-             afs_fsfragsize) ^ afs_fsfragsize) >> 10;
-       tdc->f.states &= ~(DRO|DBackup|DRW);
-       afs_DCMoveBucket(tdc, size, 0);
-       afs_blocksDiscarded -= size;
-       afs_stats_cmperf.cacheBlocksDiscarded = afs_blocksDiscarded;
-       if (lock & 2) {
-           /* Truncate the chunk so zeroes get filled properly */
-           file = afs_CFileOpen(&tdc->f.inode);
-           afs_CFileTruncate(file, 0);
-           afs_CFileClose(file);
-           afs_AdjustSize(tdc, 0);
+       tdc = afs_AllocDiscardDSlot(lock);
+       if (!tdc) {
+           tdc = afs_AllocFreeDSlot();
        }
     }
+    if (!tdc) {
+       return NULL;
+    }
 
     /*
      * Locks held:
@@ -1633,7 +1745,6 @@ afs_GetDCache(struct vcache *avc, afs_size_t abyte,
     afs_int32 index;
     afs_int32 us;
     afs_int32 chunk;
-    afs_size_t maxGoodLength;  /* amount of good data at server */
     afs_size_t Position = 0;
     afs_int32 size, tlen;      /* size of segment to transfer */
     struct afs_FetchOutput *tsmall = 0;
@@ -1756,6 +1867,7 @@ afs_GetDCache(struct vcache *avc, afs_size_t abyte,
      */
 
     if (!tdc) {                        /* If the hint wasn't the right dcache entry */
+       int dslot_error = 0;
        /*
         * Hash on the [fid, chunk] and get the corresponding dcache index
         * after write-locking the dcache.
@@ -1773,12 +1885,16 @@ afs_GetDCache(struct vcache *avc, afs_size_t abyte,
 
        ObtainWriteLock(&afs_xdcache, 280);
        us = NULLIDX;
-       for (index = afs_dchashTbl[i]; index != NULLIDX;) {
+       for (index = afs_dchashTbl[i]; index != NULLIDX; us = index, index = afs_dcnextTbl[index]) {
            if (afs_indexUnique[index] == avc->f.fid.Fid.Unique) {
                tdc = afs_GetValidDSlot(index);
                if (!tdc) {
-                   ReleaseWriteLock(&afs_xdcache);
-                   goto done;
+                   /* we got an i/o error when trying to get the given dslot,
+                    * but do not bail out just yet; it is possible the dcache
+                    * we're looking for is elsewhere, so it doesn't matter if
+                    * we can't load this one. */
+                   dslot_error = 1;
+                   continue;
                }
                ReleaseReadLock(&tdc->tlock);
                /*
@@ -1801,8 +1917,6 @@ afs_GetDCache(struct vcache *avc, afs_size_t abyte,
                afs_PutDCache(tdc);
                tdc = 0;
            }
-           us = index;
-           index = afs_dcnextTbl[index];
        }
 
        /*
@@ -1818,34 +1932,46 @@ afs_GetDCache(struct vcache *avc, afs_size_t abyte,
            afs_Trace2(afs_iclSetp, CM_TRACE_GETDCACHE1, ICL_TYPE_POINTER,
                       avc, ICL_TYPE_INT32, chunk);
 
-           /* Make sure there is a free dcache entry for us to use */
-           if (afs_discardDCList == NULLIDX && afs_freeDCList == NULLIDX) {
-               while (1) {
-                   if (!setLocks)
-                       avc->f.states |= CDCLock;
-                   /* just need slots */
-                   afs_GetDownD(5, (int *)0, afs_DCGetBucket(avc));
-                   if (!setLocks)
-                       avc->f.states &= ~CDCLock;
-                   if (afs_discardDCList != NULLIDX
-                       || afs_freeDCList != NULLIDX)
-                       break;
-                   /* If we can't get space for 5 mins we give up and panic */
-                   if (++downDCount > 300) {
-                       osi_Panic("getdcache");
-                    }
-                   ReleaseWriteLock(&afs_xdcache);
-                   /*
-                    * Locks held:
-                    * avc->lock(R) if setLocks
-                    * avc->lock(W) if !setLocks
-                    */
-                   afs_osi_Wait(1000, 0, 0);
-                   goto RetryLookup;
-               }
+           if (dslot_error) {
+               /* We couldn't find the dcache we want, but we hit some i/o
+                * errors when trying to find it, so we're not sure if the
+                * dcache we want is in the cache or not. Error out, so we
+                * don't try to possibly create 2 separate dcaches for the
+                * same exact data. */
+               ReleaseWriteLock(&afs_xdcache);
+               goto done;
            }
 
+           if (afs_discardDCList == NULLIDX && afs_freeDCList == NULLIDX) {
+               if (!setLocks)
+                   avc->f.states |= CDCLock;
+               /* just need slots */
+               afs_GetDownD(5, (int *)0, afs_DCGetBucket(avc));
+               if (!setLocks)
+                   avc->f.states &= ~CDCLock;
+           }
            tdc = afs_AllocDCache(avc, chunk, aflags, NULL);
+           if (!tdc) {
+               /* If we can't get space for 5 mins we give up and panic */
+               if (++downDCount > 300)
+                   osi_Panic("getdcache");
+               ReleaseWriteLock(&afs_xdcache);
+               /*
+                * Locks held:
+                * avc->lock(R) if setLocks
+                * avc->lock(W) if !setLocks
+                */
+               afs_osi_Wait(1000, 0, 0);
+               goto RetryLookup;
+           }
+
+           /*
+            * Locks held:
+            * avc->lock(R) if setLocks
+            * avc->lock(W) if !setLocks
+            * tdc->lock(W)
+            * afs_xdcache(W)
+            */
 
            /*
             * Now add to the two hash chains - note that i is still set
@@ -2087,10 +2213,6 @@ afs_GetDCache(struct vcache *avc, afs_size_t abyte,
            goto RetryGetDCache;
        }
 
-       /* Do not fetch data beyond truncPos. */
-       maxGoodLength = avc->f.m.Length;
-       if (avc->f.truncPos < maxGoodLength)
-           maxGoodLength = avc->f.truncPos;
        Position = AFS_CHUNKBASE(abyte);
        if (vType(avc) == VDIR) {
            size = avc->f.m.Length;
@@ -2100,16 +2222,52 @@ afs_GetDCache(struct vcache *avc, afs_size_t abyte,
            }
            size = 999999999;   /* max size for transfer */
        } else {
+           afs_size_t maxGoodLength;
+
+           /* estimate how much data we're expecting back from the server,
+            * and reserve space in the dcache entry for it */
+
+           maxGoodLength = avc->f.m.Length;
+           if (avc->f.truncPos < maxGoodLength)
+               maxGoodLength = avc->f.truncPos;
+
            size = AFS_CHUNKSIZE(abyte);        /* expected max size */
-           /* don't read past end of good data on server */
-           if (Position + size > maxGoodLength)
+            if (Position > maxGoodLength) { /* If we're beyond EOF */
+                size = 0;
+           } else if (Position + size > maxGoodLength) {
                size = maxGoodLength - Position;
-           if (size < 0)
-               size = 0;       /* Handle random races */
+            }
+            osi_Assert(size >= 0);
+
            if (size > tdc->f.chunkBytes) {
-               /* pre-reserve space for file */
+               /* pre-reserve estimated space for file */
                afs_AdjustSize(tdc, size);      /* changes chunkBytes */
-               /* max size for transfer still in size */
+           }
+
+           if (size) {
+               /* For the actual fetch, do not limit the request to the
+                * length of the file. If this results in a read past EOF on
+                * the server, the server will just reply with less data than
+                * requested. If we limit ourselves to only requesting data up
+                * to the avc file length, we open ourselves up to races if the
+                * file is extended on the server at about the same time.
+                *
+                * However, we must restrict ourselves to the avc->f.truncPos
+                * length, since this represents an outstanding local
+                * truncation of the file that will be committed to the
+                * fileserver when we actually write the fileserver contents.
+                * If we do not restrict the fetch length based on
+                * avc->f.truncPos, a different truncate operation extending
+                * the file length could cause the old data after
+                * avc->f.truncPos to reappear, instead of extending the file
+                * with NUL bytes. */
+               size = AFS_CHUNKSIZE(abyte);
+                if (Position > avc->f.truncPos) {
+                    size = 0;
+               } else if (Position + size > avc->f.truncPos) {
+                   size = avc->f.truncPos - Position;
+               }
+                osi_Assert(size >= 0);
            }
        }
        if (afs_mariner && !tdc->f.chunk)
@@ -2132,8 +2290,7 @@ afs_GetDCache(struct vcache *avc, afs_size_t abyte,
                           ICL_TYPE_POINTER, tdc, ICL_TYPE_INT32,
                           tdc->dflags);
        }
-       tsmall =
-           (struct afs_FetchOutput *)osi_AllocLargeSpace(sizeof(struct afs_FetchOutput));
+       tsmall = osi_AllocLargeSpace(sizeof(struct afs_FetchOutput));
        setVcacheStatus = 0;
 #ifndef AFS_NOSTATS
        /*
@@ -2269,6 +2426,13 @@ afs_GetDCache(struct vcache *avc, afs_size_t abyte,
                        afs_PutDCache(tdc);
                        tdc = 0;
                        ReleaseReadLock(&avc->lock);
+
+                       if (tc) {
+                           /* If we have a connection, we must put it back,
+                            * since afs_Analyze will not be called here. */
+                           afs_PutConn(tc, rxconn, SHARED_LOCK);
+                       }
+
                        slowPass = 1;
                        goto RetryGetDCache;
                    }
@@ -2552,11 +2716,7 @@ afs_WriteThroughDSlots(void)
         */
        struct afs_fheader theader;
 
-       theader.magic = AFS_FHMAGIC;
-       theader.firstCSize = AFS_FIRSTCSIZE;
-       theader.otherCSize = AFS_OTHERCSIZE;
-       theader.version = AFS_CI_VERSION;
-       theader.dataSize = sizeof(struct fcache);
+       afs_InitFHeader(&theader);
        afs_osi_Write(afs_cacheInodep, 0, &theader, sizeof(theader));
     }
     ReleaseWriteLock(&afs_xdcache);
@@ -2571,13 +2731,14 @@ afs_WriteThroughDSlots(void)
  *
  * Parameters:
  *     aslot : Dcache slot to look at.
+ *      type : What 'type' of dslot to get; see the dslot_state enum
  *
  * Environment:
  *     Must be called with afs_xdcache write-locked.
  */
 
 struct dcache *
-afs_MemGetDSlot(afs_int32 aslot, int needvalid)
+afs_MemGetDSlot(afs_int32 aslot, dslot_state type)
 {
     struct dcache *tdc;
     int existing = 0;
@@ -2598,7 +2759,14 @@ afs_MemGetDSlot(afs_int32 aslot, int needvalid)
        return tdc;
     }
 
-    osi_Assert(!needvalid);
+    /* if we got here, the given slot is not in memory in our list of known
+     * slots. for memcache, the only place a dslot can exist is in memory, so
+     * if the caller is expecting to get back a known dslot, and we've reached
+     * here, something is very wrong. DSLOT_NEW is the only type of dslot that
+     * may not exist; for all others, the caller assumes the given dslot
+     * already exists. so, 'type' had better be DSLOT_NEW here, or something is
+     * very wrong. */
+    osi_Assert(type == DSLOT_NEW);
 
     if (!afs_freeDSList)
        afs_GetDownDSlot(4);
@@ -2659,12 +2827,13 @@ unsigned int last_error = 0, lasterrtime = 0;
  *
  * Parameters:
  *     aslot : Dcache slot to look at.
+ *      type : What 'type' of dslot to get; see the dslot_state enum
  *
  * Environment:
  *     afs_xdcache lock write-locked.
  */
 struct dcache *
-afs_UFSGetDSlot(afs_int32 aslot, int needvalid)
+afs_UFSGetDSlot(afs_int32 aslot, dslot_state type)
 {
     afs_int32 code;
     struct dcache *tdc;
@@ -2723,15 +2892,21 @@ afs_UFSGetDSlot(afs_int32 aslot, int needvalid)
        entryok = 0;
 #if defined(KERNEL_HAVE_UERROR)
        last_error = getuerror();
+#else
+       last_error = code;
 #endif
        lasterrtime = osi_Time();
-       if (needvalid) {
+       if (type != DSLOT_NEW) {
+           /* If we are requesting a non-DSLOT_NEW slot, this is an error.
+            * non-DSLOT_NEW slots are supposed to already exist, so if we
+            * failed to read in the slot, something is wrong. */
            struct osi_stat tstat;
            if (afs_osi_Stat(afs_cacheInodep, &tstat)) {
                tstat.size = -1;
            }
-           afs_warn("afs: disk cache read error in CacheItems off %d/%d "
-                    "code %d/%d\n",
+           afs_warn("afs: disk cache read error in CacheItems slot %d "
+                    "off %d/%d code %d/%d\n",
+                    (int)aslot,
                     off, (int)tstat.size,
                     (int)code, (int)sizeof(struct fcache));
            /* put tdc back on the free dslot list */
@@ -2744,18 +2919,26 @@ afs_UFSGetDSlot(afs_int32 aslot, int needvalid)
     }
     if (!afs_CellNumValid(tdc->f.fid.Cell)) {
        entryok = 0;
-       if (needvalid) {
+       if (type == DSLOT_VALID) {
            osi_Panic("afs: needed valid dcache but index %d off %d has "
                      "invalid cell num %d\n",
                      (int)aslot, off, (int)tdc->f.fid.Cell);
        }
     }
 
-    if (needvalid && tdc->f.fid.Fid.Volume == 0) {
+    if (type == DSLOT_VALID && tdc->f.fid.Fid.Volume == 0) {
        osi_Panic("afs: invalid zero-volume dcache entry at slot %d off %d",
                  (int)aslot, off);
     }
 
+    if (type == DSLOT_UNUSED) {
+       /* the requested dslot is known to exist, but contain invalid data
+        * (this happens when we're using a dslot from the free or discard
+        * list). be sure not to re-use the data in it, so force invalidation.
+        */
+       entryok = 0;
+    }
+
     if (!entryok) {
        tdc->f.fid.Cell = 0;
        tdc->f.fid.Fid.Volume = 0;
@@ -2766,14 +2949,12 @@ afs_UFSGetDSlot(afs_int32 aslot, int needvalid)
        tdc->f.states &= ~(DRO|DBackup|DRW);
        afs_DCMoveBucket(tdc, 0, 0);
     } else {
-       if (&tdc->f != 0) {
-           if (tdc->f.states & DRO) {
-               afs_DCMoveBucket(tdc, 0, 2);
-           } else if (tdc->f.states & DBackup) {
-               afs_DCMoveBucket(tdc, 0, 1);
-           } else {
-               afs_DCMoveBucket(tdc, 0, 1);
-           }
+       if (tdc->f.states & DRO) {
+           afs_DCMoveBucket(tdc, 0, 2);
+       } else if (tdc->f.states & DBackup) {
+           afs_DCMoveBucket(tdc, 0, 1);
+       } else {
+           afs_DCMoveBucket(tdc, 0, 1);
        }
     }
     tdc->refCount = 1;
@@ -2892,7 +3073,7 @@ afs_wakeup(struct vcache *avc)
             * is already being handled by the higher-level code.
             */
            if ((avc->f.states & CSafeStore) == 0) {
-               tb->code = 0;
+               tb->code_raw = tb->code_checkcode = 0;
                tb->flags |= BUVALID;
                if (tb->flags & BUWAIT) {
                    tb->flags &= ~BUWAIT;
@@ -2905,7 +3086,6 @@ afs_wakeup(struct vcache *avc)
     return 0;
 }
 
-
 /*!
  * Given a file name and inode, set up that file to be an
  * active member in the AFS cache.  This also involves checking
@@ -2939,56 +3119,68 @@ afs_InitCacheFile(char *afile, ino_t ainode)
 
     ObtainWriteLock(&tdc->lock, 621);
     ObtainWriteLock(&afs_xdcache, 622);
-    if (afile) {
-       code = afs_LookupInodeByPath(afile, &tdc->f.inode.ufs, NULL);
-       if (code) {
-           ReleaseWriteLock(&afs_xdcache);
-           ReleaseWriteLock(&tdc->lock);
-           afs_PutDCache(tdc);
-           return code;
-       }
+    if (!afile && !ainode) {
+       tfile = NULL;
+       fileIsBad = 1;
     } else {
-       /* Add any other 'complex' inode types here ... */
+       if (afile) {
+           code = afs_LookupInodeByPath(afile, &tdc->f.inode.ufs, NULL);
+           if (code) {
+               ReleaseWriteLock(&afs_xdcache);
+               ReleaseWriteLock(&tdc->lock);
+               afs_PutDCache(tdc);
+               return code;
+           }
+       } else {
+           /* Add any other 'complex' inode types here ... */
 #if !defined(AFS_LINUX26_ENV) && !defined(AFS_CACHE_VNODE_PATH)
-       tdc->f.inode.ufs = ainode;
+           tdc->f.inode.ufs = ainode;
 #else
-       osi_Panic("Can't init cache with inode numbers when complex inodes are "
-                 "in use\n");
+           osi_Panic("Can't init cache with inode numbers when complex inodes are "
+                     "in use\n");
 #endif
-    }
-    fileIsBad = 0;
-    if ((tdc->f.states & DWriting) || tdc->f.fid.Fid.Volume == 0)
-       fileIsBad = 1;
-    tfile = osi_UFSOpen(&tdc->f.inode);
-    code = afs_osi_Stat(tfile, &tstat);
-    if (code)
-       osi_Panic("initcachefile stat");
+       }
+       fileIsBad = 0;
+       if ((tdc->f.states & DWriting) || tdc->f.fid.Fid.Volume == 0)
+           fileIsBad = 1;
+       tfile = osi_UFSOpen(&tdc->f.inode);
+       if (!tfile) {
+           ReleaseWriteLock(&afs_xdcache);
+           ReleaseWriteLock(&tdc->lock);
+           afs_PutDCache(tdc);
+           return ENOENT;
+       }
 
-    /*
-     * If file size doesn't match the cache info file, it's probably bad.
-     */
-    if (tdc->f.chunkBytes != tstat.size)
-       fileIsBad = 1;
+       code = afs_osi_Stat(tfile, &tstat);
+       if (code)
+           osi_Panic("initcachefile stat");
+
+       /*
+        * If file size doesn't match the cache info file, it's probably bad.
+        */
+       if (tdc->f.chunkBytes != tstat.size)
+           fileIsBad = 1;
+       /*
+        * If file changed within T (120?) seconds of cache info file, it's
+        * probably bad.  In addition, if slot changed within last T seconds,
+        * the cache info file may be incorrectly identified, and so slot
+        * may be bad.
+        */
+       if (cacheInfoModTime < tstat.mtime + 120)
+           fileIsBad = 1;
+       if (cacheInfoModTime < tdc->f.modTime + 120)
+           fileIsBad = 1;
+       /* In case write through is behind, make sure cache items entry is
+        * at least as new as the chunk.
+        */
+       if (tdc->f.modTime < tstat.mtime)
+           fileIsBad = 1;
+    }
     tdc->f.chunkBytes = 0;
 
-    /*
-     * If file changed within T (120?) seconds of cache info file, it's
-     * probably bad.  In addition, if slot changed within last T seconds,
-     * the cache info file may be incorrectly identified, and so slot
-     * may be bad.
-     */
-    if (cacheInfoModTime < tstat.mtime + 120)
-       fileIsBad = 1;
-    if (cacheInfoModTime < tdc->f.modTime + 120)
-       fileIsBad = 1;
-    /* In case write through is behind, make sure cache items entry is
-     * at least as new as the chunk.
-     */
-    if (tdc->f.modTime < tstat.mtime)
-       fileIsBad = 1;
     if (fileIsBad) {
        tdc->f.fid.Fid.Volume = 0;      /* not in the hash table */
-       if (tstat.size != 0)
+       if (tfile && tstat.size != 0)
            osi_UFSTruncate(tfile, 0);
        tdc->f.states &= ~(DRO|DBackup|DRW);
        afs_DCMoveBucket(tdc, 0, 0);
@@ -3025,7 +3217,8 @@ afs_InitCacheFile(char *afile, ino_t ainode)
        afs_indexUnique[index] = tdc->f.fid.Fid.Unique;
     }                          /*File is not bad */
 
-    osi_UFSClose(tfile);
+    if (tfile)
+       osi_UFSClose(tfile);
     tdc->f.states &= ~DWriting;
     tdc->dflags &= ~DFEntryMod;
     /* don't set f.modTime; we're just cleaning up */
@@ -3061,6 +3254,7 @@ afs_dcacheInit(int afiles, int ablocks, int aDentries, int achunk, int aflags)
     struct dcache *tdp;
     int i;
     int code;
+    int afs_dhashbits;
 
     afs_freeDCList = NULLIDX;
     afs_discardDCList = NULLIDX;
@@ -3082,31 +3276,18 @@ afs_dcacheInit(int afiles, int ablocks, int aDentries, int achunk, int aflags)
     if (!aDentries)
        aDentries = DDSIZE;
 
-    if (aflags & AFSCALL_INIT_MEMCACHE) {
-       /*
-        * Use a memory cache instead of a disk cache
-        */
-       cacheDiskType = AFS_FCACHE_TYPE_MEM;
-       afs_cacheType = &afs_MemCacheOps;
-       afiles = (afiles < aDentries) ? afiles : aDentries;     /* min */
-       ablocks = afiles * (AFS_FIRSTCSIZE / 1024);
-       /* ablocks is reported in 1K blocks */
-       code = afs_InitMemCache(afiles, AFS_FIRSTCSIZE, aflags);
-       if (code != 0) {
-           afs_warn("afsd: memory cache too large for available memory.\n");
-           afs_warn("afsd: AFS files cannot be accessed.\n\n");
-           dcacheDisabled = 1;
-           afiles = ablocks = 0;
-       } else
-           afs_warn("Memory cache: Allocating %d dcache entries...",
-                  aDentries);
-    } else {
-       cacheDiskType = AFS_FCACHE_TYPE_UFS;
-       afs_cacheType = &afs_UfsCacheOps;
-    }
-
+    /* afs_dhashsize defaults to 1024 */
     if (aDentries > 512)
        afs_dhashsize = 2048;
+    /* Try to keep the average chain length around two unless the table
+     * would be ridiculously big. */
+    if (aDentries > 4096) {
+       afs_dhashbits = opr_fls(aDentries) - 3;
+       /* Cap the hash tables to 32k entries. */
+       if (afs_dhashbits > 15)
+           afs_dhashbits = 15;
+       afs_dhashsize = opr_jhash_size(afs_dhashbits);
+    }
     /* initialize hash tables */
     afs_dvhashTbl = afs_osi_Alloc(afs_dhashsize * sizeof(afs_int32));
     osi_Assert(afs_dvhashTbl != NULL);
@@ -3179,6 +3360,28 @@ afs_dcacheInit(int afiles, int ablocks, int aDentries, int achunk, int aflags)
        afs_stats_cmperf.cacheBucket2_Discarded = 0;
     afs_DCSizeInit();
     QInit(&afs_DLRU);
+
+    if (aflags & AFSCALL_INIT_MEMCACHE) {
+       /*
+        * Use a memory cache instead of a disk cache
+        */
+       cacheDiskType = AFS_FCACHE_TYPE_MEM;
+       afs_cacheType = &afs_MemCacheOps;
+       afiles = (afiles < aDentries) ? afiles : aDentries;     /* min */
+       ablocks = afiles * (AFS_FIRSTCSIZE / 1024);
+       /* ablocks is reported in 1K blocks */
+       code = afs_InitMemCache(afiles, AFS_FIRSTCSIZE, aflags);
+       if (code != 0) {
+           afs_warn("afsd: memory cache too large for available memory.\n");
+           afs_warn("afsd: AFS files cannot be accessed.\n\n");
+           dcacheDisabled = 1;
+       } else
+           afs_warn("Memory cache: Allocating %d dcache entries...",
+                  aDentries);
+    } else {
+       cacheDiskType = AFS_FCACHE_TYPE_UFS;
+       afs_cacheType = &afs_UfsCacheOps;
+    }
 }
 
 /*!
@@ -3369,6 +3572,7 @@ afs_MakeShadowDir(struct vcache *avc, struct dcache *adc)
 
     /* Get a fresh dcache. */
     new_dc = afs_AllocDCache(avc, 0, 0, &shadow_fid);
+    osi_Assert(new_dc);
 
     ObtainReadLock(&adc->mflock);