salvageserver: unable to write child log: out of memory
[openafs.git] / src / vol / salvaged.c
index 1e6db8b..753fc18 100644 (file)
@@ -1,13 +1,13 @@
 /*
- * Copyright 2006, Sine Nomine Associates and others.
+ * Copyright 2006-2007, Sine Nomine Associates and others.
  * All Rights Reserved.
- * 
+ *
  * This software has been released under the terms of the IBM Public
  * License.  For details, see the LICENSE file in the top-level source
  * directory or online at http://www.openafs.org/dl/license10.html
  */
 
-/* 
+/*
  * demand attach fs
  * online salvager daemon
  */
 #include <afsconfig.h>
 #include <afs/param.h>
 
-RCSID
-    ("$Header$");
+#include <roken.h>
+
+#ifdef HAVE_SYS_FILE_H
+#include <sys/file.h>
+#endif
 
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <dirent.h>
-#include <sys/stat.h>
-#include <time.h>
-#include <errno.h>
 #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/opr.h>
+#include <opr/lock.h>
 #include <afs/afsint.h>
-#include <afs/assert.h>
+#include <rx/rx_queue.h>
+
 #if !defined(AFS_SGI_ENV) && !defined(AFS_NT40_ENV)
 #if defined(AFS_VFSINCL_ENV)
 #include <sys/vnode.h>
@@ -61,7 +54,7 @@ RCSID
 #ifdef AFS_OSF_ENV
 #include <ufs/inode.h>
 #else /* AFS_OSF_ENV */
-#if !defined(AFS_LINUX20_ENV) && !defined(AFS_XBSD_ENV)
+#if !defined(AFS_LINUX20_ENV) && !defined(AFS_XBSD_ENV) && !defined(AFS_DARWIN_ENV)
 #include <sys/inode.h>
 #endif
 #endif
@@ -72,17 +65,13 @@ RCSID
 #include <sys/lockf.h>
 #else
 #ifdef AFS_HPUX_ENV
-#include <unistd.h>
 #include <checklist.h>
 #else
 #if defined(AFS_SGI_ENV)
-#include <unistd.h>
-#include <fcntl.h>
 #include <mntent.h>
 #else
 #if    defined(AFS_SUN_ENV) || defined(AFS_SUN5_ENV)
 #ifdef   AFS_SUN5_ENV
-#include <unistd.h>
 #include <sys/mnttab.h>
 #include <sys/mntent.h>
 #else
@@ -93,7 +82,6 @@ RCSID
 #endif /* AFS_HPUX_ENV */
 #endif
 #endif
-#include <fcntl.h>
 #ifndef AFS_NT40_ENV
 #include <afs/osi_inode.h>
 #endif
@@ -101,9 +89,7 @@ RCSID
 #include <afs/afsutil.h>
 #include <afs/fileutil.h>
 #include <afs/procmgmt.h>      /* signal(), kill(), wait(), etc. */
-#ifndef AFS_NT40_ENV
-#include <syslog.h>
-#endif
+#include <afs/dir.h>
 
 #include "nfs.h"
 #include "lwp.h"
@@ -118,12 +104,13 @@ RCSID
 #include "salvsync.h"
 #include "viceinode.h"
 #include "salvage.h"
-#include "volinodes.h"         /* header magic number, etc. stuff */
 #include "vol-salvage.h"
+#include "common.h"
 #ifdef AFS_NT40_ENV
 #include <pthread.h>
 #endif
 
+extern int ClientMode;
 
 #if !defined(AFS_DEMAND_ATTACH_FS)
 #error "online salvager only supported for demand attach fileserver"
@@ -133,12 +120,6 @@ RCSID
 #error "online salvager not supported on NT"
 #endif /* AFS_NT40_ENV */
 
-
-/* Forward declarations */
-/*@printflike@*/ void Log(const char *format, ...);
-/*@printflike@*/ void Abort(const char *format, ...);
-
-
 /*@+fcnmacros +macrofcndecl@*/
 #ifdef O_LARGEFILE
 #define afs_fopen      fopen64
@@ -157,13 +138,21 @@ static pthread_cond_t worker_cv;
 static void * SalvageChildReaperThread(void *);
 static int DoSalvageVolume(struct SalvageQueueNode * node, int slot);
 
-static void SalvageServer(void);
+static void SalvageServer(int argc, char **argv, struct logOptions *logopts);
 static void SalvageClient(VolumeId vid, char * pname);
 
 static int Reap_Child(char * prog, int * pid, int * status);
 
 static void * SalvageLogCleanupThread(void *);
