Windows: dir buffers out of date - mark them as such
[openafs.git] / src / WINNT / afsd / cm_dir.c
index d6f3439..86744cc 100644 (file)
@@ -1,13 +1,16 @@
 /*
  * Copyright 2000, International Business Machines Corporation and others.
  * 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
  * directory or online at http://www.openafs.org/dl/license10.html
  */
 
+#include <afsconfig.h>
 #include <afs/param.h>
+#include <roken.h>
+
 #include <afs/stds.h>
 
 #include <windows.h>
@@ -98,7 +101,7 @@ static int
 cm_DirOpDelBuffer(cm_dirOp_t * op, cm_buf_t * buffer, int flags);
 
 static long
-cm_DirCheckStatus(cm_dirOp_t * op, afs_uint32 locked);
+cm_DirCheckStatus(cm_dirOp_t * op, int locked);
 
 static long
 cm_DirReleasePage(cm_dirOp_t * op, cm_buf_t ** bufferpp, int modified);
@@ -116,15 +119,17 @@ cm_DirAddPage(cm_dirOp_t * op, int pageno);
 static long
 cm_DirFreeBlobs(cm_dirOp_t * op, int firstblob, int nblobs);
 
+static long
+cm_DirPrefetchBuffers(cm_dirOp_t * op);
 
 /* compute how many 32 byte entries an AFS 3 dir requires for storing
  * the specified name.
  */
-long 
+long
 cm_NameEntries(char *namep, size_t *lenp)
 {
     long i;
-        
+
     i = (long)strlen(namep);
     if (lenp) *lenp = i;
     return 1 + ((i+16) >> 5);
@@ -393,6 +398,9 @@ cm_DirFindBlobs(cm_dirOp_t * op, int nblobs)
                 dhpModified = TRUE;
            }
 
+            /* the create flag is not set for the GetPage call below
+               since the page should have been added if necessary
+               above. */
             code = cm_DirGetPage(op, i, &pagebuf, &pp);
             if (code) {
                 cm_DirReleasePage(op, &dhpbuf, dhpModified);
@@ -440,7 +448,7 @@ cm_DirFindBlobs(cm_dirOp_t * op, int nblobs)
     return -1;
 }
 
-/* Add a page to a directory. 
+/* Add a page to a directory.
 
    Called with op->scp->rw
 */
@@ -595,6 +603,8 @@ cm_DirLookup(cm_dirOp_t * op, char *entry, cm_fid_t * cfid)
     LARGE_INTEGER       start;
     LARGE_INTEGER       end;
 
