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