windows-dfs-referrals-20080212
[openafs.git] / src / WINNT / afsd / cm_vnodeops.c
index 65b3b22..c3464b1 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,8 +255,10 @@ 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);
 
@@ -332,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;
@@ -344,10 +347,13 @@ 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);
@@ -391,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
@@ -425,6 +442,19 @@ long cm_CheckNTOpen(cm_scache_t *scp, unsigned int desiredAccess,
     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:
@@ -444,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;
 
@@ -489,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;
     }
@@ -542,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;
@@ -560,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;
-    }
     }  
 
     /*
@@ -601,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)
@@ -664,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;
             }
@@ -695,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);
@@ -904,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;
@@ -955,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.
  */
@@ -967,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;
@@ -998,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) {
@@ -1014,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;
@@ -1031,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) {
@@ -1046,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;
@@ -1073,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;
@@ -1088,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;
@@ -1133,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 */
@@ -1184,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)
@@ -1250,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)
@@ -1265,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 */
@@ -1298,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) {
@@ -1308,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;
@@ -1322,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;
 
@@ -1339,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;
 }
 
@@ -1375,6 +1709,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;
 
@@ -1399,6 +1735,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 */
@@ -1421,9 +1760,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
@@ -1460,13 +1802,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   
@@ -1482,33 +1823,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 */
@@ -1522,6 +1873,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) {
@@ -1536,7 +1898,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++;
@@ -1571,34 +1934,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;
                 }
@@ -1613,25 +2012,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) 
@@ -1639,13 +2041,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;
                     }
@@ -1664,7 +2085,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
@@ -1677,7 +2098,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 */
@@ -1690,7 +2111,7 @@ long cm_NameI(cm_scache_t *rootSCachep, char *pathp, long flags,
                         }
                         if (dirScp) {
                             cm_ReleaseSCache(dirScp);
-                            dirScp = 0;
+                            dirScp = NULL;
                         }
                         code = 0;
                         break;
@@ -1698,7 +2119,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 
@@ -1715,6 +2136,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;
 }
 
@@ -1758,7 +2184,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,
@@ -1766,6 +2192,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;
 }
 
@@ -1773,7 +2205,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 {
@@ -1833,7 +2265,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.
@@ -1871,11 +2303,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;
@@ -1890,13 +2323,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);
@@ -1906,7 +2340,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;
@@ -1928,23 +2362,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.
@@ -1979,7 +2424,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
@@ -1987,8 +2432,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);
@@ -2000,7 +2444,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)
@@ -2061,7 +2515,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;
@@ -2091,6 +2546,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;
 
@@ -2120,6 +2584,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);
@@ -2132,7 +2600,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;
@@ -2144,11 +2611,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);
@@ -2156,15 +2625,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;
 
@@ -2185,13 +2651,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;
 }       
@@ -2205,7 +2672,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;
@@ -2213,6 +2680,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.
@@ -2221,16 +2689,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;
     }
@@ -2241,7 +2722,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;
 
@@ -2265,10 +2746,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);
 
@@ -2287,7 +2772,7 @@ long cm_Create(cm_scache_t *dscp, char *namep, long flags, cm_attr_t *attrp,
             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);
@@ -2302,6 +2787,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;
 }       
 
@@ -2314,12 +2807,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;
@@ -2334,7 +2832,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;
@@ -2342,6 +2840,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.
@@ -2350,16 +2849,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;
     }
@@ -2370,7 +2882,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;
 
@@ -2394,10 +2906,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);
 
@@ -2415,7 +2931,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);
@@ -2430,6 +2946,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;
 }       
@@ -2445,15 +2969,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;
@@ -2461,7 +2989,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;
@@ -2488,13 +3016,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;
 }
 
@@ -2512,14 +3054,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;
     }
@@ -2529,7 +3075,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;
 
@@ -2552,13 +3098,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
@@ -2573,7 +3138,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);
@@ -2595,15 +3160,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;
@@ -2611,7 +3179,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;
 
@@ -2633,14 +3201,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;
 }
@@ -2676,6 +3258,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
@@ -2688,12 +3274,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 */
@@ -2710,45 +3300,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);
                 }       
             }
         }
@@ -2763,7 +3367,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;
 
