windows-buf-consistency-20080217
[openafs.git] / src / WINNT / afsd / cm_vnodeops.c
index c035c5c..3d6c771 100644 (file)
 #include <afs/param.h>
 #include <afs/stds.h>
 
-#ifndef DJGPP
 #include <windows.h>
 #include <winsock2.h>
-#endif /* !DJGPP */
 #include <stddef.h>
 #include <malloc.h>
 #include <string.h>
 #include <osi.h>
 
 #include "afsd.h"
-
-/* Used by cm_FollowMountPoint */
-#define RWVOL  0
-#define ROVOL  1
-#define BACKVOL        2
+#include "cm_btree.h"
 
 #ifdef DEBUG
 extern void afsi_log(char *pattern, ...);
@@ -35,6 +29,8 @@ extern void afsi_log(char *pattern, ...);
 
 int cm_enableServerLocks = 1;
 
+int cm_followBackupPath = 0;
+
 /*
  * Case-folding array.  This was constructed by inspecting of SMBtrace output.
  * I do not know anything more about it.
@@ -179,14 +175,16 @@ char cm_8Dot3Mapping[42] =
 };
 int cm_8Dot3MapSize = sizeof(cm_8Dot3Mapping);
 
-void cm_Gen8Dot3Name(cm_dirEntry_t *dep, char *shortName, char **shortNameEndp)
+void cm_Gen8Dot3NameInt(const char * longname, cm_dirFid_t * pfid,
+                        char *shortName, char **shortNameEndp)
 {
     char number[12];
     int i, nsize = 0;
-    int vnode = ntohl(dep->fid.vnode);
+    int vnode = ntohl(pfid->vnode);
     char *lastDot;
     int validExtension = 0;
-    char tc, *temp, *name;
+    char tc, *temp;
+    const char *name;
 
     /* Unparse the file's vnode number to get a "uniquifier" */
     do {
@@ -199,7 +197,7 @@ void cm_Gen8Dot3Name(cm_dirEntry_t *dep, char *shortName, char **shortNameEndp)
      * Look for valid extension.  There has to be a dot, and
      * at least one of the characters following has to be legal.
      */
-    lastDot = strrchr(dep->name, '.');
+    lastDot = strrchr(longname, '.');
     if (lastDot) {
         temp = lastDot; temp++;
         while (tc = *temp++)
@@ -207,11 +205,10 @@ void cm_Gen8Dot3Name(cm_dirEntry_t *dep, char *shortName, char **shortNameEndp)
                 break;
         if (tc)
             validExtension = 1;
-    }       
+    }
 
     /* Copy name characters */
-    name = dep->name;
-    for (i = 0, name = dep->name;
+    for (i = 0, name = longname;
           i < (7 - nsize) && name != lastDot; ) {
         tc = *name++;
 
@@ -258,14 +255,17 @@ long cm_CheckOpen(cm_scache_t *scp, int openMode, int trunc, cm_user_t *userp,
     long code;
 
     rights = 0;
-    if (openMode != 1) rights |= PRSFS_READ;
-    if (openMode == 1 || openMode == 2 || trunc) rights |= PRSFS_WRITE;
+    if (openMode != 1) 
+       rights |= PRSFS_READ;
+    if (openMode == 1 || openMode == 2 || trunc) 
+       rights |= PRSFS_WRITE;
         
     lock_ObtainMutex(&scp->mx);
 
     code = cm_SyncOp(scp, NULL, userp, reqp, rights,
                       CM_SCACHESYNC_GETSTATUS
-                      | CM_SCACHESYNC_NEEDCALLBACK);
+                     | CM_SCACHESYNC_NEEDCALLBACK
+                     | CM_SCACHESYNC_LOCK);
 
     if (code == 0 && 
         ((rights & PRSFS_WRITE) || (rights & PRSFS_READ)) &&
@@ -315,8 +315,15 @@ long cm_CheckOpen(cm_scache_t *scp, int openMode, int trunc, cm_user_t *userp,
                }
            }
         }
+
+    } else if (code != 0) {
+        goto _done;
     }
 
+    cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_LOCK);
+
+ _done:
+
     lock_ReleaseMutex(&scp->mx);
 
     return code;
@@ -324,11 +331,15 @@ 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)
+                    unsigned int createDisp, cm_user_t *userp, cm_req_t *reqp, 
+                   cm_lock_data_t **ldpp)
 {
     long rights;
     long code;
 
+    osi_assertx(ldpp != NULL, "null cm_lock_data_t");
+    *ldpp = NULL;
+
     /* Always allow delete; the RPC will tell us if it's OK */
     if (desiredAccess == DELETE)
         return 0;
@@ -336,17 +347,21 @@ long cm_CheckNTOpen(cm_scache_t *scp, unsigned int desiredAccess,
     rights = 0;
 
     if (desiredAccess & AFS_ACCESS_READ)
-        rights |= PRSFS_READ;
+        rights |= (scp->fileType == CM_SCACHETYPE_DIRECTORY ? PRSFS_LOOKUP : PRSFS_READ);
 
-    if ((desiredAccess & AFS_ACCESS_WRITE)
-         || createDisp == 4)
+    /* We used to require PRSFS_WRITE if createDisp was 4
+       (OPEN_ALWAYS) even if AFS_ACCESS_WRITE was not requested.
+       However, we don't need to do that since the existence of the
+       scp implies that we don't need to create it. */
+    if (desiredAccess & AFS_ACCESS_WRITE)
         rights |= PRSFS_WRITE;
 
     lock_ObtainMutex(&scp->mx);
 
     code = cm_SyncOp(scp, NULL, userp, reqp, rights,
                       CM_SCACHESYNC_GETSTATUS
-                      | CM_SCACHESYNC_NEEDCALLBACK);
+                     | CM_SCACHESYNC_NEEDCALLBACK
+                     | CM_SCACHESYNC_LOCK);
 
     /*
      * If the open will fail because the volume is readonly, then we will
@@ -356,7 +371,8 @@ long cm_CheckNTOpen(cm_scache_t *scp, unsigned int desiredAccess,
      */
     if (code == CM_ERROR_READONLY)
         code = CM_ERROR_NOACCESS;
-    else if (code == 0 &&
+
+    if (code == 0 &&
              ((rights & PRSFS_WRITE) || (rights & PRSFS_READ)) &&
              scp->fileType == CM_SCACHETYPE_FILE) {
         cm_key_t key;
@@ -381,7 +397,18 @@ 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) {
-            cm_Unlock(scp, sLockType, LOffset, LLength, key, userp, reqp);
+           (*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;
         } else {
             /* In this case, we allow the file open to go through even
                though we can't enforce mandatory locking on the
@@ -403,13 +430,31 @@ long cm_CheckNTOpen(cm_scache_t *scp, unsigned int desiredAccess,
                }
            }
         }
+    } else if (code != 0) {
+        goto _done;
     }
 
+    cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_LOCK);
+
+ _done:
     lock_ReleaseMutex(&scp->mx);
 
     return code;
 }
 
+extern long cm_CheckNTOpenDone(cm_scache_t *scp, cm_user_t *userp, cm_req_t *reqp, 
+                              cm_lock_data_t ** ldpp)
+{
+    if (*ldpp) {
+       lock_ObtainMutex(&scp->mx);
+       cm_Unlock(scp, (*ldpp)->sLockType, (*ldpp)->LOffset, (*ldpp)->LLength, 
+                 (*ldpp)->key, userp, reqp);
+       lock_ReleaseMutex(&scp->mx);
+       free(*ldpp);
+       *ldpp = NULL;
+    }
+    return 0;
+}
 /*
  * When CAP_NT_SMBS has been negotiated, deletion (of files or directories) is
  * done in three steps:
@@ -429,17 +474,17 @@ long cm_CheckNTDelete(cm_scache_t *dscp, cm_scache_t *scp, cm_user_t *userp,
     long code;
     osi_hyper_t thyper;
     cm_buf_t *bufferp;
-    cm_dirEntry_t *dep;
+    cm_dirEntry_t *dep = 0;
     unsigned short *hashTable;
     unsigned int i, idx;
     int BeyondPage = 0, HaveDot = 0, HaveDotDot = 0;
 
     /* First check permissions */
-    lock_ObtainMutex(&dscp->mx);
-    code = cm_SyncOp(dscp, NULL, userp, reqp, PRSFS_DELETE,
-                      CM_SCACHESYNC_GETSTATUS
-                      | CM_SCACHESYNC_NEEDCALLBACK);
-    lock_ReleaseMutex(&dscp->mx);
+    lock_ObtainMutex(&scp->mx);
+    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);
+    lock_ReleaseMutex(&scp->mx);
     if (code)
         return code;
 
@@ -474,6 +519,7 @@ long cm_CheckNTDelete(cm_scache_t *dscp, cm_scache_t *scp, cm_user_t *userp,
         lock_ReleaseMutex(&scp->mx);
         lock_ObtainMutex(&bufferp->mx);
         lock_ObtainMutex(&scp->mx);
+       cm_SyncOpDone(scp, bufferp, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_READ | CM_SCACHESYNC_BUFLOCKED);
         if (code)
             break;
     }
@@ -527,7 +573,7 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
 {
     char *tp;
     long code;
-    cm_dirEntry_t *dep;
+    cm_dirEntry_t *dep = 0;
     cm_buf_t *bufferp;
     long temp;
     osi_hyper_t dirLength;
@@ -545,39 +591,86 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
     lock_ObtainMutex(&scp->mx);
     code = cm_SyncOp(scp, NULL, userp, reqp, PRSFS_LOOKUP,
                       CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
-    if (code) {
-        lock_ReleaseMutex(&scp->mx);
+    lock_ReleaseMutex(&scp->mx);
+    if (code)
         return code;
-    }
         
-    if (scp->fileType != CM_SCACHETYPE_DIRECTORY) {
-        lock_ReleaseMutex(&scp->mx);
+    if (scp->fileType != CM_SCACHETYPE_DIRECTORY)
         return CM_ERROR_NOTDIR;
-    }   
 
     if (retscp)                        /* if this is a lookup call */
     {
         cm_lookupSearch_t*     sp = parmp;
 
+        if (
 #ifdef AFS_FREELANCE_CLIENT
        /* Freelance entries never end up in the DNLC because they
         * do not have an associated cm_server_t
         */
-    if ( !(cm_freelanceEnabled &&
+            !(cm_freelanceEnabled &&
             sp->fid.cell==AFS_FAKE_ROOT_CELL_ID &&
-            sp->fid.volume==AFS_FAKE_ROOT_VOL_ID ) )
-#endif /* AFS_FREELANCE_CLIENT */
-    {
-        int casefold = sp->caseFold;
-        sp->caseFold = 0; /* we have a strong preference for exact matches */
-        if ( *retscp = cm_dnlcLookup(scp, sp)) /* dnlc hit */
+              sp->fid.volume==AFS_FAKE_ROOT_VOL_ID )
+#else /* !AFS_FREELANCE_CLIENT */
+            TRUE
+#endif
+            ) 
         {
+            int casefold = sp->caseFold;
+            sp->caseFold = 0; /* we have a strong preference for exact matches */
+            if ( *retscp = cm_dnlcLookup(scp, sp))     /* dnlc hit */
+            {
+                sp->caseFold = casefold;
+                return 0;
+            }
             sp->caseFold = casefold;
-            lock_ReleaseMutex(&scp->mx);
-            return 0;
+
+            /* 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;
+#ifdef USE_BPLUS
+                int usedBplus = 0;
+#endif
+
+                code = ENOENT;
+
+                code = cm_BeginDirOp(scp, userp, reqp, CM_DIRLOCK_READ, &dirop);
+                if (code == 0) {
+
+#ifdef USE_BPLUS
+                    code = cm_BPlusDirLookup(&dirop, sp->searchNamep, &sp->fid);
+                    if (code != EINVAL)
+                        usedBplus = 1;
+                    else 
+#endif
+                        code = cm_DirLookup(&dirop, sp->searchNamep, &sp->fid);
+
+                    cm_EndDirOp(&dirop);
+                }
+
+                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 = FALSE;
+                        *retscp = NULL; /* force caller to call cm_GetSCache() */
+                        return 0;
+                    }
+                    
+                    return CM_ERROR_BPLUS_NOMATCH;
+                }
+#endif 
+            }
         }
-        sp->caseFold = casefold;
-    }
     }  
 
     /*
@@ -586,8 +679,6 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
      */
     dirLength = scp->length;
 
-    lock_ReleaseMutex(&scp->mx);
-
     bufferp = NULL;
     bufferOffset.LowPart = bufferOffset.HighPart = 0;
     if (startOffsetp)
@@ -649,16 +740,16 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
             }
 
 #ifdef AFSIFS
-                       /* for the IFS version, we bulkstat the dirents because this
-                          routine is used in place of smb_ReceiveCoreSearchDir.  our
-                          other option is to modify smb_ReceiveCoreSearchDir itself, 
-                          but this seems to be the proper use for cm_ApplyDir. */
+           /* for the IFS version, we bulkstat the dirents because this
+              routine is used in place of smb_ReceiveCoreSearchDir.  our
+              other option is to modify smb_ReceiveCoreSearchDir itself, 
+              but this seems to be the proper use for cm_ApplyDir. */
             lock_ObtainMutex(&scp->mx);
             if ((scp->flags & CM_SCACHEFLAG_BULKSTATTING) == 0
                  && (scp->bulkStatProgress.QuadPart <= thyper.QuadPart))
             {
                 scp->flags |= CM_SCACHEFLAG_BULKSTATTING;
-                cm_TryBulkStat(scp, &thyper, userp, reqp);
+                code = cm_TryBulkStat(scp, &thyper, userp, reqp);
                 scp->flags &= ~CM_SCACHEFLAG_BULKSTATTING;
                 scp->bulkStatProgress = thyper;
             }
@@ -680,6 +771,7 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
                     lock_ReleaseMutex(&scp->mx);
                     break;
                 }
