Imported Upstream version 2.5.2p1
[debian/amanda] / restore-src / restore.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1998 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  * Authors: the Amanda Development Team.  Its members are listed in a
24  * file named AUTHORS, in the root directory of this distribution.
25  */
26 /*
27  * $Id: restore.c,v 1.52 2006/08/23 11:41:54 martinea Exp $
28  *
29  * retrieves files from an amanda tape
30  */
31
32 #include "amanda.h"
33 #include "tapeio.h"
34 #include "util.h"
35 #include "restore.h"
36 #include "find.h"
37 #include "changer.h"
38 #include "logfile.h"
39 #include "fileheader.h"
40 #include "arglist.h"
41 #include <signal.h>
42
43 #define LOAD_STOP    -1
44 #define LOAD_CHANGER -2
45
46 int file_number;
47
48 /* stuff we're stuck having global */
49 static size_t blocksize = (size_t)SSIZE_MAX;
50 static char *cur_tapedev = NULL;
51 static char *searchlabel = NULL;
52 static int backwards;
53 static int exitassemble = 0;
54 static int tapefd;
55
56 char *rst_conf_logdir = NULL;
57 char *rst_conf_logfile = NULL;
58 char *curslot = NULL;
59
60 typedef struct open_output_s {
61     struct open_output_s *next;
62     dumpfile_t *file;
63     int lastpartnum;
64     pid_t comp_enc_pid;
65     int outfd;
66 } open_output_t;
67
68 typedef struct dumplist_s {
69     struct dumplist_s *next;
70     dumpfile_t *file;
71 } dumplist_t;
72
73 typedef struct seentapes_s {
74     struct seentapes_s *next;
75     char *slotstr;
76     char *label;
77     dumplist_t *files;
78 } seentapes_t;
79
80 static open_output_t *open_outputs = NULL;
81 static dumplist_t *alldumps_list = NULL;
82
83 /* local functions */
84
85 static ssize_t get_block(int tapefd, char *buffer, int isafile);
86 static void append_file_to_fd(char *filename, int fd);
87 static int headers_equal(dumpfile_t *file1, dumpfile_t *file2, int ignore_partnums);
88 static int already_have_dump(dumpfile_t *file);
89 static void handle_sigint(int sig);
90 static int scan_init(void *ud, int rc, int ns, int bk, int s);
91 int loadlabel_slot(void *ud, int rc, char *slotstr, char *device);
92 void drain_file(int tapefd, rst_flags_t *flags);
93 char *label_of_current_slot(char *cur_tapedev, FILE *prompt_out,
94                             int *tapefd, dumpfile_t *file, rst_flags_t *flags,
95                             am_feature_t *their_features,
96                             ssize_t *read_result, tapelist_t *desired_tape);
97
98 int load_next_tape(char **cur_tapedev, FILE *prompt_out, int backwards,
99                    rst_flags_t *flags, am_feature_t *their_features,
100                    tapelist_t *desired_tape);
101 int load_manual_tape(char **cur_tapedev, FILE *prompt_out, FILE *prompt_in,
102                      rst_flags_t *flags, am_feature_t *their_features,
103                      tapelist_t *desired_tape);
104 void search_a_tape(char *cur_tapedev, FILE *prompt_out, rst_flags_t *flags,
105                    am_feature_t *their_features, tapelist_t *desired_tape,
106                    int isafile, match_list_t *match_list,
107                    seentapes_t *tape_seen, dumpfile_t *file,
108                    dumpfile_t *prev_rst_file, dumpfile_t *tapestart,
109                    int slot_num, ssize_t *read_result);
110
111 /*
112  * We might want to flush any open dumps and unmerged splits before exiting
113  * on SIGINT, so do so.
114  */
115 static void
116 handle_sigint(
117     int         sig)
118 {
119     (void)sig;  /* Quiet unused parameter warning */
120
121     flush_open_outputs(exitassemble, NULL);
122     if(rst_conf_logfile) unlink(rst_conf_logfile);
123     exit(0);
124 }
125
126 int
127 lock_logfile(void)
128 {
129     rst_conf_logdir = getconf_str(CNF_LOGDIR);
130     if (*rst_conf_logdir == '/') {
131         rst_conf_logdir = stralloc(rst_conf_logdir);
132     } else {
133         rst_conf_logdir = stralloc2(config_dir, rst_conf_logdir);
134     }
135     rst_conf_logfile = vstralloc(rst_conf_logdir, "/log", NULL);
136     if (access(rst_conf_logfile, F_OK) == 0) {
137         dbprintf(("%s exists: amdump or amflush is already running, "
138                   "or you must run amcleanup\n", rst_conf_logfile));
139         return 0;
140     }
141     log_add(L_INFO, get_pname());
142     return 1;
143 }
144
145 /*
146  * Return 1 if the two fileheaders match in name, disk, type, split chunk part
147  * number, and datestamp, and 0 if not.  The part number can be optionally
148  * ignored.
149  */
150 int
151 headers_equal(
152     dumpfile_t *file1,
153     dumpfile_t *file2,
154     int         ignore_partnums)
155 {
156     if(!file1 || !file2) return(0);
157     
158     if(file1->dumplevel == file2->dumplevel &&
159            file1->type == file2->type &&
160            !strcmp(file1->datestamp, file2->datestamp) &&
161            !strcmp(file1->name, file2->name) &&
162            !strcmp(file1->disk, file2->disk) &&
163            (ignore_partnums || file1->partnum == file2->partnum)){
164         return(1);
165     }
166     return(0);
167 }
168
169
170 /*
171  * See whether we're already pulled an exact copy of the given file (chunk
172  * number and all).  Returns 0 if not, 1 if so.
173  */
174 int
175 already_have_dump(
176     dumpfile_t *file)
177 {
178     dumplist_t *fileentry = NULL;
179
180     if(!file) return(0);
181     for(fileentry=alldumps_list;fileentry;fileentry=fileentry->next){
182         if(headers_equal(file, fileentry->file, 0)) return(1);
183     }
184     return(0);
185 }
186
187 /*
188  * Open the named file and append its contents to the (hopefully open) file
189  * descriptor supplies.
190  */
191 static void
192 append_file_to_fd(
193     char *      filename,
194     int         fd)
195 {
196     ssize_t bytes_read;
197     ssize_t s;
198     off_t wc = (off_t)0;
199     char *buffer;
200
201     if(blocksize == SIZE_MAX)
202         blocksize = DISK_BLOCK_BYTES;
203     buffer = alloc(blocksize);
204
205     if((tapefd = open(filename, O_RDONLY)) == -1) {
206         error("can't open %s: %s", filename, strerror(errno));
207         /*NOTREACHED*/
208     }
209
210     for (;;) {
211         bytes_read = get_block(tapefd, buffer, 1); /* same as isafile = 1 */
212         if(bytes_read < 0) {
213             error("read error: %s", strerror(errno));
214             /*NOTREACHED*/
215         }
216
217         if (bytes_read == 0)
218                 break;
219
220         s = fullwrite(fd, buffer, (size_t)bytes_read);
221         if (s < bytes_read) {
222             fprintf(stderr,"Error (%s) offset " OFF_T_FMT "+" OFF_T_FMT ", wrote " OFF_T_FMT "\n",
223                     strerror(errno), (OFF_T_FMT_TYPE)wc,
224                     (OFF_T_FMT_TYPE)bytes_read, (OFF_T_FMT_TYPE)s);
225             if (s < 0) {
226                 if((errno == EPIPE) || (errno == ECONNRESET)) {
227                     error("%s: pipe reader has quit in middle of file.",
228                         get_pname());
229                     /*NOTREACHED*/
230                 }
231                 error("restore: write error = %s", strerror(errno));
232                 /*NOTREACHED*/
233             }
234             error("Short write: wrote " SSIZE_T_FMT " bytes expected " SSIZE_T_FMT ".", s, bytes_read);
235             /*NOTREACHCED*/
236         }
237         wc += (off_t)bytes_read;
238     }
239
240     amfree(buffer);
241     aclose(tapefd);
242 }
243
244 /*
245  * Tape changer support routines, stolen brazenly from amtape
246  */
247 static int 
248 scan_init(
249      void *     ud,
250      int        rc,
251      int        ns,
252      int        bk,
253      int        s)
254 {
255     (void)ud;   /* Quiet unused parameter warning */
256     (void)ns;   /* Quiet unused parameter warning */
257     (void)s;    /* Quiet unused parameter warning */
258
259     if(rc) {
260         error("could not get changer info: %s", changer_resultstr);
261         /*NOTREACHED*/
262     }
263     backwards = bk;
264
265     return 0;
266 }
267
268 int
269 loadlabel_slot(
270      void *     ud,
271      int        rc,
272      char *     slotstr,
273      char *     device)
274 {
275     char *errstr;
276     char *datestamp = NULL;
277     char *label = NULL;
278
279     (void)ud;   /* Quiet unused parameter warning */
280
281     if(rc > 1) {
282         error("could not load slot %s: %s", slotstr, changer_resultstr);
283         /*NOTREACHED*/
284     } else if(rc == 1) {
285         fprintf(stderr, "%s: slot %s: %s\n",
286                 get_pname(), slotstr, changer_resultstr);
287     } else if((errstr = tape_rdlabel(device, &datestamp, &label)) != NULL) {
288         fprintf(stderr, "%s: slot %s: %s\n", get_pname(), slotstr, errstr);
289     } else {
290         if(strlen(datestamp)>8)
291             fprintf(stderr, "%s: slot %s: date %-14s label %s",
292                     get_pname(), slotstr, datestamp, label);
293         else
294             fprintf(stderr, "%s: slot %s: date %-8s label %s",
295                     get_pname(), slotstr, datestamp, label);
296         if(strcmp(label, FAKE_LABEL) != 0
297            && strcmp(label, searchlabel) != 0)
298             fprintf(stderr, " (wrong tape)\n");
299         else {
300             fprintf(stderr, " (exact label match)\n");
301             if((errstr = tape_rewind(device)) != NULL) {
302                 fprintf(stderr,
303                         "%s: could not rewind %s: %s",
304                         get_pname(), device, errstr);
305                 amfree(errstr);
306             }
307             amfree(cur_tapedev);
308             curslot = newstralloc(curslot, slotstr);
309             amfree(datestamp);
310             amfree(label);
311             if(device)
312                 cur_tapedev = stralloc(device);
313             return 1;
314         }
315     }
316     amfree(datestamp);
317     amfree(label);
318
319     amfree(cur_tapedev);
320     curslot = newstralloc(curslot, slotstr);
321     if(!device) return(1);
322     cur_tapedev = stralloc(device);
323
324     return 0;
325 }
326
327
328 /* non-local functions follow */
329
330
331
332 /*
333  * Check whether we've read all of the preceding parts of a given split dump,
334  * generally used to see if we're done and can close the thing.
335  */
336 int
337 have_all_parts (
338     dumpfile_t *file,
339     int         upto)
340 {
341     int c;
342     int *foundparts = NULL;
343     dumplist_t *fileentry = NULL;
344
345     if(!file || file->partnum < 1) return(0);
346
347     if(upto < 1) upto = file->totalparts;
348
349     foundparts = alloc(SIZEOF(*foundparts) * upto); 
350     for(c = 0 ; c< upto; c++) foundparts[c] = 0;
351     
352     for(fileentry=alldumps_list;fileentry; fileentry=fileentry->next){
353         dumpfile_t *cur_file = fileentry->file;
354         if(headers_equal(file, cur_file, 1)){
355             if(cur_file->partnum > upto){
356                 amfree(foundparts);
357                 return(0);
358             }
359
360             foundparts[cur_file->partnum - 1] = 1;
361         }
362     }
363
364     for(c = 0 ; c< upto; c++){
365         if(!foundparts[c]){
366             amfree(foundparts);
367             return(0);
368         }
369     }
370     
371     amfree(foundparts);
372     return(1);
373 }
374
375 /*
376  * Free up the open filehandles and memory we were using to track in-progress
377  * dumpfiles (generally for split ones we're putting back together).  If
378  * applicable, also find the ones that are continuations of one another and
379  * string them together.  If given an optional file header argument, flush
380  * only that dump and do not flush/free any others.
381  */
382 void
383 flush_open_outputs(
384     int         reassemble,
385     dumpfile_t *only_file)
386 {
387     open_output_t *cur_out = NULL, *prev = NULL;
388     find_result_t *sorted_files = NULL;
389     amwait_t compress_status;
390
391     if(!only_file){
392         fprintf(stderr, "\n");
393     }
394
395     /*
396      * Deal with any split dumps we've been working on, appending pieces
397      * that haven't yet been appended and closing filehandles we've been
398      * holding onto.
399      */
400     if(reassemble){
401         find_result_t *cur_find_res = NULL;
402         int outfd = -1, lastpartnum = -1;
403         dumpfile_t *main_file = NULL;
404         cur_out = open_outputs;
405         
406         /* stick the dumpfile_t's into a list find_result_t's so that we can
407            abuse existing sort functionality */
408         for(cur_out=open_outputs; cur_out; cur_out=cur_out->next){
409             find_result_t *cur_find_res = NULL;
410             dumpfile_t *cur_file = cur_out->file;
411             /* if we requested a particular file, do only that one */
412             if(only_file && !headers_equal(cur_file, only_file, 1)){
413                 continue;
414             }
415             cur_find_res = alloc(SIZEOF(find_result_t));
416             memset(cur_find_res, '\0', SIZEOF(find_result_t));
417             cur_find_res->timestamp = stralloc(cur_file->datestamp);
418             cur_find_res->hostname = stralloc(cur_file->name);
419             cur_find_res->diskname = stralloc(cur_file->disk);
420             cur_find_res->level = cur_file->dumplevel;
421             if(cur_file->partnum < 1) cur_find_res->partnum = stralloc("--");
422             else{
423                 char part_str[NUM_STR_SIZE];
424                 snprintf(part_str, SIZEOF(part_str), "%d", cur_file->partnum);
425                 cur_find_res->partnum = stralloc(part_str);
426             }
427             cur_find_res->user_ptr = (void*)cur_out;
428
429             cur_find_res->next = sorted_files;
430             sorted_files = cur_find_res;
431         }
432         sort_find_result("hkdlp", &sorted_files);
433
434         /* now we have an in-order list of the files we need to concatenate */
435         cur_find_res = sorted_files;
436         for(cur_find_res=sorted_files;
437                 cur_find_res;
438                 cur_find_res=cur_find_res->next){
439             dumpfile_t *cur_file = NULL;
440             cur_out = (open_output_t*)cur_find_res->user_ptr;
441             cur_file = cur_out->file;
442
443             /* if we requested a particular file, do only that one */
444             if(only_file && !headers_equal(cur_file, only_file, 1)){
445                 continue;
446             }
447
448             if(cur_file->type == F_SPLIT_DUMPFILE) {
449                 /* is it a continuation of one we've been writing? */
450                 if(main_file && cur_file->partnum > lastpartnum &&
451                         headers_equal(cur_file, main_file, 1)){
452                     char *cur_filename;
453                     char *main_filename;
454
455                     /* effectively changing filehandles */
456                     aclose(cur_out->outfd);
457                     cur_out->outfd = outfd;
458
459                     cur_filename  = make_filename(cur_file);
460                     main_filename = make_filename(main_file);
461                     fprintf(stderr, "Merging %s with %s\n",
462                             cur_filename, main_filename);
463                     append_file_to_fd(cur_filename, outfd);
464                     if(unlink(cur_filename) < 0){
465                         fprintf(stderr, "Failed to unlink %s: %s\n",
466                                      cur_filename, strerror(errno));
467                     }
468                     amfree(cur_filename);
469                     amfree(main_filename);
470                 }
471                 /* or a new file? */
472                 else {
473                     if(outfd >= 0) aclose(outfd);
474                     amfree(main_file);
475                     main_file = alloc(SIZEOF(dumpfile_t));
476                     memcpy(main_file, cur_file, SIZEOF(dumpfile_t));
477                     outfd = cur_out->outfd;
478                     if(outfd < 0) {
479                         char *cur_filename = make_filename(cur_file);
480                         open(cur_filename, O_RDWR|O_APPEND);
481                         if (outfd < 0) {
482                           error("Couldn't open %s for appending: %s",
483                                 cur_filename, strerror(errno));
484                           /*NOTREACHED*/
485                         }
486                         amfree(cur_filename);
487                     }
488                 }
489                 lastpartnum = cur_file->partnum;
490             }
491             else {
492                 aclose(cur_out->outfd);
493             }
494         }
495         if(outfd >= 0) {
496             aclose(outfd);
497         }
498
499         amfree(main_file);
500         free_find_result(&sorted_files);
501     }
502
503     /*
504      * Now that the split dump closure is done, free up resources we don't
505      * need anymore.
506      */
507     for(cur_out=open_outputs; cur_out; cur_out=cur_out->next){
508         dumpfile_t *cur_file = NULL;
509         amfree(prev);
510         cur_file = cur_out->file;
511         /* if we requested a particular file, do only that one */
512         if(only_file && !headers_equal(cur_file, only_file, 1)){
513             continue;
514         }
515         if(!reassemble) {
516             aclose(cur_out->outfd);
517         }
518
519         if(cur_out->comp_enc_pid > 0){
520             waitpid(cur_out->comp_enc_pid, &compress_status, 0);
521         }
522         amfree(cur_out->file);
523         prev = cur_out;
524     }
525
526     open_outputs = NULL;
527 }
528
529 /*
530  * Turn a fileheader into a string suited for use on the filesystem.
531  */
532 char *
533 make_filename(
534     dumpfile_t *file)
535 {
536     char number[NUM_STR_SIZE];
537     char part[NUM_STR_SIZE];
538     char totalparts[NUM_STR_SIZE];
539     char *sfn = NULL;
540     char *fn = NULL;
541     char *pad = NULL;
542     size_t padlen = 0;
543
544     snprintf(number, SIZEOF(number), "%d", file->dumplevel);
545     snprintf(part, SIZEOF(part), "%d", file->partnum);
546
547     if(file->totalparts < 0) {
548         snprintf(totalparts, SIZEOF(totalparts), "UNKNOWN");
549     }
550     else {
551         snprintf(totalparts, SIZEOF(totalparts), "%d", file->totalparts);
552     }
553     padlen = strlen(totalparts) + 1 - strlen(part);
554     pad = alloc(padlen);
555     memset(pad, '0', padlen);
556     pad[padlen - 1] = '\0';
557
558     snprintf(part, SIZEOF(part), "%s%d", pad, file->partnum);
559
560     sfn = sanitise_filename(file->disk);
561     fn = vstralloc(file->name,
562                    ".",
563                    sfn, 
564                    ".",
565                    file->datestamp,
566                    ".",
567                    number,
568                    NULL);
569     if (file->partnum > 0) {
570         vstrextend(&fn, ".", part, NULL);
571     }
572     amfree(sfn);
573     amfree(pad);
574     return fn;
575 }
576
577
578 /*
579  * XXX Making this thing a lib functiong broke a lot of assumptions everywhere,
580  * but I think I've found them all.  Maybe.  Damn globals all over the place.
581  */
582
583 static ssize_t
584 get_block(
585     int         tapefd,
586     char *      buffer,
587     int         isafile)
588 {
589     if(isafile)
590         return (fullread(tapefd, buffer, blocksize));
591
592     return(tapefd_read(tapefd, buffer, blocksize));
593 }
594
595 /*
596  * Returns 1 if the current dump file matches the hostname and diskname
597  * regular expressions given on the command line, 0 otherwise.  As a 
598  * special case, empty regexs are considered equivalent to ".*": they 
599  * match everything.
600  */
601
602 int
603 disk_match(
604     dumpfile_t *file,
605     char *      datestamp,
606     char *      hostname,
607     char *      diskname,
608     char *      level)
609 {
610     char level_str[NUM_STR_SIZE];
611     snprintf(level_str, SIZEOF(level_str), "%d", file->dumplevel);
612
613     if(file->type != F_DUMPFILE && file->type != F_SPLIT_DUMPFILE) return 0;
614
615     if((*hostname == '\0' || match_host(hostname, file->name)) &&
616        (*diskname == '\0' || match_disk(diskname, file->disk)) &&
617        (*datestamp == '\0' || match_datestamp(datestamp, file->datestamp)) &&
618        (*level == '\0' || match_level(level, level_str)))
619         return 1;
620     else
621         return 0;
622 }
623
624
625 /*
626  * Reads the first block of a tape file.
627  */
628
629 ssize_t
630 read_file_header(
631     dumpfile_t *        file,
632     int                 tapefd,
633     int                 isafile,
634     rst_flags_t *       flags)
635 {
636     ssize_t bytes_read;
637     char *buffer;
638   
639     if(flags->blocksize > 0)
640         blocksize = (size_t)flags->blocksize;
641     else if(blocksize == (size_t)SSIZE_MAX)
642         blocksize = DISK_BLOCK_BYTES;
643     buffer = alloc(blocksize);
644
645     bytes_read = get_block(tapefd, buffer, isafile);
646     if(bytes_read < 0) {
647         fprintf(stderr, "%s: error reading file header: %s\n",
648                 get_pname(), strerror(errno));
649         file->type = F_UNKNOWN;
650     } else if((size_t)bytes_read < DISK_BLOCK_BYTES) {
651         if(bytes_read == 0) {
652             fprintf(stderr, "%s: missing file header block\n", get_pname());
653         } else {
654             fprintf(stderr, "%s: short file header block: " OFF_T_FMT " byte%s\n",
655                     get_pname(), (OFF_T_FMT_TYPE)bytes_read, (bytes_read == 1) ? "" : "s");
656         }
657         file->type = F_UNKNOWN;
658     } else {
659         parse_file_header(buffer, file, (size_t)bytes_read);
660     }
661     amfree(buffer);
662     return bytes_read;
663 }
664
665
666 void
667 drain_file(
668     int                 tapefd,
669     rst_flags_t *       flags)
670 {
671     ssize_t bytes_read;
672     char *buffer;
673
674     if(flags->blocksize)
675         blocksize = (size_t)flags->blocksize;
676     else if(blocksize == (size_t)SSIZE_MAX)
677         blocksize = DISK_BLOCK_BYTES;
678     buffer = alloc(blocksize);
679
680     do {
681        bytes_read = get_block(tapefd, buffer, 0);
682        if(bytes_read < 0) {
683            error("drain read error: %s", strerror(errno));
684            /*NOTREACHED*/
685        }
686     } while (bytes_read > 0);
687
688     amfree(buffer);
689 }
690
691 /*
692  * Restore the current file from tape.  Depending on the settings of
693  * the command line flags, the file might need to be compressed or
694  * uncompressed.  If so, a pipe through compress or uncompress is set
695  * up.  The final output usually goes to a file named host.disk.date.lev,
696  * but with the -p flag the output goes to stdout (and presumably is
697  * piped to restore).
698  */
699
700 ssize_t
701 restore(
702     dumpfile_t *        file,
703     char *              filename,
704     int                 tapefd,
705     int                 isafile,
706     rst_flags_t *       flags)
707 {
708     int dest = -1, out;
709     ssize_t s;
710     int file_is_compressed;
711     int is_continuation = 0;
712     int check_for_aborted = 0;
713     char *tmp_filename = NULL, *final_filename = NULL;
714     struct stat statinfo;
715     open_output_t *myout = NULL, *oldout = NULL;
716     dumplist_t *tempdump = NULL, *fileentry = NULL;
717     char *buffer;
718     int need_compress=0, need_uncompress=0, need_decrypt=0;
719     int stage=0;
720     ssize_t bytes_read;
721     struct pipeline {
722         int     pipe[2];
723     } pipes[3];
724
725     memset(pipes, -1, SIZEOF(pipes));
726     if(flags->blocksize)
727         blocksize = (size_t)flags->blocksize;
728     else if(blocksize == (size_t)SSIZE_MAX)
729         blocksize = DISK_BLOCK_BYTES;
730
731     if(already_have_dump(file)){
732         char *filename = make_filename(file);
733         fprintf(stderr, " *** Duplicate file %s, one is probably an aborted write\n", filename);
734         amfree(filename);
735         check_for_aborted = 1;
736     }
737
738     /* store a shorthand record of this dump */
739     tempdump = alloc(SIZEOF(dumplist_t));
740     tempdump->file = alloc(SIZEOF(dumpfile_t));
741     tempdump->next = NULL;
742     memcpy(tempdump->file, file, SIZEOF(dumpfile_t));
743
744     /*
745      * If we're appending chunked files to one another, and if this is a
746      * continuation of a file we just restored, and we've still got the
747      * output handle from that previous restore, we're golden.  Phew.
748      */
749     if(flags->inline_assemble && file->type == F_SPLIT_DUMPFILE){
750         myout = open_outputs;
751         while(myout != NULL){
752             if(myout->file->type == F_SPLIT_DUMPFILE &&
753                     headers_equal(file, myout->file, 1)){
754                 if(file->partnum == myout->lastpartnum + 1){
755                     is_continuation = 1;
756                     break;
757                 }
758             }
759             myout = myout->next;
760         }
761         if(myout != NULL) myout->lastpartnum = file->partnum;
762         else if(file->partnum != 1){
763             fprintf(stderr, "%s:      Chunk out of order, will save to disk and append to output.\n", get_pname());
764             flags->pipe_to_fd = -1;
765             flags->compress = 0;
766             flags->leave_comp = 1;
767         }
768         if(myout == NULL){
769             myout = alloc(SIZEOF(open_output_t));
770             memset(myout, 0, SIZEOF(open_output_t));
771         }
772     }
773     else{
774       myout = alloc(SIZEOF(open_output_t));
775       memset(myout, 0, SIZEOF(open_output_t));
776     }
777
778
779     if(is_continuation && flags->pipe_to_fd == -1){
780         char *filename;
781         filename = make_filename(myout->file);
782         fprintf(stderr, "%s:      appending to %s\n", get_pname(),
783                 filename);
784         amfree(filename);
785     }
786
787     /* adjust compression flag */
788     file_is_compressed = file->compressed;
789     if(!flags->compress && file_is_compressed && !known_compress_type(file)) {
790         fprintf(stderr, 
791                 "%s: unknown compression suffix %s, can't uncompress\n",
792                 get_pname(), file->comp_suffix);
793         flags->compress = 1;
794     }
795
796     /* set up final destination file */
797
798     if(is_continuation && myout != NULL) {
799       out = myout->outfd;
800     } else {
801       if(flags->pipe_to_fd != -1) {
802           dest = flags->pipe_to_fd;     /* standard output */
803       } else {
804           char *filename_ext = NULL;
805   
806           if(flags->compress) {
807               filename_ext = file_is_compressed ? file->comp_suffix
808                                               : COMPRESS_SUFFIX;
809           } else if(flags->raw) {
810               filename_ext = ".RAW";
811           } else {
812               filename_ext = "";
813           }
814           filename_ext = stralloc2(filename, filename_ext);
815           tmp_filename = stralloc(filename_ext); 
816           if(flags->restore_dir != NULL) {
817               char *tmpstr = vstralloc(flags->restore_dir, "/",
818                                        tmp_filename, NULL);
819               amfree(tmp_filename);
820               tmp_filename = tmpstr;
821           } 
822           final_filename = tmp_filename; 
823           tmp_filename = vstralloc(final_filename, ".tmp", NULL);
824           if((dest = open(tmp_filename, (O_CREAT | O_RDWR | O_TRUNC),
825                           CREAT_MODE)) < 0) {
826               error("could not create output file %s: %s",
827                     tmp_filename, strerror(errno));
828               /*NOTREACHED*/
829           }
830           amfree(filename_ext);
831       }
832   
833       out = dest;
834     }
835
836     /*
837      * If -r or -h, write the header before compress or uncompress pipe.
838      * Only write DISK_BLOCK_BYTES, regardless of how much was read.
839      * This makes the output look like a holding disk image, and also
840      * makes it easier to remove the header (e.g. in amrecover) since
841      * it has a fixed size.
842      */
843     if(flags->raw || (flags->headers && !is_continuation)) {
844         ssize_t w;
845         dumpfile_t tmp_hdr;
846
847         if(flags->compress && !file_is_compressed) {
848             file->compressed = 1;
849             snprintf(file->uncompress_cmd, SIZEOF(file->uncompress_cmd),
850                         " %s %s |", UNCOMPRESS_PATH,
851 #ifdef UNCOMPRESS_OPT
852                         UNCOMPRESS_OPT
853 #else
854                         ""
855 #endif
856                         );
857             strncpy(file->comp_suffix,
858                     COMPRESS_SUFFIX,
859                     SIZEOF(file->comp_suffix)-1);
860             file->comp_suffix[SIZEOF(file->comp_suffix)-1] = '\0';
861         }
862
863         memcpy(&tmp_hdr, file, SIZEOF(dumpfile_t));
864
865         /* remove CONT_FILENAME from header */
866         memset(file->cont_filename,'\0',SIZEOF(file->cont_filename));
867         file->blocksize = DISK_BLOCK_BYTES;
868
869         /*
870          * Dumb down split file headers as well, so that older versions of
871          * things like amrecover won't gag on them.
872          */
873         if(file->type == F_SPLIT_DUMPFILE && flags->mask_splits){
874             file->type = F_DUMPFILE;
875         }
876
877         buffer = alloc(DISK_BLOCK_BYTES);
878         build_header(buffer, file, DISK_BLOCK_BYTES);
879
880         if((w = fullwrite(out, buffer, DISK_BLOCK_BYTES)) != DISK_BLOCK_BYTES) {
881             if(w < 0) {
882                 error("write error: %s", strerror(errno));
883                 /*NOTREACHED*/
884             } else {
885                 error("write error: " SSIZE_T_FMT " instead of %d", w, DISK_BLOCK_BYTES);
886                 /*NOTREACHED*/
887             }
888         }
889         amfree(buffer);
890
891         memcpy(file, &tmp_hdr, SIZEOF(dumpfile_t));
892     }
893  
894     /* find out if compression or uncompression is needed here */
895     if(flags->compress && !file_is_compressed && !is_continuation
896           && !flags->leave_comp
897           && (flags->inline_assemble || file->type != F_SPLIT_DUMPFILE))
898        need_compress=1;
899        
900     if(!flags->raw && !flags->compress && file_is_compressed
901           && !is_continuation && !flags->leave_comp && (flags->inline_assemble
902           || file->type != F_SPLIT_DUMPFILE))
903        need_uncompress=1;   
904
905     if(!flags->raw && file->encrypted && !is_continuation
906           && (flags->inline_assemble || file->type != F_SPLIT_DUMPFILE))
907        need_decrypt=1;
908    
909     /* Setup pipes for decryption / compression / uncompression  */
910     stage = 0;
911     if (need_decrypt) {
912       if (pipe(&pipes[stage].pipe[0]) < 0) {
913         error("error [pipe[%d]: %s]", stage, strerror(errno));
914         /*NOTREACHED*/
915       }
916       stage++;
917     }
918
919     if (need_compress || need_uncompress) {
920       if (pipe(&pipes[stage].pipe[0]) < 0) {
921         error("error [pipe[%d]: %s]", stage, strerror(errno));
922         /*NOTREACHED*/
923       }
924       stage++;
925     }
926     pipes[stage].pipe[0] = -1; 
927     pipes[stage].pipe[1] = out; 
928
929     stage = 0;
930
931     /* decrypt first if it's encrypted and no -r */
932     if(need_decrypt) {
933       switch(myout->comp_enc_pid = fork()) {
934       case -1:
935         error("could not fork for decrypt: %s", strerror(errno));
936         /*NOTREACHED*/
937
938       default:
939         aclose(pipes[stage].pipe[0]);
940         aclose(pipes[stage+1].pipe[1]);
941         stage++;
942         break;
943
944       case 0:
945         if(dup2(pipes[stage].pipe[0], 0) == -1) {
946             error("error decrypt stdin [dup2 %d %d: %s]", stage,
947                 pipes[stage].pipe[0], strerror(errno));
948                 /*NOTREACHED*/
949         }
950
951         if(dup2(pipes[stage+1].pipe[1], 1) == -1) {
952             error("error decrypt stdout [dup2 %d %d: %s]", stage + 1,
953                 pipes[stage+1].pipe[1], strerror(errno));
954                 /*NOTREACHED*/
955         }
956
957         safe_fd(-1, 0);
958         if (*file->srv_encrypt) {
959           (void) execlp(file->srv_encrypt, file->srv_encrypt,
960                         file->srv_decrypt_opt, (char *)NULL);
961           error("could not exec %s: %s", file->srv_encrypt, strerror(errno));
962           /*NOTREACHED*/
963         }  else if (*file->clnt_encrypt) {
964           (void) execlp(file->clnt_encrypt, file->clnt_encrypt,
965                         file->clnt_decrypt_opt, (char *)NULL);
966           error("could not exec %s: %s", file->clnt_encrypt, strerror(errno));
967           /*NOTREACHED*/
968         }
969       }
970     }
971
972     if (need_compress) {
973         /*
974          * Insert a compress pipe
975          */
976         switch(myout->comp_enc_pid = fork()) {
977         case -1:
978             error("could not fork for %s: %s", COMPRESS_PATH, strerror(errno));
979             /*NOTREACHED*/
980
981         default:
982             aclose(pipes[stage].pipe[0]);
983             aclose(pipes[stage+1].pipe[1]);
984             stage++;
985             break;
986
987         case 0:
988             if(dup2(pipes[stage].pipe[0], 0) == -1) {
989                 error("error compress stdin [dup2 %d %d: %s]", stage,
990                   pipes[stage].pipe[0], strerror(errno));
991                 /*NOTREACHED*/
992             }
993
994             if(dup2(pipes[stage+1].pipe[1], 1) == -1) {
995                 error("error compress stdout [dup2 %d %d: %s]", stage + 1,
996                   pipes[stage+1].pipe[1], strerror(errno));
997                   /*NOTREACHED*/
998             }
999             if (*flags->comp_type == '\0') {
1000                 flags->comp_type = NULL;
1001             }
1002
1003             safe_fd(-1, 0);
1004             (void) execlp(COMPRESS_PATH, COMPRESS_PATH, flags->comp_type, (char *)0);
1005             error("could not exec %s: %s", COMPRESS_PATH, strerror(errno));
1006             /*NOTREACHED*/
1007         }
1008     } else if(need_uncompress) {
1009         /*
1010          * If not -r, -c, -l, and file is compressed, and split reassembly 
1011          * options are sane, insert uncompress pipe
1012          */
1013
1014         /* 
1015          * XXX for now we know that for the two compression types we
1016          * understand, .Z and optionally .gz, UNCOMPRESS_PATH will take
1017          * care of both.  Later, we may need to reference a table of
1018          * possible uncompress programs.
1019          */ 
1020         switch(myout->comp_enc_pid = fork()) {
1021         case -1: 
1022             error("could not fork for %s: %s",
1023                   UNCOMPRESS_PATH, strerror(errno));
1024             /*NOTREACHED*/
1025
1026         default:
1027             aclose(pipes[stage].pipe[0]);
1028             aclose(pipes[stage+1].pipe[1]);
1029             stage++;
1030             break;
1031
1032         case 0:
1033             if(dup2(pipes[stage].pipe[0], 0) == -1) {
1034                 error("error uncompress stdin [dup2 %d %d: %s]", stage,
1035                   pipes[stage].pipe[0], strerror(errno));
1036                 /*NOTREACHED*/
1037             }
1038
1039             if(dup2(pipes[stage+1].pipe[1], 1) == -1) {
1040                 error("error uncompress stdout [dup2 %d %d: %s]", stage + 1,
1041                   pipes[stage+1].pipe[1], strerror(errno));
1042                 /*NOTREACHED*/
1043             }
1044
1045             safe_fd(-1, 0);
1046             if (*file->srvcompprog) {
1047               (void) execlp(file->srvcompprog, file->srvcompprog, "-d",
1048                             (char *)NULL);
1049               error("could not exec %s: %s", file->srvcompprog,
1050                     strerror(errno));
1051               /*NOTREACHED*/
1052             } else if (*file->clntcompprog) {
1053               (void) execlp(file->clntcompprog, file->clntcompprog, "-d",
1054                             (char *)NULL);
1055               error("could not exec %s: %s", file->clntcompprog,
1056                     strerror(errno));
1057               /*NOTREACHED*/
1058             } else {
1059               (void) execlp(UNCOMPRESS_PATH, UNCOMPRESS_PATH,
1060 #ifdef UNCOMPRESS_OPT
1061                           UNCOMPRESS_OPT,
1062 #endif
1063                           (char *)NULL);
1064               error("could not exec %s: %s", UNCOMPRESS_PATH, strerror(errno));
1065               /*NOTREACHED*/
1066             }
1067         }
1068     }
1069
1070     /* copy the rest of the file from tape to the output */
1071     if(flags->blocksize > 0)
1072         blocksize = (size_t)flags->blocksize;
1073     else if(blocksize == SIZE_MAX)
1074         blocksize = DISK_BLOCK_BYTES;
1075     buffer = alloc(blocksize);
1076
1077     do {
1078         bytes_read = get_block(tapefd, buffer, isafile);
1079         if(bytes_read < 0) {
1080             error("restore read error: %s", strerror(errno));
1081             /*NOTREACHED*/
1082         }
1083
1084         if(bytes_read > 0) {
1085             if((s = fullwrite(pipes[0].pipe[1], buffer, (size_t)bytes_read)) < 0) {
1086                 if ((errno == EPIPE) || (errno == ECONNRESET)) {
1087                     /*
1088                      * reading program has ended early
1089                      * e.g: bzip2 closes pipe when it
1090                      * trailing garbage after EOF
1091                      */
1092                     break;
1093                 }
1094                 error("restore: write error: %s", strerror(errno));
1095                 /* NOTREACHED */
1096             } else if (s < bytes_read) {
1097                 error("restore: wrote " SSIZE_T_FMT " of " SSIZE_T_FMT " bytes: %s",
1098                     s, bytes_read, strerror(errno));
1099                 /* NOTREACHED */
1100             }
1101         }
1102         else if(isafile) {
1103             /*
1104              * See if we need to switch to the next file in a holding restore
1105              */
1106             if(file->cont_filename[0] == '\0') {
1107                 break;                          /* no more files */
1108             }
1109             aclose(tapefd);
1110             if((tapefd = open(file->cont_filename, O_RDONLY)) == -1) {
1111                 char *cont_filename = strrchr(file->cont_filename,'/');
1112                 if(cont_filename) {
1113                     cont_filename++;
1114                     if((tapefd = open(cont_filename,O_RDONLY)) == -1) {
1115                         error("can't open %s: %s", file->cont_filename,
1116                               strerror(errno));
1117                         /*NOTREACHED*/
1118                     }
1119                     else {
1120                         fprintf(stderr, "cannot open %s: %s\n",
1121                                 file->cont_filename, strerror(errno));
1122                         fprintf(stderr, "using %s\n",
1123                                 cont_filename);
1124                     }
1125                 }
1126                 else {
1127                     error("can't open %s: %s", file->cont_filename,
1128                           strerror(errno));
1129                     /*NOTREACHED*/
1130                 }
1131             }
1132             bytes_read = read_file_header(file, tapefd, isafile, flags);
1133             if(file->type != F_DUMPFILE && file->type != F_CONT_DUMPFILE
1134                     && file->type != F_SPLIT_DUMPFILE) {
1135                 fprintf(stderr, "unexpected header type: ");
1136                 print_header(stderr, file);
1137                 exit(2);
1138             }
1139         }
1140     } while (bytes_read > 0);
1141
1142     amfree(buffer);
1143
1144     if(!flags->inline_assemble) {
1145         if(out != dest)
1146             aclose(out);
1147     }
1148     if(!is_continuation){
1149         if(tmp_filename && stat(tmp_filename, &statinfo) < 0){
1150             error("Can't stat the file I just created (%s)!", tmp_filename);
1151             /*NOTREACHED*/
1152         } else {
1153             statinfo.st_size = (off_t)0;
1154         }
1155         if (check_for_aborted && final_filename) {
1156             char *old_dump = final_filename;
1157             struct stat oldstat;
1158             if(stat(old_dump, &oldstat) >= 0){
1159                 if(oldstat.st_size <= statinfo.st_size){
1160                     dumplist_t *prev_fileentry = NULL;
1161                     open_output_t *prev_out = NULL;
1162                     fprintf(stderr, "Newer restore is larger, using that\n");
1163                     /* nuke the old dump's entry in alldump_list */
1164                     for(fileentry=alldumps_list;
1165                             fileentry->next;
1166                             fileentry=fileentry->next){
1167                         if(headers_equal(file, fileentry->file, 0)){
1168                             if(prev_fileentry){
1169                                 prev_fileentry->next = fileentry->next;
1170                             }
1171                             else {
1172                                 alldumps_list = fileentry->next;
1173                             }
1174                             amfree(fileentry);
1175                             break;
1176                         }
1177                         prev_fileentry = fileentry;
1178                     }
1179                     myout = open_outputs;
1180                     while(myout != NULL){
1181                         if(headers_equal(file, myout->file, 0)){
1182                             if(myout->outfd >= 0)
1183                                 aclose(myout->outfd);
1184                             if(prev_out){
1185                                 prev_out->next = myout->next;
1186                             }
1187                             else open_outputs = myout->next;
1188                             amfree(myout);
1189                             break;
1190                         }
1191                         prev_out = myout;
1192                         myout = myout->next;
1193                     }
1194                 }
1195                 else{
1196                     fprintf(stderr, "Older restore is larger, using that\n");
1197                     if (tmp_filename)
1198                         unlink(tmp_filename);
1199                     amfree(tempdump->file);
1200                     amfree(tempdump);
1201                     amfree(tmp_filename);
1202                     amfree(final_filename);
1203                     return (bytes_read);
1204                 }
1205             }
1206         }
1207         if(tmp_filename && final_filename &&
1208                 rename(tmp_filename, final_filename) < 0) {
1209             error("Can't rename %s to %s: %s",
1210                    tmp_filename, final_filename, strerror(errno));
1211             /*NOTREACHED*/
1212         }
1213     }
1214     amfree(tmp_filename);
1215     amfree(final_filename);
1216
1217
1218     /*
1219      * actually insert tracking data for this file into our various
1220      * structures (we waited in case we needed to give up)
1221      */
1222     if(!is_continuation){
1223         oldout = alloc(SIZEOF(open_output_t));
1224         oldout->file = alloc(SIZEOF(dumpfile_t));
1225         memcpy(oldout->file, file, SIZEOF(dumpfile_t));
1226         if(flags->inline_assemble) oldout->outfd = pipes[0].pipe[1];
1227         else oldout->outfd = -1;
1228         oldout->comp_enc_pid = -1;
1229         oldout->lastpartnum = file->partnum;
1230         oldout->next = open_outputs;
1231         open_outputs = oldout;
1232     }
1233     if(alldumps_list){
1234         fileentry = alldumps_list;
1235         while (fileentry->next != NULL)
1236             fileentry=fileentry->next;
1237         fileentry->next = tempdump;
1238     }
1239     else {
1240         alldumps_list = tempdump;
1241     }
1242
1243     return (bytes_read);
1244 }
1245
1246 /* return NULL if the label is not the expected one                     */
1247 /* return the label if it is the expected one, and set *tapefd to a     */
1248 /* file descriptor to the tapedev                                       */
1249 char *
1250 label_of_current_slot(
1251     char         *cur_tapedev,
1252     FILE         *prompt_out,
1253     int          *tapefd,
1254     dumpfile_t   *file,
1255     rst_flags_t  *flags,
1256     am_feature_t *their_features,
1257     ssize_t      *read_result,
1258     tapelist_t   *desired_tape)
1259 {
1260     struct stat stat_tape;
1261     char *label = NULL;
1262     int wrongtape = 0;
1263     char *err;
1264
1265     if (!cur_tapedev) {
1266         send_message(prompt_out, flags, their_features,
1267                      "no tapedev specified");
1268     } else if (tape_stat(cur_tapedev, &stat_tape) !=0 ) {
1269         send_message(prompt_out, flags, their_features, 
1270                      "could not stat '%s': %s",
1271                      cur_tapedev, strerror(errno));
1272         wrongtape = 1;
1273     } else if((err = tape_rewind(cur_tapedev)) != NULL) {
1274         send_message(prompt_out, flags, their_features, 
1275                          "Could not rewind device '%s': %s",
1276                          cur_tapedev, err);
1277         wrongtape = 1;
1278         /* err should not be freed */
1279     } else if((*tapefd = tape_open(cur_tapedev, 0)) < 0){
1280         send_message(prompt_out, flags, their_features,
1281                          "could not open tape device %s: %s",
1282                          cur_tapedev, strerror(errno));
1283         wrongtape = 1;
1284     }
1285
1286     if (!wrongtape) {
1287         *read_result = read_file_header(file, *tapefd, 0, flags);
1288         if (file->type != F_TAPESTART) {
1289             send_message(prompt_out, flags, their_features,
1290                              "Not an amanda tape");
1291             tapefd_close(*tapefd);
1292         } else {
1293             if (flags->check_labels && desired_tape &&
1294                          strcmp(file->name, desired_tape->label) != 0) {
1295                 send_message(prompt_out, flags, their_features,
1296                                  "Label mismatch, got %s and expected %s",
1297                                  file->name, desired_tape->label);
1298                 tapefd_close(*tapefd);
1299             }
1300             else {
1301                 label = stralloc(file->name);
1302             }
1303         }
1304     }
1305     return label;
1306 }
1307
1308 /* return >0            the number of slot move            */
1309 /* return LOAD_STOP     if the search must be stopped      */
1310 /* return LOAD_CHANGER  if the changer search the library  */
1311 int
1312 load_next_tape(
1313     char         **cur_tapedev,
1314     FILE          *prompt_out,
1315     int            backwards,
1316     rst_flags_t   *flags,
1317     am_feature_t  *their_features,
1318     tapelist_t    *desired_tape)
1319 {
1320     int ret = -1;
1321
1322     if (desired_tape) {
1323         send_message(prompt_out, flags, their_features,
1324                      "Looking for tape %s...",
1325                      desired_tape->label);
1326         if (backwards) {
1327             searchlabel = desired_tape->label; 
1328             changer_find(NULL, scan_init, loadlabel_slot,
1329                          desired_tape->label);
1330             ret = LOAD_CHANGER;
1331         } else {
1332             amfree(curslot);
1333             changer_loadslot("next", &curslot,
1334                              cur_tapedev);
1335             ret = 1;
1336         }
1337     } else {
1338         assert(!flags->amidxtaped);
1339         amfree(curslot);
1340         changer_loadslot("next", &curslot, cur_tapedev);
1341         ret = 1;
1342     }
1343     return ret;
1344 }
1345
1346
1347 /* return  0     a new tape is loaded       */
1348 /* return -1     no new tape                */
1349 int
1350 load_manual_tape(
1351     char         **cur_tapedev,
1352     FILE          *prompt_out,
1353     FILE          *prompt_in,
1354     rst_flags_t   *flags,
1355     am_feature_t  *their_features,
1356     tapelist_t    *desired_tape)
1357 {
1358     int ret = 0;
1359     char *input = NULL;
1360
1361     if (flags->amidxtaped) {
1362         if (their_features &&
1363             am_has_feature(their_features,
1364                            fe_amrecover_FEEDME)) {
1365             fprintf(prompt_out, "FEEDME %s\r\n",
1366                     desired_tape->label);
1367             fflush(prompt_out);
1368             input = agets(prompt_in);/* Strips \n but not \r */
1369             if(!input) {
1370                 error("Connection lost with amrecover");
1371                 /*NOTREACHED*/
1372             } else if (strcmp("OK\r", input) == 0) {
1373             } else if (strncmp("TAPE ", input, 5) == 0) {
1374                 amfree(*cur_tapedev);
1375                 *cur_tapedev = alloc(1025);
1376                 if (sscanf(input, "TAPE %1024s\r", *cur_tapedev) != 1) {
1377                     error("Got bad response from amrecover: %s", input);
1378                     /*NOTREACHED*/
1379                 }
1380             } else {
1381                 send_message(prompt_out, flags, their_features,
1382                              "Got bad response from amrecover: %s", input);
1383                 error("Got bad response from amrecover: %s", input);
1384                 /*NOTREACHED*/
1385             }
1386         } else {
1387             send_message(prompt_out, flags, their_features,
1388                          "Client doesn't support fe_amrecover_FEEDME");
1389             error("Client doesn't support fe_amrecover_FEEDME");
1390             /*NOTREACHED*/
1391         }
1392     }
1393     else {
1394         if (desired_tape) {
1395             fprintf(prompt_out,
1396                     "Insert tape labeled %s in device %s \n"
1397                     "and press enter, ^D to finish reading tapes\n",
1398                     desired_tape->label, *cur_tapedev);
1399         } else {
1400             fprintf(prompt_out,"Insert a tape to search and press "
1401                     "enter, ^D to finish reading tapes\n");
1402         }
1403         fflush(prompt_out);
1404         if((input = agets(prompt_in)) == NULL)
1405             ret = -1;
1406     }
1407
1408     amfree(input);
1409     return ret;
1410 }
1411
1412
1413 void 
1414 search_a_tape(
1415     char         *cur_tapedev,
1416     FILE         *prompt_out,
1417     rst_flags_t  *flags,
1418     am_feature_t *their_features,
1419     tapelist_t   *desired_tape,
1420     int           isafile,
1421     match_list_t *match_list,
1422     seentapes_t  *tape_seen,
1423     dumpfile_t   *file,
1424     dumpfile_t   *prev_rst_file,
1425     dumpfile_t   *tapestart,
1426     int           slot_num,
1427     ssize_t      *read_result)
1428 {
1429     off_t       filenum;
1430     dumplist_t *fileentry = NULL;
1431     int         tapefile_idx = -1;
1432     int         i;
1433     char       *logline = NULL;
1434     FILE       *logstream = NULL;
1435     off_t       fsf_by;
1436
1437     filenum = (off_t)0;
1438     if(desired_tape && desired_tape->numfiles > 0)
1439         tapefile_idx = 0;
1440
1441     if (desired_tape) {
1442         dbprintf(("search_a_tape: desired_tape=%p label=%s\n",
1443                   desired_tape, desired_tape->label));
1444         dbprintf(("tape:   numfiles = %d\n", desired_tape->numfiles));
1445         for (i=0; i < desired_tape->numfiles; i++) {
1446             dbprintf(("tape:   files[%d] = " OFF_T_FMT "\n",
1447                       i, (OFF_T_FMT_TYPE)desired_tape->files[i]));
1448         }
1449     } else {
1450         dbprintf(("search_a_tape: no desired_tape\n"));
1451     }
1452     dbprintf(("current tapefile_idx = %d\n", tapefile_idx));
1453         
1454     /* if we know where we're going, fastforward there */
1455     if(flags->fsf && !isafile){
1456         /* If we have a tapelist entry, filenums will be store there */
1457         if(tapefile_idx >= 0) {
1458             fsf_by = desired_tape->files[tapefile_idx]; 
1459         } else {
1460             /*
1461              * older semantics assume we're restoring one file, with the fsf
1462              * flag being the filenum on tape for said file
1463              */
1464             fsf_by = (flags->fsf == 0) ? (off_t)0 : (off_t)1;
1465         }
1466         if(fsf_by > (off_t)0){
1467             if(tapefd_rewind(tapefd) < 0) {
1468                 send_message(prompt_out, flags, their_features,
1469                              "Could not rewind device %s: %s",
1470                              cur_tapedev, strerror(errno));
1471                 error("Could not rewind device %s: %s",
1472                       cur_tapedev, strerror(errno));
1473                 /*NOTREACHED*/
1474             }
1475
1476             if(tapefd_fsf(tapefd, fsf_by) < 0) {
1477                 send_message(prompt_out, flags, their_features,
1478                              "Could not fsf device %s by " OFF_T_FMT ": %s",
1479                              cur_tapedev, (OFF_T_FMT_TYPE)fsf_by,
1480                              strerror(errno));
1481                 error("Could not fsf device %s by " OFF_T_FMT ": %s",
1482                       cur_tapedev, (OFF_T_FMT_TYPE)fsf_by,
1483                       strerror(errno));
1484                 /*NOTREACHED*/
1485             }
1486             else {
1487                 filenum = fsf_by;
1488             }
1489             *read_result = read_file_header(file, tapefd, isafile, flags);
1490         }
1491     }
1492
1493     while((file->type == F_TAPESTART || file->type == F_DUMPFILE ||
1494            file->type == F_SPLIT_DUMPFILE) &&
1495           (tapefile_idx < 0 || tapefile_idx < desired_tape->numfiles)) {
1496         int found_match = 0;
1497         match_list_t *me;
1498         dumplist_t *tempdump = NULL;
1499
1500         /* store record of this dump for inventorying purposes */
1501         tempdump = alloc(SIZEOF(dumplist_t));
1502         tempdump->file = alloc(SIZEOF(dumpfile_t));
1503         tempdump->next = NULL;
1504         memcpy(tempdump->file, &file, SIZEOF(dumpfile_t));
1505         if(tape_seen->files){
1506             fileentry = tape_seen->files;
1507             while (fileentry->next != NULL)
1508                    fileentry = fileentry->next;
1509             fileentry->next = tempdump;
1510         }
1511         else {
1512             tape_seen->files = tempdump;
1513         }
1514
1515         /* see if we need to restore the thing */
1516         if(isafile)
1517             found_match = 1;
1518         else if(tapefile_idx >= 0){ /* do it by explicit file #s */
1519             if(filenum == desired_tape->files[tapefile_idx]){
1520                 found_match = 1;
1521                 tapefile_idx++;
1522             }
1523         }
1524         else{ /* search and match headers */
1525             for(me = match_list; me; me = me->next) {
1526                 if(disk_match(file, me->datestamp, me->hostname,
1527                               me->diskname, me->level) != 0){
1528                     found_match = 1;
1529                     break;
1530                 }
1531             }
1532         }
1533
1534         if(found_match){
1535             char *filename = make_filename(file);
1536
1537             fprintf(stderr, "%s: " OFF_T_FMT ": restoring ",
1538                     get_pname(), (OFF_T_FMT_TYPE)filenum);
1539             print_header(stderr, file);
1540             *read_result = restore(file, filename, tapefd, isafile, flags);
1541             filenum++;
1542             amfree(filename);
1543         }
1544
1545         /* advance to the next file, fast-forwarding where reasonable */
1546         if (!isafile) {
1547             if (*read_result == 0) {
1548                 tapefd_close(tapefd);
1549                 if((tapefd = tape_open(cur_tapedev, 0)) < 0) {
1550                     send_message(prompt_out, flags, their_features,
1551                                  "could not open %s: %s",
1552                                  cur_tapedev, strerror(errno));
1553                     error("could not open %s: %s",
1554                           cur_tapedev, strerror(errno));
1555                     /*NOTREACHED*/
1556                 }
1557             /* if the file is not what we're looking for fsf to next one */
1558             }
1559             else if (!found_match) {
1560                 if (tapefd_fsf(tapefd, (off_t)1) < 0) {
1561                     send_message(prompt_out, flags, their_features,
1562                                  "Could not fsf device %s: %s",
1563                                  cur_tapedev, strerror(errno));
1564                     error("Could not fsf device %s: %s",
1565                           cur_tapedev, strerror(errno));
1566                     /*NOTREACHED*/
1567                 }
1568                 filenum ++;
1569             }
1570             else if (flags->fsf && (tapefile_idx >= 0) && 
1571                      (tapefile_idx < desired_tape->numfiles)) {
1572                 fsf_by = desired_tape->files[tapefile_idx] - filenum;
1573                 if (fsf_by > (off_t)0) {
1574                     if(tapefd_fsf(tapefd, fsf_by) < 0) {
1575                         send_message(prompt_out, flags, their_features,
1576                                      "Could not fsf device %s by "
1577                                      OFF_T_FMT ": %s",
1578                                      cur_tapedev, (OFF_T_FMT_TYPE)fsf_by,
1579                                      strerror(errno));
1580                         error("Could not fsf device %s by " OFF_T_FMT ": %s",
1581                               cur_tapedev, (OFF_T_FMT_TYPE)fsf_by,
1582                               strerror(errno));
1583                         /*NOTREACHED*/
1584                     }
1585                     filenum = desired_tape->files[tapefile_idx];
1586                 }
1587             }
1588         } /* !isafile */
1589
1590         memcpy(prev_rst_file, file, SIZEOF(dumpfile_t));
1591               
1592         if(isafile)
1593             break;
1594         *read_result = read_file_header(file, tapefd, isafile, flags);
1595
1596         /* only restore a single dump, if piping to stdout */
1597         if (!headers_equal(prev_rst_file, file, 1) &&
1598             (flags->pipe_to_fd == fileno(stdout)) && found_match) {
1599             break;
1600         }
1601     } /* while we keep seeing headers */
1602
1603     if (!isafile) {
1604         if (file->type == F_EMPTY) {
1605             aclose(tapefd);
1606             if((tapefd = tape_open(cur_tapedev, 0)) < 0) {
1607                 send_message(prompt_out, flags, their_features,
1608                              "could not open %s: %s",
1609                              cur_tapedev, strerror(errno));
1610                 error("could not open %s: %s",
1611                       cur_tapedev, strerror(errno));
1612                 /*NOTREACHED*/
1613             }
1614         } else {
1615             if (tapefd_fsf(tapefd, (off_t)1) < 0) {
1616                 send_message(prompt_out, flags, their_features,
1617                              "could not fsf %s: %s",
1618                              cur_tapedev, strerror(errno));;
1619                 error("could not fsf %s: %s",
1620                       cur_tapedev, strerror(errno));
1621                 /*NOTREACHED*/
1622             }
1623         }
1624     }
1625     tapefd_close(tapefd);
1626
1627     /* spit out our accumulated list of dumps, if we're inventorying */
1628     if (logstream) {
1629         logline = log_genstring(L_START, "taper",
1630                                     "datestamp %s label %s tape %d",
1631                                     tapestart->datestamp, tapestart->name,
1632                                     slot_num);
1633         fprintf(logstream, "%s", logline);
1634         for(fileentry=tape_seen->files; fileentry; fileentry=fileentry->next){
1635             logline = NULL;
1636             switch (fileentry->file->type) {
1637                 case F_DUMPFILE:
1638                     logline = log_genstring(L_SUCCESS, "taper",
1639                                        "%s %s %s %d [faked log entry]",
1640                                        fileentry->file->name,
1641                                        fileentry->file->disk,
1642                                        fileentry->file->datestamp,
1643                                        fileentry->file->dumplevel);
1644                     break;
1645                 case F_SPLIT_DUMPFILE:
1646                     logline = log_genstring(L_CHUNK, "taper", 
1647                                        "%s %s %s %d %d [faked log entry]",
1648                                        fileentry->file->name,
1649                                        fileentry->file->disk,
1650                                        fileentry->file->datestamp,
1651                                        fileentry->file->partnum,
1652                                        fileentry->file->dumplevel);
1653                     break;
1654                 default:
1655                     break;
1656             }
1657             if(logline){
1658                 fprintf(logstream, "%s", logline);
1659                 amfree(logline);
1660                 fflush(logstream);
1661             }
1662         }
1663     }
1664 }
1665
1666 /* 
1667  * Take a pattern of dumps and restore it blind, a la amrestore.  In addition,
1668  * be smart enough to change tapes and continue with minimal operator
1669  * intervention, and write out a record of what was found on tapes in the
1670  * the regular logging format.  Can take a tapelist with a specific set of
1671  * tapes to search (rather than "everything I can find"), which in turn can
1672  * optionally list specific files to restore.
1673  */
1674 void
1675 search_tapes(
1676     FILE *              prompt_out,
1677     FILE               *prompt_in,
1678     int                 use_changer,
1679     tapelist_t *        tapelist,
1680     match_list_t *      match_list,
1681     rst_flags_t *       flags,
1682     am_feature_t *      their_features)
1683 {
1684     int have_changer = 1;
1685     int slot_num = -1;
1686     int slots = -1;
1687     FILE *logstream = NULL;
1688     tapelist_t *desired_tape = NULL;
1689     struct sigaction act, oact;
1690     ssize_t read_result;
1691     int slot;
1692     char *label = NULL;
1693     seentapes_t *seentapes = NULL;
1694     int ret;
1695
1696     if(!prompt_out) prompt_out = stderr;
1697
1698     dbprintf(("search_tapes(prompt_out=%d, prompt_in=%d,  use_changer=%d, "
1699               "tapelist=%p, "
1700               "match_list=%p, flags=%p, features=%p)\n",
1701               fileno(prompt_out), fileno(prompt_in), use_changer, tapelist,
1702               match_list, flags, their_features));
1703
1704     if(flags->blocksize)
1705         blocksize = (size_t)flags->blocksize;
1706     else if(blocksize == (size_t)SSIZE_MAX)
1707         blocksize = DISK_BLOCK_BYTES;
1708
1709     /* Don't die when child closes pipe */
1710     signal(SIGPIPE, SIG_IGN);
1711
1712     /* catch SIGINT with something that'll flush unmerged splits */
1713     act.sa_handler = handle_sigint;
1714     sigemptyset(&act.sa_mask);
1715     act.sa_flags = 0;
1716     if(sigaction(SIGINT, &act, &oact) != 0){
1717         error("error setting SIGINT handler: %s", strerror(errno));
1718         /*NOTREACHED*/
1719     }
1720     if(flags->delay_assemble || flags->inline_assemble) exitassemble = 1;
1721     else exitassemble = 0;
1722
1723     /* if given a log file, print an inventory of stuff found */
1724     if(flags->inventory_log) {
1725         if(!strcmp(flags->inventory_log, "-")) logstream = stdout;
1726         else if((logstream = fopen(flags->inventory_log, "w+")) == NULL) {
1727             error("Couldn't open log file %s for writing: %s",
1728                   flags->inventory_log, strerror(errno));
1729             /*NOTREACHED*/
1730         }
1731     }
1732
1733     /* Suss what tape device we're using, whether there's a changer, etc. */
1734     if(!use_changer || (have_changer = changer_init()) == 0) {
1735         if (flags->alt_tapedev) {
1736             cur_tapedev = stralloc(flags->alt_tapedev);
1737         } else if(!cur_tapedev) {
1738             cur_tapedev = getconf_str(CNF_TAPEDEV);
1739             if (cur_tapedev == NULL) {
1740                 error("No tapedev specified");
1741             }
1742         }
1743         /* XXX oughta complain if no config is loaded */
1744         fprintf(stderr, "%s: Using tapedev %s\n", get_pname(), cur_tapedev);
1745         have_changer = 0;
1746     } else if (have_changer != 1) {
1747         error("changer initialization failed: %s", strerror(errno));
1748         /*NOTREACHED*/
1749     }
1750     else{ /* good, the changer works, see what it can do */
1751         amfree(curslot);
1752         changer_info(&slots, &curslot, &backwards);
1753     }
1754
1755     if(tapelist && !flags->amidxtaped){
1756       slots = num_entries(tapelist);
1757       /*
1758         Spit out a list of expected tapes, so people with manual changers know
1759         what to load
1760       */
1761       fprintf(prompt_out, "The following tapes are needed:");
1762       for(desired_tape = tapelist; desired_tape != NULL;
1763           desired_tape = desired_tape->next){
1764         fprintf(prompt_out, " %s", desired_tape->label);
1765       }
1766       fprintf(prompt_out, "\n");
1767       fflush(prompt_out);
1768       if(flags->wait_tape_prompt){
1769         char *input = NULL;
1770         fprintf(prompt_out,"Press enter when ready\n");
1771         fflush(prompt_out);
1772         input = agets(prompt_in);
1773         amfree(input);
1774         fprintf(prompt_out, "\n");
1775         fflush(prompt_out);
1776       }
1777     }
1778     desired_tape = tapelist;
1779
1780     if(use_changer && !cur_tapedev) { /* load current slot */
1781         amfree(curslot);
1782         changer_loadslot("current", &curslot, &cur_tapedev);
1783     }
1784
1785     /*
1786      * If we're not given a tapelist, iterate over everything our changer can
1787      * find.  If there's no changer, we'll prompt to be handfed tapes.
1788      *
1789      * If we *are* given a tapelist, restore from those tapes in the order in
1790      * which they're listed.  Unless the changer (if we have one) can't go
1791      * backwards, in which case check every tape we see and restore from it if
1792      * appropriate.
1793      *
1794      * (obnoxious, isn't this?)
1795      */
1796
1797     do { /* all desired tape */
1798         seentapes_t *tape_seen = NULL;
1799         dumpfile_t file, tapestart, prev_rst_file;
1800         int isafile = 0;
1801         read_result = 0;
1802
1803         slot_num = 0;
1804
1805         memset(&file, 0, SIZEOF(file));
1806
1807         if (desired_tape && desired_tape->isafile) {
1808             isafile = 1;
1809             if ((tapefd = open(desired_tape->label, 0)) == -1) {
1810                 send_message(prompt_out, flags, their_features, 
1811                              "could not open %s: %s",
1812                              desired_tape->label, strerror(errno));
1813                 continue;
1814             }
1815             fprintf(stderr, "Reading %s to fd %d\n",
1816                             desired_tape->label, tapefd);
1817
1818             read_result = read_file_header(&file, tapefd, 1, flags);
1819             label = stralloc(desired_tape->label);
1820         } else {
1821             /* check current_slot */
1822             label = label_of_current_slot(cur_tapedev, prompt_out,
1823                                           &tapefd, &file, flags,
1824                                           their_features, &read_result,
1825                                           desired_tape);
1826             while (label==NULL && slot_num < slots &&
1827                    use_changer) {
1828                 /*
1829                  * If we have an incorrect tape loaded, go try to find
1830                  * the right one
1831                  * (or just see what the next available one is).
1832                  */
1833                 slot = load_next_tape(&cur_tapedev, prompt_out,
1834                                       backwards, flags,
1835                                       their_features, desired_tape);
1836                 if(slot == LOAD_STOP) {
1837                     slot_num = slots;
1838                     amfree(label);
1839                 } else {
1840                     if (slot == LOAD_CHANGER)
1841                         slot_num = slots;
1842                     else /* slot > 0 */
1843                         slot_num += slot;
1844
1845                     /* check current_slot */
1846                     label = label_of_current_slot(cur_tapedev, prompt_out,
1847                                                   &tapefd, &file, flags,
1848                                                   their_features, &read_result,
1849                                                   desired_tape);
1850                 }
1851             }
1852
1853             if (label == NULL) {
1854                 ret = load_manual_tape(&cur_tapedev, prompt_out, prompt_in,
1855                                        flags,
1856                                        their_features, desired_tape);
1857                 if (ret == 0) {
1858                     label = label_of_current_slot(cur_tapedev, prompt_out,
1859                                                   &tapefd, &file, flags,
1860                                                   their_features, &read_result,
1861                                                   desired_tape);
1862                 }
1863             }
1864
1865             if (label)
1866                 memcpy(&tapestart, &file, SIZEOF(dumpfile_t));
1867         }
1868         
1869         if (!label)
1870             continue;
1871
1872         /*
1873          * Skip this tape if we did it already.  Note that this would let
1874          * duplicate labels through, so long as they were in the same slot.
1875          * I'm over it, are you?
1876          */
1877         if (!isafile) {
1878             for (tape_seen = seentapes; tape_seen;
1879                  tape_seen = tape_seen->next) {
1880                 if (!strcmp(tape_seen->label, label) &&
1881                     !strcmp(tape_seen->slotstr, curslot)){
1882                     send_message(prompt_out, flags, their_features,
1883                                  "Saw repeat tape %s in slot %s",
1884                                  label, curslot);
1885                     amfree(label);
1886                     break;
1887                 }
1888             }
1889         }
1890
1891         if(!label)
1892             continue;
1893
1894         if(!curslot)
1895             curslot = stralloc("<none>");
1896
1897         if(!isafile){
1898             fprintf(stderr, "Scanning %s (slot %s)\n", label, curslot);
1899             fflush(stderr);
1900         }
1901
1902         tape_seen = alloc(SIZEOF(seentapes_t));
1903         memset(tape_seen, '\0', SIZEOF(seentapes_t));
1904
1905         tape_seen->label = label;
1906         tape_seen->slotstr = stralloc(curslot);
1907         tape_seen->next = seentapes;
1908         tape_seen->files = NULL;
1909         seentapes = tape_seen;
1910
1911         /*
1912          * Start slogging through the tape itself.  If our tapelist (if we
1913          * have one) contains a list of files to restore, obey that instead
1914          * of checking for matching headers on all files.
1915          */
1916
1917         search_a_tape(cur_tapedev, prompt_out, flags, their_features,
1918                       desired_tape, isafile, match_list, tape_seen,
1919                       &file, &prev_rst_file, &tapestart, slot_num,
1920                       &read_result);
1921
1922         fprintf(stderr, "%s: Search of %s complete\n",
1923                         get_pname(), tape_seen->label);
1924         if (desired_tape) desired_tape = desired_tape->next;
1925
1926         /* only restore a single dump, if piping to stdout */
1927         if (!headers_equal(&prev_rst_file, &file, 1) &&
1928             flags->pipe_to_fd == fileno(stdout))
1929                 break;
1930
1931     } while (desired_tape);
1932
1933     while (seentapes != NULL) {
1934         seentapes_t *tape_seen = seentapes;
1935         seentapes = seentapes->next;
1936         while(tape_seen->files != NULL) {
1937             dumplist_t *temp_dump = tape_seen->files;
1938             tape_seen->files = temp_dump->next;
1939             amfree(temp_dump->file);
1940             amfree(temp_dump);
1941         }
1942         amfree(tape_seen->label);
1943         amfree(tape_seen->slotstr);
1944         amfree(tape_seen);
1945         
1946     }
1947
1948     if(logstream && logstream != stderr && logstream != stdout){
1949         fclose(logstream);
1950     }
1951     if(flags->delay_assemble || flags->inline_assemble){
1952         flush_open_outputs(1, NULL);
1953     }
1954     else flush_open_outputs(0, NULL);
1955 }
1956
1957 /*
1958  * Create a new, clean set of restore flags with some sane default values.
1959  */
1960 rst_flags_t *
1961 new_rst_flags(void)
1962 {
1963     rst_flags_t *flags = alloc(SIZEOF(rst_flags_t));
1964
1965     memset(flags, 0, SIZEOF(rst_flags_t));
1966
1967     flags->fsf = 1;
1968     flags->comp_type = COMPRESS_FAST_OPT;
1969     flags->inline_assemble = 1;
1970     flags->pipe_to_fd = -1;
1971     flags->check_labels = 1;
1972
1973     return(flags);
1974 }
1975
1976 /*
1977  * Make sure the set of restore options given is sane.  Print errors for
1978  * things that're odd, and return -1 for fatal errors.
1979  */
1980 int
1981 check_rst_flags(
1982     rst_flags_t *       flags)
1983 {
1984     int ret = 0;        
1985     
1986     if(!flags) return(-1);
1987
1988     if(flags->compress && flags->leave_comp){
1989         fprintf(stderr, "Cannot specify 'compress output' and 'leave compression alone' together\n");
1990         ret = -1;
1991     }
1992
1993     if(flags->restore_dir != NULL){
1994         struct stat statinfo;
1995
1996         if(flags->pipe_to_fd != -1){
1997             fprintf(stderr, "Specifying output directory and piping output are mutually exclusive\n");
1998             ret = -1;
1999         }
2000         if(stat(flags->restore_dir, &statinfo) < 0){
2001             fprintf(stderr, "Cannot stat restore target dir '%s': %s\n",
2002                       flags->restore_dir, strerror(errno));
2003             ret = -1;
2004         }
2005         if((statinfo.st_mode & S_IFMT) != S_IFDIR){
2006             fprintf(stderr, "'%s' is not a directory\n", flags->restore_dir);
2007             ret = -1;
2008         }
2009     }
2010
2011     if((flags->pipe_to_fd != -1 || flags->compress) &&
2012             (flags->delay_assemble || !flags->inline_assemble)){
2013         fprintf(stderr, "Split dumps *must* be automatically reassembled when piping output or compressing/uncompressing\n");
2014         ret = -1;
2015     }
2016
2017     if(flags->delay_assemble && flags->inline_assemble){
2018         fprintf(stderr, "Inline split assembling and delayed assembling are mutually exclusive\n");
2019         ret = -1;
2020     }
2021
2022     return(ret);
2023 }
2024
2025 /*
2026  * Clean up after a rst_flags_t
2027  */
2028 void
2029 free_rst_flags(
2030     rst_flags_t *       flags)
2031 {
2032     if(!flags) return;
2033
2034     amfree(flags->restore_dir);
2035     amfree(flags->alt_tapedev);
2036     amfree(flags->inventory_log);
2037
2038     amfree(flags);
2039 }
2040
2041
2042 /*
2043  * Clean up after a match_list_t
2044  */
2045 void
2046 free_match_list(
2047     match_list_t *      match_list)
2048 {
2049     match_list_t *me;
2050     match_list_t *prev = NULL;
2051   
2052     for(me = match_list; me; me = me->next){
2053         /* XXX freeing these is broken? can't work out why */
2054 /*      amfree(me->hostname);
2055         amfree(me->diskname);
2056         amfree(me->datestamp);
2057         amfree(me->level); */
2058         amfree(prev);
2059         prev = me;
2060     }
2061     amfree(prev);
2062 }
2063
2064
2065 printf_arglist_function3(
2066     void send_message,
2067     FILE *, prompt_out,
2068     rst_flags_t *, flags,
2069     am_feature_t *, their_features,
2070     char *, format)
2071 {
2072     va_list argp;
2073     char linebuf[STR_SIZE];
2074
2075     arglist_start(argp, format);
2076     vsnprintf(linebuf, SIZEOF(linebuf)-1, format, argp);
2077     arglist_end(argp);
2078
2079     fprintf(stderr,"%s\n", linebuf);
2080     if (flags->amidxtaped && their_features &&
2081         am_has_feature(their_features, fe_amrecover_message)) {
2082         fprintf(prompt_out, "MESSAGE %s\r\n", linebuf);
2083         fflush(prompt_out);
2084     }
2085 }
2086