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