+               cm_SyncOpDone(scp, bufferp, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_READ | CM_SCACHESYNC_BUFLOCKED);
                                 
                 if (cm_HaveBuffer(scp, bufferp, 1)) {
                     lock_ReleaseMutex(&scp->mx);
@@ -815,7 +907,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 || matchName == shortName) {
@@ -889,27 +981,27 @@ long cm_ReadMountPoint(cm_scache_t *scp, cm_user_t *userp, cm_req_t *reqp)
     lock_ReleaseRead(&scp->bufCreateLock);
 
     lock_ObtainMutex(&scp->mx);
-    if (code) {
+    if (code)
         return code;
-    }
+
     while (1) {
         code = cm_SyncOp(scp, bufp, userp, reqp, 0,
                           CM_SCACHESYNC_READ | CM_SCACHESYNC_NEEDCALLBACK);
-        if (code) {
+        if (code)
             goto done;
-        }
+
+       cm_SyncOpDone(scp, bufp, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_READ);
 
         if (cm_HaveBuffer(scp, bufp, 0)) 
             break;
 
         /* otherwise load buffer */
         code = cm_GetBuffer(scp, bufp, NULL, userp, reqp);
-        if (code) {
+        if (code)
             goto done;
-        }
     }
     /* locked, has callback, has valid data in buffer */
-    if ((tlen = scp->length.LowPart) > 1000) 
+    if ((tlen = scp->length.LowPart) > MOUNTPOINTLEN - 1) 
         return CM_ERROR_TOOBIG;
     if (tlen <= 0) {
         code = CM_ERROR_INVAL;
@@ -940,6 +1032,7 @@ long cm_ReadMountPoint(cm_scache_t *scp, cm_user_t *userp, cm_req_t *reqp)
     return code;
 }
 
+
 /* called with a locked scp and chases the mount point, yielding outScpp.
  * scp remains locked, just for simplicity of describing the interface.
  */
@@ -952,12 +1045,12 @@ long cm_FollowMountPoint(cm_scache_t *scp, cm_scache_t *dscp, cm_user_t *userp,
     long code;
     char *cp;
     char *mpNamep;
-    cm_volume_t *volp;
+    cm_volume_t *volp = NULL;
     cm_cell_t *cellp;
     char mtType;
     cm_fid_t tfid;
     size_t vnLength;
-    int type;
+    int targetType;
 
     if (scp->mountRootFid.cell != 0 && scp->mountRootGen >= cm_data.mountRootGen) {
         tfid = scp->mountRootFid;
@@ -969,7 +1062,8 @@ long cm_FollowMountPoint(cm_scache_t *scp, cm_scache_t *dscp, cm_user_t *userp,
 
     /* parse the volume name */
     mpNamep = scp->mountPointStringp;
-    osi_assert(mpNamep[0]);
+    if (!mpNamep[0])
+       return CM_ERROR_NOSUCHPATH;
     tlen = (int)strlen(scp->mountPointStringp);
     mtType = *scp->mountPointStringp;
     cellNamep = malloc(tlen);
@@ -982,13 +1076,15 @@ long cm_FollowMountPoint(cm_scache_t *scp, cm_scache_t *dscp, cm_user_t *userp,
         strncpy(cellNamep, mpNamep+1, cp - mpNamep - 1);
         strcpy(volNamep, cp+1);
         /* now look up the cell */
+        lock_ReleaseMutex(&scp->mx);
         cellp = cm_GetCell(cellNamep, CM_FLAG_CREATE);
+        lock_ObtainMutex(&scp->mx);
     }
     else {
         /* normal mt pt */
         strcpy(volNamep, mpNamep+1);
 
-        cellp = cm_FindCellByID(scp->fid.cell);
+        cellp = cm_FindCellByID(scp->fid.cell, 0);
     }
 
     if (!cellp) {
@@ -998,15 +1094,15 @@ long cm_FollowMountPoint(cm_scache_t *scp, cm_scache_t *dscp, cm_user_t *userp,
 
     vnLength = strlen(volNamep);
     if (vnLength >= 8 && strcmp(volNamep + vnLength - 7, ".backup") == 0)
-        type = BACKVOL;
+        targetType = BACKVOL;
     else if (vnLength >= 10
               && strcmp(volNamep + vnLength - 9, ".readonly") == 0)
-        type = ROVOL;
+        targetType = ROVOL;
     else
-        type = RWVOL;
+        targetType = RWVOL;
 
     /* check for backups within backups */
-    if (type == BACKVOL
+    if (targetType == BACKVOL
          && (scp->flags & (CM_SCACHEFLAG_RO | CM_SCACHEFLAG_PURERO))
          == CM_SCACHEFLAG_RO) {
         code = CM_ERROR_NOSUCHVOLUME;
@@ -1015,7 +1111,13 @@ long cm_FollowMountPoint(cm_scache_t *scp, cm_scache_t *dscp, cm_user_t *userp,
 
     /* now we need to get the volume */
     lock_ReleaseMutex(&scp->mx);
-    code = cm_GetVolumeByName(cellp, volNamep, userp, reqp, 0, &volp);
+    if (cm_VolNameIsID(volNamep)) {
+        code = cm_GetVolumeByID(cellp, atoi(volNamep), userp, reqp, 
+                                CM_GETVOL_FLAG_CREATE, &volp);
+    } else {
+        code = cm_GetVolumeByName(cellp, volNamep, userp, reqp, 
+                                  CM_GETVOL_FLAG_CREATE, &volp);
+    }
     lock_ObtainMutex(&scp->mx);
         
     if (code == 0) {
@@ -1030,20 +1132,35 @@ long cm_FollowMountPoint(cm_scache_t *scp, cm_scache_t *dscp, cm_user_t *userp,
         lock_ReleaseMutex(&volp->mx);
 
         scp->mountRootFid.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 && 
+            volp->bk.ID != 0 &&
+            (dscp->flags & (CM_SCACHEFLAG_RO|CM_SCACHEFLAG_PURERO)) == CM_SCACHEFLAG_RO &&
+            (targetType == RWVOL || targetType == ROVOL && volp->ro.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 type '#' mount point, use
+         * target, and if this is a targetType '#' mount point, use
          * the read-only, otherwise use the one specified.
          */
-        if (mtType == '#' && (scp->flags & CM_SCACHEFLAG_PURERO)
-             && volp->roID != 0 && type == RWVOL)
-            type = ROVOL;
-        if (type == ROVOL)
-            scp->mountRootFid.volume = volp->roID;
-        else if (type == BACKVOL)
-            scp->mountRootFid.volume = volp->bkID;
+        else if (mtType == '#' && targetType == RWVOL && 
+                 (scp->flags & CM_SCACHEFLAG_PURERO) && 
+                 volp->ro.ID != 0) {
+            targetType = ROVOL;
+        }
+        if (targetType == ROVOL)
+            scp->mountRootFid.volume = volp->ro.ID;
+        else if (targetType == BACKVOL)
+            scp->mountRootFid.volume = volp->bk.ID;
         else
-            scp->mountRootFid.volume = volp->rwID;
+            scp->mountRootFid.volume = volp->rw.ID;
 
         /* the rest of the fid is a magic number */
         scp->mountRootFid.vnode = 1;
@@ -1057,6 +1174,8 @@ long cm_FollowMountPoint(cm_scache_t *scp, cm_scache_t *dscp, cm_user_t *userp,
     }
 
   done:
+    if (volp)
+       cm_PutVolume(volp);
     free(cellNamep);
     free(volNamep);
     return code;
@@ -1072,15 +1191,61 @@ long cm_LookupInternal(cm_scache_t *dscp, char *namep, long flags, cm_user_t *us
     cm_lookupSearch_t rock;
     int getroot;
 
+    memset(&rock, 0, sizeof(rock));
+
     if (dscp->fid.vnode == 1 && dscp->fid.unique == 1
          && strcmp(namep, "..") == 0) {
         if (dscp->dotdotFid.volume == 0)
             return CM_ERROR_NOSUCHVOLUME;
         rock.fid = dscp->dotdotFid;
         goto haveFid;
+    } else if (strcmp(namep, ".") == 0) {
+       rock.fid = dscp->fid;
+       goto haveFid;
+    }
+
+    if (flags & CM_FLAG_NOMOUNTCHASE) {
+        /* In this case, we should go and call cm_Dir* functions
+           directly since the following cm_ApplyDir() function will
+           not. */
+
+        cm_dirOp_t dirop;
+#ifdef USE_BPLUS
+        int usedBplus = 0;
+#endif
+
+        code = cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_READ, &dirop);
+        if (code == 0) {
+#ifdef USE_BPLUS
+            code = cm_BPlusDirLookup(&dirop, namep, &rock.fid);
+            if (code != EINVAL)
+                usedBplus = 1;
+            else
+#endif
+                code = cm_DirLookup(&dirop, namep, &rock.fid);
+
+            cm_EndDirOp(&dirop);
+        }
+
+        if (code == 0) {
+            /* found it */
+            rock.found = TRUE;
+            goto haveFid;
+        }
+#ifdef USE_BPLUS
+        if (usedBplus) {
+            if (code == CM_ERROR_INEXACT_MATCH && (flags & CM_FLAG_CASEFOLD)) {
+                /* found it */
+                code = 0;
+                rock.found = TRUE;
+                goto haveFid;
+            }
+            
+            return CM_ERROR_BPLUS_NOMATCH;
+        }
+#endif
     }
 
-    memset(&rock, 0, sizeof(rock));
     rock.fid.cell = dscp->fid.cell;
     rock.fid.volume = dscp->fid.volume;
     rock.searchNamep = namep;
@@ -1117,27 +1282,43 @@ long cm_LookupInternal(cm_scache_t *dscp, char *namep, long flags, cm_user_t *us
             else
                 return CM_ERROR_NOSUCHFILE;
         }
-        else {  /* nonexistent dir on freelance root, so add it */
+        else if (!strchr(namep, '#') && !strchr(namep, '%') &&
+                 strcmp(namep, "srvsvc") && strcmp(namep, "wkssvc") &&
+                 strcmp(namep, "ipc$")) {
+            /* nonexistent dir on freelance root, so add it */
             char fullname[200] = ".";
             int  found = 0;
 
             osi_Log1(afsd_logp,"cm_Lookup adding mount for non-existent directory: %s", 
                       osi_LogSaveString(afsd_logp,namep));
+
+            /* 
+             * 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 
+             * new value automatically.
+             */
+
+            code = -1;
             if (namep[0] == '.') {
                 if (cm_GetCell_Gen(&namep[1], &fullname[1], CM_FLAG_CREATE)) {
                     found = 1;
-                    if ( stricmp(&namep[1], &fullname[1]) )
+                    if (!cm_FreelanceMountPointExists(fullname, 0))
+                        code = cm_FreelanceAddMount(fullname, &fullname[1], "root.cell.", 1, &rock.fid);
+                    if ( stricmp(&namep[1], &fullname[1]) && 
+                                               !cm_FreelanceMountPointExists(namep, flags & CM_FLAG_DFS_REFERRAL ? 1 : 0) &&
+                                               !cm_FreelanceSymlinkExists(namep, flags & CM_FLAG_DFS_REFERRAL ? 1 : 0))
                         code = cm_FreelanceAddSymlink(namep, fullname, &rock.fid);
-                    else
-                        code = cm_FreelanceAddMount(namep, &fullname[1], "root.cell.", 1, &rock.fid);
                 }
             } else {
                 if (cm_GetCell_Gen(namep, fullname, CM_FLAG_CREATE)) {
                     found = 1;
-                    if ( stricmp(namep, fullname) )
+                    if (!cm_FreelanceMountPointExists(fullname, 0))
+                        code = cm_FreelanceAddMount(fullname, fullname, "root.cell.", 0, &rock.fid);
+                    if ( stricmp(namep, fullname) && 
+                                               !cm_FreelanceMountPointExists(namep, flags & CM_FLAG_DFS_REFERRAL ? 1 : 0) &&
+                                               !cm_FreelanceSymlinkExists(namep, flags & CM_FLAG_DFS_REFERRAL ? 1 : 0))
                         code = cm_FreelanceAddSymlink(namep, fullname, &rock.fid);
-                    else
-                        code = cm_FreelanceAddMount(namep, fullname, "root.cell.", 0, &rock.fid);
                 }
             }
             if (!found || code < 0) {   /* add mount point failed, so give up */
@@ -1168,6 +1349,7 @@ long cm_LookupInternal(cm_scache_t *dscp, char *namep, long flags, cm_user_t *us
         cm_ReleaseSCache(tscp);
         return code;
     }
+    cm_SyncOpDone(tscp, NULL, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
     /* tscp is now locked */
 
     if (!(flags & CM_FLAG_NOMOUNTCHASE)
@@ -1197,7 +1379,7 @@ long cm_LookupInternal(cm_scache_t *dscp, char *namep, long flags, cm_user_t *us
     if ( !dnlcHit && !(flags & CM_FLAG_NOMOUNTCHASE) && rock.ExactFound ) {
         /* lock the directory entry to prevent racing callback revokes */
         lock_ObtainMutex(&dscp->mx);
-        if ( dscp->cbServerp && dscp->cbExpires )
+        if ( dscp->cbServerp != NULL && dscp->cbExpires > 0 )
             cm_dnlcEnter(dscp, namep, tscp);
         lock_ReleaseMutex(&dscp->mx);
     }
@@ -1234,13 +1416,131 @@ int cm_ExpandSysName(char *inp, char *outp, long outSize, unsigned int index)
     return 1;
 }   
 
+long cm_EvaluateVolumeReference(char * namep, long flags, cm_user_t * userp,
+                                cm_req_t *reqp, cm_scache_t ** outpScpp)
+{
+    long          code = 0;
+    char          cellName[CELL_MAXNAMELEN];
+    char          volumeName[VL_MAXNAMELEN];
+    size_t        len;
+    char *        cp;
+    char *        tp;
+
+    cm_cell_t *   cellp = NULL;
+    cm_volume_t * volp = NULL;
+    cm_fid_t      fid;
+    int           volType;
+    int           mountType = RWVOL;
+
+    osi_Log1(afsd_logp, "cm_EvaluateVolumeReference for string [%s]",
+             osi_LogSaveString(afsd_logp, namep));
+
+    if (strnicmp(namep, CM_PREFIX_VOL, CM_PREFIX_VOL_CCH) != 0) {
+        goto _exit_invalid_path;
+    }
+
+    /* namep is assumed to look like the following:
+
+       @vol:<cellname>%<volume>\0
+       or
+       @vol:<cellname>#<volume>\0
+
+     */
+
+    cp = namep + CM_PREFIX_VOL_CCH; /* cp points to cell name, hopefully */
+    tp = strchr(cp, '%');
+    if (tp == NULL)
+        tp = strchr(cp, '#');
+    if (tp == NULL ||
+        (len = tp - cp) == 0 ||
+        len > CELL_MAXNAMELEN)
+        goto _exit_invalid_path;
+    strncpy(cellName, cp, len);
+    cellName[len] = '\0';
+
+    if (*tp == '#')
+        mountType = ROVOL;
+
+    cp = tp+1;                  /* cp now points to volume, supposedly */
+    strncpy(volumeName, cp, VL_MAXNAMELEN-1);
+    volumeName[VL_MAXNAMELEN - 1] = 0;
+
+    /* OK, now we have the cell and the volume */
+    osi_Log2(afsd_logp, "   Found cell [%s] and volume [%s]",
+             osi_LogSaveString(afsd_logp, cellName),
+             osi_LogSaveString(afsd_logp, volumeName));
+
+    cellp = cm_GetCell(cellName, CM_FLAG_CREATE);
+    if (cellp == NULL) {
+        goto _exit_invalid_path;
+    }
+
+    len = strlen(volumeName);
+    if (len >= 8 && strcmp(volumeName + len - 7, ".backup") == 0)
+        volType = BACKVOL;
+    else if (len >= 10 &&
+             strcmp(volumeName + len - 9, ".readonly") == 0)
+        volType = ROVOL;
+    else
+        volType = RWVOL;
+
+    if (cm_VolNameIsID(volumeName)) {
+        code = cm_GetVolumeByID(cellp, atoi(volumeName), userp, reqp,
+                                CM_GETVOL_FLAG_CREATE, &volp);
+    } else {
+        code = cm_GetVolumeByName(cellp, volumeName, userp, reqp,
+                                  CM_GETVOL_FLAG_CREATE, &volp);
+    }
+
+    if (code != 0)
+        goto _exit_cleanup;
+
+    fid.cell = cellp->cellID;
+
+    if (volType == BACKVOL)
+        fid.volume = volp->bk.ID;
+    else if (volType == ROVOL ||
+             (volType == RWVOL && mountType == ROVOL && volp->ro.ID != 0))
+        fid.volume = volp->ro.ID;
+    else
+        fid.volume = volp->rw.ID;
+
+    fid.vnode = 1;
+    fid.unique = 1;
+
+    code = cm_GetSCache(&fid, outpScpp, userp, reqp);
+
+ _exit_cleanup:
+    if (volp)
+        cm_PutVolume(volp);
+
+    if (code == 0)
+        return code;
+
+ _exit_invalid_path:
+    if (flags & CM_FLAG_CHECKPATH)
+        return CM_ERROR_NOSUCHPATH;
+    else
+        return CM_ERROR_NOSUCHFILE;
+}
+
+#ifdef DEBUG_REFCOUNT
+long cm_LookupDbg(cm_scache_t *dscp, char *namep, long flags, cm_user_t *userp,
+               cm_req_t *reqp, cm_scache_t **outpScpp, char * file, long line)
+#else
 long cm_Lookup(cm_scache_t *dscp, char *namep, long flags, cm_user_t *userp,
                cm_req_t *reqp, cm_scache_t **outpScpp)
+#endif
 {
     long code;
-    char tname[256];
+    char tname[AFSPATHMAX];
     int sysNameIndex = 0;
-    cm_scache_t *scp = 0;
+    cm_scache_t *scp = NULL;
+
+#ifdef DEBUG_REFCOUNT
+    afsi_log("%s:%d cm_Lookup dscp 0x%p ref %d", file, line, dscp, dscp->refCount, file, line);
+    osi_Log2(afsd_logp, "cm_Lookup dscp 0x%p ref %d", dscp, dscp->refCount);
+#endif
 
     if ( stricmp(namep,SMB_IOCTL_FILENAME_NOSLASH) == 0 ) {
         if (flags & CM_FLAG_CHECKPATH)
@@ -1249,21 +1549,47 @@ long cm_Lookup(cm_scache_t *dscp, char *namep, long flags, cm_user_t *userp,
             return CM_ERROR_NOSUCHFILE;
     }
 
-    for ( sysNameIndex = 0; sysNameIndex < MAXNUMSYSNAMES; sysNameIndex++) {
-        code = cm_ExpandSysName(namep, tname, sizeof(tname), sysNameIndex);
-        if (code > 0) {
-            code = cm_LookupInternal(dscp, tname, flags, userp, reqp, &scp);
-            if (code == 0) {
+    if (dscp == cm_data.rootSCachep &&
+        strnicmp(namep, CM_PREFIX_VOL, CM_PREFIX_VOL_CCH) == 0) {
+        return cm_EvaluateVolumeReference(namep, flags, userp, reqp, outpScpp);
+    }
+
+    if (cm_ExpandSysName(namep, NULL, 0, 0) > 0) {
+        for ( sysNameIndex = 0; sysNameIndex < MAXNUMSYSNAMES; sysNameIndex++) {
+            code = cm_ExpandSysName(namep, tname, sizeof(tname), sysNameIndex);
+            if (code > 0) {
+                code = cm_LookupInternal(dscp, tname, flags, userp, reqp, &scp);
+#ifdef DEBUG_REFCOUNT
+                afsi_log("%s:%d cm_LookupInternal (1) 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 (1) code 0x%x dscp 0x%p scp 0x%p", code, dscp, scp);
+#endif
+
+                if (code == 0) {
+                    *outpScpp = scp;
+                    return 0;
+                }
+                if (scp) {
+                    cm_ReleaseSCache(scp);
+                    scp = NULL;
+                }
+            } else {
+                code = cm_LookupInternal(dscp, namep, flags, userp, reqp, &scp);
+#ifdef DEBUG_REFCOUNT
+                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;
-                return 0;
-            }
-            if (scp) {
-                cm_ReleaseSCache(scp);
-                scp = 0;
+                return code;
             }
-        } else {
-            return cm_LookupInternal(dscp, namep, flags, userp, reqp, outpScpp);
         }
+    } else {
+        code = cm_LookupInternal(dscp, namep, flags, userp, reqp, &scp);
+#ifdef DEBUG_REFCOUNT
+        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;
+        return code;
     }
 
     /* None of the possible sysName expansions could be found */
@@ -1282,6 +1608,7 @@ long cm_Unlink(cm_scache_t *dscp, char *namep, cm_user_t *userp, cm_req_t *reqp)
     AFSFetchStatus newDirStatus;
     AFSVolSync volSync;
     struct rx_connection * callp;
+    cm_dirOp_t dirop;
 
 #ifdef AFS_FREELANCE_CLIENT
     if (cm_freelanceEnabled && dscp == cm_data.rootSCachep) {
@@ -1292,12 +1619,16 @@ long cm_Unlink(cm_scache_t *dscp, char *namep, cm_user_t *userp, cm_req_t *reqp)
 #endif  
 
     /* make sure we don't screw up the dir status during the merge */
+    code = cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_NONE, &dirop);
+
     lock_ObtainMutex(&dscp->mx);
     sflags = CM_SCACHESYNC_STOREDATA;
     code = cm_SyncOp(dscp, NULL, userp, reqp, 0, sflags);
     lock_ReleaseMutex(&dscp->mx);
-    if (code) 
+    if (code) {
+        cm_EndDirOp(&dirop);
         return code;
+    }
 
     /* make the RPC */
     afsFid.Volume = dscp->fid.volume;
@@ -1306,7 +1637,7 @@ long cm_Unlink(cm_scache_t *dscp, char *namep, cm_user_t *userp, cm_req_t *reqp)
 
     osi_Log1(afsd_logp, "CALL RemoveFile scp 0x%p", dscp);
     do {
-        code = cm_Conn(&dscp->fid, userp, reqp, &connp);
+        code = cm_ConnFromFID(&dscp->fid, userp, reqp, &connp);
         if (code) 
             continue;
 
@@ -1323,13 +1654,32 @@ long cm_Unlink(cm_scache_t *dscp, char *namep, cm_user_t *userp, cm_req_t *reqp)
     else
         osi_Log0(afsd_logp, "CALL RemoveFile SUCCESS");
 
+    if (dirop.scp) {
+        lock_ObtainWrite(&dirop.scp->dirlock);
+        dirop.lockType = CM_DIRLOCK_WRITE;
+    }
     lock_ObtainMutex(&dscp->mx);
     cm_dnlcRemove(dscp, namep);
     cm_SyncOpDone(dscp, NULL, sflags);
-    if (code == 0) 
-        cm_MergeStatus(dscp, &newDirStatus, &volSync, userp, 0);
+    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_ReleaseMutex(&dscp->mx);
 
+    if (code == 0 && cm_CheckDirOpForSingleChange(&dirop)) {
+        cm_DirDeleteEntry(&dirop, namep);
+#ifdef USE_BPLUS
+        cm_BPlusDirDeleteEntry(&dirop, namep);
+#endif
+    }
+    cm_EndDirOp(&dirop);
+
     return code;
 }
 
@@ -1348,7 +1698,9 @@ long cm_HandleLink(cm_scache_t *linkScp, cm_user_t *userp, cm_req_t *reqp)
         /* read the link data */
         lock_ReleaseMutex(&linkScp->mx);
         thyper.LowPart = thyper.HighPart = 0;
+        lock_ObtainRead(&linkScp->bufCreateLock);
         code = buf_Get(linkScp, &thyper, &bufp);
+        lock_ReleaseRead(&linkScp->bufCreateLock);
         lock_ObtainMutex(&linkScp->mx);
         if (code) 
             return code;
@@ -1359,6 +1711,8 @@ long cm_HandleLink(cm_scache_t *linkScp, cm_user_t *userp, cm_req_t *reqp)
                 buf_Release(bufp);
                 return code;
             }
+           cm_SyncOpDone(linkScp, bufp, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_READ);
+
             if (cm_HaveBuffer(linkScp, bufp, 0)) 
                 break;
 
@@ -1383,6 +1737,9 @@ long cm_HandleLink(cm_scache_t *linkScp, cm_user_t *userp, cm_req_t *reqp)
         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;
         }
         buf_Release(bufp);
     }  /* don't have sym link contents cached */
@@ -1405,9 +1762,12 @@ long cm_AssembleLink(cm_scache_t *linkScp, char *pathSuffixp,
     char *linkp;
     cm_space_t *tsp;
 
+    *newRootScpp = NULL;
+    *newSpaceBufferp = NULL;
+
     lock_ObtainMutex(&linkScp->mx);
     code = cm_HandleLink(linkScp, userp, reqp);
-    if (code) 
+    if (code)
         goto done;
 
     /* if we may overflow the buffer, bail out; buffer is signficantly
@@ -1444,13 +1804,12 @@ long cm_AssembleLink(cm_scache_t *linkScp, char *pathSuffixp,
         } else {
             linkScp->fileType = CM_SCACHETYPE_DFSLINK;
             strcpy(tsp->data, linkp);
-            *newRootScpp = NULL;
             code = CM_ERROR_PATH_NOT_COVERED;
         }
-    } else if ( !strnicmp(linkp, "msdfs:", (len = (long)strlen("msdfs:"))) ) {
+    } else if ( linkScp->fileType == CM_SCACHETYPE_DFSLINK ||
+                !strnicmp(linkp, "msdfs:", (len = (long)strlen("msdfs:"))) ) {
         linkScp->fileType = CM_SCACHETYPE_DFSLINK;
         strcpy(tsp->data, linkp);
-        *newRootScpp = NULL;
         code = CM_ERROR_PATH_NOT_COVERED;
     } else if (*linkp == '\\' || *linkp == '/') {
 #if 0   
@@ -1466,33 +1825,43 @@ long cm_AssembleLink(cm_scache_t *linkScp, char *pathSuffixp,
          */
         linkScp->fileType = CM_SCACHETYPE_INVALID;
         strcpy(tsp->data, linkp);
-        *newRootScpp = NULL;
         code = CM_ERROR_NOSUCHPATH;
 #endif  
     } else {
         /* a relative link */
         strcpy(tsp->data, linkp);
-        *newRootScpp = NULL;
     }
     if (pathSuffixp[0] != 0) { /* if suffix string is non-null */
         strcat(tsp->data, "\\");
         strcat(tsp->data, pathSuffixp);
     }
-    *newSpaceBufferp = tsp;
+    if (code == 0)
+        *newSpaceBufferp = tsp;
+    else {
+        cm_FreeSpace(tsp);
+
+        if (code == CM_ERROR_PATH_NOT_COVERED && reqp->tidPathp && reqp->relPathp)
+            cm_VolStatus_Notify_DFS_Mapping(linkScp, reqp->tidPathp, reqp->relPathp);
+    }
 
   done:
     lock_ReleaseMutex(&linkScp->mx);
     return code;
 }
-
+#ifdef DEBUG_REFCOUNT
+long cm_NameIDbg(cm_scache_t *rootSCachep, char *pathp, long flags,
+               cm_user_t *userp, char *tidPathp, cm_req_t *reqp, cm_scache_t **outScpp, 
+              char * file, long line)
+#else
 long cm_NameI(cm_scache_t *rootSCachep, char *pathp, long flags,
                cm_user_t *userp, char *tidPathp, cm_req_t *reqp, cm_scache_t **outScpp)
+#endif
 {
     long code;
     char *tp;                  /* ptr moving through input buffer */
     char tc;                   /* temp char */
     int haveComponent;         /* has new component started? */
-    char component[256];       /* this is the new component */
+    char component[AFSPATHMAX];        /* this is the new component */
     char *cp;                  /* component name being assembled */
     cm_scache_t *tscp;         /* current location in the hierarchy */
     cm_scache_t *nscp;         /* next dude down */
@@ -1506,6 +1875,17 @@ long cm_NameI(cm_scache_t *rootSCachep, char *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;
+
+#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>", 
+             flags);
+#endif
 
     tp = tidPathp;
     if (tp == NULL) {
@@ -1520,7 +1900,8 @@ long cm_NameI(cm_scache_t *rootSCachep, char *pathp, long flags,
     tscp = rootSCachep;
     cm_HoldSCache(tscp);
     symlinkCount = 0;
-    dirScp = 0;
+    dirScp = NULL;
+
 
     while (1) {
         tc = *tp++;
@@ -1555,34 +1936,70 @@ long cm_NameI(cm_scache_t *rootSCachep, char *pathp, long flags,
                  * is a symlink, we have more to do.
                  */
                 *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 (code) {
-                    cm_ReleaseSCache(tscp);
-                    if (dirScp)
-                        cm_ReleaseSCache(dirScp);
-                    if (psp) 
-                        cm_FreeSpace(psp);
-                    if (code == CM_ERROR_NOSUCHFILE && tscp->fileType == CM_SCACHETYPE_SYMLINK)
-                        return CM_ERROR_NOSUCHPATH;
-                    else
-                        return code;
+               extraFlag = 0;
+               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 (!strcmp(component,"..") || !strcmp(component,".")) {
+                        /* 
+                         * 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 might have been 
+                         * symlinks or mount points or both that were crossed.  
+                         */
+                        for ( i=fid_count-1; i>=0; i--) {
+                            if (!cm_FidCmp(&nscp->fid, &fids[i]))
+                                break;
+                        }
+                        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;
+                        }
+                    }
                 }
-                haveComponent = 0;     /* component done */
-                if (dirScp)
-                    cm_ReleaseSCache(dirScp);
-                dirScp = tscp;         /* for some symlinks */
-                tscp = nscp;           /* already held */
-                nscp = 0;
-                if (tc == 0 && !(flags & CM_FLAG_FOLLOW) && phase == 2) {
+
+                if (code) {
+                   cm_ReleaseSCache(tscp);
+                   if (dirScp)
+                       cm_ReleaseSCache(dirScp);
+                   if (psp) 
+                       cm_FreeSpace(psp);
+                   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;
+                   } else {
+                       osi_Log1(afsd_logp,"cm_NameI code 0x%x", code);
+                       return code;
+                   }
+               }
+
+               haveComponent = 0;      /* component done */
+               if (dirScp)
+                   cm_ReleaseSCache(dirScp);
+               dirScp = tscp;          /* for some symlinks */
+               tscp = nscp;            /* already held */
+               nscp = NULL;
+               if (tc == 0 && !(flags & CM_FLAG_FOLLOW) && phase == 2) {
                     code = 0;
                     if (dirScp) {
                         cm_ReleaseSCache(dirScp);
-                        dirScp = 0;
+                        dirScp = NULL;
                     }
                     break;
                 }
@@ -1597,25 +2014,28 @@ long cm_NameI(cm_scache_t *rootSCachep, char *pathp, long flags,
                 if (code) {
                     lock_ReleaseMutex(&tscp->mx);
                     cm_ReleaseSCache(tscp);
-                    tscp = 0;
+                    tscp = NULL;
                     if (dirScp) {
                         cm_ReleaseSCache(dirScp);
-                        dirScp = 0;
+                        dirScp = NULL;
                     }
                     break;
                 }
+               cm_SyncOpDone(tscp, NULL, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
+
                 if (tscp->fileType == CM_SCACHETYPE_SYMLINK) {
                     /* this is a symlink; assemble a new buffer */
                     lock_ReleaseMutex(&tscp->mx);
                     if (symlinkCount++ >= MAX_SYMLINK_COUNT) {
                         cm_ReleaseSCache(tscp);
-                        tscp = 0;
+                        tscp = NULL;
                         if (dirScp) {
                             cm_ReleaseSCache(dirScp);
-                            dirScp = 0;
+                            dirScp = NULL;
                         }
                         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) 
@@ -1623,13 +2043,32 @@ long cm_NameI(cm_scache_t *rootSCachep, char *pathp, long flags,
                     else 
                         restp = tp;
                     code = cm_AssembleLink(tscp, restp, &linkScp, &tempsp, userp, reqp);
+
+                    if (code == 0 && linkScp != NULL) {
+                        if (linkScp == cm_data.rootSCachep) 
+                            fid_count = 0;
+                        else {
+                            for ( i=0; i<fid_count; i++) {
+                                if ( !cm_FidCmp(&linkScp->fid, &fids[i]) ) {
+                                    code = CM_ERROR_TOO_MANY_SYMLINKS;
+                                    cm_ReleaseSCache(linkScp);
+                                    nscp = NULL;
+                                    break;
+                                }
+                            }
+                        }
+                        if (i == fid_count && fid_count < MAX_FID_COUNT) {
+                            fids[fid_count++] = linkScp->fid;
+                        }
+                    }
+
                     if (code) {
                         /* something went wrong */
                         cm_ReleaseSCache(tscp);
-                        tscp = 0;
+                        tscp = NULL;
                         if (dirScp) {
                             cm_ReleaseSCache(dirScp);
-                            dirScp = 0;
+                            dirScp = NULL;
                         }
                         break;
                     }
@@ -1648,7 +2087,7 @@ long cm_NameI(cm_scache_t *rootSCachep, char *pathp, long flags,
                     tp = psp->data;
                     cm_ReleaseSCache(tscp);
                     tscp = linkScp;
-                    linkScp = 0;
+                    linkScp = NULL;
                     /* already held
                      * by AssembleLink
                      * now, if linkScp is null, that's
@@ -1661,7 +2100,7 @@ long cm_NameI(cm_scache_t *rootSCachep, char *pathp, long flags,
                      */
                     if (tscp == NULL) {
                         tscp = dirScp;
-                        dirScp = 0;
+                        dirScp = NULL;
                     }
                 } else {
                     /* not a symlink, we may be done */
@@ -1674,7 +2113,7 @@ long cm_NameI(cm_scache_t *rootSCachep, char *pathp, long flags,
                         }
                         if (dirScp) {
                             cm_ReleaseSCache(dirScp);
-                            dirScp = 0;
+                            dirScp = NULL;
                         }
                         code = 0;
                         break;
@@ -1682,7 +2121,7 @@ long cm_NameI(cm_scache_t *rootSCachep, char *pathp, long flags,
                 }
                 if (dirScp) {
                     cm_ReleaseSCache(dirScp);
-                    dirScp = 0;
+                    dirScp = NULL;
                 }
             } /* end of a component */
             else 
@@ -1699,6 +2138,11 @@ long cm_NameI(cm_scache_t *rootSCachep, char *pathp, long flags,
         *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);
+#endif
+    osi_Log2(afsd_logp,"cm_NameI code 0x%x outScpp 0x%p", code, *outScpp);
     return code;
 }
 
@@ -1742,7 +2186,7 @@ long cm_EvaluateSymLink(cm_scache_t *dscp, cm_scache_t *linkScp,
                      CM_FLAG_CASEFOLD | CM_FLAG_FOLLOW | CM_FLAG_DIRSEARCH,
                      userp, NULL, reqp, outScpp);
 
-    if (code == CM_ERROR_NOSUCHFILE)
+    if (code == CM_ERROR_NOSUCHFILE || code == CM_ERROR_BPLUS_NOMATCH)
         code = CM_ERROR_NOSUCHPATH;
 
     /* this stuff is allocated no matter what happened on the namei call,
@@ -1750,6 +2194,12 @@ long cm_EvaluateSymLink(cm_scache_t *dscp, cm_scache_t *linkScp,
     cm_FreeSpace(spacep);
     cm_ReleaseSCache(newRootScp);
 
+    if (linkScp == *outScpp) {
+        cm_ReleaseSCache(*outScpp);
+        *outScpp = NULL;
+        code = CM_ERROR_NOSUCHPATH;
+    }
+
     return code;
 }
 
@@ -1757,7 +2207,7 @@ long cm_EvaluateSymLink(cm_scache_t *dscp, cm_scache_t *linkScp,
  * check anyway, but we want to minimize the chance that we have to leave stuff
  * unstat'd.
  */
-#define CM_BULKMAX             128
+#define CM_BULKMAX             (3 * AFSCBMAX)
 
 /* rock for bulk stat calls */
 typedef struct cm_bulkStat {
@@ -1817,7 +2267,7 @@ long cm_TryBulkProc(cm_scache_t *scp, cm_dirEntry_t *dep, void *rockp,
     if (tscp) {
         if (lock_TryMutex(&tscp->mx)) {
             /* we have an entry that we can look at */
-            if (cm_HaveCallback(tscp)) {
+            if (!(tscp->flags & CM_SCACHEFLAG_EACCESS) && 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.
@@ -1855,11 +2305,12 @@ long cm_TryBulkProc(cm_scache_t *scp, cm_dirEntry_t *dep, void *rockp,
 /* called with a locked scp and a pointer to a buffer.  Make bulk stat
  * calls on all undeleted files in the page of the directory specified.
  */
-void cm_TryBulkStat(cm_scache_t *dscp, osi_hyper_t *offsetp, cm_user_t *userp,
-                     cm_req_t *reqp)
+afs_int32
+cm_TryBulkStat(cm_scache_t *dscp, osi_hyper_t *offsetp, cm_user_t *userp,
+              cm_req_t *reqp)
 {
     long code;
-    cm_bulkStat_t bb;  /* this is *BIG*, probably 12K or so;
+    cm_bulkStat_t bb;  /* this is *BIG*, probably 16K or so;
                          * watch for stack problems */
     AFSCBFids fidStruct;
     AFSBulkStats statStruct;
@@ -1874,13 +2325,14 @@ void cm_TryBulkStat(cm_scache_t *dscp, osi_hyper_t *offsetp, cm_user_t *userp,
     cm_scache_t *scp;
     cm_fid_t tfid;
     struct rx_connection * callp;
+    int inlinebulk = 0;                /* Did we use InlineBulkStatus RPC or not? */
 
     osi_Log1(afsd_logp, "cm_TryBulkStat dir 0x%p", dscp);
 
     /* should be on a buffer boundary */
-    osi_assert((offsetp->LowPart & (cm_data.buf_blockSize - 1)) == 0);
+    osi_assertx((offsetp->LowPart & (cm_data.buf_blockSize - 1)) == 0, "invalid offset");
 
-    bb.counter = 0;
+    memset(&bb, 0, sizeof(bb));
     bb.bufOffset = *offsetp;
 
     lock_ReleaseMutex(&dscp->mx);
@@ -1890,7 +2342,7 @@ void cm_TryBulkStat(cm_scache_t *dscp, osi_hyper_t *offsetp, cm_user_t *userp,
     /* if we failed, bail out early */
     if (code && code != CM_ERROR_STOPNOW) {
         lock_ObtainMutex(&dscp->mx);
-        return;
+        return code;
     }
 
     /* otherwise, we may have one or more bulk stat's worth of stuff in bb;
@@ -1912,23 +2364,34 @@ void cm_TryBulkStat(cm_scache_t *dscp, osi_hyper_t *offsetp, cm_user_t *userp,
         cm_StartCallbackGrantingCall(NULL, &cbReq);
         osi_Log1(afsd_logp, "CALL BulkStatus, %d entries", filesThisCall);
         do {
-            code = cm_Conn(&dscp->fid, userp, reqp, &connp);
+            code = cm_ConnFromFID(&dscp->fid, userp, reqp, &connp);
             if (code) 
                 continue;
 
             callp = cm_GetRxConn(connp);
-            code = RXAFS_BulkStatus(callp, &fidStruct,
+           if (!(connp->serverp->flags & CM_SERVERFLAG_NOINLINEBULK)) {
+               code = RXAFS_InlineBulkStatus(callp, &fidStruct,
                                      &statStruct, &callbackStruct, &volSync);
+               if (code == RXGEN_OPCODE) {
+                   cm_SetServerNoInlineBulk(connp->serverp, 0);
+               } else {
+                   inlinebulk = 1;
+               }
+           }
+           if (!inlinebulk) {
+               code = RXAFS_BulkStatus(callp, &fidStruct,
+                                       &statStruct, &callbackStruct, &volSync);
+           }
             rx_PutConnection(callp);
 
         } while (cm_Analyze(connp, userp, reqp, &dscp->fid,
                              &volSync, NULL, &cbReq, code));
         code = cm_MapRPCError(code, reqp);
-
         if (code)
-            osi_Log1(afsd_logp, "CALL BulkStatus FAILURE code 0x%x", code);
+            osi_Log2(afsd_logp, "CALL %sBulkStatus FAILURE code 0x%x", 
+                     inlinebulk ? "Inline" : "", code);
         else
-            osi_Log0(afsd_logp, "CALL BulkStatus SUCCESS");
+            osi_Log1(afsd_logp, "CALL %sBulkStatus SUCCESS", inlinebulk ? "Inline" : "");
 
         /* may as well quit on an error, since we're not going to do
          * much better on the next immediate call, either.
@@ -1963,7 +2426,7 @@ void cm_TryBulkStat(cm_scache_t *dscp, osi_hyper_t *offsetp, cm_user_t *userp,
              * Right now, be pretty conservative: if there's a
              * callback or a pending call, skip it.
              */
-            if (scp->cbServerp == NULL
+            if ((scp->cbServerp == NULL || (scp->flags & CM_SCACHEFLAG_EACCESS))
                  && !(scp->flags &
                        (CM_SCACHEFLAG_FETCHING
                          | CM_SCACHEFLAG_STORING
@@ -1971,8 +2434,7 @@ void cm_TryBulkStat(cm_scache_t *dscp, osi_hyper_t *offsetp, cm_user_t *userp,
                 cm_EndCallbackGrantingCall(scp, &cbReq,
                                             &bb.callbacks[j],
                                             CM_CALLBACK_MAINTAINCOUNT);
-                cm_MergeStatus(scp, &bb.stats[j], &volSync,
-                                userp, 0);
+                cm_MergeStatus(dscp, scp, &bb.stats[j], &volSync, userp, 0);
             }       
             lock_ReleaseMutex(&scp->mx);
             cm_ReleaseSCache(scp);
@@ -1984,7 +2446,17 @@ void cm_TryBulkStat(cm_scache_t *dscp, osi_hyper_t *offsetp, cm_user_t *userp,
         filex += filesThisCall;
     }  /* while there are still more files to process */
     lock_ObtainMutex(&dscp->mx);
+
+    /* If we did the InlineBulk RPC pull out the return code and log it */
+    if (inlinebulk) {
+       if ((&bb.stats[0])->errorCode) {
+           osi_Log1(afsd_logp, "cm_TryBulkStat bulk stat error: %d", 
+                    (&bb.stats[0])->errorCode);
+       }
+    }
+
     osi_Log0(afsd_logp, "END cm_TryBulkStat");
+    return 0;
 }       
 
 void cm_StatusFromAttr(AFSStoreStatus *statusp, cm_scache_t *scp, cm_attr_t *attrp)
@@ -2045,7 +2517,8 @@ long cm_SetLength(cm_scache_t *scp, osi_hyper_t *sizep, cm_user_t *userp,
                       CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
     if (code) 
         goto done;
-        
+    cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
+
     if (scp->fileType != CM_SCACHETYPE_FILE) {
         code = CM_ERROR_ISDIR;
         goto done;
@@ -2075,6 +2548,15 @@ long cm_SetLength(cm_scache_t *scp, osi_hyper_t *sizep, cm_user_t *userp,
     code = cm_SyncOp(scp, NULL, userp, reqp, PRSFS_WRITE,
                       CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS
                       | CM_SCACHESYNC_SETSTATUS | CM_SCACHESYNC_SETSIZE);
+
+    /* If we only have 'i' bits, then we should still be able to set
+       the size of a file we created. */
+    if (code == CM_ERROR_NOACCESS && scp->creator == userp) {
+        code = cm_SyncOp(scp, NULL, userp, reqp, PRSFS_INSERT,
+                         CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS
+                         | CM_SCACHESYNC_SETSTATUS | CM_SCACHESYNC_SETSIZE);
+    }
+
     if (code) 
         goto done;
 
@@ -2104,6 +2586,10 @@ long cm_SetLength(cm_scache_t *scp, osi_hyper_t *sizep, cm_user_t *userp,
     /* done successfully */
     code = 0;
 
+    cm_SyncOpDone(scp, NULL, 
+                  CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS
+                  | CM_SCACHESYNC_SETSTATUS | CM_SCACHESYNC_SETSIZE);
+
   done:
     lock_ReleaseMutex(&scp->mx);
     lock_ReleaseWrite(&scp->bufCreateLock);
@@ -2116,7 +2602,6 @@ long cm_SetAttr(cm_scache_t *scp, cm_attr_t *attrp, cm_user_t *userp,
                 cm_req_t *reqp)
 {
     long code;
-    int flags;
     AFSFetchStatus afsOutStatus;
     AFSVolSync volSync;
     cm_conn_t *connp;
@@ -2128,11 +2613,13 @@ long cm_SetAttr(cm_scache_t *scp, cm_attr_t *attrp, cm_user_t *userp,
     if (attrp->mask & CM_ATTRMASK_LENGTH)
         return cm_SetLength(scp, &attrp->length, userp, reqp);
 
-    flags = CM_SCACHESYNC_STORESTATUS;
-
     lock_ObtainMutex(&scp->mx);
     /* otherwise, we have to make an RPC to get the status */
     code = cm_SyncOp(scp, NULL, userp, reqp, 0, CM_SCACHESYNC_STORESTATUS);
+    if (code) {
+       lock_ReleaseMutex(&scp->mx);
+        return code;
+    }
 
     /* make the attr structure */
     cm_StatusFromAttr(&afsInStatus, scp, attrp);
@@ -2140,15 +2627,12 @@ long cm_SetAttr(cm_scache_t *scp, cm_attr_t *attrp, cm_user_t *userp,
     tfid.Volume = scp->fid.volume;
     tfid.Vnode = scp->fid.vnode;
     tfid.Unique = scp->fid.unique;
-
-    lock_ReleaseMutex(&scp->mx);
-    if (code) 
-        return code;
+       lock_ReleaseMutex(&scp->mx);
 
     /* now make the RPC */
     osi_Log1(afsd_logp, "CALL StoreStatus scp 0x%p", scp);
     do {
-        code = cm_Conn(&scp->fid, userp, reqp, &connp);
+        code = cm_ConnFromFID(&scp->fid, userp, reqp, &connp);
         if (code) 
             continue;
 
@@ -2169,13 +2653,14 @@ long cm_SetAttr(cm_scache_t *scp, cm_attr_t *attrp, cm_user_t *userp,
     lock_ObtainMutex(&scp->mx);
     cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_STORESTATUS);
     if (code == 0)
-        cm_MergeStatus(scp, &afsOutStatus, &volSync, userp,
-                        CM_MERGEFLAG_FORCE);
+        cm_MergeStatus(NULL, scp, &afsOutStatus, &volSync, userp,
+                        CM_MERGEFLAG_FORCE|CM_MERGEFLAG_STOREDATA);
        
     /* if we're changing the mode bits, discard the ACL cache, 
      * since we changed the mode bits.
      */
-    if (afsInStatus.Mask & AFS_SETMODE) cm_FreeAllACLEnts(scp);
+    if (afsInStatus.Mask & AFS_SETMODE) 
+       cm_FreeAllACLEnts(scp);
     lock_ReleaseMutex(&scp->mx);
     return code;
 }       
@@ -2189,7 +2674,7 @@ long cm_Create(cm_scache_t *dscp, char *namep, long flags, cm_attr_t *attrp,
     cm_callbackRequest_t cbReq;
     AFSFid newAFSFid;
     cm_fid_t newFid;
-    cm_scache_t *scp;
+    cm_scache_t *scp = NULL;
     int didEnd;
     AFSStoreStatus inStatus;
     AFSFetchStatus updatedDirStatus;
@@ -2197,6 +2682,7 @@ long cm_Create(cm_scache_t *dscp, char *namep, long flags, cm_attr_t *attrp,
     AFSCallBack newFileCallback;
     AFSVolSync volSync;
     struct rx_connection * callp;
+    cm_dirOp_t dirop;
 
     /* can't create names with @sys in them; must expand it manually first.
      * return "invalid request" if they try.
@@ -2205,16 +2691,29 @@ long cm_Create(cm_scache_t *dscp, char *namep, long flags, cm_attr_t *attrp,
         return CM_ERROR_ATSYS;
     }
 
+#ifdef AFS_FREELANCE_CLIENT
+    /* Freelance root volume does not hold files */
+    if (cm_freelanceEnabled &&
+        dscp->fid.cell==AFS_FAKE_ROOT_CELL_ID &&
+        dscp->fid.volume==AFS_FAKE_ROOT_VOL_ID )
+    {
+        return CM_ERROR_NOACCESS;
+    }
+#endif /* AFS_FREELANCE_CLIENT */
+
     /* 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);
     lock_ObtainMutex(&dscp->mx);
     code = cm_SyncOp(dscp, NULL, userp, reqp, 0, CM_SCACHESYNC_STOREDATA);
+    lock_ReleaseMutex(&dscp->mx);
     if (code == 0) {
         cm_StartCallbackGrantingCall(NULL, &cbReq);
+    } else {
+        cm_EndDirOp(&dirop);
     }
-    lock_ReleaseMutex(&dscp->mx);
     if (code) {
         return code;
     }
@@ -2225,7 +2724,7 @@ long cm_Create(cm_scache_t *dscp, char *namep, long flags, cm_attr_t *attrp,
     /* try the RPC now */
     osi_Log1(afsd_logp, "CALL CreateFile scp 0x%p", dscp);
     do {
-        code = cm_Conn(&dscp->fid, userp, reqp, &connp);
+        code = cm_ConnFromFID(&dscp->fid, userp, reqp, &connp);
         if (code) 
             continue;
 
@@ -2249,10 +2748,14 @@ long cm_Create(cm_scache_t *dscp, char *namep, long flags, cm_attr_t *attrp,
     else
         osi_Log0(afsd_logp, "CALL CreateFile SUCCESS");
 
+    if (dirop.scp) {
+        lock_ObtainWrite(&dirop.scp->dirlock);
+        dirop.lockType = CM_DIRLOCK_WRITE;
+    }
     lock_ObtainMutex(&dscp->mx);
     cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
     if (code == 0) {
-        cm_MergeStatus(dscp, &updatedDirStatus, &volSync, userp, 0);
+        cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, CM_MERGEFLAG_DIROP);
     }
     lock_ReleaseMutex(&dscp->mx);
 
@@ -2269,8 +2772,9 @@ long cm_Create(cm_scache_t *dscp, char *namep, long flags, cm_attr_t *attrp,
         code = cm_GetSCache(&newFid, &scp, userp, reqp);
         if (code == 0) {
             lock_ObtainMutex(&scp->mx);
+           scp->creator = userp;               /* remember who created it */
             if (!cm_HaveCallback(scp)) {
-                cm_MergeStatus(scp, &newFileStatus, &volSync,
+                cm_MergeStatus(dscp, scp, &newFileStatus, &volSync,
                                 userp, 0);
                 cm_EndCallbackGrantingCall(scp, &cbReq,
                                             &newFileCallback, 0);
@@ -2285,6 +2789,14 @@ long cm_Create(cm_scache_t *dscp, char *namep, long flags, cm_attr_t *attrp,
     if (!didEnd)
         cm_EndCallbackGrantingCall(NULL, &cbReq, NULL, 0);
 
+    if (scp && cm_CheckDirOpForSingleChange(&dirop)) {
+        cm_DirCreateEntry(&dirop, namep, &newFid);
+#ifdef USE_BPLUS
+        cm_BPlusDirCreateEntry(&dirop, namep, &newFid);
+#endif
+    }
+    cm_EndDirOp(&dirop);
+
     return code;
 }       
 
@@ -2297,12 +2809,17 @@ long cm_FSync(cm_scache_t *scp, cm_user_t *userp, cm_req_t *reqp)
     lock_ReleaseWrite(&scp->bufCreateLock);
     if (code == 0) {
         lock_ObtainMutex(&scp->mx);
-        scp->flags &= ~(CM_SCACHEFLAG_OVERQUOTA
-                         | CM_SCACHEFLAG_OUTOFSPACE);
+
         if (scp->mask & (CM_SCACHEMASK_TRUNCPOS
                           | CM_SCACHEMASK_CLIENTMODTIME
                           | CM_SCACHEMASK_LENGTH))
             code = cm_StoreMini(scp, userp, reqp);
+
+        if (scp->flags & (CM_SCACHEFLAG_OVERQUOTA | CM_SCACHEFLAG_OUTOFSPACE)) {
+           code = (scp->flags & CM_SCACHEFLAG_OVERQUOTA) ? CM_ERROR_QUOTA : CM_ERROR_SPACE;
+           scp->flags &= ~(CM_SCACHEFLAG_OVERQUOTA | CM_SCACHEFLAG_OUTOFSPACE);
+       }
+
         lock_ReleaseMutex(&scp->mx);
     }
     return code;
@@ -2317,7 +2834,7 @@ long cm_MakeDir(cm_scache_t *dscp, char *namep, long flags, cm_attr_t *attrp,
     cm_callbackRequest_t cbReq;
     AFSFid newAFSFid;
     cm_fid_t newFid;
-    cm_scache_t *scp;
+    cm_scache_t *scp = NULL;
     int didEnd;
     AFSStoreStatus inStatus;
     AFSFetchStatus updatedDirStatus;
@@ -2325,6 +2842,7 @@ long cm_MakeDir(cm_scache_t *dscp, char *namep, long flags, cm_attr_t *attrp,
     AFSCallBack newDirCallback;
     AFSVolSync volSync;
     struct rx_connection * callp;
+    cm_dirOp_t dirop;
 
     /* can't create names with @sys in them; must expand it manually first.
      * return "invalid request" if they try.
@@ -2333,16 +2851,29 @@ long cm_MakeDir(cm_scache_t *dscp, char *namep, long flags, cm_attr_t *attrp,
         return CM_ERROR_ATSYS;
     }
 
+#ifdef AFS_FREELANCE_CLIENT
+    /* Freelance root volume does not hold subdirectories */
+    if (cm_freelanceEnabled &&
+        dscp->fid.cell==AFS_FAKE_ROOT_CELL_ID &&
+        dscp->fid.volume==AFS_FAKE_ROOT_VOL_ID )
+    {
+        return CM_ERROR_NOACCESS;
+    }
+#endif /* AFS_FREELANCE_CLIENT */
+
     /* 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);
     lock_ObtainMutex(&dscp->mx);
     code = cm_SyncOp(dscp, NULL, userp, reqp, 0, CM_SCACHESYNC_STOREDATA);
+    lock_ReleaseMutex(&dscp->mx);
     if (code == 0) {
         cm_StartCallbackGrantingCall(NULL, &cbReq);
+    } else {
+        cm_EndDirOp(&dirop);
     }
-    lock_ReleaseMutex(&dscp->mx);
     if (code) {
         return code;
     }
@@ -2353,7 +2884,7 @@ long cm_MakeDir(cm_scache_t *dscp, char *namep, long flags, cm_attr_t *attrp,
     /* try the RPC now */
     osi_Log1(afsd_logp, "CALL MakeDir scp 0x%p", dscp);
     do {
-        code = cm_Conn(&dscp->fid, userp, reqp, &connp);
+        code = cm_ConnFromFID(&dscp->fid, userp, reqp, &connp);
         if (code) 
             continue;
 
@@ -2377,10 +2908,14 @@ long cm_MakeDir(cm_scache_t *dscp, char *namep, long flags, cm_attr_t *attrp,
     else
         osi_Log0(afsd_logp, "CALL MakeDir SUCCESS");
 
+    if (dirop.scp) {
+        lock_ObtainWrite(&dirop.scp->dirlock);
+        dirop.lockType = CM_DIRLOCK_WRITE;
+    }
     lock_ObtainMutex(&dscp->mx);
     cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
     if (code == 0) {
-        cm_MergeStatus(dscp, &updatedDirStatus, &volSync, userp, 0);
+        cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, CM_MERGEFLAG_DIROP);
     }
     lock_ReleaseMutex(&dscp->mx);
 
@@ -2398,7 +2933,7 @@ long cm_MakeDir(cm_scache_t *dscp, char *namep, long flags, cm_attr_t *attrp,
         if (code == 0) {
             lock_ObtainMutex(&scp->mx);
             if (!cm_HaveCallback(scp)) {
-                cm_MergeStatus(scp, &newDirStatus, &volSync,
+                cm_MergeStatus(dscp, scp, &newDirStatus, &volSync,
                                 userp, 0);
                 cm_EndCallbackGrantingCall(scp, &cbReq,
                                             &newDirCallback, 0);
@@ -2413,6 +2948,14 @@ long cm_MakeDir(cm_scache_t *dscp, char *namep, long flags, cm_attr_t *attrp,
     if (!didEnd)
         cm_EndCallbackGrantingCall(NULL, &cbReq, NULL, 0);
 
+    if (scp && cm_CheckDirOpForSingleChange(&dirop)) {
+        cm_DirCreateEntry(&dirop, namep, &newFid);
+#ifdef USE_BPLUS
+        cm_BPlusDirCreateEntry(&dirop, namep, &newFid);
+#endif
+    }
+    cm_EndDirOp(&dirop);
+
     /* and return error code */
     return code;
 }       
@@ -2428,15 +2971,19 @@ long cm_Link(cm_scache_t *dscp, char *namep, cm_scache_t *sscp, long flags,
     AFSFetchStatus newLinkStatus;
     AFSVolSync volSync;
     struct rx_connection * callp;
+    cm_dirOp_t dirop;
 
     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);
     lock_ObtainMutex(&dscp->mx);
     code = cm_SyncOp(dscp, NULL, userp, reqp, 0, CM_SCACHESYNC_STOREDATA);
     lock_ReleaseMutex(&dscp->mx);
+    if (code != 0)
+        cm_EndDirOp(&dirop);
 
     if (code)
         return code;
@@ -2444,7 +2991,7 @@ long cm_Link(cm_scache_t *dscp, char *namep, cm_scache_t *sscp, long flags,
     /* try the RPC now */
     osi_Log1(afsd_logp, "CALL Link scp 0x%p", dscp);
     do {
-        code = cm_Conn(&dscp->fid, userp, reqp, &connp);
+        code = cm_ConnFromFID(&dscp->fid, userp, reqp, &connp);
         if (code) continue;
 
         dirAFSFid.Volume = dscp->fid.volume;
@@ -2471,13 +3018,27 @@ long cm_Link(cm_scache_t *dscp, char *namep, cm_scache_t *sscp, long flags,
     else
         osi_Log0(afsd_logp, "CALL Link SUCCESS");
 
+    if (dirop.scp) {
+        lock_ObtainWrite(&dirop.scp->dirlock);
+        dirop.lockType = CM_DIRLOCK_WRITE;
+    }
     lock_ObtainMutex(&dscp->mx);
     cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
     if (code == 0) {
-        cm_MergeStatus(dscp, &updatedDirStatus, &volSync, userp, 0);
+        cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, CM_MERGEFLAG_DIROP);
     }
     lock_ReleaseMutex(&dscp->mx);
 
+    if (code == 0) {
+        if (cm_CheckDirOpForSingleChange(&dirop)) {
+            cm_DirCreateEntry(&dirop, namep, &sscp->fid);
+#ifdef USE_BPLUS
+            cm_BPlusDirCreateEntry(&dirop, namep, &sscp->fid);
+#endif
+        }
+    }
+    cm_EndDirOp(&dirop);
+
     return code;
 }
 
@@ -2495,14 +3056,18 @@ long cm_SymLink(cm_scache_t *dscp, char *namep, char *contentsp, long flags,
     AFSFetchStatus newLinkStatus;
     AFSVolSync volSync;
     struct rx_connection * callp;
+    cm_dirOp_t dirop;
 
     /* 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);
     lock_ObtainMutex(&dscp->mx);
     code = cm_SyncOp(dscp, NULL, userp, reqp, 0, CM_SCACHESYNC_STOREDATA);
     lock_ReleaseMutex(&dscp->mx);
+    if (code != 0)
+        cm_EndDirOp(&dirop);
     if (code) {
         return code;
     }
@@ -2512,7 +3077,7 @@ long cm_SymLink(cm_scache_t *dscp, char *namep, char *contentsp, long flags,
     /* try the RPC now */
     osi_Log1(afsd_logp, "CALL Symlink scp 0x%p", dscp);
     do {
-        code = cm_Conn(&dscp->fid, userp, reqp, &connp);
+        code = cm_ConnFromFID(&dscp->fid, userp, reqp, &connp);
         if (code) 
             continue;
 
@@ -2535,13 +3100,32 @@ long cm_SymLink(cm_scache_t *dscp, char *namep, char *contentsp, long flags,
     else
         osi_Log0(afsd_logp, "CALL Symlink SUCCESS");
 
+    if (dirop.scp) {
+        lock_ObtainWrite(&dirop.scp->dirlock);
+        dirop.lockType = CM_DIRLOCK_WRITE;
+    }
     lock_ObtainMutex(&dscp->mx);
     cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
     if (code == 0) {
-        cm_MergeStatus(dscp, &updatedDirStatus, &volSync, userp, 0);
+        cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, CM_MERGEFLAG_DIROP);
     }
     lock_ReleaseMutex(&dscp->mx);
 
+    if (code == 0) {
+        if (cm_CheckDirOpForSingleChange(&dirop)) {
+            newFid.cell = dscp->fid.cell;
+            newFid.volume = dscp->fid.volume;
+            newFid.vnode = newAFSFid.Vnode;
+            newFid.unique = newAFSFid.Unique;
+
+            cm_DirCreateEntry(&dirop, namep, &newFid);
+#ifdef USE_BPLUS
+            cm_BPlusDirCreateEntry(&dirop, namep, &newFid);
+#endif
+        }
+    }
+    cm_EndDirOp(&dirop);
+
     /* 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
@@ -2556,7 +3140,7 @@ long cm_SymLink(cm_scache_t *dscp, char *namep, char *contentsp, long flags,
         if (code == 0) {
             lock_ObtainMutex(&scp->mx);
             if (!cm_HaveCallback(scp)) {
-                cm_MergeStatus(scp, &newLinkStatus, &volSync,
+                cm_MergeStatus(dscp, scp, &newLinkStatus, &volSync,
                                 userp, 0);
             }       
             lock_ReleaseMutex(&scp->mx);
@@ -2578,15 +3162,18 @@ long cm_RemoveDir(cm_scache_t *dscp, char *namep, cm_user_t *userp,
     AFSFetchStatus updatedDirStatus;
     AFSVolSync volSync;
     struct rx_connection * callp;
+    cm_dirOp_t dirop;
 
     /* 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);
     lock_ObtainMutex(&dscp->mx);
     code = cm_SyncOp(dscp, NULL, userp, reqp, 0, CM_SCACHESYNC_STOREDATA);
     lock_ReleaseMutex(&dscp->mx);
     if (code) {
+        cm_EndDirOp(&dirop);
         return code;
     }
     didEnd = 0;
@@ -2594,7 +3181,7 @@ long cm_RemoveDir(cm_scache_t *dscp, char *namep, cm_user_t *userp,
     /* try the RPC now */
     osi_Log1(afsd_logp, "CALL RemoveDir scp 0x%p", dscp);
     do {
-        code = cm_Conn(&dscp->fid, userp, reqp, &connp);
+        code = cm_ConnFromFID(&dscp->fid, userp, reqp, &connp);
         if (code) 
             continue;
 
@@ -2616,14 +3203,28 @@ long cm_RemoveDir(cm_scache_t *dscp, char *namep, cm_user_t *userp,
     else
         osi_Log0(afsd_logp, "CALL RemoveDir SUCCESS");
 
+    if (dirop.scp) {
+        lock_ObtainWrite(&dirop.scp->dirlock);
+        dirop.lockType = CM_DIRLOCK_WRITE;
+    }
     lock_ObtainMutex(&dscp->mx);
     cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
     if (code == 0) {
         cm_dnlcRemove(dscp, namep); 
-        cm_MergeStatus(dscp, &updatedDirStatus, &volSync, userp, 0);
+        cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, CM_MERGEFLAG_DIROP);
     }
     lock_ReleaseMutex(&dscp->mx);
 
+    if (code == 0) {
+        if (cm_CheckDirOpForSingleChange(&dirop)) {
+            cm_DirDeleteEntry(&dirop, namep);
+#ifdef USE_BPLUS
+            cm_BPlusDirDeleteEntry(&dirop, namep);
+#endif
+        }
+    }
+    cm_EndDirOp(&dirop);
+
     /* and return error code */
     return code;
 }
@@ -2659,6 +3260,10 @@ long cm_Rename(cm_scache_t *oldDscp, char *oldNamep, cm_scache_t *newDscp,
     AFSVolSync volSync;
     int oneDir;
     struct rx_connection * callp;
+    cm_dirOp_t oldDirOp;
+    cm_fid_t   fileFid;
+    int        diropCode = -1;
+    cm_dirOp_t newDirOp;
 
     /* 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
@@ -2671,12 +3276,16 @@ long cm_Rename(cm_scache_t *oldDscp, char *oldNamep, cm_scache_t *newDscp,
             return CM_ERROR_RENAME_IDENTICAL;
 
         oneDir = 1;
+        cm_BeginDirOp(oldDscp, userp, reqp, CM_DIRLOCK_NONE, &oldDirOp);
         lock_ObtainMutex(&oldDscp->mx);
         cm_dnlcRemove(oldDscp, oldNamep);
         cm_dnlcRemove(oldDscp, newNamep);
         code = cm_SyncOp(oldDscp, NULL, userp, reqp, 0,
                           CM_SCACHESYNC_STOREDATA);
         lock_ReleaseMutex(&oldDscp->mx);
+        if (code != 0) {
+            cm_EndDirOp(&oldDirOp);
+        }
     }
     else {
         /* two distinct dir vnodes */
@@ -2693,45 +3302,59 @@ long cm_Rename(cm_scache_t *oldDscp, char *oldNamep, cm_scache_t *newDscp,
             return CM_ERROR_CROSSDEVLINK;
 
         if (oldDscp->fid.vnode < newDscp->fid.vnode) {
+            cm_BeginDirOp(oldDscp, userp, reqp, CM_DIRLOCK_NONE, &oldDirOp);
             lock_ObtainMutex(&oldDscp->mx);
             cm_dnlcRemove(oldDscp, oldNamep);
             code = cm_SyncOp(oldDscp, NULL, userp, reqp, 0,
                               CM_SCACHESYNC_STOREDATA);
             lock_ReleaseMutex(&oldDscp->mx);
+            if (code != 0)
+                cm_EndDirOp(&oldDirOp);
             if (code == 0) {
+                cm_BeginDirOp(newDscp, userp, reqp, CM_DIRLOCK_NONE, &newDirOp);
                 lock_ObtainMutex(&newDscp->mx);
                 cm_dnlcRemove(newDscp, newNamep);
                 code = cm_SyncOp(newDscp, NULL, userp, reqp, 0,
                                   CM_SCACHESYNC_STOREDATA);
                 lock_ReleaseMutex(&newDscp->mx);
                 if (code) {
+                    cm_EndDirOp(&newDirOp);
+
                     /* cleanup first one */
-                    lock_ObtainMutex(&newDscp->mx);
+                    lock_ObtainMutex(&oldDscp->mx);
                     cm_SyncOpDone(oldDscp, NULL,
                                    CM_SCACHESYNC_STOREDATA);
                     lock_ReleaseMutex(&oldDscp->mx);
+                    cm_EndDirOp(&oldDirOp);
                 }       
             }
         }
         else {
             /* lock the new vnode entry first */
+            cm_BeginDirOp(newDscp, userp, reqp, CM_DIRLOCK_NONE, &newDirOp);
             lock_ObtainMutex(&newDscp->mx);
             cm_dnlcRemove(newDscp, newNamep);
             code = cm_SyncOp(newDscp, NULL, userp, reqp, 0,
                               CM_SCACHESYNC_STOREDATA);
             lock_ReleaseMutex(&newDscp->mx);
+            if (code != 0)
+                cm_EndDirOp(&newDirOp);
             if (code == 0) {
+                cm_BeginDirOp(oldDscp, userp, reqp, CM_DIRLOCK_NONE, &oldDirOp);
                 lock_ObtainMutex(&oldDscp->mx);
                 cm_dnlcRemove(oldDscp, oldNamep);
                 code = cm_SyncOp(oldDscp, NULL, userp, reqp, 0,
                                   CM_SCACHESYNC_STOREDATA);
                 lock_ReleaseMutex(&oldDscp->mx);
+                if (code != 0)
+                    cm_EndDirOp(&oldDirOp);
                 if (code) {
                     /* cleanup first one */
                     lock_ObtainMutex(&newDscp->mx);
                     cm_SyncOpDone(newDscp, NULL,
                                    CM_SCACHESYNC_STOREDATA);
                     lock_ReleaseMutex(&newDscp->mx);
+                    cm_EndDirOp(&newDirOp);
                 }       
             }
         }
@@ -2746,7 +3369,7 @@ long cm_Rename(cm_scache_t *oldDscp, char *oldNamep, cm_scache_t *newDscp,
     osi_Log2(afsd_logp, "CALL Rename old scp 0x%p new scp 0x%p", 
               oldDscp, newDscp);
     do {
-        code = cm_Conn(&oldDscp->fid, userp, reqp, &connp);
+        code = cm_ConnFromFID(&oldDscp->fid, userp, reqp, &connp);
         if (code) 
             continue;
 
@@ -2774,23 +3397,73 @@ long cm_Rename(cm_scache_t *oldDscp, char *oldNamep, cm_scache_t *newDscp,
         osi_Log0(afsd_logp, "CALL Rename SUCCESS");
 
     /* update the individual stat cache entries for the directories */
+    if (oldDirOp.scp) {
+        lock_ObtainWrite(&oldDirOp.scp->dirlock);
+        oldDirOp.lockType = CM_DIRLOCK_WRITE;
+    }
     lock_ObtainMutex(&oldDscp->mx);
     cm_SyncOpDone(oldDscp, NULL, CM_SCACHESYNC_STOREDATA);
+
+    if (code == 0)
+        cm_MergeStatus(NULL, oldDscp, &updatedOldDirStatus, &volSync,
+                        userp, CM_MERGEFLAG_DIROP);
+    lock_ReleaseMutex(&oldDscp->mx);
+
     if (code == 0) {
-        cm_MergeStatus(oldDscp, &updatedOldDirStatus, &volSync,
-                        userp, 0);
+        if (cm_CheckDirOpForSingleChange(&oldDirOp)) {
+
+#ifdef USE_BPLUS
+            diropCode = cm_BPlusDirLookup(&oldDirOp, oldNamep, &fileFid);
+            if (diropCode == CM_ERROR_INEXACT_MATCH)
+                diropCode = 0;
+            else if (diropCode == EINVAL)
+#endif
+                diropCode = cm_DirLookup(&oldDirOp, oldNamep, &fileFid);
+
+            if (diropCode == 0) {
+                if (oneDir) {
+                    diropCode = cm_DirCreateEntry(&oldDirOp, newNamep, &fileFid);
+#ifdef USE_BPLUS
+                    cm_BPlusDirCreateEntry(&oldDirOp, newNamep, &fileFid);
+#endif
+                }
+
+                if (diropCode == 0) { 
+                    diropCode = cm_DirDeleteEntry(&oldDirOp, oldNamep);
+#ifdef USE_BPLUS
+                    cm_BPlusDirDeleteEntry(&oldDirOp, oldNamep);
+#endif
+                }
+            }
+        }
     }
-    lock_ReleaseMutex(&oldDscp->mx);
+    cm_EndDirOp(&oldDirOp);
 
     /* and update it for the new one, too, if necessary */
     if (!oneDir) {
+        if (newDirOp.scp) {
+            lock_ObtainWrite(&newDirOp.scp->dirlock);
+            newDirOp.lockType = CM_DIRLOCK_WRITE;
+        }
         lock_ObtainMutex(&newDscp->mx);
         cm_SyncOpDone(newDscp, NULL, CM_SCACHESYNC_STOREDATA);
+        if (code == 0)
+            cm_MergeStatus(NULL, newDscp, &updatedNewDirStatus, &volSync,
+                            userp, CM_MERGEFLAG_DIROP);
+        lock_ReleaseMutex(&newDscp->mx);
+
         if (code == 0) {
-            cm_MergeStatus(newDscp, &updatedNewDirStatus, &volSync,
-                            userp, 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 */
+            if (diropCode == 0 && cm_CheckDirOpForSingleChange(&newDirOp)) {
+                cm_DirCreateEntry(&newDirOp, newNamep, &fileFid);
+#ifdef USE_BPLUS
+                cm_BPlusDirCreateEntry(&newDirOp, newNamep, &fileFid);
+#endif
+            }
         }
-        lock_ReleaseMutex(&newDscp->mx);
+        cm_EndDirOp(&newDirOp);
     }
 
     /* and return error code */
@@ -3087,7 +3760,23 @@ long cm_Rename(cm_scache_t *oldDscp, char *oldNamep, cm_scache_t *newDscp,
 /* unsafe */
 #define CONTAINS_RANGE(r1,r2) (((r2).offset+(r2).length) <= ((r1).offset+(r1).length) && (r1).offset <= (r2).offset)
 
-#define SERVERLOCKS_ENABLED(scp) (!(((scp)->flags & CM_SCACHEFLAG_RO) && cm_enableServerLocks))
+#if defined(VICED_CAPABILITY_USE_BYTE_RANGE_LOCKS) && !defined(LOCK_TESTING)
+#define SCP_SUPPORTS_BRLOCKS(scp) ((scp)->cbServerp && ((scp)->cbServerp->capabilities & VICED_CAPABILITY_USE_BYTE_RANGE_LOCKS))
+#else
+#define SCP_SUPPORTS_BRLOCKS(scp) (1)
+#endif
+
+#define SERVERLOCKS_ENABLED(scp) (!((scp)->flags & CM_SCACHEFLAG_RO) && cm_enableServerLocks && SCP_SUPPORTS_BRLOCKS(scp))
+
+#if defined(VICED_CAPABILITY_WRITELOCKACL)
+#define SCP_SUPPORTS_WRITELOCKACL(scp) ((scp)->cbServerp && ((scp->cbServerp->capabilities & VICED_CAPABILITY_WRITELOCKACL)))
+#else
+#define SCP_SUPPORTS_WRITELOCKACL(scp) (0)
+
+/* This should really be defined in any build that this code is being
+   compiled. */
+#error  VICED_CAPABILITY_WRITELOCKACL not defined.
+#endif
 
 static void cm_LockRangeSubtract(cm_range_t * pos, const cm_range_t * neg)
 {
@@ -3097,11 +3786,11 @@ static void cm_LockRangeSubtract(cm_range_t * pos, const cm_range_t * neg)
     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) {
+    if (int_begin < int_end) {
+        if (int_begin == pos->offset) {
             pos->length = pos->offset + pos->length - int_end;
             pos->offset = int_end;
-        } else if(int_end == pos->offset + pos->length) {
+        } else if (int_end == pos->offset + pos->length) {
             pos->length = int_begin - pos->offset;
         }
 
@@ -3146,20 +3835,20 @@ long cm_LockCheckRead(cm_scache_t *scp,
 
     lock_ObtainRead(&cm_scacheLock);
 
-    for(q = scp->fileLocksH; q && range.length > 0; q = osi_QNext(q)) {
+    for (q = scp->fileLocksH; q && range.length > 0; q = osi_QNext(q)) {
         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 (IS_LOCK_ACTIVE(fileLock)) {
+                if (fileLock->key == key) {
 
                     /* If there is an active lock for this client, it
                        is safe to substract ranges.*/
                     cm_LockRangeSubtract(&range, &fileLock->range);
                     substract_ranges = TRUE;
                 } else {
-                    if(fileLock->lockType != LockRead) {
+                    if (fileLock->lockType != LockRead) {
                         code = CM_ERROR_LOCK_CONFLICT;
                         break;
                     }
@@ -3170,10 +3859,10 @@ long cm_LockCheckRead(cm_scache_t *scp,
                        is, unless we have already seen an active lock
                        belonging to the client, in which case there
                        can't be any lost locks for this client. */
-                    if(substract_ranges)
+                    if (substract_ranges)
                         cm_LockRangeSubtract(&range, &fileLock->range);
                 }
-            } else if(IS_LOCK_LOST(fileLock) &&
+            } else if (IS_LOCK_LOST(fileLock) &&
                       (fileLock->key == key || fileLock->lockType == LockWrite)) {
                 code = CM_ERROR_BADFD;
                 break;
@@ -3227,14 +3916,14 @@ long cm_LockCheckWrite(cm_scache_t *scp,
 
     lock_ObtainRead(&cm_scacheLock);
 
-    for(q = scp->fileLocksH; q && range.length > 0; q = osi_QNext(q)) {
+    for (q = scp->fileLocksH; q && range.length > 0; q = osi_QNext(q)) {
         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(fileLock->lockType == LockWrite) {
+        if (INTERSECT_RANGE(range, fileLock->range)) {
+            if (IS_LOCK_ACTIVE(fileLock)) {
+                if (fileLock->key == key) {
+                    if (fileLock->lockType == LockWrite) {
 
                         /* if there is an active lock for this client, it
                            is safe to substract ranges */
@@ -3247,7 +3936,7 @@ long cm_LockCheckWrite(cm_scache_t *scp,
                     code = CM_ERROR_LOCK_CONFLICT;
                     break;
                 }
-            } else if(IS_LOCK_LOST(fileLock)) {
+            } else if (IS_LOCK_LOST(fileLock)) {
                 code = CM_ERROR_BADFD;
                 break;
             }
@@ -3276,11 +3965,11 @@ static cm_file_lock_t * cm_GetFileLock(void) {
     cm_file_lock_t * l;
 
     l = (cm_file_lock_t *) cm_freeFileLocks;
-    if(l) {
+    if (l) {
         osi_QRemove(&cm_freeFileLocks, &l->q);
     } else {
         l = malloc(sizeof(cm_file_lock_t));
-        osi_assert(l);
+        osi_assertx(l, "null cm_file_lock_t");
     }
 
     memset(l, 0, sizeof(cm_file_lock_t));
@@ -3293,6 +3982,180 @@ static void cm_PutFileLock(cm_file_lock_t *l) {
     osi_QAdd(&cm_freeFileLocks, &l->q);
 }
 
+/* called with scp->mx held.  May release it during processing, but
+   leaves it held on exit. */
+long cm_IntSetLock(cm_scache_t * scp, cm_user_t * userp, int lockType,
+                   cm_req_t * reqp) {
+    long code = 0;
+    AFSFid tfid;
+    cm_fid_t cfid;
+    cm_conn_t * connp;
+    struct rx_connection * callp;
+    AFSVolSync 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);
+
+    lock_ReleaseMutex(&scp->mx);
+
+    do {
+        code = cm_ConnFromFID(&cfid, userp, reqp, &connp);
+        if (code) 
+            break;
+
+        callp = cm_GetRxConn(connp);
+        code = RXAFS_SetLock(callp, &tfid, lockType,
+                             &volSync);
+        rx_PutConnection(callp);
+
+    } while (cm_Analyze(connp, userp, reqp, &cfid, &volSync,
+                        NULL, NULL, code));
+
+    code = cm_MapRPCError(code, reqp);
+    if (code) {
+        osi_Log1(afsd_logp, "CALL SetLock FAILURE, code 0x%x", code);
+    } else {
+        osi_Log0(afsd_logp, "CALL SetLock SUCCESS");
+    }
+
+    lock_ObtainMutex(&scp->mx);
+
+    return code;
+}
+
+/* called with scp->mx held.  Releases it during processing */
+long cm_IntReleaseLock(cm_scache_t * scp, cm_user_t * userp,
+                       cm_req_t * reqp) {
+    long code = 0;
+    AFSFid tfid;
+    cm_fid_t cfid;
+    cm_conn_t * connp;
+    struct rx_connection * callp;
+    AFSVolSync volSync;
+
+    tfid.Volume = scp->fid.volume;
+    tfid.Vnode = scp->fid.vnode;
+    tfid.Unique = scp->fid.unique;
+    cfid = scp->fid;
+
+    lock_ReleaseMutex(&scp->mx);
+
+    osi_Log1(afsd_logp, "CALL ReleaseLock scp 0x%p", scp);
+
+    do {
+        code = cm_ConnFromFID(&cfid, userp, reqp, &connp);
+        if (code) 
+            break;
+
+        callp = cm_GetRxConn(connp);
+        code = RXAFS_ReleaseLock(callp, &tfid, &volSync);
+        rx_PutConnection(callp);
+
+    } while (cm_Analyze(connp, userp, reqp, &cfid, &volSync,
+                        NULL, NULL, code));
+    code = cm_MapRPCError(code, reqp);
+    if (code)
+        osi_Log1(afsd_logp,
+                 "CALL ReleaseLock FAILURE, code 0x%x", code);
+    else
+        osi_Log0(afsd_logp,
+                 "CALL ReleaseLock SUCCESS");
+        
+    lock_ObtainMutex(&scp->mx);
+
+    return code;
+}
+
+/* called with scp->mx held.  May release it during processing, but
+   will exit with lock held.
+
+   This will return:
+
+   - 0 if the user has permission to get the specified lock for the scp
+
+   - CM_ERROR_NOACCESS if not
+
+   Any other error from cm_SyncOp will be sent down untranslated.
+
+   If CM_ERROR_NOACCESS is returned and lock_type is LockRead, then
+   phas_insert (if non-NULL) will receive a boolean value indicating
+   whether the user has INSERT permission or not.
+*/
+long cm_LockCheckPerms(cm_scache_t * scp,
+                       int lock_type,
+                       cm_user_t * userp,
+                       cm_req_t * reqp,
+                       int * phas_insert)
+{
+    long rights = 0;
+    long code = 0, code2 = 0;
+
+    /* lock permissions are slightly tricky because of the 'i' bit.
+       If the user has PRSFS_LOCK, she can read-lock the file.  If the
+       user has PRSFS_WRITE, she can write-lock the file.  However, if
+       the user has PRSFS_INSERT, then she can write-lock new files,
+       but not old ones.  Since we don't have information about
+       whether a file is new or not, we assume that if the user owns
+       the scp, then she has the permissions that are granted by
+       PRSFS_INSERT. */
+
+    osi_Log3(afsd_logp, "cm_LockCheckPerms for scp[0x%p] type[%d] user[0x%p]",
+             scp, lock_type, userp);
+
+    if (lock_type == LockRead)
+        rights |= PRSFS_LOCK;
+    else if (lock_type == LockWrite)
+        rights |= PRSFS_WRITE | PRSFS_LOCK;
+    else {
+        /* hmmkay */
+        osi_assertx(FALSE, "invalid lock type");
+        return 0;
+    }
+
+    if (phas_insert)
+        *phas_insert = FALSE;
+
+    code = cm_SyncOp(scp, NULL, userp, reqp, rights,
+                     CM_SCACHESYNC_GETSTATUS |
+                     CM_SCACHESYNC_NEEDCALLBACK);
+
+    if (phas_insert && scp->creator == userp) {
+
+        /* If this file was created by the user, then we check for
+           PRSFS_INSERT.  If the file server is recent enough, then
+           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. 
+           
+           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.
+         */
+
+        code2 = cm_SyncOp(scp, NULL, userp, reqp, PRSFS_INSERT,
+                         CM_SCACHESYNC_GETSTATUS |
+                         CM_SCACHESYNC_NEEDCALLBACK);
+
+       if (code2 == CM_ERROR_NOACCESS) {
+           osi_Log0(afsd_logp, "cm_LockCheckPerms user has no INSERT bits");
+        } else {
+            *phas_insert = TRUE;
+            osi_Log0(afsd_logp, "cm_LockCheckPerms user has INSERT bits");
+        }
+    }
+
+    cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
+
+    osi_Log1(afsd_logp, "cm_LockCheckPerms returning code %d", code);
+
+    return code;
+}
+
 /* called with scp->mx held */
 long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
              LARGE_INTEGER LOffset, LARGE_INTEGER LLength,
@@ -3302,14 +4165,11 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
 {
     long code = 0;
     int Which = ((sLockType & LOCKING_ANDX_SHARED_LOCK) ? LockRead : LockWrite);
-    AFSFid tfid;
-    AFSVolSync volSync;
-    cm_conn_t *connp;
     cm_file_lock_t *fileLock;
     osi_queue_t *q;
-    struct rx_connection * callp;
     cm_range_t range;
     int wait_unlock = FALSE;
+    int force_client_lock = FALSE;
 
     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);
@@ -3343,11 +4203,11 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
 
     lock_ObtainRead(&cm_scacheLock);
 
-    for(q = scp->fileLocksH; q; q = osi_QNext(q)) {
+    for (q = scp->fileLocksH; q; q = osi_QNext(q)) {
         fileLock =
             (cm_file_lock_t *)((char *) q - offsetof(cm_file_lock_t, fileq));
 
-        if(IS_LOCK_LOST(fileLock)) {
+        if (IS_LOCK_LOST(fileLock)) {
             if (fileLock->key == key) {
                 code = CM_ERROR_BADFD;
                 break;
@@ -3360,10 +4220,10 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
 
         /* we don't need to check for deleted locks here since deleted
            locks are dequeued from scp->fileLocks */
-        if(IS_LOCK_ACCEPTED(fileLock) &&
+        if (IS_LOCK_ACCEPTED(fileLock) &&
            INTERSECT_RANGE(range, fileLock->range)) {
 
-            if((sLockType & LOCKING_ANDX_SHARED_LOCK) == 0 ||
+            if ((sLockType & LOCKING_ANDX_SHARED_LOCK) == 0 ||
                 fileLock->lockType != LockRead) {
                 wait_unlock = TRUE;
                 code = CM_ERROR_WOULDBLOCK;
@@ -3374,66 +4234,101 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
 
     lock_ReleaseRead(&cm_scacheLock);
 
-    if(code == 0 && SERVERLOCKS_ENABLED(scp)) {
-        if(Which == scp->serverLock ||
+    if (code == 0 && SERVERLOCKS_ENABLED(scp)) {
+        if (Which == scp->serverLock ||
            (Which == LockRead && scp->serverLock == LockWrite)) {
 
+            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]", 
                      scp->exclusiveLocks, scp->sharedLocks, (int)(signed char) scp->serverLock);
-            code = 0; /* redundant */
 
-        } else if((scp->exclusiveLocks > 0) ||
-                (scp->sharedLocks > 0 && scp->serverLock != LockRead)) {
+            code = cm_LockCheckPerms(scp, Which, userp, reqp, &has_insert);
+
+            /* special case: if we don't have permission to read-lock
+               the file, then we force a clientside lock.  This is to
+               compensate for applications that obtain a read-lock for
+               reading files off of directories that don't grant
+               read-locks to the user. */
+            if (code == CM_ERROR_NOACCESS && Which == LockRead) {
+
+                if (has_insert && SCP_SUPPORTS_WRITELOCKACL(scp)) {
+                    osi_Log0(afsd_logp, "   User has no read-lock perms, but has INSERT perms.");
+                    code = 0;
+                } else {
+                    osi_Log0(afsd_logp, "   User has no read-lock perms. Forcing client-side lock");
+                    force_client_lock = TRUE;
+                }
+            }
+
+        } else if ((scp->exclusiveLocks > 0) ||
+                   (scp->sharedLocks > 0 && scp->serverLock != LockRead)) {
+            int has_insert = 0;
 
             /* We are already waiting for some other lock.  We should
                wait for the daemon to catch up instead of generating a
                flood of SetLock calls. */
             osi_Log3(afsd_logp, "   already waiting for other lock. exclusives[%d], shared[%d], serverLock[%d]",
                      scp->exclusiveLocks, scp->sharedLocks, (int)(signed char) scp->serverLock);
-            code = CM_ERROR_WOULDBLOCK;
 
-        } else {
-            cm_fid_t cfid;
-            int newLock;
-
-            if (scp->serverLock == LockRead && Which == LockWrite) {
-            
-                /* We want to escalate the lock to a LockWrite.
-                   Unfortunately that's not really possible without
-                   letting go of the current lock.  But for now we do
-                   it anyway. */
+            /* see if we have permission to create the lock in the
+               first place. */
+            code = cm_LockCheckPerms(scp, Which, userp, reqp, &has_insert);
+            if (code == 0)
+               code = CM_ERROR_WOULDBLOCK;
+            else if (code == CM_ERROR_NOACCESS && Which == LockRead) {
 
-                osi_Log0(afsd_logp, "   attempting to UPGRADE from LockRead to LockWrite.");
+                if (has_insert && SCP_SUPPORTS_WRITELOCKACL(scp)) {
+                    osi_Log0(afsd_logp,
+                             "   User has no read-lock perms, but has INSERT perms.");
+                    code = CM_ERROR_WOULDBLOCK;
+                } else {
+                    osi_Log0(afsd_logp,
+                             "   User has no read-lock perms. Forcing client-side lock");
+                    force_client_lock = TRUE;
+                }
+            }
 
-                tfid.Volume = scp->fid.volume;
-                tfid.Vnode = scp->fid.vnode;
-                tfid.Unique = scp->fid.unique;
-                cfid = scp->fid;
+            /* leave any other codes as-is */
 
-                lock_ReleaseMutex(&scp->mx);
-
-                osi_Log1(afsd_logp, "CALL ReleaseLock scp 0x%p", scp);
+        } else {
+            int newLock;
+            int check_data_version = FALSE;
+            int has_insert = 0;
 
-                do {
-                    code = cm_Conn(&cfid, userp, reqp, &connp);
-                    if (code) 
-                        break;
+            /* first check if we have permission to elevate or obtain
+               the lock. */
+            code = cm_LockCheckPerms(scp, Which, userp, reqp, &has_insert);
+            if (code) {
+                if (code == CM_ERROR_NOACCESS && Which == LockRead &&
+                    (!has_insert || !SCP_SUPPORTS_WRITELOCKACL(scp))) {
+                    osi_Log0(afsd_logp, "   User has no read-lock perms.  Forcing client-side lock");
+                    force_client_lock = TRUE;
+                }
+                goto check_code;
+            }
 
-                    callp = cm_GetRxConn(connp);
-                    code = RXAFS_ReleaseLock(callp, &tfid, &volSync);
-                    rx_PutConnection(callp);
+            /* has_insert => (Which == LockRead, code == CM_ERROR_NOACCESS) */
 
-                } while (cm_Analyze(connp, userp, reqp, &cfid, &volSync,
-                                    NULL, NULL, code));
-                code = cm_MapRPCError(code, reqp);
+            if (scp->serverLock == LockRead && Which == LockWrite) {
 
-                if (code)
-                    osi_Log1(afsd_logp, "CALL ReleaseLock FAILURE, code 0x%x", code);
-                else
-                    osi_Log0(afsd_logp, "CALL ReleaseLock SUCCESS");
+                /* We want to escalate the lock to a LockWrite.
+                 * Unfortunately that's not really possible without
+                 * letting go of the current lock.  But for now we do
+                 * it anyway. */
+
+                osi_Log0(afsd_logp,
+                         "   attempting to UPGRADE from LockRead to LockWrite.");
+                osi_Log1(afsd_logp,
+                         "   dataVersion on scp: %I64d", scp->dataVersion);
+
+                /* we assume at this point (because scp->serverLock
+                   was valid) that we had a valid server lock. */
+                scp->lockDataVersion = scp->dataVersion;
+                check_data_version = TRUE;
         
-                lock_ObtainMutex(&scp->mx);
+                code = cm_IntReleaseLock(scp, userp, reqp);
 
                 if (code) {
                     /* We couldn't release the lock */
@@ -3444,84 +4339,100 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
             }
 
             /* We need to obtain a server lock of type Which in order
-               to assert this file lock */
-            tfid.Volume = scp->fid.volume;
-            tfid.Vnode = scp->fid.vnode;
-            tfid.Unique = scp->fid.unique;
-            cfid = scp->fid;
-
+             * to assert this file lock */
 #ifndef AGGRESSIVE_LOCKS
             newLock = Which;
 #else
             newLock = LockWrite;
 #endif
-            osi_Log3(afsd_logp, "CALL SetLock scp 0x%p from %d to %d", scp, (int) scp->serverLock, newLock);
 
-            lock_ReleaseMutex(&scp->mx);
+            code = cm_IntSetLock(scp, userp, newLock, reqp);
 
-            do {
-                code = cm_Conn(&cfid, userp, reqp, &connp);
-                if (code) 
-                    break;
-
-                callp = cm_GetRxConn(connp);
-                code = RXAFS_SetLock(callp, &tfid, newLock,
-                                     &volSync);
-                rx_PutConnection(callp);
-
-            } while (cm_Analyze(connp, userp, reqp, &cfid, &volSync,
-                                NULL, NULL, code));
-
-            code = cm_MapRPCError(code, reqp);
-            
-            if (code) {
-                osi_Log1(afsd_logp, "CALL SetLock FAILURE, code 0x%x", code);
-            } else {
-                osi_Log0(afsd_logp, "CALL SetLock SUCCESS");
-            }
-
-            if (code == CM_ERROR_WOULDBLOCK && newLock != Which) {
-                /* we wanted LockRead.  We tried LockWrite. Now try LockRead again */
+#ifdef AGGRESSIVE_LOCKS
+            if ((code == CM_ERROR_WOULDBLOCK ||
+                 code == CM_ERROR_NOACCESS) && newLock != Which) {
+                /* we wanted LockRead.  We tried LockWrite. Now try
+                 * LockRead again */
                 newLock = Which;
 
                 /* am I sane? */
-                osi_assert(newLock == LockRead);
-                
-                osi_Log3(afsd_logp, "CALL SetLock AGAIN scp 0x%p from %d to %d", 
-                         scp, (int) scp->serverLock, newLock);
+                osi_assertx(newLock == LockRead, "lock type not read");
 
-                do {
-                    code = cm_Conn(&cfid, userp, reqp, &connp);
-                    if (code) 
-                        break;
+                code = cm_IntSetLock(scp, userp, newLock, reqp);
+            }
+#endif
 
-                    callp = cm_GetRxConn(connp);
-                    code = RXAFS_SetLock(callp, &tfid, newLock,
-                                         &volSync);
-                    rx_PutConnection(callp);
+            if (code == CM_ERROR_NOACCESS) {
+                if (Which == LockRead) {
+                    if (has_insert && SCP_SUPPORTS_WRITELOCKACL(scp)) {
+                        long tcode;
+                        /* We requested a read-lock, but we have permission to
+                         * get a write-lock. Try that */
 
-                } while (cm_Analyze(connp, userp, reqp, &cfid, &volSync,
-                                    NULL, NULL, code));
+                        tcode = cm_LockCheckPerms(scp, LockWrite, userp, reqp, NULL);
 
-                code = cm_MapRPCError(code, reqp);                
+                        if (tcode == 0) {
+                            newLock = LockWrite;
 
-                if (code) {
-                    osi_Log1(afsd_logp, "CALL SetLock FAILURE AGAIN, code 0x%x", code);
-                } else {
-                    osi_Log0(afsd_logp, "CALL SetLock SUCCESS");
+                            osi_Log0(afsd_logp, "   User has 'i' perms and the request was for a LockRead.  Trying to get a LockWrite instead");
+
+                            code = cm_IntSetLock(scp, userp, newLock, reqp);
+                        }
+                    } else {
+                        osi_Log0(afsd_logp, "   User has no read-lock perms.  Forcing client-side lock");
+                        force_client_lock = TRUE;
+                    }
+                } else if (Which == LockWrite &&
+                           scp->creator == userp && !SCP_SUPPORTS_WRITELOCKACL(scp)) {
+                    long tcode;
+
+                    /* Special case: if the lock request was for a
+                     * LockWrite and the user owns the file and we weren't
+                     * allowed to obtain the serverlock, we either lost a
+                     * race (the permissions changed from under us), or we
+                     * have 'i' bits, but we aren't allowed to lock the
+                     * file. */
+
+                    /* check if we lost a race... */
+                    tcode = cm_LockCheckPerms(scp, Which, userp, reqp, NULL);
+
+                    if (tcode == 0) {
+                        osi_Log0(afsd_logp, "   User has 'i' perms but can't obtain write locks. Using client-side locks.");
+                        force_client_lock = TRUE;
+                    }
                 }
             }
 
-            lock_ObtainMutex(&scp->mx);
+            if (code == 0 && check_data_version &&
+               scp->dataVersion != scp->lockDataVersion) {
+                /* We lost a race.  Although we successfully obtained
+                 * a lock, someone modified the file in between.  The
+                 * locks have all been technically lost. */
+
+                osi_Log0(afsd_logp,
+                         "  Data version mismatch while upgrading lock.");
+                osi_Log2(afsd_logp,
+                         "  Data versions before=%I64d, after=%I64d",
+                         scp->lockDataVersion,
+                         scp->dataVersion);
+                osi_Log1(afsd_logp,
+                         "  Releasing stale lock for scp 0x%x", scp);
 
-            if(code == 0)
+                code = cm_IntReleaseLock(scp, userp, reqp);
+
+                scp->serverLock = -1;
+
+                code = CM_ERROR_INVAL;
+            } else if (code == 0) {
                 scp->serverLock = newLock;
-            else {
-                if ((scp->sharedLocks > 0 || scp->exclusiveLocks > 0) &&
-                    scp->serverLock == -1) {
-                    /* Oops. We lost the lock. */
-                    cm_LockMarkSCacheLost(scp);
-                }
+                scp->lockDataVersion = scp->dataVersion;
+            }
+
+            if (code != 0 &&
+                (scp->sharedLocks > 0 || scp->exclusiveLocks > 0) &&
+                scp->serverLock == -1) {
+                /* Oops. We lost the lock. */
+                cm_LockMarkSCacheLost(scp);
             }
         }
     } else if (code == 0) {     /* server locks not enabled */
@@ -3531,7 +4442,7 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
 
  check_code:
 
-    if (code != 0) {
+    if (code != 0 && !force_client_lock) {
         /* Special case error translations
 
            Applications don't expect certain errors from a
@@ -3546,7 +4457,13 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
         }
     }
 
-    if (code == 0 || (code == CM_ERROR_WOULDBLOCK && allowWait)) {
+    if (code == 0 || (code == CM_ERROR_WOULDBLOCK && allowWait) ||
+        force_client_lock) {
+
+        /* clear the error if we are forcing a client lock, so we
+           don't get confused later. */
+        if (force_client_lock && code != CM_ERROR_WOULDBLOCK)
+            code = 0;
 
         lock_ObtainWrite(&cm_scacheLock);
         fileLock = cm_GetFileLock();
@@ -3564,14 +4481,13 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
                             CM_FILELOCK_FLAG_WAITUNLOCK :
                             CM_FILELOCK_FLAG_WAITLOCK));
 
-        if (!SERVERLOCKS_ENABLED(scp))
+        if (force_client_lock || !SERVERLOCKS_ENABLED(scp))
             fileLock->flags |= CM_FILELOCK_FLAG_CLIENTONLY;
 
-        fileLock->lastUpdate = (code == 0) ? time(NULL) : 0;
-
-        osi_QAddT(&scp->fileLocksH, &scp->fileLocksT, &fileLock->fileq);
+        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;
         osi_QAdd(&cm_allFileLocks, &fileLock->q);
@@ -3581,16 +4497,25 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
             *lockpp = fileLock;
         }
 
-        if (IS_LOCK_ACCEPTED(fileLock)) {
-            if(Which == LockRead)
+        if (IS_LOCK_CLIENTONLY(fileLock)) {
+            scp->clientLocks++;
+        } else if (IS_LOCK_ACCEPTED(fileLock)) {
+            if (Which == LockRead)
                 scp->sharedLocks++;
             else
                 scp->exclusiveLocks++;
         }
 
-        osi_Log2(afsd_logp, "cm_Lock Lock added 0x%p flags 0x%x", fileLock, fileLock->flags);
-        osi_Log4(afsd_logp, "   scp[0x%p] exclusives[%d] shared[%d] serverLock[%d]",
-                 scp, scp->exclusiveLocks, scp->sharedLocks, (int)(signed char) scp->serverLock);
+        osi_Log3(afsd_logp,
+                 "cm_Lock Lock added 0x%p flags 0x%x to scp [0x%p]",
+                 fileLock, fileLock->flags, scp);
+        osi_Log4(afsd_logp,
+                 "   exclusives[%d] shared[%d] client[%d] serverLock[%d]",
+                 scp->exclusiveLocks, scp->sharedLocks, scp->clientLocks,
+                 (int)(signed char) scp->serverLock);
+    } else {
+        osi_Log1(afsd_logp,
+                 "cm_Lock Rejecting lock (code = 0x%x)", code);
     }
 
     return code;
@@ -3606,20 +4531,19 @@ long cm_UnlockByKey(cm_scache_t * scp,
                     cm_req_t * reqp)
 {
     long code = 0;
-    AFSFid tfid;
-    AFSVolSync volSync;
-    cm_conn_t *connp;
     cm_file_lock_t *fileLock;
     osi_queue_t *q, *qn;
-    struct rx_connection * callp;
     int n_unlocks = 0;
 
-    osi_Log3(afsd_logp, "cm_UnlockByKey scp 0x%p key 0x%x:%x",
-             scp, (unsigned long)(key >> 32), (unsigned long)(key & 0xffffffff));
+    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);
 
     lock_ObtainWrite(&cm_scacheLock);
 
-    for(q = scp->fileLocksH; q; q = qn) {
+    for (q = scp->fileLocksH; q; q = qn) {
         qn = osi_QNext(q);
 
         fileLock = (cm_file_lock_t *)
@@ -3627,14 +4551,16 @@ long cm_UnlockByKey(cm_scache_t * scp,
 
 #ifdef DEBUG
         osi_Log4(afsd_logp, "   Checking lock[0x%x] range[%d,+%d] type[%d]",
-                fileLock, (unsigned long) fileLock->range.offset, (unsigned long) fileLock->range.length,
+                 fileLock,
+                 (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),
                  fileLock->flags);
 
-        if(cm_FidCmp(&fileLock->fid, &fileLock->scp->fid)) {
+        if (cm_FidCmp(&fileLock->fid, &fileLock->scp->fid)) {
             osi_Log0(afsd_logp, "!!fileLock->fid != scp->fid");
             osi_Log4(afsd_logp, "  fileLock->fid(cell=[%d], volume=[%d], vnode=[%d], unique=[%d]",
                      fileLock->fid.cell,
@@ -3646,7 +4572,7 @@ long cm_UnlockByKey(cm_scache_t * scp,
                      fileLock->scp->fid.volume,
                      fileLock->scp->fid.vnode,
                      fileLock->scp->fid.unique);
-            osi_assert(FALSE);
+            osi_assertx(FALSE, "invalid fid value");
         }
 #endif
 
@@ -3659,10 +4585,12 @@ long cm_UnlockByKey(cm_scache_t * scp,
 
             if (scp->fileLocksT == q)
                 scp->fileLocksT = osi_QPrev(q);
-            osi_QRemove(&scp->fileLocksH,q);
+            osi_QRemoveHT(&scp->fileLocksH, &scp->fileLocksT, q);
 
-            if(IS_LOCK_ACCEPTED(fileLock)) {
-                if(fileLock->lockType == LockRead)
+            if (IS_LOCK_CLIENTONLY(fileLock)) {
+                scp->clientLocks--;
+            } else if (IS_LOCK_ACCEPTED(fileLock)) {
+                if (fileLock->lockType == LockRead)
                     scp->sharedLocks--;
                 else
                     scp->exclusiveLocks--;
@@ -3682,7 +4610,7 @@ long cm_UnlockByKey(cm_scache_t * scp,
 
     lock_ReleaseWrite(&cm_scacheLock);
 
-    if(n_unlocks == 0) {
+    if (n_unlocks == 0) {
         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);
@@ -3694,6 +4622,7 @@ long cm_UnlockByKey(cm_scache_t * scp,
 
     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");
@@ -3707,41 +4636,19 @@ long cm_UnlockByKey(cm_scache_t * scp,
      * in that manner is done cm_RetryLock() manually.
      */
 
-    if (scp->serverLock == LockWrite && scp->exclusiveLocks == 0 && scp->sharedLocks > 0) {
-
-        cm_fid_t cfid;
+    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");
 
-        tfid.Volume = scp->fid.volume;
-        tfid.Vnode = scp->fid.vnode;
-        tfid.Unique = scp->fid.unique;
-        cfid = scp->fid;
-
-        lock_ReleaseMutex(&scp->mx);
-
-        osi_Log1(afsd_logp, "CALL ReleaseLock scp 0x%p", scp);
-
-        do {
-            code = cm_Conn(&cfid, userp, reqp, &connp);
-            if (code) 
-                break;
-
-            callp = cm_GetRxConn(connp);
-            code = RXAFS_ReleaseLock(callp, &tfid, &volSync);
-            rx_PutConnection(callp);
-            
-        } while (cm_Analyze(connp, userp, reqp, &cfid, &volSync,
-                            NULL, NULL, code));
-        code = cm_MapRPCError(code, reqp);
+        /* 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);
 
-        if (code)
-            osi_Log1(afsd_logp, "CALL ReleaseLock FAILURE, code 0x%x", code);
-        else
-            osi_Log0(afsd_logp, "CALL ReleaseLock SUCCESS");
-        
-        lock_ObtainMutex(&scp->mx);
+        code = cm_IntReleaseLock(scp, userp, reqp);
 
         if (code) {
             /* so we couldn't release it.  Just let the lock be for now */
@@ -3751,85 +4658,43 @@ long cm_UnlockByKey(cm_scache_t * scp,
             scp->serverLock = -1;
         }
 
-        tfid.Volume = scp->fid.volume;
-        tfid.Vnode = scp->fid.vnode;
-        tfid.Unique = scp->fid.unique;
-        cfid = scp->fid;
-
-        osi_Log3(afsd_logp, "CALL SetLock scp 0x%p from %d to %d", scp, (int) scp->serverLock, LockRead);
-
-        lock_ReleaseMutex(&scp->mx);
-
-        do {
-
-            code = cm_Conn(&cfid, userp, reqp, &connp);
-            if (code) 
-                break;
+        code = cm_IntSetLock(scp, userp, LockRead, reqp);
 
-            callp = cm_GetRxConn(connp);
-            code = RXAFS_SetLock(callp, &tfid, LockRead,
-                                 &volSync);
-
-            rx_PutConnection(callp);
+        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);
             
-        } while (cm_Analyze(connp, userp, reqp, &cfid, &volSync,
-                            NULL, NULL, code));
+            code = cm_IntReleaseLock(scp, userp, reqp);
 
-        if (code)
-            osi_Log1(afsd_logp, "CALL SetLock FAILURE, code 0x%x", code);
-        else {
-            osi_Log0(afsd_logp, "CALL SetLock SUCCESS");
+            code = CM_ERROR_INVAL;
+            scp->serverLock = -1;
         }
 
-        lock_ObtainMutex(&scp->mx);
-        
-        if(code == 0)
-            scp->serverLock = LockRead;
-        else {
-            if ((scp->sharedLocks > 0 || scp->exclusiveLocks > 0) &&
+        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) {
-        cm_fid_t cfid;
-
+    } else if (scp->serverLock != (-1) &&
+              scp->exclusiveLocks == 0 &&
+              scp->sharedLocks == 0) {
         /* The serverLock should be released entirely */
 
-        tfid.Volume = scp->fid.volume;
-        tfid.Vnode = scp->fid.vnode;
-        tfid.Unique = scp->fid.unique;
-        cfid = scp->fid;
-
-        lock_ReleaseMutex(&scp->mx);
-
-        osi_Log1(afsd_logp, "CALL ReleaseLock scp 0x%p", scp);
-
-        do {
-            code = cm_Conn(&cfid, userp, reqp, &connp);
-            if (code) 
-                break;
-
-            callp = cm_GetRxConn(connp);
-            code = RXAFS_ReleaseLock(callp, &tfid, &volSync);
-            rx_PutConnection(callp);
-
-        } while (cm_Analyze(connp, userp, reqp, &cfid, &volSync,
-                            NULL, NULL, code));
-        code = cm_MapRPCError(code, reqp);
-
-        if (code)
-            osi_Log1(afsd_logp, "CALL ReleaseLock FAILURE, code 0x%x", code);
-        else
-            osi_Log0(afsd_logp, "CALL ReleaseLock SUCCESS");
-        
-        lock_ObtainMutex(&scp->mx);
+        code = cm_IntReleaseLock(scp, userp, reqp);
 
         if (code == 0)
             scp->serverLock = (-1);
@@ -3838,8 +4703,9 @@ long cm_UnlockByKey(cm_scache_t * scp,
  done:
 
     osi_Log1(afsd_logp, "cm_UnlockByKey code 0x%x", code);
-    osi_Log3(afsd_logp, "   Leaving scp with exclusives[%d], shared[%d], serverLock[%d]",
-             scp->exclusiveLocks, scp->sharedLocks, (int)(signed char) scp->serverLock);
+    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);
 
     return code;
 }
@@ -3853,13 +4719,9 @@ long cm_Unlock(cm_scache_t *scp,
 {
     long code = 0;
     int Which = ((sLockType & LOCKING_ANDX_SHARED_LOCK) ? LockRead : LockWrite);
-    AFSFid tfid;
-    AFSVolSync volSync;
-    cm_conn_t *connp;
     cm_file_lock_t *fileLock;
     osi_queue_t *q;
     int release_userp = FALSE;
-    struct rx_connection * callp;
 
     osi_Log4(afsd_logp, "cm_Unlock scp 0x%p type 0x%x offset %d length %d",
              scp, sLockType, (unsigned long)LOffset.QuadPart, (unsigned long)LLength.QuadPart);
@@ -3868,12 +4730,12 @@ long cm_Unlock(cm_scache_t *scp,
 
     lock_ObtainRead(&cm_scacheLock);
 
-    for(q = scp->fileLocksH; q; q = osi_QNext(q)) {
+    for (q = scp->fileLocksH; q; q = osi_QNext(q)) {
         fileLock = (cm_file_lock_t *)
             ((char *) q - offsetof(cm_file_lock_t, fileq));
 
 #ifdef DEBUG
-        if(cm_FidCmp(&fileLock->fid, &fileLock->scp->fid)) {
+        if (cm_FidCmp(&fileLock->fid, &fileLock->scp->fid)) {
             osi_Log0(afsd_logp, "!!fileLock->fid != scp->fid");
             osi_Log4(afsd_logp, "  fileLock->fid(cell=[%d], volume=[%d], vnode=[%d], unique=[%d]",
                      fileLock->fid.cell,
@@ -3885,7 +4747,7 @@ long cm_Unlock(cm_scache_t *scp,
                      fileLock->scp->fid.volume,
                      fileLock->scp->fid.vnode,
                      fileLock->scp->fid.unique);
-            osi_assert(FALSE);
+            osi_assertx(FALSE, "invalid fid value");
         }
 #endif
         if (!IS_LOCK_DELETED(fileLock) &&
@@ -3896,31 +4758,32 @@ long cm_Unlock(cm_scache_t *scp,
         }
     }
 
-    if(!q) {
+    if (!q) {
         osi_Log0(afsd_logp, "cm_Unlock lock not found; failure");
         
         lock_ReleaseRead(&cm_scacheLock);
 
         /* The lock didn't exist anyway. *shrug* */
-        return 0;
+        return CM_ERROR_RANGE_NOT_LOCKED;
     }
 
+    lock_ReleaseRead(&cm_scacheLock);
+
     /* discard lock record */
+    lock_ObtainWrite(&cm_scacheLock);
     if (scp->fileLocksT == q)
         scp->fileLocksT = osi_QPrev(q);
-    osi_QRemove(&scp->fileLocksH, q);
-
-    lock_ReleaseRead(&cm_scacheLock);
+    osi_QRemoveHT(&scp->fileLocksH, &scp->fileLocksT, q);
 
     /*
      * Don't delete it here; let the daemon delete it, to simplify
      * the daemon's traversal of the list.
      */
 
-    lock_ObtainWrite(&cm_scacheLock);
-
-    if(IS_LOCK_ACCEPTED(fileLock)) {
-        if(fileLock->lockType == LockRead)
+    if (IS_LOCK_CLIENTONLY(fileLock)) {
+        scp->clientLocks--;
+    } else if (IS_LOCK_ACCEPTED(fileLock)) {
+        if (fileLock->lockType == LockRead)
             scp->sharedLocks--;
         else
             scp->exclusiveLocks--;
@@ -3950,42 +4813,30 @@ long cm_Unlock(cm_scache_t *scp,
      * in that manner is done cm_RetryLock() manually.
      */
 
-    if (scp->serverLock == LockWrite && scp->exclusiveLocks == 0 && scp->sharedLocks > 0) {
-
-        cm_fid_t cfid;
+    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");
 
-        tfid.Volume = scp->fid.volume;
-        tfid.Vnode = scp->fid.vnode;
-        tfid.Unique = scp->fid.unique;
-        cfid = scp->fid;
-
-        lock_ReleaseMutex(&scp->mx);
-
-        osi_Log1(afsd_logp, "CALL ReleaseLock scp 0x%p", scp);
+        /* 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);
 
-        do {
-            code = cm_Conn(&cfid, userp, reqp, &connp);
-            if (code) 
-                break;
+        /* 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) {
 
-            callp = cm_GetRxConn(connp);
-            code = RXAFS_ReleaseLock(callp, &tfid, &volSync);
-            rx_PutConnection(callp);
-            
-        } while (cm_Analyze(connp, userp, reqp, &cfid, &volSync,
-                            NULL, NULL, code));
+            osi_Log0(afsd_logp, "  SKIPPING downgrade because user doesn't have perms to get downgraded lock");
 
-        code = cm_MapRPCError(code, reqp);
+            code = 0;
+            goto done;
+        }
 
-        if (code)
-            osi_Log1(afsd_logp, "CALL ReleaseLock FAILURE, code 0x%x", code);
-        else
-            osi_Log0(afsd_logp, "CALL ReleaseLock SUCCESS");
-        
-        lock_ObtainMutex(&scp->mx);
+        code = cm_IntReleaseLock(scp, userp, reqp);
 
         if (code) {
             /* so we couldn't release it.  Just let the lock be for now */
@@ -3995,85 +4846,45 @@ long cm_Unlock(cm_scache_t *scp,
             scp->serverLock = -1;
         }
 
-        tfid.Volume = scp->fid.volume;
-        tfid.Vnode = scp->fid.vnode;
-        tfid.Unique = scp->fid.unique;
-        cfid = scp->fid;
-
-        osi_Log3(afsd_logp, "CALL SetLock scp 0x%p from %d to %d", scp, (int) scp->serverLock, LockRead);
+        code = cm_IntSetLock(scp, userp, LockRead, reqp);
 
-        lock_ReleaseMutex(&scp->mx);
-
-        do {
-
-            code = cm_Conn(&cfid, userp, reqp, &connp);
-            if (code) 
-                break;
-
-            callp = cm_GetRxConn(connp);
-            code = RXAFS_SetLock(callp, &tfid, LockRead,
-                                 &volSync);
-
-            rx_PutConnection(callp);
+        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);
             
-        } while (cm_Analyze(connp, userp, reqp, &cfid, &volSync,
-                            NULL, NULL, code));
+            code = cm_IntReleaseLock(scp, userp, reqp);
 
-        if (code)
-            osi_Log1(afsd_logp, "CALL SetLock FAILURE, code 0x%x", code);
-        else {
-            osi_Log0(afsd_logp, "CALL SetLock SUCCESS");
+            scp->serverLock = -1;
+            code = CM_ERROR_INVAL;
         }
 
-        lock_ObtainMutex(&scp->mx);
-        
-        if(code == 0)
-            scp->serverLock = LockRead;
-        else {
-            if ((scp->sharedLocks > 0 || scp->exclusiveLocks > 0) &&
+        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) {
-        cm_fid_t cfid;
-
+    } else if (scp->serverLock != (-1) &&
+              scp->exclusiveLocks == 0 &&
+              scp->sharedLocks == 0) {
         /* The serverLock should be released entirely */
 
-        tfid.Volume = scp->fid.volume;
-        tfid.Vnode = scp->fid.vnode;
-        tfid.Unique = scp->fid.unique;
-        cfid = scp->fid;
-
-        lock_ReleaseMutex(&scp->mx);
-
-        osi_Log1(afsd_logp, "CALL ReleaseLock scp 0x%p", scp);
-
-        do {
-            code = cm_Conn(&cfid, userp, reqp, &connp);
-            if (code) 
-                break;
-
-            callp = cm_GetRxConn(connp);
-            code = RXAFS_ReleaseLock(callp, &tfid, &volSync);
-            rx_PutConnection(callp);
-
-        } while (cm_Analyze(connp, userp, reqp, &cfid, &volSync,
-                            NULL, NULL, code));
-        code = cm_MapRPCError(code, reqp);
-
-        if (code)
-            osi_Log1(afsd_logp, "CALL ReleaseLock FAILURE, code 0x%x", code);
-        else
-            osi_Log0(afsd_logp, "CALL ReleaseLock SUCCESS");
-        
-        lock_ObtainMutex(&scp->mx);
+        code = cm_IntReleaseLock(scp, userp, reqp);
 
         if (code == 0) {
             scp->serverLock = (-1);
@@ -4085,8 +4896,10 @@ long cm_Unlock(cm_scache_t *scp,
 
  done:
 
-    osi_Log4(afsd_logp, "cm_Unlock code 0x%x leaving scp with exclusives[%d], shared[%d], serverLock[%d]",
-             code, scp->exclusiveLocks, scp->sharedLocks, (int)(signed char) scp->serverLock);
+    osi_Log1(afsd_logp, "cm_Unlock 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);
 
     return code;
 }
@@ -4101,17 +4914,18 @@ static void cm_LockMarkSCacheLost(cm_scache_t * scp)
 
 #ifdef DEBUG
     /* With the current code, we can't lose a lock on a RO scp */
-    osi_assert(!(scp->flags & CM_SCACHEFLAG_RO));
+    osi_assertx(!(scp->flags & CM_SCACHEFLAG_RO), "CM_SCACHEFLAG_RO unexpected");
 #endif
 
     /* cm_scacheLock needed because we are modifying fileLock->flags */
     lock_ObtainWrite(&cm_scacheLock);
 
-    for(q = scp->fileLocksH; q; q = osi_QNext(q)) {
+    for (q = scp->fileLocksH; q; q = osi_QNext(q)) {
         fileLock = 
             (cm_file_lock_t *)((char *) q - offsetof(cm_file_lock_t, fileq));
 
-        if(IS_LOCK_ACTIVE(fileLock)) {
+        if (IS_LOCK_ACTIVE(fileLock) &&
+            !IS_LOCK_CLIENTONLY(fileLock)) {
             if (fileLock->lockType == LockRead)
                 scp->sharedLocks--;
             else
@@ -4122,6 +4936,7 @@ static void cm_LockMarkSCacheLost(cm_scache_t * scp)
     }
 
     scp->serverLock = -1;
+    scp->lockDataVersion = -1;
     lock_ReleaseWrite(&cm_scacheLock);
 }
 
@@ -4146,7 +4961,7 @@ void cm_CheckLocks()
 
     osi_Log1(afsd_logp, "cm_CheckLocks starting lock check cycle %d", cm_lockRefreshCycle);
 
-    for(q = cm_allFileLocks; q; q = nq) {
+    for (q = cm_allFileLocks; q; q = nq) {
         fileLock = (cm_file_lock_t *) q;
         nq = osi_QNext(q);
        code = -1;
@@ -4160,15 +4975,15 @@ void cm_CheckLocks()
 
             /* Server locks must have been enabled for us to have
                received an active non-client-only lock. */
-            //osi_assert(cm_enableServerLocks);
+            osi_assertx(cm_enableServerLocks, "!cm_enableServerLocks");
 
             scp = fileLock->scp;
-            osi_assert(scp != NULL);
+            osi_assertx(scp != NULL, "null cm_scache_t");
 
             cm_HoldSCacheNoLock(scp);
 
 #ifdef DEBUG
-            if(cm_FidCmp(&fileLock->fid, &fileLock->scp->fid)) {
+            if (cm_FidCmp(&fileLock->fid, &fileLock->scp->fid)) {
                 osi_Log0(afsd_logp, "!!fileLock->fid != scp->fid");
                 osi_Log4(afsd_logp, "  fileLock->fid(cell=[%d], volume=[%d], vnode=[%d], unique=[%d]",
                          fileLock->fid.cell,
@@ -4180,7 +4995,7 @@ void cm_CheckLocks()
                          fileLock->scp->fid.volume,
                          fileLock->scp->fid.vnode,
                          fileLock->scp->fid.unique);
-                osi_assert(FALSE);
+                osi_assertx(FALSE, "invalid fid");
             }
 #endif
             /* Server locks are extended once per scp per refresh
@@ -4204,7 +5019,8 @@ void cm_CheckLocks()
                                  | CM_SCACHESYNC_LOCK);
 
                 if (code) {
-                    osi_Log1(smb_logp, "cm_CheckLocks SyncOp failure code 0x%x", code);
+                    osi_Log1(smb_logp,
+                             "cm_CheckLocks SyncOp failure code 0x%x", code);
                     goto post_syncopdone;
                 }
 
@@ -4213,7 +5029,7 @@ void cm_CheckLocks()
                 if (!IS_LOCK_ACTIVE(fileLock))
                     goto pre_syncopdone;
 
-                if(scp->serverLock != -1) {
+                if (scp->serverLock != -1) {
                     cm_fid_t cfid;
                     cm_user_t * userp;
 
@@ -4231,7 +5047,7 @@ void cm_CheckLocks()
                     lock_ReleaseMutex(&scp->mx);
 
                     do {
-                        code = cm_Conn(&cfid, userp,
+                        code = cm_ConnFromFID(&cfid, userp,
                                        &req, &connp);
                         if (code) 
                             break;
@@ -4253,11 +5069,47 @@ void cm_CheckLocks()
 
                     if (code) {
                         osi_Log1(afsd_logp, "CALL ExtendLock FAILURE, code 0x%x", code);
-                       if (code == EINVAL || code == CM_ERROR_INVAL)
-                           cm_LockMarkSCacheLost(scp);
                     } else {
                         osi_Log0(afsd_logp, "CALL ExtendLock SUCCESS");
+                        scp->lockDataVersion = scp->dataVersion;
+                    }
+
+                    if ((code == EINVAL || code == CM_ERROR_INVAL) &&
+                        scp->lockDataVersion == scp->dataVersion) {
+                        int lockType;
+
+                        lockType =
+                            (scp->exclusiveLocks > 0) ? LockWrite: LockRead;
+
+                        /* we might still have a chance to obtain a
+                           new lock */
+
+                        code = cm_IntSetLock(scp, userp, lockType, &req);
+
+                        if (code) {
+                            code = CM_ERROR_INVAL;
+                        } else if (scp->lockDataVersion != scp->dataVersion) {
+
+                            /* now check if we still have the file at
+                               the right data version. */
+                            osi_Log1(afsd_logp,
+                                     "Data version mismatch on scp 0x%p",
+                                     scp);
+                            osi_Log2(afsd_logp,
+                                     "   Data versions: before=%I64d, after=%I64d",
+                                     scp->lockDataVersion,
+                                     scp->dataVersion);
+
+                            code = cm_IntReleaseLock(scp, userp, &req);
+
+                            code = CM_ERROR_INVAL;
+                        }
+                    }
+
+                    if (code == EINVAL || code == CM_ERROR_INVAL) {
+                        cm_LockMarkSCacheLost(scp);
                     }
+
                 } else {
                     /* interestingly, we have found an active lock
                        belonging to an scache that has no
@@ -4304,14 +5156,13 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
 {
     long code = 0;
     cm_scache_t *scp = NULL;
-    AFSFid tfid;
-    AFSVolSync volSync;
-    cm_conn_t *connp;
     cm_file_lock_t *fileLock;
     osi_queue_t *q;
     cm_req_t req;
-    struct rx_connection * callp;
     int newLock = -1;
+    int force_client_lock = FALSE;
+    int has_insert = FALSE;
+    int check_data_version = FALSE;
 
     cm_InitReq(&req);
 
@@ -4334,14 +5185,14 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
              (unsigned)(oldFileLock->flags));
 
     /* if the lock has already been granted, then we have nothing to do */
-    if(IS_LOCK_ACTIVE(oldFileLock)) {
+    if (IS_LOCK_ACTIVE(oldFileLock)) {
         lock_ReleaseRead(&cm_scacheLock);
         osi_Log0(afsd_logp, "cm_RetryLock lock already granted");
         return 0;
     }
 
     /* we can't do anything with lost or deleted locks at the moment. */
-    if(IS_LOCK_LOST(oldFileLock) || IS_LOCK_DELETED(oldFileLock)) {
+    if (IS_LOCK_LOST(oldFileLock) || IS_LOCK_DELETED(oldFileLock)) {
         code = CM_ERROR_BADFD;
         osi_Log0(afsd_logp, "cm_RetryLock lock is lost or deleted");
         lock_ReleaseRead(&cm_scacheLock);
@@ -4350,18 +5201,33 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
 
     scp = oldFileLock->scp;
 
-    osi_assert(scp != NULL);
+    osi_assertx(scp != NULL, "null cm_scache_t");
 
     lock_ReleaseRead(&cm_scacheLock);
     lock_ObtainMutex(&scp->mx);
+
+    code = cm_LockCheckPerms(scp, oldFileLock->lockType,
+                             oldFileLock->userp,
+                             &req, &has_insert);
+
+    if (code == CM_ERROR_NOACCESS && oldFileLock->lockType == LockRead) {
+        if (!has_insert || !SCP_SUPPORTS_WRITELOCKACL(scp)) {
+        force_client_lock = TRUE;
+        }
+        code = 0;
+    } else if (code) {
+        lock_ReleaseMutex(&scp->mx);
+        return code;
+    }
+
     lock_ObtainWrite(&cm_scacheLock);
 
     /* Check if we already have a sufficient server lock to allow this
-       lock to go through */
-    if(IS_LOCK_WAITLOCK(oldFileLock) &&
-       (!SERVERLOCKS_ENABLED(scp) ||
-        scp->serverLock == oldFileLock->lockType ||
-        scp->serverLock == LockWrite)) {
+       lock to go through. */
+    if (IS_LOCK_WAITLOCK(oldFileLock) &&
+        (!SERVERLOCKS_ENABLED(scp) ||
+         scp->serverLock == oldFileLock->lockType ||
+         scp->serverLock == LockWrite)) {
 
         oldFileLock->flags &= ~CM_FILELOCK_FLAG_WAITLOCK;
 
@@ -4378,16 +5244,15 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
         return 0;
     }
 
-    if(IS_LOCK_WAITUNLOCK(oldFileLock)) {
+    if (IS_LOCK_WAITUNLOCK(oldFileLock)) {
 
         /* check if the conflicting locks have dissappeared already */
-
-        for(q = scp->fileLocksH; q; q = osi_QNext(q)) {
+        for (q = scp->fileLocksH; q; q = osi_QNext(q)) {
 
             fileLock = (cm_file_lock_t *)
                 ((char *) q - offsetof(cm_file_lock_t, fileq));
 
-            if(IS_LOCK_LOST(fileLock)) {
+            if (IS_LOCK_LOST(fileLock)) {
                 if (fileLock->key == oldFileLock->key) {
                     code = CM_ERROR_BADFD;
                     oldFileLock->flags |= CM_FILELOCK_FLAG_LOST;
@@ -4402,10 +5267,10 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
                 }
             }
 
-            if(IS_LOCK_ACCEPTED(fileLock) &&
-               INTERSECT_RANGE(oldFileLock->range, fileLock->range)) {
+            if (IS_LOCK_ACCEPTED(fileLock) &&
+                INTERSECT_RANGE(oldFileLock->range, fileLock->range)) {
 
-                if(oldFileLock->lockType != LockRead ||
+                if (oldFileLock->lockType != LockRead ||
                    fileLock->lockType != LockRead) {
 
                     osi_Log1(afsd_logp, "    found conflicting lock %p", fileLock);
@@ -4442,20 +5307,36 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
         oldFileLock->flags |= CM_FILELOCK_FLAG_WAITLOCK;
     }
 
-    if (!SERVERLOCKS_ENABLED(scp) ||
+    osi_assertx(IS_LOCK_WAITLOCK(oldFileLock), "!IS_LOCK_WAITLOCK");
+
+    if (force_client_lock ||
+        !SERVERLOCKS_ENABLED(scp) ||
         scp->serverLock == oldFileLock->lockType ||
         (oldFileLock->lockType == LockRead &&
          scp->serverLock == LockWrite)) {
 
         oldFileLock->flags &= ~CM_FILELOCK_FLAG_WAITLOCK;
 
+        if ((force_client_lock ||
+             !SERVERLOCKS_ENABLED(scp)) &&
+            !IS_LOCK_CLIENTONLY(oldFileLock)) {
+
+            oldFileLock->flags |= CM_FILELOCK_FLAG_CLIENTONLY;
+
+            if (oldFileLock->lockType == LockRead)
+                scp->sharedLocks--;
+            else
+                scp->exclusiveLocks--;
+
+            scp->clientLocks++;
+        }
+
         lock_ReleaseWrite(&cm_scacheLock);
         lock_ReleaseMutex(&scp->mx);
 
         return 0;
 
     } else {
-        cm_fid_t cfid;
         cm_user_t * userp;
 
         code = cm_SyncOp(scp, NULL, oldFileLock->userp, &req, 0,
@@ -4468,13 +5349,9 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
             goto post_syncopdone;
         }
 
-        if(!IS_LOCK_WAITLOCK(oldFileLock))
+        if (!IS_LOCK_WAITLOCK(oldFileLock))
             goto pre_syncopdone;
 
-        tfid.Volume = scp->fid.volume;
-        tfid.Vnode = scp->fid.vnode;
-        tfid.Unique = scp->fid.unique;
-        cfid = scp->fid;
         userp = oldFileLock->userp;
 
 #ifndef AGGRESSIVE_LOCKS
@@ -4483,33 +5360,67 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
         newLock = LockWrite;
 #endif
 
-        osi_Log1(afsd_logp, "CALL SetLock lock 0x%p", oldFileLock);
+        if (has_insert) {
+            /* if has_insert is non-zero, then:
+               - the lock a LockRead
+               - we don't have permission to get a LockRead
+               - we do have permission to get a LockWrite
+               - the server supports VICED_CAPABILITY_WRITELOCKACL
+            */
+
+            newLock = LockWrite;
+        }
 
         lock_ReleaseWrite(&cm_scacheLock);
-        lock_ReleaseMutex(&scp->mx);
 
-        do {
-            code = cm_Conn(&cfid, userp, &req, &connp);
-            if (code) 
-                break;
+        /* when we get here, either we have a read-lock and want a
+           write-lock or we don't have any locks and we want some
+           lock. */
 
-            callp = cm_GetRxConn(connp);
-            code = RXAFS_SetLock(callp, &tfid, newLock,
-                                  &volSync);
-            rx_PutConnection(callp);
+        if (scp->serverLock == LockRead) {
 
-        } while (cm_Analyze(connp, userp, &req,
-                             &cfid, &volSync,
-                             NULL, NULL, code));
-        code = cm_MapRPCError(code, &req);
+            osi_assertx(newLock == LockWrite, "!LockWrite");
 
-        if (code) {
-            osi_Log1(afsd_logp, "CALL SetLock FAILURE, code 0x%x", code);
-        } else {
-            osi_Log0(afsd_logp, "CALL SetLock SUCCESS");
+            osi_Log0(afsd_logp, "  Attempting to UPGRADE from LockRead to LockWrite");
+
+            scp->lockDataVersion = scp->dataVersion;
+            check_data_version = TRUE;
+
+            code = cm_IntReleaseLock(scp, userp, &req);
+
+            if (code)
+                goto pre_syncopdone;
+            else
+                scp->serverLock = -1;
+        }
+
+        code = cm_IntSetLock(scp, userp, newLock, &req);
+
+        if (code == 0) {
+            if (scp->dataVersion != scp->lockDataVersion) {
+                /* we lost a race.  too bad */
+
+                osi_Log0(afsd_logp,
+                         "  Data version mismatch while upgrading lock.");
+                osi_Log2(afsd_logp,
+                         "  Data versions before=%I64d, after=%I64d",
+                         scp->lockDataVersion,
+                         scp->dataVersion);
+                osi_Log1(afsd_logp,
+                         "  Releasing stale lock for scp 0x%x", scp);
+
+                code = cm_IntReleaseLock(scp, userp, &req);
+
+                scp->serverLock = -1;
+
+                code = CM_ERROR_INVAL;
+
+                cm_LockMarkSCacheLost(scp);
+            } else {
+                scp->serverLock = newLock;
+            }
         }
 
-        lock_ObtainMutex(&scp->mx);
     pre_syncopdone:
         cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_LOCK);
     post_syncopdone:
@@ -4518,11 +5429,11 @@ 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_QRemove(&scp->fileLocksH, &oldFileLock->fileq);
-    } else if (code == 0 && IS_LOCK_WAITLOCK(oldFileLock)) {
-        scp->serverLock = newLock;
+        osi_QRemoveHT(&scp->fileLocksH, &scp->fileLocksT, &oldFileLock->fileq);
+       lock_ReleaseWrite(&cm_scacheLock);
     }
     lock_ReleaseMutex(&scp->mx);
 
@@ -4546,9 +5457,16 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
 
 cm_key_t cm_GenerateKey(unsigned int session_id, unsigned long process_id, unsigned int file_id)
 {
-    return (((cm_key_t) process_id) << 32) |
-        (((cm_key_t) session_id) << 16) |
-        (((cm_key_t) 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
+
+    return 
+        (((cm_key_t) (process_id & 0xffffffff)) << 32) |
+        (((cm_key_t) (session_id & 0xffff)) << 16) |
+        (((cm_key_t) (file_id & 0xffff)));
 }
 
 static int cm_KeyEquals(cm_key_t k1, cm_key_t k2, int flags)
@@ -4567,11 +5485,11 @@ void cm_ReleaseAllLocks(void)
     cm_user_t *userp;
     cm_key_t   key;
     cm_file_lock_t *fileLock;
-    int i;
+    unsigned int i;
 
-    for (i = 0; i < cm_data.hashTableSize; i++)
+    for (i = 0; i < cm_data.scacheHashTableSize; i++)
     {
-       for ( scp = cm_data.hashTablep[i]; scp; scp = scp->nextp ) {
+       for ( scp = cm_data.scacheHashTablep[i]; scp; scp = scp->nextp ) {
            while (scp->fileLocksH != NULL) {
                lock_ObtainMutex(&scp->mx);
                lock_ObtainWrite(&cm_scacheLock);