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