Windows: cm_CheckNTDelete missing SyncDone call
[openafs.git] / src / WINNT / afsd / cm_vnodeops.c
index 0ba90ca..f1f691b 100644 (file)
@@ -1,22 +1,21 @@
 /*
  * 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 <afs/stds.h>
+#include <roken.h>
+
+#include <afs/unified_afs.h>
 
 #include <windows.h>
 #include <winsock2.h>
-#include <stddef.h>
-#include <malloc.h>
-#include <string.h>
-#include <stdlib.h>
-#include <errno.h>
 
 #include <osi.h>
 
@@ -109,11 +108,11 @@ long cm_CheckOpen(cm_scache_t *scp, int openMode, int trunc, cm_user_t *userp,
     long code;
 
     rights = 0;
-    if (openMode != 1) 
+    if (openMode != 1)
        rights |= PRSFS_READ;
-    if (openMode == 1 || openMode == 2 || trunc) 
+    if (openMode == 1 || openMode == 2 || trunc)
        rights |= PRSFS_WRITE;
-        
+
     lock_ObtainWrite(&scp->rw);
 
     code = cm_SyncOp(scp, NULL, userp, reqp, rights,
@@ -121,7 +120,7 @@ long cm_CheckOpen(cm_scache_t *scp, int openMode, int trunc, cm_user_t *userp,
                      | CM_SCACHESYNC_NEEDCALLBACK
                      | CM_SCACHESYNC_LOCK);
 
-    if (code == 0 && 
+    if (code == 0 &&
         ((rights & PRSFS_WRITE) || (rights & PRSFS_READ)) &&
         scp->fileType == CM_SCACHETYPE_FILE) {
 
@@ -147,7 +146,7 @@ long cm_CheckOpen(cm_scache_t *scp, int openMode, int trunc, cm_user_t *userp,
         code = cm_Lock(scp, sLockType, LOffset, LLength, key, 0, userp, reqp, NULL);
 
         if (code == 0) {
-            cm_Unlock(scp, sLockType, LOffset, LLength, key, userp, reqp);
+            cm_Unlock(scp, sLockType, LOffset, LLength, key, 0, userp, reqp);
         } else {
             /* In this case, we allow the file open to go through even
                though we can't enforce mandatory locking on the
@@ -156,17 +155,8 @@ long cm_CheckOpen(cm_scache_t *scp, int openMode, int trunc, cm_user_t *userp,
                 !(rights & PRSFS_WRITE))
                 code = 0;
             else {
-               switch (code) {
-               case CM_ERROR_ALLOFFLINE:
-               case CM_ERROR_ALLDOWN:
-               case CM_ERROR_ALLBUSY:
-               case CM_ERROR_TIMEDOUT:
-               case CM_ERROR_RETRY:
-               case CM_ERROR_WOULDBLOCK:
-                   break;
-               default:
+               if (code == CM_ERROR_LOCK_NOT_GRANTED)
                    code = CM_ERROR_SHARING_VIOLATION;
-               }
            }
         }
 
@@ -184,21 +174,42 @@ long cm_CheckOpen(cm_scache_t *scp, int openMode, int trunc, cm_user_t *userp,
 }
 
 /* return success if we can open this file in this mode */
-long cm_CheckNTOpen(cm_scache_t *scp, unsigned int desiredAccess,
-                    unsigned int createDisp, cm_user_t *userp, cm_req_t *reqp, 
+long cm_CheckNTOpen(cm_scache_t *scp,
+                    unsigned int desiredAccess,
+                    unsigned int shareAccess,
+                    unsigned int createDisp,
+                    afs_offs_t process_id,
+                    afs_offs_t handle_id,
+                    cm_user_t *userp, cm_req_t *reqp,
                    cm_lock_data_t **ldpp)
 {
     long rights;
-    long code;
+    long code = 0;
+    afs_uint16 session_id;
 
     osi_assertx(ldpp != NULL, "null cm_lock_data_t");
     *ldpp = NULL;
 
+    /* compute the session id */
+    if (reqp->flags & CM_REQ_SOURCE_SMB)
+        session_id = CM_SESSION_SMB;
+    else if (reqp->flags & CM_REQ_SOURCE_REDIR)
+        session_id = CM_SESSION_IFS;
+    else
+        session_id = CM_SESSION_CMINT;
+
+    /* Ignore the SYNCHRONIZE privilege */
+    desiredAccess &= ~SYNCHRONIZE;
+
     /* Always allow delete; the RPC will tell us if it's OK */
+    rights = 0;
+
     if (desiredAccess == DELETE)
-        return 0;
+        goto done_2;
 
-    rights = 0;
+    /* Always allow reading attributes (Hidden, System, Readonly, ...) */
+    if (desiredAccess == FILE_READ_ATTRIBUTES)
+        goto done_2;
 
     if (desiredAccess & (AFS_ACCESS_READ|AFS_ACCESS_EXECUTE))
         rights |= (scp->fileType == CM_SCACHETYPE_DIRECTORY ? PRSFS_LOOKUP : PRSFS_READ);
@@ -210,6 +221,9 @@ long cm_CheckNTOpen(cm_scache_t *scp, unsigned int desiredAccess,
     if (desiredAccess & AFS_ACCESS_WRITE)
         rights |= PRSFS_WRITE;
 
+    if (desiredAccess & DELETE)
+        rights |= PRSFS_DELETE;
+
     lock_ObtainWrite(&scp->rw);
 
     code = cm_SyncOp(scp, NULL, userp, reqp, rights,
@@ -227,8 +241,9 @@ long cm_CheckNTOpen(cm_scache_t *scp, unsigned int desiredAccess,
         code = CM_ERROR_NOACCESS;
 
     if (code == 0 &&
-             ((rights & PRSFS_WRITE) || (rights & PRSFS_READ)) &&
-             scp->fileType == CM_SCACHETYPE_FILE) {
+        !(shareAccess & FILE_SHARE_WRITE) &&
+        ((rights & PRSFS_WRITE) || (rights & PRSFS_READ)) &&
+        scp->fileType == CM_SCACHETYPE_FILE) {
         cm_key_t key;
         unsigned int sLockType;
         LARGE_INTEGER LOffset, LLength;
@@ -236,12 +251,13 @@ long cm_CheckNTOpen(cm_scache_t *scp, unsigned int desiredAccess,
         /* Check if there's some sort of lock on the file at the
            moment. */
 
-        key = cm_GenerateKey(CM_SESSION_CMINT,0,0);
         if (rights & PRSFS_WRITE)
             sLockType = 0;
         else
             sLockType = LOCKING_ANDX_SHARED_LOCK;
 
+        key = cm_GenerateKey(session_id, process_id, 0);
+
         /* single byte lock at offset 0x0100 0000 0000 0000 */
         LOffset.HighPart = CM_FLSHARE_OFFSET_HIGH;
         LOffset.LowPart  = CM_FLSHARE_OFFSET_LOW;
@@ -251,65 +267,56 @@ long cm_CheckNTOpen(cm_scache_t *scp, unsigned int desiredAccess,
         code = cm_Lock(scp, sLockType, LOffset, LLength, key, 0, userp, reqp, NULL);
 
         if (code == 0) {
-           (*ldpp) = (cm_lock_data_t *)malloc(sizeof(cm_lock_data_t));
-           if (!*ldpp) {
-               code = ENOMEM;
-               goto _syncopdone;
-           }
+            (*ldpp) = (cm_lock_data_t *)malloc(sizeof(cm_lock_data_t));
+            if (!*ldpp) {
+                code = ENOMEM;
+                goto _done;
+            }
 
-           (*ldpp)->key = key;
-           (*ldpp)->sLockType = sLockType;
-           (*ldpp)->LOffset.HighPart = LOffset.HighPart;
-           (*ldpp)->LOffset.LowPart = LOffset.LowPart;
-           (*ldpp)->LLength.HighPart = LLength.HighPart;
-           (*ldpp)->LLength.LowPart = LLength.LowPart;
+            (*ldpp)->key = key;
+            (*ldpp)->sLockType = sLockType;
+            (*ldpp)->LOffset.HighPart = LOffset.HighPart;
+            (*ldpp)->LOffset.LowPart = LOffset.LowPart;
+            (*ldpp)->LLength.HighPart = LLength.HighPart;
+            (*ldpp)->LLength.LowPart = LLength.LowPart;
         } else {
-            /* In this case, we allow the file open to go through even
-               though we can't enforce mandatory locking on the
-               file. */
+            /*
+             * In this case, we allow the file open to go through even
+             * though we can't enforce mandatory locking on the
+             * file. */
             if (code == CM_ERROR_NOACCESS &&
-                !(rights & PRSFS_WRITE))
+                 !(rights & PRSFS_WRITE))
                 code = 0;
             else {
-               switch (code) {
-               case CM_ERROR_ALLOFFLINE:
-               case CM_ERROR_ALLDOWN:
-               case CM_ERROR_ALLBUSY:
-               case CM_ERROR_TIMEDOUT:
-               case CM_ERROR_RETRY:
-               case CM_ERROR_WOULDBLOCK:
-                   break;
-               default:
-                   code = CM_ERROR_SHARING_VIOLATION;
-               }
-           }
+                if (code == CM_ERROR_LOCK_NOT_GRANTED)
+                    code = CM_ERROR_SHARING_VIOLATION;
+            }
         }
     } else if (code != 0) {
         goto _done;
     }
 
-  _syncopdone:
-    cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_LOCK);
-
  _done:
     lock_ReleaseWrite(&scp->rw);
 
+ done_2:
     osi_Log3(afsd_logp,"cm_CheckNTOpen scp 0x%p ldp 0x%p code 0x%x", scp, *ldpp, code);
     return code;
 }
 
-extern long cm_CheckNTOpenDone(cm_scache_t *scp, cm_user_t *userp, cm_req_t *reqp, 
+extern long cm_CheckNTOpenDone(cm_scache_t *scp, cm_user_t *userp, cm_req_t *reqp,
                               cm_lock_data_t ** ldpp)
 {
-    osi_Log2(afsd_logp,"cm_CheckNTOpenDone scp 0x%p ldp 0x%p", scp, *ldpp);
-    if (*ldpp) {
-       lock_ObtainWrite(&scp->rw);
-       cm_Unlock(scp, (*ldpp)->sLockType, (*ldpp)->LOffset, (*ldpp)->LLength, 
-                 (*ldpp)->key, userp, reqp);
-       lock_ReleaseWrite(&scp->rw);
+       osi_Log2(afsd_logp,"cm_CheckNTOpenDone scp 0x%p ldp 0x%p", scp, ldpp ? *ldpp : 0);
+    lock_ObtainWrite(&scp->rw);
+    if (ldpp && *ldpp) {
+       cm_Unlock(scp, (*ldpp)->sLockType, (*ldpp)->LOffset, (*ldpp)->LLength,
+                 (*ldpp)->key, 0, userp, reqp);
        free(*ldpp);
        *ldpp = NULL;
     }
+    cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_LOCK);
+    lock_ReleaseWrite(&scp->rw);
     return 0;
 }
 /*
@@ -341,7 +348,8 @@ long cm_CheckNTDelete(cm_scache_t *dscp, cm_scache_t *scp, cm_user_t *userp,
     lock_ObtainWrite(&scp->rw);
     code = cm_SyncOp(scp, NULL, userp, reqp, PRSFS_DELETE,
                       CM_SCACHESYNC_GETSTATUS | CM_SCACHESYNC_NEEDCALLBACK);
-    cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
+    if (!code)
+        cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
     lock_ReleaseWrite(&scp->rw);
     if (code)
         return code;
@@ -352,7 +360,7 @@ long cm_CheckNTDelete(cm_scache_t *dscp, cm_scache_t *scp, cm_user_t *userp,
         return code;
 
     thyper.HighPart = 0; thyper.LowPart = 0;
-    code = buf_Get(scp, &thyper, &bufferp);
+    code = buf_Get(scp, &thyper, reqp, 0, &bufferp);
     if (code)
         return code;
 
@@ -367,8 +375,10 @@ long cm_CheckNTDelete(cm_scache_t *dscp, cm_scache_t *scp, cm_user_t *userp,
         if (code)
             goto done;
 
-        if (cm_HaveBuffer(scp, bufferp, 1))
+       if (cm_HaveBuffer(scp, bufferp, 1)) {
+           cm_SyncOpDone(scp, bufferp, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_READ | CM_SCACHESYNC_BUFLOCKED);
             break;
+       }
 
         /* otherwise, load the buffer and try again */
         lock_ReleaseMutex(&bufferp->mx);
@@ -412,21 +422,21 @@ long cm_CheckNTDelete(cm_scache_t *dscp, cm_scache_t *scp, cm_user_t *userp,
         code = CM_ERROR_NOTEMPTY;
     else
         code = 0;
-  done:   
+  done:
     lock_ReleaseMutex(&bufferp->mx);
     buf_Release(bufferp);
     if (releaseLock)
         lock_ReleaseWrite(&scp->rw);
     return code;
-}       
+}
 
 /*
  * Iterate through all entries in a directory.
  * When the function funcp is called, the buffer is locked but the
  * directory vnode is not.
  *
- * If the retscp parameter is not NULL, the parmp must be a 
- * cm_lookupSearch_t object.  
+ * If the retscp parameter is not NULL, the parmp must be a
+ * cm_lookupSearch_t object.
  */
 long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
                  osi_hyper_t *startOffsetp, cm_user_t *userp, cm_req_t *reqp,
@@ -447,7 +457,7 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
     int slotInPage;
     long nextEntryCookie;
     int numDirChunks;  /* # of 32 byte dir chunks in this entry */
-        
+
     /* get the directory size */
     lock_ObtainWrite(&scp->rw);
     code = cm_SyncOp(scp, NULL, userp, reqp, PRSFS_LOOKUP,
@@ -455,7 +465,7 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
     lock_ReleaseWrite(&scp->rw);
     if (code)
         return code;
-        
+
     if (scp->fileType != CM_SCACHETYPE_DIRECTORY)
         return CM_ERROR_NOTDIR;
 
@@ -474,7 +484,7 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
 #else /* !AFS_FREELANCE_CLIENT */
             TRUE
 #endif
-            ) 
+            )
         {
             int casefold = sp->caseFold;
             sp->caseFold = 0; /* we have a strong preference for exact matches */
@@ -484,55 +494,59 @@ 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
         }
-    }  
+    }
 
     /*
      * XXX We only get the length once.  It might change when we drop the
@@ -547,7 +561,7 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
     else {
         curOffset.HighPart = 0;
         curOffset.LowPart = 0;
-    }   
+    }
 
     while (1) {
         /* make sure that curOffset.LowPart doesn't point to the first
@@ -564,11 +578,11 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
         else {
             /* we're in a later dir page */
             if (temp < 32) temp = 32;
-        }       
-               
+        }
+
         /* make sure the low order 5 bits are zero */
         temp &= ~(32-1);
-                
+
         /* now put temp bits back ito curOffset.LowPart */
         curOffset.LowPart &= ~(2048-1);
         curOffset.LowPart |= temp;
@@ -576,7 +590,7 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
         /* check if we've passed the dir's EOF */
         if (LargeIntegerGreaterThanOrEqualTo(curOffset, dirLength))
             break;
-                
+
         /* see if we can use the bufferp we have now; compute in which
          * page the current offset would be, and check whether that's
          * the offset of the buffer we have.  If not, get the buffer.
@@ -591,7 +605,7 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
                 bufferp = NULL;
             }
 
-            code = buf_Get(scp, &thyper, &bufferp);
+            code = buf_Get(scp, &thyper, reqp, 0, &bufferp);
             if (code) {
                 /* if buf_Get() fails we do not have a buffer object to lock */
                 bufferp = NULL;
@@ -614,7 +628,7 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
                     break;
                 }
                cm_SyncOpDone(scp, bufferp, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_READ | CM_SCACHESYNC_BUFLOCKED);
-                                
+
                 if (cm_HaveBuffer(scp, bufferp, 1)) {
                     lock_ReleaseWrite(&scp->rw);
                     break;
@@ -626,7 +640,7 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
                                     reqp);
                 lock_ReleaseWrite(&scp->rw);
                 lock_ObtainMutex(&bufferp->mx);
-                if (code) 
+                if (code)
                     break;
             }
             if (code) {
@@ -636,7 +650,7 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
                 break;
             }
         }      /* if (wrong buffer) ... */
-           
+
         /* now we have the buffer containing the entry we're interested
          * in; copy it out if it represents a non-deleted entry.
          */
@@ -668,12 +682,27 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
         tp = bufferp->datap + entryInBuffer;
         dep = (cm_dirEntry_t *) tp;    /* now points to AFS3 dir entry */
 
+        /*
+         * here are some consistency checks
+         */
+        if (dep->flag != CM_DIR_FFIRST ||
+            strlen(dep->name) > 256) {
+            code = CM_ERROR_INVAL;
+            osi_Log2(afsd_logp,
+                     "cm_ApplyDir invalid directory entry for scp %p bufp %p",
+                     scp, bufferp);
+            osi_Log4(afsd_logp,"... cell %u vol %u vnode %u uniq %u",
+                     scp->fid.cell, scp->fid.volume, scp->fid.vnode, scp->fid.unique);
+            bufferp->dataVersion = CM_BUF_VERSION_BAD;
+            break;
+        }
+
         /* while we're here, compute the next entry's location, too,
          * since we'll need it when writing out the cookie into the
          * dir listing stream.
          */
         numDirChunks = cm_NameEntries(dep->name, NULL);
-               
+
         /* compute the offset of the cookie representing the next entry */
         nextEntryCookie = curOffset.LowPart
             + (CM_DIR_CHUNKSIZE * numDirChunks);
@@ -681,10 +710,10 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
         if (dep->fid.vnode != 0) {
             /* this is one of the entries to use: it is not deleted */
             code = (*funcp)(scp, dep, parmp, &curOffset);
-            if (code) 
+            if (code)
                 break;
         }      /* if we're including this name */
-                
+
       nextEntry:
         /* and adjust curOffset to be where the new cookie is */
         thyper.HighPart = 0;
