Abstract /vicepX header traversal
authorAndrew Deason <adeason@sinenomine.net>
Wed, 27 Jan 2010 21:08:34 +0000 (15:08 -0600)
committerDerrick Brashear <shadow@dementia.org>
Mon, 22 Feb 2010 20:15:15 +0000 (12:15 -0800)
The code for examining all of the headers on a vice partition now exists
in two places: the VGC scanner, and the salvager. Create the
VWalkVolumeHeaders function that contains the common logic so it's in
one place, and take a couple of function callbacks for the differences.

Change-Id: I12c71b3344ffbd0b22ddf5418f9ff0509734f42b
Reviewed-on: http://gerrit.openafs.org/1264
Tested-by: Andrew Deason <adeason@sinenomine.net>
Reviewed-by: Derrick Brashear <shadow@dementia.org>

src/vol/vg_scan.c
src/vol/vol-salvage.c
src/vol/vol-salvage.h
src/vol/volume.h
src/vol/vutil.c

index 99bc6f1..c4080be 100644 (file)
@@ -206,113 +206,52 @@ _VVGC_scan_table_flush(VVGCache_scan_table_t * tbl,
 }
 
 /**
- * read a volume header from disk into a VolumeHeader structure.
+ * record a volume header found by VWalkVolumeHeaders in a VGC scan table.
  *
- * @param[in]  path     absolute path to .vol volume header
- * @param[out] hdr      volume header object
+ * @param[in] dp   the disk partition
+ * @param[in] name full path to the .vol header (unused)
+ * @param[in] hdr  the header data
+ * @param[in] last whether this is the last try or not (unused)
+ * @param[in] rock actually a VVGCache_scan_table_t* to add the volume to
  *
  * @return operation status
- *    @retval 0 success
- *    @retval ENOENT volume header does not exist
- *    @retval EINVAL volume header is invalid
- *
- * @internal
+ *  @retval 0  success
+ *  @retval -1 fatal error adding vol to the scan table
  */
 static int
