windows-local-directory-updates-20070802
authorAsanka Herath <asanka@secure-endpoints.com>
Thu, 2 Aug 2007 21:52:54 +0000 (21:52 +0000)
committerJeffrey Altman <jaltman@secure-endpoints.com>
Thu, 2 Aug 2007 21:52:54 +0000 (21:52 +0000)
The windows cache manager has suffered from poor performance as a result
of Create, Rename, and Delete operations because they invalidate the
contents of the directory pages in the cache thereby forcing them to be
reloaded from the file server.   As the directory size increases, the clock
time necessary to perform the reload increases.

This delta adds support for parsing and updating the AFS3 directory buffers
to cm_dir.c.  It then uses that functionality to perform local updates to
the directory buffers whenever the following conditions are met:

 1. the data version on the directory as a result of the change
    was incremented by one.

 2. all of the directory buffers required for the update are in
    the cache.

If these conditions are not met, the directory is reloaded from the file
server.

src/WINNT/afsd/cm_buf.c
src/WINNT/afsd/cm_buf.h
src/WINNT/afsd/cm_dir.c
src/WINNT/afsd/cm_dir.h
src/WINNT/afsd/cm_scache.c
src/WINNT/afsd/cm_vnodeops.c

index 0931f0f..1f7361d 100644 (file)
@@ -1459,6 +1459,34 @@ long buf_FlushCleanPages(cm_scache_t *scp, cm_user_t *userp, cm_req_t *reqp)
     return code;
 }       
 
