Cleanup and doxygen-ify the comments for GetVolume
[openafs.git] / src / vol / volume.c
index 816799e..59bd530 100644 (file)
@@ -25,6 +25,7 @@
 #include <rx/xdr.h>
 #include <afs/afsint.h>
 #include <ctype.h>
+#include <signal.h>
 #ifndef AFS_NT40_ENV
 #include <sys/param.h>
 #if !defined(AFS_SGI_ENV)
 #endif /* AFS_PTHREAD_ENV */
 #include "vutils.h"
 #ifndef AFS_NT40_ENV
-#include <dir/dir.h>
+#include <afs/dir.h>
 #include <unistd.h>
 #endif
 
@@ -152,11 +153,21 @@ pthread_mutex_t vol_glock_mutex;
 pthread_mutex_t vol_trans_mutex;
 pthread_cond_t vol_put_volume_cond;
 pthread_cond_t vol_sleep_cond;
+pthread_cond_t vol_init_attach_cond;
 int vol_attach_threads = 1;
 #endif /* AFS_PTHREAD_ENV */
 
+/* start-time configurable I/O parameters */
+ih_init_params vol_io_params;
+
 #ifdef AFS_DEMAND_ATTACH_FS
 pthread_mutex_t vol_salvsync_mutex;
+
+/*
+ * Set this to 1 to disallow SALVSYNC communication in all threads; used
+ * during shutdown, since the salvageserver may have gone away.
+ */
+static volatile sig_atomic_t vol_disallow_salvsync = 0;
 #endif /* AFS_DEMAND_ATTACH_FS */
 
 #ifdef AFS_OSF_ENV
@@ -166,8 +177,7 @@ extern void *calloc(), *realloc();
 /*@printflike@*/ extern void Log(const char *format, ...);
 
 /* Forward declarations */
