vol: Add VGetVolumeTimed
[openafs.git] / src / vol / volume.c
index 0eaa2e2..2a73c3c 100644 (file)
@@ -175,7 +175,7 @@ extern void *calloc(), *realloc();
 /* Forward declarations */
 static Volume *attach2(Error * ec, VolId volumeId, char *path,
                       struct DiskPartition64 *partp, Volume * vp,
-                      int isbusy, int mode);
+                      int isbusy, int mode, int *acheckedOut);
 static void ReallyFreeVolume(Volume * vp);
 #ifdef AFS_DEMAND_ATTACH_FS
 static void FreeVolume(Volume * vp);
@@ -199,7 +199,8 @@ static void VCloseVolumeHandles_r(Volume * vp);
 static void LoadVolumeHeader(Error * ec, Volume * vp);
 static int VCheckOffline(Volume * vp);
 static int VCheckDetach(Volume * vp);
-static Volume * GetVolume(Error * ec, Error * client_ec, VolId volumeId, Volume * hint, int flags);
+static Volume * GetVolume(Error * ec, Error * client_ec, VolId volumeId,
+                          Volume * hint, const struct timespec *ts);
 
 int LogLevel;                  /* Vice loglevel--not defined as extern so that it will be
                                 * defined when not linked with vice, XXXX */
@@ -2239,6 +2240,7 @@ VAttachVolumeByName_r(Error * ec, char *partition, char *name, int mode)
     char path[64];
     int isbusy = 0;
     VolId volumeId;
+    int checkedOut;
 #ifdef AFS_DEMAND_ATTACH_FS
     VolumeStats stats_save;
     Volume *svp = NULL;
@@ -2403,7 +2405,7 @@ VAttachVolumeByName_r(Error * ec, char *partition, char *name, int mode)
 
     /* attach2 is entered without any locks, and returns
      * with vol_glock_mutex held */
-    vp = attach2(ec, volumeId, path, partp, vp, isbusy, mode);
+    vp = attach2(ec, volumeId, path, partp, vp, isbusy, mode, &checkedOut);
 
     if (VCanUseFSSYNC() && vp) {
 #ifdef AFS_DEMAND_ATTACH_FS
@@ -2417,7 +2419,7 @@ VAttachVolumeByName_r(Error * ec, char *partition, char *name, int mode)
        if (mode == V_PEEK) {
            vp->needsPutBack = 0;
        } else {
-           vp->needsPutBack = 1;
+           vp->needsPutBack = VOL_PUTBACK;
        }
 #else /* !AFS_DEMAND_ATTACH_FS */
        /* duplicate computation in fssync.c about whether the server
@@ -2428,23 +2430,14 @@ VAttachVolumeByName_r(Error * ec, char *partition, char *name, int mode)
            || (!VolumeWriteable(vp) && (mode == V_CLONE || mode == V_DUMP)))
            vp->needsPutBack = 0;
        else
-           vp->needsPutBack = 1;
+           vp->needsPutBack = VOL_PUTBACK;
 #endif /* !AFS_DEMAND_ATTACH_FS */
     }
-    /* OK, there's a problem here, but one that I don't know how to
-     * fix right now, and that I don't think should arise often.
-     * Basically, we should only put back this volume to the server if
-     * it was given to us by the server, but since we don't have a vp,
-     * we can't run the VolumeWriteable function to find out as we do
-     * above when computing vp->needsPutBack.  So we send it back, but
-     * there's a path in VAttachVolume on the server which may abort
-     * if this volume doesn't have a header.  Should be pretty rare
-     * for all of that to happen, but if it does, probably the right
-     * fix is for the server to allow the return of readonly volumes
-     * that it doesn't think are really checked out. */
 #ifdef FSSYNC_BUILD_CLIENT
