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