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