-_VVGC_read_header(const char *path, struct VolumeHeader *hdr)
+_VVGC_RecordHeader(struct DiskPartition64 *dp, const char *name,
+                   struct VolumeDiskHeader *hdr, int last, void *rock)
 {
-    int fd;
     int code;
-    struct VolumeDiskHeader diskHeader;
-
-    fd = afs_open(path, O_RDONLY);
-    if (fd == -1) {
-        ViceLog(0, ("_VVGC_read_header: could not open %s; error = %d\n",
-            path, errno));
-        return ENOENT;
-    }
-
-    code = read(fd, &diskHeader, sizeof(diskHeader));
-    close(fd);
-    if (code != sizeof(diskHeader)) {
-        ViceLog(0, ("_VVGC_read_header: could not read disk header from %s; error = %d\n",
-            path, errno));
-        return EINVAL;
-    }
+    VVGCache_scan_table_t *tbl;
+    tbl = (VVGCache_scan_table_t *)rock;
 
-    if (diskHeader.stamp.magic != VOLUMEHEADERMAGIC) {
-       ViceLog(0, ("_VVGC_read_header: disk header %s has magic %lu, should "
-                   "be %lu\n", path,
-                   afs_printable_uint32_lu(diskHeader.stamp.magic),
-                   afs_printable_uint32_lu(VOLUMEHEADERMAGIC)));
-        return EINVAL;
+    code = _VVGC_scan_table_add(tbl, dp, hdr->id, hdr->parent);
+    if (code) {
+       ViceLog(0, ("VVGC_scan_partition: error %d adding volume %s to scan table\n",
+                    code, name));
+       return -1;
     }
-
-    DiskToVolumeHeader(hdr, &diskHeader);
     return 0;
 }
 
 /**
- * determines what to do with a volume header during a VGC scan.
+ * unlink a faulty volume header found by VWalkVolumeHeaders.
  *
- * @param[in] dp        the disk partition object
- * @param[in] node_path the absolute path to the header to handle
- * @param[out] hdr      the header read in from disk
- * @param[out] skip     1 if we should skip the header (pretend it doesn't
- *                      exist), 0 otherwise
- *
- * @return operation status
- *  @retval 0  success
- *  @retval -1 internal error beyond just failing to read the header file
+ * @param[in] dp   the disk partition (unused)
+ * @param[in] name the full path to the .vol header
+ * @param[in] hdr  the header data (unused)
+ * @param[in] rock unused
  */
-static int
-_VVGC_handle_header(struct DiskPartition64 *dp, const char *node_path,
-                    struct VolumeHeader *hdr, int *skip)
+static void
+_VVGC_UnlinkHeader(struct DiskPartition64 *dp, const char *name,
+                   struct VolumeDiskHeader *hdr, void *rock)
 {
-    int code;
-
-    *skip = 1;
-
-    code = _VVGC_read_header(node_path, hdr);
-    if (code) {
-       /* retry while holding a partition write lock, to ensure we're not
-        * racing a writer/creator of the header */
-
-       if (code == ENOENT) {
-           /* Ignore ENOENT; it's as if we never got it from readdir in the
-            * first place. Other error codes means the header exists, but
-            * there's something wrong with it. */
-           return 0;
-       }
-
-       code = VPartHeaderLock(dp, WRITE_LOCK);
-       if (code) {
-           ViceLog(0, ("_VVGC_handle_header: error acquiring partition "
-                       "write lock while trying to open %s\n",
-                       node_path));
-           return -1;
-       }
-       code = _VVGC_read_header(node_path, hdr);
-       VPartHeaderUnlock(dp, WRITE_LOCK);
-    }
-
-    if (code) {
-       if (code != ENOENT) {
-           ViceLog(0, ("_VVGC_scan_partition: %s does not appear to be a "
-                       "legitimate volume header file; deleted\n",
-                       node_path));
-
-           if (unlink(node_path)) {
-               ViceLog(0, ("Unable to unlink %s (errno = %d)\n",
-                           node_path, errno));
-           }
-       }
-       return 0;
+    ViceLog(0, ("%s is not a legitimate volume header file; deleted\n", name));
+    if (unlink(name)) {
+       ViceLog(0, ("Unable to unlink %s (errno = %d)\n",
+                   name, errno));
     }
-
-    /* header is fine; do not skip it, and do not error out */
-    *skip = 0;
-    return 0;
 }
 
 /**
@@ -332,13 +271,10 @@ _VVGC_handle_header(struct DiskPartition64 *dp, const char *node_path,
 static int
 _VVGC_scan_partition(struct DiskPartition64 * part)
 {
-    int code, res, skip;
+    int code, res;
     DIR *dirp = NULL;
-    struct VolumeHeader hdr;
-    struct dirent *dp;
     VVGCache_scan_table_t tbl;
-    char *part_path = NULL, *p;
-    char node_path[MAXPATHLEN];
+    char *part_path = NULL;
 
     code = _VVGC_scan_table_init(&tbl);
     if (code) {
@@ -376,36 +312,10 @@ _VVGC_scan_partition(struct DiskPartition64 * part)
     ViceLog(5, ("VVGC_scan_partition: scanning partition %s for VG cache\n",
                  part_path));
 
-    while ((dp = readdir(dirp))) {
-       p = strrchr(dp->d_name, '.');
-       if (p == NULL || strcmp(p, VHDREXT) != 0) {
-           continue;
-       }
-       snprintf(node_path,
-                sizeof(node_path),
-                "%s/%s",
-                VPartitionPath(part),
-                dp->d_name);
-
-       res = _VVGC_handle_header(part, node_path, &hdr, &skip);
-       if (res) {
-           /* internal error; error out */
-           code = -1;
-           goto done;
-       }
-       if (skip) {
-           continue;
-       }
-
-       res = _VVGC_scan_table_add(&tbl,
-                                  part,
-                                  hdr.id,
-                                  hdr.parent);
-       if (res) {
-           ViceLog(0, ("VVGC_scan_partition: error %d adding volume %s to scan table\n",
-               res, node_path));
-           code = res;
-       }
+    code = VWalkVolumeHeaders(part, part_path, _VVGC_RecordHeader,
+                              _VVGC_UnlinkHeader, &tbl);
+    if (code < 0) {
+       goto done;
     }
 
     _VVGC_scan_table_flush(&tbl, part);
index 5c90fd1..bf3c325 100644 (file)
@@ -1361,14 +1361,217 @@ AskVolumeSummary(VolumeId singleVolumeNumber)
     return code;
 }
 