-    if (VCanUseFSSYNC() && vp == NULL &&
-       mode != V_SECRETLY && mode != V_PEEK) {
+    /* Only give back the vol to the fileserver if we checked it out; attach2
+     * will set checkedOut only if we successfully checked it out from the
+     * fileserver. */
+    if (VCanUseFSSYNC() && vp == NULL && checkedOut) {
 
 #ifdef AFS_DEMAND_ATTACH_FS
         /* If we couldn't attach but we scheduled a salvage, we already
@@ -2532,6 +2525,7 @@ VAttachVolumeByVp_r(Error * ec, Volume * vp, int mode)
     VolId volumeId;
     Volume * nvp = NULL;
     VolumeStats stats_save;
+    int checkedOut;
     *ec = 0;
 
     /* volume utility should never call AttachByVp */
@@ -2599,7 +2593,7 @@ VAttachVolumeByVp_r(Error * ec, Volume * vp, int mode)
      *
      * NOTE: attach2 is entered without any locks, and returns
      * with vol_glock_mutex held */
-    vp = attach2(ec, volumeId, path, partp, vp, isbusy, mode);
+    vp = attach2(ec, volumeId, path, partp, vp, isbusy, mode, &checkedOut);
 
     /*
      * the event that an error was encountered, or
@@ -2722,6 +2716,9 @@ VUnlockVolume(Volume *vp)
  *                    we don't try to lock the vol, or check it out from
  *                    FSSYNC or anything like that; 0 otherwise, for 'normal'
  *                    operation
+ * @param[out] acheckedOut   If we successfully checked-out the volume from
+ *                           the fileserver (if we needed to), this is set
+ *                           to 1, otherwise it is untouched.
  *
  * @note As part of DAFS volume attachment, the volume header may be either
  *       read- or write-locked to ensure mutual exclusion of certain volume
@@ -2734,7 +2731,7 @@ VUnlockVolume(Volume *vp)
  */
 static void
 attach_volume_header(Error *ec, Volume *vp, struct DiskPartition64 *partp,
-                     int mode, int peek)
+                     int mode, int peek, int *acheckedOut)
 {
     struct VolumeDiskHeader diskHeader;
     struct VolumeHeader header;
@@ -2796,6 +2793,7 @@ attach_volume_header(Error *ec, Volume *vp, struct DiskPartition64 *partp,
             }
            goto done;
        }
+       *acheckedOut = 1;
     }
 #endif
 