-static int SalvageLogCleanup(int pid);
+static void SalvageLogCleanup(int pid);
+
+static void * SalvageLogScanningThread(void *);
+static void ScanLogs(struct rx_queue *log_watch_queue);
+
+struct cmdline_rock {
+    int argc;
+    char **argv;
+};
 
 struct log_cleanup_node {
     struct rx_queue q;
@@ -178,14 +167,37 @@ struct {
 
 #define DEFAULT_PARALLELISM 4 /* allow 4 parallel salvage workers by default */
 
+enum optionsList {
+    OPT_partition,
+    OPT_volumeid,
+    OPT_debug,
+    OPT_nowrite,
+    OPT_inodes,
+    OPT_oktozap,
+    OPT_rootinodes,
+    OPT_salvagedirs,
+    OPT_blockreads,
+    OPT_parallel,
+    OPT_tmpdir,
+    OPT_orphans,
+    OPT_syslog,
+    OPT_syslogfacility,
+    OPT_logfile,
+    OPT_client,
+    OPT_transarc_logs
+};
+
 static int
-handleit(struct cmd_syndesc *as, void *arock)
+handleit(struct cmd_syndesc *opts, void *arock)
 {
-    register struct cmd_item *ti;
-    char pname[100], *temp;
-    afs_int32 seenpart = 0, seenvol = 0, vid = 0, seenany = 0;
-    struct DiskPartition *partP;
+    char pname[100];
+    afs_int32 seenpart = 0, seenvol = 0;
+    VolumeId vid = 0;
+    struct cmdline_rock *rock = (struct cmdline_rock *)arock;
+    char *optstring = NULL;
+    struct logOptions logopts;
 
+    memset(&logopts, 0, sizeof(logopts));
 
 #ifdef AFS_SGI_VNODE_GLUE
     if (afs_init_kernel_config(-1) < 0) {
@@ -195,26 +207,18 @@ handleit(struct cmd_syndesc *as, void *arock)
     }
 #endif
 
-    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)    /* -oktozap */
-       OKToZap = 1;
-    if (as->parms[6].items)    /* -rootinodes */
-       ShowRootFiles = 1;
-    if (as->parms[8].items)    /* -ForceReads */
-       forceR = 1;
-    if ((ti = as->parms[9].items)) {   /* -Parallel # */
-       temp = ti->data;
-       if (strncmp(temp, "all", 3) == 0) {
+    cmd_OptionAsFlag(opts, OPT_debug, &debug);
+    cmd_OptionAsFlag(opts, OPT_nowrite, &Testing);
+    cmd_OptionAsFlag(opts, OPT_inodes, &ListInodeOption);
+    cmd_OptionAsFlag(opts, OPT_oktozap, &OKToZap);
+    cmd_OptionAsFlag(opts, OPT_rootinodes, &ShowRootFiles);
+    cmd_OptionAsFlag(opts, OPT_blockreads, &forceR);
+    if (cmd_OptionAsString(opts, OPT_parallel, &optstring) == 0) {
+       if (strncmp(optstring, "all", 3) == 0) {
            PartsPerDisk = 1;
-           temp += 3;
        }
-       if (strlen(temp) != 0) {
-           Parallel = atoi(temp);
+       if (strlen(optstring) != 0) {
+           Parallel = atoi(optstring);
            if (Parallel < 1)
                Parallel = 1;
            if (Parallel > MAXPARALLEL) {
@@ -223,56 +227,82 @@ handleit(struct cmd_syndesc *as, void *arock)
                Parallel = MAXPARALLEL;
            }
        }
+       free(optstring);
+       optstring = NULL;
     } else {
-       Parallel = MIN(DEFAULT_PARALLELISM, MAXPARALLEL);
+       Parallel = min(DEFAULT_PARALLELISM, MAXPARALLEL);
     }
-    if ((ti = as->parms[10].items)) {  /* -tmpdir */
+    if (cmd_OptionAsString(opts, OPT_tmpdir, &optstring) == 0) {
        DIR *dirp;
-
-       tmpdir = ti->data;
-       dirp = opendir(tmpdir);
+       dirp = opendir(optstring);
        if (!dirp) {
            printf
                ("Can't open temporary placeholder dir %s; using current partition \n",
-                tmpdir);
+                optstring);
            tmpdir = NULL;
        } else
            closedir(dirp);
+       free(optstring);
+       optstring = NULL;
     }
-    if ((ti = as->parms[11].items))    /* -showlog */
-       ShowLog = 1;
-    if ((ti = as->parms[12].items)) {  /* -orphans */
+    if (cmd_OptionAsString(opts, OPT_orphans, &optstring) == 0) {
        if (Testing)
            orphans = ORPH_IGNORE;
-       else if (strcmp(ti->data, "remove") == 0
-                || strcmp(ti->data, "r") == 0)
+       else if (strcmp(optstring, "remove") == 0
+                || strcmp(optstring, "r") == 0)
            orphans = ORPH_REMOVE;
-       else if (strcmp(ti->data, "attach") == 0
-                || strcmp(ti->data, "a") == 0)
+       else if (strcmp(optstring, "attach") == 0
+                || strcmp(optstring, "a") == 0)
            orphans = ORPH_ATTACH;
-    }
-#ifndef AFS_NT40_ENV           /* ignore options on NT */
-    if ((ti = as->parms[13].items)) {  /* -syslog */
-       useSyslog = 1;
-       ShowLog = 0;
-    }
-    if ((ti = as->parms[14].items)) {  /* -syslogfacility */
-       useSyslogFacility = atoi(ti->data);
+       free(optstring);
+       optstring = NULL;
     }
 
-    if ((ti = as->parms[15].items)) {  /* -datelogs */
-       TimeStampLogFile(AFSDIR_SERVER_SALSRVLOG_FILEPATH);
-    }
+#ifdef HAVE_SYSLOG
+    if (cmd_OptionPresent(opts, OPT_syslog)) {
+       if (cmd_OptionPresent(opts, OPT_logfile)) {
+           fprintf(stderr, "Invalid options: -syslog and -logfile are exclusive.\n");
+           return -1;
+       }
+       if (cmd_OptionPresent(opts, OPT_transarc_logs)) {
+           fprintf(stderr, "Invalid options: -syslog and -transarc-logs are exclusive.\n");
+           return -1;
+       }
+       logopts.lopt_dest = logDest_syslog;
+       logopts.lopt_facility = LOG_DAEMON;
+       logopts.lopt_tag = "salvageserver";
+       cmd_OptionAsInt(opts, OPT_syslogfacility, &logopts.lopt_facility);
+    } else
 #endif
+    {
+       logopts.lopt_dest = logDest_file;
+       if (cmd_OptionPresent(opts, OPT_transarc_logs)) {
+           logopts.lopt_rotateOnOpen = 1;
+           logopts.lopt_rotateStyle = logRotate_old;
+       }
+       if (cmd_OptionPresent(opts, OPT_logfile))
+           cmd_OptionAsString(opts, OPT_logfile, (char**)&logopts.lopt_filename);
+       else
+           logopts.lopt_filename = AFSDIR_SERVER_SALSRVLOG_FILEPATH;
+    }
 
-    if ((ti = as->parms[16].items)) {   /* -client */
-       if ((ti = as->parms[0].items)) {        /* -partition */
+    if (cmd_OptionPresent(opts, OPT_client)) {
+       if (cmd_OptionAsString(opts, OPT_partition, &optstring) == 0) {
            seenpart = 1;
-           strlcpy(pname, ti->data, sizeof(pname));
+           strlcpy(pname, optstring, sizeof(pname));
+           free(optstring);
+           optstring = NULL;
        }
-       if ((ti = as->parms[1].items)) {        /* -volumeid */
+       if (cmd_OptionAsString(opts, OPT_volumeid, &optstring) == 0) {
+           char *end;
+           unsigned long vid_l;
            seenvol = 1;
-           vid = atoi(ti->data);
+           vid_l = strtoul(optstring, &end, 10);
+           if (vid_l >= MAX_AFS_UINT32 || vid_l == ULONG_MAX || *end != '\0') {
+               printf("Invalid volume id specified; salvage aborted\n");
+               exit(-1);
+           }
+           vid = (VolumeId)vid_l;
        }
 
        if (!seenpart || !seenvol) {
@@ -283,7 +313,7 @@ handleit(struct cmd_syndesc *as, void *arock)
        SalvageClient(vid, pname);
 
     } else {  /* salvageserver mode */
-       SalvageServer();
+       SalvageServer(rock->argc, rock->argv, &logopts);
     }
     return (0);
 }
@@ -299,21 +329,17 @@ int n_save_args = 0;
 pthread_t main_thread;
 #endif
 
-static char commandLine[150];
-
 int
 main(int argc, char **argv)
 {
     struct cmd_syndesc *ts;
     int err = 0;
-
-    int i;
-    extern char cml_version_number[];
+    struct cmdline_rock arock;
 
 #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 
+     * 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.
      */
@@ -336,6 +362,9 @@ main(int argc, char **argv)
        exit(2);
     }
 #ifdef AFS_NT40_ENV
+    /* Default to binary mode for fopen() */
+    _set_fmode(_O_BINARY);
+
     main_thread = pthread_self();
     if (spawnDatap && spawnDataLen) {
        /* This is a child per partition salvager. Don't setup log or
@@ -345,11 +374,6 @@ main(int argc, char **argv)
            exit(3);
     } else {
 #endif
-       for (commandLine[0] = '\0', i = 0; i < argc; i++) {
-           if (i > 0)
-               strlcat(commandLine, " ", sizeof(commandLine));
-           strlcat(commandLine, argv[i], sizeof(commandLine));
-       }
 
 #ifndef AFS_NT40_ENV
        if (geteuid() != 0) {
@@ -365,48 +389,55 @@ main(int argc, char **argv)
     }
 #endif
 
-    ts = cmd_CreateSyntax("initcmd", handleit, NULL, "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, "-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, "-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");
-
-    cmd_AddParm(ts, "-client", CMD_FLAG, CMD_OPTIONAL,
+    arock.argc = argc;
+    arock.argv = argv;
+
+
+    ts = cmd_CreateSyntax("initcmd", handleit, &arock, 0, "initialize the program");
+    cmd_AddParmAtOffset(ts, OPT_partition, "-partition", CMD_SINGLE,
+           CMD_OPTIONAL, "Name of partition to salvage");
+    cmd_AddParmAtOffset(ts, OPT_volumeid, "-volumeid", CMD_SINGLE, CMD_OPTIONAL,
+           "Volume Id to salvage");
+    cmd_AddParmAtOffset(ts, OPT_debug, "-debug", CMD_FLAG, CMD_OPTIONAL,
+           "Run in Debugging mode");
+    cmd_AddParmAtOffset(ts, OPT_nowrite, "-nowrite", CMD_FLAG, CMD_OPTIONAL,
+           "Run readonly/test mode");
+    cmd_AddParmAtOffset(ts, OPT_inodes, "-inodes", CMD_FLAG, CMD_OPTIONAL,
+           "Just list affected afs inodes - debugging flag");
+    cmd_AddParmAtOffset(ts, OPT_oktozap, "-oktozap", CMD_FLAG, CMD_OPTIONAL,
+           "Give permission to destroy bogus inodes/volumes - debugging flag");
+    cmd_AddParmAtOffset(ts, OPT_rootinodes, "-rootinodes", CMD_FLAG,
+           CMD_OPTIONAL, "Show inodes owned by root - debugging flag");
+    cmd_AddParmAtOffset(ts, OPT_salvagedirs, "-salvagedirs", CMD_FLAG,
+           CMD_OPTIONAL, "Force rebuild/salvage of all directories");
+    cmd_AddParmAtOffset(ts, OPT_blockreads, "-blockreads", CMD_FLAG,
+           CMD_OPTIONAL, "Read smaller blocks to handle IO/bad blocks");
+    cmd_AddParmAtOffset(ts, OPT_parallel, "-parallel", CMD_SINGLE, CMD_OPTIONAL,
+           "# of max parallel partition salvaging");
+    cmd_AddParmAtOffset(ts, OPT_tmpdir, "-tmpdir", CMD_SINGLE, CMD_OPTIONAL,
+           "Name of dir to place tmp files ");
+    cmd_AddParmAtOffset(ts, OPT_orphans, "-orphans", CMD_SINGLE, CMD_OPTIONAL,
+           "ignore | remove | attach");
+
+#ifdef HAVE_SYSLOG
+    cmd_AddParmAtOffset(ts, OPT_syslog, "-syslog", CMD_FLAG, CMD_OPTIONAL,
+           "Write salvage log to syslogs");
+    cmd_AddParmAtOffset(ts, OPT_syslogfacility, "-syslogfacility", CMD_SINGLE,
+           CMD_OPTIONAL, "Syslog facility number to use");
+#endif
+
+    cmd_AddParmAtOffset(ts, OPT_client, "-client", CMD_FLAG, CMD_OPTIONAL,
                "Use SALVSYNC to ask salvageserver to salvage a volume");
 
+    cmd_AddParmAtOffset(ts, OPT_logfile, "-logfile", CMD_SINGLE, CMD_OPTIONAL,
+           "Location of log file ");
+
+    cmd_AddParmAtOffset(ts, OPT_transarc_logs, "-transarc-logs", CMD_FLAG,
+                       CMD_OPTIONAL, "enable Transarc style logging");
+
     err = cmd_Dispatch(argc, argv);
     Exit(err);
+    return 0; /* not reached */
 }
 
 static void
@@ -416,10 +447,21 @@ SalvageClient(VolumeId vid, char * pname)
     afs_int32 code;
     SYNC_response res;
     SALVSYNC_response_hdr sres;
-
-    VInitVolumePackage(volumeUtility, 5, 5, DONT_CONNECT_FS, 0);
+    VolumePackageOptions opts;
+
+    /* Send Log() messages to stderr in client mode. */
+    ClientMode = 1;
+
+    VOptDefaults(volumeUtility, &opts);
+    if (VInitVolumePackage2(volumeUtility, &opts)) {
+       /* VInitVolumePackage2 can fail on e.g. partition attachment errors,
+        * but we don't really care, since all we're doing is trying to use
+        * SALVSYNC */
+       fprintf(stderr, "errors encountered initializing volume package, but "
+                       "trying to continue anyway\n");
+    }
     SALVSYNC_clientInit();
-    
+
     code = SALVSYNC_SalvageVolume(vid, pname, SALVSYNC_SALVAGE, SALVSYNC_OPERATOR, 0, NULL);
     if (code != SYNC_OK) {
        goto sync_error;
@@ -461,73 +503,70 @@ SalvageClient(VolumeId vid, char * pname)
 static int * child_slot;
 
 static void
-SalvageServer(void)
+SalvageServer(int argc, char **argv, struct logOptions *logopts)
 {
     int pid, ret;
     struct SalvageQueueNode * node;
     pthread_t tid;
     pthread_attr_t attrs;
     int slot;
+    VolumePackageOptions opts;
 
     /* All entries to the log will be appended.  Useful if there are
      * multiple salvagers appending to the log.
      */
+    OpenLog(logopts);
+    SetupLogSignals();
 
-    CheckLogFile(AFSDIR_SERVER_SALSRVLOG_FILEPATH);
-#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);
-
-    fprintf(logFile, "%s\n", cml_version_number);
-    Log("Starting OpenAFS Online Salvage Server %s (%s)\n", SalvageVersion, commandLine);
-    
+    Log("%s\n", cml_version_number);
+    LogCommandLine(argc, argv, "Online Salvage Server",
+                  SalvageVersion, "Starting OpenAFS", Log);
     /* 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
+     * VInitVolumePackage2 (called below) makes sure that a file server or
      * other volume utilities don't interfere with the salvage.
      */
-    
+
     /* even demand attach online salvager
      * still needs this because we don't want
      * a stand-alone salvager to conflict with
      * the salvager daemon */
-    ObtainSalvageLock();
+    ObtainSharedSalvageLock();
+
+    child_slot = calloc(Parallel, sizeof(int));
+    opr_Assert(child_slot != NULL);
 
-    child_slot = (int *) malloc(Parallel * sizeof(int));
-    assert(child_slot != NULL);
-    memset(child_slot, 0, Parallel * sizeof(int));
-           
     /* initialize things */
-    VInitVolumePackage(salvageServer, 5, 5,
-                      1, 0);
+    VOptDefaults(salvageServer, &opts);
+    if (VInitVolumePackage2(salvageServer, &opts)) {
+       Log("Shutting down: errors encountered initializing volume package\n");
+       Exit(1);
+    }
     DInit(10);
     queue_Init(&pending_q);
     queue_Init(&log_cleanup_queue);
-    assert(pthread_mutex_init(&worker_lock, NULL) == 0);
-    assert(pthread_cond_init(&worker_cv, NULL) == 0);
-    assert(pthread_cond_init(&log_cleanup_queue.queue_change_cv, NULL) == 0);
-    assert(pthread_attr_init(&attrs) == 0);
+    opr_mutex_init(&worker_lock);
+    opr_cv_init(&worker_cv);
+    opr_cv_init(&log_cleanup_queue.queue_change_cv);
+    opr_Verify(pthread_attr_init(&attrs) == 0);
 
     /* start up the reaper and log cleaner threads */
-    assert(pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED) == 0);
-    assert(pthread_create(&tid, 
-                         &attrs, 
-                         &SalvageChildReaperThread,
-                         NULL) == 0);
-    assert(pthread_create(&tid, 
-                         &attrs, 
-                         &SalvageLogCleanupThread,
-                         NULL) == 0);
+    opr_Verify(pthread_attr_setdetachstate(&attrs,
+                                          PTHREAD_CREATE_DETACHED) == 0);
+    opr_Verify(pthread_create(&tid, &attrs,
+                             &SalvageChildReaperThread, NULL) == 0);
+    opr_Verify(pthread_create(&tid, &attrs,
+                             &SalvageLogCleanupThread, NULL) == 0);
+    opr_Verify(pthread_create(&tid, &attrs,
+                             &SalvageLogScanningThread, NULL) == 0);
 
     /* loop forever serving requests */
     while (1) {
        node = SALVSYNC_getWork();
-       assert(node != NULL);
+       opr_Assert(node != NULL);
+
+       Log("dispatching child to salvage volume %u...\n",
+           node->command.sop.parent);
 
        VOL_LOCK;
        /* find a slot */
@@ -535,32 +574,34 @@ SalvageServer(void)
          if (!child_slot[slot])
            break;
        }
-       assert (slot < Parallel);
+       opr_Assert (slot < Parallel);
 
+    do_fork:
        pid = Fork();
        if (pid == 0) {
            VOL_UNLOCK;
            ret = DoSalvageVolume(node, slot);
            Exit(ret);
        } else if (pid < 0) {
-           VOL_UNLOCK;
-           SALVSYNC_doneWork(node, 1);
+           Log("failed to fork child worker process\n");
+           sleep(1);
+           goto do_fork;
        } else {
            child_slot[slot] = pid;
            node->pid = pid;
            VOL_UNLOCK;
-           
-           assert(pthread_mutex_lock(&worker_lock) == 0);
+
+           opr_mutex_enter(&worker_lock);
            current_workers++;
-           
+
            /* let the reaper thread know another worker was spawned */
-           assert(pthread_cond_broadcast(&worker_cv) == 0);
-           
+           opr_cv_broadcast(&worker_cv);
+
            /* if we're overquota, wait for the reaper */
            while (current_workers >= Parallel) {
-               assert(pthread_cond_wait(&worker_cv, &worker_lock) == 0);
+               opr_cv_wait(&worker_cv, &worker_lock);
            }
-           assert(pthread_mutex_unlock(&worker_lock) == 0);
+           opr_mutex_exit(&worker_lock);
        }
     }
 }
@@ -568,43 +609,51 @@ SalvageServer(void)
 static int
 DoSalvageVolume(struct SalvageQueueNode * node, int slot)
 {
-    char childLog[AFSDIR_PATH_MAX];
-    int ret;
-    struct DiskPartition * partP;
-
-    VChildProcReconnectFS();
+    char *filename = NULL;
+    struct logOptions logopts;
+    struct DiskPartition64 * partP;
 
-    /* do not attempt to close parent's logFile handle as
-     * another thread may have held the lock on the FILE
-     * structure when fork was called! */
+    /* do not allow further forking inside salvager */
+    canfork = 0;
 
-    afs_snprintf(childLog, sizeof(childLog), "%s.%d", 
-                AFSDIR_SERVER_SLVGLOG_FILEPATH, getpid());
-
-    logFile = afs_fopen(childLog, "a");
-    if (!logFile) {            /* still nothing, use stdout */
-       logFile = stdout;
-       ShowLog = 0;
+    /*
+     * Do not attempt to close parent's log file handle as
+     * another thread may have held the lock when fork was
+     * called!
+     */
+    memset(&logopts, 0, sizeof(logopts));
+    logopts.lopt_dest = logDest_file;
+    logopts.lopt_rotateStyle = logRotate_none;
+    if (asprintf(&filename, "%s.%d",
+                AFSDIR_SERVER_SLVGLOG_FILEPATH, getpid()) < 0) {
+       fprintf(stderr, "out of memory\n");
+       return ENOMEM;
     }
+    logopts.lopt_filename = filename;
+    OpenLog(&logopts);
+    free(filename);
 
-    if (node->command.sop.volume <= 0) {
+    if (node->command.sop.parent <= 0) {
        Log("salvageServer: invalid volume id specified; salvage aborted\n");
        return 1;
     }
-    
+
     partP = VGetPartition(node->command.sop.partName, 0);
     if (!partP) {
-       Log("salvageServer: Unknown or unmounted partition %s; salvage aborted\n", 
+       Log("salvageServer: Unknown or unmounted partition %s; salvage aborted\n",
            node->command.sop.partName);
        return 1;
     }
 
-    /* Salvage individual volume; don't notify fs */
-    SalvageFileSys1(partP, node->command.sop.volume);
+    /* obtain a shared salvage lock in the child worker, so if the
+     * salvageserver restarts (and we continue), we will still hold a lock and
+     * prevent standalone salvagers from interfering */
+    ObtainSharedSalvageLock();
 
-    VDisconnectFS();
+    /* Salvage individual volume; don't notify fs */
+    SalvageFileSys1(partP, node->command.sop.parent);
 
-    fclose(logFile);
+    CloseLog();
     return 0;
 }
 
@@ -612,23 +661,22 @@ DoSalvageVolume(struct SalvageQueueNode * node, int slot)
 static void *
 SalvageChildReaperThread(void * args)
 {
-    int slot, pid, status, code, found;
-    struct SalvageQueueNode *qp, *nqp;
+    int slot, pid, status;
     struct log_cleanup_node * cleanup;
 
-    assert(pthread_mutex_lock(&worker_lock) == 0);
+    opr_mutex_enter(&worker_lock);
 
     /* loop reaping our children */
     while (1) {
        /* wait() won't block unless we have children, so
         * block on the cond var if we're childless */
        while (current_workers == 0) {
-           assert(pthread_cond_wait(&worker_cv, &worker_lock) == 0);
+           opr_cv_wait(&worker_cv, &worker_lock);
        }
 
-       assert(pthread_mutex_unlock(&worker_lock) == 0);
+       opr_mutex_exit(&worker_lock);
 
-       cleanup = (struct log_cleanup_node *) malloc(sizeof(struct log_cleanup_node));
+       cleanup = malloc(sizeof(struct log_cleanup_node));
 
        while (Reap_Child("salvageserver", &pid, &status) < 0) {
            /* try to prevent livelock if something goes wrong */
@@ -640,22 +688,23 @@ SalvageChildReaperThread(void * args)
            if (child_slot[slot] == pid)
                break;
        }
-       assert(slot < Parallel);
+       opr_Assert(slot < Parallel);
        child_slot[slot] = 0;
        VOL_UNLOCK;
 
-       assert(pthread_mutex_lock(&worker_lock) == 0);
+       SALVSYNC_doneWorkByPid(pid, status);
+
+       opr_mutex_enter(&worker_lock);
 
        if (cleanup) {
            cleanup->pid = pid;
            queue_Append(&log_cleanup_queue, cleanup);
-           assert(pthread_cond_signal(&log_cleanup_queue.queue_change_cv) == 0);
+           opr_cv_signal(&log_cleanup_queue.queue_change_cv);
        }
 
        /* ok, we've reaped a child */
        current_workers--;
-       SALVSYNC_doneWorkByPid(pid, 0);
-       assert(pthread_cond_broadcast(&worker_cv) == 0);
+       opr_cv_broadcast(&worker_cv);
     }
 
     return NULL;
@@ -671,7 +720,9 @@ Reap_Child(char *prog, int * pid, int * status)
        *pid = ret;
         if (WCOREDUMP(*status))
            Log("\"%s\" core dumped!\n", prog);
-       if (WIFSIGNALED(*status) != 0 || WEXITSTATUS(*status) != 0)
+       if ((WIFSIGNALED(*status) != 0) ||
+           ((WEXITSTATUS(*status) != 0) &&
+            (WEXITSTATUS(*status) != SALSRV_EXIT_VOLGROUP_LINK)))
            Log("\"%s\" (pid=%d) terminated abnormally!\n", prog, ret);
     } else {
        Log("wait returned -1\n");
@@ -688,51 +739,168 @@ SalvageLogCleanupThread(void * arg)
 {
     struct log_cleanup_node * cleanup;
 
-    assert(pthread_mutex_lock(&worker_lock) == 0);
+    opr_mutex_enter(&worker_lock);
 
     while (1) {
        while (queue_IsEmpty(&log_cleanup_queue)) {
-           assert(pthread_cond_wait(&log_cleanup_queue.queue_change_cv, &worker_lock) == 0);
+           opr_cv_wait(&log_cleanup_queue.queue_change_cv, &worker_lock);
        }
 
        while (queue_IsNotEmpty(&log_cleanup_queue)) {
            cleanup = queue_First(&log_cleanup_queue, log_cleanup_node);
            queue_Remove(cleanup);
-           assert(pthread_mutex_unlock(&worker_lock) == 0);
+           opr_mutex_exit(&worker_lock);
            SalvageLogCleanup(cleanup->pid);
            free(cleanup);
-           assert(pthread_mutex_lock(&worker_lock) == 0);
-       }           
+           opr_mutex_enter(&worker_lock);
+       }
     }
 
-    assert(pthread_mutex_unlock(&worker_lock) == 0);
+    opr_mutex_exit(&worker_lock);
     return NULL;
 }
 
 #define LOG_XFER_BUF_SIZE 65536
-static int
+static void
 SalvageLogCleanup(int pid)
 {
     int pidlog, len;
-    char fn[AFSDIR_PATH_MAX];
-    static char buf[LOG_XFER_BUF_SIZE];
+    char *fn = NULL;
+    char *buf = NULL;
 
-    afs_snprintf(fn, sizeof(fn), "%s.%d", 
-                AFSDIR_SERVER_SLVGLOG_FILEPATH, pid);
-    
+    if (asprintf(&fn, "%s.%d", AFSDIR_SERVER_SLVGLOG_FILEPATH, pid) < 0) {
+       Log("Unable to write child log: out of memory\n");
+       goto done;
+    }
+
+    buf = calloc(1, LOG_XFER_BUF_SIZE);
+    if (buf == NULL) {
+       Log("Unable to write child log: out of memory\n");
+       goto done;
+    }
 
     pidlog = open(fn, O_RDONLY);
     unlink(fn);
     if (pidlog < 0)
-       return 1;
+       goto done;
 
     len = read(pidlog, buf, LOG_XFER_BUF_SIZE);
     while (len) {
-       fwrite(buf, len, 1, logFile);
+       WriteLogBuffer(buf, len);
        len = read(pidlog, buf, LOG_XFER_BUF_SIZE);
     }
 
     close(pidlog);
 
-    return 0;
+ done:
+    free(fn);
+    free(buf);
+}
+
+/* wake up every five minutes to see if a non-child salvage has finished */
+#define SALVAGE_SCAN_POLL_INTERVAL 300
+
+/**
+ * Thread to look for SalvageLog.$pid files that are not from our child
+ * worker salvagers, and notify SalvageLogCleanupThread to clean them
+ * up. This can happen if we restart during salvages, or the
+ * salvageserver crashes or something.
+ *
+ * @param arg  unused
+ *
+ * @return always NULL
+ */
+static void *
+SalvageLogScanningThread(void * arg)
+{
+    struct rx_queue log_watch_queue;
+    char *prefix;
+    int prefix_len;
+
+    queue_Init(&log_watch_queue);
+
+    prefix_len = asprintf(&prefix, "%s.", AFSDIR_SLVGLOG_FILE);
+    if (prefix_len >= 0) {
+       DIR *dp;
+       struct dirent *dirp;
+
+       dp = opendir(AFSDIR_LOGS_DIR);
+       opr_Assert(dp);
+
+       while ((dirp = readdir(dp)) != NULL) {
+           pid_t pid;
+           struct log_cleanup_node *cleanup;
+           int i;
+
+           if (strncmp(dirp->d_name, prefix, prefix_len) != 0) {
+               /* not a salvage logfile; skip */
+               continue;
+           }
+
+           errno = 0;
+           pid = strtol(dirp->d_name + prefix_len, NULL, 10);
+
+           if (errno != 0) {
+               /* file is SalvageLog.<something> but <something> isn't
+                * a pid, so skip */
+                continue;
+           }
+
+           VOL_LOCK;
+           for (i = 0; i < Parallel; ++i) {
+               if (pid == child_slot[i]) {
+                   break;
+               }
+           }
+           VOL_UNLOCK;
+           if (i < Parallel) {
+               /* this pid is one of our children, so the reaper thread
+                * will take care of it; skip */
+               continue;
+           }
+
+           cleanup = malloc(sizeof(struct log_cleanup_node));
+           cleanup->pid = pid;
+
+           queue_Append(&log_watch_queue, cleanup);
+       }
+       free(prefix);
+       closedir(dp);
+    }
+
+    ScanLogs(&log_watch_queue);
+
+    while (queue_IsNotEmpty(&log_watch_queue)) {
+       sleep(SALVAGE_SCAN_POLL_INTERVAL);
+       ScanLogs(&log_watch_queue);
+    }
+
+    return NULL;
+}
+
+/**
+ * look through log_watch_queue, and if any processes are not still
+ * running, hand them off to the SalvageLogCleanupThread
+ *
+ * @param log_watch_queue  a queue of PIDs that we should clean up if
+ * that PID has died
+ */
+static void
+ScanLogs(struct rx_queue *log_watch_queue)
+{
+    struct log_cleanup_node *cleanup, *next;
+
+    opr_mutex_enter(&worker_lock);
+
+    for (queue_Scan(log_watch_queue, cleanup, next, log_cleanup_node)) {
+       /* if a process is still running, assume it's the salvage process
+        * still going, and keep waiting for it */
+       if (kill(cleanup->pid, 0) < 0 && errno == ESRCH) {
+           queue_Remove(cleanup);
+           queue_Append(&log_cleanup_queue, cleanup);
+           opr_cv_signal(&log_cleanup_queue.queue_change_cv);
+       }
+    }
+
+    opr_mutex_exit(&worker_lock);
 }