GetInodeSummary: free inode info
[openafs.git] / src / vol / vol-salvage.c
index bf3c325..1488bee 100644 (file)
@@ -183,12 +183,15 @@ Vnodes with 0 inode pointers in RW volumes are now deleted.
 #include "partition.h"
 #include "daemon_com.h"
 #include "fssync.h"
+#include "volume_inline.h"
 #include "salvsync.h"
 #include "viceinode.h"
 #include "salvage.h"
 #include "volinodes.h"         /* header magic number, etc. stuff */
 #include "vol-salvage.h"
 #include "vol_internal.h"
+#include <afs/acl.h>
+#include <afs/prs_fs.h>
 
 #ifdef FSSYNC_BUILD_CLIENT
 #include "vg_cache.h"
@@ -303,10 +306,14 @@ char *tmpdir = NULL;
 
 /* Forward declarations */
 /*@printflike@*/ void Log(const char *format, ...);
-/*@printflike@*/ void Abort(const char *format, ...);
+/*@printflike@*/ void Abort(const char *format, ...) AFS_NORETURN;
 static int IsVnodeOrphaned(VnodeId vnode);
 static int AskVolumeSummary(VolumeId singleVolumeNumber);
 
+#ifdef AFS_DEMAND_ATTACH_FS
+static int LockVolume(VolumeId volumeId);
+#endif /* AFS_DEMAND_ATTACH_FS */
+
 /* Uniquifier stored in the Inode */
 static Unique
 IUnique(Unique u)
@@ -338,40 +345,43 @@ extern pthread_t main_thread;
 childJob_t myjob = { SALVAGER_MAGIC, NOT_CHILD, "" };
 #endif
 
