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