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