f9fabea9535f5ed26d7096d5e198bd1219454e6d
[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 = index(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         skip_integer(s, ch);
2144         skip_whitespace(s, ch);
2145         if(ch == '\0') {
2146             bogus_line(s - 1);
2147             amfree(label);
2148             return NULL;
2149         }
2150         amfree(label);
2151     }
2152
2153     fp = s - 1;
2154     skip_non_whitespace(s, ch);
2155     s[-1] = '\0';
2156     hostname = stralloc(fp);
2157     s[-1] = (char)ch;
2158     
2159     skip_whitespace(s, ch);
2160     if(ch == '\0') {
2161         bogus_line(s - 1);
2162         amfree(hostname);
2163         return NULL;
2164     }
2165     fp = s - 1;
2166     skip_quoted_string(s, ch);
2167     s[-1] = '\0';
2168     diskname = unquote_string(fp);
2169     s[-1] = (char)ch;
2170     
2171     skip_whitespace(s, ch);
2172     if(ch == '\0') {
2173         bogus_line(s - 1);
2174         amfree(hostname);
2175         amfree(diskname);
2176         return NULL;
2177     }
2178     fp = s - 1;
2179     skip_non_whitespace(s, ch);
2180     s[-1] = '\0';
2181     datestamp = stralloc(fp);
2182     s[-1] = (char)ch;
2183  
2184     skip_whitespace(s, ch);
2185     if(ch == '\0' || sscanf(s - 1, "%d", &chunk) != 1) {
2186         bogus_line(s - 1);
2187         amfree(hostname);
2188         amfree(diskname);
2189         amfree(datestamp);
2190         return NULL;
2191     }
2192     skip_integer(s, ch);
2193
2194     if (ch != '\0' && s[-1] == '/') {
2195         s++; ch = s[-1];
2196         if (sscanf(s - 1, "%d", &totpart) != 1) {
2197             bogus_line(s - 1);
2198             amfree(hostname);
2199             amfree(diskname);
2200             amfree(datestamp);
2201             return NULL;
2202         }
2203         skip_integer(s, ch);
2204     }
2205
2206     skip_whitespace(s, ch);
2207     if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
2208         bogus_line(s - 1);
2209         amfree(hostname);
2210         amfree(diskname);
2211         amfree(datestamp);
2212         return NULL;
2213     }
2214     skip_integer(s, ch);
2215     
2216     /*@ignore@*/
2217     if(level < 0 || level > 9) {
2218         amfree(hostname);
2219         amfree(diskname);
2220         amfree(datestamp);
2221         return NULL;
2222     }
2223     /*@end@*/
2224  
2225     skip_whitespace(s, ch);
2226     if(sscanf(s - 1,"[sec %lf kb %lf kps %lf", &sec, &kbytes, &kps) != 3)  {
2227         bogus_line(s - 1);
2228         amfree(hostname);
2229         amfree(diskname);
2230         amfree(datestamp);
2231         return NULL;
2232     }
2233     
2234     
2235     dp = lookup_disk(hostname, diskname);
2236     if(dp == NULL) {
2237         char *str = NULL;
2238         
2239         str = vstrallocf(_("  %s ERROR [not in disklist]"),
2240                         prefix(hostname, diskname, level));
2241         addline(&errsum, str);
2242         amfree(str);
2243         amfree(hostname);
2244         amfree(diskname);
2245         amfree(datestamp);
2246         return NULL;
2247     }
2248     
2249     repdata = find_repdata(dp, datestamp, level);
2250     
2251     sp = &(repdata->taper);
2252     
2253     i = level > 0;
2254     
2255     amfree(hostname);
2256     amfree(diskname);
2257     amfree(datestamp);
2258     
2259     if(current_tape == NULL) {
2260         error("current_tape == NULL");
2261     }
2262     if (sp->filenum == 0) {
2263         sp->filenum = ++tapefcount;
2264         sp->tapelabel = current_tape->label;
2265     }
2266     tapechunks[level] +=1;
2267     stats[i].tapechunks +=1;
2268     current_tape->taper_time += sec;
2269     current_tape->coutsize += kbytes;
2270     current_tape->tapechunks += 1;
2271     return repdata;
2272 }
2273
2274 static repdata_t *
2275 handle_success(
2276     logtype_t   logtype)
2277 {
2278     disk_t *dp;
2279     double sec = 0.0;
2280     double kps = 0.0;
2281     double kbytes = 0.0;
2282     double origkb = 0.0;
2283     timedata_t *sp;
2284     int i;
2285     char *s, *fp, *qdiskname;
2286     int ch;
2287     char *hostname = NULL;
2288     char *diskname = NULL;
2289     repdata_t *repdata;
2290     int level = 0;
2291     int totpart = 0;
2292     char *datestamp;
2293
2294     (void)logtype;
2295
2296     if(curprog != P_TAPER && curprog != P_DUMPER && curprog != P_PLANNER &&
2297        curprog != P_CHUNKER) {
2298         bogus_line(curstr);
2299         return NULL;
2300     }
2301
2302     s = curstr;
2303     ch = *s++;
2304
2305     skip_whitespace(s, ch);
2306     if(ch == '\0') {
2307         bogus_line(s - 1);
2308         return NULL;
2309     }
2310     fp = s - 1;
2311     skip_non_whitespace(s, ch);
2312     s[-1] = '\0';
2313     hostname = stralloc(fp);
2314     s[-1] = (char)ch;
2315
2316     skip_whitespace(s, ch);
2317     if(ch == '\0') {
2318         bogus_line(s - 1);
2319         amfree(hostname);
2320         return NULL;
2321     }
2322     qdiskname = s - 1;
2323     skip_quoted_string(s, ch);
2324     s[-1] = '\0';
2325     diskname = unquote_string(qdiskname);
2326
2327     skip_whitespace(s, ch);
2328     if(ch == '\0') {
2329         bogus_line(s - 1);
2330         amfree(hostname);
2331         amfree(diskname);
2332         return NULL;
2333     }
2334     fp = s - 1;
2335     skip_non_whitespace(s, ch);
2336     s[-1] = '\0';
2337     datestamp = stralloc(fp);
2338     s[-1] = (char)ch;
2339
2340     //datestamp is optional
2341     if(strlen(datestamp) < 6) {
2342         totpart = atoi(datestamp);
2343         datestamp = newstralloc(datestamp, run_datestamp);
2344     }
2345     else {
2346         skip_whitespace(s, ch);
2347         if(ch == '\0' || sscanf(s - 1, "%d", &totpart) != 1) {
2348             bogus_line(s - 1);
2349             amfree(hostname);
2350             amfree(diskname);
2351             amfree(datestamp);
2352             return NULL;
2353         }
2354         skip_integer(s, ch);
2355     }
2356
2357     skip_whitespace(s, ch);
2358
2359     //totpart is optional
2360     if (*(s-1) == '"')
2361         s++;
2362     if (*(s-1) == '[') {
2363         level = totpart;
2364         totpart = -1;
2365     } else {
2366         if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
2367             bogus_line(s - 1);
2368             amfree(hostname);
2369             amfree(diskname);
2370             amfree(datestamp);
2371             return NULL;
2372         }
2373         skip_integer(s, ch);
2374         skip_whitespace(s, ch);
2375     }
2376
2377
2378     if(level < 0 || level > 9) {
2379         amfree(hostname);
2380         amfree(diskname);
2381         amfree(datestamp);
2382         return NULL;
2383     }
2384
2385                                 /* Planner success messages (for skipped
2386                                    dumps) do not contain statistics */
2387     if(curprog != P_PLANNER) {
2388         if(*(s - 1) == '"')
2389             s++;
2390         if((curprog != P_DUMPER)
2391             || (sscanf(s - 1,"[sec %lf kb %lf kps %lf orig-kb %lf", 
2392                   &sec, &kbytes, &kps, &origkb) != 4))  {
2393             origkb = -1;
2394             if(sscanf(s - 1,"[sec %lf kb %lf kps %lf",
2395                       &sec, &kbytes, &kps) != 3) {
2396                 bogus_line(s - 1);
2397                 amfree(hostname);
2398                 amfree(diskname);
2399                 amfree(datestamp);
2400                 return NULL;
2401             }
2402         }
2403         else {
2404             if(!isnormal(origkb))
2405                 origkb = 0.1;
2406         }
2407         if (curprog == P_TAPER && logtype == L_PARTIAL) {
2408             char *t = index(s-1,']');
2409             if (t) {
2410                 char *errmsg, *u;
2411                 errmsg = unquote_string(t+1);
2412                 u = vstrallocf("  %s: partial %s: %s",
2413                                prefix(hostname, diskname, level),
2414                                program_str[curprog], errmsg);
2415                 addline(&errsum, u);
2416             }
2417         }
2418     }
2419
2420
2421     dp = lookup_disk(hostname, diskname);
2422     if(dp == NULL) {
2423         addtoX_summary(&first_failed, &last_failed, hostname, qdiskname, level,
2424                        _("ERROR [not in disklist]"));
2425         exit_status |= STATUS_FAILED;
2426         amfree(hostname);
2427         amfree(diskname);
2428         amfree(datestamp);
2429         return NULL;
2430     }
2431
2432     repdata = find_repdata(dp, datestamp, level);
2433
2434     if(curprog == P_PLANNER) {
2435         repdata->dumper.result = L_SKIPPED;
2436         amfree(hostname);
2437         amfree(diskname);
2438         amfree(datestamp);
2439         return repdata;
2440     }
2441
2442     if(curprog == P_TAPER)
2443         sp = &(repdata->taper);
2444     else if(curprog == P_DUMPER)
2445         sp = &(repdata->dumper);
2446     else sp = &(repdata->chunker);
2447
2448     i = level > 0;
2449
2450     if (origkb < 0.0 && (curprog == P_CHUNKER || curprog == P_TAPER) &&
2451         isnormal(repdata->dumper.outsize)) {
2452         /* take origkb from DUMPER line */
2453         origkb = repdata->dumper.outsize;
2454     } else if (origkb < 0.0) {
2455         /* take origkb from infofile, needed for amflush */
2456         info_t inf;
2457         struct tm *tm;
2458         int Idatestamp;
2459
2460         get_info(hostname, diskname, &inf);
2461         tm = localtime(&inf.inf[level].date);
2462         if (tm) {
2463             Idatestamp = 10000*(tm->tm_year+1900) +
2464                          100*(tm->tm_mon+1) + tm->tm_mday;
2465         } else {
2466             Idatestamp = 19000101;
2467         }
2468
2469         if(atoi(datestamp) == Idatestamp) {
2470             /* grab original size from record */
2471             origkb = (double)inf.inf[level].size;
2472         }
2473         else
2474             origkb = 0.0;
2475     }
2476
2477     if (curprog == P_DUMPER &&
2478         (sp->result == L_FAIL || sp->result == L_PARTIAL)) {
2479         addtoX_summary(&first_failed, &last_failed, hostname, qdiskname, level,
2480                        _("was successfully retried"));
2481     }
2482
2483     amfree(hostname);
2484     amfree(diskname);
2485     amfree(datestamp);
2486
2487     sp->result = L_SUCCESS;
2488     sp->datestamp = repdata->datestamp;
2489     sp->sec = sec;
2490     sp->kps = kps;
2491     sp->origsize = origkb;
2492     sp->outsize = kbytes;
2493
2494     if(curprog == P_TAPER) {
2495         if(current_tape == NULL) {
2496             error(_("current_tape == NULL"));
2497             /*NOTREACHED*/
2498         }
2499         stats[i].taper_time += sec;
2500         sp->filenum = ++tapefcount;
2501         sp->tapelabel = current_tape->label;
2502         sp->totpart = totpart;
2503         tapedisks[level] +=1;
2504         stats[i].tapedisks +=1;
2505         stats[i].tapesize += kbytes;
2506         sp->outsize = kbytes;
2507         if(!isnormal(repdata->chunker.outsize) && isnormal(repdata->dumper.outsize)) { /* dump to tape */
2508             stats[i].outsize += kbytes;
2509             if (abs(kbytes - origkb) >= 32) {
2510                 /* server compressed */
2511                 stats[i].corigsize += origkb;
2512                 stats[i].coutsize += kbytes;
2513             }
2514         }
2515         current_tape->tapedisks += 1;
2516     }
2517
2518     if(curprog == P_DUMPER) {
2519         stats[i].dumper_time += sec;
2520         if (abs(kbytes - origkb) < 32) {
2521             /* not client compressed */
2522             sp->origsize = kbytes;
2523         }
2524         else {
2525             /* client compressed */
2526             stats[i].corigsize += sp->origsize;
2527             stats[i].coutsize += kbytes;
2528         }
2529         dumpdisks[level] +=1;
2530         stats[i].dumpdisks +=1;
2531         stats[i].origsize += sp->origsize;
2532     }
2533
2534     if(curprog == P_CHUNKER) {
2535         sp->outsize = kbytes;
2536         stats[i].outsize += kbytes;
2537         if (abs(kbytes - origkb) >= 32) {
2538             /* server compressed */
2539             stats[i].corigsize += origkb;
2540             stats[i].coutsize += kbytes;
2541         }
2542     }
2543     return repdata;
2544 }
2545
2546 static void
2547 handle_partial(void)
2548 {
2549     repdata_t *repdata;
2550     timedata_t *sp;
2551
2552     repdata = handle_success(L_PARTIAL);
2553     if (!repdata)
2554         return;
2555
2556     if(curprog == P_TAPER)
2557         sp = &(repdata->taper);
2558     else if(curprog == P_DUMPER)
2559         sp = &(repdata->dumper);
2560     else sp = &(repdata->chunker);
2561
2562     sp->result = L_PARTIAL;
2563 }
2564
2565 static void
2566 handle_strange(void)
2567 {
2568     char *str = NULL;
2569     char *strangestr = NULL;
2570     repdata_t *repdata;
2571     char *qdisk;
2572
2573     repdata = handle_success(L_SUCCESS);
2574     if (!repdata)
2575         return;
2576
2577     qdisk = quote_string(repdata->disk->name);
2578
2579     addline(&strangedet,"");
2580     str = vstrallocf("/-- %s STRANGE",
2581                 prefix(repdata->disk->host->hostname, qdisk, repdata->level));
2582     addline(&strangedet, str);
2583     amfree(str);
2584
2585     while(contline_next()) {
2586         char *s, ch;
2587         get_logline(logfile);
2588         s = curstr;
2589         if(strncmp_const_skip(curstr, "sendbackup: warning ", s, ch) == 0) {
2590             strangestr = newstralloc(strangestr, s);
2591         }
2592         addline(&strangedet, curstr);
2593     }
2594     addline(&strangedet,"\\--------");
2595
2596     str = vstrallocf("STRANGE %s", strangestr? strangestr : _("(see below)"));
2597     addtoX_summary(&first_strange, &last_strange,
2598                    repdata->disk->host->hostname, qdisk, repdata->level, str);
2599     exit_status |= STATUS_STRANGE;
2600     amfree(qdisk);
2601     amfree(str);
2602     amfree(strangestr);
2603 }
2604
2605 static void
2606 handle_failed(void)
2607 {
2608     disk_t *dp;
2609     char *hostname;
2610     char *diskname;
2611     char *datestamp;
2612     char *errstr;
2613     int level = 0;
2614     char *s, *fp, *qdiskname;
2615     int ch;
2616     char *str = NULL;
2617     repdata_t *repdata;
2618     timedata_t *sp;
2619
2620     hostname = NULL;
2621     diskname = NULL;
2622
2623     s = curstr;
2624     ch = *s++;
2625
2626     skip_whitespace(s, ch);
2627     if(ch == '\0') {
2628         bogus_line(s - 1);
2629         return;
2630     }
2631     hostname = s - 1;
2632     skip_non_whitespace(s, ch);
2633     s[-1] = '\0';
2634
2635     skip_whitespace(s, ch);
2636     if(ch == '\0') {
2637         bogus_line(s - 1);
2638         return;
2639     }
2640     qdiskname = s - 1;
2641     skip_quoted_string(s, ch);
2642     s[-1] = '\0';
2643     diskname = unquote_string(qdiskname);
2644
2645     skip_whitespace(s, ch);
2646     if(ch == '\0') {
2647         bogus_line(s - 1);
2648         amfree(diskname);
2649         return;
2650     }
2651     fp = s - 1;
2652     skip_non_whitespace(s, ch);
2653     s[-1] = '\0';
2654     datestamp = stralloc(fp);
2655
2656     if(strlen(datestamp) < 3) { /* there is no datestamp, it's the level */
2657         level = atoi(datestamp);
2658         datestamp = newstralloc(datestamp, run_datestamp);
2659     }
2660     else { /* read the level */
2661         skip_whitespace(s, ch);
2662         if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
2663             bogus_line(s - 1);
2664             amfree(datestamp);
2665             amfree(diskname);
2666             return;
2667         }
2668         skip_integer(s, ch);
2669     }
2670
2671     skip_whitespace(s, ch);
2672     if(ch == '\0') {
2673         bogus_line(s - 1);
2674         amfree(datestamp);
2675         amfree(diskname);
2676         return;
2677     }
2678     errstr = s - 1;
2679     if((s = strchr(errstr, '\n')) != NULL) {
2680         *s = '\0';
2681     }
2682
2683     dp = lookup_disk(hostname, diskname);
2684     amfree(diskname);
2685     if(dp == NULL) {
2686         addtoX_summary(&first_failed, &last_failed, hostname, qdiskname, level,
2687                        _("ERROR [not in disklist]"));
2688     } else {
2689         repdata = find_repdata(dp, datestamp, level);
2690
2691         if(curprog == P_TAPER)
2692             sp = &(repdata->taper);
2693         else if (curprog == P_PLANNER)
2694             sp = &(repdata->planner);
2695         else sp = &(repdata->dumper);
2696
2697         if(sp->result != L_SUCCESS)
2698             sp->result = L_FAIL;
2699     }
2700     amfree(datestamp);
2701
2702     if (!((curprog == P_CHUNKER &&
2703            strcmp(errstr, "[dumper returned FAILED]") == 0) ||
2704           (curprog == P_CHUNKER &&
2705            strcmp(errstr, "[Not enough holding disk space]") == 0) ||
2706           (curprog == P_CHUNKER &&
2707            strcmp(errstr, "[cannot read header: got 0 bytes instead of 32768]") == 0))) {
2708         str = vstrallocf(_("FAILED %s"), errstr);
2709         addtoX_summary(&first_failed, &last_failed, hostname, qdiskname, level,
2710                        str);
2711         amfree(str);
2712     }
2713
2714     if(curprog == P_DUMPER) {
2715         addline(&errdet,"");
2716         str = vstrallocf("/-- %s FAILED %s",
2717                         prefix(hostname, qdiskname, level), 
2718                         errstr);
2719         addline(&errdet, str);
2720         amfree(str);
2721         while(contline_next()) {
2722             get_logline(logfile);
2723             addline(&errdet, curstr);
2724         }
2725         addline(&errdet,"\\--------");
2726         exit_status |= STATUS_FAILED;
2727     }
2728     return;
2729 }
2730
2731
2732 static void
2733 generate_missing(void)
2734 {
2735     disk_t *dp;
2736     char *qdisk;
2737
2738     for(dp = diskq.head; dp != NULL; dp = dp->next) {
2739         if(dp->todo && data(dp) == NULL) {
2740             qdisk = quote_string(dp->name);
2741             addtoX_summary(&first_failed, &last_failed, dp->host->hostname,
2742                            qdisk, -987, _("RESULTS MISSING"));
2743             exit_status |= STATUS_MISSING;
2744             amfree(qdisk);
2745         }
2746     }
2747 }
2748
2749 static void
2750 generate_bad_estimate(void)
2751 {
2752     disk_t *dp;
2753     repdata_t *repdata;
2754     char s[1000];
2755     double outsize;
2756
2757     for(dp = diskq.head; dp != NULL; dp = dp->next) {
2758         if(dp->todo) {
2759             for(repdata = data(dp); repdata != NULL; repdata = repdata->next) {
2760                 if(repdata->est_csize >= 0.1) {
2761                     if(repdata->taper.result == L_SUCCESS ||
2762                        repdata->taper.result == L_CHUNKSUCCESS)
2763                         outsize  = repdata->taper.outsize;
2764                     else if(repdata->chunker.result == L_SUCCESS ||
2765                             repdata->chunker.result == L_PARTIAL ||
2766                             repdata->chunker.result == L_CHUNKSUCCESS)
2767                         outsize  = repdata->chunker.outsize;
2768                     else if(repdata->taper.result == L_PARTIAL)
2769                         outsize  = repdata->taper.outsize;
2770                     else
2771                         outsize  = repdata->dumper.outsize;
2772
2773                     if( (repdata->est_csize * 0.9 > outsize) && ( repdata->est_csize - outsize > 1.0e5 ) ) {
2774                         g_snprintf(s, 1000,
2775                                 _("  big estimate: %s %s %d"),
2776                                  repdata->disk->host->hostname,
2777                                  repdata->disk->name,
2778                                  repdata->level);
2779                         s[999] = '\0';
2780                         addline(&notes, s);
2781                         g_snprintf(s, 1000,
2782                                  _("                est: %.0lf%s    out %.0lf%s"),
2783                                  du(repdata->est_csize), displayunit,
2784                                  du(outsize), displayunit);
2785                         s[999] = '\0';
2786                         addline(&notes, s);
2787                     }
2788                     else if( (repdata->est_csize * 1.1 < outsize) && (outsize - repdata->est_csize > 1.0e5 ) ) {
2789                         g_snprintf(s, 1000,
2790                                 _("  small estimate: %s %s %d"),
2791                                  repdata->disk->host->hostname,
2792                                  repdata->disk->name,
2793                                  repdata->level);
2794                         s[999] = '\0';
2795                         addline(&notes, s);
2796                         g_snprintf(s, 1000,
2797                                  _("                  est: %.0lf%s    out %.0lf%s"),
2798                                  du(repdata->est_csize), displayunit,
2799                                  du(outsize), displayunit);
2800                         s[999] = '\0';
2801                         addline(&notes, s);
2802                     }
2803                 }
2804             }
2805         }
2806     }
2807 }
2808
2809 static char *
2810 prefix (
2811     char *      host,
2812     char *      disk,
2813     int         level)
2814 {
2815     static char *str = NULL;
2816
2817     if (level == -987) {
2818         str = newvstrallocf(str, " %s %s",
2819                         host ? host : _("(host?)"),
2820                         disk ? disk : _("(disk?)"));
2821     } else {
2822         str = newvstrallocf(str, " %s %s lev %d",
2823                         host ? host : _("(host?)"),
2824                         disk ? disk : _("(disk?)"),
2825                         level);
2826     }
2827     return str;
2828 }
2829
2830
2831 static char *
2832 prefixstrange (
2833     char *      host,
2834     char *      disk,
2835     int         level,
2836     size_t      len_host,
2837     size_t      len_disk)
2838 {
2839     char *h, *d;
2840     size_t l;
2841     static char *str = NULL;
2842
2843     h=alloc(len_host+1);
2844     if(host) {
2845         strncpy(h, host, len_host);
2846     } else {
2847         strncpy(h, _("(host?)"), len_host);
2848     }
2849     h[len_host] = '\0';
2850     for(l = strlen(h); l < len_host; l++) {
2851         h[l] = ' ';
2852     }
2853     d=alloc(len_disk+1);
2854     if(disk) {
2855         strncpy(d, disk, len_disk);
2856     } else {
2857         strncpy(d, _("(disk?)"), len_disk);
2858     }
2859     d[len_disk] = '\0';
2860     for(l = strlen(d); l < len_disk; l++) {
2861         d[l] = ' ';
2862     }
2863     if (level == -987) {
2864         str = newvstrallocf(str, " %s %s", h, d);
2865     } else {
2866         str = newvstrallocf(str, " %s %s lev %d", h, d, level);
2867     }
2868     amfree(h);
2869     amfree(d);
2870     return str;
2871 }
2872
2873
2874 static void
2875 addtoX_summary (
2876     X_summary_t **first,
2877     X_summary_t **last,
2878     char         *host,
2879     char         *disk,
2880     int           level,
2881     char         *str)
2882 {
2883     X_summary_t *X_summary;
2884
2885     X_summary = alloc(SIZEOF(X_summary_t));
2886     X_summary->hostname = stralloc(host);
2887     X_summary->diskname = stralloc(disk);
2888     X_summary->level    = level;
2889     X_summary->str      = stralloc(str);
2890     X_summary->next = NULL;
2891     if (*first == NULL) {
2892         *first = X_summary;
2893     }
2894     else {
2895         (*last)->next = X_summary;
2896     }
2897     *last = X_summary;
2898 }
2899
2900 static void
2901 copy_template_file(
2902     char *      lbl_templ)
2903 {
2904   char buf[BUFSIZ];
2905   int fd;
2906   ssize_t numread;
2907
2908   lbl_templ = config_dir_relative(lbl_templ);
2909   if ((fd = open(lbl_templ, 0)) < 0) {
2910     curlog = L_ERROR;
2911     curprog = P_REPORTER;
2912     curstr = vstrallocf(_("could not open PostScript template file %s: %s"),
2913                        lbl_templ, strerror(errno));
2914     handle_error();
2915     amfree(curstr);
2916     amfree(lbl_templ);
2917     afclose(postscript);
2918     return;
2919   }
2920   while ((numread = read(fd, buf, SIZEOF(buf))) > 0) {
2921     if (fwrite(buf, (size_t)numread, 1, postscript) != 1) {
2922       curlog = L_ERROR;
2923       curprog = P_REPORTER;
2924       curstr = vstrallocf(_("error copying PostScript template file %s: %s"),
2925                          lbl_templ, strerror(errno));
2926       handle_error();
2927       amfree(curstr);
2928       amfree(lbl_templ);
2929       afclose(postscript);
2930       return;
2931     }
2932   }
2933   if (numread < 0) {
2934     curlog = L_ERROR;
2935     curprog = P_REPORTER;
2936     curstr = vstrallocf(_("error reading PostScript template file %s: %s"),
2937                        lbl_templ, strerror(errno));
2938     handle_error();
2939     amfree(curstr);
2940     amfree(lbl_templ);
2941     afclose(postscript);
2942     return;
2943   }
2944   close(fd);
2945   amfree(lbl_templ);
2946 }
2947
2948 static repdata_t *
2949 find_repdata(
2950     /*@keep@*/  disk_t *dp,
2951                 char *  datestamp,
2952                 int     level)
2953 {
2954     repdata_t *repdata, *prev;
2955
2956     if(!datestamp)
2957         datestamp = run_datestamp;
2958     prev = NULL;
2959     for(repdata = data(dp); repdata != NULL && (repdata->level != level || strcmp(repdata->datestamp,datestamp)!=0); repdata = repdata->next) {
2960         prev = repdata;
2961     }
2962     if(!repdata) {
2963         repdata = (repdata_t *)alloc(SIZEOF(repdata_t));
2964         memset(repdata, '\0', SIZEOF(repdata_t));
2965         repdata->disk = dp;
2966         repdata->datestamp = stralloc(datestamp ? datestamp : "");
2967         repdata->level = level;
2968         repdata->dumper.result = L_BOGUS;
2969         repdata->taper.result = L_BOGUS;
2970         repdata->next = NULL;
2971         if(prev)
2972             prev->next = repdata;
2973         else
2974             dp->up = (void *)repdata;
2975     }
2976     return repdata;
2977 }
2978
2979
2980 static void
2981 do_postscript_output(void)
2982 {
2983     tapetype_t *tp = lookup_tapetype(getconf_str(CNF_TAPETYPE));
2984     disk_t *dp;
2985     repdata_t *repdata;
2986     double outsize, origsize;
2987     off_t tapesize;
2988     off_t marksize;
2989
2990     if (!tp)
2991         return;
2992
2993     tapesize = tapetype_get_length(tp);
2994     marksize = tapetype_get_filemark(tp);
2995
2996     for(current_tape = stats_by_tape; current_tape != NULL;
2997             current_tape = current_tape->next) {
2998
2999         if (current_tape->label == NULL) {
3000             break;
3001         }
3002
3003         copy_template_file(tapetype_get_lbl_templ(tp));
3004
3005         if (postscript == NULL)
3006             return;
3007
3008         /* generate a few elements */
3009         g_fprintf(postscript,"(%s) DrawDate\n\n",
3010                     nicedate(run_datestamp ? run_datestamp : "0"));
3011         g_fprintf(postscript,_("(Amanda Version %s) DrawVers\n"),version());
3012         g_fprintf(postscript,"(%s) DrawTitle\n", current_tape->label);
3013
3014         /* Stats */
3015         g_fprintf(postscript, "(Total Size:        %6.1lf MB) DrawStat\n",
3016               mb(current_tape->coutsize));
3017         g_fprintf(postscript, _("(Tape Used (%%)       "));
3018         divzero(postscript, pct(current_tape->coutsize + 
3019                                 marksize * (current_tape->tapedisks + current_tape->tapechunks)),
3020                                 (double)tapesize);
3021         g_fprintf(postscript," %%) DrawStat\n");
3022         g_fprintf(postscript, _("(Compression Ratio:  "));
3023         divzero(postscript, pct(current_tape->coutsize),current_tape->corigsize);
3024         g_fprintf(postscript," %%) DrawStat\n");
3025         g_fprintf(postscript,_("(Filesystems Taped: %4d) DrawStat\n"),
3026                   current_tape->tapedisks);
3027
3028         /* Summary */
3029
3030         g_fprintf(postscript,
3031               "(-) (%s) (-) (  0) (      32) (      32) DrawHost\n",
3032               current_tape->label);
3033
3034         for(dp = sortq.head; dp != NULL; dp = dp->next) {
3035             if (dp->todo == 0) {
3036                  continue;
3037             }
3038             for(repdata = data(dp); repdata != NULL; repdata = repdata->next) {
3039
3040                 if(repdata->taper.tapelabel != current_tape->label) {
3041                     continue;
3042                 }
3043
3044                 if(repdata->dumper.result == L_SUCCESS ||
3045                    repdata->dumper.result == L_PARTIAL)
3046                     origsize = repdata->dumper.origsize;
3047                 else
3048                     origsize = repdata->taper.origsize;
3049
3050                 if(repdata->taper.result == L_SUCCESS ||
3051                    repdata->taper.result == L_PARTIAL)
3052                     outsize  = repdata->taper.outsize;
3053                 else
3054                     outsize  = repdata->dumper.outsize;
3055
3056                 if (repdata->taper.result == L_SUCCESS ||
3057                     repdata->taper.result == L_PARTIAL) {
3058                     if(isnormal(origsize)) {
3059                         g_fprintf(postscript,"(%s) (%s) (%d) (%3.0d) (%8.0lf) (%8.0lf) DrawHost\n",
3060                             dp->host->hostname, dp->name, repdata->level,
3061                             repdata->taper.filenum, origsize, 
3062                             outsize);
3063                     }
3064                     else {
3065                         g_fprintf(postscript,"(%s) (%s) (%d) (%3.0d) (%8s) (%8.0lf) DrawHost\n",
3066                             dp->host->hostname, dp->name, repdata->level,
3067                             repdata->taper.filenum, "", 
3068                             outsize);
3069                     }
3070                 }
3071             }
3072         }
3073         
3074         g_fprintf(postscript,"\nshowpage\n");
3075     }
3076 }