@@ -2791,23 +3395,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 */
@@ -3112,6 +3766,16 @@ long cm_Rename(cm_scache_t *oldDscp, char *oldNamep, cm_scache_t *newDscp,
 
 #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)
 {
     afs_int64 int_begin;
@@ -3303,7 +3967,7 @@ static cm_file_lock_t * cm_GetFileLock(void) {
         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));
@@ -3337,7 +4001,7 @@ long cm_IntSetLock(cm_scache_t * scp, cm_user_t * userp, int lockType,
     lock_ReleaseMutex(&scp->mx);
 
     do {
-        code = cm_Conn(&cfid, userp, reqp, &connp);
+        code = cm_ConnFromFID(&cfid, userp, reqp, &connp);
         if (code) 
             break;
 
@@ -3381,7 +4045,7 @@ long cm_IntReleaseLock(cm_scache_t * scp, cm_user_t * userp,
     osi_Log1(afsd_logp, "CALL ReleaseLock scp 0x%p", scp);
 
     do {
-        code = cm_Conn(&cfid, userp, reqp, &connp);
+        code = cm_ConnFromFID(&cfid, userp, reqp, &connp);
         if (code) 
             break;
 
@@ -3414,14 +4078,19 @@ long cm_IntReleaseLock(cm_scache_t * scp, cm_user_t * userp,
    - 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)
+                       cm_req_t * reqp,
+                       int * phas_insert)
 {
     long rights = 0;
-    long code = 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
@@ -3438,32 +4107,48 @@ long cm_LockCheckPerms(cm_scache_t * scp,
     if (lock_type == LockRead)
         rights |= PRSFS_LOCK;
     else if (lock_type == LockWrite)
-        rights |= PRSFS_WRITE;
+        rights |= PRSFS_WRITE | PRSFS_LOCK;
     else {
         /* hmmkay */
-        osi_assert(FALSE);
+        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 (code == CM_ERROR_NOACCESS &&
-        lock_type == LockWrite &&
-       scp->creator == userp) {
-        /* check for PRSFS_INSERT. */
-        cm_ucell_t * ucp;
+    if (phas_insert && scp->creator == userp) {
 
-        code = cm_SyncOp(scp, NULL, userp, reqp, PRSFS_INSERT,
+        /* 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 (code == CM_ERROR_NOACCESS)
-           osi_Log0(afsd_logp, "cm_LockCheckPerms user is creator but has no INSERT bits for scp");
+       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");
+        }
     }
 
- return_code:
+    cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
+
     osi_Log1(afsd_logp, "cm_LockCheckPerms returning code %d", code);
 
     return code;
@@ -3551,11 +4236,13 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
         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 = cm_LockCheckPerms(scp, Which, userp, reqp);
+            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
@@ -3563,12 +4250,19 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
                reading files off of directories that don't grant
                read-locks to the user. */
             if (code == CM_ERROR_NOACCESS && Which == LockRead) {
-                osi_Log0(afsd_logp, "   User has no read-lock perms. Forcing client-side lock");
-                force_client_lock = TRUE;
+
+                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)) {
+                   (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
@@ -3578,12 +4272,20 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
 
             /* see if we have permission to create the lock in the
                first place. */
-            code = cm_LockCheckPerms(scp, Which, userp, reqp);
+            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, "   User has no read-lock perms.  Forcing client-side lock");
-                force_client_lock = TRUE;
+
+                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;
+                }
             }
 
             /* leave any other codes as-is */
@@ -3591,29 +4293,33 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
         } else {
             int newLock;
             int check_data_version = FALSE;
+            int has_insert = 0;
 
             /* first check if we have permission to elevate or obtain
                the lock. */
-            code = cm_LockCheckPerms(scp, Which, userp, reqp);
+            code = cm_LockCheckPerms(scp, Which, userp, reqp, &has_insert);
             if (code) {
-                if (code == CM_ERROR_NOACCESS && Which == LockRead) {
+                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;
             }
 
+            /* has_insert => (Which == LockRead, code == CM_ERROR_NOACCESS) */
+
             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. */
+                 * 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: %d", scp->dataVersion);
+                         "   dataVersion on scp: %I64d", scp->dataVersion);
 
                 /* we assume at this point (because scp->serverLock
                    was valid) that we had a valid server lock. */
@@ -3631,35 +4337,80 @@ 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 */
+             * to assert this file lock */
 #ifndef AGGRESSIVE_LOCKS
             newLock = Which;
 #else
             newLock = LockWrite;
 #endif
+
             code = cm_IntSetLock(scp, userp, newLock, reqp);
 
-            if (code == CM_ERROR_WOULDBLOCK && newLock != Which) {
+#ifdef AGGRESSIVE_LOCKS
+            if ((code == CM_ERROR_WOULDBLOCK ||
+                 code == CM_ERROR_NOACCESS) && newLock != Which) {
                 /* we wanted LockRead.  We tried LockWrite. Now try
-                   LockRead again */
+                 * LockRead again */
                 newLock = Which;
 
                 /* am I sane? */
-                osi_assert(newLock == LockRead);
-                
+                osi_assertx(newLock == LockRead, "lock type not read");
+
                 code = cm_IntSetLock(scp, userp, newLock, reqp);
             }
+#endif
+
+            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 */
+
+                        tcode = cm_LockCheckPerms(scp, LockWrite, userp, reqp, NULL);
+
+                        if (tcode == 0) {
+                            newLock = LockWrite;
+
+                            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;
+                    }
+                }
+            }
 
             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. */
+                 * 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=%d, after=%d",
+                         "  Data versions before=%I64d, after=%I64d",
                          scp->lockDataVersion,
                          scp->dataVersion);
                 osi_Log1(afsd_logp,
@@ -3677,11 +4428,11 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
 
             if (code != 0 &&
                 (scp->sharedLocks > 0 || scp->exclusiveLocks > 0) &&
-                    scp->serverLock == -1) {
-                    /* Oops. We lost the lock. */
-                    cm_LockMarkSCacheLost(scp);
-                }
+                scp->serverLock == -1) {
+                /* Oops. We lost the lock. */
+                cm_LockMarkSCacheLost(scp);
             }
+        }
     } else if (code == 0) {     /* server locks not enabled */
         osi_Log0(afsd_logp,
                  "  Skipping server lock for scp");
@@ -3819,7 +4570,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
 
@@ -3832,7 +4583,7 @@ 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_CLIENTONLY(fileLock)) {
                 scp->clientLocks--;
@@ -3893,7 +4644,7 @@ long cm_UnlockByKey(cm_scache_t * scp,
         /* 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 = %d", scp->dataVersion);
+        osi_Log1(afsd_logp, "  dataVersion on scp = %I64d", scp->dataVersion);
 
         code = cm_IntReleaseLock(scp, userp, reqp);
 
@@ -3915,7 +4666,7 @@ long cm_UnlockByKey(cm_scache_t * scp,
                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=%d, after=%d",
+            osi_Log2(afsd_logp, "  Data versions before=%I64d, after=%I64d",
                      scp->lockDataVersion,
                      scp->dataVersion);
             
@@ -3994,7 +4745,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) &&
@@ -4011,7 +4762,7 @@ long cm_Unlock(cm_scache_t *scp,
         lock_ReleaseRead(&cm_scacheLock);
 
         /* The lock didn't exist anyway. *shrug* */
-        return 0;
+        return CM_ERROR_RANGE_NOT_LOCKED;
     }
 
     lock_ReleaseRead(&cm_scacheLock);
@@ -4020,7 +4771,7 @@ long cm_Unlock(cm_scache_t *scp,
     lock_ObtainWrite(&cm_scacheLock);
     if (scp->fileLocksT == q)
         scp->fileLocksT = osi_QPrev(q);
-    osi_QRemove(&scp->fileLocksH, q);
+    osi_QRemoveHT(&scp->fileLocksH, &scp->fileLocksT, q);
 
     /*
      * Don't delete it here; let the daemon delete it, to simplify
@@ -4070,7 +4821,18 @@ long cm_Unlock(cm_scache_t *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 %d", scp->dataVersion);
+        osi_Log1(afsd_logp, "   dataVersion on scp is %I64d", scp->dataVersion);
+
+        /* before we downgrade, make sure that we have enough
+           permissions to get the read lock. */
+        code = cm_LockCheckPerms(scp, LockRead, userp, reqp, NULL);
+        if (code != 0) {
+
+            osi_Log0(afsd_logp, "  SKIPPING downgrade because user doesn't have perms to get downgraded lock");
+
+            code = 0;
+            goto done;
+        }
 
         code = cm_IntReleaseLock(scp, userp, reqp);
 
@@ -4094,7 +4856,7 @@ long cm_Unlock(cm_scache_t *scp,
             osi_Log0(afsd_logp,
                      "Data version mismatch while downgrading lock");
             osi_Log2(afsd_logp,
-                     "  Data versions before=%d, after=%d",
+                     "  Data versions before=%I64d, after=%I64d",
                      scp->lockDataVersion,
                      scp->dataVersion);
             
@@ -4150,7 +4912,7 @@ 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 */
@@ -4211,10 +4973,10 @@ 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);
 
@@ -4231,7 +4993,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
@@ -4283,7 +5045,7 @@ void cm_CheckLocks()
                     lock_ReleaseMutex(&scp->mx);
 
                     do {
-                        code = cm_Conn(&cfid, userp,
+                        code = cm_ConnFromFID(&cfid, userp,
                                        &req, &connp);
                         if (code) 
                             break;
@@ -4332,7 +5094,7 @@ void cm_CheckLocks()
                                      "Data version mismatch on scp 0x%p",
                                      scp);
                             osi_Log2(afsd_logp,
-                                     "   Data versions: before=%d, after=%d",
+                                     "   Data versions: before=%I64d, after=%I64d",
                                      scp->lockDataVersion,
                                      scp->dataVersion);
 
@@ -4397,6 +5159,8 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
     cm_req_t req;
     int newLock = -1;
     int force_client_lock = FALSE;
+    int has_insert = FALSE;
+    int check_data_version = FALSE;
 
     cm_InitReq(&req);
 
@@ -4435,17 +5199,19 @@ 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);
+                             &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);
@@ -4457,9 +5223,9 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
     /* 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)) {
+        (!SERVERLOCKS_ENABLED(scp) ||
+         scp->serverLock == oldFileLock->lockType ||
+         scp->serverLock == LockWrite)) {
 
         oldFileLock->flags &= ~CM_FILELOCK_FLAG_WAITLOCK;
 
@@ -4500,7 +5266,7 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
             }
 
             if (IS_LOCK_ACCEPTED(fileLock) &&
-               INTERSECT_RANGE(oldFileLock->range, fileLock->range)) {
+                INTERSECT_RANGE(oldFileLock->range, fileLock->range)) {
 
                 if (oldFileLock->lockType != LockRead ||
                    fileLock->lockType != LockRead) {
@@ -4539,6 +5305,8 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
         oldFileLock->flags |= CM_FILELOCK_FLAG_WAITLOCK;
     }
 
+    osi_assertx(IS_LOCK_WAITLOCK(oldFileLock), "!IS_LOCK_WAITLOCK");
+
     if (force_client_lock ||
         !SERVERLOCKS_ENABLED(scp) ||
         scp->serverLock == oldFileLock->lockType ||
@@ -4590,10 +5358,67 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
         newLock = LockWrite;
 #endif
 
+        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);
 
+        /* 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. */
+
+        if (scp->serverLock == LockRead) {
+
+            osi_assertx(newLock == LockWrite, "!LockWrite");
+
+            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;
+            }
+        }
+
     pre_syncopdone:
         cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_LOCK);
     post_syncopdone:
@@ -4605,10 +5430,8 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
        lock_ObtainWrite(&cm_scacheLock);
         if (scp->fileLocksT == &oldFileLock->fileq)
             scp->fileLocksT = osi_QPrev(&oldFileLock->fileq);
-        osi_QRemove(&scp->fileLocksH, &oldFileLock->fileq);
+        osi_QRemoveHT(&scp->fileLocksH, &scp->fileLocksT, &oldFileLock->fileq);
        lock_ReleaseWrite(&cm_scacheLock);
-    } else if (code == 0 && IS_LOCK_WAITLOCK(oldFileLock)) {
-        scp->serverLock = newLock;
     }
     lock_ReleaseMutex(&scp->mx);
 
@@ -4633,9 +5456,9 @@ 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)
 {
 #ifdef DEBUG
-    osi_assert((process_id & 0xffffffff) == process_id);
-    osi_assert((session_id & 0xffff) == session_id);
-    osi_assert((file_id & 0xffff) == file_id);
+    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 
@@ -4662,9 +5485,9 @@ void cm_ReleaseAllLocks(void)
     cm_file_lock_t *fileLock;
     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);