+/* Must be called with scp->mx held */
+long buf_ForceDataVersion(cm_scache_t * scp, afs_uint32 fromVersion, afs_uint32 toVersion)
+{
+    cm_buf_t * bp;
+    cm_buf_t * nbp;
+    unsigned int i;
+    int found = 0;
+
+    i = BUF_FILEHASH(&scp->fid);
+
+    lock_ObtainWrite(&buf_globalLock);
+
+    for (bp = cm_data.buf_fileHashTablepp[i]; bp; bp = bp->fileHashp) {
+        if (cm_FidCmp(&bp->fid, &scp->fid) == 0) {
+            if (bp->dataVersion == fromVersion) {
+                bp->dataVersion = toVersion;
+                found = 1;
+            }
+        }
+    }
+    lock_ReleaseWrite(&buf_globalLock);
+
+    if (found)
+        return 0;
+    else
+        return ENOENT;
+}
+
 long buf_CleanVnode(struct cm_scache *scp, cm_user_t *userp, cm_req_t *reqp)
 {
     long code = 0;
index 4b8319c..53183f8 100644 (file)
@@ -98,7 +98,9 @@ typedef struct cm_buf {
 #define CM_BUF_CMFETCHING      1       /* fetching this buffer */
 #define CM_BUF_CMSTORING       2       /* storing this buffer */
 #define CM_BUF_CMFULLYFETCHED  4       /* read-while-fetching optimization */
-/* waiting is done based on scp->flags */
+#define CM_BUF_CMWRITING        8       /* writing to this buffer */
+/* waiting is done based on scp->flags.  Removing bits from cmFlags
+   should be followed by waking the scp. */
 
 /* represents soft reference which is OK to lose on a recycle */
 typedef struct cm_softRef {
@@ -194,6 +196,8 @@ extern long buf_DirtyBuffersExist(cm_fid_t * fidp);
 
 extern long buf_CleanDirtyBuffers(cm_scache_t *scp);
 
+extern long buf_ForceDataVersion(cm_scache_t * scp, afs_uint32 fromVersion, afs_uint32 toVersion);
+
 /* error codes */
 #define CM_BUF_EXISTS  1       /* buffer exists, and shouldn't */
 #endif /*  _BUF_H__ENV_ */
index b5327dc..758af9a 100644 (file)
 #include <rx/rx.h>
 
 
+afs_int32 DErrno;
+
+/* Local static prototypes */
+static long
+cm_DirGetBlob(cm_dirOp_t * op,
+              unsigned int blobno, cm_buf_t ** bufferpp, cm_dirEntry_t ** blobpp);
+
+static long
+cm_DirFindItem(cm_dirOp_t * op,
+               char *ename,
+               cm_buf_t ** itembufpp, cm_dirEntry_t ** itempp,
+               cm_buf_t ** prevbufpp, unsigned short **previtempp);
+
+static long
+cm_DirOpAddBuffer(cm_dirOp_t * op, cm_buf_t * buffer);
+
+/* flags for cm_DirOpDelBuffer */
+#define DIROP_MODIFIED  1
+#define DIROP_SCPLOCKED 2
+
+static int
+cm_DirOpDelBuffer(cm_dirOp_t * op, cm_buf_t * buffer, int flags);
+
+static long
+cm_DirCheckStatus(cm_dirOp_t * op);
+
+static long
+cm_DirReleasePage(cm_dirOp_t * op, cm_buf_t ** bufferpp, int modified);
+
+static long
+cm_DirGetPage(cm_dirOp_t * op,
+              long index, cm_buf_t ** bufferpp, void ** datapp);
+
+static long
+cm_DirFindBlobs(cm_dirOp_t * op, int nblobs);
+
+static long
+cm_DirAddPage(cm_dirOp_t * op, int pageno);
+
+static long
+cm_DirFreeBlobs(cm_dirOp_t * op, int firstblob, int nblobs);
+
+
 /* compute how many 32 byte entries an AFS 3 dir requires for storing
  * the specified name.
  */
-long cm_NameEntries(char *namep, long *lenp)
+long 
+cm_NameEntries(char *namep, long *lenp)
 {
     long i;
         
-    i = (long)strlen(namep);
+    i = (long)strlen(namep) + 1;
     if (lenp) *lenp = i;
-    return 1+((i+16)>>5);
+    return 1 + ((i+15) >> 5);
+}
+
+/* Create an entry in a file.  Dir is a file representation, while
+   entry is a string name.
+
+   On entry:
+       op->scp->mx is locked
+
+   On exit:
+       op->scp->mx is locked
+
+   None of the directory buffers for op->scp should be locked by the
+   calling thread.
+*/
+long
+cm_DirCreateEntry(cm_dirOp_t * op, char *entry, cm_fid_t * cfid)
+{
+    int blobs, firstelt;
+    int i;
+
+    cm_dirEntry_t *ep = NULL;
+    cm_buf_t *entrybuf = NULL;
+
+    unsigned short *pp = NULL;
+    cm_buf_t *prevptrbuf = NULL;
+
+    cm_dirHeader_t *dhp = NULL;
+    cm_buf_t *dhpbuf = NULL;
+
+    long code = 0;
+
+    /* check name quality */
+    if (*entry == 0)
+       return EINVAL;
+
+    osi_Log4(afsd_logp, "cm_DirCreateEntry for op 0x%p, name [%s] and fid[%d,%d]",
+             op, osi_LogSaveString(afsd_logp, entry), cfid->vnode, cfid->unique);
+
+    /* First check if file already exists. */
+    code = cm_DirFindItem(op,
+                          entry,
+                          &entrybuf, &ep,
+                          &prevptrbuf, &pp);
+    if (code == 0) {
+        cm_DirReleasePage(op, &entrybuf, FALSE);
+        cm_DirReleasePage(op, &prevptrbuf, FALSE);
+       return EEXIST;
+    }
+
+    blobs = cm_NameEntries(entry, NULL);       /* number of entries required */
+    firstelt = cm_DirFindBlobs(op, blobs);
+    if (firstelt < 0) {
+        osi_Log0(afsd_logp, "cm_DirCreateEntry returning EFBIG");
+       return EFBIG;           /* directory is full */
+    }
+
+    /* First, we fill in the directory entry. */
+    code = cm_DirGetBlob(op, firstelt, &entrybuf, &ep);
+    if (code != 0)
+       return EIO;
+
+    ep->flag = CM_DIR_FFIRST;
+    ep->fid.vnode = htonl(cfid->vnode);
+    ep->fid.unique = htonl(cfid->unique);
+    strcpy(ep->name, entry);
+
+    /* Now we just have to thread it on the hash table list. */
+    code = cm_DirGetPage(op, 0, &dhpbuf, &dhp);
+    if (code != 0) {
+       cm_DirReleasePage(op, &entrybuf, TRUE);
+       return EIO;
+    }
+
+    i = cm_DirHash(entry);
+
+    ep->next = dhp->hashTable[i];
+    dhp->hashTable[i] = htons(firstelt);
+
+    cm_DirReleasePage(op, &dhpbuf, TRUE);
+    cm_DirReleasePage(op, &entrybuf, TRUE);
+
+    osi_Log0(afsd_logp, "cm_DirCreateEntry returning success");
+
+    return 0;
+}
+
+/* Return the length of a directory in pages
+
+   On entry:
+       op->scp->mx is locked
+
+   On exit:
+       op->scp->mx is locked
+
+   The first directory page for op->scp should not be locked by the
+   calling thread.
+*/
+int
+cm_DirLength(cm_dirOp_t * op)
+{
+    int i, ctr;
+    cm_dirHeader_t *dhp = NULL;
+    cm_buf_t       *dhpbuf = NULL;
+
+    long code;
+
+    code = cm_DirGetPage(op, 0, &dhpbuf, &dhp);
+    if (code != 0)
+       return 0;
+
+    if (dhp->header.pgcount != 0)
+       ctr = ntohs(dhp->header.pgcount);
+    else {
+       /* old style, count the pages */
+       ctr = 0;
+       for (i = 0; i < CM_DIR_MAXPAGES; i++)
+           if (dhp->alloMap[i] != CM_DIR_EPP)
+               ctr++;
+    }
+    cm_DirReleasePage(op, &dhpbuf, FALSE);
+    return ctr * CM_DIR_PAGESIZE;
+}
+
+/* Delete a directory entry.
+
+   On entry:
+       op->scp->mx is locked
+
+   On exit:
+       op->scp->mx is locked
+
+   None of the directory buffers for op->scp should be locked by the
+   calling thread.
+ */
+int
+cm_DirDeleteEntry(cm_dirOp_t * op, char *entry)
+{
+    /* Delete an entry from a directory, including update of all free
+       entry descriptors. */
+
+    int nitems, index;
+    cm_dirEntry_t *firstitem = NULL;
+    cm_buf_t      *itembuf = NULL;
+    unsigned short *previtem = NULL;
+    cm_buf_t      *pibuf = NULL;
+    osi_hyper_t    thyper;
+    unsigned long  junk;
+
+    long code;
+
+    osi_Log2(afsd_logp, "cm_DirDeleteEntry for op 0x%p, entry [%s]",
+             op, osi_LogSaveString(afsd_logp, entry));
+
+    code = cm_DirFindItem(op, entry,
+                          &itembuf, &firstitem,
+                          &pibuf, &previtem);
+    if (code != 0) {
+        osi_Log0(afsd_logp, "cm_DirDeleteEntry returning ENOENT");
+       return ENOENT;
+    }
+
+    *previtem = firstitem->next;
+    cm_DirReleasePage(op, &pibuf, TRUE);
+
+    thyper = itembuf->offset;
+    thyper = LargeIntegerAdd(thyper,
+                             ConvertLongToLargeInteger(((char *) firstitem) - itembuf->datap));
+    thyper = ExtendedLargeIntegerDivide(thyper, 32, &junk);
+
+    index = thyper.LowPart;
+    osi_assert(thyper.HighPart == 0);
+
+    nitems = cm_NameEntries(firstitem->name, NULL);
+    cm_DirReleasePage(op, &itembuf, FALSE);
+
+    cm_DirFreeBlobs(op, index, nitems);
+
+    osi_Log0(afsd_logp, "cm_DirDeleteEntry returning success");
+
+    return 0;
+}
+
+/* Find a bunch of contiguous entries; at least nblobs in a row.
+
+   Called with op->scp->mx */
+static long
+cm_DirFindBlobs(cm_dirOp_t * op, int nblobs)
+{
+    int i, j, k;
+    int failed = 0;
+
+    cm_dirHeader_t *dhp = NULL;
+    cm_buf_t *dhpbuf = NULL;
+    int dhpModified = FALSE;
+
+    cm_pageHeader_t *pp = NULL;
+    cm_buf_t *pagebuf = NULL;
+    int pageModified = FALSE;
+
+    int pgcount;
+
+    long code;
+
+    osi_Log2(afsd_logp, "cm_DirFindBlobs for op 0x%p, nblobs = %d",
+             op, nblobs);
+
+    code = cm_DirGetPage(op, 0, &dhpbuf, (void **) &dhp);
+    if (code)
+       return -1;
+
+    for (i = 0; i < CM_DIR_BIGMAXPAGES; i++) {
+       if (i >= CM_DIR_MAXPAGES || dhp->alloMap[i] >= nblobs) {
+           /* if page could contain enough entries */
+           /* If there are CM_DIR_EPP free entries, then the page is
+               not even allocated. */
+           if (i >= CM_DIR_MAXPAGES) {
+
+               /* this pages exists past the end of the old-style dir */
+               pgcount = ntohs(dhp->header.pgcount);
+               if (pgcount == 0) {
+                   pgcount = CM_DIR_MAXPAGES;
+                   dhp->header.pgcount = htons(pgcount);
+                    dhpModified = TRUE;
+               }
+
+               if (i > pgcount - 1) {
+                   /* this page is bigger than last allocated page */
+                    cm_DirAddPage(op, i);
+                   dhp->header.pgcount = htons(i + 1);
+                    dhpModified = TRUE;
+               }
+           } else if (dhp->alloMap[i] == CM_DIR_EPP) {
+               /* Add the page to the directory. */
+               cm_DirAddPage(op, i);
+               dhp->alloMap[i] = CM_DIR_EPP - 1;
+               dhp->header.pgcount = htons(i + 1);
+                dhpModified = TRUE;
+           }
+
+            code = cm_DirGetPage(op, i, &pagebuf, &pp);
+            if (code) {
+                cm_DirReleasePage(op, &dhpbuf, dhpModified);
+                break;
+            }
+
+           for (j = 0; j <= CM_DIR_EPP - nblobs; j++) {
+               failed = 0;
+               for (k = 0; k < nblobs; k++)
+                   if ((pp->freeBitmap[(j + k) >> 3] >> ((j + k) & 7)) & 1) {
+                       failed = 1;
+                       break;
+                   }
+               if (!failed)
+                   break;
+               failed = 1;
+           }
+
+           if (!failed) {
+               /* Here we have the first index in j.  We update the allocation maps
+                * and free up any resources we've got allocated. */
+               if (i < CM_DIR_MAXPAGES) {
+                   dhp->alloMap[i] -= nblobs;
+                    dhpModified = TRUE;
+                }
+
+                cm_DirReleasePage(op, &dhpbuf, dhpModified);
+
+               for (k = 0; k < nblobs; k++)
+                   pp->freeBitmap[(j + k) >> 3] |= 1 << ((j + k) & 7);
+
+                cm_DirReleasePage(op, &pagebuf, TRUE);
+
+                osi_Log0(afsd_logp, "cm_DirFindBlobs returning success");
+
+               return j + i * CM_DIR_EPP;
+           }
+            cm_DirReleasePage(op, &pagebuf, pageModified);
+       }
+    }
+
+    /* If we make it here, the directory is full. */
+    osi_Log0(afsd_logp, "cm_DirFindBlobs directory is full");
+    cm_DirReleasePage(op, &dhpbuf, dhpModified);
+    return -1;
+}
+
+/* Add a page to a directory. 
+
+   Called with op->scp->mx
+*/
+static long
+cm_DirAddPage(cm_dirOp_t * op, int pageno)
+{
+    int i;
+    cm_pageHeader_t *pp = NULL;
+    cm_buf_t *pagebuf = NULL;
+    long code = 0;
+
+    osi_Log2(afsd_logp, "cm_DirAddPage for op 0x%p, pageno=%d", op, pageno);
+
+    code = cm_DirGetPage(op, pageno, &pagebuf, (void **) &pp);
+    if (code != 0)
+        return code;
+
+    pp->tag = htons(1234);
+    if (pageno > 0)
+       pp->pgcount = 0;
+    pp->freeCount = CM_DIR_EPP - 1; /* The first dude is already allocated */
+    pp->freeBitmap[0] = 0x01;
+    for (i = 1; i < CM_DIR_EPP / 8; i++) /* It's a constant */
+       pp->freeBitmap[i] = 0;
+
+    cm_DirReleasePage(op, &pagebuf, TRUE);
+
+    osi_Log0(afsd_logp, "cm_DirAddPage returning success");
+
+    return code;
+}
+
+/* Free a whole bunch of directory entries.
+
+   Called with op->scp->mx
+*/
+static long
+cm_DirFreeBlobs(cm_dirOp_t * op, int firstblob, int nblobs)
+{
+    int i;
+    int page;
+
+    cm_dirHeader_t *dhp = NULL;
+    cm_buf_t       *dhpbuf = NULL;
+    int             dhpmodified = FALSE;
+
+    cm_pageHeader_t *pp = NULL;
+    cm_buf_t        *pagebuf = NULL;
+    long code = 0;
+
+    osi_Log3(afsd_logp, "cm_DirFreeBlobs for op 0x%p, firstblob=%d, nblobs=%d",
+             op, firstblob, nblobs);
+
+    page = firstblob / CM_DIR_EPP;
+    firstblob -= CM_DIR_EPP * page;    /* convert to page-relative entry */
+
+    code = cm_DirGetPage(op, 0, &dhpbuf, &dhp);
+    if (code)
+        return code;
+
+    if (page < CM_DIR_MAXPAGES) {
+       dhp->alloMap[page] += nblobs;
+        dhpmodified = TRUE;
+    }
+
+    cm_DirReleasePage(op, &dhpbuf, dhpmodified);
+
+    code = cm_DirGetPage(op, page, &pagebuf, &pp);
+    if (code == 0) {
+       for (i = 0; i < nblobs; i++)
+           pp->freeBitmap[(firstblob + i) >> 3] &=
+               ~(1 << ((firstblob + i) & 7));
+        cm_DirReleasePage(op, &pagebuf, TRUE);
+    }
+
+    osi_Log1(afsd_logp, "cm_DirFreeBlobs returning code 0x%x", code);
+
+    return code;
+}
+
+/*
+ * Format an empty directory properly.  Note that the first 13 entries in a
+ * directory header page are allocated, 1 to the page header, 4 to the
+ * allocation map and 8 to the hash table.
+ *
+ * Called with op->scp->mx
+ */
+int
+cm_DirMakeDir(cm_dirOp_t * op, cm_fid_t * me, cm_fid_t * parent)
+{
+    int i;
+    cm_dirHeader_t *dhp = NULL;
+    cm_buf_t *dhpbuf = NULL;
+
+    long code;
+
+    osi_Log3(afsd_logp, "cm_DirMakeDir for op 0x%p, directory fid[%d, %d]",
+             op, me->vnode, me->unique);
+    osi_Log2(afsd_logp, "              parent[%d, %d]",
+             parent->vnode, parent->unique);
+
+    code = cm_DirGetPage(op, 0, &dhpbuf, &dhp);
+    if (code)
+        return 1;
+
+    dhp->header.pgcount = htons(1);
+    dhp->header.tag = htons(1234);
+    dhp->header.freeCount = (CM_DIR_EPP - CM_DIR_DHE - 1);
+    dhp->header.freeBitmap[0] = 0xff;
+    dhp->header.freeBitmap[1] = 0x1f;
+    for (i = 2; i < CM_DIR_EPP / 8; i++)
+       dhp->header.freeBitmap[i] = 0;
+    dhp->alloMap[0] = (CM_DIR_EPP - CM_DIR_DHE - 1);
+    for (i = 1; i < CM_DIR_MAXPAGES; i++)
+       dhp->alloMap[i] = CM_DIR_EPP;
+    for (i = 0; i < CM_DIR_NHASHENT; i++)
+       dhp->hashTable[i] = 0;
+
+    cm_DirReleasePage(op, &dhpbuf, TRUE);
+
+    cm_DirCreateEntry(op, ".", me);
+    cm_DirCreateEntry(op, "..", parent);       /* Virtue is its own .. */
+
+    osi_Log0(afsd_logp, "cm_DirMakeDir returning success");
+
+    return 0;
+}
+
+/* Look up a file name in directory.
+
+   On entry:
+       op->scp->mx is locked
+
+   On exit:
+       op->scp->mx is locked
+
+   None of the directory buffers for op->scp should be locked by the
+   calling thread.
+*/
+int
+cm_DirLookup(cm_dirOp_t * op, char *entry, cm_fid_t * cfid)
+{
+    cm_dirEntry_t *firstitem = NULL;
+    cm_buf_t      *itembuf = NULL;
+    unsigned short *previtem = NULL;
+    cm_buf_t      *pibuf = NULL;
+
+    long code;
+
+    osi_Log2(afsd_logp, "cm_DirLookup for op 0x%p, entry[%s]",
+             op, osi_LogSaveString(afsd_logp, entry));
+
+    code = cm_DirFindItem(op, entry,
+                          &itembuf, &firstitem,
+                          &pibuf, &previtem);
+    if (code != 0) {
+        return ENOENT;
+    }
+
+    cm_DirReleasePage(op, &pibuf, FALSE);
+
+    cfid->cell = op->scp->fid.cell;
+    cfid->volume = op->scp->fid.volume;
+    cfid->vnode = ntohl(firstitem->fid.vnode);
+    cfid->unique = ntohl(firstitem->fid.unique);
+
+    cm_DirReleasePage(op, &itembuf, FALSE);
+
+    osi_Log2(afsd_logp, "cm_DirLookup returning fid[%d,%d]",
+             cfid->vnode, cfid->unique);
+
+    return 0;
+}
+
+/* Look up a file name in directory.
+
+   On entry:
+       op->scp->mx is locked
+
+   On exit:
+       op->scp->mx is locked
+
+   None of the directory buffers for op->scp should be locked by the
+   calling thread.
+*/
+int
+cm_DirLookupOffset(cm_dirOp_t * op, char *entry, cm_fid_t *cfid, osi_hyper_t *offsetp)
+{
+    cm_dirEntry_t *firstitem = NULL;
+    cm_buf_t      *itembuf = NULL;
+    unsigned short *previtem = NULL;
+    cm_buf_t      *pibuf = NULL;
+
+    long code;
+
+    osi_Log2(afsd_logp, "cm_DirLookupOffset for op 0x%p, entry[%s]",
+             op, osi_LogSaveString(afsd_logp, entry));
+
+    code = cm_DirFindItem(op, entry,
+                          &itembuf, &firstitem,
+                          &pibuf, &previtem);
+    if (code != 0)
+        return ENOENT;
+
+    cm_DirReleasePage(op, &pibuf, FALSE);
+
+    cfid->cell = op->scp->fid.cell;
+    cfid->volume = op->scp->fid.volume;
+    cfid->vnode = ntohl(firstitem->fid.vnode);
+    cfid->unique = ntohl(firstitem->fid.unique);
+    if (offsetp) {
+        osi_hyper_t thyper;
+
+        thyper = itembuf->offset;
+        thyper = LargeIntegerAdd(thyper,
+                                 ConvertLongToLargeInteger(((char *) firstitem) - itembuf->datap));
+
+       *offsetp = thyper;
+    }
+
+    cm_DirReleasePage(op, &itembuf, FALSE);
+
+    osi_Log2(afsd_logp, "cm_DirLookupOffset returning fid[%d,%d]",
+             cfid->vnode, cfid->unique);
+    if (offsetp) {
+        osi_Log2(afsd_logp, "               offset [%x:%x]",
+                 offsetp->HighPart, offsetp->LowPart);
+    }
+
+    return 0;
+}
+
+/* Apply a function to every directory entry in a directory.
+
+   On entry:
+       op->scp->mx is locked
+
+   On exit:
+       op->scp->mx is locked
+
+   None of the directory buffers for op->scp should be locked by the
+   calling thread.
+
+   The hook function cannot modify or lock any directory buffers.
+ */
+int
+cm_DirApply(cm_dirOp_t * op, int (*hookproc) (void *, char *, long, long), void *hook)
+{
+    /* Enumerate the contents of a directory. */
+    int i;
+    int num;
+
+    cm_dirHeader_t *dhp = NULL;
+    cm_buf_t       *dhpbuf = NULL;
+
+    cm_dirEntry_t  *ep = NULL;
+    cm_buf_t       *epbuf = NULL;
+
+    long code = 0;
+
+    code = cm_DirGetPage(op, 0, &dhpbuf, &dhp);
+    if (code != 0)
+        return EIO;
+
+    for (i = 0; i < CM_DIR_NHASHENT; i++) {
+       /* For each hash chain, enumerate everyone on the list. */
+       num = ntohs(dhp->hashTable[i]);
+       while (num != 0) {
+           /* Walk down the hash table list. */
+           code = cm_DirGetBlob(op, num, &epbuf, &ep);
+           if (code != 0) {
+                cm_DirReleasePage(op, &dhpbuf, FALSE);
+                return code;
+           }
+
+           num = ntohs(ep->next);
+           (*hookproc) (hook, ep->name, ntohl(ep->fid.vnode),
+                        ntohl(ep->fid.unique));
+
+            cm_DirReleasePage(op, &epbuf, FALSE);
+       }
+    }
+    cm_DirReleasePage(op, &dhpbuf, FALSE);
+
+    return 0;
+}
+
+/* Check if a directory is empty
+
+   On entry:
+       op->scp->mx is locked
+
+   On exit:
+       op->scp->mx is locked
+
+   None of the directory buffers for op->scp should be locked by the
+   calling thread.
+ */
+int
+cm_DirIsEmpty(cm_dirOp_t * op)
+{
+    /* Enumerate the contents of a directory. */
+    int i;
+    int num;
+
+    cm_dirHeader_t *dhp = NULL;
+    cm_buf_t       *dhpbuf = NULL;
+
+    cm_dirEntry_t  *ep = NULL;
+    cm_buf_t       *epbuf = NULL;
+
+    long code = 0;
+
+    code = cm_DirGetPage(op, 0, &dhpbuf, &dhp);
+    if (code != 0)
+        return 0;
+
+    for (i = 0; i < CM_DIR_NHASHENT; i++) {
+       /* For each hash chain, enumerate everyone on the list. */
+       num = ntohs(dhp->hashTable[i]);
+
+       while (num != 0) {
+           /* Walk down the hash table list. */
+           code = cm_DirGetBlob(op, num, &epbuf, &ep);
+           if (code != 0)
+               break;
+
+           if (strcmp(ep->name, "..") && strcmp(ep->name, ".")) {
+                cm_DirReleasePage(op, &epbuf, FALSE);
+                cm_DirReleasePage(op, &dhpbuf, FALSE);
+               return 1;
+           }
+           num = ntohs(ep->next);
+           cm_DirReleasePage(op, &epbuf, FALSE);
+       }
+    }
+    cm_DirReleasePage(op, &dhpbuf, FALSE);
+    return 0;
+}
+
+/* Return a pointer to an entry, given its number.
+
+   On entry:
+     scp->mx locked
+     if *bufferpp != NULL, then *bufferpp->mx is locked
+
+   During:
+     scp->mx may be unlocked
+     *bufferpp may be released
+
+   On exit:
+     scp->mx locked
+     if *bufferpp != NULL, then *bufferpp->mx is locked
+
+     *bufferpp should be released via cm_DirReleasePage() or any other
+     *call that releases a directory buffer.
+*/
+static long
+cm_DirGetBlob(cm_dirOp_t * op,
+              unsigned int blobno, cm_buf_t ** bufferpp, cm_dirEntry_t ** blobpp)
+{
+    unsigned char * ep;
+    long code = 0;
+
+    osi_Log2(afsd_logp, "cm_DirGetBlob for op 0x%p, blobno=%d",
+             op, blobno);
+
+    code = cm_DirGetPage(op, blobno >> CM_DIR_LEPP,
+                         bufferpp, (void **) &ep);
+    if (code != 0)
+        return code;
+
+    *blobpp = (cm_dirEntry_t *) (ep + 32 * (blobno & (CM_DIR_EPP - 1)));
+
+    return code;
 }      
+
+int
+cm_DirHash(char *string)
+{
+    /* Hash a string to a number between 0 and NHASHENT. */
+    register unsigned char tc;
+    register int hval;
+    register int tval;
+    hval = 0;
+    while ((tc = (*string++))) {
+       hval *= 173;
+       hval += tc;
+    }
+    tval = hval & (CM_DIR_NHASHENT - 1);
+    if (tval == 0)
+       return tval;
+    else if (hval < 0)
+       tval = CM_DIR_NHASHENT - tval;
+    return tval;
+}
+
+/* Find a directory entry, given its name.  This entry returns a
+ * pointer to a locked buffer, and a pointer to a locked buffer (in
+ * previtem) referencing the found item (to aid the delete code).  If
+ * no entry is found, however, no items are left locked, and a null
+ * pointer is returned instead.
+ *
+ * On entry:
+ *  scp->mx locked
+ *
+ * On exit:
+ *  scp->mx locked
+ */
+static long
+cm_DirFindItem(cm_dirOp_t * op,
+               char *ename,
+               cm_buf_t ** itembufpp, cm_dirEntry_t ** itempp,
+               cm_buf_t ** prevbufpp, unsigned short **previtempp)
+{
+    int                  i;
+    cm_dirHeader_t      *dhp = NULL;
+    unsigned short      *lp = NULL;
+    cm_dirEntry_t       *tp = NULL;
+    cm_buf_t            *hashbufp = NULL;
+    cm_buf_t            *itembufp = NULL;
+    long code = 0;
+
+    osi_Log2(afsd_logp, "cm_DirFindItem for op 0x%p, entry[%s]",
+             op, osi_LogSaveString(afsd_logp, ename));
+
+    i = cm_DirHash(ename);
+
+    if (op->scp->fileType != CM_SCACHETYPE_DIRECTORY) {
+        osi_Log0(afsd_logp, "cm_DirFindItem: The scp is not a directory");
+        return CM_ERROR_INVAL;
+    }
+
+    code = cm_DirGetPage(op, 0, &hashbufp, (void **) &dhp);
+    if (code != 0) {
+       return code;
+    }
+
+    if (dhp->hashTable[i] == 0) {
+       /* no such entry */
+        osi_Log1(afsd_logp, "cm_DirFindItem: Hash bucket %d is empty", i);
+       cm_DirReleasePage(op, &hashbufp, FALSE);
+       return ENOENT;
+    }
+
+    code = cm_DirGetBlob(op,
+                         (u_short) ntohs(dhp->hashTable[i]),
+                         &itembufp, &tp);
+    if (code != 0) {
+        cm_DirReleasePage(op, &hashbufp, FALSE);
+       return code;
+    }
+
+    lp = &(dhp->hashTable[i]);
+
+    /* loop invariant:
+
+       lp       : pointer to blob number of entry we are looking at
+       hashbufp : buffer containing lp
+       tp       : pointer to entry we are looking at
+       itembufp : buffer containing tp
+     */
+    while (1) {
+       /* Look at each hash conflict entry. */
+       if (!strcmp(ename, tp->name)) {
+            osi_Log0(afsd_logp, "cm_DirFindItem: returning success");
+           /* Found our entry. */
+           *previtempp = lp;
+            *prevbufpp = hashbufp;
+            *itempp = tp;
+            *itembufpp = itembufp;
+           return 0;
+       }
+
+       lp = &(tp->next);
+        cm_DirReleasePage(op, &hashbufp, FALSE);
+        hashbufp = itembufp;
+
+        itembufp = NULL;
+        tp = NULL;
+
+       if (*lp == 0) {
+           /* The end of the line */
+            osi_Log0(afsd_logp, "cm_DirFindItem: returning ENOENT");
+           cm_DirReleasePage(op, &hashbufp, FALSE);
+           return ENOENT;
+       }
+
+       code = cm_DirGetBlob(op,
+                             (u_short) ntohs(*lp),
+                             &itembufp, &tp);
+
+       if (code != 0) {
+           cm_DirReleasePage(op, &hashbufp, FALSE);
+           return code;
+       }
+    }
+}
+
+/* Begin a sequence of directory operations.  scp->mx should be
+   locked.
+*/
+long
+cm_BeginDirOp(cm_scache_t * scp, cm_user_t * userp, cm_req_t * reqp,
+              cm_dirOp_t * op)
+{
+    long code;
+    int i;
+
+    osi_Log3(afsd_logp, "Beginning dirOp[0x%p] for scp[0x%p], userp[0x%p]",
+             op, scp, userp);
+
+    memset(op, 0, sizeof(*op));
+
+    cm_HoldSCache(scp);
+    op->scp = scp;
+    cm_HoldUser(userp);
+    op->userp = userp;
+    cm_InitReq(&op->req);
+
+    op->dirtyBufCount = 0;
+    op->nBuffers = 0;
+
+    for (i=0; i < CM_DIROP_MAXBUFFERS; i++) {
+        op->buffers[i].flags = 0;
+    }
+
+    code = cm_DirCheckStatus(op);
+
+    if (code == 0) {
+        op->length = scp->length;
+        op->newLength = op->length;
+        op->dataVersion = scp->dataVersion;
+        op->newDataVersion = op->dataVersion;
+    } else {
+        cm_EndDirOp(op);
+    }
+
+    return code;
+}
+
+/* Check if it is safe for us to perform local directory updates.
+   Called with scp->mx held. */
+int
+cm_CheckDirOpForSingleChange(cm_dirOp_t * op)
+{
+    long code;
+
+    if (op->scp == NULL)
+        return 0;
+
+    code = cm_DirCheckStatus(op);
+
+    if (code == 0 &&
+        op->dataVersion == op->scp->dataVersion - 1) {
+        /* only one set of changes happened between cm_BeginDirOp()
+           and this function.  It is safe for us to perform local
+           changes. */
+        op->newDataVersion = op->scp->dataVersion;
+        op->newLength = op->scp->serverLength;
+
+        osi_Log0(afsd_logp, "cm_CheckDirOpForSingleChange succeeded");
+
+        return 1;
+    }
+
+    osi_Log3(afsd_logp,
+             "cm_CheckDirOpForSingleChange failed.  code=0x%x, old dv=%d, new dv=%d",
+             code, op->dataVersion, op->scp->dataVersion);
+    return 0;
+}
+
+/* End a sequence of directory operations.  Called with op->scp->mx
+   locked.*/
+long
+cm_EndDirOp(cm_dirOp_t * op)
+{
+    long code = 0;
+
+    if (op->scp == NULL)
+        return 0;
+
+    osi_Log2(afsd_logp, "Ending dirOp 0x%p with %d dirty buffer releases",
+             op, op->dirtyBufCount);
+
+    if (op->dirtyBufCount > 0) {
+        /* we made changes.  We should go through the list of buffers
+           and update the dataVersion for each. */
+
+        lock_ReleaseMutex(&op->scp->mx);
+        code = buf_ForceDataVersion(op->scp, op->dataVersion, op->newDataVersion);
+        lock_ObtainMutex(&op->scp->mx);
+    }
+
+    if (op->scp)
+        cm_ReleaseSCache(op->scp);
+    op->scp = NULL;
+
+    if (op->userp)
+        cm_ReleaseUser(op->userp);
+    op->userp = 0;
+
+    osi_assertx(op->nBuffers == 0, "Buffer leak after dirOp termination");
+
+    return code;
+}
+
+/* NOTE: Called without scp->mx and without bufferp->mx */
+static long
+cm_DirOpAddBuffer(cm_dirOp_t * op, cm_buf_t * bufferp)
+{
+    int i;
+    long code = 0;
+
+    osi_Log2(afsd_logp, "cm_DirOpAddBuffer for op 0x%p, buffer %p", op, bufferp);
+
+    if (bufferp == NULL)
+        return -1;
+
+    for (i=0; i < CM_DIROP_MAXBUFFERS; i++) {
+        if ((op->buffers[i].flags & CM_DIROPBUFF_INUSE) &&
+            op->buffers[i].bufferp == bufferp) {
+            break;
+        }
+    }
+
+    if (i < CM_DIROP_MAXBUFFERS) {
+        /* we already have this buffer on our list */
+
+        op->buffers[i].refcount++;
+        osi_Log0(afsd_logp,
+                 "cm_DirOpAddBuffer: the buffer is already listed for the dirOp");
+        return 0;
+    } else {
+        /* we have to add a new buffer */
+        osi_assertx(op->nBuffers < CM_DIROP_MAXBUFFERS - 1,
+                    "DirOp has exceeded CM_DIROP_MAXBUFFERS buffers");
+
+        for (i=0; i < CM_DIROP_MAXBUFFERS; i++) {
+            if (!(op->buffers[i].flags & CM_DIROPBUFF_INUSE))
+                break;
+        }
+
+        osi_assert(i < CM_DIROP_MAXBUFFERS);
+
+        lock_ObtainMutex(&bufferp->mx);
+        lock_ObtainMutex(&op->scp->mx);
+
+        /* Make sure we are synchronized. */
+        code = cm_SyncOp(op->scp, bufferp, op->userp, &op->req, PRSFS_LOOKUP,
+                         CM_SCACHESYNC_NEEDCALLBACK |
+                         CM_SCACHESYNC_WRITE |
+                         CM_SCACHESYNC_BUFLOCKED);
+
+        if (code == 0 &&
+            bufferp->dataVersion != op->dataVersion) {
+
+            osi_Log2(afsd_logp, "cm_DirOpAddBuffer: buffer version mismatch. buf ver = %d. want %d", bufferp->dataVersion, op->dataVersion);
+
+            cm_SyncOpDone(op->scp, bufferp,
+                          CM_SCACHESYNC_NEEDCALLBACK |
+                          CM_SCACHESYNC_WRITE |
+                          CM_SCACHESYNC_BUFLOCKED);
+
+            code = CM_ERROR_INVAL;
+        }
+
+        lock_ReleaseMutex(&op->scp->mx);
+        lock_ReleaseMutex(&bufferp->mx);
+
+        if (code) {
+            osi_Log1(afsd_logp, "cm_DirOpAddBuffer: failed to sync buffer.  code=0x%x",
+                     code);
+            return code;
+        }
+
+        buf_Hold(bufferp);
+        op->buffers[i].bufferp = bufferp;
+        op->buffers[i].refcount = 1; /* start with one ref */
+        op->buffers[i].flags = CM_DIROPBUFF_INUSE;
+
+        op->nBuffers++;
+
+        osi_Log0(afsd_logp, "cm_DirOpAddBuffer: returning success");
+
+        return 0;
+    }
+}
+
+/* Note: Called without op->scp->mx */
+static int
+cm_DirOpFindBuffer(cm_dirOp_t * op, osi_hyper_t offset, cm_buf_t ** bufferpp)
+{
+    int i;
+
+    for (i=0; i < CM_DIROP_MAXBUFFERS; i++) {
+        if ((op->buffers[i].flags & CM_DIROPBUFF_INUSE) &&
+            LargeIntegerEqualTo(op->buffers[i].bufferp->offset, offset))
+            break;
+    }
+
+    if (i < CM_DIROP_MAXBUFFERS) {
+        /* found it */
+        op->buffers[i].refcount++;
+        buf_Hold(op->buffers[i].bufferp);
+        *bufferpp = op->buffers[i].bufferp;
+
+        osi_Log2(afsd_logp, "cm_DirOpFindBuffer: found buffer for offset [%x:%x]",
+                 offset.HighPart, offset.LowPart);
+        return 1;
+    }
+
+    osi_Log2(afsd_logp, "cm_DirOpFindBuffer: buffer not found for offset [%x:%x]",
+             offset.HighPart, offset.LowPart);
+    return 0;
+}
+
+
+/* NOTE: called with scp->mx NOT held */
+static int
+cm_DirOpDelBuffer(cm_dirOp_t * op, cm_buf_t * bufferp, int flags)
+{
+    int i;
+
+    osi_Log3(afsd_logp, "cm_DirOpDelBuffer for op 0x%p, buffer 0x%p, flags=%d",
+             op, bufferp, flags);
+
+    for (i=0; i < CM_DIROP_MAXBUFFERS; i++) {
+        if ((op->buffers[i].flags & CM_DIROPBUFF_INUSE) &&
+            op->buffers[i].bufferp == bufferp)
+            break;
+    }
+
+    if (i < CM_DIROP_MAXBUFFERS) {
+
+        if (flags & DIROP_MODIFIED)
+            op->dirtyBufCount++;
+
+        osi_assert(op->buffers[i].refcount > 0);
+        op->buffers[i].refcount --;
+
+        if (op->buffers[i].refcount == 0) {
+            /* this was the last reference we had */
+
+            osi_Log0(afsd_logp, "cm_DirOpDelBuffer: releasing buffer");
+
+            /* if this buffer was modified, then we update the data
+               version of the buffer with the data version of the
+               scp. */
+            if (!(flags & DIROP_SCPLOCKED)) {
+                lock_ObtainMutex(&op->scp->mx);
+            }
+
+            /* first make sure that the buffer is idle.  It should
+               have been idle all along. */
+            osi_assertx((bufferp->cmFlags & (CM_BUF_CMFETCHING |
+                                            CM_BUF_CMSTORING)) == 0,
+                        "Buffer is not idle while performing dirOp");
+
+            cm_SyncOpDone(op->scp, bufferp,
+                          CM_SCACHESYNC_NEEDCALLBACK |
+                          CM_SCACHESYNC_WRITE);
+
+#ifdef DEBUG
+            osi_assert(bufferp->dataVersion == op->dataVersion);
+#endif
+
+            lock_ReleaseMutex(&op->scp->mx);
+
+            lock_ObtainMutex(&bufferp->mx);
+
+            if (flags & DIROP_SCPLOCKED) {
+                lock_ObtainMutex(&op->scp->mx);
+            }
+
+            if (flags & DIROP_MODIFIED) {
+                /* We don't update the dataversion here.  Instead we
+                   wait until the dirOp is completed and then flip the
+                   dataversion on all the buffers in one go.
+                   Otherwise we won't know if the dataversion is
+                   current because it was fetched from the server or
+                   because we touched it during the dirOp. */
+
+                if (bufferp->userp != op->userp) {
+                    if (bufferp->userp != NULL)
+                        cm_ReleaseUser(bufferp->userp);
+                    cm_HoldUser(op->userp);
+                    bufferp->userp = op->userp;
+                }
+            }
+
+            lock_ReleaseMutex(&bufferp->mx);
+
+            op->buffers[i].bufferp = NULL;
+            buf_Release(bufferp);
+            op->buffers[i].flags = 0;
+
+            op->nBuffers--;
+
+            return 1;
+        } else {
+            /* we have other references to this buffer. so we have to
+               let it be */
+            return 0;
+        }
+
+    } else {
+        osi_Log0(afsd_logp, "cm_DirOpDelBuffer: buffer not found");
+        osi_assertx(FALSE, "Attempt to delete a non-existent buffer from a dirOp");
+        return -1;
+    }
+}
+
+/* Check if we have current status and a callback for the given scp.
+   This should be called before cm_DirGetPage() is called per scp.
+
+   On entry:
+     scp->mx locked
+
+   On exit:
+     scp->mx locked
+
+   During:
+     scp->mx may be released
+ */
+static long
+cm_DirCheckStatus(cm_dirOp_t * op)
+{
+    long code;
+
+    code = cm_SyncOp(op->scp, NULL, op->userp, &op->req, PRSFS_LOOKUP,
+                     CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
+
+    osi_Log2(afsd_logp, "cm_DirCheckStatus for op 0x%p returning code 0x%x",
+             op, code);
+
+    return code;
+}
+
+/* Release a directory buffer that was obtained via a call to
+   cm_DirGetPage() or any other function that returns a locked, held,
+   directory page buffer.
+
+   Called with scp->mx held
+ */
+static long
+cm_DirReleasePage(cm_dirOp_t * op, cm_buf_t ** bufferpp, int modified)
+{
+    long code = 0;
+
+    if (!*bufferpp)
+        return EINVAL;
+
+    cm_DirOpDelBuffer(op, *bufferpp,
+                      ((modified ? DIROP_MODIFIED : 0) | DIROP_SCPLOCKED));
+    buf_Release(*bufferpp);
+    *bufferpp = NULL;
+
+    return code;
+}
+
+/*
+   Returns the index'th directory page from scp.  The userp and reqp
+   will be used to fetch the buffer from the fileserver if necessary.
+   If the call is successful, a locked and held cm_buf_t is returned
+   via buferpp and a pointer to the directory page is returned via
+   datapp.
+
+   The returned buffer should be released via a call to
+   cm_DirReleasePage() or by passing it into a subsequent call to
+   cm_DirGetPage() for the *same* scp.
+
+   If a *locked* buffer for the *same* scp is passed in via bufferpp
+   to the function, it will check if the requested directory page is
+   located in the specified buffer.  If not, the buffer will be
+   released and a new buffer returned that contains the requested
+   page.
+
+   Note: If a buffer is specified on entry via bufferpp, it is assumed
+   that the buffer is unmodified.  If the buffer is modified, it
+   should be released via cm_DirReleasePage().
+
+   On entry:
+     scp->mx locked.
+     If *bufferpp is non-NULL, then *bufferpp->mx is locked.
+
+   On exit:
+     scp->mxlocked
+     If *bufferpp is non-NULL, then *bufferpp->mx is locked.
+
+   During:
+     scp->mx will be released
+
+ */
+static long
+cm_DirGetPage(cm_dirOp_t * op,
+              long index, cm_buf_t ** bufferpp, void ** datapp)
+{
+    osi_hyper_t pageOffset;     /* offset of the dir page from the
+                                   start of the directory */
+    osi_hyper_t bufferOffset;   /* offset of the buffer from the start
+                                   of the directory */
+    osi_hyper_t thyper;
+
+    cm_buf_t * bufferp = NULL;
+
+    void * datap = NULL;
+
+    long code = 0;
+
+    osi_Log2(afsd_logp, "cm_DirGetPage for op 0x%p, index %d", op, index);
+
+    pageOffset = ConvertLongToLargeInteger(index * CM_DIR_PAGESIZE);
+    bufferOffset.HighPart = pageOffset.HighPart;
+    bufferOffset.LowPart = pageOffset.LowPart & ~(cm_data.buf_blockSize - 1);
+
+    bufferp = *bufferpp;
+    if (bufferp != NULL) {
+        osi_assert(cm_FidCmp(&bufferp->fid, &op->scp->fid) == 0);
+
+        thyper = bufferp->offset;
+    }
+
+    lock_ReleaseMutex(&op->scp->mx);
+
+    if (!bufferp || !LargeIntegerEqualTo(thyper, bufferOffset)) {
+        /* wrong buffer */
+
+        if (bufferp) {
+            buf_Release(bufferp);
+            cm_DirOpDelBuffer(op, bufferp, 0);
+            bufferp = NULL;
+        }
+
+        /* first check if we are already working with the buffer */
+        if (cm_DirOpFindBuffer(op, bufferOffset, &bufferp)) {
+            code = 0;
+            goto _has_buffer;
+        }
+
+        lock_ObtainRead(&op->scp->bufCreateLock);
+        code = buf_Get(op->scp, &bufferOffset, &bufferp);
+        lock_ReleaseRead(&op->scp->bufCreateLock);
+
+        if (code) {
+            osi_Log1(afsd_logp, "    buf_Get returned code 0x%x", code);
+            bufferp = NULL;
+            goto _exit;
+        }
+
+        osi_assert(bufferp != NULL);
+
+        /* DirOpAddBuffer will obtain bufferp->mx if necessary */
+        code = cm_DirOpAddBuffer(op, bufferp);
+
+        if (code != 0) {
+            /* for some reason, the buffer was rejected.  We can't use
+               this buffer, and since this is the only buffer we can
+               potentially use, there's no recourse.*/
+            buf_Release(bufferp);
+            bufferp = NULL;
+            goto _exit;
+        }
+
+#if 0
+        /* The code below is for making sure the buffer contains
+           current data.  This is a bad idea, since the whole point of
+           doing directory updates locally is to avoid fetching all
+           the data from the server. */
+        while (1) {
+            lock_ObtainMutex(&op->scp->mx);
+            code = cm_SyncOp(op->scp, bufferp, op->userp, &op->req, PRSFS_LOOKUP,
+                             CM_SCACHESYNC_NEEDCALLBACK |
+                             CM_SCACHESYNC_READ |
+                             CM_SCACHESYNC_BUFLOCKED);
+
+            if (code) {
+                lock_ReleaseMutex(&op->scp->mx);
+                break;
+            }
+
+            cm_SyncOpDone(op->scp, bufferp,
+                          CM_SCACHESYNC_NEEDCALLBACK |
+                          CM_SCACHESYNC_READ |
+                          CM_SCACHESYNC_BUFLOCKED);
+
+            if (cm_HaveBuffer(op->scp, bufferp, 1)) {
+                lock_ReleaseMutex(&op->scp->mx);
+                break;
+            }
+
+            lock_ReleaseMutex(&bufferp->mx);
+            code = cm_GetBuffer(op->scp, bufferp, NULL, op->userp, &op->req);
+            lock_ReleaseMutex(&op->scp->mx);
+            lock_ObtainMutex(&bufferp->mx);
+
+            if (code)
+                break;
+        }
+
+        if (code) {
+            cm_DirOpDelBuffer(op, bufferp, 0);
+            buf_Release(bufferp);
+            bufferp = NULL;
+            goto _exit;
+        }
+#endif
+    }
+
+ _has_buffer:
+
+    /* now to figure out where the data is */
+    thyper = LargeIntegerSubtract(pageOffset, bufferOffset);
+
+    osi_assert(thyper.HighPart == 0);
+    osi_assert(cm_data.buf_blockSize > thyper.LowPart &&
+               cm_data.buf_blockSize - thyper.LowPart >= CM_DIR_PAGESIZE);
+
+    datap = (void *) (((char *)bufferp->datap) + thyper.LowPart);
+
+    if (datapp)
+        *datapp = datap;
+
+    /* also, if we are writing past EOF, we should make a note of the
+       new length */
+    thyper = LargeIntegerAdd(pageOffset,
+                             ConvertLongToLargeInteger(CM_DIR_PAGESIZE));
+    if (LargeIntegerLessThan(op->newLength, thyper)) {
+        op->newLength = thyper;
+    }
+
+ _exit:
+
+    *bufferpp = bufferp;
+    if (op->scp)
+        lock_ObtainMutex(&op->scp->mx);
+
+    osi_Log1(afsd_logp, "cm_DirGetPage returning code 0x%x", code);
+
+    return code;
+}
+
+
+void
+cm_DirEntryListAdd(char * namep, cm_dirEntryList_t ** list)
+{
+    size_t len;
+    cm_dirEntryList_t * entry;
+
+    len = strlen(namep);
+    len += sizeof(cm_dirEntryList_t);
+
+    entry = malloc(len);
+    if (entry) {
+        entry->nextp = *list;
+        strcpy(entry->name, namep);
+        *list = entry;
+    }
+}
+
+void
+cm_DirEntryListFree(cm_dirEntryList_t ** list)
+{
+    cm_dirEntryList_t * entry;
+    cm_dirEntryList_t * next;
+
+    for (entry = *list; entry; entry = next) {
+        next = entry->nextp;
+        free(entry);
+    }
+
+    *list = NULL;
+}
+
index d53a14a..3a720ef 100644 (file)
@@ -31,6 +31,9 @@
                                                 * header alone.
                                                 */
 
+#define CM_DIR_FFIRST           1
+#define CM_DIR_FNEXT            2
+
 typedef struct cm_dirFid {
        /* A file identifier. */
        afs_int32 vnode;        /* file's vnode slot */
@@ -56,8 +59,9 @@ typedef struct cm_dirHeader {
        unsigned short hashTable[CM_DIR_NHASHENT];
 } cm_dirHeader_t;
 
-/* this represents a directory entry.  We use strlen to find out how many bytes are
- * really in the dir entry; it is always a multiple of 32.
+/* this represents a directory entry.  We use strlen to find out how
+ * many bytes are really in the dir entry; it is always a multiple of
+ * 32.
  */
 typedef struct cm_dirEntry {
        /* A directory entry */
@@ -87,6 +91,88 @@ typedef struct cm_dirPage1 {
 } cm_dirPage1_t;
 #endif /* UNUSED */
 
-extern long cm_NameEntries(char *namep, long *lenp);
+#define CM_DIROP_MAXBUFFERS 8
+
+typedef struct cm_dirOpBuffer {
+    int        flags;
+    cm_buf_t * bufferp;
+    int        refcount;
+} cm_dirOpBuffer_t;
+
+#define CM_DIROPBUFF_INUSE      0x1
+
+/* Used for managing transactional directory operations.  Each
+   instance should only be used by one thread. */
+typedef struct cm_dirOp {
+    cm_scache_t * scp;
+    cm_user_t *   userp;
+    cm_req_t      req;
+
+    osi_hyper_t   length;       /* scp->length at the time
+                                   cm_BeginDirOp() was called.*/
+    osi_hyper_t   newLength;    /* adjusted scp->length */
+    afs_uint32    dataVersion;  /* scp->dataVersion when
+                                   cm_BeginDirOp() was called.*/
+    afs_uint32    newDataVersion; /* scp->dataVersion when
+                                     cm_CheckDirOpForSingleChange()
+                                     was called. */
+
+    afs_uint32    dirtyBufCount;
+
+    int           nBuffers;     /* number of buffers below */
+    cm_dirOpBuffer_t buffers[CM_DIROP_MAXBUFFERS];
+} cm_dirOp_t;
+
+extern long
+cm_BeginDirOp(cm_scache_t * scp, cm_user_t * userp, cm_req_t * reqp,
+              cm_dirOp_t * op);
+
+extern int
+cm_CheckDirOpForSingleChange(cm_dirOp_t * op);
+
+extern long
+cm_EndDirOp(cm_dirOp_t * op);
+
+extern long
+cm_NameEntries(char *namep, long *lenp);
+
+extern long
+cm_DirCreateEntry(cm_dirOp_t * op, char *entry, cm_fid_t * cfid);
+
+extern int
+cm_DirLength(cm_dirOp_t * op);
+
+extern int
+cm_DirDeleteEntry(cm_dirOp_t * op, char *entry);
+
+extern int
+cm_DirMakeDir(cm_dirOp_t * op, cm_fid_t * me, cm_fid_t * parent);
+
+extern int
+cm_DirLookup(cm_dirOp_t * op, char *entry, cm_fid_t * cfid);
+
+extern int
+cm_DirLookupOffset(cm_dirOp_t * op, char *entry, cm_fid_t *cfid, osi_hyper_t *offsetp);
+
+extern int
+cm_DirApply(cm_dirOp_t * op, int (*hookproc) (void *, char *, long, long), void *hook);
+
+extern int
+cm_DirIsEmpty(cm_dirOp_t * op);
+
+extern int
+cm_DirHash(char *string);
+
+/* Directory entry lists */
+typedef struct cm_dirEntryList {
+    struct cm_dirEntryList * nextp;
+    char   name[1];
+} cm_dirEntryList_t;
+
+extern void
+cm_DirEntryListAdd(char * namep, cm_dirEntryList_t ** list);
+
+extern void
+cm_DirEntryListFree(cm_dirEntryList_t ** list);
 
 #endif /*  __CM_DIR_ENV__ */
index 5f6937f..437c999 100644 (file)
@@ -964,8 +964,8 @@ long cm_SyncOp(cm_scache_t *scp, cm_buf_t *bufp, cm_user_t *userp, cm_req_t *req
                 osi_Log1(afsd_logp, "CM SyncOp scp 0x%p is FETCHING|STORING|SIZESTORING|GETCALLBACK want FETCHDATA", scp);
                 goto sleep;
             }
-            if (bufp && (bufp->cmFlags & (CM_BUF_CMFETCHING | CM_BUF_CMSTORING))) {
-                osi_Log2(afsd_logp, "CM SyncOp scp 0x%p bufp 0x%p is BUF_CMFETCHING|BUF_CMSTORING want FETCHDATA", scp, bufp);
+            if (bufp && (bufp->cmFlags & (CM_BUF_CMFETCHING | CM_BUF_CMSTORING | CM_BUF_CMWRITING))) {
+                osi_Log2(afsd_logp, "CM SyncOp scp 0x%p bufp 0x%p is BUF_CMFETCHING|BUF_CMSTORING|BUF_CMWRITING want FETCHDATA", scp, bufp);
                 goto sleep;
             }
         }
@@ -976,8 +976,8 @@ long cm_SyncOp(cm_scache_t *scp, cm_buf_t *bufp, cm_user_t *userp, cm_req_t *req
                 osi_Log1(afsd_logp, "CM SyncOp scp 0x%p is FETCHING|STORING|SIZESTORING|GETCALLBACK want STOREDATA", scp);
                 goto sleep;
             }
-            if (bufp && (bufp->cmFlags & (CM_BUF_CMFETCHING | CM_BUF_CMSTORING))) {
-                osi_Log2(afsd_logp, "CM SyncOp scp 0x%p bufp 0x%p is BUF_CMFETCHING|BUF_CMSTORING want STOREDATA", scp, bufp);
+            if (bufp && (bufp->cmFlags & (CM_BUF_CMFETCHING | CM_BUF_CMSTORING | CM_BUF_CMWRITING))) {
+                osi_Log2(afsd_logp, "CM SyncOp scp 0x%p bufp 0x%p is BUF_CMFETCHING|BUF_CMSTORING|BUF_CMWRITING want STOREDATA", scp, bufp);
                 goto sleep;
             }
         }
@@ -1043,6 +1043,10 @@ long cm_SyncOp(cm_scache_t *scp, cm_buf_t *bufp, cm_user_t *userp, cm_req_t *req
                 osi_Log2(afsd_logp, "CM SyncOp scp 0x%p bufp 0x%p is BUF_CMFETCHING want READ", scp, bufp);
                 goto sleep;
             }
+            if (bufp && (bufp->cmFlags & CM_BUF_CMWRITING)) {
+                osi_Log2(afsd_logp, "CM SyncOp scp 0x%p bufp 0x%p is BUF_CMWRITING want READ", scp, bufp);
+                goto sleep;
+            }
         }
         if (flags & CM_SCACHESYNC_WRITE) {
             /* don't write unless the status is stable and the chunk
@@ -1053,8 +1057,15 @@ long cm_SyncOp(cm_scache_t *scp, cm_buf_t *bufp, cm_user_t *userp, cm_req_t *req
                 osi_Log1(afsd_logp, "CM SyncOp scp 0x%p is FETCHING|STORING|SIZESTORING want WRITE", scp);
                 goto sleep;
             }
-            if (bufp && (bufp->cmFlags & (CM_BUF_CMFETCHING | CM_BUF_CMSTORING))) {
-                osi_Log2(afsd_logp, "CM SyncOp scp 0x%p bufp 0x%p is BUF_CMFETCHING|BUF_CMSTORING want WRITE", scp, bufp);
+            if (bufp && (bufp->cmFlags & (CM_BUF_CMFETCHING |
+                                          CM_BUF_CMSTORING |
+                                          CM_BUF_CMWRITING))) {
+                osi_Log3(afsd_logp, "CM SyncOp scp 0x%p bufp 0x%p is %s want WRITE",
+                         scp, bufp,
+                         ((bufp->cmFlags & CM_BUF_CMFETCHING) ? "CM_BUF_CMFETCHING":
+                          ((bufp->cmFlags & CM_BUF_CMSTORING) ? "CM_BUF_CMSTORING" :
+                           ((bufp->cmFlags & CM_BUF_CMWRITING) ? "CM_BUF_CMWRITING" :
+                            "UNKNOWN!!!"))));
                 goto sleep;
             }
         }
@@ -1215,6 +1226,13 @@ long cm_SyncOp(cm_scache_t *scp, cm_buf_t *bufp, cm_user_t *userp, cm_req_t *req
         osi_QAdd((osi_queue_t **) &scp->bufWritesp, &qdp->q);
     }
 
+    if (flags & CM_SCACHESYNC_WRITE) {
+        /* mark the buffer as being written to. */
+        if (bufp) {
+            bufp->cmFlags |= CM_BUF_CMWRITING;
+        }
+    }
+
     return 0;
 }
 
@@ -1295,6 +1313,14 @@ void cm_SyncOpDone(cm_scache_t *scp, cm_buf_t *bufp, afs_uint32 flags)
         }
     }
 
+    if (flags & CM_SCACHESYNC_WRITE) {
+        if (bufp) {
+            osi_assert(bufp->cmFlags & CM_BUF_CMWRITING);
+
+            bufp->cmFlags &= ~CM_BUF_CMWRITING;
+        }
+    }
+
     /* and wakeup anyone who is waiting */
     if (scp->flags & CM_SCACHEFLAG_WAITING) {
         osi_Log1(afsd_logp, "CM SyncOpDone Waking scp 0x%p", scp);
index 5c29700..60e895d 100644 (file)
@@ -602,15 +602,18 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
     {
         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 */
-    {
+              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 */
@@ -620,6 +623,32 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
             return 0;
         }
         sp->caseFold = casefold;
+
+            /* see if we can find it using the directory hash tables.
+               we can only do exact matches, since the hash is case
+               sensitive. */
+            {
+                cm_dirOp_t dirop;
+
+                code = ENOENT;
+
+                code = cm_BeginDirOp(scp, userp, reqp, &dirop);
+                if (code == 0) {
+                    code = cm_DirLookup(&dirop, sp->searchNamep, &sp->fid);
+                    cm_EndDirOp(&dirop);
+                }
+
+                if (code == 0) {
+                    /* found it */
+                    sp->found = TRUE;
+                    sp->ExactFound = TRUE;
+                    lock_ReleaseMutex(&scp->mx);
+
+                    *retscp = NULL; /* force caller to call cm_GetSCache() */
+
+                    return 0;
+                }
+            }
     }
     }  
 
@@ -1126,6 +1155,8 @@ 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)
@@ -1137,7 +1168,30 @@ long cm_LookupInternal(cm_scache_t *dscp, char *namep, long flags, cm_user_t *us
        goto haveFid;
     }
 
-    memset(&rock, 0, sizeof(rock));
+    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;
+
+        lock_ObtainMutex(&dscp->mx);
+
+        code = cm_BeginDirOp(dscp, userp, reqp, &dirop);
+        if (code == 0) {
+            code = cm_DirLookup(&dirop, namep, &rock.fid);
+            cm_EndDirOp(&dirop);
+        }
+
+        lock_ReleaseMutex(&dscp->mx);
+
+        if (code == 0) {
+            /* found it */
+            rock.found = TRUE;
+            goto haveFid;
+        }
+    }
+
     rock.fid.cell = dscp->fid.cell;
     rock.fid.volume = dscp->fid.volume;
     rock.searchNamep = namep;
