Windows: cm_TryBulkStatRPC must process VIO errors
[openafs.git] / src / WINNT / afsd / cm_vnodeops.c
index 71163be..ba80889 100644 (file)
@@ -483,53 +483,57 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
                 return 0;
             }
             sp->caseFold = casefold;
+        }
 
-            /* see if we can find it using the directory hash tables.
-               we can only do exact matches, since the hash is case
-               sensitive. */
-            {
-                cm_dirOp_t dirop;
+        /*
+         * see if we can find it using the directory hash tables.
+         * we can only do exact matches, since the hash is case
+         * sensitive.
+         */
+        if (funcp != (cm_DirFuncp_t)cm_BPlusDirFoo)
+        {
+            cm_dirOp_t dirop;
 #ifdef USE_BPLUS
-                int usedBplus = 0;
+            int usedBplus = 0;
 #endif
 
-                code = ENOENT;
+            code = ENOENT;
 
-                code = cm_BeginDirOp(scp, userp, reqp, CM_DIRLOCK_READ, &dirop);
-                if (code == 0) {
+            code = cm_BeginDirOp(scp, userp, reqp, CM_DIRLOCK_READ,
+                                 CM_DIROP_FLAG_NONE, &dirop);
+            if (code == 0) {
 
 #ifdef USE_BPLUS
-                    code = cm_BPlusDirLookup(&dirop, sp->nsearchNamep, &sp->fid);
-                    if (code != EINVAL)
-                        usedBplus = 1;
-                    else 
+                code = cm_BPlusDirLookup(&dirop, sp->nsearchNamep, &sp->fid);
+                if (code != EINVAL)
+                    usedBplus = 1;
+                else
 #endif
-                        code = cm_DirLookup(&dirop, sp->searchNamep, &sp->fid);
+                    code = cm_DirLookup(&dirop, sp->searchNamep, &sp->fid);
 
-                    cm_EndDirOp(&dirop);
-                }
+                cm_EndDirOp(&dirop);
+            }
 
-                if (code == 0) {
+            if (code == 0) {
+                /* found it */
+                sp->found = TRUE;
+                sp->ExactFound = TRUE;
+                *retscp = NULL; /* force caller to call cm_GetSCache() */
+                return 0;
+            }
+#ifdef USE_BPLUS
+            if (usedBplus) {
+                if (sp->caseFold && code == CM_ERROR_INEXACT_MATCH) {
                     /* found it */
                     sp->found = TRUE;
-                    sp->ExactFound = TRUE;
+                    sp->ExactFound = FALSE;
                     *retscp = NULL; /* force caller to call cm_GetSCache() */
                     return 0;
                 }
-#ifdef USE_BPLUS
-                if (usedBplus) {
-                    if (sp->caseFold && code == CM_ERROR_INEXACT_MATCH) {
-                        /* found it */
-                        sp->found = TRUE;
-                        sp->ExactFound = FALSE;
-                        *retscp = NULL; /* force caller to call cm_GetSCache() */
-                        return 0;
-                    }
-                    
-                    return CM_ERROR_BPLUS_NOMATCH;
-                }
-#endif 
+
+                return CM_ERROR_BPLUS_NOMATCH;
             }
+#endif
         }
     }  
 
@@ -893,7 +897,6 @@ long cm_FollowMountPoint(cm_scache_t *scp, cm_scache_t *dscp, cm_user_t *userp,
 {
     fschar_t *cellNamep = NULL;
     fschar_t *volNamep = NULL;
-    int tlen;
     afs_uint32 code;
     fschar_t *cp;
     fschar_t *mpNamep;
@@ -918,7 +921,6 @@ long cm_FollowMountPoint(cm_scache_t *scp, cm_scache_t *dscp, cm_user_t *userp,
     mpNamep = scp->mountPointStringp;
     if (!mpNamep[0])
        return CM_ERROR_NOSUCHPATH;
-    tlen = cm_FsStrLen(scp->mountPointStringp);
     mtType = *scp->mountPointStringp;
 
     cp = cm_FsStrChr(mpNamep, _FS(':'));
@@ -1052,6 +1054,7 @@ long cm_LookupInternal(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_u
     int getroot;
     normchar_t *nnamep = NULL;
     fschar_t *fnamep = NULL;
+    size_t fnlen;
 
     *outScpp = NULL;
 
@@ -1079,6 +1082,7 @@ long cm_LookupInternal(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_u
         goto done;
     }
 
+retry_lookup:
     if (flags & CM_FLAG_NOMOUNTCHASE) {
         /* In this case, we should go and call cm_Dir* functions
            directly since the following cm_ApplyDir() function will
@@ -1089,7 +1093,8 @@ long cm_LookupInternal(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_u
         int usedBplus = 0;
 #endif
 
-        code = cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_READ, &dirop);
+        code = cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_READ,
+                             CM_DIROP_FLAG_NONE, &dirop);
         if (code == 0) {
 #ifdef USE_BPLUS
             code = cm_BPlusDirLookup(&dirop, nnamep, &rock.fid);
@@ -1116,7 +1121,8 @@ long cm_LookupInternal(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_u
                 goto haveFid;
             }
             
-            return CM_ERROR_BPLUS_NOMATCH;
+            code = CM_ERROR_BPLUS_NOMATCH;
+            goto notfound;
         }
 #endif
     }
@@ -1136,7 +1142,7 @@ long cm_LookupInternal(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_u
      * that we stopped early, probably because we found the entry we're
      * looking for.  Any other non-zero code is an error.
      */
-    if (code && code != CM_ERROR_STOPNOW) {
+    if (code && code != CM_ERROR_STOPNOW && code != CM_ERROR_BPLUS_NOMATCH) {
         /* if the cm_scache_t we are searching in is not a directory 
          * we must return path not found because the error 
          * is to describe the final component not an intermediary
@@ -1150,6 +1156,7 @@ long cm_LookupInternal(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_u
         goto done;
     }
 
+notfound:
     getroot = (dscp==cm_data.rootSCachep) ;
     if (!rock.found) {
         if (!cm_freelanceEnabled || !getroot) {
@@ -1168,6 +1175,7 @@ long cm_LookupInternal(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_u
             /* nonexistent dir on freelance root, so add it */
             fschar_t fullname[CELL_MAXNAMELEN + 1] = ".";  /* +1 so that when we skip the . the size is still CELL_MAXNAMELEN */
             int  found = 0;
+            int  retry = 0;
 
             osi_Log1(afsd_logp,"cm_Lookup adding mount for non-existent directory: %S", 
                      osi_LogSaveClientString(afsd_logp,cnamep));
@@ -1180,28 +1188,71 @@ long cm_LookupInternal(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_u
              */
 
             code = -1;
+            fnlen = strlen(fnamep);
+            if ( fnamep[fnlen-1] == '.') {
+                fnamep[fnlen-1] = '\0';
+                fnlen--;
+                retry = 1;
+            }
+
             if (cnamep[0] == '.') {
                 if (cm_GetCell_Gen(&fnamep[1], &fullname[1], CM_FLAG_CREATE)) {
                     found = 1;
-                    if (!cm_FreelanceMountPointExists(fullname, 0))
-                        code = cm_FreelanceAddMount(fullname, &fullname[1], "root.cell.",
-                                                    1, &rock.fid);
-                    if ( cm_FsStrCmpI(&fnamep[1], &fullname[1]) && 
-                         !cm_FreelanceMountPointExists(fnamep, flags & CM_FLAG_DFS_REFERRAL ? 1 : 0) &&
-                         !cm_FreelanceSymlinkExists(fnamep, flags & CM_FLAG_DFS_REFERRAL ? 1 : 0))
-                        code = cm_FreelanceAddSymlink(fnamep, fullname, &rock.fid);
+                    code = cm_FreelanceAddMount(fullname, &fullname[1], "root.cell.", 1, &rock.fid);
+                    if ( cm_FsStrCmpI(&fnamep[1], &fullname[1])) {
+                        /*
+                         * Do not permit symlinks that are one of:
+                         *  . the cellname followed by a dot
+                         *  . the cellname minus a single character
+                         *  . a substring of the cellname that does not consist of full components
+                         */
+                        if ( cm_strnicmp_utf8(&fnamep[1], fullname, (int)fnlen-1) == 0 &&
+                             (fnlen-1 == strlen(fullname)-1 || fullname[fnlen-1] != '.'))
+                        {
+                            /* do not add; substitute fullname for the search */
+                            free(fnamep);
+                            fnamep = malloc(strlen(fullname)+2);
+                            fnamep[0] = '.';
+                            strncpy(&fnamep[1], fullname, strlen(fullname)+1);
+                            retry = 1;
+                        } else {
+                            code = cm_FreelanceAddSymlink(fnamep, fullname, &rock.fid);
+                        }
+                    }
                 }
             } else {
                 if (cm_GetCell_Gen(fnamep, fullname, CM_FLAG_CREATE)) {
                     found = 1;
-                    if (!cm_FreelanceMountPointExists(fullname, 0))
-                        code = cm_FreelanceAddMount(fullname, fullname, "root.cell.", 0, &rock.fid);
-                    if ( cm_FsStrCmpI(fnamep, fullname) && 
-                         !cm_FreelanceMountPointExists(fnamep, flags & CM_FLAG_DFS_REFERRAL ? 1 : 0) &&
-                         !cm_FreelanceSymlinkExists(fnamep, flags & CM_FLAG_DFS_REFERRAL ? 1 : 0))
-                        code = cm_FreelanceAddSymlink(fnamep, fullname, &rock.fid);
+                    code = cm_FreelanceAddMount(fullname, fullname, "root.cell.", 0, &rock.fid);
+                    if ( cm_FsStrCmpI(fnamep, fullname)) {
+                        /*
+                         * Do not permit symlinks that are one of:
+                         *  . the cellname followed by a dot
+                         *  . the cellname minus a single character
+                         *  . a substring of the cellname that does not consist of full components
+                         */
+                        if ( cm_strnicmp_utf8(fnamep, fullname, (int)fnlen-1) == 0 &&
+                             (fnlen == strlen(fullname)-1 || fullname[fnlen] != '.'))
+                        {
+                            /* do not add; substitute fullname for the search */
+                                free(fnamep);
+                                fnamep = strdup(fullname);
+                                code = 0;
+                                retry = 1;
+                        } else {
+                            code = cm_FreelanceAddSymlink(fnamep, fullname, &rock.fid);
+                        }
+                    }
                 }
             }
+
+            if (retry) {
+                if (nnamep)
+                    free(nnamep);
+                nnamep = cm_FsStringToNormStringAlloc(fnamep, -1, NULL);
+                goto retry_lookup;
+            }
+
             if (!found || code) {   /* add mount point failed, so give up */
                 if (flags & CM_FLAG_CHECKPATH)
                     code = CM_ERROR_NOSUCHPATH;
@@ -1219,7 +1270,7 @@ long cm_LookupInternal(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_u
         }
     }
 
-  haveFid:       
+  haveFid:
     if ( !tscp )    /* we did not find it in the dnlc */
     {
         dnlcHit = 0; 
@@ -1538,10 +1589,13 @@ long cm_Unlink(cm_scache_t *dscp, fschar_t *fnamep, clientchar_t * cnamep,
     cm_scache_t *scp = NULL;
     int free_fnamep = FALSE;
 
+    memset(&volSync, 0, sizeof(volSync));
+
     if (fnamep == NULL) {
         code = -1;
 #ifdef USE_BPLUS
-        code = cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_READ, &dirop);
+        code = cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_READ,
+                             CM_DIROP_FLAG_NONE, &dirop);
         if (code == 0) {
             code = cm_BPlusDirLookupOriginalName(&dirop, cnamep, &fnamep);
             if (code == 0)
@@ -1564,7 +1618,8 @@ long cm_Unlink(cm_scache_t *dscp, fschar_t *fnamep, clientchar_t * cnamep,
     code = cm_Lookup(dscp, cnamep, CM_FLAG_NOMOUNTCHASE, userp, reqp, &scp);
 
     /* make sure we don't screw up the dir status during the merge */
-    code = cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_NONE, &dirop);
+    code = cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_NONE,
+                         CM_DIROP_FLAG_NONE, &dirop);
 
     lock_ObtainWrite(&dscp->rw);
     sflags = CM_SCACHESYNC_STOREDATA;
@@ -1607,7 +1662,7 @@ long cm_Unlink(cm_scache_t *dscp, fschar_t *fnamep, clientchar_t * cnamep,
     cm_dnlcRemove(dscp, cnamep);
     cm_SyncOpDone(dscp, NULL, sflags);
     if (code == 0) {
-        cm_MergeStatus(NULL, dscp, &newDirStatus, &volSync, userp, CM_MERGEFLAG_DIROP);
+        cm_MergeStatus(NULL, dscp, &newDirStatus, &volSync, userp, reqp, CM_MERGEFLAG_DIROP);
     } else if (code == CM_ERROR_NOSUCHFILE) {
        /* windows would not have allowed the request to delete the file 
         * if it did not believe the file existed.  therefore, we must 
@@ -1758,8 +1813,8 @@ long cm_AssembleLink(cm_scache_t *linkScp, fschar_t *pathSuffixp,
             StringCbCopyA((char *) tsp->data, sizeof(tsp->data), linkp+cm_mountRootLen+1);
         else
             tsp->data[0] = 0;
-        *newRootScpp = cm_data.rootSCachep;
-        cm_HoldSCache(cm_data.rootSCachep);
+        *newRootScpp = cm_RootSCachep(userp, reqp);
+        cm_HoldSCache(*newRootScpp);
     } else if (linkp[0] == '\\' && linkp[1] == '\\') {
         if (!strnicmp(&linkp[2], cm_NetbiosName, (len = (long)strlen(cm_NetbiosName)))) 
         {
@@ -1772,8 +1827,8 @@ long cm_AssembleLink(cm_scache_t *linkScp, fschar_t *pathSuffixp,
                 if (*p == '\\')
                     *p = '/';
             }
-            *newRootScpp = cm_data.rootSCachep;
-            cm_HoldSCache(cm_data.rootSCachep);
+            *newRootScpp = cm_RootSCachep(userp, reqp);
+            cm_HoldSCache(*newRootScpp);
         } else {
             linkScp->fileType = CM_SCACHETYPE_DFSLINK;
             StringCchCopyA(tsp->data,lengthof(tsp->data), linkp);
@@ -1790,8 +1845,8 @@ long cm_AssembleLink(cm_scache_t *linkScp, fschar_t *pathSuffixp,
          * but this seems to create problems.  instead, we will just
          * reject the link */
         StringCchCopyA(tsp->data,lengthof(tsp->data), linkp+1);
-        *newRootScpp = cm_data.rootSCachep;
-        cm_HoldSCache(cm_data.rootSCachep);
+        *newRootScpp = cm_RootSCachep(userp, reqp);
+        cm_HoldSCache(*newRootScpp);
 #else
         /* we still copy the link data into the response so that 
          * the user can see what the link points to
@@ -2038,9 +2093,10 @@ long cm_NameI(cm_scache_t *rootSCachep, clientchar_t *pathp, long flags,
                     }
 
                     if (code == 0 && linkScp != NULL) {
-                        if (linkScp == cm_data.rootSCachep) 
+                        if (linkScp == cm_data.rootSCachep) {
                             fid_count = 0;
-                        else {
+                            i = 0;
+                        } else {
                             for ( i=0; i<fid_count; i++) {
                                 if ( !cm_FidCmp(&linkScp->fid, &fids[i]) ) {
                                     code = CM_ERROR_TOO_MANY_SYMLINKS;
@@ -2294,8 +2350,10 @@ cm_TryBulkStatRPC(cm_scache_t *dscp, cm_bulkStat_t *bbp, cm_user_t *userp, cm_re
     cm_scache_t *scp;
     cm_fid_t tfid;
     struct rx_connection * rxconnp;
-    int inlinebulk = 0;                /* Did we use InlineBulkStatus RPC or not? */
-        
+    int inlinebulk;            /* Did we use InlineBulkStatus RPC or not? */
+
+    memset(&volSync, 0, sizeof(volSync));
+
     /* otherwise, we may have one or more bulk stat's worth of stuff in bb;
      * make the calls to create the entries.  Handle AFSCBMAX files at a
      * time.
@@ -2313,15 +2371,26 @@ cm_TryBulkStatRPC(cm_scache_t *dscp, cm_bulkStat_t *bbp, cm_user_t *userp, cm_re
         callbackStruct.AFSCBs_val = &bbp->callbacks[filex];
         cm_StartCallbackGrantingCall(NULL, &cbReq);
         osi_Log1(afsd_logp, "CALL BulkStatus, %d entries", filesThisCall);
+
+        /*
+         * Whenever cm_Analyze is called for a RXAFS_ RPC there must
+         * be a FID provided.  However, the error code from RXAFS_BulkStatus
+         * or RXAFS_InlinkBulkStatus does not apply to any FID.  Therefore,
+         * we generate an invalid FID to match with the RPC error.
+         */
+        cm_SetFid(&tfid, dscp->fid.cell, dscp->fid.volume, 0, 0);
+
         do {
-            code = cm_ConnFromFID(&dscp->fid, userp, reqp, &connp);
+            inlinebulk = 0;
+
+            code = cm_ConnFromFID(&tfid, userp, reqp, &connp);
             if (code) 
                 continue;
 
             rxconnp = cm_GetRxConn(connp);
            if (!(connp->serverp->flags & CM_SERVERFLAG_NOINLINEBULK)) {
                code = RXAFS_InlineBulkStatus(rxconnp, &fidStruct,
-                                     &statStruct, &callbackStruct, &volSync);
+                                              &statStruct, &callbackStruct, &volSync);
                if (code == RXGEN_OPCODE) {
                    cm_SetServerNoInlineBulk(connp->serverp, 0);
                } else {
@@ -2334,72 +2403,117 @@ cm_TryBulkStatRPC(cm_scache_t *dscp, cm_bulkStat_t *bbp, cm_user_t *userp, cm_re
            }
             rx_PutConnection(rxconnp);
 
-        } while (cm_Analyze(connp, userp, reqp, &dscp->fid,
-                             &volSync, NULL, &cbReq, code));
+            /*
+             * If InlineBulk RPC was called and it succeeded,
+             * then pull out the return code from the status info
+             * and use it for cm_Analyze so that we can failover to other
+             * .readonly volume instances.  But only do it for errors that
+             * are volume global.
+             */
+            if (inlinebulk && code == 0 && (&bbp->stats[0])->errorCode) {
+                osi_Log1(afsd_logp, "cm_TryBulkStat inline-bulk stat error: %d",
+                          (&bbp->stats[0])->errorCode);
+                switch ((&bbp->stats[0])->errorCode) {
+                case VBUSY:
+                case VRESTARTING:
+                case VNOVOL:
+                case VMOVED:
+                case VOFFLINE:
+                case VSALVAGE:
+                case VNOSERVICE:
+                case VIO:
+                    code = (&bbp->stats[0])->errorCode;
+                    break;
+                default:
+                    /* Rx and Rxkad errors are volume global */
+                    if ( (&bbp->stats[0])->errorCode >= -64 && (&bbp->stats[0])->errorCode < 0 ||
+                         (&bbp->stats[0])->errorCode >= ERROR_TABLE_BASE_RXK && (&bbp->stats[0])->errorCode < ERROR_TABLE_BASE_RXK + 256)
+                        code = (&bbp->stats[0])->errorCode;
+                }
+            }
+        } while (cm_Analyze(connp, userp, reqp, &tfid, &volSync, NULL, &cbReq, code));
         code = cm_MapRPCError(code, reqp);
-        if (code)
-            osi_Log2(afsd_logp, "CALL %sBulkStatus FAILURE code 0x%x", 
-                     inlinebulk ? "Inline" : "", code);
-        else
-            osi_Log1(afsd_logp, "CALL %sBulkStatus SUCCESS", inlinebulk ? "Inline" : "");
 
-        /* may as well quit on an error, since we're not going to do
+        /*
+         * might as well quit on an error, since we're not going to do
          * much better on the next immediate call, either.
          */
         if (code) {
-            cm_EndCallbackGrantingCall(NULL, &cbReq, NULL, 0);
+            osi_Log2(afsd_logp, "CALL %sBulkStatus FAILURE code 0x%x",
+                     inlinebulk ? "Inline" : "", code);
+            cm_EndCallbackGrantingCall(NULL, &cbReq, NULL, NULL, 0);
             break;
         }
 
-        /* otherwise, we should do the merges */
+        /*
+         * The bulk RPC has succeeded or at least not failed with a
+         * volume global error result.  For items that have inlineBulk
+         * errors we must call cm_Analyze in order to perform required
+         * logging of errors.
+         *
+         * If the RPC was not inline bulk or the entry either has no error
+         * the status must be merged.
+         */
+        osi_Log1(afsd_logp, "CALL %sBulkStatus SUCCESS", inlinebulk ? "Inline" : "");
+
         for (i = 0; i<filesThisCall; i++) {
             j = filex + i;
             cm_SetFid(&tfid, dscp->fid.cell, bbp->fids[j].Volume, bbp->fids[j].Vnode, bbp->fids[j].Unique);
-            code = cm_GetSCache(&tfid, &scp, userp, reqp);
-            if (code != 0) 
-                continue;
 
-            /* otherwise, if this entry has no callback info, 
-             * merge in this.
-             */
-            lock_ObtainWrite(&scp->rw);
-            /* now, we have to be extra paranoid on merging in this
-             * information, since we didn't use cm_SyncOp before
-             * starting the fetch to make sure that no bad races
-             * were occurring.  Specifically, we need to make sure
-             * we don't obliterate any newer information in the
-             * vnode than have here.
-             *
-             * Right now, be pretty conservative: if there's a
-             * callback or a pending call, skip it.
-             */
-            if ((scp->cbServerp == NULL || (scp->flags & CM_SCACHEFLAG_EACCESS))
-                 && !(scp->flags &
-                       (CM_SCACHEFLAG_FETCHING
-                         | CM_SCACHEFLAG_STORING
-                         | CM_SCACHEFLAG_SIZESTORING))) {
-                cm_EndCallbackGrantingCall(scp, &cbReq,
-                                            &bbp->callbacks[j],
-                                            CM_CALLBACK_MAINTAINCOUNT);
-                cm_MergeStatus(dscp, scp, &bbp->stats[j], &volSync, userp, 0);
-            }       
-            lock_ReleaseWrite(&scp->rw);
-            cm_ReleaseSCache(scp);
+            if (inlinebulk && (&bbp->stats[j])->errorCode) {
+                cm_req_t treq = *reqp;
+                cm_Analyze(NULL, userp, &treq, &tfid, &volSync, NULL, &cbReq, (&bbp->stats[j])->errorCode);
+            } else {
+                code = cm_GetSCache(&tfid, &scp, userp, reqp);
+                if (code != 0)
+                    continue;
+
+                /*
+                 * otherwise, if this entry has no callback info,
+                 * merge in this.  If there is existing callback info
+                 * we skip the merge because the existing data must be
+                 * current (we have a callback) and the response from
+                 * a non-inline bulk rpc might actually be wrong.
+                 *
+                 * now, we have to be extra paranoid on merging in this
+                 * information, since we didn't use cm_SyncOp before
+                 * starting the fetch to make sure that no bad races
+                 * were occurring.  Specifically, we need to make sure
+                 * we don't obliterate any newer information in the
+                 * vnode than have here.
+                 *
+                 * Right now, be pretty conservative: if there's a
+                 * callback or a pending call, skip it.
+                 * However, if the prior attempt to obtain status
+                 * was refused access or the volume is .readonly,
+                 * take the data in any case since we have nothing
+                 * better for the in flight directory enumeration that
+                 * resulted in this function being called.
+                 */
+                lock_ObtainRead(&scp->rw);
+                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))
+                {
+                    lock_ConvertRToW(&scp->rw);
+                    cm_EndCallbackGrantingCall(scp, &cbReq,
+                                               &bbp->callbacks[j],
+                                               &volSync,
+                                               CM_CALLBACK_MAINTAINCOUNT);
+                    cm_MergeStatus(dscp, scp, &bbp->stats[j], &volSync, userp, reqp, 0);
+                    lock_ReleaseWrite(&scp->rw);
+                } else {
+                    lock_ReleaseRead(&scp->rw);
+                }
+                cm_ReleaseSCache(scp);
+            }
         } /* all files in the response */
         /* now tell it to drop the count,
          * after doing the vnode processing above */
-        cm_EndCallbackGrantingCall(NULL, &cbReq, NULL, 0);
+        cm_EndCallbackGrantingCall(NULL, &cbReq, NULL, NULL, 0);
     }  /* while there are still more files to process */
 
-    /* If we did the InlineBulk RPC pull out the return code and log it */
-    if (inlinebulk) {
-       if ((&bbp->stats[0])->errorCode) {
-           osi_Log1(afsd_logp, "cm_TryBulkStat bulk stat error: %d", 
-                    (&bbp->stats[0])->errorCode);
-            code = (&bbp->stats[0])->errorCode;
-       }
-    }
-
     return code;
 }
 
@@ -2591,6 +2705,8 @@ long cm_SetAttr(cm_scache_t *scp, cm_attr_t *attrp, cm_user_t *userp,
     AFSStoreStatus afsInStatus;
     struct rx_connection * rxconnp;
 
+    memset(&volSync, 0, sizeof(volSync));
+
     /* handle file length setting */
     if (attrp->mask & CM_ATTRMASK_LENGTH)
         return cm_SetLength(scp, &attrp->length, userp, reqp);
@@ -2636,7 +2752,7 @@ long cm_SetAttr(cm_scache_t *scp, cm_attr_t *attrp, cm_user_t *userp,
     lock_ObtainWrite(&scp->rw);
     cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_STORESTATUS);
     if (code == 0)
-        cm_MergeStatus(NULL, scp, &afsOutStatus, &volSync, userp,
+        cm_MergeStatus(NULL, scp, &afsOutStatus, &volSync, userp, reqp,
                         CM_MERGEFLAG_FORCE|CM_MERGEFLAG_STOREDATA);
        
     /* if we're changing the mode bits, discard the ACL cache, 
@@ -2668,6 +2784,8 @@ long cm_Create(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *a
     cm_dirOp_t dirop;
     fschar_t * fnamep = NULL;
 
+    memset(&volSync, 0, sizeof(volSync));
+
     /* can't create names with @sys in them; must expand it manually first.
      * return "invalid request" if they try.
      */
@@ -2689,7 +2807,8 @@ long cm_Create(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *a
      * that someone who does a chmod will know to wait until our call
      * completes.
      */
-    cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_NONE, &dirop);
+    cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_NONE, CM_DIROP_FLAG_NONE,
+                  &dirop);
     lock_ObtainWrite(&dscp->rw);
     code = cm_SyncOp(dscp, NULL, userp, reqp, 0, CM_SCACHESYNC_STOREDATA);
     lock_ReleaseWrite(&dscp->rw);
@@ -2741,7 +2860,7 @@ long cm_Create(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *a
     lock_ObtainWrite(&dscp->rw);
     cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
     if (code == 0) {
-        cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, CM_MERGEFLAG_DIROP);
+        cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, reqp, CM_MERGEFLAG_DIROP);
     }
     lock_ReleaseWrite(&dscp->rw);
 
@@ -2757,10 +2876,10 @@ long cm_Create(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *a
             lock_ObtainWrite(&scp->rw);
            scp->creator = userp;               /* remember who created it */
             if (!cm_HaveCallback(scp)) {
-                cm_MergeStatus(dscp, scp, &newFileStatus, &volSync,
-                               userp, 0);
                 cm_EndCallbackGrantingCall(scp, &cbReq,
-                                           &newFileCallback, 0);
+                                           &newFileCallback, &volSync, 0);
+                cm_MergeStatus(dscp, scp, &newFileStatus, &volSync,
+                               userp, reqp, 0);
                 didEnd = 1;     
             }       
             lock_ReleaseWrite(&scp->rw);
@@ -2769,7 +2888,7 @@ long cm_Create(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *a
 
     /* make sure we end things properly */
     if (!didEnd)
-        cm_EndCallbackGrantingCall(NULL, &cbReq, NULL, 0);
+        cm_EndCallbackGrantingCall(NULL, &cbReq, NULL, NULL, 0);
 
     if (scp && cm_CheckDirOpForSingleChange(&dirop)) {
         cm_DirCreateEntry(&dirop, fnamep, &newFid);
@@ -2791,10 +2910,16 @@ long cm_Create(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *a
     return code;
 }       
 
-long cm_FSync(cm_scache_t *scp, cm_user_t *userp, cm_req_t *reqp)
+/*
+ * locked if TRUE means write-locked
+ * else the cm_scache_t rw must not be held
+ */
+long cm_FSync(cm_scache_t *scp, cm_user_t *userp, cm_req_t *reqp, afs_uint32 locked)
 {
     long code;
 
+    if (locked)
+        lock_ReleaseWrite(&scp->rw);
     code = buf_CleanVnode(scp, userp, reqp);
     if (code == 0) {
         lock_ObtainWrite(&scp->rw);
@@ -2809,7 +2934,10 @@ long cm_FSync(cm_scache_t *scp, cm_user_t *userp, cm_req_t *reqp)
            scp->flags &= ~(CM_SCACHEFLAG_OVERQUOTA | CM_SCACHEFLAG_OUTOFSPACE);
        }
 
-        lock_ReleaseWrite(&scp->rw);
+        if (!locked)
+            lock_ReleaseWrite(&scp->rw);
+    } else if (locked) {
+        lock_ObtainWrite(&scp->rw);
     }
     return code;
 }
@@ -2834,6 +2962,8 @@ long cm_MakeDir(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *
     cm_dirOp_t dirop;
     fschar_t * fnamep = NULL;
 
+    memset(&volSync, 0, sizeof(volSync));
+
     /* can't create names with @sys in them; must expand it manually first.
      * return "invalid request" if they try.
      */
@@ -2855,7 +2985,8 @@ long cm_MakeDir(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *
      * data, so that someone who does a chmod on the dir will wait until
      * our call completes.
      */
-    cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_NONE, &dirop);
+    cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_NONE, CM_DIROP_FLAG_NONE,
+                  &dirop);
     lock_ObtainWrite(&dscp->rw);
     code = cm_SyncOp(dscp, NULL, userp, reqp, 0, CM_SCACHESYNC_STOREDATA);
     lock_ReleaseWrite(&dscp->rw);
@@ -2906,7 +3037,7 @@ long cm_MakeDir(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *
     lock_ObtainWrite(&dscp->rw);
     cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
     if (code == 0) {
-        cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, CM_MERGEFLAG_DIROP);
+        cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, reqp, CM_MERGEFLAG_DIROP);
     }
     lock_ReleaseWrite(&dscp->rw);
 
@@ -2921,10 +3052,10 @@ long cm_MakeDir(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *
         if (code == 0) {
             lock_ObtainWrite(&scp->rw);
             if (!cm_HaveCallback(scp)) {
-                cm_MergeStatus(dscp, scp, &newDirStatus, &volSync,
-                                userp, 0);
                 cm_EndCallbackGrantingCall(scp, &cbReq,
-                                            &newDirCallback, 0);
+                                            &newDirCallback, &volSync, 0);
+                cm_MergeStatus(dscp, scp, &newDirStatus, &volSync,
+                                userp, reqp, 0);
                 didEnd = 1;             
             }
             lock_ReleaseWrite(&scp->rw);
@@ -2933,7 +3064,7 @@ long cm_MakeDir(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *
 
     /* make sure we end things properly */
     if (!didEnd)
-        cm_EndCallbackGrantingCall(NULL, &cbReq, NULL, 0);
+        cm_EndCallbackGrantingCall(NULL, &cbReq, NULL, NULL, 0);
 
     if (scp && cm_CheckDirOpForSingleChange(&dirop)) {
         cm_DirCreateEntry(&dirop, fnamep, &newFid);
@@ -2970,12 +3101,15 @@ long cm_Link(cm_scache_t *dscp, clientchar_t *cnamep, cm_scache_t *sscp, long fl
     cm_dirOp_t dirop;
     fschar_t * fnamep = NULL;
 
+    memset(&volSync, 0, sizeof(volSync));
+
     if (dscp->fid.cell != sscp->fid.cell ||
         dscp->fid.volume != sscp->fid.volume) {
         return CM_ERROR_CROSSDEVLINK;
     }
 
-    cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_NONE, &dirop);
+    cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_NONE, CM_DIROP_FLAG_NONE,
+                  &dirop);
     lock_ObtainWrite(&dscp->rw);
     code = cm_SyncOp(dscp, NULL, userp, reqp, 0, CM_SCACHESYNC_STOREDATA);
     lock_ReleaseWrite(&dscp->rw);
@@ -3024,7 +3158,7 @@ long cm_Link(cm_scache_t *dscp, clientchar_t *cnamep, cm_scache_t *sscp, long fl
     lock_ObtainWrite(&dscp->rw);
     cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
     if (code == 0) {
-        cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, CM_MERGEFLAG_DIROP);
+        cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, reqp, CM_MERGEFLAG_DIROP);
     }
     lock_ReleaseWrite(&dscp->rw);
 
@@ -3041,7 +3175,7 @@ long cm_Link(cm_scache_t *dscp, clientchar_t *cnamep, cm_scache_t *sscp, long fl
     /* Update the linked object status */
     if (code == 0) {
         lock_ObtainWrite(&sscp->rw);
-        cm_MergeStatus(NULL, sscp, &newLinkStatus, &volSync, userp, 0);
+        cm_MergeStatus(NULL, sscp, &newLinkStatus, &volSync, userp, reqp, 0);
         lock_ReleaseWrite(&sscp->rw);
     }
 
@@ -3067,11 +3201,14 @@ long cm_SymLink(cm_scache_t *dscp, clientchar_t *cnamep, fschar_t *contentsp, lo
     cm_dirOp_t dirop;
     fschar_t *fnamep = NULL;
 
+    memset(&volSync, 0, sizeof(volSync));
+
     /* before starting the RPC, mark that we're changing the directory data,
      * so that someone who does a chmod on the dir will wait until our
      * call completes.
      */
-    cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_NONE, &dirop);
+    cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_NONE, CM_DIROP_FLAG_NONE,
+                  &dirop);
     lock_ObtainWrite(&dscp->rw);
     code = cm_SyncOp(dscp, NULL, userp, reqp, 0, CM_SCACHESYNC_STOREDATA);
     lock_ReleaseWrite(&dscp->rw);
@@ -3118,7 +3255,7 @@ long cm_SymLink(cm_scache_t *dscp, clientchar_t *cnamep, fschar_t *contentsp, lo
     lock_ObtainWrite(&dscp->rw);
     cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
     if (code == 0) {
-        cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, CM_MERGEFLAG_DIROP);
+        cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, reqp, CM_MERGEFLAG_DIROP);
     }
     lock_ReleaseWrite(&dscp->rw);
 
@@ -3146,7 +3283,7 @@ long cm_SymLink(cm_scache_t *dscp, clientchar_t *cnamep, fschar_t *contentsp, lo
             lock_ObtainWrite(&scp->rw);
             if (!cm_HaveCallback(scp)) {
                 cm_MergeStatus(dscp, scp, &newLinkStatus, &volSync,
-                                userp, 0);
+                                userp, reqp, 0);
             }       
             lock_ReleaseWrite(&scp->rw);
             cm_ReleaseSCache(scp);
@@ -3191,10 +3328,13 @@ long cm_RemoveDir(cm_scache_t *dscp, fschar_t *fnamep, clientchar_t *cnamep, cm_
     cm_scache_t *scp = NULL;
     int free_fnamep = FALSE;
 
+    memset(&volSync, 0, sizeof(volSync));
+
     if (fnamep == NULL) {
         code = -1;
 #ifdef USE_BPLUS
-        code = cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_READ, &dirop);
+        code = cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_READ,
+                             CM_DIROP_FLAG_NONE, &dirop);
         if (code == 0) {
             code = cm_BPlusDirLookupOriginalName(&dirop, cnamep, &fnamep);
             if (code == 0)
@@ -3214,7 +3354,8 @@ long cm_RemoveDir(cm_scache_t *dscp, fschar_t *fnamep, clientchar_t *cnamep, cm_
      * so that someone who does a chmod on the dir will wait until our
      * call completes.
      */
-    cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_NONE, &dirop);
+    cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_NONE, CM_DIROP_FLAG_NONE,
+                  &dirop);
     lock_ObtainWrite(&dscp->rw);
     code = cm_SyncOp(dscp, NULL, userp, reqp, 0, CM_SCACHESYNC_STOREDATA);
     lock_ReleaseWrite(&dscp->rw);
@@ -3257,7 +3398,7 @@ long cm_RemoveDir(cm_scache_t *dscp, fschar_t *fnamep, clientchar_t *cnamep, cm_
     cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
     if (code == 0) {
         cm_dnlcRemove(dscp, cnamep); 
-        cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, CM_MERGEFLAG_DIROP);
+        cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, reqp, CM_MERGEFLAG_DIROP);
     }
     lock_ReleaseWrite(&dscp->rw);
 
@@ -3352,6 +3493,8 @@ long cm_Rename(cm_scache_t *oldDscp, fschar_t *oldNamep, clientchar_t *cOldNamep
     int free_oldNamep = FALSE;
     cm_scache_t *oldScp = NULL, *newScp = NULL;
 
+    memset(&volSync, 0, sizeof(volSync));
+
     if (cOldNamep == NULL || cNewNamep == NULL ||
         cm_ClientStrLen(cOldNamep) == 0 ||
         cm_ClientStrLen(cNewNamep) == 0)
@@ -3391,7 +3534,8 @@ long cm_Rename(cm_scache_t *oldDscp, fschar_t *oldNamep, clientchar_t *cOldNamep
     if (oldNamep == NULL) {
         code = -1;
 #ifdef USE_BPLUS
-        code = cm_BeginDirOp(oldDscp, userp, reqp, CM_DIRLOCK_READ, &oldDirOp);
+        code = cm_BeginDirOp(oldDscp, userp, reqp, CM_DIRLOCK_READ,
+                             CM_DIROP_FLAG_NONE, &oldDirOp);
         if (code == 0) {
             code = cm_BPlusDirLookupOriginalName(&oldDirOp, cOldNamep, &oldNamep);
             if (code == 0)
@@ -3422,7 +3566,8 @@ long cm_Rename(cm_scache_t *oldDscp, fschar_t *oldNamep, clientchar_t *cOldNamep
         }
 
         oneDir = 1;
-        cm_BeginDirOp(oldDscp, userp, reqp, CM_DIRLOCK_NONE, &oldDirOp);
+        cm_BeginDirOp(oldDscp, userp, reqp, CM_DIRLOCK_NONE,
+                      CM_DIROP_FLAG_NONE, &oldDirOp);
         lock_ObtainWrite(&oldDscp->rw);
         cm_dnlcRemove(oldDscp, cOldNamep);
         cm_dnlcRemove(oldDscp, cNewNamep);
@@ -3456,7 +3601,8 @@ long cm_Rename(cm_scache_t *oldDscp, fschar_t *oldNamep, clientchar_t *cOldNamep
         }
 
         if (oldDscp->fid.vnode < newDscp->fid.vnode) {
-            cm_BeginDirOp(oldDscp, userp, reqp, CM_DIRLOCK_NONE, &oldDirOp);
+            cm_BeginDirOp(oldDscp, userp, reqp, CM_DIRLOCK_NONE,
+                          CM_DIROP_FLAG_NONE, &oldDirOp);
             lock_ObtainWrite(&oldDscp->rw);
             cm_dnlcRemove(oldDscp, cOldNamep);
             code = cm_SyncOp(oldDscp, NULL, userp, reqp, 0,
@@ -3465,7 +3611,8 @@ long cm_Rename(cm_scache_t *oldDscp, fschar_t *oldNamep, clientchar_t *cOldNamep
             if (code != 0)
                 cm_EndDirOp(&oldDirOp);
             if (code == 0) {
-                cm_BeginDirOp(newDscp, userp, reqp, CM_DIRLOCK_NONE, &newDirOp);
+                cm_BeginDirOp(newDscp, userp, reqp, CM_DIRLOCK_NONE,
+                              CM_DIROP_FLAG_NONE, &newDirOp);
                 lock_ObtainWrite(&newDscp->rw);
                 cm_dnlcRemove(newDscp, cNewNamep);
                 code = cm_SyncOp(newDscp, NULL, userp, reqp, 0,
@@ -3485,7 +3632,8 @@ long cm_Rename(cm_scache_t *oldDscp, fschar_t *oldNamep, clientchar_t *cOldNamep
         }
         else {
             /* lock the new vnode entry first */
-            cm_BeginDirOp(newDscp, userp, reqp, CM_DIRLOCK_NONE, &newDirOp);
+            cm_BeginDirOp(newDscp, userp, reqp, CM_DIRLOCK_NONE,
+                          CM_DIROP_FLAG_NONE, &newDirOp);
             lock_ObtainWrite(&newDscp->rw);
             cm_dnlcRemove(newDscp, cNewNamep);
             code = cm_SyncOp(newDscp, NULL, userp, reqp, 0,
@@ -3494,7 +3642,8 @@ long cm_Rename(cm_scache_t *oldDscp, fschar_t *oldNamep, clientchar_t *cOldNamep
             if (code != 0)
                 cm_EndDirOp(&newDirOp);
             if (code == 0) {
-                cm_BeginDirOp(oldDscp, userp, reqp, CM_DIRLOCK_NONE, &oldDirOp);
+                cm_BeginDirOp(oldDscp, userp, reqp, CM_DIRLOCK_NONE,
+                              CM_DIROP_FLAG_NONE, &oldDirOp);
                 lock_ObtainWrite(&oldDscp->rw);
                 cm_dnlcRemove(oldDscp, cOldNamep);
                 code = cm_SyncOp(oldDscp, NULL, userp, reqp, 0,
@@ -3562,7 +3711,7 @@ long cm_Rename(cm_scache_t *oldDscp, fschar_t *oldNamep, clientchar_t *cOldNamep
 
     if (code == 0)
         cm_MergeStatus(NULL, oldDscp, &updatedOldDirStatus, &volSync,
-                       userp, CM_MERGEFLAG_DIROP);
+                       userp, reqp, CM_MERGEFLAG_DIROP);
     lock_ReleaseWrite(&oldDscp->rw);
 
     if (code == 0 && cm_CheckDirOpForSingleChange(&oldDirOp)) {
@@ -3602,7 +3751,7 @@ long cm_Rename(cm_scache_t *oldDscp, fschar_t *oldNamep, clientchar_t *cOldNamep
         cm_SyncOpDone(newDscp, NULL, CM_SCACHESYNC_STOREDATA);
         if (code == 0)
             cm_MergeStatus(NULL, newDscp, &updatedNewDirStatus, &volSync,
-                            userp, CM_MERGEFLAG_DIROP);
+                            userp, reqp, CM_MERGEFLAG_DIROP);
         lock_ReleaseWrite(&newDscp->rw);
 
 #if 0 
@@ -4174,6 +4323,8 @@ long cm_IntSetLock(cm_scache_t * scp, cm_user_t * userp, int lockType,
     AFSVolSync volSync;
     afs_uint32 reqflags = reqp->flags;
 
+    memset(&volSync, 0, sizeof(volSync));
+
     tfid.Volume = scp->fid.volume;
     tfid.Vnode = scp->fid.vnode;
     tfid.Unique = scp->fid.unique;
@@ -4219,6 +4370,8 @@ long cm_IntReleaseLock(cm_scache_t * scp, cm_user_t * userp,
     struct rx_connection * rxconnp;
     AFSVolSync volSync;
 
+    memset(&volSync, 0, sizeof(volSync));
+
     tfid.Volume = scp->fid.volume;
     tfid.Vnode = scp->fid.vnode;
     tfid.Unique = scp->fid.unique;
@@ -4700,6 +4853,10 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
                  "cm_Lock Rejecting lock (code = 0x%x)", code);
     }
 
+    /* Convert from would block to lock not granted */
+    if (code == CM_ERROR_WOULDBLOCK)
+        code = CM_ERROR_LOCK_NOT_GRANTED;
+
     return code;
 }
 
@@ -4816,10 +4973,12 @@ long cm_UnlockByKey(cm_scache_t * scp,
     if (scp->serverLock == LockWrite &&
         scp->exclusiveLocks == 0 &&
         scp->sharedLocks > 0) {
-
         /* The serverLock should be downgraded to LockRead */
         osi_Log0(afsd_logp, "  DOWNGRADE lock from LockWrite to LockRead");
 
+        /* Make sure there are no dirty buffers left. */
+        code = cm_FSync(scp, userp, reqp, TRUE);
+
         /* since scp->serverLock looked sane, we are going to assume
            that we have a valid server lock. */
         scp->lockDataVersion = scp->dataVersion;
@@ -4871,6 +5030,11 @@ long cm_UnlockByKey(cm_scache_t * scp,
               scp->sharedLocks == 0) {
         /* The serverLock should be released entirely */
 
+        if (scp->serverLock == LockWrite) {
+            /* Make sure there are no dirty buffers left. */
+            code = cm_FSync(scp, userp, reqp, TRUE);
+        }
+
         code = cm_IntReleaseLock(scp, userp, reqp);
 
         if (code == 0)
@@ -5021,6 +5185,9 @@ long cm_Unlock(cm_scache_t *scp,
         /* The serverLock should be downgraded to LockRead */
         osi_Log0(afsd_logp, "  DOWNGRADE lock from LockWrite to LockRead");
 
+        /* Make sure there are no dirty buffers left. */
+        code = cm_FSync(scp, userp, reqp, TRUE);
+
         /* Since we already had a lock, we assume that there is a
            valid server lock. */
         scp->lockDataVersion = scp->dataVersion;
@@ -5085,6 +5252,11 @@ long cm_Unlock(cm_scache_t *scp,
               scp->sharedLocks == 0) {
         /* The serverLock should be released entirely */
 
+        if (scp->serverLock == LockWrite) {
+            /* Make sure there are no dirty buffers left. */
+            code = cm_FSync(scp, userp, reqp, TRUE);
+        }
+
         code = cm_IntReleaseLock(scp, userp, reqp);
 
         if (code == 0) {
@@ -5154,6 +5326,8 @@ void cm_CheckLocks()
     struct rx_connection * rxconnp;
     cm_scache_t * scp;
 
+    memset(&volSync, 0, sizeof(volSync));
+
     cm_InitReq(&req);
 
     lock_ObtainWrite(&cm_scacheLock);