Imported Upstream version 2.4.4p3
[debian/amanda] / server-src / reporter.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1998, 2000 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  * Author: James da Silva, Systems Design and Analysis Group
24  *                         Computer Science Department
25  *                         University of Maryland at College Park
26  */
27 /*
28  * $Id: reporter.c,v 1.44.2.17.4.6.2.16 2003/11/26 16:10:23 martinea Exp $
29  *
30  * nightly Amanda Report generator
31  */
32 /*
33 report format
34     tape label message
35     error messages
36     summary stats
37     details for errors
38     notes
39     success summary
40 */
41
42 #include "amanda.h"
43 #include "conffile.h"
44 #include "tapefile.h"
45 #include "diskfile.h"
46 #include "infofile.h"
47 #include "logfile.h"
48 #include "version.h"
49 #include "util.h"
50
51 /* don't have (or need) a skipped type except internally to reporter */
52 #define L_SKIPPED       L_MARKER
53
54 typedef struct line_s {
55     struct line_s *next;
56     char *str;
57 } line_t;
58
59 typedef struct timedata_s {
60     logtype_t result;
61     float origsize, outsize;
62     char *datestamp;
63     float sec, kps;
64     int filenum;
65     char *tapelabel;
66 } timedata_t;
67
68 typedef struct repdata_s {
69     disk_t *disk;
70     char *datestamp;
71     timedata_t taper;
72     timedata_t dumper;
73     int level;
74     struct repdata_s *next;
75 } repdata_t;
76
77 #define data(dp) ((repdata_t *)(dp)->up)
78
79 struct cumulative_stats {
80     int dumpdisks, tapedisks;
81     double taper_time, dumper_time;
82     double outsize, origsize, tapesize;
83     double coutsize, corigsize;                 /* compressed dump only */
84 } stats[3];
85
86 int dumpdisks[10], tapedisks[10];       /* by-level breakdown of disk count */
87
88 typedef struct taper_s {
89     char *label;
90     double taper_time;
91     double coutsize, corigsize;
92     int tapedisks;
93     struct taper_s *next;
94 } taper_t;
95
96 taper_t *stats_by_tape = NULL;
97 taper_t *current_tape = NULL;
98
99 float total_time, startup_time;
100
101 /* count files to tape */
102 int tapefcount = 0;
103
104 char *run_datestamp;
105 char *today_datestamp;
106 char *tape_labels = NULL;
107 int last_run_tapes = 0;
108 static int degraded_mode = 0; /* defined in driverio too */
109 int normal_run = 0;
110 int amflush_run = 0;
111 int got_finish = 0;
112
113 char *tapestart_error = NULL;
114
115 FILE *logfile, *mailf;
116
117 FILE *postscript;
118 char *printer;
119
120 disklist_t *diskq;
121 disklist_t sortq;
122
123 line_t *errsum = NULL;
124 line_t *errdet = NULL;
125 line_t *notes = NULL;
126
127 static char MaxWidthsRequested = 0;     /* determined via config data */
128
129 /*
130 char *hostname = NULL, *diskname = NULL;
131 */
132
133 /* local functions */
134 int contline_next P((void));
135 void addline P((line_t **lp, char *str));
136 void usage P((void));
137 int main P((int argc, char **argv));
138
139 void copy_template_file P((char *lbl_templ));
140 void do_postscript_output P((void));
141 void handle_start P((void));
142 void handle_finish P((void));
143 void handle_note P((void));
144 void handle_summary P((void));
145 void handle_stats P((void));
146 void handle_error P((void));
147 void handle_disk P((void));
148 repdata_t *handle_success P((void));
149 void handle_strange P((void));
150 void handle_failed P((void));
151 void generate_missing P((void));
152 void output_tapeinfo P((void));
153 void output_lines P((line_t *lp, FILE *f));
154 void output_stats P((void));
155 void output_summary P((void));
156 void sort_disks P((void));
157 int sort_by_time P((disk_t *a, disk_t *b));
158 int sort_by_name P((disk_t *a, disk_t *b));
159 void bogus_line P((void));
160 char *nicedate P((int datestamp));
161 static char *prefix P((char *host, char *disk, int level));
162 repdata_t *find_repdata P((disk_t *dp, char *datestamp, int level));
163
164
165 static int ColWidth(int From, int To) {
166     int i, Width= 0;
167     for (i=From; i<=To && ColumnData[i].Name != NULL; i++) {
168         Width+= ColumnData[i].PrefixSpace + ColumnData[i].Width;
169     }
170     return Width;
171 }
172
173 static char *Rule(int From, int To) {
174     int i, ThisLeng;
175     int Leng= ColWidth(0, ColumnDataCount());
176     char *RuleSpace= alloc(Leng+1);
177     ThisLeng= ColWidth(From, To);
178     for (i=0;i<ColumnData[From].PrefixSpace; i++)
179         RuleSpace[i]= ' ';
180     for (; i<ThisLeng; i++)
181         RuleSpace[i]= '-';
182     RuleSpace[ThisLeng]= '\0';
183     return RuleSpace;
184 }
185
186 static char *TextRule(int From, int To, char *s) {
187     ColumnInfo *cd= &ColumnData[From];
188     int leng, nbrules, i, txtlength;
189     int RuleSpaceSize= ColWidth(0, ColumnDataCount());
190     char *RuleSpace= alloc(RuleSpaceSize), *tmp;
191
192     leng= strlen(s);
193     if(leng >= (RuleSpaceSize - cd->PrefixSpace))
194         leng = RuleSpaceSize - cd->PrefixSpace - 1;
195     ap_snprintf(RuleSpace, RuleSpaceSize, "%*s%*.*s ", cd->PrefixSpace, "", 
196                 leng, leng, s);
197     txtlength = cd->PrefixSpace + leng + 1;
198     nbrules = ColWidth(From,To) - txtlength;
199     for(tmp=RuleSpace + txtlength, i=nbrules ; i>0; tmp++,i--)
200         *tmp='-';
201     *tmp = '\0';
202     return RuleSpace;
203 }
204
205 char *sDivZero(float a, float b, int cn) {
206     ColumnInfo *cd= &ColumnData[cn];
207     static char PrtBuf[256];
208     if (b == 0.0)
209         ap_snprintf(PrtBuf, sizeof(PrtBuf),
210           "%*s", cd->Width, "-- ");
211     else
212         ap_snprintf(PrtBuf, sizeof(PrtBuf),
213           cd->Format, cd->Width, cd->Precision, a/b);
214     return PrtBuf;
215 }
216
217
218
219 int contline_next()
220 {
221     int ch;
222
223     ch = getc(logfile);
224     ungetc(ch, logfile);
225
226     return ch == ' ';
227 }
228
229 void addline(lp, str)
230 line_t **lp;
231 char *str;
232 {
233     line_t *new, *p, *q;
234
235     /* allocate new line node */
236     new = (line_t *) alloc(sizeof(line_t));
237     new->next = NULL;
238     new->str = stralloc(str);
239
240     /* add to end of list */
241     for(p = *lp, q = NULL; p != NULL; q = p, p = p->next);
242     if(q == NULL) *lp = new;
243     else q->next = new;
244 }
245
246 void usage()
247 {
248     error("Usage: amreport conf [-f output-file] [-l logfile] [-p postscript-file]");
249 }
250
251 int main(argc, argv)
252 int argc;
253 char **argv;
254 {
255     char *conffile;
256     char *conf_diskfile;
257     char *conf_tapelist;
258     char *conf_infofile;
259     char *logfname, *psfname, *outfname, *subj_str = NULL;
260     tapetype_t *tp;
261     int fd, opt;
262     unsigned long malloc_hist_1, malloc_size_1;
263     unsigned long malloc_hist_2, malloc_size_2;
264     char *mail_cmd = NULL, *printer_cmd = NULL;
265     extern int optind;
266     char my_cwd[STR_SIZE];
267     char *ColumnSpec = "";
268     char *errstr = NULL;
269     int cn;
270
271     for(fd = 3; fd < FD_SETSIZE; fd++) {
272         /*
273          * Make sure nobody spoofs us with a lot of extra open files
274          * that would cause an open we do to get a very high file
275          * descriptor, which in turn might be used as an index into
276          * an array (e.g. an fd_set).
277          */
278         close(fd);
279     }
280
281     set_pname("amreport");
282
283     malloc_size_1 = malloc_inuse(&malloc_hist_1);
284
285     /* Process options */
286     
287     erroutput_type = ERR_INTERACTIVE;
288     outfname = NULL;
289     psfname = NULL;
290     logfname = NULL;
291
292     if (getcwd(my_cwd, sizeof(my_cwd)) == NULL) {
293         error("cannot determine current working directory");
294     }
295
296     if (argc < 2) {
297         config_dir = stralloc2(my_cwd, "/");
298         if ((config_name = strrchr(my_cwd, '/')) != NULL) {
299             config_name = stralloc(config_name + 1);
300         }
301     } else {
302         if (argv[1][0] == '-') {
303             usage();
304             return 1;
305         }
306         config_name = stralloc(argv[1]);
307         config_dir = vstralloc(CONFIG_DIR, "/", config_name, "/", NULL);
308         --argc; ++argv;
309         while((opt = getopt(argc, argv, "f:l:p:")) != EOF) {
310             switch(opt) {
311             case 'f':
312                 if (outfname != NULL) {
313                     error("you may specify at most one -f");
314                 }
315                 if (*optarg == '/') {
316                     outfname = stralloc(optarg);
317                 } else {
318                     outfname = vstralloc(my_cwd, "/", optarg, NULL);
319                 }
320                 break;
321             case 'l':
322                 if (logfname != NULL) {
323                     error("you may specify at most one -l");
324                 }
325                 if (*optarg == '/') {
326                     logfname = stralloc(optarg);
327                 } else {
328                     logfname = vstralloc(my_cwd, "/", optarg, NULL);
329                 }
330                 break;
331             case 'p':
332                 if (psfname != NULL) {
333                     error("you may specify at most one -p");
334                 }
335                 if (*optarg == '/') {
336                     psfname = stralloc(optarg);
337                 } else {
338                     psfname = vstralloc(my_cwd, "/", optarg, NULL);
339                 }
340                 break;
341             case '?':
342             default:
343                 usage();
344                 return 1;
345             }
346         }
347
348         argc -= optind;
349         argv += optind;
350
351         if (argc > 1) {
352             usage();
353             return 1;
354         }
355     }
356
357 #if !defined MAILER
358     if(!outfname) {
359         printf("You must run amreport with '-f <output file>' because configure\n");
360         printf("didn't find a mailer.\n");
361         exit (1);
362     }
363 #endif
364
365     safe_cd();
366
367     /* read configuration files */
368
369     conffile = stralloc2(config_dir, CONFFILE_NAME);
370     if(read_conffile(conffile)) {
371         error("errors processing config file \"%s\"", conffile);
372     }
373     amfree(conffile);
374     conf_diskfile = getconf_str(CNF_DISKFILE);
375     if (*conf_diskfile == '/') {
376         conf_diskfile = stralloc(conf_diskfile);
377     } else {
378         conf_diskfile = stralloc2(config_dir, conf_diskfile);
379     }
380     if((diskq = read_diskfile(conf_diskfile)) == NULL) {
381         error("could not load disklist \"%s\"", conf_diskfile);
382     }
383     amfree(conf_diskfile);
384     conf_tapelist = getconf_str(CNF_TAPELIST);
385     if (*conf_tapelist == '/') {
386         conf_tapelist = stralloc(conf_tapelist);
387     } else {
388         conf_tapelist = stralloc2(config_dir, conf_tapelist);
389     }
390     if(read_tapelist(conf_tapelist)) {
391         error("could not read tapelist \"%s\"", conf_tapelist);
392     }
393     amfree(conf_tapelist);
394     conf_infofile = getconf_str(CNF_INFOFILE);
395     if (*conf_infofile == '/') {
396         conf_infofile = stralloc(conf_infofile);
397     } else {
398         conf_infofile = stralloc2(config_dir, conf_infofile);
399     }
400     if(open_infofile(conf_infofile)) {
401         error("could not open info db \"%s\"", conf_infofile);
402     }
403     amfree(conf_infofile);
404
405     today_datestamp = construct_datestamp(NULL);
406
407     ColumnSpec = getconf_str(CNF_COLUMNSPEC);
408     if(SetColumDataFromString(ColumnData, ColumnSpec, &errstr) < 0) {
409         curlog = L_ERROR;
410         curprog = P_REPORTER;
411         curstr = errstr;
412         handle_error();
413         amfree(errstr);
414         curstr = NULL;
415         ColumnSpec = "";                /* use the default */
416         if(SetColumDataFromString(ColumnData, ColumnSpec, &errstr) < 0) {
417             curlog = L_ERROR;
418             curprog = P_REPORTER;
419             curstr = errstr;
420             handle_error();
421             amfree(errstr);
422             curstr = NULL;
423         }
424     }
425     for (cn = 0; ColumnData[cn].Name != NULL; cn++) {
426         if (ColumnData[cn].MaxWidth) {
427             MaxWidthsRequested = 1;
428             break;
429         }
430     }
431
432     if(!logfname) {
433         char *conf_logdir;
434
435         conf_logdir = getconf_str(CNF_LOGDIR);
436         if (*conf_logdir == '/') {
437             conf_logdir = stralloc(conf_logdir);
438         } else {
439             conf_logdir = stralloc2(config_dir, conf_logdir);
440         }
441         logfname = vstralloc(conf_logdir, "/", "log", NULL);
442         amfree(conf_logdir);
443     }
444
445     if((logfile = fopen(logfname, "r")) == NULL) {
446         curlog = L_ERROR;
447         curprog = P_REPORTER;
448         curstr = vstralloc("could not open log ",
449                            logfname,
450                            ": ",
451                            strerror(errno),
452                            NULL);
453         handle_error();
454         amfree(curstr);
455     }
456
457     while(logfile && get_logline(logfile)) {
458         switch(curlog) {
459         case L_START:   handle_start(); break;
460         case L_FINISH:  handle_finish(); break;
461
462         case L_INFO:    handle_note(); break;
463         case L_WARNING: handle_note(); break;
464
465         case L_SUMMARY: handle_summary(); break;
466         case L_STATS:   handle_stats(); break;
467
468         case L_ERROR:   handle_error(); break;
469         case L_FATAL:   handle_error(); break;
470
471         case L_DISK:    handle_disk(); break;
472
473         case L_SUCCESS: handle_success(); break;
474         case L_STRANGE: handle_strange(); break;
475         case L_FAIL:    handle_failed(); break;
476
477         default:
478             curlog = L_ERROR;
479             curprog = P_REPORTER;
480             curstr = stralloc2("unexpected log line: ", curstr);
481             handle_error();
482             amfree(curstr);
483         }
484     }
485     afclose(logfile);
486     close_infofile();
487     if(!amflush_run)
488         generate_missing();
489
490     subj_str = vstralloc(getconf_str(CNF_ORG),
491                          " ", amflush_run ? "AMFLUSH" : "AMANDA",
492                          " ", "MAIL REPORT FOR",
493                          " ", nicedate(run_datestamp ? atoi(run_datestamp) : 0),
494                          NULL);
495         
496     /* lookup the tapetype and printer type from the amanda.conf file. */
497     tp = lookup_tapetype(getconf_str(CNF_TAPETYPE));
498     printer = getconf_str(CNF_PRINTER);
499
500     /* ignore SIGPIPE so if a child process dies we do not also go away */
501     signal(SIGPIPE, SIG_IGN);
502
503     /* open pipe to mailer */
504
505     if(outfname) {
506         /* output to a file */
507         if((mailf = fopen(outfname,"w")) == NULL) {
508             error("could not open output file: %s %s", outfname, strerror(errno));
509         }
510         fprintf(mailf, "To: %s\n", getconf_str(CNF_MAILTO));
511         fprintf(mailf, "Subject: %s\n\n", subj_str);
512
513     } else {
514 #ifdef MAILER
515         mail_cmd = vstralloc(MAILER,
516                              " -s", " \"", subj_str, "\"",
517                              " ", getconf_str(CNF_MAILTO),
518                              NULL);
519         if((mailf = popen(mail_cmd, "w")) == NULL)
520             error("could not open pipe to \"%s\": %s",
521                   mail_cmd, strerror(errno));
522 #endif
523     }
524
525     /* open pipe to print spooler if necessary) */
526
527     if(psfname) {
528         /* if the postscript_label_template (tp->lbl_templ) field is not */
529         /* the empty string (i.e. it is set to something), open the      */
530         /* postscript debugging file for writing.                        */
531         if ((strcmp(tp->lbl_templ, "")) != 0) {
532             if ((postscript = fopen(psfname, "w")) == NULL) {
533                 curlog = L_ERROR;
534                 curprog = P_REPORTER;
535                 curstr = vstralloc("could not open ",
536                                    psfname,
537                                    ": ",
538                                    strerror(errno),
539                                    NULL);
540                 handle_error();
541                 amfree(curstr);
542             }
543         }
544     } else {
545 #ifdef LPRCMD
546         if (strcmp(printer, "") != 0)   /* alternate printer is defined */
547             /* print to the specified printer */
548 #ifdef LPRFLAG
549             printer_cmd = vstralloc(LPRCMD, " ", LPRFLAG, printer, NULL);
550 #else
551             printer_cmd = vstralloc(LPRCMD, NULL);
552 #endif
553         else
554             /* print to the default printer */
555             printer_cmd = vstralloc(LPRCMD, NULL);
556 #endif
557
558         if ((strcmp(tp->lbl_templ, "")) != 0) {
559 #ifdef LPRCMD
560             if ((postscript = popen(printer_cmd, "w")) == NULL) {
561                 curlog = L_ERROR;
562                 curprog = P_REPORTER;
563                 curstr = vstralloc("could not open pipe to ",
564                                    printer_cmd,
565                                    ": ",
566                                    strerror(errno),
567                                    NULL);
568                 handle_error();
569                 amfree(curstr);
570             }
571 #else
572             curlog = L_ERROR;
573             curprog = P_REPORTER;
574             curstr = stralloc("no printer command defined");
575             handle_error();
576             amfree(curstr);
577 #endif
578         }
579     }
580
581     amfree(subj_str);
582
583
584     if(!got_finish) fputs("*** THE DUMPS DID NOT FINISH PROPERLY!\n\n", mailf);
585
586     output_tapeinfo();
587
588     if(errsum) {
589         fprintf(mailf,"\nFAILURE AND STRANGE DUMP SUMMARY:\n");
590         output_lines(errsum, mailf);
591     }
592     fputs("\n\n", mailf);
593
594     output_stats();
595
596     if(errdet) {
597         fprintf(mailf,"\n\014\nFAILED AND STRANGE DUMP DETAILS:\n");
598         output_lines(errdet, mailf);
599     }
600     if(notes) {
601         fprintf(mailf,"\n\014\nNOTES:\n");
602         output_lines(notes, mailf);
603     }
604     sort_disks();
605     if(sortq.head != NULL) {
606         fprintf(mailf,"\n\014\nDUMP SUMMARY:\n");
607         output_summary();
608     }
609     fprintf(mailf,"\n(brought to you by Amanda version %s)\n",
610             version());
611
612     if (postscript) {
613         do_postscript_output();
614     }
615
616
617     /* close postscript file */
618     if (psfname && postscript) {
619         /* it may be that postscript is NOT opened */
620         afclose(postscript);
621     }
622     else {
623         if (postscript != NULL && pclose(postscript) != 0)
624             error("printer command failed: %s", printer_cmd);
625         postscript = NULL;
626     }
627
628     /* close output file */
629     if(outfname) {
630         afclose(mailf);
631     }
632     else {
633         if(pclose(mailf) != 0)
634             error("mail command failed: %s", mail_cmd);
635         mailf = NULL;
636     }
637
638     amfree(run_datestamp);
639     amfree(tape_labels);
640     amfree(config_dir);
641     amfree(config_name);
642     amfree(printer_cmd);
643     amfree(mail_cmd);
644     amfree(logfname);
645
646     malloc_size_2 = malloc_inuse(&malloc_hist_2);
647
648     if(malloc_size_1 != malloc_size_2) {
649         malloc_list(fileno(stderr), malloc_hist_1, malloc_hist_2);
650     }
651
652     return 0;
653 }
654
655 /* ----- */
656
657 #define mb(f)   ((f)/1024.0)            /* kbytes -> mbytes */
658 #define pct(f)  ((f)*100.0)             /* percent */
659 #define hrmn(f) ((int)(f)+30)/3600, (((int)(f)+30)%3600)/60
660 #define mnsc(f) ((int)(f+0.5))/60, ((int)(f+0.5)) % 60
661
662 #define divzero(fp,a,b)                     \
663     do {                                    \
664         double q = (b);                     \
665         if (q == 0.0)                       \
666             fprintf((fp),"  -- ");          \
667         else if ((q = (a)/q) >= 999.95)     \
668             fprintf((fp), "###.#");         \
669         else                                \
670             fprintf((fp), "%5.1f",q);       \
671     } while(0)
672 #define divzero_wide(fp,a,b)                \
673     do {                                    \
674         double q = (b);                     \
675         if (q == 0.0)                       \
676             fprintf((fp),"    -- ");        \
677         else if ((q = (a)/q) >= 99999.95)   \
678             fprintf((fp), "#####.#");       \
679         else                                \
680             fprintf((fp), "%7.1f",q);       \
681     } while(0)
682
683 void output_stats()
684 {
685     double idle_time;
686     tapetype_t *tp = lookup_tapetype(getconf_str(CNF_TAPETYPE));
687     int tapesize, marksize, lv, first;
688
689     tapesize = tp->length;
690     marksize = tp->filemark;
691
692     stats[2].dumpdisks   = stats[0].dumpdisks   + stats[1].dumpdisks;
693     stats[2].tapedisks   = stats[0].tapedisks   + stats[1].tapedisks;
694     stats[2].outsize     = stats[0].outsize     + stats[1].outsize;
695     stats[2].origsize    = stats[0].origsize    + stats[1].origsize;
696     stats[2].tapesize    = stats[0].tapesize    + stats[1].tapesize;
697     stats[2].coutsize    = stats[0].coutsize    + stats[1].coutsize;
698     stats[2].corigsize   = stats[0].corigsize   + stats[1].corigsize;
699     stats[2].taper_time  = stats[0].taper_time  + stats[1].taper_time;
700     stats[2].dumper_time = stats[0].dumper_time + stats[1].dumper_time;
701
702     if(!got_finish)     /* no driver finish line, estimate total run time */
703         total_time = stats[2].taper_time + startup_time;
704
705     idle_time = (total_time - startup_time) - stats[2].taper_time;
706     if(idle_time < 0) idle_time = 0.0;
707
708     fprintf(mailf,"STATISTICS:\n");
709     fprintf(mailf,
710             "                          Total       Full      Daily\n");
711     fprintf(mailf,
712             "                        --------   --------   --------\n");
713
714     fprintf(mailf,
715             "Estimate Time (hrs:min)   %2d:%02d\n", hrmn(startup_time));
716
717     fprintf(mailf,
718             "Run Time (hrs:min)        %2d:%02d\n", hrmn(total_time));
719
720     fprintf(mailf,
721             "Dump Time (hrs:min)       %2d:%02d      %2d:%02d      %2d:%02d\n",
722             hrmn(stats[2].dumper_time), hrmn(stats[0].dumper_time),
723             hrmn(stats[1].dumper_time));
724
725     fprintf(mailf,
726             "Output Size (meg)      %8.1f   %8.1f   %8.1f\n",
727             mb(stats[2].outsize), mb(stats[0].outsize), mb(stats[1].outsize));
728
729     fprintf(mailf,
730             "Original Size (meg)    %8.1f   %8.1f   %8.1f\n",
731             mb(stats[2].origsize), mb(stats[0].origsize),
732             mb(stats[1].origsize));
733
734     fprintf(mailf, "Avg Compressed Size (%%)   ");
735     divzero(mailf, pct(stats[2].coutsize),stats[2].corigsize);
736     fputs("      ", mailf);
737     divzero(mailf, pct(stats[0].coutsize),stats[0].corigsize);
738     fputs("      ", mailf);
739     divzero(mailf, pct(stats[1].coutsize),stats[1].corigsize);
740
741     if(stats[1].dumpdisks > 0) fputs("   (level:#disks ...)", mailf);
742     putc('\n', mailf);
743
744     fprintf(mailf,
745             "Filesystems Dumped         %4d       %4d       %4d",
746             stats[2].dumpdisks, stats[0].dumpdisks, stats[1].dumpdisks);
747
748     if(stats[1].dumpdisks > 0) {
749         first = 1;
750         for(lv = 1; lv < 10; lv++) if(dumpdisks[lv]) {
751             fputs(first?"   (":" ", mailf);
752             first = 0;
753             fprintf(mailf, "%d:%d", lv, dumpdisks[lv]);
754         }
755         putc(')', mailf);
756     }
757     putc('\n', mailf);
758
759     fprintf(mailf, "Avg Dump Rate (k/s)     ");
760     divzero_wide(mailf, stats[2].outsize,stats[2].dumper_time);
761     fputs("    ", mailf);
762     divzero_wide(mailf, stats[0].outsize,stats[0].dumper_time);
763     fputs("    ", mailf);
764     divzero_wide(mailf, stats[1].outsize,stats[1].dumper_time);
765     putc('\n', mailf);
766
767     putc('\n', mailf);
768     fprintf(mailf,
769             "Tape Time (hrs:min)       %2d:%02d      %2d:%02d      %2d:%02d\n",
770             hrmn(stats[2].taper_time), hrmn(stats[0].taper_time),
771             hrmn(stats[1].taper_time));
772
773     fprintf(mailf,
774             "Tape Size (meg)        %8.1f   %8.1f   %8.1f\n",
775             mb(stats[2].tapesize), mb(stats[0].tapesize),
776             mb(stats[1].tapesize));
777
778     fprintf(mailf, "Tape Used (%%)             ");
779     divzero(mailf, pct(stats[2].tapesize+marksize*stats[2].tapedisks),tapesize);
780     fputs("      ", mailf);
781     divzero(mailf, pct(stats[0].tapesize+marksize*stats[0].tapedisks),tapesize);
782     fputs("      ", mailf);
783     divzero(mailf, pct(stats[1].tapesize+marksize*stats[1].tapedisks),tapesize);
784
785     if(stats[1].tapedisks > 0) fputs("   (level:#disks ...)", mailf);
786     putc('\n', mailf);
787
788     fprintf(mailf,
789             "Filesystems Taped          %4d       %4d       %4d",
790             stats[2].tapedisks, stats[0].tapedisks, stats[1].tapedisks);
791
792     if(stats[1].tapedisks > 0) {
793         first = 1;
794         for(lv = 1; lv < 10; lv++) if(tapedisks[lv]) {
795             fputs(first?"   (":" ", mailf);
796             first = 0;
797             fprintf(mailf, "%d:%d", lv, tapedisks[lv]);
798         }
799         putc(')', mailf);
800     }
801     putc('\n', mailf);
802
803     fprintf(mailf, "Avg Tp Write Rate (k/s) ");
804     divzero_wide(mailf, stats[2].tapesize,stats[2].taper_time);
805     fputs("    ", mailf);
806     divzero_wide(mailf, stats[0].tapesize,stats[0].taper_time);
807     fputs("    ", mailf);
808     divzero_wide(mailf, stats[1].tapesize,stats[1].taper_time);
809     putc('\n', mailf);
810
811     if(stats_by_tape) {
812         int label_length = strlen(stats_by_tape->label) + 5;
813         fprintf(mailf,"\nUSAGE BY TAPE:\n");
814         fprintf(mailf,"  %-*s  Time      Size      %%    Nb\n",
815                 label_length, "Label");
816         for(current_tape = stats_by_tape; current_tape != NULL;
817             current_tape = current_tape->next) {
818             fprintf(mailf, "  %-*s", label_length, current_tape->label);
819             fprintf(mailf, " %2d:%02d", hrmn(current_tape->taper_time));
820             fprintf(mailf, " %9.1f  ", mb(current_tape->coutsize));
821             divzero(mailf, pct(current_tape->coutsize + 
822                                marksize * current_tape->tapedisks),
823                            tapesize);
824             fprintf(mailf, "  %4d\n", current_tape->tapedisks);
825         }
826     }
827
828 }
829
830 /* ----- */
831
832 void output_tapeinfo()
833 {
834     tape_t *tp, *lasttp;
835     int run_tapes;
836     int skip = 0;
837
838     if (last_run_tapes > 0) {
839         if(amflush_run)
840             fprintf(mailf, "The dumps were flushed to tape%s %s.\n",
841                     last_run_tapes == 1 ? "" : "s",
842                     tape_labels ? tape_labels : "");
843         else
844             fprintf(mailf, "These dumps were to tape%s %s.\n",
845                     last_run_tapes == 1 ? "" : "s",
846                     tape_labels ? tape_labels : "");
847     }
848
849     if(degraded_mode) {
850         fprintf(mailf,
851                 "*** A TAPE ERROR OCCURRED: %s.\n", tapestart_error);
852         fputs("Some dumps may have been left in the holding disk.\n", mailf);
853         fprintf(mailf,
854                 "Run amflush%s to flush them to tape.\n",
855                 amflush_run ? " again" : "");
856     }
857
858     tp = lookup_last_reusable_tape(skip);
859
860     run_tapes = getconf_int(CNF_RUNTAPES);
861
862     if (run_tapes <= 1)
863         fputs("The next tape Amanda expects to use is: ", mailf);
864     else
865         fprintf(mailf, "The next %d tapes Amanda expects to used are: ",
866                 run_tapes);
867     
868     while(run_tapes > 0) {
869         if(tp != NULL)
870             fprintf(mailf, "%s", tp->label);
871         else
872             fputs("a new tape", mailf);
873
874         if(run_tapes > 1) fputs(", ", mailf);
875
876         run_tapes -= 1;
877         skip++;
878         tp = lookup_last_reusable_tape(skip);
879     }
880     fputs(".\n", mailf);
881
882     lasttp = lookup_tapepos(lookup_nb_tape());
883     run_tapes = getconf_int(CNF_RUNTAPES);
884     if(lasttp && run_tapes > 0 && lasttp->datestamp == 0) {
885         int c = 0;
886         while(lasttp && run_tapes > 0 && lasttp->datestamp == 0) {
887             c++;
888             lasttp = lasttp->prev;
889             run_tapes--;
890         }
891         lasttp = lookup_tapepos(lookup_nb_tape());
892         if(c == 1) {
893             fprintf(mailf, "The next new tape already labelled is: %s.\n",
894                     lasttp->label);
895         }
896         else {
897             fprintf(mailf, "The next %d new tapes already labelled are: %s", c,
898                     lasttp->label);
899             lasttp = lasttp->prev;
900             c--;
901             while(lasttp && c > 0 && lasttp->datestamp == 0) {
902                 fprintf(mailf, ", %s", lasttp->label);
903                 lasttp = lasttp->prev;
904                 c--;
905             }
906             fprintf(mailf, ".\n");
907         }
908     }
909 }
910
911 /* ----- */
912
913 void output_lines(lp, f)
914 line_t *lp;
915 FILE *f;
916 {
917     line_t *next;
918
919     while(lp) {
920         fputs(lp->str, f);
921         amfree(lp->str);
922         fputc('\n', f);
923         next = lp->next;
924         amfree(lp);
925         lp = next;
926     }
927 }
928
929 /* ----- */
930
931 int sort_by_time(a, b)
932 disk_t *a, *b;
933 {
934     return data(b)->dumper.sec - data(a)->dumper.sec;
935 }
936
937 int sort_by_name(a, b)
938 disk_t *a, *b;
939 {
940     int rc;
941
942     rc = strcmp(a->host->hostname, b->host->hostname);
943     if(rc == 0) rc = strcmp(a->name, b->name);
944     return rc;
945 }
946
947 void sort_disks()
948 {
949     disk_t *dp;
950
951     sortq.head = sortq.tail = NULL;
952     while(!empty(*diskq)) {
953         dp = dequeue_disk(diskq);
954         if(data(dp) == NULL) { /* create one */
955             find_repdata(dp, run_datestamp, 0);
956         }
957         insert_disk(&sortq, dp, sort_by_name);
958     }
959 }
960
961 void CheckStringMax(ColumnInfo *cd, char *s) {
962     if (cd->MaxWidth) {
963         int l= strlen(s);
964         if (cd->Width < l)
965             cd->Width= l;
966     }
967 }
968
969 void CheckIntMax(ColumnInfo *cd, int n) {
970     if (cd->MaxWidth) {
971         char testBuf[200];
972         int l;
973         ap_snprintf(testBuf, sizeof(testBuf),
974           cd->Format, cd->Width, cd->Precision, n);
975         l= strlen(testBuf);
976         if (cd->Width < l)
977             cd->Width= l;
978     }
979 }
980
981 void CheckFloatMax(ColumnInfo *cd, double d) {
982     if (cd->MaxWidth) {
983         char testBuf[200];
984         int l;
985         ap_snprintf(testBuf, sizeof(testBuf),
986           cd->Format, cd->Width, cd->Precision, d);
987         l= strlen(testBuf);
988         if (cd->Width < l)
989             cd->Width= l;
990     }
991 }
992
993 static int HostName;
994 static int Disk;
995 static int Level;
996 static int OrigKB;
997 static int OutKB;
998 static int Compress;
999 static int DumpTime;
1000 static int DumpRate;
1001 static int TapeTime;
1002 static int TapeRate;
1003
1004 void CalcMaxWidth() {
1005     /* we have to look for columspec's, that require the recalculation.
1006      * we do here the same loops over the sortq as is done in
1007      * output_summary. So, if anything is changed there, we have to
1008      * change this here also.
1009      *                                                  ElB, 1999-02-24.
1010      */
1011     disk_t *dp;
1012     float f;
1013     repdata_t *repdata;
1014
1015     for(dp = sortq.head; dp != NULL; dp = dp->next) {
1016       if(dp->todo) {
1017         for(repdata = data(dp); repdata != NULL; repdata = repdata->next) {
1018             ColumnInfo *cd;
1019             char TimeRateBuffer[40];
1020
1021             CheckStringMax(&ColumnData[HostName], dp->host->hostname);
1022             CheckStringMax(&ColumnData[Disk], dp->name);
1023             if (repdata->dumper.result == L_BOGUS && 
1024                 repdata->taper.result == L_BOGUS)
1025                 continue;
1026             CheckIntMax(&ColumnData[Level], repdata->level);
1027             if(repdata->dumper.result == L_SUCCESS) {
1028                 CheckFloatMax(&ColumnData[OrigKB], repdata->dumper.origsize);
1029                 CheckFloatMax(&ColumnData[OutKB], repdata->dumper.outsize);
1030                 if(dp->compress == COMP_NONE)
1031                     f = 0.0;
1032                 else 
1033                     f = repdata->dumper.origsize;
1034                 CheckStringMax(&ColumnData[Disk], 
1035                         sDivZero(pct(repdata->dumper.outsize), f, Compress));
1036
1037                 if(!amflush_run)
1038                     ap_snprintf(TimeRateBuffer, sizeof(TimeRateBuffer),
1039                                 "%3d:%02d", mnsc(repdata->dumper.sec));
1040                 else
1041                     ap_snprintf(TimeRateBuffer, sizeof(TimeRateBuffer),
1042                                 "N/A ");
1043                 CheckStringMax(&ColumnData[DumpTime], TimeRateBuffer);
1044
1045                 CheckFloatMax(&ColumnData[DumpRate], repdata->dumper.kps); 
1046             }
1047
1048             cd= &ColumnData[TapeTime];
1049             if(repdata->taper.result == L_FAIL) {
1050                 CheckStringMax(cd, "FAILED");
1051                 continue;
1052             }
1053             if(repdata->taper.result == L_SUCCESS)
1054                 ap_snprintf(TimeRateBuffer, sizeof(TimeRateBuffer), 
1055                   "%3d:%02d", mnsc(repdata->taper.sec));
1056             else
1057                 ap_snprintf(TimeRateBuffer, sizeof(TimeRateBuffer),
1058                   "N/A ");
1059             CheckStringMax(cd, TimeRateBuffer);
1060
1061             cd= &ColumnData[TapeRate];
1062             if(repdata->taper.result == L_SUCCESS)
1063                 CheckFloatMax(cd, repdata->taper.kps);
1064             else
1065                 CheckStringMax(cd, "N/A ");
1066         }
1067       }
1068     }
1069 }
1070
1071 void output_summary()
1072 {
1073     disk_t *dp;
1074     repdata_t *repdata;
1075     char *ds="DUMPER STATS";
1076     char *ts=" TAPER STATS";
1077     char *tmp;
1078
1079     int i, h, w1, wDump, wTape;
1080     float outsize, origsize;
1081     float f;
1082
1083     HostName = StringToColumn("HostName");
1084     Disk = StringToColumn("Disk");
1085     Level = StringToColumn("Level");
1086     OrigKB = StringToColumn("OrigKB");
1087     OutKB = StringToColumn("OutKB");
1088     Compress = StringToColumn("Compress");
1089     DumpTime = StringToColumn("DumpTime");
1090     DumpRate = StringToColumn("DumpRate");
1091     TapeTime = StringToColumn("TapeTime");
1092     TapeRate = StringToColumn("TapeRate");
1093
1094     /* at first determine if we have to recalculate our widths */
1095     if (MaxWidthsRequested)
1096         CalcMaxWidth();
1097
1098     /* title for Dumper-Stats */
1099     w1= ColWidth(HostName, Level);
1100     wDump= ColWidth(OrigKB, DumpRate);
1101     wTape= ColWidth(TapeTime, TapeRate);
1102
1103     /* print centered top titles */
1104     h= strlen(ds);
1105     if (h > wDump) {
1106         h= 0;
1107     } else {
1108         h= (wDump-h)/2;
1109     }
1110     fprintf(mailf, "%*s", w1+h, "");
1111     fprintf(mailf, "%-*s", wDump-h, ds);
1112     h= strlen(ts);
1113     if (h > wTape) {
1114         h= 0;
1115     } else {
1116         h= (wTape-h)/2;
1117     }
1118     fprintf(mailf, "%*s", h, "");
1119     fprintf(mailf, "%-*s", wTape-h, ts);
1120     fputc('\n', mailf);
1121
1122     /* print the titles */
1123     for (i=0; ColumnData[i].Name != NULL; i++) {
1124         char *fmt;
1125         ColumnInfo *cd= &ColumnData[i];
1126         fprintf(mailf, "%*s", cd->PrefixSpace, "");
1127         if (cd->Format[1] == '-')
1128             fmt= "%-*s";
1129         else
1130             fmt= "%*s";
1131         fprintf(mailf, fmt, cd->Width, cd->Title);
1132     }
1133     fputc('\n', mailf);
1134
1135     /* print the rules */
1136     fputs(tmp=Rule(HostName, Level), mailf); amfree(tmp);
1137     fputs(tmp=Rule(OrigKB, DumpRate), mailf); amfree(tmp);
1138     fputs(tmp=Rule(TapeTime, TapeRate), mailf); amfree(tmp);
1139     fputc('\n', mailf);
1140
1141     for(dp = sortq.head; dp != NULL; dp = dp->next) {
1142       if(dp->todo) {
1143         ColumnInfo *cd;
1144         char TimeRateBuffer[40];
1145         for(repdata = data(dp); repdata != NULL; repdata = repdata->next) {
1146             int devlen;
1147
1148             cd= &ColumnData[HostName];
1149             fprintf(mailf, "%*s", cd->PrefixSpace, "");
1150             fprintf(mailf, cd->Format, cd->Width, cd->Width, dp->host->hostname);
1151
1152             cd= &ColumnData[Disk];
1153             fprintf(mailf, "%*s", cd->PrefixSpace, "");
1154             devlen= strlen(dp->name);
1155             if (devlen > cd->Width) {
1156                 fputc('-', mailf); 
1157                 fprintf(mailf, cd->Format, cd->Width-1, cd->Precision-1,
1158                   dp->name+devlen - (cd->Width-1) );
1159             }
1160             else
1161                 fprintf(mailf, cd->Format, cd->Width, cd->Width, dp->name);
1162
1163             cd= &ColumnData[Level];
1164             if (repdata->dumper.result == L_BOGUS &&
1165                 repdata->taper.result  == L_BOGUS) {
1166               if(amflush_run){
1167                 fprintf(mailf, "%*s%s\n", cd->PrefixSpace+cd->Width, "",
1168                         tmp=TextRule(OrigKB, TapeRate, "NO FILE TO FLUSH"));
1169               } else {
1170                 fprintf(mailf, "%*s%s\n", cd->PrefixSpace+cd->Width, "",
1171                         tmp=TextRule(OrigKB, TapeRate, "MISSING"));
1172               }
1173               amfree(tmp);
1174               continue;
1175             }
1176             
1177             cd= &ColumnData[Level];
1178             fprintf(mailf, "%*s", cd->PrefixSpace, "");
1179             fprintf(mailf, cd->Format, cd->Width, cd->Precision,repdata->level);
1180
1181             if (repdata->dumper.result == L_SKIPPED) {
1182                 fprintf(mailf, "%s\n",
1183                         tmp=TextRule(OrigKB, TapeRate, "SKIPPED"));
1184                 amfree(tmp);
1185                 continue;
1186             }
1187             if (repdata->dumper.result == L_FAIL) {
1188                 fprintf(mailf, "%s\n",
1189                         tmp=TextRule(OrigKB, TapeRate, "FAILED"));
1190                 amfree(tmp);
1191                 continue;
1192             }
1193
1194             if(repdata->dumper.result == L_SUCCESS)
1195                 origsize = repdata->dumper.origsize;
1196             else
1197                 origsize = repdata->taper.origsize;
1198
1199             if(repdata->taper.result == L_SUCCESS) 
1200                 outsize  = repdata->taper.outsize;
1201             else
1202                 outsize  = repdata->dumper.outsize;
1203
1204             cd= &ColumnData[OrigKB];
1205             fprintf(mailf, "%*s", cd->PrefixSpace, "");
1206             if(origsize != 0.0)
1207                 fprintf(mailf, cd->Format, cd->Width, cd->Precision, origsize);
1208             else
1209                 fprintf(mailf, "%*.*s", cd->Width, cd->Width, "N/A");
1210
1211             cd= &ColumnData[OutKB];
1212             fprintf(mailf, "%*s", cd->PrefixSpace, "");
1213
1214             fprintf(mailf, cd->Format, cd->Width, cd->Precision, outsize);
1215                 
1216             cd= &ColumnData[Compress];
1217             fprintf(mailf, "%*s", cd->PrefixSpace, "");
1218
1219             if(dp->compress == COMP_NONE)
1220                 f = 0.0;
1221             else if(origsize < 1.0)
1222                 f = 0.0;
1223             else
1224                 f = origsize;
1225
1226             fputs(sDivZero(pct(outsize), f, Compress), mailf);
1227
1228             cd= &ColumnData[DumpTime];
1229             fprintf(mailf, "%*s", cd->PrefixSpace, "");
1230             if(repdata->dumper.result == L_SUCCESS)
1231                 ap_snprintf(TimeRateBuffer, sizeof(TimeRateBuffer),
1232                   "%3d:%02d", mnsc(repdata->dumper.sec));
1233             else
1234                 ap_snprintf(TimeRateBuffer, sizeof(TimeRateBuffer),
1235                   "N/A ");
1236             fprintf(mailf, cd->Format, cd->Width, cd->Width, TimeRateBuffer);
1237
1238             cd= &ColumnData[DumpRate];
1239             fprintf(mailf, "%*s", cd->PrefixSpace, "");
1240             if(repdata->dumper.result == L_SUCCESS)
1241                 fprintf(mailf, cd->Format, cd->Width, cd->Precision, repdata->dumper.kps);
1242             else
1243                 fprintf(mailf, "%*s", cd->Width, "N/A ");
1244
1245             cd= &ColumnData[TapeTime];
1246             fprintf(mailf, "%*s", cd->PrefixSpace, "");
1247             if(repdata->taper.result == L_FAIL) {
1248                 fprintf(mailf, "%s\n",
1249                         tmp=TextRule(TapeTime, TapeRate, "FAILED "));
1250                 amfree(tmp);
1251                 continue;
1252             }
1253
1254             if(repdata->taper.result == L_SUCCESS)
1255                 ap_snprintf(TimeRateBuffer, sizeof(TimeRateBuffer),
1256                   "%3d:%02d", mnsc(repdata->taper.sec));
1257             else
1258                 ap_snprintf(TimeRateBuffer, sizeof(TimeRateBuffer),
1259                   "N/A ");
1260             fprintf(mailf, cd->Format, cd->Width, cd->Width, TimeRateBuffer);
1261
1262             cd= &ColumnData[TapeRate];
1263             fprintf(mailf, "%*s", cd->PrefixSpace, "");
1264             if(repdata->taper.result == L_SUCCESS)
1265                 fprintf(mailf, cd->Format, cd->Width, cd->Precision, repdata->taper.kps);
1266             else
1267                 fprintf(mailf, "%*s", cd->Width, "N/A ");
1268             fputc('\n', mailf);
1269         }
1270       }
1271     }
1272 }
1273
1274 void bogus_line()
1275 {
1276     printf("line %d of log is bogus\n", curlinenum);
1277 }
1278
1279
1280 char *nicedate(datestamp)
1281 int datestamp;
1282 /*
1283  * Formats an integer of the form YYYYMMDD into the string
1284  * "Monthname DD, YYYY".  A pointer to the statically allocated string
1285  * is returned, so it must be copied to other storage (or just printed)
1286  * before calling nicedate() again.
1287  */
1288 {
1289     static char nice[64];
1290     static char *months[13] = { "BogusMonth",
1291         "January", "February", "March", "April", "May", "June",
1292         "July", "August", "September", "October", "November", "December"
1293     };
1294     int year, month, day;
1295
1296     year  = datestamp / 10000;
1297     day   = datestamp % 100;
1298     month = (datestamp / 100) % 100;
1299
1300     ap_snprintf(nice, sizeof(nice), "%s %d, %d", months[month], day, year);
1301
1302     return nice;
1303 }
1304
1305 void handle_start()
1306 {
1307     static int started = 0;
1308     char *label;
1309     char *s, *fp;
1310     int ch;
1311
1312     switch(curprog) {
1313     case P_TAPER:
1314         s = curstr;
1315         ch = *s++;
1316
1317         skip_whitespace(s, ch);
1318 #define sc "datestamp"
1319         if(ch == '\0' || strncmp(s - 1, sc, sizeof(sc)-1) != 0) {
1320             bogus_line();
1321             return;
1322         }
1323         s += sizeof(sc)-1;
1324         ch = s[-1];
1325 #undef sc
1326         skip_whitespace(s, ch);
1327         if(ch == '\0') {
1328             bogus_line();
1329             return;
1330         }
1331         fp = s - 1;
1332         skip_non_whitespace(s, ch);
1333         s[-1] = '\0';
1334         run_datestamp = newstralloc(run_datestamp, fp);
1335         s[-1] = ch;
1336
1337         skip_whitespace(s, ch);
1338 #define sc "label"
1339         if(ch == '\0' || strncmp(s - 1, sc, sizeof(sc)-1) != 0) {
1340             bogus_line();
1341             return;
1342         }
1343         s += sizeof(sc)-1;
1344         ch = s[-1];
1345 #undef sc
1346         skip_whitespace(s, ch);
1347         if(ch == '\0') {
1348             bogus_line();
1349             return;
1350         }
1351         fp = s - 1;
1352         skip_non_whitespace(s, ch);
1353         s[-1] = '\0';
1354
1355         label = stralloc(fp);
1356         s[-1] = ch;
1357
1358         if(tape_labels) {
1359             fp = vstralloc(tape_labels, ", ", label, NULL);
1360             amfree(tape_labels);
1361             tape_labels = fp;
1362         } else {
1363             tape_labels = stralloc(label);
1364         }
1365
1366         last_run_tapes++;
1367
1368         if(stats_by_tape == NULL) {
1369             stats_by_tape = current_tape = (taper_t *)alloc(sizeof(taper_t));
1370         }
1371         else {
1372             current_tape->next = (taper_t *)alloc(sizeof(taper_t));
1373             current_tape = current_tape->next;
1374         }
1375         current_tape->label = label;
1376         current_tape->taper_time = 0.0;
1377         current_tape->coutsize = 0.0;
1378         current_tape->corigsize = 0.0;
1379         current_tape->tapedisks = 0;
1380         current_tape->next = NULL;
1381         tapefcount = 0;
1382
1383         return;
1384     case P_PLANNER:
1385         normal_run = 1;
1386         break;
1387     case P_DRIVER:
1388         break;
1389     case P_AMFLUSH:
1390         amflush_run = 1;
1391         break;
1392     default:
1393         ;
1394     }
1395
1396     if(!started) {
1397         s = curstr;
1398         ch = *s++;
1399
1400         skip_whitespace(s, ch);
1401 #define sc "date"
1402         if(ch == '\0' || strncmp(s - 1, sc, sizeof(sc)-1) != 0) {
1403             return;                             /* ignore bogus line */
1404         }
1405         s += sizeof(sc)-1;
1406         ch = s[-1];
1407 #undef sc
1408         skip_whitespace(s, ch);
1409         if(ch == '\0') {
1410             bogus_line();
1411             return;
1412         }
1413         fp = s - 1;
1414         skip_non_whitespace(s, ch);
1415         s[-1] = '\0';
1416         run_datestamp = newstralloc(run_datestamp, fp);
1417         s[-1] = ch;
1418
1419         started = 1;
1420     }
1421     if(amflush_run && normal_run) {
1422         amflush_run = 0;
1423         addline(&notes,
1424      "  reporter: both amflush and planner output in log, ignoring amflush.");
1425     }
1426 }
1427
1428
1429 void handle_finish()
1430 {
1431     char *s;
1432     int ch;
1433
1434     if(curprog == P_DRIVER || curprog == P_AMFLUSH) {
1435         s = curstr;
1436         ch = *s++;
1437
1438         skip_whitespace(s, ch);
1439 #define sc "date"
1440         if(ch == '\0' || strncmp(s - 1, sc, sizeof(sc)-1) != 0) {
1441             bogus_line();
1442             return;
1443         }
1444         s += sizeof(sc)-1;
1445         ch = s[-1];
1446 #undef sc
1447
1448         skip_whitespace(s, ch);
1449         if(ch == '\0') {
1450             bogus_line();
1451             return;
1452         }
1453         skip_non_whitespace(s, ch);     /* ignore the date string */
1454
1455         skip_whitespace(s, ch);
1456 #define sc "time"
1457         if(ch == '\0' || strncmp(s - 1, sc, sizeof(sc)-1) != 0) {
1458             bogus_line();
1459             return;
1460         }
1461         s += sizeof(sc)-1;
1462         ch = s[-1];
1463 #undef sc
1464
1465         skip_whitespace(s, ch);
1466         if(ch == '\0') {
1467             bogus_line();
1468             return;
1469         }
1470         if(sscanf(s - 1, "%f", &total_time) != 1) {
1471             bogus_line();
1472             return;
1473         }
1474
1475         got_finish = 1;
1476     }
1477 }
1478
1479 void handle_stats()
1480 {
1481     char *s;
1482     int ch;
1483
1484     if(curprog == P_DRIVER) {
1485         s = curstr;
1486         ch = *s++;
1487
1488         skip_whitespace(s, ch);
1489 #define sc "startup time"
1490         if(ch == '\0' || strncmp(s - 1, sc, sizeof(sc)-1) != 0) {
1491             bogus_line();
1492             return;
1493         }
1494         s += sizeof(sc)-1;
1495         ch = s[-1];
1496 #undef sc
1497
1498         skip_whitespace(s, ch);
1499         if(ch == '\0') {
1500             bogus_line();
1501             return;
1502         }
1503         if(sscanf(s - 1, "%f", &startup_time) != 1) {
1504             bogus_line();
1505             return;
1506         }
1507     }
1508 }
1509
1510
1511 void handle_note()
1512 {
1513     char *str = NULL;
1514
1515     str = vstralloc("  ", program_str[curprog], ": ", curstr, NULL);
1516     addline(&notes, str);
1517     amfree(str);
1518 }
1519
1520
1521 /* ----- */
1522
1523 void handle_error()
1524 {
1525     char *s = NULL, *nl;
1526     int ch;
1527
1528     if(curlog == L_ERROR && curprog == P_TAPER) {
1529         s = curstr;
1530         ch = *s++;
1531
1532         skip_whitespace(s, ch);
1533 #define sc "no-tape"
1534         if(ch == '\0' || strncmp(s - 1, sc, sizeof(sc)-1) != 0) {
1535             bogus_line();
1536             return;
1537         }
1538         s += sizeof(sc)-1;
1539         ch = s[-1];
1540 #undef sc
1541
1542         skip_whitespace(s, ch);
1543         if(ch != '\0') {
1544             if((nl = strchr(s - 1, '\n')) != NULL) {
1545                 *nl = '\0';
1546             }
1547             tapestart_error = newstralloc(tapestart_error, s - 1);
1548             if(nl) *nl = '\n';
1549             degraded_mode = 1;
1550             return;
1551         }
1552         /* else some other tape error, handle like other errors */
1553     }
1554     s = vstralloc("  ", program_str[curprog], ": ",
1555                   logtype_str[curlog], " ", curstr, NULL);
1556     addline(&errsum, s);
1557     amfree(s);
1558 }
1559
1560 /* ----- */
1561
1562 void handle_summary()
1563 {
1564     bogus_line();
1565 }
1566
1567 /* ----- */
1568
1569 int nb_disk=0;
1570 void handle_disk()
1571 {
1572     disk_t *dp;
1573     char *s, *fp;
1574     int ch;
1575     char *hostname = NULL, *diskname = NULL;
1576
1577     if(curprog != P_PLANNER && curprog != P_AMFLUSH) {
1578         bogus_line();
1579         return;
1580     }
1581
1582     if(nb_disk==0) {
1583         for(dp = diskq->head; dp != NULL; dp = dp->next)
1584             dp->todo = 0;
1585     }
1586     nb_disk++;
1587
1588     s = curstr;
1589     ch = *s++;
1590
1591     skip_whitespace(s, ch);
1592     if(ch == '\0') {
1593         bogus_line();
1594         return;
1595     }
1596     fp = s - 1;
1597     skip_non_whitespace(s, ch);
1598     s[-1] = '\0';
1599     hostname = newstralloc(hostname, fp);
1600     s[-1] = ch;
1601
1602     skip_whitespace(s, ch);
1603     if(ch == '\0') {
1604         bogus_line();
1605         return;
1606     }
1607     fp = s - 1;
1608     skip_non_whitespace(s, ch);
1609     s[-1] = '\0';
1610     diskname = newstralloc(diskname, fp);
1611     s[-1] = ch;
1612
1613     dp = lookup_disk(hostname, diskname);
1614     if(dp == NULL) {
1615         dp = add_disk(hostname, diskname);
1616     }
1617
1618     amfree(hostname);
1619     amfree(diskname);
1620     dp->todo = 1;
1621 }
1622
1623 repdata_t *handle_success()
1624 {
1625     disk_t *dp;
1626     float sec, kps, kbytes, origkb;
1627     timedata_t *sp;
1628     int i;
1629     char *s, *fp;
1630     int ch;
1631     char *hostname = NULL;
1632     char *diskname = NULL;
1633     repdata_t *repdata;
1634     int level;
1635     char *datestamp;
1636
1637     if(curprog != P_TAPER && curprog != P_DUMPER && curprog != P_PLANNER) {
1638         bogus_line();
1639         return NULL;
1640     }
1641
1642     s = curstr;
1643     ch = *s++;
1644
1645     skip_whitespace(s, ch);
1646     if(ch == '\0') {
1647         bogus_line();
1648         return NULL;
1649     }
1650     fp = s - 1;
1651     skip_non_whitespace(s, ch);
1652     s[-1] = '\0';
1653     hostname = stralloc(fp);
1654     s[-1] = ch;
1655
1656     skip_whitespace(s, ch);
1657     if(ch == '\0') {
1658         bogus_line();
1659         amfree(hostname);
1660         return NULL;
1661     }
1662     fp = s - 1;
1663     skip_non_whitespace(s, ch);
1664     s[-1] = '\0';
1665     diskname = stralloc(fp);
1666     s[-1] = ch;
1667
1668     skip_whitespace(s, ch);
1669     if(ch == '\0') {
1670         bogus_line();
1671         amfree(hostname);
1672         amfree(diskname);
1673         return NULL;
1674     }
1675     fp = s - 1;
1676     skip_non_whitespace(s, ch);
1677     s[-1] = '\0';
1678     datestamp = stralloc(fp);
1679     s[-1] = ch;
1680
1681     level = atoi(datestamp);
1682     if(level < 100)  {
1683         datestamp = newstralloc(datestamp, run_datestamp);
1684     }
1685     else {
1686         skip_whitespace(s, ch);
1687         if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
1688             bogus_line();
1689             amfree(hostname);
1690             amfree(diskname);
1691             amfree(datestamp);
1692             return NULL;
1693         }
1694         skip_integer(s, ch);
1695     }
1696
1697     skip_whitespace(s, ch);
1698                                 /* Planner success messages (for skipped
1699                                    dumps) do not contain statistics */
1700     if(curprog != P_PLANNER) {
1701         if(curprog != P_DUMPER ||
1702            sscanf(s - 1,"[sec %f kb %f kps %f orig-kb %f", 
1703                   &sec, &kbytes, &kps, &origkb) != 4)  {
1704             origkb = -1;
1705             if(sscanf(s - 1,"[sec %f kb %f kps %f",
1706                       &sec, &kbytes, &kps) != 3) {
1707                 bogus_line();
1708                 amfree(hostname);
1709                 amfree(diskname);
1710                 amfree(datestamp);
1711                 return NULL;
1712             }
1713         }
1714         else {
1715             if(origkb == 0.0) origkb = 0.1;
1716         }
1717     }
1718
1719
1720     dp = lookup_disk(hostname, diskname);
1721     if(dp == NULL) {
1722         char *str = NULL;
1723
1724         str = vstralloc("  ", prefix(hostname, diskname, level),
1725                         " ", "ERROR [not in disklist]",
1726                         NULL);
1727         addline(&errsum, str);
1728         amfree(str);
1729         amfree(hostname);
1730         amfree(diskname);
1731         amfree(datestamp);
1732         return NULL;
1733     }
1734
1735     repdata = find_repdata(dp, datestamp, level);
1736
1737     if(curprog == P_PLANNER) {
1738         repdata->dumper.result = L_SKIPPED;
1739         amfree(hostname);
1740         amfree(diskname);
1741         amfree(datestamp);
1742         return repdata;
1743     }
1744
1745     if(curprog == P_TAPER)
1746         sp = &(repdata->taper);
1747     else sp = &(repdata->dumper);
1748
1749     i = level > 0;
1750
1751     if(origkb == -1) {
1752         info_t inf;
1753         struct tm *tm;
1754         int Idatestamp;
1755
1756         get_info(hostname, diskname, &inf);
1757         tm = localtime(&inf.inf[level].date);
1758         Idatestamp = 10000*(tm->tm_year+1900) +
1759                       100*(tm->tm_mon+1) + tm->tm_mday;
1760
1761         if(atoi(datestamp) == Idatestamp) {
1762             /* grab original size from record */
1763             origkb = (double)inf.inf[level].size;
1764         }
1765         else
1766             origkb = 0.0;
1767     }
1768     amfree(hostname);
1769     amfree(diskname);
1770     amfree(datestamp);
1771
1772     sp->result = L_SUCCESS;
1773     sp->datestamp = repdata->datestamp;
1774     sp->sec = sec;
1775     sp->kps = kps;
1776     sp->origsize = origkb;
1777     sp->outsize = kbytes;
1778
1779     if(curprog == P_TAPER) {
1780         if(current_tape == NULL) {
1781             error("current_tape == NULL");
1782         }
1783         stats[i].taper_time += sec;
1784         sp->filenum = ++tapefcount;
1785         sp->tapelabel = current_tape->label;
1786         tapedisks[level] +=1;
1787         stats[i].tapedisks +=1;
1788         stats[i].tapesize += kbytes;
1789         current_tape->taper_time += sec;
1790         current_tape->coutsize += kbytes;
1791         current_tape->corigsize += origkb;
1792         current_tape->tapedisks += 1;
1793     }
1794
1795     if(curprog == P_DUMPER) {
1796         stats[i].dumper_time += sec;
1797         if(dp->compress == COMP_NONE) {
1798             sp->origsize = kbytes;
1799         }
1800         else {
1801             stats[i].coutsize += kbytes;
1802             stats[i].corigsize += sp->origsize;
1803         }
1804         dumpdisks[level] +=1;
1805         stats[i].dumpdisks +=1;
1806         stats[i].origsize += sp->origsize;
1807         stats[i].outsize += kbytes;
1808     }
1809
1810     return repdata;
1811 }
1812
1813 void handle_strange()
1814 {
1815     char *str = NULL;
1816     repdata_t *repdata;
1817
1818     repdata = handle_success();
1819
1820     str = vstralloc("  ", prefix(repdata->disk->host->hostname, 
1821                                  repdata->disk->name, repdata->level),
1822                     " ", "STRANGE",
1823                     NULL);
1824     addline(&errsum, str);
1825     amfree(str);
1826
1827     addline(&errdet,"");
1828     str = vstralloc("/-- ", prefix(repdata->disk->host->hostname, 
1829                                    repdata->disk->name, repdata->level),
1830                     " ", "STRANGE",
1831                     NULL);
1832     addline(&errdet, str);
1833     amfree(str);
1834
1835     while(contline_next()) {
1836         get_logline(logfile);
1837         addline(&errdet, curstr);
1838     }
1839     addline(&errdet,"\\--------");
1840 }
1841
1842 void handle_failed()
1843 {
1844     disk_t *dp;
1845     char *hostname;
1846     char *diskname;
1847     char *datestamp;
1848     char *errstr;
1849     int level;
1850     char *s, *fp;
1851     int ch;
1852     char *str = NULL;
1853     repdata_t *repdata;
1854     timedata_t *sp;
1855
1856     hostname = NULL;
1857     diskname = NULL;
1858
1859     s = curstr;
1860     ch = *s++;
1861
1862     skip_whitespace(s, ch);
1863     if(ch == '\0') {
1864         bogus_line();
1865         return;
1866     }
1867     hostname = s - 1;
1868     skip_non_whitespace(s, ch);
1869     s[-1] = '\0';
1870
1871     skip_whitespace(s, ch);
1872     if(ch == '\0') {
1873         bogus_line();
1874         return;
1875     }
1876     diskname = s - 1;
1877     skip_non_whitespace(s, ch);
1878     s[-1] = '\0';
1879
1880     skip_whitespace(s, ch);
1881     if(ch == '\0') {
1882         bogus_line();
1883         return;
1884     }
1885     fp = s - 1;
1886     skip_non_whitespace(s, ch);
1887     s[-1] = '\0';
1888     datestamp = stralloc(fp);
1889
1890     if(strlen(datestamp) < 3) { /* there is no datestamp, it's the level */
1891         level = atoi(datestamp);
1892         datestamp = newstralloc(datestamp, run_datestamp);
1893     }
1894     else { /* read the level */
1895         skip_whitespace(s, ch);
1896         if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
1897             bogus_line();
1898             amfree(datestamp);
1899             return;
1900         }
1901         skip_integer(s, ch);
1902     }
1903
1904     skip_whitespace(s, ch);
1905     if(ch == '\0') {
1906         bogus_line();
1907         amfree(datestamp);
1908         return;
1909     }
1910     errstr = s - 1;
1911     if((s = strchr(errstr, '\n')) != NULL) {
1912         *s = '\0';
1913     }
1914
1915     dp = lookup_disk(hostname, diskname);
1916     if(dp == NULL) {
1917         str = vstralloc("  ", prefix(hostname, diskname, level),
1918                         " ", "ERROR [not in disklist]",
1919                         NULL);
1920         addline(&errsum, str);
1921         amfree(str);
1922     } else {
1923         repdata = find_repdata(dp, datestamp, level);
1924
1925         if(curprog == P_TAPER)
1926             sp = &(repdata->taper);
1927         else sp = &(repdata->dumper);
1928
1929         if(sp->result != L_SUCCESS)
1930             sp->result = L_FAIL;
1931     }
1932     amfree(datestamp);
1933
1934     str = vstralloc("  ", prefix(hostname, diskname, level),
1935                     " ", "FAILED",
1936                     " ", errstr,
1937                     NULL);
1938     addline(&errsum, str);
1939     amfree(str);
1940
1941     if(curprog == P_DUMPER) {
1942         addline(&errdet,"");
1943         str = vstralloc("/-- ", prefix(hostname, diskname, level),
1944                         " ", "FAILED",
1945                         " ", errstr,
1946                         NULL);
1947         addline(&errdet, str);
1948         amfree(str);
1949         while(contline_next()) {
1950             get_logline(logfile);
1951             addline(&errdet, curstr);
1952         }
1953         addline(&errdet,"\\--------");
1954     }
1955     return;
1956 }
1957
1958 void generate_missing()
1959 {
1960     disk_t *dp;
1961     char *str = NULL;
1962
1963     for(dp = diskq->head; dp != NULL; dp = dp->next) {
1964         if(dp->todo && data(dp) == NULL) {
1965             str = vstralloc("  ", prefix(dp->host->hostname, dp->name, -987),
1966                             " ", "RESULTS MISSING",
1967                             NULL);
1968             addline(&errsum, str);
1969             amfree(str);
1970         }
1971     }
1972 }
1973
1974 static char *
1975 prefix (host, disk, level)
1976     char *host;
1977     char *disk;
1978     int level;
1979 {
1980     char h[10+1];
1981     int l;
1982     char number[NUM_STR_SIZE];
1983     static char *str = NULL;
1984
1985     ap_snprintf(number, sizeof(number), "%d", level);
1986     if(host) {
1987         strncpy(h, host, sizeof(h)-1);
1988     } else {
1989         strncpy(h, "(host?)", sizeof(h)-1);
1990     }
1991     h[sizeof(h)-1] = '\0';
1992     for(l = strlen(h); l < sizeof(h)-1; l++) {
1993         h[l] = ' ';
1994     }
1995     str = newvstralloc(str,
1996                        h,
1997                        " ", disk ? disk : "(disk?)",
1998                        level != -987 ? " lev " : "",
1999                        level != -987 ? number : "",
2000                        NULL);
2001     return str;
2002 }
2003
2004 void copy_template_file(lbl_templ)
2005 char *lbl_templ;
2006 {
2007   char buf[BUFSIZ];
2008   int fd;
2009   int numread;
2010
2011   if (strchr(lbl_templ, '/') == NULL) {
2012     lbl_templ = stralloc2(config_dir, lbl_templ);
2013   } else {
2014     lbl_templ = stralloc(lbl_templ);
2015   }
2016   if ((fd = open(lbl_templ, 0)) < 0) {
2017     curlog = L_ERROR;
2018     curprog = P_REPORTER;
2019     curstr = vstralloc("could not open PostScript template file ",
2020                        lbl_templ,
2021                        ": ",
2022                        strerror(errno),
2023                        NULL);
2024     handle_error();
2025     amfree(curstr);
2026     afclose(postscript);
2027     return;
2028   }
2029   while ((numread = read(fd, buf, sizeof(buf))) > 0) {
2030     if (fwrite(buf, numread, 1, postscript) != 1) {
2031       curlog = L_ERROR;
2032       curprog = P_REPORTER;
2033       curstr = vstralloc("error copying PostScript template file ",
2034                          lbl_templ,
2035                          ": ",
2036                          strerror(errno),
2037                          NULL);
2038       handle_error();
2039       amfree(curstr);
2040       afclose(postscript);
2041       return;
2042     }
2043   }
2044   if (numread < 0) {
2045     curlog = L_ERROR;
2046     curprog = P_REPORTER;
2047     curstr = vstralloc("error reading PostScript template file ",
2048                        lbl_templ,
2049                        ": ",
2050                        strerror(errno),
2051                        NULL);
2052     handle_error();
2053     amfree(curstr);
2054     afclose(postscript);
2055     return;
2056   }
2057   close(fd);
2058   amfree(lbl_templ);
2059 }
2060
2061 repdata_t *find_repdata(dp, datestamp, level)
2062 disk_t *dp;
2063 char *datestamp;
2064 int level;
2065 {
2066     repdata_t *repdata, *prev;
2067
2068     if(!datestamp)
2069         datestamp = run_datestamp;
2070     prev = NULL;
2071     for(repdata = data(dp); repdata != NULL && (repdata->level != level || strcmp(repdata->datestamp,datestamp)!=0); repdata = repdata->next) {
2072         prev = repdata;
2073     }
2074     if(!repdata) {
2075         repdata = (repdata_t *)alloc(sizeof(repdata_t));
2076         memset(repdata, '\0',sizeof(repdata_t));
2077         repdata->disk = dp;
2078         repdata->datestamp = stralloc(datestamp ? datestamp : "");
2079         repdata->level = level;
2080         repdata->dumper.result = L_BOGUS;
2081         repdata->taper.result = L_BOGUS;
2082         repdata->next = NULL;
2083         if(prev)
2084             prev->next = repdata;
2085         else
2086             dp->up = (void *)repdata;
2087     }
2088     return repdata;
2089 }
2090
2091
2092 void do_postscript_output()
2093 {
2094     tapetype_t *tp = lookup_tapetype(getconf_str(CNF_TAPETYPE));
2095     disk_t *dp;
2096     repdata_t *repdata;
2097     float outsize, origsize;
2098     int tapesize, marksize;
2099
2100     tapesize = tp->length;
2101     marksize = tp->filemark;
2102
2103     for(current_tape = stats_by_tape; current_tape != NULL;
2104             current_tape = current_tape->next) {
2105
2106         if (current_tape->label == NULL) {
2107             break;
2108         }
2109
2110         copy_template_file(tp->lbl_templ);
2111
2112         /* generate a few elements */
2113         fprintf(postscript,"(%s) DrawDate\n\n",
2114                     nicedate(run_datestamp ? atoi(run_datestamp) : 0));
2115         fprintf(postscript,"(Amanda Version %s) DrawVers\n",version());
2116         fprintf(postscript,"(%s) DrawTitle\n", current_tape->label);
2117
2118         /* Stats */
2119         fprintf(postscript, "(Total Size:        %6.1f MB) DrawStat\n",
2120               mb(current_tape->coutsize));
2121         fprintf(postscript, "(Tape Used (%%)       ");
2122         divzero(postscript, pct(current_tape->coutsize + 
2123                                 marksize * current_tape->tapedisks),
2124                                 tapesize);
2125         fprintf(postscript," %%) DrawStat\n");
2126         fprintf(postscript, "(Compression Ratio:  ");
2127         divzero(postscript, pct(current_tape->coutsize),current_tape->corigsize);
2128         fprintf(postscript," %%) DrawStat\n");
2129         fprintf(postscript,"(Filesystems Taped: %4d) DrawStat\n",
2130                   current_tape->tapedisks);
2131
2132         /* Summary */
2133
2134         fprintf(postscript,
2135               "(-) (%s) (-) (  0) (      32) (      32) DrawHost\n",
2136               current_tape->label);
2137
2138         for(dp = sortq.head; dp != NULL; dp = dp->next) {
2139             if (dp->todo == 0) {
2140                  continue;
2141             }
2142             for(repdata = data(dp); repdata != NULL; repdata = repdata->next) {
2143
2144                 if(repdata->taper.tapelabel != current_tape->label) {
2145                     continue;
2146                 }
2147
2148                 if(repdata->dumper.result == L_SUCCESS)
2149                     origsize = repdata->dumper.origsize;
2150                 else
2151                     origsize = repdata->taper.origsize;
2152
2153                 if(repdata->taper.result == L_SUCCESS) 
2154                     outsize  = repdata->taper.outsize;
2155                 else
2156                     outsize  = repdata->dumper.outsize;
2157
2158                 if (repdata->taper.result == L_SUCCESS) {
2159                     if(origsize != 0.0) {
2160                         fprintf(postscript,"(%s) (%s) (%d) (%3.0d) (%8.0f) (%8.0f) DrawHost\n",
2161                             dp->host->hostname, dp->name, repdata->level,
2162                             repdata->taper.filenum, origsize, 
2163                             outsize);
2164                     }
2165                     else {
2166                         fprintf(postscript,"(%s) (%s) (%d) (%3.0d) (%8s) (%8.0f) DrawHost\n",
2167                             dp->host->hostname, dp->name, repdata->level,
2168                             repdata->taper.filenum, "N/A", 
2169                             outsize);
2170                     }
2171                 }
2172             }
2173         }
2174         
2175         fprintf(postscript,"\nshowpage\n");
2176     }
2177 }