@@ -728,7 +757,11 @@ long cm_LookupSearchProc(cm_scache_t *scp, cm_dirEntry_t *dep, void *rockp,
 
     sp = (cm_lookupSearch_t *) rockp;
 
-    cm_FsStringToNormString(dep->name, -1, matchName, lengthof(matchName));
+    if (cm_FsStringToNormString(dep->name, -1, matchName, lengthof(matchName)) == 0) {
+        /* Can't normalize FS string. */
+        return 0;
+    }
+
     if (sp->caseFold)
         match = cm_NormStrCmpI(matchName, sp->nsearchNamep);
     else
@@ -736,6 +769,7 @@ long cm_LookupSearchProc(cm_scache_t *scp, cm_dirEntry_t *dep, void *rockp,
 
     if (match != 0
         && sp->hasTilde
+        && cm_shortNames
         && !cm_Is8Dot3(matchName)) {
 
         cm_Gen8Dot3NameInt(dep->name, &dep->fid, matchName, NULL);
@@ -750,7 +784,7 @@ long cm_LookupSearchProc(cm_scache_t *scp, cm_dirEntry_t *dep, void *rockp,
         return 0;
 
     sp->found = 1;
-    if (!sp->caseFold) 
+    if (!sp->caseFold)
         sp->ExactFound = 1;
 
     if (!sp->caseFold || looking_for_short_name) {
@@ -797,76 +831,69 @@ long cm_LookupSearchProc(cm_scache_t *scp, cm_dirEntry_t *dep, void *rockp,
   inexact:
     cm_SetFid(&sp->fid, sp->fid.cell, sp->fid.volume, ntohl(dep->fid.vnode), ntohl(dep->fid.unique));
     return 0;
-}       
+}
 
 /* read the contents of a mount point into the appropriate string.
  * called with write locked scp, and returns with locked scp.
  */
 long cm_ReadMountPoint(cm_scache_t *scp, cm_user_t *userp, cm_req_t *reqp)
 {
-    long code;
-    cm_buf_t *bufp;
-    osi_hyper_t thyper;
-    int tlen;
-
-    if (scp->mountPointStringp[0]) 
-        return 0;
-        
-    /* otherwise, we have to read it in */
-    lock_ReleaseWrite(&scp->rw);
-
-    thyper.LowPart = thyper.HighPart = 0;
-    code = buf_Get(scp, &thyper, &bufp);
+    long code = 0;
 
-    lock_ObtainWrite(&scp->rw);
+    code = cm_SyncOp(scp, NULL, userp, reqp, 0, CM_SCACHESYNC_FETCHDATA);
     if (code)
         return code;
 
-    while (1) {
-        code = cm_SyncOp(scp, bufp, userp, reqp, 0,
-                          CM_SCACHESYNC_READ | CM_SCACHESYNC_NEEDCALLBACK);
-        if (code)
-            goto done;
-
-       cm_SyncOpDone(scp, bufp, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_READ);
+    if (scp->mountPointStringp[0] &&
+         scp->mpDataVersion == scp->dataVersion) {
+        code = 0;
+        goto done;
+    }
 
-        if (cm_HaveBuffer(scp, bufp, 0)) 
-            break;
+#ifdef AFS_FREELANCE_CLIENT
+    /* File servers do not have data for freelance entries */
+    if (cm_freelanceEnabled &&
+        scp->fid.cell==AFS_FAKE_ROOT_CELL_ID &&
+        scp->fid.volume==AFS_FAKE_ROOT_VOL_ID )
+    {
+        code = cm_FreelanceFetchMountPointString(scp);
+    } else
+#endif /* AFS_FREELANCE_CLIENT */
+    {
+        char temp[MOUNTPOINTLEN];
+        osi_hyper_t offset;
+        afs_uint32 bytesRead = 0;
 
-        /* otherwise load buffer */
-        code = cm_GetBuffer(scp, bufp, NULL, userp, reqp);
+        /* otherwise, we have to read it in */
+        offset.LowPart = offset.HighPart = 0;
+        code = cm_GetData(scp, &offset, temp, MOUNTPOINTLEN, &bytesRead, userp, reqp);
         if (code)
             goto done;
-    }
-    /* locked, has callback, has valid data in buffer */
-    if ((tlen = scp->length.LowPart) > MOUNTPOINTLEN - 1) 
-        return CM_ERROR_TOOBIG;
-    if (tlen <= 0) {
-        code = CM_ERROR_INVAL;
-        goto done;
-    }
 
-    /* someone else did the work while we were out */
-    if (scp->mountPointStringp[0]) {
-        code = 0;
-        goto done;
-    }
+        /*
+         * scp->length is the actual length of the mount point string.
+         * It is current because cm_GetData merged the most up to date
+         * status info into scp and has not dropped the rwlock since.
+         */
+        if (scp->length.LowPart > MOUNTPOINTLEN - 1) {
+            code = CM_ERROR_TOOBIG;
+            goto done;
+        }
 
-    /* otherwise, copy out the link */
-    memcpy(scp->mountPointStringp, bufp->datap, tlen);
+        if (scp->length.LowPart == 0) {
+            code = CM_ERROR_INVAL;
+            goto done;
+        }
 
-    /* now make it null-terminated.  Note that the original contents of a
-     * link that is a mount point is "#volname." where "." is there just to
-     * be turned into a null.  That is, we can trash the last char of the
-     * link without damaging the vol name.  This is a stupid convention,
-     * but that's the protocol.
-     */
-    scp->mountPointStringp[tlen-1] = 0;
-    code = 0;
+        /* convert the terminating dot to a NUL */
+        temp[scp->length.LowPart - 1] = 0;
+        memcpy(scp->mountPointStringp, temp, scp->length.LowPart);
+        scp->mpDataVersion = scp->dataVersion;
+    }
 
   done:
-    if (bufp) 
-        buf_Release(bufp);
+    cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_FETCHDATA);
+
     return code;
 }
 
@@ -879,7 +906,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;
@@ -890,10 +916,12 @@ long cm_FollowMountPoint(cm_scache_t *scp, cm_scache_t *dscp, cm_user_t *userp,
     size_t vnLength;
     int targetType;
 
+    *outScpp = NULL;
+
     if (scp->mountRootFid.cell != 0 && scp->mountRootGen >= cm_data.mountRootGen) {
         tfid = scp->mountRootFid;
         lock_ReleaseWrite(&scp->rw);
-        code = cm_GetSCache(&tfid, outScpp, userp, reqp);
+        code = cm_GetSCache(&tfid, NULL, outScpp, userp, reqp);
         lock_ObtainWrite(&scp->rw);
         return code;
     }
@@ -902,7 +930,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(':'));
@@ -914,13 +941,27 @@ long cm_FollowMountPoint(cm_scache_t *scp, cm_scache_t *dscp, cm_user_t *userp,
 
         /* now look up the cell */
         lock_ReleaseWrite(&scp->rw);
-        cellp = cm_GetCell(cellNamep, CM_FLAG_CREATE);
+        cellp = cm_GetCell(cellNamep, CM_FLAG_CREATE|CM_FLAG_NOPROBE);
         lock_ObtainWrite(&scp->rw);
     } else {
         /* normal mt pt */
         volNamep = cm_FsStrDup(mpNamep + 1);
 
-        cellp = cm_FindCellByID(scp->fid.cell, 0);
+#ifdef AFS_FREELANCE_CLIENT
+        /*
+         * Mount points in the Freelance cell should default
+         * to the workstation cell.
+         */
+        if (cm_freelanceEnabled &&
+             scp->fid.cell==AFS_FAKE_ROOT_CELL_ID &&
+             scp->fid.volume==AFS_FAKE_ROOT_VOL_ID )
+        {
+            fschar_t rootCellName[256]="";
+            cm_GetRootCellName(rootCellName);
+            cellp = cm_GetCell(rootCellName, 0);
+        } else
+#endif /* AFS_FREELANCE_CLIENT */
+            cellp = cm_FindCellByID(scp->fid.cell, 0);
     }
 
     if (!cellp) {
@@ -941,46 +982,46 @@ long cm_FollowMountPoint(cm_scache_t *scp, cm_scache_t *dscp, cm_user_t *userp,
     if (targetType == BACKVOL
          && (scp->flags & (CM_SCACHEFLAG_RO | CM_SCACHEFLAG_PURERO))
          == CM_SCACHEFLAG_RO) {
-        code = CM_ERROR_NOSUCHVOLUME;
+       code = CM_ERROR_TOO_MANY_SYMLINKS;
         goto done;
     }
 
     /* now we need to get the volume */
     lock_ReleaseWrite(&scp->rw);
     if (cm_VolNameIsID(volNamep)) {
-        code = cm_FindVolumeByID(cellp, atoi(volNamep), userp, reqp, 
+        code = cm_FindVolumeByID(cellp, atoi(volNamep), userp, reqp,
                                 CM_GETVOL_FLAG_CREATE, &volp);
     } else {
-        code = cm_FindVolumeByName(cellp, volNamep, userp, reqp, 
+        code = cm_FindVolumeByName(cellp, volNamep, userp, reqp,
                                   CM_GETVOL_FLAG_CREATE, &volp);
     }
     lock_ObtainWrite(&scp->rw);
-        
+
     if (code == 0) {
         afs_uint32 cell, volume;
         cm_vol_state_t *statep;
 
         cell = cellp->cellID;
-        
+
         /* if the mt pt originates in a .backup volume (not a .readonly)
          * and FollowBackupPath is active, and if there is a .backup
          * volume for the target, then use the .backup of the target
          * instead of the read-write.
          */
-        if (cm_followBackupPath && 
+        if (cm_followBackupPath &&
             volp->vol[BACKVOL].ID != 0 &&
             (dscp->flags & (CM_SCACHEFLAG_RO|CM_SCACHEFLAG_PURERO)) == CM_SCACHEFLAG_RO &&
             (targetType == RWVOL || targetType == ROVOL && volp->vol[ROVOL].ID == 0)
             ) {
             targetType = BACKVOL;
-        } 
+        }
         /* if the mt pt is in a read-only volume (not just a
          * backup), and if there is a read-only volume for the
          * target, and if this is a targetType '#' mount point, use
          * the read-only, otherwise use the one specified.
          */
-        else if (mtType == '#' && targetType == RWVOL && 
-                 (scp->flags & CM_SCACHEFLAG_PURERO) && 
+        else if (mtType == '#' && targetType == RWVOL &&
+                 (scp->flags & CM_SCACHEFLAG_PURERO) &&
                  volp->vol[ROVOL].ID != 0) {
             targetType = ROVOL;
         }
@@ -997,7 +1038,7 @@ long cm_FollowMountPoint(cm_scache_t *scp, cm_scache_t *dscp, cm_user_t *userp,
 
         tfid = scp->mountRootFid;
         lock_ReleaseWrite(&scp->rw);
-        code = cm_GetSCache(&tfid, outScpp, userp, reqp);
+        code = cm_GetSCache(&tfid, NULL, outScpp, userp, reqp);
         lock_ObtainWrite(&scp->rw);
     }
 
@@ -1009,10 +1050,10 @@ long cm_FollowMountPoint(cm_scache_t *scp, cm_scache_t *dscp, cm_user_t *userp,
     if (volNamep)
         free(volNamep);
     return code;
-}       
+}
 
 long cm_LookupInternal(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_user_t *userp,
-                       cm_req_t *reqp, cm_scache_t **outpScpp)
+                       cm_req_t *reqp, cm_scache_t **outScpp)
 {
     long code;
     int dnlcHit = 1;   /* did we hit in the dnlc? yes, we did */
@@ -1022,6 +1063,9 @@ 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;
 
     memset(&rock, 0, sizeof(rock));
 
@@ -1037,8 +1081,17 @@ long cm_LookupInternal(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_u
     }
 
     nnamep = cm_ClientStringToNormStringAlloc(cnamep, -1, NULL);
+    if (!nnamep) {
+        code = CM_ERROR_NOSUCHFILE;
+        goto done;
+    }
     fnamep = cm_ClientStringToFsStringAlloc(cnamep, -1, NULL);
+    if (!fnamep) {
+        code = CM_ERROR_NOSUCHFILE;
+        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
@@ -1049,7 +1102,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);
@@ -1075,8 +1129,9 @@ long cm_LookupInternal(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_u
                 rock.found = TRUE;
                 goto haveFid;
             }
-            
-            return CM_ERROR_BPLUS_NOMATCH;
+
+            code = CM_ERROR_BPLUS_NOMATCH;
+            goto notfound;
         }
 #endif
     }
@@ -1097,8 +1152,8 @@ long cm_LookupInternal(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_u
      * looking for.  Any other non-zero code is an error.
      */
     if (code && code != CM_ERROR_STOPNOW) {
-        /* if the cm_scache_t we are searching in is not a directory 
-         * we must return path not found because the error 
+        /* 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
          */
         if (code == CM_ERROR_NOTDIR) {
@@ -1110,9 +1165,10 @@ 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) {
+        if (!(cm_freelanceEnabled && cm_freelanceDiscovery) || !getroot) {
             if (flags & CM_FLAG_CHECKPATH)
                 code = CM_ERROR_NOSUCHPATH;
             else
@@ -1123,46 +1179,90 @@ long cm_LookupInternal(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_u
                  !cm_ClientStrChr(cnamep, '%') &&
                  cm_ClientStrCmpI(cnamep, _C("srvsvc")) &&
                  cm_ClientStrCmpI(cnamep, _C("wkssvc")) &&
-                 cm_ClientStrCmpI(cnamep, _C("ipc$"))) 
+                 cm_ClientStrCmpI(cnamep, _C("ipc$")))
         {
             /* nonexistent dir on freelance root, so add it */
-            fschar_t fullname[200] = ".";
+            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_Log1(afsd_logp,"cm_Lookup adding mount for non-existent directory: %S",
                      osi_LogSaveClientString(afsd_logp,cnamep));
 
-            /* 
+            /*
              * There is an ugly behavior where a share name "foo" will be searched
              * for as "fo".  If the searched for name differs by an already existing
-             * symlink or mount point in the Freelance directory, do not add the 
+             * symlink or mount point in the Freelance directory, do not add the
              * new value automatically.
              */
 
             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 (!found || code < 0) {   /* add mount point failed, so give up */
+
+            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;
                 else
@@ -1179,25 +1279,37 @@ 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; 
-        code = cm_GetSCache(&rock.fid, &tscp, userp, reqp);
-        if (code) 
+        dnlcHit = 0;
+        code = cm_GetSCache(&rock.fid, &dscp->fid, &tscp, userp, reqp);
+        if (code)
             goto done;
     }
     /* tscp is now held */
 
     lock_ObtainWrite(&tscp->rw);
-    code = cm_SyncOp(tscp, NULL, userp, reqp, 0,
-                      CM_SCACHESYNC_GETSTATUS | CM_SCACHESYNC_NEEDCALLBACK);
-    if (code) { 
-        lock_ReleaseWrite(&tscp->rw);
-        cm_ReleaseSCache(tscp);
-        goto done;
+
+    /*
+     * Do not get status if we do not already have a callback or know the type.
+     * The process of reading the mount point string will obtain status information
+     * in a single RPC.  No reason to add a second round trip.
+     *
+     * If we do have a callback, use cm_SyncOp to get status in case the
+     * current cm_user_t is not the same as the one that obtained the
+     * mount point string contents.
+     */
+    if (cm_HaveCallback(tscp) || tscp->fileType == CM_SCACHETYPE_UNKNOWN) {
+        code = cm_SyncOp(tscp, NULL, userp, reqp, 0,
+                          CM_SCACHESYNC_GETSTATUS | CM_SCACHESYNC_NEEDCALLBACK);
+        if (code) {
+            lock_ReleaseWrite(&tscp->rw);
+            cm_ReleaseSCache(tscp);
+            goto done;
+        }
+        cm_SyncOpDone(tscp, NULL, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
     }
-    cm_SyncOpDone(tscp, NULL, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
     /* tscp is now locked */
 
     if (!(flags & CM_FLAG_NOMOUNTCHASE)
@@ -1221,7 +1333,7 @@ long cm_LookupInternal(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_u
     }
 
     /* copy back pointer */
-    *outpScpp = tscp;
+    *outScpp = tscp;
 
     /* insert scache in dnlc */
     if ( !dnlcHit && !(flags & CM_FLAG_NOMOUNTCHASE) && rock.ExactFound ) {
@@ -1229,10 +1341,11 @@ long cm_LookupInternal(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_u
         lock_ObtainRead(&dscp->rw);
         if ( dscp->cbServerp != NULL && dscp->cbExpires > 0 ) {
             /* TODO: reuse nnamep from above */
-            if (nnamep) 
+            if (nnamep)
                 free(nnamep);
             nnamep = cm_ClientStringToNormStringAlloc(cnamep, -1, NULL);
-            cm_dnlcEnter(dscp, nnamep, tscp);
+            if (nnamep)
+                cm_dnlcEnter(dscp, nnamep, tscp);
         }
         lock_ReleaseRead(&dscp->rw);
     }
@@ -1251,22 +1364,33 @@ long cm_LookupInternal(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_u
     return code;
 }
 
-int cm_ExpandSysName(clientchar_t *inp, clientchar_t *outp, long outSizeCch, unsigned int index)
+int cm_ExpandSysName(cm_req_t * reqp, clientchar_t *inp, clientchar_t *outp, long outSizeCch, unsigned int index)
 {
     clientchar_t *tp;
     int prefixCount;
+#ifdef _WIN64
+    int use_sysname64 = 0;
+
+    if (cm_sysName64Count > 0 && reqp && !(reqp->flags & CM_REQ_WOW64) && (reqp->flags & CM_REQ_SOURCE_REDIR))
+        use_sysname64 = 1;
+#endif
 
     tp = cm_ClientStrRChr(inp, '@');
-    if (tp == NULL) 
+    if (tp == NULL)
         return 0;              /* no @sys */
 
-    if (cm_ClientStrCmp(tp, _C("@sys")) != 0) 
+    if (cm_ClientStrCmp(tp, _C("@sys")) != 0)
         return 0;      /* no @sys */
 
     /* caller just wants to know if this is a valid @sys type of name */
-    if (outp == NULL) 
+    if (outp == NULL)
         return 1;
 
+#ifdef _WIN64
+    if (use_sysname64 && index >= cm_sysName64Count)
+        return -1;
+    else
+#endif
     if (index >= cm_sysNameCount)
         return -1;
 
@@ -1274,13 +1398,19 @@ int cm_ExpandSysName(clientchar_t *inp, clientchar_t *outp, long outSizeCch, uns
     prefixCount = (int)(tp - inp);
 
     cm_ClientStrCpyN(outp, outSizeCch, inp, prefixCount);      /* copy out "a." from "a.@sys" */
-    outp[prefixCount] = 0;             /* null terminate the "a." */
-    cm_ClientStrCat(outp, outSizeCch, cm_sysNameList[index]);/* append i386_nt40 */
+    outp[prefixCount] = 0;                                     /* null terminate the "a." */
+#ifdef _WIN64
+    if (use_sysname64)
+        cm_ClientStrCat(outp, outSizeCch, cm_sysName64List[index]);
+    else
+#endif
+        cm_ClientStrCat(outp, outSizeCch, cm_sysNameList[index]);
+
     return 1;
-}   
+}
 
 long cm_EvaluateVolumeReference(clientchar_t * namep, long flags, cm_user_t * userp,
-                                cm_req_t *reqp, cm_scache_t ** outpScpp)
+                                cm_req_t *reqp, cm_scache_t ** outScpp)
 {
     afs_uint32    code = 0;
     fschar_t      cellName[CELL_MAXNAMELEN];
@@ -1369,7 +1499,7 @@ long cm_EvaluateVolumeReference(clientchar_t * namep, long flags, cm_user_t * us
 
     cm_SetFid(&fid, cellp->cellID, volume, 1, 1);
 
-    code = cm_GetSCache(&fid, outpScpp, userp, reqp);
+    code = cm_GetSCache(&fid, NULL, outScpp, userp, reqp);
 
   _exit_cleanup:
     if (fnamep)
@@ -1390,10 +1520,10 @@ long cm_EvaluateVolumeReference(clientchar_t * namep, long flags, cm_user_t * us
 
 #ifdef DEBUG_REFCOUNT
 long cm_LookupDbg(cm_scache_t *dscp, clientchar_t *namep, long flags, cm_user_t *userp,
-               cm_req_t *reqp, cm_scache_t **outpScpp, char * file, long line)
+               cm_req_t *reqp, cm_scache_t **outScpp, char * file, long line)
 #else
 long cm_Lookup(cm_scache_t *dscp, clientchar_t *namep, long flags, cm_user_t *userp,
-               cm_req_t *reqp, cm_scache_t **outpScpp)
+               cm_req_t *reqp, cm_scache_t **outScpp)
 #endif
 {
     long code;
@@ -1415,12 +1545,12 @@ long cm_Lookup(cm_scache_t *dscp, clientchar_t *namep, long flags, cm_user_t *us
 
     if (dscp == cm_data.rootSCachep &&
         cm_ClientStrCmpNI(namep, _C(CM_PREFIX_VOL), CM_PREFIX_VOL_CCH) == 0) {
-        return cm_EvaluateVolumeReference(namep, flags, userp, reqp, outpScpp);
+        return cm_EvaluateVolumeReference(namep, flags, userp, reqp, outScpp);
     }
 
-    if (cm_ExpandSysName(namep, NULL, 0, 0) > 0) {
+    if (cm_ExpandSysName(reqp, namep, NULL, 0, 0) > 0) {
         for ( sysNameIndex = 0; sysNameIndex < MAXNUMSYSNAMES; sysNameIndex++) {
-            code = cm_ExpandSysName(namep, tname, lengthof(tname), sysNameIndex);
+            code = cm_ExpandSysName(reqp, namep, tname, lengthof(tname), sysNameIndex);
             if (code > 0) {
                 code = cm_LookupInternal(dscp, tname, flags, userp, reqp, &scp);
 #ifdef DEBUG_REFCOUNT
@@ -1429,7 +1559,7 @@ long cm_Lookup(cm_scache_t *dscp, clientchar_t *namep, long flags, cm_user_t *us
 #endif
 
                 if (code == 0) {
-                    *outpScpp = scp;
+                    *outScpp = scp;
                     return 0;
                 }
                 if (scp) {
@@ -1442,7 +1572,7 @@ long cm_Lookup(cm_scache_t *dscp, clientchar_t *namep, long flags, cm_user_t *us
                 afsi_log("%s:%d cm_LookupInternal (2) code 0x%x dscp 0x%p ref %d scp 0x%p ref %d", file, line, code, dscp, dscp->refCount, scp, scp ? scp->refCount : 0);
                 osi_Log3(afsd_logp, "cm_LookupInternal (2) code 0x%x dscp 0x%p scp 0x%p", code, dscp, scp);
 #endif
-                *outpScpp = scp;
+                *outScpp = scp;
                 return code;
             }
         }
@@ -1452,7 +1582,7 @@ long cm_Lookup(cm_scache_t *dscp, clientchar_t *namep, long flags, cm_user_t *us
         afsi_log("%s:%d cm_LookupInternal (2) code 0x%x dscp 0x%p ref %d scp 0x%p ref %d", file, line, code, dscp, dscp->refCount, scp, scp ? scp->refCount : 0);
         osi_Log3(afsd_logp, "cm_LookupInternal (2) code 0x%x dscp 0x%p scp 0x%p", code, dscp, scp);
 #endif
-        *outpScpp = scp;
+        *outScpp = scp;
         return code;
     }
 
@@ -1481,7 +1611,7 @@ long cm_Lookup(cm_scache_t *dscp, clientchar_t *namep, long flags, cm_user_t *us
   \param[in] userp cm_user_t for the request.
 
   \param[in] reqp Request tracker.
+
  */
 long cm_Unlink(cm_scache_t *dscp, fschar_t *fnamep, clientchar_t * cnamep,
                cm_user_t *userp, cm_req_t *reqp)
@@ -1496,11 +1626,15 @@ long cm_Unlink(cm_scache_t *dscp, fschar_t *fnamep, clientchar_t * cnamep,
     cm_dirOp_t dirop;
     cm_scache_t *scp = NULL;
     int free_fnamep = FALSE;
+    int invalidate = 0;
+
+    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)
@@ -1518,12 +1652,21 @@ long cm_Unlink(cm_scache_t *dscp, fschar_t *fnamep, clientchar_t * cnamep,
         code = cm_FreelanceRemoveMount(fnamep);
         goto done;
     }
-#endif  
+#endif
 
     code = cm_Lookup(dscp, cnamep, CM_FLAG_NOMOUNTCHASE, userp, reqp, &scp);
+    if (code)
+        goto done;
+
+    /* Check for RO volume */
+    if (dscp->flags & CM_SCACHEFLAG_RO) {
+        code = CM_ERROR_READONLY;
+        goto done;
+    }
 
     /* 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;
@@ -1535,6 +1678,8 @@ long cm_Unlink(cm_scache_t *dscp, fschar_t *fnamep, clientchar_t * cnamep,
     }
 
     /* make the RPC */
+    InterlockedIncrement(&dscp->activeRPCs);
+
     afsFid.Volume = dscp->fid.volume;
     afsFid.Vnode = dscp->fid.vnode;
     afsFid.Unique = dscp->fid.unique;
@@ -1550,7 +1695,7 @@ long cm_Unlink(cm_scache_t *dscp, fschar_t *fnamep, clientchar_t * cnamep,
                                 &newDirStatus, &volSync);
         rx_PutConnection(rxconnp);
 
-    } while (cm_Analyze(connp, userp, reqp, &dscp->fid, &volSync, NULL, NULL, code));
+    } while (cm_Analyze(connp, userp, reqp, &dscp->fid, NULL, 1, &newDirStatus, &volSync, NULL, NULL, code));
     code = cm_MapRPCError(code, reqp);
 
     if (code)
@@ -1564,33 +1709,56 @@ long cm_Unlink(cm_scache_t *dscp, fschar_t *fnamep, clientchar_t * cnamep,
     }
     lock_ObtainWrite(&dscp->rw);
     cm_dnlcRemove(dscp, cnamep);
-    cm_SyncOpDone(dscp, NULL, sflags);
     if (code == 0) {
-        cm_MergeStatus(NULL, dscp, &newDirStatus, &volSync, userp, 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 
-        * have an inconsistent view of the world.
-        */
-       dscp->cbServerp = NULL;
-    }
-    lock_ReleaseWrite(&dscp->rw);
-
-    if (code == 0 && cm_CheckDirOpForSingleChange(&dirop) && cnamep) {
-        cm_DirDeleteEntry(&dirop, fnamep);
+        code = cm_MergeStatus(NULL, dscp, &newDirStatus, &volSync, userp, reqp, CM_MERGEFLAG_DIROP);
+        invalidate = 1;
+        if (cm_CheckDirOpForSingleChange(&dirop) && cnamep) {
+            lock_ReleaseWrite(&dscp->rw);
+            cm_DirDeleteEntry(&dirop, fnamep);
 #ifdef USE_BPLUS
-        cm_BPlusDirDeleteEntry(&dirop, cnamep);
+            cm_BPlusDirDeleteEntry(&dirop, cnamep);
 #endif
+            lock_ObtainWrite(&dscp->rw);
+        }
+    } else {
+        InterlockedDecrement(&scp->activeRPCs);
+        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
+             * have an inconsistent view of the world.
+             */
+            dscp->cbServerp = NULL;
+        }
     }
+
+    cm_SyncOpDone(dscp, NULL, sflags);
+    lock_ReleaseWrite(&dscp->rw);
+
     cm_EndDirOp(&dirop);
 
+    if (invalidate && RDR_Initialized &&
+        scp->fileType != CM_SCACHETYPE_FILE && scp->fileType != CM_SCACHETYPE_DIRECTORY)
+        RDR_InvalidateObject(dscp->fid.cell, dscp->fid.volume, dscp->fid.vnode,
+                              dscp->fid.unique, dscp->fid.hash,
+                              dscp->fileType, AFS_INVALIDATE_DATA_VERSION);
+
     if (scp) {
-        cm_ReleaseSCache(scp);
         if (code == 0) {
            lock_ObtainWrite(&scp->rw);
-            scp->flags |= CM_SCACHEFLAG_DELETED;
+            if (--scp->linkCount == 0) {
+               _InterlockedOr(&scp->flags, CM_SCACHEFLAG_DELETED);
+               lock_ObtainWrite(&cm_scacheLock);
+                cm_AdjustScacheLRU(scp);
+               lock_ReleaseWrite(&cm_scacheLock);
+            }
+            cm_DiscardSCache(scp);
            lock_ReleaseWrite(&scp->rw);
+            if (RDR_Initialized && !(reqp->flags & CM_REQ_SOURCE_REDIR))
+                RDR_InvalidateObject(scp->fid.cell, scp->fid.volume, scp->fid.vnode,
+                                     scp->fid.unique, scp->fid.hash,
+                                     scp->fileType, AFS_INVALIDATE_DELETED);
         }
+        cm_ReleaseSCache(scp);
     }
 
   done:
@@ -1605,62 +1773,55 @@ long cm_Unlink(cm_scache_t *dscp, fschar_t *fnamep, clientchar_t * cnamep,
  */
 long cm_HandleLink(cm_scache_t *linkScp, cm_user_t *userp, cm_req_t *reqp)
 {
-    long code;
-    cm_buf_t *bufp;
-    long temp;
-    osi_hyper_t thyper;
+    long code = 0;
 
     lock_AssertWrite(&linkScp->rw);
-    if (!linkScp->mountPointStringp[0]) {
-        /* read the link data */
-        lock_ReleaseWrite(&linkScp->rw);
-        thyper.LowPart = thyper.HighPart = 0;
-        code = buf_Get(linkScp, &thyper, &bufp);
-        lock_ObtainWrite(&linkScp->rw);
-        if (code) 
-            return code;
-        while (1) {
-            code = cm_SyncOp(linkScp, bufp, userp, reqp, 0,
-                              CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_READ);
-            if (code) {
-                buf_Release(bufp);
-                return code;
-            }
-           cm_SyncOpDone(linkScp, bufp, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_READ);
+    if (!linkScp->mountPointStringp[0] ||
+        linkScp->mpDataVersion != linkScp->dataVersion) {
 
-            if (cm_HaveBuffer(linkScp, bufp, 0)) 
-                break;
+#ifdef AFS_FREELANCE_CLIENT
+       /* File servers do not have data for freelance entries */
+        if (cm_freelanceEnabled &&
+            linkScp->fid.cell==AFS_FAKE_ROOT_CELL_ID &&
+            linkScp->fid.volume==AFS_FAKE_ROOT_VOL_ID )
+        {
+            code = cm_FreelanceFetchMountPointString(linkScp);
+        } else
+#endif /* AFS_FREELANCE_CLIENT */
+        {
+            char temp[MOUNTPOINTLEN];
+            osi_hyper_t offset;
+            afs_uint32 bytesRead = 0;
 
-            code = cm_GetBuffer(linkScp, bufp, NULL, userp, reqp);
-            if (code) {
-                buf_Release(bufp);
+            /* read the link data from the file server */
+            offset.LowPart = offset.HighPart = 0;
+            code = cm_GetData(linkScp, &offset, temp, MOUNTPOINTLEN, &bytesRead, userp, reqp);
+            if (code)
                 return code;
-            }
-        } /* while loop to get the data */
-                
-        /* now if we still have no link read in,
-         * copy the data from the buffer */
-        if ((temp = linkScp->length.LowPart) >= MOUNTPOINTLEN) {
-            buf_Release(bufp);
-            return CM_ERROR_TOOBIG;
+
+            /*
+             * linkScp->length is the actual length of the symlink target string.
+             * It is current because cm_GetData merged the most up to date
+             * status info into scp and has not dropped the rwlock since.
+             */
+            if (linkScp->length.LowPart > MOUNTPOINTLEN - 1)
+                return CM_ERROR_TOOBIG;
+            if (linkScp->length.LowPart == 0)
+                return CM_ERROR_INVAL;
+
+            /* make sure we are NUL terminated */
+            temp[linkScp->length.LowPart] = 0;
+            memcpy(linkScp->mountPointStringp, temp, linkScp->length.LowPart + 1);
+            linkScp->mpDataVersion = linkScp->dataVersion;
         }
 
-        /* otherwise, it fits; make sure it is still null (could have
-         * lost race with someone else referencing this link above),
-         * and if so, copy in the data.
-         */
-        if (!linkScp->mountPointStringp[0]) {
-            strncpy(linkScp->mountPointStringp, bufp->datap, temp);
-            linkScp->mountPointStringp[temp] = 0;      /* null terminate */
+        if ( !strnicmp(linkScp->mountPointStringp, "msdfs:", strlen("msdfs:")) )
+            linkScp->fileType = CM_SCACHETYPE_DFSLINK;
 
-            if ( !strnicmp(linkScp->mountPointStringp, "msdfs:", strlen("msdfs:")) )
-                 linkScp->fileType = CM_SCACHETYPE_DFSLINK;
-        }
-        buf_Release(bufp);
-    }  /* don't have sym link contents cached */
+    }  /* don't have symlink contents cached */
 
-    return 0;
-}       
+    return code;
+}
 
 /* called with a held vnode and a path suffix, with the held vnode being a
  * symbolic link.  Our goal is to generate a new path to interpret, and return
@@ -1681,6 +1842,25 @@ long cm_AssembleLink(cm_scache_t *linkScp, fschar_t *pathSuffixp,
     *newSpaceBufferp = NULL;
 
     lock_ObtainWrite(&linkScp->rw);
+    /*
+     * Do not get status if we do not already have a callback.
+     * The process of reading the symlink string will obtain status information
+     * in a single RPC.  No reason to add a second round trip.
+     *
+     * If we do have a callback, use cm_SyncOp to get status in case the
+     * current cm_user_t is not the same as the one that obtained the
+     * symlink string contents.
+     */
+    if (cm_HaveCallback(linkScp)) {
+        code = cm_SyncOp(linkScp, NULL, userp, reqp, 0,
+                          CM_SCACHESYNC_GETSTATUS | CM_SCACHESYNC_NEEDCALLBACK);
+        if (code) {
+            lock_ReleaseWrite(&linkScp->rw);
+            cm_ReleaseSCache(linkScp);
+            goto done;
+        }
+        cm_SyncOpDone(linkScp, NULL, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
+    }
     code = cm_HandleLink(linkScp, userp, reqp);
     if (code)
         goto done;
@@ -1702,10 +1882,10 @@ 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)))) 
+        if (!strnicmp(&linkp[2], cm_NetbiosName, (len = (long)strlen(cm_NetbiosName))))
         {
             char * p = &linkp[len + 3];
             if (strnicmp(p, "all", 3) == 0)
@@ -1716,8 +1896,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);
@@ -1729,21 +1909,21 @@ long cm_AssembleLink(cm_scache_t *linkScp, fschar_t *pathSuffixp,
         StringCchCopyA(tsp->data,lengthof(tsp->data), linkp);
         code = CM_ERROR_PATH_NOT_COVERED;
     } else if (*linkp == '\\' || *linkp == '/') {
-#if 0   
+#if 0
         /* formerly, this was considered to be from the AFS root,
          * 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 
+        /* we still copy the link data into the response so that
          * the user can see what the link points to
          */
         linkScp->fileType = CM_SCACHETYPE_INVALID;
         StringCchCopyA(tsp->data,lengthof(tsp->data), linkp);
         code = CM_ERROR_NOSUCHPATH;
-#endif  
+#endif
     } else {
         /* a relative link */
         StringCchCopyA(tsp->data,lengthof(tsp->data), linkp);
@@ -1752,12 +1932,19 @@ long cm_AssembleLink(cm_scache_t *linkScp, fschar_t *pathSuffixp,
         StringCchCatA(tsp->data,lengthof(tsp->data), "\\");
         StringCchCatA(tsp->data,lengthof(tsp->data), pathSuffixp);
     }
+
     if (code == 0) {
         clientchar_t * cpath = cm_FsStringToClientStringAlloc(tsp->data, -1, NULL);
+        if (cpath != NULL) {
         cm_ClientStrCpy(tsp->wdata, lengthof(tsp->wdata), cpath);
         free(cpath);
         *newSpaceBufferp = tsp;
     } else {
+            code = CM_ERROR_NOSUCHPATH;
+        }
+    }
+
+    if (code != 0) {
         cm_FreeSpace(tsp);
 
         if (code == CM_ERROR_PATH_NOT_COVERED && reqp->tidPathp && reqp->relPathp) {
@@ -1772,7 +1959,7 @@ long cm_AssembleLink(cm_scache_t *linkScp, fschar_t *pathSuffixp,
 #ifdef DEBUG_REFCOUNT
 long cm_NameIDbg(cm_scache_t *rootSCachep, clientchar_t *pathp, long flags,
                  cm_user_t *userp, clientchar_t *tidPathp, cm_req_t *reqp,
-                 cm_scache_t **outScpp, 
+                 cm_scache_t **outScpp,
                  char * file, long line)
 #else
 long cm_NameI(cm_scache_t *rootSCachep, clientchar_t *pathp, long flags,
@@ -1798,15 +1985,16 @@ long cm_NameI(cm_scache_t *rootSCachep, clientchar_t *pathp, long flags,
     int symlinkCount;          /* count of # of symlinks traversed */
     int extraFlag;             /* avoid chasing mt pts for dir cmd */
     int phase = 1;             /* 1 = tidPathp, 2 = pathp */
-#define MAX_FID_COUNT 512
     cm_fid_t fids[MAX_FID_COUNT]; /* array of fids processed in this path walk */
     int fid_count = 0;          /* number of fids processed in this path walk */
     int i;
 
+    *outScpp = NULL;
+
 #ifdef DEBUG_REFCOUNT
     afsi_log("%s:%d cm_NameI rootscp 0x%p ref %d", file, line, rootSCachep, rootSCachep->refCount);
     osi_Log4(afsd_logp,"cm_NameI rootscp 0x%p path %S tidpath %S flags 0x%x",
-             rootSCachep, pathp ? pathp : "<NULL>", tidPathp ? tidPathp : "<NULL>", 
+             rootSCachep, pathp ? pathp : L"<NULL>", tidPathp ? tidPathp : L"<NULL>",
              flags);
 #endif
 
@@ -1832,7 +2020,7 @@ long cm_NameI(cm_scache_t *rootSCachep, clientchar_t *pathp, long flags,
         /* map Unix slashes into DOS ones so we can interpret Unix
          * symlinks properly
          */
-        if (tc == '/') 
+        if (tc == '/')
             tc = '\\';
 
         if (!haveComponent) {
@@ -1860,16 +2048,57 @@ long cm_NameI(cm_scache_t *rootSCachep, clientchar_t *pathp, long flags,
                  */
                 *cp++ = 0;     /* add null termination */
                extraFlag = 0;
-               if ((flags & CM_FLAG_DIRSEARCH) && tc == 0)
-                   extraFlag = CM_FLAG_NOMOUNTCHASE;
-               code = cm_Lookup(tscp, component,
-                                 flags | extraFlag,
-                                 userp, reqp, &nscp);
 
+                if (tscp == cm_RootSCachep(userp, reqp)) {
+                    code = cm_Lookup(tscp, component, CM_FLAG_CHECKPATH, userp, reqp, &nscp);
+
+                    if ((code == CM_ERROR_NOSUCHPATH || code == CM_ERROR_NOSUCHFILE ||
+                         code == CM_ERROR_BPLUS_NOMATCH) &&
+                         tscp == cm_data.rootSCachep) {
+
+                        clientchar_t volref[AFSPATHMAX];
+
+                        if (wcschr(component, '%') != NULL || wcschr(component, '#') != NULL) {
+                            /*
+                             * A volume reference:  <cell>{%,#}<volume> -> @vol:<cell>{%,#}<volume>
+                             */
+                            cm_ClientStrCpyN(volref, AFSPATHMAX, _C(CM_PREFIX_VOL), CM_PREFIX_VOL_CCH);
+                            cm_ClientStrCat(volref, AFSPATHMAX, component);
+
+                            code = cm_EvaluateVolumeReference(volref, CM_FLAG_CHECKPATH, userp, reqp, &nscp);
+                        }
+#ifdef AFS_FREELANCE_CLIENT
+                        else if (tscp->fid.cell == AFS_FAKE_ROOT_CELL_ID && tscp->fid.volume == AFS_FAKE_ROOT_VOL_ID &&
+                                  tscp->fid.vnode == 1 && tscp->fid.unique == 1) {
+                            /*
+                             * If this is the Freelance volume root directory then treat unrecognized
+                             * names as cell names and attempt to find the appropriate "root.cell".
+                             */
+                            cm_ClientStrCpyN(volref, AFSPATHMAX, _C(CM_PREFIX_VOL), CM_PREFIX_VOL_CCH);
+                            if (component[0] == L'.') {
+                                cm_ClientStrCat(volref, AFSPATHMAX, &component[1]);
+                                cm_ClientStrCatN(volref, AFSPATHMAX, L"%", sizeof(WCHAR));
+                            } else {
+                                cm_ClientStrCat(volref, AFSPATHMAX, component);
+                                cm_ClientStrCatN(volref, AFSPATHMAX, L"#", sizeof(WCHAR));
+                            }
+                            cm_ClientStrCatN(volref, AFSPATHMAX, L"root.cell", 9 * sizeof(WCHAR));
+
+                            code = cm_EvaluateVolumeReference(volref, CM_FLAG_CHECKPATH, userp, reqp, &nscp);
+                        }
+#endif
+                    }
+                } else {
+                    if ((flags & CM_FLAG_DIRSEARCH) && tc == 0)
+                        extraFlag = CM_FLAG_NOMOUNTCHASE;
+                    code = cm_Lookup(tscp, component,
+                                     flags | extraFlag,
+                                     userp, reqp, &nscp);
+                }
                 if (code == 0) {
                     if (!cm_ClientStrCmp(component,_C("..")) ||
                         !cm_ClientStrCmp(component,_C("."))) {
-                        /* 
+                        /*
                          * roll back the fid list until we find the
                          * fid that matches where we are now.  Its not
                          * necessarily one or two fids because they
@@ -1883,17 +2112,13 @@ long cm_NameI(cm_scache_t *rootSCachep, clientchar_t *pathp, long flags,
                         fid_count = i+1;
                     } else {
                         /* add the new fid to the list */
-                        for ( i=0; i<fid_count; i++) {
-                            if ( !cm_FidCmp(&nscp->fid, &fids[i]) ) {
-                                code = CM_ERROR_TOO_MANY_SYMLINKS;
-                                cm_ReleaseSCache(nscp);
-                                nscp = NULL;
-                                break;
-                            }
-                        }
-                        if (i == fid_count && fid_count < MAX_FID_COUNT) {
-                            fids[fid_count++] = nscp->fid;
+                        if (fid_count == MAX_FID_COUNT) {
+                            code = CM_ERROR_TOO_MANY_SYMLINKS;
+                            cm_ReleaseSCache(nscp);
+                            nscp = NULL;
+                            break;
                         }
+                        fids[fid_count++] = nscp->fid;
                     }
                 }
 
@@ -1901,9 +2126,9 @@ long cm_NameI(cm_scache_t *rootSCachep, clientchar_t *pathp, long flags,
                    cm_ReleaseSCache(tscp);
                    if (dirScp)
                        cm_ReleaseSCache(dirScp);
-                   if (psp) 
+                   if (psp)
                        cm_FreeSpace(psp);
-                   if ((code == CM_ERROR_NOSUCHFILE || code == CM_ERROR_BPLUS_NOMATCH) && 
+                   if ((code == CM_ERROR_NOSUCHFILE || code == CM_ERROR_BPLUS_NOMATCH) &&
                         tscp->fileType == CM_SCACHETYPE_SYMLINK) {
                        osi_Log0(afsd_logp,"cm_NameI code CM_ERROR_NOSUCHPATH");
                        return CM_ERROR_NOSUCHPATH;
@@ -1957,14 +2182,14 @@ long cm_NameI(cm_scache_t *rootSCachep, clientchar_t *pathp, long flags,
                             cm_ReleaseSCache(dirScp);
                             dirScp = NULL;
                         }
-                        if (psp) 
+                        if (psp)
                             cm_FreeSpace(psp);
                        osi_Log0(afsd_logp,"cm_NameI code CM_ERROR_TOO_MANY_SYMLINKS");
                         return CM_ERROR_TOO_MANY_SYMLINKS;
                     }
-                    if (tc == 0) 
+                    if (tc == 0)
                         restp = _C("");
-                    else 
+                    else
                         restp = tp;
 
                     {
@@ -1977,9 +2202,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;
@@ -2013,7 +2239,7 @@ long cm_NameI(cm_scache_t *rootSCachep, clientchar_t *pathp, long flags,
                      * space allocation and tracking the
                      * vnode reference counts.
                      */
-                    if (psp) 
+                    if (psp)
                         cm_FreeSpace(psp);
                     psp = tempsp;
                     tp = psp->wdata;
@@ -2056,7 +2282,7 @@ long cm_NameI(cm_scache_t *rootSCachep, clientchar_t *pathp, long flags,
                     dirScp = NULL;
                 }
             } /* end of a component */
-            else 
+            else
                 *cp++ = tc;
         } /* we have a component */
     } /* big while loop over all components */
@@ -2064,15 +2290,15 @@ long cm_NameI(cm_scache_t *rootSCachep, clientchar_t *pathp, long flags,
     /* already held */
     if (dirScp)
         cm_ReleaseSCache(dirScp);
-    if (psp) 
+    if (psp)
         cm_FreeSpace(psp);
-    if (code == 0) 
+    if (code == 0)
         *outScpp = tscp;
     else if (tscp)
         cm_ReleaseSCache(tscp);
 
 #ifdef DEBUG_REFCOUNT
-    afsi_log("%s:%d cm_NameI code 0x%x outScpp 0x%p ref %d", file, line, code, *outScpp, (*outScpp)->refCount);
+    afsi_log("%s:%d cm_NameI code 0x%x outScpp 0x%p ref %d", file, line, code, *outScpp, (*outScpp) ? (*outScpp)->refCount : 0);
 #endif
     osi_Log2(afsd_logp,"cm_NameI code 0x%x outScpp 0x%p", code, *outScpp);
     return code;
@@ -2100,10 +2326,12 @@ long cm_EvaluateSymLink(cm_scache_t *dscp, cm_scache_t *linkScp,
     cm_space_t *spacep;
     cm_scache_t *newRootScp;
 
+    *outScpp = NULL;
+
     osi_Log1(afsd_logp, "Evaluating symlink scp 0x%p", linkScp);
 
     code = cm_AssembleLink(linkScp, "", &newRootScp, &spacep, userp, reqp);
-    if (code) 
+    if (code)
         return code;
 
     /* now, if newRootScp is NULL, we're really being told that the symlink
@@ -2179,7 +2407,7 @@ long cm_TryBulkProc(cm_scache_t *scp, cm_dirEntry_t *dep, void *rockp,
     if (tscp) {
         if (lock_TryWrite(&tscp->rw)) {
             /* we have an entry that we can look at */
-            if (!(tscp->flags & CM_SCACHEFLAG_EACCESS) && cm_HaveCallback(tscp)) {
+            if (!cm_EAccesFindEntry(bsp->userp, &tscp->fid) && cm_HaveCallback(tscp)) {
                 /* we have a callback on it.  Don't bother
                  * fetching this stat entry, since we're happy
                  * with the info we have.
@@ -2198,12 +2426,12 @@ long cm_TryBulkProc(cm_scache_t *scp, cm_dirEntry_t *dep, void *rockp,
     // to be bulkstat-ed, instead, we call getSCache directly and under
     // getSCache, it is handled specially.
     if         ( cm_freelanceEnabled &&
-          tfid.cell==AFS_FAKE_ROOT_CELL_ID && 
+          tfid.cell==AFS_FAKE_ROOT_CELL_ID &&
           tfid.volume==AFS_FAKE_ROOT_VOL_ID &&
           !(tfid.vnode==0x1 && tfid.unique==0x1) )
-    {       
+    {
         osi_Log0(afsd_logp, "cm_TryBulkProc Freelance calls cm_SCache on root.afs mountpoint");
-        return cm_GetSCache(&tfid, &tscp, NULL, NULL);
+        return cm_GetSCache(&tfid, NULL, &tscp, NULL, NULL);
     }
 #endif /* AFS_FREELANCE_CLIENT */
 
@@ -2212,7 +2440,7 @@ long cm_TryBulkProc(cm_scache_t *scp, cm_dirEntry_t *dep, void *rockp,
     bsp->fids[i].Vnode = tfid.vnode;
     bsp->fids[i].Unique = tfid.unique;
     return 0;
-}       
+}
 
 afs_int32
 cm_TryBulkStatRPC(cm_scache_t *dscp, cm_bulkStat_t *bbp, cm_user_t *userp, cm_req_t *reqp)
@@ -2225,21 +2453,24 @@ cm_TryBulkStatRPC(cm_scache_t *dscp, cm_bulkStat_t *bbp, cm_user_t *userp, cm_re
     long filex;
     AFSVolSync volSync;
     cm_callbackRequest_t cbReq;
+    int lostRace;
     long filesThisCall;
     long i;
     long j;
     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.
      */
     for (filex = 0; filex < bbp->counter; filex += filesThisCall) {
         filesThisCall = bbp->counter - filex;
-        if (filesThisCall > AFSCBMAX) 
+        if (filesThisCall > AFSCBMAX)
             filesThisCall = AFSCBMAX;
 
         fidStruct.AFSCBFids_len = filesThisCall;
@@ -2250,93 +2481,182 @@ 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);
-            if (code) 
+            inlinebulk = 0;
+
+            code = cm_ConnFromFID(&tfid, userp, reqp, &connp);
+            if (code)
                 continue;
 
             rxconnp = cm_GetRxConn(connp);
-           if (!(connp->serverp->flags & CM_SERVERFLAG_NOINLINEBULK)) {
+           if (SERVERHASINLINEBULK(connp)) {
                code = RXAFS_InlineBulkStatus(rxconnp, &fidStruct,
-                                     &statStruct, &callbackStruct, &volSync);
+                                              &statStruct, &callbackStruct, &volSync);
                if (code == RXGEN_OPCODE) {
-                   cm_SetServerNoInlineBulk(connp->serverp, 0);
+                   SET_SERVERHASNOINLINEBULK(connp);
                } else {
                    inlinebulk = 1;
                }
            }
            if (!inlinebulk) {
+                /*
+                 * It is important to note that RXAFS_BulkStatus is quite braindead.
+                 * The AFS 3.6 file server implementation returns arrays that are
+                 * sized to hold responses for all of the requested FIDs but it only
+                 * populates their contents up to the point where it detects an error.
+                 * Unfortunately, it does inform the caller which entries were filled
+                 * and which were not.  The caller has no ability to determine which
+                 * FID the RPC return code applies to or which of the FIDs valid status
+                 * info and callbacks have been issued for.  As a result, when an
+                 * error is returned, none of the data received can be trusted.
+                 */
                code = RXAFS_BulkStatus(rxconnp, &fidStruct,
                                        &statStruct, &callbackStruct, &volSync);
            }
             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, NULL, 0, &bbp->stats[0], &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);
+            if (!inlinebulk) {
+                /*
+                 * Since an error occurred and it is impossible to determine
+                 * the context in which the returned error code should be
+                 * interpreted, we return the CM_ERROR_BULKSTAT_FAILURE error
+                 * which indicates that Bulk Stat cannot be used for the
+                 * current request.  The caller should fallback to using
+                 * individual RXAFS_FetchStatus calls.
+                 */
+                code = CM_ERROR_BULKSTAT_FAILURE;
+            }
+            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, NULL, 0, &bbp->stats[j], &volSync,
+                           NULL, &cbReq, (&bbp->stats[j])->errorCode);
+                switch ((&bbp->stats[j])->errorCode) {
+                case EACCES:
+                case UAEACCES:
+                case EPERM:
+                case UAEPERM:
+                    cm_EAccesAddEntry(userp, &tfid, &dscp->fid);
+                }
+            } else {
+                code = cm_GetSCache(&tfid, &dscp->fid, &scp, userp, reqp);
+                if (code != 0)
+                    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) ||
+                     cm_EAccesFindEntry(userp, &scp->fid))
+                {
+                    lock_ConvertRToW(&scp->rw);
+                    lostRace = cm_EndCallbackGrantingCall(scp, &cbReq,
+                                                          &bbp->callbacks[j],
+                                                          &volSync,
+                                                          CM_CALLBACK_MAINTAINCOUNT|CM_CALLBACK_BULKSTAT);
+                   if (!lostRace) {
+                       InterlockedIncrement(&scp->activeRPCs);
+                        code = cm_MergeStatus(dscp, scp, &bbp->stats[j], &volSync, userp, reqp, CM_MERGEFLAG_BULKSTAT);
+                   }
+                    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;
 }
 
@@ -2357,6 +2677,7 @@ cm_TryBulkStat(cm_scache_t *dscp, osi_hyper_t *offsetp, cm_user_t *userp,
 
     bbp = malloc(sizeof(cm_bulkStat_t));
     memset(bbp, 0, sizeof(cm_bulkStat_t));
+    bbp->userp = userp;
     bbp->bufOffset = *offsetp;
 
     lock_ReleaseWrite(&dscp->rw);
@@ -2376,7 +2697,7 @@ cm_TryBulkStat(cm_scache_t *dscp, osi_hyper_t *offsetp, cm_user_t *userp,
     lock_ObtainWrite(&dscp->rw);
     free(bbp);
     return 0;
-}       
+}
 
 void cm_StatusFromAttr(AFSStoreStatus *statusp, cm_scache_t *scp, cm_attr_t *attrp)
 {
@@ -2415,7 +2736,92 @@ void cm_StatusFromAttr(AFSStoreStatus *statusp, cm_scache_t *scp, cm_attr_t *att
         }
     }
     statusp->Mask = mask;
-}       
+}
+
+int
+cm_IsSpaceAvailable(cm_fid_t * fidp, osi_hyper_t *sizep, cm_user_t *userp, cm_req_t *reqp)
+{
+    int spaceAvail = 1;
+    afs_uint32  code;
+    cm_conn_t *connp;
+    struct rx_connection * rxconnp;
+    AFSFetchVolumeStatus volStat;
+    cm_volume_t *volp = NULL;
+    afs_uint32   volType;
+    char *Name;
+    char *OfflineMsg;
+    char *MOTD;
+    char volName[32]="(unknown)";
+    char offLineMsg[256]="server temporarily inaccessible";
+    char motd[256]="server temporarily inaccessible";
+    osi_hyper_t freespace;
+    cm_fid_t    vfid;
+    cm_scache_t *vscp;
+
+    if (fidp->cell==AFS_FAKE_ROOT_CELL_ID &&
+        fidp->volume==AFS_FAKE_ROOT_VOL_ID)
+    {
+        goto _done;
+    }
+
+    volp = cm_FindVolumeByFID(fidp, userp, reqp);
+    if (!volp) {
+        spaceAvail = 0;
+        goto _done;
+    }
+    volType = cm_VolumeType(volp, fidp->volume);
+    if (volType == ROVOL || volType == BACKVOL) {
+        spaceAvail = 0;
+        goto _done;
+    }
+
+    cm_SetFid(&vfid, fidp->cell, fidp->volume, 1, 1);
+    code = cm_GetSCache(&vfid, NULL, &vscp, userp, reqp);
+    if (code == 0) {
+        lock_ObtainWrite(&vscp->rw);
+        code = cm_SyncOp(vscp, NULL, userp, reqp, PRSFS_READ,
+                          CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
+        lock_ReleaseWrite(&vscp->rw);
+        if (code == 0) {
+            Name = volName;
+            OfflineMsg = offLineMsg;
+            MOTD = motd;
+
+            do {
+                code = cm_ConnFromFID(&vfid, userp, reqp, &connp);
+                if (code) continue;
+
+                rxconnp = cm_GetRxConn(connp);
+                code = RXAFS_GetVolumeStatus(rxconnp, fidp->volume,
+                                             &volStat, &Name, &OfflineMsg, &MOTD);
+                rx_PutConnection(rxconnp);
+
+            } while (cm_Analyze(connp, userp, reqp, &vfid, NULL, 0, NULL, NULL, NULL, NULL, code));
+            code = cm_MapRPCError(code, reqp);
+        }
+
+        lock_ObtainWrite(&vscp->rw);
+        cm_SyncOpDone(vscp, NULL, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
+        lock_ReleaseWrite(&vscp->rw);
+        cm_ReleaseSCache(vscp);
+    }
+
+    if (code == 0) {
+        if (volStat.MaxQuota) {
+            freespace.QuadPart = 1024 * (afs_int64)min(volStat.MaxQuota - volStat.BlocksInUse, volStat.PartBlocksAvail);
+        } else {
+            freespace.QuadPart = 1024 * (afs_int64)volStat.PartBlocksAvail;
+        }
+        spaceAvail = LargeIntegerGreaterThanOrEqualTo(freespace, *sizep);
+    }
+    /* the rpc failed, assume there is space and we can fail it later. */
+
+  _done:
+    if (volp)
+        cm_PutVolume(volp);
+
+    return spaceAvail;
+}
 
 /* set the file size, and make sure that all relevant buffers have been
  * truncated.  Ensure that any partially truncated buffers have been zeroed
@@ -2426,6 +2832,7 @@ long cm_SetLength(cm_scache_t *scp, osi_hyper_t *sizep, cm_user_t *userp,
 {
     long code;
     int shrinking;
+    int available;
 
     /* start by locking out buffer creation */
     lock_ObtainWrite(&scp->bufCreateLock);
@@ -2434,7 +2841,7 @@ long cm_SetLength(cm_scache_t *scp, osi_hyper_t *sizep, cm_user_t *userp,
     lock_ObtainWrite(&scp->rw);
     code = cm_SyncOp(scp, NULL, userp, reqp, 0,
                       CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
-    if (code) 
+    if (code)
         goto done;
     cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
 
@@ -2476,7 +2883,7 @@ long cm_SetLength(cm_scache_t *scp, osi_hyper_t *sizep, cm_user_t *userp,
                          | CM_SCACHESYNC_SETSTATUS | CM_SCACHESYNC_SETSIZE);
     }
 
-    if (code) 
+    if (code)
         goto done;
 
     if (LargeIntegerLessThan(*sizep, scp->length)) {
@@ -2484,8 +2891,12 @@ long cm_SetLength(cm_scache_t *scp, osi_hyper_t *sizep, cm_user_t *userp,
          * than where we're truncating the file, set truncPos to this
          * new value.
          */
-        if (!shrinking)
+       if (!shrinking) {
+           cm_SyncOpDone(scp, NULL,
+                         CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS
+                         | CM_SCACHESYNC_SETSTATUS | CM_SCACHESYNC_SETSIZE);
             goto startover;
+       }
         if (!(scp->mask & CM_SCACHEMASK_TRUNCPOS)
              || LargeIntegerLessThan(*sizep, scp->length)) {
             /* set trunc pos */
@@ -2497,15 +2908,45 @@ long cm_SetLength(cm_scache_t *scp, osi_hyper_t *sizep, cm_user_t *userp,
         scp->mask |= CM_SCACHEMASK_LENGTH;
     }
     else if (LargeIntegerGreaterThan(*sizep, scp->length)) {
-        /* really extending the file */
-        scp->length = *sizep;
-        scp->mask |= CM_SCACHEMASK_LENGTH;
+        /*
+         * Really extending the file so must check to see if we
+         * have sufficient quota.  cm_IsSpaceAvailable() obtains
+         * the cm_scache.rw lock on the volume root directory.
+         * vnode 1 < scp->fid.vnode therefore calling cm_IsSpaceAvailable
+         * while holding scp->rw is a lock order violation.
+         * Dropping it is ok because we are holding scp->bufCreateLock
+         * which prevents the size of the file from changing.
+         */
+        afs_uint64 nextChunk = scp->length.QuadPart;
+
+        nextChunk -= (nextChunk & 0xFFFFF);
+        nextChunk += 0x100000;
+
+        if (sizep->QuadPart > nextChunk) {
+            lock_ReleaseWrite(&scp->rw);
+            available = cm_IsSpaceAvailable(&scp->fid, sizep, userp, reqp);
+            lock_ObtainWrite(&scp->rw);
+        } else {
+            /*
+             * The file server permits 1MB quota overruns so only check
+             * when the file size increases by at least that much.
+             */
+            available = 1;
+        }
+        if (available) {
+            scp->length = *sizep;
+            scp->mask |= CM_SCACHEMASK_LENGTH;
+        } else {
+            code = CM_ERROR_SPACE;
+            goto syncopdone;
+        }
     }
 
     /* done successfully */
     code = 0;
 
-    cm_SyncOpDone(scp, NULL, 
+  syncopdone:
+    cm_SyncOpDone(scp, NULL,
                   CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS
                   | CM_SCACHESYNC_SETSTATUS | CM_SCACHESYNC_SETSIZE);
 
@@ -2528,11 +2969,20 @@ 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);
 
     lock_ObtainWrite(&scp->rw);
+    /* Check for RO volume */
+    if (scp->flags & CM_SCACHEFLAG_RO) {
+        code = CM_ERROR_READONLY;
+       lock_ReleaseWrite(&scp->rw);
+        return code;
+    }
+
     /* otherwise, we have to make an RPC to get the status */
     code = cm_SyncOp(scp, NULL, userp, reqp, 0, CM_SCACHESYNC_STORESTATUS);
     if (code) {
@@ -2550,10 +3000,12 @@ long cm_SetAttr(cm_scache_t *scp, cm_attr_t *attrp, cm_user_t *userp,
     lock_ReleaseRead(&scp->rw);
 
     /* now make the RPC */
+    InterlockedIncrement(&scp->activeRPCs);
+
     osi_Log1(afsd_logp, "CALL StoreStatus scp 0x%p", scp);
     do {
         code = cm_ConnFromFID(&scp->fid, userp, reqp, &connp);
-        if (code) 
+        if (code)
             continue;
 
         rxconnp = cm_GetRxConn(connp);
@@ -2562,7 +3014,7 @@ long cm_SetAttr(cm_scache_t *scp, cm_attr_t *attrp, cm_user_t *userp,
         rx_PutConnection(rxconnp);
 
     } while (cm_Analyze(connp, userp, reqp,
-                         &scp->fid, &volSync, NULL, NULL, code));
+                         &scp->fid, NULL, 1, &afsOutStatus, &volSync, NULL, NULL, code));
     code = cm_MapRPCError(code, reqp);
 
     if (code)
@@ -2571,23 +3023,25 @@ long cm_SetAttr(cm_scache_t *scp, cm_attr_t *attrp, cm_user_t *userp,
         osi_Log0(afsd_logp, "CALL StoreStatus SUCCESS");
 
     lock_ObtainWrite(&scp->rw);
-    cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_STORESTATUS);
     if (code == 0)
-        cm_MergeStatus(NULL, scp, &afsOutStatus, &volSync, userp,
-                        CM_MERGEFLAG_FORCE|CM_MERGEFLAG_STOREDATA);
-       
-    /* if we're changing the mode bits, discard the ACL cache, 
+        code = cm_MergeStatus( NULL, scp, &afsOutStatus, &volSync, userp, reqp,
+                               CM_MERGEFLAG_FORCE|CM_MERGEFLAG_STOREDATA);
+    else
+        InterlockedDecrement(&scp->activeRPCs);
+    cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_STORESTATUS);
+
+    /* if we're changing the mode bits, discard the ACL cache,
      * since we changed the mode bits.
      */
-    if (afsInStatus.Mask & AFS_SETMODE) 
+    if (afsInStatus.Mask & AFS_SETMODE)
        cm_FreeAllACLEnts(scp);
     lock_ReleaseWrite(&scp->rw);
     return code;
-}       
+}
 
 long cm_Create(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *attrp,
                cm_scache_t **scpp, cm_user_t *userp, cm_req_t *reqp)
-{       
+{
     cm_conn_t *connp;
     long code;
     AFSFid dirAFSFid;
@@ -2596,6 +3050,7 @@ long cm_Create(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *a
     cm_fid_t newFid;
     cm_scache_t *scp = NULL;
     int didEnd;
+    int lostRace;
     AFSStoreStatus inStatus;
     AFSFetchStatus updatedDirStatus;
     AFSFetchStatus newFileStatus;
@@ -2605,10 +3060,12 @@ 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.
      */
-    if (cm_ExpandSysName(cnamep, NULL, 0, 0)) {
+    if (cm_ExpandSysName(NULL, cnamep, NULL, 0, 0)) {
         return CM_ERROR_ATSYS;
     }
 
@@ -2622,11 +3079,16 @@ long cm_Create(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *a
     }
 #endif /* AFS_FREELANCE_CLIENT */
 
+    /* Check for RO volume */
+    if (dscp->flags & CM_SCACHEFLAG_RO)
+        return CM_ERROR_READONLY;
+
     /* before starting the RPC, mark that we're changing the file data, so
      * 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);
@@ -2645,10 +3107,11 @@ long cm_Create(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *a
     cm_StatusFromAttr(&inStatus, NULL, attrp);
 
     /* try the RPC now */
+    InterlockedIncrement(&dscp->activeRPCs);
     osi_Log1(afsd_logp, "CALL CreateFile scp 0x%p", dscp);
     do {
         code = cm_ConnFromFID(&dscp->fid, userp, reqp, &connp);
-        if (code) 
+        if (code)
             continue;
 
         dirAFSFid.Volume = dscp->fid.volume;
@@ -2663,9 +3126,9 @@ long cm_Create(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *a
         rx_PutConnection(rxconnp);
 
     } while (cm_Analyze(connp, userp, reqp,
-                         &dscp->fid, &volSync, NULL, &cbReq, code));
+                         &dscp->fid, NULL, 1, &updatedDirStatus, &volSync, NULL, &cbReq, code));
     code = cm_MapRPCError(code, reqp);
-        
+
     if (code)
         osi_Log1(afsd_logp, "CALL CreateFile FAILURE, code 0x%x", code);
     else
@@ -2676,44 +3139,51 @@ long cm_Create(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *a
         dirop.lockType = CM_DIRLOCK_WRITE;
     }
     lock_ObtainWrite(&dscp->rw);
-    cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
     if (code == 0) {
-        cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, CM_MERGEFLAG_DIROP);
+        code = cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, reqp, CM_MERGEFLAG_DIROP);
+        cm_SetFid(&newFid, dscp->fid.cell, dscp->fid.volume, newAFSFid.Vnode, newAFSFid.Unique);
+        if (cm_CheckDirOpForSingleChange(&dirop)) {
+            lock_ReleaseWrite(&dscp->rw);
+            cm_DirCreateEntry(&dirop, fnamep, &newFid);
+#ifdef USE_BPLUS
+            cm_BPlusDirCreateEntry(&dirop, cnamep, &newFid);
+#endif
+            lock_ObtainWrite(&dscp->rw);
+        }
+    } else {
+        InterlockedDecrement(&dscp->activeRPCs);
     }
+    cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
     lock_ReleaseWrite(&dscp->rw);
 
-    /* now try to create the file's entry, too, but be careful to 
+    /* now try to create the file's entry, too, but be careful to
      * make sure that we don't merge in old info.  Since we weren't locking
      * out any requests during the file's creation, we may have pretty old
      * info.
      */
     if (code == 0) {
-        cm_SetFid(&newFid, dscp->fid.cell, dscp->fid.volume, newAFSFid.Vnode, newAFSFid.Unique);
-        code = cm_GetSCache(&newFid, &scp, userp, reqp);
+        code = cm_GetSCache(&newFid, &dscp->fid, &scp, userp, reqp);
         if (code == 0) {
             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);
-                didEnd = 1;     
-            }       
+                lostRace = cm_EndCallbackGrantingCall(scp, &cbReq,
+                                                      &newFileCallback, &volSync, 0);
+               if (!lostRace) {
+                   InterlockedIncrement(&scp->activeRPCs);
+                    code = cm_MergeStatus( dscp, scp, &newFileStatus, &volSync,
+                                           userp, reqp, 0);
+               }
+                didEnd = 1;
+            }
             lock_ReleaseWrite(&scp->rw);
         }
     }
 
     /* 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);
-#ifdef USE_BPLUS
-        cm_BPlusDirCreateEntry(&dirop, cnamep, &newFid);
-#endif
-    }
     cm_EndDirOp(&dirop);
 
     if (fnamep)
@@ -2726,12 +3196,21 @@ long cm_Create(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *a
             cm_ReleaseSCache(scp);
     }
     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);
+
+    osi_Log2(afsd_logp, "cm_FSync scp 0x%p userp 0x%p", scp, userp);
+
     code = buf_CleanVnode(scp, userp, reqp);
     if (code == 0) {
         lock_ObtainWrite(&scp->rw);
@@ -2746,7 +3225,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;
 }
@@ -2762,6 +3244,7 @@ long cm_MakeDir(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *
     cm_fid_t newFid;
     cm_scache_t *scp = NULL;
     int didEnd;
+    int lostRace;
     AFSStoreStatus inStatus;
     AFSFetchStatus updatedDirStatus;
     AFSFetchStatus newDirStatus;
@@ -2771,10 +3254,12 @@ 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.
      */
-    if (cm_ExpandSysName(cnamep, NULL, 0, 0)) {
+    if (cm_ExpandSysName(NULL, cnamep, NULL, 0, 0)) {
         return CM_ERROR_ATSYS;
     }
 
@@ -2788,11 +3273,16 @@ long cm_MakeDir(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *
     }
 #endif /* AFS_FREELANCE_CLIENT */
 
+    /* Check for RO volume */
+    if (dscp->flags & CM_SCACHEFLAG_RO)
+        return CM_ERROR_READONLY;
+
     /* 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);
@@ -2810,10 +3300,11 @@ long cm_MakeDir(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *
     cm_StatusFromAttr(&inStatus, NULL, attrp);
 
     /* try the RPC now */
+    InterlockedIncrement(&dscp->activeRPCs);
     osi_Log1(afsd_logp, "CALL MakeDir scp 0x%p", dscp);
     do {
         code = cm_ConnFromFID(&dscp->fid, userp, reqp, &connp);
-        if (code) 
+        if (code)
             continue;
 
         dirAFSFid.Volume = dscp->fid.volume;
@@ -2828,9 +3319,9 @@ long cm_MakeDir(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *
         rx_PutConnection(rxconnp);
 
     } while (cm_Analyze(connp, userp, reqp,
-                        &dscp->fid, &volSync, NULL, &cbReq, code));
+                        &dscp->fid, NULL, 1, &updatedDirStatus, &volSync, NULL, &cbReq, code));
     code = cm_MapRPCError(code, reqp);
-        
+
     if (code)
         osi_Log1(afsd_logp, "CALL MakeDir FAILURE, code 0x%x", code);
     else
@@ -2841,28 +3332,41 @@ long cm_MakeDir(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *
         dirop.lockType = CM_DIRLOCK_WRITE;
     }
     lock_ObtainWrite(&dscp->rw);
-    cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
     if (code == 0) {
-        cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, CM_MERGEFLAG_DIROP);
+        code = cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, reqp, CM_MERGEFLAG_DIROP);
+        cm_SetFid(&newFid, dscp->fid.cell, dscp->fid.volume, newAFSFid.Vnode, newAFSFid.Unique);
+        if (cm_CheckDirOpForSingleChange(&dirop)) {
+            lock_ReleaseWrite(&dscp->rw);
+            cm_DirCreateEntry(&dirop, fnamep, &newFid);
+#ifdef USE_BPLUS
+            cm_BPlusDirCreateEntry(&dirop, cnamep, &newFid);
+#endif
+            lock_ObtainWrite(&dscp->rw);
+        }
+    } else {
+        InterlockedDecrement(&dscp->activeRPCs);
     }
+    cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
     lock_ReleaseWrite(&dscp->rw);
 
-    /* now try to create the new dir's entry, too, but be careful to 
+    /* now try to create the new dir's entry, too, but be careful to
      * make sure that we don't merge in old info.  Since we weren't locking
      * out any requests during the file's creation, we may have pretty old
      * info.
      */
     if (code == 0) {
-        cm_SetFid(&newFid, dscp->fid.cell, dscp->fid.volume, newAFSFid.Vnode, newAFSFid.Unique);
-        code = cm_GetSCache(&newFid, &scp, userp, reqp);
+        code = cm_GetSCache(&newFid, &dscp->fid, &scp, userp, reqp);
         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);
-                didEnd = 1;             
+                lostRace = cm_EndCallbackGrantingCall(scp, &cbReq,
+                                                      &newDirCallback, &volSync, 0);
+               if (!lostRace) {
+                   InterlockedIncrement(&scp->activeRPCs);
+                    code = cm_MergeStatus( dscp, scp, &newDirStatus, &volSync,
+                                           userp, reqp, 0);
+               }
+                didEnd = 1;
             }
             lock_ReleaseWrite(&scp->rw);
         }
@@ -2870,14 +3374,8 @@ 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);
-#ifdef USE_BPLUS
-        cm_BPlusDirCreateEntry(&dirop, cnamep, &newFid);
-#endif
-    }
     cm_EndDirOp(&dirop);
 
     free(fnamep);
@@ -2891,7 +3389,7 @@ long cm_MakeDir(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *
 
     /* and return error code */
     return code;
-}       
+}
 
 long cm_Link(cm_scache_t *dscp, clientchar_t *cnamep, cm_scache_t *sscp, long flags,
              cm_user_t *userp, cm_req_t *reqp)
@@ -2906,13 +3404,21 @@ long cm_Link(cm_scache_t *dscp, clientchar_t *cnamep, cm_scache_t *sscp, long fl
     struct rx_connection * rxconnp;
     cm_dirOp_t dirop;
     fschar_t * fnamep = NULL;
+    int invalidate = 0;
+
+    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);
+    /* Check for RO volume */
+    if (dscp->flags & CM_SCACHEFLAG_RO)
+        return CM_ERROR_READONLY;
+
+    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);
@@ -2925,6 +3431,7 @@ long cm_Link(cm_scache_t *dscp, clientchar_t *cnamep, cm_scache_t *sscp, long fl
     fnamep = cm_ClientStringToFsStringAlloc(cnamep, -1, NULL);
 
     /* try the RPC now */
+    InterlockedIncrement(&dscp->activeRPCs);
     osi_Log1(afsd_logp, "CALL Link scp 0x%p", dscp);
     do {
         code = cm_ConnFromFID(&dscp->fid, userp, reqp, &connp);
@@ -2944,8 +3451,7 @@ long cm_Link(cm_scache_t *dscp, clientchar_t *cnamep, cm_scache_t *sscp, long fl
         rx_PutConnection(rxconnp);
         osi_Log1(afsd_logp,"  RXAFS_Link returns 0x%x", code);
 
-    } while (cm_Analyze(connp, userp, reqp,
-        &dscp->fid, &volSync, NULL, NULL, code));
+    } while (cm_Analyze(connp, userp, reqp, &dscp->fid, NULL, 1, &updatedDirStatus, &volSync, NULL, NULL, code));
 
     code = cm_MapRPCError(code, reqp);
 
@@ -2959,29 +3465,46 @@ long cm_Link(cm_scache_t *dscp, clientchar_t *cnamep, cm_scache_t *sscp, long fl
         dirop.lockType = CM_DIRLOCK_WRITE;
     }
     lock_ObtainWrite(&dscp->rw);
-    cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
     if (code == 0) {
-        cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, CM_MERGEFLAG_DIROP);
-    }
-    lock_ReleaseWrite(&dscp->rw);
+        code = cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, reqp, CM_MERGEFLAG_DIROP);
+        invalidate = 1;
 
-    if (code == 0) {
         if (cm_CheckDirOpForSingleChange(&dirop)) {
+            lock_ReleaseWrite(&dscp->rw);
             cm_DirCreateEntry(&dirop, fnamep, &sscp->fid);
 #ifdef USE_BPLUS
             cm_BPlusDirCreateEntry(&dirop, cnamep, &sscp->fid);
 #endif
+            lock_ObtainWrite(&dscp->rw);
         }
+    } else {
+        InterlockedDecrement(&dscp->activeRPCs);
     }
+    cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
+    lock_ReleaseWrite(&dscp->rw);
+
     cm_EndDirOp(&dirop);
 
+    if (invalidate && RDR_Initialized)
+        RDR_InvalidateObject(dscp->fid.cell, dscp->fid.volume, dscp->fid.vnode,
+                             dscp->fid.unique, dscp->fid.hash,
+                             dscp->fileType, AFS_INVALIDATE_DATA_VERSION);
+
+    /* Update the linked object status */
+    if (code == 0) {
+        lock_ObtainWrite(&sscp->rw);
+        InterlockedIncrement(&sscp->activeRPCs);
+        code = cm_MergeStatus(NULL, sscp, &newLinkStatus, &volSync, userp, reqp, 0);
+        lock_ReleaseWrite(&sscp->rw);
+    }
+
     free(fnamep);
 
     return code;
 }
 
 long cm_SymLink(cm_scache_t *dscp, clientchar_t *cnamep, fschar_t *contentsp, long flags,
-                cm_attr_t *attrp, cm_user_t *userp, cm_req_t *reqp)
+                cm_attr_t *attrp, cm_user_t *userp, cm_req_t *reqp, cm_scache_t **scpp)
 {
     cm_conn_t *connp;
     long code;
@@ -2997,11 +3520,21 @@ long cm_SymLink(cm_scache_t *dscp, clientchar_t *cnamep, fschar_t *contentsp, lo
     cm_dirOp_t dirop;
     fschar_t *fnamep = NULL;
 
+    if (scpp)
+        *scpp = NULL;
+
+    /* Check for RO volume */
+    if (dscp->flags & CM_SCACHEFLAG_RO)
+        return CM_ERROR_READONLY;
+
+    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);
@@ -3016,10 +3549,11 @@ long cm_SymLink(cm_scache_t *dscp, clientchar_t *cnamep, fschar_t *contentsp, lo
     cm_StatusFromAttr(&inStatus, NULL, attrp);
 
     /* try the RPC now */
+    InterlockedIncrement(&dscp->activeRPCs);
     osi_Log1(afsd_logp, "CALL Symlink scp 0x%p", dscp);
     do {
         code = cm_ConnFromFID(&dscp->fid, userp, reqp, &connp);
-        if (code) 
+        if (code)
             continue;
 
         dirAFSFid.Volume = dscp->fid.volume;
@@ -3033,9 +3567,9 @@ long cm_SymLink(cm_scache_t *dscp, clientchar_t *cnamep, fschar_t *contentsp, lo
         rx_PutConnection(rxconnp);
 
     } while (cm_Analyze(connp, userp, reqp,
-                         &dscp->fid, &volSync, NULL, NULL, code));
+                         &dscp->fid, NULL, 1, &updatedDirStatus, &volSync, NULL, NULL, code));
     code = cm_MapRPCError(code, reqp);
-        
+
     if (code)
         osi_Log1(afsd_logp, "CALL Symlink FAILURE, code 0x%x", code);
     else
@@ -3046,45 +3580,53 @@ long cm_SymLink(cm_scache_t *dscp, clientchar_t *cnamep, fschar_t *contentsp, lo
         dirop.lockType = CM_DIRLOCK_WRITE;
     }
     lock_ObtainWrite(&dscp->rw);
-    cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
-    if (code == 0) {
-        cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, CM_MERGEFLAG_DIROP);
-    }
-    lock_ReleaseWrite(&dscp->rw);
-
     if (code == 0) {
+        code = cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, reqp, CM_MERGEFLAG_DIROP);
+        cm_SetFid(&newFid, dscp->fid.cell, dscp->fid.volume, newAFSFid.Vnode, newAFSFid.Unique);
         if (cm_CheckDirOpForSingleChange(&dirop)) {
+            lock_ReleaseWrite(&dscp->rw);
             cm_SetFid(&newFid, dscp->fid.cell, dscp->fid.volume, newAFSFid.Vnode, newAFSFid.Unique);
 
             cm_DirCreateEntry(&dirop, fnamep, &newFid);
 #ifdef USE_BPLUS
             cm_BPlusDirCreateEntry(&dirop, cnamep, &newFid);
 #endif
+            lock_ObtainWrite(&dscp->rw);
         }
+    } else {
+        InterlockedDecrement(&dscp->activeRPCs);
     }
+    cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
+    lock_ReleaseWrite(&dscp->rw);
+
     cm_EndDirOp(&dirop);
 
-    /* now try to create the new dir's entry, too, but be careful to 
+    /* now try to create the new dir's entry, too, but be careful to
      * make sure that we don't merge in old info.  Since we weren't locking
      * out any requests during the file's creation, we may have pretty old
      * info.
      */
     if (code == 0) {
-        cm_SetFid(&newFid, dscp->fid.cell, dscp->fid.volume, newAFSFid.Vnode, newAFSFid.Unique);
-        code = cm_GetSCache(&newFid, &scp, userp, reqp);
+        code = cm_GetSCache(&newFid, &dscp->fid, &scp, userp, reqp);
         if (code == 0) {
             lock_ObtainWrite(&scp->rw);
             if (!cm_HaveCallback(scp)) {
-                cm_MergeStatus(dscp, scp, &newLinkStatus, &volSync,
-                                userp, 0);
-            }       
+                InterlockedIncrement(&scp->activeRPCs);
+                code = cm_MergeStatus( dscp, scp, &newLinkStatus, &volSync,
+                                       userp, reqp, 0);
+            }
             lock_ReleaseWrite(&scp->rw);
-            cm_ReleaseSCache(scp);
+
+            if (scpp) {
+                *scpp = scp;
+            } else {
+                cm_ReleaseSCache(scp);
+            }
         }
     }
 
     free(fnamep);
-       
+
     /* and return error code */
     return code;
 }
@@ -3121,10 +3663,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)
@@ -3140,11 +3685,18 @@ long cm_RemoveDir(cm_scache_t *dscp, fschar_t *fnamep, clientchar_t *cnamep, cm_
     if (code)
         goto done;
 
+    /* Check for RO volume */
+    if (dscp->flags & CM_SCACHEFLAG_RO) {
+        code = CM_ERROR_READONLY;
+        goto done;
+    }
+
     /* 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);
@@ -3155,10 +3707,11 @@ long cm_RemoveDir(cm_scache_t *dscp, fschar_t *fnamep, clientchar_t *cnamep, cm_
     didEnd = 0;
 
     /* try the RPC now */
+    InterlockedIncrement(&dscp->activeRPCs);
     osi_Log1(afsd_logp, "CALL RemoveDir scp 0x%p", dscp);
     do {
         code = cm_ConnFromFID(&dscp->fid, userp, reqp, &connp);
-        if (code) 
+        if (code)
             continue;
 
         dirAFSFid.Volume = dscp->fid.volume;
@@ -3171,7 +3724,7 @@ long cm_RemoveDir(cm_scache_t *dscp, fschar_t *fnamep, clientchar_t *cnamep, cm_
         rx_PutConnection(rxconnp);
 
     } while (cm_Analyze(connp, userp, reqp,
-                        &dscp->fid, &volSync, NULL, NULL, code));
+                        &dscp->fid, NULL, 1, &updatedDirStatus, &volSync, NULL, NULL, code));
     code = cm_MapRPCErrorRmdir(code, reqp);
 
     if (code)
@@ -3184,30 +3737,39 @@ long cm_RemoveDir(cm_scache_t *dscp, fschar_t *fnamep, clientchar_t *cnamep, cm_
         dirop.lockType = CM_DIRLOCK_WRITE;
     }
     lock_ObtainWrite(&dscp->rw);
-    cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
-    if (code == 0) {
-        cm_dnlcRemove(dscp, cnamep); 
-        cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, CM_MERGEFLAG_DIROP);
-    }
-    lock_ReleaseWrite(&dscp->rw);
-
     if (code == 0) {
+        cm_dnlcRemove(dscp, cnamep);
+        code = cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, reqp, CM_MERGEFLAG_DIROP);
         if (cm_CheckDirOpForSingleChange(&dirop) && cnamep != NULL) {
+            lock_ReleaseWrite(&dscp->rw);
             cm_DirDeleteEntry(&dirop, fnamep);
 #ifdef USE_BPLUS
             cm_BPlusDirDeleteEntry(&dirop, cnamep);
 #endif
+            lock_ObtainWrite(&dscp->rw);
         }
+    } else {
+        InterlockedDecrement(&dscp->activeRPCs);
     }
+    cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
+    lock_ReleaseWrite(&dscp->rw);
+
     cm_EndDirOp(&dirop);
 
     if (scp) {
-        cm_ReleaseSCache(scp);
         if (code == 0) {
            lock_ObtainWrite(&scp->rw);
-            scp->flags |= CM_SCACHEFLAG_DELETED;
+           _InterlockedOr(&scp->flags, CM_SCACHEFLAG_DELETED);
+            lock_ObtainWrite(&cm_scacheLock);
+            cm_AdjustScacheLRU(scp);
+            lock_ReleaseWrite(&cm_scacheLock);
            lock_ReleaseWrite(&scp->rw);
+            if (RDR_Initialized && !(reqp->flags & CM_REQ_SOURCE_REDIR))
+                RDR_InvalidateObject(scp->fid.cell, scp->fid.volume, scp->fid.vnode,
+                                     scp->fid.unique, scp->fid.hash,
+                                     scp->fileType, AFS_INVALIDATE_DELETED);
         }
+        cm_ReleaseSCache(scp);
     }
 
   done:
@@ -3234,7 +3796,7 @@ long cm_Open(cm_scache_t *scp, int type, cm_user_t *userp)
 
     /* we're done */
     return 0;
-}       
+}
 
 /*! \brief Rename a file or directory
 
@@ -3265,14 +3827,14 @@ long cm_Rename(cm_scache_t *oldDscp, fschar_t *oldNamep, clientchar_t *cOldNamep
                cm_req_t *reqp)
 {
     cm_conn_t *connp;
-    long code;
+    long code = 0;
     AFSFid oldDirAFSFid;
     AFSFid newDirAFSFid;
-    int didEnd;
     AFSFetchStatus updatedOldDirStatus;
     AFSFetchStatus updatedNewDirStatus;
     AFSVolSync volSync;
-    int oneDir;
+    int oneDir = 0;
+    int bTargetExists = 0;
     struct rx_connection * rxconnp;
     cm_dirOp_t oldDirOp;
     cm_fid_t   fileFid;
@@ -3280,16 +3842,34 @@ long cm_Rename(cm_scache_t *oldDscp, fschar_t *oldNamep, clientchar_t *cOldNamep
     cm_dirOp_t newDirOp;
     fschar_t * newNamep = NULL;
     int free_oldNamep = FALSE;
+    cm_scache_t *oldScp = NULL, *oldTargetScp = NULL;
+    int rpc_skipped = 0;
+
+    memset(&volSync, 0, sizeof(volSync));
 
     if (cOldNamep == NULL || cNewNamep == NULL ||
         cm_ClientStrLen(cOldNamep) == 0 ||
         cm_ClientStrLen(cNewNamep) == 0)
         return CM_ERROR_INVAL;
 
+    /* check for identical names */
+    if (oldDscp == newDscp &&
+        cm_ClientStrCmp(cOldNamep, cNewNamep) == 0) {
+        osi_Log2(afsd_logp, "cm_Rename oldDscp 0x%p newDscp 0x%p CM_ERROR_RENAME_IDENTICAL",
+                  oldDscp, newDscp);
+        return CM_ERROR_RENAME_IDENTICAL;
+    }
+
+    /* Check for RO volume */
+    if ((oldDscp->flags & CM_SCACHEFLAG_RO) || (newDscp->flags & CM_SCACHEFLAG_RO)) {
+        return CM_ERROR_READONLY;
+    }
+
     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)
@@ -3298,29 +3878,21 @@ long cm_Rename(cm_scache_t *oldDscp, fschar_t *oldNamep, clientchar_t *cOldNamep
         }
 #endif
         if (code) {
-            osi_Log2(afsd_logp, "cm_Rename oldDscp 0x%p cOldName %S Original Name lookup failed", 
+            osi_Log2(afsd_logp, "cm_Rename oldDscp 0x%p cOldName %S Original Name lookup failed",
                       oldDscp, osi_LogSaveStringW(afsd_logp, cOldNamep));
             goto done;
         }
     }
 
-
     /* 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.  We do this in vnode order so that we don't deadlock,
      * which makes the code a little verbose.
      */
     if (oldDscp == newDscp) {
-        /* check for identical names */
-        if (cm_ClientStrCmp(cOldNamep, cNewNamep) == 0) {
-            osi_Log2(afsd_logp, "cm_Rename oldDscp 0x%p newDscp 0x%p CM_ERROR_RENAME_IDENTICAL", 
-                      oldDscp, newDscp);
-            code = CM_ERROR_RENAME_IDENTICAL;
-            goto done;
-        }
-
         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);
@@ -3336,7 +3908,7 @@ long cm_Rename(cm_scache_t *oldDscp, fschar_t *oldNamep, clientchar_t *cOldNamep
         oneDir = 0;
         if (oldDscp->fid.cell != newDscp->fid.cell ||
              oldDscp->fid.volume != newDscp->fid.volume) {
-            osi_Log2(afsd_logp, "cm_Rename oldDscp 0x%p newDscp 0x%p CM_ERROR_CROSSDEVLINK", 
+            osi_Log2(afsd_logp, "cm_Rename oldDscp 0x%p newDscp 0x%p CM_ERROR_CROSSDEVLINK",
                       oldDscp, newDscp);
             code = CM_ERROR_CROSSDEVLINK;
             goto done;
@@ -3347,14 +3919,15 @@ long cm_Rename(cm_scache_t *oldDscp, fschar_t *oldNamep, clientchar_t *cOldNamep
          * stale info.  Avoid deadlocks and quit now.
          */
         if (oldDscp->fid.vnode == newDscp->fid.vnode) {
-            osi_Log2(afsd_logp, "cm_Rename oldDscp 0x%p newDscp 0x%p vnode collision", 
+            osi_Log2(afsd_logp, "cm_Rename oldDscp 0x%p newDscp 0x%p vnode collision",
                       oldDscp, newDscp);
             code = CM_ERROR_CROSSDEVLINK;
             goto done;
         }
 
         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,
@@ -3363,7 +3936,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,
@@ -3378,12 +3952,13 @@ long cm_Rename(cm_scache_t *oldDscp, fschar_t *oldNamep, clientchar_t *cOldNamep
                                    CM_SCACHESYNC_STOREDATA);
                     lock_ReleaseWrite(&oldDscp->rw);
                     cm_EndDirOp(&oldDirOp);
-                }       
+                }
             }
         }
         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,
@@ -3392,7 +3967,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,
@@ -3407,24 +3983,75 @@ long cm_Rename(cm_scache_t *oldDscp, fschar_t *oldNamep, clientchar_t *cOldNamep
                                    CM_SCACHESYNC_STOREDATA);
                     lock_ReleaseWrite(&newDscp->rw);
                     cm_EndDirOp(&newDirOp);
-                }       
+                }
             }
         }
     }  /* two distinct vnodes */
 
-    if (code) 
+    if (code)
         goto done;
 
-    didEnd = 0;
+    /*
+     * The source and destination directories are now locked and no other local
+     * changes can occur.
+     *
+     * Before we permit the operation, make sure that we do not already have
+     * an object in the destination directory that has a case-insensitive match
+     * for this name UNLESS the matching object is the object we are renaming.
+     */
+    code = cm_Lookup(oldDscp, cOldNamep, 0, userp, reqp, &oldScp);
+    if (code) {
+        osi_Log2(afsd_logp, "cm_Rename oldDscp 0x%p cOldName %S old name lookup failed",
+                 oldDscp, osi_LogSaveStringW(afsd_logp, cOldNamep));
+        rpc_skipped = 1;
+        goto post_rpc;
+    }
+
+    /* Case sensitive lookup.  If this succeeds we are done. */
+    code = cm_Lookup(newDscp, cNewNamep, 0, userp, reqp, &oldTargetScp);
+    if (code) {
+        /*
+         * Case insensitive lookup.  If this succeeds, it could have found the
+         * same file with a name that differs only by case or it could be a
+         * different file entirely.
+         */
+        code = cm_Lookup(newDscp, cNewNamep, CM_FLAG_CASEFOLD, userp, reqp, &oldTargetScp);
+        if (code == 0) {
+            /* found a matching object with the new name */
+            if (cm_FidCmp(&oldScp->fid, &oldTargetScp->fid)) {
+                /* and they don't match so return an error */
+                osi_Log2(afsd_logp, "cm_Rename newDscp 0x%p cNewName %S new name already exists",
+                          newDscp, osi_LogSaveStringW(afsd_logp, cNewNamep));
+                code = CM_ERROR_EXISTS;
+            }
+            cm_ReleaseSCache(oldTargetScp);
+            oldTargetScp = NULL;
+        } else if (code == CM_ERROR_AMBIGUOUS_FILENAME) {
+            code = CM_ERROR_EXISTS;
+        } else {
+            /* The target does not exist.  Clear the error and perform the rename. */
+            code = 0;
+        }
+    } else {
+        bTargetExists = 1;
+    }
+
+    if (code) {
+        rpc_skipped = 1;
+        goto post_rpc;
+    }
 
     newNamep = cm_ClientStringToFsStringAlloc(cNewNamep, -1, NULL);
 
     /* try the RPC now */
-    osi_Log2(afsd_logp, "CALL Rename old scp 0x%p new scp 0x%p", 
+    InterlockedIncrement(&oldDscp->activeRPCs);
+    if (!oneDir)
+        InterlockedIncrement(&newDscp->activeRPCs);
+    osi_Log2(afsd_logp, "CALL Rename old scp 0x%p new scp 0x%p",
               oldDscp, newDscp);
     do {
         code = cm_ConnFromFID(&oldDscp->fid, userp, reqp, &connp);
-        if (code) 
+        if (code)
             continue;
 
         oldDirAFSFid.Volume = oldDscp->fid.volume;
@@ -3441,30 +4068,34 @@ long cm_Rename(cm_scache_t *oldDscp, fschar_t *oldNamep, clientchar_t *cOldNamep
                             &volSync);
         rx_PutConnection(rxconnp);
 
-    } while (cm_Analyze(connp, userp, reqp, &oldDscp->fid,
-                         &volSync, NULL, NULL, code));
+    } while (cm_Analyze(connp, userp, reqp, &oldDscp->fid, NULL, 1,
+                        &updatedOldDirStatus, &volSync, NULL, NULL, code));
     code = cm_MapRPCError(code, reqp);
-        
+
     if (code)
         osi_Log1(afsd_logp, "CALL Rename FAILURE, code 0x%x", code);
     else
         osi_Log0(afsd_logp, "CALL Rename SUCCESS");
 
+  post_rpc:
     /* update the individual stat cache entries for the directories */
     if (oldDirOp.scp) {
         lock_ObtainWrite(&oldDirOp.scp->dirlock);
         oldDirOp.lockType = CM_DIRLOCK_WRITE;
     }
-    lock_ObtainWrite(&oldDscp->rw);
-    cm_SyncOpDone(oldDscp, NULL, CM_SCACHESYNC_STOREDATA);
-
-    if (code == 0)
-        cm_MergeStatus(NULL, oldDscp, &updatedOldDirStatus, &volSync,
-                       userp, CM_MERGEFLAG_DIROP);
-    lock_ReleaseWrite(&oldDscp->rw);
 
+    lock_ObtainWrite(&oldDscp->rw);
     if (code == 0) {
+        code = cm_MergeStatus( NULL, oldDscp, &updatedOldDirStatus, &volSync,
+                               userp, reqp, CM_MERGEFLAG_DIROP);
         if (cm_CheckDirOpForSingleChange(&oldDirOp)) {
+            lock_ReleaseWrite(&oldDscp->rw);
+            if (bTargetExists && oneDir) {
+                diropCode = cm_DirDeleteEntry(&oldDirOp, newNamep);
+#ifdef USE_BPLUS
+                cm_BPlusDirDeleteEntry(&oldDirOp, cNewNamep);
+#endif
+            }
 
 #ifdef USE_BPLUS
             diropCode = cm_BPlusDirLookup(&oldDirOp, cOldNamep, &fileFid);
@@ -3481,16 +4112,23 @@ long cm_Rename(cm_scache_t *oldDscp, fschar_t *oldNamep, clientchar_t *cOldNamep
                     cm_BPlusDirCreateEntry(&oldDirOp, cNewNamep, &fileFid);
 #endif
                 }
-                
-                if (diropCode == 0) { 
+
+                if (diropCode == 0) {
                     diropCode = cm_DirDeleteEntry(&oldDirOp, oldNamep);
 #ifdef USE_BPLUS
                     cm_BPlusDirDeleteEntry(&oldDirOp, cOldNamep);
 #endif
                 }
             }
+            lock_ObtainWrite(&oldDscp->rw);
         }
+    } else {
+        if (!rpc_skipped)
+            InterlockedDecrement(&oldDscp->activeRPCs);
     }
+    cm_SyncOpDone(oldDscp, NULL, CM_SCACHESYNC_STOREDATA);
+    lock_ReleaseWrite(&oldDscp->rw);
+
     cm_EndDirOp(&oldDirOp);
 
     /* and update it for the new one, too, if necessary */
@@ -3500,27 +4138,72 @@ long cm_Rename(cm_scache_t *oldDscp, fschar_t *oldNamep, clientchar_t *cOldNamep
             newDirOp.lockType = CM_DIRLOCK_WRITE;
         }
         lock_ObtainWrite(&newDscp->rw);
-        cm_SyncOpDone(newDscp, NULL, CM_SCACHESYNC_STOREDATA);
-        if (code == 0)
-            cm_MergeStatus(NULL, newDscp, &updatedNewDirStatus, &volSync,
-                            userp, CM_MERGEFLAG_DIROP);
-        lock_ReleaseWrite(&newDscp->rw);
-
         if (code == 0) {
-            /* we only make the local change if we successfully made
-               the change in the old directory AND there was only one
-               change in the new directory */
+            code = cm_MergeStatus( NULL, newDscp, &updatedNewDirStatus, &volSync,
+                                   userp, reqp, CM_MERGEFLAG_DIROP);
+
+            /*
+             * we only make the local change if we successfully made
+             * the change in the old directory AND there was only one
+             * change in the new directory
+             */
             if (diropCode == 0 && cm_CheckDirOpForSingleChange(&newDirOp)) {
+                lock_ReleaseWrite(&newDscp->rw);
+
+                if (bTargetExists && !oneDir) {
+                    diropCode = cm_DirDeleteEntry(&newDirOp, newNamep);
+#ifdef USE_BPLUS
+                    cm_BPlusDirDeleteEntry(&newDirOp, cNewNamep);
+#endif
+                }
+
                 cm_DirCreateEntry(&newDirOp, newNamep, &fileFid);
 #ifdef USE_BPLUS
                 cm_BPlusDirCreateEntry(&newDirOp, cNewNamep, &fileFid);
 #endif
+                lock_ObtainWrite(&newDscp->rw);
             }
+        } else {
+            if (!rpc_skipped)
+                InterlockedIncrement(&newDscp->activeRPCs);
         }
+        cm_SyncOpDone(newDscp, NULL, CM_SCACHESYNC_STOREDATA);
+        lock_ReleaseWrite(&newDscp->rw);
+
         cm_EndDirOp(&newDirOp);
     }
 
+    if (code == 0) {
+        /*
+         * After the rename the file server has invalidated the callbacks
+         * on the file that was moved and destroyed any target file.
+         */
+        lock_ObtainWrite(&oldScp->rw);
+        cm_DiscardSCache(oldScp);
+        lock_ReleaseWrite(&oldScp->rw);
+
+        if (RDR_Initialized)
+            RDR_InvalidateObject(oldScp->fid.cell, oldScp->fid.volume, oldScp->fid.vnode, oldScp->fid.unique,
+                                  oldScp->fid.hash, oldScp->fileType, AFS_INVALIDATE_CALLBACK);
+
+        if (oldTargetScp) {
+            lock_ObtainWrite(&oldTargetScp->rw);
+            cm_DiscardSCache(oldTargetScp);
+            lock_ReleaseWrite(&oldTargetScp->rw);
+
+            if (RDR_Initialized)
+                RDR_InvalidateObject(oldTargetScp->fid.cell, oldTargetScp->fid.volume, oldTargetScp->fid.vnode, oldTargetScp->fid.unique,
+                                     oldTargetScp->fid.hash, oldTargetScp->fileType, AFS_INVALIDATE_CALLBACK);
+        }
+    }
+
   done:
+    if (oldScp)
+        cm_ReleaseSCache(oldScp);
+
+    if (oldTargetScp)
+        cm_ReleaseSCache(oldTargetScp);
+
     if (free_oldNamep)
         free(oldNamep);
 
@@ -3843,8 +4526,8 @@ static void cm_LockRangeSubtract(cm_range_t * pos, const cm_range_t * neg)
     afs_int64 int_begin;
     afs_int64 int_end;
 
-    int_begin = MAX(pos->offset, neg->offset);
-    int_end = MIN(pos->offset+pos->length, neg->offset+neg->length);
+    int_begin = max(pos->offset, neg->offset);
+    int_end = min(pos->offset+pos->length, neg->offset+neg->length);
 
     if (int_begin < int_end) {
         if (int_begin == pos->offset) {
@@ -3863,9 +4546,9 @@ static void cm_LockRangeSubtract(cm_range_t * pos, const cm_range_t * neg)
 /* Called with scp->rw held.  Returns 0 if all is clear to read the
    specified range by the client identified by key.
  */
-long cm_LockCheckRead(cm_scache_t *scp, 
-                      LARGE_INTEGER LOffset, 
-                      LARGE_INTEGER LLength, 
+long cm_LockCheckRead(cm_scache_t *scp,
+                      LARGE_INTEGER LOffset,
+                      LARGE_INTEGER LLength,
                       cm_key_t key)
 {
 #ifndef ADVISORY_LOCKS
@@ -3896,12 +4579,12 @@ long cm_LockCheckRead(cm_scache_t *scp,
     lock_ObtainRead(&cm_scacheLock);
 
     for (q = scp->fileLocksH; q && range.length > 0; q = osi_QNext(q)) {
-        fileLock = 
+        fileLock =
             (cm_file_lock_t *)((char *) q - offsetof(cm_file_lock_t, fileq));
 
         if (INTERSECT_RANGE(range, fileLock->range)) {
             if (IS_LOCK_ACTIVE(fileLock)) {
-                if (fileLock->key == key) {
+                if (cm_KeyEquals(&fileLock->key, &key, 0)) {
 
                     /* If there is an active lock for this client, it
                        is safe to substract ranges.*/
@@ -3923,7 +4606,7 @@ long cm_LockCheckRead(cm_scache_t *scp,
                         cm_LockRangeSubtract(&range, &fileLock->range);
                 }
             } else if (IS_LOCK_LOST(fileLock) &&
-                      (fileLock->key == key || fileLock->lockType == LockWrite)) {
+                       (cm_KeyEquals(&fileLock->key, &key, 0) || fileLock->lockType == LockWrite)) {
                 code = CM_ERROR_BADFD;
                 break;
             }
@@ -3977,12 +4660,12 @@ long cm_LockCheckWrite(cm_scache_t *scp,
     lock_ObtainRead(&cm_scacheLock);
 
     for (q = scp->fileLocksH; q && range.length > 0; q = osi_QNext(q)) {
-        fileLock = 
+        fileLock =
             (cm_file_lock_t *)((char *) q - offsetof(cm_file_lock_t, fileq));
 
         if (INTERSECT_RANGE(range, fileLock->range)) {
             if (IS_LOCK_ACTIVE(fileLock)) {
-                if (fileLock->key == key) {
+                if (cm_KeyEquals(&fileLock->key, &key, 0)) {
                     if (fileLock->lockType == LockWrite) {
 
                         /* if there is an active lock for this client, it
@@ -4051,19 +4734,35 @@ long cm_IntSetLock(cm_scache_t * scp, cm_user_t * userp, int lockType,
     AFSVolSync volSync;
     afs_uint32 reqflags = reqp->flags;
 
+    osi_Log2(afsd_logp, "CALL SetLock scp 0x%p for lock %d", scp, lockType);
+
+#if 0
+    /*
+     * The file server prior to 1.6.2 does not report an accurate value
+     * and callbacks are not issued if the lock is dropped due to expiration.
+     */
+    if ((lockType != LOCKING_ANDX_SHARED_LOCK && scp->fsLockCount != 0) ||
+         (lockType == LOCKING_ANDX_SHARED_LOCK && scp->fsLockCount < 0))
+    {
+        code = CM_ERROR_LOCK_NOT_GRANTED;
+        osi_Log2(afsd_logp, "CALL SetLock FAILURE, fsLockCount %d code 0x%x", scp->fsLockCount, code);
+        return code;
+    }
+#endif
+
+    memset(&volSync, 0, sizeof(volSync));
+
     tfid.Volume = scp->fid.volume;
     tfid.Vnode = scp->fid.vnode;
     tfid.Unique = scp->fid.unique;
     cfid = scp->fid;
 
-    osi_Log2(afsd_logp, "CALL SetLock scp 0x%p for lock %d", scp, lockType);
-
     reqp->flags |= CM_REQ_NORETRY;
     lock_ReleaseWrite(&scp->rw);
 
     do {
         code = cm_ConnFromFID(&cfid, userp, reqp, &connp);
-        if (code) 
+        if (code)
             break;
 
         rxconnp = cm_GetRxConn(connp);
@@ -4071,7 +4770,7 @@ long cm_IntSetLock(cm_scache_t * scp, cm_user_t * userp, int lockType,
                              &volSync);
         rx_PutConnection(rxconnp);
 
-    } while (cm_Analyze(connp, userp, reqp, &cfid, &volSync,
+    } while (cm_Analyze(connp, userp, reqp, &cfid, NULL, 1, NULL, &volSync,
                         NULL, NULL, code));
 
     code = cm_MapRPCError(code, reqp);
@@ -4081,8 +4780,20 @@ long cm_IntSetLock(cm_scache_t * scp, cm_user_t * userp, int lockType,
         osi_Log0(afsd_logp, "CALL SetLock SUCCESS");
     }
 
-    lock_ObtainWrite(&scp->rw);
     reqp->flags = reqflags;
+
+    lock_ObtainWrite(&scp->rw);
+    if (code == 0) {
+        /*
+         * The file server does not return a status structure so we must
+         * locally track the file server lock count to the best of our
+         * ability.
+         */
+        if (lockType == LockWrite)
+            scp->fsLockCount = -1;
+        else
+            scp->fsLockCount++;
+    }
     return code;
 }
 
@@ -4096,6 +4807,13 @@ long cm_IntReleaseLock(cm_scache_t * scp, cm_user_t * userp,
     struct rx_connection * rxconnp;
     AFSVolSync volSync;
 
+    if (scp->flags & CM_SCACHEFLAG_DELETED) {
+        osi_Log1(afsd_logp, "CALL ReleaseLock on Deleted Vnode scp 0x%p", scp);
+        return 0;
+    }
+
+    memset(&volSync, 0, sizeof(volSync));
+
     tfid.Volume = scp->fid.volume;
     tfid.Vnode = scp->fid.vnode;
     tfid.Unique = scp->fid.unique;
@@ -4107,14 +4825,14 @@ long cm_IntReleaseLock(cm_scache_t * scp, cm_user_t * userp,
 
     do {
         code = cm_ConnFromFID(&cfid, userp, reqp, &connp);
-        if (code) 
+        if (code)
             break;
 
         rxconnp = cm_GetRxConn(connp);
         code = RXAFS_ReleaseLock(rxconnp, &tfid, &volSync);
         rx_PutConnection(rxconnp);
 
-    } while (cm_Analyze(connp, userp, reqp, &cfid, &volSync,
+    } while (cm_Analyze(connp, userp, reqp, &cfid, NULL, 1, NULL, &volSync,
                         NULL, NULL, code));
     code = cm_MapRPCError(code, reqp);
     if (code)
@@ -4123,10 +4841,20 @@ long cm_IntReleaseLock(cm_scache_t * scp, cm_user_t * userp,
     else
         osi_Log0(afsd_logp,
                  "CALL ReleaseLock SUCCESS");
-        
+
     lock_ObtainWrite(&scp->rw);
+    if (code == 0) {
+        /*
+         * The file server does not return a status structure so we must
+         * locally track the file server lock count to the best of our
+         * ability.
+         */
+        scp->fsLockCount--;
+        if (scp->fsLockCount < 0)
+            scp->fsLockCount = 0;
+    }
 
-    return code;
+    return (code != CM_ERROR_BADFD ? code : 0);
 }
 
 /* called with scp->rw held.  May release it during processing, but
@@ -4189,8 +4917,8 @@ long cm_LockCheckPerms(cm_scache_t * scp,
            this should be sufficient for her to get a write-lock (but
            not necessarily a read-lock). VICED_CAPABILITY_WRITELOCKACL
            indicates whether a file server supports getting write
-           locks when the user only has PRSFS_INSERT. 
-           
+           locks when the user only has PRSFS_INSERT.
+
            If the file was not created by the user we skip the check
            because the INSERT bit will not apply to this user even
            if it is set.
@@ -4232,8 +4960,8 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
 
     osi_Log4(afsd_logp, "cm_Lock scp 0x%x type 0x%x offset %d length %d",
              scp, sLockType, (unsigned long)LOffset.QuadPart, (unsigned long)LLength.QuadPart);
-    osi_Log3(afsd_logp, "... allowWait %d key 0x%x:%x", allowWait, 
-             (unsigned long)(key >> 32), (unsigned long)(key & 0xffffffff));
+    osi_Log4(afsd_logp, "... allowWait %d key <0x%x, 0x%x, 0x%x>", allowWait,
+             key.process_id, key.session_id, key.file_id);
 
     /*
    A client C can OBTAIN a lock L on cm_scache_t S iff (both 3 and 4):
@@ -4267,7 +4995,7 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
             (cm_file_lock_t *)((char *) q - offsetof(cm_file_lock_t, fileq));
 
         if (IS_LOCK_LOST(fileLock)) {
-            if (fileLock->key == key) {
+            if (cm_KeyEquals(&fileLock->key, &key, 0)) {
                 code = CM_ERROR_BADFD;
                 break;
             } else if (fileLock->lockType == LockWrite && INTERSECT_RANGE(range, fileLock->range)) {
@@ -4300,7 +5028,7 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
             int has_insert = 0;
 
             /* we already have the lock we need */
-            osi_Log3(afsd_logp, "   we already have the correct lock. exclusives[%d], shared[%d], serverLock[%d]", 
+            osi_Log3(afsd_logp, "   we already have the correct lock. exclusives[%d], shared[%d], serverLock[%d]",
                      scp->exclusiveLocks, scp->sharedLocks, (int)(signed char) scp->serverLock);
 
             code = cm_LockCheckPerms(scp, Which, userp, reqp, &has_insert);
@@ -4386,7 +5114,7 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
                    was valid) that we had a valid server lock. */
                 scp->lockDataVersion = scp->dataVersion;
                 check_data_version = TRUE;
-        
+
                 code = cm_IntReleaseLock(scp, userp, reqp);
 
                 if (code) {
@@ -4524,18 +5252,18 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
         if (force_client_lock && code != CM_ERROR_WOULDBLOCK)
             code = 0;
 
+        cm_HoldUser(userp);
+
         lock_ObtainWrite(&cm_scacheLock);
         fileLock = cm_GetFileLock();
-        lock_ReleaseWrite(&cm_scacheLock);
 #ifdef DEBUG
         fileLock->fid = scp->fid;
 #endif
         fileLock->key = key;
         fileLock->lockType = Which;
-        cm_HoldUser(userp);
         fileLock->userp = userp;
         fileLock->range = range;
-        fileLock->flags = (code == 0 ? 0 : 
+        fileLock->flags = (code == 0 ? 0 :
                            ((wait_unlock)?
                             CM_FILELOCK_FLAG_WAITUNLOCK :
                             CM_FILELOCK_FLAG_WAITLOCK));
@@ -4545,7 +5273,6 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
 
         fileLock->lastUpdate = (code == 0 && !force_client_lock) ? time(NULL) : 0;
 
-        lock_ObtainWrite(&cm_scacheLock);
         osi_QAddT(&scp->fileLocksH, &scp->fileLocksT, &fileLock->fileq);
         cm_HoldSCacheNoLock(scp);
         fileLock->scp = scp;
@@ -4577,28 +5304,139 @@ 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;
 }
 
-static int cm_KeyEquals(cm_key_t k1, cm_key_t k2, int flags);
+static long
+cm_IntUnlock(cm_scache_t * scp,
+             cm_user_t * userp,
+             cm_req_t *  reqp)
+{
+    long code = 0;
+
+    osi_assertx(scp->sharedLocks >= 0, "scp->sharedLocks < 0");
+    osi_assertx(scp->exclusiveLocks >= 0, "scp->exclusiveLocks < 0");
+    osi_assertx(scp->clientLocks >= 0, "scp->clientLocks < 0");
+
+    if (!SERVERLOCKS_ENABLED(scp)) {
+        osi_Log0(afsd_logp, "  Skipping server lock for scp");
+        goto done;
+    }
 
+    /* Ideally we would go through the rest of the locks to determine
+     * if one or more locks that were formerly in WAITUNLOCK can now
+     * be put to ACTIVE or WAITLOCK and update scp->exclusiveLocks and
+     * scp->sharedLocks accordingly.  However, the retrying of locks
+     * in that manner is done cm_RetryLock() manually.
+     */
+
+    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;
+        osi_Log1(afsd_logp, "  dataVersion on scp = %I64d", scp->dataVersion);
+
+        /* before we downgrade, make sure that we have enough
+           permissions to get the read lock. */
+        code = cm_LockCheckPerms(scp, LockRead, userp, reqp, NULL);
+        if (code != 0) {
+
+            osi_Log0(afsd_logp, "  SKIPPING downgrade because user doesn't have perms to get downgraded lock");
+
+            code = 0;
+            goto done;
+        }
+
+        code = cm_IntReleaseLock(scp, userp, reqp);
+
+        if (code) {
+            /* so we couldn't release it.  Just let the lock be for now */
+            code = 0;
+            goto done;
+        } else {
+            scp->serverLock = -1;
+        }
+
+        code = cm_IntSetLock(scp, userp, LockRead, reqp);
+
+        if (code == 0 && scp->lockDataVersion == scp->dataVersion) {
+            scp->serverLock = LockRead;
+        } else if (code == 0 && scp->lockDataVersion != scp->dataVersion) {
+            /* We lost a race condition.  Although we have a valid
+               lock on the file, the data has changed and essentially
+               we have lost the lock we had during the transition. */
+
+            osi_Log0(afsd_logp, "Data version mismatch during lock downgrade");
+            osi_Log2(afsd_logp, "  Data versions before=%I64d, after=%I64d",
+                     scp->lockDataVersion,
+                     scp->dataVersion);
+
+            code = cm_IntReleaseLock(scp, userp, reqp);
+
+            code = CM_ERROR_INVAL;
+            scp->serverLock = -1;
+        }
+
+        if (code != 0 &&
+            (scp->sharedLocks > 0 || scp->exclusiveLocks > 0) &&
+                (scp->serverLock == -1)) {
+                /* Oopsie */
+                cm_LockMarkSCacheLost(scp);
+            }
+
+        /* failure here has no bearing on the return value of cm_Unlock() */
+        code = 0;
+
+    } else if (scp->serverLock != (-1) &&
+              scp->exclusiveLocks == 0 &&
+              scp->sharedLocks == 0) {
+        /* The serverLock should be released entirely */
+
+        if (scp->serverLock == LockWrite) {
+            osi_Log0(afsd_logp, "  RELEASE LockWrite -> LockNone");
+
+            /* Make sure there are no dirty buffers left. */
+            code = cm_FSync(scp, userp, reqp, TRUE);
+        } else {
+            osi_Log0(afsd_logp, "  RELEASE LockRead -> LockNone");
+        }
+
+        code = cm_IntReleaseLock(scp, userp, reqp);
+
+        if (code == 0)
+            scp->serverLock = (-1);
+    }
+
+  done:
+    return code;
+}
 /* Called with scp->rw held */
 long cm_UnlockByKey(cm_scache_t * scp,
                    cm_key_t key,
-                   int flags,
+                   afs_uint32 flags,
                    cm_user_t * userp,
-                    cm_req_t * reqp)
+                   cm_req_t * reqp)
 {
     long code = 0;
     cm_file_lock_t *fileLock;
     osi_queue_t *q, *qn;
     int n_unlocks = 0;
 
-    osi_Log4(afsd_logp, "cm_UnlockByKey scp 0x%p key 0x%x:%x flags=0x%x",
-             scp,
-             (unsigned long)(key >> 32),
-             (unsigned long)(key & 0xffffffff),
-             flags);
+    osi_Log4(afsd_logp, "cm_UnlockByKey scp 0x%p key <0x%x,0x%x,0x%x",
+             scp, key.process_id, key.session_id, key.file_id);
+    osi_Log1(afsd_logp, "    flags=0x%x", flags);
 
     lock_ObtainWrite(&cm_scacheLock);
 
@@ -4614,9 +5452,8 @@ long cm_UnlockByKey(cm_scache_t * scp,
                  (unsigned long) fileLock->range.offset,
                  (unsigned long) fileLock->range.length,
                 fileLock->lockType);
-        osi_Log3(afsd_logp, "     key[0x%x:%x] flags[0x%x]",
-                 (unsigned long)(fileLock->key >> 32),
-                 (unsigned long)(fileLock->key & 0xffffffff),
+        osi_Log4(afsd_logp, "     key<0x%x, 0x%x, 0x%x> flags[0x%x]",
+                 fileLock->key.process_id, fileLock->key.session_id, fileLock->key.file_id,
                  fileLock->flags);
 
         if (cm_FidCmp(&fileLock->fid, &fileLock->scp->fid)) {
@@ -4636,14 +5473,12 @@ long cm_UnlockByKey(cm_scache_t * scp,
 #endif
 
         if (!IS_LOCK_DELETED(fileLock) &&
-            cm_KeyEquals(fileLock->key, key, flags)) {
+            cm_KeyEquals(&fileLock->key, &key, flags)) {
             osi_Log3(afsd_logp, "...Unlock range [%d,+%d] type %d",
                     fileLock->range.offset,
                     fileLock->range.length,
                     fileLock->lockType);
 
-            if (scp->fileLocksT == q)
-                scp->fileLocksT = osi_QPrev(q);
             osi_QRemoveHT(&scp->fileLocksH, &scp->fileLocksT, q);
 
             if (IS_LOCK_CLIENTONLY(fileLock)) {
@@ -4673,95 +5508,13 @@ long cm_UnlockByKey(cm_scache_t * scp,
         osi_Log0(afsd_logp, "cm_UnlockByKey no locks found");
         osi_Log3(afsd_logp, "   Leaving scp with exclusives[%d], shared[%d], serverLock[%d]",
                  scp->exclusiveLocks, scp->sharedLocks, (int)(signed char) scp->serverLock);
-        
-        return 0;
-    }
-
-    osi_Log1(afsd_logp, "cm_UnlockByKey done with %d locks", n_unlocks);
-
-    osi_assertx(scp->sharedLocks >= 0, "scp->sharedLocks < 0");
-    osi_assertx(scp->exclusiveLocks >= 0, "scp->exclusiveLocks < 0");
-    osi_assertx(scp->clientLocks >= 0, "scp->clientLocks < 0");
-
-    if (!SERVERLOCKS_ENABLED(scp)) {
-        osi_Log0(afsd_logp, "  Skipping server lock for scp");
-        goto done;
-    }
-
-    /* Ideally we would go through the rest of the locks to determine
-     * if one or more locks that were formerly in WAITUNLOCK can now
-     * be put to ACTIVE or WAITLOCK and update scp->exclusiveLocks and
-     * scp->sharedLocks accordingly.  However, the retrying of locks
-     * in that manner is done cm_RetryLock() manually.
-     */
-
-    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");
-
-        /* since scp->serverLock looked sane, we are going to assume
-           that we have a valid server lock. */
-        scp->lockDataVersion = scp->dataVersion;
-        osi_Log1(afsd_logp, "  dataVersion on scp = %I64d", scp->dataVersion);
-
-        code = cm_IntReleaseLock(scp, userp, reqp);
-
-        if (code) {
-            /* so we couldn't release it.  Just let the lock be for now */
-            code = 0;
-            goto done;
-        } else {
-            scp->serverLock = -1;
-        }
-
-        code = cm_IntSetLock(scp, userp, LockRead, reqp);
 
-        if (code == 0 && scp->lockDataVersion == scp->dataVersion) {
-            scp->serverLock = LockRead;
-        } else if (code == 0 && scp->lockDataVersion != scp->dataVersion) {
-            /* We lost a race condition.  Although we have a valid
-               lock on the file, the data has changed and essentially
-               we have lost the lock we had during the transition. */
-
-            osi_Log0(afsd_logp, "Data version mismatch during lock downgrade");
-            osi_Log2(afsd_logp, "  Data versions before=%I64d, after=%I64d",
-                     scp->lockDataVersion,
-                     scp->dataVersion);
-            
-            code = cm_IntReleaseLock(scp, userp, reqp);
-
-            code = CM_ERROR_INVAL;
-            scp->serverLock = -1;
-        }
-
-        if (code != 0 &&
-            (scp->sharedLocks > 0 || scp->exclusiveLocks > 0) &&
-                (scp->serverLock == -1)) {
-                /* Oopsie */
-                cm_LockMarkSCacheLost(scp);
-            }
-
-        /* failure here has no bearing on the return value of
-           cm_Unlock() */
-        code = 0;
-
-    } else if (scp->serverLock != (-1) &&
-              scp->exclusiveLocks == 0 &&
-              scp->sharedLocks == 0) {
-        /* The serverLock should be released entirely */
-
-        code = cm_IntReleaseLock(scp, userp, reqp);
-
-        if (code == 0)
-            scp->serverLock = (-1);
+        return 0;
     }
 
- done:
-
+    code = cm_IntUnlock(scp, userp, reqp);
     osi_Log1(afsd_logp, "cm_UnlockByKey code 0x%x", code);
+
     osi_Log4(afsd_logp, "   Leaving scp with excl[%d], shared[%d], client[%d], serverLock[%d]",
              scp->exclusiveLocks, scp->sharedLocks, scp->clientLocks,
              (int)(signed char) scp->serverLock);
@@ -4769,11 +5522,13 @@ long cm_UnlockByKey(cm_scache_t * scp,
     return code;
 }
 
-long cm_Unlock(cm_scache_t *scp, 
+/* Called with scp->rw held */
+long cm_Unlock(cm_scache_t *scp,
                unsigned char sLockType,
                LARGE_INTEGER LOffset, LARGE_INTEGER LLength,
-               cm_key_t key, 
-               cm_user_t *userp, 
+               cm_key_t key,
+               afs_uint32 flags,
+               cm_user_t *userp,
                cm_req_t *reqp)
 {
     long code = 0;
@@ -4781,12 +5536,19 @@ long cm_Unlock(cm_scache_t *scp,
     cm_file_lock_t *fileLock;
     osi_queue_t *q;
     int release_userp = FALSE;
+    int exact_match = !(flags & CM_UNLOCK_FLAG_MATCH_RANGE);
+    int lock_found  = 0;
+    LARGE_INTEGER RangeEnd;
 
-    osi_Log4(afsd_logp, "cm_Unlock scp 0x%p type 0x%x offset %d length %d",
+    osi_Log4(afsd_logp, "cm_Unlock scp 0x%p type 0x%x offset 0x%x length 0x%x",
              scp, sLockType, (unsigned long)LOffset.QuadPart, (unsigned long)LLength.QuadPart);
-    osi_Log2(afsd_logp, "... key 0x%x:%x",
-             (unsigned long) (key >> 32), (unsigned long) (key & 0xffffffff));
+    osi_Log4(afsd_logp, "... key <0x%x,0x%x,0x%x> flags 0x%x",
+             key.process_id, key.session_id, key.file_id, flags);
+
+    if (!exact_match)
+        RangeEnd.QuadPart = LOffset.QuadPart + LLength.QuadPart;
 
+  try_again:
     lock_ObtainRead(&cm_scacheLock);
 
     for (q = scp->fileLocksH; q; q = osi_QNext(q)) {
@@ -4809,27 +5571,43 @@ long cm_Unlock(cm_scache_t *scp,
             osi_assertx(FALSE, "invalid fid value");
         }
 #endif
-        if (!IS_LOCK_DELETED(fileLock) &&
-            fileLock->key == key &&
-            fileLock->range.offset == LOffset.QuadPart &&
-            fileLock->range.length == LLength.QuadPart) {
-            break;
+        if (exact_match) {
+            if (!IS_LOCK_DELETED(fileLock) &&
+                 cm_KeyEquals(&fileLock->key, &key, 0) &&
+                 fileLock->range.offset == LOffset.QuadPart &&
+                 fileLock->range.length == LLength.QuadPart) {
+                lock_found = 1;
+                break;
+            }
+        } else {
+
+            if (!IS_LOCK_DELETED(fileLock) &&
+                 cm_KeyEquals(&fileLock->key, &key, 0) &&
+                 fileLock->range.offset >= LOffset.QuadPart &&
+                 fileLock->range.offset < RangeEnd.QuadPart &&
+                 (fileLock->range.offset + fileLock->range.length) <= RangeEnd.QuadPart) {
+                lock_found = 1;
+                break;
+            }
         }
     }
 
     if (!q) {
-        osi_Log0(afsd_logp, "cm_Unlock lock not found; failure");
-        
         lock_ReleaseRead(&cm_scacheLock);
 
-        /* The lock didn't exist anyway. *shrug* */
-        return CM_ERROR_RANGE_NOT_LOCKED;
+        if (lock_found && !exact_match) {
+            code = 0;
+            goto done;
+        } else {
+            osi_Log0(afsd_logp, "cm_Unlock lock not found; failure");
+
+            /* The lock didn't exist anyway. *shrug* */
+            return CM_ERROR_RANGE_NOT_LOCKED;
+        }
     }
 
     /* discard lock record */
     lock_ConvertRToW(&cm_scacheLock);
-    if (scp->fileLocksT == q)
-        scp->fileLocksT = osi_QPrev(q);
     osi_QRemoveHT(&scp->fileLocksH, &scp->fileLocksT, q);
 
     /*
@@ -4847,109 +5625,29 @@ long cm_Unlock(cm_scache_t *scp,
     }
 
     fileLock->flags |= CM_FILELOCK_FLAG_DELETED;
+
     if (userp != NULL) {
         cm_ReleaseUser(fileLock->userp);
     } else {
         userp = fileLock->userp;
         release_userp = TRUE;
     }
-    fileLock->userp = NULL;
     cm_ReleaseSCacheNoLock(scp);
+    fileLock->userp = NULL;
     fileLock->scp = NULL;
     lock_ReleaseWrite(&cm_scacheLock);
 
-    if (!SERVERLOCKS_ENABLED(scp)) {
-        osi_Log0(afsd_logp, "   Skipping server locks for scp");
-        goto done;
-    }
+    code = cm_IntUnlock(scp, userp, reqp);
 
-    /* Ideally we would go through the rest of the locks to determine
-     * if one or more locks that were formerly in WAITUNLOCK can now
-     * be put to ACTIVE or WAITLOCK and update scp->exclusiveLocks and
-     * scp->sharedLocks accordingly.  However, the retrying of locks
-     * in that manner is done cm_RetryLock() manually.
-     */
-
-    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");
-
-        /* Since we already had a lock, we assume that there is a
-           valid server lock. */
-        scp->lockDataVersion = scp->dataVersion;
-        osi_Log1(afsd_logp, "   dataVersion on scp is %I64d", scp->dataVersion);
-
-        /* before we downgrade, make sure that we have enough
-           permissions to get the read lock. */
-        code = cm_LockCheckPerms(scp, LockRead, userp, reqp, NULL);
-        if (code != 0) {
-
-            osi_Log0(afsd_logp, "  SKIPPING downgrade because user doesn't have perms to get downgraded lock");
-
-            code = 0;
-            goto done;
-        }
-
-        code = cm_IntReleaseLock(scp, userp, reqp);
-
-        if (code) {
-            /* so we couldn't release it.  Just let the lock be for now */
-            code = 0;
-            goto done;
-        } else {
-            scp->serverLock = -1;
-        }
-
-        code = cm_IntSetLock(scp, userp, LockRead, reqp);
-
-        if (code == 0 && scp->lockDataVersion == scp->dataVersion) {
-            scp->serverLock = LockRead;
-        } else if (code == 0 && scp->lockDataVersion != scp->dataVersion) {
-            /* Lost a race.  We obtained a new lock, but that is
-               meaningless since someone modified the file
-               inbetween. */
-
-            osi_Log0(afsd_logp,
-                     "Data version mismatch while downgrading lock");
-            osi_Log2(afsd_logp,
-                     "  Data versions before=%I64d, after=%I64d",
-                     scp->lockDataVersion,
-                     scp->dataVersion);
-            
-            code = cm_IntReleaseLock(scp, userp, reqp);
-
-            scp->serverLock = -1;
-            code = CM_ERROR_INVAL;
-        }
-
-        if (code != 0 &&
-            (scp->sharedLocks > 0 || scp->exclusiveLocks > 0) &&
-                (scp->serverLock == -1)) {
-                /* Oopsie */
-                cm_LockMarkSCacheLost(scp);
-            }
-
-        /* failure here has no bearing on the return value of
-           cm_Unlock() */
-        code = 0;
-
-    } else if (scp->serverLock != (-1) &&
-              scp->exclusiveLocks == 0 &&
-              scp->sharedLocks == 0) {
-        /* The serverLock should be released entirely */
-
-        code = cm_IntReleaseLock(scp, userp, reqp);
-
-        if (code == 0) {
-            scp->serverLock = (-1);
-        }
+    if (release_userp) {
+        cm_ReleaseUser(userp);
+        release_userp = FALSE;
     }
 
-    if (release_userp)
-        cm_ReleaseUser(userp);
+    if (!exact_match) {
+        osi_Log1(afsd_logp, "cm_Unlock not exact match, searching for next lock, code 0x%x", code);
+        goto try_again;         /* might be more than one lock in the range */
+    }
 
  done:
 
@@ -4973,7 +5671,7 @@ void cm_LockMarkSCacheLost(cm_scache_t * scp)
     lock_ObtainWrite(&cm_scacheLock);
 
     for (q = scp->fileLocksH; q; q = osi_QNext(q)) {
-        fileLock = 
+        fileLock =
             (cm_file_lock_t *)((char *) q - offsetof(cm_file_lock_t, fileq));
 
         if (IS_LOCK_ACTIVE(fileLock) &&
@@ -4988,7 +5686,7 @@ void cm_LockMarkSCacheLost(cm_scache_t * scp)
     }
 
     scp->serverLock = -1;
-    scp->lockDataVersion = -1;
+    scp->lockDataVersion = CM_SCACHE_VERSION_BAD;
     lock_ReleaseWrite(&cm_scacheLock);
 }
 
@@ -5005,6 +5703,8 @@ void cm_CheckLocks()
     struct rx_connection * rxconnp;
     cm_scache_t * scp;
 
+    memset(&volSync, 0, sizeof(volSync));
+
     cm_InitReq(&req);
 
     lock_ObtainWrite(&cm_scacheLock);
@@ -5019,7 +5719,21 @@ void cm_CheckLocks()
        code = -1;
 
         if (IS_LOCK_DELETED(fileLock)) {
+            cm_user_t *userp = fileLock->userp;
+            cm_scache_t *scp = fileLock->scp;
+            fileLock->userp = NULL;
+            fileLock->scp = NULL;
+
+            if (scp && userp) {
+                lock_ReleaseWrite(&cm_scacheLock);
+                lock_ObtainWrite(&scp->rw);
+                code = cm_IntUnlock(scp, userp, &req);
+                lock_ReleaseWrite(&scp->rw);
 
+                cm_ReleaseUser(userp);
+                lock_ObtainWrite(&cm_scacheLock);
+                cm_ReleaseSCacheNoLock(scp);
+            }
             osi_QRemove(&cm_allFileLocks, q);
             cm_PutFileLock(fileLock);
 
@@ -5066,9 +5780,7 @@ void cm_CheckLocks()
                     goto post_syncopdone;
 
                 code = cm_SyncOp(scp, NULL, fileLock->userp, &req, 0,
-                                 CM_SCACHESYNC_NEEDCALLBACK
-                                 | CM_SCACHESYNC_GETSTATUS
-                                 | CM_SCACHESYNC_LOCK);
+                                CM_SCACHESYNC_LOCK);
 
                 if (code) {
                     osi_Log1(afsd_logp,
@@ -5090,8 +5802,8 @@ void cm_CheckLocks()
                     tfid.Unique = scp->fid.unique;
                     cfid = scp->fid;
                     userp = fileLock->userp;
-                    
-                    osi_Log3(afsd_logp, "CALL ExtendLock lock 0x%p for scp=0x%p with lock %d", 
+
+                    osi_Log3(afsd_logp, "CALL ExtendLock lock 0x%p for scp=0x%p with lock %d",
                              fileLock,
                              scp,
                              (int) scp->serverLock);
@@ -5101,7 +5813,7 @@ void cm_CheckLocks()
                     do {
                         code = cm_ConnFromFID(&cfid, userp,
                                        &req, &connp);
-                        if (code) 
+                        if (code)
                             break;
 
                         rxconnp = cm_GetRxConn(connp);
@@ -5112,7 +5824,7 @@ void cm_CheckLocks()
                         osi_Log1(afsd_logp, "   ExtendLock returns %d", code);
 
                     } while (cm_Analyze(connp, userp, &req,
-                                        &cfid, &volSync, NULL, NULL,
+                                        &cfid, NULL, 1, NULL, &volSync, NULL, NULL,
                                         code));
 
                     code = cm_MapRPCError(code, &req);
@@ -5121,6 +5833,7 @@ void cm_CheckLocks()
 
                     if (code) {
                         osi_Log1(afsd_logp, "CALL ExtendLock FAILURE, code 0x%x", code);
+                        scp->fsLockCount = 0;
                     } else {
                         osi_Log0(afsd_logp, "CALL ExtendLock SUCCESS");
                         scp->lockDataVersion = scp->dataVersion;
@@ -5184,7 +5897,7 @@ void cm_CheckLocks()
                 if (code == 0) {
                     fileLock->lastUpdate = time(NULL);
                 }
-                
+
                 if (scp_done)
                     scp->lastRefreshCycle = cm_lockRefreshCycle;
 
@@ -5232,9 +5945,8 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
              (unsigned)(oldFileLock->range.offset & 0xffffffff),
              (unsigned)(oldFileLock->range.length >> 32),
              (unsigned)(oldFileLock->range.length & 0xffffffff));
-    osi_Log3(afsd_logp, "    key(%x:%x) flags=%x",
-             (unsigned)(oldFileLock->key >> 32),
-             (unsigned)(oldFileLock->key & 0xffffffff),
+    osi_Log4(afsd_logp, "    key<0x%x,0x%x,0x%x> flags=%x",
+             oldFileLock->key.process_id, oldFileLock->key.session_id, oldFileLock->key.file_id,
              (unsigned)(oldFileLock->flags));
 
     /* if the lock has already been granted, then we have nothing to do */
@@ -5306,7 +6018,7 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
                 ((char *) q - offsetof(cm_file_lock_t, fileq));
 
             if (IS_LOCK_LOST(fileLock)) {
-                if (fileLock->key == oldFileLock->key) {
+                if (cm_KeyEquals(&fileLock->key, &oldFileLock->key, 0)) {
                     code = CM_ERROR_BADFD;
                     oldFileLock->flags |= CM_FILELOCK_FLAG_LOST;
                     osi_Log1(afsd_logp, "    found lost lock %p for same key.  Marking lock as lost",
@@ -5393,9 +6105,7 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
         cm_user_t * userp;
 
         code = cm_SyncOp(scp, NULL, oldFileLock->userp, &req, 0,
-                         CM_SCACHESYNC_NEEDCALLBACK
-                        | CM_SCACHESYNC_GETSTATUS
-                        | CM_SCACHESYNC_LOCK);
+                        CM_SCACHESYNC_LOCK);
         if (code) {
             osi_Log1(afsd_logp, "cm_RetryLock SyncOp failure code 0x%x", code);
             lock_ReleaseWrite(&cm_scacheLock);
@@ -5483,8 +6193,6 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
   handleCode:
     if (code != 0 && code != CM_ERROR_WOULDBLOCK) {
        lock_ObtainWrite(&cm_scacheLock);
-        if (scp->fileLocksT == &oldFileLock->fileq)
-            scp->fileLocksT = osi_QPrev(&oldFileLock->fileq);
         osi_QRemoveHT(&scp->fileLocksH, &scp->fileLocksT, &oldFileLock->fileq);
        lock_ReleaseWrite(&cm_scacheLock);
     }
@@ -5508,27 +6216,21 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
     return code;
 }
 
-cm_key_t cm_GenerateKey(unsigned int session_id, unsigned long process_id, unsigned int file_id)
+cm_key_t cm_GenerateKey(afs_uint16 session_id, afs_offs_t process_id, afs_uint64 file_id)
 {
-#ifdef DEBUG
-    osi_assertx((process_id & 0xffffffff) == process_id, "unexpected process_id");
-    osi_assertx((session_id & 0xffff) == session_id, "unexpected session_id");
-    osi_assertx((file_id & 0xffff) == file_id, "unexpected file_id");
-#endif
+    cm_key_t key;
+
+    key.process_id = process_id;
+    key.session_id = session_id;
+    key.file_id = file_id;
 
-    return 
-        (((cm_key_t) (process_id & 0xffffffff)) << 32) |
-        (((cm_key_t) (session_id & 0xffff)) << 16) |
-        (((cm_key_t) (file_id & 0xffff)));
+    return key;
 }
 
-static int cm_KeyEquals(cm_key_t k1, cm_key_t k2, int flags)
+int cm_KeyEquals(cm_key_t *k1, cm_key_t *k2, int flags)
 {
-    if (flags & CM_UNLOCK_BY_FID) {
-        return ((k1 & 0xffffffff) == (k2 & 0xffffffff));
-    } else {
-        return (k1 == k2);
-    }
+    return (k1->session_id == k2->session_id) && (k1->file_id == k2->file_id) &&
+        ((flags & CM_UNLOCK_FLAG_BY_FID) || (k1->process_id == k2->process_id));
 }
 
 void cm_ReleaseAllLocks(void)