Imported Upstream version 2.5.1
[debian/amanda] / oldrecover-src / extract_list.c
diff --git a/oldrecover-src/extract_list.c b/oldrecover-src/extract_list.c
new file mode 100644 (file)
index 0000000..5c5d92a
--- /dev/null
@@ -0,0 +1,2247 @@
+/*
+ * Amanda, The Advanced Maryland Automatic Network Disk Archiver
+ * Copyright (c) 1991-1998, 2000 University of Maryland at College Park
+ * All Rights Reserved.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of U.M. not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  U.M. makes no representations about the
+ * suitability of this software for any purpose.  It is provided "as is"
+ * without express or implied warranty.
+ *
+ * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
+ * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors: the Amanda Development Team.  Its members are listed in a
+ * file named AUTHORS, in the root directory of this distribution.
+ */
+/*
+ * $Id: extract_list.c,v 1.6 2006/08/24 01:57:15 paddy_s Exp $
+ *
+ * implements the "extract" command in amrecover
+ */
+
+#include "amanda.h"
+#include "version.h"
+#include "amrecover.h"
+#include "fileheader.h"
+#include "dgram.h"
+#include "stream.h"
+#include "tapelist.h"
+#ifdef SAMBA_CLIENT
+#include "findpass.h"
+#endif
+#include "util.h"
+
+typedef struct EXTRACT_LIST_ITEM {
+    char *path;
+
+    struct EXTRACT_LIST_ITEM *next;
+}
+EXTRACT_LIST_ITEM;
+
+typedef struct EXTRACT_LIST {
+    char *date;                        /* date tape created */
+    int  level;                        /* level of dump */
+    char *tape;                        /* tape label */
+    off_t fileno;              /* fileno on tape */
+    EXTRACT_LIST_ITEM *files;  /* files to get off tape */
+
+    struct EXTRACT_LIST *next;
+}
+EXTRACT_LIST;
+
+#define SKIP_TAPE 2
+#define RETRY_TAPE 3
+
+char *dump_device_name = NULL;
+
+extern char *localhost;
+
+/* global pid storage for interrupt handler */
+pid_t extract_restore_child_pid = -1;
+
+static EXTRACT_LIST *extract_list = NULL;
+static int tape_control_sock = -1;
+static int tape_data_sock = -1;
+
+#ifdef SAMBA_CLIENT
+unsigned short samba_extract_method = SAMBA_TAR;
+#endif /* SAMBA_CLIENT */
+
+#define READ_TIMEOUT   240*60
+
+EXTRACT_LIST *first_tape_list(void);
+EXTRACT_LIST *next_tape_list(EXTRACT_LIST *list);
+int is_extract_list_nonempty(void);
+int length_of_tape_list(EXTRACT_LIST *tape_list);
+void add_file(char *path, char *regex);
+void add_glob(char *glob);
+void add_regex(char *regex);
+void clear_extract_list(void);
+void clean_tape_list(EXTRACT_LIST *tape_list);
+void clean_extract_list(void);
+void delete_file(char *path, char *regex);
+void delete_glob(char *glob);
+void delete_regex(char *regex);
+void delete_tape_list(EXTRACT_LIST *tape_list);
+void display_extract_list(char *file);
+void extract_files(void);
+void read_file_header(char *buffer,
+                       dumpfile_t *file,
+                       size_t buflen,
+                       int tapedev);
+void writer_intermediary(int ctl_fd, int data_fd, EXTRACT_LIST *elist);
+void writer_intermediary(int ctl_fd, int data_fd, EXTRACT_LIST *elist);
+
+static int add_extract_item(DIR_ITEM *ditem);
+static int delete_extract_item(DIR_ITEM *ditem);
+static int extract_files_setup(char *label, off_t fsf);
+static int okay_to_continue(int allow_tape,
+                       int allow_skip,
+                       int allow_retry);
+static int okay_to_continue(int, int,  int);
+static ssize_t read_buffer(int datafd,
+                       char *buffer,
+                       size_t buflen,
+                       long timeout_s);
+static void clear_tape_list(EXTRACT_LIST *tape_list);
+static void extract_files_child(int in_fd, EXTRACT_LIST *elist);
+static void send_to_tape_server(int tss, char *cmd);
+
+
+/*
+ * Function:  ssize_t read_buffer(datafd, buffer, buflen, timeout_s)
+ *
+ * Description:
+ *     read data from input file desciptor waiting up to timeout_s
+ *     seconds before returning data.
+ *
+ * Inputs:
+ *     datafd    - File descriptor to read from.
+ *     buffer    - Buffer to read into.
+ *     buflen    - Maximum number of bytes to read into buffer.
+ *     timeout_s - Seconds to wait before returning what was already read.
+ *
+ * Returns:
+ *      >0       - Number of data bytes in buffer.
+ *       0       - EOF
+ *      -1        - errno == ETIMEDOUT if no data available in specified time.
+ *                  errno == ENFILE if datafd is invalid.
+ *                  otherwise errno is set by select or read..
+ */
+
+static ssize_t
+read_buffer(
+    int                datafd,
+    char *     buffer,
+    size_t     buflen,
+    long       timeout_s)
+{
+    ssize_t size = 0;
+    fd_set readset;
+    struct timeval timeout;
+    char *dataptr;
+    ssize_t spaceleft;
+    int nfound;
+
+    if(datafd < 0 || datafd >= (int)FD_SETSIZE) {
+       errno = EMFILE;                                 /* out of range */
+       return -1;
+    }
+
+    dataptr = buffer;
+    spaceleft = (ssize_t)buflen;
+
+    do {
+        FD_ZERO(&readset);
+        FD_SET(datafd, &readset);
+        timeout.tv_sec = timeout_s;
+        timeout.tv_usec = 0;
+        nfound = select(datafd+1, &readset, NULL, NULL, &timeout);
+        if(nfound < 0 ) {
+            /* Select returned an error. */
+           fprintf(stderr,"select error: %s\n", strerror(errno));
+            size = -1;
+           break;
+        }
+
+       if (nfound == 0) {
+            /* Select timed out. */
+            if (timeout_s != 0)  {
+                /* Not polling: a real read timeout */
+                fprintf(stderr,"timeout waiting for restore\n");
+                fprintf(stderr,"increase READ_TIMEOUT in recover-src/extract_list.c if your tape is slow\n");
+            }
+            errno = ETIMEDOUT;
+            size = -1;
+           break;
+        }
+
+       if(!FD_ISSET(datafd, &readset))
+           continue;
+
+        /* Select says data is available, so read it.  */
+        size = read(datafd, dataptr, (size_t)spaceleft);
+        if (size < 0) {
+           if ((errno == EINTR) || (errno == EAGAIN)) {
+               continue;
+           }
+           if (errno != EPIPE) {
+               fprintf(stderr, "read_buffer: read error - %s",
+                   strerror(errno));
+               break;
+           }
+           size = 0;
+       }
+        spaceleft -= size;
+        dataptr += size;
+    } while ((size > 0) && (spaceleft > 0));
+
+    return ((((ssize_t)buflen-spaceleft) > 0) ? ((ssize_t)buflen-spaceleft) : size);
+}
+
+
+EXTRACT_LIST *
+first_tape_list(void)
+{
+    return extract_list;
+}
+
+EXTRACT_LIST *
+next_tape_list(
+    /*@keep@*/ EXTRACT_LIST *list)
+{
+    if (list == NULL)
+       return NULL;
+    return list->next;
+}
+
+static void
+clear_tape_list(
+    EXTRACT_LIST *     tape_list)
+{
+    EXTRACT_LIST_ITEM *this, *next;
+    
+
+    this = tape_list->files;
+    while (this != NULL)
+    {
+       next = this->next;
+        amfree(this->path);
+        amfree(this);
+       this = next;
+    }
+    tape_list->files = NULL;
+}
+
+
+/* remove a tape list from the extract list, clearing the tape list
+   beforehand if necessary */
+void
+delete_tape_list(
+    EXTRACT_LIST *     tape_list)
+{
+    EXTRACT_LIST *this, *prev;
+
+    if (tape_list == NULL)
+        return;
+
+    /* is it first on the list? */
+    if (tape_list == extract_list)
+    {
+       extract_list = tape_list->next;
+       clear_tape_list(tape_list);
+        amfree(tape_list->date);
+        amfree(tape_list->tape);
+       amfree(tape_list);
+       return;
+    }
+
+    /* so not first on list - find it and delete */
+    prev = extract_list;
+    this = extract_list->next;
+    while (this != NULL)
+    {
+       if (this == tape_list)
+       {
+           prev->next = tape_list->next;
+           clear_tape_list(tape_list);
+            amfree(tape_list->date);
+            amfree(tape_list->tape);
+           amfree(tape_list);
+           return;
+       }
+       prev = this;
+       this = this->next;
+    }
+    /*NOTREACHED*/
+}
+
+
+/* return the number of files on a tape's list */
+int
+length_of_tape_list(
+    EXTRACT_LIST *     tape_list)
+{
+    EXTRACT_LIST_ITEM *fn;
+    int n;
+
+    n = 0;
+    for (fn = tape_list->files; fn != NULL; fn = fn->next)
+       n++;
+
+    return n;
+}
+
+
+void
+clear_extract_list(void)
+{
+    while (extract_list != NULL)
+       delete_tape_list(extract_list);
+}
+
+
+void
+clean_tape_list(
+    EXTRACT_LIST *tape_list)
+{
+    EXTRACT_LIST_ITEM *fn1, *pfn1, *ofn1;
+    EXTRACT_LIST_ITEM *fn2, *pfn2, *ofn2;
+    int remove_fn1;
+    int remove_fn2;
+
+    pfn1 = NULL;
+    fn1 = tape_list->files;
+    while (fn1 != NULL) {
+       remove_fn1 = 0;
+
+       pfn2 = fn1;
+       fn2 = fn1->next;
+       while (fn2 != NULL && remove_fn1 == 0) {
+           remove_fn2 = 0;
+           if(strcmp(fn1->path, fn2->path) == 0) {
+               remove_fn2 = 1;
+           } else if (strncmp(fn1->path, fn2->path, strlen(fn1->path)) == 0 &&
+                      ((strlen(fn2->path) > strlen(fn1->path) &&
+                        fn2->path[strlen(fn1->path)] == '/') ||
+                      (fn1->path[strlen(fn1->path)-1] == '/'))) {
+               remove_fn2 = 1;
+           } else if (strncmp(fn2->path, fn1->path, strlen(fn2->path)) == 0 &&
+                      ((strlen(fn1->path) > strlen(fn2->path) &&
+                        fn1->path[strlen(fn2->path)] == '/')  ||
+                      (fn2->path[strlen(fn2->path)-1] == '/'))) {
+               remove_fn1 = 1;
+               break;
+           }
+
+           if (remove_fn2) {
+               dbprintf(("removing path %s, it is included in %s\n",
+                         fn2->path, fn1->path));
+               ofn2 = fn2;
+               fn2 = fn2->next;
+               amfree(ofn2->path);
+               amfree(ofn2);
+               pfn2->next = fn2;
+           } else if (remove_fn1 == 0) {
+               pfn2 = fn2;
+               fn2 = fn2->next;
+           }
+       }
+
+       if(remove_fn1 != 0) {
+           /* fn2->path is always valid */
+           /*@i@*/ dbprintf(("removing path %s, it is included in %s\n",
+           /*@i@*/           fn1->path, fn2->path));
+           ofn1 = fn1;
+           fn1 = fn1->next;
+           amfree(ofn1->path);
+           if(pfn1 == NULL) {
+               amfree(tape_list->files);
+               tape_list->files = fn1;
+           } else {
+               amfree(pfn1->next);
+               pfn1->next = fn1;
+           }
+       } else {
+           pfn1 = fn1;
+           fn1 = fn1->next;
+       }
+    }
+}
+
+
+void
+clean_extract_list(void)
+{
+    EXTRACT_LIST *this;
+
+    for (this = extract_list; this != NULL; this = this->next)
+       clean_tape_list(this);
+}
+
+
+/* returns -1 if error */
+/* returns  0 on succes */
+/* returns  1 if already added */
+static int
+add_extract_item(
+    DIR_ITEM * ditem)
+{
+    EXTRACT_LIST *this, *this1;
+    EXTRACT_LIST_ITEM *that, *curr;
+    char *ditem_path = NULL;
+
+    ditem_path = stralloc(ditem->path);
+    clean_pathname(ditem_path);
+
+    for (this = extract_list; this != NULL; this = this->next)
+    {
+       /* see if this is the list for the tape */      
+       if (this->level == ditem->level && strcmp(this->tape, ditem->tape) == 0)
+       {
+           /* yes, so add to list */
+           curr=this->files;
+           while(curr!=NULL)
+           {
+               if (strcmp(curr->path, ditem_path) == 0) {
+                   amfree(ditem_path);
+                   return 1;
+               }
+               curr=curr->next;
+           }
+           that = (EXTRACT_LIST_ITEM *)alloc(sizeof(EXTRACT_LIST_ITEM));
+            that->path = stralloc(ditem_path);
+           that->next = this->files;
+           this->files = that;         /* add at front since easiest */
+           amfree(ditem_path);
+           return 0;
+       }
+    }
+
+    /* so this is the first time we have seen this tape */
+    this = (EXTRACT_LIST *)alloc(sizeof(EXTRACT_LIST));
+    this->tape = stralloc(ditem->tape);
+    this->level = ditem->level;
+    this->fileno = ditem->fileno;
+    this->date = stralloc(ditem->date);
+    that = (EXTRACT_LIST_ITEM *)alloc(sizeof(EXTRACT_LIST_ITEM));
+    that->path = stralloc(ditem_path);
+    that->next = NULL;
+    this->files = that;
+
+    /* add this in date increasing order          */
+    /* because restore must be done in this order */
+    /* add at begining */
+    if(extract_list==NULL || strcmp(this->date,extract_list->date) < 0) 
+    {
+       this->next = extract_list;
+       extract_list = this;
+       amfree(ditem_path);
+       return 0;
+    }
+    for (this1 = extract_list; this1->next != NULL; this1 = this1->next)
+    {
+       /* add in the middle */
+       if(strcmp(this->date,this1->next->date) < 0)
+       {
+           this->next = this1->next;
+           this1->next = this;
+           amfree(ditem_path);
+           return 0;
+       }
+    }
+    /* add at end */
+    this->next = NULL;
+    this1->next = this;
+    amfree(ditem_path);
+    return 0;
+}
+
+
+/* returns -1 if error */
+/* returns  0 on deletion */
+/* returns  1 if not there */
+static int
+delete_extract_item(
+    DIR_ITEM * ditem)
+{
+    EXTRACT_LIST *this;
+    EXTRACT_LIST_ITEM *that, *prev;
+    char *ditem_path = NULL;
+
+    ditem_path = stralloc(ditem->path);
+    clean_pathname(ditem_path);
+
+    for (this = extract_list; this != NULL; this = this->next)
+    {
+       /* see if this is the list for the tape */      
+       if (this->level == ditem->level && strcmp(this->tape, ditem->tape) == 0)
+       {
+           /* yes, so find file on list */
+           that = this->files;
+           if (strcmp(that->path, ditem_path) == 0)
+           {
+               /* first on list */
+               this->files = that->next;
+                amfree(that->path);
+               amfree(that);
+               /* if list empty delete it */
+               if (this->files == NULL)
+                   delete_tape_list(this);
+               amfree(ditem_path);
+               return 0;
+           }
+           prev = that;
+           that = that->next;
+           while (that != NULL)
+           {
+               if (strcmp(that->path, ditem_path) == 0)
+               {
+                   prev->next = that->next;
+                    amfree(that->path);
+                   amfree(that);
+                   amfree(ditem_path);
+                   return 0;
+               }
+               prev = that;
+               that = that->next;
+           }
+           amfree(ditem_path);
+           return 1;
+       }
+    }
+
+    amfree(ditem_path);
+    return 1;
+}
+
+void
+add_glob(
+    char *     glob)
+{
+    char *regex;
+    char *regex_path;
+    char *s;
+    char *uqglob = unquote_string(glob);
+
+    regex = glob_to_regex(uqglob);
+    dbprintf(("add_glob (%s) -> %s\n", uqglob, regex));
+    if ((s = validate_regexp(regex)) != NULL) {
+       printf("%s is not a valid shell wildcard pattern: ", glob);
+       puts(s);
+    } else {
+        /*
+         * glob_to_regex() anchors the beginning of the pattern with ^,
+         * but we will be tacking it onto the end of the current directory
+         * in add_file, so strip that off.  Also, it anchors the end with
+         * $, but we need to match an optional trailing /, so tack that on
+         * the end.
+         */
+        regex_path = stralloc(regex + 1);
+        regex_path[strlen(regex_path) - 1] = '\0';
+        strappend(regex_path, "[/]*$");
+        add_file(uqglob, regex_path);
+        amfree(regex_path);
+    }
+    amfree(regex);
+    amfree(uqglob);
+}
+
+void
+add_regex(
+    char *     regex)
+{
+    char *s;
+    char *uqregex = unquote_string(regex);
+
+    if ((s = validate_regexp(uqregex)) != NULL) {
+       printf("%s is not a valid regular expression: ", regex);
+       puts(s);
+    } else {
+        add_file(uqregex, regex);
+    }
+    amfree(uqregex);
+}
+
+void add_file(
+    char *     path,
+    char *     regex)
+{
+    DIR_ITEM *ditem, lditem;
+    char *path_on_disk = NULL;
+    char *path_on_disk_slash = NULL;
+    char *cmd = NULL;
+    char *err = NULL;
+    int i;
+    ssize_t j;
+    char *dir, *dir_undo, dir_undo_ch = '\0';
+    char *ditem_path = NULL;
+    char *l = NULL;
+    int  added;
+    char *s, *fp, *quoted;
+    int ch;
+    int found_one;
+    int dir_entries;
+
+    if (disk_path == NULL) {
+       printf("Must select directory before adding files\n");
+       return;
+    }
+    memset(&lditem, 0, sizeof(lditem)); /* Prevent use of bogus data... */
+
+    dbprintf(("add_file: Looking for \"%s\"\n", regex));
+
+    if(strcmp(regex, "/[/]*$") == 0) { /* "/" behave like "." */
+       regex = "\\.[/]*$";
+    }
+    else if(strcmp(regex, "[^/]*[/]*$") == 0) {                /* "*" */
+       //regex = 
+       regex = "([^/.]|\\.[^/]+|[^/.][^/]*)[/]*$";
+    } else {
+       /* remove "/" at end of path */
+       j = (ssize_t)(strlen(regex) - 1);
+       while(j >= 0 && regex[j] == '/')
+           regex[j--] = '\0';
+    }
+
+    /* convert path (assumed in cwd) to one on disk */
+    if (strcmp(disk_path, "/") == 0) {
+        if (*regex == '/') {
+           /* No mods needed if already starts with '/' */
+           path_on_disk = stralloc(regex);
+       } else {
+           /* Prepend '/' */
+           path_on_disk = stralloc2("/", regex);
+       }
+    } else {
+       char *clean_disk_path = clean_regex(disk_path);
+       path_on_disk = vstralloc(clean_disk_path, "/", regex, NULL);
+       amfree(clean_disk_path);
+    }
+
+    path_on_disk_slash = stralloc2(path_on_disk, "/");
+
+    dbprintf(("add_file: Converted path=\"%s\" to path_on_disk=\"%s\"\n",
+             regex, path_on_disk));
+
+    found_one = 0;
+    dir_entries = 0;
+    for (ditem=get_dir_list(); ditem!=NULL; ditem=get_next_dir_item(ditem))
+    {
+       dir_entries++;
+       quoted = quote_string(ditem->path);
+       dbprintf(("add_file: Pondering ditem->path=%s\n", quoted));
+       amfree(quoted);
+       if (match(path_on_disk, ditem->path)
+           || match(path_on_disk_slash, ditem->path))
+       {
+           found_one = 1;
+           j = (ssize_t)strlen(ditem->path);
+           if((j > 0 && ditem->path[j-1] == '/')
+              || (j > 1 && ditem->path[j-2] == '/' && ditem->path[j-1] == '.'))
+           {   /* It is a directory */
+               ditem_path = newstralloc(ditem_path, ditem->path);
+               clean_pathname(ditem_path);
+
+               cmd = stralloc2("ORLD ", ditem_path);
+               if(send_command(cmd) == -1) {
+                   amfree(cmd);
+                   amfree(ditem_path);
+                   amfree(path_on_disk);
+                   amfree(path_on_disk_slash);
+                   exit(1);
+               }
+               amfree(cmd);
+               cmd = NULL;
+               /* skip preamble */
+               if ((i = get_reply_line()) == -1) {
+                   amfree(ditem_path);
+                   amfree(path_on_disk);
+                   amfree(path_on_disk_slash);
+                   exit(1);
+               }
+               if(i==0) {              /* assume something wrong */
+                   amfree(ditem_path);
+                   amfree(path_on_disk);
+                   amfree(path_on_disk_slash);
+                   l = reply_line();
+                   printf("%s\n", l);
+                   return;
+               }
+               dir_undo = NULL;
+               added=0;
+                lditem.path = newstralloc(lditem.path, ditem->path);
+               /* skip the last line -- duplicate of the preamble */
+
+               while ((i = get_reply_line()) != 0) {
+                   if (i == -1) {
+                       amfree(ditem_path);
+                       amfree(path_on_disk);
+                       amfree(path_on_disk_slash);
+                       exit(1);
+                   }
+                   if(err) {
+                       if(cmd == NULL) {
+                           if(dir_undo) *dir_undo = dir_undo_ch;
+                           dir_undo = NULL;
+                           cmd = stralloc(l);  /* save for error report */
+                       }
+                       continue;       /* throw the rest of the lines away */
+                   }
+                   l=reply_line();
+                   if (!server_happy()) {
+                       puts(l);
+                       continue;
+                   }
+#define sc "201-"
+                   if(strncmp(l, sc, sizeof(sc)-1) != 0) {
+                       err = "bad reply: not 201-";
+                       continue;
+                   }
+
+                   s = l + sizeof(sc)-1;
+                   ch = *s++;
+#undef sc
+                   skip_whitespace(s, ch);
+                   if(ch == '\0') {
+                       err = "bad reply: missing date field";
+                       continue;
+                   }
+                    fp = s-1;
+                    skip_non_whitespace(s, ch);
+                    s[-1] = '\0';
+                    lditem.date = newstralloc(lditem.date, fp);
+                    s[-1] = (char)ch;
+
+                   skip_whitespace(s, ch);
+                   if(ch == '\0' || sscanf(s - 1, "%d", &lditem.level) != 1) {
+                       err = "bad reply: cannot parse level field";
+                       continue;
+                   }
+                   skip_integer(s, ch);
+
+                   skip_whitespace(s, ch);
+                   if(ch == '\0') {
+                       err = "bad reply: missing tape field";
+                       continue;
+                   }
+                    fp = s-1;
+                    skip_non_whitespace(s, ch);
+                    s[-1] = '\0';
+                    lditem.tape = newstralloc(lditem.tape, fp);
+                    s[-1] = (char)ch;
+
+                   if(am_has_feature(indexsrv_features, fe_amindexd_fileno_in_ORLD)) {
+                       skip_whitespace(s, ch);
+                       if(ch == '\0' || sscanf(s - 1, OFF_T_FMT,
+                               (OFF_T_FMT_TYPE *)&lditem.fileno) != 1) {
+                           err = "bad reply: cannot parse fileno field";
+                           continue;
+                       }
+                       skip_integer(s, ch);
+                   }
+
+                   skip_whitespace(s, ch);
+                   if(ch == '\0') {
+                       err = "bad reply: missing directory field";
+                       continue;
+                   }
+                   dir = s - 1;
+                   skip_quoted_string(s, ch);
+                   dir_undo = s - 1;
+                   dir_undo_ch = *dir_undo;
+                   *dir_undo = '\0';
+
+                   switch(add_extract_item(&lditem)) {
+                   case -1:
+                       printf("System error\n");
+                       dbprintf(("add_file: (Failed) System error\n"));
+                       break;
+
+                   case  0:
+                       quoted = quote_string(lditem.path);
+                       printf("Added dir %s at date %s\n",
+                              quoted, lditem.date);
+                       dbprintf(("add_file: (Successful) Added dir %s at date %s\n",
+                                 quoted, lditem.date));
+                       amfree(quoted);
+                       added=1;
+                       break;
+
+                   case  1:
+                       break;
+                   }
+               }
+               if(!server_happy()) {
+                   puts(reply_line());
+               } else if(err) {
+                   if (*err)
+                       puts(err);
+                   if (cmd)
+                       puts(cmd);
+               } else if(added == 0) {
+                   quoted = quote_string(ditem_path);
+                   printf("dir %s already added\n", quoted);
+                   dbprintf(("add_file: dir %s already added\n", quoted));
+                   amfree(quoted);
+               }
+           }
+           else /* It is a file */
+           {
+               switch(add_extract_item(ditem)) {
+               case -1:
+                   printf("System error\n");
+                   dbprintf(("add_file: (Failed) System error\n"));
+                   break;
+
+               case  0:
+                   quoted = quote_string(ditem->path);
+                   printf("Added file %s\n", quoted);
+                   dbprintf(("add_file: (Successful) Added %s\n", quoted));
+                   amfree(quoted);
+                   break;
+
+               case  1:
+                   quoted = quote_string(ditem->path);
+                   printf("File %s already added\n", quoted);
+                   dbprintf(("add_file: file %s already added\n", quoted));
+                   amfree(quoted);
+                   break;
+               }
+           }
+       }
+    }
+    if (cmd != NULL)
+       amfree(cmd);
+    amfree(ditem_path);
+    amfree(path_on_disk);
+    amfree(path_on_disk_slash);
+
+    if(! found_one) {
+       quoted = quote_string(path);
+       printf("File %s doesn't exist in directory\n", quoted);
+       dbprintf(("add_file: (Failed) File %s doesn't exist in directory\n",
+                 quoted));
+       amfree(quoted);
+    }
+}
+
+
+void
+delete_glob(
+    char *     glob)
+{
+    char *regex;
+    char *regex_path;
+    char *s;
+    char *uqglob = unquote_string(glob);
+
+    regex = glob_to_regex(uqglob);
+    dbprintf(("delete_glob (%s) -> %s\n", uqglob, regex));
+    if ((s = validate_regexp(regex)) != NULL) {
+       printf("\"%s\" is not a valid shell wildcard pattern: ", glob);
+       puts(s);
+    } else {
+        /*
+         * glob_to_regex() anchors the beginning of the pattern with ^,
+         * but we will be tacking it onto the end of the current directory
+         * in add_file, so strip that off.  Also, it anchors the end with
+         * $, but we need to match an optional trailing /, so tack that on
+         * the end.
+         */
+        regex_path = stralloc(regex + 1);
+        regex_path[strlen(regex_path) - 1] = '\0';
+        strappend(regex_path, "[/]*$");
+        delete_file(uqglob, regex_path);
+        amfree(regex_path);
+    }
+    amfree(regex);
+    amfree(uqglob);
+}
+
+void
+delete_regex(
+    char *     regex)
+{
+    char *s;
+    char *uqregex = unquote_string(regex);
+
+    if ((s = validate_regexp(regex)) != NULL) {
+       printf("\"%s\" is not a valid regular expression: ", regex);
+       puts(s);
+    } else {
+       delete_file(uqregex, uqregex);
+    }
+    amfree(uqregex);
+}
+
+void
+delete_file(
+    char *     path,
+    char *     regex)
+{
+    DIR_ITEM *ditem, lditem;
+    char *path_on_disk = NULL;
+    char *path_on_disk_slash = NULL;
+    char *cmd = NULL;
+    char *err = NULL;
+    int i;
+    ssize_t j;
+    char *date;
+    char *tape, *tape_undo, tape_undo_ch = '\0';
+    char *dir_undo, dir_undo_ch = '\0';
+    int  level = 0;
+    off_t fileno;
+    char *ditem_path = NULL;
+    char *l = NULL;
+    int  deleted;
+    char *s;
+    int ch;
+    int found_one;
+    char *quoted;
+
+    if (disk_path == NULL) {
+       printf("Must select directory before deleting files\n");
+       return;
+    }
+    memset(&lditem, 0, sizeof(lditem)); /* Prevent use of bogus data... */
+
+    dbprintf(("delete_file: Looking for \"%s\"\n", path));
+
+    if (strcmp(regex, "[^/]*[/]*$") == 0) {
+       /* Looking for * find everything but single . */
+       regex = "([^/.]|\\.[^/]+|[^/.][^/]*)[/]*$";
+    } else {
+       /* remove "/" at end of path */
+       j = (ssize_t)(strlen(regex) - 1);
+       while(j >= 0 && regex[j] == '/') regex[j--] = '\0';
+    }
+
+    /* convert path (assumed in cwd) to one on disk */
+    if (strcmp(disk_path, "/") == 0) {
+        if (*regex == '/') {
+           if (strcmp(regex, "/[/]*$") == 0) {
+               /* We want "/" to match the directory itself: "/." */
+               path_on_disk = stralloc("/\\.[/]*$");
+           } else {
+               /* No mods needed if already starts with '/' */
+               path_on_disk = stralloc(regex);
+           }
+       } else {
+           /* Prepend '/' */
+           path_on_disk = stralloc2("/", regex);
+       }
+    } else {
+       char *clean_disk_path = clean_regex(disk_path);
+       path_on_disk = vstralloc(clean_disk_path, "/", regex, NULL);
+       amfree(clean_disk_path);
+    }
+
+    path_on_disk_slash = stralloc2(path_on_disk, "/");
+
+    dbprintf(("delete_file: Converted path=\"%s\" to path_on_disk=\"%s\"\n",
+             regex, path_on_disk));
+    found_one = 0;
+    for (ditem=get_dir_list(); ditem!=NULL; ditem=get_next_dir_item(ditem))
+    {
+       quoted = quote_string(ditem->path);
+       dbprintf(("delete_file: Pondering ditem->path=%s\n", quoted));
+       amfree(quoted);
+       if (match(path_on_disk, ditem->path)
+           || match(path_on_disk_slash, ditem->path))
+       {
+           found_one = 1;
+           j = (ssize_t)strlen(ditem->path);
+           if((j > 0 && ditem->path[j-1] == '/')
+              || (j > 1 && ditem->path[j-2] == '/' && ditem->path[j-1] == '.'))
+           {   /* It is a directory */
+               ditem_path = newstralloc(ditem_path, ditem->path);
+               clean_pathname(ditem_path);
+
+               cmd = stralloc2("ORLD ", ditem_path);
+               if(send_command(cmd) == -1) {
+                   amfree(cmd);
+                   amfree(ditem_path);
+                   amfree(path_on_disk);
+                   amfree(path_on_disk_slash);
+                   exit(1);
+               }
+               amfree(cmd);
+               /* skip preamble */
+               if ((i = get_reply_line()) == -1) {
+                   amfree(ditem_path);
+                   amfree(path_on_disk);
+                   amfree(path_on_disk_slash);
+                   exit(1);
+               }
+               if(i==0)                /* assume something wrong */
+               {
+                   amfree(ditem_path);
+                   amfree(path_on_disk);
+                   amfree(path_on_disk_slash);
+                   l = reply_line();
+                   printf("%s\n", l);
+                   return;
+               }
+               deleted=0;
+                lditem.path = newstralloc(lditem.path, ditem->path);
+               amfree(cmd);
+               tape_undo = dir_undo = NULL;
+               /* skip the last line -- duplicate of the preamble */
+               while ((i = get_reply_line()) != 0)
+               {
+                   if (i == -1) {
+                       amfree(ditem_path);
+                       amfree(path_on_disk);
+                       amfree(path_on_disk_slash);
+                       exit(1);
+                   }
+                   if(err) {
+                       if(cmd == NULL) {
+                           if(tape_undo) *tape_undo = tape_undo_ch;
+                           if(dir_undo) *dir_undo = dir_undo_ch;
+                           tape_undo = dir_undo = NULL;
+                           cmd = stralloc(l);  /* save for the error report */
+                       }
+                       continue;       /* throw the rest of the lines away */
+                   }
+                   l=reply_line();
+                   if (!server_happy()) {
+                       puts(l);
+                       continue;
+                   }
+#define sc "201-"
+                   if(strncmp(l, sc, sizeof(sc)-1) != 0) {
+                       err = "bad reply: not 201-";
+                       continue;
+                   }
+                   s = l + sizeof(sc)-1;
+                   ch = *s++;
+#undef sc
+                   skip_whitespace(s, ch);
+                   if(ch == '\0') {
+                       err = "bad reply: missing date field";
+                       continue;
+                   }
+                   date = s - 1;
+                   skip_non_whitespace(s, ch);
+                   *(s - 1) = '\0';
+
+                   skip_whitespace(s, ch);
+                   if(ch == '\0' || sscanf(s - 1, "%d", &level) != 1) {
+                       err = "bad reply: cannot parse level field";
+                       continue;
+                   }
+                   skip_integer(s, ch);
+
+                   skip_whitespace(s, ch);
+                   if(ch == '\0') {
+                       err = "bad reply: missing tape field";
+                       continue;
+                   }
+                   tape = s - 1;
+                   skip_non_whitespace(s, ch);
+                   tape_undo = s - 1;
+                   tape_undo_ch = *tape_undo;
+                   *tape_undo = '\0';
+
+                   if(am_has_feature(indexsrv_features, fe_amindexd_fileno_in_ORLD)) {
+                       skip_whitespace(s, ch);
+                       if(ch == '\0' || sscanf(s - 1, OFF_T_FMT,
+                               (OFF_T_FMT_TYPE *)&fileno) != 1) {
+                           err = "bad reply: cannot parse fileno field";
+                           continue;
+                       }
+                       skip_integer(s, ch);
+                   }
+
+                   skip_whitespace(s, ch);
+                   if(ch == '\0') {
+                       err = "bad reply: missing directory field";
+                       continue;
+                   }
+                   skip_non_whitespace(s, ch);
+                   dir_undo = s - 1;
+                   dir_undo_ch = *dir_undo;
+                   *dir_undo = '\0';
+
+                    lditem.date = newstralloc(lditem.date, date);
+                   lditem.level=level;
+                    lditem.tape = newstralloc(lditem.tape, tape);
+                   switch(delete_extract_item(&lditem)) {
+                   case -1:
+                       printf("System error\n");
+                       dbprintf(("delete_file: (Failed) System error\n"));
+                       break;
+                   case  0:
+                       printf("Deleted dir %s at date %s\n", ditem_path, date);
+                       dbprintf(("delete_file: (Successful) Deleted dir %s at date %s\n",
+                                 ditem_path, date));
+                       deleted=1;
+                       break;
+                   case  1:
+                       break;
+                   }
+               }
+               if(!server_happy()) {
+                   puts(reply_line());
+               } else if(err) {
+                   if (*err)
+                       puts(err);
+                   if (cmd)
+                       puts(cmd);
+               } else if(deleted == 0) {
+                   printf("Warning - dir '%s' not on tape list\n",
+                          ditem_path);
+                   dbprintf(("delete_file: dir '%s' not on tape list\n",
+                             ditem_path));
+               }
+           }
+           else
+           {
+               switch(delete_extract_item(ditem)) {
+               case -1:
+                   printf("System error\n");
+                   dbprintf(("delete_file: (Failed) System error\n"));
+                   break;
+               case  0:
+                   printf("Deleted %s\n", ditem->path);
+                   dbprintf(("delete_file: (Successful) Deleted %s\n",
+                             ditem->path));
+                   break;
+               case  1:
+                   printf("Warning - file '%s' not on tape list\n",
+                          ditem->path);
+                   dbprintf(("delete_file: file '%s' not on tape list\n",
+                             ditem->path));
+                   break;
+               }
+           }
+       }
+    }
+    amfree(cmd);
+    amfree(ditem_path);
+    amfree(path_on_disk);
+    amfree(path_on_disk_slash);
+
+    if(! found_one) {
+       printf("File %s doesn't exist in directory\n", path);
+       dbprintf(("delete_file: (Failed) File %s doesn't exist in directory\n",
+                 path));
+    }
+}
+
+
+/* print extract list into file. If NULL ptr passed print to screen */
+void
+display_extract_list(
+    char *     file)
+{
+    EXTRACT_LIST *this;
+    EXTRACT_LIST_ITEM *that;
+    FILE *fp;
+    char *pager;
+    char *pager_command;
+    char *uqfile;
+
+    if (file == NULL)
+    {
+       if ((pager = getenv("PAGER")) == NULL)
+       {
+           pager = "more";
+       }
+       /*
+        * Set up the pager command so if the pager is terminated, we do
+        * not get a SIGPIPE back.
+        */
+       pager_command = stralloc2(pager, " ; /bin/cat > /dev/null");
+       if ((fp = popen(pager_command, "w")) == NULL)
+       {
+           printf("Warning - can't pipe through %s\n", pager);
+           fp = stdout;
+       }
+       amfree(pager_command);
+    }
+    else
+    {
+       uqfile = unquote_string(file);
+       if ((fp = fopen(uqfile, "w")) == NULL)
+       {
+           printf("Can't open file %s to print extract list into\n", file);
+           amfree(uqfile);
+           return;
+       }
+       amfree(uqfile);
+    }
+
+    for (this = extract_list; this != NULL; this = this->next)
+    {
+       fprintf(fp, "TAPE %s LEVEL %d DATE %s\n",
+               this->tape, this->level, this->date);
+       for (that = this->files; that != NULL; that = that->next)
+           fprintf(fp, "\t%s\n", that->path);
+    }
+
+    if (file == NULL) {
+       apclose(fp);
+    } else {
+       printf("Extract list written to file %s\n", file);
+       afclose(fp);
+    }
+}
+
+
+/* returns 0 if extract list empty and 1 if it isn't */
+int
+is_extract_list_nonempty(void)
+{
+    return (extract_list != NULL);
+}
+
+
+/* prints continue prompt and waits for response,
+   returns 0 if don't, non-0 if do */
+static int
+okay_to_continue(
+    int                allow_tape,
+    int                allow_skip,
+    int                allow_retry)
+{
+    int ch;
+    int ret = -1;
+    char *line = NULL;
+    char *s;
+    char *prompt;
+    int get_tape;
+
+    get_tape = 0;
+    while (ret < 0) {
+       if (get_tape) {
+           prompt = "New tape device [?]: ";
+       } else if (allow_tape && allow_skip) {
+           prompt = "Continue [?/Y/n/s/t]? ";
+       } else if (allow_tape && !allow_skip) {
+           prompt = "Continue [?/Y/n/t]? ";
+       } else if (allow_retry) {
+           prompt = "Continue [?/Y/n/r]? ";
+       } else {
+           prompt = "Continue [?/Y/n]? ";
+       }
+       fputs(prompt, stdout);
+       fflush(stdout); fflush(stderr);
+       amfree(line);
+       if ((line = agets(stdin)) == NULL) {
+           putchar('\n');
+           clearerr(stdin);
+           if (get_tape) {
+               get_tape = 0;
+               continue;
+           }
+           ret = 0;
+           break;
+       }
+       s = line;
+       while ((ch = *s++) != '\0' && isspace(ch)) {
+           (void)ch;  /* Quiet empty loop body warning */
+       }
+       if (ch == '?') {
+           if (get_tape) {
+               printf("Enter a new device ([host:]device) or \"default\"\n");
+           } else {
+               printf("Enter \"y\"es to continue, \"n\"o to stop");
+               if(allow_skip) {
+                   printf(", \"s\"kip this tape");
+               }
+               if(allow_retry) {
+                   printf(" or \"r\"etry this tape");
+               }
+               if (allow_tape) {
+                   printf(" or \"t\"ape to change tape drives");
+               }
+               putchar('\n');
+           }
+       } else if (get_tape) {
+           set_tape(s - 1);
+           get_tape = 0;
+       } else if (ch == '\0' || ch == 'Y' || ch == 'y') {
+           ret = 1;
+       } else if (allow_tape && (ch == 'T' || ch == 't')) {
+           get_tape = 1;
+       } else if (ch == 'N' || ch == 'n') {
+           ret = 0;
+       } else if (allow_retry && (ch == 'R' || ch == 'r')) {
+           ret = RETRY_TAPE;
+       } else if (allow_skip && (ch == 'S' || ch == 's')) {
+           ret = SKIP_TAPE;
+       }
+    }
+    /*@ignore@*/
+    amfree(line);
+    /*@end@*/
+    return ret;
+}
+
+static void
+send_to_tape_server(
+    int                tss,
+    char *     cmd)
+{
+    char *msg = stralloc2(cmd, "\r\n");
+
+    if (fullwrite(tss, msg, strlen(msg)) < 0)
+    {
+       error("Error writing to tape server");
+       /*NOTREACHED*/
+    }
+    amfree(msg);
+}
+
+
+/* start up connection to tape server and set commands to initiate
+   transfer of dump image.
+   Return tape server socket on success, -1 on error. */
+static int
+extract_files_setup(
+    char *     label,
+    off_t      fsf)
+{
+    struct servent *sp;
+    in_port_t my_port, my_data_port;
+    char *disk_regex = NULL;
+    char *host_regex = NULL;
+    char *service_name = NULL;
+    char *line = NULL;
+    char *clean_datestamp, *ch, *ch1;
+    char *our_feature_string = NULL;
+    char *tt = NULL;
+
+    service_name = stralloc2("amidxtape", SERVICE_SUFFIX);
+
+    /* get tape server details */
+    if ((sp = getservbyname(service_name, "tcp")) == NULL)
+    {
+       printf("%s/tcp unknown protocol - config error?\n", service_name);
+       amfree(service_name);
+       return -1;
+    }
+    amfree(service_name);
+    seteuid(0);                                        /* it either works ... */
+    setegid(0);
+    tape_control_sock = stream_client_privileged(tape_server_name,
+                                                 (in_port_t)ntohs((in_port_t)sp->s_port),
+                                                 0,
+                                                 STREAM_BUFSIZE,
+                                                 &my_port,
+                                                 0);
+    if (tape_control_sock < 0)
+    {
+       printf("cannot connect to %s: %s\n", tape_server_name, strerror(errno));
+       return -1;
+    }
+    if (my_port >= IPPORT_RESERVED) {
+       aclose(tape_control_sock);
+       printf("did not get a reserved port: %u\n", (unsigned)my_port);
+       return -1;
+    }
+    setegid(getgid());
+    seteuid(getuid());                         /* put it back */
+
+    /* do the security thing */
+    line = get_security();
+    send_to_tape_server(tape_control_sock, line);
+    memset(line, '\0', strlen(line));
+    amfree(line);
+
+    disk_regex = alloc(strlen(disk_name) * 2 + 3);
+
+    ch = disk_name;
+    ch1 = disk_regex;
+
+    /* we want to force amrestore to only match disk_name exactly */
+    *(ch1++) = '^';
+
+    /* We need to escape some characters first... NT compatibilty crap */
+    for (; *ch != 0; ch++, ch1++) {
+       switch (*ch) {     /* done this way in case there are more */
+       case '$':
+           *(ch1++) = '\\';
+           /* no break; we do want to fall through... */
+       default:
+           *ch1 = *ch;
+       }
+    }
+
+    /* we want to force amrestore to only match disk_name exactly */
+    *(ch1++) = '$';
+
+    *ch1 = '\0';
+
+    host_regex = alloc(strlen(dump_hostname) * 2 + 3);
+
+    ch = dump_hostname;
+    ch1 = host_regex;
+
+    /* we want to force amrestore to only match dump_hostname exactly */
+    *(ch1++) = '^';
+
+    /* We need to escape some characters first... NT compatibilty crap */
+    for (; *ch != 0; ch++, ch1++) {
+       switch (*ch) {     /* done this way in case there are more */
+       case '$':
+           *(ch1++) = '\\';
+           /* no break; we do want to fall through... */
+       default:
+           *ch1 = *ch;
+       }
+    }
+
+    /* we want to force amrestore to only match dump_hostname exactly */
+    *(ch1++) = '$';
+
+    *ch1 = '\0';
+
+    clean_datestamp = stralloc(dump_datestamp);
+    for(ch=ch1=clean_datestamp;*ch1 != '\0';ch1++) {
+       if(*ch1 != '-') {
+           *ch = *ch1;
+           ch++;
+       }
+    }
+    *ch = '\0';
+
+    /* push our feature list off to the tape server */
+    /* XXX assumes that index server and tape server are equivalent, ew */
+    if(am_has_feature(indexsrv_features, fe_amidxtaped_exchange_features)){
+       char buffer[32768] = "\0";
+
+       our_feature_string = am_feature_to_string(our_features);
+       tt = newstralloc2(tt, "FEATURES=", our_feature_string);
+       send_to_tape_server(tape_control_sock, tt);
+       if (read(tape_control_sock, buffer, sizeof(buffer)) <= 0) {
+           error("Could not read features from control socket\n");
+           /*NOTREACHED*/
+       }
+       tapesrv_features = am_string_to_feature(buffer);
+       amfree(our_feature_string);
+    }
+
+
+    if(am_has_feature(indexsrv_features, fe_amidxtaped_header) &&
+       am_has_feature(indexsrv_features, fe_amidxtaped_device) &&
+       am_has_feature(indexsrv_features, fe_amidxtaped_host) &&
+       am_has_feature(indexsrv_features, fe_amidxtaped_disk) &&
+       am_has_feature(indexsrv_features, fe_amidxtaped_datestamp)) {
+
+       if(am_has_feature(indexsrv_features, fe_amidxtaped_config)) {
+           tt = newstralloc2(tt, "CONFIG=", config);
+           send_to_tape_server(tape_control_sock, tt);
+       }
+       if(am_has_feature(indexsrv_features, fe_amidxtaped_label) &&
+          label && label[0] != '/') {
+           tt = newstralloc2(tt,"LABEL=",label);
+           send_to_tape_server(tape_control_sock, tt);
+       }
+       if(am_has_feature(indexsrv_features, fe_amidxtaped_fsf)) {
+           char v_fsf[100];
+           snprintf(v_fsf, 99, OFF_T_FMT, (OFF_T_FMT_TYPE)fsf);
+           tt = newstralloc2(tt, "FSF=",v_fsf);
+           send_to_tape_server(tape_control_sock, tt);
+       }
+       send_to_tape_server(tape_control_sock, "HEADER");
+       tt = newstralloc2(tt, "DEVICE=", dump_device_name);
+       send_to_tape_server(tape_control_sock, tt);
+       tt = newstralloc2(tt, "HOST=", host_regex);
+       send_to_tape_server(tape_control_sock, tt);
+       tt = newstralloc2(tt, "DISK=", disk_regex);
+       send_to_tape_server(tape_control_sock, tt);
+       tt = newstralloc2(tt, "DATESTAMP=", clean_datestamp);
+       send_to_tape_server(tape_control_sock, tt);
+       send_to_tape_server(tape_control_sock, "END");
+       amfree(tt);
+    }
+    else if(am_has_feature(indexsrv_features, fe_amidxtaped_nargs)) {
+       /* send to the tape server what tape file we want */
+       /* 6 args:
+        *   "-h"
+        *   "-p"
+        *   "tape device"
+        *   "hostname"
+        *   "diskname"
+        *   "datestamp"
+        */
+       send_to_tape_server(tape_control_sock, "6");
+       send_to_tape_server(tape_control_sock, "-h");
+       send_to_tape_server(tape_control_sock, "-p");
+       send_to_tape_server(tape_control_sock, dump_device_name);
+       send_to_tape_server(tape_control_sock, host_regex);
+       send_to_tape_server(tape_control_sock, disk_regex);
+       send_to_tape_server(tape_control_sock, clean_datestamp);
+
+       dbprintf(("Started amidxtaped with arguments \"6 -h -p %s %s %s %s\"\n",
+                 dump_device_name, host_regex, disk_regex, clean_datestamp));
+    }
+
+    /*
+     * split-restoring amidxtaped versions will expect to set up a data
+     * connection for dumpfile data, distinct from the socket we're already
+     * using for control data
+     */
+
+    if(am_has_feature(tapesrv_features, fe_recover_splits)){
+       char buffer[32768];
+       in_port_t data_port = (in_port_t)-1;
+        ssize_t nread;
+
+        nread = read(tape_control_sock, buffer, sizeof(buffer));
+
+       if (nread <= 0) {
+           error("Could not read from control socket: %s\n", 
+                  strerror(errno));
+           /*NOTREACHED*/
+        }
+
+       buffer[nread] = '\0';
+        if (sscanf(buffer, "CONNECT %hu\n",
+               (unsigned short *)&data_port) != 1) {
+           error("Recieved invalid port number message from control socket: %s\n",
+                  buffer);
+           /*NOTREACHED*/
+        }      
+
+       tape_data_sock = stream_client_privileged(server_name,
+                                                 data_port,
+                                                 0,
+                                                 STREAM_BUFSIZE,
+                                                 &my_data_port,
+                                                 0);
+       if(tape_data_sock == -1){
+           error("Unable to make data connection to server: %s\n",
+                     strerror(errno));
+           /*NOTREACHED*/
+       }
+
+       amfree(our_feature_string);
+    
+       line = get_security();
+
+       send_to_tape_server(tape_data_sock, line);
+       memset(line, '\0', strlen(line));
+       amfree(line);
+    }
+
+    amfree(disk_regex);
+    amfree(host_regex);
+    amfree(clean_datestamp);
+
+    return tape_control_sock;
+}
+
+
+/*
+ * Reads the first block of a tape file.
+ */
+
+void
+read_file_header(
+    char *     buffer,
+    dumpfile_t *file,
+    size_t     buflen,
+    int                tapedev)
+{
+    ssize_t bytes_read;
+
+    bytes_read = read_buffer(tapedev, buffer, buflen, READ_TIMEOUT);
+    if(bytes_read < 0) {
+       error("error reading header (%s), check amidxtaped.*.debug on server",
+             strerror(errno));
+       /*NOTREACHED*/
+    }
+
+    if((size_t)bytes_read < buflen) {
+       fprintf(stderr, "%s: short block %d byte%s\n",
+               get_pname(), (int)bytes_read, (bytes_read == 1) ? "" : "s");
+       print_header(stdout, file);
+       error("Can't read file header");
+       /*NOTREACHED*/
+    }
+
+    /* bytes_read == buflen */
+    parse_file_header(buffer, file, (size_t)bytes_read);
+}
+
+enum dumptypes {
+       IS_UNKNOWN,
+       IS_DUMP,
+       IS_GNUTAR,
+       IS_TAR,
+       IS_SAMBA,
+       IS_SAMBA_TAR
+};
+
+static void
+extract_files_child(
+    int                        in_fd,
+    EXTRACT_LIST *     elist)
+{
+    int save_errno;
+    int extra_params = 0;
+    int i,j=0;
+    char **restore_args = NULL;
+    int files_off_tape;
+    EXTRACT_LIST_ITEM *fn;
+    enum dumptypes dumptype = IS_UNKNOWN;
+    char buffer[DISK_BLOCK_BYTES];
+    dumpfile_t file;
+    size_t len_program;
+    char *cmd = NULL;
+    int passwd_field = -1;
+#ifdef SAMBA_CLIENT
+    char *domain = NULL, *smbpass = NULL;
+#endif
+
+    /* code executed by child to do extraction */
+    /* never returns */
+
+    /* make in_fd be our stdin */
+    if (dup2(in_fd, STDIN_FILENO) == -1)
+    {
+       error("dup2 failed in extract_files_child: %s", strerror(errno));
+       /*NOTREACHED*/
+    }
+
+    /* read the file header */
+    fh_init(&file);
+    read_file_header(buffer, &file, sizeof(buffer), STDIN_FILENO);
+
+    if(file.type != F_DUMPFILE) {
+       print_header(stdout, &file);
+       error("bad header");
+       /*NOTREACHED*/
+    }
+
+    if (file.program != NULL) {
+#ifdef GNUTAR
+       if (strcmp(file.program, GNUTAR) == 0)
+           dumptype = IS_GNUTAR;
+#endif
+
+       if (dumptype == IS_UNKNOWN) {
+           len_program = strlen(file.program);
+           if(len_program >= 3 &&
+              strcmp(&file.program[len_program-3],"tar") == 0)
+               dumptype = IS_TAR;
+       }
+
+#ifdef SAMBA_CLIENT
+       if (dumptype == IS_UNKNOWN && strcmp(file.program, SAMBA_CLIENT) ==0) {
+           if (samba_extract_method == SAMBA_TAR)
+             dumptype = IS_SAMBA_TAR;
+           else
+             dumptype = IS_SAMBA;
+       }
+#endif
+    }
+
+    /* form the arguments to restore */
+    files_off_tape = length_of_tape_list(elist);
+    switch (dumptype) {
+    case IS_SAMBA:
+#ifdef SAMBA_CLIENT
+       extra_params = 10;
+       break;
+#endif
+    case IS_TAR:
+    case IS_GNUTAR:
+        extra_params = 4;
+        break;
+    case IS_SAMBA_TAR:
+        extra_params = 3;
+        break;
+    case IS_UNKNOWN:
+    case IS_DUMP:
+#ifdef AIX_BACKUP
+        extra_params = 2;
+#else
+#if defined(XFSDUMP)
+       if (strcmp(file.program, XFSDUMP) == 0) {
+            extra_params = 4 + files_off_tape;
+       } else
+#endif
+       {
+        extra_params = 4;
+       }
+#endif
+       break;
+    }
+
+    restore_args = (char **)alloc((size_t)((extra_params + files_off_tape + 1)
+                                 * sizeof(char *)));
+    switch(dumptype) {
+    case IS_SAMBA:
+#ifdef SAMBA_CLIENT
+       restore_args[j++] = stralloc("smbclient");
+       smbpass = findpass(file.disk, &domain);
+       if (smbpass) {
+            restore_args[j++] = stralloc(file.disk);
+           passwd_field=j;
+           restore_args[j++] = stralloc("-U");
+           restore_args[j++] = smbpass;
+           if (domain) {
+               restore_args[j++] = stralloc("-W");
+               restore_args[j++] = stralloc(domain);
+           } else
+               extra_params -= 2;
+       } else
+           extra_params -= 6;
+       restore_args[j++] = stralloc("-d0");
+       restore_args[j++] = stralloc("-Tx");
+       restore_args[j++] = stralloc("-");      /* data on stdin */
+       break;
+#endif
+    case IS_TAR:
+    case IS_GNUTAR:
+       restore_args[j++] = stralloc("tar");
+       restore_args[j++] = stralloc("--numeric-owner");
+       restore_args[j++] = stralloc("-xpGvf");
+       restore_args[j++] = stralloc("-");      /* data on stdin */
+       break;
+    case IS_SAMBA_TAR:
+       restore_args[j++] = stralloc("tar");
+       restore_args[j++] = stralloc("-xpvf");
+       restore_args[j++] = stralloc("-");      /* data on stdin */
+       break;
+    case IS_UNKNOWN:
+    case IS_DUMP:
+        restore_args[j++] = stralloc("restore");
+#ifdef AIX_BACKUP
+        restore_args[j++] = stralloc("-xB");
+#else
+#if defined(XFSDUMP)
+       if (strcmp(file.program, XFSDUMP) == 0) {
+            restore_args[j++] = stralloc("-v");
+            restore_args[j++] = stralloc("silent");
+       } else
+#endif
+#if defined(VDUMP)
+       if (strcmp(file.program, VDUMP) == 0) {
+            restore_args[j++] = stralloc("xf");
+            restore_args[j++] = stralloc("-"); /* data on stdin */
+       } else
+#endif
+       {
+        restore_args[j++] = stralloc("xbf");
+        restore_args[j++] = stralloc("2");     /* read in units of 1K */
+        restore_args[j++] = stralloc("-");     /* data on stdin */
+       }
+#endif
+    }
+  
+    for (i = 0, fn = elist->files; i < files_off_tape; i++, fn = fn->next)
+    {
+       switch (dumptype) {
+       case IS_TAR:
+       case IS_GNUTAR:
+       case IS_SAMBA_TAR:
+       case IS_SAMBA:
+           if (strcmp(fn->path, "/") == 0)
+               restore_args[j++] = stralloc(".");
+           else
+               restore_args[j++] = stralloc2(".", fn->path);
+           break;
+       case IS_UNKNOWN:
+       case IS_DUMP:
+#if defined(XFSDUMP)
+           if (strcmp(file.program, XFSDUMP) == 0) {
+               /*
+                * xfsrestore needs a -s option before each file to be
+                * restored, and also wants them to be relative paths.
+                */
+               restore_args[j++] = stralloc("-s");
+               restore_args[j++] = stralloc(fn->path + 1);
+           } else
+#endif
+           {
+           restore_args[j++] = stralloc(fn->path);
+           }
+       }
+    }
+#if defined(XFSDUMP)
+    if (strcmp(file.program, XFSDUMP) == 0) {
+       restore_args[j++] = stralloc("-");
+       restore_args[j++] = stralloc(".");
+    }
+#endif
+    restore_args[j] = NULL;
+
+    switch (dumptype) {
+    case IS_SAMBA:
+#ifdef SAMBA_CLIENT
+       cmd = stralloc(SAMBA_CLIENT);
+       break;
+#else
+       /* fall through to ... */
+#endif
+    case IS_TAR:
+    case IS_GNUTAR:
+    case IS_SAMBA_TAR:
+#ifndef GNUTAR
+       fprintf(stderr, "warning: GNUTAR program not available.\n");
+       cmd = stralloc("tar");
+#else
+       cmd = stralloc(GNUTAR);
+#endif
+       break;
+    case IS_UNKNOWN:
+    case IS_DUMP:
+       cmd = NULL;
+#if defined(DUMP)
+       if (strcmp(file.program, DUMP) == 0) {
+           cmd = stralloc(RESTORE);
+       }
+#endif
+#if defined(VDUMP)
+       if (strcmp(file.program, VDUMP) == 0) {
+           cmd = stralloc(VRESTORE);
+       }
+#endif
+#if defined(VXDUMP)
+       if (strcmp(file.program, VXDUMP) == 0) {
+           cmd = stralloc(VXRESTORE);
+       }
+#endif
+#if defined(XFSDUMP)
+       if (strcmp(file.program, XFSDUMP) == 0) {
+           cmd = stralloc(XFSRESTORE);
+       }
+#endif
+       if (cmd == NULL) {
+           fprintf(stderr, "warning: restore program for %s not available.\n",
+                   file.program);
+           cmd = stralloc("restore");
+       }
+    }
+    if (cmd) {
+        dbprintf(("Exec'ing %s with arguments:\n", cmd));
+       for (i = 0; i < j; i++) {
+           if( i == passwd_field)
+               dbprintf(("\tXXXXX\n"));
+           else
+               dbprintf(("\t%s\n", restore_args[i]));
+       }
+        (void)execv(cmd, restore_args);
+       /* only get here if exec failed */
+       save_errno = errno;
+       for (i = 0; i < j; i++) {
+           amfree(restore_args[i]);
+       }
+       amfree(restore_args);
+       errno = save_errno;
+        perror("amrecover couldn't exec");
+        fprintf(stderr, " problem executing %s\n", cmd);
+       amfree(cmd);
+    }
+    exit(1);
+    /*NOT REACHED */
+}
+
+/*
+ * Interpose something between the process writing out the dump (writing it to
+ * some extraction program, really) and the socket from which we're reading, so
+ * that we can do things like prompt for human interaction for multiple tapes.
+ */
+void
+writer_intermediary(
+    int                        ctl_fd,
+    int                        data_fd,
+    EXTRACT_LIST *     elist)
+{
+    int child_pipe[2];
+    pid_t pid;
+    char buffer[DISK_BLOCK_BYTES];
+    ssize_t bytes_read;
+    amwait_t extractor_status;
+    int max_fd, nfound;
+    SELECT_ARG_TYPE readset, selectset;
+    struct timeval timeout;
+
+    /*
+     * If there's no distinct data channel (such as if we're talking to an
+     * older server), don't bother doing anything complicated.  Just run the
+     * extraction.
+     */
+    if(data_fd == -1){
+       extract_files_child(ctl_fd, elist);
+       /*NOTREACHED*/
+    }
+
+    if(pipe(child_pipe) == -1) {
+       error("extract_list - error setting up pipe to extractor: %s\n",
+           strerror(errno));
+       /*NOTREACHED*/
+    }
+
+    /* okay, ready to extract. fork a child to do the actual work */
+    if ((pid = fork()) == 0) {
+       /* this is the child process */
+       /* never gets out of this clause */
+       aclose(child_pipe[1]);
+        extract_files_child(child_pipe[0], elist);
+       /*NOTREACHED*/
+    }
+
+    /* This is the parent */
+    if (pid == -1) {
+       error("writer_intermediary - error forking child");
+       /*NOTREACHED*/
+    }
+
+    aclose(child_pipe[0]);
+
+    if(data_fd > ctl_fd) max_fd = data_fd+1;
+                    else max_fd = ctl_fd+1;
+    FD_ZERO(&readset);
+    FD_SET(data_fd, &readset);
+    FD_SET(ctl_fd, &readset);
+
+    do {
+       timeout.tv_sec = READ_TIMEOUT;
+       timeout.tv_usec = 0;
+       FD_COPY(&readset, &selectset);
+        
+       nfound = select(max_fd, &selectset, NULL, NULL,
+                       &timeout);
+       if(nfound < 0) {
+           fprintf(stderr,"select error: %s\n", strerror(errno));
+           break;
+       }
+        
+       if (nfound == 0) { /* timeout */
+           fprintf(stderr, "timeout waiting %d seconds for restore\n",
+                   READ_TIMEOUT);
+           fprintf(stderr, "increase READ_TIMEOUT in recover-src/extract_list.c if your tape is slow\n");
+           break;
+       }
+        
+       if(FD_ISSET(ctl_fd, &selectset)) {
+           bytes_read = read(ctl_fd, buffer, sizeof(buffer)-1);
+           switch(bytes_read) {
+            case -1:
+                if ((errno != EINTR) && (errno != EAGAIN)) {
+                    if (errno != EPIPE) {
+                        fprintf(stderr,"writer ctl fd read error: %s",
+                                strerror(errno));
+                    }
+                    FD_CLR(ctl_fd, &readset);
+                }
+                break;
+                
+            case  0:
+                FD_CLR(ctl_fd, &readset);
+                break;
+                
+            default: {
+                char desired_tape[MAX_TAPE_LABEL_BUF];
+                
+                buffer[bytes_read] = '\0';
+                /* if prompted for a tape, relay said prompt to the user */
+                if(sscanf(buffer, "FEEDME %132s\n", desired_tape) == 1) {
+                    int done = 0;
+                    while (!done) {
+                        char *input = NULL;
+                        printf("Please insert tape %s. Continue? [Y|n]: ",
+                               desired_tape);
+                        fflush(stdout);
+                        
+                        input = agets(stdin); /* strips \n */
+                        if (strcasecmp("", input) == 0|| 
+                            strcasecmp("y", input) == 0|| 
+                            strcasecmp("yes", input) == 0) {
+                            send_to_tape_server(tape_control_sock, "OK");
+                            done = 1;
+                        } else if (strcasecmp("n", input) == 0|| 
+                                   strcasecmp("no", input) == 0) {
+                            send_to_tape_server(tape_control_sock, "ERROR");
+                            /* Abort!
+                               We are the middle process, so just die. */
+                            exit(EXIT_FAILURE);
+                        }
+                        amfree(input);
+                    }
+                } else {
+                    fprintf(stderr, "Strange message from tape server: %s", buffer);
+                   break;
+                }    
+             }
+            }
+        }
+
+        /* now read some dump data */
+        if(FD_ISSET(data_fd, &selectset)) {
+            bytes_read = read(data_fd, buffer, sizeof(buffer)-1);
+            switch(bytes_read) {
+            case -1:
+                if ((errno != EINTR) && (errno != EAGAIN)) {
+                    if (errno != EPIPE) {
+                        fprintf(stderr,"writer data fd read error: %s",
+                                strerror(errno));
+                    }
+                    FD_CLR(data_fd, &readset);
+                }
+                break;
+                
+            case  0:
+                FD_CLR(data_fd, &readset);
+                break;
+                
+            default:
+                /*
+                 * spit what we got from the server to the child
+                 *  process handling actual dumpfile extraction
+                 */
+                if(fullwrite(child_pipe[1], buffer, (size_t)bytes_read) < 0) {
+                    if(errno == EPIPE) {
+                        error("%s: pipe data reader has quit: %s\n",
+                              get_pname(), strerror(errno));
+                        /* NOTREACHED */
+                    }
+                    error("Write error to extract child: %s\n",
+                          strerror(errno));
+                    /* NOTREACHED */
+                }
+                break;
+           }
+       }
+    } while(FD_ISSET(ctl_fd, &readset) || FD_ISSET(data_fd, &readset));
+
+    aclose(child_pipe[1]);
+
+    waitpid(pid, &extractor_status, 0);
+    if(WEXITSTATUS(extractor_status) != 0){
+       int ret = WEXITSTATUS(extractor_status);
+        if(ret == 255) ret = -1;
+       error("Extractor child exited with status %d\n", ret);
+       /*NOTREACHED*/
+    }
+
+    exit(0);
+}
+
+/* exec restore to do the actual restoration */
+
+/* does the actual extraction of files */
+/*
+ * The original design had the dump image being returned exactly as it
+ * appears on the tape, and this routine getting from the index server
+ * whether or not it is compressed, on the assumption that the tape
+ * server may not know how to uncompress it. But
+ * - Amrestore can't do that. It returns either compressed or uncompressed
+ * (always). Amrestore assumes it can uncompress files. It is thus a good
+ * idea to run the tape server on a machine with gzip.
+ * - The information about compression in the disklist is really only
+ * for future dumps. It is possible to change compression on a drive
+ * so the information in the disklist may not necessarily relate to
+ * the dump image on the tape.
+ *   Consequently the design was changed to assuming that amrestore can
+ * uncompress any dump image and have it return an uncompressed file
+ * always.
+ */
+void
+extract_files(void)
+{
+    EXTRACT_LIST *elist;
+    pid_t pid;
+    amwait_t child_stat;
+    char buf[STR_SIZE];
+    char *l;
+    int first;
+    int otc;
+    tapelist_t *tlist = NULL;
+
+    if (!is_extract_list_nonempty())
+    {
+       printf("Extract list empty - No files to extract!\n");
+       return;
+    }
+
+    clean_extract_list();
+
+    /* get tape device name from index server if none specified */
+    if (tape_server_name == NULL) {
+       tape_server_name = newstralloc(tape_server_name, server_name);
+    }
+    if (tape_device_name == NULL) {
+       if (send_command("TAPE") == -1)
+           exit(1);
+       if (get_reply_line() == -1)
+           exit(1);
+       l = reply_line();
+       if (!server_happy())
+       {
+           printf("%s\n", l);
+           exit(1);
+       }
+       /* skip reply number */
+       tape_device_name = newstralloc(tape_device_name, l+4);
+    }
+
+    if (strcmp(tape_device_name, "/dev/null") == 0)
+    {
+       printf("amrecover: warning: using %s as the tape device will not work\n",
+              tape_device_name);
+    }
+
+    first=1;
+    for (elist = first_tape_list(); elist != NULL; elist = next_tape_list(elist))
+       if(elist->tape[0]!='/') {
+           if(first) {
+               printf("\nExtracting files using tape drive %s on host %s.\n",
+                       tape_device_name, tape_server_name);
+               printf("The following tapes are needed:");
+               first=0;
+           }
+           else
+               printf("                               ");
+           tlist = unmarshal_tapelist_str(elist->tape); 
+           for( ; tlist != NULL; tlist = tlist->next)
+               printf(" %s", tlist->label);
+           printf("\n");
+           amfree(tlist);
+       }
+    first=1;
+    for (elist = first_tape_list(); elist != NULL; elist = next_tape_list(elist))
+    {
+       if(elist->tape[0]=='/') {
+           if(first) {
+               printf("\nExtracting files from holding disk on host %s.\n",
+                       tape_server_name);
+               printf("The following files are needed:");
+               first=0;
+           }
+           else
+               printf("                               ");
+           tlist = unmarshal_tapelist_str(elist->tape); 
+           for( ; tlist != NULL; tlist = tlist->next)
+               printf(" %s", tlist->label);
+           printf("\n");
+           amfree(tlist);
+       }
+    }
+    printf("\n");
+
+    if (getcwd(buf, sizeof(buf)) == NULL) {
+       perror("extract_list: Current working directory unavailable");
+       exit(1);
+    }
+
+    printf("Restoring files into directory %s\n", buf);
+#ifdef SAMBA_CLIENT
+    if (samba_extract_method == SAMBA_SMBCLIENT)
+      printf("(unless it is a Samba backup, that will go through to the SMB server)\n");
+#endif
+    if (!okay_to_continue(0,0,0))
+       return;
+    printf("\n");
+
+    while ((elist = first_tape_list()) != NULL)
+    {
+       if(elist->tape[0]=='/') {
+           dump_device_name = newstralloc(dump_device_name, elist->tape);
+           printf("Extracting from file ");
+           tlist = unmarshal_tapelist_str(dump_device_name); 
+           for( ; tlist != NULL; tlist = tlist->next)
+               printf(" %s", tlist->label);
+           printf("\n");
+           amfree(tlist);
+       }
+       else {
+           printf("Extracting files using tape drive %s on host %s.\n",
+                  tape_device_name, tape_server_name);
+           tlist = unmarshal_tapelist_str(elist->tape); 
+           printf("Load tape %s now\n", tlist->label);
+           amfree(tlist);
+           otc = okay_to_continue(1,1,0);
+           if (otc == 0)
+               return;
+           else if (otc == SKIP_TAPE) {
+               delete_tape_list(elist); /* skip this tape */
+               continue;
+           }
+           dump_device_name = newstralloc(dump_device_name, tape_device_name);
+       }
+       dump_datestamp = newstralloc(dump_datestamp, elist->date);
+
+       /* connect to the tape handler daemon on the tape drive server */
+       if ((tape_control_sock = extract_files_setup(elist->tape, elist->fileno)) == -1)
+       {
+           fprintf(stderr, "amrecover - can't talk to tape server\n");
+           return;
+       }
+
+       /* okay, ready to extract. fork a child to do the actual work */
+       if ((pid = fork()) == 0)
+       {
+           /* this is the child process */
+           /* never gets out of this clause */
+           writer_intermediary(tape_control_sock, tape_data_sock, elist);
+           /*NOT REACHED*/
+       }
+       /* this is the parent */
+       if (pid == -1)
+       {
+           perror("extract_list - error forking child");
+           aclose(tape_control_sock);
+           exit(1);
+       }
+
+       /* store the child pid globally so that it can be killed on intr */
+       extract_restore_child_pid = pid;
+
+       /* wait for the child process to finish */
+       if ((pid = waitpid(-1, &child_stat, 0)) == (pid_t)-1)
+       {
+           perror("extract_list - error waiting for child");
+           exit(1);
+       }
+
+       if(tape_data_sock != -1) {
+           aclose(tape_data_sock);
+       }
+
+       if (pid == extract_restore_child_pid)
+       {
+           extract_restore_child_pid = -1;
+       }
+       else
+       {
+           fprintf(stderr, "extract list - unknown child terminated?\n");
+           exit(1);
+       }
+       if ((WIFEXITED(child_stat) != 0) && (WEXITSTATUS(child_stat) != 0))
+       {
+           fprintf(stderr,
+                   "extract_list - child returned non-zero status: %d\n",
+                   WEXITSTATUS(child_stat));
+           otc = okay_to_continue(0,0,1);
+           if(otc == 0)
+               return;
+
+           if(otc == 1) {
+               delete_tape_list(elist); /* tape failed so delete from list */
+           }
+       }
+       else {
+           delete_tape_list(elist);    /* tape done so delete from list */
+       }
+    }
+}