+/**
+ * count how many volume headers are found by VWalkVolumeHeaders.
+ *
+ * @param[in] dp   the disk partition (unused)
+ * @param[in] name full path to the .vol header (unused)
+ * @param[in] hdr  the header data (unused)
+ * @param[in] last whether this is the last try or not (unused)
+ * @param[in] rock actually an afs_int32*; the running count of how many
+ *                 volumes we have found
+ *
+ * @retval 0 always
+ */
+static int
+CountHeader(struct DiskPartition64 *dp, const char *name,
+            struct VolumeDiskHeader *hdr, int last, void *rock)
+{
+    afs_int32 *nvols = (afs_int32 *)rock;
+    (*nvols)++;
+    return 0;
+}
+
+/**
+ * parameters to pass to the VWalkVolumeHeaders callbacks when recording volume
+ * data.
+ */
+struct SalvageScanParams {
+    VolumeId singleVolumeNumber; /**< 0 for a partition-salvage, otherwise the
+                                  * vol id of the VG we're salvaging */
+    struct VolumeSummary *vsp;   /**< ptr to the current volume summary object
+                                  * we're filling in */
+    afs_int32 nVolumes;          /**< # of vols we've encountered */
+    afs_int32 totalVolumes;      /**< max # of vols we should encounter (the
+                                  * # of vols we've alloc'd memory for) */
+};
+
+/**
+ * records volume summary info found from VWalkVolumeHeaders.
+ *
+ * Found volumes are also taken offline if they are in the specific volume
+ * group we are looking for.
+ *
+ * @param[in] dp   the disk partition
+ * @param[in] name full path to the .vol header
+ * @param[in] hdr  the header data
+ * @param[in] last 1 if this is the last try to read the header, 0 otherwise
+ * @param[in] rock actually a struct SalvageScanParams*, containing the
+ *                 information needed to record the volume summary data
+ *
+ * @return operation status
+ *  @retval 0 success
+ *  @retval 1 volume header is mis-named and should be deleted
+ */
+static int
+RecordHeader(struct DiskPartition64 *dp, const char *name,
+             struct VolumeDiskHeader *hdr, int last, void *rock)
+{
+    char nameShouldBe[64];
+    struct SalvageScanParams *params;
+    struct VolumeSummary summary;
+    VolumeId singleVolumeNumber;
+
+    params = (struct SalvageScanParams *)rock;
+
+    singleVolumeNumber = params->singleVolumeNumber;
+
+    DiskToVolumeHeader(&summary.header, hdr);
+
+    if (singleVolumeNumber && summary.header.id == singleVolumeNumber
+        && summary.header.parent != singleVolumeNumber) {
+
+       if (programType == salvageServer) {
+#ifdef SALVSYNC_BUILD_CLIENT
+           Log("fileserver requested salvage of clone %u; scheduling salvage of volume group %u...\n",
+               summary.header.id, summary.header.parent);
+           if (SALVSYNC_LinkVolume(summary.header.parent,
+                                   summary.header.id,
+                                   dp->name,
+                                   NULL) != SYNC_OK) {
+               Log("schedule request failed\n");
+           }
+#endif
+           Exit(SALSRV_EXIT_VOLGROUP_LINK);
+
+       } else {
+           Log("%u is a read-only volume; not salvaged\n",
+               singleVolumeNumber);
+           Exit(1);
+       }
+    }
+
+    if (!singleVolumeNumber || summary.header.id == singleVolumeNumber
+       || summary.header.parent == singleVolumeNumber) {
+
+       /* check if the header file is incorrectly named */
+       int badname = 0;
+       const char *base = strrchr(name, '/');
+       if (base) {
+           base++;
+       } else {
+           base = name;
+       }
+
+       (void)afs_snprintf(nameShouldBe, sizeof nameShouldBe,
+                          VFORMAT, afs_printable_uint32_lu(summary.header.id));
+
+
+       if (strcmp(nameShouldBe, base)) {
+           /* .vol file has wrong name; retry/delete */
+           badname = 1;
+       }
+
+       if (!badname || last) {
+           /* only offline the volume if the header is good, or if this is
+            * the last try looking at it; avoid AskOffline'ing the same vol
+            * multiple times */
+
+           if (singleVolumeNumber 
+               && summary.header.id != singleVolumeNumber) {
+               /* don't offline singleVolumeNumber; we already did that
+                * earlier */
+
+               AskOffline(summary.header.id, fileSysPartition->name);
+           }
+       }
+       if (badname) {
+           if (last && !Showmode) {
+               Log("Volume header file %s is incorrectly named (should be %s "
+                   "not %s); %sdeleted (it will be recreated later, if "
+                   "necessary)\n", name, nameShouldBe, base,
+                   (Testing ? "it would have been " : ""));
+           }
+           return 1;
+       }
+
+       summary.fileName = ToString(base);
+       params->nVolumes++;
+
+       if (params->nVolumes > params->totalVolumes) {
+           /* We found more volumes than we found on the first partition walk;
+            * apparently something created a volume while we were
+            * partition-salvaging, or we found more than 20 vols when salvaging a
+            * particular volume. Abort if we detect this, since other programs
+            * supposed to not touch the partition while it is partition-salvaging,
+            * and we shouldn't find more than 20 vols in a VG.
+            */
+           Abort("Found %ld vol headers, but should have found at most %ld! "
+                 "Make sure the volserver/fileserver are not running at the "
+                 "same time as a partition salvage\n",
+                 afs_printable_int32_ld(params->nVolumes),
+                 afs_printable_int32_ld(params->totalVolumes));
+       }
+
+       memcpy(params->vsp, &summary, sizeof(summary));
+       params->vsp++;
+    }
+
+    return 0;
+}
+
+/**
+ * possibly unlinks bad volume headers found from VWalkVolumeHeaders.
+ *
+ * If the header could not be read in at all, the header is always unlinked.
+ * If instead RecordHeader said the header was bad (that is, the header file
+ * is mis-named), we only unlink if we are doing a partition salvage, as
+ * opposed to salvaging a specific volume group.
+ *
+ * @param[in] dp   the disk partition
+ * @param[in] name full path to the .vol header
+ * @param[in] hdr  header data, or NULL if the header could not be read
+ * @param[in] rock actually a struct SalvageScanParams*, with some information
+ *                 about the scan
+ */
+static void
+UnlinkHeader(struct DiskPartition64 *dp, const char *name,
+             struct VolumeDiskHeader *hdr, void *rock)
+{
+    struct SalvageScanParams *params;
+    int dounlink = 0;
+
+    params = (struct SalvageScanParams *)rock;
+
+    if (!hdr) {
+       /* no header; header is too bogus to read in at all */
+       if (!Showmode) {
+           Log("%s is not a legitimate volume header file; %sdeleted\n", name, (Testing ? "it would have been " : ""));
+       }
+       if (!Testing) {
+           dounlink = 1;
+       }
+
+    } else if (!params->singleVolumeNumber) {
+       /* We were able to read in a header, but RecordHeader said something
+        * was wrong with it. We only unlink those if we are doing a partition
+        * salvage. */
+       if (!Testing) {
+           dounlink = 1;
+       }
+    }
+
+    if (dounlink && unlink(name)) {
+       Log("Error %d while trying to unlink %s\n", errno, name);
+    }
+}
+
 void
 GetVolumeSummary(VolumeId singleVolumeNumber)
 {
-    DIR *dirp = NULL;
     afs_int32 nvols = 0;
-    struct VolumeSummary *vsp, vs;
-    struct VolumeDiskHeader diskHeader;
-    struct dirent *dp;
+    struct SalvageScanParams params;
+    int code;
 
     if (AskVolumeSummary(singleVolumeNumber) == 0) {
        /* we successfully got the vol information from the fileserver; no
@@ -1376,34 +1579,13 @@ GetVolumeSummary(VolumeId singleVolumeNumber)
        return;
     }
 
-    /* Get headers from volume directory */
-    dirp = opendir(fileSysPath);
-    if (dirp  == NULL)
-       Abort("Can't read directory %s; not salvaged\n", fileSysPath);
     if (!singleVolumeNumber) {
-       while ((dp = readdir(dirp))) {
-           char *p = dp->d_name;
-           p = strrchr(dp->d_name, '.');
-           if (p != NULL && strcmp(p, VHDREXT) == 0) {
-               int fd;
-               char name[64];
-               sprintf(name, "%s/%s", fileSysPath, dp->d_name);
-               if ((fd = afs_open(name, O_RDONLY)) != -1
-                   && read(fd, (char *)&diskHeader, sizeof(diskHeader))
-                   == sizeof(diskHeader)
-                   && diskHeader.stamp.magic == VOLUMEHEADERMAGIC) {
-                   DiskToVolumeHeader(&vs.header, &diskHeader);
-                   nvols++;
-               }
-               close(fd);
-           }
+       /* Count how many volumes we have in /vicepX */
+       code = VWalkVolumeHeaders(fileSysPartition, fileSysPath, CountHeader,
+                                 NULL, &nvols);
+       if (code < 0) {
+           Abort("Can't read directory %s; not salvaged\n", fileSysPath);
        }
-#ifdef AFS_NT40_ENV
-       closedir(dirp);
-       dirp = opendir(".");    /* No rewinddir for NT */
-#else
-       rewinddir(dirp);
-#endif
        if (!nvols)
            nvols = 1;
     } else {
@@ -1413,83 +1595,20 @@ GetVolumeSummary(VolumeId singleVolumeNumber)
     volumeSummaryp = malloc(nvols * sizeof(struct VolumeSummary));
     assert(volumeSummaryp != NULL);
 
-    nVolumes = 0;
-    vsp = volumeSummaryp;
-    while ((dp = readdir(dirp))) {
-       char *p = dp->d_name;
-       p = strrchr(dp->d_name, '.');
-       if (p != NULL && strcmp(p, VHDREXT) == 0) {
-           int error = 0;
-           int fd;
-           char name[64];
-           sprintf(name, "%s/%s", fileSysPath, dp->d_name);
-           if ((fd = afs_open(name, O_RDONLY)) == -1
-               || read(fd, &diskHeader, sizeof(diskHeader))
-               != sizeof(diskHeader)
-               || diskHeader.stamp.magic != VOLUMEHEADERMAGIC) {
-               error = 1;
-           }
-           close(fd);
-           if (error) {
-               if (!singleVolumeNumber) {
-                   if (!Showmode)
-                       Log("%s is not a legitimate volume header file; %sdeleted\n", name, (Testing ? "it would have been " : ""));
-                   if (!Testing) {
-                       if (unlink(name)) {
-                           Log("Unable to unlink %s (errno = %d)\n", name, errno);
-                       }
-                   }
-               }
-           } else {
-               char nameShouldBe[64];
-               DiskToVolumeHeader(&vsp->header, &diskHeader);
-               if (singleVolumeNumber && vsp->header.id == singleVolumeNumber
-                   && vsp->header.parent != singleVolumeNumber) {
-                   if (programType == salvageServer) {
-#ifdef SALVSYNC_BUILD_CLIENT
-                       Log("fileserver requested salvage of clone %u; scheduling salvage of volume group %u...\n",
-                           vsp->header.id, vsp->header.parent);
-                       if (SALVSYNC_LinkVolume(vsp->header.parent,
-                                               vsp->header.id,
-                                               fileSysPartition->name,
-                                               NULL) != SYNC_OK) {
-                           Log("schedule request failed\n");
-                       }
-#endif
-                       Exit(SALSRV_EXIT_VOLGROUP_LINK);
-                   } else {
-                       Log("%u is a read-only volume; not salvaged\n",
-                           singleVolumeNumber);
-                       Exit(1);
-                   }
-               }
-               if (!singleVolumeNumber
-                   || (vsp->header.id == singleVolumeNumber
-                       || vsp->header.parent == singleVolumeNumber)) {
-                   (void)afs_snprintf(nameShouldBe, sizeof nameShouldBe,
-                                      VFORMAT, afs_printable_uint32_lu(vsp->header.id));
-                   if (singleVolumeNumber 
-                       && vsp->header.id != singleVolumeNumber)
-                       AskOffline(vsp->header.id, fileSysPartition->name);
-                   if (strcmp(nameShouldBe, dp->d_name)) {
-                       if (!Showmode)
-                           Log("Volume header file %s is incorrectly named; %sdeleted (it will be recreated later, if necessary)\n", name, (Testing ? "it would have been " : ""));
-                       if (!Testing) {
-                           if (unlink(name)) {
-                               Log("Unable to unlink %s (errno = %d)\n", name, errno);
-                           }
-                       }
-                   } else {
-                       vsp->fileName = ToString(dp->d_name);
-                       nVolumes++;
-                       vsp++;
-                   }
-               }
-           }
-           close(fd);
-       }
+    params.singleVolumeNumber = singleVolumeNumber;
+    params.vsp = volumeSummaryp;
+    params.nVolumes = 0;
+    params.totalVolumes = nvols;
+
+    /* walk the partition directory of volume headers and record the info
+     * about them; unlinking invalid headers */
+    code = VWalkVolumeHeaders(fileSysPartition, fileSysPath, RecordHeader,
+                              UnlinkHeader, &params);
+    if (code < 0) {
+       Abort("Failed to get volume header summary\n");
     }
-    closedir(dirp);
+    nVolumes = params.nVolumes;
+
     qsort(volumeSummaryp, nVolumes, sizeof(struct VolumeSummary),
          CompareVolumes);
 }
@@ -3901,7 +4020,7 @@ Abort(const char *format, ...)
 }
 
 char *
-ToString(char *s)
+ToString(const char *s)
 {
     register char *p;
     p = (char *)malloc(strlen(s) + 1);
index b0900d3..002e12b 100644 (file)
@@ -220,7 +220,7 @@ extern int canfork;
 extern void Exit(int code);
 extern int Fork(void);
 extern int Wait(char *prog);
-extern char *ToString(char *s);
+extern char *ToString(const char *s);
 extern void AskOffline(VolumeId volumeId, char * partition);
 extern void AskOnline(VolumeId volumeId, char *partition);
 extern void CheckLogFile(char * log_path);
index 33b4aeb..227ec67 100644 (file)
@@ -877,6 +877,54 @@ extern afs_int32 VCreateVolumeDiskHeader(VolumeDiskHeader_t * hdr,
 extern afs_int32 VDestroyVolumeDiskHeader(struct DiskPartition64 * dp,
                                          VolumeId volid, VolumeId parent);
 
+/**
+ * VWalkVolumeHeaders header callback.
+ *
+ * @param[in] dp   disk partition
+ * @param[in] name full path to the .vol header file
+ * @param[in] hdr  the header data that was read from the .vol header
+ * @param[in] last 1 if this is the last attempt to read the vol header, 0
+ *                 otherwise. DAFS VWalkVolumeHeaders will retry reading the
+ *                 header once, if a non-fatal error occurs when reading the
+ *                 header, or if this function returns a positive error code.
+ *                 So, if there is a problem, this function will be called
+ *                 first with last=0, then with last=1, then the error function
+ *                 callback will be called. For non-DAFS, this is always 1.
+ * @param[in] rock the rock passed to VWalkVolumeHeaders
+ *
+ * @return operation status
+ *  @retval 0 success
+ *  @retval negative a fatal error that should stop the walk immediately
+ *  @retval positive an error with the volume header was encountered; the walk
+ *          should continue, but the error function should be called on this
+ *          header
+ *
+ * @see VWalkVolumeHeaders
+ */
+typedef int (*VWalkVolFunc)(struct DiskPartition64 *dp, const char *name,
+                            struct VolumeDiskHeader *hdr, int last,
+                            void *rock);
+/**
+ * VWalkVolumeHeaders error callback.
+ *
+ * This is called from VWalkVolumeHeaders when an invalid or otherwise
+ * problematic volume header is encountered. It is typically implemented as a
+ * wrapper to unlink the .vol file.
+ *
+ * @param[in] dp   disk partition
+ * @param[in] name full path to the .vol header file
+ * @param[in] hdr  header read in from the .vol file, or NULL if it could not
+ *                 be read
+ * @param[in] rock rock passed to VWalkVolumeHeaders
+ *
+ * @see VWalkVolumeHeaders
+ */
+typedef void (*VWalkErrFunc)(struct DiskPartition64 *dp, const char *name,
+                             struct VolumeDiskHeader *hdr, void *rock);
+extern int VWalkVolumeHeaders(struct DiskPartition64 *dp, const char *partpath,
+                              VWalkVolFunc volfunc, VWalkErrFunc errfunc,
+                              void *rock);
+
 /* Naive formula relating number of file size to number of 1K blocks in file */
 /* Note:  we charge 1 block for 0 length files so the user can't store
    an inifite number of them; for most files, we give him the inode, vnode,
index a28c22a..09360e8 100644 (file)
@@ -29,6 +29,7 @@
 #include <sys/file.h>
 #include <unistd.h>
 #endif
+#include <dirent.h>
 #include <sys/stat.h>
 #ifdef AFS_PTHREAD_ENV
 #include <assert.h>
@@ -675,6 +676,167 @@ VDestroyVolumeDiskHeader(struct DiskPartition64 * dp,
 }
 #endif /* FSSYNC_BUILD_CLIENT */
 
+/**
+ * handle a single vol header as part of VWalkVolumeHeaders.
+ *
+ * @param[in] dp      disk partition
+ * @param[in] volfunc function to call when a vol header is successfully read
+ * @param[in] name    full path name to the .vol header
+ * @param[out] hdr    header data read in from the .vol header
+ * @param[in] locked  1 if the partition headers are locked, 0 otherwise
+ * @param[in] rock    the rock to pass to volfunc
+ *
+ * @return operation status
+ *  @retval 0  success
+ *  @retval -1 fatal error, stop scanning
+ *  @retval 1  failed to read header
+ *  @retval 2  volfunc callback indicated error after header read
+ */
+static int
+_VHandleVolumeHeader(struct DiskPartition64 *dp, VWalkVolFunc volfunc,
+                     const char *name, struct VolumeDiskHeader *hdr,
+                     int locked, void *rock)
+{
+    int error = 0;
+    int fd;
+
+    if ((fd = afs_open(name, O_RDONLY)) == -1
+        || read(fd, hdr, sizeof(*hdr))
+        != sizeof(*hdr)
+        || hdr->stamp.magic != VOLUMEHEADERMAGIC) {
+        error = 1;
+    }
+
+    if (fd >= 0) {
+       close(fd);
+    }
+
+#ifdef AFSFS_DEMAND_ATTACH_FS
+    if (locked) {
+       VPartHeaderUnlock(dp);
+    }
+#endif /* AFS_DEMAND_ATTACH_FS */
+
+    if (!error && volfunc) {
+       /* the volume header seems fine; call the caller-supplied
+        * 'we-found-a-volume-header' function */
+       int last = 1;
+
+#ifdef AFS_DEMAND_ATTACH_FS
+       if (!locked) {
+           last = 0;
+       }
+#endif /* AFS_DEMAND_ATTACH_FS */
+
+       error = (*volfunc) (dp, name, hdr, last, rock);
+       if (error < 0) {
+           return -1;
+       }
+       if (error) {
+           error = 2;
+       }
+    }
+
+#ifdef AFS_DEMAND_ATTACH_FS
+    if (error && !locked) {
+       int code;
+       /* retry reading the volume header under the partition
+        * header lock, just to be safe and ensure we're not
+        * racing something rewriting the vol header */
+       code = VPartHeaderLock(dp, WRITE_LOCK);
+       if (code) {
+           Log("Error acquiring partition write lock when "
+               "looking at header %s\n", name);
+           return -1;
+       }
+
+       return _VHandleVolumeHeader(dp, volfunc, name, hdr, 1, rock);
+    }
+#endif /* AFS_DEMAND_ATTACH_FS */
+
+    return error;
+}
+
+/**
+ * walk through the list of volume headers on a partition.
+ *
+ * This function looks through all of the .vol headers on a partition, reads in
+ * each header, and calls the supplied volfunc function on each one. If the
+ * header cannot be read (or volfunc returns a positive error code), DAFS will
+ * VPartHeaderExLock() and retry. If that fails, or if we are non-DAFS, errfunc
+ * will be called (which typically will unlink the problem volume header).
+ *
+ * If volfunc returns a negative error code, walking the partition will stop
+ * and we will return an error immediately.
+ *
+ * @param[in] dp       partition to walk
+ * @param[in] partpath the path opendir()
+ * @param[in] volfunc  the function to call when a header is encountered, or
+ *                     NULL to just skip over valid headers
+ * @param[in] errfunc  the function to call when a problematic header is
+ *                     encountered, or NULL to just skip over bad headers
+ * @param[in] rock     rock for volfunc and errfunc
+ *
+ * @see VWalkVolFunc
+ * @see VWalkErrFunc
+ *
+ * @return operation status
+ *  @retval 0 success
+ *  @retval negative fatal error, walk did not finish
+ */
+int
+VWalkVolumeHeaders(struct DiskPartition64 *dp, const char *partpath,
+                   VWalkVolFunc volfunc, VWalkErrFunc errfunc, void *rock)
+{
+    DIR *dirp = NULL;
+    struct dirent *dentry = NULL;
+    int code = 0;
+    struct VolumeDiskHeader diskHeader;
+
+    dirp = opendir(partpath);
+    if (!dirp) {
+       Log("VWalkVolumeHeaders: cannot open directory %s\n", partpath);
+       code = -1;
+       goto done;
+    }
+
+    while ((dentry = readdir(dirp))) {
+       char *p = dentry->d_name;
+       p = strrchr(dentry->d_name, '.');
+       if (p != NULL && strcmp(p, VHDREXT) == 0) {
+           char name[VMAXPATHLEN];
+
+           sprintf(name, "%s/%s", partpath, dentry->d_name);
+
+           code = _VHandleVolumeHeader(dp, volfunc, name, &diskHeader, -1, rock);
+           if (code < 0) {
+               /* fatal error, stop walking */
+               goto done;
+           }
+           if (code && errfunc) {
+               /* error with header; call the caller-supplied vol error
+                * function */
+
+               struct VolumeDiskHeader *hdr = &diskHeader;
+               if (code == 1) {
+                   /* we failed to read the header at all, so don't pass in
+                    * the header ptr */
+                   hdr = NULL;
+               }
+               (*errfunc) (dp, name, hdr, rock);
+           }
+           code = 0;
+       }
+    }
+ done:
+    if (dirp) {
+       closedir(dirp);
+       dirp = NULL;
+    }
+
+    return code;
+}
+
 #ifdef AFS_DEMAND_ATTACH_FS
 
 /**