salvage: Zero root/readme vnodes before writing
[openafs.git] / src / vol / vol-salvage.c
index 0e673b0..e273f40 100644 (file)
@@ -83,17 +83,17 @@ Vnodes with 0 inode pointers in RW volumes are now deleted.
 */
 
 
-#define SalvageVersion "2.4"
-
-/* Main program file. Define globals. */
-#define MAIN 1
-
 #include <afsconfig.h>
 #include <afs/param.h>
 
-RCSID
-    ("$Header$");
 
+#ifndef AFS_NT40_ENV
+#include <sys/param.h>
+#include <sys/file.h>
+#ifndef ITIMER_REAL
+#include <sys/time.h>
+#endif /* ITIMER_REAL */
+#endif
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
@@ -104,15 +104,9 @@ RCSID
 #ifdef AFS_NT40_ENV
 #include <io.h>
 #include <WINNT/afsevent.h>
-#else
-#include <sys/param.h>
-#include <sys/file.h>
-#ifndef ITIMER_REAL
-#include <sys/time.h>
-#endif /* ITIMER_REAL */
 #endif
-#if    defined(AFS_AIX_ENV) || defined(AFS_SUN4_ENV)
-#define WCOREDUMP(x)   (x & 0200)
+#ifndef WCOREDUMP
+#define WCOREDUMP(x)   ((x) & 0200)
 #endif
 #include <rx/xdr.h>
 #include <afs/afsint.h>
@@ -171,6 +165,7 @@ RCSID
 #include <afs/osi_inode.h>
 #endif
 #include <afs/cmd.h>
+#include <afs/dir.h>
 #include <afs/afsutil.h>
 #include <afs/fileutil.h>
 #include <afs/procmgmt.h>      /* signal(), kill(), wait(), etc. */
@@ -186,10 +181,23 @@ RCSID
 #include "vnode.h"
 #include "volume.h"
 #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 "common.h"
+#include "vol_internal.h"
+#include <afs/acl.h>
+#include <afs/prs_fs.h>
+
+#ifdef FSSYNC_BUILD_CLIENT
+#include "vg_cache.h"
+#endif
+
 #ifdef AFS_NT40_ENV
 #include <pthread.h>
 #endif
@@ -221,13 +229,9 @@ extern void *calloc();
 #endif
 static char *TimeStamp(time_t clock, int precision);
 
-#define ORPH_IGNORE 0
-#define ORPH_REMOVE 1
-#define ORPH_ATTACH 2
-
 
 int debug;                     /* -d flag */
-int Testing = 0;               /* -n flag */
+extern int Testing;            /* -n flag */
 int ListInodeOption;           /* -i flag */
 int ShowRootFiles;             /* -r flag */
 int RebuildDirs;               /* -sal flag */
@@ -240,18 +244,25 @@ int ShowMounts = 0;               /* -showmounts flag */
 int orphans = ORPH_IGNORE;     /* -orphans option */
 int Showmode = 0;
 
+
 #ifndef AFS_NT40_ENV
 int useSyslog = 0;             /* -syslog flag */
 int useSyslogFacility = LOG_DAEMON;    /* -syslogfacility option */
 #endif
 
+#ifdef AFS_NT40_ENV
+int canfork = 0;
+#else
+int canfork = 1;
+#endif
+
 #define        MAXPARALLEL     32
 
 int OKToZap;                   /* -o flag */
 int ForceSalvage;              /* If salvage should occur despite the DONT_SALVAGE flag
                                 * in the volume header */
 
-static FILE *logFile = 0;      /* one of {/usr/afs/logs,/vice/file}/SalvageLog */
+FILE *logFile = 0;     /* one of {/usr/afs/logs,/vice/file}/SalvageLog */
 
 #define ROOTINODE      2       /* Root inode of a 4.2 Unix file system
                                 * partition */
@@ -267,7 +278,7 @@ char *fileSysPath;          /* The path of the mounted partition currently
 char *fileSysPathName;         /* NT needs this to make name pretty in log. */
 IHandle_t *VGLinkH;            /* Link handle for current volume group. */
 int VGLinkH_cnt;               /* # of references to lnk handle. */
-struct DiskPartition *fileSysPartition;        /* Partition  being salvaged */
+struct DiskPartition64 *fileSysPartition;      /* Partition  being salvaged */
 #ifndef AFS_NT40_ENV
 char *fileSysDeviceName;       /* The block device where the file system
                                 * being salvaged was mounted */
@@ -279,202 +290,28 @@ int VolumeChanged;               /* Set by any routine which would change the volume in
 
 VolumeDiskData VolInfo;                /* A copy of the last good or salvaged volume header dealt with */
 
-struct InodeSummary {          /* Inode summary file--an entry for each
-                                * volume in the inode file for a partition */
-    VolId volumeId;            /* Volume id */
-    VolId RWvolumeId;          /* RW volume associated */
-    int index;                 /* index into inode file (0, 1, 2 ...) */
-    int nInodes;               /* Number of inodes for this volume */
-    int nSpecialInodes;                /* Number of special inodes, i.e.  volume
-                                * header, index, etc.  These are all
-                                * marked (viceinode.h) and will all be sorted
-                                * to the beginning of the information for
-                                * this volume.  Read-only volumes should
-                                * ONLY have special inodes (all the other
-                                * inodes look as if they belong to the
-                                * original RW volume). */
-    Unique maxUniquifier;      /* The maximum uniquifier found in all the inodes.
-                                * This is only useful for RW volumes and is used
-                                * to compute a new volume uniquifier in the event
-                                * that the header needs to be recreated. The inode
-                                * uniquifier may be a truncated version of vnode
-                                * uniquifier (AFS_3DISPARES). The real maxUniquifer
-                                * is from the vnodes and later calcuated from it */
-    struct VolumeSummary *volSummary;
-    /* Either a pointer to the original volume
-     * header summary, or constructed summary
-     * information */
-} *inodeSummary;
-#define readOnly(isp)  ((isp)->volumeId != (isp)->RWvolumeId)
 int nVolumesInInodeFile;       /* Number of read-write volumes summarized */
 int inodeFd;                   /* File descriptor for inode file */
 
 
-struct VolumeSummary {         /* Volume summary an entry for each
-                                * volume in a volume directory.
-                                * Assumption: one volume directory per
-                                * partition */
-    char *fileName;            /* File name on the partition for the volume
-                                * header */
-    struct VolumeHeader header;
-    /* volume number, rw volume number, inode
-     * numbers of each major component of
-     * the volume */
-    IHandle_t *volumeInfoHandle;
-    byte wouldNeedCallback;    /* set if the file server should issue
-                                * call backs for all the files in this volume when
-                                * the volume goes back on line */
-};
-
-struct VnodeInfo {
-    IHandle_t *handle;         /* Inode containing this index */
-    int nVnodes;               /* Total number of vnodes in index */
-    int nAllocatedVnodes;      /* Total number actually used */
-    int volumeBlockCount;      /* Total number of blocks used by volume */
-    Inode *inodes;             /* Directory only */
-    struct VnodeEssence {
-       short count;            /* Number of references to vnode; MUST BE SIGNED */
-       unsigned claimed:1;     /* Set when a parent directory containing an entry
-                                * referencing this vnode is found.  The claim
-                                * is that the parent in "parent" can point to
-                                * this vnode, and no other */
-       unsigned changed:1;     /* Set if any parameters (other than the count)
-                                * in the vnode change.   It is determined if the
-                                * link count has changed by noting whether it is
-                                * 0 after scanning all directories */
-       unsigned salvaged:1;    /* Set if this directory vnode has already been salvaged. */
-       unsigned todelete:1;    /* Set if this vnode is to be deleted (should not be claimed) */
-       afs_fsize_t blockCount;
-       /* Number of blocks (1K) used by this vnode,
-        * approximately */
-       VnodeId parent;         /* parent in vnode */
-       Unique unique;          /* Must match entry! */
-       char *name;             /* Name of directory entry */
-       int modeBits;           /* File mode bits */
-       Inode InodeNumber;      /* file's inode */
-       int type;               /* File type */
-       int author;             /* File author */
-       int owner;              /* File owner */
-       int group;              /* File group */
-    } *vnodes;
-} vnodeInfo[nVNODECLASSES];
-
-struct DirSummary {
-    struct DirHandle dirHandle;
-    VnodeId vnodeNumber;
-    Unique unique;
-    unsigned haveDot, haveDotDot;
-    VolumeId rwVid;
-    int copied;                        /* If the copy-on-write stuff has been applied */
-    VnodeId parent;
-    char *name;
-    char *vname;
-    IHandle_t *ds_linkH;
-};
+struct VnodeInfo vnodeInfo[nVNODECLASSES];
 
 
-struct VolumeSummary *volumeSummaryp;  /* Holds all the volumes in a part */
+struct VolumeSummary *volumeSummaryp = NULL;   /* Holds all the volumes in a part */
 int nVolumes;                  /* Number of volumes (read-write and read-only)
                                 * in volume summary */
 
-#ifdef AFS_NT40_ENV
-/* For NT, we can fork the per partition salvagers to gain the required
- * safety against Aborts. But there's too many complex data structures at
- * the per volume salvager layer to easilty copy the data across.
- * childJobNumber is resset from -1 to the job number if this is a
- * per partition child of the main salvager. This information is passed
- * out-of-band in the extra data area setup for the now unused parent/child
- * data transfer.
- */
-#define SALVAGER_MAGIC 0x00BBaaDD
-#define NOT_CHILD -1           /* job numbers start at 0 */
-/* If new options need to be passed to child, add them here. */
-typedef struct {
-    int cj_magic;
-    int cj_number;
-    char cj_part[32];
-} childJob_t;
-
-/* Child job this process is running. */
-childJob_t myjob = { SALVAGER_MAGIC, NOT_CHILD, "" };
-
-int nt_SalvagePartition(char *partName, int jobn);
-int nt_SetupPartitionSalvage(void *datap, int len);
-
-typedef struct {
-    struct InodeSummary *svgp_inodeSummaryp;
-    int svgp_count;
-} SVGParms_t;
-#define canfork 0
-#else
-#define canfork 1
-#endif
+char *tmpdir = NULL;
 
 
 
 /* Forward declarations */