@@ -1484,6 +1538,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) {
@@ -1495,11 +1550,16 @@ long cm_Unlink(cm_scache_t *dscp, char *namep, cm_user_t *userp, cm_req_t *reqp)
 
     /* make sure we don't screw up the dir status during the merge */
     lock_ObtainMutex(&dscp->mx);
+
+    code = cm_BeginDirOp(dscp, userp, reqp, &dirop);
+
     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;
@@ -1528,15 +1588,19 @@ long cm_Unlink(cm_scache_t *dscp, char *namep, cm_user_t *userp, cm_req_t *reqp)
     lock_ObtainMutex(&dscp->mx);
     cm_dnlcRemove(dscp, namep);
     cm_SyncOpDone(dscp, NULL, sflags);
-    if (code == 0) 
+    if (code == 0) {
         cm_MergeStatus(NULL, dscp, &newDirStatus, &volSync, userp, 0);
-    else if (code == CM_ERROR_NOSUCHFILE) {
+        if (cm_CheckDirOpForSingleChange(&dirop)) {
+            cm_DirDeleteEntry(&dirop, namep);
+        }
+    } 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;
     }
+    cm_EndDirOp(&dirop);
     lock_ReleaseMutex(&dscp->mx);
 
     return code;
@@ -2459,7 +2523,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;
@@ -2467,6 +2531,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.
@@ -2480,9 +2545,12 @@ long cm_Create(cm_scache_t *dscp, char *namep, long flags, cm_attr_t *attrp,
      * completes.
      */
     lock_ObtainMutex(&dscp->mx);
+    cm_BeginDirOp(dscp, userp, reqp, &dirop);
     code = cm_SyncOp(dscp, NULL, userp, reqp, 0, CM_SCACHESYNC_STOREDATA);
     if (code == 0) {
         cm_StartCallbackGrantingCall(NULL, &cbReq);
+    } else {
+        cm_EndDirOp(&dirop);
     }
     lock_ReleaseMutex(&dscp->mx);
     if (code) {
@@ -2556,6 +2624,13 @@ long cm_Create(cm_scache_t *dscp, char *namep, long flags, cm_attr_t *attrp,
     if (!didEnd)
         cm_EndCallbackGrantingCall(NULL, &cbReq, NULL, 0);
 
+    lock_ObtainMutex(&dscp->mx);
+    if (scp && cm_CheckDirOpForSingleChange(&dirop)) {
+        cm_DirCreateEntry(&dirop, namep, &newFid);
+    }
+    cm_EndDirOp(&dirop);
+    lock_ReleaseMutex(&dscp->mx);
+
     return code;
 }       
 
@@ -2601,6 +2676,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.
@@ -2614,9 +2690,12 @@ long cm_MakeDir(cm_scache_t *dscp, char *namep, long flags, cm_attr_t *attrp,
      * our call completes.
      */
     lock_ObtainMutex(&dscp->mx);
+    cm_BeginDirOp(dscp, userp, reqp, &dirop);
     code = cm_SyncOp(dscp, NULL, userp, reqp, 0, CM_SCACHESYNC_STOREDATA);
     if (code == 0) {
         cm_StartCallbackGrantingCall(NULL, &cbReq);
+    } else {
+        cm_EndDirOp(&dirop);
     }
     lock_ReleaseMutex(&dscp->mx);
     if (code) {
@@ -2689,6 +2768,13 @@ long cm_MakeDir(cm_scache_t *dscp, char *namep, long flags, cm_attr_t *attrp,
     if (!didEnd)
         cm_EndCallbackGrantingCall(NULL, &cbReq, NULL, 0);
 
+    lock_ObtainMutex(&dscp->mx);
+    if (scp && cm_CheckDirOpForSingleChange(&dirop)) {
+        cm_DirCreateEntry(&dirop, namep, &newFid);
+    }
+    cm_EndDirOp(&dirop);
+    lock_ReleaseMutex(&dscp->mx);
+
     /* and return error code */
     return code;
 }       
@@ -2704,6 +2790,7 @@ 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) {
@@ -2711,7 +2798,10 @@ long cm_Link(cm_scache_t *dscp, char *namep, cm_scache_t *sscp, long flags,
     }
 
     lock_ObtainMutex(&dscp->mx);
+    cm_BeginDirOp(dscp, userp, reqp, &dirop);
     code = cm_SyncOp(dscp, NULL, userp, reqp, 0, CM_SCACHESYNC_STOREDATA);
+    if (code != 0)
+        cm_EndDirOp(&dirop);
     lock_ReleaseMutex(&dscp->mx);
 
     if (code)
@@ -2751,7 +2841,11 @@ long cm_Link(cm_scache_t *dscp, char *namep, cm_scache_t *sscp, long flags,
     cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
     if (code == 0) {
         cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, 0);
+        if (cm_CheckDirOpForSingleChange(&dirop)) {
+            cm_DirCreateEntry(&dirop, namep, &sscp->fid);
+        }
     }
+    cm_EndDirOp(&dirop);
     lock_ReleaseMutex(&dscp->mx);
 
     return code;
@@ -2771,13 +2865,17 @@ 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.
      */
     lock_ObtainMutex(&dscp->mx);
+    cm_BeginDirOp(dscp, userp, reqp, &dirop);
     code = cm_SyncOp(dscp, NULL, userp, reqp, 0, CM_SCACHESYNC_STOREDATA);
+    if (code != 0)
+        cm_EndDirOp(&dirop);
     lock_ReleaseMutex(&dscp->mx);
     if (code) {
         return code;
@@ -2815,7 +2913,16 @@ long cm_SymLink(cm_scache_t *dscp, char *namep, char *contentsp, long flags,
     cm_SyncOpDone(dscp, NULL, CM_SCACHESYNC_STOREDATA);
     if (code == 0) {
         cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, 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);
+        }
     }
+    cm_EndDirOp(&dirop);
     lock_ReleaseMutex(&dscp->mx);
 
     /* now try to create the new dir's entry, too, but be careful to 
@@ -2854,17 +2961,21 @@ 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.
      */
     lock_ObtainMutex(&dscp->mx);
+    cm_BeginDirOp(dscp, userp, reqp, &dirOp);
     code = cm_SyncOp(dscp, NULL, userp, reqp, 0, CM_SCACHESYNC_STOREDATA);
-    lock_ReleaseMutex(&dscp->mx);
     if (code) {
+        cm_EndDirOp(&dirOp);
+        lock_ReleaseMutex(&dscp->mx);
         return code;
     }
+    lock_ReleaseMutex(&dscp->mx);
     didEnd = 0;
 
     /* try the RPC now */
@@ -2897,7 +3008,11 @@ long cm_RemoveDir(cm_scache_t *dscp, char *namep, cm_user_t *userp,
     if (code == 0) {
         cm_dnlcRemove(dscp, namep); 
         cm_MergeStatus(NULL, dscp, &updatedDirStatus, &volSync, userp, 0);
+        if (cm_CheckDirOpForSingleChange(&dirOp)) {
+            cm_DirDeleteEntry(&dirOp, namep);
+    }
     }
+    cm_EndDirOp(&dirOp);
     lock_ReleaseMutex(&dscp->mx);
 
     /* and return error code */
@@ -2935,6 +3050,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
@@ -2950,8 +3069,12 @@ long cm_Rename(cm_scache_t *oldDscp, char *oldNamep, cm_scache_t *newDscp,
         lock_ObtainMutex(&oldDscp->mx);
         cm_dnlcRemove(oldDscp, oldNamep);
         cm_dnlcRemove(oldDscp, newNamep);
+        cm_BeginDirOp(oldDscp, userp, reqp, &oldDirOp);
         code = cm_SyncOp(oldDscp, NULL, userp, reqp, 0,
                           CM_SCACHESYNC_STOREDATA);
+        if (code != 0) {
+            cm_EndDirOp(&oldDirOp);
+        }
         lock_ReleaseMutex(&oldDscp->mx);
     }
     else {
@@ -2970,21 +3093,28 @@ long cm_Rename(cm_scache_t *oldDscp, char *oldNamep, cm_scache_t *newDscp,
 
         if (oldDscp->fid.vnode < newDscp->fid.vnode) {
             lock_ObtainMutex(&oldDscp->mx);
+            cm_BeginDirOp(oldDscp, userp, reqp, &oldDirOp);
             cm_dnlcRemove(oldDscp, oldNamep);
             code = cm_SyncOp(oldDscp, NULL, userp, reqp, 0,
                               CM_SCACHESYNC_STOREDATA);
+            if (code != 0)
+                cm_EndDirOp(&oldDirOp);
             lock_ReleaseMutex(&oldDscp->mx);
             if (code == 0) {
                 lock_ObtainMutex(&newDscp->mx);
+                cm_BeginDirOp(newDscp, userp, reqp, &newDirOp);
                 cm_dnlcRemove(newDscp, newNamep);
                 code = cm_SyncOp(newDscp, NULL, userp, reqp, 0,
                                   CM_SCACHESYNC_STOREDATA);
+                if (code != 0)
+                    cm_EndDirOp(&newDirOp);
                 lock_ReleaseMutex(&newDscp->mx);
                 if (code) {
                     /* cleanup first one */
                     lock_ObtainMutex(&oldDscp->mx);
                     cm_SyncOpDone(oldDscp, NULL,
                                    CM_SCACHESYNC_STOREDATA);
+                    cm_EndDirOp(&oldDirOp);
                     lock_ReleaseMutex(&oldDscp->mx);
                 }       
             }
@@ -2992,21 +3122,28 @@ long cm_Rename(cm_scache_t *oldDscp, char *oldNamep, cm_scache_t *newDscp,
         else {
             /* lock the new vnode entry first */
             lock_ObtainMutex(&newDscp->mx);
+            cm_BeginDirOp(newDscp, userp, reqp, &newDirOp);
             cm_dnlcRemove(newDscp, newNamep);
             code = cm_SyncOp(newDscp, NULL, userp, reqp, 0,
                               CM_SCACHESYNC_STOREDATA);
+            if (code != 0)
+                cm_EndDirOp(&newDirOp);
             lock_ReleaseMutex(&newDscp->mx);
             if (code == 0) {
                 lock_ObtainMutex(&oldDscp->mx);
+                cm_BeginDirOp(oldDscp, userp, reqp, &oldDirOp);
                 cm_dnlcRemove(oldDscp, oldNamep);
                 code = cm_SyncOp(oldDscp, NULL, userp, reqp, 0,
                                   CM_SCACHESYNC_STOREDATA);
+                if (code != 0)
+                    cm_EndDirOp(&oldDirOp);
                 lock_ReleaseMutex(&oldDscp->mx);
                 if (code) {
                     /* cleanup first one */
                     lock_ObtainMutex(&newDscp->mx);
                     cm_SyncOpDone(newDscp, NULL,
                                    CM_SCACHESYNC_STOREDATA);
+                    cm_EndDirOp(&newDirOp);
                     lock_ReleaseMutex(&newDscp->mx);
                 }       
             }
@@ -3052,10 +3189,27 @@ long cm_Rename(cm_scache_t *oldDscp, char *oldNamep, cm_scache_t *newDscp,
     /* update the individual stat cache entries for the directories */
     lock_ObtainMutex(&oldDscp->mx);
     cm_SyncOpDone(oldDscp, NULL, CM_SCACHESYNC_STOREDATA);
+
     if (code == 0) {
         cm_MergeStatus(NULL, oldDscp, &updatedOldDirStatus, &volSync,
                         userp, 0);
+
+        if (cm_CheckDirOpForSingleChange(&oldDirOp)) {
+
+            diropCode = cm_DirLookup(&oldDirOp, oldNamep, &fileFid);
+
+            if (diropCode == 0) {
+                if (oneDir) {
+                    diropCode = cm_DirCreateEntry(&oldDirOp, newNamep, &fileFid);
+                }
+
+                if (diropCode == 0) {
+                    diropCode = cm_DirDeleteEntry(&oldDirOp, oldNamep);
     }
+            }
+        }
+    }
+    cm_EndDirOp(&oldDirOp);
     lock_ReleaseMutex(&oldDscp->mx);
 
     /* and update it for the new one, too, if necessary */
@@ -3065,7 +3219,15 @@ long cm_Rename(cm_scache_t *oldDscp, char *oldNamep, cm_scache_t *newDscp,
         if (code == 0) {
             cm_MergeStatus(NULL, 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);
+            }
         }
+        cm_EndDirOp(&newDirOp);
         lock_ReleaseMutex(&newDscp->mx);
     }