salvage: update inodes array after CopyAndSalvage
[openafs.git] / src / vol / vol-salvage.c
index d74a788..c4ace1d 100644 (file)
@@ -189,7 +189,10 @@ Vnodes with 0 inode pointers in RW volumes are now deleted.
 #include "salvage.h"
 #include "volinodes.h"         /* header magic number, etc. stuff */
 #include "vol-salvage.h"
+#include "common.h"
 #include "vol_internal.h"
+#include <afs/acl.h>
+#include <afs/prs_fs.h>
 
 #ifdef FSSYNC_BUILD_CLIENT
 #include "vg_cache.h"
@@ -303,8 +306,6 @@ char *tmpdir = NULL;
 
 
 /* Forward declarations */
-/*@printflike@*/ void Log(const char *format, ...);
-/*@printflike@*/ void Abort(const char *format, ...);
 static int IsVnodeOrphaned(VnodeId vnode);
 static int AskVolumeSummary(VolumeId singleVolumeNumber);
 
@@ -1109,7 +1110,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;
@@ -1211,6 +1212,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) {
@@ -1222,6 +1224,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);
@@ -1250,7 +1254,7 @@ GetInodeSummary(FILE *inodeFile, VolumeId singleVolumeNumber)
        assert(ret == st_status);
     }
     nVolumesInInodeFile =(unsigned long)(status.st_size) / sizeof(struct InodeSummary);
-    Log("%d nVolumesInInodeFile %d \n",nVolumesInInodeFile,(unsigned long)(status.st_size));
+    Log("%d nVolumesInInodeFile %lu \n",nVolumesInInodeFile,(unsigned long)(status.st_size));
     fclose(summaryFile);
     return 0;
 }
@@ -2317,7 +2321,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)
@@ -2373,6 +2377,9 @@ SalvageHeader(register struct stuff *sp, struct InodeSummary *isp, int check,
        Log("Part of the header (%s) is corrupted; recreating\n",
            sp->description);
        recreate = 1;
+       /* header can be garbage; make sure we don't read garbage data from
+        * it below */
+       memset(&header, 0, sizeof(header));
     }
     if (sp->inodeType == VI_VOLINFO
        && header.volumeInfo.destroyMe == DESTROY_ME) {
@@ -2387,8 +2394,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);
 
@@ -2415,11 +2422,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);
@@ -2432,9 +2439,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);
@@ -2502,8 +2509,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;
@@ -3057,7 +3065,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;
@@ -3072,7 +3081,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;
@@ -3080,8 +3089,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",
@@ -3094,7 +3103,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);
@@ -3320,6 +3329,7 @@ SalvageDir(char *name, VolumeId rwVid, struct VnodeInfo *dirVnodeInfo,
        if (!Testing) {
            CopyAndSalvage(&dir);
            dirok = 1;
+           dirVnodeInfo->inodes[i] = dir.dirHandle.dirh_inode;
        }
     }
     dirHandle = dir.dirHandle;
@@ -3356,6 +3366,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 = calloc(1, 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 = calloc(1, 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)
 {
@@ -3379,6 +3755,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);
@@ -3408,6 +3785,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).
      */
@@ -3429,8 +3819,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)
@@ -3507,7 +3909,7 @@ SalvageVolume(register struct InodeSummary *rwIsp, IHandle_t * alinkH)
 
     /* Delete the old rootinode directory if the rootdir was CopyOnWrite */
     DFlush();
-    if (!oldrootdir.copied && rootdir.copied) {
+    if (rootdirfound && !oldrootdir.copied && rootdir.copied) {
        code =
            IH_DEC(oldrootdir.ds_linkH, oldrootdir.dirHandle.dirh_inode,
                   oldrootdir.rwVid);
@@ -3545,7 +3947,6 @@ SalvageVolume(register struct InodeSummary *rwIsp, IHandle_t * alinkH)
 
            if (vnp->changed || vnp->count) {
                int oldCount;
-               int code;
                nBytes =
                    IH_IREAD(vnodeInfo[class].handle,
                             vnodeIndexOffset(vcp, vnodeNumber),
@@ -3628,6 +4029,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 */
@@ -3889,17 +4313,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);