lintian doesn't like orphan packages with uploaders...
[debian/amanda] / server-src / amflush.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1998 University of Maryland at College Park
4  * Copyright (c) 2007-2012 Zmanda, Inc.  All Rights Reserved.
5  * All Rights Reserved.
6  *
7  * Permission to use, copy, modify, distribute, and sell this software and its
8  * documentation for any purpose is hereby granted without fee, provided that
9  * the above copyright notice appear in all copies and that both that
10  * copyright notice and this permission notice appear in supporting
11  * documentation, and that the name of U.M. not be used in advertising or
12  * publicity pertaining to distribution of the software without specific,
13  * written prior permission.  U.M. makes no representations about the
14  * suitability of this software for any purpose.  It is provided "as is"
15  * without express or implied warranty.
16  *
17  * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
19  * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
21  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
22  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23  *
24  * Authors: the Amanda Development Team.  Its members are listed in a
25  * file named AUTHORS, in the root directory of this distribution.
26  */
27 /*
28  * $Id: amflush.c,v 1.95 2006/07/25 21:41:24 martinea Exp $
29  *
30  * write files from work directory onto tape
31  */
32 #include "amanda.h"
33
34 #include "match.h"
35 #include "find.h"
36 #include "conffile.h"
37 #include "diskfile.h"
38 #include "tapefile.h"
39 #include "logfile.h"
40 #include "clock.h"
41 #include "holding.h"
42 #include "server_util.h"
43 #include "timestamp.h"
44 #include "getopt.h"
45
46 static struct option long_options[] = {
47     {"version"         , 0, NULL,  1},
48     {"exact-match"     , 0, NULL,  2},
49     {NULL, 0, NULL, 0}
50 };
51
52 static char *conf_logdir;
53 FILE *driver_stream;
54 char *driver_program;
55 char *reporter_program;
56 char *logroll_program;
57 char *datestamp;
58 char *amflush_timestamp;
59 char *amflush_datestamp;
60
61 /* local functions */
62 void flush_holdingdisk(char *diskdir, char *datestamp);
63 static GSList * pick_datestamp(void);
64 void confirm(GSList *datestamp_list);
65 void redirect_stderr(void);
66 void detach(void);
67 void run_dumps(void);
68 static int get_letter_from_user(void);
69
70 int
71 main(
72     int         argc,
73     char **     argv)
74 {
75     int foreground;
76     int batch;
77     int redirect;
78     char **datearg = NULL;
79     int nb_datearg = 0;
80     char *conf_diskfile;
81     char *conf_tapelist;
82     char *conf_logfile;
83     int conf_usetimestamps;
84     disklist_t diskq;
85     disk_t *dp;
86     pid_t pid;
87     pid_t driver_pid, reporter_pid;
88     amwait_t exitcode;
89     int opt;
90     GSList *holding_list=NULL, *holding_file;
91     int driver_pipe[2];
92     char date_string[100];
93     char date_string_standard[100];
94     time_t today;
95     char *errstr;
96     struct tm *tm;
97     char *tapedev;
98     char *tpchanger;
99     char *qdisk, *qhname;
100     GSList *datestamp_list = NULL;
101     config_overrides_t *cfg_ovr;
102     char **config_options;
103     find_result_t *holding_files;
104     disklist_t holding_disklist = { NULL, NULL };
105     gboolean exact_match = FALSE;
106
107     /*
108      * Configure program for internationalization:
109      *   1) Only set the message locale for now.
110      *   2) Set textdomain for all amanda related programs to "amanda"
111      *      We don't want to be forced to support dozens of message catalogs.
112      */  
113     setlocale(LC_MESSAGES, "C");
114     textdomain("amanda"); 
115
116     safe_fd(-1, 0);
117     safe_cd();
118
119     set_pname("amflush");
120
121     /* Don't die when child closes pipe */
122     signal(SIGPIPE, SIG_IGN);
123
124     dbopen(DBG_SUBDIR_SERVER);
125
126     add_amanda_log_handler(amanda_log_stderr);
127     foreground = 0;
128     batch = 0;
129     redirect = 1;
130
131     /* process arguments */
132
133     cfg_ovr = new_config_overrides(argc/2);
134     while((opt = getopt_long(argc, argv, "bfso:D:", long_options, NULL)) != EOF) {
135         switch(opt) {
136         case 1  : printf("amflush-%s\n", VERSION);
137                   return(0);
138                   break;
139         case 2  : exact_match = TRUE;
140                   break;
141         case 'b': batch = 1;
142                   break;
143         case 'f': foreground = 1;
144                   break;
145         case 's': redirect = 0;
146                   break;
147         case 'o': add_config_override_opt(cfg_ovr, optarg);
148                   break;
149         case 'D': if (datearg == NULL)
150                       datearg = alloc(21*SIZEOF(char *));
151                   if(nb_datearg == 20) {
152                       g_fprintf(stderr,_("maximum of 20 -D arguments.\n"));
153                       exit(1);
154                   }
155                   datearg[nb_datearg++] = stralloc(optarg);
156                   datearg[nb_datearg] = NULL;
157                   break;
158         }
159     }
160     argc -= optind, argv += optind;
161
162     if(!foreground && !redirect) {
163         g_fprintf(stderr,_("Can't redirect to stdout/stderr if not in forground.\n"));
164         exit(1);
165     }
166
167     if(argc < 1) {
168         error(_("Usage: amflush [-b] [-f] [-s] [-D date]* [--exact-match] [-o configoption]* <confdir> [host [disk]* ]*"));
169         /*NOTREACHED*/
170     }
171
172     set_config_overrides(cfg_ovr);
173     config_init(CONFIG_INIT_EXPLICIT_NAME,
174                 argv[0]);
175
176     conf_diskfile = config_dir_relative(getconf_str(CNF_DISKFILE));
177     read_diskfile(conf_diskfile, &diskq);
178     amfree(conf_diskfile);
179
180     if (config_errors(NULL) >= CFGERR_WARNINGS) {
181         config_print_errors();
182         if (config_errors(NULL) >= CFGERR_ERRORS) {
183             g_critical(_("errors processing config file"));
184         }
185     }
186
187     check_running_as(RUNNING_AS_DUMPUSER);
188
189     dbrename(get_config_name(), DBG_SUBDIR_SERVER);
190
191     /* load DLEs from the holding disk, in case there's anything to flush there */
192     search_holding_disk(&holding_files, &holding_disklist);
193     /* note that the dumps are added to the global disklist, so we need not
194      * consult holding_files or holding_disklist after this.  The holding-only
195      * dumps will be filtered properly by match_disklist, setting the dp->todo
196      * flag appropriately. */
197
198     errstr = match_disklist(&diskq, exact_match, argc-1, argv+1);
199     if (errstr) {
200         g_printf(_("%s"),errstr);
201         amfree(errstr);
202     }
203
204     conf_tapelist = config_dir_relative(getconf_str(CNF_TAPELIST));
205     if(read_tapelist(conf_tapelist)) {
206         error(_("could not load tapelist \"%s\""), conf_tapelist);
207         /*NOTREACHED*/
208     }
209     amfree(conf_tapelist);
210
211     conf_usetimestamps = getconf_boolean(CNF_USETIMESTAMPS);
212
213     amflush_datestamp = get_datestamp_from_time(0);
214     if(conf_usetimestamps == 0) {
215         amflush_timestamp = stralloc(amflush_datestamp);
216     }
217     else {
218         amflush_timestamp = get_timestamp_from_time(0);
219     }
220
221     conf_logdir = config_dir_relative(getconf_str(CNF_LOGDIR));
222     conf_logfile = vstralloc(conf_logdir, "/log", NULL);
223     if (access(conf_logfile, F_OK) == 0) {
224         run_amcleanup(get_config_name());
225     }
226     if (access(conf_logfile, F_OK) == 0) {
227         char *process_name = get_master_process(conf_logfile);
228         error(_("%s exists: %s is already running, or you must run amcleanup"), conf_logfile, process_name);
229         /*NOTREACHED*/
230     }
231
232     driver_program = vstralloc(amlibexecdir, "/", "driver", NULL);
233     reporter_program = vstralloc(sbindir, "/", "amreport", NULL);
234     logroll_program = vstralloc(amlibexecdir, "/", "amlogroll", NULL);
235
236     tapedev = getconf_str(CNF_TAPEDEV);
237     tpchanger = getconf_str(CNF_TPCHANGER);
238     if (tapedev == NULL && tpchanger == NULL) {
239         error(_("No tapedev or tpchanger specified"));
240     }
241
242     /* if dates were specified (-D), then use match_datestamp
243      * against the list of all datestamps to turn that list
244      * into a set of existing datestamps (basically, evaluate the
245      * expressions into actual datestamps) */
246     if(datearg) {
247         GSList *all_datestamps;
248         GSList *datestamp;
249         int i, ok;
250
251         all_datestamps = holding_get_all_datestamps();
252         for(datestamp = all_datestamps; datestamp != NULL; datestamp = datestamp->next) {
253             ok = 0;
254             for(i=0; i<nb_datearg && ok==0; i++) {
255                 ok = match_datestamp(datearg[i], (char *)datestamp->data);
256             }
257             if (ok)
258                 datestamp_list = g_slist_insert_sorted(datestamp_list,
259                     stralloc((char *)datestamp->data),
260                     g_compare_strings);
261         }
262         slist_free_full(all_datestamps, g_free);
263     }
264     else {
265         /* otherwise, in batch mode, use all datestamps */
266         if(batch) {
267             datestamp_list = holding_get_all_datestamps();
268         }
269         /* or allow the user to pick datestamps */
270         else {
271             datestamp_list = pick_datestamp();
272         }
273     }
274
275     if(!datestamp_list) {
276         g_printf(_("Could not find any Amanda directories to flush.\n"));
277         exit(1);
278     }
279
280     holding_list = holding_get_files_for_flush(datestamp_list);
281     if (holding_list == NULL) {
282         g_printf(_("Could not find any valid dump image, check directory.\n"));
283         exit(1);
284     }
285
286     if (access(conf_logfile, F_OK) == 0) {
287         char *process_name = get_master_process(conf_logfile);
288         error(_("%s exists: someone started %s"), conf_logfile, process_name);
289         /*NOTREACHED*/
290     }
291     log_add(L_INFO, "%s pid %ld", get_pname(), (long)getpid());
292
293     if(!batch) confirm(datestamp_list);
294
295     for(dp = diskq.head; dp != NULL; dp = dp->next) {
296         if(dp->todo) {
297             /* is it holding_list */
298             for (holding_file=holding_list; holding_file != NULL;
299                                             holding_file = holding_file->next) {
300                 dumpfile_t file;
301                 holding_file_get_dumpfile((char *)holding_file->data, &file);
302                 if (g_str_equal(dp->host->hostname, file.name) &&
303                     g_str_equal(dp->name, file.disk)) {
304                     char *qname;
305                     qname = quote_string(dp->name);
306                     log_add(L_DISK, "%s %s", dp->host->hostname, qname);
307                     amfree(qname);
308                 }
309             }
310         }
311     }
312
313     if(!foreground) { /* write it before redirecting stdout */
314         puts(_("Running in background, you can log off now."));
315         puts(_("You'll get mail when amflush is finished."));
316     }
317
318     if(redirect) redirect_stderr();
319
320     if(!foreground) detach();
321
322     add_amanda_log_handler(amanda_log_stderr);
323     add_amanda_log_handler(amanda_log_trace_log);
324     today = time(NULL);
325     tm = localtime(&today);
326     if (tm) {
327         strftime(date_string, 100, "%a %b %e %H:%M:%S %Z %Y", tm);
328         strftime(date_string_standard, 100, "%Y-%m-%d %H:%M:%S %Z", tm);
329     } else {
330         error(_("BAD DATE")); /* should never happen */
331     }
332     g_fprintf(stderr, _("amflush: start at %s\n"), date_string);
333     g_fprintf(stderr, _("amflush: datestamp %s\n"), amflush_timestamp);
334     g_fprintf(stderr, _("amflush: starttime %s\n"), amflush_timestamp);
335     g_fprintf(stderr, _("amflush: starttime-locale-independent %s\n"),
336               date_string_standard);
337     log_add(L_START, _("date %s"), amflush_timestamp);
338
339     /* START DRIVER */
340     if(pipe(driver_pipe) == -1) {
341         error(_("error [opening pipe to driver: %s]"), strerror(errno));
342         /*NOTREACHED*/
343     }
344     if((driver_pid = fork()) == 0) {
345         /*
346          * This is the child process.
347          */
348         dup2(driver_pipe[0], 0);
349         close(driver_pipe[1]);
350         config_options = get_config_options(3);
351         config_options[0] = "driver";
352         config_options[1] = get_config_name();
353         config_options[2] = "nodump";
354         safe_fd(-1, 0);
355         execve(driver_program, config_options, safe_env());
356         error(_("cannot exec %s: %s"), driver_program, strerror(errno));
357         /*NOTREACHED*/
358     } else if(driver_pid == -1) {
359         error(_("cannot fork for %s: %s"), driver_program, strerror(errno));
360         /*NOTREACHED*/
361     }
362     driver_stream = fdopen(driver_pipe[1], "w");
363     if (!driver_stream) {
364         error(_("Can't fdopen: %s"), strerror(errno));
365         /*NOTREACHED*/
366     }
367
368     g_fprintf(driver_stream, "DATE %s\n", amflush_timestamp);
369     for(holding_file=holding_list; holding_file != NULL;
370                                    holding_file = holding_file->next) {
371         dumpfile_t file;
372         holding_file_get_dumpfile((char *)holding_file->data, &file);
373
374         if (holding_file_size((char *)holding_file->data, 1) <= 0) {
375             g_debug("%s is empty - ignoring", (char *)holding_file->data);
376             log_add(L_INFO, "%s: removing file with no data.",
377                     (char *)holding_file->data);
378             holding_file_unlink((char *)holding_file->data);
379             dumpfile_free_data(&file);
380             continue;
381         }
382
383         /* search_holding_disk should have already ensured that every
384          * holding dumpfile has an entry in the dynamic disklist */
385         dp = lookup_disk(file.name, file.disk);
386         assert(dp != NULL);
387
388         /* but match_disklist may have indicated we should not flush it */
389         if (dp->todo == 0) continue;
390
391         qdisk = quote_string(file.disk);
392         qhname = quote_string((char *)holding_file->data);
393         g_fprintf(stderr,
394                 "FLUSH %s %s %s %d %s\n",
395                 file.name,
396                 qdisk,
397                 file.datestamp,
398                 file.dumplevel,
399                 qhname);
400
401         g_debug("flushing '%s'", (char *)holding_file->data);
402         g_fprintf(driver_stream,
403                 "FLUSH %s %s %s %d %s\n",
404                 file.name,
405                 qdisk,
406                 file.datestamp,
407                 file.dumplevel,
408                 qhname);
409         amfree(qdisk);
410         amfree(qhname);
411         dumpfile_free_data(&file);
412     }
413     g_fprintf(stderr, "ENDFLUSH\n"); fflush(stderr);
414     g_fprintf(driver_stream, "ENDFLUSH\n"); fflush(driver_stream);
415     fclose(driver_stream);
416
417     /* WAIT DRIVER */
418     while(1) {
419         if((pid = wait(&exitcode)) == -1) {
420             if(errno == EINTR) {
421                 continue;
422             } else {
423                 error(_("wait for %s: %s"), driver_program, strerror(errno));
424                 /*NOTREACHED*/
425             }
426         } else if (pid == driver_pid) {
427             break;
428         }
429     }
430
431     slist_free_full(datestamp_list, g_free);
432     datestamp_list = NULL;
433     slist_free_full(holding_list, g_free);
434     holding_list = NULL;
435
436     if(redirect) { /* rename errfile */
437         char *errfile, *errfilex, *nerrfilex, number[100];
438         int tapecycle;
439         int maxdays, days;
440                 
441         struct stat stat_buf;
442
443         errfile = vstralloc(conf_logdir, "/amflush", NULL);
444         errfilex = NULL;
445         nerrfilex = NULL;
446         tapecycle = getconf_int(CNF_TAPECYCLE);
447         maxdays = tapecycle + 2;
448         days = 1;
449         /* First, find out the last existing errfile,           */
450         /* to avoid ``infinite'' loops if tapecycle is infinite */
451
452         g_snprintf(number,100,"%d",days);
453         errfilex = newvstralloc(errfilex, errfile, ".", number, NULL);
454         while ( days < maxdays && stat(errfilex,&stat_buf)==0) {
455             days++;
456             g_snprintf(number,100,"%d",days);
457             errfilex = newvstralloc(errfilex, errfile, ".", number, NULL);
458         }
459         g_snprintf(number,100,"%d",days);
460         errfilex = newvstralloc(errfilex, errfile, ".", number, NULL);
461         nerrfilex = NULL;
462         while (days > 1) {
463             amfree(nerrfilex);
464             nerrfilex = errfilex;
465             days--;
466             g_snprintf(number,100,"%d",days);
467             errfilex = vstralloc(errfile, ".", number, NULL);
468             if (rename(errfilex, nerrfilex) != 0) {
469                 error(_("cannot rename \"%s\" to \"%s\": %s"),
470                       errfilex, nerrfilex, strerror(errno));
471                 /*NOTREACHED*/
472             }
473         }
474         errfilex = newvstralloc(errfilex, errfile, ".1", NULL);
475         if (rename(errfile,errfilex) != 0) {
476             error(_("cannot rename \"%s\" to \"%s\": %s"),
477                   errfilex, nerrfilex, strerror(errno));
478             /*NOTREACHED*/
479         }
480         amfree(errfile);
481         amfree(errfilex);
482         amfree(nerrfilex);
483     }
484
485     /*
486      * Have amreport generate report and send mail.  Note that we do
487      * not bother checking the exit status.  If it does not work, it
488      * can be rerun.
489      */
490
491     if((reporter_pid = fork()) == 0) {
492         /*
493          * This is the child process.
494          */
495         config_options = get_config_options(3);
496         config_options[0] = "amreport";
497         config_options[1] = get_config_name();
498         config_options[2] = "--from-amdump";
499         safe_fd(-1, 0);
500         execve(reporter_program, config_options, safe_env());
501         error(_("cannot exec %s: %s"), reporter_program, strerror(errno));
502         /*NOTREACHED*/
503     } else if(reporter_pid == -1) {
504         error(_("cannot fork for %s: %s"), reporter_program, strerror(errno));
505         /*NOTREACHED*/
506     }
507     while(1) {
508         if((pid = wait(&exitcode)) == -1) {
509             if(errno == EINTR) {
510                 continue;
511             } else {
512                 error(_("wait for %s: %s"), reporter_program, strerror(errno));
513                 /*NOTREACHED*/
514             }
515         } else if (pid == reporter_pid) {
516             break;
517         }
518     }
519
520     log_add(L_INFO, "pid-done %ld", (long)getpid());
521
522     /*
523      * Call amlogroll to rename the log file to its datestamped version.
524      * Since we exec at this point, our exit code will be that of amlogroll.
525      */
526     config_options = get_config_options(2);
527     config_options[0] = "amlogroll";
528     config_options[1] = get_config_name();
529     safe_fd(-1, 0);
530     execve(logroll_program, config_options, safe_env());
531     error(_("cannot exec %s: %s"), logroll_program, strerror(errno));
532     /*NOTREACHED*/
533     return 0;                           /* keep the compiler happy */
534 }
535
536
537 static int
538 get_letter_from_user(void)
539 {
540     int r, ch;
541
542     fflush(stdout); fflush(stderr);
543     while((ch = getchar()) != EOF && ch != '\n' && g_ascii_isspace(ch)) {
544         (void)ch; /* Quite lint */
545     }
546     if(ch == '\n') {
547         r = '\0';
548     } else if (ch != EOF) {
549         r = ch;
550         if(islower(r)) r = toupper(r);
551         while((ch = getchar()) != EOF && ch != '\n') { 
552             (void)ch; /* Quite lint */
553         }
554     } else {
555         r = ch;
556         clearerr(stdin);
557     }
558     return r;
559 }
560
561 /* Allow the user to select a set of datestamps from those in
562  * holding disks.  The result can be passed to 
563  * holding_get_files_for_flush.  If less than two dates are
564  * available, then no user interaction takes place.
565  *
566  * @returns: a new GSList listing the selected datestamps
567  */
568 static GSList *
569 pick_datestamp(void)
570 {
571     GSList *datestamp_list;
572     GSList *r_datestamp_list = NULL;
573     GSList *ds;
574     char **datestamps = NULL;
575     int i;
576     char *answer = NULL;
577     char *a = NULL;
578     int ch = 0;
579     char max_char = '\0', chupper = '\0';
580
581     datestamp_list = holding_get_all_datestamps();
582
583     if(g_slist_length(datestamp_list) < 2) {
584         return datestamp_list;
585     } else {
586         datestamps = alloc(g_slist_length(datestamp_list) * SIZEOF(char *));
587         for(ds = datestamp_list, i=0; ds != NULL; ds = ds->next,i++) {
588             datestamps[i] = (char *)ds->data; /* borrowing reference */
589         }
590
591         while(1) {
592             puts(_("\nMultiple Amanda runs in holding disks; please pick one by letter:"));
593             for(ds = datestamp_list, max_char = 'A';
594                 ds != NULL && max_char <= 'Z';
595                 ds = ds->next, max_char++) {
596                 g_printf("  %c. %s\n", max_char, (char *)ds->data);
597             }
598             max_char--;
599             g_printf(_("Select datestamps to flush [A..%c or <enter> for all]: "), max_char);
600             fflush(stdout); fflush(stderr);
601             amfree(answer);
602             if ((answer = agets(stdin)) == NULL) {
603                 clearerr(stdin);
604                 continue;
605             }
606
607             if (*answer == '\0' || strncasecmp(answer, "ALL", 3) == 0) {
608                 break;
609             }
610
611             a = answer;
612             while ((ch = *a++) != '\0') {
613                 if (!g_ascii_isspace(ch))
614                     break;
615             }
616
617             /* rewrite the selected list into r_datestamp_list, then copy it over
618              * to datestamp_list */
619             do {
620                 if (g_ascii_isspace(ch) || ch == ',') {
621                     continue;
622                 }
623                 chupper = (char)toupper(ch);
624                 if (chupper < 'A' || chupper > max_char) {
625                     slist_free_full(r_datestamp_list, g_free);
626                     r_datestamp_list = NULL;
627                     break;
628                 }
629                 r_datestamp_list = g_slist_append(r_datestamp_list,
630                                            stralloc(datestamps[chupper - 'A']));
631             } while ((ch = *a++) != '\0');
632             if (r_datestamp_list && ch == '\0') {
633                 slist_free_full(datestamp_list, g_free);
634                 datestamp_list = r_datestamp_list;
635                 break;
636             }
637         }
638     }
639     amfree(datestamps); /* references in this array are borrowed */
640     amfree(answer);
641
642     return datestamp_list;
643 }
644
645
646 /*
647  * confirm before detaching and running
648  */
649
650 void
651 confirm(GSList *datestamp_list)
652 {
653     tape_t *tp;
654     char *tpchanger;
655     GSList *datestamp;
656     int ch;
657     char *extra;
658
659     g_printf(_("\nToday is: %s\n"),amflush_datestamp);
660     g_printf(_("Flushing dumps from"));
661     extra = "";
662     for(datestamp = datestamp_list; datestamp != NULL; datestamp = datestamp->next) {
663         g_printf("%s %s", extra, (char *)datestamp->data);
664         extra = ",";
665     }
666     tpchanger = getconf_str(CNF_TPCHANGER);
667     if(*tpchanger != '\0') {
668         g_printf(_(" using tape changer \"%s\".\n"), tpchanger);
669     } else {
670         g_printf(_(" to tape drive \"%s\".\n"), getconf_str(CNF_TAPEDEV));
671     }
672
673     g_printf(_("Expecting "));
674     tp = lookup_last_reusable_tape(0);
675     if(tp != NULL)
676         g_printf(_("tape %s or "), tp->label);
677     g_printf(_("a new tape."));
678     tp = lookup_tapepos(1);
679     if(tp != NULL)
680         g_printf(_("  (The last dumps were to tape %s)"), tp->label);
681
682     while (1) {
683         g_printf(_("\nAre you sure you want to do this [yN]? "));
684         if((ch = get_letter_from_user()) == 'Y') {
685             return;
686         } else if (ch == 'N' || ch == '\0' || ch == EOF) {
687             if (ch == EOF) {
688                 putchar('\n');
689             }
690             break;
691         }
692     }
693
694     g_printf(_("Ok, quitting.  Run amflush again when you are ready.\n"));
695     log_add(L_INFO, "pid-done %ld", (long)getpid());
696     exit(1);
697 }
698
699 void
700 redirect_stderr(void)
701 {
702     int fderr;
703     char *errfile;
704
705     fflush(stdout); fflush(stderr);
706     errfile = vstralloc(conf_logdir, "/amflush", NULL);
707     if((fderr = open(errfile, O_WRONLY| O_APPEND | O_CREAT | O_TRUNC, 0600)) == -1) {
708         error(_("could not open %s: %s"), errfile, strerror(errno));
709         /*NOTREACHED*/
710     }
711     dup2(fderr,1);
712     dup2(fderr,2);
713     aclose(fderr);
714     amfree(errfile);
715 }
716
717 void
718 detach(void)
719 {
720     int fd;
721
722     fflush(stdout); fflush(stderr);
723     if((fd = open("/dev/null", O_RDWR, 0666)) == -1) {
724         error(_("could not open /dev/null: %s"), strerror(errno));
725         /*NOTREACHED*/
726     }
727
728     dup2(fd,0);
729     aclose(fd);
730
731     switch(fork()) {
732     case -1:
733         error(_("could not fork: %s"), strerror(errno));
734         /*NOTREACHED*/
735
736     case 0:
737         setsid();
738         return;
739     }
740
741     exit(0);
742 }