@@ -2961,7 +2959,7 @@ attach_volume_header(Error *ec, Volume *vp, struct DiskPartition64 *partp,
 #ifdef AFS_DEMAND_ATTACH_FS
 static void
 attach_check_vop(Error *ec, VolumeId volid, struct DiskPartition64 *partp,
-                 Volume *vp)
+                 Volume *vp, int *acheckedOut)
 {
     *ec = 0;
 
@@ -2986,7 +2984,7 @@ attach_check_vop(Error *ec, VolumeId volid, struct DiskPartition64 *partp,
                /* attach header with peek=1 to avoid checking out the volume
                 * or locking it; we just want the header info, we're not
                 * messing with the volume itself at all */
-               attach_volume_header(ec, vp, partp, V_PEEK, 1);
+               attach_volume_header(ec, vp, partp, V_PEEK, 1, acheckedOut);
                if (*ec) {
                    return;
                }
@@ -3058,6 +3056,9 @@ attach_check_vop(Error *ec, VolumeId volid, struct DiskPartition64 *partp,
  *                     otherwise. (see VVolOpSetVBusy_r)
  * @param[in] mode     attachment mode such as V_VOLUPD, V_DUMP, etc (see
  *                     volume.h)
+ * @param[out] acheckedOut   If we successfully checked-out the volume from
+ *                           the fileserver (if we needed to), this is set
+ *                           to 1, otherwise it is 0.
  *
  * @return pointer to the semi-attached volume pointer
  *  @retval NULL an error occurred (check value of *ec)
@@ -3069,7 +3070,7 @@ attach_check_vop(Error *ec, VolumeId volid, struct DiskPartition64 *partp,
  */
 static Volume *
 attach2(Error * ec, VolId volumeId, char *path, struct DiskPartition64 *partp,
-        Volume * vp, int isbusy, int mode)
+        Volume * vp, int isbusy, int mode, int *acheckedOut)
 {
     /* have we read in the header successfully? */
     int read_header = 0;
@@ -3091,19 +3092,21 @@ attach2(Error * ec, VolId volumeId, char *path, struct DiskPartition64 *partp,
     vp->diskDataHandle = NULL;
     vp->linkHandle = NULL;
 
+    *acheckedOut = 0;
+
 #ifdef AFS_DEMAND_ATTACH_FS
-    attach_check_vop(ec, volumeId, partp, vp);
+    attach_check_vop(ec, volumeId, partp, vp, acheckedOut);
     if (!*ec) {
-       attach_volume_header(ec, vp, partp, mode, 0);
+       attach_volume_header(ec, vp, partp, mode, 0, acheckedOut);
     }
 #else
-    attach_volume_header(ec, vp, partp, mode, 0);
+    attach_volume_header(ec, vp, partp, mode, 0, acheckedOut);
 #endif /* !AFS_DEMAND_ATTACH_FS */
 
     if (*ec == VNOVOL) {
        /* if the volume doesn't exist, skip straight to 'error' so we don't
         * request a salvage */
-       goto error;
+       goto unlocked_error;
     }
 
     if (!*ec) {
@@ -3191,17 +3194,15 @@ attach2(Error * ec, VolId volumeId, char *path, struct DiskPartition64 *partp,
                                                  VOL_SALVAGE_NO_OFFLINE);
        vp->nUsers = 0;
 
-       goto error;
+       goto locked_error;
     } else if (*ec) {
        /* volume operation in progress */
-       VOL_LOCK;
-       goto error;
+       goto unlocked_error;
     }
 #else /* AFS_DEMAND_ATTACH_FS */
     if (*ec) {
        Log("VAttachVolume: Error attaching volume %s; volume needs salvage; error=%u\n", path, *ec);
-        VOL_LOCK;
-       goto error;
+       goto unlocked_error;
     }
 #endif /* AFS_DEMAND_ATTACH_FS */
 
@@ -3221,7 +3222,7 @@ attach2(Error * ec, VolId volumeId, char *path, struct DiskPartition64 *partp,
        *ec = VSALVAGE;
 #endif /* AFS_DEMAND_ATTACH_FS */
 
-       goto error;
+       goto locked_error;
     }
 
     VOL_LOCK;
@@ -3245,7 +3246,7 @@ attach2(Error * ec, VolId volumeId, char *path, struct DiskPartition64 *partp,
        *ec = VSALVAGE;
 #endif /* AFS_DEMAND_ATTACH_FS */
 
-       goto error;
+       goto locked_error;
     }
 
     if (programType == fileServer && V_destroyMe(vp) == DESTROY_ME) {
@@ -3267,7 +3268,7 @@ attach2(Error * ec, VolId volumeId, char *path, struct DiskPartition64 *partp,
 #endif /* AFS_DEMAND_ATTACH_FS */
        Log("VAttachVolume: volume %s is junk; it should be destroyed at next salvage\n", path);
        *ec = VNOVOL;
-       goto error;
+       goto locked_error;
     }
 
     vp->vnodeIndex[vSmall].bitmap = vp->vnodeIndex[vLarge].bitmap = NULL;
@@ -3284,7 +3285,7 @@ attach2(Error * ec, VolId volumeId, char *path, struct DiskPartition64 *partp,
 #endif /* AFS_DEMAND_ATTACH_FS */
                Log("VAttachVolume: error getting bitmap for volume (%s)\n",
                    path);
-               goto error;
+               goto locked_error;
            }
        }
     }
@@ -3333,7 +3334,7 @@ attach2(Error * ec, VolId volumeId, char *path, struct DiskPartition64 *partp,
 #else /* !AFS_DEMAND_ATTACH_FS */
            *ec = VSALVAGE;
 #endif /* !AFS_DEMAND_ATTACh_FS */
-           goto error;
+           goto locked_error;
        }
     }
 
@@ -3373,7 +3374,7 @@ attach2(Error * ec, VolId volumeId, char *path, struct DiskPartition64 *partp,
 #ifdef AFS_DEMAND_ATTACH_FS
            vp->nUsers = 0;
 #endif
-           goto error;
+           goto locked_error;
        }
     } else {
 #ifdef AFS_DEMAND_ATTACH_FS
@@ -3400,7 +3401,9 @@ attach2(Error * ec, VolId volumeId, char *path, struct DiskPartition64 *partp,
 
     return vp;
 
- error:
+unlocked_error:
+    VOL_LOCK;
+locked_error:
 #ifdef AFS_DEMAND_ATTACH_FS
     if (!VIsErrorState(V_attachState(vp))) {
        VChangeState_r(vp, error_state);
@@ -3499,6 +3502,46 @@ VHold_r(Volume * vp)
 }
 #endif /* AFS_DEMAND_ATTACH_FS */
 
+/**** volume timeout-related stuff ****/
+
+#ifdef AFS_PTHREAD_ENV
+
+static_inline int
+VTimedOut(const struct timespec *ts)
+{
+    struct timeval tv;
+    int code;
+
+    if (ts->tv_sec == 0) {
+       /* short-circuit; this will have always timed out */
+       return 1;
+    }
+
+    code = gettimeofday(&tv, NULL);
+    if (code) {
+       Log("Error %d from gettimeofday, assuming we have not timed out\n", errno);
+       /* assume no timeout; failure mode is we just wait longer than normal
+        * instead of returning errors when we shouldn't */
+       return 0;
+    }
+
+    if (tv.tv_sec < ts->tv_sec ||
+        (tv.tv_sec == ts->tv_sec && tv.tv_usec*1000 < ts->tv_nsec)) {
+
+       return 0;
+    }
+
+    return 1;
+}
+
+#else /* AFS_PTHREAD_ENV */
+
+/* Waiting a certain amount of time for offlining volumes is not supported
+ * for LWP due to a lack of primitives. So, we never time out */
+# define VTimedOut(x) (0)
+
+#endif /* !AFS_PTHREAD_ENV */
+
 #if 0
 static int
 VHold(Volume * vp)
@@ -3569,14 +3612,16 @@ VGetVolume(Error * ec, Error * client_ec, VolId volumeId)
     return retVal;
 }
 
-/* same as VGetVolume, but if a volume is waiting to go offline, we return
- * that it is actually offline, instead of waiting for it to go offline */
+/* same as VGetVolume, but if a volume is waiting to go offline, we only wait
+ * until time ts. If we have waited longer than that, we return that it is
+ * actually offline, instead of waiting for it to go offline */
 Volume *
-VGetVolumeNoWait(Error * ec, Error * client_ec, VolId volumeId)
+VGetVolumeTimed(Error * ec, Error * client_ec, VolId volumeId,
+                const struct timespec *ts)
 {
     Volume *retVal;
     VOL_LOCK;
-    retVal = GetVolume(ec, client_ec, volumeId, NULL, 1);
+    retVal = GetVolume(ec, client_ec, volumeId, NULL, ts);
     VOL_UNLOCK;
     return retVal;
 }
@@ -3584,7 +3629,7 @@ VGetVolumeNoWait(Error * ec, Error * client_ec, VolId volumeId)
 Volume *
 VGetVolume_r(Error * ec, VolId volumeId)
 {
-    return GetVolume(ec, NULL, volumeId, NULL, 0);
+    return GetVolume(ec, NULL, volumeId, NULL, NULL);
 }
 
 /* try to get a volume we've previously looked up */
@@ -3592,7 +3637,7 @@ VGetVolume_r(Error * ec, VolId volumeId)
 Volume *
 VGetVolumeByVp_r(Error * ec, Volume * vp)
 {
-    return GetVolume(ec, NULL, vp->hashid, vp, 0);
+    return GetVolume(ec, NULL, vp->hashid, vp, NULL);
 }
 
 /**
@@ -3602,17 +3647,24 @@ VGetVolumeByVp_r(Error * ec, Volume * vp)
  * @param[out] client_ec  wire error code to be given to clients
  * @param[in]  volumeId   ID of the volume we want
  * @param[in]  hint       optional hint for hash lookups, or NULL
- * @param[in]  nowait     0 to wait for a 'goingOffline' volume to go offline
- *                        before returning, 1 to return immediately
+ * @param[in]  timeout    absolute deadline for waiting for the volume to go
+ *                        offline, if it is going offline. NULL to wait forever.
  *
  * @return a volume handle for the specified volume
  *  @retval NULL an error occurred, or the volume is in such a state that
  *               we cannot load a header or return any volume struct
  *
  * @note for DAFS, caller must NOT hold a ref count on 'hint'
+ *
+ * @note 'timeout' is only checked if the volume is actually going offline; so
+ *       if you pass timeout->tv_sec = 0, this will exhibit typical
+ *       nonblocking behavior.
+ *
+ * @note for LWP builds, 'timeout' must be NULL
  */
 static Volume *
-GetVolume(Error * ec, Error * client_ec, VolId volumeId, Volume * hint, int nowait)
+GetVolume(Error * ec, Error * client_ec, VolId volumeId, Volume * hint,
+          const struct timespec *timeout)
 {
     Volume *vp = hint;
     /* pull this profiling/debugging code out of regular builds */
@@ -3867,19 +3919,26 @@ GetVolume(Error * ec, Error * client_ec, VolId volumeId, Volume * hint, int nowa
 
        if (programType == fileServer) {
            VGET_CTR_INC(V9);
-           if (vp->goingOffline && !nowait) {
-               VGET_CTR_INC(V10);
+           if (vp->goingOffline) {
+               if (timeout && VTimedOut(timeout)) {
+                   /* we've timed out; don't wait for the vol */
+               } else {
+                   VGET_CTR_INC(V10);
 #ifdef AFS_DEMAND_ATTACH_FS
-               /* wait for the volume to go offline */
-               if (V_attachState(vp) == VOL_STATE_GOING_OFFLINE) {
-                   VWaitStateChange_r(vp);
-               }
+                   /* wait for the volume to go offline */
+                   if (V_attachState(vp) == VOL_STATE_GOING_OFFLINE) {
+                       VTimedWaitStateChange_r(vp, timeout, NULL);
+                   }
 #elif defined(AFS_PTHREAD_ENV)
-               VOL_CV_WAIT(&vol_put_volume_cond);
+                   VOL_CV_TIMEDWAIT(&vol_put_volume_cond, timeout, NULL);
 #else /* AFS_PTHREAD_ENV */
-               LWP_WaitProcess(VPutVolume);
+                   /* LWP has no timed wait, so the caller better not be
+                    * expecting one */
+                   osi_Assert(!timeout);
+                   LWP_WaitProcess(VPutVolume);
 #endif /* AFS_PTHREAD_ENV */
-               continue;
+                   continue;
+               }
            }
            if (vp->specialStatus) {
                VGET_CTR_INC(V11);
@@ -4178,7 +4237,7 @@ VDetachVolume_r(Error * ec, Volume * vp)
     if (VCanUseFSSYNC()) {
        notifyServer = vp->needsPutBack;
        if (V_destroyMe(vp) == DESTROY_ME)
-           useDone = FSYNC_VOL_DONE;
+           useDone = FSYNC_VOL_LEAVE_OFF;
 #ifdef AFS_DEMAND_ATTACH_FS
        else if (!V_blessed(vp) || !V_inService(vp))
            useDone = FSYNC_VOL_LEAVE_OFF;
@@ -4207,6 +4266,12 @@ VDetachVolume_r(Error * ec, Volume * vp)
      */
 #ifdef FSSYNC_BUILD_CLIENT
     if (VCanUseFSSYNC() && notifyServer) {
+       if (notifyServer == VOL_PUTBACK_DELETE) {
+           /* Only send FSYNC_VOL_DONE if the volume was actually deleted.
+            * volserver code will set needsPutBack to VOL_PUTBACK_DELETE
+            * to signify a deleted volume. */
+           useDone = FSYNC_VOL_DONE;
+       }
        /*
         * Note:  The server is not notified in the case of a bogus volume
         * explicitly to make it possible to create a volume, do a partial
@@ -4963,6 +5028,10 @@ VOfflineForSalvage_r(struct Volume *vp)
  *
  * @note this is one of the event handlers called by VCancelReservation_r
  *
+ * @note the caller must check if the volume needs to be freed after calling
+ *       this; the volume may not have any references or be on any lists after
+ *       we return, and we do not free it
+ *
  * @see VCancelReservation_r
  *
  * @internal volume package internal use only.
@@ -5237,6 +5306,10 @@ try_FSSYNC(Volume *vp, char *partName, int *code) {
  * server over SALVSYNC. If we are not the fileserver, the request will be
  * sent to the fileserver over FSSYNC (FSYNC_VOL_FORCE_ERROR/FSYNC_SALVAGE).
  *
+ * @note the caller must check if the volume needs to be freed after calling
+ *       this; the volume may not have any references or be on any lists after
+ *       we return, and we do not free it
+ *
  * @note DAFS only
  *
  * @internal volume package internal use only.
@@ -5329,7 +5402,14 @@ VScheduleSalvage_r(Volume * vp)
            }
        }
     }
-    VCancelReservation_r(vp);
+
+    /* NB: this is cancelling the reservation we obtained above, but we do
+     * not call VCancelReservation_r, since that may trigger the vp dtor,
+     * possibly free'ing the vp. We need to keep the vp around after
+     * this, as the caller may reference vp without any refs. Instead, it
+     * is the duty of the caller to inspect 'vp' after we return to see if
+     * needs to be freed. */
+    osi_Assert(--vp->nWaiters >= 0);
     return ret;
 }
 #endif /* SALVSYNC_BUILD_CLIENT || FSSYNC_BUILD_CLIENT */