-/*@printflike@*/ void Log(const char *format, ...);
-/*@printflike@*/ void Abort(const char *format, ...);
-void Exit(int code);
-int Fork(void);
-int Wait(char *prog);
-char *ToString(char *s);
-void AskOffline(VolumeId volumeId);
-void AskOnline(VolumeId volumeId, char *partition);
-void CheckLogFile(void);
-#ifndef AFS_NT40_ENV
-void TimeStampLogFile(void);
-#endif
-void ClearROInUseBit(struct VolumeSummary *summary);
-void CopyAndSalvage(register struct DirSummary *dir);
-int CopyInode(Device device, Inode inode1, Inode inode2, int rwvolume);
-void CopyOnWrite(register struct DirSummary *dir);
-void CountVolumeInodes(register struct ViceInodeInfo *ip, int maxInodes,
-                      register struct InodeSummary *summary);
-void DeleteExtraVolumeHeaderFile(register struct VolumeSummary *vsp);
-void DistilVnodeEssence(VolumeId vid, VnodeClass class, Inode ino,
-                       Unique * maxu);
-int GetInodeSummary(char *path, VolumeId singleVolumeNumber);
-void GetVolumeSummary(VolumeId singleVolumeNumber);
-void JudgeEntry(struct DirSummary *dir, char *name, VnodeId vnodeNumber,
-               Unique unique);
-void MaybeZapVolume(register struct InodeSummary *isp, char *message,
-                   int deleteMe, int check);
-void ObtainSalvageLock(void);
-void PrintInodeList(void);
-void PrintInodeSummary(void);
-void PrintVolumeSummary(void);
-int QuickCheck(register struct InodeSummary *isp, int nVols);
-void RemoveTheForce(char *path);
-void SalvageDir(char *name, VolumeId rwVid, struct VnodeInfo *dirVnodeInfo,
-               IHandle_t * alinkH, int i, struct DirSummary *rootdir,
-               int *rootdirfound);
-void SalvageFileSysParallel(struct DiskPartition *partP);
-void SalvageFileSys(struct DiskPartition *partP, VolumeId singleVolumeNumber);
-void SalvageFileSys1(struct DiskPartition *partP,
-                    VolumeId singleVolumeNumber);
-int SalvageHeader(register struct stuff *sp, struct InodeSummary *isp,
-                 int check, int *deleteMe);
-int SalvageIndex(Inode ino, VnodeClass class, int RW,
-                register struct ViceInodeInfo *ip, int nInodes,
-                struct VolumeSummary *volSummary, int check);
-int SalvageVnodes(register struct InodeSummary *rwIsp,
-                 register struct InodeSummary *thisIsp,
-                 register struct ViceInodeInfo *inodes, int check);
-int SalvageVolume(register struct InodeSummary *rwIsp, IHandle_t * alinkH);
-void DoSalvageVolumeGroup(register struct InodeSummary *isp, int nVols);
-#ifdef AFS_NT40_ENV
-void SalvageVolumeGroup(register struct InodeSummary *isp, int nVols);
-#else
-#define SalvageVolumeGroup DoSalvageVolumeGroup
-#endif
-int SalvageVolumeHeaderFile(register struct InodeSummary *isp,
-                           register struct ViceInodeInfo *inodes, int RW,
-                           int check, int *deleteMe);
-void showlog(void);
-int UseTheForceLuke(char *path);
-
 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
@@ -499,387 +336,51 @@ BadError(register int aerror)
     return 0;                  /* otherwise may be transient, e.g. EMFILE */
 }
 
-
-char *tmpdir = 0;
-static int
-handleit(struct cmd_syndesc *as)
-{
-    register struct cmd_item *ti;
-    char pname[100], *temp;
-    afs_int32 seenpart = 0, seenvol = 0, vid = 0, seenany = 0;
-    struct DiskPartition *partP;
-
-#ifdef AFS_SGI_VNODE_GLUE
-    if (afs_init_kernel_config(-1) < 0) {
-       printf
-           ("Can't determine NUMA configuration, not starting salvager.\n");
-       exit(1);
-    }
-#endif
-
-#ifdef FAST_RESTART
-    {
-       afs_int32 i;
-       for (i = 0; i < CMD_MAXPARMS; i++) {
-           if (as->parms[i].items) {
-               seenany = 1;
-               break;
-           }
-       }
-    }
-    if (!seenany) {
-       char *msg =
-           "Exiting immediately without salvage. Look into the FileLog to find volumes which really need to be salvaged!";
-
-       if (useSyslog)
-           Log(msg);
-       else
-           printf("%s\n", msg);
-
-       Exit(0);
-    }
-#endif /* FAST_RESTART */
-    if ((ti = as->parms[0].items)) {   /* -partition */
-       seenpart = 1;
-       strncpy(pname, ti->data, 100);
-    }
-    if ((ti = as->parms[1].items)) {   /* -volumeid */
-       if (!seenpart) {
-           printf
-               ("You must also specify '-partition' option with the '-volumeid' option\n");
-           exit(-1);
-       }
-       seenvol = 1;
-       vid = atoi(ti->data);
-    }
-    if (as->parms[2].items)    /* -debug */
-       debug = 1;
-    if (as->parms[3].items)    /* -nowrite */
-       Testing = 1;
-    if (as->parms[4].items)    /* -inodes */
-       ListInodeOption = 1;
-    if (as->parms[5].items)    /* -force */
-       ForceSalvage = 1;
-    if (as->parms[6].items)    /* -oktozap */
-       OKToZap = 1;
-    if (as->parms[7].items)    /* -rootinodes */
-       ShowRootFiles = 1;
-    if (as->parms[8].items)    /* -RebuildDirs */
-       RebuildDirs = 1;
-    if (as->parms[9].items)    /* -ForceReads */
-       forceR = 1;
-    if ((ti = as->parms[10].items)) {  /* -Parallel # */
-       temp = ti->data;
-       if (strncmp(temp, "all", 3) == 0) {
-           PartsPerDisk = 1;
-           temp += 3;
-       }
-       if (strlen(temp) != 0) {
-           Parallel = atoi(temp);
-           if (Parallel < 1)
-               Parallel = 1;
-           if (Parallel > MAXPARALLEL) {
-               printf("Setting parallel salvages to maximum of %d \n",
-                      MAXPARALLEL);
-               Parallel = MAXPARALLEL;
-           }
-       }
-    }
-    if ((ti = as->parms[11].items)) {  /* -tmpdir */
-       DIR *dirp;
-
-       tmpdir = ti->data;
-       dirp = opendir(tmpdir);
-       if (!dirp) {
-           printf
-               ("Can't open temporary placeholder dir %s; using current partition \n",
-                tmpdir);
-           tmpdir = NULL;
-       } else
-           closedir(dirp);
-    }
-    if ((ti = as->parms[12].items))    /* -showlog */
-       ShowLog = 1;
-    if ((ti = as->parms[13].items)) {  /* -log */
-       Testing = 1;
-       ShowSuid = 1;
-       Showmode = 1;
-    }
-    if ((ti = as->parms[14].items)) {  /* -showmounts */
-       Testing = 1;
-       Showmode = 1;
-       ShowMounts = 1;
-    }
-    if ((ti = as->parms[15].items)) {  /* -orphans */
-       if (Testing)
-           orphans = ORPH_IGNORE;
-       else if (strcmp(ti->data, "remove") == 0
-                || strcmp(ti->data, "r") == 0)
-           orphans = ORPH_REMOVE;
-       else if (strcmp(ti->data, "attach") == 0
-                || strcmp(ti->data, "a") == 0)
-           orphans = ORPH_ATTACH;
-    }
-#ifndef AFS_NT40_ENV           /* ignore options on NT */
-    if ((ti = as->parms[16].items)) {  /* -syslog */
-       useSyslog = 1;
-       ShowLog = 0;
-    }
-    if ((ti = as->parms[17].items)) {  /* -syslogfacility */
-       useSyslogFacility = atoi(ti->data);
-    }
-
-    if ((ti = as->parms[18].items)) {  /* -datelogs */
-       TimeStampLogFile();
-    }
-#endif
-
-#ifdef FAST_RESTART
-    if (ti = as->parms[19].items) {    /* -DontSalvage */
-       char *msg =
-           "Exiting immediately without salvage. Look into the FileLog to find volumes which really need to be salvaged!";
-
-       if (useSyslog)
-           Log(msg);
-       else
-           printf("%s\n", msg);
-       Exit(0);
-    }
-#endif /* FAST_RESTART */
-
-    /* Note:  if seemvol we initialize this as a standard volume utility:  this has the
-     * implication that the file server may be running; negotations have to be made with
-     * the file server in this case to take the read write volume and associated read-only
-     * volumes off line before salvaging */
-#ifdef AFS_NT40_ENV
-    if (seenvol) {
-       if (afs_winsockInit() < 0) {
-           ReportErrorEventAlt(AFSEVT_SVR_WINSOCK_INIT_FAILED, 0,
-                               AFSDIR_SALVAGER_FILE, 0);
-           Log("Failed to initailize winsock, exiting.\n");
-           Exit(1);
-       }
-    }
-#endif
-    VInitVolumePackage(seenvol ? volumeUtility : salvager, 5, 5,
-                      DONT_CONNECT_FS, 0);
-    DInit(10);
-#ifdef AFS_NT40_ENV
-    if (myjob.cj_number != NOT_CHILD) {
-       if (!seenpart) {
-           seenpart = 1;
-           (void)strcpy(pname, myjob.cj_part);
-       }
-    }
-#endif
-    if (seenpart == 0) {
-       for (partP = DiskPartitionList; partP; partP = partP->next) {
-           SalvageFileSysParallel(partP);
-       }
-       SalvageFileSysParallel(0);
-    } else {
-       partP = VGetPartition(pname, 0);
-       if (!partP) {
-           Log("salvage: Unknown or unmounted partition %s; salvage aborted\n", pname);
-           Exit(1);
-       }
-       if (!seenvol)
-           SalvageFileSys(partP, 0);
-       else {
-           /* Salvage individual volume */
-           if (vid <= 0) {
-               Log("salvage: invalid volume id specified; salvage aborted\n");
-               Exit(1);
-           }
-           SalvageFileSys(partP, vid);
-       }
-    }
-    return (0);
-}
-
-
-#ifndef AFS_NT40_ENV
-#include "AFS_component_version_number.c"
-#endif
 #define MAX_ARGS 128
 #ifdef AFS_NT40_ENV
 char *save_args[MAX_ARGS];
 int n_save_args = 0;
-pthread_t main_thread;
+extern pthread_t main_thread;
+childJob_t myjob = { SALVAGER_MAGIC, NOT_CHILD, "" };
 #endif
 
-int
-main(int argc, char **argv)
+/**
+ * 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)
 {
-    struct cmd_syndesc *ts;
-    int err = 0;
-    char commandLine[150];
-
-    int i;
-    extern char cml_version_number[];
-
-#ifdef AFS_AIX32_ENV
-    /*
-     * The following signal action for AIX is necessary so that in case of a 
-     * crash (i.e. core is generated) we can include the user's data section 
-     * in the core dump. Unfortunately, by default, only a partial core is
-     * generated which, in many cases, isn't too useful.
-     */
-    struct sigaction nsa;
-
-    sigemptyset(&nsa.sa_mask);
-    nsa.sa_handler = SIG_DFL;
-    nsa.sa_flags = SA_FULLDUMP;
-    sigaction(SIGABRT, &nsa, NULL);
-    sigaction(SIGSEGV, &nsa, NULL);
-#endif
-
-    /* Initialize directory paths */
-    if (!(initAFSDirPath() & AFSDIR_SERVER_PATHS_OK)) {
-#ifdef AFS_NT40_ENV
-       ReportErrorEventAlt(AFSEVT_SVR_NO_INSTALL_DIR, 0, argv[0], 0);
-#endif
-       fprintf(stderr, "%s: Unable to obtain AFS server directory.\n",
-               argv[0]);
-       exit(2);
-    }
-#ifdef AFS_NT40_ENV
-    main_thread = pthread_self();
-    if (spawnDatap && spawnDataLen) {
-       /* This is a child per partition salvager. Don't setup log or
-        * try to lock the salvager lock.
-        */
-       if (nt_SetupPartitionSalvage(spawnDatap, spawnDataLen) < 0)
-           exit(3);
-    } else {
-#endif
-       for (commandLine[0] = '\0', i = 0; i < argc; i++) {
-           if (i > 0)
-               strcat(commandLine, " ");
-           strcat(commandLine, argv[i]);
-       }
-
-       /* All entries to the log will be appended.  Useful if there are
-        * multiple salvagers appending to the log.
-        */
-
-       CheckLogFile();
-#ifndef AFS_NT40_ENV
-#ifdef AFS_LINUX20_ENV
-       fcntl(fileno(logFile), F_SETFL, O_APPEND);      /* Isn't this redundant? */
-#else
-       fcntl(fileno(logFile), F_SETFL, FAPPEND);       /* Isn't this redundant? */
-#endif
-#endif
-       setlinebuf(logFile);
-
-#ifndef AFS_NT40_ENV
-       if (geteuid() != 0) {
-           printf("Salvager must be run as root.\n");
-           fflush(stdout);
-           Exit(0);
-       }
-#endif
-
-       /* bad for normal help flag processing, but can do nada */
-
-       fprintf(logFile, "%s\n", cml_version_number);
-       Log("STARTING AFS SALVAGER %s (%s)\n", SalvageVersion, commandLine);
-
-       /* Get and hold a lock for the duration of the salvage to make sure
-        * that no other salvage runs at the same time.  The routine
-        * VInitVolumePackage (called below) makes sure that a file server or
-        * other volume utilities don't interfere with the salvage.
-        */
-       ObtainSalvageLock();
-#ifdef AFS_NT40_ENV
-    }
-#endif
+    struct VLockFile salvageLock;
+    int offset = 0;
+    int nonblock = 1;
+    int code;
 
