afs: Handle easy GetValidDSlot errors
[openafs.git] / src / afs / afs_dcache.c
index 9d4a7e1..388a413 100644 (file)
@@ -1087,7 +1087,8 @@ afs_FreeDiscardedDCache(void)
     /*
      * Get an entry from the list of discarded cache elements
      */
-    tdc = afs_GetNewDSlot(afs_discardDCList);
+    tdc = afs_GetUnusedDSlot(afs_discardDCList);
+    osi_Assert(tdc);
     osi_Assert(tdc->refCount == 1);
     ReleaseReadLock(&tdc->tlock);
 
@@ -1314,7 +1315,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 +1462,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);
@@ -1507,7 +1517,8 @@ afs_AllocDCache(struct vcache *avc, afs_int32 chunk, afs_int32 lock,
        || ((lock & 2) && afs_freeDCList != NULLIDX)) {
 
        afs_indexFlags[afs_freeDCList] &= ~IFFree;
-       tdc = afs_GetNewDSlot(afs_freeDCList);
+       tdc = afs_GetUnusedDSlot(afs_freeDCList);
+       osi_Assert(tdc);
        osi_Assert(tdc->refCount == 1);
        ReleaseReadLock(&tdc->tlock);
        ObtainWriteLock(&tdc->lock, 604);
@@ -1515,7 +1526,8 @@ afs_AllocDCache(struct vcache *avc, afs_int32 chunk, afs_int32 lock,
        afs_freeDCCount--;
     } else {
        afs_indexFlags[afs_discardDCList] &= ~IFDiscarded;
-       tdc = afs_GetNewDSlot(afs_discardDCList);
+       tdc = afs_GetUnusedDSlot(afs_discardDCList);
+       osi_Assert(tdc);
        osi_Assert(tdc->refCount == 1);
        ReleaseReadLock(&tdc->tlock);
        ObtainWriteLock(&tdc->lock, 605);
@@ -1633,7 +1645,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 +1767,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 +1785,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 +1817,6 @@ afs_GetDCache(struct vcache *avc, afs_size_t abyte,
                afs_PutDCache(tdc);
                tdc = 0;
            }
-           us = index;
-           index = afs_dcnextTbl[index];
        }
 
        /*
@@ -1818,6 +1832,16 @@ afs_GetDCache(struct vcache *avc, afs_size_t abyte,
            afs_Trace2(afs_iclSetp, CM_TRACE_GETDCACHE1, ICL_TYPE_POINTER,
                       avc, ICL_TYPE_INT32, chunk);
 
+           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;
+           }
+
            /* Make sure there is a free dcache entry for us to use */
            if (afs_discardDCList == NULLIDX && afs_freeDCList == NULLIDX) {
                while (1) {
@@ -2087,10 +2111,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 +2120,49 @@ 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)
                size = maxGoodLength - Position;
            if (size < 0)
                size = 0;       /* Handle random races */
            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 + size > avc->f.truncPos) {
+                   size = avc->f.truncPos - Position;
+               }
+               if (size < 0) {
+                   size = 0;
+               }
            }
        }
        if (afs_mariner && !tdc->f.chunk)
@@ -2132,8 +2185,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
        /*
@@ -2571,13 +2623,14 @@ afs_WriteThroughDSlots(void)
  *
  * Parameters:
  *     aslot : Dcache slot to look at.
+ *      needvalid : Whether the specified slot should already exist
  *
  * Environment:
  *     Must be called with afs_xdcache write-locked.
  */
 
 struct dcache *
-afs_MemGetDSlot(afs_int32 aslot, int needvalid)
+afs_MemGetDSlot(afs_int32 aslot, int indexvalid, int datavalid)
 {
     struct dcache *tdc;
     int existing = 0;
@@ -2598,7 +2651,10 @@ afs_MemGetDSlot(afs_int32 aslot, int needvalid)
        return tdc;
     }
 
-    osi_Assert(!needvalid);
+    /* if 'indexvalid' is true, the slot must already exist and be populated
+     * somewhere. for memcache, the only place that dcache entries exist is
+     * in memory, so if we did not find it above, something is very wrong. */
+    osi_Assert(!indexvalid);
 
     if (!afs_freeDSList)
        afs_GetDownDSlot(4);
