Imported Upstream version 2.6.0
[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, *lasttp;
1006     int run_tapes;
1007     int skip = 0;
1008
1009     if (last_run_tapes > 0) {
1010         if(amflush_run)
1011             g_fprintf(mailf,
1012                     plural(_("The dumps were flushed to tape %s.\n"),
1013                            _("The dumps were flushed to tapes %s.\n"),
1014                            last_run_tapes),
1015                     tape_labels ? tape_labels : "");
1016         else
1017             g_fprintf(mailf,
1018                     plural(_("These dumps were to tape %s.\n"),
1019                            _("These dumps were to tapes %s.\n"),
1020                            last_run_tapes),
1021                     tape_labels ? tape_labels : "");
1022     }
1023
1024     if(degraded_mode) {
1025         g_fprintf(mailf,
1026                 _("*** A TAPE ERROR OCCURRED: %s.\n"), tapestart_error);
1027     }
1028     if (cmdlogfname == 1) {
1029         if(degraded_mode) {
1030             fputs(_("Some dumps may have been left in the holding disk.\n"),
1031                   mailf);
1032             g_fprintf(mailf,"\n");
1033         }
1034     }  else {
1035         GSList *holding_list, *holding_file;
1036         off_t  h_size = 0, mh_size;
1037
1038         holding_list = holding_get_files_for_flush(NULL);
1039         for(holding_file=holding_list; holding_file != NULL;
1040                                        holding_file = holding_file->next) {
1041             mh_size = holding_file_size((char *)holding_file->data, 1);
1042             if (mh_size > 0)
1043                 h_size += mh_size;
1044         }
1045
1046         if (h_size > 0) {
1047             g_fprintf(mailf,
1048                     _("There are %lld%s of dumps left in the holding disk.\n"),
1049                     (long long)h_size, displayunit);
1050             if (getconf_boolean(CNF_AUTOFLUSH)) {
1051                 g_fprintf(mailf, _("They will be flushed on the next run.\n"));
1052             } else {
1053                 g_fprintf(mailf, _("Run amflush to flush them to tape.\n"));
1054             }
1055             g_fprintf(mailf,"\n");
1056         } else if (degraded_mode) {
1057             g_fprintf(mailf, _("No dumps are left in the holding disk. %lld%s\n"), (long long)h_size, displayunit);
1058             g_fprintf(mailf,"\n");
1059         }
1060     }
1061
1062     tp = lookup_last_reusable_tape(skip);
1063
1064     run_tapes = getconf_int(CNF_RUNTAPES);
1065
1066     if (run_tapes == 1)
1067         fputs(_("The next tape Amanda expects to use is: "), mailf);
1068     else if(run_tapes > 1)
1069         g_fprintf(mailf, _("The next %d tapes Amanda expects to use are: "),
1070                 run_tapes);
1071     
1072     while(run_tapes > 0) {
1073         if(tp != NULL) {
1074             g_fprintf(mailf, "%s", tp->label);
1075         } else {
1076             if (run_tapes == 1)
1077                 g_fprintf(mailf, _("a new tape"));
1078             else
1079                 g_fprintf(mailf, _("%d new tapes"), run_tapes);
1080             run_tapes = 1;
1081         }
1082
1083         if(run_tapes > 1) fputs(", ", mailf);
1084
1085         run_tapes -= 1;
1086         skip++;
1087         tp = lookup_last_reusable_tape(skip);
1088     }
1089     fputs(".\n", mailf);
1090
1091     lasttp = lookup_tapepos(lookup_nb_tape());
1092     run_tapes = getconf_int(CNF_RUNTAPES);
1093     if(lasttp && run_tapes > 0 && strcmp(lasttp->datestamp,"0") == 0) {
1094         int c = 0;
1095         while(lasttp && run_tapes > 0 && strcmp(lasttp->datestamp,"0") == 0) {
1096             c++;
1097             lasttp = lasttp->prev;
1098             run_tapes--;
1099         }
1100         lasttp = lookup_tapepos(lookup_nb_tape());
1101         if(c == 1) {
1102             g_fprintf(mailf, _("The next new tape already labelled is: %s.\n"),
1103                     lasttp->label);
1104         }
1105         else {
1106             g_fprintf(mailf, _("The next %d new tapes already labelled are: %s"), c,
1107                     lasttp->label);
1108             lasttp = lasttp->prev;
1109             c--;
1110             while(lasttp && c > 0 && strcmp(lasttp->datestamp,"0") == 0) {
1111                 g_fprintf(mailf, ", %s", lasttp->label);
1112                 lasttp = lasttp->prev;
1113                 c--;
1114             }
1115             g_fprintf(mailf, ".\n");
1116         }
1117     }
1118 }
1119
1120 /* ----- */
1121 static void
1122 output_X_summary(
1123     X_summary_t *first)
1124 {
1125     size_t len_host=0, len_disk=0;
1126     X_summary_t *strange;
1127     char *str = NULL;
1128
1129     for(strange=first; strange != NULL; strange = strange->next) {
1130         if(strlen(strange->hostname) > len_host)
1131             len_host = strlen(strange->hostname);
1132         if(strlen(strange->diskname) > len_disk)
1133             len_disk = strlen(strange->diskname);
1134     }
1135     for(strange=first; strange != NULL; strange = strange->next) {
1136         str = vstralloc("  ", prefixstrange(strange->hostname, strange->diskname, strange->level, len_host, len_disk),
1137                         "  ", strange->str, NULL);
1138         g_fprintf(mailf, "%s\n", str);
1139         amfree(str);
1140     }
1141 }
1142
1143 static void
1144 output_lines(
1145     line_t *    lp,
1146     FILE *      f)
1147 {
1148     line_t *next;
1149
1150     while(lp) {
1151         fputs(lp->str, f);
1152         amfree(lp->str);
1153         fputc('\n', f);
1154         next = lp->next;
1155         amfree(lp);
1156         lp = next;
1157     }
1158 }
1159
1160 /* ----- */
1161
1162 static int
1163 sort_by_name(
1164     disk_t *    a,
1165     disk_t *    b)
1166 {
1167     int rc;
1168
1169     rc = strcmp(a->host->hostname, b->host->hostname);
1170     if(rc == 0) rc = strcmp(a->name, b->name);
1171     return rc;
1172 }
1173
1174 static void
1175 sort_disks(void)
1176 {
1177     disk_t *dp;
1178
1179     sortq.head = sortq.tail = NULL;
1180     while(!empty(diskq)) {
1181         dp = dequeue_disk(&diskq);
1182         if(data(dp) == NULL) { /* create one */
1183             find_repdata(dp, run_datestamp, 0);
1184         }
1185         insert_disk(&sortq, dp, sort_by_name);
1186     }
1187 }
1188
1189 static void
1190 CheckStringMax(
1191     ColumnInfo *cd,
1192     char *      s)
1193 {
1194     if (cd->MaxWidth) {
1195         int l = (int)strlen(s);
1196
1197         if (cd->Width < l)
1198             cd->Width= l;
1199     }
1200 }
1201
1202 static void
1203 CheckIntMax(
1204     ColumnInfo *cd,
1205     int         n)
1206 {
1207     if (cd->MaxWidth) {
1208         char testBuf[200];
1209         int l;
1210
1211         g_snprintf(testBuf, SIZEOF(testBuf),
1212           cd->Format, cd->Width, cd->Precision, n);
1213         l = (int)strlen(testBuf);
1214         if (cd->Width < l)
1215             cd->Width= l;
1216     }
1217 }
1218
1219 static void
1220 CheckFloatMax(
1221     ColumnInfo *cd,
1222     double      d)
1223 {
1224     if (cd->MaxWidth) {
1225         char testBuf[200];
1226         int l;
1227
1228         g_snprintf(testBuf, SIZEOF(testBuf),
1229           cd->Format, cd->Width, cd->Precision, d);
1230         l = (int)strlen(testBuf);
1231         if (cd->Width < l)
1232             cd->Width= l;
1233     }
1234 }
1235
1236 static int HostName;
1237 static int Disk;
1238 static int Level;
1239 static int OrigKB;
1240 static int OutKB;
1241 static int Compress;
1242 static int DumpTime;
1243 static int DumpRate;
1244 static int TapeTime;
1245 static int TapeRate;
1246
1247 static void
1248 CalcMaxWidth(void)
1249 {
1250     /* we have to look for columspec's, that require the recalculation.
1251      * we do here the same loops over the sortq as is done in
1252      * output_summary. So, if anything is changed there, we have to
1253      * change this here also.
1254      *                                                  ElB, 1999-02-24.
1255      */
1256     disk_t *dp;
1257     double f;
1258     repdata_t *repdata;
1259     char *qdevname;
1260     int i, l;
1261
1262     for (i=0;ColumnData[i].Name != NULL; i++) {
1263         if (ColumnData[i].MaxWidth) {
1264             l = (int)strlen(ColumnData[i].Title);
1265             if (ColumnData[i].Width < l)
1266                 ColumnData[i].Width= l;
1267         }
1268     }
1269
1270     for(dp = sortq.head; dp != NULL; dp = dp->next) {
1271       if(dp->todo) {
1272         for(repdata = data(dp); repdata != NULL; repdata = repdata->next) {
1273             char TimeRateBuffer[40];
1274
1275             CheckStringMax(&ColumnData[HostName], dp->host->hostname);
1276             qdevname = quote_string(dp->name);
1277             CheckStringMax(&ColumnData[Disk], qdevname);
1278             amfree(qdevname);
1279             if (repdata->dumper.result == L_BOGUS && 
1280                 repdata->taper.result == L_BOGUS)
1281                 continue;
1282             CheckIntMax(&ColumnData[Level], repdata->level);
1283             if(repdata->dumper.result == L_SUCCESS || 
1284                    repdata->dumper.result == L_CHUNKSUCCESS) {
1285                 CheckFloatMax(&ColumnData[OrigKB],
1286                               (double)du(repdata->dumper.origsize));
1287                 CheckFloatMax(&ColumnData[OutKB],
1288                               (double)du(repdata->dumper.outsize));
1289                 if(abs(repdata->dumper.outsize - repdata->dumper.origsize)< 32)
1290                     f = 0.0;
1291                 else 
1292                     f = repdata->dumper.origsize;
1293                 CheckStringMax(&ColumnData[Compress], 
1294                         sDivZero(pct(repdata->dumper.outsize), f, Compress));
1295
1296                 if(!amflush_run)
1297                     g_snprintf(TimeRateBuffer, SIZEOF(TimeRateBuffer),
1298                                 "%3d:%02d", mnsc(repdata->dumper.sec));
1299                 else
1300                     g_snprintf(TimeRateBuffer, SIZEOF(TimeRateBuffer),
1301                                 "N/A ");
1302                 CheckStringMax(&ColumnData[DumpTime], TimeRateBuffer);
1303
1304                 CheckFloatMax(&ColumnData[DumpRate], repdata->dumper.kps); 
1305             }
1306
1307             if(repdata->taper.result == L_FAIL) {
1308                 CheckStringMax(&ColumnData[TapeTime], "FAILED");
1309                 continue;
1310             }
1311             if(repdata->taper.result == L_SUCCESS ||
1312                repdata->taper.result == L_CHUNKSUCCESS)
1313                 g_snprintf(TimeRateBuffer, SIZEOF(TimeRateBuffer), 
1314                   "%3d:%02d", mnsc(repdata->taper.sec));
1315             else
1316                 g_snprintf(TimeRateBuffer, SIZEOF(TimeRateBuffer),
1317                   "N/A ");
1318             CheckStringMax(&ColumnData[TapeTime], TimeRateBuffer);
1319
1320             if(repdata->taper.result == L_SUCCESS ||
1321                     repdata->taper.result == L_CHUNKSUCCESS)
1322                 CheckFloatMax(&ColumnData[TapeRate], repdata->taper.kps);
1323             else
1324                 CheckStringMax(&ColumnData[TapeRate], "N/A ");
1325         }
1326       }
1327     }
1328 }
1329
1330 static void
1331 output_summary(void)
1332 {
1333     disk_t *dp;
1334     repdata_t *repdata;
1335     char *ds="DUMPER STATS";
1336     char *ts=" TAPER STATS";
1337     char *tmp;
1338
1339     int i, h, w1, wDump, wTape;
1340     double outsize, origsize;
1341     double f;
1342
1343     HostName = StringToColumn("HostName");
1344     Disk = StringToColumn("Disk");
1345     Level = StringToColumn("Level");
1346     OrigKB = StringToColumn("OrigKB");
1347     OutKB = StringToColumn("OutKB");
1348     Compress = StringToColumn("Compress");
1349     DumpTime = StringToColumn("DumpTime");
1350     DumpRate = StringToColumn("DumpRate");
1351     TapeTime = StringToColumn("TapeTime");
1352     TapeRate = StringToColumn("TapeRate");
1353
1354     /* at first determine if we have to recalculate our widths */
1355     if (MaxWidthsRequested)
1356         CalcMaxWidth();
1357
1358     /* title for Dumper-Stats */
1359     w1= ColWidth(HostName, Level);
1360     wDump= ColWidth(OrigKB, DumpRate);
1361     wTape= ColWidth(TapeTime, TapeRate);
1362
1363     /* print centered top titles */
1364     h = (int)strlen(ds);
1365     if (h > wDump) {
1366         h = 0;
1367     } else {
1368         h = (wDump-h)/2;
1369     }
1370     g_fprintf(mailf, "%*s", w1+h, "");
1371     g_fprintf(mailf, "%-*s", wDump-h, ds);
1372     h = (int)strlen(ts);
1373     if (h > wTape) {
1374         h = 0;
1375     } else {
1376         h = (wTape-h)/2;
1377     }
1378     g_fprintf(mailf, "%*s", h, "");
1379     g_fprintf(mailf, "%-*s", wTape-h, ts);
1380     fputc('\n', mailf);
1381
1382     /* print the titles */
1383     for (i=0; ColumnData[i].Name != NULL; i++) {
1384         char *fmt;
1385         ColumnInfo *cd= &ColumnData[i];
1386         g_fprintf(mailf, "%*s", cd->PrefixSpace, "");
1387         if (cd->Format[1] == '-')
1388             fmt= "%-*s";
1389         else
1390             fmt= "%*s";
1391         if(strcmp(cd->Title,"ORIG-KB") == 0) {
1392             /* cd->Title must be re-allocated in write-memory */
1393             cd->Title = stralloc("ORIG-KB");
1394             cd->Title[5] = displayunit[0];
1395         }
1396         if(strcmp(cd->Title,"OUT-KB") == 0) {
1397             /* cd->Title must be re-allocated in write-memory */
1398             cd->Title = stralloc("OUT-KB");
1399             cd->Title[4] = displayunit[0];
1400         }
1401         g_fprintf(mailf, fmt, cd->Width, cd->Title);
1402     }
1403     fputc('\n', mailf);
1404
1405     /* print the rules */
1406     fputs(tmp=Rule(HostName, Level), mailf); amfree(tmp);
1407     fputs(tmp=Rule(OrigKB, DumpRate), mailf); amfree(tmp);
1408     fputs(tmp=Rule(TapeTime, TapeRate), mailf); amfree(tmp);
1409     fputc('\n', mailf);
1410
1411     for(dp = sortq.head; dp != NULL; dp = dp->next) {
1412       if(dp->todo) {
1413         ColumnInfo *cd;
1414         char TimeRateBuffer[40];
1415         for(repdata = data(dp); repdata != NULL; repdata = repdata->next) {
1416             char *devname;
1417             char *qdevname;
1418             size_t devlen;
1419
1420             cd= &ColumnData[HostName];
1421             g_fprintf(mailf, "%*s", cd->PrefixSpace, "");
1422             g_fprintf(mailf, cd->Format, cd->Width, cd->Width, dp->host->hostname);
1423
1424             cd= &ColumnData[Disk];
1425             g_fprintf(mailf, "%*s", cd->PrefixSpace, "");
1426             devname = sanitize_string(dp->name);
1427             qdevname = quote_string(devname);
1428             devlen = strlen(qdevname);
1429             if (devlen > (size_t)cd->Width) {
1430                 fputc('-', mailf); 
1431                 g_fprintf(mailf, cd->Format, cd->Width-1, cd->Precision-1,
1432                         qdevname+devlen - (cd->Width-1) );
1433             }
1434             else
1435                 g_fprintf(mailf, cd->Format, cd->Width, cd->Width, qdevname);
1436             amfree(devname);
1437             amfree(qdevname);
1438             cd= &ColumnData[Level];
1439             if (repdata->dumper.result == L_BOGUS &&
1440                 repdata->taper.result  == L_BOGUS) {
1441               if(amflush_run){
1442                 g_fprintf(mailf, "%*s%s\n", cd->PrefixSpace+cd->Width, "",
1443                         tmp=TextRule(OrigKB, TapeRate, "NO FILE TO FLUSH"));
1444               } else {
1445                 g_fprintf(mailf, "%*s%s\n", cd->PrefixSpace+cd->Width, "",
1446                         tmp=TextRule(OrigKB, TapeRate, "MISSING"));
1447               }
1448               amfree(tmp);
1449               continue;
1450             }
1451             
1452             g_fprintf(mailf, "%*s", cd->PrefixSpace, "");
1453             g_fprintf(mailf, cd->Format, cd->Width, cd->Precision,repdata->level);
1454
1455             if (repdata->dumper.result == L_SKIPPED) {
1456                 g_fprintf(mailf, "%s\n",
1457                         tmp=TextRule(OrigKB, TapeRate, "SKIPPED"));
1458                 amfree(tmp);
1459                 continue;
1460             }
1461             if (repdata->dumper.result == L_FAIL && (repdata->chunker.result != L_PARTIAL && repdata->taper.result  != L_PARTIAL)) {
1462                 g_fprintf(mailf, "%s\n",
1463                         tmp=TextRule(OrigKB, TapeRate, "FAILED"));
1464                 amfree(tmp);
1465                 exit_status |= STATUS_FAILED;
1466                 continue;
1467             }
1468
1469             if(repdata->dumper.result == L_SUCCESS ||
1470                repdata->dumper.result == L_CHUNKSUCCESS)
1471                 origsize = repdata->dumper.origsize;
1472             else if(repdata->taper.result == L_SUCCESS ||
1473                     repdata->taper.result == L_PARTIAL)
1474                 origsize = repdata->taper.origsize;
1475             else
1476                 origsize = repdata->chunker.origsize;
1477
1478             if(repdata->taper.result == L_SUCCESS ||
1479                repdata->taper.result == L_CHUNKSUCCESS)
1480                 outsize  = repdata->taper.outsize;
1481             else if(repdata->chunker.result == L_SUCCESS ||
1482                     repdata->chunker.result == L_PARTIAL ||
1483                     repdata->chunker.result == L_CHUNKSUCCESS)
1484                 outsize  = repdata->chunker.outsize;
1485             else if (repdata->taper.result == L_PARTIAL)
1486                 outsize  = repdata->taper.outsize;
1487             else
1488                 outsize  = repdata->dumper.outsize;
1489
1490             cd= &ColumnData[OrigKB];
1491             g_fprintf(mailf, "%*s", cd->PrefixSpace, "");
1492             if(isnormal(origsize))
1493                 g_fprintf(mailf, cd->Format, cd->Width, cd->Precision, du(origsize));
1494             else
1495                 g_fprintf(mailf, "%*.*s", cd->Width, cd->Width, "N/A");
1496
1497             cd= &ColumnData[OutKB];
1498             g_fprintf(mailf, "%*s", cd->PrefixSpace, "");
1499
1500             g_fprintf(mailf, cd->Format, cd->Width, cd->Precision, du(outsize));
1501                 
1502             cd= &ColumnData[Compress];
1503             g_fprintf(mailf, "%*s", cd->PrefixSpace, "");
1504
1505             if(abs(outsize - origsize) < 32)
1506                 f = 0.0;
1507             else if(origsize < 1.0)
1508                 f = 0.0;
1509             else
1510                 f = origsize;
1511
1512             fputs(sDivZero(pct(outsize), f, Compress), mailf);
1513
1514             cd= &ColumnData[DumpTime];
1515             g_fprintf(mailf, "%*s", cd->PrefixSpace, "");
1516             if(repdata->dumper.result == L_SUCCESS ||
1517                repdata->dumper.result == L_CHUNKSUCCESS)
1518                 g_snprintf(TimeRateBuffer, SIZEOF(TimeRateBuffer),
1519                   "%3d:%02d", mnsc(repdata->dumper.sec));
1520             else
1521                 g_snprintf(TimeRateBuffer, SIZEOF(TimeRateBuffer),
1522                   "N/A ");
1523             g_fprintf(mailf, cd->Format, cd->Width, cd->Width, TimeRateBuffer);
1524
1525             cd= &ColumnData[DumpRate];
1526             g_fprintf(mailf, "%*s", cd->PrefixSpace, "");
1527             if(repdata->dumper.result == L_SUCCESS ||
1528                     repdata->dumper.result == L_CHUNKSUCCESS)
1529                 g_fprintf(mailf, cd->Format, cd->Width, cd->Precision, repdata->dumper.kps);
1530             else
1531                 g_fprintf(mailf, "%*s", cd->Width, "N/A ");
1532
1533             cd= &ColumnData[TapeTime];
1534             g_fprintf(mailf, "%*s", cd->PrefixSpace, "");
1535             if(repdata->taper.result == L_FAIL) {
1536                 g_fprintf(mailf, "%s\n",
1537                         tmp=TextRule(TapeTime, TapeRate, "FAILED "));
1538                 amfree(tmp);
1539                 continue;
1540             }
1541
1542             if(repdata->taper.result == L_SUCCESS || 
1543                repdata->taper.result == L_PARTIAL ||
1544                repdata->taper.result == L_CHUNKSUCCESS)
1545                 g_snprintf(TimeRateBuffer, SIZEOF(TimeRateBuffer),
1546                   "%3d:%02d", mnsc(repdata->taper.sec));
1547             else
1548                 g_snprintf(TimeRateBuffer, SIZEOF(TimeRateBuffer),
1549                   "N/A ");
1550             g_fprintf(mailf, cd->Format, cd->Width, cd->Width, TimeRateBuffer);
1551
1552             cd= &ColumnData[TapeRate];
1553             g_fprintf(mailf, "%*s", cd->PrefixSpace, "");
1554             if(repdata->taper.result == L_SUCCESS || 
1555                repdata->taper.result == L_PARTIAL ||
1556                repdata->taper.result == L_CHUNKSUCCESS)
1557                 g_fprintf(mailf, cd->Format, cd->Width, cd->Precision, repdata->taper.kps);
1558             else
1559                 g_fprintf(mailf, "%*s", cd->Width, "N/A ");
1560
1561             if (repdata->chunker.result == L_PARTIAL)
1562                 g_fprintf(mailf, " PARTIAL");
1563             else if(repdata->taper.result == L_PARTIAL)
1564                 g_fprintf(mailf, " TAPE-PARTIAL");
1565
1566             fputc('\n', mailf);
1567         }
1568       }
1569     }
1570 }
1571
1572 static void
1573 bogus_line(
1574     const char *err_text)
1575 {
1576     char * s;
1577     s = g_strdup_printf(_("line %d of log is bogus: <%s %s %s>\n"),
1578                         curlinenum, 
1579                         logtype_str[curlog], program_str[curprog], curstr);
1580     g_printf("%s\n", s);
1581     g_printf(_("  Scan failed at: <%s>\n"), err_text);
1582     addline(&errsum, s);
1583     amfree(s);
1584 }
1585
1586
1587 /*
1588  * Formats an integer of the form YYYYMMDDHHMMSS into the string
1589  * "Monthname DD, YYYY".  A pointer to the statically allocated string
1590  * is returned, so it must be copied to other storage (or just printed)
1591  * before calling nicedate() again.
1592  */
1593 static char *
1594 nicedate(
1595     const char *datestamp)
1596 {
1597     static char nice[64];
1598     char date[9];
1599     int  numdate;
1600     static char *months[13] = {
1601                 T_("BogusMonth"),
1602                 T_("January"),
1603                 T_("February"),
1604                 T_("March"),
1605                 T_("April"),
1606                 T_("May"),
1607                 T_("June"),
1608                 T_("July"),
1609                 T_("August"),
1610                 T_("September"),
1611                 T_("October"),
1612                 T_("November"),
1613                 T_("December")
1614     };
1615     int year, month, day;
1616
1617     strncpy(date, datestamp, 8);
1618     date[8] = '\0';
1619     numdate = atoi(date);
1620     year  = numdate / 10000;
1621     day   = numdate % 100;
1622     month = (numdate / 100) % 100;
1623     if (month > 12 )
1624         month = 0;
1625
1626     g_snprintf(nice, SIZEOF(nice), "%s %d, %d", _(months[month]), day, year);
1627
1628     return nice;
1629 }
1630
1631 static void
1632 handle_start(void)
1633 {
1634     static int started = 0;
1635     char *label;
1636     char *s, *fp;
1637     int ch;
1638
1639     switch(curprog) {
1640     case P_TAPER:
1641         s = curstr;
1642         ch = *s++;
1643
1644         skip_whitespace(s, ch);
1645         if(ch == '\0' || strncmp_const_skip(s - 1, "datestamp", s, ch) != 0) {
1646             bogus_line(s - 1);
1647             return;
1648         }
1649
1650         skip_whitespace(s, ch);
1651         if(ch == '\0') {
1652             bogus_line(s - 1);
1653             return;
1654         }
1655         fp = s - 1;
1656         skip_non_whitespace(s, ch);
1657         s[-1] = '\0';
1658         run_datestamp = newstralloc(run_datestamp, fp);
1659         s[-1] = (char)ch;
1660
1661         skip_whitespace(s, ch);
1662         if(ch == '\0' || strncmp_const_skip(s - 1, "label", s, ch) != 0) {
1663             bogus_line(s - 1);
1664             return;
1665         }
1666
1667         skip_whitespace(s, ch);
1668         if(ch == '\0') {
1669             bogus_line(s - 1);
1670             return;
1671         }
1672         fp = s - 1;
1673         skip_non_whitespace(s, ch);
1674         s[-1] = '\0';
1675
1676         label = stralloc(fp);
1677         s[-1] = (char)ch;
1678
1679         if(tape_labels) {
1680             fp = vstralloc(tape_labels, ", ", label, NULL);
1681             amfree(tape_labels);
1682             tape_labels = fp;
1683         } else {
1684             tape_labels = stralloc(label);
1685         }
1686
1687         last_run_tapes++;
1688
1689         if(stats_by_tape == NULL) {
1690             stats_by_tape = current_tape = (taper_t *)alloc(SIZEOF(taper_t));
1691         }
1692         else {
1693             current_tape->next = (taper_t *)alloc(SIZEOF(taper_t));
1694             current_tape = current_tape->next;
1695         }
1696         current_tape->label = label;
1697         current_tape->taper_time = 0.0;
1698         current_tape->coutsize = 0.0;
1699         current_tape->corigsize = 0.0;
1700         current_tape->tapedisks = 0;
1701         current_tape->tapechunks = 0;
1702         current_tape->next = NULL;
1703         tapefcount = 0;
1704
1705         return;
1706     case P_PLANNER:
1707         normal_run = 1;
1708         break;
1709     case P_DRIVER:
1710         break;
1711     case P_AMFLUSH:
1712         amflush_run = 1;
1713         break;
1714     default:
1715         ;
1716     }
1717
1718     if(!started) {
1719         s = curstr;
1720         ch = *s++;
1721
1722         skip_whitespace(s, ch);
1723         if(ch == '\0' || strncmp_const_skip(s - 1, "date", s, ch) != 0) {
1724             return;                             /* ignore bogus line */
1725         }
1726
1727         skip_whitespace(s, ch);
1728         if(ch == '\0') {
1729             bogus_line(s - 1);
1730             return;
1731         }
1732         fp = s - 1;
1733         skip_non_whitespace(s, ch);
1734         s[-1] = '\0';
1735         run_datestamp = newstralloc(run_datestamp, fp);
1736         s[-1] = (char)ch;
1737
1738         started = 1;
1739     }
1740     if(amflush_run && normal_run) {
1741         amflush_run = 0;
1742         addline(&notes,
1743      _("  reporter: both amflush and planner output in log, ignoring amflush."));
1744     }
1745 }
1746
1747
1748 static void
1749 handle_finish(void)
1750 {
1751     char *s;
1752     int ch;
1753     double a_time;
1754
1755     if(curprog == P_DRIVER || curprog == P_AMFLUSH || curprog == P_PLANNER) {
1756         s = curstr;
1757         ch = *s++;
1758
1759         skip_whitespace(s, ch);
1760         if(ch == '\0' || strncmp_const_skip(s - 1, "date", s, ch) != 0) {
1761             bogus_line(s - 1);
1762             return;
1763         }
1764
1765         skip_whitespace(s, ch);
1766         if(ch == '\0') {
1767             bogus_line(s - 1);
1768             return;
1769         }
1770         skip_non_whitespace(s, ch);     /* ignore the date string */
1771
1772         skip_whitespace(s, ch);
1773         if(ch == '\0' || strncmp_const_skip(s - 1, "time", s, ch) != 0) {
1774             /* older planner doesn't write time */
1775             if(curprog == P_PLANNER) return;
1776             bogus_line(s - 1);
1777             return;
1778         }
1779
1780         skip_whitespace(s, ch);
1781         if(ch == '\0') {
1782             bogus_line(s - 1);
1783             return;
1784         }
1785         if(sscanf(s - 1, "%lf", &a_time) != 1) {
1786             bogus_line(s - 1);
1787             return;
1788         }
1789         if(curprog == P_PLANNER) {
1790             planner_time = a_time;
1791         }
1792         else {
1793             total_time = a_time;
1794             got_finish = 1;
1795         }
1796     }
1797 }
1798
1799 static void
1800 handle_stats(void)
1801 {
1802     char *s, *fp;
1803     int ch;
1804     char *hostname, *diskname, *datestamp, *qdiskname;
1805     int level = 0;
1806     double sec, kps, nbytes, cbytes;
1807     repdata_t *repdata;
1808     disk_t *dp;
1809
1810     if(curprog == P_DRIVER) {
1811         s = curstr;
1812         ch = *s++;
1813
1814         skip_whitespace(s, ch);
1815         if(ch != '\0' && strncmp_const_skip(s - 1, "startup time", s, ch) == 0) {
1816             skip_whitespace(s, ch);
1817             if(ch == '\0') {
1818                 bogus_line(s - 1);
1819                 return;
1820             }
1821             if(sscanf(s - 1, "%lf", &startup_time) != 1) {
1822                 bogus_line(s - 1);
1823                 return;
1824             }
1825             planner_time = startup_time;
1826         }
1827         else if(ch != '\0' && strncmp_const_skip(s - 1, "hostname", s, ch) == 0) {
1828             skip_whitespace(s, ch);
1829             if(ch == '\0') {
1830                 bogus_line(s - 1);
1831                 return;
1832             }
1833             ghostname = stralloc(s-1);
1834         }
1835         else if(ch != '\0' && strncmp_const_skip(s - 1, "estimate", s, ch) == 0) {
1836             skip_whitespace(s, ch);
1837             if(ch == '\0') {
1838                 bogus_line(s - 1);
1839                 return;
1840             }
1841             fp = s - 1;
1842             skip_non_whitespace(s, ch);
1843             s[-1] = '\0';
1844             hostname = stralloc(fp);
1845             s[-1] = (char)ch;
1846
1847             skip_whitespace(s, ch);
1848             if(ch == '\0') {
1849                 bogus_line(s - 1);
1850                 amfree(hostname);
1851                 return;
1852             }
1853
1854             qdiskname = s - 1;
1855             skip_quoted_string(s, ch);
1856             s[-1] = '\0';
1857             diskname = unquote_string(qdiskname);
1858             s[-1] = (char)ch;
1859
1860             skip_whitespace(s, ch);
1861             if(ch == '\0') {
1862                 bogus_line(s - 1);
1863                 amfree(hostname);
1864                 amfree(diskname);
1865                 return;
1866             }
1867             fp = s - 1;
1868             skip_non_whitespace(s, ch);
1869             s[-1] = '\0';
1870             datestamp = stralloc(fp);
1871             s[-1] = (char)ch;
1872             skip_whitespace(s, ch);
1873
1874             if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
1875                 bogus_line(s - 1);
1876                 amfree(hostname);
1877                 amfree(diskname);
1878                 amfree(datestamp);
1879                 return;
1880             }
1881             skip_integer(s, ch);
1882             if(level < 0 || level > 9) {
1883                 amfree(hostname);
1884                 amfree(diskname);
1885                 amfree(datestamp);
1886                 return;
1887             }
1888
1889             skip_whitespace(s, ch);
1890
1891             if(sscanf(s - 1,"[sec %lf nkb %lf ckb %lf kps %lf",
1892                       &sec, &nbytes, &cbytes, &kps) != 4)  {
1893                 bogus_line(s - 1);
1894                 amfree(hostname);
1895                 amfree(diskname);
1896                 amfree(datestamp);
1897                 return;
1898             }
1899
1900             dp = lookup_disk(hostname, diskname);
1901             if(dp == NULL) {
1902                 addtoX_summary(&first_failed, &last_failed,
1903                                hostname, diskname, level,
1904                                _("ERROR [not in disklist]"));
1905                 exit_status |= STATUS_FAILED;
1906                 amfree(hostname);
1907                 amfree(diskname);
1908                 amfree(datestamp);
1909                 return;
1910             }
1911
1912             repdata = find_repdata(dp, datestamp, level);
1913
1914             repdata->est_nsize = nbytes;
1915             repdata->est_csize = cbytes;
1916
1917             amfree(hostname);
1918             amfree(diskname);
1919             amfree(datestamp);
1920         }
1921         else {
1922             bogus_line(s - 1);
1923             return;
1924         }
1925 #undef sc
1926
1927     }
1928 }
1929
1930
1931 static void
1932 handle_note(void)
1933 {
1934     char *str = NULL;
1935
1936     str = vstrallocf("  %s: %s", program_str[curprog], curstr);
1937     addline(&notes, str);
1938     amfree(str);
1939 }
1940
1941
1942 /* ----- */
1943
1944 static void
1945 handle_error(void)
1946 {
1947     char *s = NULL, *nl;
1948     int ch;
1949
1950     if(curlog == L_ERROR && curprog == P_TAPER) {
1951         s = curstr;
1952         ch = *s++;
1953
1954         skip_whitespace(s, ch);
1955         if(ch != '\0' && strncmp_const_skip(s - 1, "no-tape", s, ch) == 0) {
1956             skip_whitespace(s, ch);
1957             if(ch != '\0') {
1958                 if((nl = strchr(s - 1, '\n')) != NULL) {
1959                     *nl = '\0';
1960                 }
1961                 tapestart_error = newstralloc(tapestart_error, s - 1);
1962                 if(nl) *nl = '\n';
1963                 degraded_mode = 1;
1964                 exit_status |= STATUS_TAPE;;
1965                 return;
1966             }
1967             /* else some other tape error, handle like other errors */
1968         }
1969         /* else some other tape error, handle like other errors */
1970     }
1971     s = vstrallocf("  %s: %s %s", program_str[curprog],
1972                   logtype_str[curlog], curstr);
1973     addline(&errsum, s);
1974     amfree(s);
1975 }
1976
1977 /* ----- */
1978
1979 static void
1980 handle_summary(void)
1981 {
1982     bogus_line(curstr);
1983 }
1984
1985 /* ----- */
1986
1987 static int nb_disk=0;
1988 static void
1989 handle_disk(void)
1990 {
1991     disk_t *dp;
1992     char *s, *fp, *qdiskname;
1993     int ch;
1994     char *hostname = NULL, *diskname = NULL;
1995
1996     if(curprog != P_PLANNER && curprog != P_AMFLUSH) {
1997         bogus_line(curstr);
1998         return;
1999     }
2000
2001     if(nb_disk==0) {
2002         for(dp = diskq.head; dp != NULL; dp = dp->next)
2003             dp->todo = 0;
2004     }
2005     nb_disk++;
2006
2007     s = curstr;
2008     ch = *s++;
2009
2010     skip_whitespace(s, ch);
2011     if(ch == '\0') {
2012         bogus_line(s - 1);
2013         return;
2014     }
2015     fp = s - 1;
2016     skip_non_whitespace(s, ch);
2017     s[-1] = '\0';
2018     hostname = newstralloc(hostname, fp);
2019     s[-1] = (char)ch;
2020
2021     skip_whitespace(s, ch);
2022     if(ch == '\0') {
2023         bogus_line(s - 1);
2024         amfree(hostname);
2025         return;
2026     }
2027     qdiskname = s - 1;
2028     skip_quoted_string(s, ch);
2029     s[-1] = '\0';
2030     diskname = unquote_string(qdiskname);
2031     s[-1] = (char)ch;
2032
2033     dp = lookup_disk(hostname, diskname);
2034     if(dp == NULL) {
2035         dp = add_disk(&diskq, hostname, diskname);
2036     }
2037
2038     amfree(hostname);
2039     amfree(diskname);
2040     dp->todo = 1;
2041 }
2042
2043 /* XXX Just a placeholder, in case we decide to do something with L_CHUNK
2044  * log entries.  Right now they're just the equivalent of L_SUCCESS, but only
2045  * for a split chunk of the overall dumpfile.
2046  */
2047 static repdata_t *
2048 handle_chunk(
2049     logtype_t logtype)
2050 {
2051     disk_t *dp;
2052     double sec, kps, kbytes;
2053     timedata_t *sp;
2054     int i;
2055     char *s, *fp;
2056     int ch;
2057     char *hostname = NULL;
2058     char *diskname = NULL;
2059     repdata_t *repdata;
2060     int level, chunk;
2061     char *datestamp;
2062     char *label = NULL;
2063     int fileno;
2064     int totpart;
2065     
2066     if(curprog != P_TAPER) {
2067         bogus_line(curstr);
2068         return NULL;
2069     }
2070     
2071     s = curstr;
2072     ch = *s++;
2073
2074     skip_whitespace(s, ch);
2075     if(ch == '\0') {
2076         bogus_line(s - 1);
2077         return NULL;
2078     }
2079
2080     if (logtype == L_PART || logtype == L_PARTPARTIAL) {
2081         fp = s - 1;
2082         skip_non_whitespace(s, ch);
2083         s[-1] = '\0';
2084         label = stralloc(fp);
2085         s[-1] = (char)ch;
2086     
2087         skip_whitespace(s, ch);
2088         if(ch == '\0' || sscanf(s - 1, "%d", &fileno) != 1) {
2089             bogus_line(s - 1);
2090             amfree(label);
2091             return NULL;
2092         }
2093         skip_integer(s, ch);
2094         skip_whitespace(s, ch);
2095         if(ch == '\0') {
2096             bogus_line(s - 1);
2097             amfree(label);
2098             return NULL;
2099         }
2100         amfree(label);
2101     }
2102
2103     fp = s - 1;
2104     skip_non_whitespace(s, ch);
2105     s[-1] = '\0';
2106     hostname = stralloc(fp);
2107     s[-1] = (char)ch;
2108     
2109     skip_whitespace(s, ch);
2110     if(ch == '\0') {
2111         bogus_line(s - 1);
2112         amfree(hostname);
2113         return NULL;
2114     }
2115     fp = s - 1;
2116     skip_quoted_string(s, ch);
2117     s[-1] = '\0';
2118     diskname = unquote_string(fp);
2119     s[-1] = (char)ch;
2120     
2121     skip_whitespace(s, ch);
2122     if(ch == '\0') {
2123         bogus_line(s - 1);
2124         amfree(hostname);
2125         amfree(diskname);
2126         return NULL;
2127     }
2128     fp = s - 1;
2129     skip_non_whitespace(s, ch);
2130     s[-1] = '\0';
2131     datestamp = stralloc(fp);
2132     s[-1] = (char)ch;
2133  
2134     skip_whitespace(s, ch);
2135     if(ch == '\0' || sscanf(s - 1, "%d", &chunk) != 1) {
2136         bogus_line(s - 1);
2137         amfree(hostname);
2138         amfree(diskname);
2139         amfree(datestamp);
2140         return NULL;
2141     }
2142     skip_integer(s, ch);
2143
2144     if (ch != '\0' && s[-1] == '/') {
2145         s++; ch = s[-1];
2146         if (sscanf(s - 1, "%d", &totpart) != 1) {
2147             bogus_line(s - 1);
2148             amfree(hostname);
2149             amfree(diskname);
2150             amfree(datestamp);
2151             return NULL;
2152         }
2153         skip_integer(s, ch);
2154     }
2155
2156     skip_whitespace(s, ch);
2157     if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
2158         bogus_line(s - 1);
2159         amfree(hostname);
2160         amfree(diskname);
2161         amfree(datestamp);
2162         return NULL;
2163     }
2164     skip_integer(s, ch);
2165     
2166     /*@ignore@*/
2167     if(level < 0 || level > 9) {
2168         amfree(hostname);
2169         amfree(diskname);
2170         amfree(datestamp);
2171         return NULL;
2172     }
2173     /*@end@*/
2174  
2175     skip_whitespace(s, ch);
2176     if(sscanf(s - 1,"[sec %lf kb %lf kps %lf", &sec, &kbytes, &kps) != 3)  {
2177         bogus_line(s - 1);
2178         amfree(hostname);
2179         amfree(diskname);
2180         amfree(datestamp);
2181         return NULL;
2182     }
2183     
2184     
2185     dp = lookup_disk(hostname, diskname);
2186     if(dp == NULL) {
2187         char *str = NULL;
2188         
2189         str = vstrallocf(_("  %s ERROR [not in disklist]"),
2190                         prefix(hostname, diskname, level));
2191         addline(&errsum, str);
2192         amfree(str);
2193         amfree(hostname);
2194         amfree(diskname);
2195         amfree(datestamp);
2196         return NULL;
2197     }
2198     
2199     repdata = find_repdata(dp, datestamp, level);
2200     
2201     sp = &(repdata->taper);
2202     
2203     i = level > 0;
2204     
2205     amfree(hostname);
2206     amfree(diskname);
2207     amfree(datestamp);
2208     
2209     if(current_tape == NULL) {
2210         error("current_tape == NULL");
2211     }
2212     if (sp->filenum == 0) {
2213         sp->filenum = ++tapefcount;
2214         sp->tapelabel = current_tape->label;
2215     }
2216     tapechunks[level] +=1;
2217     stats[i].tapechunks +=1;
2218     current_tape->taper_time += sec;
2219     current_tape->coutsize += kbytes;
2220     current_tape->tapechunks += 1;
2221     return repdata;
2222 }
2223
2224 static repdata_t *
2225 handle_success(
2226     logtype_t   logtype)
2227 {
2228     disk_t *dp;
2229     double sec = 0.0;
2230     double kps = 0.0;
2231     double kbytes = 0.0;
2232     double origkb = 0.0;
2233     timedata_t *sp;
2234     int i;
2235     char *s, *fp, *qdiskname;
2236     int ch;
2237     char *hostname = NULL;
2238     char *diskname = NULL;
2239     repdata_t *repdata;
2240     int level = 0;
2241     int totpart = 0;
2242     char *datestamp;
2243
2244     (void)logtype;
2245
2246     if(curprog != P_TAPER && curprog != P_DUMPER && curprog != P_PLANNER &&
2247        curprog != P_CHUNKER) {
2248         bogus_line(curstr);
2249         return NULL;
2250     }
2251
2252     s = curstr;
2253     ch = *s++;
2254
2255     skip_whitespace(s, ch);
2256     if(ch == '\0') {
2257         bogus_line(s - 1);
2258         return NULL;
2259     }
2260     fp = s - 1;
2261     skip_non_whitespace(s, ch);
2262     s[-1] = '\0';
2263     hostname = stralloc(fp);
2264     s[-1] = (char)ch;
2265
2266     skip_whitespace(s, ch);
2267     if(ch == '\0') {
2268         bogus_line(s - 1);
2269         amfree(hostname);
2270         return NULL;
2271     }
2272     qdiskname = s - 1;
2273     skip_quoted_string(s, ch);
2274     s[-1] = '\0';
2275     diskname = unquote_string(qdiskname);
2276
2277     skip_whitespace(s, ch);
2278     if(ch == '\0') {
2279         bogus_line(s - 1);
2280         amfree(hostname);
2281         amfree(diskname);
2282         return NULL;
2283     }
2284     fp = s - 1;
2285     skip_non_whitespace(s, ch);
2286     s[-1] = '\0';
2287     datestamp = stralloc(fp);
2288     s[-1] = (char)ch;
2289
2290     //datestamp is optional
2291     if(strlen(datestamp) < 6) {
2292         totpart = atoi(datestamp);
2293         datestamp = newstralloc(datestamp, run_datestamp);
2294     }
2295     else {
2296         skip_whitespace(s, ch);
2297         if(ch == '\0' || sscanf(s - 1, "%d", &totpart) != 1) {
2298             bogus_line(s - 1);
2299             amfree(hostname);
2300             amfree(diskname);
2301             amfree(datestamp);
2302             return NULL;
2303         }
2304         skip_integer(s, ch);
2305     }
2306
2307     skip_whitespace(s, ch);
2308
2309     //totpart is optional
2310     if (*(s-1) == '"')
2311         s++;
2312     if (*(s-1) == '[') {
2313         level = totpart;
2314         totpart = -1;
2315     } else {
2316         if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
2317             bogus_line(s - 1);
2318             amfree(hostname);
2319             amfree(diskname);
2320             amfree(datestamp);
2321             return NULL;
2322         }
2323         skip_integer(s, ch);
2324         skip_whitespace(s, ch);
2325     }
2326
2327
2328     if(level < 0 || level > 9) {
2329         amfree(hostname);
2330         amfree(diskname);
2331         amfree(datestamp);
2332         return NULL;
2333     }
2334
2335                                 /* Planner success messages (for skipped
2336                                    dumps) do not contain statistics */
2337     if(curprog != P_PLANNER) {
2338         if(*(s - 1) == '"')
2339             s++;
2340         if((curprog != P_DUMPER)
2341             || (sscanf(s - 1,"[sec %lf kb %lf kps %lf orig-kb %lf", 
2342                   &sec, &kbytes, &kps, &origkb) != 4))  {
2343             origkb = -1;
2344             if(sscanf(s - 1,"[sec %lf kb %lf kps %lf",
2345                       &sec, &kbytes, &kps) != 3) {
2346                 bogus_line(s - 1);
2347                 amfree(hostname);
2348                 amfree(diskname);
2349                 amfree(datestamp);
2350                 return NULL;
2351             }
2352         }
2353         else {
2354             if(!isnormal(origkb))
2355                 origkb = 0.1;
2356         }
2357     }
2358
2359
2360     dp = lookup_disk(hostname, diskname);
2361     if(dp == NULL) {
2362         addtoX_summary(&first_failed, &last_failed, hostname, qdiskname, level,
2363                        _("ERROR [not in disklist]"));
2364         exit_status |= STATUS_FAILED;
2365         amfree(hostname);
2366         amfree(diskname);
2367         amfree(datestamp);
2368         return NULL;
2369     }
2370
2371     repdata = find_repdata(dp, datestamp, level);
2372
2373     if(curprog == P_PLANNER) {
2374         repdata->dumper.result = L_SKIPPED;
2375         amfree(hostname);
2376         amfree(diskname);
2377         amfree(datestamp);
2378         return repdata;
2379     }
2380
2381     if(curprog == P_TAPER)
2382         sp = &(repdata->taper);
2383     else if(curprog == P_DUMPER)
2384         sp = &(repdata->dumper);
2385     else sp = &(repdata->chunker);
2386
2387     i = level > 0;
2388
2389     if(origkb < 0.0) {
2390         info_t inf;
2391         struct tm *tm;
2392         int Idatestamp;
2393
2394         get_info(hostname, diskname, &inf);
2395         tm = localtime(&inf.inf[level].date);
2396         if (tm) {
2397             Idatestamp = 10000*(tm->tm_year+1900) +
2398                          100*(tm->tm_mon+1) + tm->tm_mday;
2399         } else {
2400             Idatestamp = 19000101;
2401         }
2402
2403         if(atoi(datestamp) == Idatestamp) {
2404             /* grab original size from record */
2405             origkb = (double)inf.inf[level].size;
2406         }
2407         else
2408             origkb = 0.0;
2409     }
2410
2411     if (curprog == P_DUMPER &&
2412         (sp->result == L_FAIL || sp->result == L_PARTIAL)) {
2413         addtoX_summary(&first_failed, &last_failed, hostname, qdiskname, level,
2414                        _("was successfully retried"));
2415     }
2416
2417     amfree(hostname);
2418     amfree(diskname);
2419     amfree(datestamp);
2420
2421     sp->result = L_SUCCESS;
2422     sp->datestamp = repdata->datestamp;
2423     sp->sec = sec;
2424     sp->kps = kps;
2425     sp->origsize = origkb;
2426     sp->outsize = kbytes;
2427
2428     if(curprog == P_TAPER) {
2429         if(current_tape == NULL) {
2430             error(_("current_tape == NULL"));
2431             /*NOTREACHED*/
2432         }
2433         stats[i].taper_time += sec;
2434         sp->filenum = ++tapefcount;
2435         sp->tapelabel = current_tape->label;
2436         sp->totpart = totpart;
2437         tapedisks[level] +=1;
2438         stats[i].tapedisks +=1;
2439         stats[i].tapesize += kbytes;
2440         sp->outsize = kbytes;
2441         if(!isnormal(repdata->chunker.outsize) && isnormal(repdata->dumper.outsize)) { /* dump to tape */
2442             stats[i].outsize += kbytes;
2443             if (abs(kbytes - origkb) >= 32) {
2444                 stats[i].coutsize += kbytes;
2445             }
2446         }
2447         current_tape->tapedisks += 1;
2448     }
2449
2450     if(curprog == P_DUMPER) {
2451         stats[i].dumper_time += sec;
2452         if (abs(kbytes - origkb) < 32) {
2453             sp->origsize = kbytes;
2454         }
2455         else {
2456             stats[i].corigsize += sp->origsize;
2457         }
2458         dumpdisks[level] +=1;
2459         stats[i].dumpdisks +=1;
2460         stats[i].origsize += sp->origsize;
2461     }
2462
2463     if(curprog == P_CHUNKER) {
2464         sp->outsize = kbytes;
2465         stats[i].outsize += kbytes;
2466         if (abs(kbytes - origkb) >= 32) {
2467             stats[i].coutsize += kbytes;
2468         }
2469     }
2470     return repdata;
2471 }
2472
2473 static void
2474 handle_partial(void)
2475 {
2476     repdata_t *repdata;
2477     timedata_t *sp;
2478
2479     repdata = handle_success(L_PARTIAL);
2480     if (!repdata)
2481         return;
2482
2483     if(curprog == P_TAPER)
2484         sp = &(repdata->taper);
2485     else if(curprog == P_DUMPER)
2486         sp = &(repdata->dumper);
2487     else sp = &(repdata->chunker);
2488
2489     sp->result = L_PARTIAL;
2490 }
2491
2492 static void
2493 handle_strange(void)
2494 {
2495     char *str = NULL;
2496     char *strangestr = NULL;
2497     repdata_t *repdata;
2498     char *qdisk;
2499
2500     repdata = handle_success(L_SUCCESS);
2501     if (!repdata)
2502         return;
2503
2504     qdisk = quote_string(repdata->disk->name);
2505
2506     addline(&strangedet,"");
2507     str = vstrallocf("/-- %s STRANGE",
2508                 prefix(repdata->disk->host->hostname, qdisk, repdata->level));
2509     addline(&strangedet, str);
2510     amfree(str);
2511
2512     while(contline_next()) {
2513         char *s, ch;
2514         get_logline(logfile);
2515         s = curstr;
2516         if(strncmp_const_skip(curstr, "sendbackup: warning ", s, ch) == 0) {
2517             strangestr = newstralloc(strangestr, s);
2518         }
2519         addline(&strangedet, curstr);
2520     }
2521     addline(&strangedet,"\\--------");
2522
2523     str = vstrallocf("STRANGE %s", strangestr? strangestr : _("(see below)"));
2524     addtoX_summary(&first_strange, &last_strange,
2525                    repdata->disk->host->hostname, qdisk, repdata->level, str);
2526     exit_status |= STATUS_STRANGE;
2527     amfree(qdisk);
2528     amfree(str);
2529     amfree(strangestr);
2530 }
2531
2532 static void
2533 handle_failed(void)
2534 {
2535     disk_t *dp;
2536     char *hostname;
2537     char *diskname;
2538     char *datestamp;
2539     char *errstr;
2540     int level = 0;
2541     char *s, *fp, *qdiskname;
2542     int ch;
2543     char *str = NULL;
2544     repdata_t *repdata;
2545     timedata_t *sp;
2546
2547     hostname = NULL;
2548     diskname = NULL;
2549
2550     s = curstr;
2551     ch = *s++;
2552
2553     skip_whitespace(s, ch);
2554     if(ch == '\0') {
2555         bogus_line(s - 1);
2556         return;
2557     }
2558     hostname = s - 1;
2559     skip_non_whitespace(s, ch);
2560     s[-1] = '\0';
2561
2562     skip_whitespace(s, ch);
2563     if(ch == '\0') {
2564         bogus_line(s - 1);
2565         return;
2566     }
2567     qdiskname = s - 1;
2568     skip_quoted_string(s, ch);
2569     s[-1] = '\0';
2570     diskname = unquote_string(qdiskname);
2571
2572     skip_whitespace(s, ch);
2573     if(ch == '\0') {
2574         bogus_line(s - 1);
2575         amfree(diskname);
2576         return;
2577     }
2578     fp = s - 1;
2579     skip_non_whitespace(s, ch);
2580     s[-1] = '\0';
2581     datestamp = stralloc(fp);
2582
2583     if(strlen(datestamp) < 3) { /* there is no datestamp, it's the level */
2584         level = atoi(datestamp);
2585         datestamp = newstralloc(datestamp, run_datestamp);
2586     }
2587     else { /* read the level */
2588         skip_whitespace(s, ch);
2589         if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
2590             bogus_line(s - 1);
2591             amfree(datestamp);
2592             amfree(diskname);
2593             return;
2594         }
2595         skip_integer(s, ch);
2596     }
2597
2598     skip_whitespace(s, ch);
2599     if(ch == '\0') {
2600         bogus_line(s - 1);
2601         amfree(datestamp);
2602         amfree(diskname);
2603         return;
2604     }
2605     errstr = s - 1;
2606     if((s = strchr(errstr, '\n')) != NULL) {
2607         *s = '\0';
2608     }
2609
2610     dp = lookup_disk(hostname, diskname);
2611     amfree(diskname);
2612     if(dp == NULL) {
2613         addtoX_summary(&first_failed, &last_failed, hostname, qdiskname, level,
2614                        _("ERROR [not in disklist]"));
2615     } else {
2616         repdata = find_repdata(dp, datestamp, level);
2617
2618         if(curprog == P_TAPER)
2619             sp = &(repdata->taper);
2620         else sp = &(repdata->dumper);
2621
2622         if(sp->result != L_SUCCESS)
2623             sp->result = L_FAIL;
2624     }
2625     amfree(datestamp);
2626
2627     str = vstrallocf(_("FAILED %s"), errstr);
2628     addtoX_summary(&first_failed, &last_failed, hostname, qdiskname, level,
2629                    str);
2630     amfree(str);
2631
2632     if(curprog == P_DUMPER) {
2633         addline(&errdet,"");
2634         str = vstrallocf("/-- %s FAILED %s",
2635                         prefix(hostname, qdiskname, level), 
2636                         errstr);
2637         addline(&errdet, str);
2638         amfree(str);
2639         while(contline_next()) {
2640             get_logline(logfile);
2641             addline(&errdet, curstr);
2642         }
2643         addline(&errdet,"\\--------");
2644         exit_status |= STATUS_FAILED;
2645     }
2646     return;
2647 }
2648
2649
2650 static void
2651 generate_missing(void)
2652 {
2653     disk_t *dp;
2654     char *qdisk;
2655
2656     for(dp = diskq.head; dp != NULL; dp = dp->next) {
2657         if(dp->todo && data(dp) == NULL) {
2658             qdisk = quote_string(dp->name);
2659             addtoX_summary(&first_failed, &last_failed, dp->host->hostname,
2660                            qdisk, -987, _("RESULTS MISSING"));
2661             exit_status |= STATUS_MISSING;
2662             amfree(qdisk);
2663         }
2664     }
2665 }
2666
2667 static void
2668 generate_bad_estimate(void)
2669 {
2670     disk_t *dp;
2671     repdata_t *repdata;
2672     char s[1000];
2673     double outsize;
2674
2675     for(dp = diskq.head; dp != NULL; dp = dp->next) {
2676         if(dp->todo) {
2677             for(repdata = data(dp); repdata != NULL; repdata = repdata->next) {
2678                 if(repdata->est_csize >= 0.1) {
2679                     if(repdata->taper.result == L_SUCCESS ||
2680                        repdata->taper.result == L_CHUNKSUCCESS)
2681                         outsize  = repdata->taper.outsize;
2682                     else if(repdata->chunker.result == L_SUCCESS ||
2683                             repdata->chunker.result == L_PARTIAL ||
2684                             repdata->chunker.result == L_CHUNKSUCCESS)
2685                         outsize  = repdata->chunker.outsize;
2686                     else if(repdata->taper.result == L_PARTIAL)
2687                         outsize  = repdata->taper.outsize;
2688                     else
2689                         outsize  = repdata->dumper.outsize;
2690
2691                     if(repdata->est_csize * 0.9 > outsize) {
2692                         g_snprintf(s, 1000,
2693                                 _("  big estimate: %s %s %d"),
2694                                  repdata->disk->host->hostname,
2695                                  repdata->disk->name,
2696                                  repdata->level);
2697                         s[999] = '\0';
2698                         addline(&notes, s);
2699                         g_snprintf(s, 1000,
2700                                  _("                est: %.0lf%s    out %.0lf%s"),
2701                                  du(repdata->est_csize), displayunit,
2702                                  du(outsize), displayunit);
2703                         s[999] = '\0';
2704                         addline(&notes, s);
2705                     }
2706                     else if(repdata->est_csize * 1.1 < outsize) {
2707                         g_snprintf(s, 1000,
2708                                 _("  small estimate: %s %s %d"),
2709                                  repdata->disk->host->hostname,
2710                                  repdata->disk->name,
2711                                  repdata->level);
2712                         s[999] = '\0';
2713                         addline(&notes, s);
2714                         g_snprintf(s, 1000,
2715                                  _("                  est: %.0lf%s    out %.0lf%s"),
2716                                  du(repdata->est_csize), displayunit,
2717                                  du(outsize), displayunit);
2718                         s[999] = '\0';
2719                         addline(&notes, s);
2720                     }
2721                 }
2722             }
2723         }
2724     }
2725 }
2726
2727 static char *
2728 prefix (
2729     char *      host,
2730     char *      disk,
2731     int         level)
2732 {
2733     static char *str = NULL;
2734
2735     if (level == -987) {
2736         str = newvstrallocf(str, " %s %s",
2737                         host ? host : _("(host?)"),
2738                         disk ? disk : _("(disk?)"));
2739     } else {
2740         str = newvstrallocf(str, " %s %s lev %d",
2741                         host ? host : _("(host?)"),
2742                         disk ? disk : _("(disk?)"),
2743                         level);
2744     }
2745     return str;
2746 }
2747
2748
2749 static char *
2750 prefixstrange (
2751     char *      host,
2752     char *      disk,
2753     int         level,
2754     size_t      len_host,
2755     size_t      len_disk)
2756 {
2757     char *h, *d;
2758     size_t l;
2759     static char *str = NULL;
2760
2761     h=alloc(len_host+1);
2762     if(host) {
2763         strncpy(h, host, len_host);
2764     } else {
2765         strncpy(h, _("(host?)"), len_host);
2766     }
2767     h[len_host] = '\0';
2768     for(l = strlen(h); l < len_host; l++) {
2769         h[l] = ' ';
2770     }
2771     d=alloc(len_disk+1);
2772     if(disk) {
2773         strncpy(d, disk, len_disk);
2774     } else {
2775         strncpy(d, _("(disk?)"), len_disk);
2776     }
2777     d[len_disk] = '\0';
2778     for(l = strlen(d); l < len_disk; l++) {
2779         d[l] = ' ';
2780     }
2781     if (level == -987) {
2782         str = newvstrallocf(str, " %s %s", h, d);
2783     } else {
2784         str = newvstrallocf(str, " %s %s lev %d", h, d, level);
2785     }
2786     amfree(h);
2787     amfree(d);
2788     return str;
2789 }
2790
2791
2792 static void
2793 addtoX_summary (
2794     X_summary_t **first,
2795     X_summary_t **last,
2796     char         *host,
2797     char         *disk,
2798     int           level,
2799     char         *str)
2800 {
2801     X_summary_t *X_summary;
2802
2803     X_summary = alloc(SIZEOF(X_summary_t));
2804     X_summary->hostname = stralloc(host);
2805     X_summary->diskname = stralloc(disk);
2806     X_summary->level    = level;
2807     X_summary->str      = stralloc(str);
2808     X_summary->next = NULL;
2809     if (*first == NULL) {
2810         *first = X_summary;
2811     }
2812     else {
2813         (*last)->next = X_summary;
2814     }
2815     *last = X_summary;
2816 }
2817
2818 static void
2819 copy_template_file(
2820     char *      lbl_templ)
2821 {
2822   char buf[BUFSIZ];
2823   int fd;
2824   ssize_t numread;
2825
2826   lbl_templ = config_dir_relative(lbl_templ);
2827   if ((fd = open(lbl_templ, 0)) < 0) {
2828     curlog = L_ERROR;
2829     curprog = P_REPORTER;
2830     curstr = vstrallocf(_("could not open PostScript template file %s: %s"),
2831                        lbl_templ, strerror(errno));
2832     handle_error();
2833     amfree(curstr);
2834     amfree(lbl_templ);
2835     afclose(postscript);
2836     return;
2837   }
2838   while ((numread = read(fd, buf, SIZEOF(buf))) > 0) {
2839     if (fwrite(buf, (size_t)numread, 1, postscript) != 1) {
2840       curlog = L_ERROR;
2841       curprog = P_REPORTER;
2842       curstr = vstrallocf(_("error copying PostScript template file %s: %s"),
2843                          lbl_templ, strerror(errno));
2844       handle_error();
2845       amfree(curstr);
2846       amfree(lbl_templ);
2847       afclose(postscript);
2848       return;
2849     }
2850   }
2851   if (numread < 0) {
2852     curlog = L_ERROR;
2853     curprog = P_REPORTER;
2854     curstr = vstrallocf(_("error reading PostScript template file %s: %s"),
2855                        lbl_templ, strerror(errno));
2856     handle_error();
2857     amfree(curstr);
2858     amfree(lbl_templ);
2859     afclose(postscript);
2860     return;
2861   }
2862   close(fd);
2863   amfree(lbl_templ);
2864 }
2865
2866 static repdata_t *
2867 find_repdata(
2868     /*@keep@*/  disk_t *dp,
2869                 char *  datestamp,
2870                 int     level)
2871 {
2872     repdata_t *repdata, *prev;
2873
2874     if(!datestamp)
2875         datestamp = run_datestamp;
2876     prev = NULL;
2877     for(repdata = data(dp); repdata != NULL && (repdata->level != level || strcmp(repdata->datestamp,datestamp)!=0); repdata = repdata->next) {
2878         prev = repdata;
2879     }
2880     if(!repdata) {
2881         repdata = (repdata_t *)alloc(SIZEOF(repdata_t));
2882         memset(repdata, '\0', SIZEOF(repdata_t));
2883         repdata->disk = dp;
2884         repdata->datestamp = stralloc(datestamp ? datestamp : "");
2885         repdata->level = level;
2886         repdata->dumper.result = L_BOGUS;
2887         repdata->taper.result = L_BOGUS;
2888         repdata->next = NULL;
2889         if(prev)
2890             prev->next = repdata;
2891         else
2892             dp->up = (void *)repdata;
2893     }
2894     return repdata;
2895 }
2896
2897
2898 static void
2899 do_postscript_output(void)
2900 {
2901     tapetype_t *tp = lookup_tapetype(getconf_str(CNF_TAPETYPE));
2902     disk_t *dp;
2903     repdata_t *repdata;
2904     double outsize, origsize;
2905     off_t tapesize;
2906     off_t marksize;
2907
2908     if (!tp)
2909         return;
2910
2911     tapesize = tapetype_get_length(tp);
2912     marksize = tapetype_get_filemark(tp);
2913
2914     for(current_tape = stats_by_tape; current_tape != NULL;
2915             current_tape = current_tape->next) {
2916
2917         if (current_tape->label == NULL) {
2918             break;
2919         }
2920
2921         copy_template_file(tapetype_get_lbl_templ(tp));
2922
2923         if (postscript == NULL)
2924             return;
2925
2926         /* generate a few elements */
2927         g_fprintf(postscript,"(%s) DrawDate\n\n",
2928                     nicedate(run_datestamp ? run_datestamp : "0"));
2929         g_fprintf(postscript,_("(Amanda Version %s) DrawVers\n"),version());
2930         g_fprintf(postscript,"(%s) DrawTitle\n", current_tape->label);
2931
2932         /* Stats */
2933         g_fprintf(postscript, "(Total Size:        %6.1lf MB) DrawStat\n",
2934               mb(current_tape->coutsize));
2935         g_fprintf(postscript, _("(Tape Used (%%)       "));
2936         divzero(postscript, pct(current_tape->coutsize + 
2937                                 marksize * (current_tape->tapedisks + current_tape->tapechunks)),
2938                                 (double)tapesize);
2939         g_fprintf(postscript," %%) DrawStat\n");
2940         g_fprintf(postscript, _("(Compression Ratio:  "));
2941         divzero(postscript, pct(current_tape->coutsize),current_tape->corigsize);
2942         g_fprintf(postscript," %%) DrawStat\n");
2943         g_fprintf(postscript,_("(Filesystems Taped: %4d) DrawStat\n"),
2944                   current_tape->tapedisks);
2945
2946         /* Summary */
2947
2948         g_fprintf(postscript,
2949               "(-) (%s) (-) (  0) (      32) (      32) DrawHost\n",
2950               current_tape->label);
2951
2952         for(dp = sortq.head; dp != NULL; dp = dp->next) {
2953             if (dp->todo == 0) {
2954                  continue;
2955             }
2956             for(repdata = data(dp); repdata != NULL; repdata = repdata->next) {
2957
2958                 if(repdata->taper.tapelabel != current_tape->label) {
2959                     continue;
2960                 }
2961
2962                 if(repdata->dumper.result == L_SUCCESS ||
2963                    repdata->dumper.result == L_PARTIAL)
2964                     origsize = repdata->dumper.origsize;
2965                 else
2966                     origsize = repdata->taper.origsize;
2967
2968                 if(repdata->taper.result == L_SUCCESS ||
2969                    repdata->taper.result == L_PARTIAL)
2970                     outsize  = repdata->taper.outsize;
2971                 else
2972                     outsize  = repdata->dumper.outsize;
2973
2974                 if (repdata->taper.result == L_SUCCESS ||
2975                     repdata->taper.result == L_PARTIAL) {
2976                     if(isnormal(origsize)) {
2977                         g_fprintf(postscript,"(%s) (%s) (%d) (%3.0d) (%8.0lf) (%8.0lf) DrawHost\n",
2978                             dp->host->hostname, dp->name, repdata->level,
2979                             repdata->taper.filenum, origsize, 
2980                             outsize);
2981                     }
2982                     else {
2983                         g_fprintf(postscript,"(%s) (%s) (%d) (%3.0d) (%8s) (%8.0lf) DrawHost\n",
2984                             dp->host->hostname, dp->name, repdata->level,
2985                             repdata->taper.filenum, "N/A", 
2986                             outsize);
2987                     }
2988                 }
2989             }
2990         }
2991         
2992         g_fprintf(postscript,"\nshowpage\n");
2993     }
2994 }