-static Volume *attach2(Error * ec, VolId vid, char *path,
-                      register struct VolumeHeader *header,
+static Volume *attach2(Error * ec, VolId volumeId, char *path,
                       struct DiskPartition64 *partp, Volume * vp, 
                       int isbusy, int mode);
 static void ReallyFreeVolume(Volume * vp);
@@ -194,13 +204,11 @@ static void LoadVolumeHeader(Error * ec, Volume * vp);
 static int VCheckOffline(register Volume * vp);
 static int VCheckDetach(register Volume * vp);
 static Volume * GetVolume(Error * ec, Error * client_ec, VolId volumeId, Volume * hint, int flags);
-#ifdef AFS_DEMAND_ATTACH_FS
-static int VolumeExternalName_r(VolumeId volumeId, char * name, size_t len);
-#endif
 
 int LogLevel;                  /* Vice loglevel--not defined as extern so that it will be
                                 * defined when not linked with vice, XXXX */
 ProgramType programType;       /* The type of program using the package */
+static VolumePackageOptions vol_opts;
 
 /* extended volume package statistics */
 VolPkgStats VStats;
@@ -276,20 +284,77 @@ ffs(x)
 #endif /* !AFS_HAVE_FFS */
 
 #ifdef AFS_PTHREAD_ENV
+/**
+ * disk partition queue element
+ */
 typedef struct diskpartition_queue_t {
-    struct rx_queue queue;
-    struct DiskPartition64 * diskP;
+    struct rx_queue queue;             /**< queue header */
+    struct DiskPartition64 *diskP;     /**< disk partition table entry */
 } diskpartition_queue_t;
+
+#ifndef AFS_DEMAND_ATTACH_FS
+
 typedef struct vinitvolumepackage_thread_t {
     struct rx_queue queue;
     pthread_cond_t thread_done_cv;
     int n_threads_complete;
 } vinitvolumepackage_thread_t;
 static void * VInitVolumePackageThread(void * args);
+
+#else  /* !AFS_DEMAND_ATTTACH_FS */
+#define VINIT_BATCH_MAX_SIZE 512
+
+/**
+ * disk partition work queue
+ */
+struct partition_queue {
+    struct rx_queue head;              /**< diskpartition_queue_t queue */
+    pthread_mutex_t mutex;
+    pthread_cond_t cv;
+};
+
+/**
+ * volumes parameters for preattach
+ */
+struct volume_init_batch {
+    struct rx_queue queue;               /**< queue header */
+    int thread;                          /**< posting worker thread */
+    int last;                            /**< indicates thread is done */
+    int size;                            /**< number of volume ids in batch */
+    Volume *batch[VINIT_BATCH_MAX_SIZE]; /**< volumes ids to preattach */
+};
+
+/**
+ * volume parameters work queue
+ */
+struct volume_init_queue {
+    struct rx_queue head;                /**< volume_init_batch queue */
+    pthread_mutex_t mutex;
+    pthread_cond_t cv;
+};
+
+/**
+ * volume init worker thread parameters
+ */
+struct vinitvolumepackage_thread_param {
+    int nthreads;                        /**< total number of worker threads */
+    int thread;                          /**< thread number for this worker thread */
+    struct partition_queue *pq;          /**< queue partitions to scan */
+    struct volume_init_queue *vq;        /**< queue of volume to preattach */
+};
+
+static void *VInitVolumePackageThread(void *args);
+static struct DiskPartition64 *VInitNextPartition(struct partition_queue *pq);
+static VolId VInitNextVolumeId(DIR *dirp);
+static int VInitPreAttachVolumes(int nthreads, struct volume_init_queue *vq);
+
+#endif /* !AFS_DEMAND_ATTACH_FS */
 #endif /* AFS_PTHREAD_ENV */
 
+#ifndef AFS_DEMAND_ATTACH_FS
 static int VAttachVolumesByPartition(struct DiskPartition64 *diskP, 
                                     int * nAttached, int * nUnattached);
+#endif /* AFS_DEMAND_ATTACH_FS */
 
 
 #ifdef AFS_DEMAND_ATTACH_FS
@@ -351,8 +416,9 @@ static void VVByPListWait_r(struct DiskPartition64 * dp);
 
 /* online salvager */
 static int VCheckSalvage(register Volume * vp);
-static int VUpdateSalvagePriority_r(Volume * vp);
+#if defined(SALVSYNC_BUILD_CLIENT) || defined(FSSYNC_BUILD_CLIENT)
 static int VScheduleSalvage_r(Volume * vp);
+#endif
 
 /* Volume hash table */
 static void VReorderHash_r(VolumeHashChainHead * head, Volume * pp, Volume * vp);
@@ -370,20 +436,20 @@ static void ShutdownCreateSchedule(vshutdown_thread_t * params);
 /* VLRU */
 static void VLRU_ComputeConstants(void);
 static void VInitVLRU(void);
-static void VLRU_Init_Node_r(volatile Volume * vp);
-static void VLRU_Add_r(volatile Volume * vp);
-static void VLRU_Delete_r(volatile Volume * vp);
-static void VLRU_UpdateAccess_r(volatile Volume * vp);
+static void VLRU_Init_Node_r(Volume * vp);
+static void VLRU_Add_r(Volume * vp);
+static void VLRU_Delete_r(Volume * vp);
+static void VLRU_UpdateAccess_r(Volume * vp);
 static void * VLRU_ScannerThread(void * args);
 static void VLRU_Scan_r(int idx);
 static void VLRU_Promote_r(int idx);
 static void VLRU_Demote_r(int idx);
-static void VLRU_SwitchQueues(volatile Volume * vp, int new_idx, int append);
+static void VLRU_SwitchQueues(Volume * vp, int new_idx, int append);
 
 /* soft detach */
-static int VCheckSoftDetach(volatile Volume * vp, afs_uint32 thresh);
-static int VCheckSoftDetachCandidate(volatile Volume * vp, afs_uint32 thresh);
-static int VSoftDetachVolume_r(volatile Volume * vp, afs_uint32 thresh);
+static int VCheckSoftDetach(Volume * vp, afs_uint32 thresh);
+static int VCheckSoftDetachCandidate(Volume * vp, afs_uint32 thresh);
+static int VSoftDetachVolume_r(Volume * vp, afs_uint32 thresh);
 
 
 pthread_key_t VThread_key;
@@ -399,8 +465,6 @@ struct Lock vol_listLock;   /* Lock obtained when listing volumes:
                                 * list volumes */
 
 
-static int TimeZoneCorrection; /* Number of seconds west of GMT */
-
 /* Common message used when the volume goes off line */
 char *VSalvageMessage =
     "Files in this volume are currently unavailable; call operations";
@@ -411,6 +475,7 @@ int VInit;                  /* 0 - uninitialized,
                                 * 3 - initialized, all volumes have been attached, and
                                 * VConnectFS() has completed. */
 
+static int vinit_attach_abort = 0;
 
 bit32 VolumeCacheCheck;                /* Incremented everytime a volume goes on line--
                                 * used to stamp volume headers and in-core
@@ -424,16 +489,56 @@ bit32 VolumeCacheCheck;           /* Incremented everytime a volume goes on line--
 /***************************************************/
 /* Startup routines                                */
 /***************************************************/
+/**
+ * assign default values to a VolumePackageOptions struct.
+ *
+ * Always call this on a VolumePackageOptions struct first, then set any
+ * specific options you want, then call VInitVolumePackage2.
+ *
+ * @param[in]  pt   caller's program type
+ * @param[out] opts volume package options
+ */
+void
+VOptDefaults(ProgramType pt, VolumePackageOptions *opts)
+{
+    opts->nLargeVnodes = opts->nSmallVnodes = 5;
+    opts->volcache = 0;
+
+    opts->canScheduleSalvage = 0;
+    opts->canUseFSSYNC = 0;
+    opts->canUseSALVSYNC = 0;
+
+    switch (pt) {
+    case fileServer:
+       opts->canScheduleSalvage = 1;
+       opts->canUseSALVSYNC = 1;
+       break;
+
+    case salvageServer:
+       opts->canUseFSSYNC = 1;
+       break;
+
+    case volumeServer:
+       opts->nLargeVnodes = 0;
+       opts->nSmallVnodes = 0;
+
+       opts->canScheduleSalvage = 1;
+       opts->canUseFSSYNC = 1;
+       break;
+
+    default:
+       /* noop */
+       break;
+    }
+}
 
 int
-VInitVolumePackage(ProgramType pt, afs_uint32 nLargeVnodes, afs_uint32 nSmallVnodes,
-                  int connect, afs_uint32 volcache)
+VInitVolumePackage2(ProgramType pt, VolumePackageOptions * opts)
 {
     int errors = 0;            /* Number of errors while finding vice partitions. */
-    struct timeval tv;
-    struct timezone tz;
 
     programType = pt;
+    vol_opts = *opts;
 
     memset(&VStats, 0, sizeof(VStats));
     VStats.hdr_cache_size = 200;
@@ -454,14 +559,13 @@ VInitVolumePackage(ProgramType pt, afs_uint32 nLargeVnodes, afs_uint32 nSmallVno
     assert(pthread_mutex_init(&vol_trans_mutex, NULL) == 0);
     assert(pthread_cond_init(&vol_put_volume_cond, NULL) == 0);
     assert(pthread_cond_init(&vol_sleep_cond, NULL) == 0);
+    assert(pthread_cond_init(&vol_init_attach_cond, NULL) == 0);
 #else /* AFS_PTHREAD_ENV */
     IOMGR_Initialize();
 #endif /* AFS_PTHREAD_ENV */
     Lock_Init(&vol_listLock);
 
     srandom(time(0));          /* For VGetVolumeInfo */
-    gettimeofday(&tv, &tz);
-    TimeZoneCorrection = tz.tz_minuteswest * 60;
 
 #ifdef AFS_DEMAND_ATTACH_FS
     assert(pthread_mutex_init(&vol_salvsync_mutex, NULL) == 0);
@@ -484,27 +588,96 @@ VInitVolumePackage(ProgramType pt, afs_uint32 nLargeVnodes, afs_uint32 nSmallVno
     }
 #endif
 #if defined(AFS_DEMAND_ATTACH_FS) && defined(SALVSYNC_BUILD_CLIENT)
-    if (programType == fileServer) {
+    if (VCanUseSALVSYNC()) {
        /* establish a connection to the salvager at this point */
        assert(VConnectSALV() != 0);
     }
 #endif /* AFS_DEMAND_ATTACH_FS */
 
-    if (volcache > VStats.hdr_cache_size)
-       VStats.hdr_cache_size = volcache;
+    if (opts->volcache > VStats.hdr_cache_size)
+       VStats.hdr_cache_size = opts->volcache;
     VInitVolumeHeaderCache(VStats.hdr_cache_size);
 
-    VInitVnodes(vLarge, nLargeVnodes);
-    VInitVnodes(vSmall, nSmallVnodes);
+    VInitVnodes(vLarge, opts->nLargeVnodes);
+    VInitVnodes(vSmall, opts->nSmallVnodes);
 
 
     errors = VAttachPartitions();
     if (errors)
        return -1;
 
-    if (programType == fileServer) {
+    if (programType != fileServer) {
+        errors = VInitAttachVolumes(programType);
+        if (errors) {
+            return -1;
+        }
+    }
+
+#ifdef FSSYNC_BUILD_CLIENT
+    if (VCanUseFSSYNC()) {
+       if (!VConnectFS()) {
+#ifdef AFS_DEMAND_ATTACH_FS
+           if (programType == salvageServer) {
+               Log("Unable to connect to file server; aborted\n");
+               exit(1);
+           }
+#endif /* AFS_DEMAND_ATTACH_FS */
+           Log("Unable to connect to file server; will retry at need\n");
+       }
+    }
+#endif /* FSSYNC_BUILD_CLIENT */
+    return 0;
+}
+
+
+#if !defined(AFS_PTHREAD_ENV)
+/**
+ * Attach volumes in vice partitions
+ *
+ * @param[in]  pt         calling program type
+ *
+ * @return 0
+ * @note This is the original, non-threaded version of attach parititions.
+ *
+ * @post VInit state is 2
+ */
+int
+VInitAttachVolumes(ProgramType pt)
+{
+    assert(VInit==1);
+    if (pt == fileServer) {
+       struct DiskPartition64 *diskP;
+       /* Attach all the volumes in this partition */
+       for (diskP = DiskPartitionList; diskP; diskP = diskP->next) {
+           int nAttached = 0, nUnattached = 0;
+           assert(VAttachVolumesByPartition(diskP, &nAttached, &nUnattached) == 0);
+       }
+    }
+    VOL_LOCK;
+    VInit = 2;                 /* Initialized, and all volumes have been attached */
+    LWP_NoYieldSignal(VInitAttachVolumes);
+    VOL_UNLOCK;
+    return 0;
+}
+#endif /* !AFS_PTHREAD_ENV */
+
+#if defined(AFS_PTHREAD_ENV) && !defined(AFS_DEMAND_ATTACH_FS)
+/**
+ * Attach volumes in vice partitions
+ *
+ * @param[in]  pt         calling program type
+ *
+ * @return 0
+ * @note Threaded version of attach parititions.
+ *
+ * @post VInit state is 2
+ */
+int
+VInitAttachVolumes(ProgramType pt)
+{
+    assert(VInit==1);
+    if (pt == fileServer) {
        struct DiskPartition64 *diskP;
-#ifdef AFS_PTHREAD_ENV
        struct vinitvolumepackage_thread_t params;
        struct diskpartition_queue_t * dpq;
        int i, threads, parts;
@@ -531,19 +704,17 @@ VInitVolumePackage(ProgramType pt, afs_uint32 nLargeVnodes, afs_uint32 nSmallVno
            assert(pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED) == 0);
 
            Log("VInitVolumePackage: beginning parallel fileserver startup\n");
-#ifdef AFS_DEMAND_ATTACH_FS
-           Log("VInitVolumePackage: using %d threads to pre-attach volumes on %d partitions\n",
-               threads, parts);
-#else /* AFS_DEMAND_ATTACH_FS */
            Log("VInitVolumePackage: using %d threads to attach volumes on %d partitions\n",
                threads, parts);
-#endif /* AFS_DEMAND_ATTACH_FS */
 
            VOL_LOCK;
            for (i=0; i < threads; i++) {
+                AFS_SIGSET_DECL;
+                AFS_SIGSET_CLEAR();
                assert(pthread_create
                       (&tid, &attrs, &VInitVolumePackageThread,
                        &params) == 0);
+                AFS_SIGSET_RESTORE();
            }
 
            while(params.n_threads_complete < threads) {
@@ -556,50 +727,21 @@ VInitVolumePackage(ProgramType pt, afs_uint32 nLargeVnodes, afs_uint32 nSmallVno
            /* if we're only going to run one init thread, don't bother creating
             * another LWP */
            Log("VInitVolumePackage: beginning single-threaded fileserver startup\n");
-#ifdef AFS_DEMAND_ATTACH_FS
-           Log("VInitVolumePackage: using 1 thread to pre-attach volumes on %d partition(s)\n",
-               parts);
-#else /* AFS_DEMAND_ATTACH_FS */
            Log("VInitVolumePackage: using 1 thread to attach volumes on %d partition(s)\n",
                parts);
-#endif /* AFS_DEMAND_ATTACH_FS */
 
            VInitVolumePackageThread(&params);
        }
 
        assert(pthread_cond_destroy(&params.thread_done_cv) == 0);
-
-#else /* AFS_PTHREAD_ENV */
-
-       /* Attach all the volumes in this partition */
-       for (diskP = DiskPartitionList; diskP; diskP = diskP->next) {
-           int nAttached = 0, nUnattached = 0;
-           assert(VAttachVolumesByPartition(diskP, &nAttached, &nUnattached) == 0);
-       }
-#endif /* AFS_PTHREAD_ENV */
     }
-
+    VOL_LOCK;
     VInit = 2;                 /* Initialized, and all volumes have been attached */
-#ifdef FSSYNC_BUILD_CLIENT
-    if (programType == volumeUtility && connect) {
-       if (!VConnectFS()) {
-           Log("Unable to connect to file server; will retry at need\n");
-           /*exit(1);*/
-       }
-    }
-#ifdef AFS_DEMAND_ATTACH_FS
-    else if (programType == salvageServer) {
-       if (!VConnectFS()) {
-           Log("Unable to connect to file server; aborted\n");
-           exit(1);
-       }
-    }
-#endif /* AFS_DEMAND_ATTACH_FS */
-#endif /* FSSYNC_BUILD_CLIENT */
+    assert(pthread_cond_broadcast(&vol_init_attach_cond) == 0);
+    VOL_UNLOCK;
     return 0;
 }
 
-#ifdef AFS_PTHREAD_ENV
 static void *
 VInitVolumePackageThread(void * args) {
 
@@ -615,6 +757,11 @@ VInitVolumePackageThread(void * args) {
     while (queue_IsNotEmpty(params)) {
         int nAttached = 0, nUnattached = 0;
 
+        if (vinit_attach_abort) {
+            Log("Aborting initialization\n");
+            goto done;
+        }
+
         dpq = queue_First(params,diskpartition_queue_t);
        queue_Remove(dpq);
        VOL_UNLOCK;
@@ -626,13 +773,291 @@ VInitVolumePackageThread(void * args) {
        VOL_LOCK;
     }
 
+done:
     params->n_threads_complete++;
     pthread_cond_signal(&params->thread_done_cv);
     VOL_UNLOCK;
     return NULL;
 }
-#endif /* AFS_PTHREAD_ENV */
+#endif /* AFS_PTHREAD_ENV && !AFS_DEMAND_ATTACH_FS */
+
+#if defined(AFS_DEMAND_ATTACH_FS)
+/**
+ * Attach volumes in vice partitions
+ *
+ * @param[in]  pt         calling program type
+ *
+ * @return 0
+ * @note Threaded version of attach partitions.
+ *
+ * @post VInit state is 2
+ */
+int
+VInitAttachVolumes(ProgramType pt)
+{
+    assert(VInit==1);
+    if (pt == fileServer) {
+
+       struct DiskPartition64 *diskP;
+       struct partition_queue pq;
+        struct volume_init_queue vq;
+
+       int i, threads, parts;
+       pthread_t tid;
+       pthread_attr_t attrs;
+
+       /* create partition work queue */
+        queue_Init(&pq);
+        assert(pthread_cond_init(&(pq.cv), NULL) == 0);
+        assert(pthread_mutex_init(&(pq.mutex), NULL) == 0);
+       for (parts = 0, diskP = DiskPartitionList; diskP; diskP = diskP->next, parts++) {
+           struct diskpartition_queue_t *dp;
+           dp = (struct diskpartition_queue_t*)malloc(sizeof(struct diskpartition_queue_t));
+           assert(dp != NULL);
+           dp->diskP = diskP;
+           queue_Append(&pq, dp);
+       }
 
+        /* number of worker threads; at least one, not to exceed the number of partitions */
+       threads = MIN(parts, vol_attach_threads);
+
+        /* create volume work queue */
+        queue_Init(&vq);
+        assert(pthread_cond_init(&(vq.cv), NULL) == 0);
+        assert(pthread_mutex_init(&(vq.mutex), NULL) == 0);
+
+        assert(pthread_attr_init(&attrs) == 0);
+        assert(pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED) == 0);
+
+        Log("VInitVolumePackage: beginning parallel fileserver startup\n");
+        Log("VInitVolumePackage: using %d threads to pre-attach volumes on %d partitions\n",
+               threads, parts);
+
+        /* create threads to scan disk partitions. */
+       for (i=0; i < threads; i++) {
+           struct vinitvolumepackage_thread_param *params;
+           params = (struct vinitvolumepackage_thread_param *)malloc(sizeof(struct vinitvolumepackage_thread_param));
+            assert(params);
+            params->pq = &pq;
+            params->vq = &vq;
+            params->nthreads = threads;
+            params->thread = i+1;
+
+            AFS_SIGSET_DECL;
+            AFS_SIGSET_CLEAR();
+           assert(pthread_create (&tid, &attrs, &VInitVolumePackageThread, (void*)params) == 0);
+            AFS_SIGSET_RESTORE();
+       }
+
+        VInitPreAttachVolumes(threads, &vq);
+
+        assert(pthread_attr_destroy(&attrs) == 0);
+        assert(pthread_cond_destroy(&pq.cv) == 0);
+        assert(pthread_mutex_destroy(&pq.mutex) == 0);
+        assert(pthread_cond_destroy(&vq.cv) == 0);
+        assert(pthread_mutex_destroy(&vq.mutex) == 0);
+    }
+
+    VOL_LOCK;
+    VInit = 2;                 /* Initialized, and all volumes have been attached */
+    assert(pthread_cond_broadcast(&vol_init_attach_cond) == 0);
+    VOL_UNLOCK;
+
+    return 0;
+}
+
+/**
+ * Volume package initialization worker thread. Scan partitions for volume
+ * header files. Gather batches of volume ids and dispatch them to
+ * the main thread to be preattached.  The volume preattachement is done
+ * in the main thread to avoid global volume lock contention.
+ */
+static void *
+VInitVolumePackageThread(void *args)
+{
+    struct vinitvolumepackage_thread_param *params;
+    struct DiskPartition64 *partition;
+    struct partition_queue *pq;
+    struct volume_init_queue *vq;
+    struct volume_init_batch *vb;
+
+    assert(args);
+    params = (struct vinitvolumepackage_thread_param *)args;
+    pq = params->pq;
+    vq = params->vq;
+    assert(pq);
+    assert(vq);
+
+    vb = (struct volume_init_batch*)malloc(sizeof(struct volume_init_batch));
+    assert(vb);
+    vb->thread = params->thread;
+    vb->last = 0;
+    vb->size = 0;
+
+    Log("Scanning partitions on thread %d of %d\n", params->thread, params->nthreads);
+    while((partition = VInitNextPartition(pq))) {
+        DIR *dirp;
+        VolId vid;
+
+        Log("Partition %s: pre-attaching volumes\n", partition->name);
+        dirp = opendir(VPartitionPath(partition));
+        if (!dirp) {
+            Log("opendir on Partition %s failed, errno=%d!\n", partition->name, errno);
+            continue;
+        }
+        while ((vid = VInitNextVolumeId(dirp))) {
+            Volume *vp = (Volume*)malloc(sizeof(Volume));
+            assert(vp);
+            memset(vp, 0, sizeof(Volume));
+            vp->device = partition->device;
+            vp->partition = partition;
+            vp->hashid = vid;
+            queue_Init(&vp->vnode_list);
+            assert(pthread_cond_init(&V_attachCV(vp), NULL) == 0);
+
+            vb->batch[vb->size++] = vp;
+            if (vb->size == VINIT_BATCH_MAX_SIZE) {
+                assert(pthread_mutex_lock(&vq->mutex) == 0);
+                queue_Append(vq, vb);
+                assert(pthread_cond_broadcast(&vq->cv) == 0);
+                assert(pthread_mutex_unlock(&vq->mutex) == 0);
+
+                vb = (struct volume_init_batch*)malloc(sizeof(struct volume_init_batch));
+                assert(vb);
+                vb->thread = params->thread;
+                vb->size = 0;
+                vb->last = 0;
+            }
+        }
+        closedir(dirp);
+    }
+
+    vb->last = 1;
+    assert(pthread_mutex_lock(&vq->mutex) == 0);
+    queue_Append(vq, vb);
+    assert(pthread_cond_broadcast(&vq->cv) == 0);
+    assert(pthread_mutex_unlock(&vq->mutex) == 0);
+
+    Log("Partition scan thread %d of %d ended\n", params->thread, params->nthreads);
+    free(params);
+    return NULL;
+}
+
+/**
+ * Read next element from the pre-populated partition list.
+ */
+static struct DiskPartition64*
+VInitNextPartition(struct partition_queue *pq)
+{
+    struct DiskPartition64 *partition;
+    struct diskpartition_queue_t *dp; /* queue element */
+
+    if (vinit_attach_abort) {
+        Log("Aborting volume preattach thread.\n");
+        return NULL;
+    }
+
+    /* get next partition to scan */
+    assert(pthread_mutex_lock(&pq->mutex) == 0);
+    if (queue_IsEmpty(pq)) {
+        assert(pthread_mutex_unlock(&pq->mutex) == 0);
+        return NULL;
+    }
+    dp = queue_First(pq, diskpartition_queue_t);
+    queue_Remove(dp);
+    assert(pthread_mutex_unlock(&pq->mutex) == 0);
+
+    assert(dp);
+    assert(dp->diskP);
+
+    partition = dp->diskP;
+    free(dp);
+    return partition;
+}
+
+/**
+ * Find next volume id on the partition.
+ */
+static VolId
+VInitNextVolumeId(DIR *dirp)
+{
+    struct dirent *d;
+    VolId vid = 0;
+    char *ext;
+
+    while((d = readdir(dirp))) {
+        if (vinit_attach_abort) {
+            Log("Aborting volume preattach thread.\n");
+            break;
+        }
+        ext = strrchr(d->d_name, '.');
+        if (d->d_name[0] == 'V' && ext && strcmp(ext, VHDREXT) == 0) {
+            vid = VolumeNumber(d->d_name);
+            if (vid) {
+               break;
+            }
+            Log("Warning: bogus volume header file: %s\n", d->d_name);
+        }
+    }
+    return vid;
+}
+
+/**
+ * Preattach volumes in batches to avoid lock contention.
+ */
+static int
+VInitPreAttachVolumes(int nthreads, struct volume_init_queue *vq)
+{
+    struct volume_init_batch *vb;
+    int i;
+
+    while (nthreads) {
+        /* dequeue next volume */
+        pthread_mutex_lock(&vq->mutex);
+        if (queue_IsEmpty(vq)) {
+            pthread_cond_wait(&vq->cv, &vq->mutex);
+        }
+        vb = queue_First(vq, volume_init_batch);
+        queue_Remove(vb);
+        pthread_mutex_unlock(&vq->mutex);
+
+        if (vb->size) {
+            VOL_LOCK;
+            for (i = 0; i<vb->size; i++) {
+                Volume *vp;
+                Volume *dup;
+                Error ec = 0;
+
+                vp = vb->batch[i];
+               dup = VLookupVolume_r(&ec, vp->hashid, NULL);
+                if (ec) {
+                    Log("Error looking up volume, code=%d\n", ec);
+                }
+                else if (dup) {
+                    Log("Warning: Duplicate volume id %d detected.\n", vp->hashid);
+                }
+                else {
+                    /* put pre-attached volume onto the hash table
+                     * and bring it up to the pre-attached state */
+                    AddVolumeToHashTable(vp, vp->hashid);
+                    AddVolumeToVByPList_r(vp);
+                    VLRU_Init_Node_r(vp);
+                    VChangeState_r(vp, VOL_STATE_PREATTACHED);
+                }
+            }
+            VOL_UNLOCK;
+        }
+
+        if (vb->last) {
+            nthreads--;
+        }
+        free(vb);
+    }
+    return 0;
+}
+#endif /* AFS_DEMAND_ATTACH_FS */
+
+#if !defined(AFS_DEMAND_ATTACH_FS)
 /*
  * attach all volumes on a given disk partition
  */
@@ -653,15 +1078,17 @@ VAttachVolumesByPartition(struct DiskPartition64 *diskP, int * nAttached, int *
   while ((dp = readdir(dirp))) {
     char *p;
     p = strrchr(dp->d_name, '.');
+
+    if (vinit_attach_abort) {
+      Log("Partition %s: abort attach volumes\n", diskP->name);
+      goto done;
+    }
+
     if (p != NULL && strcmp(p, VHDREXT) == 0) {
       Error error;
       Volume *vp;
-#ifdef AFS_DEMAND_ATTACH_FS
-      vp = VPreAttachVolumeByName(&error, diskP->name, dp->d_name);
-#else /* AFS_DEMAND_ATTACH_FS */
       vp = VAttachVolumeByName(&error, diskP->name, dp->d_name,
                               V_VOLUPD);
-#endif /* AFS_DEMAND_ATTACH_FS */
       (*(vp ? nAttached : nUnattached))++;
       if (error == VOFFLINE)
        Log("Volume %d stays offline (/vice/offline/%s exists)\n", VolumeNumber(dp->d_name), dp->d_name);
@@ -670,19 +1097,18 @@ VAttachVolumesByPartition(struct DiskPartition64 *diskP, int * nAttached, int *
            diskP->name, VolumeNumber(dp->d_name),
            dp->d_name);
       }
-#if !defined(AFS_DEMAND_ATTACH_FS)
       if (vp) {
        VPutVolume(vp);
       }
-#endif /* AFS_DEMAND_ATTACH_FS */
     }
   }
 
   Log("Partition %s: attached %d volumes; %d volumes not attached\n", diskP->name, *nAttached, *nUnattached);
+done:
   closedir(dirp);
   return ret;
 }
-
+#endif /* !AFS_DEMAND_ATTACH_FS */
 
 /***************************************************/
 /* Shutdown routines                               */
@@ -741,13 +1167,12 @@ VAttachVolumesByPartition(struct DiskPartition64 *diskP, int * nAttached, int *
  *   shutdown all remaining volumes
  */
 
+#ifdef AFS_DEMAND_ATTACH_FS
+
 void
 VShutdown_r(void)
 {
     int i;
-    register Volume *vp, *np;
-    register afs_int32 code;
-#ifdef AFS_DEMAND_ATTACH_FS
     struct DiskPartition64 * diskP;
     struct diskpartition_queue_t * dpq;
     vshutdown_thread_t params;
@@ -756,6 +1181,12 @@ VShutdown_r(void)
 
     memset(&params, 0, sizeof(vshutdown_thread_t));
 
+    if (VInit < 2) {
+        Log("VShutdown:  aborting attach volumes\n");
+        vinit_attach_abort = 1;
+        VOL_CV_WAIT(&vol_init_attach_cond);
+    }
+
     for (params.n_parts=0, diskP = DiskPartitionList;
         diskP; diskP = diskP->next, params.n_parts++);
 
@@ -862,7 +1293,27 @@ VShutdown_r(void)
     }
 
     Log("VShutdown:  complete.\n");
+}
+
 #else /* AFS_DEMAND_ATTACH_FS */
+
+void
+VShutdown_r(void)
+{
+    int i;
+    register Volume *vp, *np;
+    register afs_int32 code;
+
+    if (VInit < 2) {
+        Log("VShutdown:  aborting attach volumes\n");
+        vinit_attach_abort = 1;
+#ifdef AFS_PTHREAD_ENV
+        VOL_CV_WAIT(&vol_init_attach_cond);
+#else
+        LWP_WaitProcess(VInitAttachVolumes);
+#endif /* AFS_PTHREAD_ENV */
+    }
+
     Log("VShutdown:  shutting down on-line volumes...\n");
     for (i = 0; i < VolumeHashTable.Size; i++) {
        /* try to hold first volume in the hash table */
@@ -879,17 +1330,39 @@ VShutdown_r(void)
        }
     }
     Log("VShutdown:  complete.\n");
-#endif /* AFS_DEMAND_ATTACH_FS */
 }
+#endif /* AFS_DEMAND_ATTACH_FS */
+
 
 void
 VShutdown(void)
 {
+    assert(VInit>0);
     VOL_LOCK;
     VShutdown_r();
     VOL_UNLOCK;
 }
 
+/**
+ * stop new activity (e.g. SALVSYNC) from occurring
+ *
+ * Use this to make the volume package less busy; for example, during
+ * shutdown. This doesn't actually shutdown/detach anything in the
+ * volume package, but prevents certain processes from ocurring. For
+ * example, preventing new SALVSYNC communication in DAFS. In theory, we
+ * could also use this to prevent new volume attachment, or prevent
+ * other programs from checking out volumes, etc.
+ */
+void
+VSetTranquil(void)
+{
+#ifdef AFS_DEMAND_ATTACH_FS
+    /* make sure we don't try to contact the salvageserver, since it may
+     * not be around anymore */
+    vol_disallow_salvsync = 1;
+#endif
+}
+
 #ifdef AFS_DEMAND_ATTACH_FS
 /*
  * demand attach fs
@@ -1006,7 +1479,7 @@ ShutdownCreateSchedule(vshutdown_thread_t * params)
     if (thr_left) {
        /* try to assign any leftover threads to partitions that
         * had volume lengths closer to needing thread_target+1 */
-       int max_residue, max_id;
+       int max_residue, max_id = 0;
 
        /* compute the residues */
        for (diskP = DiskPartitionList; diskP; diskP = diskP->next) {
@@ -1062,10 +1535,8 @@ ShutdownCreateSchedule(vshutdown_thread_t * params)
 static void *
 VShutdownThread(void * args)
 {
-    struct rx_queue *qp;
-    Volume * vp;
     vshutdown_thread_t * params;
-    int part, code, found, pass, schedule_version_save, count;
+    int found, pass, schedule_version_save, count;
     struct DiskPartition64 *diskP;
     struct diskpartition_queue_t * dpq;
     Device id;
@@ -1217,7 +1688,7 @@ VShutdownThread(void * args)
 int
 VShutdownByPartition_r(struct DiskPartition64 * dp)
 {
-    int pass, retVal;
+    int pass;
     int pass_stats[4];
     int total;
 
@@ -1242,7 +1713,7 @@ VShutdownByPartition_r(struct DiskPartition64 * dp)
     Log("VShutdownByPartition:  shut down %d volumes on %s (pass[0]=%d, pass[1]=%d, pass[2]=%d, pass[3]=%d)\n",
        total, VPartitionPath(dp), pass_stats[0], pass_stats[1], pass_stats[2], pass_stats[3]);
 
-    return retVal;
+    return 0;
 }
 
 /* internal shutdown functionality
@@ -1365,6 +1836,8 @@ VShutdownVolume_r(Volume * vp)
            VOffline_r(vp, "File server was shut down");
        }
        break;
+    default:
+       break;
     }
     
     VCancelReservation_r(vp);
@@ -1458,7 +1931,7 @@ void
 VolumeHeaderToDisk(VolumeDiskHeader_t * dh, VolumeHeader_t * h)
 {
 
-    memset((char *)dh, 0, sizeof(VolumeDiskHeader_t));
+    memset(dh, 0, sizeof(VolumeDiskHeader_t));
     dh->stamp = h->stamp;
     dh->id = h->id;
     dh->parent = h->parent;
@@ -1493,7 +1966,7 @@ VolumeHeaderToDisk(VolumeDiskHeader_t * dh, VolumeHeader_t * h)
 void
 DiskToVolumeHeader(VolumeHeader_t * h, VolumeDiskHeader_t * dh)
 {
-    memset((char *)h, 0, sizeof(VolumeHeader_t));
+    memset(h, 0, sizeof(VolumeHeader_t));
     h->stamp = dh->stamp;
     h->id = dh->id;
     h->parent = dh->parent;
@@ -1746,10 +2219,6 @@ Volume *
 VAttachVolumeByName_r(Error * ec, char *partition, char *name, int mode)
 {
     register Volume *vp = NULL;
-    int fd, n;
-    struct afs_stat status;
-    struct VolumeDiskHeader diskHeader;
-    struct VolumeHeader iheader;
     struct DiskPartition64 *partp;
     char path[64];
     int isbusy = 0;
@@ -1769,7 +2238,7 @@ VAttachVolumeByName_r(Error * ec, char *partition, char *name, int mode)
        goto done;
     }
 
-    if (programType == volumeUtility) {
+    if (VRequiresPartLock()) {
        assert(VInit == 3);
        VLockPartition_r(partition);
     } else if (programType == fileServer) {
@@ -1901,47 +2370,11 @@ VAttachVolumeByName_r(Error * ec, char *partition, char *name, int mode)
 
     strcat(path, "/");
     strcat(path, name);
-    if ((fd = afs_open(path, O_RDONLY)) == -1 || afs_fstat(fd, &status) == -1) {
-       Log("VAttachVolume: Failed to open %s (errno %d)\n", path, errno);
-       if (fd > -1)
-           close(fd);
-       *ec = VNOVOL;
-       VOL_LOCK;
-       goto done;
-    }
-    n = read(fd, &diskHeader, sizeof(diskHeader));
-    close(fd);
-    if (n != sizeof(diskHeader)
-       || diskHeader.stamp.magic != VOLUMEHEADERMAGIC) {
-       Log("VAttachVolume: Error reading volume header %s\n", path);
-       *ec = VSALVAGE;
-       VOL_LOCK;
-       goto done;
-    }
-    if (diskHeader.stamp.version != VOLUMEHEADERVERSION) {
-       Log("VAttachVolume: Volume %s, version number is incorrect; volume needs salvaged\n", path);
-       *ec = VSALVAGE;
-       VOL_LOCK;
-       goto done;
-    }
-
-    DiskToVolumeHeader(&iheader, &diskHeader);
-#ifdef FSSYNC_BUILD_CLIENT
-    if (programType == volumeUtility && mode != V_SECRETLY && mode != V_PEEK) {
-        VOL_LOCK;
-       if (FSYNC_VolOp(iheader.id, partition, FSYNC_VOL_NEEDVOLUME, mode, NULL)
-           != SYNC_OK) {
-           Log("VAttachVolume: attach of volume %u apparently denied by file server\n", iheader.id);
-           *ec = VNOVOL;       /* XXXX */
-           goto done;
-       }
-       VOL_UNLOCK;
-    }
-#endif
 
     if (!vp) {
       vp = (Volume *) calloc(1, sizeof(Volume));
       assert(vp != NULL);
+      vp->hashid = volumeId;
       vp->device = partp->device;
       vp->partition = partp;
       queue_Init(&vp->vnode_list);
@@ -1952,15 +2385,15 @@ 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, &iheader, partp, vp, isbusy, mode);
+    vp = attach2(ec, volumeId, path, partp, vp, isbusy, mode);
 
-    if (programType == volumeUtility && vp) {
+    if (VCanUseFSSYNC() && vp) {
+#ifdef AFS_DEMAND_ATTACH_FS
        if ((mode == V_VOLUPD) || (VolumeWriteable(vp) && (mode == V_CLONE))) {
            /* mark volume header as in use so that volser crashes lead to a
             * salvage attempt */
            VUpdateVolume_r(ec, vp, 0);
        }
-#ifdef AFS_DEMAND_ATTACH_FS
        /* for dafs, we should tell the fileserver, except for V_PEEK
          * where we know it is not necessary */
        if (mode == V_PEEK) {
@@ -1992,9 +2425,15 @@ VAttachVolumeByName_r(Error * ec, char *partition, char *name, int mode)
      * 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 (programType == volumeUtility && vp == NULL &&
+    if (VCanUseFSSYNC() && vp == NULL &&
        mode != V_SECRETLY && mode != V_PEEK) {
-       FSYNC_VolOp(iheader.id, partition, FSYNC_VOL_ON, 0, NULL);
+
+#ifdef AFS_DEMAND_ATTACH_FS
+        /* If we couldn't attach but we scheduled a salvage, we already
+         * notified the fileserver; don't online it now */
+        if (*ec != VSALVAGING)
+#endif /* AFS_DEMAND_ATTACH_FS */
+       FSYNC_VolOp(volumeId, partition, FSYNC_VOL_ON, 0, NULL);
     } else 
 #endif
     if (programType == fileServer && vp) {
@@ -2048,7 +2487,7 @@ VAttachVolumeByName_r(Error * ec, char *partition, char *name, int mode)
     }
 
   done:
-    if (programType == volumeUtility) {
+    if (VRequiresPartLock()) {
        VUnlockPartition_r(partition);
     }
     if (*ec) {
@@ -2075,15 +2514,12 @@ static Volume *
 VAttachVolumeByVp_r(Error * ec, Volume * vp, int mode)
 {
     char name[VMAXPATHLEN];
-    int fd, n, reserve = 0;
-    struct afs_stat status;
-    struct VolumeDiskHeader diskHeader;
-    struct VolumeHeader iheader;
+    int reserve = 0;
     struct DiskPartition64 *partp;
     char path[64];
     int isbusy = 0;
     VolId volumeId;
-    Volume * nvp;
+    Volume * nvp = NULL;
     VolumeStats stats_save;
     *ec = 0;
 
@@ -2139,48 +2575,19 @@ VAttachVolumeByVp_r(Error * ec, Volume * vp, int mode)
 
     *ec = 0;
 
-
-    /* compute path to disk header, 
-     * read in header, 
-     * and verify magic and version stamps */
+    /* compute path to disk header */
     strcpy(path, VPartitionPath(partp));
 
     VOL_UNLOCK;
 
     strcat(path, "/");
     strcat(path, name);
-    if ((fd = afs_open(path, O_RDONLY)) == -1 || afs_fstat(fd, &status) == -1) {
-       Log("VAttachVolume: Failed to open %s (errno %d)\n", path, errno);
-       if (fd > -1)
-           close(fd);
-       *ec = VNOVOL;
-       VOL_LOCK;
-       goto done;
-    }
-    n = read(fd, &diskHeader, sizeof(diskHeader));
-    close(fd);
-    if (n != sizeof(diskHeader)
-       || diskHeader.stamp.magic != VOLUMEHEADERMAGIC) {
-       Log("VAttachVolume: Error reading volume header %s\n", path);
-       *ec = VSALVAGE;
-       VOL_LOCK;
-       goto done;
-    }
-    if (diskHeader.stamp.version != VOLUMEHEADERVERSION) {
-       Log("VAttachVolume: Volume %s, version number is incorrect; volume needs salvaged\n", path);
-       *ec = VSALVAGE;
-       VOL_LOCK;
-       goto done;
-    }
-
-    /* convert on-disk header format to in-memory header format */
-    DiskToVolumeHeader(&iheader, &diskHeader);
 
     /* do volume attach
      *
      * NOTE: attach2 is entered without any locks, and returns
      * with vol_glock_mutex held */
-    vp = attach2(ec, volumeId, path, &iheader, partp, vp, isbusy, mode);
+    vp = attach2(ec, volumeId, path, partp, vp, isbusy, mode);
 
     /*
      * the event that an error was encountered, or
@@ -2238,40 +2645,204 @@ VAttachVolumeByVp_r(Error * ec, Volume * vp, int mode)
        return vp;
     }
 }
+
+/**
+ * lock a volume on disk (non-blocking).
+ *
+ * @param[in] vp  The volume to lock
+ * @param[in] locktype READ_LOCK or WRITE_LOCK
+ *
+ * @return operation status
+ *  @retval 0 success, lock was obtained
+ *  @retval EBUSY a conflicting lock was held by another process
+ *  @retval EIO   error acquiring lock
+ *
+ * @pre If we're in the fileserver, vp is in an exclusive state
+ *
+ * @pre vp is not already locked
+ */
+static int
+VLockVolumeNB(Volume *vp, int locktype)
+{
+    int code;
+
+    assert(programType != fileServer || VIsExclusiveState(V_attachState(vp)));
+    assert(!(V_attachFlags(vp) & VOL_LOCKED));
+
+    code = VLockVolumeByIdNB(vp->hashid, vp->partition, locktype);
+    if (code == 0) {
+       V_attachFlags(vp) |= VOL_LOCKED;
+    }
+
+    return code;
+}
+
+/**
+ * unlock a volume on disk that was locked with VLockVolumeNB.
+ *
+ * @param[in] vp  volume to unlock
+ *
+ * @pre If we're in the fileserver, vp is in an exclusive state
+ *
+ * @pre vp has already been locked
+ */
+static void
+VUnlockVolume(Volume *vp)
+{
+    assert(programType != fileServer || VIsExclusiveState(V_attachState(vp)));
+    assert((V_attachFlags(vp) & VOL_LOCKED));
+
+    VUnlockVolumeById(vp->hashid, vp->partition);
+
+    V_attachFlags(vp) &= ~VOL_LOCKED;
+}
 #endif /* AFS_DEMAND_ATTACH_FS */
 
-/*
- * called without any locks held
- * returns with vol_glock_mutex held
+/**
+ * read in a vol header, possibly lock the vol header, and possibly check out
+ * the vol header from the fileserver, as part of volume attachment.
+ *
+ * @param[out] ec     error code
+ * @param[in] vp      volume pointer object
+ * @param[in] partp   disk partition object of the attaching partition
+ * @param[in] mode    attachment mode such as V_VOLUPD, V_DUMP, etc (see
+ *                    volume.h)
+ * @param[in] peek    1 to just try to read in the volume header and make sure
+ *                    we don't try to lock the vol, or check it out from
+ *                    FSSYNC or anything like that; 0 otherwise, for 'normal'
+ *                    operation
+ *
+ * @note As part of DAFS volume attachment, the volume header may be either
+ *       read- or write-locked to ensure mutual exclusion of certain volume
+ *       operations. In some cases in order to determine whether we need to
+ *       read- or write-lock the header, we need to read in the header to see
+ *       if the volume is RW or not. So, if we read in the header under a
+ *       read-lock and determine that we actually need a write-lock on the
+ *       volume header, this function will drop the read lock, acquire a write
+ *       lock, and read the header in again.
  */
-private Volume * 
-attach2(Error * ec, VolId volumeId, char *path, register struct VolumeHeader * header,
-       struct DiskPartition64 * partp, register Volume * vp, int isbusy, int mode)
-{
-    vp->specialStatus = (byte) (isbusy ? VBUSY : 0);
-    IH_INIT(vp->vnodeIndex[vLarge].handle, partp->device, header->parent,
-           header->largeVnodeIndex);
-    IH_INIT(vp->vnodeIndex[vSmall].handle, partp->device, header->parent,
-           header->smallVnodeIndex);
-    IH_INIT(vp->diskDataHandle, partp->device, header->parent,
-           header->volumeInfo);
-    IH_INIT(vp->linkHandle, partp->device, header->parent, header->linkTable);
-    vp->shuttingDown = 0;
-    vp->goingOffline = 0;
-    vp->nUsers = 1;
+static void
+attach_volume_header(Error *ec, Volume *vp, struct DiskPartition64 *partp,
+                     int mode, int peek)
+{
+    struct VolumeDiskHeader diskHeader;
+    struct VolumeHeader header;
+    int code;
+    int first_try = 1;
+    int lock_tries = 0, checkout_tries = 0;
+    int retry;
+    VolumeId volid = vp->hashid;
+#ifdef FSSYNC_BUILD_CLIENT
+    int checkout, done_checkout = 0;
+#endif /* FSSYNC_BUILD_CLIENT */
 #ifdef AFS_DEMAND_ATTACH_FS
-    vp->stats.last_attach = FT_ApproxTime();
-    vp->stats.attaches++;
+    int locktype = 0, use_locktype = -1;
+#endif /* AFS_DEMAND_ATTACH_FS */
+
+ retry:
+    retry = 0;
+    *ec = 0;
+
+    if (lock_tries > VOL_MAX_CHECKOUT_RETRIES) {
+       Log("VAttachVolume: retried too many times trying to lock header for "
+           "vol %lu part %s; giving up\n", afs_printable_uint32_lu(volid),
+           VPartitionPath(partp));
+       *ec = VNOVOL;
+       goto done;
+    }
+    if (checkout_tries > VOL_MAX_CHECKOUT_RETRIES) {
+       Log("VAttachVolume: retried too many times trying to checkout "
+           "vol %lu part %s; giving up\n", afs_printable_uint32_lu(volid),
+           VPartitionPath(partp));
+       *ec = VNOVOL;
+       goto done;
+    }
+
+    if (VReadVolumeDiskHeader(volid, partp, NULL)) {
+       /* short-circuit the 'volume does not exist' case */
+       *ec = VNOVOL;
+       goto done;
+    }
+
+#ifdef FSSYNC_BUILD_CLIENT
+    checkout = !done_checkout;
+    done_checkout = 1;
+    if (!peek && checkout && VMustCheckoutVolume(mode)) {
+        SYNC_response res;
+        memset(&res, 0, sizeof(res));
+
+       if (FSYNC_VolOp(volid, VPartitionPath(partp), FSYNC_VOL_NEEDVOLUME, mode, &res)
+           != SYNC_OK) {
+
+            if (res.hdr.reason == FSYNC_SALVAGE) {
+                Log("VAttachVolume: file server says volume %lu is salvaging\n",
+                     afs_printable_uint32_lu(volid));
+                *ec = VSALVAGING;
+            } else {
+               Log("VAttachVolume: attach of volume %lu apparently denied by file server\n",
+                     afs_printable_uint32_lu(volid));
+               *ec = VNOVOL;   /* XXXX */
+            }
+           goto done;
+       }
+    }
 #endif
 
-    VOL_LOCK;
-    IncUInt64(&VStats.attaches);
-    vp->cacheCheck = ++VolumeCacheCheck;
-    /* just in case this ever rolls over */
-    if (!vp->cacheCheck)
-       vp->cacheCheck = ++VolumeCacheCheck;
-    GetVolumeHeader(vp);
-    VOL_UNLOCK;
+#ifdef AFS_DEMAND_ATTACH_FS
+    if (use_locktype < 0) {
+       /* don't know whether vol is RO or RW; assume it's RO and we can retry
+        * if it turns out to be RW */
+       locktype = VVolLockType(mode, 0);
+
+    } else {
+       /* a previous try says we should use use_locktype to lock the volume,
+        * so use that */
+       locktype = use_locktype;
+    }
+
+    if (!peek && locktype) {
+       code = VLockVolumeNB(vp, locktype);
+       if (code) {
+           if (code == EBUSY) {
+               Log("VAttachVolume: another program has vol %lu locked\n",
+                   afs_printable_uint32_lu(volid));
+           } else {
+               Log("VAttachVolume: error %d trying to lock vol %lu\n",
+                   code, afs_printable_uint32_lu(volid));
+           }
+
+           *ec = VNOVOL;
+           goto done;
+       }
+    }
+#endif /* AFS_DEMAND_ATTACH_FS */
+
+    code = VReadVolumeDiskHeader(volid, partp, &diskHeader);
+    if (code) {
+       if (code == EIO) {
+           *ec = VSALVAGE;
+       } else {
+           *ec = VNOVOL;
+       }
+       goto done;
+    }
+
+    DiskToVolumeHeader(&header, &diskHeader);
+
+    IH_INIT(vp->vnodeIndex[vLarge].handle, partp->device, header.parent,
+           header.largeVnodeIndex);
+    IH_INIT(vp->vnodeIndex[vSmall].handle, partp->device, header.parent,
+           header.smallVnodeIndex);
+    IH_INIT(vp->diskDataHandle, partp->device, header.parent,
+           header.volumeInfo);
+    IH_INIT(vp->linkHandle, partp->device, header.parent, header.linkTable);
+
+    if (first_try) {
+       /* only need to do this once */
+       VOL_LOCK;
+       GetVolumeHeader(vp);
+       VOL_UNLOCK;
+    }
 
 #if defined(AFS_DEMAND_ATTACH_FS) && defined(FSSYNC_BUILD_CLIENT)
     /* demand attach changes the V_PEEK mechanism
@@ -2283,12 +2854,12 @@ attach2(Error * ec, VolId volumeId, char *path, register struct VolumeHeader * h
      *  to demand attach fileservers.  However, I'm trying
      *  to limit the number of common code changes)
      */
-    if (programType != fileServer && mode == V_PEEK) {
+    if (VCanUseFSSYNC() && (mode == V_PEEK || peek)) {
        SYNC_response res;
        res.payload.len = sizeof(VolumeDiskData);
        res.payload.buf = &vp->header->diskstuff;
 
-       if (FSYNC_VolOp(volumeId,
+       if (FSYNC_VolOp(vp->hashid,
                        partp->name,
                        FSYNC_VOL_QUERY_HDR,
                        FSYNC_WHATEVER,
@@ -2309,52 +2880,240 @@ attach2(Error * ec, VolId volumeId, char *path, register struct VolumeHeader * h
 #endif /* AFS_DEMAND_ATTACH_FS */
     
     if (*ec) {
-       Log("VAttachVolume: Error reading diskDataHandle vol header %s; error=%u\n", path, *ec);
+       Log("VAttachVolume: Error reading diskDataHandle header for vol %lu; "
+           "error=%u\n", afs_printable_uint32_lu(volid), *ec);
+       goto done;
     }
 
 #ifdef AFS_DEMAND_ATTACH_FS
+# ifdef FSSYNC_BUILD_CLIENT
  disk_header_loaded:
-    if (!*ec) {
+# endif /* FSSYNC_BUILD_CLIENT */
 
-       /* check for pending volume operations */
-       if (vp->pending_vol_op) {
-           /* see if the pending volume op requires exclusive access */
-           switch (vp->pending_vol_op->vol_op_state) {
-           case FSSYNC_VolOpPending:
-               /* this should never happen */
-               assert(vp->pending_vol_op->vol_op_state != FSSYNC_VolOpPending);
-               break;
+    /* if the lock type we actually used to lock the volume is different than
+     * the lock type we should have used, retry with the lock type we should
+     * use */
+    use_locktype = VVolLockType(mode, VolumeWriteable(vp));
+    if (locktype != use_locktype) {
+       retry = 1;
+       lock_tries++;
+    }
+#endif /* AFS_DEMAND_ATTACH_FS */
+
+    *ec = 0;
+
+ done:
+#if defined(AFS_DEMAND_ATTACH_FS) && defined(FSSYNC_BUILD_CLIENT)
+    if (!peek && *ec == 0 && retry == 0 && VMustCheckoutVolume(mode)) {
+
+       code = FSYNC_VerifyCheckout(volid, VPartitionPath(partp), FSYNC_VOL_NEEDVOLUME, mode);
+
+       if (code == SYNC_DENIED) {
+           /* must retry checkout; fileserver no longer thinks we have
+            * the volume */
+           retry = 1;
+           checkout_tries++;
+           done_checkout = 0;
+
+       } else if (code != SYNC_OK) {
+           *ec = VNOVOL;
+       }
+    }
+#endif /* AFS_DEMAND_ATTACH_FS && FSSYNC_BUILD_CLIENT */
+
+    if (*ec || retry) {
+       /* either we are going to be called again for a second pass, or we
+        * encountered an error; clean up in either case */
+
+#ifdef AFS_DEMAND_ATTACH_FS
+       if ((V_attachFlags(vp) & VOL_LOCKED)) {
+           VUnlockVolume(vp);
+       }
+#endif /* AFS_DEMAND_ATTACH_FS */
+       if (vp->linkHandle) {
+           IH_RELEASE(vp->vnodeIndex[vLarge].handle);
+           IH_RELEASE(vp->vnodeIndex[vSmall].handle);
+           IH_RELEASE(vp->diskDataHandle);
+           IH_RELEASE(vp->linkHandle);
+       }
+    }
+
+    if (*ec) {
+       return;
+    }
+    if (retry) {
+       first_try = 0;
+       goto retry;
+    }
+}
+
+#ifdef AFS_DEMAND_ATTACH_FS
+static void
+attach_check_vop(Error *ec, VolumeId volid, struct DiskPartition64 *partp,
+                 Volume *vp)
+{
+    *ec = 0;
+
+    if (vp->pending_vol_op) {
+
+       VOL_LOCK;
+
+       if (vp->pending_vol_op->vol_op_state == FSSYNC_VolOpRunningUnknown) {
+           int code;
+           code = VVolOpLeaveOnlineNoHeader_r(vp, vp->pending_vol_op);
+           if (code == 1) {
+               vp->pending_vol_op->vol_op_state = FSSYNC_VolOpRunningOnline;
+           } else if (code == 0) {
+               vp->pending_vol_op->vol_op_state = FSSYNC_VolOpRunningOffline;
+
+           } else {
+               /* we need the vol header to determine if the volume can be
+                * left online for the vop, so... get the header */
+
+               VOL_UNLOCK;
+
+               /* 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);
+               if (*ec) {
+                   return;
+               }
+
+               VOL_LOCK;
 
-           case FSSYNC_VolOpRunningUnknown:
                if (VVolOpLeaveOnline_r(vp, vp->pending_vol_op)) {
                    vp->pending_vol_op->vol_op_state = FSSYNC_VolOpRunningOnline;
-                   break;
                } else {
                    vp->pending_vol_op->vol_op_state = FSSYNC_VolOpRunningOffline;
-                   /* fall through to take volume offline */
                }
 
-           case FSSYNC_VolOpRunningOffline:
-               /* mark the volume down */
-               *ec = VOFFLINE;
-               VChangeState_r(vp, VOL_STATE_UNATTACHED);
-               if (V_offlineMessage(vp)[0] == '\0')
-                   strlcpy(V_offlineMessage(vp),
-                           "A volume utility is running.", 
-                           sizeof(V_offlineMessage(vp)));
-               V_offlineMessage(vp)[sizeof(V_offlineMessage(vp)) - 1] = '\0';
-
-               /* check to see if we should set the specialStatus flag */
-               if (VVolOpSetVBusy_r(vp, vp->pending_vol_op)) {
-                   vp->specialStatus = VBUSY;
-               }
+               /* make sure we grab a new vol header and re-open stuff on
+                * actual attachment; we can't keep the data we grabbed, since
+                * it was not done under a lock and thus not safe */
+               FreeVolumeHeader(vp);
+               VReleaseVolumeHandles_r(vp);
+           }
+       }
+       /* see if the pending volume op requires exclusive access */
+       switch (vp->pending_vol_op->vol_op_state) {
+       case FSSYNC_VolOpPending:
+           /* this should never happen */
+           assert(vp->pending_vol_op->vol_op_state != FSSYNC_VolOpPending);
+           break;
+
+       case FSSYNC_VolOpRunningUnknown:
+           /* this should never happen; we resolved 'unknown' above */
+           assert(vp->pending_vol_op->vol_op_state != FSSYNC_VolOpRunningUnknown);
+           break;
+
+       case FSSYNC_VolOpRunningOffline:
+           /* mark the volume down */
+           *ec = VOFFLINE;
+           VChangeState_r(vp, VOL_STATE_UNATTACHED);
+
+           /* do not set V_offlineMessage here; we don't have ownership of
+            * the volume (and probably do not have the header loaded), so we
+            * can't alter the disk header */
+
+           /* check to see if we should set the specialStatus flag */
+           if (VVolOpSetVBusy_r(vp, vp->pending_vol_op)) {
+               vp->specialStatus = VBUSY;
            }
+           break;
+
+       default:
+           break;
        }
 
+       VOL_UNLOCK;
+    }
+}
+#endif /* AFS_DEMAND_ATTACH_FS */
+
+/**
+ * volume attachment helper function.
+ *
+ * @param[out] ec      error code
+ * @param[in] volumeId volume ID of the attaching volume
+ * @param[in] path     full path to the volume header .vol file
+ * @param[in] partp    disk partition object for the attaching partition
+ * @param[in] vp       volume object; vp->hashid, vp->device, vp->partition,
+ *                     vp->vnode_list, and V_attachCV (for DAFS) should already
+ *                     be initialized
+ * @param[in] isbusy   1 if vp->specialStatus should be set to VBUSY; that is,
+ *                     if there is a volume operation running for this volume
+ *                     that should set the volume to VBUSY during its run. 0
+ *                     otherwise. (see VVolOpSetVBusy_r)
+ * @param[in] mode     attachment mode such as V_VOLUPD, V_DUMP, etc (see
+ *                     volume.h)
+ *
+ * @return pointer to the semi-attached volume pointer
+ *  @retval NULL an error occurred (check value of *ec)
+ *  @retval vp volume successfully attaching
+ *
+ * @pre no locks held
+ *
+ * @post VOL_LOCK held
+ */
+static Volume *
+attach2(Error * ec, VolId volumeId, char *path, struct DiskPartition64 *partp,
+        Volume * vp, int isbusy, int mode)
+{
+    /* have we read in the header successfully? */
+    int read_header = 0;
+
+    /* should we FreeVolume(vp) instead of VCheckFree(vp) in the error
+     * cleanup? */
+    int forcefree = 0;
+
+    *ec = 0;
+
+    vp->vnodeIndex[vLarge].handle = NULL;
+    vp->vnodeIndex[vSmall].handle = NULL;
+    vp->diskDataHandle = NULL;
+    vp->linkHandle = NULL;
+
+#ifdef AFS_DEMAND_ATTACH_FS
+    attach_check_vop(ec, volumeId, partp, vp);
+    if (!*ec) {
+       attach_volume_header(ec, vp, partp, mode, 0);
+    }
+#else
+    attach_volume_header(ec, vp, partp, mode, 0);
+#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;
+    }
+
+    if (!*ec) {
+       read_header = 1;
+
+       vp->specialStatus = (byte) (isbusy ? VBUSY : 0);
+       vp->shuttingDown = 0;
+       vp->goingOffline = 0;
+       vp->nUsers = 1;
+#ifdef AFS_DEMAND_ATTACH_FS
+       vp->stats.last_attach = FT_ApproxTime();
+       vp->stats.attaches++;
+#endif
+
+       VOL_LOCK;
+       IncUInt64(&VStats.attaches);
+       vp->cacheCheck = ++VolumeCacheCheck;
+       /* just in case this ever rolls over */
+       if (!vp->cacheCheck)
+           vp->cacheCheck = ++VolumeCacheCheck;
+       VOL_UNLOCK;
+
+#ifdef AFS_DEMAND_ATTACH_FS
        V_attachFlags(vp) |= VOL_HDR_LOADED;
        vp->stats.last_hdr_load = vp->stats.last_attach;
-    }
 #endif /* AFS_DEMAND_ATTACH_FS */
+    }
 
     if (!*ec) {
        struct IndexFileHeader iHead;
@@ -2366,7 +3125,7 @@ attach2(Error * ec, VolId volumeId, char *path, register struct VolumeHeader * h
         * area and mark it as initialized.
         */
        if (!(V_stat_initialized(vp))) {
-           memset((char *)(V_stat_area(vp)), 0, VOL_STATS_BYTES);
+           memset((V_stat_area(vp)), 0, VOL_STATS_BYTES);
            V_stat_initialized(vp) = 1;
        }
 #endif /* OPENAFS_VOL_STATS */
@@ -2406,28 +3165,25 @@ attach2(Error * ec, VolId volumeId, char *path, register struct VolumeHeader * h
 #endif /* AFS_NAMEI_ENV */
 
 #if defined(AFS_DEMAND_ATTACH_FS)
-    if (*ec && ((*ec != VOFFLINE) || (V_attachState(vp) != VOL_STATE_UNATTACHED))) {
-        VOL_LOCK;
-       if (programType == fileServer) {
-           VRequestSalvage_r(ec, vp, SALVSYNC_ERROR, VOL_SALVAGE_INVALIDATE_HEADER);
-           vp->nUsers = 0;
-       } else {
+    if (*ec && ((*ec != VOFFLINE) || (V_attachState(vp) != VOL_STATE_UNATTACHED))) {
+        VOL_LOCK;
+       if (!VCanScheduleSalvage()) {
            Log("VAttachVolume: Error attaching volume %s; volume needs salvage; error=%u\n", path, *ec);
-           FreeVolume(vp);
-           *ec = VSALVAGE;
        }
-       return NULL;
+       VRequestSalvage_r(ec, vp, SALVSYNC_ERROR, VOL_SALVAGE_INVALIDATE_HEADER);
+       vp->nUsers = 0;
+
+       goto error;
     } else if (*ec) {
        /* volume operation in progress */
        VOL_LOCK;
-       return NULL;
+       goto error;
     }
 #else /* AFS_DEMAND_ATTACH_FS */
     if (*ec) {
        Log("VAttachVolume: Error attaching volume %s; volume needs salvage; error=%u\n", path, *ec);
         VOL_LOCK;
-       FreeVolume(vp);
-       return NULL;
+       goto error;
     }
 #endif /* AFS_DEMAND_ATTACH_FS */
 
@@ -2436,23 +3192,22 @@ attach2(Error * ec, VolId volumeId, char *path, register struct VolumeHeader * h
            vp->specialStatus = 0;
         VOL_LOCK;
 #if defined(AFS_DEMAND_ATTACH_FS)
-       if (programType == fileServer) {
-           VRequestSalvage_r(ec, vp, SALVSYNC_NEEDED, VOL_SALVAGE_INVALIDATE_HEADER);
-           vp->nUsers = 0;
-       } else {
+       if (!VCanScheduleSalvage()) {
            Log("VAttachVolume: volume salvage flag is ON for %s; volume needs salvage\n", path);
-           FreeVolume(vp);
-           *ec = VSALVAGE;
        }
+       VRequestSalvage_r(ec, vp, SALVSYNC_NEEDED, VOL_SALVAGE_INVALIDATE_HEADER);
+       vp->nUsers = 0;
+
 #else /* AFS_DEMAND_ATTACH_FS */
-       FreeVolume(vp);
        *ec = VSALVAGE;
 #endif /* AFS_DEMAND_ATTACH_FS */
-       return NULL;
+
+       goto error;
     }
 
     VOL_LOCK;
-    if (programType == fileServer) {
+    vp->nextVnodeUnique = V_uniquifier(vp);
+    if (VShouldCheckInUse(mode)) {
 #ifndef FAST_RESTART
        if (V_inUse(vp) && VolumeWriteable(vp)) {
            if (!V_needsSalvaged(vp)) {
@@ -2460,32 +3215,43 @@ attach2(Error * ec, VolId volumeId, char *path, register struct VolumeHeader * h
                VUpdateVolume_r(ec, vp, 0);
            }
 #if defined(AFS_DEMAND_ATTACH_FS)
+           if (!VCanScheduleSalvage()) {
+               Log("VAttachVolume: volume %s needs to be salvaged; not attached.\n", path);
+           }
            VRequestSalvage_r(ec, vp, SALVSYNC_NEEDED, VOL_SALVAGE_INVALIDATE_HEADER);
            vp->nUsers = 0;
+
 #else /* AFS_DEMAND_ATTACH_FS */
            Log("VAttachVolume: volume %s needs to be salvaged; not attached.\n", path);
-           FreeVolume(vp);
            *ec = VSALVAGE;
 #endif /* AFS_DEMAND_ATTACH_FS */
-           return NULL;
+
+           goto error;
        }
 #endif /* FAST_RESTART */
 
-       if (V_destroyMe(vp) == DESTROY_ME) {
+       if (programType == fileServer && V_destroyMe(vp) == DESTROY_ME) {
+           /* Only check destroyMe if we are the fileserver, since the
+            * volserver et al sometimes need to work with volumes with
+            * destroyMe set. Examples are 'temporary' volumes the
+            * volserver creates, and when we create a volume (destroyMe
+            * is set on creation; sometimes a separate volserver
+            * transaction is created to clear destroyMe).
+            */
+
 #if defined(AFS_DEMAND_ATTACH_FS)
            /* schedule a salvage so the volume goes away on disk */
            VRequestSalvage_r(ec, vp, SALVSYNC_ERROR, VOL_SALVAGE_INVALIDATE_HEADER);
            VChangeState_r(vp, VOL_STATE_ERROR);
            vp->nUsers = 0;
 #endif /* AFS_DEMAND_ATTACH_FS */
-           FreeVolume(vp);
            Log("VAttachVolume: volume %s is junk; it should be destroyed at next salvage\n", path);
            *ec = VNOVOL;
-           return NULL;
+           forcefree = 1;
+           goto error;
        }
     }
 
-    vp->nextVnodeUnique = V_uniquifier(vp);
     vp->vnodeIndex[vSmall].bitmap = vp->vnodeIndex[vLarge].bitmap = NULL;
 #ifndef BITMAP_LATER
     if (programType == fileServer && VolumeWriteable(vp)) {
@@ -2496,12 +3262,10 @@ attach2(Error * ec, VolId volumeId, char *path, register struct VolumeHeader * h
 #ifdef AFS_DEMAND_ATTACH_FS
                VRequestSalvage_r(ec, vp, SALVSYNC_ERROR, VOL_SALVAGE_INVALIDATE_HEADER);
                vp->nUsers = 0;
-#else /* AFS_DEMAND_ATTACH_FS */
-               FreeVolume(vp);
 #endif /* AFS_DEMAND_ATTACH_FS */
                Log("VAttachVolume: error getting bitmap for volume (%s)\n",
                    path);
-               return NULL;
+               goto error;
            }
        }
     }
@@ -2515,13 +3279,18 @@ attach2(Error * ec, VolId volumeId, char *path, register struct VolumeHeader * h
            V_offlineMessage(vp)[0] = '\0';
        }
     } else {
+#ifdef AFS_DEMAND_ATTACH_FS
        if ((mode != V_PEEK) && (mode != V_SECRETLY))
            V_inUse(vp) = programType;
+#endif /* AFS_DEMAND_ATTACH_FS */
        V_checkoutMode(vp) = mode;
     }
 
     AddVolumeToHashTable(vp, V_id(vp));
 #ifdef AFS_DEMAND_ATTACH_FS
+    if (VCanUnlockAttached() && (V_attachFlags(vp) & VOL_LOCKED)) {
+       VUnlockVolume(vp);
+    }
     if ((programType != fileServer) ||
        (V_inUse(vp) == fileServer)) {
        AddVolumeToVByPList_r(vp);
@@ -2531,7 +3300,31 @@ attach2(Error * ec, VolId volumeId, char *path, register struct VolumeHeader * h
        VChangeState_r(vp, VOL_STATE_UNATTACHED);
     }
 #endif
+
     return vp;
+
+ error:
+#ifdef AFS_DEMAND_ATTACH_FS
+    if (!VIsErrorState(V_attachState(vp))) {
+       VChangeState_r(vp, VOL_STATE_ERROR);
+    }
+#endif /* AFS_DEMAND_ATTACH_FS */
+
+    if (read_header) {
+       VReleaseVolumeHandles_r(vp);
+    }
+
+#ifdef AFS_DEMAND_ATTACH_FS
+    VCheckSalvage(vp);
+    if (forcefree) {
+       FreeVolume(vp);
+    } else {
+       VCheckFree(vp);
+    }
+#else /* !AFS_DEMAND_ATTACH_FS */
+    FreeVolume(vp);
+#endif /* !AFS_DEMAND_ATTACH_FS */
+    return NULL;
 }
 
 /* Attach an existing volume.
@@ -2693,12 +3486,21 @@ VGetVolumeByVp_r(Error * ec, Volume * vp)
     return GetVolume(ec, NULL, vp->hashid, vp, 0);
 }
 
-/* private interface for getting a volume handle
- * volumeId must be provided.
- * hint is an optional parameter to speed up hash lookups
- * flags is not used at this time
+/**
+ * private interface for getting a volume handle
+ *
+ * @param[out] ec         error code (0 if no error)
+ * @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]  flags      unused; always 0
+ *
+ * @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'
  */
-/* for demand attach fs, caller MUST NOT hold a ref count on hint */
 static Volume *
 GetVolume(Error * ec, Error * client_ec, VolId volumeId, Volume * hint, int flags)
 {
@@ -2877,35 +3679,15 @@ GetVolume(Error * ec, Error * client_ec, VolId volumeId, Volume * hint, int flag
        }
 #endif
 
-       LoadVolumeHeader(ec, vp);
-       if (*ec) {
-           VGET_CTR_INC(V6);
-           /* Only log the error if it was a totally unexpected error.  Simply
-            * a missing inode is likely to be caused by the volume being deleted */
-           if (errno != ENXIO || LogLevel)
-               Log("Volume %u: couldn't reread volume header\n",
-                   vp->hashid);
-#ifdef AFS_DEMAND_ATTACH_FS
-           if (programType == fileServer) {
-               VRequestSalvage_r(ec, vp, SALVSYNC_ERROR, VOL_SALVAGE_INVALIDATE_HEADER);
-           } else {
-               FreeVolume(vp);
-               vp = NULL;
-           }
-#else /* AFS_DEMAND_ATTACH_FS */
-           FreeVolume(vp);
-           vp = NULL;
-#endif /* AFS_DEMAND_ATTACH_FS */
-           break;
-       }
-
 #ifdef AFS_DEMAND_ATTACH_FS
        /*
-        * this test MUST happen after the volume header is loaded
+        * this test MUST happen after VAttachVolymeByVp, so vol_op_state is
+        * not VolOpRunningUnknown (attach2 would have converted it to Online
+        * or Offline)
         */
         
          /* only valid before/during demand attachment */
-         assert(!vp->pending_vol_op || vp->pending_vol_op != FSSYNC_VolOpRunningUnknown);
+         assert(!vp->pending_vol_op || vp->pending_vol_op->vol_op_state != FSSYNC_VolOpRunningUnknown);
         
          /* deny getvolume due to running mutually exclusive vol op */
          if (vp->pending_vol_op && vp->pending_vol_op->vol_op_state==FSSYNC_VolOpRunningOffline) {
@@ -2940,6 +3722,28 @@ GetVolume(Error * ec, Error * client_ec, VolId volumeId, Volume * hint, int flag
           break;
        }
 #endif /* AFS_DEMAND_ATTACH_FS */
+
+       LoadVolumeHeader(ec, vp);
+       if (*ec) {
+           VGET_CTR_INC(V6);
+           /* Only log the error if it was a totally unexpected error.  Simply
+            * a missing inode is likely to be caused by the volume being deleted */
+           if (errno != ENXIO || LogLevel)
+               Log("Volume %u: couldn't reread volume header\n",
+                   vp->hashid);
+#ifdef AFS_DEMAND_ATTACH_FS
+           if (VCanScheduleSalvage()) {
+               VRequestSalvage_r(ec, vp, SALVSYNC_ERROR, VOL_SALVAGE_INVALIDATE_HEADER);
+           } else {
+               FreeVolume(vp);
+               vp = NULL;
+           }
+#else /* AFS_DEMAND_ATTACH_FS */
+           FreeVolume(vp);
+           vp = NULL;
+#endif /* AFS_DEMAND_ATTACH_FS */
+           break;
+       }
        
        VGET_CTR_INC(V7);
        if (vp->shuttingDown) {
@@ -3141,10 +3945,12 @@ VForceOffline(Volume * vp)
 void
 VOffline_r(Volume * vp, char *message)
 {
+#ifndef AFS_DEMAND_ATTACH_FS
     Error error;
     VolumeId vid = V_id(vp);
+#endif
 
-    assert(programType != volumeUtility);
+    assert(programType != volumeUtility && programType != volumeServer);
     if (!V_inUse(vp)) {
        VPutVolume_r(vp);
        return;
@@ -3257,7 +4063,7 @@ VDetachVolume_r(Error * ec, Volume * vp)
     int  useDone = FSYNC_VOL_ON;
 
     *ec = 0;                   /* always "succeeds" */
-    if (programType == volumeUtility) {
+    if (VCanUseFSSYNC()) {
        notifyServer = vp->needsPutBack;
        if (V_destroyMe(vp) == DESTROY_ME)
            useDone = FSYNC_VOL_DONE;
@@ -3285,7 +4091,7 @@ VDetachVolume_r(Error * ec, Volume * vp)
      * is not technically detached until the refcounts reach zero
      */
 #ifdef FSSYNC_BUILD_CLIENT
-    if (programType == volumeUtility && notifyServer) {
+    if (VCanUseFSSYNC() && notifyServer) {
        /* 
         * 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 
@@ -3342,7 +4148,7 @@ VCloseVolumeHandles_r(Volume * vp)
      * DFlushVolume outside of vol_glock_mutex... 
      *
      * VCloseVnodeFiles_r drops the glock internally */
-    DFlushVolume(V_id(vp));
+    DFlushVolume(vp->hashid);
     VCloseVnodeFiles_r(vp);
 
 #ifdef AFS_DEMAND_ATTACH_FS
@@ -3350,7 +4156,7 @@ VCloseVolumeHandles_r(Volume * vp)
 #endif
 
     /* Too time consuming and unnecessary for the volserver */
-    if (programType != volumeUtility) {
+    if (programType == fileServer) {
        IH_CONDSYNC(vp->vnodeIndex[vLarge].handle);
        IH_CONDSYNC(vp->vnodeIndex[vSmall].handle);
        IH_CONDSYNC(vp->diskDataHandle);
@@ -3365,6 +4171,10 @@ VCloseVolumeHandles_r(Volume * vp)
     IH_REALLYCLOSE(vp->linkHandle);
 
 #ifdef AFS_DEMAND_ATTACH_FS
+    if ((V_attachFlags(vp) & VOL_LOCKED)) {
+       VUnlockVolume(vp);
+    }
+
     VOL_LOCK;
     VChangeState_r(vp, state_save);
 #endif
@@ -3386,7 +4196,7 @@ VReleaseVolumeHandles_r(Volume * vp)
 
     /* XXX need to investigate whether we can perform
      * DFlushVolume outside of vol_glock_mutex... */
-    DFlushVolume(V_id(vp));
+    DFlushVolume(vp->hashid);
 
     VReleaseVnodeFiles_r(vp); /* releases the glock internally */
 
@@ -3395,7 +4205,7 @@ VReleaseVolumeHandles_r(Volume * vp)
 #endif
 
     /* Too time consuming and unnecessary for the volserver */
-    if (programType != volumeUtility) {
+    if (programType == fileServer) {
        IH_CONDSYNC(vp->vnodeIndex[vLarge].handle);
        IH_CONDSYNC(vp->vnodeIndex[vSmall].handle);
        IH_CONDSYNC(vp->diskDataHandle);
@@ -3410,6 +4220,10 @@ VReleaseVolumeHandles_r(Volume * vp)
     IH_RELEASE(vp->linkHandle);
 
 #ifdef AFS_DEMAND_ATTACH_FS
+    if ((V_attachFlags(vp) & VOL_LOCKED)) {
+       VUnlockVolume(vp);
+    }
+
     VOL_LOCK;
     VChangeState_r(vp, state_save);
 #endif
@@ -3669,11 +4483,14 @@ VCheckOffline(register Volume * vp)
        VCloseVolumeHandles_r(vp);
 
        if (LogLevel) {
-           Log("VOffline: Volume %u (%s) is now offline", V_id(vp),
-               V_name(vp));
-           if (V_offlineMessage(vp)[0])
-               Log(" (%s)", V_offlineMessage(vp));
-           Log("\n");
+           if (V_offlineMessage(vp)[0]) {
+               Log("VOffline: Volume %lu (%s) is now offline (%s)\n",
+                   afs_printable_uint32_lu(V_id(vp)), V_name(vp),
+                   V_offlineMessage(vp));
+           } else {
+               Log("VOffline: Volume %lu (%s) is now offline\n",
+                   afs_printable_uint32_lu(V_id(vp)), V_name(vp));
+           }
        }
 
        /* invalidate the volume header cache entry */
@@ -3885,6 +4702,48 @@ VVolOpLeaveOnline_r(Volume * vp, FSSYNC_VolOp_info * vopinfo)
 }
 
 /**
+ * same as VVolOpLeaveOnline_r, but does not require a volume with an attached
+ * header.
+ *
+ * @param[in] vp        volume object
+ * @param[in] vopinfo   volume operation info object
+ *
+ * @return whether it is safe to leave volume online
+ *    @retval 0  it is NOT SAFE to leave the volume online
+ *    @retval 1  it is safe to leave the volume online during the operation
+ *    @retval -1 unsure; volume header is required in order to know whether or
+ *               not is is safe to leave the volume online
+ *
+ * @pre VOL_LOCK is held
+ *
+ * @internal volume package internal use only
+ */
+int
+VVolOpLeaveOnlineNoHeader_r(Volume * vp, FSSYNC_VolOp_info * vopinfo)
+{
+    /* follow the logic in VVolOpLeaveOnline_r; this is the same, except
+     * assume that we don't know VolumeWriteable; return -1 if the answer
+     * depends on VolumeWriteable */
+
+    if (vopinfo->vol_op_state == FSSYNC_VolOpRunningOnline) {
+       return 1;
+    }
+    if (vopinfo->com.command == FSYNC_VOL_NEEDVOLUME &&
+        vopinfo->com.reason == V_READONLY) {
+
+       return 1;
+    }
+    if (vopinfo->com.command == FSYNC_VOL_NEEDVOLUME &&
+        (vopinfo->com.reason == V_CLONE ||
+         vopinfo->com.reason == V_DUMP)) {
+
+       /* must know VolumeWriteable */
+       return -1;
+    }
+    return 0;
+}
+
+/**
  * determine whether VBUSY should be set during this volume operation.
  *
  * @param[in] vp        volume object
@@ -3913,17 +4772,6 @@ VVolOpSetVBusy_r(Volume * vp, FSSYNC_VolOp_info * vopinfo)
 /* online salvager routines                        */
 /***************************************************/
 #if defined(AFS_DEMAND_ATTACH_FS)
-#define SALVAGE_PRIO_UPDATE_INTERVAL 3      /**< number of seconds between prio updates */
-#define SALVAGE_COUNT_MAX 16                /**< number of online salvages we
-                                            *   allow before moving the volume
-                                            *   into a permanent error state
-                                            *
-                                            *   once this threshold is reached,
-                                            *   the operator will have to manually
-                                            *   issue a 'bos salvage' to bring
-                                            *   the volume back online
-                                            */
-
 /**
  * check whether a salvage needs to be performed on this volume.
  *
@@ -3948,14 +4796,14 @@ static int
 VCheckSalvage(register Volume * vp)
 {
     int ret = 0;
-#ifdef SALVSYNC_BUILD_CLIENT
+#if defined(SALVSYNC_BUILD_CLIENT) || defined(FSSYNC_BUILD_CLIENT)
     if (vp->nUsers || vp->nWaiters)
        return ret;
     if (vp->salvage.requested) {
        VScheduleSalvage_r(vp);
        ret = 1;
     }
-#endif /* SALVSYNC_BUILD_CLIENT */
+#endif /* SALVSYNC_BUILD_CLIENT || FSSYNC_BUILD_CLIENT */
     return ret;
 }
 
@@ -3980,12 +4828,12 @@ VCheckSalvage(register Volume * vp)
  *   @retval 0  volume salvage will occur
  *   @retval 1  volume salvage could not be scheduled
  *
- * @note DAFS fileserver only
+ * @note DAFS only
  *
- * @note this call does not synchronously schedule a volume salvage.  rather,
- *       it sets volume state so that when volume refcounts reach zero, a
- *       volume salvage will occur.  by "refcounts", we mean both nUsers and 
- *       nWaiters must be zero.
+ * @note in the fileserver, this call does not synchronously schedule a volume
+ *       salvage. rather, it sets volume state so that when volume refcounts
+ *       reach zero, a volume salvage will occur. by "refcounts", we mean both
+ *       nUsers and nWaiters must be zero.
  *
  * @internal volume package internal use only.
  */
@@ -3994,45 +4842,45 @@ VRequestSalvage_r(Error * ec, Volume * vp, int reason, int flags)
 {
     int code = 0;
     /*
-     * for DAFS volume utilities, transition to error state
-     * (at some point in the future, we should consider
-     *  making volser talk to salsrv)
+     * for DAFS volume utilities that are not supposed to schedule salvages,
+     * just transition to error state instead
      */
-    if (programType != fileServer) {
+    if (!VCanScheduleSalvage()) {
        VChangeState_r(vp, VOL_STATE_ERROR);
        *ec = VSALVAGE;
        return 1;
     }
 
+    if (programType != fileServer && !VCanUseFSSYNC()) {
+        VChangeState_r(vp, VOL_STATE_ERROR);
+        *ec = VSALVAGE;
+        return 1;
+    }
+
     if (!vp->salvage.requested) {
        vp->salvage.requested = 1;
        vp->salvage.reason = reason;
        vp->stats.last_salvage = FT_ApproxTime();
-       if (VIsSalvager(V_inUse(vp))) {
-           Log("VRequestSalvage: volume %u appears to be salvaging, but we\n", vp->hashid);
-           Log("  didn't request a salvage. Forcing it offline waiting for the\n");
-           Log("  salvage to finish; if you are sure no salvage is running,\n");
-           Log("  run a salvage manually.\n");
-
-           /* make sure neither VScheduleSalvage_r nor
-            * VUpdateSalvagePriority_r try to schedule another salvage */
-           vp->salvage.requested = vp->salvage.scheduled = 0;
-
-           /* these stats aren't correct, but doing this makes them
-            * slightly closer to being correct */
-           vp->stats.salvages++;
-           vp->stats.last_salvage_req = FT_ApproxTime();
-           IncUInt64(&VStats.salvages);
 
-           VChangeState_r(vp, VOL_STATE_ERROR);
-           *ec = VSALVAGE;
-           code = 1;
+       /* Note that it is not possible for us to reach this point if a
+        * salvage is already running on this volume (even if the fileserver
+        * was restarted during the salvage). If a salvage were running, the
+        * salvager would have write-locked the volume header file, so when
+        * we tried to lock the volume header, the lock would have failed,
+        * and we would have failed during attachment prior to calling
+        * VRequestSalvage. So we know that we can schedule salvages without
+        * fear of a salvage already running for this volume. */
 
-       } else if (vp->stats.salvages < SALVAGE_COUNT_MAX) {
+       if (vp->stats.salvages < SALVAGE_COUNT_MAX) {
            VChangeState_r(vp, VOL_STATE_SALVAGING);
            *ec = VSALVAGING;
        } else {
            Log("VRequestSalvage: volume %u online salvaged too many times; forced offline.\n", vp->hashid);
+
+           /* make sure neither VScheduleSalvage_r nor
+            * VUpdateSalvagePriority_r try to schedule another salvage */
+           vp->salvage.requested = vp->salvage.scheduled = 0;
+
            VChangeState_r(vp, VOL_STATE_ERROR);
            *ec = VSALVAGE;
            code = 1;
@@ -4076,13 +4924,15 @@ VRequestSalvage_r(Error * ec, Volume * vp, int reason, int flags)
  *
  * @internal volume package internal use only.
  */
-static int
+int
 VUpdateSalvagePriority_r(Volume * vp)
 {
-    int code, ret=0;
-    afs_uint32 now;
+    int ret=0;
 
 #ifdef SALVSYNC_BUILD_CLIENT
+    afs_uint32 now;
+    int code;
+
     vp->salvage.prio++;
     now = FT_ApproxTime();
 
@@ -4107,34 +4957,84 @@ VUpdateSalvagePriority_r(Volume * vp)
 }
 
 
+#if defined(SALVSYNC_BUILD_CLIENT) || defined(FSSYNC_BUILD_CLIENT)
+
+/* A couple of little helper functions. These return true if we tried to
+ * use this mechanism to schedule a salvage, false if we haven't tried.
+ * If we did try a salvage then the results are contained in code.
+ */
+
+static inline int
+try_SALVSYNC(Volume *vp, char *partName, int *code) {
+#ifdef SALVSYNC_BUILD_CLIENT
+    if (VCanUseSALVSYNC()) {
+       /* can't use V_id() since there's no guarantee
+        * we have the disk data header at this point */
+       *code = SALVSYNC_SalvageVolume(vp->hashid,
+                                      partName,
+                                      SALVSYNC_SALVAGE,
+                                      vp->salvage.reason,
+                                      vp->salvage.prio,
+                                      NULL);
+       return 1;
+    }
+#endif
+    return 0;
+}
+
+static_inline int
+try_FSSYNC(Volume *vp, char *partName, int *code) {
+#ifdef FSSYNC_BUILD_CLIENT
+    if (VCanUseFSSYNC()) {
+       /*
+        * If we aren't the fileserver, tell the fileserver the volume
+        * needs to be salvaged. We could directly tell the
+        * salvageserver, but the fileserver keeps track of some stats
+        * related to salvages, and handles some other salvage-related
+        * complications for us.
+         */
+        *code = FSYNC_VolOp(vp->hashid, partName,
+                            FSYNC_VOL_FORCE_ERROR, FSYNC_SALVAGE, NULL);
+       return 1;
+    }
+#endif /* FSSYNC_BUILD_CLIENT */
+    return 0;
+}
+
 /**
- * schedule a salvage with the salvage server.
+ * schedule a salvage with the salvage server or fileserver.
  *
  * @param[in] vp  pointer to volume object
  *
  * @return operation status
  *    @retval 0 salvage scheduled successfully
- *    @retval 1 salvage not scheduled, or SALVSYNC com error
+ *    @retval 1 salvage not scheduled, or SALVSYNC/FSSYNC com error
  *
  * @pre 
  *    @arg VOL_LOCK is held.
  *    @arg nUsers and nWaiters should be zero.
  *
- * @post salvageserver is sent a salvage request
+ * @post salvageserver or fileserver is sent a salvage request
  *
- * @note DAFS fileserver only
+ * @note If we are the fileserver, the request will be sent to the salvage
+ * 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 DAFS only
  *
  * @internal volume package internal use only.
  */
 static int
 VScheduleSalvage_r(Volume * vp)
 {
-    int code, ret=0;
-#ifdef SALVSYNC_BUILD_CLIENT
+    int ret=0;
+    int code;
     VolState state_save;
     VThreadOptions_t * thread_opts;
     char partName[16];
 
+    assert(VCanUseSALVSYNC() || VCanUseFSSYNC());
+
     if (vp->nWaiters || vp->nUsers) {
        return 1;
     }
@@ -4150,7 +5050,7 @@ VScheduleSalvage_r(Volume * vp)
     if (thread_opts == NULL) {
        thread_opts = &VThread_defaults;
     }
-    if (thread_opts->disallow_salvsync) {
+    if (thread_opts->disallow_salvsync || vol_disallow_salvsync) {
        return 1;
     }
 
@@ -4171,22 +5071,21 @@ VScheduleSalvage_r(Volume * vp)
        state_save = VChangeState_r(vp, VOL_STATE_SALVSYNC_REQ);
        VOL_UNLOCK;
 
-       /* can't use V_id() since there's no guarantee
-        * we have the disk data header at this point */
-       code = SALVSYNC_SalvageVolume(vp->hashid,
-                                     partName,
-                                     SALVSYNC_SALVAGE,
-                                     vp->salvage.reason,
-                                     vp->salvage.prio,
-                                     NULL);
+       assert(try_SALVSYNC(vp, partName, &code) ||
+              try_FSSYNC(vp, partName, &code));
+
        VOL_LOCK;
        VChangeState_r(vp, state_save);
 
        if (code == SYNC_OK) {
            vp->salvage.scheduled = 1;
-           vp->stats.salvages++;
            vp->stats.last_salvage_req = FT_ApproxTime();
-           IncUInt64(&VStats.salvages);
+           if (VCanUseSALVSYNC()) {
+               /* don't record these stats for non-fileservers; let the
+                * fileserver take care of these */
+               vp->stats.salvages++;
+               IncUInt64(&VStats.salvages);
+           }
        } else {
            ret = 1;
            switch(code) {
@@ -4194,19 +5093,27 @@ VScheduleSalvage_r(Volume * vp)
            case SYNC_COM_ERROR:
                break;
            case SYNC_DENIED:
-               Log("VScheduleSalvage_r:  SALVSYNC request denied\n");
+               Log("VScheduleSalvage_r: Salvage request for volume %lu "
+                   "denied\n", afs_printable_uint32_lu(vp->hashid));
                break;
            default:
-               Log("VScheduleSalvage_r:  SALVSYNC unknown protocol error\n");
+               Log("VScheduleSalvage_r: Salvage request for volume %lu "
+                   "received unknown protocol error %d\n",
+                   afs_printable_uint32_lu(vp->hashid), code);
                break;
            }
+
+           if (VCanUseFSSYNC()) {
+               VChangeState_r(vp, VOL_STATE_ERROR);
+           }
        }
     }
-#endif /* SALVSYNC_BUILD_CLIENT */
     return ret;
 }
+#endif /* SALVSYNC_BUILD_CLIENT || FSSYNC_BUILD_CLIENT */
 
 #ifdef SALVSYNC_BUILD_CLIENT
+
 /**
  * connect to the salvageserver SYNC service.
  *
@@ -4271,11 +5178,10 @@ VConnectSALV_r(void)
 int
 VDisconnectSALV(void)
 {
-    int retVal;
     VOL_LOCK;
     VDisconnectSALV_r();
     VOL_UNLOCK;
-    return retVal;
+    return 0;
 }
 
 /**
@@ -4362,7 +5268,7 @@ VReconnectSALV_r(void)
 /***************************************************/
 
 /* This must be called by any volume utility which needs to run while the
-   file server is also running.  This is separated from VInitVolumePackage so
+   file server is also running.  This is separated from VInitVolumePackage2 so
    that a utility can fork--and each of the children can independently
    initialize communication with the file server */
 #ifdef FSSYNC_BUILD_CLIENT
@@ -4530,19 +5436,33 @@ VChildProcReconnectFS(void)
 /* volume bitmap routines                          */
 /***************************************************/
 
+/**
+ * allocate a vnode bitmap number for the vnode
+ *
+ * @param[out] ec  error code
+ * @param[in] vp   volume object pointer
+ * @param[in] index vnode index number for the vnode
+ * @param[in] flags flag values described in note
+ *
+ * @note for DAFS, flags parameter controls locking behavior.
+ * If (flags & VOL_ALLOC_BITMAP_WAIT) is set, then this function
+ * will create a reservation and block on any other exclusive
+ * operations.  Otherwise, this function assumes the caller
+ * already has exclusive access to vp, and we just change the
+ * volume state.
+ *
+ * @pre VOL_LOCK held
+ *
+ * @return bit number allocated
+ */
 /*
- * For demand attach fs, flags parameter controls
- * locking behavior.  If (flags & VOL_ALLOC_BITMAP_WAIT)
- * is set, then this function will create a reservation
- * and block on any other exclusive operations.  Otherwise,
- * this function assumes the caller already has exclusive
- * access to vp, and we just change the volume state.
+
  */
-VnodeId
+int
 VAllocBitmapEntry_r(Error * ec, Volume * vp, 
                    struct vnodeIndex *index, int flags)
 {
-    VnodeId ret;
+    int ret = 0;
     register byte *bp, *ep;
 #ifdef AFS_DEMAND_ATTACH_FS
     VolState state_save;
@@ -4553,7 +5473,7 @@ VAllocBitmapEntry_r(Error * ec, Volume * vp,
     /* This test is probably redundant */
     if (!VolumeWriteable(vp)) {
        *ec = (bit32) VREADONLY;
-       return 0;
+       return ret;
     }
 
 #ifdef AFS_DEMAND_ATTACH_FS
@@ -4604,7 +5524,6 @@ VAllocBitmapEntry_r(Error * ec, Volume * vp,
                    vp->shuttingDown = 1;       /* Let who has it free it. */
                    vp->specialStatus = 0;
 #endif /* AFS_DEMAND_ATTACH_FS */
-                   ret = NULL;
                    goto done;
                }
            }
@@ -4629,7 +5548,7 @@ VAllocBitmapEntry_r(Error * ec, Volume * vp,
                bp++;
            o = ffs(~*bp) - 1;  /* ffs is documented in BSTRING(3) */
            *bp |= (1 << o);
-           ret = (VnodeId) ((bp - index->bitmap) * 8 + o);
+           ret = ((bp - index->bitmap) * 8 + o);
 #ifdef AFS_DEMAND_ATTACH_FS
            VOL_LOCK;
 #endif /* AFS_DEMAND_ATTACH_FS */
@@ -4662,10 +5581,10 @@ VAllocBitmapEntry_r(Error * ec, Volume * vp,
     return ret;
 }
 
-VnodeId
+int
 VAllocBitmapEntry(Error * ec, Volume * vp, register struct vnodeIndex * index)
 {
-    VnodeId retVal;
+    int retVal;
     VOL_LOCK;
     retVal = VAllocBitmapEntry_r(ec, vp, index, VOL_ALLOC_BITMAP_WAIT);
     VOL_UNLOCK;
@@ -4924,16 +5843,12 @@ VolumeExternalName(VolumeId volumeId)
  * @see afs_snprintf
  *
  * @note re-entrant equivalent of VolumeExternalName
- *
- * @internal volume package internal use only.
  */
-#ifdef AFS_DEMAND_ATTACH_FS
-static int
+int
 VolumeExternalName_r(VolumeId volumeId, char * name, size_t len)
 {
     return afs_snprintf(name, len, VFORMAT, afs_printable_uint32_lu(volumeId));
 }
-#endif
 
 
 /***************************************************/
@@ -4946,7 +5861,34 @@ VolumeExternalName_r(VolumeId volumeId, char * name, size_t len)
 #define OneDay (24*60*60)      /* 24 hours */
 #endif /* OPENAFS_VOL_STATS */
 
-#define Midnight(date) ((date-TimeZoneCorrection)/OneDay*OneDay+TimeZoneCorrection)
+static time_t
+Midnight(time_t t) {
+    struct tm local, *l;
+    time_t midnight;
+
+#if defined(AFS_PTHREAD_ENV) && !defined(AFS_NT40_ENV)
+    l = localtime_r(&t, &local);
+#else
+    l = localtime(&t);
+#endif
+
+    if (l != NULL) {
+       /* the following is strictly speaking problematic on the
+          switching day to daylight saving time, after the switch,
+          as tm_isdst does not match.  Similarly, on the looong day when
+          switching back the OneDay check will not do what naively expected!
+          The effects are minor, though, and more a matter of interpreting
+          the numbers. */
+#ifndef AFS_PTHREAD_ENV
+       local = *l;
+#endif
+       local.tm_hour = local.tm_min=local.tm_sec = 0;
+       midnight = mktime(&local);
+       if (midnight != (time_t) -1) return(midnight);
+    }
+    return( (t/OneDay)*OneDay );
+
+}
 
 /*------------------------------------------------------------------------
  * [export] VAdjustVolumeStatistics
@@ -4993,7 +5935,7 @@ VAdjustVolumeStatistics_r(register Volume * vp)
         * All we need to do is bzero the entire VOL_STATS_BYTES of
         * the detailed volume statistics area.
         */
-       memset((char *)(V_stat_area(vp)), 0, VOL_STATS_BYTES);
+       memset((V_stat_area(vp)), 0, VOL_STATS_BYTES);
 #endif /* OPENAFS_VOL_STATS */
     }
 
@@ -5289,7 +6231,7 @@ static void VLRU_Wait_r(struct VLRU_q * q);
  * @param[in] option  tunable option to modify
  * @param[in] val     new value for tunable parameter
  *
- * @pre @c VInitVolumePackage has not yet been called.
+ * @pre @c VInitVolumePackage2 has not yet been called.
  *
  * @post tunable parameter is modified
  *
@@ -5424,7 +6366,7 @@ VInitVLRU(void)
  * @internal volume package interal use only.
  */
 static void
-VLRU_Init_Node_r(volatile Volume * vp)
+VLRU_Init_Node_r(Volume * vp)
 {
     if (!VLRU_enabled)
        return;
@@ -5461,7 +6403,7 @@ VLRU_Init_Node_r(volatile Volume * vp)
  * @internal volume package internal use only.
  */
 static void
-VLRU_Add_r(volatile Volume * vp)
+VLRU_Add_r(Volume * vp)
 {
     int idx;
     VolState state_save;
@@ -5516,7 +6458,7 @@ VLRU_Add_r(volatile Volume * vp)
  * @internal volume package internal use only.
  */
 static void
-VLRU_Delete_r(volatile Volume * vp)
+VLRU_Delete_r(Volume * vp)
 {
     int idx;
 
@@ -5563,9 +6505,8 @@ VLRU_Delete_r(volatile Volume * vp)
  * @internal volume package internal use only.
  */
 static void
-VLRU_UpdateAccess_r(volatile Volume * vp)
+VLRU_UpdateAccess_r(Volume * vp)
 {
-    afs_uint32 live_interval;
     Volume * rvp = NULL;
 
     if (!VLRU_enabled)
@@ -5640,7 +6581,7 @@ VLRU_UpdateAccess_r(volatile Volume * vp)
  * @internal volume package internal use only.
  */
 static void
-VLRU_SwitchQueues(volatile Volume * vp, int new_idx, int append)
+VLRU_SwitchQueues(Volume * vp, int new_idx, int append)
 {
     if (queue_IsNotOnQueue(&vp->vlru))
        return;
@@ -5677,8 +6618,6 @@ static void *
 VLRU_ScannerThread(void * args)
 {
     afs_uint32 now, min_delay, delay;
-    afs_uint32 next_scan[VLRU_GENERATIONS];
-    afs_uint32 next_promotion[VLRU_GENERATIONS];
     int i, min_idx, min_op, overdue, state;
 
     /* set t=0 for promotion cycle to be 
@@ -5689,7 +6628,7 @@ VLRU_ScannerThread(void * args)
     }
 
     /* don't start the scanner until VLRU_offline_thresh
-     * plus a small delay for VInitVolumePackage to finish
+     * plus a small delay for VInitVolumePackage2 to finish
      * has gone by */
 
     sleep(VLRU_offline_thresh + 60);
@@ -5821,7 +6760,7 @@ VLRU_Promote_r(int idx)
     int len, chaining, promote;
     afs_uint32 now, thresh;
     struct rx_queue *qp, *nqp;
-    Volume * vp, *start, *end;
+    Volume * vp, *start = NULL, *end = NULL;
 
     /* get exclusive access to two chains, and drop the glock */
     VLRU_Wait_r(&volume_LRU.q[idx]);
@@ -5884,7 +6823,7 @@ VLRU_Demote_r(int idx)
     int len, chaining, demote;
     afs_uint32 now, thresh;
     struct rx_queue *qp, *nqp;
-    Volume * vp, *start, *end;
+    Volume * vp, *start = NULL, *end = NULL;
     Volume ** salv_flag_vec = NULL;
     int salv_vec_offset = 0;
 
@@ -5984,7 +6923,7 @@ VLRU_Scan_r(int idx)
 {
     afs_uint32 now, thresh;
     struct rx_queue *qp, *nqp;
-    volatile Volume * vp;
+    Volume * vp;
     int i, locked = 1;
 
     assert(idx == VLRU_QUEUE_NEW || idx == VLRU_QUEUE_CANDIDATE);
@@ -6053,7 +6992,7 @@ VLRU_Scan_r(int idx)
 /* check whether volume is safe to soft detach
  * caller MUST NOT hold a ref count on vp */
 static int
-VCheckSoftDetach(volatile Volume * vp, afs_uint32 thresh)
+VCheckSoftDetach(Volume * vp, afs_uint32 thresh)
 {
     int ret=0;
 
@@ -6070,7 +7009,7 @@ VCheckSoftDetach(volatile Volume * vp, afs_uint32 thresh)
 /* check whether volume should be made a 
  * soft detach candidate */
 static int
-VCheckSoftDetachCandidate(volatile Volume * vp, afs_uint32 thresh)
+VCheckSoftDetachCandidate(Volume * vp, afs_uint32 thresh)
 {
     int idx, ret = 0;
     if (vp->nUsers || vp->nWaiters)
@@ -6125,7 +7064,7 @@ VLRU_Wait_r(struct VLRU_q * q)
  *
  * caller MUST NOT hold a ref count on vp */
 static int
-VSoftDetachVolume_r(volatile Volume * vp, afs_uint32 thresh)
+VSoftDetachVolume_r(Volume * vp, afs_uint32 thresh)
 {
     afs_uint32 ts_save;
     int ret = 0;
@@ -6161,6 +7100,8 @@ VSoftDetachVolume_r(volatile Volume * vp, afs_uint32 thresh)
        V_attachFlags(vp) &= ~(VOL_ON_VLRU);
        VCancelReservation_r(vp);
        return 0;
+    default:
+       break;
     }
 
     /* hold the volume and take it offline.
@@ -6284,7 +7225,7 @@ GetVolumeHeader(register Volume * vp)
     static int everLogged = 0;
 
 #ifdef AFS_DEMAND_ATTACH_FS
-    VolState vp_save, back_save;
+    VolState vp_save = 0, back_save = 0;
 
     /* XXX debug 9/19/05 we've apparently got
      * a ref counting bug somewhere that's
@@ -6534,7 +7475,7 @@ FreeVolumeHeader(register Volume * vp)
  *    @retval 0 success
  *    @retval -1 failure
  *
- * @pre MUST be called prior to VInitVolumePackage
+ * @pre MUST be called prior to VInitVolumePackage2
  *
  * @post Volume Hash Table will have 2^logsize buckets
  */
@@ -7232,7 +8173,7 @@ vlru_idx_to_string(int idx)
 void
 VPrintExtendedCacheStats_r(int flags)
 {
-    int i, j;
+    int i;
     afs_uint32 vol_sum = 0;
     struct stats {
        double min;
@@ -7496,3 +8437,21 @@ VPrintExtendedCacheStats(int flags)
     VOL_UNLOCK;
 }
 #endif /* AFS_DEMAND_ATTACH_FS */
+
+afs_int32
+VCanScheduleSalvage(void)
+{
+    return vol_opts.canScheduleSalvage;
+}
+
+afs_int32
+VCanUseFSSYNC(void)
+{
+    return vol_opts.canUseFSSYNC;
+}
+
+afs_int32
+VCanUseSALVSYNC(void)
+{
+    return vol_opts.canUseSALVSYNC;
+}