Imported Upstream version 3.2.0
[debian/amanda] / server-src / amflush.c
index f6a5df5d79315905ccc0d9fa73aec2481e9df0ea..4d28e44755118821311cffa1a934d9a8234fa689 100644 (file)
  * file named AUTHORS, in the root directory of this distribution.
  */
 /*
- * $Id: amflush.c,v 1.41.2.13.4.6.2.9.2.2 2004/10/20 21:49:26 martinea Exp $
+ * $Id: amflush.c,v 1.95 2006/07/25 21:41:24 martinea Exp $
  *
  * write files from work directory onto tape
  */
 #include "amanda.h"
 
+#include "match.h"
+#include "find.h"
 #include "conffile.h"
 #include "diskfile.h"
 #include "tapefile.h"
 #include "logfile.h"
 #include "clock.h"
-#include "version.h"
 #include "holding.h"
-#include "driverio.h"
 #include "server_util.h"
+#include "timestamp.h"
 
 static char *conf_logdir;
 FILE *driver_stream;
@@ -46,69 +47,82 @@ char *driver_program;
 char *reporter_program;
 char *logroll_program;
 char *datestamp;
-sl_t *datestamp_list;
+char *amflush_timestamp;
+char *amflush_datestamp;
 
 /* local functions */
-int main P((int main_argc, char **main_argv));
-void flush_holdingdisk P((char *diskdir, char *datestamp));
-void confirm P((void));
-void redirect_stderr P((void));
-void detach P((void));
-void run_dumps P((void));
-
-
-int main(main_argc, main_argv)
-int main_argc;
-char **main_argv;
+void flush_holdingdisk(char *diskdir, char *datestamp);
+static GSList * pick_datestamp(void);
+void confirm(GSList *datestamp_list);
+void redirect_stderr(void);
+void detach(void);
+void run_dumps(void);
+static int get_letter_from_user(void);
+
+int
+main(
+    int                argc,
+    char **    argv)
 {
     int foreground;
     int batch;
     int redirect;
-    struct passwd *pw;
-    char *dumpuser;
     char **datearg = NULL;
     int nb_datearg = 0;
-    int fd;
-    char *conffile;
     char *conf_diskfile;
     char *conf_tapelist;
     char *conf_logfile;
-    disklist_t *diskqp;
+    int conf_usetimestamps;
+    disklist_t diskq;
     disk_t *dp;
-    pid_t pid, driver_pid, reporter_pid;
+    pid_t pid;
+    pid_t driver_pid, reporter_pid;
     amwait_t exitcode;
     int opt;
-    dumpfile_t file;
-    sl_t *holding_list=NULL;
-    sle_t *holding_file;
+    GSList *holding_list=NULL, *holding_file;
     int driver_pipe[2];
     char date_string[100];
+    char date_string_standard[100];
     time_t today;
+    char *errstr;
+    struct tm *tm;
+    char *tapedev;
+    char *tpchanger;
+    char *qdisk, *qhname;
+    GSList *datestamp_list = NULL;
+    config_overrides_t *cfg_ovr;
+    char **config_options;
+    find_result_t *holding_files;
+    disklist_t holding_disklist = { NULL, NULL };
 
-    for(fd = 3; fd < FD_SETSIZE; fd++) {
-       /*
-        * Make sure nobody spoofs us with a lot of extra open files
-        * that would cause an open we do to get a very high file
-        * descriptor, which in turn might be used as an index into
-        * an array (e.g. an fd_set).
-        */
-       close(fd);
-    }
-
+    /*
+     * Configure program for internationalization:
+     *   1) Only set the message locale for now.
+     *   2) Set textdomain for all amanda related programs to "amanda"
+     *      We don't want to be forced to support dozens of message catalogs.
+     */  
+    setlocale(LC_MESSAGES, "C");
+    textdomain("amanda"); 
+
+    safe_fd(-1, 0);
     safe_cd();
 
     set_pname("amflush");
 
+    /* Don't die when child closes pipe */
     signal(SIGPIPE, SIG_IGN);
 
-    erroutput_type = ERR_INTERACTIVE;
+    dbopen(DBG_SUBDIR_SERVER);
+
+    add_amanda_log_handler(amanda_log_stderr);
     foreground = 0;
     batch = 0;
     redirect = 1;
 
     /* process arguments */
 
-    while((opt = getopt(main_argc, main_argv, "bfsD:")) != EOF) {
+    cfg_ovr = new_config_overrides(argc/2);
+    while((opt = getopt(argc, argv, "bfso:D:")) != EOF) {
        switch(opt) {
        case 'b': batch = 1;
                  break;
@@ -116,10 +130,12 @@ char **main_argv;
                  break;
        case 's': redirect = 0;
                  break;
+       case 'o': add_config_override_opt(cfg_ovr, optarg);
+                 break;
        case 'D': if (datearg == NULL)
-                       datearg = alloc(21*sizeof(char *));
+                     datearg = alloc(21*SIZEOF(char *));
                  if(nb_datearg == 20) {
-                     fprintf(stderr,"maximum of 20 -D arguments.\n");
+                     g_fprintf(stderr,_("maximum of 20 -D arguments.\n"));
                      exit(1);
                  }
                  datearg[nb_datearg++] = stralloc(optarg);
@@ -127,138 +143,180 @@ char **main_argv;
                  break;
        }
     }
+    argc -= optind, argv += optind;
+
     if(!foreground && !redirect) {
-       fprintf(stderr,"Can't redirect to stdout/stderr if not in forground.\n");
+       g_fprintf(stderr,_("Can't redirect to stdout/stderr if not in forground.\n"));
        exit(1);
     }
 
-    main_argc -= optind, main_argv += optind;
-
-    if(main_argc < 1) {
-       error("Usage: amflush%s [-b] [-f] [-s] [-D date]* <confdir> [host [disk]* ]*", versionsuffix());
+    if(argc < 1) {
+       error(_("Usage: amflush [-b] [-f] [-s] [-D date]* [-o configoption]* <confdir> [host [disk]* ]*"));
+       /*NOTREACHED*/
     }
 
-    config_name = main_argv[0];
+    set_config_overrides(cfg_ovr);
+    config_init(CONFIG_INIT_EXPLICIT_NAME,
+               argv[0]);
 
-    config_dir = vstralloc(CONFIG_DIR, "/", config_name, "/", NULL);
-    conffile = stralloc2(config_dir, CONFFILE_NAME);
-    if(read_conffile(conffile)) {
-       error("errors processing config file \"%s\"", conffile);
-    }
-    amfree(conffile);
-    conf_diskfile = getconf_str(CNF_DISKFILE);
-    if (*conf_diskfile == '/') {
-       conf_diskfile = stralloc(conf_diskfile);
-    } else {
-       conf_diskfile = stralloc2(config_dir, conf_diskfile);
-    }
-    if((diskqp = read_diskfile(conf_diskfile)) == NULL) {
-       error("could not read disklist file \"%s\"", conf_diskfile);
-    }
-    match_disklist(diskqp, main_argc-1, main_argv+1);
+    conf_diskfile = config_dir_relative(getconf_str(CNF_DISKFILE));
+    read_diskfile(conf_diskfile, &diskq);
     amfree(conf_diskfile);
-    conf_tapelist = getconf_str(CNF_TAPELIST);
-    if (*conf_tapelist == '/') {
-       conf_tapelist = stralloc(conf_tapelist);
-    } else {
-       conf_tapelist = stralloc2(config_dir, conf_tapelist);
+
+    if (config_errors(NULL) >= CFGERR_WARNINGS) {
+       config_print_errors();
+       if (config_errors(NULL) >= CFGERR_ERRORS) {
+           g_critical(_("errors processing config file"));
+       }
     }
+
+    check_running_as(RUNNING_AS_DUMPUSER);
+
+    dbrename(get_config_name(), DBG_SUBDIR_SERVER);
+
+    /* load DLEs from the holding disk, in case there's anything to flush there */
+    search_holding_disk(&holding_files, &holding_disklist);
+    /* note that the dumps are added to the global disklist, so we need not
+     * consult holding_files or holding_disklist after this.  The holding-only
+     * dumps will be filtered properly by match_disklist, setting the dp->todo
+     * flag appropriately. */
+
+    errstr = match_disklist(&diskq, argc-1, argv+1);
+    if (errstr) {
+       g_printf(_("%s"),errstr);
+       amfree(errstr);
+    }
+
+    conf_tapelist = config_dir_relative(getconf_str(CNF_TAPELIST));
     if(read_tapelist(conf_tapelist)) {
-       error("could not load tapelist \"%s\"", conf_tapelist);
+       error(_("could not load tapelist \"%s\""), conf_tapelist);
+       /*NOTREACHED*/
     }
     amfree(conf_tapelist);
 
-    datestamp = construct_datestamp(NULL);
+    conf_usetimestamps = getconf_boolean(CNF_USETIMESTAMPS);
 
-    dumpuser = getconf_str(CNF_DUMPUSER);
-    if((pw = getpwnam(dumpuser)) == NULL)
-       error("dumpuser %s not found in password file", dumpuser);
-    if(pw->pw_uid != getuid())
-       error("must run amflush as user %s", dumpuser);
-
-    conf_logdir = getconf_str(CNF_LOGDIR);
-    if (*conf_logdir == '/') {
-       conf_logdir = stralloc(conf_logdir);
-    } else {
-       conf_logdir = stralloc2(config_dir, conf_logdir);
+    amflush_datestamp = get_datestamp_from_time(0);
+    if(conf_usetimestamps == 0) {
+       amflush_timestamp = stralloc(amflush_datestamp);
     }
+    else {
+       amflush_timestamp = get_timestamp_from_time(0);
+    }
+
+    conf_logdir = config_dir_relative(getconf_str(CNF_LOGDIR));
     conf_logfile = vstralloc(conf_logdir, "/log", NULL);
     if (access(conf_logfile, F_OK) == 0) {
-       error("%s exists: amdump or amflush is already running, or you must run amcleanup", conf_logfile);
+       run_amcleanup(get_config_name());
+    }
+    if (access(conf_logfile, F_OK) == 0) {
+       char *process_name = get_master_process(conf_logfile);
+       error(_("%s exists: %s is already running, or you must run amcleanup"), conf_logfile, process_name);
+       /*NOTREACHED*/
+    }
+
+    driver_program = vstralloc(amlibexecdir, "/", "driver", NULL);
+    reporter_program = vstralloc(sbindir, "/", "amreport", NULL);
+    logroll_program = vstralloc(amlibexecdir, "/", "amlogroll", NULL);
+
+    tapedev = getconf_str(CNF_TAPEDEV);
+    tpchanger = getconf_str(CNF_TPCHANGER);
+    if (tapedev == NULL && tpchanger == NULL) {
+       error(_("No tapedev or tpchanger specified"));
     }
-    amfree(conf_logfile);
-    driver_program = vstralloc(libexecdir, "/", "driver", versionsuffix(),
-                                NULL);
-    reporter_program = vstralloc(sbindir, "/", "amreport", versionsuffix(),
-                                NULL);
-    logroll_program = vstralloc(libexecdir, "/", "amlogroll", versionsuffix(),
-                               NULL);
 
+    /* if dates were specified (-D), then use match_datestamp
+     * against the list of all datestamps to turn that list
+     * into a set of existing datestamps (basically, evaluate the
+     * expressions into actual datestamps) */
     if(datearg) {
-       sle_t *dir, *next_dir;
+       GSList *all_datestamps;
+       GSList *datestamp;
        int i, ok;
 
-       datestamp_list = pick_all_datestamp(1);
-       for(dir = datestamp_list->first; dir != NULL;) {
-           next_dir = dir->next;
+       all_datestamps = holding_get_all_datestamps();
+       for(datestamp = all_datestamps; datestamp != NULL; datestamp = datestamp->next) {
            ok = 0;
            for(i=0; i<nb_datearg && ok==0; i++) {
-               ok = match_datestamp(datearg[i], dir->name);
-           }
-           if(ok == 0) { /* remove dir */
-               remove_sl(datestamp_list, dir);
+               ok = match_datestamp(datearg[i], (char *)datestamp->data);
            }
-           dir = next_dir;
+           if (ok)
+               datestamp_list = g_slist_insert_sorted(datestamp_list,
+                   stralloc((char *)datestamp->data),
+                   g_compare_strings);
        }
+       g_slist_free_full(all_datestamps);
     }
     else {
+       /* otherwise, in batch mode, use all datestamps */
        if(batch) {
-           datestamp_list = pick_all_datestamp(1);
+           datestamp_list = holding_get_all_datestamps();
        }
+       /* or allow the user to pick datestamps */
        else {
-           datestamp_list = pick_datestamp(1);
+           datestamp_list = pick_datestamp();
        }
     }
 
-    if(is_empty_sl(datestamp_list)) {
-       printf("Could not find any Amanda directories to flush.\n");
+    if(!datestamp_list) {
+       g_printf(_("Could not find any Amanda directories to flush.\n"));
        exit(1);
     }
 
-    holding_list = get_flush(datestamp_list, NULL, 1, 0);
-    if(holding_list->first == NULL) {
-       printf("Could not find any valid dump image, check directory.\n");
+    holding_list = holding_get_files_for_flush(datestamp_list);
+    if (holding_list == NULL) {
+       g_printf(_("Could not find any valid dump image, check directory.\n"));
        exit(1);
     }
 
-    if(!batch) confirm();
+    if (access(conf_logfile, F_OK) == 0) {
+       char *process_name = get_master_process(conf_logfile);
+       error(_("%s exists: someone started %s"), conf_logfile, process_name);
+       /*NOTREACHED*/
+    }
+    log_add(L_INFO, "%s pid %ld", get_pname(), (long)getpid());
+
+    if(!batch) confirm(datestamp_list);
 
-    for(dp = diskqp->head; dp != NULL; dp = dp->next) {
-       if(dp->todo)
-           log_add(L_DISK, "%s %s", dp->host->hostname, dp->name);
+    for(dp = diskq.head; dp != NULL; dp = dp->next) {
+       if(dp->todo) {
+           char *qname;
+           qname = quote_string(dp->name);
+           log_add(L_DISK, "%s %s", dp->host->hostname, qname);
+           amfree(qname);
+       }
     }
 
     if(!foreground) { /* write it before redirecting stdout */
-       puts("Running in background, you can log off now.");
-       puts("You'll get mail when amflush is finished.");
+       puts(_("Running in background, you can log off now."));
+       puts(_("You'll get mail when amflush is finished."));
     }
 
     if(redirect) redirect_stderr();
 
     if(!foreground) detach();
 
-    erroutput_type = (ERR_AMANDALOG|ERR_INTERACTIVE);
-    set_logerror(logerror);
+    add_amanda_log_handler(amanda_log_stderr);
+    add_amanda_log_handler(amanda_log_trace_log);
     today = time(NULL);
-    strftime(date_string, 100, "%a %b %e %H:%M:%S %Z %Y", localtime (&today));
-    fprintf(stderr, "amflush: start at %s\n", date_string);
-    fprintf(stderr, "amflush: datestamp %s\n", datestamp);
-    log_add(L_START, "date %s", datestamp);
+    tm = localtime(&today);
+    if (tm) {
+       strftime(date_string, 100, "%a %b %e %H:%M:%S %Z %Y", tm);
+       strftime(date_string_standard, 100, "%Y-%m-%d %H:%M:%S %Z", tm);
+    } else {
+       error(_("BAD DATE")); /* should never happen */
+    }
+    g_fprintf(stderr, _("amflush: start at %s\n"), date_string);
+    g_fprintf(stderr, _("amflush: datestamp %s\n"), amflush_timestamp);
+    g_fprintf(stderr, _("amflush: starttime %s\n"), amflush_timestamp);
+    g_fprintf(stderr, _("amflush: starttime-locale-independent %s\n"),
+             date_string_standard);
+    log_add(L_START, _("date %s"), amflush_timestamp);
 
     /* START DRIVER */
     if(pipe(driver_pipe) == -1) {
-       error("error [opening pipe to driver: %s]", strerror(errno));
+       error(_("error [opening pipe to driver: %s]"), strerror(errno));
+       /*NOTREACHED*/
     }
     if((driver_pid = fork()) == 0) {
        /*
@@ -266,39 +324,71 @@ char **main_argv;
         */
        dup2(driver_pipe[0], 0);
        close(driver_pipe[1]);
-       execle(driver_program,
-              "driver", config_name, "nodump", (char *)0,
-              safe_env());
-       error("cannot exec %s: %s", driver_program, strerror(errno));
+       config_options = get_config_options(3);
+       config_options[0] = "driver";
+       config_options[1] = get_config_name();
+       config_options[2] = "nodump";
+       safe_fd(-1, 0);
+       execve(driver_program, config_options, safe_env());
+       error(_("cannot exec %s: %s"), driver_program, strerror(errno));
+       /*NOTREACHED*/
     } else if(driver_pid == -1) {
-       error("cannot fork for %s: %s", driver_program, strerror(errno));
+       error(_("cannot fork for %s: %s"), driver_program, strerror(errno));
+       /*NOTREACHED*/
     }
     driver_stream = fdopen(driver_pipe[1], "w");
+    if (!driver_stream) {
+       error(_("Can't fdopen: %s"), strerror(errno));
+       /*NOTREACHED*/
+    }
 
-    for(holding_file=holding_list->first; holding_file != NULL;
+    g_fprintf(driver_stream, "DATE %s\n", amflush_timestamp);
+    for(holding_file=holding_list; holding_file != NULL;
                                   holding_file = holding_file->next) {
-       get_dumpfile(holding_file->name, &file);
+       dumpfile_t file;
+       holding_file_get_dumpfile((char *)holding_file->data, &file);
+
+       if (holding_file_size((char *)holding_file->data, 1) <= 0) {
+           g_debug("%s is empty - ignoring", (char *)holding_file->data);
+           log_add(L_INFO, "%s: removing file with no data.",
+                   (char *)holding_file->data);
+           holding_file_unlink((char *)holding_file->data);
+           dumpfile_free_data(&file);
+           continue;
+       }
 
+       /* search_holding_disk should have already ensured that every
+        * holding dumpfile has an entry in the dynamic disklist */
        dp = lookup_disk(file.name, file.disk);
+       assert(dp != NULL);
+
+       /* but match_disklist may have indicated we should not flush it */
        if (dp->todo == 0) continue;
 
-       fprintf(stderr,
+       qdisk = quote_string(file.disk);
+       qhname = quote_string((char *)holding_file->data);
+       g_fprintf(stderr,
                "FLUSH %s %s %s %d %s\n",
                file.name,
-               file.disk,
+               qdisk,
                file.datestamp,
                file.dumplevel,
-               holding_file->name);
-       fprintf(driver_stream,
+               qhname);
+
+       g_debug("flushing '%s'", (char *)holding_file->data);
+       g_fprintf(driver_stream,
                "FLUSH %s %s %s %d %s\n",
                file.name,
-               file.disk,
+               qdisk,
                file.datestamp,
                file.dumplevel,
-               holding_file->name);
+               qhname);
+       amfree(qdisk);
+       amfree(qhname);
+       dumpfile_free_data(&file);
     }
-    fprintf(stderr, "ENDFLUSH\n"); fflush(stderr);
-    fprintf(driver_stream, "ENDFLUSH\n"); fflush(driver_stream);
+    g_fprintf(stderr, "ENDFLUSH\n"); fflush(stderr);
+    g_fprintf(driver_stream, "ENDFLUSH\n"); fflush(driver_stream);
     fclose(driver_stream);
 
     /* WAIT DRIVER */
@@ -307,16 +397,17 @@ char **main_argv;
            if(errno == EINTR) {
                continue;
            } else {
-               error("wait for %s: %s", driver_program, strerror(errno));
+               error(_("wait for %s: %s"), driver_program, strerror(errno));
+               /*NOTREACHED*/
            }
        } else if (pid == driver_pid) {
            break;
        }
     }
 
-    free_sl(datestamp_list);
+    g_slist_free_full(datestamp_list);
     datestamp_list = NULL;
-    free_sl(holding_list);
+    g_slist_free_full(holding_list);
     holding_list = NULL;
 
     if(redirect) { /* rename errfile */
@@ -335,31 +426,33 @@ char **main_argv;
        /* First, find out the last existing errfile,           */
        /* to avoid ``infinite'' loops if tapecycle is infinite */
 
-       ap_snprintf(number,100,"%d",days);
+       g_snprintf(number,100,"%d",days);
        errfilex = newvstralloc(errfilex, errfile, ".", number, NULL);
        while ( days < maxdays && stat(errfilex,&stat_buf)==0) {
            days++;
-           ap_snprintf(number,100,"%d",days);
+           g_snprintf(number,100,"%d",days);
            errfilex = newvstralloc(errfilex, errfile, ".", number, NULL);
        }
-       ap_snprintf(number,100,"%d",days);
+       g_snprintf(number,100,"%d",days);
        errfilex = newvstralloc(errfilex, errfile, ".", number, NULL);
        nerrfilex = NULL;
        while (days > 1) {
            amfree(nerrfilex);
            nerrfilex = errfilex;
            days--;
-           ap_snprintf(number,100,"%d",days);
+           g_snprintf(number,100,"%d",days);
            errfilex = vstralloc(errfile, ".", number, NULL);
            if (rename(errfilex, nerrfilex) != 0) {
-               error("cannot rename \"%s\" to \"%s\": %s",
+               error(_("cannot rename \"%s\" to \"%s\": %s"),
                      errfilex, nerrfilex, strerror(errno));
+               /*NOTREACHED*/
            }
        }
        errfilex = newvstralloc(errfilex, errfile, ".1", NULL);
        if (rename(errfile,errfilex) != 0) {
-           error("cannot rename \"%s\" to \"%s\": %s",
+           error(_("cannot rename \"%s\" to \"%s\": %s"),
                  errfilex, nerrfilex, strerror(errno));
+           /*NOTREACHED*/
        }
        amfree(errfile);
        amfree(errfilex);
@@ -376,51 +469,65 @@ char **main_argv;
        /*
         * This is the child process.
         */
-       execle(reporter_program,
-              "amreport", config_name, (char *)0,
-              safe_env());
-       error("cannot exec %s: %s", reporter_program, strerror(errno));
+       config_options = get_config_options(3);
+       config_options[0] = "amreport";
+       config_options[1] = get_config_name();
+        config_options[2] = "--from-amdump";
+       safe_fd(-1, 0);
+       execve(reporter_program, config_options, safe_env());
+       error(_("cannot exec %s: %s"), reporter_program, strerror(errno));
+       /*NOTREACHED*/
     } else if(reporter_pid == -1) {
-       error("cannot fork for %s: %s", reporter_program, strerror(errno));
+       error(_("cannot fork for %s: %s"), reporter_program, strerror(errno));
+       /*NOTREACHED*/
     }
     while(1) {
        if((pid = wait(&exitcode)) == -1) {
            if(errno == EINTR) {
                continue;
            } else {
-               error("wait for %s: %s", reporter_program, strerror(errno));
+               error(_("wait for %s: %s"), reporter_program, strerror(errno));
+               /*NOTREACHED*/
            }
        } else if (pid == reporter_pid) {
            break;
        }
     }
 
+    log_add(L_INFO, "pid-done %ld", (long)getpid());
+
     /*
      * Call amlogroll to rename the log file to its datestamped version.
      * Since we exec at this point, our exit code will be that of amlogroll.
      */
-
-    execle(logroll_program,
-          "amlogroll", config_name, (char *)0,
-          safe_env());
-    error("cannot exec %s: %s", logroll_program, strerror(errno));
+    config_options = get_config_options(2);
+    config_options[0] = "amlogroll";
+    config_options[1] = get_config_name();
+    safe_fd(-1, 0);
+    execve(logroll_program, config_options, safe_env());
+    error(_("cannot exec %s: %s"), logroll_program, strerror(errno));
+    /*NOTREACHED*/
     return 0;                          /* keep the compiler happy */
 }
 
 
-int get_letter_from_user()
+static int
+get_letter_from_user(void)
 {
-    int r = '\0';
-    int ch;
+    int r, ch;
 
     fflush(stdout); fflush(stderr);
-    while((ch = getchar()) != EOF && ch != '\n' && isspace(ch)) {}
+    while((ch = getchar()) != EOF && ch != '\n' && g_ascii_isspace(ch)) {
+       (void)ch; /* Quite lint */
+    }
     if(ch == '\n') {
        r = '\0';
     } else if (ch != EOF) {
        r = ch;
        if(islower(r)) r = toupper(r);
-       while((ch = getchar()) != EOF && ch != '\n') {}
+       while((ch = getchar()) != EOF && ch != '\n') { 
+           (void)ch; /* Quite lint */
+       }
     } else {
        r = ch;
        clearerr(stdin);
@@ -428,39 +535,129 @@ int get_letter_from_user()
     return r;
 }
 
+/* Allow the user to select a set of datestamps from those in
+ * holding disks.  The result can be passed to 
+ * holding_get_files_for_flush.  If less than two dates are
+ * available, then no user interaction takes place.
+ *
+ * @returns: a new GSList listing the selected datestamps
+ */
+static GSList *
+pick_datestamp(void)
+{
+    GSList *datestamp_list;
+    GSList *r_datestamp_list = NULL;
+    GSList *ds;
+    char **datestamps = NULL;
+    int i;
+    char *answer = NULL;
+    char *a = NULL;
+    int ch = 0;
+    char max_char = '\0', chupper = '\0';
+
+    datestamp_list = holding_get_all_datestamps();
+
+    if(g_slist_length(datestamp_list) < 2) {
+       return datestamp_list;
+    } else {
+       datestamps = alloc(g_slist_length(datestamp_list) * SIZEOF(char *));
+       for(ds = datestamp_list, i=0; ds != NULL; ds = ds->next,i++) {
+           datestamps[i] = (char *)ds->data; /* borrowing reference */
+       }
+
+       while(1) {
+           puts(_("\nMultiple Amanda runs in holding disks; please pick one by letter:"));
+           for(ds = datestamp_list, max_char = 'A';
+               ds != NULL && max_char <= 'Z';
+               ds = ds->next, max_char++) {
+               g_printf("  %c. %s\n", max_char, (char *)ds->data);
+           }
+           max_char--;
+           g_printf(_("Select datestamps to flush [A..%c or <enter> for all]: "), max_char);
+           fflush(stdout); fflush(stderr);
+           amfree(answer);
+           if ((answer = agets(stdin)) == NULL) {
+               clearerr(stdin);
+               continue;
+           }
+
+           if (*answer == '\0' || strncasecmp(answer, "ALL", 3) == 0) {
+               break;
+           }
+
+           a = answer;
+           while ((ch = *a++) != '\0') {
+               if (!g_ascii_isspace(ch))
+                   break;
+           }
+
+           /* rewrite the selected list into r_datestamp_list, then copy it over
+            * to datestamp_list */
+           do {
+               if (g_ascii_isspace(ch) || ch == ',') {
+                   continue;
+               }
+               chupper = (char)toupper(ch);
+               if (chupper < 'A' || chupper > max_char) {
+                   g_slist_free_full(r_datestamp_list);
+                   r_datestamp_list = NULL;
+                   break;
+               }
+               r_datestamp_list = g_slist_append(r_datestamp_list,
+                                          stralloc(datestamps[chupper - 'A']));
+           } while ((ch = *a++) != '\0');
+           if (r_datestamp_list && ch == '\0') {
+               g_slist_free_full(datestamp_list);
+               datestamp_list = r_datestamp_list;
+               break;
+           }
+       }
+    }
+    amfree(datestamps); /* references in this array are borrowed */
+    amfree(answer);
+
+    return datestamp_list;
+}
+
+
+/*
+ * confirm before detaching and running
+ */
 
-void confirm()
-/* confirm before detaching and running */
+void
+confirm(GSList *datestamp_list)
 {
     tape_t *tp;
     char *tpchanger;
-    sle_t *dir;
+    GSList *datestamp;
     int ch;
     char *extra;
 
-    printf("\nToday is: %s\n",datestamp);
-    printf("Flushing dumps in");
+    g_printf(_("\nToday is: %s\n"),amflush_datestamp);
+    g_printf(_("Flushing dumps from"));
     extra = "";
-    for(dir = datestamp_list->first; dir != NULL; dir = dir->next) {
-       printf("%s %s", extra, dir->name);
+    for(datestamp = datestamp_list; datestamp != NULL; datestamp = datestamp->next) {
+       g_printf("%s %s", extra, (char *)datestamp->data);
        extra = ",";
     }
     tpchanger = getconf_str(CNF_TPCHANGER);
     if(*tpchanger != '\0') {
-       printf(" using tape changer \"%s\".\n", tpchanger);
+       g_printf(_(" using tape changer \"%s\".\n"), tpchanger);
     } else {
-       printf(" to tape drive \"%s\".\n", getconf_str(CNF_TAPEDEV));
+       g_printf(_(" to tape drive \"%s\".\n"), getconf_str(CNF_TAPEDEV));
     }
 
-    printf("Expecting ");
+    g_printf(_("Expecting "));
     tp = lookup_last_reusable_tape(0);
-    if(tp != NULL) printf("tape %s or ", tp->label);
-    printf("a new tape.");
+    if(tp != NULL)
+       g_printf(_("tape %s or "), tp->label);
+    g_printf(_("a new tape."));
     tp = lookup_tapepos(1);
-    if(tp != NULL) printf("  (The last dumps were to tape %s)", tp->label);
+    if(tp != NULL)
+       g_printf(_("  (The last dumps were to tape %s)"), tp->label);
 
     while (1) {
-       printf("\nAre you sure you want to do this [yN]? ");
+       g_printf(_("\nAre you sure you want to do this [yN]? "));
        if((ch = get_letter_from_user()) == 'Y') {
            return;
        } else if (ch == 'N' || ch == '\0' || ch == EOF) {
@@ -471,38 +668,48 @@ void confirm()
        }
     }
 
-    printf("Ok, quitting.  Run amflush again when you are ready.\n");
+    g_printf(_("Ok, quitting.  Run amflush again when you are ready.\n"));
+    log_add(L_INFO, "pid-done %ld", (long)getpid());
     exit(1);
 }
 
-void redirect_stderr()
+void
+redirect_stderr(void)
 {
     int fderr;
     char *errfile;
 
     fflush(stdout); fflush(stderr);
     errfile = vstralloc(conf_logdir, "/amflush", NULL);
-    if((fderr = open(errfile, O_WRONLY| O_CREAT | O_TRUNC, 0600)) == -1)
-       error("could not open %s: %s", errfile, strerror(errno));
+    if((fderr = open(errfile, O_WRONLY| O_CREAT | O_TRUNC, 0600)) == -1) {
+       error(_("could not open %s: %s"), errfile, strerror(errno));
+       /*NOTREACHED*/
+    }
     dup2(fderr,1);
     dup2(fderr,2);
     aclose(fderr);
     amfree(errfile);
 }
 
-void detach()
+void
+detach(void)
 {
     int fd;
 
     fflush(stdout); fflush(stderr);
-    if((fd = open("/dev/null", O_RDWR, 0666)) == -1)
-       error("could not open /dev/null: %s", strerror(errno));
+    if((fd = open("/dev/null", O_RDWR, 0666)) == -1) {
+       error(_("could not open /dev/null: %s"), strerror(errno));
+       /*NOTREACHED*/
+    }
 
     dup2(fd,0);
     aclose(fd);
 
     switch(fork()) {
-    case -1: error("could not fork: %s", strerror(errno));
+    case -1:
+       error(_("could not fork: %s"), strerror(errno));
+       /*NOTREACHED*/
+
     case 0:
        setsid();
        return;
@@ -510,5 +717,3 @@ void detach()
 
     exit(0);
 }
-
-