-    ts = cmd_CreateSyntax("initcmd", handleit, 0, "initialize the program");
-    cmd_AddParm(ts, "-partition", CMD_SINGLE, CMD_OPTIONAL,
-               "Name of partition to salvage");
-    cmd_AddParm(ts, "-volumeid", CMD_SINGLE, CMD_OPTIONAL,
-               "Volume Id to salvage");
-    cmd_AddParm(ts, "-debug", CMD_FLAG, CMD_OPTIONAL,
-               "Run in Debugging mode");
-    cmd_AddParm(ts, "-nowrite", CMD_FLAG, CMD_OPTIONAL,
-               "Run readonly/test mode");
-    cmd_AddParm(ts, "-inodes", CMD_FLAG, CMD_OPTIONAL,
-               "Just list affected afs inodes - debugging flag");
-    cmd_AddParm(ts, "-force", CMD_FLAG, CMD_OPTIONAL, "Force full salvaging");
-    cmd_AddParm(ts, "-oktozap", CMD_FLAG, CMD_OPTIONAL,
-               "Give permission to destroy bogus inodes/volumes - debugging flag");
-    cmd_AddParm(ts, "-rootinodes", CMD_FLAG, CMD_OPTIONAL,
-               "Show inodes owned by root - debugging flag");
-    cmd_AddParm(ts, "-salvagedirs", CMD_FLAG, CMD_OPTIONAL,
-               "Force rebuild/salvage of all directories");
-    cmd_AddParm(ts, "-blockreads", CMD_FLAG, CMD_OPTIONAL,
-               "Read smaller blocks to handle IO/bad blocks");
-    cmd_AddParm(ts, "-parallel", CMD_SINGLE, CMD_OPTIONAL,
-               "# of max parallel partition salvaging");
-    cmd_AddParm(ts, "-tmpdir", CMD_SINGLE, CMD_OPTIONAL,
-               "Name of dir to place tmp files ");
-    cmd_AddParm(ts, "-showlog", CMD_FLAG, CMD_OPTIONAL,
-               "Show log file upon completion");
-    cmd_AddParm(ts, "-showsuid", CMD_FLAG, CMD_OPTIONAL,
-               "Report on suid/sgid files");
-    cmd_AddParm(ts, "-showmounts", CMD_FLAG, CMD_OPTIONAL,
-               "Report on mountpoints");
-    cmd_AddParm(ts, "-orphans", CMD_SINGLE, CMD_OPTIONAL,
-               "ignore | remove | attach");
-
-    /* note - syslog isn't avail on NT, but if we make it conditional, have
-     * to deal with screwy offsets for cmd params */
-    cmd_AddParm(ts, "-syslog", CMD_FLAG, CMD_OPTIONAL,
-               "Write salvage log to syslogs");
-    cmd_AddParm(ts, "-syslogfacility", CMD_SINGLE, CMD_OPTIONAL,
-               "Syslog facility number to use");
-    cmd_AddParm(ts, "-datelogs", CMD_FLAG, CMD_OPTIONAL,
-               "Include timestamp in logfile filename");
-
-#ifdef FAST_RESTART
-    cmd_AddParm(ts, "-DontSalvage", CMD_FLAG, CMD_OPTIONAL,
-               "Don't salvage. This my be set in BosConfig to let the fileserver restart immediately after a crash. Bad volumes will be taken offline");
-#endif /* FAST_RESTART */
-    err = cmd_Dispatch(argc, argv);
-    Exit(err);
-}
-
-/* Get the salvage lock if not already held. Hold until process exits. */
-void
-ObtainSalvageLock(void)
-{
-    int salvageLock;
+    VLockFileInit(&salvageLock, AFSDIR_SERVER_SLVGLOCK_FILEPATH);
 
-#ifdef AFS_NT40_ENV
-    salvageLock =
-       (int)CreateFile(AFSDIR_SERVER_SLVGLOCK_FILEPATH, 0, 0, NULL,
-                       OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
-    if (salvageLock == (int)INVALID_HANDLE_VALUE) {
-       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) {
+    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);
 }
 
 
@@ -953,7 +454,7 @@ CheckIfBigFilesFS(char *mountPoint, char *devName)
 #define HDSTR "\\Device\\Harddisk"
 #define HDLEN  (sizeof(HDSTR)-1)       /* Length of "\Device\Harddisk" */
 int
-SameDisk(struct DiskPartition *p1, struct DiskPartition *p2)
+SameDisk(struct DiskPartition64 *p1, struct DiskPartition64 *p2)
 {
 #define RES_LEN 256
     char res[RES_LEN];
@@ -994,10 +495,10 @@ SameDisk(struct DiskPartition *p1, struct DiskPartition *p2)
  * PartsPerDisk are on the same disk.
  */
 void
-SalvageFileSysParallel(struct DiskPartition *partP)
+SalvageFileSysParallel(struct DiskPartition64 *partP)
 {
     struct job {
-       struct DiskPartition *partP;
+       struct DiskPartition64 *partP;
        int pid;                /* Pid for this job */
        int jobnumb;            /* Log file job number */
        struct job *nextjob;    /* Next partition on disk to salvage */
@@ -1185,7 +686,7 @@ SalvageFileSysParallel(struct DiskPartition *partP)
 
 
 void
-SalvageFileSys(struct DiskPartition *partP, VolumeId singleVolumeNumber)
+SalvageFileSys(struct DiskPartition64 *partP, VolumeId singleVolumeNumber)
 {
     if (!canfork || debug || Fork() == 0) {
        SalvageFileSys1(partP, singleVolumeNumber);
@@ -1217,14 +718,35 @@ get_DevName(char *pbuffer, char *wpath)
 }
 
 void
-SalvageFileSys1(struct DiskPartition *partP, VolumeId singleVolumeNumber)
+SalvageFileSys1(struct DiskPartition64 *partP, VolumeId singleVolumeNumber)
 {
     char *name, *tdir;
     char inodeListPath[256];
+    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;
@@ -1242,18 +764,34 @@ SalvageFileSys1(struct DiskPartition *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) {
-       if (!VConnectFS()) {
+       /* salvageserver already setup fssync conn for us */
+       if ((programType != salvageServer) && !VConnectFS()) {
            Abort("Couldn't connect to file server\n");
        }
-       AskOffline(singleVolumeNumber);
+
+       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)" : ""));
@@ -1292,9 +830,10 @@ SalvageFileSys1(struct DiskPartition *partP, VolumeId singleVolumeNumber)
     snprintf(inodeListPath, 255, "%s/salvage.inodes.%s.%d", tdir, name,
             getpid());
 #endif
-    if (GetInodeSummary(inodeListPath, singleVolumeNumber) < 0) {
-       unlink(inodeListPath);
-       return;
+
+    inodeFile = fopen(inodeListPath, "w+b");
+    if (!inodeFile) {
+       Abort("Error %d when creating inode description file %s; not salvaged\n", errno, inodeListPath);
     }
 #ifdef AFS_NT40_ENV
     /* Using nt_unlink here since we're really using the delete on close
@@ -1302,15 +841,22 @@ SalvageFileSys1(struct DiskPartition *partP, VolumeId singleVolumeNumber)
      * mean to unlink the file at that point. Those places have been
      * modified to actually do that so that the NT crt can be used there.
      */
-    inodeFd =
-       _open_osfhandle((long)nt_open(inodeListPath, O_RDWR, 0), O_RDWR);
-    nt_unlink(inodeListPath);  /* NT's crt unlink won't if file is open. */
+    code = nt_unlink(inodeListPath);
 #else
-    inodeFd = afs_open(inodeListPath, O_RDONLY);
-    unlink(inodeListPath);
+    code = unlink(inodeListPath);
 #endif
+    if (code < 0) {
+       Log("Error %d when trying to unlink %s\n", errno, inodeListPath);
+    }
+
+    if (GetInodeSummary(inodeFile, singleVolumeNumber) < 0) {
+       fclose(inodeFile);
+       return;
+    }
+    inodeFd = fileno(inodeFile);
     if (inodeFd == -1)
        Abort("Temporary file %s is missing...\n", inodeListPath);
+    afs_lseek(inodeFd, 0L, SEEK_SET);
     if (ListInodeOption) {
        PrintInodeList();
        return;
@@ -1321,7 +867,9 @@ SalvageFileSys1(struct DiskPartition *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) {
@@ -1366,6 +914,11 @@ SalvageFileSys1(struct DiskPartition *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.
@@ -1380,19 +933,37 @@ SalvageFileSys1(struct DiskPartition *partP, VolumeId singleVolumeNumber)
                fileSysPartition->name, (Testing ? " (READONLY mode)" : ""));
     }
 
-    close(inodeFd);            /* SalvageVolumeGroup was the last which needed it. */
+    fclose(inodeFile);         /* SalvageVolumeGroup was the last which needed it. */
 }
 
 void
 DeleteExtraVolumeHeaderFile(register struct VolumeSummary *vsp)
 {
+    char path[64];
+    sprintf(path, "%s/%s", fileSysPath, vsp->fileName);
+
     if (!Showmode)
-       Log("The volume header file %s is not associated with any actual data (%sdeleted)\n", vsp->fileName, (Testing ? "would have been " : ""));
-    if (!Testing)
-       unlink(vsp->fileName);
+       Log("The volume header file %s is not associated with any actual data (%sdeleted)\n", path, (Testing ? "would have been " : ""));
+    if (!Testing) {
+       afs_int32 code;
+       code = VDestroyVolumeDiskHeader(fileSysPartition, vsp->header.id, vsp->header.parent);
+       if (code) {
+           Log("Error %ld destroying volume disk header for volume %lu\n",
+               afs_printable_int32_ld(code),
+               afs_printable_uint32_lu(vsp->header.id));
+       }
+
+       /* make sure we actually delete the fileName file; ENOENT
+        * is fine, since VDestroyVolumeDiskHeader probably already
+        * unlinked it */
+       if (unlink(path) && errno != ENOENT) {
+           Log("Unable to unlink %s (errno = %d)\n", path, errno);
+       }
+    }
     vsp->fileName = 0;
 }
 
+int
 CompareInodes(const void *_p1, const void *_p2)
 {
     register const struct ViceInodeInfo *p1 = _p1;
@@ -1493,9 +1064,9 @@ void
 CountVolumeInodes(register struct ViceInodeInfo *ip, int maxInodes,
                  register struct InodeSummary *summary)
 {
-    int volume = ip->u.vnode.volumeId;
-    int rwvolume = volume;
-    register n, nSpecial;
+    VolumeId volume = ip->u.vnode.volumeId;
+    VolumeId rwvolume = volume;
+    register int n, nSpecial;
     register Unique maxunique;
     n = nSpecial = 0;
     maxunique = 0;
@@ -1520,7 +1091,7 @@ CountVolumeInodes(register struct ViceInodeInfo *ip, int maxInodes,
 }
 
 int
-OnlyOneVolume(struct ViceInodeInfo *inodeinfo, VolumeId singleVolumeNumber, void *rock)
+OnlyOneVolume(struct ViceInodeInfo *inodeinfo, afs_uint32 singleVolumeNumber, void *rock)
 {
     if (inodeinfo->u.vnode.vnodeNumber == INODESPECIAL)
        return (inodeinfo->u.special.parentId == singleVolumeNumber);
@@ -1534,11 +1105,12 @@ OnlyOneVolume(struct ViceInodeInfo *inodeinfo, VolumeId singleVolumeNumber, void
  * be unlinked by the caller.
  */
 int
-GetInodeSummary(char *path, VolumeId singleVolumeNumber)
+GetInodeSummary(FILE *inodeFile, VolumeId singleVolumeNumber)
 {
     struct afs_stat status;
     int forceSal, err;
-    struct ViceInodeInfo *ip;
+    int code;
+    struct ViceInodeInfo *ip, *ip_save;
     struct InodeSummary summary;
     char summaryFileName[50];
     FILE *summaryFile;
@@ -1554,23 +1126,22 @@ GetInodeSummary(char *path, VolumeId singleVolumeNumber)
 
     /* This file used to come from vfsck; cobble it up ourselves now... */
     if ((err =
-        ListViceInodes(dev, fileSysPath, path,
+        ListViceInodes(dev, fileSysPath, inodeFile,
                        singleVolumeNumber ? OnlyOneVolume : 0,
                        singleVolumeNumber, &forceSal, forceR, wpath, NULL)) < 0) {
        if (err == -2) {
-           Log("*** I/O error %d when writing a tmp inode file %s; Not salvaged %s ***\nIncrease space on partition or use '-tmpdir'\n", errno, path, dev);
+           Log("*** I/O error %d when writing a tmp inode file; Not salvaged %s ***\nIncrease space on partition or use '-tmpdir'\n", errno, dev);
            return -1;
        }
-       unlink(path);
        Abort("Unable to get inodes for \"%s\"; not salvaged\n", dev);
     }
     if (forceSal && !ForceSalvage) {
        Log("***Forced salvage of all volumes on this partition***\n");
        ForceSalvage = 1;
     }
-    inodeFd = afs_open(path, O_RDWR);
+    fseek(inodeFile, 0L, SEEK_SET);
+    inodeFd = fileno(inodeFile);
     if (inodeFd == -1 || afs_fstat(inodeFd, &status) == -1) {
-       unlink(path);
        Abort("No inode description file for \"%s\"; not salvaged\n", dev);
     }
     tdir = (tmpdir ? tmpdir : part);
@@ -1583,22 +1154,34 @@ GetInodeSummary(char *path, VolumeId singleVolumeNumber)
 #endif
     summaryFile = afs_fopen(summaryFileName, "a+");
     if (summaryFile == NULL) {
-       close(inodeFd);
-       unlink(path);
        Abort("Unable to create inode summary file\n");
     }
+
+#ifdef AFS_NT40_ENV
+    /* Using nt_unlink here since we're really using the delete on close
+     * semantics of unlink. In most places in the salvager, we really do
+     * mean to unlink the file at that point. Those places have been
+     * modified to actually do that so that the NT crt can be used there.
+     */
+    code = nt_unlink(summaryFileName);
+#else
+    code = unlink(summaryFileName);
+#endif
+    if (code < 0) {
+       Log("Error %d when trying to unlink %s\n", errno, summaryFileName);
+    }
+
     if (!canfork || debug || Fork() == 0) {
        int nInodes;
-       nInodes = status.st_size / sizeof(struct ViceInodeInfo);
+       unsigned long st_size=(unsigned long) status.st_size;
+       nInodes = st_size / sizeof(struct ViceInodeInfo);
        if (nInodes == 0) {
            fclose(summaryFile);
-           close(inodeFd);
-           unlink(summaryFileName);
            if (!singleVolumeNumber)    /* Remove the FORCESALVAGE file */
                RemoveTheForce(fileSysPath);
            else {
                struct VolumeSummary *vsp;
-               int i, j;
+               int i;
 
                GetVolumeSummary(singleVolumeNumber);
 
@@ -1611,50 +1194,42 @@ GetInodeSummary(char *path, VolumeId singleVolumeNumber)
                singleVolumeNumber ? "No applicable" : "No", dev);
            return -1;
        }
-       ip = (struct ViceInodeInfo *)malloc(status.st_size);
+       ip = (struct ViceInodeInfo *)malloc(nInodes*sizeof(struct ViceInodeInfo));
        if (ip == NULL) {
            fclose(summaryFile);
-           close(inodeFd);
-           unlink(path);
-           unlink(summaryFileName);
            Abort
                ("Unable to allocate enough space to read inode table; %s not salvaged\n",
                 dev);
        }
-       if (read(inodeFd, ip, status.st_size) != status.st_size) {
+       if (read(inodeFd, ip, st_size) != st_size) {
            fclose(summaryFile);
-           close(inodeFd);
-           unlink(path);
-           unlink(summaryFileName);
            Abort("Unable to read inode table; %s not salvaged\n", dev);
        }
        qsort(ip, nInodes, sizeof(struct ViceInodeInfo), CompareInodes);
        if (afs_lseek(inodeFd, 0, SEEK_SET) == -1
-           || write(inodeFd, ip, status.st_size) != status.st_size) {
+           || write(inodeFd, ip, st_size) != st_size) {
            fclose(summaryFile);
-           close(inodeFd);
-           unlink(path);
-           unlink(summaryFileName);
            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) {
                Log("Difficulty writing summary file (errno = %d); %s not salvaged\n", errno, dev);
                fclose(summaryFile);
-               close(inodeFd);
                return -1;
            }
            summary.index += (summary.nInodes);
            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);
            fclose(summaryFile);
-           close(inodeFd);
            return -1;
        }
        if (canfork && !debug) {
@@ -1664,26 +1239,23 @@ GetInodeSummary(char *path, VolumeId singleVolumeNumber)
     } else {
        if (Wait("Inode summary") == -1) {
            fclose(summaryFile);
-           close(inodeFd);
-           unlink(path);
-           unlink(summaryFileName);
            Exit(1);            /* salvage of this partition aborted */
        }
     }
     assert(afs_fstat(fileno(summaryFile), &status) != -1);
     if (status.st_size != 0) {
        int ret;
-       inodeSummary = (struct InodeSummary *)malloc(status.st_size);
+       unsigned long st_status=(unsigned long)status.st_size;
+       inodeSummary = (struct InodeSummary *)malloc(st_status);
        assert(inodeSummary != NULL);
        /* For GNU we need to do lseek to get the file pointer moved. */
        assert(afs_lseek(fileno(summaryFile), 0, SEEK_SET) == 0);
-       ret = read(fileno(summaryFile), inodeSummary, status.st_size);
-       assert(ret == status.st_size);
+       ret = read(fileno(summaryFile), inodeSummary, st_status);
+       assert(ret == st_status);
     }
-    nVolumesInInodeFile = status.st_size / sizeof(struct InodeSummary);
+    nVolumesInInodeFile =(unsigned long)(status.st_size) / sizeof(struct InodeSummary);
+    Log("%d nVolumesInInodeFile %lu \n",nVolumesInInodeFile,(unsigned long)(status.st_size));
     fclose(summaryFile);
-    close(inodeFd);
-    unlink(summaryFileName);
     return 0;
 }
 
@@ -1704,106 +1276,441 @@ CompareVolumes(const void *_p1, const void *_p2)
     return p1->header.id < p2->header.id ? -1 : 1;     /* Both read-only */
 }
 
-void
-GetVolumeSummary(VolumeId singleVolumeNumber)
+/**
+ * Gleans volumeSummary information by asking the fileserver
+ *
+ * @param[in] singleVolumeNumber  the volume we're salvaging. 0 if we're
+ *                                salvaging a whole partition
+ *
+ * @return whether we obtained the volume summary information or not
+ *  @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
+ */
+static int
+AskVolumeSummary(VolumeId singleVolumeNumber)
 {
-    DIR *dirp;
-    afs_int32 nvols = 0;
-    struct VolumeSummary *vsp, vs;
-    struct VolumeDiskHeader diskHeader;
-    struct dirent *dp;
+    afs_int32 code = 1;
+#if defined(FSSYNC_BUILD_CLIENT) && defined(AFS_DEMAND_ATTACH_FS)
+    if (programType == salvageServer) {
+       if (singleVolumeNumber) {
+           FSSYNC_VGQry_response_t q_res;
+           SYNC_response res;
+           struct VolumeSummary *vsp;
+           int i;
+           struct VolumeDiskHeader diskHdr;
+
+           memset(&res, 0, sizeof(res));
+
+           code = FSYNC_VGCQuery(fileSysPartition->name, singleVolumeNumber, &q_res, &res);
+
+           /*
+            * We must wait for the partition to finish scanning before
+            * can continue, since we will not know if we got the entire
+            * VG membership unless the partition is fully scanned.
+            * We could, in theory, just scan the partition ourselves if
+            * the VG cache is not ready, but we would be doing the exact
+            * same scan the fileserver is doing; it will almost always
+            * be faster to wait for the fileserver. The only exceptions
+            * are if the partition does not take very long to scan, and
+            * in that case it's fast either way, so who cares?
+            */
+           if (code == SYNC_FAILED && res.hdr.reason == FSYNC_PART_SCANNING) {
+               Log("waiting for fileserver to finish scanning partition %s...\n",
+                   fileSysPartition->name);
+
+               for (i = 1; code == SYNC_FAILED && res.hdr.reason == FSYNC_PART_SCANNING; i++) {
+                   /* linearly ramp up from 1 to 10 seconds; nothing fancy,
+                    * just so small partitions don't need to wait over 10
+                    * seconds every time, and large partitions are generally
+                    * polled only once every ten seconds. */
+                   sleep((i > 10) ? (i = 10) : i);
+
+                   code = FSYNC_VGCQuery(fileSysPartition->name, singleVolumeNumber, &q_res, &res);
+               }
+           }
 
-    /* Get headers from volume directory */
-    if (chdir(fileSysPath) == -1 || (dirp = opendir(".")) == 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;
-               if ((fd = afs_open(dp->d_name, O_RDONLY)) != -1
-                   && read(fd, (char *)&diskHeader, sizeof(diskHeader))
-                   == sizeof(diskHeader)
-                   && diskHeader.stamp.magic == VOLUMEHEADERMAGIC) {
-                   DiskToVolumeHeader(&vs.header, &diskHeader);
-                   nvols++;
+           if (code == SYNC_FAILED && res.hdr.reason == FSYNC_UNKNOWN_VOLID) {
+               /* This can happen if there's no header for the volume
+                * we're salvaging, or no headers exist for the VG (if
+                * we're salvaging an RW). Act as if we got a response
+                * with no VG members. The headers may be created during
+                * salvaging, if there are inodes in this VG. */
+               code = 0;
+               memset(&q_res, 0, sizeof(q_res));
+               q_res.rw = singleVolumeNumber;
+           }
+
+           if (code) {
+               Log("fileserver refused VGCQuery request for volume %lu on "
+                   "partition %s, code %ld reason %ld\n",
+                   afs_printable_uint32_lu(singleVolumeNumber),
+                   fileSysPartition->name,
+                   afs_printable_int32_ld(code),
+                   afs_printable_int32_ld(res.hdr.reason));
+               goto done;
+           }
+
+           if (q_res.rw != singleVolumeNumber) {
+               Log("fileserver requested salvage of clone %lu; scheduling salvage of volume group %lu...\n",
+                   afs_printable_uint32_lu(singleVolumeNumber),
+                   afs_printable_uint32_lu(q_res.rw));
+#ifdef SALVSYNC_BUILD_CLIENT
+               if (SALVSYNC_LinkVolume(q_res.rw,
+                                      singleVolumeNumber,
+                                      fileSysPartition->name,
+                                      NULL) != SYNC_OK) {
+                   Log("schedule request failed\n");
+               }
+#endif /* SALVSYNC_BUILD_CLIENT */
+               Exit(SALSRV_EXIT_VOLGROUP_LINK);
+           }
+
+           volumeSummaryp = malloc(VOL_VG_MAX_VOLS * sizeof(struct VolumeSummary));
+           assert(volumeSummaryp != NULL);
+
+           nVolumes = 0;
+           vsp = volumeSummaryp;
+
+           for (i = 0; i < VOL_VG_MAX_VOLS; i++) {
+               char name[VMAXPATHLEN];
+
+               if (!q_res.children[i]) {
+                   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;
+                   }
                }
-               close(fd);
+
+               code = VReadVolumeDiskHeader(q_res.children[i], fileSysPartition, &diskHdr);
+               if (code) {
+                   Log("Cannot read header for %lu; trying to salvage group anyway\n",
+                       afs_printable_uint32_lu(q_res.children[i]));
+                   code = 0;
+                   continue;
+               }
+
+               DiskToVolumeHeader(&vsp->header, &diskHdr);
+               VolumeExternalName_r(q_res.children[i], name, sizeof(name));
+               vsp->fileName = ToString(name);
+               nVolumes++;
+               vsp++;
            }
+
+           qsort(volumeSummaryp, nVolumes, sizeof(struct VolumeSummary),
+                 CompareVolumes);
        }
-#ifdef AFS_NT40_ENV
-       closedir(dirp);
-       dirp = opendir(".");    /* No rewinddir for NT */
-#else
-       rewinddir(dirp);
+      done:
+       if (code) {
+           Log("Cannot get volume summary from fileserver; falling back to scanning "
+               "entire partition\n");
+       }
+    }
+#endif /* FSSYNC_BUILD_CLIENT && AFS_DEMAND_ATTACH_FS */
+    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) */
+    int retry;  /**< do we need to retry vol lock/checkout? */
+};
+
+/**
+ * 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 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,
+             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);
+
+#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) {
+           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);
+    }
+}
+
+/**
+ * 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;
+
+    code = AskVolumeSummary(singleVolumeNumber);
+    if (code == 0) {
+       /* we successfully got the vol information from the fileserver; no
+        * need to scan the partition */
+       return 0;
+    }
+    if (code < 0) {
+       /* we need to retry volume checkout */
+       return code;
+    }
+
+    if (!singleVolumeNumber) {
+       /* 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);
+       }
        if (!nvols)
            nvols = 1;
-       volumeSummaryp =
-           (struct VolumeSummary *)malloc(nvols *
-                                          sizeof(struct VolumeSummary));
-    } else
-       volumeSummaryp =
-           (struct VolumeSummary *)malloc(20 * sizeof(struct VolumeSummary));
+    } else {
+       nvols = VOL_VG_MAX_VOLS;
+    }
+
+    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;
-           if ((fd = afs_open(dp->d_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/%s is not a legitimate volume header file; %sdeleted\n", fileSysPathName, dp->d_name, (Testing ? "it would have been " : ""));
-                   if (!Testing)
-                       unlink(dp->d_name);
-               }
-           } else {
-               char nameShouldBe[64];
-               DiskToVolumeHeader(&vsp->header, &diskHeader);
-               if (singleVolumeNumber && vsp->header.id == singleVolumeNumber
-                   && vsp->header.parent != singleVolumeNumber) {
-                   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, vsp->header.id);
-                   if (singleVolumeNumber)
-                       AskOffline(vsp->header.id);
-                   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", dp->d_name, (Testing ? "it would have been " : ""));
-                       if (!Testing)
-                           unlink(dp->d_name);
-                   } else {
-                       vsp->fileName = ToString(dp->d_name);
-                       nVolumes++;
-                       vsp++;
-                   }
-               }
-           }
-           close(fd);
-       }
+    params.singleVolumeNumber = 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;
     }
-    closedir(dirp);
+    if (code < 0) {
+       Abort("Failed to get volume header summary\n");
+    }
+    nVolumes = params.nVolumes;
+
     qsort(volumeSummaryp, nVolumes, sizeof(struct VolumeSummary),
          CompareVolumes);
+
+    return 0;
 }
 
 /* Find the link table. This should be associated with the RW volume or, if
@@ -1846,7 +1753,7 @@ CreateLinkTable(register struct InodeSummary *isp, Inode ino)
        Abort("Can't open link table for volume %u (error = %d)\n",
              isp->RWvolumeId, errno);
 
-    if (FDH_TRUNC(fdP, 0) < 0)
+    if (FDH_TRUNC(fdP, sizeof(version) + sizeof(short)) < 0)
        Abort("Can't truncate link table for volume %u (error = %d)\n",
              isp->RWvolumeId, errno);
 
@@ -1924,7 +1831,7 @@ DoSalvageVolumeGroup(register struct InodeSummary *isp, int nVols)
     int check;
     Inode ino;
     int dec_VGLinkH = 0;
-    int VGLinkH_p1;
+    int VGLinkH_p1 =0;
     FdHandle_t *fdP = NULL;
 
     VGLinkH_cnt = 0;
@@ -1968,7 +1875,23 @@ DoSalvageVolumeGroup(register struct InodeSummary *isp, int nVols)
        if (Testing) {
            IH_INIT(VGLinkH, fileSysDevice, -1, -1);
        } else {
+            int i, j;
+            struct ViceInodeInfo *ip;
            CreateLinkTable(isp, ino);
+           fdP = IH_OPEN(VGLinkH);
+            /* Sync fake 1 link counts to the link table, now that it exists */
+            if (fdP) {
+               for (i = 0; i < nVols; i++) {
+                       ip = allInodes + isp[i].index;
+                        for (j = isp[i].nSpecialInodes; j < isp[i].nInodes; j++) {
+#ifdef AFS_NT40_ENV
+                                nt_SetLinkCount(fdP, ip[j].inodeNumber, 1, 1);
+#else
+                                namei_SetLinkCount(fdP, ip[j].inodeNumber, 1, 1);
+#endif
+                   }
+               }
+           }
        }
     }
     if (fdP)
@@ -2026,6 +1949,7 @@ DoSalvageVolumeGroup(register struct InodeSummary *isp, int nVols)
 
     /* Fix actual inode counts */
     if (!Showmode) {
+       Log("totalInodes %d\n",totalInodes);
        for (ip = inodes; totalInodes; ip++, totalInodes--) {
            static int TraceBadLinkCounts = 0;
 #ifdef AFS_NAMEI_ENV
@@ -2112,7 +2036,7 @@ QuickCheck(register struct InodeSummary *isp, int nVols)
            && volHeader.stamp.magic == VOLUMEINFOMAGIC
            && volHeader.dontSalvage == DONT_SALVAGE
            && volHeader.needsSalvaged == 0 && volHeader.destroyMe == 0) {
-           if (volHeader.inUse == 1) {
+           if (volHeader.inUse != 0) {
                volHeader.inUse = 0;
                volHeader.inService = 1;
                if (!Testing) {
@@ -2146,43 +2070,148 @@ SalvageVolumeHeaderFile(register struct InodeSummary *isp,
                        register struct ViceInodeInfo *inodes, int RW,
                        int check, int *deleteMe)
 {
-    int headerFd = 0;
     int i;
     register struct ViceInodeInfo *ip;
     int allinodesobsolete = 1;
     struct VolumeDiskHeader diskHeader;
+    afs_int32 (*writefunc)(VolumeDiskHeader_t *, struct DiskPartition64 *) = NULL;
+    int *skip;
+
+    /* keeps track of special inodes that are probably 'good'; they are
+     * referenced in the vol header, and are included in the given inodes
+     * array */
+    struct {
+       int valid;
+       Inode inode;
+    } goodspecial[MAXINODETYPE];
 
     if (deleteMe)
        *deleteMe = 0;
+
+    memset(goodspecial, 0, sizeof(goodspecial));
+
+    skip = malloc(isp->nSpecialInodes * sizeof(*skip));
+    if (skip) {
+       memset(skip, 0, isp->nSpecialInodes * sizeof(*skip));
+    } else {
+       Log("cannot allocate memory for inode skip array when salvaging "
+           "volume %lu; not performing duplicate special inode recovery\n",
+           afs_printable_uint32_lu(isp->volumeId));
+       /* still try to perform the salvage; the skip array only does anything
+        * if we detect duplicate special inodes */
+    }
+
+    /*
+     * First, look at the special inodes and see if any are referenced by
+     * the existing volume header. If we find duplicate special inodes, we
+     * can use this information to use the referenced inode (it's more
+     * likely to be the 'good' one), and throw away the duplicates.
+     */
+    if (isp->volSummary && skip) {
+       /* use tempHeader, so we can use the stuff[] array to easily index
+        * into the isp->volSummary special inodes */
+       memcpy(&tempHeader, &isp->volSummary->header, sizeof(struct VolumeHeader));
+
+       for (i = 0; i < isp->nSpecialInodes; i++) {
+           ip = &inodes[isp->index + i];
+           if (ip->u.special.type <= 0 || ip->u.special.type > MAXINODETYPE) {
+               /* will get taken care of in a later loop */
+               continue;
+           }
+           if (ip->inodeNumber == *(stuff[ip->u.special.type - 1].inode)) {
+               goodspecial[ip->u.special.type-1].valid = 1;
+               goodspecial[ip->u.special.type-1].inode = ip->inodeNumber;
+           }
+       }
+    }
+
     memset(&tempHeader, 0, sizeof(tempHeader));
     tempHeader.stamp.magic = VOLUMEHEADERMAGIC;
     tempHeader.stamp.version = VOLUMEHEADERVERSION;
     tempHeader.id = isp->volumeId;
     tempHeader.parent = isp->RWvolumeId;
+
     /* Check for duplicates (inodes are sorted by type field) */
     for (i = 0; i < isp->nSpecialInodes - 1; i++) {
        ip = &inodes[isp->index + i];
        if (ip->u.special.type == (ip + 1)->u.special.type) {
-           if (!Showmode)
-               Log("Duplicate special inodes in volume header; salvage of volume %u aborted\n", isp->volumeId);
-           return -1;
+           afs_ino_str_t stmp1, stmp2;
+
+           if (ip->u.special.type <= 0 || ip->u.special.type > MAXINODETYPE) {
+               /* Will be caught in the loop below */
+               continue;
+           }
+           if (!Showmode) {
+               Log("Duplicate special %d inodes for volume %u found (%s, %s);\n",
+                   ip->u.special.type, isp->volumeId,
+                   PrintInode(stmp1, ip->inodeNumber),
+                   PrintInode(stmp2, (ip+1)->inodeNumber));
+           }
+           if (skip && goodspecial[ip->u.special.type-1].valid) {
+               Inode gi = goodspecial[ip->u.special.type-1].inode;
+
+               if (!Showmode) {
+                   Log("using special inode referenced by vol header (%s)\n",
+                       PrintInode(stmp1, gi));
+               }
+
+               /* the volume header references some special inode of
+                * this type in the inodes array; are we it? */
+               if (ip->inodeNumber != gi) {
+                   skip[i] = 1;
+               } else if ((ip+1)->inodeNumber != gi) {
+                   /* in case this is the last iteration; we need to
+                    * make sure we check ip+1, too */
+                   skip[i+1] = 1;
+               }
+           } else {
+               if (!Showmode)
+                   Log("cannot determine which is correct; salvage of volume %u aborted\n", isp->volumeId);
+               if (skip) {
+                   free(skip);
+               }
+               return -1;
+           }
        }
     }
     for (i = 0; i < isp->nSpecialInodes; i++) {
        ip = &inodes[isp->index + i];
        if (ip->u.special.type <= 0 || ip->u.special.type > MAXINODETYPE) {
            if (check) {
-               Log("Rubbish header inode\n");
+               Log("Rubbish header inode %s of type %d\n",
+                   PrintInode(NULL, ip->inodeNumber),
+                   ip->u.special.type);
+               if (skip) {
+                   free(skip);
+               }
                return -1;
            }
-           Log("Rubbish header inode; deleted\n");
+           Log("Rubbish header inode %s of type %d; deleted\n",
+               PrintInode(NULL, ip->inodeNumber),
+               ip->u.special.type);
        } else if (!stuff[ip->u.special.type - 1].obsolete) {
-           *(stuff[ip->u.special.type - 1].inode) = ip->inodeNumber;
+           if (skip && skip[i]) {
+               if (orphans == ORPH_REMOVE) {
+                   Log("Removing orphan special inode %s of type %d\n",
+                       PrintInode(NULL, ip->inodeNumber), ip->u.special.type);
+                   continue;
+               } else {
+                   Log("Ignoring orphan special inode %s of type %d\n",
+                       PrintInode(NULL, ip->inodeNumber), ip->u.special.type);
+                   /* fall through to the ip->linkCount--; line below */
+               }
+           } else {
+               *(stuff[ip->u.special.type - 1].inode) = ip->inodeNumber;
+               allinodesobsolete = 0;
+           }
            if (!check && ip->u.special.type != VI_LINKTABLE)
                ip->linkCount--;        /* Keep the inode around */
-           allinodesobsolete = 0;
        }
     }
+    if (skip) {
+       free(skip);
+    }
+    skip = NULL;
 
     if (allinodesobsolete) {
        if (deleteMe)
@@ -2214,23 +2243,26 @@ SalvageVolumeHeaderFile(register struct InodeSummary *isp,
     }
 
     if (isp->volSummary == NULL) {
-       char name[64];
-       (void)afs_snprintf(name, sizeof name, VFORMAT, isp->volumeId);
+       char path[64];
+       char headerName[64];
+       (void)afs_snprintf(headerName, sizeof headerName, VFORMAT, afs_printable_uint32_lu(isp->volumeId));
+       (void)afs_snprintf(path, sizeof path, "%s/%s", fileSysPath, headerName);
        if (check) {
            Log("No header file for volume %u\n", isp->volumeId);
            return -1;
        }
        if (!Showmode)
-           Log("No header file for volume %u; %screating %s/%s\n",
+           Log("No header file for volume %u; %screating %s\n",
                isp->volumeId, (Testing ? "it would have been " : ""),
-               fileSysPathName, name);
-       headerFd = afs_open(name, O_RDWR | O_CREAT | O_TRUNC, 0644);
-       assert(headerFd != -1);
+               path);
        isp->volSummary = (struct VolumeSummary *)
            malloc(sizeof(struct VolumeSummary));
-       isp->volSummary->fileName = ToString(name);
+       isp->volSummary->fileName = ToString(headerName);
+
+       writefunc = VCreateVolumeDiskHeader;
     } else {
-       char name[64];
+       char path[64];
+       char headerName[64];
        /* hack: these two fields are obsolete... */
        isp->volSummary->header.volumeAcl = 0;
        isp->volSummary->header.volumeMountTable = 0;
@@ -2240,22 +2272,22 @@ SalvageVolumeHeaderFile(register struct InodeSummary *isp,
             sizeof(struct VolumeHeader))) {
            /* We often remove the name before calling us, so we make a fake one up */
            if (isp->volSummary->fileName) {
-               strcpy(name, isp->volSummary->fileName);
+               strcpy(headerName, isp->volSummary->fileName);
            } else {
-               (void)afs_snprintf(name, sizeof name, VFORMAT, isp->volumeId);
-               isp->volSummary->fileName = ToString(name);
+               (void)afs_snprintf(headerName, sizeof headerName, VFORMAT, afs_printable_uint32_lu(isp->volumeId));
+               isp->volSummary->fileName = ToString(headerName);
            }
+           (void)afs_snprintf(path, sizeof path, "%s/%s", fileSysPath, headerName);
 
-           Log("Header file %s is damaged or no longer valid%s\n", name,
+           Log("Header file %s is damaged or no longer valid%s\n", path,
                (check ? "" : "; repairing"));
            if (check)
                return -1;
 
-           headerFd = afs_open(name, O_RDWR | O_TRUNC, 0644);
-           assert(headerFd != -1);
+           writefunc = VWriteVolumeDiskHeader;
        }
     }
-    if (headerFd) {
+    if (writefunc) {
        memcpy(&isp->volSummary->header, &tempHeader,
               sizeof(struct VolumeHeader));
        if (Testing) {
@@ -2263,15 +2295,16 @@ SalvageVolumeHeaderFile(register struct InodeSummary *isp,
                Log("It would have written a new header file for volume %u\n",
                    isp->volumeId);
        } else {
+           afs_int32 code;
            VolumeHeaderToDisk(&diskHeader, &tempHeader);
-           if (write(headerFd, &diskHeader, sizeof(struct VolumeDiskHeader))
-               != sizeof(struct VolumeDiskHeader)) {
-               Log("Couldn't rewrite volume header file!\n");
-               close(headerFd);
+           code = (*writefunc)(&diskHeader, fileSysPartition);
+           if (code) {
+               Log("Error %ld writing volume header file for volume %lu\n",
+                   afs_printable_int32_ld(code),
+                   afs_printable_uint32_lu(diskHeader.id));
                return -1;
            }
        }
-       close(headerFd);
     }
     IH_INIT(isp->volSummary->volumeInfoHandle, fileSysDevice, isp->RWvolumeId,
            isp->volSummary->header.volumeInfo);
@@ -2288,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)
@@ -2358,8 +2391,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);
 
@@ -2386,11 +2419,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);
@@ -2403,9 +2436,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);
@@ -2473,8 +2506,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;
@@ -2550,7 +2584,7 @@ SalvageIndex(Inode ino, VnodeClass class, int RW,
                     * if no such match, take the first determined by our sort
                     * order */
                    register struct ViceInodeInfo *lip = ip;
-                   register lnInodes = nInodes;
+                   register int lnInodes = nInodes;
                    while (lnInodes
                           && lip->u.vnode.vnodeNumber == vnodeNumber) {
                        if (VNDISK_GET_INO(vnode) == lip->inodeNumber) {
@@ -2680,11 +2714,14 @@ SalvageIndex(Inode ino, VnodeClass class, int RW,
                        }
                        if (VNDISK_GET_INO(vnode)) {
                            if (!Showmode) {
-                               Log("Vnode %d (unique %u): corresponding inode %s is missing; vnode deleted, vnode mod time=%s", vnodeNumber, vnode->uniquifier, PrintInode(NULL, VNDISK_GET_INO(vnode)), ctime((time_t *) & (vnode->serverModifyTime)));
+                               time_t serverModifyTime = vnode->serverModifyTime;
+                               Log("Vnode %d (unique %u): corresponding inode %s is missing; vnode deleted, vnode mod time=%s", vnodeNumber, vnode->uniquifier, PrintInode(NULL, VNDISK_GET_INO(vnode)), ctime(&serverModifyTime));
                            }
                        } else {
-                           if (!Showmode)
-                               Log("Vnode %d (unique %u): bad directory vnode (no inode number listed); vnode deleted, vnode mod time=%s", vnodeNumber, vnode->uniquifier, ctime((time_t *) & (vnode->serverModifyTime)));
+                           if (!Showmode) {
+                               time_t serverModifyTime = vnode->serverModifyTime;
+                               Log("Vnode %d (unique %u): bad directory vnode (no inode number listed); vnode deleted, vnode mod time=%s", vnodeNumber, vnode->uniquifier, ctime(&serverModifyTime));
+                           }
                        }
                        memset(vnode, 0, vcp->diskSize);
                        vnodeChanged = 1;
@@ -2740,7 +2777,7 @@ CopyOnWrite(register struct DirSummary *dir)
     struct VnodeDiskObject vnode;
     struct VnodeClassInfo *vcp = &VnodeClassInfo[vLarge];
     Inode oldinode, newinode;
-    int code;
+    afs_sfsize_t code;
 
     if (dir->copied || Testing)
        return;
@@ -2791,18 +2828,21 @@ CopyAndSalvage(register struct DirSummary *dir)
     struct VnodeClassInfo *vcp = &VnodeClassInfo[vLarge];
     Inode oldinode, newinode;
     DirHandle newdir;
-    register afs_int32 code;
+    FdHandle_t *fdP;
+    afs_int32 code;
+    afs_sfsize_t lcode;
     afs_int32 parentUnique = 1;
     struct VnodeEssence *vnodeEssence;
+    afs_fsize_t length;
 
     if (Testing)
        return;
     Log("Salvaging directory %u...\n", dir->vnodeNumber);
-    code =
+    lcode =
        IH_IREAD(vnodeInfo[vLarge].handle,
                 vnodeIndexOffset(vcp, dir->vnodeNumber), (char *)&vnode,
                 sizeof(vnode));
-    assert(code == sizeof(vnode));
+    assert(lcode == sizeof(vnode));
     oldinode = VNDISK_GET_INO(&vnode);
     /* Increment the version number by a whole lot to avoid problems with
      * clients that were promised new version numbers--but the file server
@@ -2833,8 +2873,10 @@ CopyAndSalvage(register struct DirSummary *dir)
     if (code) {
        /* didn't really build the new directory properly, let's just give up. */
        code = IH_DEC(dir->ds_linkH, newinode, dir->rwVid);
-       assert(code == 0);
        Log("Directory salvage returned code %d, continuing.\n", code);
+       if (code) {
+           Log("also failed to decrement link count on new inode");
+       }
        assert(1 == 2);
     }
     Log("Checking the results of the directory salvage...\n");
@@ -2846,12 +2888,14 @@ CopyAndSalvage(register struct DirSummary *dir)
     }
     vnode.cloned = 0;
     VNDISK_SET_INO(&vnode, newinode);
-    VNDISK_SET_LEN(&vnode, Length(&newdir));
-    code =
+    length = Length(&newdir);
+    VNDISK_SET_LEN(&vnode, length);
+    lcode =
        IH_IWRITE(vnodeInfo[vLarge].handle,
                  vnodeIndexOffset(vcp, dir->vnodeNumber), (char *)&vnode,
                  sizeof(vnode));
-    assert(code == sizeof(vnode));
+    assert(lcode == sizeof(vnode));
+#if 0
 #ifdef AFS_NT40_ENV
     nt_sync(fileSysDevice);
 #else
@@ -2859,15 +2903,23 @@ CopyAndSalvage(register struct DirSummary *dir)
                                 * an open FD on the file itself to fsync.
                                 */
 #endif
+#else
+    vnodeInfo[vLarge].handle->ih_synced = 1;
+#endif
+    /* make sure old directory file is really closed */
+    fdP = IH_OPEN(dir->dirHandle.dirh_handle);
+    FDH_REALLYCLOSE(fdP);
+    
     code = IH_DEC(dir->ds_linkH, oldinode, dir->rwVid);
     assert(code == 0);
     dir->dirHandle = newdir;
 }
 
-void
-JudgeEntry(struct DirSummary *dir, char *name, VnodeId vnodeNumber,
-          Unique unique)
+int
+JudgeEntry(void *dirVal, char *name, afs_int32 vnodeNumber,
+          afs_int32 unique)
 {
+    struct DirSummary *dir = (struct DirSummary *)dirVal;
     struct VnodeEssence *vnodeEssence;
     afs_int32 dirOrphaned, todelete;
 
@@ -2882,7 +2934,7 @@ JudgeEntry(struct DirSummary *dir, char *name, VnodeId vnodeNumber,
            CopyOnWrite(dir);
            assert(Delete(&dir->dirHandle, name) == 0);
        }
-       return;
+       return 0;
     }
 #ifdef AFS_AIX_ENV
 #ifndef AFS_NAMEI_ENV
@@ -2896,7 +2948,7 @@ JudgeEntry(struct DirSummary *dir, char *name, VnodeId vnodeNumber,
            CopyOnWrite(dir);
            assert(Delete(&dir->dirHandle, name) == 0);
        }
-       return;
+       return 0;
     }
 #endif
 #endif
@@ -2914,7 +2966,7 @@ JudgeEntry(struct DirSummary *dir, char *name, VnodeId vnodeNumber,
                CopyOnWrite(dir);
                assert(Delete(&dir->dirHandle, name) == 0);
            }
-           return;
+           return 0;
        }
     }
 
@@ -2929,7 +2981,7 @@ JudgeEntry(struct DirSummary *dir, char *name, VnodeId vnodeNumber,
             * entry. Otherwise, it will get created in the next 
             * salvage and deleted again here. So Just skip it.
             */
-           return;
+           return 0;
        }
 
        todelete = ((!vnodeEssence->unique || dirOrphaned) ? 1 : 0);
@@ -2938,7 +2990,7 @@ JudgeEntry(struct DirSummary *dir, char *name, VnodeId vnodeNumber,
            Log("dir vnode %u: %s/%s (vnode %u): unique changed from %u to %u %s\n", dir->vnodeNumber, (dir->name ? dir->name : "??"), name, vnodeNumber, unique, vnodeEssence->unique, (!todelete ? "" : (Testing ? "-- would have deleted" : "-- deleted")));
        }
        if (!Testing) {
-           ViceFid fid;
+           AFSFid fid;
            fid.Vnode = vnodeNumber;
            fid.Unique = vnodeEssence->unique;
            CopyOnWrite(dir);
@@ -2947,12 +2999,12 @@ JudgeEntry(struct DirSummary *dir, char *name, VnodeId vnodeNumber,
                assert(Create(&dir->dirHandle, name, &fid) == 0);
        }
        if (todelete)
-           return;             /* no need to continue */
+           return 0;           /* no need to continue */
     }
 
     if (strcmp(name, ".") == 0) {
        if (dir->vnodeNumber != vnodeNumber || (dir->unique != unique)) {
-           ViceFid fid;
+           AFSFid fid;
            if (!Showmode)
                Log("directory vnode %u.%u: bad '.' entry (was %u.%u); fixed\n", dir->vnodeNumber, dir->unique, vnodeNumber, unique);
            if (!Testing) {
@@ -2969,7 +3021,7 @@ JudgeEntry(struct DirSummary *dir, char *name, VnodeId vnodeNumber,
        }
        dir->haveDot = 1;
     } else if (strcmp(name, "..") == 0) {
-       ViceFid pa;
+       AFSFid pa;
        if (dir->parent) {
            struct VnodeEssence *dotdot;
            pa.Vnode = dir->parent;
@@ -3004,31 +3056,52 @@ JudgeEntry(struct DirSummary *dir, char *name, VnodeId vnodeNumber,
        }
        vnodeEssence->claimed = 0;      /* Not claimed: Orphaned */
        vnodeEssence->todelete = 1;     /* Will later delete vnode and decr inode */
-       return;
+       return 0;
     } else {
        if (ShowSuid && (vnodeEssence->modeBits & 06000))
            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)
+       if (/* ShowMounts && */ (vnodeEssence->type == vSymlink)
            && !(vnodeEssence->modeBits & 0111)) {
-           int code, size;
-           char buf[1024];
+           ssize_t nBytes;
+           afs_sfsize_t size;
+           char buf[1025];
            IHandle_t *ihP;
            FdHandle_t *fdP;
 
            IH_INIT(ihP, fileSysDevice, dir->dirHandle.dirh_handle->ih_vid,
                    vnodeEssence->InodeNumber);
            fdP = IH_OPEN(ihP);
-           assert(fdP != NULL);
+           if (fdP == NULL) {
+               Log("ERROR %s could not open mount point vnode %u\n", dir->vname, vnodeNumber);
+               IH_RELEASE(ihP);
+               return 0;
+           }
            size = FDH_SIZE(fdP);
-           assert(size != -1);
-           memset(buf, 0, 1024);
+           if (size < 0) {
+               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;
+           }
+       
            if (size > 1024)
                size = 1024;
-           code = FDH_READ(fdP, buf, size);
-           assert(code == size);
-           Log("In volume %u (%s) found mountpoint %s/%s to '%s'\n",
-               dir->dirHandle.dirh_handle->ih_vid, dir->vname,
-               dir->name ? dir->name : "??", name, buf);
+           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",
+                       dir->dirHandle.dirh_handle->ih_vid, dir->vname, dir->name ? dir->name : "??", name, buf,
+                       Testing ? "would convert" : "converted");
+                   vnodeEssence->modeBits |= 0111;
+                   vnodeEssence->changed = 1;
+               } else if (ShowMounts) Log("In volume %u (%s) found mountpoint %s/%s to '%s'\n",
+                   dir->dirHandle.dirh_handle->ih_vid, dir->vname,
+                   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, (int)size, (int)nBytes);
+           }
            FDH_REALLYCLOSE(fdP);
            IH_RELEASE(ihP);
        }
@@ -3075,13 +3148,14 @@ JudgeEntry(struct DirSummary *dir, char *name, VnodeId vnodeNumber,
                    CopyOnWrite(dir);
                    assert(Delete(&dir->dirHandle, name) == 0);
                }
-               return;
+               return 0;
            }
        }
        /* This directory claims the vnode */
        vnodeEssence->claimed = 1;
     }
     vnodeEssence->count--;
+    return 0;
 }
 
 void
@@ -3091,7 +3165,7 @@ DistilVnodeEssence(VolumeId rwVId, VnodeClass class, Inode ino, Unique * maxu)
     struct VnodeClassInfo *vcp = &VnodeClassInfo[class];
     char buf[SIZEOF_LARGEDISKVNODE];
     struct VnodeDiskObject *vnode = (struct VnodeDiskObject *)buf;
-    int size;
+    afs_sfsize_t size;
     StreamHandle_t *file;
     int vnodeIndex;
     int nVnodes;
@@ -3144,8 +3218,16 @@ DistilVnodeEssence(VolumeId rwVId, VnodeClass class, Inode ino, Unique * maxu)
            vep->owner = vnode->owner;
            vep->group = vnode->group;
            if (vnode->type == vDirectory) {
-               assert(class == vLarge);
-               vip->inodes[vnodeIndex] = VNDISK_GET_INO(vnode);
+               if (class != vLarge) {
+                   VnodeId vnodeNumber = bitNumberToVnodeNumber(vnodeIndex, class);
+                   vip->nAllocatedVnodes--;
+                   memset(vnode, 0, sizeof(vnode));
+                   IH_IWRITE(vnodeInfo[vSmall].handle,
+                             vnodeIndexOffset(vcp, vnodeNumber),
+                             (char *)&vnode, sizeof(vnode));
+                   VolumeChanged = 1;
+               } else
+                   vip->inodes[vnodeIndex] = VNDISK_GET_INO(vnode);
            }
        }
     }
@@ -3280,6 +3362,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)
 {
@@ -3298,11 +3746,12 @@ SalvageVolume(register struct InodeSummary *rwIsp, IHandle_t * alinkH)
     struct VnodeEssence *vep;
     afs_int32 v, pv;
     IHandle_t *h;
-    int nBytes;
-    ViceFid pa;
+    afs_sfsize_t nBytes;
+    AFSFid pa;
     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);
@@ -3322,11 +3771,29 @@ SalvageVolume(register struct InodeSummary *rwIsp, IHandle_t * alinkH)
        SalvageDir(volHeader.name, vid, dirVnodeInfo, alinkH, i, &rootdir,
                   &rootdirfound);
     }
+#ifdef AFS_NT40_ENV
+    nt_sync(fileSysDevice);
+#else
+    sync();                            /* This used to be done lower level, for every dir */
+#endif
     if (Showmode) {
        IH_RELEASE(h);
        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).
      */
@@ -3348,8 +3815,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)
@@ -3378,7 +3857,7 @@ SalvageVolume(register struct InodeSummary *rwIsp, IHandle_t * alinkH)
                 * won't be visible there.
                 */
                if (class == vLarge) {
-                   ViceFid pa;
+                   AFSFid pa;
                    DirHandle dh;
 
                    /* Remove and recreate the ".." entry in this orphaned directory */
@@ -3499,6 +3978,8 @@ SalvageVolume(register struct InodeSummary *rwIsp, IHandle_t * alinkH)
                    if (!Showmode) {
                        Log("Vnode %u: link count incorrect (was %d, %s %d)\n", vnodeNumber, oldCount, (Testing ? "would have changed to" : "now"), vnode.linkCount);
                    }
+               } else {
+                   vnode.modeBits = vnp->modeBits;
                }
 
                vnode.dataVersion++;
@@ -3545,6 +4026,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 */
@@ -3571,7 +4075,7 @@ void
 ClearROInUseBit(struct VolumeSummary *summary)
 {
     IHandle_t *h = summary->volumeInfoHandle;
-    int nBytes;
+    afs_sfsize_t nBytes;
 
     VolumeDiskData volHeader;
 
@@ -3583,7 +4087,7 @@ ClearROInUseBit(struct VolumeSummary *summary)
     volHeader.inService = 1;
     volHeader.dontSalvage = DONT_SALVAGE;
     if (!Testing) {
-       nBytes = IH_IREAD(h, 0, (char *)&volHeader, sizeof(volHeader));
+       nBytes = IH_IWRITE(h, 0, (char *)&volHeader, sizeof(volHeader));
        assert(nBytes == sizeof(volHeader));
     }
 }
@@ -3610,8 +4114,25 @@ MaybeZapVolume(register struct InodeSummary *isp, char *message, int deleteMe,
                if (!Showmode)
                    Log("it will be deleted instead.  It should be recloned.\n");
            }
-           if (!Testing)
-               unlink(isp->volSummary->fileName);
+           if (!Testing) {
+               afs_int32 code;
+               char path[64];
+               sprintf(path, "%s/%s", fileSysPath, isp->volSummary->fileName);
+
+               code = VDestroyVolumeDiskHeader(fileSysPartition, isp->volumeId, isp->RWvolumeId);
+               if (code) {
+                   Log("Error %ld destroying volume disk header for volume %lu\n",
+                       afs_printable_int32_ld(code),
+                       afs_printable_uint32_lu(isp->volumeId));
+               }
+
+               /* make sure we actually delete the fileName file; ENOENT
+                * is fine, since VDestroyVolumeDiskHeader probably already
+                * unlinked it */
+               if (unlink(path) && errno != ENOENT) {
+                   Log("Unable to unlink %s (errno = %d)\n", path, errno);
+               }
+           }
        }
     } else if (!check) {
        Log("%s salvage was unsuccessful: read-write volume %u\n", message,
@@ -3620,12 +4141,132 @@ 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)
+AskOffline(VolumeId volumeId, char * partition)
 {
-    if (FSYNC_askfs(volumeId, NULL, FSYNC_OFF, FSYNC_SALVAGE) == FSYNC_DENIED) {
-       Log("AskOffline:  file server denied offline request; a general salvage is required.\n");
+    afs_int32 code, i;
+    SYNC_response res;
+
+    memset(&res, 0, sizeof(res));
+
+    for (i = 0; i < 3; i++) {
+       code = FSYNC_VolOp(volumeId, partition, FSYNC_VOL_OFF, FSYNC_SALVAGE, &res);
+
+       if (code == SYNC_OK) {
+           break;
+       } else if (code == SYNC_DENIED) {
+#ifdef DEMAND_ATTACH_ENABLE
+           Log("AskOffline:  file server denied offline request; a general salvage may be required.\n");
+#else
+           Log("AskOffline:  file server denied offline request; a general salvage is required.\n");
+#endif
+           Abort("Salvage aborted\n");
+       } else if (code == SYNC_BAD_COMMAND) {
+           Log("AskOffline:  fssync protocol mismatch (bad command word '%d'); salvage aborting.\n",
+               FSYNC_VOL_OFF);
+#ifdef DEMAND_ATTACH_ENABLE
+           Log("AskOffline:  please make sure fileserver, volserver, salvageserver and salvager binaries are same version.\n");
+#else
+           Log("AskOffline:  please make sure fileserver, volserver and salvager binaries are same version.\n");
+#endif
+           Abort("Salvage aborted\n");
+       } else if (i < 2) {
+           /* try it again */
+           Log("AskOffline:  request for fileserver to take volume offline failed; trying again...\n");
+           FSYNC_clientFinis();
+           FSYNC_clientInit();
+       }
+    }
+    if (code != SYNC_OK) {
+       Log("AskOffline:  request for fileserver to take volume offline failed; salvage aborting.\n");
        Abort("Salvage aborted\n");
     }
 }
@@ -3633,8 +4274,30 @@ AskOffline(VolumeId volumeId)
 void
 AskOnline(VolumeId volumeId, char *partition)
 {
-    if (FSYNC_askfs(volumeId, partition, FSYNC_ON, 0) == FSYNC_DENIED) {
-       Log("AskOnline:  file server denied online request to volume %u partition %s\n", volumeId, partition);
+    afs_int32 code, i;
+
+    for (i = 0; i < 3; i++) {
+       code = FSYNC_VolOp(volumeId, partition, FSYNC_VOL_ON, FSYNC_WHATEVER, NULL);
+
+       if (code == SYNC_OK) {
+           break;
+       } else if (code == SYNC_DENIED) {
+           Log("AskOnline:  file server denied online request to volume %u partition %s; trying again...\n", volumeId, partition);
+       } else if (code == SYNC_BAD_COMMAND) {
+           Log("AskOnline:  fssync protocol mismatch (bad command word '%d')\n",
+               FSYNC_VOL_ON);
+#ifdef DEMAND_ATTACH_ENABLE
+           Log("AskOnline:  please make sure fileserver, volserver, salvageserver and salvager binaries are same version.\n");
+#else
+           Log("AskOnline:  please make sure fileserver, volserver and salvager binaries are same version.\n");
+#endif
+           break;
+       } else if (i < 2) {
+           /* try it again */
+           Log("AskOnline:  request for fileserver to take volume offline failed; trying again...\n");
+           FSYNC_clientFinis();
+           FSYNC_clientInit();
+       }
     }
 }
 
@@ -3647,17 +4310,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);
@@ -3671,7 +4333,7 @@ PrintInodeList(void)
     register struct ViceInodeInfo *ip;
     struct ViceInodeInfo *buf;
     struct afs_stat status;
-    register nInodes;
+    register int nInodes;
 
     assert(afs_fstat(inodeFd, &status) == 0);
     buf = (struct ViceInodeInfo *)malloc(status.st_size);
@@ -3720,16 +4382,38 @@ Fork(void)
 #else
     f = fork();
     assert(f >= 0);
+#ifdef AFS_DEMAND_ATTACH_FS
+    if ((f == 0) && (programType == salvageServer)) {
+       /* we are a salvageserver child */
+#ifdef FSSYNC_BUILD_CLIENT
+       VChildProcReconnectFS_r();
+#endif
+#ifdef SALVSYNC_BUILD_CLIENT
+       VReconnectSALV_r();
 #endif
+    }
+#endif /* AFS_DEMAND_ATTACH_FS */
+#endif /* !AFS_NT40_ENV */
     return f;
 }
 
 void
-Exit(code)
-     int code;
+Exit(int code)
 {
     if (ShowLog)
        showlog();
+
+#ifdef AFS_DEMAND_ATTACH_FS
+    if (programType == salvageServer) {
+#ifdef SALVSYNC_BUILD_CLIENT
+       VDisconnectSALV();
+#endif
+#ifdef FSSYNC_BUILD_CLIENT
+       VDisconnectFS();
+#endif
+    }
+#endif /* AFS_DEMAND_ATTACH_FS */
+
 #ifdef AFS_NT40_ENV
     if (main_thread != pthread_self())
        pthread_exit((void *)code);
@@ -3761,14 +4445,14 @@ TimeStamp(time_t clock, int precision)
     static char timestamp[20];
     lt = localtime(&clock);
     if (precision)
-       (void)strftime(timestamp, 20, "%m/%d/%Y %T", lt);
+       (void)strftime(timestamp, 20, "%m/%d/%Y %H:%M:%S", lt);
     else
        (void)strftime(timestamp, 20, "%m/%d/%Y %H:%M", lt);
     return timestamp;
 }
 
 void
-CheckLogFile(void)
+CheckLogFile(char * log_path)
 {
     char oldSlvgLog[AFSDIR_PATH_MAX];
 
@@ -3779,11 +4463,11 @@ CheckLogFile(void)
     }
 #endif
 
-    strcpy(oldSlvgLog, AFSDIR_SERVER_SLVGLOG_FILEPATH);
+    strcpy(oldSlvgLog, log_path);
     strcat(oldSlvgLog, ".old");
     if (!logFile) {
-       renamefile(AFSDIR_SERVER_SLVGLOG_FILEPATH, oldSlvgLog);
-       logFile = afs_fopen(AFSDIR_SERVER_SLVGLOG_FILEPATH, "a");
+       renamefile(log_path, oldSlvgLog);
+       logFile = afs_fopen(log_path, "a");
 
        if (!logFile) {         /* still nothing, use stdout */
            logFile = stdout;
@@ -3797,7 +4481,7 @@ CheckLogFile(void)
 
 #ifndef AFS_NT40_ENV
 void
-TimeStampLogFile(void)
+TimeStampLogFile(char * log_path)
 {
     char stampSlvgLog[AFSDIR_PATH_MAX];
     struct tm *lt;
@@ -3807,13 +4491,13 @@ TimeStampLogFile(void)
     lt = localtime(&now);
     (void)afs_snprintf(stampSlvgLog, sizeof stampSlvgLog,
                       "%s.%04d-%02d-%02d.%02d:%02d:%02d",
-                      AFSDIR_SERVER_SLVGLOG_FILEPATH, lt->tm_year + 1900,
+                      log_path, lt->tm_year + 1900,
                       lt->tm_mon + 1, lt->tm_mday, lt->tm_hour, lt->tm_min,
                       lt->tm_sec);
 
     /* try to link the logfile to a timestamped filename */
     /* if it fails, oh well, nothing we can do */
-    link(AFSDIR_SERVER_SLVGLOG_FILEPATH, stampSlvgLog);
+    link(log_path, stampSlvgLog);
 }
 #endif
 
@@ -3830,8 +4514,10 @@ showlog(void)
     }
 #endif
 
-    rewind(logFile);
-    fclose(logFile);
+    if (logFile) {
+       rewind(logFile);
+       fclose(logFile);
+    }
 
     logFile = afs_fopen(AFSDIR_SERVER_SLVGLOG_FILEPATH, "r");
 
@@ -3860,11 +4546,11 @@ Log(const char *format, ...)
        syslog(LOG_INFO, "%s", tmp);
     } else
 #endif
-    {
-       gettimeofday(&now, 0);
-       fprintf(logFile, "%s %s", TimeStamp(now.tv_sec, 1), tmp);
-       fflush(logFile);
-    }
+       if (logFile) {
+           gettimeofday(&now, 0);
+           fprintf(logFile, "%s %s", TimeStamp(now.tv_sec, 1), tmp);
+           fflush(logFile);
+       }
 }
 
 void
@@ -3881,12 +4567,12 @@ Abort(const char *format, ...)
        syslog(LOG_INFO, "%s", tmp);
     } else
 #endif
-    {
-       fprintf(logFile, "%s", tmp);
-       fflush(logFile);
-       if (ShowLog)
-           showlog();
-    }
+       if (logFile) {
+           fprintf(logFile, "%s", tmp);
+           fflush(logFile);
+           if (ShowLog)
+               showlog();
+       }
 
     if (debug)
        abort();
@@ -3894,23 +4580,25 @@ Abort(const char *format, ...)
 }
 
 char *
