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