+    lock_AssertNone(&op->scp->rw);
+
     QueryPerformanceCounter(&start);
 
     osi_Log2(afsd_logp, "cm_DirLookup for op 0x%p, entry[%s]",
@@ -603,6 +613,14 @@ cm_DirLookup(cm_dirOp_t * op, char *entry, cm_fid_t * cfid)
     code = cm_DirFindItem(op, entry,
                           &itembuf, &firstitem,
                           &pibuf, &previtem);
+
+    if (code == CM_ERROR_NOTINCACHE) {
+        code = cm_DirPrefetchBuffers(op);
+        if (code == 0)
+            code = cm_DirFindItem(op, entry, &itembuf, &firstitem,
+                                  &pibuf, &previtem);
+    }
+
     if (code != 0) {
         dir_lookup_misses++;
         code = ENOENT;
@@ -833,15 +851,15 @@ cm_DirGetBlob(cm_dirOp_t * op,
     *blobpp = (cm_dirEntry_t *) (ep + 32 * (blobno & (CM_DIR_EPP - 1)));
 
     return code;
-}      
+}
 
 int
 cm_DirHash(char *string)
 {
     /* Hash a string to a number between 0 and NHASHENT. */
-    register unsigned char tc;
-    register int hval;
-    register int tval;
+    unsigned char tc;
+    int hval;
+    int tval;
     hval = 0;
     while ((tc = (*string++))) {
        hval *= 173;
@@ -957,18 +975,18 @@ cm_DirFindItem(cm_dirOp_t * op,
     }
 }
 
-/* Begin a sequence of directory operations.  
+/* Begin a sequence of directory operations.
  * Called with scp->rw unlocked.
  */
 long
 cm_BeginDirOp(cm_scache_t * scp, cm_user_t * userp, cm_req_t * reqp,
-              afs_uint32 lockType, cm_dirOp_t * op)
+              afs_uint32 lockType, afs_uint32 flags, cm_dirOp_t * op)
 {
     long code;
     int i, mxheld = 0, haveWrite = 0;
 
-    osi_Log3(afsd_logp, "Beginning dirOp[0x%p] for scp[0x%p], userp[0x%p]",
-             op, scp, userp);
+    osi_Log4(afsd_logp, "Beginning dirOp[0x%p] for scp[0x%p], userp[0x%p] lockType[0x%x]",
+             op, scp, userp, lockType);
 
     memset(op, 0, sizeof(*op));
 
@@ -988,7 +1006,7 @@ cm_BeginDirOp(cm_scache_t * scp, cm_user_t * userp, cm_req_t * reqp,
     if (lockType == CM_DIRLOCK_WRITE) {
         lock_ObtainWrite(&scp->dirlock);
         haveWrite = 1;
-    } else { 
+    } else {
         lock_ObtainRead(&scp->dirlock);
         haveWrite = 0;
     }
@@ -1004,7 +1022,7 @@ cm_BeginDirOp(cm_scache_t * scp, cm_user_t * userp, cm_req_t * reqp,
 #ifdef USE_BPLUS
         if (!cm_BPlusTrees ||
             (scp->dirBplus &&
-             scp->dirDataVersion == scp->dataVersion)) 
+             scp->dirDataVersion == scp->dataVersion))
         {
             /* we know that haveWrite matches lockType at this point */
             switch (lockType) {
@@ -1021,9 +1039,10 @@ cm_BeginDirOp(cm_scache_t * scp, cm_user_t * userp, cm_req_t * reqp,
             default:
                 osi_assert(haveWrite);
             }
+            op->lockType = lockType;
         } else {
-            if (!(scp->dirBplus && 
-                  scp->dirDataVersion == scp->dataVersion)) 
+            if (!(scp->dirBplus &&
+                  scp->dirDataVersion == scp->dataVersion))
             {
               repeat:
                 if (!haveWrite) {
@@ -1038,54 +1057,65 @@ cm_BeginDirOp(cm_scache_t * scp, cm_user_t * userp, cm_req_t * reqp,
                     lock_ObtainWrite(&scp->rw);
                     mxheld = 1;
                 }
-                if (scp->dirBplus && 
+                if (scp->dirBplus &&
                      scp->dirDataVersion != scp->dataVersion)
                 {
                     bplus_dv_error++;
                     bplus_free_tree++;
                     freeBtree(scp->dirBplus);
                     scp->dirBplus = NULL;
-                    scp->dirDataVersion = -1;
+                    scp->dirDataVersion = CM_SCACHE_VERSION_BAD;
                 }
 
-                if (!scp->dirBplus) {
+                if ((!scp->dirBplus) &&
+                    (!(flags & CM_DIROP_FLAG_NOBUILDTREE))) {
                     if (mxheld) {
                         lock_ReleaseWrite(&scp->rw);
                         mxheld = 0;
                     }
-                    cm_BPlusDirBuildTree(scp, userp, reqp);
+                    code = cm_BPlusDirBuildTree(scp, userp, reqp);
+                    osi_Log1(afsd_logp, "cm_BeginDirOp cm_BPlusDirBuildTree code 0x%x", code);
                     if (!mxheld) {
                         lock_ObtainWrite(&scp->rw);
                         mxheld = 1;
                     }
-                    if (op->dataVersion != scp->dataVersion) {
-                        /* We lost the race, therefore we must update the
-                         * dirop state and retry to build the tree.
-                         */
-                        op->length = scp->length;
-                        op->newLength = op->length;
-                        op->dataVersion = scp->dataVersion;
-                        op->newDataVersion = op->dataVersion;
-                        goto repeat;
+                    if (code) {
+                        bplus_free_tree++;
+                        freeBtree(scp->dirBplus);
+                        scp->dirBplus = NULL;
+                        scp->dirDataVersion = CM_SCACHE_VERSION_BAD;
+                    } else {
+                        if (op->dataVersion != scp->dataVersion) {
+                            /* We lost the race, therefore we must update the
+                             * dirop state and retry to build the tree.
+                            */
+                            op->length = scp->length;
+                            op->newLength = op->length;
+                            op->dataVersion = scp->dataVersion;
+                            op->newDataVersion = op->dataVersion;
+                            goto repeat;
+                        }
+
+                         if (scp->dirBplus)
+                            scp->dirDataVersion = scp->dataVersion;
                     }
-
-                    if (scp->dirBplus)
-                        scp->dirDataVersion = scp->dataVersion;
-                }
+                } /* build btree */
             }
 
-            switch (lockType) {
-            case CM_DIRLOCK_NONE:
-                lock_ReleaseWrite(&scp->dirlock);
-                break;
-            case CM_DIRLOCK_READ:
-                lock_ConvertWToR(&scp->dirlock);
-                break;
-            case CM_DIRLOCK_WRITE:
-            default:
-                /* got it already */;
+            if (code == 0) {
+                switch (lockType) {
+                case CM_DIRLOCK_NONE:
+                    lock_ReleaseWrite(&scp->dirlock);
+                    break;
+                case CM_DIRLOCK_READ:
+                    lock_ConvertWToR(&scp->dirlock);
+                    break;
+                case CM_DIRLOCK_WRITE:
+                default:
+                    /* got it already */;
+                }
+                op->lockType = lockType;
             }
-            haveWrite = 0;
         }
 #else
         /* we know that haveWrite matches lockType at this point */
@@ -1103,25 +1133,28 @@ cm_BeginDirOp(cm_scache_t * scp, cm_user_t * userp, cm_req_t * reqp,
         default:
             osi_assert(haveWrite);
         }
-#endif
         op->lockType = lockType;
-        if (mxheld)
-            lock_ReleaseWrite(&scp->rw);
-    } else {
+#endif
+    }
+
+    if (mxheld)
+        lock_ReleaseWrite(&scp->rw);
+
+    if (code) {
         if (haveWrite)
             lock_ReleaseWrite(&scp->dirlock);
         else
             lock_ReleaseRead(&scp->dirlock);
-        if (mxheld)
-            lock_ReleaseWrite(&scp->rw);
         cm_EndDirOp(op);
+
+        osi_Log1(afsd_logp, "cm_BeginDirOp return code 0x%x", code);
     }
 
     return code;
 }
 
 /* Check if it is safe for us to perform local directory updates.
-   Called with scp->rw unlocked. */
+   Called with op->scp->rw write-locked. */
 int
 cm_CheckDirOpForSingleChange(cm_dirOp_t * op)
 {
@@ -1131,21 +1164,30 @@ cm_CheckDirOpForSingleChange(cm_dirOp_t * op)
     if (op->scp == NULL)
         return 0;
 
-    lock_ObtainWrite(&op->scp->rw);
+    lock_AssertWrite(&op->scp->rw);
+
     code = cm_DirCheckStatus(op, 1);
 
     if (code == 0 &&
-        op->dataVersion == op->scp->dataVersion - 1) {
-        /* only one set of changes happened between cm_BeginDirOp()
-           and this function.  It is safe for us to perform local
-           changes. */
+        op->dataVersion == op->scp->dataVersion - 1)
+    {
+        /*
+         * only one set of changes happened between cm_BeginDirOp()
+         * and this function.  It is safe for us to perform local
+         * changes. */
         op->newDataVersion = op->scp->dataVersion;
         op->newLength = op->scp->serverLength;
 
         rc = 1;
+    } else {
+        /*
+         * The directory buffers are no longer up to date.
+         */
+        op->scp->bufDataVersionLow = op->scp->dataVersion;
+
+        rc = 0;
     }
-    lock_ReleaseWrite(&op->scp->rw); 
-    
+
     if (rc)
         osi_Log0(afsd_logp, "cm_CheckDirOpForSingleChange succeeded");
     else
@@ -1155,29 +1197,30 @@ cm_CheckDirOpForSingleChange(cm_dirOp_t * op)
     return rc;
 }
 
-/* End a sequence of directory operations.  
+/* End a sequence of directory operations.
  * Called with op->scp->rw unlocked.*/
 long
 cm_EndDirOp(cm_dirOp_t * op)
 {
     long code = 0;
 
+    osi_Log4(afsd_logp, "Ending dirOp[0x%p] scp[0x%p] lockType[0x%x] with %d dirty buffer releases",
+             op, op->scp, op->lockType, op->dirtyBufCount);
+
     if (op->scp == NULL)
         return 0;
 
-    osi_Log2(afsd_logp, "Ending dirOp 0x%p with %d dirty buffer releases",
-             op, op->dirtyBufCount);
-
     if (op->dirtyBufCount > 0) {
 #ifdef USE_BPLUS
         /* update the data version on the B+ tree */
-        if (op->scp->dirBplus && 
+        if (op->scp->dirBplus &&
              op->scp->dirDataVersion == op->dataVersion) {
 
             switch (op->lockType) {
             case CM_DIRLOCK_READ:
-                lock_ReleaseRead(&op->scp->dirlock);
-                /* fall through ... */
+                lock_ConvertRToW(&op->scp->dirlock);
+                op->lockType = CM_DIRLOCK_WRITE;
+                break;
             case CM_DIRLOCK_NONE:
                 lock_ObtainWrite(&op->scp->dirlock);
                 op->lockType = CM_DIRLOCK_WRITE;
@@ -1194,6 +1237,7 @@ cm_EndDirOp(cm_dirOp_t * op)
          * and update the dataVersion for each. */
         lock_ObtainWrite(&op->scp->rw);
         code = buf_ForceDataVersion(op->scp, op->dataVersion, op->newDataVersion);
+        op->scp->flags |= CM_SCACHEFLAG_LOCAL;
         lock_ReleaseWrite(&op->scp->rw);
     }
 
@@ -1218,6 +1262,9 @@ cm_EndDirOp(cm_dirOp_t * op)
 
     osi_assertx(op->nBuffers == 0, "Buffer leak after dirOp termination");
 
+    if (code)
+        osi_Log1(afsd_logp, "cm_EndDirOp return code 0x%x", code);
+
     return code;
 }
 
@@ -1270,22 +1317,16 @@ cm_DirOpAddBuffer(cm_dirOp_t * op, cm_buf_t * bufferp)
                          (op->lockType == CM_DIRLOCK_WRITE ? CM_SCACHESYNC_WRITE : CM_SCACHESYNC_READ) |
                          CM_SCACHESYNC_BUFLOCKED);
 
-        if (code == 0) {
-            if (bufferp->dataVersion == CM_BUF_VERSION_BAD) {
-                /* This is a new buffer */
-                bufferp->dataVersion = op->dataVersion;
-            } else if (bufferp->dataVersion != op->dataVersion) {
+        if (code == 0 && bufferp->dataVersion != op->dataVersion) {
                 osi_Log2(afsd_logp,
-                         "cm_DirOpAddBuffer: buffer data version mismatch. buf dv = %d. needs %d", 
+                         "cm_DirOpAddBuffer: buffer data version mismatch. buf dv = %d. needs %d",
                          bufferp->dataVersion, op->dataVersion);
 
                 cm_SyncOpDone(op->scp, bufferp,
                               CM_SCACHESYNC_NEEDCALLBACK |
                               (op->lockType == CM_DIRLOCK_WRITE ? CM_SCACHESYNC_WRITE : CM_SCACHESYNC_READ) |
                               CM_SCACHESYNC_BUFLOCKED);
-
-                code = CM_ERROR_INVAL;
-            }
+            code = CM_ERROR_NOTINCACHE;
         }
 
         lock_ReleaseWrite(&op->scp->rw);
@@ -1447,15 +1488,15 @@ cm_DirOpDelBuffer(cm_dirOp_t * op, cm_buf_t * bufferp, int flags)
      scp->rw may be released
  */
 static long
-cm_DirCheckStatus(cm_dirOp_t * op, afs_uint32 locked)
+cm_DirCheckStatus(cm_dirOp_t * op, int scp_locked)
 {
     long code;
 
-    if (!locked)
+    if (!scp_locked)
         lock_ObtainWrite(&op->scp->rw);
     code = cm_SyncOp(op->scp, NULL, op->userp, &op->req, PRSFS_LOOKUP,
                      CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
-    if (!locked)
+    if (!scp_locked)
         lock_ReleaseWrite(&op->scp->rw);
 
     osi_Log2(afsd_logp, "cm_DirCheckStatus for op 0x%p returning code 0x%x",
@@ -1464,6 +1505,86 @@ cm_DirCheckStatus(cm_dirOp_t * op, afs_uint32 locked)
     return code;
 }
 
+/* Attempt to prefetch all the buffers for this operation.
+
+   Called with scp->rw unlocked
+ */
+static long
+cm_DirPrefetchBuffers(cm_dirOp_t * op)
+{
+    long code = 0;
+    osi_hyper_t offset;
+    cm_buf_t *bufferp = NULL;
+
+    osi_Log1(afsd_logp, "cm_DirPrefetchBuffers for op 0x%p", op);
+
+    /* prefetching is only done on read operations where we don't
+       expect the data version to change. */
+    if (op->dataVersion != op->newDataVersion) {
+        osi_Log0(afsd_logp, "Skipping prefetch for write operation.");
+        return CM_ERROR_INVAL;
+    }
+
+    lock_ObtainWrite(&op->scp->rw);
+
+    /* When we are prefetching a file, we first flush out any of its
+       contents just to make sure that we don't end up with buffers
+       that was locally modified. */
+
+    if (op->scp->flags & CM_SCACHEFLAG_LOCAL)
+        op->scp->bufDataVersionLow = op->scp->dataVersion;
+
+    offset = ConvertLongToLargeInteger(0);
+    while (LargeIntegerLessThan(offset, op->scp->length)) {
+        osi_Log2(afsd_logp, "Trying prefetch for offset %08x:%08x",
+                 offset.HighPart, offset.LowPart);
+        lock_ReleaseWrite(&op->scp->rw);
+
+        code = buf_Get(op->scp, &offset, &op->req, &bufferp);
+
+        lock_ObtainWrite(&op->scp->rw);
+
+        if (code)
+            break;
+
+        while (1) {
+
+            code = cm_SyncOp(op->scp, bufferp, op->userp, &op->req, PRSFS_LOOKUP,
+                             CM_SCACHESYNC_NEEDCALLBACK |
+                             (op->lockType == CM_DIRLOCK_WRITE ? CM_SCACHESYNC_WRITE : CM_SCACHESYNC_READ));
+
+            if (code)
+                break;
+
+            cm_SyncOpDone(op->scp, bufferp, CM_SCACHESYNC_NEEDCALLBACK |
+                          (op->lockType == CM_DIRLOCK_WRITE ? CM_SCACHESYNC_WRITE : CM_SCACHESYNC_READ));
+
+            if (cm_HaveBuffer(op->scp, bufferp, 0))
+                break;
+
+            code = cm_GetBuffer(op->scp, bufferp, NULL, op->userp, &op->req);
+            if (code)
+                break;
+        }
+
+        if (code)
+            break;
+
+        if (bufferp) {
+            buf_Release(bufferp);
+            bufferp = NULL;
+        }
+
+        offset = LargeIntegerAdd(offset, ConvertLongToLargeInteger(cm_data.buf_blockSize));
+    }
+
+    lock_ReleaseWrite(&op->scp->rw);
+
+    osi_Log1(afsd_logp, "cm_DirPrefetchBuffers returning code 0x%x", code);
+
+    return code;
+}
+
 /* Release a directory buffer that was obtained via a call to
    cm_DirGetPage() or any other function that returns a locked, held,
    directory page buffer.
@@ -1503,6 +1624,9 @@ cm_DirReleasePage(cm_dirOp_t * op, cm_buf_t ** bufferpp, int modified)
    released and a new buffer returned that contains the requested
    page.
 
+   If the specified page exists beyond the EOF for the scp, a new
+   buffer will be allocated only if create is set to TRUE.
+
    Note: If a buffer is specified on entry via bufferpp, it is assumed
    that the buffer is unmodified.  If the buffer is modified, it
    should be released via cm_DirReleasePage().
@@ -1563,7 +1687,7 @@ cm_DirGetPage(cm_dirOp_t * op,
             goto _has_buffer;
         }
 
-        code = buf_Get(op->scp, &bufferOffset, &bufferp);
+        code = buf_Get(op->scp, &bufferOffset, &op->req, &bufferp);
         if (code) {
             osi_Log1(afsd_logp, "    buf_Get returned code 0x%x", code);
             bufferp = NULL;
@@ -1583,50 +1707,6 @@ cm_DirGetPage(cm_dirOp_t * op,
             bufferp = NULL;
             goto _exit;
         }
-
-#if 0
-        /* The code below is for making sure the buffer contains
-           current data.  This is a bad idea, since the whole point of
-           doing directory updates locally is to avoid fetching all
-           the data from the server. */
-        while (1) {
-            lock_ObtainWrite(&op->scp->rw);
-            code = cm_SyncOp(op->scp, bufferp, op->userp, &op->req, PRSFS_LOOKUP,
-                             CM_SCACHESYNC_NEEDCALLBACK |
-                             CM_SCACHESYNC_READ |
-                             CM_SCACHESYNC_BUFLOCKED);
-
-            if (code) {
-                lock_ReleaseWrite(&op->scp->rw);
-                break;
-            }
-
-            cm_SyncOpDone(op->scp, bufferp,
-                          CM_SCACHESYNC_NEEDCALLBACK |
-                          CM_SCACHESYNC_READ |
-                          CM_SCACHESYNC_BUFLOCKED);
-
-            if (cm_HaveBuffer(op->scp, bufferp, 1)) {
-                lock_ReleaseWrite(&op->scp->rw);
-                break;
-            }
-
-            lock_ReleaseMutex(&bufferp->mx);
-            code = cm_GetBuffer(op->scp, bufferp, NULL, op->userp, &op->req);
-            lock_ReleaseWrite(&op->scp->rw);
-            lock_ObtainMutex(&bufferp->mx);
-
-            if (code)
-                break;
-        }
-
-        if (code) {
-            cm_DirOpDelBuffer(op, bufferp, 0);
-            buf_Release(bufferp);
-            bufferp = NULL;
-            goto _exit;
-        }
-#endif
     }
 
  _has_buffer: