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