windows-dot-dir-part-two-20060906
[openafs.git] / src / WINNT / afsd / cm_vnodeops.c
index b3feb75..ee8127c 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>
@@ -265,7 +263,8 @@ long cm_CheckOpen(cm_scache_t *scp, int openMode, int trunc, cm_user_t *userp,
 
     code = cm_SyncOp(scp, NULL, userp, reqp, rights,
                       CM_SCACHESYNC_GETSTATUS
-                      | CM_SCACHESYNC_NEEDCALLBACK);
+                     | CM_SCACHESYNC_NEEDCALLBACK
+                     | CM_SCACHESYNC_LOCK);
 
     if (code == 0 && 
         ((rights & PRSFS_WRITE) || (rights & PRSFS_READ)) &&
@@ -315,8 +314,15 @@ long cm_CheckOpen(cm_scache_t *scp, int openMode, int trunc, cm_user_t *userp,
                }
            }
         }
+
+    } else if (code != 0) {
+        goto _done;
     }
 
+    cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_LOCK);
+
+ _done:
+
     lock_ReleaseMutex(&scp->mx);
 
     return code;
@@ -346,7 +352,8 @@ long cm_CheckNTOpen(cm_scache_t *scp, unsigned int desiredAccess,
 
     code = cm_SyncOp(scp, NULL, userp, reqp, rights,
                       CM_SCACHESYNC_GETSTATUS
-                      | CM_SCACHESYNC_NEEDCALLBACK);
+                     | CM_SCACHESYNC_NEEDCALLBACK
+                     | CM_SCACHESYNC_LOCK);
 
     /*
      * If the open will fail because the volume is readonly, then we will
@@ -356,7 +363,8 @@ long cm_CheckNTOpen(cm_scache_t *scp, unsigned int desiredAccess,
      */
     if (code == CM_ERROR_READONLY)
         code = CM_ERROR_NOACCESS;
-    else if (code == 0 &&
+
+    if (code == 0 &&
              ((rights & PRSFS_WRITE) || (rights & PRSFS_READ)) &&
              scp->fileType == CM_SCACHETYPE_FILE) {
         cm_key_t key;
@@ -403,8 +411,13 @@ long cm_CheckNTOpen(cm_scache_t *scp, unsigned int desiredAccess,
                }
            }
         }
+    } else if (code != 0) {
+        goto _done;
     }
 
+    cm_SyncOpDone(scp, NULL, CM_SCACHESYNC_LOCK);
+
+ _done:
     lock_ReleaseMutex(&scp->mx);
 
     return code;
@@ -649,16 +662,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;
             }