-/* Get the salvage lock if not already held. Hold until process exits. */
-void
-ObtainSalvageLock(void)
+/**
+ * Get the salvage lock if not already held. Hold until process exits.
+ *
+ * @param[in] locktype READ_LOCK or WRITE_LOCK
+ */
+static void
+_ObtainSalvageLock(int locktype)
 {
-    FD_t salvageLock;
+    struct VLockFile salvageLock;
+    int offset = 0;
+    int nonblock = 1;
+    int code;
 
-#ifdef AFS_NT40_ENV
-    salvageLock =
-       (FD_t)CreateFile(AFSDIR_SERVER_SLVGLOCK_FILEPATH, 0, 0, NULL,
-                       OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
-    if (salvageLock == INVALID_FD) {
-       fprintf(stderr,
-               "salvager:  There appears to be another salvager running!  Aborted.\n");
-       Exit(1);
-    }
-#else
-    salvageLock =
-       afs_open(AFSDIR_SERVER_SLVGLOCK_FILEPATH, O_CREAT | O_RDWR, 0666);
-    if (salvageLock < 0) {
+    VLockFileInit(&salvageLock, AFSDIR_SERVER_SLVGLOCK_FILEPATH);
+
+    code = VLockFileLock(&salvageLock, offset, locktype, nonblock);
+    if (code == EBUSY) {
        fprintf(stderr,
-               "salvager:  can't open salvage lock file %s, aborting\n",
-               AFSDIR_SERVER_SLVGLOCK_FILEPATH);
+               "salvager:  There appears to be another salvager running!  "
+               "Aborted.\n");
        Exit(1);
-    }
-#ifdef AFS_DARWIN_ENV
-    if (flock(salvageLock, LOCK_EX) == -1) {
-#else
-    if (lockf(salvageLock, F_LOCK, 0) == -1) {
-#endif
+    } else if (code) {
        fprintf(stderr,
-               "salvager:  There appears to be another salvager running!  Aborted.\n");
+               "salvager:  Error %d trying to acquire salvage lock!  "
+               "Aborted.\n", code);
        Exit(1);
     }
-#endif
+}
+void
+ObtainSalvageLock(void)
+{
+    _ObtainSalvageLock(WRITE_LOCK);
+}
+void
+ObtainSharedSalvageLock(void)
+{
+    _ObtainSalvageLock(READ_LOCK);
 }
 
 
@@ -713,12 +723,31 @@ SalvageFileSys1(struct DiskPartition64 *partP, VolumeId singleVolumeNumber)
 {
     char *name, *tdir;
     char inodeListPath[256];
-    FILE *inodeFile;
+    FILE *inodeFile = NULL;
     static char tmpDevName[100];
     static char wpath[100];
     struct VolumeSummary *vsp, *esp;
     int i, j;
     int code;
+    int tries = 0;
+
+ retry:
+    tries++;
+    if (inodeFile) {
+       fclose(inodeFile);
+       inodeFile = NULL;
+    }
+    if (tries > VOL_MAX_CHECKOUT_RETRIES) {
+       Abort("Raced too many times with fileserver restarts while trying to "
+             "checkout/lock volumes; Aborted\n");
+    }
+#ifdef AFS_DEMAND_ATTACH_FS
+    if (tries > 1) {
+       /* unlock all previous volume locks, since we're about to lock them
+        * again */
+       VLockFileReinit(&partP->volLockFile);
+    }
+#endif /* AFS_DEMAND_ATTACH_FS */
 
     fileSysPartition = partP;
     fileSysDevice = fileSysPartition->device;
@@ -736,19 +765,34 @@ SalvageFileSys1(struct DiskPartition64 *partP, VolumeId singleVolumeNumber)
     filesysfulldev = wpath;
 #endif
 
-    VLockPartition(partP->name);
-    if (singleVolumeNumber || ForceSalvage)
+    if (singleVolumeNumber) {
+#ifndef AFS_DEMAND_ATTACH_FS
+       /* only non-DAFS locks the partition when salvaging a single volume;
+        * DAFS will lock the individual volumes in the VG */
+       VLockPartition(partP->name);
+#endif /* !AFS_DEMAND_ATTACH_FS */
+
        ForceSalvage = 1;
-    else
-       ForceSalvage = UseTheForceLuke(fileSysPath);
 
-    if (singleVolumeNumber) {
        /* salvageserver already setup fssync conn for us */
        if ((programType != salvageServer) && !VConnectFS()) {
            Abort("Couldn't connect to file server\n");
        }
+
        AskOffline(singleVolumeNumber, partP->name);
+#ifdef AFS_DEMAND_ATTACH_FS
+       if (LockVolume(singleVolumeNumber)) {
+           goto retry;
+       }
+#endif /* AFS_DEMAND_ATTACH_FS */
+
     } else {
+       VLockPartition(partP->name);
+       if (ForceSalvage) {
+           ForceSalvage = 1;
+       } else {
+           ForceSalvage = UseTheForceLuke(fileSysPath);
+       }
        if (!Showmode)
            Log("SALVAGING FILE SYSTEM PARTITION %s (device=%s%s)\n",
                partP->name, name, (Testing ? "(READONLY mode)" : ""));
@@ -824,7 +868,9 @@ SalvageFileSys1(struct DiskPartition64 *partP, VolumeId singleVolumeNumber)
      * Fix up inodes on last volume in set (whether it is read-write
      * or read-only).
      */
-    GetVolumeSummary(singleVolumeNumber);
+    if (GetVolumeSummary(singleVolumeNumber)) {
+       goto retry;
+    }
 
     for (i = j = 0, vsp = volumeSummaryp, esp = vsp + nVolumes;
         i < nVolumesInInodeFile; i = j) {
@@ -869,6 +915,11 @@ SalvageFileSys1(struct DiskPartition64 *partP, VolumeId singleVolumeNumber)
        RemoveTheForce(fileSysPath);
 
     if (!Testing && singleVolumeNumber) {
+#ifdef AFS_DEMAND_ATTACH_FS
+       /* unlock vol headers so the fs can attach them when we AskOnline */
+       VLockFileReinit(&fileSysPartition->volLockFile);
+#endif /* AFS_DEMAND_ATTACH_FS */
+
        AskOnline(singleVolumeNumber, fileSysPartition->name);
 
        /* Step through the volumeSummary list and set all volumes on-line.
@@ -1060,7 +1111,7 @@ GetInodeSummary(FILE *inodeFile, VolumeId singleVolumeNumber)
     struct afs_stat status;
     int forceSal, err;
     int code;
-    struct ViceInodeInfo *ip;
+    struct ViceInodeInfo *ip, *ip_save;
     struct InodeSummary summary;
     char summaryFileName[50];
     FILE *summaryFile;
@@ -1162,6 +1213,7 @@ GetInodeSummary(FILE *inodeFile, VolumeId singleVolumeNumber)
            Abort("Unable to rewrite inode table; %s not salvaged\n", dev);
        }
        summary.index = 0;
+       ip_save = ip;
        while (nInodes) {
            CountVolumeInodes(ip, nInodes, &summary);
            if (fwrite(&summary, sizeof(summary), 1, summaryFile) != 1) {
@@ -1173,6 +1225,8 @@ GetInodeSummary(FILE *inodeFile, VolumeId singleVolumeNumber)
            nInodes -= summary.nInodes;
            ip += summary.nInodes;
        }
+       free(ip_save);
+       ip = ip_save = NULL;
        /* Following fflush is not fclose, because if it was debug mode would not work */
        if (fflush(summaryFile) == EOF || fsync(fileno(summaryFile)) == -1) {
            Log("Unable to write summary file (errno = %d); %s not salvaged\n", errno, dev);
@@ -1230,11 +1284,13 @@ CompareVolumes(const void *_p1, const void *_p2)
  *                                salvaging a whole partition
  *
  * @return whether we obtained the volume summary information or not
- *  @retval 0 success; we obtained the volume summary information
- *  @retval nonzero we did not get the volume summary information; either the
- *            fileserver responded with an error, or we are not supposed to
- *            ask the fileserver for the information (e.g. we are salvaging
- *            the entire partition or we are not the salvageserver)
+ *  @retval 0  success; we obtained the volume summary information
+ *  @retval -1 we raced with a fileserver restart; volume locks and checkout
+ *             must be retried
+ *  @retval 1  we did not get the volume summary information; either the
+ *             fileserver responded with an error, or we are not supposed to
+ *             ask the fileserver for the information (e.g. we are salvaging
+ *             the entire partition or we are not the salvageserver)
  *
  * @note for non-DAFS, always returns 1
  */
@@ -1242,7 +1298,7 @@ static int
 AskVolumeSummary(VolumeId singleVolumeNumber)
 {
     afs_int32 code = 1;
-#ifdef FSSYNC_BUILD_CLIENT
+#if defined(FSSYNC_BUILD_CLIENT) && defined(AFS_DEMAND_ATTACH_FS)
     if (programType == salvageServer) {
        if (singleVolumeNumber) {
            FSSYNC_VGQry_response_t q_res;
@@ -1330,9 +1386,15 @@ AskVolumeSummary(VolumeId singleVolumeNumber)
                    continue;
                }
 
+               /* AskOffline for singleVolumeNumber was called much earlier */
                if (q_res.children[i] != singleVolumeNumber) {
                    AskOffline(q_res.children[i], fileSysPartition->name);
+                   if (LockVolume(q_res.children[i])) {
+                       /* need to retry */
+                       return -1;
+                   }
                }
+
                code = VReadVolumeDiskHeader(q_res.children[i], fileSysPartition, &diskHdr);
                if (code) {
                    Log("Cannot read header for %lu; trying to salvage group anyway\n",
@@ -1357,7 +1419,7 @@ AskVolumeSummary(VolumeId singleVolumeNumber)
                "entire partition\n");
        }
     }
-#endif /* FSSYNC_BUILD_CLIENT */
+#endif /* FSSYNC_BUILD_CLIENT && AFS_DEMAND_ATTACH_FS */
     return code;
 }
 
@@ -1394,6 +1456,7 @@ struct SalvageScanParams {
     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) */
+    int retry;  /**< do we need to retry vol lock/checkout? */
 };
 
 /**
@@ -1410,8 +1473,10 @@ struct SalvageScanParams {
  *                 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
+ *  @retval 0  success
+ *  @retval -1 volume locking raced with fileserver restart; checking out
+ *             and locking volumes needs to be retried
+ *  @retval 1  volume header is mis-named and should be deleted
  */
 static int
 RecordHeader(struct DiskPartition64 *dp, const char *name,
@@ -1483,6 +1548,17 @@ RecordHeader(struct DiskPartition64 *dp, const char *name,
                 * earlier */
 
                AskOffline(summary.header.id, fileSysPartition->name);
+
+#ifdef AFS_DEMAND_ATTACH_FS
+               if (!badname) {
+                   /* don't lock the volume if the header is bad, since we're
+                    * about to delete it anyway. */
+                   if (LockVolume(summary.header.id)) {
+                       params->retry = 1;
+                       return -1;
+                   }
+               }
+#endif /* AFS_DEMAND_ATTACH_FS */
            }
        }
        if (badname) {
@@ -1566,17 +1642,35 @@ UnlinkHeader(struct DiskPartition64 *dp, const char *name,
     }
 }
 
-void
+/**
+ * Populates volumeSummaryp with volume summary information, either by asking
+ * the fileserver for VG information, or by scanning the /vicepX partition.
+ *
+ * @param[in] singleVolumeNumber  the volume ID of the single volume group we
+ *                                are salvaging, or 0 if this is a partition
+ *                                salvage
+ *
+ * @return operation status
+ *  @retval 0  success
+ *  @retval -1 we raced with a fileserver restart; checking out and locking
+ *             volumes must be retried
+ */
+int
 GetVolumeSummary(VolumeId singleVolumeNumber)
 {
     afs_int32 nvols = 0;
     struct SalvageScanParams params;
     int code;
 
-    if (AskVolumeSummary(singleVolumeNumber) == 0) {
+    code = AskVolumeSummary(singleVolumeNumber);
+    if (code == 0) {
        /* we successfully got the vol information from the fileserver; no
         * need to scan the partition */
-       return;
+       return 0;
+    }
+    if (code < 0) {
+       /* we need to retry volume checkout */
+       return code;
     }
 
     if (!singleVolumeNumber) {
@@ -1599,11 +1693,16 @@ GetVolumeSummary(VolumeId singleVolumeNumber)
     params.vsp = volumeSummaryp;
     params.nVolumes = 0;
     params.totalVolumes = nvols;
+    params.retry = 0;
 
     /* walk the partition directory of volume headers and record the info
      * about them; unlinking invalid headers */
     code = VWalkVolumeHeaders(fileSysPartition, fileSysPath, RecordHeader,
                               UnlinkHeader, &params);
+    if (params.retry) {
+       /* we apparently need to retry checking-out/locking volumes */
+       return -1;
+    }
     if (code < 0) {
        Abort("Failed to get volume header summary\n");
     }
@@ -1611,6 +1710,8 @@ GetVolumeSummary(VolumeId singleVolumeNumber)
 
     qsort(volumeSummaryp, nVolumes, sizeof(struct VolumeSummary),
          CompareVolumes);
+
+    return 0;
 }
 
 /* Find the link table. This should be associated with the RW volume or, if
@@ -2221,7 +2322,7 @@ SalvageHeader(register struct stuff *sp, struct InodeSummary *isp, int check,
     } header;
     IHandle_t *specH;
     int recreate = 0;
-    afs_int32 code;
+    ssize_t nBytes;
     FdHandle_t *fdP;
 
     if (sp->obsolete)
@@ -2291,8 +2392,8 @@ SalvageHeader(register struct stuff *sp, struct InodeSummary *isp, int check,
            Abort
                ("Internal error: recreating volume header (%s) in check mode\n",
                 sp->description);
-       code = FDH_TRUNC(fdP, 0);
-       if (code == -1)
+       nBytes = FDH_TRUNC(fdP, 0);
+       if (nBytes == -1)
            Abort("Unable to truncate volume header file (%s) (error = %d)\n",
                  sp->description, errno);
 
@@ -2319,11 +2420,11 @@ SalvageHeader(register struct stuff *sp, struct InodeSummary *isp, int check,
                    ("Unable to seek to beginning of volume header file (%s) (errno = %d)\n",
                     sp->description, errno);
            }
-           code =
+           nBytes =
                FDH_WRITE(fdP, (char *)&header.volumeInfo,
                          sizeof(header.volumeInfo));
-           if (code != sizeof(header.volumeInfo)) {
-               if (code < 0)
+           if (nBytes != sizeof(header.volumeInfo)) {
+               if (nBytes < 0)
                    Abort
                        ("Unable to write volume header file (%s) (errno = %d)\n",
                         sp->description, errno);
@@ -2336,9 +2437,9 @@ SalvageHeader(register struct stuff *sp, struct InodeSummary *isp, int check,
                    ("Unable to seek to beginning of volume header file (%s) (errno = %d)\n",
                     sp->description, errno);
            }
-           code = FDH_WRITE(fdP, (char *)&sp->stamp, sizeof(sp->stamp));
-           if (code != sizeof(sp->stamp)) {
-               if (code < 0)
+           nBytes = FDH_WRITE(fdP, (char *)&sp->stamp, sizeof(sp->stamp));
+           if (nBytes != sizeof(sp->stamp)) {
+               if (nBytes < 0)
                    Abort
                        ("Unable to write version stamp in volume header file (%s) (errno = %d)\n",
                         sp->description, errno);
@@ -2406,8 +2507,9 @@ SalvageIndex(Inode ino, VnodeClass class, int RW,
     StreamHandle_t *file;
     struct VnodeClassInfo *vcp;
     afs_sfsize_t size;
+    afs_sfsize_t nVnodes;
     afs_fsize_t vnodeLength;
-    int vnodeIndex, nVnodes;
+    int vnodeIndex;
     afs_ino_str_t stmp1, stmp2;
     IHandle_t *handle;
     FdHandle_t *fdP;
@@ -2961,7 +3063,8 @@ JudgeEntry(void *dirVal, char *name, afs_int32 vnodeNumber,
            Log("FOUND suid/sgid file: %s/%s (%u.%u %05o) author %u (vnode %u dir %u)\n", dir->name ? dir->name : "??", name, vnodeEssence->owner, vnodeEssence->group, vnodeEssence->modeBits, vnodeEssence->author, vnodeNumber, dir->vnodeNumber);
        if (/* ShowMounts && */ (vnodeEssence->type == vSymlink)
            && !(vnodeEssence->modeBits & 0111)) {
-           int code, size;
+           ssize_t nBytes;
+           afs_sfsize_t size;
            char buf[1025];
            IHandle_t *ihP;
            FdHandle_t *fdP;
@@ -2976,7 +3079,7 @@ JudgeEntry(void *dirVal, char *name, afs_int32 vnodeNumber,
            }
            size = FDH_SIZE(fdP);
            if (size < 0) {
-               Log("ERROR %s mount point has invalid size %d, vnode %u\n", dir->vname, size, vnodeNumber);
+               Log("ERROR %s mount point has invalid size %d, vnode %u\n", dir->vname, (int)size, vnodeNumber);
                FDH_REALLYCLOSE(fdP);
                IH_RELEASE(ihP);
                return 0;
@@ -2984,8 +3087,8 @@ JudgeEntry(void *dirVal, char *name, afs_int32 vnodeNumber,
        
            if (size > 1024)
                size = 1024;
-           code = FDH_READ(fdP, buf, size);
-           if (code == size) {
+           nBytes = FDH_READ(fdP, buf, size);
+           if (nBytes == size) {
                buf[size] = '\0';
                if ( (*buf != '#' && *buf != '%') || buf[strlen(buf)-1] != '.' ) {
                    Log("Volume %u (%s) mount point %s/%s to '%s' invalid, %s to symbolic link\n",
@@ -2998,7 +3101,7 @@ JudgeEntry(void *dirVal, char *name, afs_int32 vnodeNumber,
                    dir->name ? dir->name : "??", name, buf);
            } else {
                Log("Volume %s cound not read mount point vnode %u size %d code %d\n",
-                   dir->vname, vnodeNumber, size, code);
+                   dir->vname, vnodeNumber, (int)size, (int)nBytes);
            }
            FDH_REALLYCLOSE(fdP);
            IH_RELEASE(ihP);
@@ -3260,6 +3363,372 @@ SalvageDir(char *name, VolumeId rwVid, struct VnodeInfo *dirVnodeInfo,
     return;
 }
 
+/**
+ * Get a new FID that can be used to create a new file.
+ *
+ * @param[in] volHeader vol header for the volume
+ * @param[in] class     what type of vnode we'll be creating (vLarge or vSmall)
+ * @param[out] afid     the FID that we can use (only Vnode and Unique are set)
+ * @param[inout] maxunique  max uniquifier for all vnodes in the volume;
+ *                          updated to the new max unique if we create a new
+ *                          vnode
+ */
+static void
+GetNewFID(VolumeDiskData *volHeader, VnodeClass class, AFSFid *afid,
+          Unique *maxunique)
+{
+    int i;
+    for (i = 0; i < vnodeInfo[class].nVnodes; i++) {
+       if (vnodeInfo[class].vnodes[i].type == vNull) {
+           break;
+       }
+    }
+    if (i == vnodeInfo[class].nVnodes) {
+       /* no free vnodes; make a new one */
+       vnodeInfo[class].nVnodes++;
+       vnodeInfo[class].vnodes = realloc(vnodeInfo[class].vnodes,
+                                         sizeof(struct VnodeEssence) * (i+1));
+       vnodeInfo[class].vnodes[i].type = vNull;
+    }
+
+    afid->Vnode = bitNumberToVnodeNumber(i, class);
+
+    if (volHeader->uniquifier < (*maxunique + 1)) {
+       /* header uniq is bad; it will get bumped by 2000 later */
+       afid->Unique = *maxunique + 1 + 2000;
+       (*maxunique)++;
+    } else {
+       /* header uniq seems okay; just use that */
+       afid->Unique = *maxunique = volHeader->uniquifier++;
+    }
+}
+
+/**
+ * Create a vnode for a README file explaining not to use a recreated-root vol.
+ *
+ * @param[in] volHeader vol header for the volume
+ * @param[in] alinkH    ihandle for i/o for the volume
+ * @param[in] vid       volume id
+ * @param[inout] maxunique  max uniquifier for all vnodes in the volume;
+ *                          updated to the new max unique if we create a new
+ *                          vnode
+ * @param[out] afid     FID for the new readme vnode
+ * @param[out] ainode   the inode for the new readme file
+ *
+ * @return operation status
+ *  @retval 0 success
+ *  @retval -1 error
+ */
+static int
+CreateReadme(VolumeDiskData *volHeader, IHandle_t *alinkH,
+             VolumeId vid, Unique *maxunique, AFSFid *afid, Inode *ainode)
+{
+    Inode readmeinode;
+    struct VnodeDiskObject *rvnode = NULL;
+    afs_sfsize_t bytes;
+    IHandle_t *readmeH = NULL;
+    struct VnodeEssence *vep;
+    afs_fsize_t length;
+    time_t now = time(NULL);
+
+    /* Try to make the note brief, but informative. Only administrators should
+     * be able to read this file at first, so we can hopefully assume they
+     * know what AFS is, what a volume is, etc. */
+    char readme[] =
+"This volume has been salvaged, but has lost its original root directory.\n"
+"The root directory that exists now has been recreated from orphan files\n"
+"from the rest of the volume. This recreated root directory may interfere\n"
+"with old cached data on clients, and there is no way the salvager can\n"
+"reasonably prevent that. So, it is recommended that you do not continue to\n"
+"use this volume, but only copy the salvaged data to a new volume.\n"
+"Continuing to use this volume as it exists now may cause some clients to\n"
+"behave oddly when accessing this volume.\n"
+"\n\t -- Your friendly neighborhood OpenAFS salvager\n";
+    /* ^ the person reading this probably just lost some data, so they could
+     * use some cheering up. */
+
+    /* -1 for the trailing NUL */
+    length = sizeof(readme) - 1;
+
+    GetNewFID(volHeader, vSmall, afid, maxunique);
+
+    vep = &vnodeInfo[vSmall].vnodes[vnodeIdToBitNumber(afid->Vnode)];
+
+    /* create the inode and write the contents */
+    readmeinode = IH_CREATE(alinkH, fileSysDevice, fileSysPath, 0, vid,
+                            afid->Vnode, afid->Unique, 1);
+    if (!VALID_INO(readmeinode)) {
+       Log("CreateReadme: readme IH_CREATE failed\n");
+       goto error;
+    }
+
+    IH_INIT(readmeH, fileSysDevice, vid, readmeinode);
+    bytes = IH_IWRITE(readmeH, 0, readme, length);
+    IH_RELEASE(readmeH);
+
+    if (bytes != length) {
+       Log("CreateReadme: IWRITE failed (%d/%d)\n", (int)bytes,
+           (int)sizeof(readme));
+       goto error;
+    }
+
+    /* create the vnode and write it out */
+    rvnode = malloc(SIZEOF_SMALLDISKVNODE);
+    if (!rvnode) {
+       Log("CreateRootDir: error alloc'ing memory\n");
+       goto error;
+    }
+
+    rvnode->type = vFile;
+    rvnode->cloned = 0;
+    rvnode->modeBits = 0777;
+    rvnode->linkCount = 1;
+    VNDISK_SET_LEN(rvnode, length);
+    rvnode->uniquifier = afid->Unique;
+    rvnode->dataVersion = 1;
+    VNDISK_SET_INO(rvnode, readmeinode);
+    rvnode->unixModifyTime = rvnode->serverModifyTime = now;
+    rvnode->author = 0;
+    rvnode->owner = 0;
+    rvnode->parent = 1;
+    rvnode->group = 0;
+    rvnode->vnodeMagic = VnodeClassInfo[vSmall].magic;
+
+    bytes = IH_IWRITE(vnodeInfo[vSmall].handle,
+                      vnodeIndexOffset(&VnodeClassInfo[vSmall], afid->Vnode),
+                      (char*)rvnode, SIZEOF_SMALLDISKVNODE);
+
+    if (bytes != SIZEOF_SMALLDISKVNODE) {
+       Log("CreateReadme: IH_IWRITE failed (%d/%d)\n", (int)bytes,
+           (int)SIZEOF_SMALLDISKVNODE);
+       goto error;
+    }
+
+    /* update VnodeEssence for new readme vnode */
+    vnodeInfo[vSmall].nAllocatedVnodes++;
+    vep->count = 0;
+    vep->blockCount = nBlocks(length);
+    vnodeInfo[vSmall].volumeBlockCount += vep->blockCount;
+    vep->parent = rvnode->parent;
+    vep->unique = rvnode->uniquifier;
+    vep->modeBits = rvnode->modeBits;
+    vep->InodeNumber = VNDISK_GET_INO(rvnode);
+    vep->type = rvnode->type;
+    vep->author = rvnode->author;
+    vep->owner = rvnode->owner;
+    vep->group = rvnode->group;
+
+    free(rvnode);
+    rvnode = NULL;
+
+    vep->claimed = 1;
+    vep->changed = 0;
+    vep->salvaged = 1;
+    vep->todelete = 0;
+
+    *ainode = readmeinode;
+
+    return 0;
+
+ error:
+    if (IH_DEC(alinkH, readmeinode, vid)) {
+       Log("CreateReadme (recovery): IH_DEC failed\n");
+    }
+
+    if (rvnode) {
+       free(rvnode);
+       rvnode = NULL;
+    }
+
+    return -1;
+}
+
+/**
+ * create a root dir for a volume that lacks one.
+ *
+ * @param[in] volHeader vol header for the volume
+ * @param[in] alinkH    ihandle for disk access for this volume group
+ * @param[in] vid       volume id we're dealing with
+ * @param[out] rootdir  populated with info about the new root dir
+ * @param[inout] maxunique  max uniquifier for all vnodes in the volume;
+ *                          updated to the new max unique if we create a new
+ *                          vnode
+ *
+ * @return operation status
+ *  @retval 0  success
+ *  @retval -1 error
+ */
+static int
+CreateRootDir(VolumeDiskData *volHeader, IHandle_t *alinkH, VolumeId vid,
+              struct DirSummary *rootdir, Unique *maxunique)
+{
+    FileVersion dv;
+    int decroot = 0, decreadme = 0;
+    AFSFid did, readmeid;
+    afs_fsize_t length;
+    Inode rootinode;
+    struct VnodeDiskObject *rootvnode = NULL;
+    struct acl_accessList *ACL;
+    Inode *ip;
+    afs_sfsize_t bytes;
+    struct VnodeEssence *vep;
+    Inode readmeinode;
+    time_t now = time(NULL);
+
+    if (!vnodeInfo[vLarge].vnodes && !vnodeInfo[vSmall].vnodes) {
+       Log("Not creating new root dir; volume appears to lack any vnodes\n");
+       goto error;
+    }
+
+    if (!vnodeInfo[vLarge].vnodes) {
+       /* We don't have any large vnodes in the volume; allocate room
+        * for one so we can recreate the root dir */
+       vnodeInfo[vLarge].nVnodes = 1;
+       vnodeInfo[vLarge].vnodes = calloc(1, sizeof(struct VnodeEssence));
+       vnodeInfo[vLarge].inodes = calloc(1, sizeof(Inode));
+
+       assert(vnodeInfo[vLarge].vnodes);
+       assert(vnodeInfo[vLarge].inodes);
+    }
+
+    vep = &vnodeInfo[vLarge].vnodes[vnodeIdToBitNumber(1)];
+    ip = &vnodeInfo[vLarge].inodes[vnodeIdToBitNumber(1)];
+    if (vep->type != vNull) {
+       Log("Not creating new root dir; existing vnode 1 is non-null\n");
+       goto error;
+    }
+
+    if (CreateReadme(volHeader, alinkH, vid, maxunique, &readmeid, &readmeinode)) {
+       goto error;
+    }
+    decreadme = 1;
+
+    /* set the DV to a very high number, so it is unlikely that we collide
+     * with a cached DV */
+    dv = 1 << 30;
+
+    rootinode = IH_CREATE(alinkH, fileSysDevice, fileSysPath, 0, vid, 1, 1, dv);
+    if (!VALID_INO(rootinode)) {
+       Log("CreateRootDir: IH_CREATE failed\n");
+       goto error;
+    }
+    decroot = 1;
+
+    SetSalvageDirHandle(&rootdir->dirHandle, vid, fileSysDevice, rootinode);
+    did.Volume = vid;
+    did.Vnode = 1;
+    did.Unique = 1;
+    if (MakeDir(&rootdir->dirHandle, (afs_int32*)&did, (afs_int32*)&did)) {
+       Log("CreateRootDir: MakeDir failed\n");
+       goto error;
+    }
+    if (Create(&rootdir->dirHandle, "README.ROOTDIR", &readmeid)) {
+       Log("CreateRootDir: Create failed\n");
+       goto error;
+    }
+    DFlush();
+    length = Length(&rootdir->dirHandle);
+    DZap((void *)&rootdir->dirHandle);
+
+    /* create the new root dir vnode */
+    rootvnode = malloc(SIZEOF_LARGEDISKVNODE);
+    if (!rootvnode) {
+       Log("CreateRootDir: malloc failed\n");
+       goto error;
+    }
+
+    /* only give 'rl' permissions to 'system:administrators'. We do this to
+     * try to catch the attention of an administrator, that they should not
+     * be writing to this directory or continue to use it. */
+    ACL = VVnodeDiskACL(rootvnode);
+    ACL->size = sizeof(struct acl_accessList);
+    ACL->version = ACL_ACLVERSION;
+    ACL->total = 1;
+    ACL->positive = 1;
+    ACL->negative = 0;
+    ACL->entries[0].id = -204; /* system:administrators */
+    ACL->entries[0].rights = PRSFS_READ | PRSFS_LOOKUP;
+
+    rootvnode->type = vDirectory;
+    rootvnode->cloned = 0;
+    rootvnode->modeBits = 0777;
+    rootvnode->linkCount = 2;
+    VNDISK_SET_LEN(rootvnode, length);
+    rootvnode->uniquifier = 1;
+    rootvnode->dataVersion = dv;
+    VNDISK_SET_INO(rootvnode, rootinode);
+    rootvnode->unixModifyTime = rootvnode->serverModifyTime = now;
+    rootvnode->author = 0;
+    rootvnode->owner = 0;
+    rootvnode->parent = 0;
+    rootvnode->group = 0;
+    rootvnode->vnodeMagic = VnodeClassInfo[vLarge].magic;
+
+    /* write it out to disk */
+    bytes = IH_IWRITE(vnodeInfo[vLarge].handle,
+             vnodeIndexOffset(&VnodeClassInfo[vLarge], 1),
+             (char*)rootvnode, SIZEOF_LARGEDISKVNODE);
+
+    if (bytes != SIZEOF_LARGEDISKVNODE) {
+       /* just cast to int and don't worry about printing real 64-bit ints;
+        * a large disk vnode isn't anywhere near the 32-bit limit */
+       Log("CreateRootDir: IH_IWRITE failed (%d/%d)\n", (int)bytes,
+           (int)SIZEOF_LARGEDISKVNODE);
+       goto error;
+    }
+
+    /* update VnodeEssence for the new root vnode */
+    vnodeInfo[vLarge].nAllocatedVnodes++;
+    vep->count = 0;
+    vep->blockCount = nBlocks(length);
+    vnodeInfo[vLarge].volumeBlockCount += vep->blockCount;
+    vep->parent = rootvnode->parent;
+    vep->unique = rootvnode->uniquifier;
+    vep->modeBits = rootvnode->modeBits;
+    vep->InodeNumber = VNDISK_GET_INO(rootvnode);
+    vep->type = rootvnode->type;
+    vep->author = rootvnode->author;
+    vep->owner = rootvnode->owner;
+    vep->group = rootvnode->group;
+
+    free(rootvnode);
+    rootvnode = NULL;
+
+    vep->claimed = 0;
+    vep->changed = 0;
+    vep->salvaged = 1;
+    vep->todelete = 0;
+
+    /* update DirSummary for the new root vnode */
+    rootdir->vnodeNumber = 1;
+    rootdir->unique = 1;
+    rootdir->haveDot = 1;
+    rootdir->haveDotDot = 1;
+    rootdir->rwVid = vid;
+    rootdir->copied = 0;
+    rootdir->parent = 0;
+    rootdir->name = strdup(".");
+    rootdir->vname = volHeader->name;
+    rootdir->ds_linkH = alinkH;
+
+    *ip = rootinode;
+
+    return 0;
+
+ error:
+    if (decroot && IH_DEC(alinkH, rootinode, vid)) {
+       Log("CreateRootDir (recovery): IH_DEC (root) failed\n");
+    }
+    if (decreadme && IH_DEC(alinkH, readmeinode, vid)) {
+       Log("CreateRootDir (recovery): IH_DEC (readme) failed\n");
+    }
+    if (rootvnode) {
+       free(rootvnode);
+       rootvnode = NULL;
+    }
+    return -1;
+}
+
 int
 SalvageVolume(register struct InodeSummary *rwIsp, IHandle_t * alinkH)
 {
@@ -3283,6 +3752,7 @@ SalvageVolume(register struct InodeSummary *rwIsp, IHandle_t * alinkH)
     VnodeId LFVnode, ThisVnode;
     Unique LFUnique, ThisUnique;
     char npath[128];
+    int newrootdir = 0;
 
     vid = rwIsp->volSummary->header.id;
     IH_INIT(h, fileSysDevice, vid, rwIsp->volSummary->header.volumeInfo);
@@ -3312,6 +3782,19 @@ SalvageVolume(register struct InodeSummary *rwIsp, IHandle_t * alinkH)
        return 0;
     }
 
+    if (!rootdirfound && (orphans == ORPH_ATTACH) && !Testing) {
+
+       Log("Cannot find root directory for volume %lu; attempting to create "
+           "a new one\n", afs_printable_uint32_lu(vid));
+
+       code = CreateRootDir(&volHeader, alinkH, vid, &rootdir, &maxunique);
+       if (code == 0) {
+           rootdirfound = 1;
+           newrootdir = 1;
+           VolumeChanged = 1;
+       }
+    }
+
     /* Parse each vnode looking for orphaned vnodes and
      * connect them to the tree as orphaned (if requested).
      */
@@ -3333,8 +3816,20 @@ SalvageVolume(register struct InodeSummary *rwIsp, IHandle_t * alinkH)
             */
            if (class == vLarge) {      /* directory vnode */
                pv = vnodeIdToBitNumber(vep->parent);
-               if (vnodeInfo[vLarge].vnodes[pv].unique != 0)
-                   vnodeInfo[vLarge].vnodes[pv].count++;
+               if (vnodeInfo[vLarge].vnodes[pv].unique != 0) {
+                   if (vep->parent == 1 && newrootdir) {
+                       /* this vnode's parent was the volume root, and
+                        * we just created the volume root. So, the parent
+                        * dir didn't exist during JudgeEntry, so the link
+                        * count was not inc'd there, so don't dec it here.
+                        */
+
+                        /* noop */
+
+                   } else {
+                       vnodeInfo[vLarge].vnodes[pv].count++;
+                   }
+               }
            }
 
            if (!rootdirfound)
@@ -3532,6 +4027,29 @@ SalvageVolume(register struct InodeSummary *rwIsp, IHandle_t * alinkH)
        volHeader.uniquifier = (maxunique + 1 + 2000);
     }
 
+    if (newrootdir) {
+       Log("*** WARNING: Root directory recreated, but volume is fragile! "
+           "Only use this salvaged volume to copy data to another volume; "
+           "do not continue to use this volume (%lu) as-is.\n",
+           afs_printable_uint32_lu(vid));
+    }
+
+#ifdef FSSYNC_BUILD_CLIENT
+    if (!Testing && VolumeChanged) {
+       afs_int32 fsync_code;
+
+       fsync_code = FSYNC_VolOp(vid, NULL, FSYNC_VOL_BREAKCBKS, FSYNC_SALVAGE, NULL);
+       if (fsync_code) {
+           Log("Error trying to tell the fileserver to break callbacks for "
+               "changed volume %lu; error code %ld\n",
+               afs_printable_uint32_lu(vid),
+               afs_printable_int32_ld(fsync_code));
+       } else {
+           VolumeChanged = 0;
+       }
+    }
+#endif /* FSSYNC_BUILD_CLIENT */
+
     /* Turn off the inUse bit; the volume's been salvaged! */
     volHeader.inUse = 0;       /* clear flag indicating inUse@last crash */
     volHeader.needsSalvaged = 0;       /* clear 'damaged' flag */
@@ -3624,6 +4142,93 @@ MaybeZapVolume(register struct InodeSummary *isp, char *message, int deleteMe,
     }
 }
 
+#ifdef AFS_DEMAND_ATTACH_FS
+/**
+ * Locks a volume on disk for salvaging.
+ *
+ * @param[in] volumeId   volume ID to lock
+ *
+ * @return operation status
+ *  @retval 0  success
+ *  @retval -1 volume lock raced with a fileserver restart; all volumes must
+ *             checked out and locked again
+ *
+ * @note DAFS only
+ */
+static int
+LockVolume(VolumeId volumeId)
+{
+    afs_int32 code;
+    int locktype;
+
+    /* should always be WRITE_LOCK, but keep the lock-type logic all
+     * in one place, in VVolLockType. Params will be ignored, but
+     * try to provide what we're logically doing. */
+    locktype = VVolLockType(V_VOLUPD, 1);
+
+    code = VLockVolumeByIdNB(volumeId, fileSysPartition, locktype);
+    if (code) {
+       if (code == EBUSY) {
+           Abort("Someone else appears to be using volume %lu; Aborted\n",
+                 afs_printable_uint32_lu(volumeId));
+       }
+       Abort("Error %ld trying to lock volume %lu; Aborted\n",
+             afs_printable_int32_ld(code),
+             afs_printable_uint32_lu(volumeId));
+    }
+
+    code = FSYNC_VerifyCheckout(volumeId, fileSysPathName, FSYNC_VOL_OFF, FSYNC_SALVAGE);
+    if (code == SYNC_DENIED) {
+       /* need to retry checking out volumes */
+       return -1;
+    }
+    if (code != SYNC_OK) {
+       Abort("FSYNC_VerifyCheckout failed for volume %lu with code %ld\n",
+             afs_printable_uint32_lu(volumeId), afs_printable_int32_ld(code));
+    }
+
+    /* set inUse = programType in the volume header to ensure that nobody
+     * tries to use this volume again without salvaging, if we somehow crash
+     * or otherwise exit before finishing the salvage.
+     */
+    if (!Testing) {
+       IHandle_t *h;
+       struct VolumeHeader header;
+       struct VolumeDiskHeader diskHeader;
+       struct VolumeDiskData volHeader;
+
+       code = VReadVolumeDiskHeader(volumeId, fileSysPartition, &diskHeader);
+       if (code) {
+           return 0;
+       }
+
+       DiskToVolumeHeader(&header, &diskHeader);
+
+       IH_INIT(h, fileSysDevice, header.parent, header.volumeInfo);
+       if (IH_IREAD(h, 0, (char*)&volHeader, sizeof(volHeader)) != sizeof(volHeader) ||
+           volHeader.stamp.magic != VOLUMEINFOMAGIC) {
+
+           IH_RELEASE(h);
+           return 0;
+       }
+
+       volHeader.inUse = programType;
+
+       /* If we can't re-write the header, bail out and error. We don't
+        * assert when reading the header, since it's possible the
+        * header isn't really there (when there's no data associated
+        * with the volume; we just delete the vol header file in that
+        * case). But if it's there enough that we can read it, but
+        * somehow we cannot write to it to signify we're salvaging it,
+        * we've got a big problem and we cannot continue. */
+       assert(IH_IWRITE(h, 0, (char*)&volHeader, sizeof(volHeader)) == sizeof(volHeader));
+
+       IH_RELEASE(h);
+    }
+
+    return 0;
+}
+#endif /* AFS_DEMAND_ATTACH_FS */
 
 void
 AskOffline(VolumeId volumeId, char * partition)
@@ -3665,49 +4270,6 @@ AskOffline(VolumeId volumeId, char * partition)
        Log("AskOffline:  request for fileserver to take volume offline failed; salvage aborting.\n");
        Abort("Salvage aborted\n");
     }
-
-#ifdef AFS_DEMAND_ATTACH_FS
-    /* set inUse = programType in the volume header. We do this in case
-     * the fileserver restarts/crashes while we are salvaging.
-     * Otherwise, the fileserver could attach the volume again on
-     * startup while we are salvaging, which would be very bad, or
-     * schedule another salvage while we are salvaging, which would be
-     * annoying. */
-    if (!Testing) {
-       IHandle_t *h;
-       struct VolumeHeader header;
-       struct VolumeDiskHeader diskHeader;
-       struct VolumeDiskData volHeader;
-
-       code = VReadVolumeDiskHeader(volumeId, fileSysPartition, &diskHeader);
-       if (code) {
-           return;
-       }
-
-       DiskToVolumeHeader(&header, &diskHeader);
-
-       IH_INIT(h, fileSysDevice, header.parent, header.volumeInfo);
-       if (IH_IREAD(h, 0, (char*)&volHeader, sizeof(volHeader)) != sizeof(volHeader) ||
-           volHeader.stamp.magic != VOLUMEINFOMAGIC) {
-
-           IH_RELEASE(h);
-           return;
-       }
-
-       volHeader.inUse = programType;
-
-       /* If we can't re-write the header, bail out and error. We don't
-        * assert when reading the header, since it's possible the
-        * header isn't really there (when there's no data associated
-        * with the volume; we just delete the vol header file in that
-        * case). But if it's there enough that we can read it, but
-        * somehow we cannot write to it to signify we're salvaging it,
-        * we've got a big problem and we cannot continue. */
-       assert(IH_IWRITE(h, 0, (char*)&volHeader, sizeof(volHeader)) == sizeof(volHeader));
-
-       IH_RELEASE(h);
-    }
-#endif /* AFS_DEMAND_ATTACH_FS */
 }
 
 void
@@ -3749,17 +4311,16 @@ CopyInode(Device device, Inode inode1, Inode inode2, int rwvolume)
     char buf[4096];
     IHandle_t *srcH, *destH;
     FdHandle_t *srcFdP, *destFdP;
-    register int n = 0;
+    ssize_t nBytes = 0;
 
     IH_INIT(srcH, device, rwvolume, inode1);
     srcFdP = IH_OPEN(srcH);
     assert(srcFdP != NULL);
     IH_INIT(destH, device, rwvolume, inode2);
     destFdP = IH_OPEN(destH);
-    assert(n != -1);
-    while ((n = FDH_READ(srcFdP, buf, sizeof(buf))) > 0)
-       assert(FDH_WRITE(destFdP, buf, n) == n);
-    assert(n == 0);
+    while ((nBytes = FDH_READ(srcFdP, buf, sizeof(buf))) > 0)
+       assert(FDH_WRITE(destFdP, buf, nBytes) == nBytes);
+    assert(nBytes == 0);
     FDH_REALLYCLOSE(srcFdP);
     FDH_REALLYCLOSE(destFdP);
     IH_RELEASE(srcH);