@@ -2659,12 +2715,20 @@ unsigned int last_error = 0, lasterrtime = 0;
  *
  * Parameters:
  *     aslot : Dcache slot to look at.
+ *      indexvalid : 1 if we know the slot we're giving is valid, and thus
+ *                   reading the dcache from the disk index should succeed. 0
+ *                   if we are initializing a new dcache, and so reading from
+ *                   the disk index may fail.
+ *      datavalid : 0 if we are loading a dcache entry from the free or
+ *                  discard list, so we know the data in the given dcache is
+ *                  not valid. 1 if we are loading a known used dcache, so the
+ *                  data in the dcache must be valid.
  *
  * Environment:
  *     afs_xdcache lock write-locked.
  */
 struct dcache *
-afs_UFSGetDSlot(afs_int32 aslot, int needvalid)
+afs_UFSGetDSlot(afs_int32 aslot, int indexvalid, int datavalid)
 {
     afs_int32 code;
     struct dcache *tdc;
@@ -2723,15 +2787,18 @@ 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 (indexvalid) {
            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,19 +2811,19 @@ afs_UFSGetDSlot(afs_int32 aslot, int needvalid)
     }
     if (!afs_CellNumValid(tdc->f.fid.Cell)) {
        entryok = 0;
-       if (needvalid) {
+       if (datavalid) {
            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 (datavalid && tdc->f.fid.Fid.Volume == 0) {
        osi_Panic("afs: invalid zero-volume dcache entry at slot %d off %d",
                  (int)aslot, off);
     }
 
-    if (!entryok) {
+    if (!entryok || !datavalid) {
        tdc->f.fid.Cell = 0;
        tdc->f.fid.Fid.Volume = 0;
        tdc->f.chunk = -1;
@@ -2829,6 +2896,19 @@ afs_WriteDCache(struct dcache *adc, int atime)
     osi_Assert(WriteLocked(&afs_xdcache));
     if (atime)
        adc->f.modTime = osi_Time();
+
+    if ((afs_indexFlags[adc->index] & (IFFree | IFDiscarded)) == 0 &&
+        adc->f.fid.Fid.Volume == 0) {
+       /* If a dcache slot is not on the free or discard list, it must be
+        * in the hash table. Thus, the volume must be non-zero, since that
+        * is how we determine whether or not to unhash the entry when kicking
+        * it out of the cache. Do this check now, since otherwise this can
+        * cause hash table corruption and a panic later on after we read the
+        * entry back in. */
+       osi_Panic("afs_WriteDCache zero volume index %d flags 0x%x\n",
+                 adc->index, (unsigned)afs_indexFlags[adc->index]);
+    }
+
     /*
      * Seek to the right dcache slot and write the in-memory image out to disk.
      */
@@ -2838,8 +2918,12 @@ afs_WriteDCache(struct dcache *adc, int atime)
                      sizeof(struct fcache) * adc->index +
                      sizeof(struct afs_fheader), (char *)(&adc->f),
                      sizeof(struct fcache));
-    if (code != sizeof(struct fcache))
+    if (code != sizeof(struct fcache)) {
+       afs_warn("afs: failed to write to CacheItems off %ld code %d/%d\n",
+                (long)(sizeof(struct fcache) * adc->index + sizeof(struct afs_fheader)),
+                (int)code, (int)sizeof(struct fcache));
        return EIO;
+    }
     return 0;
 }
 
@@ -2888,7 +2972,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
@@ -2922,56 +3005,61 @@ 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);
+       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 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);
@@ -3008,7 +3096,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 */
@@ -3065,29 +3154,6 @@ 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;
-    }
-
     if (aDentries > 512)
        afs_dhashsize = 2048;
     /* initialize hash tables */
@@ -3162,6 +3228,29 @@ 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;
+           afiles = ablocks = 0;
+       } else
+           afs_warn("Memory cache: Allocating %d dcache entries...",
+                  aDentries);
+    } else {
+       cacheDiskType = AFS_FCACHE_TYPE_UFS;
+       afs_cacheType = &afs_UfsCacheOps;
+    }
 }
 
 /*!