@@ -969,7 +982,8 @@ long cm_FollowMountPoint(cm_scache_t *scp, cm_scache_t *dscp, cm_user_t *userp,
 
     /* parse the volume name */
     mpNamep = scp->mountPointStringp;
-    osi_assert(mpNamep[0]);
+    if (!mpNamep[0])
+       return CM_ERROR_NOSUCHPATH;
     tlen = (int)strlen(scp->mountPointStringp);
     mtType = *scp->mountPointStringp;
     cellNamep = malloc(tlen);
@@ -1078,6 +1092,9 @@ long cm_LookupInternal(cm_scache_t *dscp, char *namep, long flags, cm_user_t *us
             return CM_ERROR_NOSUCHVOLUME;
         rock.fid = dscp->dotdotFid;
         goto haveFid;
+    } else if (strcmp(namep, ".") == 0) {
+       rock.fid = dscp->fid;
+       goto haveFid;
     }
 
     memset(&rock, 0, sizeof(rock));
@@ -1197,7 +1214,7 @@ long cm_LookupInternal(cm_scache_t *dscp, char *namep, long flags, cm_user_t *us
     if ( !dnlcHit && !(flags & CM_FLAG_NOMOUNTCHASE) && rock.ExactFound ) {
         /* lock the directory entry to prevent racing callback revokes */
         lock_ObtainMutex(&dscp->mx);
-        if ( dscp->cbServerp && dscp->cbExpires )
+        if ( dscp->cbServerp != NULL && dscp->cbExpires > 0 )
             cm_dnlcEnter(dscp, namep, tscp);
         lock_ReleaseMutex(&dscp->mx);
     }
@@ -1240,7 +1257,7 @@ long cm_Lookup(cm_scache_t *dscp, char *namep, long flags, cm_user_t *userp,
     long code;
     char tname[256];
     int sysNameIndex = 0;
-    cm_scache_t *scp = 0;
+    cm_scache_t *scp = NULL;
 
     if ( stricmp(namep,SMB_IOCTL_FILENAME_NOSLASH) == 0 ) {
         if (flags & CM_FLAG_CHECKPATH)
@@ -1259,7 +1276,7 @@ long cm_Lookup(cm_scache_t *dscp, char *namep, long flags, cm_user_t *userp,
             }
             if (scp) {
                 cm_ReleaseSCache(scp);
-                scp = 0;
+                scp = NULL;
             }
         } else {
             return cm_LookupInternal(dscp, namep, flags, userp, reqp, outpScpp);
@@ -1328,6 +1345,13 @@ long cm_Unlink(cm_scache_t *dscp, char *namep, cm_user_t *userp, cm_req_t *reqp)
     cm_SyncOpDone(dscp, NULL, sflags);
     if (code == 0) 
         cm_MergeStatus(dscp, &newDirStatus, &volSync, userp, 0);
+       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);
 
     return code;
@@ -1520,7 +1544,7 @@ long cm_NameI(cm_scache_t *rootSCachep, char *pathp, long flags,
     tscp = rootSCachep;
     cm_HoldSCache(tscp);
     symlinkCount = 0;
-    dirScp = 0;
+    dirScp = NULL;
 
     while (1) {
         tc = *tp++;
@@ -1555,34 +1579,42 @@ 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)
+               if (!strcmp(".",component)) {
+                    code = 0;
+                    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;
-                }
-                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) {
+                        dirScp = NULL;
+                    }
+                    break;
+               }
+               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;
+               }       
+               haveComponent = 0;      /* component done */
+               if (dirScp)
+                   cm_ReleaseSCache(dirScp);
+               dirScp = tscp;          /* for some symlinks */
+               tscp = nscp;            /* already held */
+               nscp = NULL;
+               if (tc == 0 && !(flags & CM_FLAG_FOLLOW) && phase == 2) {
                     code = 0;
                     if (dirScp) {
                         cm_ReleaseSCache(dirScp);
-                        dirScp = 0;
+                        dirScp = NULL;
                     }
                     break;
                 }
@@ -1597,10 +1629,10 @@ 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;
                 }
@@ -1609,10 +1641,10 @@ long cm_NameI(cm_scache_t *rootSCachep, char *pathp, long flags,
                     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);