-ToString(char *s)
+ToString(const char *s)
 {
     register char *p;
     p = (char *)malloc(strlen(s) + 1);
     assert(p != NULL);
     strcpy(p, s);
     return p;
-
 }
 
 /* Remove the FORCESALVAGE file */
 void
 RemoveTheForce(char *path)
 {
+    char target[1024];
+    struct afs_stat force; /* so we can use afs_stat to find it */
+    strcpy(target,path);
+    strcat(target,"/FORCESALVAGE");
     if (!Testing && ForceSalvage) {
-       if (chdir(path) == 0)
-           unlink("FORCESALVAGE");
+        if (afs_stat(target,&force) == 0)  unlink(target);
     }
 }
 
@@ -3922,10 +4610,11 @@ int
 UseTheForceLuke(char *path)
 {
     struct afs_stat force;
+    char target[1024];
+    strcpy(target,path);
+    strcat(target,"/FORCESALVAGE");
 
-    assert(chdir(path) != -1);
-
-    return (afs_stat("FORCESALVAGE", &force) == 0);
+    return (afs_stat(target, &force) == 0);
 }
 #else
 /*
@@ -3933,7 +4622,7 @@ UseTheForceLuke(char *path)
  *
  * NOTE:
  *     The VRMIX fsck will not muck with the filesystem it is supposedly
- *     fixing and create a "FORCESAVAGE" file (by design).  Instead, we
+ *     fixing and create a "FORCESALVAGE" file (by design).  Instead, we
  *     muck directly with the root inode, which is within the normal
  *     domain of fsck.
  *     ListViceInodes() has a side effect of setting ForceSalvage if