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