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