@@ -1626,10 +1658,10 @@ long cm_NameI(cm_scache_t *rootSCachep, char *pathp, long flags,
                     if (code) {
                         /* something went wrong */
                         cm_ReleaseSCache(tscp);
-                        tscp = 0;
+                        tscp = NULL;
                         if (dirScp) {
                             cm_ReleaseSCache(dirScp);
-                            dirScp = 0;
+                            dirScp = NULL;
                         }
                         break;
                     }
@@ -1648,7 +1680,7 @@ long cm_NameI(cm_scache_t *rootSCachep, char *pathp, long flags,
                     tp = psp->data;
                     cm_ReleaseSCache(tscp);
                     tscp = linkScp;
-                    linkScp = 0;
+                    linkScp = NULL;
                     /* already held
                      * by AssembleLink
                      * now, if linkScp is null, that's
@@ -1661,7 +1693,7 @@ long cm_NameI(cm_scache_t *rootSCachep, char *pathp, long flags,
                      */
                     if (tscp == NULL) {
                         tscp = dirScp;
-                        dirScp = 0;
+                        dirScp = NULL;
                     }
                 } else {
                     /* not a symlink, we may be done */
@@ -1674,7 +1706,7 @@ long cm_NameI(cm_scache_t *rootSCachep, char *pathp, long flags,
                         }
                         if (dirScp) {
                             cm_ReleaseSCache(dirScp);
-                            dirScp = 0;
+                            dirScp = NULL;
                         }
                         code = 0;
                         break;
@@ -1682,7 +1714,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 
@@ -1757,7 +1789,7 @@ long cm_EvaluateSymLink(cm_scache_t *dscp, cm_scache_t *linkScp,
  * check anyway, but we want to minimize the chance that we have to leave stuff
  * unstat'd.
  */
-#define CM_BULKMAX             128
+#define CM_BULKMAX             (3 * AFSCBMAX)
 
 /* rock for bulk stat calls */
 typedef struct cm_bulkStat {
@@ -1817,7 +1849,7 @@ long cm_TryBulkProc(cm_scache_t *scp, cm_dirEntry_t *dep, void *rockp,
     if (tscp) {
         if (lock_TryMutex(&tscp->mx)) {
             /* we have an entry that we can look at */
-            if (cm_HaveCallback(tscp)) {
+            if (!(tscp->flags & CM_SCACHEFLAG_EACCESS) && cm_HaveCallback(tscp)) {
                 /* we have a callback on it.  Don't bother
                  * fetching this stat entry, since we're happy
                  * with the info we have.
@@ -1855,11 +1887,12 @@ long cm_TryBulkProc(cm_scache_t *scp, cm_dirEntry_t *dep, void *rockp,
 /* called with a locked scp and a pointer to a buffer.  Make bulk stat
  * calls on all undeleted files in the page of the directory specified.
  */
-void cm_TryBulkStat(cm_scache_t *dscp, osi_hyper_t *offsetp, cm_user_t *userp,
-                     cm_req_t *reqp)
+afs_int32
+cm_TryBulkStat(cm_scache_t *dscp, osi_hyper_t *offsetp, cm_user_t *userp,
+              cm_req_t *reqp)
 {
     long code;
-    cm_bulkStat_t bb;  /* this is *BIG*, probably 12K or so;
+    cm_bulkStat_t bb;  /* this is *BIG*, probably 16K or so;
                          * watch for stack problems */
     AFSCBFids fidStruct;
     AFSBulkStats statStruct;
@@ -1874,13 +1907,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);
 
-    bb.counter = 0;
+    memset(&bb, 0, sizeof(bb));
     bb.bufOffset = *offsetp;
 
     lock_ReleaseMutex(&dscp->mx);
@@ -1890,7 +1924,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;
@@ -1917,18 +1951,29 @@ void cm_TryBulkStat(cm_scache_t *dscp, osi_hyper_t *offsetp, cm_user_t *userp,
                 continue;
 
             callp = cm_GetRxConn(connp);
-            code = RXAFS_BulkStatus(callp, &fidStruct,
+           if (!(connp->serverp->flags & CM_SERVERFLAG_NOINLINEBULK)) {
+               code = RXAFS_InlineBulkStatus(callp, &fidStruct,
                                      &statStruct, &callbackStruct, &volSync);
+               if (code == RXGEN_OPCODE) {
+                   cm_SetServerNoInlineBulk(connp->serverp, 0);
+               } else {
+                   inlinebulk = 1;
+               }
+           }
+           if (!inlinebulk) {
+               code = RXAFS_BulkStatus(callp, &fidStruct,
+                                       &statStruct, &callbackStruct, &volSync);
+           }
             rx_PutConnection(callp);
 
         } while (cm_Analyze(connp, userp, reqp, &dscp->fid,
                              &volSync, NULL, &cbReq, code));
         code = cm_MapRPCError(code, reqp);
-
         if (code)
-            osi_Log1(afsd_logp, "CALL BulkStatus FAILURE code 0x%x", code);
+            osi_Log2(afsd_logp, "CALL %sBulkStatus FAILURE code 0x%x", 
+                     inlinebulk ? "Inline" : "", code);
         else
-            osi_Log0(afsd_logp, "CALL BulkStatus SUCCESS");
+            osi_Log1(afsd_logp, "CALL %sBulkStatus SUCCESS", inlinebulk ? "Inline" : "");
 
         /* may as well quit on an error, since we're not going to do
          * much better on the next immediate call, either.
@@ -1963,7 +2008,7 @@ void cm_TryBulkStat(cm_scache_t *dscp, osi_hyper_t *offsetp, cm_user_t *userp,
              * Right now, be pretty conservative: if there's a
              * callback or a pending call, skip it.
              */
-            if (scp->cbServerp == NULL
+            if ((scp->cbServerp == NULL || (scp->flags & CM_SCACHEFLAG_EACCESS))
                  && !(scp->flags &
                        (CM_SCACHEFLAG_FETCHING
                          | CM_SCACHEFLAG_STORING
@@ -1971,8 +2016,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(scp, &bb.stats[j], &volSync, userp, 0);
             }       
             lock_ReleaseMutex(&scp->mx);
             cm_ReleaseSCache(scp);
@@ -1984,7 +2028,23 @@ 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);
-    osi_Log0(afsd_logp, "END cm_TryBulkStat");
+
+#if 0
+    /* If we did the InlineBulk RPC pull out the return code */
+    if (inlinebulk) {
+       if ((&bb.stats[0])->errorCode) {
+           cm_Analyze(NULL /*connp was released by the previous cm_Analyze */, 
+                       userp, reqp, &dscp->fid, &volSync, NULL, NULL, (&bb.stats[0])->errorCode);
+           code = cm_MapRPCError((&bb.stats[0])->errorCode, reqp);
+       }
+    } else
+#endif 
+    { 
+       code = 0;
+    }
+
+    osi_Log1(afsd_logp, "END cm_TryBulkStat code = 0x%x", code);
+    return code;
 }       
 
 void cm_StatusFromAttr(AFSStoreStatus *statusp, cm_scache_t *scp, cm_attr_t *attrp)
@@ -2269,6 +2329,7 @@ long cm_Create(cm_scache_t *dscp, char *namep, long flags, cm_attr_t *attrp,
         code = cm_GetSCache(&newFid, &scp, userp, reqp);
         if (code == 0) {
             lock_ObtainMutex(&scp->mx);
+           scp->creator = userp;               /* remember who created it */
             if (!cm_HaveCallback(scp)) {
                 cm_MergeStatus(scp, &newFileStatus, &volSync,
                                 userp, 0);
@@ -2297,12 +2358,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;
@@ -3299,7 +3365,8 @@ static void cm_PutFileLock(cm_file_lock_t *l) {
     osi_QAdd(&cm_freeFileLocks, &l->q);
 }
 
-/* called with scp->mx held.  Releases it during processing */
+/* called with scp->mx held.  May release it during processing, but
+   leaves it held on exit. */
 long cm_IntSetLock(cm_scache_t * scp, cm_user_t * userp, int lockType,
                    cm_req_t * reqp) {
     long code = 0;
@@ -3386,6 +3453,69 @@ long cm_IntReleaseLock(cm_scache_t * scp, cm_user_t * userp,
     return code;
 }
 
+/* called with scp->mx held.  May release it during processing, but
+   will exit with lock held.
+
+   This will return:
+
+   - 0 if the user has permission to get the specified lock for the scp
+
+   - CM_ERROR_NOACCESS if not
+
+   Any other error from cm_SyncOp will be sent down untranslated.
+*/
+long cm_LockCheckPerms(cm_scache_t * scp,
+                       int lock_type,
+                       cm_user_t * userp,
+                       cm_req_t * reqp)
+{
+    long rights = 0;
+    long code = 0;
+
+    /* lock permissions are slightly tricky because of the 'i' bit.
+       If the user has PRSFS_LOCK, she can read-lock the file.  If the
+       user has PRSFS_WRITE, she can write-lock the file.  However, if
+       the user has PRSFS_INSERT, then she can write-lock new files,
+       but not old ones.  Since we don't have information about
+       whether a file is new or not, we assume that if the user owns
+       the scp, then she has the permissions that are granted by
+       PRSFS_INSERT. */
+
+    osi_Log3(afsd_logp, "cm_LockCheckPerms for scp[0x%p] type[%d] user[0x%p]",
+             scp, lock_type, userp);
+
+    if (lock_type == LockRead)
+        rights |= PRSFS_LOCK;
+    else if (lock_type == LockWrite)
+        rights |= PRSFS_WRITE;
+    else {
+        /* hmmkay */
+        osi_assert(FALSE);
+        return 0;
+    }
+
+    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. */
+
+        code = 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");
+    }
+
+    osi_Log1(afsd_logp, "cm_LockCheckPerms returning code %d", code);
+
+    return code;
+}
+
 /* called with scp->mx held */
 long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
              LARGE_INTEGER LOffset, LARGE_INTEGER LLength,
@@ -3399,6 +3529,7 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
     osi_queue_t *q;
     cm_range_t range;
     int wait_unlock = FALSE;
+    int force_client_lock = FALSE;
 
     osi_Log4(afsd_logp, "cm_Lock scp 0x%x type 0x%x offset %d length %d",
              scp, sLockType, (unsigned long)LOffset.QuadPart, (unsigned long)LLength.QuadPart);
@@ -3470,24 +3601,57 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
             /* we already have the lock we need */
             osi_Log3(afsd_logp, "   we already have the correct lock. exclusives[%d], shared[%d], serverLock[%d]", 
                      scp->exclusiveLocks, scp->sharedLocks, (int)(signed char) scp->serverLock);
-            code = 0; /* redundant */
+
+            code = cm_LockCheckPerms(scp, Which, userp, reqp);
+
+            /* special case: if we don't have permission to read-lock
+               the file, then we force a clientside lock.  This is to
+               compensate for applications that obtain a read-lock for
+               reading files off of directories that don't grant
+               read-locks to the user. */
+            if (code == CM_ERROR_NOACCESS && Which == LockRead) {
+                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)) {
 
             /* We are already waiting for some other lock.  We should
                wait for the daemon to catch up instead of generating a
                flood of SetLock calls. */
             osi_Log3(afsd_logp, "   already waiting for other lock. exclusives[%d], shared[%d], serverLock[%d]",
                      scp->exclusiveLocks, scp->sharedLocks, (int)(signed char) scp->serverLock);
-            code = CM_ERROR_WOULDBLOCK;
+
+            /* see if we have permission to create the lock in the
+               first place. */
+            code = cm_LockCheckPerms(scp, Which, userp, reqp);
+            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;
+            }
+
+            /* leave any other codes as-is */
 
         } else {
             int newLock;
             int check_data_version = FALSE;
 
+            /* first check if we have permission to elevate or obtain
+               the lock. */
+            code = cm_LockCheckPerms(scp, Which, userp, reqp);
+            if (code) {
+                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;
+                }
+                goto check_code;
+            }
+
             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
@@ -3529,7 +3693,7 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
 
                 /* am I sane? */
                 osi_assert(newLock == LockRead);
-                
+
                 code = cm_IntSetLock(scp, userp, newLock, reqp);
             }
 
@@ -3572,7 +3736,7 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
 
  check_code:
 
-    if (code != 0) {
+    if (code != 0 && !force_client_lock) {
         /* Special case error translations
 
            Applications don't expect certain errors from a
@@ -3587,7 +3751,13 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
         }
     }
 
-    if (code == 0 || (code == CM_ERROR_WOULDBLOCK && allowWait)) {
+    if (code == 0 || (code == CM_ERROR_WOULDBLOCK && allowWait) ||
+        force_client_lock) {
+
+        /* clear the error if we are forcing a client lock, so we
+           don't get confused later. */
+        if (force_client_lock && code != CM_ERROR_WOULDBLOCK)
+            code = 0;
 
         lock_ObtainWrite(&cm_scacheLock);
         fileLock = cm_GetFileLock();
@@ -3605,14 +3775,13 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
                             CM_FILELOCK_FLAG_WAITUNLOCK :
                             CM_FILELOCK_FLAG_WAITLOCK));
 
-        if (!SERVERLOCKS_ENABLED(scp))
+        if (force_client_lock || !SERVERLOCKS_ENABLED(scp))
             fileLock->flags |= CM_FILELOCK_FLAG_CLIENTONLY;
 
-        fileLock->lastUpdate = (code == 0) ? time(NULL) : 0;
-
-        osi_QAddT(&scp->fileLocksH, &scp->fileLocksT, &fileLock->fileq);
+        fileLock->lastUpdate = (code == 0 && !force_client_lock) ? time(NULL) : 0;
 
         lock_ObtainWrite(&cm_scacheLock);
+        osi_QAddT(&scp->fileLocksH, &scp->fileLocksT, &fileLock->fileq);
         cm_HoldSCacheNoLock(scp);
         fileLock->scp = scp;
         osi_QAdd(&cm_allFileLocks, &fileLock->q);
@@ -3622,23 +3791,25 @@ long cm_Lock(cm_scache_t *scp, unsigned char sLockType,
             *lockpp = fileLock;
         }
 
-        if (IS_LOCK_ACCEPTED(fileLock)) {
+        if (IS_LOCK_CLIENTONLY(fileLock)) {
+            scp->clientLocks++;
+        } else if (IS_LOCK_ACCEPTED(fileLock)) {
             if (Which == LockRead)
                 scp->sharedLocks++;
             else
                 scp->exclusiveLocks++;
         }
 
-        osi_Log2(afsd_logp,
-                 "cm_Lock Lock added 0x%p flags 0x%x",
-                 fileLock, fileLock->flags);
+        osi_Log3(afsd_logp,
+                 "cm_Lock Lock added 0x%p flags 0x%x to scp [0x%p]",
+                 fileLock, fileLock->flags, scp);
         osi_Log4(afsd_logp,
-                 "   scp[0x%p] exclusives[%d] shared[%d] serverLock[%d]",
-                 scp, scp->exclusiveLocks, scp->sharedLocks,
+                 "   exclusives[%d] shared[%d] client[%d] serverLock[%d]",
+                 scp->exclusiveLocks, scp->sharedLocks, scp->clientLocks,
                  (int)(signed char) scp->serverLock);
     } else {
         osi_Log1(afsd_logp,
-                 "cm_Lock Rejecting lock (code = %d)", code);
+                 "cm_Lock Rejecting lock (code = 0x%x)", code);
     }
 
     return code;
@@ -3708,9 +3879,11 @@ long cm_UnlockByKey(cm_scache_t * scp,
 
             if (scp->fileLocksT == q)
                 scp->fileLocksT = osi_QPrev(q);
-            osi_QRemove(&scp->fileLocksH,q);
+            osi_QRemoveHT(&scp->fileLocksH, &scp->fileLocksT, q);
 
-            if (IS_LOCK_ACCEPTED(fileLock)) {
+            if (IS_LOCK_CLIENTONLY(fileLock)) {
+                scp->clientLocks--;
+            } else if (IS_LOCK_ACCEPTED(fileLock)) {
                 if (fileLock->lockType == LockRead)
                     scp->sharedLocks--;
                 else
@@ -3743,6 +3916,7 @@ long cm_UnlockByKey(cm_scache_t * scp,
 
     osi_assertx(scp->sharedLocks >= 0, "scp->sharedLocks < 0");
     osi_assertx(scp->exclusiveLocks >= 0, "scp->exclusiveLocks < 0");
+    osi_assertx(scp->clientLocks >= 0, "scp->clientLocks < 0");
 
     if (!SERVERLOCKS_ENABLED(scp)) {
         osi_Log0(afsd_logp, "  Skipping server lock for scp");
@@ -3823,8 +3997,9 @@ long cm_UnlockByKey(cm_scache_t * scp,
  done:
 
     osi_Log1(afsd_logp, "cm_UnlockByKey code 0x%x", code);
-    osi_Log3(afsd_logp, "   Leaving scp with exclusives[%d], shared[%d], serverLock[%d]",
-             scp->exclusiveLocks, scp->sharedLocks, (int)(signed char) scp->serverLock);
+    osi_Log4(afsd_logp, "   Leaving scp with excl[%d], shared[%d], client[%d], serverLock[%d]",
+             scp->exclusiveLocks, scp->sharedLocks, scp->clientLocks,
+             (int)(signed char) scp->serverLock);
 
     return code;
 }
@@ -3886,21 +4061,22 @@ long cm_Unlock(cm_scache_t *scp,
         return 0;
     }
 
+    lock_ReleaseRead(&cm_scacheLock);
+
     /* discard lock record */
+    lock_ObtainWrite(&cm_scacheLock);
     if (scp->fileLocksT == q)
         scp->fileLocksT = osi_QPrev(q);
-    osi_QRemove(&scp->fileLocksH, q);
-
-    lock_ReleaseRead(&cm_scacheLock);
+    osi_QRemoveHT(&scp->fileLocksH, &scp->fileLocksT, q);
 
     /*
      * Don't delete it here; let the daemon delete it, to simplify
      * the daemon's traversal of the list.
      */
 
-    lock_ObtainWrite(&cm_scacheLock);
-
-    if (IS_LOCK_ACCEPTED(fileLock)) {
+    if (IS_LOCK_CLIENTONLY(fileLock)) {
+        scp->clientLocks--;
+    } else if (IS_LOCK_ACCEPTED(fileLock)) {
         if (fileLock->lockType == LockRead)
             scp->sharedLocks--;
         else
@@ -4003,8 +4179,10 @@ long cm_Unlock(cm_scache_t *scp,
 
  done:
 
-    osi_Log4(afsd_logp, "cm_Unlock code 0x%x leaving scp with exclusives[%d], shared[%d], serverLock[%d]",
-             code, scp->exclusiveLocks, scp->sharedLocks, (int)(signed char) scp->serverLock);
+    osi_Log1(afsd_logp, "cm_Unlock code 0x%x", code);
+    osi_Log4(afsd_logp, "  leaving scp with excl[%d], shared[%d], client[%d], serverLock[%d]",
+             scp->exclusiveLocks, scp->sharedLocks, scp->clientLocks,
+             (int)(signed char) scp->serverLock);
 
     return code;
 }
@@ -4029,7 +4207,8 @@ static void cm_LockMarkSCacheLost(cm_scache_t * scp)
         fileLock = 
             (cm_file_lock_t *)((char *) q - offsetof(cm_file_lock_t, fileq));
 
-        if (IS_LOCK_ACTIVE(fileLock)) {
+        if (IS_LOCK_ACTIVE(fileLock) &&
+            !IS_LOCK_CLIENTONLY(fileLock)) {
             if (fileLock->lockType == LockRead)
                 scp->sharedLocks--;
             else
@@ -4264,6 +4443,7 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
     osi_queue_t *q;
     cm_req_t req;
     int newLock = -1;
+    int force_client_lock = FALSE;
 
     cm_InitReq(&req);
 
@@ -4306,14 +4486,27 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
 
     lock_ReleaseRead(&cm_scacheLock);
     lock_ObtainMutex(&scp->mx);
+
+    code = cm_LockCheckPerms(scp, oldFileLock->lockType,
+                             oldFileLock->userp,
+                             &req);
+
+    if (code == CM_ERROR_NOACCESS && oldFileLock->lockType == LockRead) {
+        force_client_lock = TRUE;
+        code = 0;
+    } else if (code) {
+        lock_ReleaseMutex(&scp->mx);
+        return code;
+    }
+
     lock_ObtainWrite(&cm_scacheLock);
 
     /* Check if we already have a sufficient server lock to allow this
-       lock to go through */
+       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;
 
@@ -4354,7 +4547,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) {
@@ -4393,13 +4586,28 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
         oldFileLock->flags |= CM_FILELOCK_FLAG_WAITLOCK;
     }
 
-    if (!SERVERLOCKS_ENABLED(scp) ||
+    if (force_client_lock ||
+        !SERVERLOCKS_ENABLED(scp) ||
         scp->serverLock == oldFileLock->lockType ||
         (oldFileLock->lockType == LockRead &&
          scp->serverLock == LockWrite)) {
 
         oldFileLock->flags &= ~CM_FILELOCK_FLAG_WAITLOCK;
 
+        if ((force_client_lock ||
+             !SERVERLOCKS_ENABLED(scp)) &&
+            !IS_LOCK_CLIENTONLY(oldFileLock)) {
+
+            oldFileLock->flags |= CM_FILELOCK_FLAG_CLIENTONLY;
+
+            if (oldFileLock->lockType == LockRead)
+                scp->sharedLocks--;
+            else
+                scp->exclusiveLocks--;
+
+            scp->clientLocks++;
+        }
+
         lock_ReleaseWrite(&cm_scacheLock);
         lock_ReleaseMutex(&scp->mx);
 
@@ -4441,9 +4649,11 @@ long cm_RetryLock(cm_file_lock_t *oldFileLock, int client_is_dead)
 
   handleCode:
     if (code != 0 && code != CM_ERROR_WOULDBLOCK) {
+       lock_ObtainWrite(&cm_scacheLock);
         if (scp->fileLocksT == &oldFileLock->fileq)
             scp->fileLocksT = osi_QPrev(&oldFileLock->fileq);
-        osi_QRemove(&scp->fileLocksH, &oldFileLock->fileq);
+        osi_QRemoveHT(&scp->fileLocksH, &scp->fileLocksT, &oldFileLock->fileq);
+       lock_ReleaseWrite(&cm_scacheLock);
     } else if (code == 0 && IS_LOCK_WAITLOCK(oldFileLock)) {
         scp->serverLock = newLock;
     }
@@ -4497,7 +4707,7 @@ void cm_ReleaseAllLocks(void)
     cm_user_t *userp;
     cm_key_t   key;
     cm_file_lock_t *fileLock;
-    int i;
+    unsigned int i;
 
     for (i = 0; i < cm_data.hashTableSize; i++)
     {