Windows: avoid race when writing mountPointString
[openafs.git] / src / WINNT / afsd / cm_vnodeops.c
index 5625000..90f6800 100644 (file)
@@ -7,7 +7,10 @@
  * directory or online at http://www.openafs.org/dl/license10.html
  */
 
+#include <afsconfig.h>
 #include <afs/param.h>
+#include <roken.h>
+
 #include <afs/stds.h>
 
 #include <windows.h>
@@ -189,17 +192,17 @@ long cm_CheckNTOpen(cm_scache_t *scp, unsigned int desiredAccess,
                    cm_lock_data_t **ldpp)
 {
     long rights;
-    long code;
+    long code = 0;
 
     osi_assertx(ldpp != NULL, "null cm_lock_data_t");
     *ldpp = NULL;
 
     /* Always allow delete; the RPC will tell us if it's OK */
-    if (desiredAccess == DELETE)
-        return 0;
-
     rights = 0;
 
+    if (desiredAccess == DELETE)
+        goto done_2;
+
     if (desiredAccess & (AFS_ACCESS_READ|AFS_ACCESS_EXECUTE))
         rights |= (scp->fileType == CM_SCACHETYPE_DIRECTORY ? PRSFS_LOOKUP : PRSFS_READ);
 
@@ -210,6 +213,9 @@ long cm_CheckNTOpen(cm_scache_t *scp, unsigned int desiredAccess,
     if (desiredAccess & AFS_ACCESS_WRITE)
         rights |= PRSFS_WRITE;
 
+    if (desiredAccess & DELETE)
+        rights |= PRSFS_DELETE;
+
     lock_ObtainWrite(&scp->rw);
 
     code = cm_SyncOp(scp, NULL, userp, reqp, rights,
@@ -291,6 +297,7 @@ long cm_CheckNTOpen(cm_scache_t *scp, unsigned int desiredAccess,
  _done:
     lock_ReleaseWrite(&scp->rw);
 
+ done_2:
     osi_Log3(afsd_logp,"cm_CheckNTOpen scp 0x%p ldp 0x%p code 0x%x", scp, *ldpp, code);
     return code;
 }
@@ -671,6 +678,21 @@ long cm_ApplyDir(cm_scache_t *scp, cm_DirFuncp_t funcp, void *parmp,
         tp = bufferp->datap + entryInBuffer;
         dep = (cm_dirEntry_t *) tp;    /* now points to AFS3 dir entry */
 
+        /*
+         * here are some consistency checks
+         */
+        if (dep->flag != CM_DIR_FFIRST ||
+            strlen(dep->name) > 256) {
+            code = CM_ERROR_INVAL;
+            osi_Log2(afsd_logp,
+                     "cm_ApplyDir invalid directory entry for scp %p bufp %p",
+                     scp, bufferp);
+            osi_Log4(afsd_logp,"... cell %u vol %u vnode %u uniq %u",
+                     scp->fid.cell, scp->fid.volume, scp->fid.vnode, scp->fid.unique);
+            bufferp->dataVersion = CM_BUF_VERSION_BAD;
+            break;
+        }
+
         /* while we're here, compute the next entry's location, too,
          * since we'll need it when writing out the cookie into the
          * dir listing stream.
@@ -812,9 +834,6 @@ long cm_LookupSearchProc(cm_scache_t *scp, cm_dirEntry_t *dep, void *rockp,
 long cm_ReadMountPoint(cm_scache_t *scp, cm_user_t *userp, cm_req_t *reqp)
 {
     long code;
-    cm_buf_t *bufp = NULL;
-    osi_hyper_t thyper;
-    int tlen;
 
     if (scp->mountPointStringp[0]) 
         return 0;
@@ -829,62 +848,30 @@ long cm_ReadMountPoint(cm_scache_t *scp, cm_user_t *userp, cm_req_t *reqp)
     } else 
 #endif /* AFS_FREELANCE_CLIENT */        
     {
-        /* otherwise, we have to read it in */
-        lock_ReleaseWrite(&scp->rw);
+        char temp[MOUNTPOINTLEN];
+        osi_hyper_t thyper;
 
+        /* otherwise, we have to read it in */
         thyper.LowPart = thyper.HighPart = 0;
-        code = buf_Get(scp, &thyper, reqp, &bufp);
-
-        lock_ObtainWrite(&scp->rw);
+        code = cm_GetData(scp, &thyper, temp, MOUNTPOINTLEN, userp, reqp);
         if (code)
             return code;
 
-        while (1) {
-            code = cm_SyncOp(scp, bufp, userp, reqp, 0,
-                              CM_SCACHESYNC_READ | CM_SCACHESYNC_NEEDCALLBACK);
-            if (code)
-                goto done;
-
-            cm_SyncOpDone(scp, bufp, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_READ);
-
-            if (cm_HaveBuffer(scp, bufp, 0)) 
-                break;
-
-            /* otherwise load buffer */
-            code = cm_GetBuffer(scp, bufp, NULL, userp, reqp);
-            if (code)
-                goto done;
-        }
-        /* locked, has callback, has valid data in buffer */
-        if ((tlen = scp->length.LowPart) > MOUNTPOINTLEN - 1) 
-            return CM_ERROR_TOOBIG;
-        if (tlen <= 0) {
-            code = CM_ERROR_INVAL;
-            goto done;
-        }
-
-        /* someone else did the work while we were out */
-        if (scp->mountPointStringp[0]) {
-            code = 0;
-            goto done;
-        }
-
-        /* otherwise, copy out the link */
-        memcpy(scp->mountPointStringp, bufp->datap, tlen);
-
-        /* now make it null-terminated.  Note that the original contents of a
-         * link that is a mount point is "#volname." where "." is there just to
-         * be turned into a null.  That is, we can trash the last char of the
-         * link without damaging the vol name.  This is a stupid convention,
-         * but that's the protocol.
+        /*
+         * scp->length is the actual length of the mount point string.
+         * It is current because cm_GetData merged the most up to date
+         * status info into scp and has not dropped the rwlock since.
          */
-        scp->mountPointStringp[tlen-1] = 0;
-        code = 0;
+        if (scp->length.LowPart > MOUNTPOINTLEN - 1)
+            return CM_ERROR_TOOBIG;
+        if (scp->length.LowPart == 0)
+            return CM_ERROR_INVAL;
 
-      done:
-        if (bufp) 
-            buf_Release(bufp);
+        /* convert the terminating dot to a NUL */
+        temp[thyper.LowPart - 1] = 0;
+        memcpy(scp->mountPointStringp, temp, thyper.LowPart);
     }
+
     return code;
 }
 
@@ -1198,7 +1185,7 @@ notfound:
             if (cnamep[0] == '.') {
                 if (cm_GetCell_Gen(&fnamep[1], &fullname[1], CM_FLAG_CREATE)) {
                     found = 1;
-                    code = cm_FreelanceAddMount(fullname, &fullname[1], "root.cell.", 1, &rock.fid);
+                    code = cm_FreelanceAddMount(fullname, &fullname[1], "root.cell", 1, &rock.fid);
                     if ( cm_FsStrCmpI(&fnamep[1], &fullname[1])) {
                         /*
                          * Do not permit symlinks that are one of:
@@ -1223,7 +1210,7 @@ notfound:
             } else {
                 if (cm_GetCell_Gen(fnamep, fullname, CM_FLAG_CREATE)) {
                     found = 1;
-                    code = cm_FreelanceAddMount(fullname, fullname, "root.cell.", 0, &rock.fid);
+                    code = cm_FreelanceAddMount(fullname, fullname, "root.cell", 0, &rock.fid);
                     if ( cm_FsStrCmpI(fnamep, fullname)) {
                         /*
                          * Do not permit symlinks that are one of:
@@ -1281,14 +1268,26 @@ notfound:
     /* tscp is now held */
 
     lock_ObtainWrite(&tscp->rw);
-    code = cm_SyncOp(tscp, NULL, userp, reqp, 0,
-                      CM_SCACHESYNC_GETSTATUS | CM_SCACHESYNC_NEEDCALLBACK);
-    if (code) { 
-        lock_ReleaseWrite(&tscp->rw);
-        cm_ReleaseSCache(tscp);
-        goto done;
+
+    /*
+     * Do not get status if we do not already have a callback.
+     * The process of reading the mount point string will obtain status information
+     * in a single RPC.  No reason to add a second round trip.
+     *
+     * If we do have a callback, use cm_SyncOp to get status in case the
+     * current cm_user_t is not the same as the one that obtained the
+     * mount point string contents.
+     */
+    if (cm_HaveCallback(tscp)) {
+        code = cm_SyncOp(tscp, NULL, userp, reqp, 0,
+                          CM_SCACHESYNC_GETSTATUS | CM_SCACHESYNC_NEEDCALLBACK);
+        if (code) {
+            lock_ReleaseWrite(&tscp->rw);
+            cm_ReleaseSCache(tscp);
+            goto done;
+        }
+        cm_SyncOpDone(tscp, NULL, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
     }
-    cm_SyncOpDone(tscp, NULL, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
     /* tscp is now locked */
 
     if (!(flags & CM_FLAG_NOMOUNTCHASE)
@@ -1616,6 +1615,14 @@ long cm_Unlink(cm_scache_t *dscp, fschar_t *fnamep, clientchar_t * cnamep,
 #endif  
 
     code = cm_Lookup(dscp, cnamep, CM_FLAG_NOMOUNTCHASE, userp, reqp, &scp);
+    if (code)
+        goto done;
+
+    /* Check for RO volume */
+    if (dscp->flags & CM_SCACHEFLAG_RO) {
+        code = CM_ERROR_READONLY;
+        goto done;
+    }
 
     /* make sure we don't screw up the dir status during the merge */
     code = cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_NONE,
@@ -1703,10 +1710,7 @@ long cm_Unlink(cm_scache_t *dscp, fschar_t *fnamep, clientchar_t * cnamep,
  */
 long cm_HandleLink(cm_scache_t *linkScp, cm_user_t *userp, cm_req_t *reqp)
 {
-    long code;
-    cm_buf_t *bufp;
-    long temp;
-    osi_hyper_t thyper;
+    long code = 0;
 
     lock_AssertWrite(&linkScp->rw);
     if (!linkScp->mountPointStringp[0]) {
@@ -1721,56 +1725,36 @@ long cm_HandleLink(cm_scache_t *linkScp, cm_user_t *userp, cm_req_t *reqp)
         } else 
 #endif /* AFS_FREELANCE_CLIENT */        
         {
-            /* read the link data from the file server*/
-            lock_ReleaseWrite(&linkScp->rw);
+            char temp[MOUNTPOINTLEN];
+            osi_hyper_t thyper;
+
+            /* read the link data from the file server */
             thyper.LowPart = thyper.HighPart = 0;
-            code = buf_Get(linkScp, &thyper, reqp, &bufp);
-            lock_ObtainWrite(&linkScp->rw);
-            if (code) 
+            code = cm_GetData(linkScp, &thyper, temp, MOUNTPOINTLEN, userp, reqp);
+            if (code)
                 return code;
-            while (1) {
-                code = cm_SyncOp(linkScp, bufp, userp, reqp, 0,
-                                  CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_READ);
-                if (code) {
-                    buf_Release(bufp);
-                    return code;
-                }
-                cm_SyncOpDone(linkScp, bufp, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_READ);
-
-                if (cm_HaveBuffer(linkScp, bufp, 0)) 
-                    break;
 
-                code = cm_GetBuffer(linkScp, bufp, NULL, userp, reqp);
-                if (code) {
-                    buf_Release(bufp);
-                    return code;
-                }
-            } /* while loop to get the data */
-
-            /* now if we still have no link read in,
-             * copy the data from the buffer */
-            if ((temp = linkScp->length.LowPart) >= MOUNTPOINTLEN) {
-                buf_Release(bufp);
+            /*
+             * linkScp->length is the actual length of the symlink target string.
+             * It is current because cm_GetData merged the most up to date
+             * status info into scp and has not dropped the rwlock since.
+             */
+            if (linkScp->length.LowPart > MOUNTPOINTLEN - 1)
                 return CM_ERROR_TOOBIG;
-            }       
+            if (linkScp->length.LowPart == 0)
+                return CM_ERROR_INVAL;
 
-            /* otherwise, it fits; make sure it is still null (could have
-             * lost race with someone else referencing this link above),
-             * and if so, copy in the data.
-             */
-            if (!linkScp->mountPointStringp[0]) {
-                strncpy(linkScp->mountPointStringp, bufp->datap, temp);
-                linkScp->mountPointStringp[temp] = 0;  /* null terminate */
-            }
-            buf_Release(bufp);
+            /* convert the terminating dot to a NUL */
+            temp[thyper.LowPart - 1] = 0;
+            memcpy(linkScp->mountPointStringp, temp, thyper.LowPart);
         }
         
         if ( !strnicmp(linkScp->mountPointStringp, "msdfs:", strlen("msdfs:")) )
             linkScp->fileType = CM_SCACHETYPE_DFSLINK;
 
-    }  /* don't have sym link contents cached */
+    }  /* don't have symlink contents cached */
 
-    return 0;
+    return code;
 }       
 
 /* called with a held vnode and a path suffix, with the held vnode being a
@@ -1792,6 +1776,25 @@ long cm_AssembleLink(cm_scache_t *linkScp, fschar_t *pathSuffixp,
     *newSpaceBufferp = NULL;
 
     lock_ObtainWrite(&linkScp->rw);
+    /*
+     * Do not get status if we do not already have a callback.
+     * The process of reading the symlink string will obtain status information
+     * in a single RPC.  No reason to add a second round trip.
+     *
+     * If we do have a callback, use cm_SyncOp to get status in case the
+     * current cm_user_t is not the same as the one that obtained the
+     * symlink string contents.
+     */
+    if (cm_HaveCallback(linkScp)) {
+        code = cm_SyncOp(linkScp, NULL, userp, reqp, 0,
+                          CM_SCACHESYNC_GETSTATUS | CM_SCACHESYNC_NEEDCALLBACK);
+        if (code) {
+            lock_ReleaseWrite(&linkScp->rw);
+            cm_ReleaseSCache(linkScp);
+            goto done;
+        }
+        cm_SyncOpDone(linkScp, NULL, CM_SCACHESYNC_NEEDCALLBACK | CM_SCACHESYNC_GETSTATUS);
+    }
     code = cm_HandleLink(linkScp, userp, reqp);
     if (code)
         goto done;
@@ -2421,6 +2424,7 @@ cm_TryBulkStatRPC(cm_scache_t *dscp, cm_bulkStat_t *bbp, cm_user_t *userp, cm_re
                 case VOFFLINE:
                 case VSALVAGE:
                 case VNOSERVICE:
+                case VIO:
                     code = (&bbp->stats[0])->errorCode;
                     break;
                 default:
@@ -2711,6 +2715,13 @@ long cm_SetAttr(cm_scache_t *scp, cm_attr_t *attrp, cm_user_t *userp,
         return cm_SetLength(scp, &attrp->length, userp, reqp);
 
     lock_ObtainWrite(&scp->rw);
+    /* Check for RO volume */
+    if (scp->flags & CM_SCACHEFLAG_RO) {
+        code = CM_ERROR_READONLY;
+       lock_ReleaseWrite(&scp->rw);
+        return code;
+    }
+
     /* otherwise, we have to make an RPC to get the status */
     code = cm_SyncOp(scp, NULL, userp, reqp, 0, CM_SCACHESYNC_STORESTATUS);
     if (code) {
@@ -2802,6 +2813,10 @@ long cm_Create(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *a
     }
 #endif /* AFS_FREELANCE_CLIENT */
 
+    /* Check for RO volume */
+    if (dscp->flags & CM_SCACHEFLAG_RO)
+        return CM_ERROR_READONLY;
+
     /* before starting the RPC, mark that we're changing the file data, so
      * that someone who does a chmod will know to wait until our call
      * completes.
@@ -2980,6 +2995,10 @@ long cm_MakeDir(cm_scache_t *dscp, clientchar_t *cnamep, long flags, cm_attr_t *
     }
 #endif /* AFS_FREELANCE_CLIENT */
 
+    /* Check for RO volume */
+    if (dscp->flags & CM_SCACHEFLAG_RO)
+        return CM_ERROR_READONLY;
+
     /* before starting the RPC, mark that we're changing the directory
      * data, so that someone who does a chmod on the dir will wait until
      * our call completes.
@@ -3107,6 +3126,10 @@ long cm_Link(cm_scache_t *dscp, clientchar_t *cnamep, cm_scache_t *sscp, long fl
         return CM_ERROR_CROSSDEVLINK;
     }
 
+    /* Check for RO volume */
+    if (dscp->flags & CM_SCACHEFLAG_RO)
+        return CM_ERROR_READONLY;
+
     cm_BeginDirOp(dscp, userp, reqp, CM_DIRLOCK_NONE, CM_DIROP_FLAG_NONE,
                   &dirop);
     lock_ObtainWrite(&dscp->rw);
@@ -3200,6 +3223,10 @@ long cm_SymLink(cm_scache_t *dscp, clientchar_t *cnamep, fschar_t *contentsp, lo
     cm_dirOp_t dirop;
     fschar_t *fnamep = NULL;
 
+    /* Check for RO volume */
+    if (dscp->flags & CM_SCACHEFLAG_RO)
+        return CM_ERROR_READONLY;
+
     memset(&volSync, 0, sizeof(volSync));
 
     /* before starting the RPC, mark that we're changing the directory data,
@@ -3349,6 +3376,12 @@ long cm_RemoveDir(cm_scache_t *dscp, fschar_t *fnamep, clientchar_t *cnamep, cm_
     if (code)
         goto done;
 
+    /* Check for RO volume */
+    if (dscp->flags & CM_SCACHEFLAG_RO) {
+        code = CM_ERROR_READONLY;
+        goto done;
+    }
+
     /* before starting the RPC, mark that we're changing the directory data,
      * so that someone who does a chmod on the dir will wait until our
      * call completes.
@@ -3527,6 +3560,13 @@ long cm_Rename(cm_scache_t *oldDscp, fschar_t *oldNamep, clientchar_t *cOldNamep
     } else {
         code = 0;
     }
+
+    /* Check for RO volume */
+    if (code == 0 &&
+        (oldDscp->flags & CM_SCACHEFLAG_RO) || (newDscp->flags & CM_SCACHEFLAG_RO)) {
+        code = CM_ERROR_READONLY;
+    }
+
     if (code) 
         goto done;