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